UV & aspect ratio
Turn a pixel's position into 0–1 coordinates, center them, and keep circles round.
How it works
A fragment shaderThe GPU program that runs once for every pixel a triangle covers and decides its color. Fragment shader The GPU program that runs once for every pixel a triangle covers and decides its color.gl_FragCoord, the pixel’s position in the window, in pixels:
gl_FragCoord.xy is (0.5, 0.5) at the center of the bottom-left pixel and grows toward the top right. Pixels are an
awkward unit, though: a shader written in them looks different on every screen size.
Dividing by the size of the canvas fixes that. The result is the UV coordinate: 0 at the left and bottom edges, 1 at the right and top, whatever the resolution.
// at file scope:
uniform vec2 uResolution; // the canvas size in pixels, set by the host code
// inside main():
vec2 uv = gl_FragCoord.xy / uResolution; // 0..1 on both axes
On a mesh, UVs come from the model instead: each vertex stores a 2D coordinate saying where it sits on a flat texture,
and the rasterizer interpolates it across the triangle. Either way, the fragment shader sees a vec2 that runs from 0
to 1 across the surface. The cards on this site hand it to you as i.uv, with the origin at the bottom left.
See it as color
The quickest way to understand a coordinate is to output it. Writing uv into the red and green channels paints
black at the bottom left, red at the bottom right, green at the top left and yellow at the top right:
#version 300 es
precision highp float;
uniform vec2 uResolution;
out vec4 fragColor;
void main() {
vec2 uv = gl_FragCoord.xy / uResolution;
fragColor = vec4(uv, 0.0, 1.0);
}
This is also your first debugging tool: any value between 0 and 1 can be checked by writing it to a color channel.
Centering
Radial shapes are easier to write around the middle of the screen. Doubling and shifting moves the origin to the center and the range to -1..1:
vec2 p = uv * 2.0 - 1.0; // -1..1, with (0, 0) in the middle
Now Vector A group of two to four numbers handled as one value (vec2, vec3, vec4). The same type holds a position, a direction, a color or a UV.length(p) is the distance from the center, the start of every circle and ring (see
vectorsA group of two to four numbers handled as one value (vec2, vec3, vec4). The same type holds a position, a direction, a color or a UV.
Aspect ratio
UVs stretch with the canvas. On a 1600×900 canvas, 0.1 in x covers 160 pixels but 0.1 in y only 90, so a circle drawn
as length(p) < 0.5 comes out as an ellipse. Multiplying x by the aspect ratio (width divided by height) makes one
unit the same number of pixels on both axes:
p.x *= uResolution.x / uResolution.y;
A common shorthand centers and corrects in one line, dividing by the height only:
vec2 p = (2.0 * gl_FragCoord.xy - uResolution) / uResolution.y;
Here y runs from -1 to 1 and x runs a little further on a wide screen. Pick one convention per shader and keep it: every number in the shapes that follow depends on it.