如何在Three.js中实现父物体内子物体的溢出裁剪?
我是Three.js新手,想请教是否可以裁剪或隐藏父物体外部的子物体?例如我有一个盒子,内部有一个球体(如图:
),希望隐藏球体超出盒子的部分,实现类似CSS中overflow:hidden的效果。
以下是我的示例代码:
// Simple example import * as THREE from "https://cdn.skypack.dev/three@0.136.0"; import {OrbitControls} from "https://cdn.skypack.dev/three@0.136.0/examples/jsm/controls/OrbitControls"; let renderer, scene, camera, controls; init(); animate(); function init() { // renderer renderer = new THREE.WebGLRenderer({alpha:true, antialias:true}); renderer.autoClear = false; renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); console.log(renderer) // scene scene = new THREE.Scene(); // camera camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 ); camera.position.set(0, 0, 40); // controls controls = new OrbitControls( camera, renderer.domElement ); const materialA = new THREE.MeshStandardMaterial({ roughness: 0, metalness: 0, color: 0xffffff, }) const materialB = new THREE.MeshPhysicalMaterial({ roughness: 0, transmission: 1, thickness: 0.5 }); const sphere = new THREE.Mesh(new THREE.SphereGeometry(-5,30,30),materialA); sphere.position.set(5, 0, 0) const box = new THREE.Mesh(new THREE.BoxGeometry(12,12,12),materialB); scene.add(box); const light = new THREE.DirectionalLight('white', 1); light.position.set(10, 10, 0); const light2 = new THREE.DirectionalLight('white', 1); light2.position.set(-10, -10, -10); scene.add(sphere, box,light,light2); } function animate() { requestAnimationFrame( animate ); renderer.render( scene, camera ); }
解决方案:使用剪裁平面(Clipping Planes)
Three.js可以通过**剪裁平面(THREE.Plane)**实现这种局部裁剪效果,原理是创建六个平面模拟盒子的六个面,让渲染器只渲染盒子内部的部分。
修改后的代码示例
// Simple example import * as THREE from "https://cdn.skypack.dev/three@0.136.0"; import {OrbitControls} from "https://cdn.skypack.dev/three@0.136.0/examples/jsm/controls/OrbitControls"; let renderer, scene, camera, controls; init(); animate(); function init() { // renderer renderer = new THREE.WebGLRenderer({alpha:true, antialias:true}); renderer.autoClear = false; renderer.setSize( window.innerWidth, window.innerHeight ); // 开启局部剪裁支持 renderer.localClippingEnabled = true; document.body.appendChild( renderer.domElement ); // scene scene = new THREE.Scene(); // camera camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 ); camera.position.set(0, 0, 40); // controls controls = new OrbitControls( camera, renderer.domElement ); // 创建盒子对应的六个剪裁平面 const boxHalfSize = new THREE.Vector3(6,6,6); // 对应BoxGeometry(12,12,12)的半尺寸 const clipPlanes = [ new THREE.Plane(new THREE.Vector3(1,0,0), boxHalfSize.x), // 右平面 new THREE.Plane(new THREE.Vector3(-1,0,0), boxHalfSize.x), // 左平面 new THREE.Plane(new THREE.Vector3(0,1,0), boxHalfSize.y), // 上平面 new THREE.Plane(new THREE.Vector3(0,-1,0), boxHalfSize.y), // 下平面 new THREE.Plane(new THREE.Vector3(0,0,1), boxHalfSize.z), // 前平面 new THREE.Plane(new THREE.Vector3(0,0,-1), boxHalfSize.z), // 后平面 ]; const materialA = new THREE.MeshStandardMaterial({ roughness: 0, metalness: 0, color: 0xffffff, // 给球体材质绑定剪裁平面 clippingPlanes: clipPlanes, clipIntersection: true // 仅渲染所有平面交集内的部分(盒子内部) }) const materialB = new THREE.MeshPhysicalMaterial({ roughness: 0, transmission: 1, thickness: 0.5 }); const sphere = new THREE.Mesh(new THREE.SphereGeometry(5,30,30),materialA); // 修正负半径导致的面反转问题 sphere.position.set(5, 0, 0) const box = new THREE.Mesh(new THREE.BoxGeometry(12,12,12),materialB); scene.add(box); const light = new THREE.DirectionalLight('white', 1); light.position.set(10, 10, 0); const light2 = new THREE.DirectionalLight('white', 1); light2.position.set(-10, -10, -10); scene.add(sphere, box,light,light2); } function animate() { requestAnimationFrame( animate ); renderer.render( scene, camera ); }
关键说明
- 开启渲染器的
localClippingEnabled属性,启用局部剪裁功能 - 六个剪裁平面分别对应盒子的六个面,法线方向指向盒子外部,第二个参数为平面到原点的距离(即盒子的半尺寸)
- 给需要裁剪的球体材质设置
clippingPlanes为这六个平面,并开启clipIntersection: true,表示只渲染所有平面共同交集内的区域 - 修正原代码中
SphereGeometry(-5,30,30)的负半径问题,负半径会导致球体表面反转,改为正半径5
其他可选方案:CSG布尔运算
如果需要对几何体进行实体级剪裁(比如生成新的裁剪后几何体),可以使用Three.js的CSG库(如three-csg-ts),通过布尔运算直接计算出球体在盒子内部的部分。这种方式适合静态场景,动态修改会存在一定性能开销。
内容的提问来源于stack exchange,提问作者dkv
相关产品推荐
相关产品推荐

