使用assimp的骨骼动画中的矩阵顺序

时间:2015-03-22 05:31:59

标签: c++ opengl matrix glsl assimp

我已经按照this教程获得了预期的装配模型的输出动画。本教程使用assimp,glsl和c ++从文件加载装配模型。然而,有些事情我无法弄清楚。 首先是assimp的变换矩阵是行主矩阵,本教程使用的是Matrix4f类,它使用那些变换矩阵,就像行主要顺序一样。 Matrix4f类的构造函数如下:

Matrix4f(const aiMatrix4x4& AssimpMatrix)
{
    m[0][0] = AssimpMatrix.a1; m[0][2] = AssimpMatrix.a2; m[0][2] = AssimpMatrix.a3; m[0][3] = AssimpMatrix.a4;
    m[1][0] = AssimpMatrix.b1; m[1][3] = AssimpMatrix.b2; m[1][2] = AssimpMatrix.b3; m[1][3] = AssimpMatrix.b4;
    m[2][0] = AssimpMatrix.c1; m[2][4] = AssimpMatrix.c2; m[2][2] = AssimpMatrix.c3; m[2][3] = AssimpMatrix.c4;
    m[3][0] = AssimpMatrix.d1; m[3][5] = AssimpMatrix.d2; m[3][2] = AssimpMatrix.d3; m[3][3] = AssimpMatrix.d4;
}

但是,在计算最终节点转换的教程中,计算完成时期望矩阵按列主要顺序排列,如下所示:

Matrix4f NodeTransformation;
NodeTransformation = TranslationM * RotationM * ScalingM;  //note here
Matrix4f GlobalTransformation = ParentTransform * NodeTransformation;

    if(m_BoneMapping.find(NodeName) != m_BoneMapping.end())
{
    unsigned int BoneIndex = m_BoneMapping[NodeName];
    m_BoneInfo[BoneIndex].FinalTransformation = m_GlobalInverseTransform * GlobalTransformation * m_BoneInfo[BoneIndex].BoneOffset;
m_BoneInfo[BoneIndex].NodeTransformation = GlobalTransformation;
}

最后,由于计算的矩阵是行主要顺序,因此通过在以下函数中设置GL_TRUE标志,在着色器中传递矩阵时指定它。然后,openGL知道它是行主要顺序,因为openGL本身使用列主要顺序。

void SetBoneTransform(unsigned int Index, const Matrix4f& Transform)
{
glUniformMatrix4fv(m_boneLocation[Index], 1, GL_TRUE, (const GLfloat*)Transform);
}

那么,考虑列主要顺序

,如何完成计算
transformation = translation * rotation * scale * vertices

产生正确的输出。我希望为了使计算成立,每个矩阵首先应转换为更改为列顺序,然后进行上述计算,最后再次转置以获得后行顺序矩阵,这也在此link中讨论。然而,这样做产生了可怕的输出。我在这里找不到什么东西吗?

2 个答案:

答案 0 :(得分:2)

你混淆了两件事:

  1. 数据在内存中的布局(行与列主要顺序)
  2. 操作的数学解释(乘法顺序等)
  3. 通常声称,当使用行主要与列主要时,必须转换事物并且矩阵乘法顺序可以反转。 但这不是真的

    在数学上,transpose(A*B) = transpose(B) * transpose(A)是真实的。然而,这与此无关,因为矩阵存储顺序与矩阵的数学解释无关且正交。

    我的意思是:在数学中,它被精确地定义为矩阵的行和列,并且每个元素可以由这两个“坐标”唯一地寻址。所有矩阵运算都是基于此约定定义的。例如,在C=A*B中,C的第一行和第一列中的元素被计算为第一行A的点积(转换为列向量)以及B的第一列。

    现在,矩阵存储顺序只定义了矩阵数据在内存中的布局方式。作为概括,我们可以定义一个函数f(row,col),将每个(row, col)对映射到某个内存地址。我们现在可以使用f编写或矩阵函数,我们可以更改f以适应行主要,列主要或其他完全不同的东西(如Z曲线,如果我们想要一些乐趣)。

    无关紧要我们实际使用的f(只要映射是双射的),操作C=A*B将始终具有相同的结果。内存中的数据发生了哪些变化,但我们还必须使用f来插入数据。我们也可以编写一个简单的打印函数,也可以使用f来打印矩阵作为列x行中的2D数组,就像人们所期望的那样。

    当你在不同的布局中使用矩阵而不是设计矩阵函数的实现时,会产生混淆。

    如果你有一个矩阵库,它在内部保持colum-major布局,并以行主格式传递数据,就好像你之前改变了那个矩阵 - 而且只是在这一点上,事情变得搞砸了。 / p>

    为了使事情更加混乱,还有另一个与此相关的问题:矩阵*向量与向量*矩阵问题。有些人喜欢写x' = x * Mv'v是行向量),而其他人喜欢写y' = N *y(带列向量)。很明显,在数学上M*x = transpose((transpose(x) * transpose(M)),所以人们经常也会把它与行与列主要的顺序效应混淆 - 但它也完全独立于此。如果您想使用其中一个,那只是约定的问题。

    所以,最后回答你的问题:

    为那里创建的变换矩阵编写了多矩阵矩阵*向量的约定,因此Mparent * Mchild是正确的矩阵乘法顺序。

    到目前为止,内存中的实际数据布局根本不重要。它只是开始重要,因为现在,我们正在使用自己的约定连接不同的API。 GL的默认订单是列专业。使用的矩阵类是为行主存储器布局编写的。所以你只需转置,这样GL对该矩阵的解释就与你的其他库相匹配。

    替代方案不会转换它们并通过将由此创建的隐式操作合并到系统中来解释它 - 通过更改着色器中的乘法顺序,或者通过调整首先创建矩阵的操作。但是,我不建议使用该路径,因为生成的代码将完全不直观,因为最终,这意味着使用行主要解释在矩阵类中处理列主矩阵。

答案 1 :(得分:0)

是的,glm和assimp的内存布局相似:data.html

但是,根据文档页面:classai_matrix4x4t

assimp矩阵始终是行主要的,而glm矩阵始终是行主要的,这意味着您需要在转换时创建一个发送应答:

inline static Mat4 Assimp2Glm(const aiMatrix4x4& from)
        {
            return Mat4(
                (double)from.a1, (double)from.b1, (double)from.c1, (double)from.d1,
                (double)from.a2, (double)from.b2, (double)from.c2, (double)from.d2,
                (double)from.a3, (double)from.b3, (double)from.c3, (double)from.d3,
                (double)from.a4, (double)from.b4, (double)from.c4, (double)from.d4
            );
        }
inline static aiMatrix4x4 Glm2Assimp(const Mat4& from)
        {
            return aiMatrix4x4(from[0][0], from[1][0], from[2][0], from[3][0],
                from[0][1], from[1][1], from[2][1], from[3][1],
                from[0][2], from[1][2], from[2][2], from[3][2],
                from[0][3], from[1][3], from[2][3], from[3][3]
            );
        }

PS:在assimp中,abcd代表行,1234代表col。