【问题标题】:My SSE implementation of lookAt doesn't work我的 lookAt 的 SSE 实现不起作用
【发布时间】:2014-03-07 03:59:21
【问题描述】:

所以,我正在编写一个使用 SSE 内在函数的数学库,以便与我的 OpenGL 应用程序一起使用。现在我正在实现一些更重要的功能,比如lookAt,使用glm 库来检查正确性,但由于某种原因,我的lookAt 实现不能正常工作。

这里是源代码:

inline void lookAt(__m128 position, __m128 target, __m128 up)
{
    /* Get the target vector relative to the camera position */
    __m128 t = vec4::normalize3(_mm_sub_ps(target, position));
    __m128 u = vec4::normalize3(up);
    /* Get the right vector by crossing target and up. */
    __m128 r = vec4::normalize3(vec4::cross(t, u));
    /* Correct the up vector by crossing right and target. */
    u = vec4::cross(r, t);
    /* Negate the target vector. */
    t = _mm_sub_ps(_mm_setzero_ps(), t);

    /* Treat the right, up, and target vector as a matrix, and transpose it. */
    /* Conveniently, this also sets the w component of all four to 0.0f */
    _MM_TRANSPOSE4_PS(r, u, t, _mm_setr_ps(0.0f, 0.0f, 0.0f, 1.0f));

    vec4 pos = _mm_sub_ps(_mm_setzero_ps(), position);
    pos.w = 1.0f;

    /* Multiply our matrix by the transposed vectors. */
    mat4 temp;
    temp.col0 = r;
    temp.col1 = u;
    temp.col2 = t;
    temp.col3 = _mm_setr_ps(0.0f, 0.0f, 0.0f, 1.0f);

    multiply(temp);
    translate(pos);
}

我的矩阵是列优先的,内部存储为“__m128 col0, col1, col2, col3;”。

我是在阅读了 gluLookAt 的手册页 Here 后制作的。一旦我意识到右向量、上向量和目标向量看起来非常像行优先矩阵,我就可以很简单地将它们转置,这样我就可以将它们分配给旋转矩阵。

normalize3 的代码,如果有帮助的话:

inline static __m128 normalize3(const __m128& vec)
{
    __m128 v = _mm_mul_ps(vec, vec);
    v = _mm_add_ps(
        _mm_add_ps(
            _mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 0, 0, 0)),
            _mm_shuffle_ps(v, v, _MM_SHUFFLE(1, 1, 1, 1))),
        _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 2, 2, 2)));

    return _mm_mul_ps(vec, _mm_rsqrt_ps(v));
}

它通过忽略向量的 w 分量节省了几次调用。

我做错了什么?

这是一些示例输出。使用 position(5.0, 5.0, 0.0), target(10.0, 20.0, 55.0) 和 up (0.0, 1.0, 0.0),我得到:

来自 GLM:

  • [-0.9959] [ 0.0000] [ 0.0905] [ 4.9795]
  • [-0.0237] [ 0.9650] [-0.2610] [-4.7065]
  • [-0.0874] [-0.2621] [-0.9611] [1.7474]
  • [0.0000][0.0000][0.0000][1.0000]

从我的lookAt():

  • [-0.9959] [ 0.0000] [ 0.0905] [-5.0000]
  • [-0.0237] [ 0.9651] [-0.2610] [-5.0000]
  • [-0.0874] [-0.2621] [-0.9611] [0.0000]
  • [0.0000][0.0000][0.0000][1.0000]

似乎唯一的区别在于第三列,但老实说,我不确定两者中哪一个是正确的。我倾向于说 GLM 是正确的,因为它被设计为与 glu 版本相同。

编辑: 我刚刚发现了一些有趣的事情。如果我调用“translate(pos);”在调用“multiply(temp);”之前,我得到的矩阵与 glm 的完全相同。哪个是对的?根据 gluLookAt 上的 OpenGL 手册页,这(以及 glm)正在倒退。我是以前做对了,还是现在做对了?

【问题讨论】:

  • 能否包含实际的输出矩阵?这是调试此类内容的最佳起点;使用gluLookAt (...) 的输出作为参考。
  • 是的,我确实需要修复它,但在这种情况下,这并不重要,因为 _MM_TRANSPOSE4_PS 无论如何都会用 0 替换 w 分量。
  • 在尝试了一些不同的输出后,我的版本与应有的版本相差 0.000243。这只是舍入错误的情况吗?
  • 问题可能出在_mm_rsqrt_ps(v) 内在函数中。不够准确。

标签: c++ opengl matrix sse


【解决方案1】:

_mm_rsqrt_ps(v) 可能存在一个问题。这不是很准确。将其替换为_mm_div_ps(_mm_set1_ps(1.0f),_mm_sqrt_ps(v))。如果这样可以解决问题,那么您也许可以通过某种根部抛光来加速它Newton Raphson with SSE2 - can someone explain me these 3 lines

另一个建议是,您可以通过不执行水平操作(您在规范化函数中执行的操作)使您的函数对 SIMD 更加友好。您可以先转置,而不是在转置之前对向量进行归一化。这将向量从 (x,y,z,w) 获取到 (x,x,x,x), (y,y,y,y), (z,z,z,z), (w,w, w,w) - 结构数组 (AoS) 到数组结构 (SoA)。那么你只需要做 1.0f/sqrt(rr+uu+t*t) 即可归一化。

__m128 t = _mm_sub_ps(target, position));
__m128 u = up;
__m128 r = vec4::cross(t, u);
u = vec4::cross(r, t);
t = _mm_sub_ps(_mm_setzero_ps(), t);
_MM_TRANSPOSE4_PS(r, u, t, _mm_setr_ps(0.0f, 0.0f, 0.0f, 1.0f));  //AoS to SoA

//now normalize
__m128 den = _mm_add_ps(_mm_add_ps(_mm_mul_ps(r,r),_mm_mul_ps(u,u)), _mm_mul_ps(t,t));
__m128 norm = _mm_div_ps(_mm_set1_ps(1.0f), _mm_sqrt_ps(den));
r= _mm_mul_ps(norm,r); u =_mm_mul_ps(norm,u); t = _mm_mul_ps(norm,t);

norm 不是单个标量。它包含四种不同的归一化 (n1,n2,n3,n4),因此 norm*r = (n1*x1, n2*x2, n3*x3, n4*x4)。有关使用 SSE 进行矩阵乘法的有效方法,请参阅此链接

Efficient 4x4 matrix vector multiplication with SSE: horizontal add and dot product - what's the point?

【讨论】:

  • 该代码的问题在于,例如,当您规范化“r”时,您实际上并没有规范化“r”向量 - 因为它已被转置,“r”现在实际上是"rx, ux, tx, 0")。
  • 将 _mm_rsqrt_ps(v) 替换为 _mm_div_ps(_mm_set1_ps(1.0f), _mm_sqrt_ps(v)) 后,我不再遇到舍入错误。谢谢你的提示!不幸的是,第 4 列仍然与 glm 给我的不同,即使我使用了 multiply(temp);translate(-position);,这正是手册页所说的。
  • @HaydnV.Harach,很高兴我能提供帮助。是的,我在转置后所说的规范是愚蠢的。我从我的答案中删除了它。
  • @HaydnV.Harach,实际上,我所说的在转置后进行归一化是正确的。确实 r 不是 3D 矢量,但没关系。代码中的范数包含四个不同的标量 (n1,n2,n3,n4) 所以 norm*r = (n1*r.x, n2*u.x, n3*u.y, n4*0)。这样做效率更高。
【解决方案2】:

我发现了问题所在。我的乘法函数以错误的顺序将矩阵相乘。

【讨论】:

    猜你喜欢
    • 2015-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多