three.jszbuffer

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


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:

Any advice or direction to move in?


Solution

  • 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);
    }