5 - Shaping Functions
Chapter 5 - Shaping Functions
In chapter 5 of the Book of Shaders we are introduced to the essential kata of shaders, shaping functions. These are functions as in C-programming-language-like functions, but they are also functions as in mathematical pure functions. As pure functions, given the same input they always give the same output. (This includes shaping functions that include pseudo-random processes, if the RNG is always same-seeded.) We use these functions in our shaders to "shape" features like pixel colour across across the shader, or movement within the shader over time.
GLSL includes a number of built-in shaping functions. The built-in smoothstep is explored below. We can also build our own custom shaping functions through the compositino of built-in math functions like sin, abs, sign, clamp, min, max, mod, fract, ceil, floor, etc.
Like with the other chapters my shader explorations led to a number of "side quests". I proudly went down the rabbit holes of bit-mapped text output and 2D signed-distance functions. The end result was an animated gallery of shaping functions and these hypnotic waves:
Download
5.2-purple-waves.frag
or interact with the ShaderToy port of this shader.
Plotting Smooth Lines
In chapter 4 we learn how to graphically plot 2D y = mx + b
-like functions. The plotting is anti-aliased using a lovely shaping trick that uses two smoothstep
calls to produce a soft line:
This function took me a while to grok. More than a while. Thinking in pixels is tricky.
Sinusoidal Movement Over Time
The first shader I made using these technics was an animated sine wave. The background of the shader is a gradient produced by the sin function, with an x/y plot of the sin function overlaid.
The function was plotted using the smoothstep
trick show above. To produce the gradient the x position of each pixel is used to produce a black-to-white colour intensity:
University is 20 years back for me, so I'd completely forgotten the subtleties of sin. Spent 20 minutes messing with this interactive sin plot to relearn some trig fundamentals.
All pixel positions used in the shader were normalized to a 0.0 to 1.0 range as a function of shader resolution:
Animated Sine Wave - Shader
Animated Sine Wave - GLSL Source
Download
5.0-animated-sine-wave.frag
or interact with the ShaderToy port of this shader.
Shaping Function Gallery
The next shader, I realize now, was quite ambitious. It's an animated gallery of shaping functions, where each function is use to generate:
- A black to white background gradient based on the x position of each pixel.
- A 2D x/y graph of the function plotted in purple. (Bottom left corner is x == 0 and y == 0.)
- The easing movement of an orange circle from the bottom left corner to the top right one.
Along with this I wanted to:
- Display the current shaping formula as text rendered to the shader.
- Display a time-to-next-function progress bar along the bottom of the shader.
Shaping Gallery - Shader
This shader took me a few days to hack together, but boy did I learn a lot!
Shaping Gallery - Rabbit Holes
Before we get to the source code, here are some of the rabbit holes I followed:
- Ultra Smooth Function Plotting - The
smoothstep
-based plotting worked well until I got to functions liketan
that included step curves, at which point the plot line would go jagged or discontinuous. I eventually stumbled across and then use this distance-based anti-aliased technique. I fully admit that I don't actually understand how this code works, but the plot lines it produces are silky smooth! - Text Rendering - I wanted to be able to print out the current function being visulized as text, but GLSL doesn't have native text output. I played around with font textures but settled on a bitmapped-font technique. This could be super handy for debugging too!
- Array Constructors: WebGL is built on the OpenGL 2.0 spec, this means Googling for code samples sometimes fails because OpenGL 3.x or 4.x features won't work. I wanted to store arrays of bitmapped characters to build the display strings but 2.0 doesn't allow for array literals or array constructors. Building the text string arrays was a chore. This is not an issue in ShaderToy, but I didn't bother refactoring with literals for the ShaderToy port.
- 2D Signed Distance Circle Easing - While trying to figure out how the above mentioned ultra smooth plotting worked I stumbled across signed distance functions, which look to be a lovely way to represent and display 2D and 3D primitives. Inigo Quilez's articles on the subject seem like a treasure trove. The orange circle in the gallery shader was inspired by iq's 2D SDF functions page.
- Normalizing Coordinates - Normalized texture coordinates in GLSL shaders are often labeled
uv
orst
, with different coders normalizing the ranges in different ways. This blog post on GLSL Programming Tricks helped me figure out how to use two different coordinate normalizations in the shader (one for the text and one for everything else):
From that post, here are a few different coordinate normalization tactics that allow you to change the shader's point of origin (x == 0 and y == 0):
Notice that the pixel position fragCoord
is normalized by the resolution to fall within a range of shader coordinates. Shader coders usually use the variable names uv
or st
for these texture coordinate vectors, but there appears to be a subtle difference between the two that I don't get yet. The variable names uv
and st
don't stand for anything. Each character in the name refers to one of the floats contained in the vec2
. Instead of x/y coordinates we have u/v coordinates or s/t coordinates.
Shaping Gallery - GLSL Code
Here's the WebGL GLSL 2.x source for the above shaping gallery shader. I've tried my best to break things down into named and commented functions.
There's a lot of code here. Many functions. Skip to the bottom first and read through main()
before exploring the rest.
Download
5.1-shaping-gallery.frag
or interact with the ShaderToy port of this shader.
Up next we'll be exploring colours, gradients, and colour spaces.