3 - Uniforms

Chapter 3 - Uniforms

In chapter 3 of the Book of Shaders we are introduced to the concept of uniform input:

Shader pixels are rendered in parallel and each thread is "blind" to the others. To send input to these threads the input must be equal or "uniform" across all threads (and read only). Built-in uniforms exist for things like texture resolution, mouse input, and time.

Here are a few of "uniforms" in WebGL:

  • uniform vec2 u_resolution - Width and height of the canvas.
  • uniform vec2 u_mouse - Mouse x / y position.
  • uniform float u_time - Time in seconds since the shader loaded.

The names of the built-in uniforms differ across implementations. Here are similar ShaderToy equivalents:

  • uniform vec3 iResolution - Viewport pixel resolution
  • uniform vec4 iMouse - Mouse cordinates x/y and left/right mouse clicks z/w.
  • uniform float iTime - Shader playback time in seconds.

Notice that the resolution and mouse uniforms are different dimensions depending on the implementation. This is important to know if you are porting code between implementations.

Check the official GLSL Uniforms Wiki Docs for the nitty-gritty on this variable type.

In ShaderToy the uniforms are available by default, but with WebGL we need to specify them in our code, right after we specify the float precision:

#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

Varying Built-Ins

Along with uniforms there are also built-in varying variables. In the previous chapter we saw the built-in output varying gl_FragColor, an out parameter of type vec4 targetting the RGBA colour of each pixel.

โš ๏ธ Warning: Apparently gl_FragColor is depreciated in current versions of the standard in favour of a manually defined output paramater. We can re-create gl_FragColor like so: layout(location=0) out vec4 gl_FragCoord; Details.

There is also a default output varying in parameter that specifies which x/y/z coordinates of the pixel the shader is working on called gl_FragCoord. The official docs shows its declaration as a 4D input paramater (in vec4 gl_FragCoord ;) defined as (x, y, z, 1/w).

๐Ÿ™‹ Question: I've only worked with gl_FragCoord so far in two dimensions gl_FragCoord.xy. What does it mean for a shader to have a z-index depth? And what the heck is 1/w?

Building a Colour Gradient Shader

Let create a Shader using the shader pixel varying out parameter gl_FragColor and the shader position varying out parameter gl_FragCoord along with the shader image-resolution uniform input parameter, u_resolution.

#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
// Color gradient based on a 2D coordinate.
// Takes a vec2 (x, y) coordinate. Returns a vec4 (r, g, b, a) color.
vec4 gradient(vec4 coord) {
// Normalized pixel coordinates (from 0 to 1) using the image
// dimensions pulled from vec2 iResolution.
vec2 uv = coord.xy / u_resolution.xy;
// Return vec4 (r, g, b, a) with red gradated by the pixel's x coord.
// Green, blue, and alpha are kept constant.
return vec4(uv.x, 0.364, 0.499, 1.0);
// Or, involve both the x and y axis:
// return vec4(uv.x, uv.y, 0.499, 1.0);
}
// Function is automatically executed. Here it assigns a color to every pixel.
void main() {
// Set the color of each pixel to a gradient based its coordinates.
gl_FragColor = gradient(gl_FragCoord);
}
// Stung Eye 2020 - Unlicense - https://unlicense.org
// This is free and unencumbered software released into the public domain

Download 3.0-hello-gradient.frag or interact with the ShaderToy port of this shader.

Shader Output

And here is our pretty gradient:

Building a Dynamic Gradient Colur Shader

Here's a more complex shader that also plays with colour gradients but also involves the u_mouse and u_time uniforms.

#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
// Sinusoidal function shifted and scaled to range betwen 0 and 1.
// See: https://www.desmos.com/calculator/w9jrdpvsmk
float sinEase(float x) {
return 0.5 * sin(x) + 0.5;
}
void main() {
// Set scale according to how far mouse is from left side.
// 0.0 (full left) to 1.0 (full right)
// Mouse coordinate is set when left-mouse-button is clicked.
float blueRateScale = u_mouse.x / u_resolution.x;
// Ease back and forth 1 and 100 using time-based sine.
float stretchFactor = 99.0 * sinEase(u_time) + 1.0;
// Setting red and green pixel value.
// Ease back and forth between 0 and 1 based on pixel position
// and time-based stretch factor.
float red = sinEase( gl_FragCoord.x / stretchFactor);
float green = sinEase( gl_FragCoord.y / stretchFactor);
// Blue will ease in and out based on time and the x position of the mouse.
// Ease speed increases from left to right.
float blue = abs(tan(u_time * blueRateScale));
// Set the pixel value based on the RGB calculated above.
gl_FragColor = vec4(red,green,blue,1);
}
// Stung Eye 2020 - Unlicense - https://unlicense.org
// This is free and unencumbered software released into the public domain.

Download 3.0-uniforms.frag or interact with the ShaderToy port of this shader.

Shader Output

Hover your mouse over the shader to change the rate at which it changes from a pink/blue/cyan colour scheme to a red/yellow/green scheme.

Trigonometric Rabbit Hole

๐ŸŽต Shadertime, and the living is "Ease-y". ๐ŸŽต

This shader took me down a trig rabbit hole, chasing down a formula that would allow a gently transition, called an "ease", between 0.0 and 1.0 along a sinusoidal path. I used this interactive sin plot to determine the answer. Sin normally transitions between -1 and +1, so we need to divide it by 2 (now transitioning between -0.5 and 0.5) and then add 0.5 (now transitioning between 0.0 and 1.0):

float sinEase(float x) {
return 0.5 * sin(x) + 0.5;
}

This sinEase function is used to ease R & G pixel colour strength according to pixel position in both x and y, creating what looks a bit like an interference pattern. The "width" of the colour easing sinusoid is stretched out from 1% to 99% according to the shader's playback time in seconds.

float stretchFactor = 99.0 * sinEase(u_time) + 1.0;
float red = sinEase( gl_FragCoord.x / stretchFactor);
float green = sinEase( gl_FragCoord.y / stretchFactor);

The mouse's x position changes rate at which the Blue component strength eases between 0.0 and 1.0:

float blue = abs(tan(u_time * blueRateScale));

I'm not sure why I went with the ridiculous function of the absolute value of the tangent of the delta time for the blue easing.

Graident Shader in Unreal Engine

Here's the above blue/pink gradient shader implemented as an Unreal Engine material using their node-base language:

Unreal Graident Material Nodes

Copy these nodes to your clipboard to paste into Unreal Engine.

From "the outside" this material has percentage value (0.0 to 1.0) parameters for the material's Metallic, Specular, Roughness, and Emissive Color, properties.

In the GLSL shader the colours gradated by holding the Green and Blue component of every pixel constant (G = 36.4% intensity, B = 49.9% intensity), and changing the Red component with the x position of each pixel. The x position of the shader pixels is converted to a normalized range (0.0 to 1.0) by dividing actual x pixel position by the image resolution. From left to right the Red component of each pixels will increase from 0.0 (no red) to 1.0 (full stregth red).

vec2 uv = gl_FragCoord.xy / u_resolution.xy;
// Return vec4 (r, g, b, a) with red gradated by the pixel's x coord.
// Green, blue, and alpha are kept constant.
return vec4(uv.x, 0.364, 0.499, 1.0); // Full alpha for all pixels

In the Unreal Material I used two colour nodes, represented in Unreal as two 4D constant vectors Constant4Vector: A blue shade (0, 0.364, 0.499) and a pink shade (1, 0.364. 0.499) with full alpha for both.

The Lerp node performs linear interpolation on these two colour vectors, using the normalized x position of the material's boundingBoxBased_0-1_UVW as the position in the gradient between the two colours.

The lerpped colour is then used as the material's Base Color and is also optionally added as the material's Emissive Color. Lerping is another example of an easing function.

And here's a gif of this material applied to a simple sphere:

Unreal Graident Material Output

Next up we'll explore running shaders across different platforms and embedding shaders in online docs.