GPU Computing · Graphics Pipeline
Deferred Shading and the Tile-Binned Alternative
Why recording surface attributes instead of shaded colour defers the expensive lighting work, and how binning fragments per screen tile reaches the same locality without the bandwidth.
A forward renderer shades a fragment before it knows whether anything in front of it will cover it. The depth test rejects the fragment after the shader has run, so a scene with a depth complexity of four pays for four shades per visible pixel. Deferred shading inverts the order: it stores the surface parameters and lights each visible surface once. The inversion is old — Deering and colleagues described it in 1988 — and its costs have moved over time from arithmetic to memory bandwidth, which is what makes the modern variants differ.
What the geometry pass must produce
The deferred renderer splits rendering into two passes over the same screen. The first, the geometry pass, rasterizes the scene and runs a fragment shader that does as little work as possible: it may sample textures to fetch material parameters, but it performs no lighting. Its output is the surface description for the nearest surface at each pixel, written to a set of render targets collectively called the G-buffer.
The contents of the G-buffer are determined by the lighting model. A typical set holds the view-space normal, the albedo, a material identifier or a roughness and metallic pair, the depth, and often the emissive colour or an ambient occlusion term. Each is a full-screen attachment, and each costs a write in the geometry pass and a read in the lighting pass. With four 32-bit components per attachment, a G-buffer of three attachments costs 48 bytes per pixel, and each of those bytes is written in the geometry pass and read again in the lighting pass. At a resolution of 1920 by 1080 that is about 100 megabytes for one full write of the G-buffer and another 100 for the read, before the depth buffer, which is itself a full attachment.
That number is the design constraint. The arithmetic the deferred renderer saves is the shading of occluded fragments; the bandwidth it adds is a full-screen write and read of the G-buffer, plus the depth buffer traffic that the forward renderer also paid. Whether the trade wins depends on the ratio of the two: a scene with a high depth complexity and a cheap lighting model will lose, because the saved shading was cheap and the added bandwidth is not; a scene with many lights and a moderate depth complexity wins, because the lighting cost grows with the light count and the bandwidth does not.
Lighting becomes a screen-space pass
The second pass decouples the work from the geometry. It draws a full-screen primitive — a triangle or a quad covering the viewport — and the fragment shader reads the G-buffer at its pixel, reconstructs the surface, and evaluates every light that could affect it. The geometry has already been reduced to its visible surface, so each pixel is shaded exactly once. Overdraw in the lighting pass is zero by construction.
The light evaluation loop is where the savings concentrate. A forward renderer must run its light loop once per fragment per light, including fragments that are occluded, and it must do so with all the state of a full material shader. A deferred renderer runs a single lighting shader over the visible surface, and it can restrict work per pixel by the light’s screen-space extent: a light whose bounding volume does not intersect the pixel’s reconstructed position contributes nothing, and the loop can skip it. The classic optimisation draws the light’s bounding sphere as geometry, with the stencil buffer marking affected pixels, so that the lighting shader runs only inside the light’s volume. That variant uses the stencil buffer as a conservative coverage mask, and it reintroduces geometry-dependent shading counts into what was meant to be a screen-space pass. It also interacts with multisampling: a stencil mask maintained per sample lets the light shader run once per covered sample bundle, as multisample coverage and the resolve step describes.
The pass structure has a consequence for materials. A forward renderer chooses a shader per material, and the number of distinct materials is bounded by the number of shader permutations its pipeline can compile. A deferred renderer must express every material within a single lighting equation whose parameters come from the G-buffer, so the material space is fixed by the G-buffer layout. A material whose shading model does not fit — a hair shader, a refractive glass shader, a subsurface scattering model with a diffusion profile — cannot be expressed by parameters and must be handled outside the deferred pass, usually by drawing it forward after the lighting pass has produced its result.
Transparent geometry and the limits of deferral
The G-buffer holds one surface per pixel, so the deferred approach cannot handle geometry that must be blended. Transparency requires the contributions of several surfaces at one pixel, in a fixed order, and the G-buffer’s nearest-surface-only structure has no room for it. The standard arrangement is a hybrid: opaque geometry goes through the deferred pass, transparent geometry is drawn afterwards with forward shading and blending against the lit result. The forward pass pays its own overdraw and its own lighting cost, but the transparent fraction of a scene is usually small.
Antialiasing interacts badly with the G-buffer for the same reason. Multisampled deferred rendering multiplies the G-buffer’s memory by the sample count, because the surface description lives at the same per-sample granularity as the coverage, and the lighting pass must then evaluate per sample. Some implementations avoid the multiplication by shading at a lower sample rate than coverage: the G-buffer is kept per sample for depth and coverage but stored at one surface description per pixel, with a per-sample depth comparison deciding which sample’s surface wins. That keeps the memory growth sublinear in the sample count while accepting a residual error on partially covered pixels, which is the same compromise multisample coverage and the resolve step describes between coverage precision and shading cost. Others use a post-process edge filter instead. The common modern answer is temporal accumulation: shade one sample per pixel per frame and use motion vectors to reproject and accumulate across frames, which pushes the antialiasing problem into the temporal domain and away from the framebuffer’s memory layout.
The G-buffer also stores normals in a compressed form, usually two 16-bit floats or a pair of octahedral-encoded values, because a unit vector has two degrees of freedom and the third component is therefore redundant. The reconstruction of the third component from two requires a sign choice, and the sign is usually derived from the view direction or from the compression scheme’s convention. Getting the convention wrong produces a lighting discontinuity at the silhouette of the visible surface, where the reconstructed normal’s sign flips.
Tiling as an alternative to deferral
A different answer to the same problem is to keep the shader immediate but make its memory traffic local. Tile-based rendering — the predominant design for mobile GPUs, and a design that desktop GPUs partially adopt for their internal caches — splits the screen into a grid of small tiles, typically 16×16 or 32×32 pixels, and processes one tile at a time through the entire pipeline.
The geometry for a frame is first binned: each triangle is assigned to the tiles its projected bounding box intersects, and the assignment is recorded in a per-tile list. The rasterization and fragment shading then traverse one tile’s list at a time. Because the tile’s colour and depth live in an on-chip buffer for the duration of the tile, the depth test, the blending and the framebuffer reads all happen in memory that is orders of magnitude closer than the external frame buffer — an on-chip tile buffer of a few tens of kilobytes against an external memory hierarchy with a latency of hundreds of cycles. The frame’s external traffic becomes one write of the resolved tile rather than a read and write per fragment per blend.
The saved bandwidth is largest exactly where the deferred renderer’s cost is largest: blending, which otherwise reads the destination for every fragment. A single-sampled opaque render’s depth test is a read that the tiler turns into an on-chip access; a blended render’s colour read is the same. The tiler’s cost is the binning pass’s memory for the per-tile triangle lists, which grows with the triangle count and the average number of tiles per triangle, and the loss of flexibility when a shader needs to write to a different pixel than the one it is shading — a case the binning structure cannot support without a flush.
/* The essential loop of a tiler: bin triangles, then shade per tile from
an on-chip colour/depth buffer. Geometry and tile sizes are illustrative. */
struct tile { uint32_t x, y; }; /* 32x32 pixels */
struct bin { const struct tri *tris; size_t count; };
for (uint32_t ty = 0; ty < height / 32; ty++) {
for (uint32_t tx = 0; tx < width / 32; tx++) {
struct tile t = { tx * 32, ty * 32 };
struct bin b = tiles[ty * (width / 32) + tx];
/* Colour and depth for this tile live in on-chip memory and are
never read from or written to the framebuffer mid-tile. */
for (size_t i = 0; i < b.count; i++)
rasterize_and_shade(&b.tris[i], &t, onchip_color, onchip_depth);
resolve_and_store(&t, onchip_color, framebuffer);
}
}
The two designs compose rather than compete. A tiler can run a deferred shading pass per tile, which is what most mobile deferred renderers do: the G-buffer lives on chip, tile by tile, so its bandwidth cost disappears and only the deferred pass’s arithmetic saving remains. The combination keeps the light-loop savings and removes the memory penalty, at the cost of the binning pass and of the pipeline’s increased dependence on the tile’s size matching the render target’s use.
Choosing between them
The decisive question is where the frame’s cost sits. If it sits in shading — many lights, expensive materials, high depth complexity — deferral wins, because it converts the cost from per-fragment-per-light to per-visible-pixel-per-light. If it sits in bandwidth — heavy blending, many attachments, large render targets — tiling wins, because it converts the cost from external memory traffic to on-chip traffic. If both, the two combine, with the caveat that a deferred G-buffer on chip limits its size, so the tile’s material complexity is bounded by the tile buffer’s capacity.
Neither technique removes the fundamental problem, which is that the work per pixel depends on scene content that the pipeline does not know in advance. Deferred shading makes the visible-surface count exact but fixes the material model; tiling makes the memory local but preserves the shading order. Renderers that need both — a scene with many lights and a wide material variety — typically use a hybrid: a deferred pass for the majority material, and forward passes for the exceptions, with a depth prepass establishing visibility for both.
References
- The Triangle Processor and Normal Vector Shader: A VLSI System for High Performance Graphics — Michael Deering, Stephanie Winner, Bic Schediwy, Chris Duffy, Neil Hunt, SIGGRAPH 1988
- A Sorting Classification of Parallel Rendering — Steven Molnar, Michael Cox, David Ellsworth, IEEE CG&A 1994
- Vulkan Specification — Khronos Group
- The OpenGL Graphics System: A Specification, Version 4.6 (Core Profile) — Khronos Group