Introduction

Every shiny floor you've walked across in a video game hides a lie. The reflection you see is not a real image of the scene rendered from the floor's point of view — that would double the cost of every frame. Instead, modern games use Screen-Space Reflections (SSR): a technique that fabricates convincing mirrors from data the GPU already computed while drawing the frame normally.

The core idea is elegant. After the scene is rasterized, the GPU has two buffers it can reuse: the color buffer (what every pixel looks like) and the depth buffer (how far away each pixel's surface is). SSR takes those two snapshots and, for every shiny pixel, fires a virtual ray from the camera's reflection direction — but instead of tracing the ray through geometry, it marches through the 2D screen image, stepping forward and checking the depth buffer at each step until it finds where the ray would intersect a surface.

That intersection point is already colored in the color buffer. SSR just copies that color onto the shiny pixel, and — from the viewer's angle — the floor appears to perfectly mirror what's above it.

The whole trick costs a fraction of a second render pass, which is why SSR became the default reflection method in almost every AAA game between 2013 and the arrival of real-time ray tracing in 2018. Its limits are just as instructive as its strengths: anything off-screen cannot be reflected, and the march fails the moment the reflected ray dips behind a surface. Understanding those limits is understanding how algorithmic shortcuts trade accuracy for speed.

Try It

The canvas below renders a simple scene: colored shapes floating above a reflective floor. The floor uses SSR — for every floor pixel, a ray is marched upward through the color buffer until it hits a shape, and that shape's color is sampled as the reflection.

<!-- {{c_scene_html}} -->
<div class="controls">
  <label>{{lbl_roughness}} <input type="range" id="roughness" min="0" max="10" value="0"></label>
  <label>{{lbl_steps}} <input type="range" id="steps" min="4" max="64" value="32"></label>
  <button id="toggleAnim" type="button">{{btn_animate}}</button>
  <button id="resetBtn" type="button" class="ghost">{{btn_reset}}</button>
</div>
<canvas id="c" width="480" height="360"></canvas>
<div class="status" id="status"></div>
/* {{c_css_root}} */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; background: #111; color: #eee; }
.controls { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; padding: .5rem; background: #1a1a2e; }
label { font-size: .82rem; color: #ccc; display: flex; gap: .35rem; align-items: center; }
input[type=range] { width: 80px; accent-color: #4fc3f7; }
button { font: 600 13px system-ui; padding: .38rem .8rem; border: 1px solid #4fc3f7;
         background: #4fc3f7; color: #000; border-radius: 6px; cursor: pointer; }
button.ghost { background: transparent; color: #4fc3f7; }
canvas { display: block; width: 100%; }
.status { font-size: .8rem; color: #888; padding: .3rem .5rem; min-height: 1.4em; }
// Code not found

Drag the Roughness slider to blur the reflection and simulate a less-than-perfect mirror. Drag Steps to control how many depth-buffer samples the ray takes: too few and the ray misses thin geometry or gets the wrong hit point; more steps sharpen the result at the cost of more work. Hit Animate to move the shapes and watch the reflections track them in real time — the moment a shape moves off the left or right edge of the canvas, its reflection vanishes, revealing SSR's key limitation: it can only reflect what is currently on screen.

The Real Complexity

SSR is not an approximation of ray tracing — it is a fundamentally different algorithm that happens to produce ray-tracing-like results in common cases. The cost and correctness analysis shows why.

Why it is fast. A full ray-traced reflection must intersect the ray with every triangle in the scene — O(n)O(n) in triangle count, or O(logn)O(\log n) with a BVH. SSR instead marches kk steps along a 2D line in screen space, each step being a single texture lookup. For a 1920×10801920 \times 1080 screen, that texture is already in the GPU's L2 cache. The cost is O(k)O(k) per shiny pixel, independent of scene complexity. Typical values are k=32k = 32 to k=128k = 128.

Where it breaks.

  • Off-screen geometry: if the reflected object is outside the camera frustum, its pixels are simply not in the color buffer. The reflection hole is filled with a fallback (sky, a cubemap, or black).
  • Occluded surfaces: the depth buffer stores only the nearest surface per pixel. A ray that should reflect a wall behind a closer wall will instead hit the closer wall — a systematic error that appears as reflection "leaking."
  • Thin geometry: if step size Δs\Delta s is larger than a thin object's screen-space thickness, the march skips over it entirely. Smaller steps fix this but increase cost proportionally.
  • Self-intersection: the ray starts at the shiny surface and may immediately re-intersect it. A small offset along the surface normal is applied at the start, analogous to the "shadow bias" trick in ray tracing.

Binary search refinement. A standard optimization: march coarsely with large Δs\Delta s to find an approximate hit, then bisect the last interval with a binary search. This cuts step count while preserving accuracy near the hit point — the same divide-and-conquer pattern that appears in closest-pair algorithms.

The algorithmic signature of SSR — cheap, approximate, screen-space-limited — is the same signature as SSAO (ambient occlusion), SSGI (global illumination), and contact shadows. All trade completeness for speed by reusing the buffers the rasterizer already produced.

Where It Matters

SSR shipped in enough high-profile games between 2013 and 2019 that it effectively defined what "modern game reflections" looked like for a generation of players:

  • Wet urban environments: rain-soaked streets reflect neon signs and headlights. SSR gives these scenes their cinematic quality at a fraction of the cost of planar reflections.
  • Deferred rendering pipelines: SSR is a natural fit for deferred rendering, where the G-buffer already contains per-pixel normals, depth, and albedo — exactly the inputs SSR needs. The technique arrived just as deferred rendering became standard.
  • Hybrid pipelines: today, ray-traced reflections handle hero surfaces (car hoods, mirrors), while SSR covers everything else. The GPU traces fewer than 1% of reflection rays yet the visible difference is small — a classic algorithmic win.
  • SSAO and SSGI: ambient occlusion and global illumination computed in screen space use the same depth-buffer march. Optimizing SSR's step loop carries over directly to those effects.
  • Water surfaces: ocean and puddle shaders combine SSR for nearby reflections with a cubemap for the horizon, stitching the two seamlessly at a configurable screen-edge distance.

The screen-space pattern is a recurring theme in real-time graphics: defer expensive work until you know exactly which pixels matter, then approximate only those pixels using data you already have. SSR is perhaps the clearest example of that philosophy in action.

Conclusion

Screen-Space Reflections are a masterclass in algorithmic opportunism: the GPU already rendered the scene and filled two buffers, so why render it again? A short march through those buffers, kk texture lookups at a cost far below a second draw call, produces reflections that fooled millions of players for years.

The technique's limits — no off-screen objects, no occluded surfaces, artifacts on thin geometry — are not flaws to fix but design parameters to tune. A roughness blur hides the march artifacts. A cubemap fallback fills the off-screen gaps. Binary search sharpens the hit point without extra global steps. Every limitation has a cheap screen-space patch.

That is the deeper lesson: the best algorithms for constrained environments are not the most correct ones but the ones that fail gracefully within the constraints. SSR fails only where you are not looking. In the world of real-time graphics — and, more broadly, in any system where data reuse matters — that is often enough.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/screen-space-reflections/Content licensed under CC BY-NC 4.0.