I’ve been following ilithya’s shader demos for a while and wanted to write about her work weeks ago, but quickly learned that I was out of my depth with shaders. Instead, ilithya helped me write the Introduction to Shaders issue as a warm-up. Now it's race day!
If you're new to shaders and haven't read the Introduction to Shaders issue, that would be a good place to start. It provides a conceptual foundation that will make it easier to understand the shaders we're covering.
Summer Screen
Let's dive deep into how ilithya made this awesome shader, Summer Screen.
There's a few things going on here. First, we've got these vertical line sections that are shifting back and forth. Then we've got these gradients coming in from the top and bottom like color waves on a beach. Let's figure out these two effects.
First we'll check out a shortened version of the GLSL code so we can see what's going on. You might see a new language and some math here but don't worry! We'll trot through them nice and slow.
// Create three static colors
vec3 colorA = vec3(0.5, 0., 0.5); // Purple
vec3 colorB = vec3(1.0, 0.41, 0.71); // Pink
vec3 colorC = vec3(0.0, 1.0, 1.0); // Teal blue
// Create three dynamic colors
vec3 color1 = mix(colorA, colorB, fract(x*fr+cos(u_time)));
vec3 color2 = mix(colorB, colorC, (y-sin(u_time))*0.5);
vec3 color3 = mix(color1, color2, x*cos(u_time+2.));
// Our color output
gl_FragColor = vec4(color3, 1.0);
The first thing to address is that GLSL is a typed language, so when you declare a variable you also declare its type.
A float
for our purposes can be thought of as a number with a decimal.
A vec3
for our purposes is kind of like a special JavaScript array with three values.
A vec4
has one more value than a vec3
and is what the output of a shader main()
function needs to be. The four values there align to red, green, blue, alpha.
First ilithya creates colorA
, colorB
, and colorC
. Each of these are static colors that aren't going to change.
Then she uses these colors and other variables to create dynamic colors that will change. We'll look at the vertical lines first.
Vertical Lines
For the vertical lines let's zoom in and see what's really going on here.
The lines are really repeated gradients going from dark to light.
To make gradients with GLSL we use the mix
function. mix
takes three arguments: x
: the start of the range, y
: the end of the range, and a
: the point between x
and y
.
// vec3 mix(vec3 x, vec3 y, vec3 a)
vec3 color1 = mix(colorA, colorB, fract(x*fr+cos(u_time)));
ilithya is making a gradient between the purple and pink, so those are her first two arguments. The colors get mixed in different amounts based on the value of a
, which is a value from 0
to 1
. If a
is 0
, we'll see 100% of the first color. If a
is 1
we'll see 100% of the second color. Anything in between and we get a blend:
So that can give us one gradient, but how do we get repeating gradients?
To get the gradients to stop and start from 0
again, ilithya is using a GLSL function called fract
. This returns the fraction of a number, dropping the integer. fract(x)
is the same as doing x - Math.floor(x)
or x % 1
in JavaScript. So as x
increases, fract(x)
will always be a float between 0 and 1.
I'm going to simplify it beyond what ilithya has in her piece to show how the fract
function works:
As we can see, even though x
keeps increasing, the result of fract(x)
just returns the fraction, or whatever comes after the decimal, giving us that same purplish color. Translate that over the entire piece and you've got ilithya's cool vertical bars!
We'll touch on how all these pieces animate a bit later.
Pink & Blue Gradient
Now we move to color2
.
vec3 color2 = mix(colorB, colorC, (y - sin(u_time)) * 0.5);
Another mix, this time between the pink and blue. No fract
this time, so we're getting a smooth gradient. The y
value makes it change color from bottom to top. We'll touch on the sin(u_time)
bit soon.
Putting it All Together
Then she combines color1
and color2
, using a mix amount that gets closer to 1
on the right side. The horizontal change is due to the x
value. The cos(u_time+2)
is for animation.
vec3 color3 = mix(color1, color2, x*cos(u_time+2.));
Here's a simplified version of the shader's parts and the final color output:
You can see our simplified version looks similar to the finished shader, it's just missing animations.
Animation
There are three main animations happening in Summer Screen:
- The bars shifting left and right in
color1
- The pink and blue gradient moving up and down in
color2
- The mix value for
color3
moving left and right
Each of these are using the value of the current time, and familiar math functions, sin
and cos
.
vec3 color1 = mix(colorA, colorB, fract(x*fr+cos(u_time)));
vec3 color2 = mix(colorB, colorC, (y-sin(u_time))*0.5);
vec3 color3 = mix(color1, color2, x*cos(u_time+2.));
That time
value comes from a Three.js Clock
and is how ilithya animates most of her shaders. It returns the elapsed time since the page loaded.
Don't worry if you don't know Three.js. The code ilithya uses is reusable boilerplate that makes the shader code simpler. We'd have to have a lot more GLSL code without the little bit of Three.js. It's a handy WebGL abstraction.
When ilithya calls either sin(u_time)
or cos(u_time)
she's going to get back a value between -1 and 1, and it'll transition smoothly. If you're familiar with CSS animations, it feels a lot like an ease-in-out
.
Super Quick Sine Wave Refresher:
As we increase the value along the horizontal axis, the sine wave moves us up and peaks at 1
, brings us down to 0
and bottoms out at -1
before climbing again. No matter high the horizontal value is, the sine of that value will always be between -1
and 1
. Cosine works similarly but gives a different value.
And there you have it! Three different parts being blended and animated together to make one awesome animation.
Break it Yourself
As she was helping me understand it, ilithya encouraged me to swap values out in the shader code. I highly recommend you go and do just that.
To see the vertical bars isolated, change the last line of the shader code to:
gl_FragColor = vec4(color1, 1.0);
To see the vertical pink and blue gradient:
gl_FragColor = vec4(color2, 1.0);
Or you can change that to colorA
, colorB
, or colorC
to see a screen of solid color. Play with it, have fun, figure out how it works.
Bubble Gum
I won't be diving into how this shader works, but it's just so dang soothing to stare at, I had to share it. Let me know if you'd like to read about it in a future issue.
Shader Resources
Ilithya shared some great resources for learning shaders over on her blog. The resource that everyone recommends is The Book of Shaders, so definitely check that out if you're interested.
Top comments (0)