Does anyone know how the "depth pre-pass" option of babylonjs is implemented?

It can make incorrect translucent renderings correct, and I want to learn this method.
for example,



I checked some of its source code, and I only know that it renders the mesh twice when “depth pre-pass” enabled, and only the depth is rendered the first time. Here is the relevant part of the source code I found:

        for (subIndex = 0; subIndex < sortedArray.length; subIndex++) {
            subMesh = sortedArray[subIndex];

            if (transparent) {
                let material = subMesh.getMaterial();

                if (material && material.needDepthPrePass) {
                    let engine = material.getScene().getEngine();
                    // Turn off color writing
                    engine.setColorWrite(false);
                    // Turn off Blend
                    engine.setAlphaMode(Constants.ALPHA_DISABLE);
                    // Render the mesh with  Blend off
                    subMesh.render(false);
                    // Turn on color writing
                    engine.setColorWrite(true);
                }
            }
			// Turn on the Blend rendering mesh again
            subMesh.render(transparent);
        }

I noticed that there are discussions about oit in the community, some have implemented depth peel, and some have implemented weighted average. But both of these have certain problems. The former consumes a lot of performance, while the latter is incorrectly mixed when the depth changes,such as I move the camera.

I implemented the weighted average method.And I don’t know if this is correct.hahah… :joy:




As you can see, the effect of near and far is like this.

My work is based on:https://github.com/mrdoob/three.js/issues/4814

Thank you @arose for the inspiration!

And the root cause should be here: http://casual-effects.blogspot.com/2014/03/weighted-blended-order-independent.html

The closest thing to babylonjs’ material.needDepthPrePass, in three.js, would be material.depthWrite. If you haven’t yet, I would try disabling that on transparent materials, before getting into anything more difficult like OIT.

You may also want to look into alpha hash/dither as an option.

I also followed the discussion about depthWrite, but this did not solve the problem. Babylonjs’s depth pre-pass and depthWrite are two things.

2 Likes

For transparent objects you can add two versions of the transparent object to the scene with the same transform (or make a multi material mesh) with one material that writes only to depth first and one that draws the blended transparent object. Here’s a js fiddle showing how:

With multiple overlapping transparent objects there are couple ways to do it. You could draw all transparent depth prepass objects first and then draw the blended materials afterward which will result in no transparent overlap effects but therefore also no “pop” effect as the transparent objects reorder when you move the camera. Here’s how the render order is set for that:

dpMesh1.renderOrder = 1;
mesh1.renderOrder = 2;
    
dpMesh2.renderOrder = 1;
mesh2.renderOrder = 2;

image

Or you could draw the depth prepass material just before drawing the blended material for a single object which would result in removing transparent rendering artifacts within a single mesh and retain transparent object overlap but you’ll still have the transparent resort popping:

dpMesh1.renderOrder = 1;
mesh1.renderOrder = 2;
   
dpMesh2.renderOrder = 3;
mesh2.renderOrder = 4;

image

And for reference here’s what it looks like with no depth prepass:

image

@donmccurdy this might be a pretty easy feature to add into three.js with an option per transparent material. Do prefer either of the approaches I listed above if I were to look into adding it?

2 Likes

First of all, I want to thank you@gkjohnson(Sorry I don’t know how to use @…)! Really interesting method, is this the depth pre-pass of babylonjs? Let me try to see if I can eliminate the wrong translucent rendering.

I don’t know the details of how Babylonjs is achieving this but this is generally how a depth prepass works (though if three.js had it built in you wouldn’t have to add a duplicate mesh). A depth prepass can also be done on the whole scene by drawing only the opaque objects to depth to help prevent overdraw and improve performance for intensive fragment shaders.

Judging by the screenshot you posted that does have some transparent object overlap it looks like Babylon is using something more similar to the second approach I posted.

It turns out that this method is useful! :laughing:


Maybe three.js can also consider adding depth pre-pass?

From the effect point of view, the depth pre-pass of babylonjs does use this method. I now also figure out why his option is only found in the source code. I thought there were some algorithms used elsewhere, like the two OIT algorithms I mentioned above. babylonjs makes a judgment on the fragment shader if it is turned on, it returns (0., 0., 0., 1.) in advance.

babylonjs makes a judgment on the fragment shader if it is turned on, it returns (0., 0., 0., 1.) in advance.

Yes effectively the object is rendered with color writing disabled so only depth is written to.

Maybe three.js can also consider adding depth pre-pass ?

I think it would be useful – I assume you are using the second option I listed above to achieve the affect you want that includes transparent object overlap?

Yes, I implemented it quickly based on your thoughts.

I didn’t expect such a simple method to handle it. I thought it was too complicated at first, and I also studied the weighted average OIT. :sob:

Below is my code:

      let depthMaterial = new MeshStandardMaterial({
        colorWrite: false,
        depthWrite: true,
        transparent: true,
      });
      let _mesh = new Mesh(mesh.geometry, depthMaterial);
      _mesh.renderOrder = -1;
      mesh.add(_mesh);

      material.transparent = true;
      material.opacity = alpha;
1 Like

I’ve made a preliminary PR into three.js to support this more easily:

4 Likes