使用四元数“观察”一个物体

11
所以我现在正在尝试创建一个函数,该函数将获取两个3D点A和B,并为我提供表示所需旋转的四元数,使得点A“看向”点B(如果您愿意,则点A的本地Z轴通过点B)。
我最初发现了这篇文章,其中排名第一的答案似乎为我提供了一个很好的起点。然后我实施了以下代码;不像原始答案建议的那样假设默认的(0,0,-1)方向,而是尝试提取表示相机实际方向的单位向量。
void Camera::LookAt(sf::Vector3<float> Target)
{
    ///Derived from pseudocode found here:
    ///https://dev59.com/OmnWa4cB1Zd3GeqP5epZ

    //Get the normalized vector from the camera position to Target
    sf::Vector3<float> VectorTo(Target.x - m_Position.x,
                                Target.y - m_Position.y,
                                Target.z - m_Position.z);
    //Get the length of VectorTo
    float VectorLength = sqrt(VectorTo.x*VectorTo.x +
                              VectorTo.y*VectorTo.y +
                              VectorTo.z*VectorTo.z);
    //Normalize VectorTo
    VectorTo.x /= VectorLength;
    VectorTo.y /= VectorLength;
    VectorTo.z /= VectorLength;

    //Straight-ahead vector
    sf::Vector3<float> LocalVector = m_Orientation.MultVect(sf::Vector3<float>(0, 0, -1));

    //Get the cross product as the axis of rotation
    sf::Vector3<float> Axis(VectorTo.y*LocalVector.z - VectorTo.z*LocalVector.y,
                            VectorTo.z*LocalVector.x - VectorTo.x*LocalVector.z,
                            VectorTo.x*LocalVector.y - VectorTo.y*LocalVector.x);

    //Get the dot product to find the angle
    float Angle = acos(VectorTo.x*LocalVector.x +
                       VectorTo.y*LocalVector.y +
                       VectorTo.z*LocalVector.z);

    //Determine whether or not the angle is positive
    //Get the cross product of the axis and the local vector
    sf::Vector3<float> ThirdVect(Axis.y*LocalVector.z - Axis.z*LocalVector.y,
                                 Axis.z*LocalVector.x - Axis.x*LocalVector.z,
                                 Axis.x*LocalVector.y - Axis.y*LocalVector.x);
    //If the dot product of that and the local vector is negative, so is the angle
    if (ThirdVect.x*VectorTo.x + ThirdVect.y*VectorTo.y + ThirdVect.z*VectorTo.z < 0)
    {
        Angle = -Angle;
    }

    //Finally, create a quaternion
    Quaternion AxisAngle;
    AxisAngle.FromAxisAngle(Angle, Axis.x, Axis.y, Axis.z);

    //And multiply it into the current orientation
    m_Orientation = AxisAngle * m_Orientation;
}

这个方案 几乎 可以工作。问题在于相机似乎只旋转了距离目标点的一半。如果我再次尝试旋转,它会执行剩余旋转的一半,如此循环往复,如果我按住“观察”按钮,相机的方向会越来越接近目标,但旋转速度也会不断减慢,因此它永远无法完全到达目标。
请注意,我不想使用gluLookAt(),因为我还需要让代码将对象 其他 指向彼此,而我的对象已经使用四元数进行定位。例如,我可能希望创建一个眼球,跟踪其前方移动的物体的位置,或者更新弹丸的方向以寻找其目标。

这可能并不回答你的问题,但是为什么你想要使用四元数来实现这个呢?你所尝试做的事情本质上与 gluLookAt() 相同,如果你想要一个不会连接到堆栈的版本,它的代码相当简单。 - JasonD
正如最后一句所述,我还希望能够定位非相机对象,使它们面向彼此。例如,我可能想要定位一个炮塔,使其始终指向目标;由于我将所有对象的方向存储为四元数,因此我需要找到一种更普遍的方法来解决这个问题。使用相机作为第一步仅仅是一种方便的方法。 - GarrickW
@yiding 没有,我为此编写了自己的四元数类(到目前为止,它已经很好地用于旋转和平移对象)。 - GarrickW
@GarrickW,你介意分享一份代码副本吗?我真的想计算旋转四元数以从(XYZ)1指向(XYZ)2:$(我的意思是这段代码使用的类)。 - Gizmo
@Gizmo 当然,我可以找到它 - 这里是四元数源代码的转储:http://pastebin.com/9JBMWizm。你要找的函数是FromLookVector()。请注意,这段代码优化得很差,注释可能不准确(我浏览了一遍并进行了一些更改,但我可能会错过某些东西);但我在我的项目中使用它,它可以工作。 - GarrickW
显示剩余3条评论
2个回答

4

在将 Axis 向量传递给FromAxisAngle之前,先对其进行规范化处理。


这解决了问题的一半;总是那些小细节... 当查找对象的方向等于单位四元数时,它可以正常工作,但否则摄像机会在锁定目标之前以螺旋形式旋转。然而,我能够通过在LookAt()开始时简单地将方向重置为单位四元数,然后在最后从轴和角度派生一个新的m_Orientation来避免这个问题;这个解决方案可以解决任何先前的旋转问题。谢谢! - GarrickW
哦,是的,叉积的长度取决于两个向量之间的角度。 - yiding

1

为什么要使用四元数?这只会让事情变得更加复杂,需要更多的计算。要设置一个矩阵:

calculate vector from observer to observed (which you're doing already)
normalise it (again, doing it already) = at
cross product this with the observer's up direction = right
normalise right
cross product at and right to get up

完成了。右、上和前向量是矩阵的第一、二和三行(或列,取决于您如何设置)。最后一行/列是对象的位置。

但看起来您想在几个帧中将现有矩阵转换为此新矩阵。SLERP也可以对矩阵进行这样的操作,就像四元数一样(当您深入研究数学时,这并不奇怪)。对于变换,存储初始和目标矩阵,然后在它们之间进行SLERP,每帧更改SLERP的数量(例如0、0.25、0.5、0.75、1.0-尽管非线性进展会更好看)。

不要忘记,您需要将四元数转换回矩阵,以便将其传递给渲染流水线(除非着色器中有处理四元数的新功能)。因此,由于转换过程,任何由于四元数使用而产生的效率都必须考虑到转换过程。


我并不原则上反对使用矩阵,但目前我的项目在每个环节都使用四元数,因此暂时坚持使用四元数更为简单。不过,感谢您的回复;如果将来开始更直接地实现矩阵(这总是有可能的),我会记住您的建议(灵活性从来不会有害)。 - GarrickW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接