Unraveling the Magic of Graphic Shaders

Omar Shehata
Apr 25, 2016 · 7 min read

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:

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:

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:


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?

Here’s my solution to the gradient above:

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:

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:

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

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.

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:

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:

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:

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?

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?

My solution:

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

Here’s my solution:

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

Here’s my solution:

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).

Omar Shehata

Written by

Graphics programmer at Cesium. I absolutely love telling stories and it's why I do what I do, from making games, to teaching & writing. http://omarshehata.me/