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://docs.shaderpark.com/references-js/
https://youtube.com/playlist?list=PLgfxkm9xFocbc6wiO3A8vXalP4K57yUKo&si=lHQvF4EqiHuExvyB
Our Sculpture on Shaderpark: