Creating Shaders for p5.js using Shaderpark

In this short tutorial, you will learn the basics of how to use the open source web-based platform »Shaderpark« to create stunning, animated 3D-visuals with only a few lines of code.

To implement Shaderpark Code in p5.js...

Link the Shaderpark library in the head of the index.html file.

<script src="https://unpkg.com/shader-park-core@0.2.8/dist/shader-park-p5.js"></script>

Add your Shaderpark code to the p5.js file by creating a new let and assigning the shader to it in the setup function (Note: only works in WebGL).

let sdf;

function setup() {
  createCanvas(512, 512, WEBGL);
  sdf = createShaderPark(() => {
      sphere(0.5); // your Shaderpark code
    }
  );
}

To make the object appear on your canvas, simply use .draw in the draw function.

function draw() {
  sdf.draw();
}

To interact with the Shaderpark code while running the sketch, create a variable that's connected to an input in the Shaderpark code and use setUniform and the draw function.

let sdf;

function setup() {
  createCanvas(512, 512, WEBGL);
  sdf = createShaderPark(() => {
    let size1 = input();
    sphere(size1);
  }
  );
}

function draw() {
   clear();
  size1 = map(mouseX, 0, width, 0.1, 1); 
  sdf.shader.setUniform("size1", size1); //making the size depend on the mouse position
  sdf.draw();
}

Example: Colorful lava sphere

First, we are creating a sphere (size: 0.7) as well as a torus (size: 0.7, thickness: 0.1).

sphere(0.7);
torus(0.7, 0.1);

Primitive Shapes

sphere(radius) Creates a sphere
box(width, height, depth) Creates a box
torus(radius, tubeRadius) Creates a torus (donut shape)
line(start, end, thickness) Draws a line between two points with a defined thickness

Now, let's rotate the torus by a little bit.

sphere(0.7);
rotateX(1);
torus(0.7, 0.1);

Coordinate Modifiers

displace(fn) Displaces geometry based on a function
rotateX(angle), rotateY(angle), rotateZ(angle) Rotates the space around a given axis
mirrorX(), mirrorY(), mirrorZ() Mirrors the shape across a specific axis
reset() Resets transformations to the default state

Let's also rotate the torus through the Z-axis. Using a time-input allows us to animate the rotation as time moves forward.

sphere(0.7);
rotateX(1);
rotateZ(PI/2 + time);
torus(0.7, 0.1);

Space & Time

getSpace() Returns the current 3D space coordinate
getRayDirection() Returns the view ray direction
time The elapsed time since the start
normal Returns the surface normal vector

Now, we're subtracting the torus from the sphere by using the construction mode difference.

sphere(0.7);
difference();
rotateX(1);
rotateZ(PI/2 + time);
torus(0.7, 0.1);

Constructive Operations

union() Combines two shapes
difference() Subtracts one shape from another
intersect() Keeps only the overlapping part
blend(factor) Smoothly blends between two shapes

Let's turn our »sculpture« into a structure by saving it to a variable.

let mysphere = shape(() => {
    sphere(0.7 );
    difference();
  rotateX(1);
  rotateZ(PI/2 + time);
    torus(0.7 ,0.1);
});

Create the saved structure by calling the variable.

let mysphere = shape(() => {
    sphere(0.7 );
    difference();
  rotateX(1);
  rotateZ(PI/2 + time);
    torus(0.7 ,0.1);
});
mysphere();

Next, we're creating more spheres with varying values.

let mysphere = shape(() => {
    sphere(0.7 );
    difference();
    rotateX(1);
    rotateZ(PI/2 + time);
    torus(0.7 ,0.1);
});
mysphere();
sphere(0.2);
sphere(0.3);
sphere(0.1);
sphere(0.25);

We can move the spheres by using the displace-function.

let mysphere = shape(() => {
    sphere(0.7 );
    difference();
    rotateX(1);
    rotateZ(PI/2 + time);
    torus(0.7 ,0.1);
});
mysphere();
displace(0.5);
sphere(0.2);
sphere(0.3);
sphere(0.1);
sphere(0.25);

By placing the displace-function before our spheres, we're moving all spheres by the same value. To avoid this, implement the reset()-function at whichever point you want it to stop impacting the spheres.

let mysphere = shape(() => {
sphere(0.7);
difference();
rotateX(1);
rotateZ(PI/2 + time);
torus(0.7 , 0.1);
  });
mysphere();
displace(0.5);
sphere(0.2);
reset();
displace(0.1,0.5,0.0);
sphere(0.3);
reset();
displace(0.4,0.0,1);
sphere(0.1);
reset();
displace(0.4,-1.0,-2);
sphere(0.25);

To animate the spheres' behaviour, let's add some time-inputs.

let mysphere = shape(() => {
sphere(0.7);
difference();
rotateX(1);
rotateZ(PI/2 + time);
torus(0.7 , 0.1);
  });
mysphere();
displace(0.5);
sphere(0.2);
reset();
displace(sin(time),0.5,0.0);
sphere(0.3);
reset();
displace(0.4, 0.1 ,cos(time));
sphere(0.1);
reset();
displace(0.4,sin(time),-2);
sphere(0.25);

Customize the values by your liking until you find something that suits your personal style!

let mysphere = shape(() => {
sphere(0.7);
difference();
rotateX(1);
rotateZ(PI/2 + time);
torus(0.7 , 0.1);
  });
mysphere();
displace(sin(time*2.3)/1.3, 0, cos(time*1)/1.3);
sphere(0.2);
reset();
displace(cos(time*2.3)/1.3, sin(time*1)/1.3, 0);
sphere(0.3);
reset();
displace(cos(time*1.5)/1.3, 0, sin(time*1));
sphere(0.1);
reset();
displace( 0, 0, sin(time*1));
sphere(0.25);

We're using the construction mode blend to merge the smaller spheres with the big center sphere.

let mysphere = shape(() => {
sphere(0.7);
difference();
rotateX(1);
rotateZ(PI/2 + time);
torus(0.7 , 0.1);
  });
mysphere();
blend(0.2);
displace(sin(time*2.3)/1.3, 0, cos(time*1)/1.3);
sphere(0.2);
reset();
displace(cos(time*2.3)/1.3, sin(time*1)/1.3, 0);
sphere(0.3);
reset();
displace(cos(time*1.5)/1.3, 0, sin(time*1));
sphere(0.1);
reset();
displace( 0, 0, sin(time*1));
sphere(0.25);

By using the color- and the shine-function, we're adding color and texture to the spheres.

let mysphere = shape(() => {
  color(0,1,0);
  shine(0.8);
sphere(0.7);
difference();
rotateX(1);
rotateZ(PI/2 + time);
torus(0.7 , 0.1);
  });
mysphere();
blend(0.2);
displace(sin(time*2.3)/1.3, 0, cos(time*1)/1.3);
color(1,0,0);
shine(0.8);
sphere(0.2);
reset();
displace(cos(time*2.3)/1.3, sin(time*1)/1.3, 0);
shine(0.8);
color(0,0,1);
sphere(0.3);
reset();
displace(cos(time*1.5)/1.3, 0, sin(time*1));
shine(0.8);
color(0,0,0);
sphere(0.1);
reset();
displace( 0, 0, sin(time*1));
shine(0.8);
color(1,1,1);
sphere(0.25);

Material & Color

color(r, g, b) Sets the color
metal(amount) Gives the surface a metallic look
shine(amount) Controls surface shininess
mixMat(mat1, mat2, factor) Mixes two materials

By changing the value of rotateX() to the current x-position of the rotating torus and multiplying that value, our torus gains a »wobbling«-effect. We're also adjusting the StepSize to render the sculpture in a higher level of detail to avoid artifacts.

setStepSize(0.4);
let mysphere = shape(() => {
  color(0,1,0);
  shine(0.8);
sphere(0.7);
difference();
rotateX(getSpace().x*4);
rotateZ(PI/2 + time);
torus(0.7 , 0.1);
  });
mysphere();
blend(0.2);
displace(sin(time*2.3)/1.3, 0, cos(time*1)/1.3);
color(1,0,0);
shine(0.8);
sphere(0.2);
reset();
displace(cos(time*2.3)/1.3, sin(time*1)/1.3, 0);
shine(0.8);
color(0,0,1);
sphere(0.3);
reset();
displace(cos(time*1.5)/1.3, 0, sin(time*1));
shine(0.8);
color(0,0,0);
sphere(0.1);
reset();
displace( 0, 0, sin(time*1));
shine(0.8);
color(1,1,1);
sphere(0.25);

Global Settings

setGeometryQuality(level) Adjusts the rendering quality
setStepSize(size) Sets the raymarching step size
setMaxIterations(count) Max iterations for raymarching

Now, let's create a new let n that stores an animated noise-algorithm. We're also adding 10% of the noise to our spheres and torus.

setStepSize(0.4);
let n =  noise(getSpace() + time);

let mysphere = shape(() => {
  color(0,1,0);
  shine(0.8);
sphere(0.7 + n * 0.1);
difference();
rotateX(getSpace().x*4);
rotateZ(PI/2 + time);
torus(0.7 + n * 0.1, 0.1+ n * 0.1);
  });
mysphere();
blend(0.2);
displace(sin(time*2.3)/1.3, 0, cos(time*1)/1.3);
color(1,0,0);
shine(0.8);
sphere(0.2+ n * 0.1);
reset();
displace(cos(time*2.3)/1.3, sin(time*1)/1.3, 0);
shine(0.8);
color(0,0,1);
sphere(0.3+ n * 0.1);
reset();
displace(cos(time*1.5)/1.3, 0, sin(time*1));
shine(0.8);
color(0,0,0);
sphere(0.1+ n * 0.1);
reset();
displace( 0, 0, sin(time*1));
shine(0.8);
color(1,1,1);
sphere(0.25+ n * 0.1);

Algorithms

noise(x, y, z) Generates procedural noise
fractalNoise(x, y, z, octaves) More complex, layered noise

We're creating a new let c for color and storing the normal input.

setStepSize(0.4);
let n =  noise(getSpace()+ time);
let c = normal;

let mysphere = shape(() => {
  color(c);
  shine(.8);
sphere(0.7 + n * 0.1);
difference();
rotateX(getSpace().x * 4);
rotateZ(PI/2 + time);
torus(0.7 + n * 0.1, 0.1+ n * 0.1);
  });
mysphere();
blend(0.2);
displace(sin(time*2.3)/1.3, 0, cos(time*1)/1.3);
color(c);
shine(.8);
sphere(0.2+ n * 0.1);
reset();
displace(cos(time*2.3)/1.3, sin(time*1)/1.3, 0);
shine(.8);
color(c);
sphere(0.3+ n * 0.1);
reset();
displace(cos(time*1.5)/1.3, 0, sin(time*1));
shine(.8);
color(c);
sphere(0.1+ n * 0.1);
reset();
displace( 0, 0, sin(time*1));
shine(.8);
color(c);
sphere(0.25+ n * 0.1);

Because the color needs three input values, we're creating three values from the noise we stored in n earlier using vec3(n).

let c = vec3(n)*0.5+0.5 + normal ;

Let's further adjust our colors by adding other values. Feel free to experiment with these values to get different results!

let c = vec3(n)*0.5+0.5 + normal + vec3(0.4,0,0);

Now, we're creating a let scale and a let noiselvl that stores an input-function. This adds a slider that allows you to adjust the value in real-time.

setStepSize(0.4);
let scale = input(1.0 , 1.0, 10.0);
let noiselvl = input(1.0, 0.0, 1.0);

let n = noiselvl*  noise(getSpace() * scale+ time);
let c = vec3(n)*0.5+0.5 + normal + vec3(0.4,0,0) ;

let mysphere = shape(() => {
  color(c);
  shine(.8);
sphere(0.7 + n * 0.1);
difference();
rotateX(getSpace().x * 4);
rotateZ(PI/2 + time);
torus(0.7 + n * 0.1, 0.1+ n * 0.1);
  });
mysphere();
 
blend(0.2);
displace(sin(time*2.3)/1.3, 0, cos(time*1)/1.3);
color(c);
shine(.8);
sphere(0.2+ n * 0.1);
reset();
displace(cos(time*2.3)/1.3, sin(time*1)/1.3, 0);
shine(.8);
color(c);
sphere(0.3+ n * 0.1);
reset();
displace(cos(time*1.5)/1.3, 0, sin(time*1));
shine(.8);
color(c);
sphere(0.1+ n * 0.1);
reset();

Now, you can implement this sculpture into your p5.js code as shown above.

let sdf;

function setup() {
  createCanvas(512, 512, WEBGL);
  sdf = createShaderPark(() => {
    setStepSize(0.4);
    let scale = input();
    let noiselvl = input();
    let n = noiselvl*  noise(getSpace() * scale+ time);
    let c = vec3(n)*0.5+0.5 + normal + vec3(0.4,0,0) ;
    let mysphere = shape(() => {
      color(c);
      shine(.8);
      sphere(0.7 + n * 0.1);
      difference();
      rotateX(getSpace().x * 4);
      rotateZ(PI/2 + time);
      torus(0.7 + n * 0.1, 0.1+ n * 0.1);
    });
    mysphere();
    blend(0.2);
    displace(sin(time*2.3)/1.3, 0, cos(time*1)/1.3);
    color(c);
    shine(.8);
    sphere(0.2+ n * 0.1);
    reset();
    displace(cos(time*2.3)/1.3, sin(time*1)/1.3, 0);
    shine(.8);
    color(c);
    sphere(0.3+ n * 0.1);
    reset();
    displace(cos(time*1.5)/1.3, 0, sin(time*1));
    shine(.8);
    color(c);
    sphere(0.1+ n * 0.1);
    reset();
    }
  );
}

function draw() {
  orbitControl();
  clear();
  scale = map(mouseX, 0, width, 1, 10);
  noiselvl = map(mouseY, 0, height, 0, 1);
  sdf.shader.setUniform("scale", scale);
  sdf.shader.setUniform("noiselvl", noiselvl);
  sdf.draw();
}

All done!

Sources and important links:

https://shaderpark.com/

https://docs.shaderpark.com/references-js/

https://youtube.com/playlist?list=PLgfxkm9xFocbc6wiO3A8vXalP4K57yUKo&si=lHQvF4EqiHuExvyB

Our Sculpture on Shaderpark:

https://shaderpark.com/sculpture/-OP2FS6HAdJbt19sj8Y3

Schlagworte


© 0t1

Cookies

0t1 mag keine Kekse (und kein Tracking). Wir verwenden lediglich notwendige Cookies für essentielle Funktionen.

Wir verwenden Schriftarten von Adobe Fonts. Dafür stellt dein Browser eine Verbindung zu den Servern von Adobe in den USA her. Wenn du unsere Seite nutzen möchtest, musst du dich damit einverstanden erklären.

Weitere Informationen in unserer Datenschutzerklärung.