Game Development · Guide

Real-Time Graphics

How a triangle becomes a pixel, what a shader is actually doing, and the lighting model behind every modern renderer.

— min read Game Development

Sixteen Milliseconds

At 60 frames per second you have about 16 ms to turn the whole world into pixels. Offline renderers spend hours per frame. Everything in real-time graphics is an approximation chosen to fit that budget.

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.

StageDoes
Vertex shader (yours)Transforms each vertex into clip space
Clipping & cullingThrows away what is off-screen or facing away
RasterisationWorks out which pixels a triangle covers
Fragment shader (yours)Computes a colour for each covered pixel
Depth & blendDecides 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.

The usual bottleneck is not triangle count but draw calls. Each one is CPU work to set up state, and thousands of them starve the GPU. Batching objects that share a material into a single call is the first optimisation on every list.

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.

Branches are expensive because cores run in lockstep groups. If some pixels in a group take one side of an 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.

InputControls
AlbedoBase colour, with no lighting baked in
MetalnessWhether reflections take the surface colour or the light's
RoughnessHow tight or spread out the highlight is
Normal mapFake 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.

Quick Quiz

1. The fragment shader runs once per…
2. The depth buffer lets you…
3. Thousands of small draw calls mostly cost…
4. Roughness in a PBR material controls…
5. Shadow maps work by rendering depth from…