裁剪三.js子画面以适合其父对象的边界



我有一个精灵,按如下方式创建,并添加为具有透明材质的对象的子级:

let mySprite = new THREE.Sprite(new SpriteMaterial({
map: myTexture
}));
mySprite.scale.set(2, 2, 1.0);
mySprite.position.set(0, 0, 0);
myObject.add(mySprite);

对象已经depthWrite: false,所以我可以通过它看到精灵,但是,由于对象是一个球体,而精灵是正方形的,因此精灵的角溢出了对象。

有没有办法可以剪裁精灵的角,以便只显示球形对象范围内的内容?

一种方法是让父级绘制一个唯一的 ID 到模板缓冲区

const parentMaterial = new THREE.MeshPhongMaterial({
...
stencilWrite: true,                    // turn on stenci writing
stencilRef: stencilId,                 // write this value
stencilZPass: THREE.ReplaceStencilOp,  // write if the depth buffer test passes
});

并将子画面设置为仅在模具缓冲区中出现该 id 的位置绘制

const spriteMaterial = new THREE.SpriteMaterial({
stencilWrite: true,                   // turn on writing
stencilRef: stencilId,                // 
stencilFunc: THREE.EqualStencilFunc,  // draw only if stencil = stencilRef;
depthTest: false,
});

注意:写入模板有 3 个案例。模板测试失败怎么办,深度测试失败怎么办,两者都通过怎么办。所有 3 个的默认值是不执行任何操作。因此,即使我们为精灵打开stencilWrite正确的,因为 3 个默认值什么都不做(THREE.KeepStencilOp(绘制精灵时不会写入任何内容。

您需要确保父母先于孩子抽取。如果他们有同样的立场,那么我认为这已经是正确的了。如果他们有不同的位置,那么您可能需要设置renderOrder.在上面的例子中,精灵是透明的,主体是不透明的,默认情况下,透明的东西在不透明之后绘制。

请注意,这仅适用于 255 个精灵,因为模具通常只有 256 个可能的值。过去,您需要以 255 个对象的组呈现内容,清除组之间的模板缓冲区。

function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2;  // the canvas default
const near = 0.1;
const far = 50;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 2, 5);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 2, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
function addLight(position) {
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(...position);
scene.add(light);
scene.add(light.target);
}
addLight([-3, 1, 1]);
addLight([ 2, 1, .5]);
const bodyRadiusTop = .4;
const bodyRadiusBottom = .2;
const bodyHeight = 2;
const bodyRadialSegments = 6;
const bodyGeometry = new THREE.CylinderBufferGeometry(
bodyRadiusTop, bodyRadiusBottom, bodyHeight, bodyRadialSegments);
const headRadius = bodyRadiusTop * 0.8;
const headLonSegments = 12;
const headLatSegments = 5;
const headGeometry = new THREE.SphereBufferGeometry(
headRadius, headLonSegments, headLatSegments);
function makeLabelCanvas(baseWidth, size, name) {
const borderSize = 2;
const ctx = document.createElement('canvas').getContext('2d');
const font =  `${size}px bold sans-serif`;
ctx.font = font;
// measure how long the name will be
const textWidth = ctx.measureText(name).width;
const doubleBorderSize = borderSize * 2;
const width = baseWidth + doubleBorderSize;
const height = size + doubleBorderSize;
ctx.canvas.width = width;
ctx.canvas.height = height;
// need to set font again after resizing canvas
ctx.font = font;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
// scale to fit but don't stretch
const scaleFactor = Math.min(1, baseWidth / textWidth);
ctx.translate(width / 2, height / 2);
ctx.scale(scaleFactor, 1);
ctx.fillStyle = 'white';
ctx.fillText(name, 0, 0);
return ctx.canvas;
}
function makePerson(stencilId, x, labelWidth, size, name, color) {
const canvas = makeLabelCanvas(labelWidth, size, name);
const texture = new THREE.CanvasTexture(canvas);
// because our canvas is likely not a power of 2
// in both dimensions set the filtering appropriately.
texture.minFilter = THREE.LinearFilter;
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
const labelMaterial = new THREE.SpriteMaterial({
map: texture,
transparent: true,
stencilWrite: true,
stencilRef: stencilId,
stencilFunc: THREE.EqualStencilFunc,
depthTest: false,
});
const bodyMaterial = new THREE.MeshPhongMaterial({
color,
flatShading: true,
stencilWrite: true,
stencilRef: stencilId,
stencilZPass: THREE.ReplaceStencilOp,
});
const root = new THREE.Object3D();
root.position.x = x;
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
root.add(body);
body.position.y = bodyHeight / 2;
const head = new THREE.Mesh(headGeometry, bodyMaterial);
root.add(head);
head.position.y = bodyHeight + headRadius * 1.1;
const label = new THREE.Sprite(labelMaterial);
root.add(label);
label.position.y = bodyHeight * 4 / 5;
// if units are meters then 0.01 here makes size
// of the label into centimeters.
const labelBaseScale = 0.01;
label.scale.x = canvas.width  * labelBaseScale;
label.scale.y = canvas.height * labelBaseScale;
scene.add(root);
return root;
}
makePerson(1, -3, 128, 128, '😜', 'purple');
makePerson(2, -0, 128, 128, '🚙', 'green');
makePerson(3, +3, 128, 128, '🔥', 'red');
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.min.js"></script>
<script src= "https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/js/controls/OrbitControls.js"></script>
<canvas id="c"></canvas>

最新更新