Course outline
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.
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.depthTestturns it on or off. - near and far planes
- The two flat ends of the frustum. Nothing closer than
nearor farther thanfaris 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
Push near too far down
In the first example set
NEARto0.0001andFARto1000000000. 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 setLOG_DEPTHtotrue: orange and blue split again, with a small notch in the middle where the panels are 0 mm apart. - 2
A moderate near
Set
NEARto0.01andFARto1000. 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?
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.
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)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.
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: 12Break 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);.
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
WebGLRendereralso takesreversedDepthBuffer: true. It needs theEXT_clip_controlextension, 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
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
Near and far clip the world. Things closer than
nearor farther thanfarare 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
Depth is not stored in metres. The matrix of Topic 7.1 turns the distance
dinto a value from 0 to 1 that grows quickly at first and then flattens. Withnear0.1 andfar100, 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
How small a gap can it see? At distance
d, two surfaces closer together thand² · (far − near) / (near · far · 2²⁴)can get the same stored value. At 10 m that is 0.0595 mm fornear0.1 andfar100, and 0.596 mm for 0.01 and 1000. With a largefarthe formula is almostd² / (near · 2²⁴):nearmatters,farhardly does. Makingnearten times smaller makes the precision ten times worse. - 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
nearthat is too small. The cures: move the surfaces apart, raisenear, lowerfar, or usepolygonOffseton the one that must win. - 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 writesgl_FragDepth, which on many GPUs disables early depth testing. Use it for huge scenes (a city, planets) whennearcan't go up.
Explain it without notes
Why does lowering near from 0.1 to 0.01 hurt depth precision, while raising far from 100 to 1000 hardly does?
A decal (a logo) lies flat on the bike's frame and flickers when the camera moves. List three ways to fix it.
Practice
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.
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,farand the distance with the formulaUse
renderer.infoto tell a culled object from a drawn but invisible oneChoose a fix: move apart, raise
near,polygonOffset, or logarithmic depth