Transforming Shader Space
by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026
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.
Shader inputs
void mainImage(out vec4 fragColor, in vec2 fragCoord)
Called once per pixel. Write the colour to fragColor.
-
iResolutionvec3 - Viewport size in pixels (z is the pixel aspect ratio).
-
iTimefloat - Seconds since the shader started.
-
iTimeDeltafloat - Seconds since the previous frame.
-
iFrameRatefloat - Frames per second, smoothed.
-
iFrameint - Frames rendered since the start.
-
iMousevec4 - Mouse position: xy while held, zw of the last click.
-
iDatevec4 - Year, month, day, and seconds within the day.
-
iChannel0sampler2D - Texture bound to channel 0.
-
iChannel1sampler2D - Texture bound to channel 1.
-
iChannel2sampler2D - Texture bound to channel 2.
-
iChannel3sampler2D - Texture bound to channel 3.
-
iChannelResolutionvec3[4] - Pixel size of each bound channel texture.
-
iChannelTimefloat[4] - Playback time of each channel, in seconds.
-
iSampleRatefloat - Audio sample rate, always 44100.
Main Image6
uniform float uRotation; // @param -3.14..3.14 = 0.55 "Rotation"
uniform float uScale; // @param 0.5..2.5 = 1.2 "Scale"
uniform vec2 uOffset; // @param -0.5..0.5 = 0.0, 0.0 "Translation"
mat2 rotationMatrix(float angle) {
float c = cos(angle);
float s = sin(angle);
return mat2(c, -s, s, c);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
p = rotationMatrix(-uRotation) * (p - uOffset) / uScale;
vec2 boxDistance = abs(p) - vec2(0.24, 0.14);
float signedBox = length(max(boxDistance, 0.0)) + min(max(boxDistance.x, boxDistance.y), 0.0);
float shape = 1.0 - smoothstep(0.0, 0.008, signedBox);
vec3 color = mix(vec3(0.04, 0.05, 0.09), vec3(0.30, 0.82, 0.94), shape);
fragColor = vec4(color, 1.0);
}
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.
Discussion