Unraveling the Magic of Graphic Shaders

This article is written for my 2016 GlitchCon shader workshop. If you’d like a more in-depth guide on shaders, check out my Tuts+ tutorials.

What can you do with shaders?

Most modern games owe a lot to the GPU for their visual effects. Here are some of my favorite examples:

Lava (ThreeJS)
Lighting Model (ThreeJS)
Electric Pulses
Water ripple effect
It wobbles to the music

What is a shader?

A piece of code (written in GLSL — OpenGL Shading Language, very similar to C syntax) that runs on every pixel at the same time. It returns the color to set the pixel to. If you want to visualize this, the Mythbusters have you covered:

CPU at 0:15 — GPU at 1:10

The OpenGL documentation is useful, but shaderific.com also has a really nice reference.

A pixel shader (also commonly called fragment shader ) runs on every pixel. A vertex shader runs on every vertex. We’ll be working with pixel shaders today.

Step 1: Your first shader!

Here’s the simplest shader we can start with:

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
fragColor = vec4(1.0,1.0,1.0,1.0);
}

You can run this in the ShaderToy editor here:

https://www.shadertoy.com/view/4scSD2

Challenge 1: Can you turn it red? What about gray?

Step 2: Create a variable

To declare any variable:

float x = 1.0;//A floating point number
vec4 myVector = vec4(1.0,0.5,0.25,0.1);//A vector of four numbers

You can also create vec2 & vec3. To access the individual components, you can do:

vec4 myVector = vec4(1.0,0.5,0.25,0.1);
myVector.r //This would be 1.0
myVector.g //0.5
myVector.b //0.25
myVector.a //0.1

Instead of “rgba”, you can also use “xyzw”. They’re equivalent ways of accessing a vector’s components.

Step 3: Half & Half

You are given the pixel’s coordinates (x,y) in the argument fragCoord. Can you use that to set half the screen to red and the other to blue? So that you get something like this:

Note: if you’re getting the error below, it’s because GLSL is strict about its types. Make sure to always use “1.0” instead of “1” for any number.

If you want to see how I did, here’s my solution: http://pastebin.com/raw/peV6ZmZG

Challenge 2: Can you make it split vertically instead of horizontally?
Challenge 3: Can you make it a gradient instead of a sharp split?
Challenge 3: Creating a gradient

Here’s my solution to the gradient above:
 http://pastebin.com/raw/RRkUuijP

Step 4: Shader inputs

Your shader runs on the GPU, so it doesn’t have access to the rest of your code. If you want to pass something to the shader, you need to create a uniform variable.

Now ShaderToy already does this for us. If you click on “Shader Inputs”, above the code editor, you’ll see all the variables it’s passing:

All the variables passed to your shader

Note: if you were programming outside of ShaderToy, you will NOT have any uniform variables by default! You’d have to pass those yourself to the shader depending on what language/platform you’re working in.

iGlobalTime is a variable that’s constantly increasing. It’s actually the counter at the bottom of the simulation:

You can also pause or reset the simulation

Challenge 4: Can you use iGlobalTime to make it slowly turn red?

Using iGlobalTime to change the color over time

Step 5: Normalized coordinates

When we wanted to split the screen red & blue, we did something like this:

if(fragCoord.x > 500.0){

500 is only the center if the screen is 1000 pixels. What if we wanted to get the center of the screen regardless of its size?

iResolution is a vector that has the screen’s width and height. So if we divide:

float pixelX = fragCoord.x / iResolution.x; 

pixelX would be 0 on the leftmost border, and 1 at the rightmost border.

Challenge 5: Can you use iResolution to fix our red/blue split or gradient to look the same in any resolution?

Hint: Click on the fullscreen button on the bottom right of the simulation window to confirm that it looks the same when you change the view size.

Full screen button (bottom right of simulation)

Challenge 6: Can you create the effect below?

Hint: This is a green gradient on the Y axis, and a red gradient on the X axis.

My solution:
http://pastebin.com/raw/RrWx1PqR

Step 6: Rendering a texture

If you want to render an image, you need a way to know what the color is at every pixel. GLSL has a function for this called texture

vec4 color = texture(imageData,point);

It takes in two arguments: Image data, and a point (as a 2d vector of x,y). It returns the color of that pixel as RGBA.

ShaderToy does NOT let you upload any image you like. However you can choose to set an image by clicking on the channels below the code window:

Click on one of them and select an image (or a video!)

Here’s an example of grabbing the color of the pixel at (0,0) and setting the screen to that color:

vec2 point = vec2(0.0,0.0);
fragColor = texture(iChannel0,point);//Get the color of the pixel at (0,0) in iChannel0

Note: texture expects the coordinates to be normalized between 0 and 1

Challenge 7: Can you make it render the whole image?

My solution:
http://pastebin.com/raw/rJPkB1W9

Step 7: Post Processing

If you can grab the color of each pixel of an image, then you can do whatever you like before you draw it.

Challenge 8: Remove all blue from the image

Challenge 9: Switch all red in the image with green and vice versa

Step 8: Combining images

We’re not limited to grabbing pixels from only one image. You can very well do:

vec4 color1 = texture(iChannel0,point);
vec4 color2 = texture(iChannel1,point);
fragColor = color1+color2;

Try that (after selecting images for iChannel0 and iChannel1).

Challenge 10: Can you think of other ways to combine pixels from two images? Can you get an effect like the one below?

Combining two textures

Step 9: Displacement

What happens if, instead of getting the color of the pixel at x,y like so:

vec2 point = fragCoord.xy/iResolution.xy;
fragColor = texture(iChannel0,point);

You instead, got the color of the pixel a little to the right?

vec2 point = fragCoord.xy/iResolution.xy
point.x += 0.05;
fragColor = texture(iChannel0,point);

Challenge 11: Can you make the image move back and forth like in the gif below?

Displacing an image back and forth

My solution:
http://pastebin.com/raw/eDeHNChd

Challenge 12: Can you make the image’s pixels explode like the gif below?

Exploding Cats

Here’s my solution:
http://pastebin.com/raw/AsaxjSrV

Challenge 13: Can you make it wobble like the gif below?

Wibbling Wobbling Cats

Here’s my solution:
http://pastebin.com/raw/ZwH8FW0N

Bonus Challenge 1: Remove the green screen in the videos on ShaderToy. Can you remove as much as you can without distorting the original video?

Tip: The green in the background isn’t a pure green. It has RGB (13,161,37)
Tip 2: The GLSL distance function might be useful here

Bonus Challenge 2: Can you break apart the sun demo and figure out how it works?

Tip: It might look complicated, but remember that any shader in the world boils down to returning just a single color. It’s just layers of effects compounded on each other. Start from the bottom of the code and trace your way up.
Tip 2: Change iChannel0 to a different image 
Tip 3: Make the snoise function return 1.0 to cancel its effect (and in doing so figure out what piece of the image it contributes to).

Show your support

Clapping shows how much you appreciated Omar Shehata’s story.