The near plane, not the far one
Z-fighting gets blamed on the far plane every time. Depth precision is distributed hyperbolically, so moving near from 0.1 to 1 buys more than cutting far by a factor of ten.
Two surfaces at the same depth flicker against each other as the camera moves. The standard advice is to pull the far plane in.
With near: 0.1 and far: 1000, dropping far to 100 gains you almost nothing. Raising near to 1 gains you a factor of ten. The far plane is barely in the equation.
Where the depth buffer spends itself
Ninety percent of the 24-bit range covers the first 1.0 units, which is 0.09% of the view distance.
The depth written to the buffer is a function of the reciprocal of the distance from the camera. The perspective divide happens before the depth test, and 1/z is the quantity that interpolates linearly across a triangle in screen space.
The consequence is on the chart. With near at 0.1 and far at 1000, half the entire depth range is consumed within the first 0.2 units, and ninety percent of it within the first unit. The remaining 999 units of your scene share what's left.
Drag the near slider and watch the curve straighten. Drag far and watch almost nothing happen.
Why near dominates
Depth resolution at a distance is proportional to z²·(1/far − 1/near). The 1/near term is the large one whenever near is small, so it sets the scale of the whole expression.
Going from near = 0.1 to near = 1 changes 1/near from 10 to 1. Going from far = 1000 to far = 100 changes 1/far from 0.001 to 0.01, against a term that is 10. That difference is noise.
Two surfaces, one depth value
At 400 units this buffer can only separate surfaces 0.0954 apart. Yours are 0.0500.
Set distance to 400 and separation to 0.05, and the two surfaces quantize to the same integer across most of the span. Where they tie, the winner is whatever the rasterizer got to last, and it changes as the camera moves. That's the flicker.
Now raise near to 1. The tie disappears without touching the geometry, the far plane, or the buffer format.
Picking a near plane
Set it to the closest distance you will actually let the camera approach something. Not the smallest number that looks safe.
The reflex is to write 0.001 so nothing ever clips, and that reflex costs an order of magnitude of precision across the whole scene to solve a problem you'd notice immediately if it happened. If your camera never gets closer than half a meter to anything, near should be around 0.5.
For scenes that genuinely span a huge range, from a cockpit interior to a horizon, there is no single value that works. The options are:
- Logarithmic depth. three.js exposes
logarithmicDepthBuffer: trueon the renderer. It distributes precision far more evenly, at the cost of a per-fragment write togl_FragDepthin the shader, which disables early depth testing on some hardware. - Reversed Z. Map near to 1 and far to 0, and pair it with a floating point depth buffer. Float precision is densest near zero, which cancels almost exactly against the hyperbolic distribution. This is what most engines do now, though on the web it depends on what your context and extensions give you.
- Split the render into depth ranges. Draw the far range first with its own near and far, clear depth, then draw the near range. Two passes, each with a sane ratio.
When it isn't precision
Not every flicker is a precision problem, and swapping the near plane on a genuinely coplanar surface won't help.
Two faces at mathematically identical depth will fight at any precision, because there is no correct answer for the depth test to find. That's a modeling issue, and the fixes are polygonOffset, an explicit small offset in world space, or not putting two surfaces in the same place.
The way to tell them apart is to move the camera closer. Precision fighting gets better with proximity, because z² shrinks. Coplanar fighting looks exactly the same from every distance.