Screen-space derivatives
dFdx, dFdy and fwidth measure how fast a value changes from one pixel to the next, for edges that stay one pixel soft at any size.
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.
dFdx, dFdy and fwidth
dFdx(v)is roughly the difference invbetween this pixel and its horizontal neighbor in the quad.dFdy(v)is the same, vertically.fwidth(v)isabs(dFdx(v)) + abs(dFdy(v)): how muchvchanges over about one pixel, in any direction.
They work on any float or vector expression the shader computes, not only on inputs. If uv runs from 0 to 1 across a
1000-pixel canvas, fwidth(uv.x) is about 0.001. Zoom in on the surface and it shrinks; tilt the surface away so its
texture is squeezed into fewer pixels and it grows. GLSL ES 3.00 has all three built in (WebGL 1 needed an extension),
and they exist only in the fragment shader.
Edges one pixel wide
SmoothstepA soft threshold. It is 0 below the first edge, 1 above the second, and follows an S-shaped curve with flat ends in between. Smoothstep A soft threshold. It is 0 below the first edge, 1 above the second, and follows an S-shaped curve with flat ends in between.t = clamp((x − e0) / (e1 − e0), 0, 1); s = t² (3 − 2t)smoothstep(0.49, 0.51, d), looks sharp at one
size and blurry or jagged at another, because 0.02 units covers a different number of pixels each time. fwidth
measures one pixel in the units of d, so the edge can be exactly that wide wherever it is drawn:
float d = length(p) - 0.5; // signed distance to a circle of radius 0.5
float w = fwidth(d); // one pixel, in the units of d
float disc = clamp(0.5 - d / w, 0.0, 1.0);
The last line is a linear ramp from 1 to 0 across one pixel centered on the edge, close to how much of the pixel the shape covers. This is analytic anti-aliasing: no extra samples, just one derivative. It works for any field that changes smoothly, which is why signed distance fields and derivatives go together.
Filtering a pattern
A repeating pattern drawn with fract aliases at a distance: once one period gets smaller than a pixel, the pixel
picks an essentially random point in it, and the pattern breaks into shimmer and moiré. The derivative tells you how
many pixels a period covers, so the pattern can fade out before that happens. Here, grid lines that fade out over one
pixel on each side at any distance:
vec2 g = uv * 10.0; // ten cells per unit
vec2 fw = fwidth(g); // cells per pixel, per axis
vec2 dist = abs(fract(g - 0.5) - 0.5) / fw; // distance to the nearest line, in pixels
float line = 1.0 - clamp(min(dist.x, dist.y), 0.0, 1.0);
Once fw passes 0.5, the lines fill the whole cell. A full solution fades line toward the pattern’s average value as
fw grows. Texture filtering does the same thing in hardware: the mip level a texture lookup picks comes from the
derivatives of its coordinates.
A normal for free
Derivatives also give a surface’s orientation. The differences in world position to the right and upward are two vectors along the surface; their cross product is perpendicular to it:
vec3 n = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos)));
The result is constant over each triangle, a flat-shaded look, and needs no normals in the mesh. Flip it if your faces come out dark: the sign depends on the convention for the y axis.
Pitfalls
- Branches. Derivatives compare values across the quad, so all four pixels must compute the value. Inside an
ifwhose condition differs between neighbors, the result is undefined. Compute derivatives, and texture lookups that rely on them, before the branch. - Jumps. A value with a discontinuity, like
fract(x)at its wrap or anatanangle at ±π, has a huge derivative there, and a filter based on it draws a seam. Take the derivative of the smooth value before the jump, as the grid above does withgrather thanfract(g). For a polar anglet, which has no smooth version, take the derivatives of bothtandfract(t + 0.5), keep the pair with the smallerfwidth, and pass it totextureGrad. - Precision. The derivatives are approximate: often one value is shared by the whole quad. They are good for filtering and edge widths, not for exact math.