3

我已按照教程进行操作,并按预期获得了装配模型的输出动画。本教程使用 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

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

4

2 回答 2

3

你混淆了两件不同的事情:

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

人们经常声称,当使用行专业与列专业时,必须进行转置,并且矩阵乘法顺序必须颠倒。但事实并非如此

正确的是,在数学上,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 行的二维数组,正如典型人类所期望的那样。

当您在与设计矩阵函数的实现不同的布局中使用矩阵时,混淆来自这一事实。

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

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

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

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

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

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

于 2015-03-22T14:37:56.657 回答
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 代表 row 而 1234 代表 col。

于 2020-10-14T17:37:29.230 回答