Per-sample operations
The fixed tests and merges between the fragment shader's output and the pixel that actually changes.
How it works
When the 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.
None of them is written in GLSL. The host code switches and configures them (gl.enable, gl.depthFunc,
gl.blendFunc and friends) before the draw call, and they apply to everything that draw call produces.
The order
The OpenGL ES 3.0 specification lists them in this order (section 4.1):
- Pixel ownership: the pixel belongs to this context (a covered window, for example, may not).
- Scissor test: drop fragments outside a rectangle, set with
gl.scissor(). Cheap clipping for UI panels and split screens. - Multisample operations: with MSAA, decide which samples of the pixel the fragment covers. With
gl.SAMPLE_ALPHA_TO_COVERAGEenabled, the output alpha also removes samples, turning alpha into soft-edged coverage. - Stencil test: compare the pixel’s value in the stencil buffer, a small integer per sample, with a reference, and update it. It is a mask the scene draws for itself: portals, mirrors, outlines drawn only outside an object.
- Depth test: compare the fragment’s depth with the stored one and keep only the nearer (see the
depth bufferAn image the size of the framebuffer that holds, for each pixel, how far away the nearest surface drawn so far is. The depth test uses it to hide what is behind.Rendering
Depth buffer
An image the size of the framebuffer that holds, for each pixel, how far away the nearest surface drawn so far is. The depth test uses it to hide what is behind.
Sources). - Blending: combine the fragment’s color with the color already stored, instead of replacing it (see
blendingCombining the color a fragment shader outputs with the color already in the framebuffer, instead of replacing it. Transparency, additive glow and multiply all come from it.Rendering
Blending
Combining the color a fragment shader outputs with the color already in the framebuffer, instead of replacing it. Transparency, additive glow and multiply all come from it.
result = src × srcFactor + dst × dstFactor). - sRGB conversion and dithering: encode the color for an sRGB target if there is one, and optionally dither it down to the target’s bit depth.
Then the result is written, through the write masks: gl.colorMask() can block single channels, and
gl.depthMask(false) keeps the depth test running while leaving the stored depth untouched. A fragment that fails
any test, or that the shader discarded, writes nothing.
Setting them up
A typical frame turns these on and off between draw calls. Opaque geometry first, depth tested and written; then transparent geometry, depth tested but not written, blended over it:
// opaque pass
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.depthMask(true);
gl.disable(gl.BLEND);
drawOpaque();
// transparent pass: hidden by walls in front, but doesn't hide what's behind it
gl.depthMask(false);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
drawTransparent();
Per sample, and early
With MSAA, each pixel holds several samples, and the fragment shader still runs once per pixel for each triangle. The tests and the write happen per sample, using the coverage the rasterizer found, so a triangle edge cuts a pixel partly; at the end the samples are averaged (resolved) into one color. That is how MSAA smooths edges without shading every sample.
The specification describes the depth and stencil tests as running after the shader, but running a shader whose
result will be thrown away wastes time, so GPUs test early whenever the result can’t differ. Two things in the
shader prevent that: writing gl_FragDepth, because the depth isn’t known until the shader finishes, and discard
on many GPUs, because the fragment may vanish after the test. Keep both out of shaders that cover a lot of the
screen unless you need them.
In Godot and on this site
Godot sets these from the material and the shader’s render_mode instead of host code: blend_mix, blend_add,
blend_sub, blend_mul and blend_premul_alpha for blending; depth_draw_opaque, depth_draw_always and
depth_draw_never for the depth write; depth_test_disabled to skip the test; alpha_to_coverage for MSAA
coverage. Writing ALPHA moves the material to the transparent pipeline, drawn after the opaque one.
On this site, a pass’s blend and alphaClip in render.json pick a combination: "opaque" is tested and
written, "alpha" and "additive" are tested and blended but leave the depth alone, and alphaClip discards the
fragments under a threshold.