旋转矩阵可以通过观察向量构造,观察向量可以是3D空间的两个或三个点。如果一个处于P1点的对象面向P2点,则观察向量就是P2-P1,如下图:
首先,前轴向量通过归一化的观察向量简单计算而来。
其次,左轴通过指定向上方向向量与前轴的差乘计算而来。向上方向向量用于确定对象的roll角度。并且她不必与前轴垂直。如果我们不考虑对象的roll旋转,我们可以使用(0,1,0)。这就是说对象始终向上站立着。
实际上,与前轴与左轴都正交的上轴向量可以动过另外前轴与左轴向量的差乘计算出来。为了拥有单位长度的向量,左轴与上轴在差乘之后需要归一化。
下面是从观察向量计算左轴、上轴与前轴的C++代码。第一段代码块是Vector3结构体变量的最简便实现。第二代码块从两点(位置与目标向量)计算3个轴。最后一个代码块从3点(位置、目标向量与向上向量)计算3轴。
// Vector3结构体的最简化实现 struct Vector3 { float x; float y; float z; Vector3() : x(0), y(0), z(0) {}; // 构造函数 Vector3(float x, float y, float z) : x(x), y(y), z(z) {}; // 函数 Vector3& normalize(); // Vector3 operator-(const Vector3& rhs) const; // 减法 Vector3 operator*(const Vector3& rhs) const; // 差乘 Vector3& operator*=(const float scale); // 缩放与更新 }; Vector3& Vector3::normalize() { float invLength = 1 / sqrtf(x*x + y*y + z*z); x *= invLength; y *= invLength; z *= invLength; return *this; } Vector3 Vector3::operator-(const Vector3& rhs) const { return Vector3(x-rhs.x, y-rhs.y, z-rhs.z); } Vector3 Vector3::cross(const Vector3& rhs) const { return Vector3(y*rhs.z - z*rhs.y, z*rhs.x - x*rhs.z, x*rhs.y - y*rhs.x); }
/////////////////////////////////////////////////////////////////////////////// // 从对象的位置与目标点计算变换轴 /////////////////////////////////////////////////////////////////////////////// void lookAtToAxes(const Vector3& position, const Vector3& target, Vector3& left, Vector3& up, Vector3& forward) { // 计算前向量 forward = target - position; forward.normalize(); // 基于前向量计算临时上向量 // 注意向上/下观察角度为90°的情况 // 例如:前向量在Y轴上 if(fabs(forward.x) < EPSILON && fabs(forward.z) < EPSILON) { // 前向量指向+Y轴 if(forward.y > 0) up = Vector3(0, 0, -1); // 前向量指向-Y轴 else up = Vector3(0, 0, 1); } // 通常情况上向量为直立的 else { up = Vector3(0, 1, 0); } // 计算左向量 left = up.cross(forward); // cross product left.normalize(); // 重新计算正交化的上向量 up = forward.cross(left); // 差乘 up.normalize(); }
/////////////////////////////////////////////////////////////////////////////// // 从位置、目标与向上方向计算变换轴 /////////////////////////////////////////////////////////////////////////////// void lookAtToAxes(const Vector3& pos, const Vector3& target, const Vector3& upDir, Vector3& left, Vector3& up, Vector3& forward) { // 计算前向量 forward = target - pos; forward.normalize(); // 计算左向量 left = upDir.cross(forward); // 差乘 left.normalize(); // 计算正交化的上向量 up = forward.cross(left); // 差乘 up.normalize(); }