Milkshake.io logo

Pre-DepthPass based transparency

World of Warcraft transparency example

The key insight: even when transparent, characters don't expose internal geometry—just a clean transparent mesh. To understand how they achieve this, we can use a frame debugger to inspect the draw calls.

Debugging with RenderDoc

Using an old World of Warcraft client that only supports DirectX 9, modern frame debuggers no longer support DX9. Fortunately, we can use DXVK a DX9 to Vulkan translation layer—which allows RenderDoc to inspect the draw calls.

RenderDoc debugging setup

Looking at the draw calls, we see that opaque geometry is rendered first. Then, for each transparent object:

  • Render depth-only
  • Render color-pass

The trick? The depth pre-pass blocks transparent triangles from overdrawing their internals. This ensures that when rendering the color pass, internal geometry is already occluded by the depth buffer.

Implementing in Three.js

Now let's replicate this technique in Three.js. First, here's what the default transparency looks like—notice the internal geometry showing through:

Step 1. Render Opaque

First, render all opaque geometry:

camera.layers.set(0);
renderer.render(scene, camera);

Step 2. Render Transparent Object to Depth Buffer

Disable auto-clear and disable the color mask to render only to the depth buffer:

camera.layers.set(1);
renderer.autoClear = false;
const gl = renderer.getContext();
gl.colorMask(false, false, false, false);
renderer.render(scene, camera);

Step 3. Render Transparent Object Color

Re-enable the color mask and render the transparent object with color:

gl.colorMask(true, true, true, true);
renderer.render(scene, camera);
renderer.autoClear = true;

The final result is a clean transparent object without internal geometry showing through!