You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Three.js:如何将裁剪Plane绑定到Object3D的变换上?

我来帮你解决这个Three.js里裁剪平面跟随Object3D的问题——你遇到的核心痛点是Plane.applyMatrix4()是叠加变换,而非重置到目标对象的当前状态,所以循环调用会导致变换累积。下面给你两种可行的解决方案,从易实现到性能最优:

解决方案1:在JS中正确同步Plane与Object3D的变换

要避免累积变换,关键是每次更新时都从Plane的初始状态出发,重新应用Object3D的当前世界矩阵,而不是在已有变换的Plane上叠加。

具体实现步骤:

  1. 先保存Plane的初始状态(比如你想要绑定的初始平面方向和位置):
// 示例:初始平面是Y轴向上、穿过原点的平面
const initialNormal = new THREE.Vector3(0, 1, 0);
const initialConstant = 0;
const clippingPlane = new THREE.Plane(initialNormal, initialConstant);
  1. 在动画循环(或onBeforeRender回调)中,先重置Plane到初始状态,再应用Object3D的matrixWorld:
function updateClippingPlane() {
  // 重置Plane到初始状态
  clippingPlane.set(initialNormal.x, initialNormal.y, initialNormal.z, initialConstant);
  // 应用Object3D的当前世界矩阵,得到跟随的平面
  clippingPlane.applyMatrix4(object.matrixWorld);
}

// 在动画循环里调用
function animate() {
  requestAnimationFrame(animate);
  
  // 这里可以修改Object3D的变换,比如旋转
  object.rotation.z += 0.01;
  
  // 更新裁剪平面
  updateClippingPlane();
  
  renderer.render(scene, camera);
}

或者更高效的方式,直接通过Object3D的世界空间法线和点来更新Plane:

function updateClippingPlane() {
  // 获取Object3D世界空间下的初始法线(需归一化,避免缩放影响)
  const worldNormal = initialNormal.clone().applyMatrix4(object.matrixWorld).normalize();
  // 获取Object3D世界空间下的位置(作为平面上的参考点)
  const worldPosition = new THREE.Vector3().setFromMatrixPosition(object.matrixWorld);
  // 用法线和点更新Plane
  clippingPlane.setFromNormalAndCoplanarPoint(worldNormal, worldPosition);
}

这种方式不会有累积变换的问题,因为每次都是基于Object3D的当前状态重新计算Plane的位置和方向。


解决方案2:Shader层面处理(性能最优)

如果你的场景中有大量网格或频繁的变换,在JS中更新Plane会带来额外的CPU开销,直接在Shader中处理裁剪平面的变换是更优的选择。核心思路是:将裁剪平面定义在Object3D的局部空间,然后在Shader中把顶点变换到该局部空间,再进行裁剪判断。

具体实现:

  1. 创建自定义ShaderMaterial,传递Object3D的世界逆矩阵(用于将顶点从世界空间转换到Object3D局部空间):
// 局部空间的裁剪平面,格式为(nx, ny, nz, d),示例是Y轴向上的平面
const clippingPlaneLocal = new THREE.Vector4(0, 1, 0, 0);

const material = new THREE.ShaderMaterial({
  uniforms: {
    objectMatrixWorldInverse: { value: object.matrixWorldInverse },
    clippingPlaneLocal: { value: clippingPlaneLocal }
  },
  vertexShader: `
    uniform mat4 objectMatrixWorldInverse;
    varying vec3 vWorldPosition;

    void main() {
      // 计算顶点的世界空间位置,传递给片元着色器
      vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform mat4 objectMatrixWorldInverse;
    uniform vec4 clippingPlaneLocal;
    varying vec3 vWorldPosition;

    void main() {
      // 将世界空间顶点转换到Object3D的局部空间
      vec3 localPos = (objectMatrixWorldInverse * vec4(vWorldPosition, 1.0)).xyz;
      // 计算顶点到局部裁剪平面的距离(平面方程:dot(normal, pos) + d = 0)
      float dist = dot(clippingPlaneLocal.xyz, localPos) + clippingPlaneLocal.w;
      // 裁剪掉平面上方的部分(可根据需求调整符号)
      if (dist > 0.0) discard;

      // 这里替换成你的片元渲染逻辑
      gl_FragColor = vec4(0.8, 0.2, 0.2, 1.0);
    }
  `,
  transparent: true // 如果需要半透明效果,开启该属性
});
  1. 在动画循环中,只需要更新Object3D的世界逆矩阵即可:
function animate() {
  requestAnimationFrame(animate);
  
  object.rotation.z += 0.01;
  // 必须先更新Object3D的世界矩阵,才能获取正确的逆矩阵
  object.updateMatrixWorld();
  material.uniforms.objectMatrixWorldInverse.value.copy(object.matrixWorldInverse);
  
  renderer.render(scene, camera);
}

这种方式的优势是所有变换计算都在GPU中完成,避免了JS层面的Plane更新操作,性能更高效,尤其适合复杂场景。

内容的提问来源于stack exchange,提问作者stmaccarelli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:53:20