Skip to content

Add depth prepass to ThreeJS #32195

@appdoo

Description

@appdoo

Description

Use Case
I'm rendering complex scenes with massive object counts using optimized instanced rendering. My rendering pipeline includes:

Spatial partitioning: AABB bounding boxes for hierarchical scene organisation
Optimized frustum culling: O(1) near/far corner detection using signbit-based plane testing https://www.cse.chalmers.se/~uffe/vfc_bbox.pdf
Front-to-back sorting: AABBs sorted by camera distance for optimal draw order
Instanced rendering: Each visible AABB triggers a glDrawElementsInstanced call rendering hundreds of objects with transformation matrices

While threeJS offers a simple and easy to understand interface, simple things like rendering THREE.InstancedMesh geometry to depth buffer with a custom shader seem hard to do.

But maybe I am just doing something wrong.

Thank you in advance,
Andor

Solution

As an 3D/OpenGL experienced but ThreeJS newbie I though something like this should work:

  • Setting a shader/material
  • Setting a render Target (Depth Buffer)
  • Render Indexed geometry with the global shader to render target.

// Sort AABBs front-to-back by distance - only once for all passes
this.engine.sortNodesFrontToBack(this.camera.position);
this.engine.applyFrustumCulling(this.camera);

// Z-PrePass (depth-only, front-to-back)
if (this.passManager.isEnabled('zPrePass')) {
this.passManager.startPass('zPrePass');

// Override all materials with depth-only material - should set vertex and fragment shaders
this.scene.overrideMaterial = this.depthOnlyMaterial;

// Configure depth-only rendering state
gl.colorMask(false, false, false, false);  // Disable color writes
gl.enable(gl.DEPTH_TEST);                    // Enable depth testing
gl.depthFunc(gl.LEQUAL);                      // Standard depth comparison (or gl.LEQUAL)
gl.depthMask(true);                                 // Enable depth buffer writes

this.renderer.setRenderTraget(DepthBuffer);

// Traverse sorted AABBs and draw instanced meshes front to back, if visible
this.engine.nodes.forEach(aabb => {
    const indexedMesh = this.engine.getMeshForNode(aabb); 
    if (indexedMesh && indexedMesh.visible) {
        // Single glDrawElementsInstanced call for all meshes in this node
        this.renderer.render(indexedMesh, this.camera);
        
        // low level but not available method would be
        // this.renderer.drawIndexedMesh(indexedMesh);
    }
});

// Restore rendering state
gl.colorMask(true, true, true, true);       // Re-enable color writes
gl.depthMask(false);                               // Disable depth writes

// Restore normal materials
this.scene.overrideMaterial = null;
this.passManager.endPass('zPrePass');

}

// Some other passes like SSAO
if (this.passManager.isEnabled('ssao')) {
....
}

// Simple Shading pass would be e.g.
gl.depthFunc(gl.EQUAL);
this.renderer.setRenderTraget(FrameBuffer);

// Traverse sorted AABBs and draw instanced meshes front to back, if visible
this.engine.nodes.forEach(aabb => {
const indexedMesh = this.engine.getMeshForNode(aabb);
if (indexedMesh && indexedMesh.visible) {
// This one works already
this.renderer.render(indexedMesh, this.camera);
}
});

Alternatives

I followed several issues like the following:
#8676
#20673

And especially:
Implementing Depth Pre-Pass Optimization for Three.JS
https://cprimozic.net/blog/threejs-depth-pre-pass-optimization/

I also tried extending the renderer which basically works, but I stumbled across some RenderState errors (which could be fixed somehow of course).

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions