求垂直于平面法向量且俯视指向目标点的3D向量
计算垂直于平面法向量且指向目标点的向量
需求:计算一个垂直于平面法向量的向量,当从平面上方俯视时,该向量指向指定的目标点。
示例示意图:
已尝试的代码实现
我编写了如下代码尝试计算该向量,但未能得到正确结果,需要修正方案:
int sgn(float val) { return (0.0f < val) - (val < 0.0f); } Vector3 DetermineVector(Vector3 normalVector, Vector3 hitPosition, Vector3 goalPoint) { float deltaX = goalPoint.X - hitPosition.X; // 目标点到撞击点的X差 float deltaY = goalPoint.Y - hitPosition.Y; // 目标点到撞击点的Y差 float deltaZ = goalPoint.Z - hitPosition.Z; // 目标点到撞击点的Z差 // 计算向量的x、z分量(基于俯视视角) float length = sqrt(deltaX * deltaX + deltaZ * deltaZ); float x = deltaX / length; float z = deltaZ / length; // 计算向量的y分量 // 获取旋转方向的符号 float crossProductSum = -normalVector.X * deltaY + normalVector.Y * deltaX; int sign = sgn(crossProductSum); // 返回-1、0或1 // 计算法向量与UP向量(0,1,0)的夹角,旋转90度后得到y分量 float angle = acos(normalVector.Y); float y = cos(angle - 90) * sign; // 合并分量并归一化x、z分量 return Vector3(x / (1 - y), y, z / (1 - y)); }
修正方案
你的核心思路方向正确,但在角度单位处理、y分量推导、向量归一化逻辑上存在问题,以下是修正后的实现:
关键问题分析
- 角度单位混淆:
acos返回弧度值,但你直接用角度值90做减法,导致角度计算完全错误。 - y分量推导逻辑冗余:无需通过角度转换,利用向量垂直的数学性质直接计算更可靠。
- 归一化逻辑错误:最后一步的分量缩放不符合向量归一化的数学规则,会导致向量长度异常。
修正后的代码
#include <cmath> // 符号函数保持不变 int sgn(float val) { return (0.0f < val) - (val < 0.0f); } Vector3 DetermineVector(Vector3 normalVector, Vector3 hitPosition, Vector3 goalPoint) { // 1. 计算撞击点到目标点的方向向量 Vector3 toGoal = goalPoint - hitPosition; // 2. 提取俯视视角下的水平方向(忽略Y分量) Vector3 horizontalDir = {toGoal.X, 0.0f, toGoal.Z}; if (horizontalDir.LengthSquared() < 1e-6f) { // 目标点与撞击点垂直重合,返回默认水平向量(如X轴方向) return Vector3(1.0f, 0.0f, 0.0f); } horizontalDir.Normalize(); // 3. 计算平面内的基准切线向量(垂直于法向量) Vector3 planeTangent = CrossProduct(normalVector, Vector3(0.0f, 1.0f, 0.0f)); if (planeTangent.LengthSquared() < 1e-6f) { // 法向量与UP向量平行(平面为水平面),直接返回水平方向向量 return horizontalDir; } planeTangent.Normalize(); // 4. 将水平目标方向投影到平面切线向量上,得到平面内的初始方向 float dot = DotProduct(horizontalDir, planeTangent); Vector3 inPlaneDir = planeTangent * dot; // 5. 正交化确保向量严格垂直于法向量,最后归一化 Vector3 result = inPlaneDir - normalVector * DotProduct(inPlaneDir, normalVector); result.Normalize(); return result; } // 补充引擎未内置的基础向量运算函数 Vector3 CrossProduct(Vector3 a, Vector3 b) { return { a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X }; } float DotProduct(Vector3 a, Vector3 b) { return a.X * b.X + a.Y * b.Y + a.Z * b.Z; } void Vector3::Normalize() { float len = sqrt(X*X + Y*Y + Z*Z); if (len > 1e-6f) { X /= len; Y /= len; Z /= len; } } float Vector3::LengthSquared() { return X*X + Y*Y + Z*Z; }
修正逻辑说明
- 水平方向提取:先过滤Y分量得到俯视视角的目标方向,避免平面倾斜时Y分量干扰判断。
- 平面切线向量:通过法向量与UP向量的叉乘,得到平面内的基准垂直向量,确保该向量天然垂直于法向量。
- 投影与正交化:将水平目标方向投影到平面切线向量上,再通过正交化操作消除法向量方向的分量,保证最终向量严格垂直于平面法向量。
- 边界处理:添加了目标点垂直重合、法向量与UP平行的特殊情况处理,避免除以零或无效计算。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

