如何在 Threejs 中使用外部和内部网格对象渲染带有剪裁平面和模板的帽子
How to render caps with clipping planes and stencil in Threejs with outer and inner mesh objects
我是 three.js 和 Whosebug 的新手。我正在尝试对已渲染的 three.js 个对象进行剪辑和加盖,以便我可以在对象中来回移动 helperPlane 以查看其内部。里面有一个物体。我要做的类似于此处对 OpenGL 中高级裁剪技术的描述:More OpenGL Game Programming - Bonus - Advanced Clip Planes。那么,如果这可以在 OpenGL 中完成,那么一定有某种方法可以在 WebGL 中完成吗?
我改编了 threejs ( webgl - clipping stencil ) 中的 clipping_stencil 示例,只要我不移动 helperPlanes,一切看起来都是正确的。当 helperPlanes 移动时,较大网格的一些帽面消失,有一些渲染伪影 - 我认为这是 z-fighting - 并且帽可能没有在所需位置渲染。
为网格设置 renderingOrder 属性 是让内部网格在场景中渲染的大技巧,但我不知道如何处理 z-fighting?当我移动滑块上的剪裁平面时。
我也在 discourse.threejs. Everything is on a JSFiddle 上发布了这个。任何帮助将不胜感激。
import * as THREE from 'three';
import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js';
import {GUI} from 'https://threejs.org/examples/jsm/libs/lil-gui.module.min.js';
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
let camera, scene, renderer, object, object2, stats;
let planes, planeObjects, planeObjects2, planeHelpers;
let clock;
const params = {
animate: false,
planeX: {
constant: 1,
negated: false,
displayHelper: false
},
planeY: {
constant: 1,
negated: false,
displayHelper: false
},
planeZ: {
constant: 0,
negated: false,
displayHelper: false
}
};
init();
animate();
function createPlaneStencilGroup( geometry, plane, renderOrder ) {
const group = new THREE.Group();
const baseMat = new THREE.MeshBasicMaterial();
baseMat.depthWrite = false;
baseMat.depthTest = false;
baseMat.colorWrite = false;
baseMat.stencilWrite = true;
baseMat.stencilFunc = THREE.AlwaysStencilFunc;
/* Subtract the mask created from the front-facing image
from the mask created from the back-facing image, we get
a new mask that represents the area where the clip edge
would be. Set the stencil buffer operation to increment
when rederering back-facing polygons and decrement on
front-facing polygons. This results in the desired mask
stored in the stencil buffer : http://glbook.gamedev.net/GLBOOK/glbook.gamedev.net/moglgp/advclip.html */
// back faces
const mat0 = baseMat.clone();
mat0.side = THREE.BackSide;
mat0.clippingPlanes = [ plane ];
mat0.stencilFail = THREE.IncrementWrapStencilOp;
mat0.stencilZFail = THREE.IncrementWrapStencilOp;
mat0.stencilZPass = THREE.IncrementWrapStencilOp;
//mat0.depthFunc = THREE.LessDepth; // See reference above
const mesh0 = new THREE.Mesh( geometry, mat0 );
mesh0.renderOrder = renderOrder;
group.add( mesh0 );
// front faces
const mat1 = baseMat.clone();
mat1.side = THREE.FrontSide;
mat1.clippingPlanes = [ plane ];
mat1.stencilFail = THREE.DecrementWrapStencilOp;
mat1.stencilZFail = THREE.DecrementWrapStencilOp;
mat1.stencilZPass = THREE.DecrementWrapStencilOp;
//mat1.depthFunc = THREE.LessDepth;
const mesh1 = new THREE.Mesh( geometry, mat1 );
mesh1.renderOrder = renderOrder;
group.add( mesh1 );
return group;
}
function init(){
//clock
clock = new THREE.Clock();
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(36, window.innerWidth/window.innerHeight, 1,100);
camera.position.set(2,2,2);
// Lights
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff,1);
dirLight.position.set(5,10,7.5);
dirLight.castShadow = true;
dirLight.shadow.camera.right = 2;
dirLight.shadow.camera.left = -2;
dirLight.shadow.camera.top = 2;
dirLight.shadow.camera.bottom = -2;
dirLight.shadow.mapSize.width = 1024;
dirLight.shadow.mapSize.height = 1024;
scene.add(dirLight);
//Clipping planes
planes = [
new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 1 ),
new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 1 ),
new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0 )
];
planeHelpers = planes.map( p => new THREE.PlaneHelper( p, 2, 0xffffff ) );
planeHelpers.forEach( ph => {
ph.visible = false;
scene.add( ph );
} );
//Inner Cube
const geometry = new THREE.BoxGeometry( 0.5,0.5,0.5 );
//Outer Cube
const geometry2 = new THREE.BoxGeometry( 1,1,1 );
object = new THREE.Group();
scene.add(object);
//Set up clip plane rendering
/*
See https://discourse.threejs.org/t/capping-two-clipped-geometries-using-two-planes-which-are-negated-to-each-other/32643
Object 1
Render order 1: Draw front face / back face clipped and front face
/ back face not clipped (4 meshes)
Render order 2: Draw planar clip cap
Object 2
Render order 3: Draw front face / back face clipped and front face
/ back face not clipped (4 meshes)
Render order 4: Draw planar clip cap
*/
planeObjects = [];
planeObjects2 = [];
const planeGeom = new THREE.PlaneGeometry( 4, 4 );
for ( let i = 0; i < 3; i ++ ) {
const poGroup = new THREE.Group();
const poGroup2 = new THREE.Group()
const plane = planes[ i ];
// Object 1
const stencilGroup = createPlaneStencilGroup( geometry,
plane, i + 4 ); // Render after first group
// Object 2
const stencilGroup2 = createPlaneStencilGroup( geometry2,
plane, i + 1 ); // Render this first
// PLANAR CLIP CAP
// plane is clipped by the other clipping planes
const planeMat =
new THREE.MeshStandardMaterial( {
color: 0xfff000, // inner torus colour
metalness: 0.1,
roughness: 0.75,
clippingPlanes: planes.filter( p => p !== plane ),
//depthFunc: THREE.LessDepth,
stencilWrite: true,
stencilRef: 0,
stencilFunc: THREE.NotEqualStencilFunc,
stencilFail: THREE.ReplaceStencilOp,
stencilZFail: THREE.ReplaceStencilOp,
stencilZPass: THREE.ReplaceStencilOp,
} );
const planeMat2 =
new THREE.MeshStandardMaterial( {
color: 0xff0000, // inner torus colour
metalness: 0.1,
roughness: 0.75,
clippingPlanes: planes.filter( p => p !== plane ),
//depthFunc: THREE.LessDepth,
stencilWrite: true,
stencilRef: 0,
stencilFunc: THREE.NotEqualStencilFunc,
stencilFail: THREE.ReplaceStencilOp,
stencilZFail: THREE.ReplaceStencilOp,
stencilZPass: THREE.ReplaceStencilOp,
} );
const po = new THREE.Mesh( planeGeom, planeMat );
const po2 = new THREE.Mesh( planeGeom, planeMat2 );
po.onAfterRender = function ( renderer ) {
renderer.clearStencil();
};
po2.onAfterRender = function ( renderer ) {
renderer.clearStencil();
};
// Draw Planar Clip Cap
po.renderOrder = i + 4.1; // Render last (slightly)
po2.renderOrder = i + 1.1; // Render slightly after first group
object.add( stencilGroup );
object.add( stencilGroup2 );
poGroup.add( po );
poGroup2.add( po2 );
planeObjects.push( po );
planeObjects2.push( po2 );
scene.add( poGroup );
scene.add( poGroup2 );
}
// Object 1
const material = new THREE.MeshStandardMaterial( {
color: 0xfff000, // outer torus colour
metalness: 0.1,
roughness: 0.75,
clippingPlanes: planes,
clipShadows: true,
shadowSide: THREE.DoubleSide,
} );
// add the color
const clippedColorFront = new THREE.Mesh( geometry, material );
clippedColorFront.castShadow = true;
clippedColorFront.renderOrder = 6;
object.add( clippedColorFront );
// Object 2
const material2 = new THREE.MeshStandardMaterial( {
color: 0xff0000, // outer colour
metalness: 0.1,
roughness: 0.75,
side: THREE.DoubleSide,
clippingPlanes: planes,
clipShadows: true,
shadowSide: THREE.DoubleSide,
} );
// add the color
const clippedColorFront2 = new THREE.Mesh( geometry2, material2 );
clippedColorFront2.castShadow = true;
clippedColorFront2.renderOrder = 3;
object.add( clippedColorFront2 );
//Ground
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(9,9,1,1),
new THREE.MeshPhongMaterial({color:0x999999, opacity:0.25, side:THREE.DoubleSide})
);
ground.rotation.x = - Math.PI/2; // rotates x/y to x/z
ground.position.y = -1;
ground.receiveShadow = true;
scene.add(ground);
//Stats
stats = new Stats();
document.body.appendChild(stats.dom);
//Renderer
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.shadowMap.enabled = true;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor( 0x263238 );
window.addEventListener('resize',onWindowResize);
document.body.appendChild(renderer.domElement);
renderer.localClippingEnabled = true;
const controls = new OrbitControls(camera, renderer.domElement);
controls.minDistance = 2;
controls.maxDistance = 20;
controls.update();
//GUI
const gui = new GUI();
gui.add(params, 'animate');
const planeX = gui.addFolder( 'planeX' );
planeX.add( params.planeX, 'displayHelper' ).onChange( v => planeHelpers[ 0 ].visible = v );
planeX.add( params.planeX, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 0 ].constant = d );
planeX.add( params.planeX, 'negated' ).onChange( () => {
planes[ 0 ].negate();
params.planeX.constant = planes[ 0 ].constant;
} );
planeX.open();
const planeY = gui.addFolder( 'planeY' );
planeY.add( params.planeY, 'displayHelper' ).onChange( v => planeHelpers[ 1 ].visible = v );
planeY.add( params.planeY, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 1 ].constant = d );
planeY.add( params.planeY, 'negated' ).onChange( () => {
planes[ 1 ].negate();
params.planeY.constant = planes[ 1 ].constant;
} );
planeY.open();
const planeZ = gui.addFolder( 'planeZ' );
planeZ.add( params.planeZ, 'displayHelper' ).onChange( v => planeHelpers[ 2 ].visible = v );
planeZ.add( params.planeZ, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 2 ].constant = d );
planeZ.add( params.planeZ, 'negated' ).onChange( () => {
planes[ 2 ].negate();
params.planeZ.constant = planes[ 2 ].constant;
} );
planeZ.open();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
const delta = clock.getDelta();
requestAnimationFrame( animate );
if ( params.animate ) {
object.rotation.x += delta * 0.5;
object.rotation.y += delta * 0.2;
}
for ( let i = 0; i < planeObjects.length; i ++ ) {
const plane = planes[ i ];
// Planar clip cap for object 1
const po = planeObjects[ i ];
plane.coplanarPoint( po.position );
// planar clip cap for object 2
const po2 = planeObjects[ i ];
plane.coplanarPoint( po2.position );
// planar clip cap for object 1
po.lookAt(
po.position.x - plane.normal.x,
po.position.y - plane.normal.y,
po.position.z - plane.normal.z,
);
// planar clip cap for object 2
po2.lookAt(
po2.position.x - plane.normal.x,
po2.position.y - plane.normal.y,
po2.position.z - plane.normal.z,
);
}
stats.begin();
renderer.render( scene, camera );
stats.end();
}
我开始做的事情取得了一些成功。这是更新后的 JSFiddle。我能够使用剪裁和模板实现将一个对象覆盖在另一个对象内。我将拖动和轨道控制以及 gui 添加到 select 平面 (x,y,z) 以进行剖切。我注意到在根据对象位置和相机旋转渲染盖子时出现一些奇怪的行为。
- 我需要将对象移到离相机更远的地方才能看到在 x 和 y 平面而不是 z 平面中切片时渲染的端盖
- 如果我将相机从正 x 旋转到负 x,盖子似乎像滑动门一样消失了
所以我认为上限与裁剪平面在同一位置渲染,深度测试无法在某些相机点区分两者。我认为,当我移动相机时,沿着垂直于平面的矢量以一定公差将封口从裁剪平面移开将使封口以更多角度渲染。我在我的动画函数中试过这个:
innerCap.translateOnAxis(clipPlane.normal, -1.5);
这使得瓶盖在负 x 方向上呈现多一点的角度。我认为这个公差是物体到相机距离的函数,但我不确定如何实现它。感谢您的帮助。
我是 three.js 和 Whosebug 的新手。我正在尝试对已渲染的 three.js 个对象进行剪辑和加盖,以便我可以在对象中来回移动 helperPlane 以查看其内部。里面有一个物体。我要做的类似于此处对 OpenGL 中高级裁剪技术的描述:More OpenGL Game Programming - Bonus - Advanced Clip Planes。那么,如果这可以在 OpenGL 中完成,那么一定有某种方法可以在 WebGL 中完成吗?
我改编了 threejs ( webgl - clipping stencil ) 中的 clipping_stencil 示例,只要我不移动 helperPlanes,一切看起来都是正确的。当 helperPlanes 移动时,较大网格的一些帽面消失,有一些渲染伪影 - 我认为这是 z-fighting - 并且帽可能没有在所需位置渲染。
为网格设置 renderingOrder 属性 是让内部网格在场景中渲染的大技巧,但我不知道如何处理 z-fighting?当我移动滑块上的剪裁平面时。
我也在 discourse.threejs. Everything is on a JSFiddle 上发布了这个。任何帮助将不胜感激。
import * as THREE from 'three';
import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js';
import {GUI} from 'https://threejs.org/examples/jsm/libs/lil-gui.module.min.js';
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
let camera, scene, renderer, object, object2, stats;
let planes, planeObjects, planeObjects2, planeHelpers;
let clock;
const params = {
animate: false,
planeX: {
constant: 1,
negated: false,
displayHelper: false
},
planeY: {
constant: 1,
negated: false,
displayHelper: false
},
planeZ: {
constant: 0,
negated: false,
displayHelper: false
}
};
init();
animate();
function createPlaneStencilGroup( geometry, plane, renderOrder ) {
const group = new THREE.Group();
const baseMat = new THREE.MeshBasicMaterial();
baseMat.depthWrite = false;
baseMat.depthTest = false;
baseMat.colorWrite = false;
baseMat.stencilWrite = true;
baseMat.stencilFunc = THREE.AlwaysStencilFunc;
/* Subtract the mask created from the front-facing image
from the mask created from the back-facing image, we get
a new mask that represents the area where the clip edge
would be. Set the stencil buffer operation to increment
when rederering back-facing polygons and decrement on
front-facing polygons. This results in the desired mask
stored in the stencil buffer : http://glbook.gamedev.net/GLBOOK/glbook.gamedev.net/moglgp/advclip.html */
// back faces
const mat0 = baseMat.clone();
mat0.side = THREE.BackSide;
mat0.clippingPlanes = [ plane ];
mat0.stencilFail = THREE.IncrementWrapStencilOp;
mat0.stencilZFail = THREE.IncrementWrapStencilOp;
mat0.stencilZPass = THREE.IncrementWrapStencilOp;
//mat0.depthFunc = THREE.LessDepth; // See reference above
const mesh0 = new THREE.Mesh( geometry, mat0 );
mesh0.renderOrder = renderOrder;
group.add( mesh0 );
// front faces
const mat1 = baseMat.clone();
mat1.side = THREE.FrontSide;
mat1.clippingPlanes = [ plane ];
mat1.stencilFail = THREE.DecrementWrapStencilOp;
mat1.stencilZFail = THREE.DecrementWrapStencilOp;
mat1.stencilZPass = THREE.DecrementWrapStencilOp;
//mat1.depthFunc = THREE.LessDepth;
const mesh1 = new THREE.Mesh( geometry, mat1 );
mesh1.renderOrder = renderOrder;
group.add( mesh1 );
return group;
}
function init(){
//clock
clock = new THREE.Clock();
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(36, window.innerWidth/window.innerHeight, 1,100);
camera.position.set(2,2,2);
// Lights
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff,1);
dirLight.position.set(5,10,7.5);
dirLight.castShadow = true;
dirLight.shadow.camera.right = 2;
dirLight.shadow.camera.left = -2;
dirLight.shadow.camera.top = 2;
dirLight.shadow.camera.bottom = -2;
dirLight.shadow.mapSize.width = 1024;
dirLight.shadow.mapSize.height = 1024;
scene.add(dirLight);
//Clipping planes
planes = [
new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 1 ),
new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 1 ),
new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0 )
];
planeHelpers = planes.map( p => new THREE.PlaneHelper( p, 2, 0xffffff ) );
planeHelpers.forEach( ph => {
ph.visible = false;
scene.add( ph );
} );
//Inner Cube
const geometry = new THREE.BoxGeometry( 0.5,0.5,0.5 );
//Outer Cube
const geometry2 = new THREE.BoxGeometry( 1,1,1 );
object = new THREE.Group();
scene.add(object);
//Set up clip plane rendering
/*
See https://discourse.threejs.org/t/capping-two-clipped-geometries-using-two-planes-which-are-negated-to-each-other/32643
Object 1
Render order 1: Draw front face / back face clipped and front face
/ back face not clipped (4 meshes)
Render order 2: Draw planar clip cap
Object 2
Render order 3: Draw front face / back face clipped and front face
/ back face not clipped (4 meshes)
Render order 4: Draw planar clip cap
*/
planeObjects = [];
planeObjects2 = [];
const planeGeom = new THREE.PlaneGeometry( 4, 4 );
for ( let i = 0; i < 3; i ++ ) {
const poGroup = new THREE.Group();
const poGroup2 = new THREE.Group()
const plane = planes[ i ];
// Object 1
const stencilGroup = createPlaneStencilGroup( geometry,
plane, i + 4 ); // Render after first group
// Object 2
const stencilGroup2 = createPlaneStencilGroup( geometry2,
plane, i + 1 ); // Render this first
// PLANAR CLIP CAP
// plane is clipped by the other clipping planes
const planeMat =
new THREE.MeshStandardMaterial( {
color: 0xfff000, // inner torus colour
metalness: 0.1,
roughness: 0.75,
clippingPlanes: planes.filter( p => p !== plane ),
//depthFunc: THREE.LessDepth,
stencilWrite: true,
stencilRef: 0,
stencilFunc: THREE.NotEqualStencilFunc,
stencilFail: THREE.ReplaceStencilOp,
stencilZFail: THREE.ReplaceStencilOp,
stencilZPass: THREE.ReplaceStencilOp,
} );
const planeMat2 =
new THREE.MeshStandardMaterial( {
color: 0xff0000, // inner torus colour
metalness: 0.1,
roughness: 0.75,
clippingPlanes: planes.filter( p => p !== plane ),
//depthFunc: THREE.LessDepth,
stencilWrite: true,
stencilRef: 0,
stencilFunc: THREE.NotEqualStencilFunc,
stencilFail: THREE.ReplaceStencilOp,
stencilZFail: THREE.ReplaceStencilOp,
stencilZPass: THREE.ReplaceStencilOp,
} );
const po = new THREE.Mesh( planeGeom, planeMat );
const po2 = new THREE.Mesh( planeGeom, planeMat2 );
po.onAfterRender = function ( renderer ) {
renderer.clearStencil();
};
po2.onAfterRender = function ( renderer ) {
renderer.clearStencil();
};
// Draw Planar Clip Cap
po.renderOrder = i + 4.1; // Render last (slightly)
po2.renderOrder = i + 1.1; // Render slightly after first group
object.add( stencilGroup );
object.add( stencilGroup2 );
poGroup.add( po );
poGroup2.add( po2 );
planeObjects.push( po );
planeObjects2.push( po2 );
scene.add( poGroup );
scene.add( poGroup2 );
}
// Object 1
const material = new THREE.MeshStandardMaterial( {
color: 0xfff000, // outer torus colour
metalness: 0.1,
roughness: 0.75,
clippingPlanes: planes,
clipShadows: true,
shadowSide: THREE.DoubleSide,
} );
// add the color
const clippedColorFront = new THREE.Mesh( geometry, material );
clippedColorFront.castShadow = true;
clippedColorFront.renderOrder = 6;
object.add( clippedColorFront );
// Object 2
const material2 = new THREE.MeshStandardMaterial( {
color: 0xff0000, // outer colour
metalness: 0.1,
roughness: 0.75,
side: THREE.DoubleSide,
clippingPlanes: planes,
clipShadows: true,
shadowSide: THREE.DoubleSide,
} );
// add the color
const clippedColorFront2 = new THREE.Mesh( geometry2, material2 );
clippedColorFront2.castShadow = true;
clippedColorFront2.renderOrder = 3;
object.add( clippedColorFront2 );
//Ground
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(9,9,1,1),
new THREE.MeshPhongMaterial({color:0x999999, opacity:0.25, side:THREE.DoubleSide})
);
ground.rotation.x = - Math.PI/2; // rotates x/y to x/z
ground.position.y = -1;
ground.receiveShadow = true;
scene.add(ground);
//Stats
stats = new Stats();
document.body.appendChild(stats.dom);
//Renderer
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.shadowMap.enabled = true;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor( 0x263238 );
window.addEventListener('resize',onWindowResize);
document.body.appendChild(renderer.domElement);
renderer.localClippingEnabled = true;
const controls = new OrbitControls(camera, renderer.domElement);
controls.minDistance = 2;
controls.maxDistance = 20;
controls.update();
//GUI
const gui = new GUI();
gui.add(params, 'animate');
const planeX = gui.addFolder( 'planeX' );
planeX.add( params.planeX, 'displayHelper' ).onChange( v => planeHelpers[ 0 ].visible = v );
planeX.add( params.planeX, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 0 ].constant = d );
planeX.add( params.planeX, 'negated' ).onChange( () => {
planes[ 0 ].negate();
params.planeX.constant = planes[ 0 ].constant;
} );
planeX.open();
const planeY = gui.addFolder( 'planeY' );
planeY.add( params.planeY, 'displayHelper' ).onChange( v => planeHelpers[ 1 ].visible = v );
planeY.add( params.planeY, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 1 ].constant = d );
planeY.add( params.planeY, 'negated' ).onChange( () => {
planes[ 1 ].negate();
params.planeY.constant = planes[ 1 ].constant;
} );
planeY.open();
const planeZ = gui.addFolder( 'planeZ' );
planeZ.add( params.planeZ, 'displayHelper' ).onChange( v => planeHelpers[ 2 ].visible = v );
planeZ.add( params.planeZ, 'constant' ).min( - 1 ).max( 1 ).onChange( d => planes[ 2 ].constant = d );
planeZ.add( params.planeZ, 'negated' ).onChange( () => {
planes[ 2 ].negate();
params.planeZ.constant = planes[ 2 ].constant;
} );
planeZ.open();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
const delta = clock.getDelta();
requestAnimationFrame( animate );
if ( params.animate ) {
object.rotation.x += delta * 0.5;
object.rotation.y += delta * 0.2;
}
for ( let i = 0; i < planeObjects.length; i ++ ) {
const plane = planes[ i ];
// Planar clip cap for object 1
const po = planeObjects[ i ];
plane.coplanarPoint( po.position );
// planar clip cap for object 2
const po2 = planeObjects[ i ];
plane.coplanarPoint( po2.position );
// planar clip cap for object 1
po.lookAt(
po.position.x - plane.normal.x,
po.position.y - plane.normal.y,
po.position.z - plane.normal.z,
);
// planar clip cap for object 2
po2.lookAt(
po2.position.x - plane.normal.x,
po2.position.y - plane.normal.y,
po2.position.z - plane.normal.z,
);
}
stats.begin();
renderer.render( scene, camera );
stats.end();
}
我开始做的事情取得了一些成功。这是更新后的 JSFiddle。我能够使用剪裁和模板实现将一个对象覆盖在另一个对象内。我将拖动和轨道控制以及 gui 添加到 select 平面 (x,y,z) 以进行剖切。我注意到在根据对象位置和相机旋转渲染盖子时出现一些奇怪的行为。
- 我需要将对象移到离相机更远的地方才能看到在 x 和 y 平面而不是 z 平面中切片时渲染的端盖
- 如果我将相机从正 x 旋转到负 x,盖子似乎像滑动门一样消失了
所以我认为上限与裁剪平面在同一位置渲染,深度测试无法在某些相机点区分两者。我认为,当我移动相机时,沿着垂直于平面的矢量以一定公差将封口从裁剪平面移开将使封口以更多角度渲染。我在我的动画函数中试过这个:
innerCap.translateOnAxis(clipPlane.normal, -1.5);
这使得瓶盖在负 x 方向上呈现多一点的角度。我认为这个公差是物体到相机距离的函数,但我不确定如何实现它。感谢您的帮助。