Three.js如何创建始终位于相机前方的Raycaster相交平面
Three.js 射线求交平面随相机同步移动方案
问题描述
场景中已实现物体跟随鼠标朝向旋转的交互:通过Raycaster射线投射与固定平面求交,获取鼠标对应的三维空间坐标后,调用物体的lookAt方法完成旋转,初始视角下运行正常。
核心初始化代码如下:
this.intersectionPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 10); this.raycaster = new THREE.Raycaster(); this.mouse = new THREE.Vector2(); this.pointOfIntersection = new THREE.Vector3();
原有鼠标移动事件处理逻辑:
onDocumentMouseMove = (event) => { event.preventDefault(); this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1; this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; this.raycaster.setFromCamera(this.mouse, this.camera); this.raycaster.ray.intersectPlane(this.intersectionPlane, this.pointOfIntersection); let v3 = new THREE.Vector3(this.pointOfIntersection.x*0.05, this.pointOfIntersection.y*0.05, this.pointOfIntersection.z); if(this.pebbleLogo){ this.pebbleLogo.lookAt(v3); } if(this.videoWall){ this.videoWall.lookAt(v3); } }
存在的问题:
- 原有相交平面是世界空间下的固定平面,当相机动画飞行到z轴-20深度位置时会穿过该平面,导致射线求交逻辑失效
- 尝试调用
translate方法平移平面,无法稳定维持平面与相机的固定相对位置 THREE.Plane是纯数学对象,不属于Object3D类型,无法作为相机子物体加入场景层级,跟随相机同步变换的方案不生效
THREE.Plane的两个核心属性定义:
normal - 单位长度向量,定义平面法线方向,默认值(1, 0, 0) constant - 原点到平面的有符号距离,默认值0
可行解决方案
不需要新增场景物体,也不需要修改现有交互逻辑,只需要每次执行射线求交前,根据相机当前的位置和朝向动态更新平面的法线与constant值,即可让平面始终保持在相机正前方固定距离处,完全避免相机穿平面的问题。
具体修改逻辑:
- 提前定义平面到相机的固定距离(可根据场景尺度调整,比如取15单位,只要值在相机近裁剪面和远裁剪面范围内即可)
- 每次触发鼠标移动事件时,先获取相机当前的世界空间前方向量,作为平面的新法线
- 根据平面点法式公式,计算当前平面的constant值,保证平面沿法线方向、距离相机为预设的固定距离
- 增加求交结果校验,避免极端情况下求交返回null导致坐标异常
修改后的事件处理代码:
onDocumentMouseMove = (event) => { event.preventDefault(); this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1; this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // 动态更新相交平面,始终保持在相机正前方固定距离 const currentCamDir = new THREE.Vector3(); this.camera.getWorldDirection(currentCamDir); this.intersectionPlane.normal.copy(currentCamDir); const fixedPlaneDistance = 15; // 可根据场景尺度自行调整 this.intersectionPlane.constant = -this.camera.position.dot(currentCamDir) + fixedPlaneDistance; this.raycaster.setFromCamera(this.mouse, this.camera); // 校验求交结果,无效则直接返回 const hasIntersection = this.raycaster.ray.intersectPlane(this.intersectionPlane, this.pointOfIntersection); if (!hasIntersection) return; let v3 = new THREE.Vector3(this.pointOfIntersection.x*0.05, this.pointOfIntersection.y*0.05, this.pointOfIntersection.z); if(this.pebbleLogo){ this.pebbleLogo.lookAt(v3); } if(this.videoWall){ this.videoWall.lookAt(v3); } }
之前手动translate平面失效的核心原因是没有同步更新平面法线,相机发生旋转后平面朝向和相机视线方向不垂直,自然无法保持在相机正前方。该方案直接在数学层面同步平面的朝向和位置,和场景层级完全解耦,性能开销可以忽略。
内容的提问来源于stack exchange,提问作者Lee Probert
相关产品推荐
相关产品推荐

