我有一个相机(在自定义 3D 引擎中),它接受四元数进行旋转变换。我有两个 3D 点,分别代表相机和要查看的对象。我想计算从相机到物体的四元数,同时尊重世界上轴。
这个问题要求同样的事情,但没有“向上”向量。所有三个答案都会导致相机指向正确的方向,但会滚动(如偏航/俯仰/滚动;想象一下在看某物时将头靠在耳朵上)。
我可以通过以下方式计算与所需坐标系匹配的向量的正交基:
lookAt = normalize(target - camera)
sideaxis = cross(lookAt, worldUp)
rotatedup = cross(sideaxis, lookAt)
如何从这三个向量创建四元数? 这个问题要求同样的事情......但不幸的是,唯一被接受的答案说〜“让我们假设你不关心滚动”,然后忽略上轴。我确实关心滚动。我不想忽略上轴。
之前的答案已经给出了使用角度的有效解决方案。这个答案将提供一种替代方法。
正交基向量,将其重命名为
F = lookAt, R = sideaxis, U = rotatedup
,直接形成3x3旋转矩阵的列,相当于你想要的四元数:
与向量相乘相当于使用该向量的分量作为相机基础中的坐标。
3x3 旋转矩阵可以转换为四元数,而无需转换为角度/使用昂贵的三角函数。下面是一个数值稳定的 C++ 代码片段,它可以执行此操作,返回归一化的四元数:
inline void CalculateRotation( Quaternion& q ) const {
float trace = a[0][0] + a[1][1] + a[2][2];
if( trace > 0 ) {
float s = 0.5f / sqrtf(trace + 1.0f);
q.w = 0.25f / s;
q.x = ( a[2][1] - a[1][2] ) * s;
q.y = ( a[0][2] - a[2][0] ) * s;
q.z = ( a[1][0] - a[0][1] ) * s;
} else {
if ( a[0][0] > a[1][1] && a[0][0] > a[2][2] ) {
float s = 2.0f * sqrtf( 1.0f + a[0][0] - a[1][1] - a[2][2]);
q.w = (a[2][1] - a[1][2] ) / s;
q.x = 0.25f * s;
q.y = (a[0][1] + a[1][0] ) / s;
q.z = (a[0][2] + a[2][0] ) / s;
} else if (a[1][1] > a[2][2]) {
float s = 2.0f * sqrtf( 1.0f + a[1][1] - a[0][0] - a[2][2]);
q.w = (a[0][2] - a[2][0] ) / s;
q.x = (a[0][1] + a[1][0] ) / s;
q.y = 0.25f * s;
q.z = (a[1][2] + a[2][1] ) / s;
} else {
float s = 2.0f * sqrtf( 1.0f + a[2][2] - a[0][0] - a[1][1] );
q.w = (a[1][0] - a[0][1] ) / s;
q.x = (a[0][2] + a[2][0] ) / s;
q.y = (a[1][2] + a[2][1] ) / s;
q.z = 0.25f * s;
}
}
}
来源:http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion
将其转换为适合您的情况当然只需将矩阵元素与相应的向量分量交换即可:
// your code from before
F = normalize(target - camera); // lookAt
R = normalize(cross(F, worldUp)); // sideaxis
U = cross(R, F); // rotatedup
// note that R needed to be re-normalized
// since F and worldUp are not necessary perpendicular
// so must remove the sin(angle) factor of the cross-product
// same not true for U because dot(R, F) = 0
// adapted source
Quaternion q;
double trace = R.x + U.y + F.z;
if (trace > 0.0) {
double s = 0.5 / sqrt(trace + 1.0);
q.w = 0.25 / s;
q.x = (U.z - F.y) * s;
q.y = (F.x - R.z) * s;
q.z = (R.y - U.x) * s;
} else {
if (R.x > U.y && R.x > F.z) {
double s = 2.0 * sqrt(1.0 + R.x - U.y - F.z);
q.w = (U.z - F.y) / s;
q.x = 0.25 * s;
q.y = (U.x + R.y) / s;
q.z = (F.x + R.z) / s;
} else if (U.y > F.z) {
double s = 2.0 * sqrt(1.0 + U.y - R.x - F.z);
q.w = (F.x - R.z) / s;
q.x = (U.x + R.y) / s;
q.y = 0.25 * s;
q.z = (F.y + U.z) / s;
} else {
double s = 2.0 * sqrt(1.0 + F.z - R.x - U.y);
q.w = (R.y - U.x) / s;
q.x = (F.x + R.z) / s;
q.y = (F.y + U.z) / s;
q.z = 0.25 * s;
}
}
(如果您使用 OpenGL,不用说交换
y
和
z
。)将操作一分为二。首先,围绕 worldUp 旋转。然后绕 sideAxis 旋转,现在它实际上与旋转后的 worldSide 平行。
轴1 = 世界向上
角度1 =(见下文)角度2 =(见下文)
Axis2 = 交叉(lookAt,worldUp)= sideAxis这些旋转中的每一个都对应于一个四元数,使用:
Q = cos(角度/2) + i * Axis_x * sin(角度/2) + j * Axis_y * sin(角度/2) + k * Axis_z * sin(角度/2)
将 Q1 和 Q2 相乘,即可得到所需的四元数。
角度详情:
设 P(worldUp) 为 worldUp 方向上的投影矩阵,即 P(worldUp).v = cos(worldUp,v).worldUp 或者使用 kets 和 bras,P(worldUp) = |worldUp >
< worldUp|. Let I be the identity matrix.
tmp1 = (I - P(worldUp)).lookAt
n1 = 归一化(tmp1)
请注意,无需计算超越函数。由于一对归一化向量的点积是角度的余弦,并假设
cos(t) = x
,我们有三角恒等式:
cos(t/2) = sqrt((1 + x)/2)
sin(t/2) = sqrt((1 - x)/2)
public static SoftQuaternion LookRotation(SoftVector3 forward, SoftVector3 up)
{
forward = SoftVector3.Normalize(forward);
// First matrix column
SoftVector3 sideAxis = SoftVector3.Normalize(SoftVector3.Cross(up, forward));
// Second matrix column
SoftVector3 rotatedUp = SoftVector3.Cross(forward, sideAxis);
// Third matrix column
SoftVector3 lookAt = forward;
// Sums of matrix main diagonal elements
SoftFloat trace1 = SoftFloat.One + sideAxis.X - rotatedUp.Y - lookAt.Z;
SoftFloat trace2 = SoftFloat.One - sideAxis.X + rotatedUp.Y - lookAt.Z;
SoftFloat trace3 = SoftFloat.One - sideAxis.X - rotatedUp.Y + lookAt.Z;
// If orthonormal vectors forms identity matrix, then return identity rotation
if (trace1 + trace2 + trace3 < SoftMath.CalculationsEpsilon)
{
return Identity;
}
// Choose largest diagonal
if (trace1 + SoftMath.CalculationsEpsilon > trace2 && trace1 + SoftMath.CalculationsEpsilon > trace3)
{
SoftFloat s = SoftMath.Sqrt(trace1) * (SoftFloat)2.0f;
return new SoftQuaternion(
(SoftFloat)0.25f * s,
(rotatedUp.X + sideAxis.Y) / s,
(lookAt.X + sideAxis.Z) / s,
(rotatedUp.Z - lookAt.Y) / s);
}
else if (trace2 + SoftMath.CalculationsEpsilon > trace1 && trace2 + SoftMath.CalculationsEpsilon > trace3)
{
SoftFloat s = SoftMath.Sqrt(trace2) * (SoftFloat)2.0f;
return new SoftQuaternion(
(rotatedUp.X + sideAxis.Y) / s,
(SoftFloat)0.25f * s,
(lookAt.Y + rotatedUp.Z) / s,
(lookAt.X - sideAxis.Z) / s);
}
else
{
SoftFloat s = SoftMath.Sqrt(trace3) * (SoftFloat)2.0f;
return new SoftQuaternion(
(lookAt.X + sideAxis.Z) / s,
(lookAt.Y + rotatedUp.Z) / s,
(SoftFloat)0.25f * s,
(sideAxis.Y - rotatedUp.X) / s);
}
}
这种实现基于对this
四元数的构造函数是(x, y, z, w)
extension simd_quatf {
public init(lookingFrom from: Vector3, to: Vector3, up: Vector3 = SCNNode.simdLocalUp) {
let front = simd_normalize(from - to)
let side = simd_normalize(simd_cross(up, front))
self.init(simd_float3x3(side, simd_cross(front, side), front))
}
}
如果对这 3 个向量进行归一化,它是旋转矩阵 3x3 的一个分量。所以只需将这个旋转矩阵转换为四元数即可。