如何在Three.js中使用裁剪平面正确对球体进行四分之一切割?
如何在Three.js中使用裁剪平面正确对球体进行四分之一切割?
嘿,我看了你的代码和需求,问题主要出在裁剪平面的方向和参数设置上,咱们一步步来把它搞定~
问题根源:裁剪平面的方向搞反了
Three.js的裁剪平面是基于平面法向量和平面到原点的距离来工作的,而且默认会保留满足 plane.distanceToPoint(顶点) >= 0 的几何体部分。另外,clipIntersection 默认是 false,意味着会保留任意一个裁剪平面的保留侧(也就是去掉同时不满足所有平面条件的四分之一区域)——这正是你要的四分之三球体效果,但你的平面方向设置反了,导致切掉了错误的部分。
你的原代码里,裁剪平面的法向量是(-1,0,0)和(0,-1,0),这会保留x≤-cut和y≤-cut的区域,切掉的是x>-cut且y>-cut的部分,和你想要的截面方向正好相反。
解决方案:调整裁剪平面的设置
我们需要把法向量改成(1,0,0)和(0,1,0),同时调整constant参数,让平面位于你想要的切割位置:
- 平面法向量指向你想保留的一侧(比如x正方向、y正方向)
- constant参数设为
-cut,这样平面方程就是x = cut和y = cut - 滑块控制时,把constant设为
-depth,让滑块值直接对应切割深度
修改后的关键代码部分
首先是裁剪平面的初始化:
// 初始切割深度设为0(切到球体中心) const cut = 0; // 调整平面法向量和constant,保留x >= cut 或 y >= cut的部分(去掉x<cut且y<cut的四分之一) const planeX = new THREE.Plane(new THREE.Vector3(1, 0, 0), -cut); const planeY = new THREE.Plane(new THREE.Vector3(0, 1, 0), -cut);
然后是滑块的事件处理:
// Handle slider input slider.addEventListener('input', function() { const depth = parseFloat(this.value); // 把constant设为-depth,对应平面x=depth和y=depth planeX.constant = -depth; planeY.constant = -depth; });
完整修改后的代码
下面是调整后的完整代码,测试一下:当你把滑块调到0时,会切到球体中心,完美露出内部的分层截面;调大正数时,切割变浅,去掉的区域变小;调负数时,切割方向反转~
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Orbital</title> <style> #slider, #rotationSlider { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); } #rotationSlider { top: 40px; } </style> </head> <body> <input type="range" id="slider" min="-8" max="8" value="0" step="0.1"> <input type="range" id="rotationSlider" min="0" max="0.1" value="0.01" step="0.001"> <script type="module"> import * as THREE from "https://esm.sh/three@0.152.2"; import { OrbitControls } from "https://esm.sh/three@0.152.2/examples/jsm/controls/OrbitControls.js"; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 30; const renderer = new THREE.WebGLRenderer(); renderer.localClippingEnabled = true; renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.25; controls.enableZoom = true; const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(5, 5, 5).normalize(); scene.add(directionalLight); // 修正后的裁剪平面设置 const cut = 0; const planeX = new THREE.Plane(new THREE.Vector3(1, 0, 0), -cut); const planeY = new THREE.Plane(new THREE.Vector3(0, 1, 0), -cut); const planeHelperX = new THREE.PlaneHelper(planeX, 20, 0xff0000); const planeHelperY = new THREE.PlaneHelper(planeY, 20, 0x00ff00); scene.add(planeHelperX); scene.add(planeHelperY); // Add additional spheres const numSpheres = 6; const radius = 3; const spheres = []; for (let i = 0; i < numSpheres; i++) { const colors = [0xff0000, 0xff3b3b, 0xff6b6b, 0xff9292, 0xffbdbd, 0xfffafa]; const material = new THREE.MeshPhongMaterial({ color: colors[i], side: THREE.DoubleSide, wireframe: false, transparent: false, opacity: 1-(i/numSpheres), clippingPlanes: [ planeX, planeY ], clipShadows: true }); const geometry = new THREE.SphereGeometry(radius + i, 64, 64); const sphere = new THREE.Mesh(geometry, material); sphere.position.set(0, 0, 0); spheres.push(sphere); scene.add(sphere); } // Create particles as small spheres const particleCountX = 150; const particleCountY = 150; const particleGeometry = new THREE.SphereGeometry(0.05, 8, 8); const particleMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff, transparent:true, opacity:0.2 }); const particlesMatrix = []; const particleAngles = []; for (let j = 0; j < particleCountX; j++) { particlesMatrix[j] = []; for (let k = 0; k < particleCountY; k++) { const particle = new THREE.Mesh(particleGeometry, particleMaterial); particlesMatrix[j][k] = particle; particleAngles.push({ angleX: (j / particleCountX) * Math.PI * 2, angleY: (k / particleCountY) * Math.PI * 2 }); scene.add(particle); } } // 修正后的滑块控制 slider.addEventListener('input', function() { const depth = parseFloat(this.value); planeX.constant = -depth; planeY.constant = -depth; }); const rotationSlider = document.getElementById('rotationSlider'); let rotationSpeed = parseFloat(rotationSlider.value); rotationSlider.addEventListener('input', function() { rotationSpeed = parseFloat(this.value); }); let clock = new THREE.Clock(); let timeScale = 10; // Animation loop function animate() { requestAnimationFrame(animate); let deltaTime = clock.getDelta() * timeScale; // Update particles to follow spheres for (let j = 0; j < particleCountX; j++) { for (let k = 0; k < particleCountY; k++) { const particle = particlesMatrix[j][k]; const sphere = spheres[(j + k) % spheres.length]; const angles = particleAngles[j * particleCountY + k]; angles.angleX += rotationSpeed * deltaTime; angles.angleY += rotationSpeed * deltaTime; const x = sphere.position.x + (radius + (j + k) % numSpheres) * Math.cos(angles.angleX) * Math.sin(angles.angleY); const y = sphere.position.y + (radius + (j + k) % numSpheres) * Math.sin(angles.angleX) * Math.sin(angles.angleY); const z = sphere.position.z + (radius + (j + k) % numSpheres) * Math.cos(angles.angleY); particle.position.set(x, y, z); } } // Rotate spheres spheres.forEach(sphere => { sphere.rotation.y += rotationSpeed * deltaTime; }); controls.update(); renderer.render(scene, camera); } animate(); </script> </body> </html>
额外小调整
我还把滑块的max改成了8(因为你最大的球体半径是3+5=8),这样滑块调到最大值时,平面刚好切到最外层球体的表面,不会出现无效的裁剪;另外把两个平面助手的颜色改成了红和绿,方便你区分两个平面的位置。
现在你拖动滑块就能完美调整切割深度,看到你想要的四分之一切割效果啦~
备注:内容来源于stack exchange,提问作者Simon
相关产品推荐
相关产品推荐

