Polar coordinates
Describe a point by its angle and its distance from a center, for anything radial.
How it works
UVA 2D position on a surface or the screen, usually from 0 to 1 on both axes. Shaders draw shapes and patterns as functions of it. UV coordinates A 2D position on a surface or the screen, usually from 0 to 1 on both axes. Shaders draw shapes and patterns as functions of it.uv = gl_FragCoord.xy / resolution
From x, y to angle and radius
Start from a centered, aspect-corrected coordinate 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.p, so the center is (0, 0). The radius is the
vectorA 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.atan with two arguments:
float r = length(p); // 0 at the center
float a = atan(p.y, p.x); // -PI..PI, 0 pointing right (+x), counterclockwise
The two-argument atan(y, x) looks at the signs of both components, so it knows which quarter of the circle the point
is in and returns the full range from -π to π. The one-argument atan(p.y / p.x) only sees the ratio, can’t tell
opposite directions apart, returns -π/2 to π/2, and divides by zero on the vertical axis. HLSL calls the two-argument
form atan2, with the same order: y first. At the exact center, where both are zero, the GLSL result is undefined; it
rarely matters, because the radius there is zero anyway.
An angle in radians is hard to feed into other functions, so remap it to 0..1:
const float PI = 3.14159265;
float t = a / (2.0 * PI) + 0.5; // 0..1 once around, starting on the -x axis
Shapes from angle and radius
In polar form, a circle is “radius below a constant”. Let the limit depend on the angle and the circle grows petals:
#version 300 es
precision highp float;
uniform vec2 uResolution;
out vec4 fragColor;
void main() {
vec2 p = (2.0 * gl_FragCoord.xy - uResolution) / uResolution.y;
float r = length(p);
float a = atan(p.y, p.x);
float edge = 0.55 + 0.2 * cos(5.0 * a); // five petals
float flower = 1.0 - smoothstep(edge - 0.01, edge + 0.01, r);
fragColor = vec4(vec3(flower), 1.0);
}
The whole number in cos(5.0 * a) sets the petal count. It has to be whole: the angle jumps from π to -π on the left
side, and only a whole number of repeats lines up across that jump. A fractional count leaves a visible seam there.
Back to x and y
Converting back is r * vec2(cos(a), sin(a)). The useful pattern is: convert, change the angle or the radius, convert
back, and use the result as a new UV. Adding an amount to the angle that grows toward the center twists the image into
a swirl:
float twist = 2.0 * (1.0 - smoothstep(0.0, 1.0, r)); // strongest at the center
vec2 q = r * vec2(cos(a + twist), sin(a + twist)); // sample or draw with q instead of p
The seam
The angle is discontinuous on the -x axis, where it jumps from π to -π (or t from 1 to 0). Anything that depends on
the angle directly, like a texture lookup with t as a coordinate, can show a line there. Repeating patterns with a
whole number of repeats hide it, as above. Texture lookups along the seam need more care, because the GPU estimates how
far the coordinate moves between neighboring pixels, and the jump makes that estimate huge; the screen-space
derivatives fundamental explains why and how to work around it.