Shadertoy: Basic Things You Can Do

Overview

This is a collection of simple shaders in Shadertoy that show how to draw basic elements using basic functions like step and smoothstep. If you're new to Shadertoy or shader programming in general, you might find these useful.

In the following paragraphs, you'll find shaders that illustrate some simple functions and basic drawing techniques. The code includes plenty of comments to guide you through the implementation.

The shader code is pasted below each implementation, but if you click on the shader's name, you'll be taken to Shadertoy, where you can experiment with it.

Info

On this page you'll find shaders written in Shadertoy. Read about use the interactive content in this blog

When reading the following shaders, keep in mind that each shader is executed for every pixel independently, with only the pixel’s coordinates as input.

If the drawing logic seems cumbersome, the key point is that a shader can only control the color of the pixel it is executed on—it cannot modify other pixels on the screen. This means the logic is somewhat reversed: instead of actively placing shapes, the shader must determine whether the desired drawing overlaps its own coordinates and, if so, output the appropriate color.

The Step Function

Probably the simplest and most commonly used function in Shadertoy shaders. The step(edge, x) function is returns 0 if x is less than edge and 1.0 otherwise. It creates a sharp transition.

1// The step function: zero in the left half of the screen, 1.0 on the right
2void mainImage( out vec4 fragColor, in vec2 fragCoord )
3{
4    // Step return 0 if fragCoord.x is less then the half screen resolution, 1.0 otherwise
5    float c = step(iResolution.x/2.0, fragCoord.x);
6    fragColor = vec4(c, c, c, 0.0);
7}



The Smoothstep Function

The "smooth" version of the previous one. With this function (smoothstep) the transition is smooth. It's probably the second most basic function used in Shadertoy shaders.

 1void mainImage( out vec4 fragColor, in vec2 fragCoord )
 2{
 3    // Performs the Hermite interpolation.
 4    // See https://registry.khronos.org/OpenGL-Refpages/gl4/html/smoothstep.xhtml
 5
 6    // Smooth transition from left to right
 7    float c = smoothstep(0., iResolution.x, fragCoord.x);
 8    
 9    fragColor = vec4(c,c,c,1.);
10}



Centered Normalized Coordinates

When working in Shadertoy, it's often useful to convert the input pixel coordinates into a more manageable form, like normalized coordinates. In this example, the pixel coordinates are converted to the range [-Aspect Ratio, Aspect Ratio]×[-1,1] (width × height). Aspect Ration is screen_width / screen_height.

This coordinate system is more convenient because:

  • The origin ((0,0)) is at the center of the image.
  • The y-axis ranges from (-1) to (1).
  • The x-axis is scaled to preserve the aspect ratio, so its values fall within [-aspectRatio, aspectRatio].
 1void mainImage( out vec4 fragColor, in vec2 fragCoord )
 2{
 3    // Normalizing coordinates: the center of the screen is (0,0)
 4    // and the coordinates range from -1 to 1 along the y axis
 5    vec2 U = ( 2. * fragCoord - iResolution.xy ) / iResolution.y;
 6    
 7    // Compute the length of the vectors in normalized coordiantes (that it's the distance from the center)
 8    float f = length(U);
 9    
10    // Draw the length as a shade of grey, black is near, while white is far
11    fragColor = vec4(f,f,f,1.);
12}



Draw a Circle

The most basic of all drawings: a circle and not even an antialiased one! For the antialiased version, use smoothstep instead of step.

 1// Draw a circle P with a radius of r
 2#define drawPoint(P, r) step( length(U - P), r)
 3
 4void mainImage( out vec4 fragColor, in vec2 fragCoord )
 5{
 6    // Normalizing coordinates: the center of the screen is (0,0)
 7    //  and the coordinates range from -1 to 1 along the y axis
 8    vec2 U = ( 2. * fragCoord - iResolution.xy ) / iResolution.y;
 9    
10    // Coordinates of the points
11    vec2 P1 = vec2(-1.,0.);
12    vec2 P2 = vec2(-.5,0.);
13    vec2 P3 = vec2(0.,0.);
14    vec2 P4 = vec2(.5,0.);
15    vec2 P5 = vec2(1.,0.);
16    
17    // The size of 1 pixel in normalized coordinates
18    float px_size = 2. / iResolution.y;
19    
20    // Draw the points: if this pixel belongs to one of the points, then one of the drawPoint 
21    // will return 1., and we save this info. For this reason we getting the maximum value of each call.
22    // If the pixel doesn't belong to any point, all the functions will return 0.0
23    float f = drawPoint( P1, px_size);
24    f = max(f, drawPoint( P2, px_size * 2.));
25    f = max(f,drawPoint( P3, px_size * 4.));
26    f = max(f,drawPoint( P4, px_size * 8.));
27    f = max(f,drawPoint( P5, px_size * 16.));
28    
29    fragColor = vec4(f,f,f,1.);
30}



Draw a Segment

How do you draw a segment? Here it is! The key is to compute the distance from the pixel to the segment line. If this distance is 0, the pixel might belong to the segment, but only if it lies between the segment's endpoints, A and B. So we need to check that as well.

 1// Returns 1.0 if the point is closed to the segment less than the thickness
 2#define drawLine(A, B, r) smoothstep(r,0.,distanceFromSegment(U, A, B))
 3
 4// Draw a segment between A and B, with thickness r.
 5// It computes the distance of a point from the segment line and return it. If this distance is 0 
 6// the point belong to the line (and so to the segment)
 7float distanceFromSegment(vec2 U, vec2 A, vec2 B) 
 8{
 9	vec2 UA = U - A;
10    vec2 BA = (B - A);
11    
12    float s = dot(UA, BA) / length(BA);   // scalar projection of U-A on B-A
13    s = s / length(BA); 				  // normalize the projection value in the range [0,1], 
14    								      //  a value of 0 means the projection correspond to A, 1 to B,
15    									  //  in between the projection is inside the segment, 
16                                          //  outside [0,1] the projection doesn't belong to the segment.
17    s = clamp(s, 0., 1.);                 // If the scalar projection is outside [0,1], its value is clamped to 
18                                          //  0 or 1 ...
19    return length(UA - s*BA);          	  // ... so here we compute the distance of U from its projection if it is
20                                          // inside the segment, or from the extreme points A or B if it is outside
21}
22
23void mainImage( out vec4 fragColor, in vec2 fragCoord )
24{
25    // Normalizing coordinates: the center of the screen is (0,0)
26    // and the coordinates range from -1 to 1 along the y axis
27	  vec2 U = ( 2. * fragCoord - iResolution.xy ) / iResolution.y;
28     
29    // The size of 1 pixel in normalized coordinates
30    float px_size = 2. / iResolution.y;
31    
32    // Thickness
33    float thickness = px_size;
34    
35    // The segments points
36    vec2 A = vec2(-1.0,-0.5);
37    vec2 B = vec2(-0.5,0.5);
38    vec2 C = vec2(0.0,-0.5);
39    vec2 D = vec2(0.5,0.5);
40    vec2 E = vec2(1.0,-0.5);
41    
42    // If the point belong at one line the value will be 1., otherwise 0.
43    float f = drawLine(A, B, thickness)
44        	 +drawLine(B, C, thickness)
45        	 +drawLine(C, D, thickness)
46        	 +drawLine(D, E, thickness);
47    
48    fragColor = vec4(f,f,f,1.);
49}



Draw a Function

Now let's use everything we've learned to draw some functions. Note also how the centered normalized coordinates are scaled up (zoom out) here.

 1// Draw a function
 2// The functions are scaled or translated so they fit better in the viewport
 3
 4#define f_sin(x) sin(6. * x)      // The sin function scaled along the x-axis to make it more visible
 5#define f_x(x) x                // A line
 6#define f_log(x) log(x)           // Logarithm
 7#define f_exp(x) exp(x - 1.) - 1. // Exponential function translated on the x and y axes
 8#define f_ss(x) smoothstep(-1., 1., x)
 9
10
11// Draw a circular point P with a radius of r
12#define drawPoint(P, r) smoothstep( r/iResolution.y, 0., abs(length(U - P)))
13
14void mainImage( out vec4 fragColor, in vec2 fragCoord )
15{
16    fragColor = vec4(0);
17    
18    // Normalizing coordinates: the center of the screen is (0,0)
19    // and the coordinates range from -1 to 1 along the y axis
20	  vec2 U = ( 2. * fragCoord - iResolution.xy ) / iResolution.y;
21    
22    // This scales up the coordinates (zoom out)
23    U *= 4.;
24    
25    // The position of the point to be drawn
26    vec2 P = vec2(U.x, f_ss(U.x));
27    float thickness = 10.;
28    fragColor = vec4(drawPoint(P, thickness)); // Smoothstep (White)
29    
30    P = vec2(U.x, f_sin(U.x));
31    thickness = 25.;
32    // Sum up all the result from drawpoint, if this pixel fall alt least in one of the graphs will be drawn
33    fragColor += vec4(0.,0.,drawPoint(P, thickness),1.); // sin(6x) (BLUE)
34    
35    P = vec2(U.x, f_log(U.x));
36    thickness = 25.;
37    fragColor += vec4(drawPoint(P, thickness),0.,0.,1.); // log(x) (RED)
38    
39    P = vec2(U.x, f_exp(U.x));
40    thickness = 25.;
41    fragColor += vec4(0.,drawPoint(P, thickness),0.,1.); // exp(x-1)-1 (GREEN)
42}



A Checkerboard Pattern

Now let's get a small taste of the power of math in shaders: draw a checkerboard pattern with just one line of code (the last one, by the way). Try to figure out why it works!

 1void mainImage( out vec4 fragColor, in vec2 fragCoord )
 2{
 3    // Change coordinates from [0,iResolution.x]x[0,iResolution.y] to [-1,1]x[-1,-1]
 4    vec2 UV = (2.*fragCoord-iResolution.xy)/iResolution.x;
 5    
 6    // Scale coordinate a bit, to get a bigger checkboard
 7    UV *= 10.;
 8    
 9    // Shift checboard to the right
10    UV.x -= iTime;
11    
12    // Generate checkboard pattern (1 line!!)
13    fragColor = vec4(mod(floor(UV.x)+floor(UV.y),2.));
14}



Shadertoy for Fun and Profit!