Capping clipped planes using stencil on a BufferGeometry()

I haven’t worked with this in a while.

First thing is, whether you’re doing this or not I don’t know, but I would apply the code as one function separately to each piece of geometry.

1 Like

I will try to keep my solution as simple as possible as I don’t know what you are trying to achieve / what kind of parts or objects you are using. I will assume that you have multiple mesh type “part” objects that all exist inside a THREE.Group() object called partGroup. This is what I did that worked:

var renderer, scene, camera; // These are already assumed to be created
var backFaceStencilMat, frontFaceStencilMat, planeStencilMat;
function main()
{
   renderer.localClippingEnabled = true; // make sure that this flag on the global renderer is set to true for clipping
   initializeStencilMaterials(); //creates all stencil materials for capping (scroll down for the full function)
   var clipMeshGroup = new THREE.Group(); // Group that contains all capping meshes
   var planes = [plane1, plane2, plane3, plane4, plane5] ; //Array of 5 THREE.Plane() clipping planes that cut the objects 
   var partGroup  = new THREE.Group(); //Group containing part meshes of THREE.Mesh() type 'parts' that are to be cut by the planes. Make sure that the property on each part: part.material.side = THREE.DoubleSide otherwise the capping procedure won't work as expected in some cases.
   /*Note: partGroup is already assumed to be populated with children of THREE.Mesh() type to keep this example short*/
 
      for(var i = 0; i < partGroup.children.length; i++)
      {
         var part = partGroup.children[i]; //part is a THREE.Mesh() object with already assigned geometry and material
         part.material.clippingPlanes = planes; //applying all clipping planes to the current part mesh
         
         var frontMesh = new THREE.Mesh(part.geometry, frontFaceStencilMat);
         frontMesh.rotation.copy(part.rotation);
         clipMeshGroup.add(frontMesh);

         var backMesh = new THREE.Mesh(part.geometry, backFaceStencilMat);
         backMesh.rotation.copy(part.rotation);
         clipMeshGroup.add(backMesh);
       
        // Add the plane
         var planeGeometry = new THREE.PlaneBufferGeometry();
         var planeMesh = new THREE.Mesh(planeGeometry, planeStencilMat);
         planeMesh.renderOrder = 1;
         clipMeshGroup.add(planeMesh);
      }

   scene.add(clipMeshGroup);
   renderer.render(scene, camera);
}

function initializeStencilMaterials(){
// PASS 1
    // everywhere that the back faces are visible (clipped region) the stencil
    // buffer is incremented by 1.
     backFaceStencilMat =
        new THREE.MeshBasicMaterial({

            depthWrite: false,
            depthTest: false,
            colorWrite: false,
            stencilWrite: true,
            stencilFunc: THREE.AlwaysStencilFunc,
            side: THREE.BackSide,

            stencilFail: THREE.IncrementWrapStencilOp,
            stencilZFail: THREE.IncrementWrapStencilOp,
            stencilZPass: THREE.IncrementWrapStencilOp,

        });


    // PASS 2
    // everywhere that the front faces are visible the stencil
    // buffer is decremented back to 0.
    frontFaceStencilMat =
        new THREE.MeshBasicMaterial({

            depthWrite: false,
            depthTest: false,
            colorWrite: false,
            stencilWrite: true,
            stencilFunc: THREE.AlwaysStencilFunc,
            side: THREE.FrontSide,

            stencilFail: THREE.DecrementWrapStencilOp,
            stencilZFail: THREE.DecrementWrapStencilOp,
            stencilZPass: THREE.DecrementWrapStencilOp,

        });

    // PASS 3
    // draw the plane everywhere that the stencil buffer != 0, which will
    // only be in the clipped region where back faces are visible.
     planeStencilMat =
        new THREE.MeshStandardMaterial({
            color: 0xffefbb,
            metalness: 0.1,
            roughness: 0.75,

            stencilWrite: true,
            stencilRef: 0,
            stencilFunc: THREE.NotEqualStencilFunc,
            stencilFail: THREE.ReplaceStencilOp,
            stencilZFail: THREE.ReplaceStencilOp,
            stencilZPass: THREE.ReplaceStencilOp,

        });

}

Thank you so much I now understand what needs to be done here. the code for drawing the plane everywhere that the stencil buffer != 0, which will only be in the clipped region where back faces are visible makes sense now.

I was wondering if this solution works for a scene with a lots of meshes…
I’m trying to implements such logic on complex GLTF scenes but without success so far.
I’ve also tried to ‘fill’ the geometry with the following fragment shader but it doesn’t work in the later threejs version (0.140.2):

material.onBeforeCompile = function( shader ) {
    shader.fragmentShader = shader.fragmentShader.replace(
        `gl_FragColor = vec4( outgoingLight, diffuseColor.a );`,
        `gl_FragColor = ( gl_FrontFacing ) ? vec4( outgoingLight, diffuseColor.a ) : vec4( diffuse, opacity );`
    );
};