Command Palette

Search for a command to run...

Hectal
Course outline
Topic 7.2Intermediate ~21 min · step 2 of 6

Near, Far and the Depth Buffer

In one line

The GPU decides what is in front with a depth buffer: one number per pixel. Those numbers are bunched up near the camera, so a tiny near and a huge far make surfaces flicker (z-fighting), and a wrong near or far makes objects vanish.

0/6 · 0%

Think of it like this

A clerk keeps a board with one ticket number per pixel. A new ticket may overwrite a number only if it is smaller, that is, closer. The board is the depth buffer. The clerk never needs the tickets in order, which is how the GPU can draw triangles in any order.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

depth buffer
A grid with one number per pixel: the distance of the nearest surface drawn there so far.
depth test
The check that draws a fragment only if its depth is smaller than the stored one. material.depthTest turns it on or off.
near and far planes
The two flat ends of the frustum. Nothing closer than near or farther than far is drawn.
z-fighting
Flicker or stripes where two surfaces have (almost) the same depth and the buffer cannot tell which is in front.
frustum culling
Skipping an object's draw call when its bounding sphere is completely outside the frustum.
logarithmic depth buffer
A renderer option that stores the logarithm of the distance, so precision does not collapse far away.

Step by step

01See the depth buffer

In the widget, switch on "show stored depth": the picture shows what the buffer holds. Move gap down until the two panels merge into noise, and compare the number the widget shows with the formula.

02Work out the smallest gap

Run the first example. It prints the smallest gap at 10 m for four near / far pairs, and checks it the long way: it builds a projection matrix, rounds the depth of two surfaces 2 mm apart to 24 bits and compares them.

03Make the failure visible

Set NEAR to 0.0001 and FAR to 1000000000. In the checker's software GPU the orange panel vanishes behind the blue one. Then set LOG_DEPTH to true: the two panels are told apart again.

04Make an object vanish

Run the second example. Each case changes one thing, draws once and prints what the renderer sent to the GPU. Some cases look the same on screen (empty) but print different numbers: that is the clue that tells them apart.

Try it yourself

  1. 1

    Push near too far down

    In the first example set NEAR to 0.0001 and FAR to 1000000000. The log row for these values says 59.6 mm, and the canvas shows only the blue panel: the later-drawn blue one passes the depth test everywhere the panels are closer than that. On a real GPU you may see stripes instead. Then set LOG_DEPTH to true: orange and blue split again, with a small notch in the middle where the panels are 0 mm apart.

  2. 2

    A moderate near

    Set NEAR to 0.01 and FAR to 1000. The table says 0.596 mm, more than the 0.3 mm the panels are apart at their edges, yet the picture stays clean in our test browser, whose depth buffer holds more than 24 bits. The table is the worst case to plan for, not a promise about your GPU.

Code & diagrams

Try it: interactive

The depth buffer, seen

Move near, far and the gap between the panels. Turn on the stored depth, then the logarithmic buffer. At what gap does the fighting start?

The smallest gap a depth buffer can tell apart New tab

Two panels cross at 10 m. The log shows idealised 24-bit numbers; a real GPU may keep more bits, so the picture can look better than the table says. The last part of each row comes from rounding two depths to 24 bits.

idle

Console

Run the scene to see its console.

Expected console output

logarithmic depth buffer: false
near 0.1, far 100: smallest gap at 10 m = 0.0595 mm; surfaces 2 mm apart are told apart
near 1, far 100: smallest gap at 10 m = 0.00590 mm; surfaces 2 mm apart are told apart
near 0.01, far 1000: smallest gap at 10 m = 0.596 mm; surfaces 2 mm apart are told apart
near 0.0001, far 1000000000: smallest gap at 10 m = 59.6 mm; surfaces 2 mm apart FIGHT (same stored depth)
Six ways to see nothing New tab

One renderer draws the same box six times with one thing changed each time. "draw calls: 1" with an empty screen means the box was sent but nothing of it could be seen.

idle

Console

Run the scene to see its console.

Expected console output

fine: camera 3 m away        draw calls: 1, triangles: 12
near = 5 (box is closer)     draw calls: 0, triangles: 0
far = 2 (box is farther)     draw calls: 0, triangles: 0
camera inside the box        draw calls: 1, triangles: 12
box behind the camera        draw calls: 0, triangles: 0
box scale 0                  draw calls: 1, triangles: 12

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Switch frustum culling off

In the second example add box.frustumCulled = false; before scene.add(box);.

terminal
$ Run
── what you'll see ──
fine: camera 3 m away draw calls: 1, triangles: 12
near = 5 (box is closer) draw calls: 1, triangles: 12
far = 2 (box is farther) draw calls: 1, triangles: 12
camera inside the box draw calls: 1, triangles: 12
box behind the camera draw calls: 1, triangles: 12
box scale 0 draw calls: 1, triangles: 12

Myth vs fact

Myth

Setting far to a huge number is harmless, so set it to 1e9 to be safe.

Fact

A large far costs almost nothing. A tiny near is what hurts: precision is about 1 / near. A near of 0.1 with a far of 1e9 is fine; a near of 0.0001 is not.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The numbers above assume an idealised 24-bit fixed-point buffer. Some GPUs offer a 32-bit float depth, so the real gap can be smaller than the formula. Plan with the formula, then test on a phone.

  • ▸

    In r186 WebGLRenderer also takes reversedDepthBuffer: true. It needs the EXT_clip_control extension, so check support. It flips the range so that floating-point precision, which is densest near 0, lands on the far distances, avoiding both z-fighting and the speed cost of log depth. It is not tested on this page.

Remember this

  1. 1

    The problem this solves: two triangles can cover the same pixel and the GPU must pick the nearer one, millions of times per frame, without sorting. The depth buffer stores the nearest distance drawn so far at each pixel, and a new fragment is drawn only if it beats it (depthTest, on by default). The buffer has limited precision, usually 24 bits, and that is where the trouble starts.

  2. 2

    Near and far clip the world. Things closer than near or farther than far are not drawn. If an object's bounding sphere is completely outside, three.js skips its draw call (frustum culling, shown in the second example), and nothing is logged. These two numbers are the first suspects for an object that is "just not there".

  3. 3

    Depth is not stored in metres. The matrix of Topic 7.1 turns the distance d into a value from 0 to 1 that grows quickly at first and then flattens. With near 0.1 and far 100, the value at 0.3 m is already 0.667, at 1 m it is 0.901 and at 10 m it is 0.991. Most of the 16.7 million steps of a 24-bit buffer are spent on the first metre, and few are left for far things.

  4. 4

    How small a gap can it see? At distance d, two surfaces closer together than d² · (far − near) / (near · far · 2²⁴) can get the same stored value. At 10 m that is 0.0595 mm for near 0.1 and far 100, and 0.596 mm for 0.01 and 1000. With a large far the formula is almost d² / (near · 2²⁴): near matters, far hardly does. Making near ten times smaller makes the precision ten times worse.

  5. 5

    Z-fighting. When two surfaces get the same stored depth, rounding decides per pixel which one wins, so you see stripes, noise or a flicker that changes as the camera moves. Typical causes are two surfaces at the same place (a decal on a wall) and a near that is too small. The cures: move the surfaces apart, raise near, lower far, or use polygonOffset on the one that must win.

  6. 6

    Logarithmic depth. new WebGLRenderer({ logarithmicDepthBuffer: true }) stores the distance's logarithm, spreading precision evenly and curing far-away fighting. The price: each fragment shader writes gl_FragDepth, which on many GPUs disables early depth testing. Use it for huge scenes (a city, planets) when near can't go up.

Explain it without notes

01

Why does lowering near from 0.1 to 0.01 hurt depth precision, while raising far from 100 to 1000 hardly does?

02

A decal (a logo) lies flat on the bike's frame and flickers when the camera moves. List three ways to fix it.

Practice

01

Write smallestGap(near, far, d) for a 24-bit buffer and print it in millimetres for near 0.5 and far 200, at 5 m and 50 m.

02

A scene shows a bike 2 m away with near 3. Say what the screen shows and give the smallest change that fixes it.

Done when you can

  • Say what the depth buffer stores and why it is not linear

  • Predict z-fighting from near, far and the distance with the formula

  • Use renderer.info to tell a culled object from a drawn but invisible one

  • Choose a fix: move apart, raise near, polygonOffset, or logarithmic depth