Shader Toy Tutorial

../../_images/cyber_fuji_2020.png

cyber_fuji_2020.glsl Full Listing

Graphics cards can run programs written in the C-like language OpenGL Shading Language, or GLSL for short. These programs can be easily parallelized and run across the processors of the graphics card GPU.

Shaders take a bit of set-up to write. The ShaderToy website has standardized some of these and made it easier to experiment with writing shaders. The website is at:

https://www.shadertoy.com/

Arcade includes additional code making it easier to run these ShaderToy shaders in an Arcade program. This tutorial helps you get started.

Step 1: Open a window

This is simple program that just opens a basic Arcade window. We’ll add a shader in the next step.

Open a window
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import arcade

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

    def on_draw(self):
        # Clear the screen
        self.clear()

if __name__ == "__main__":
    MyGame()
    arcade.run()

Step 2: Load and display a shader

This program will load a GLSL program and display it.

Run a shader
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import arcade
from arcade.experimental import Shadertoy

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

        # Read a GLSL program into a string
        file = open("circle_3.glsl")
        shader_sourcecode = file.read()
        # Create a shader from it
        self.shadertoy = Shadertoy(size=self.get_size(),
                                   main_source=shader_sourcecode)

    def on_draw(self):
        # Run the GLSL code
        self.shadertoy.render()

if __name__ == "__main__":
    MyGame()
    arcade.run()

Next, let’s create a simple first GLSL program. Our program will:

  • Normalize the coordinates. Instead of 0 to 1024, we’ll go 0.0 to 1.0. This is standard practice, and allows us to work independently of resolution. Resolution is already stored for us in a standardized variable named iResolution.

  • Next, we’ll use a white color as default.

  • If we are greater that 0.2 for our coordinate (20% of screen size) we’ll use black instead.

  • Set our output color, standardized with the variable name fracColor.

GLSL code for creating a shader.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Default our color to white
    vec3 color = vec3(1.0, 1.0, 1.0);

    // If we are more than 20% of the screen away from origin, use black.
    if (distance > 0.2)
        color = vec3(0.0, 0.0, 0.0);

    // Output to the screen
    fragColor = vec4(color, 1.0);
}

The output of the program looks like this:

../../_images/circle_1.png

Other default variables you can use:

uniform vec3 iResolution;
uniform float iTime;
uniform float iTimeDelta;
uniform float iFrame;
uniform float iChannelTime[4];
uniform vec4 iMouse;
uniform vec4 iDate;
uniform float iSampleRate;
uniform vec3 iChannelResolution[4];
uniform samplerXX iChanneli;

“Uniform” means the data is the same for each pixel the GLSL program runs on.

Step 3: Move origin to center of screen, adjust for aspect

Center the origin
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Position of fragment relative to center of screen
    vec2 pos = uv - 0.5;
    // Adjust y by aspect ratio
    pos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(pos);

    // Default our color to white
    vec3 color = vec3(1.0, 1.0, 1.0);

    // If we are more than 20% of the screen away from origin, use black.
    if (distance > 0.2)
        color = vec3(0.0, 0.0, 0.0);

    // Output to the screen
    fragColor = vec4(color, 1.0);
}
../../_images/circle_2.png

Note

To Be Done…

The rest of the is TBD

Glow

GLSL code for creating a shader.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Adapted from https://www.shadertoy.com/view/3s3GDn

void mainImage( out vec4 fragColor, in vec2 fragCoord ){

    //***********    Basic setup    **********

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
	// Position of fragment relative to centre of screen
    vec2 pos = 0.5 - uv;
    // Adjust y by aspect for uniform transforms
    pos.y /= iResolution.x/iResolution.y;

    //**********         Glow        **********

    // Equation 1/x gives a hyperbola which is a nice shape to use for drawing glow as
    // it is intense near 0 followed by a rapid fall off and an eventual slow fade
    float dist = 1.0/length(pos);

    //**********        Radius       **********

    // Dampen the glow to control the radius
    dist *= 0.1;

    //**********       Intensity     **********

    // Raising the result to a power allows us to change the glow fade behaviour
    // See https://www.desmos.com/calculator/eecd6kmwy9 for an illustration
    // (Move the slider of m to see different fade rates)
    dist = pow(dist, 0.8);

    // Knowing the distance from a fragment to the source of the glow, the above can be
    // written compactly as:
    //	float getGlow(float dist, float radius, float intensity){
    //		return pow(radius/dist, intensity);
	//	}
    // The returned value can then be multiplied with a colour to get the final result

    // Add colour
    vec3 col = dist * vec3(1.0, 0.5, 0.25);

    // Tonemapping. See comment by P_Malin
    col = 1.0 - exp( -col );

    // Output to screen
    fragColor = vec4(col, 1.0);
}

Other examples

This short ShaderToy demo loads a GLSL file and displays it:

Shader Toy Demo
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import arcade
from arcade.experimental import Shadertoy


class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080, title="Shader Demo", resizable=True)

        # Keep track of total run-time
        self.time = 0.0

        # Read in a GLSL program and create a shadertoy out of it
        # file_name = "fractal_pyramid.glsl"
        # file_name = "cyber_fuji_2020.glsl"
        file_name = "earth_planet_sky.glsl"
        # file_name = "flame.glsl"
        # file_name = "star_nest.glsl"
        file = open(file_name)
        shader_sourcecode = file.read()
        self.shadertoy = Shadertoy(size=self.get_size(), main_source=shader_sourcecode)

    def on_draw(self):
        self.clear()
        mouse_pos = self.mouse["x"], self.mouse["y"]
        self.shadertoy.render(time=self.time, mouse_position=mouse_pos)

    def on_update(self, dt):
        # Keep track of elapsed time
        self.time += dt


if __name__ == "__main__":
    MyGame()
    arcade.run()

You can click on the caption below the example shaders here to see the source code for the shader.

Some other sample shaders:

../../_images/star_nest.png

star_nest.glsl Full Listing

../../_images/flame.png

flame.glsl Full Listing

../../_images/fractal_pyramid.png

fractal_pyramid.glsl Full Listing

Writing shaders is beyond the scope of this tutorial. Unfortunately, I haven’t found one comprehensive tutorial on how to write a shader. There are several smaller tutorials out there that are good.

Here is one learn-by-example tutorial:

https://www.shadertoy.com/view/Md23DV

Here’s a video tutorial that steps through how to do an explosion:

https://www.youtube.com/watch?v=xDxAnguEOn8