To add a touch of uniqueness, one option is to have the parents select a distinct ID to be applied to the stencil buffer
const parentMaterial = new THREE.MeshPhongMaterial({
...
stencilWrite: true, // activate stencil writing
stencilRef: stencilId, // set this value
stencilZPass: THREE.ReplaceStencilOp, // write if depth buffer test passes
});
Subsequently, configure the sprite to only render where that ID is found in the stencil buffer
const spriteMaterial = new THREE.SpriteMaterial({
stencilWrite: true, // activate writing
stencilRef: stencilId, //
stencilFunc: THREE.EqualStencilFunc, // render only if stencil = stencilRef;
depthTest: false,
});
Keep in mind: writing to the stencil buffer has 3 scenarios. What to do if the stencil test fails, what to do if the depth test fails, what to do if both pass. The default action for all 3 is to do nothing. Hence, despite setting stencilWrite
to true for the sprite, nothing will be written during sprite rendering due to the default behavior being set to do nothing (THREE.KeepStencilOp).
Ensure that the parents are rendered before the children. If they share the same position, this condition is likely satisfied. However, if they have different positions, adjusting the renderOrder
may be necessary. In the provided example, the sprites are transparent and the bodies are opaque; by default, transparent elements are rendered after opaque elements.
Note: this method is limited to 255 sprites as the stencil buffer typically allows for only 256 possible values. For scenarios exceeding this limit, rendering in groups of 255 objects and clearing the stencil buffer between groups is essential.
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
...
// Code continuation (trimmed for brevity)