Concept:
My midterm project consists of a landscape NFT collection as a combination of my interest in cryptography and generative art. It uses different mathematical and artistic concepts to produce unique nature-related artwork. Through a GUI, the user can specify the style (acrylic, japanese, animated…) and generate their own NFT. As for choosing nature as a theme, I was inspired by the course’s name “decoding nature”.
Librairies/Algorithms:
TweakPane
Perlin Noise
Sin-Wave
Particle System
Recursion
Granulation
Linear Interpolation
Code: https://editor.p5js.org/bdr/sketches/qhH_1I3FP
Progress:
Code Walkthrough (Special Functions):
Vintage effect:
In order to get an ancient style, I’m using noise as a primary way to simulate the imperfections and irregularities alongside the random function. To generate the dots in the corner, I’m using the following:
function displayNoise(){ strokeWeight(0.5); for(let dx = 0; dx < width; dx += random(1, 3)) { for(let dy = 0; dy < height; dy += random(1, 3)) { let pointX = dx * noise(dy/10); let pointY = dy * noise(dx/10); if (get(pointX, pointY)[0]==241) { stroke(darkColor); } else { stroke(lightColor); } point(pointX, pointY); } } }
Mountains:
To create hand-drawn mountain contours, I’m using sin-waves relying on vertices, noise, and random functions. To get varying amplitudes I’m using the following:
let a = random(-width/2, width/2); let b = random(-width/2, width/2); let c = random(2, 4); let d = random(40, 50); let e = random(-width/2, width/2); for (let x = 0; x < width; x ++){ let y = currY[j]; y += 10*j*sin(2*dx/j + a); y += c*j*sin(5*dx/j + b); y += d*j*noise(1.2*dx/j +e); y += 1.7*j*noise(10*dx); dx+=0.02; mountains.push({x,y}) }
As for the vertices and to be able to close the shape in order to be able to fill it later on, I’m using:
beginShape(); vertex(0, height); for (let i = 0; i < mountains.length; i++) { stroke(darkColor); vertex(mountains[i].x, mountains[i].y); } vertex(width, height); endShape(CLOSE);
Matrix Japanese Text:
The Japanese text represents different nature and life related quotes that are displayed vertically in a Matrix style. I’m thinking of animating it later.
["風流韻事" // Appreciating nature through elegant pursuits such as poetry, calligraphy and painting ,"柳は緑花は紅" // Willows are green, flowers are crimson; natural state, unspoiled by human touch, nature is beautiful, all things have different natures or characteristics. ,"花鳥風月" // The beauties of nature. ,"旅はまだ途中だぞ" // Our adventure is not done yet ,"前向きにね" // Stay positive ,"雨降って地固まる" // After rain, comes fair weather ,"苦あれば楽あり" // There are hardships and also there are pleasures ,"初心忘るべからず" // Should not forget our original intention ,"浮世憂き世" // Floating world ,"自分の生きる人生を愛せ" // Love the life you are living ,"行雲流水" // Flow as the clouds and water ,"人生は夢だらけ" // Life is full of dreams ,"春になると森は青々としてくるです" // In spring, the forest becomes lush ,"今日の夕焼けはとてもきれいです" // Today's sky is nice and red ,"生き甲斐" // Realisation of what one expects and hopes for in life ];
Gradient:
To get a smooth gradient, I’m using the mathematical concept: linear interpolation. It constructs new data points within the range of a given discrete set data points which are in our case colors.
function applyGradient(x, y, w, h, color1, color2) { noFill(); for (let i = y; i <= y + h; i++) { let mid = map(i, y, y + h, 0, 1); let clr = lerpColor(color1, color2, mid); stroke(clr); line(x, i, x + w, i); } }
Granulation and Blur:
To do that, I looped over the canvas pixels and shifted some of them randomly. To further fuzzify the artwork, I rounded the pixels after moving them as well and getting their density.
function granulateFuzzify(amount) { loadPixels(); let d = pixelDensity(); let fuzzyPixels = 2; let modC = 4 * fuzzyPixels; let modW = 4 * width * d; let pixelsCount = modW * (height * d); for (let i = 0; i < pixelsCount; i += 4) { let f = modC + modW; if (pixels[i+f]) { pixels[i] = round((pixels[i] + pixels[i+f])/2); pixels[i+1] = round((pixels[i+1] + pixels[i+f+1])/2); pixels[i+2] = round((pixels[i+2] + pixels[i+f+2])/2); } pixels[i] = pixels[i] + random(-amount, amount); pixels[i+1] = pixels[i+1] + random(-amount, amount); pixels[i+2] = pixels[i+2] + random(-amount, amount); } updatePixels(); }
Stars Particle System:
To get a stars system, I had a separate class named Star and created new instances using a for loop.
class Star { constructor(x, y) { this.xPos = x; this.yPos = y; } draw() { let randSize = random(3); fill(255, 255, 255, random(0, 200)); ellipse(this.xPos, this.yPos, randSize, randSize); } } function makeStars(numStars) { if (starCount < numStars) { let s = new Star(int(random(width)), int(random(height / 1.8))); s.draw(); } starCount++; }
Challenges:
One of the challenges I have faced is certainly setting the right parameters for the waves to get a somewhat hand-drawn but still realistic set of mountains. It took a lot of experimentation, trial and errors to finally find the right combination. Similarly, for the gradient to be smooth and match all the components of the artwork, I had to find the right settings for the linear interpolation.
Next Steps:
Animation: animate the canvas/artwork, most probably a 3D effect.