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.
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.
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 );`
);
};
In later versions, fill with shader replacement works, just gl_FragColor = vec4( outgoingLight, diffuseColor.a ); has been moved to the output_fragment chunk, the code below works (v0.147.0):
material.onBeforeCompile = function( shader ) {
shader.fragmentShader = shader.fragmentShader.replace(
`#include <output_fragment>`,
`#ifdef OPAQUE
diffuseColor.a = 1.0;
#endif
// https://github.com/mrdoob/three.js/pull/22425
#ifdef USE_TRANSMISSION
diffuseColor.a *= material.transmissionAlpha + 0.1;
#endif
gl_FragColor = ( gl_FrontFacing ) ? vec4( outgoingLight, diffuseColor.a ) : vec4( diffuse * vec3(0.61, 0.41, 0.06), 1. );
`
);
};
vec4( diffuse * vec3(0.61, 0.41, 0.06), 1. ) can be replaced with vec4( diffuse, 1. ), I just mixed my color
This is a much simpler solution, thanks
Unfortunately it doesn’t work for semi-transparent materials (transparency is set to true).
sorry to necro but I was banging my head on this for ages and then figured out my outline pass in effect composition needed its FBO to have { stencilBuffer: true }
like so:
const renderTarget = useFBO(size.width, size.height, { stencilBuffer: true, depthBuffer: true })
cheers