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

Three.js:如何让平面在Y轴约束下尽可能朝向相机?

解决平面绕Y轴旋转朝向相机的角度计算问题

问题背景

初始平面位于X-Y平面,约束仅能绕(0,1,0)轴旋转,需要实现相机移动时平面尽可能朝向相机(类似带轴约束的精灵)。现有代码在相机逆时针移动时正常,顺时针移动时角度计算异常,无法正常跟随。

初始化代码

/////init
let planeGeometry;
let planeMaterial;
let planeMesh;
let rotationAngle = 0;
planeGeometry = new THREE.PlaneGeometry( 100, 100 );
planeMaterial = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } );
planeMesh = new THREE.Mesh( planeGeometry, planeMaterial );

逻辑处理代码

/////handle
console.log( camera.position )

// 获取平面在世界坐标系中的法向量
const localNormals = planegeometry.attributes.normal.array;
const localNormal = new THREE.Vector3();
localNormal.fromArray( localNormals, 0 );
planemesh.updateMatrixWorld( true );
const worldNormal = localNormal.clone().applyMatrix4( planemesh.matrixWorld ).normalize();

console.log( worldNormal );

// 获取相机看向该平面的视线向量
const cameraViewVector = new THREE.Vector3( camera.position.x, 0, camera.position.z ).normalize();

// 实际上不能这样计算上述两个向量的夹角,因为若平面不是特殊的X-Y平面,夹角计算需依赖三个向量(此处通过将视线向量Y值设为0简化)
const angle = cameraViewVector.angleTo( worldNormal );

const rotationAxis = new THREE.Vector3( 0, 1, 0 ).normalize();
rotationAngle += angle;
rotationAngle = rotationAngle % ( Math.PI * 2 );

console.log( angle )
console.log( rotationAngle )

const quaternion = new THREE.Quaternion().setFromAxisAngle( rotationAxis, rotationAngle );
planeMesh.setRotationFromQuaternion( quaternion );

问题根源与解决方法

1. 问题所在

angleTo()方法返回的是两个向量之间的最小夹角(范围0到π),无法区分顺时针和逆时针方向,导致顺时针移动时累加的角度方向错误,平面旋转异常。另外原代码存在变量名大小写错误(planegeometry应为planeGeometry,planemesh应为planeMesh),需先修正。

2. 正确实现思路

方法一:直接计算目标旋转角度(推荐)

因为约束绕Y轴旋转,将相机位置投影到X-Z平面,计算该点相对于原点的方位角,直接作为平面的Y轴旋转角度,平面正面会始终朝向相机在X-Z平面的投影方向:

/////handle
// 将相机位置投影到X-Z平面,计算方位角(atan2(x,z)对应绕Y轴的旋转角度)
const targetAngle = Math.atan2(camera.position.x, camera.position.z);
// 设置平面绕Y轴的旋转角度
planeMesh.rotation.y = targetAngle;

这种方法完全避免了角度累加的方向问题,代码简洁高效。

方法二:修正角度计算的方向

如果需要保留向量计算逻辑,通过叉乘判断旋转方向,给角度添加正确的正负符号:

/////handle
console.log( camera.position )

// 修正变量名大小写
const localNormals = planeGeometry.attributes.normal.array;
const localNormal = new THREE.Vector3();
localNormal.fromArray( localNormals, 0 );
planeMesh.updateMatrixWorld( true );
const worldNormal = localNormal.clone().applyMatrix4( planeMesh.matrixWorld ).normalize();

// 获取相机看向平面的视线向量(投影到X-Z平面)
const cameraViewVector = new THREE.Vector3( camera.position.x, 0, camera.position.z ).normalize();

// 计算叉乘判断旋转方向:叉乘结果的Y分量正负决定顺时针/逆时针
const cross = new THREE.Vector3();
cross.crossVectors(worldNormal, cameraViewVector);
const angleSign = cross.y >= 0 ? 1 : -1;

// 计算带方向的角度
const angle = cameraViewVector.angleTo(worldNormal) * angleSign;

const rotationAxis = new THREE.Vector3(0, 1, 0).normalize();
rotationAngle += angle;
// 保持角度在0~2π范围内
rotationAngle = (rotationAngle + Math.PI * 2) % (Math.PI * 2);

const quaternion = new THREE.Quaternion().setFromAxisAngle(rotationAxis, rotationAngle);
planeMesh.setRotationFromQuaternion(quaternion);

3. 额外优化

  • 平面默认朝向Z轴正方向,若需调整初始朝向,只需在初始化时设置planeMesh.rotation.y的初始值。
  • 若相机Y轴方向移动幅度大,可计算相机到平面中心的向量,再投影到垂直于Y轴的平面上,用该投影向量计算目标角度,确保朝向更精准。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 17:22:07