depthCompare is never, less, equal, less-equal, greater, not-equal, greater-equal or always: a fragment passes when its depth compares true against the stored value. With the usual clear value of 1, less keeps the nearest surface; less-equal lets a second pass redraw the same geometry, and equal after a depth pre-pass shades each pixel once.
A perspective projection spends most depth values near the camera, so distant surfaces z-fight. Reversed depth maps near to 1 and far to 0, clears to 0 and compares with greater, so depth32float's extra precision near 0 lands where the projection lacks it. For coplanar surfaces, such as a price sticker on a cover or a shadow receiver, depth bias pushes one away: depthBias (a constant in depth units), depthBiasSlopeScale (scaled by the triangle's slope) and depthBiasClamp; bias has no effect on points and lines.
<!doctype html>
<style>
body { margin: 0; background: #f7f4ee; font: 14px system-ui, sans-serif; }
.stage { position: relative; width: 100%; max-width: 600px; }
.stage canvas { display: block; width: 100%; }
.stage canvas + canvas { position: absolute; inset: 0; pointer-events: none; }
</style>
<div class="stage">
<canvas id="view" width="600" height="300"></canvas>
<canvas id="labels" width="600" height="300"></canvas>
</div>
<script>
const canvas = document.getElementById('view');
const ink = document.getElementById('labels').getContext('2d');
function showMessage(text) { // 2D fallback when WebGPU is missing
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fbeaea'; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#8a2b2b'; ctx.font = '18px system-ui, sans-serif'; ctx.textAlign = 'center';
ctx.fillText(text, canvas.width / 2, canvas.height / 2);
}
// A large cover lying on a table far from the camera; instance 1 is a sticker in exactly the same plane.
const code = (reversed) => /* wgsl */ `
struct Out { @builtin(position) pos: vec4f, @location(0) color: vec3f }
@vertex fn vs(@builtin(vertex_index) v: u32, @builtin(instance_index) i: u32) -> Out {
let q = vec2f(f32(v & 1), f32(v >> 1));
let rect = select(vec4f(-3, -4, 6, 8), vec4f(-1.6, -2.2, 3.2, 3.0), i == 1u); // x, t, width, depth
let t = rect.y + q.y * rect.w; // distance along the tilted table
let p = vec3f(rect.x + q.x * rect.z, -1.5 + t * 0.34, 16 + t * 0.94);
let near = 0.5; let far = 40.0;
var depth = far / (far - near) * (p.z - near); // 0 at near, p.z at far (before the divide)
${reversed ? 'depth = p.z - depth; // reversed: near maps to 1, far to 0' : ''}
let color = select(vec3f(0.08, 0.40, 0.75), vec3f(0.98, 0.85, 0.20), i == 1u);
return Out(vec4f(p.x * 3.2, p.y * 3.2 + p.z * 0.25, depth, p.z), color);
}
@fragment fn fs(in: Out) -> @location(0) vec4f { return vec4f(in.color, 1); }`;
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
if (!adapter) return showMessage('WebGPU is not available in this browser');
const device = await adapter.requestDevice();
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });
const make = (reversed, depthFormat, compare, bias) => {
const module = device.createShaderModule({ code: code(reversed) });
return device.createRenderPipeline({ layout: 'auto', primitive: { topology: 'triangle-strip' },
vertex: { module }, fragment: { module, targets: [{ format }] },
depthStencil: { format: depthFormat, depthWriteEnabled: true, depthCompare: compare, ...bias } });
};
const panels = [
["'less', no bias", make(false, 'depth24plus', 'less', {}), 'depth24plus', 1, null],
["'less', depthBias -40", make(false, 'depth24plus', 'less', {}), 'depth24plus', 1,
make(false, 'depth24plus', 'less', { depthBias: -40, depthBiasSlopeScale: -2 })],
["reversed: 'greater', clear 0", make(true, 'depth32float', 'greater', {}), 'depth32float', 0,
make(true, 'depth32float', 'greater', { depthBias: 40, depthBiasSlopeScale: 2 })]];
const encoder = device.createCommandEncoder();
const view = context.getCurrentTexture().createView();
panels.forEach(([, coverPipeline, depthFormat, clear, stickerPipeline], i) => {
const depth = device.createTexture({ size: [600, 300], format: depthFormat, usage: GPUTextureUsage.RENDER_ATTACHMENT });
const pass = encoder.beginRenderPass({
colorAttachments: [{ view, clearValue: [0.97, 0.96, 0.93, 1], loadOp: i === 0 ? 'clear' : 'load', storeOp: 'store' }],
depthStencilAttachment: { view: depth.createView(), depthClearValue: clear, depthLoadOp: 'clear', depthStoreOp: 'discard' } });
pass.setViewport(i * 200, 40, 200, 220, 0, 1);
pass.setPipeline(coverPipeline);
pass.draw(4, 1, 0, 0); // the cover
pass.setPipeline(stickerPipeline ?? coverPipeline);
pass.draw(4, 1, 0, 1); // the sticker, exactly coplanar
pass.end();
});
device.queue.submit([encoder.finish()]);
ink.font = '12px ui-monospace, monospace'; ink.fillStyle = '#222'; ink.textAlign = 'center';
panels.forEach(([name], i) => ink.fillText(name, 100 + i * 200, 24));
ink.font = '11.5px system-ui, sans-serif'; ink.fillStyle = '#444';
ink.fillText('Without bias the coplanar sticker loses the depth test, wholly or in speckles; depthBias fixes it.', 300, 280);
ink.fillText('Reversed depth (near 1, far 0, compare greater) puts float precision where perspective lacks it.', 300, 296);
}
main();
</script>