Real-Time Graphics
How a triangle becomes a pixel, what a shader is actually doing, and the lighting model behind every modern renderer.
Sixteen Milliseconds
The GPU is the reason it fits. It is not a fast CPU — it is thousands of narrow cores running the same instruction over different data. That shape decides everything: work that can be expressed as the same small program applied to millions of vertices or pixels is nearly free, and work that branches unpredictably is not.
The Rasterisation Pipeline
Every frame runs the same fixed sequence. Two stages are yours to program; the rest is hardware doing exactly one job very quickly.
| Stage | Does |
|---|---|
| Vertex shader (yours) | Transforms each vertex into clip space |
| Clipping & culling | Throws away what is off-screen or facing away |
| Rasterisation | Works out which pixels a triangle covers |
| Fragment shader (yours) | Computes a colour for each covered pixel |
| Depth & blend | Decides what is in front, mixes transparency |
The depth buffer is what makes drawing in arbitrary order work: each pixel remembers the nearest distance written so far, and a fragment further away is discarded. Transparent surfaces break this — they must not write depth, and they have to be drawn back to front, which is why transparency is a recurring source of sorting bugs.
Shaders
A shader is a small program compiled for the GPU and run in parallel over enormous numbers of items — once per vertex, then once per covered pixel. A fragment shader on a 1080p screen runs about two million times per frame per full-screen layer, so a single expensive line inside it is a measurable cost.
// Fragment shader: sample a texture, tint it, apply a simple light
in vec2 uv;
in vec3 normal;
out vec4 fragColor;
uniform sampler2D albedo;
uniform vec3 lightDir;
void main() {
vec3 base = texture(albedo, uv).rgb;
float ndl = max(dot(normalize(normal), -lightDir), 0.0);
fragColor = vec4(base * (0.2 + 0.8 * ndl), 1.0);
}
Note what the lighting term is: a dot product between the surface normal and the light direction, clamped at zero. Surfaces facing the light get full brightness, surfaces at a glancing angle get less, surfaces facing away get none. That single line is the foundation the rest of the lighting model is built on.
if and the rest take the other, the hardware runs both sides and masks the results — you pay for both paths.Lighting & Materials
Modern engines use physically based rendering: describe a material by properties that mean something physically — base colour, metalness, roughness, normal detail — and let one shading model handle every light. The payoff is that an asset looks correct in a bright exterior and a dim interior without an artist re-tuning it.
| Input | Controls |
|---|---|
| Albedo | Base colour, with no lighting baked in |
| Metalness | Whether reflections take the surface colour or the light's |
| Roughness | How tight or spread out the highlight is |
| Normal map | Fake surface detail without extra geometry |
Shadows are the expensive part. The standard technique renders the scene from the light's point of view into a depth map, then compares each pixel's distance against it. It is a sampling problem, so it comes with a permanent set of artefacts: hard edges, acne on curved surfaces, and shimmer as the camera moves.
Global illumination — light bouncing off surfaces onto others — is what makes a scene look real, and it is far too expensive to compute honestly per frame. Engines bake it into lightmaps ahead of time for static geometry, approximate it with probes for moving objects, or trace a limited number of rays on hardware that supports it.
Interview Questions
Which pipeline stages do you write?
The vertex shader and the fragment shader. Clipping, rasterisation, the depth test and blending are fixed-function hardware.
What does the depth buffer solve?
It lets geometry be drawn in any order — each pixel keeps the nearest depth written so far and discards fragments behind it — so the CPU does not have to sort every triangle.
Why is transparency awkward?
Transparent surfaces must blend with what is behind them, so they cannot rely on the depth test alone. They are drawn after opaque geometry, back to front, without writing depth.
Draw calls or triangle count — which usually bottlenecks?
Draw calls. Each one is CPU-side state setup, and thousands of small ones leave the GPU idle. Batching objects that share a material is the standard fix.
Why are branches costly in a shader?
Cores execute in lockstep groups. If a group diverges at an if, both sides are executed and the unwanted results masked out, so you pay for both.
What does PBR actually buy you?
Materials described in physical terms — albedo, metalness, roughness — respond correctly to any lighting, so an asset holds up across scenes without per-level tweaking.