【问题标题】:matrix order in skeletal animation using assimp使用assimp的骨骼动画中的矩阵顺序
【发布时间】:2015-05-25 07:51:02
【问题描述】:

我已按照this 教程进行操作,并按预期获得了装配模型的输出动画。本教程使用 assimp、gslsl 和 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中进行了讨论。然而,这样做产生了可怕的输出。我在这里有什么遗漏吗?

【问题讨论】:

  • fyi,您从 a2、b2、c2 和 d2 进行的更新确实有些奇怪。向下看他们的第二列,它计数为 2,3,4,5——我认为它们都应该是 1。

标签: c++ opengl matrix glsl assimp


【解决方案1】:

你混淆了两个不同的东西:

  1. 数据在内存中的布局(行与列的主要顺序)
  2. 运算的数学解释(例如乘法顺序)

通常声称,在使用行主要与列主要时,必须转置事物并且矩阵乘法顺序必须颠倒。 但事实并非如此

从数学上讲,transpose(A*B) = transpose(B) * transpose(A) 是正确的。但是,这在这里无关紧要,因为矩阵存储顺序独立于矩阵的数学解释,并且与矩阵的数学解释正交。

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

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

无关我们实际使用什么f(只要映射是双射的),操作C=A*B 将始终具有相同的结果。改变的只是内存中的数据,但我们还必须使用f 来解释该数据。我们可以编写一个简单的打印函数,同样使用f,将矩阵打印为 x 行中的 2D 数组,如典型人类所期望的那样。

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

如果您有一个内部采用列优先布局的矩阵库,并以行优先格式传递数据,就好像您之前转换了该矩阵一样 - 只是在这一点上,事情就搞砸了。

更令人困惑的是,还有一个与此相关的问题:矩阵 * 向量 vs 向量 * 矩阵问题。有些人喜欢写x' = x * Mv'v 是行向量),而另一些人喜欢写y' = N *y(带有列向量)。很明显,从数学上讲,M*x = transpose((transpose(x) * transpose(M)),因此人们经常将其与行优先与列优先顺序效应混淆——但它也完全独立于此。如果您想使用其中一个或另一个,这只是约定的问题。

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

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

到目前为止,内存中的实际数据布局根本不重要。它只是开始变得重要,因为现在,我们正在连接一个不同的 API,有它自己的约定。 GL 的默认顺序是列优先。使用的矩阵类是为行优先的内存布局编写的。因此,此时您只需转置,以便 GL 对该矩阵的解释与您其他库的解释相匹配。

另一种方法是不转换它们,并通过将由此创建的隐式运算合并到系统中来解释这一点——要么通过更改着色器中的乘法顺序,要么通过调整首先创建矩阵的运算。但是,我不建议走这条路,因为生成的代码将完全不直观,因为最后,这意味着使用行优先解释来处理矩阵类中的列优先矩阵。

【讨论】:

    【解决方案2】:

    是的,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代表row,1234代表col。

    【讨论】:

      猜你喜欢
      • 2015-07-21
      • 2017-04-10
      • 2020-09-27
      • 2017-07-22
      • 2017-05-05
      • 2014-09-23
      • 2017-01-30
      • 2014-01-15
      • 2021-11-07
      相关资源
      最近更新 更多