Skip to content

Depth and z-sort

Mesh and SDF shapes share a single depth texture in a single render pass. That is what makes them occlude each other correctly without an inter-pass copy.

DrawPipelineDepth writeDepth compare
Fast SDFssdfFastPipelineyesless
Meshmesh.pipeline, or glassPipeline for a glass meshyesless
Glass SDFssdfRefractPipelineyesless
The <Fast> halosdfFastEffectPipelinenoless
Effect SDFssdfPipelinenoalways

Fast SDFs → mesh → glass SDFs → the <Fast> halo → effect SDFs, then the rig gizmo.

  1. Fast SDFs write depth. Dense clusters self-cull their own overdraw, and the depth they leave is what the mesh tests against.
  2. Mesh writes and tests. It is occluded by fast SDFs in front of it and occludes those behind it.
  3. Glass SDFs write and test like fast shapes. The image they refract is the background copy and the shape pre-pass, both made before pass 2.
  4. The <Fast> halo tests against everything above and writes nothing. It comes after the glass so an outline in front of a glass shape paints over it; a band behind the glass fails against the glass depth and reaches the screen only through refraction, when enableRefraction is on.
  5. Effect SDFs draw last with no depth interaction. Their halo, glow and outline composite over everything before them.

An effect SDF’s surface also always draws on top of the mesh, whatever its world position. A shape behind the mesh still appears in front of it.

This is why <Fast> shapes clip into a mesh and <Effect> shapes do not.

Fixing it needs the effect pipeline split into two: surfaces that write depth, halos that do not. That is designed and not built.

Effect shapes and the <Fast> halo do get a depth elsewhere, in the shape pre-pass: sdfDepthOnlyPipeline draws the effect shapes, halo bands included, and sdfFastEffectDepthPipeline draws the <Fast> halo at its shape’s closest approach. Refraction uses that depth to reject what sits in front of the glass, reflection to reject what sits behind it. The pass writes to its own depth target and never reaches the main one, so it changes nothing about the order above.

A fragment shader has no per-fragment control over depth writing. The effect shader emits three kinds of pixel:

  • Surface — the actual SDF surface. Should write depth.
  • Halo and glow — colour beyond the silhouette. Must not write depth.
  • Outline — near the surface, not on it. Must not write depth.

Turning on depthWriteEnabled writes depth for all three. The result:

  • Halos erase geometry behind them. The halo claims depth at the surface position, so anything drawn later at greater depth fails its test across the whole halo region.
  • Outlines go grainy. Sub-pixel raymarch variance gives neighbouring halo pixels slightly different depths, which then z-fight.

Both were observed. Do not enable depth writing on the effect pipeline without splitting the pass.