Skip to main content
GameDev.net gamedev.net

Transforming Shader Space

by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026

Use in your engine

Run the shader to adjust these controls.

What it demonstrates

This project applies translation, rotation, and scale to a procedural rectangle without moving any vertices. The controls transform the coordinate space used to evaluate a signed-distance shape. It introduces inverse mapping, transformation order, and a small reusable two-dimensional rotation matrix.

Source Revision 1

Author notes are linked to specific lines.

Shader inputs

void mainImage(out vec4 fragColor, in vec2 fragCoord)

Called once per pixel. Write the colour to fragColor.

iResolution vec3
Viewport size in pixels (z is the pixel aspect ratio).
iTime float
Seconds since the shader started.
iTimeDelta float
Seconds since the previous frame.
iFrameRate float
Frames per second, smoothed.
iFrame int
Frames rendered since the start.
iMouse vec4
Mouse position: xy while held, zw of the last click.
iDate vec4
Year, month, day, and seconds within the day.
iChannel0 sampler2D
Texture bound to channel 0.
iChannel1 sampler2D
Texture bound to channel 1.
iChannel2 sampler2D
Texture bound to channel 2.
iChannel3 sampler2D
Texture bound to channel 3.
iChannelResolution vec3[4]
Pixel size of each bound channel texture.
iChannelTime float[4]
Playback time of each channel, in seconds.
iSampleRate float
Audio sample rate, always 44100.
Main Image6
1 uniform float uRotation; // @param -3.14..3.14 = 0.55 "Rotation"
2 uniform float uScale; // @param 0.5..2.5 = 1.2 "Scale"
3 uniform vec2 uOffset; // @param -0.5..0.5 = 0.0, 0.0 "Translation"
4
5 mat2 rotationMatrix(float angle) {
6 float c = cos(angle);
7 float s = sin(angle);
8 return mat2(c, -s, s, c);
9 }
10
11 void mainImage(out vec4 fragColor, in vec2 fragCoord) {
12 vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
13 p = rotationMatrix(-uRotation) * (p - uOffset) / uScale;
14 vec2 boxDistance = abs(p) - vec2(0.24, 0.14);
15 float signedBox = length(max(boxDistance, 0.0)) + min(max(boxDistance.x, boxDistance.y), 0.0);
16 float shape = 1.0 - smoothstep(0.0, 0.008, signedBox);
17 vec3 color = mix(vec3(0.04, 0.05, 0.09), vec3(0.30, 0.82, 0.94), shape);
18 fragColor = vec4(color, 1.0);
19 }
20

Learn from this shader

How it works

The shader begins with centered, aspect-correct coordinates. To place an object, each fragment is mapped backward into the object's local space: subtracting Offset reverses translation, multiplying by a negative-angle matrix reverses rotation, and dividing by Scale reverses enlargement. The order matters because rotation and translation do not generally commute. Once transformed, the rectangle remains a simple centered box. Absolute value folds the plane into one quadrant, and outside and inside distance terms combine into a signed box distance. A narrow smooth threshold turns that field into an antialiased mask.

Try changing

Adjust one control at a time, then combine a nonzero Offset with Rotation to observe the chosen transform order. In a fork, move subtraction outside the matrix expression and compare the orbit-like result. Use nonuniform vec2 scale, change the box half-size, or apply the same local coordinates to stripes so the interior pattern follows the object.

Using it in a game

Inverse coordinate transforms are fundamental to procedural decals, UI elements, sprite effects, and ray-based rendering. Game engines usually provide object, world, view, and clip-space matrices rather than these exact controls. Match the effect's coordinate assumptions and matrix convention before porting the technique; row-versus-column multiplication order can differ.

Explore the techniques

Continue with curated explanations and progressively related examples.

LicenseMIT
Views0
Forks0

Discussion

Loading comments...