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.