如何基于JOML数学库实现3D场景中2D投射物保持运动方向并仅绕X轴朝向相机?
解决方案:让投射物绕自身X轴旋转朝向相机(保持运动方向不变)
我来帮你搞定这个问题——你需要投射物维持原有运动方向,仅绕自身局部X轴旋转来朝向相机,用JOML可以按以下步骤实现:
1. 先理清核心向量
首先我们要明确几个关键的世界空间向量:
projectilePos:投射物当前位置(对应你的this.position)cameraPos:相机位置(你要传入的cameraPosition参数)moveDir:投射物的运动方向(就是你之前代码里的objectRay,记得先归一化)
2. 提取相机在垂直运动方向平面上的投影
因为我们不能改变运动方向,所以只需要关注相机方向在垂直于运动方向的平面上的分量:
// 计算相机相对于投射物的方向向量 Vector3f camToProjectile = new Vector3f(cameraPos).sub(projectilePos).normalize(); // 把相机方向投影到垂直于运动方向的平面上(去除运动方向上的分量) float dotProduct = camToProjectile.dot(moveDir); Vector3f camDirOnPlane = new Vector3f(camToProjectile) .sub(new Vector3f(moveDir).mul(dotProduct)) .normalize();
这一步的目的是过滤掉相机在投射物前进方向上的远近差异,只保留左右/上下的朝向信息。
3. 定义投射物的初始正面向量
假设你的投射物模型初始时,正面朝向是局部Y轴(如果你的模型初始正面是Z轴,直接替换即可),而且这个初始正面是垂直于运动方向的:
Vector3f initialForward = new Vector3f(0, 1, 0); // 处理初始正面和运动方向不垂直的情况(生成一个垂直的向量) if (Math.abs(initialForward.dot(moveDir)) > 0.001f) { // 用运动方向和世界X轴叉乘生成垂直向量 initialForward = new Vector3f(moveDir).cross(new Vector3f(1, 0, 0)).normalize(); // 如果运动方向和X轴平行,换用世界Y轴叉乘 if (initialForward.lengthSquared() < 0.001f) { initialForward = new Vector3f(moveDir).cross(new Vector3f(0, 1, 0)).normalize(); } }
4. 计算绕局部X轴的旋转角度
我们需要的旋转轴是投射物的局部X轴——也就是同时垂直于运动方向和初始正面的轴,然后计算初始正面到相机投影方向的旋转角度:
// 计算投射物的局部X轴(右手坐标系下,运动方向叉乘初始正面) Vector3f localXAxis = new Vector3f(moveDir).cross(initialForward).normalize(); // 计算初始正面和相机投影方向的夹角(点积求角度,注意clamp避免NaN) float dot = initialForward.dot(camDirOnPlane); dot = Math.max(-1.0f, Math.min(1.0f, dot)); float rotationAngle = (float) Math.acos(dot); // 判断旋转方向:确保是最短路径旋转 Vector3f crossCheck = new Vector3f(localXAxis).cross(initialForward); if (crossCheck.dot(camDirOnPlane) < 0) { rotationAngle = -rotationAngle; }
5. 合并旋转并应用到模型
现在把朝向运动方向的旋转和朝向相机的旋转合并,再应用到模型矩阵:
// 先获取你之前实现的朝向运动方向的旋转 Quaternionf moveRotation = findRotation(/* 你的投射物初始朝向向量 */, moveDir); // 创建绕局部X轴的旋转四元数 Quaternionf lookAtCamRot = new Quaternionf().rotateAxis(rotationAngle, localXAxis.x, localXAxis.y, localXAxis.z); // 合并旋转:先应用运动方向旋转,再应用朝向相机的旋转(顺序不能错!) Quaternionf finalRotation = new Quaternionf(moveRotation).mul(lookAtCamRot); // 应用到模型矩阵 this.lasers[i].getModel().identity() .scale(this.lasers[i].getScale()) .rotate(finalRotation) .translate(this.lasers[i].getPosition());
为什么你之前的方案没生效?
- 你之前直接绕世界X轴旋转(
rotate(..., 1,0,0))是错误的,因为需要绕投射物自身的局部X轴旋转,这个轴会随着投射物的运动方向变化,不是固定的世界轴。 - 变换向量到模型空间的方案问题在于没有分离出垂直于运动方向的分量,导致旋转时会改变投射物的前进朝向,违背了你的需求。
小提示
- 所有向量记得归一化,避免浮点数计算误差。
- 如果你的模型初始朝向不是Y轴,一定要调整
initialForward的定义,匹配你的模型初始姿态。 - 判断向量平行/垂直时,用小阈值(比如0.001f)和点积绝对值/长度平方比较,避免浮点数精度问题导致的异常。
内容的提问来源于stack exchange,提问作者Joezzz




