How to fix z-fighting of the line on mesh surface in three.js?

2k Views Asked by At

I'm trying to draw lines on a face in three.js

Everything's working fine except the lines are barely visible - no matter how thick I make them: they look like this:

barely visible lines

The code that draws the line is:

var lgeometry = new THREE.Geometry();
var lmaterial = new THREE.LineBasicMaterial( { color: 0xffffff, linewidth: 4 } );

var v1 = p1;
var v2 = p2;

lgeometry.vertices.push(v1);
lgeometry.vertices.push(v2);

console.log(lgeometry);
var line = new THREE.Line( lgeometry, lmaterial );

scene.add( line );

I suspect that - since the lines are exactly on the surface, they don't get rendered (is this what the call z-fighting?)

Is there a way to solve this?

I am considering:

  • drawing cylinders or other shapes instead of lines
  • drawing the line barely above the surface along normals

Any advice or direction to move in?

1

There are 1 best solutions below

1
Alex Khoroshylov On BEST ANSWER

This is achievable with combination of layers and stencil buffers.

Working Demo: https://jsfiddle.net/mmalex/dg417kvn/

how to fix z-fighting issues with stencil buffers


Solution:

For explanation, please follow the comments in code below:

document.fixZFighting = function() {
    // 1. Get the current WebGL context
    const gl = renderer.getContext();

    // 2. Set rendering order: mesh before line,
    //    because we want mesh to initialize stencil buffers before line rendering.
    cube.renderOrder = 1;
    line.renderOrder = 2;

    // 3. Provide render callbacks
    cube.onBeforeRender = function() {
        // enable stencil buffer test
        gl.enable(gl.STENCIL_TEST);

        // do it just for all mesh pixels
        gl.stencilFunc(gl.ALWAYS, 1, 0xFF);

        // ... with no masking
        gl.stencilMask(0xFF);

        // ... simply increment stencil buffer value for each draw call,
        // (important, here we have 
        gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
    }
    cube.onAfterRender = function() {
        // nothing to do
    }

    line.onBeforeRender = function() {
        // don't rely on z-Buffer for line, disable depth check
        gl.disable(gl.DEPTH_TEST);

        // enable stencil buffer check instead
        gl.enable(gl.STENCIL_TEST)

        gl.stencilMask(0x00);

        // render line only where stencil buffer was incremented exactly twice
        gl.stencilFunc(gl.EQUAL, 2, 0xFF);
    }

    line.onAfterRender = function() {
        // restore flags to initial order
        gl.disable(gl.STENCIL_TEST);
        gl.enable(gl.DEPTH_TEST);
    }

    // don't let user click the button twice
    document.getElementById("btn").setAttribute("disabled", true);
}