简体   繁体   English

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

[英]matrix order in skeletal animation using assimp

I had followed this tutorial and got the output animation for a rigged model as expected.我遵循了教程,并按预期获得了装配模型的输出动画。 The tutorial uses assimp, glsl and c++ to load a rigged model from a file.本教程使用 assimp、glsl 和 C++ 从文件加载装配模型。 However, there were things that I couldn't figure out.然而,有些事情我想不通。 First thing is assimp's transformation matrix are row major matrices and the tutorial uses a Matrix4f class which uses those transformation matrices just as they are ie row major order.首先是assimp 的变换矩阵是行主矩阵,本教程使用Matrix4f 类,该类使用这些变换矩阵,就像它们是行主序一样。 The constructor of that Matrix4f class is as given:该 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;
}

However, in the tutorial for calculating the final node transformation, the calculations are done expecting the matrices to be in column major order, which is shown below:但是,在计算最终节点变换的教程中,计算是在期望矩阵按列主序排列的情况下完成的,如下所示:

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;
}

Finally, since the matrices calculated are in row major order, it is specified so while passing the matrices in the shader by setting GL_TRUE flag in the following function.最后,由于计算的矩阵按行主顺序排列,因此在着色器中传递矩阵时通过在以下函数中设置 GL_TRUE 标志来指定。 Then, openGL knows it is in row major order as openGL itself uses column major order.然后,openGL 知道它是按行主序排列的,因为 openGL 本身使用列主序。

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

So, how does the calculation done considering column major order那么,考虑列主要顺序的计算是如何完成的

transformation = translation * rotation * scale * vertices

yield a correct output.产生正确的输出。 I expected that for the calculation to hold true, each matrices should first be transposed to change to column order, followed by the above calculation and finally transposed again to obtain back row order matrix, which is also discussed in this link .我预计要使计算成立,首先应将每个矩阵转置以更改为列顺序,然后进行上述计算,最后再次转置以获得后排顺序矩阵,这也在此链接中讨论。 However, doing so produced a horrible output.然而,这样做产生了可怕的输出。 Is there something that I am missing here?有什么我在这里想念的吗?

You are confusing two different things:你混淆了两件不同的事情:

  1. the layout the data has in memory (row vs. column major order)数据在内存中的布局(行与列的主要顺序)
  2. the mathematical interpretation of the operations (things like multiplication order)运算的数学解释(比如乘法顺序)

It is often claimed that when working with row major vs. column major, things have to be transposed and matrix multipication order hase to be reversed.人们经常声称,在处理行专业与列专业时,事情必须转置,矩阵乘法顺序必须颠倒。 But this is not true .但事实并非如此

What is true is that mathematically, transpose(A*B) = transpose(B) * transpose(A) .正确的是,从数学上讲, transpose(A*B) = transpose(B) * transpose(A) However, that is irrelevant here, because the matrix storage order is independent of, and orthogonal to, the mathematical interpretation of the matrices.然而,这在这里无关紧要,因为矩阵存储顺序与矩阵的数学解释无关且正交。

What I mean by this is: In math, it is exactly defined what a row and a column of a matrix is, and each element can be uniquely addressed by these two "coordinates".我的意思是:在数学中,它确切地定义了矩阵的行和列是什么,并且每个元素都可以由这两个“坐标”唯一寻址。 All the matrix operations are defined based on this convention.所有的矩阵运算都是基于这个约定定义的。 For example, in C=A*B , the element in the first row and the first column of C , is calculated as the dot product of the first row of A (transposed to a column vector) and the first column of B .例如,在C=A*B ,第一行和第一列中的元素C ,被计算为的第一行的点积A (换位到一列向量)和第一列B

Now, the matrix storage order just defines how the matrix data is laid out in memory.现在,矩阵存储顺序只是定义了矩阵数据在内存中的布局方式。 As a generalization, we could define a function f(row,col) mapping each (row, col) pair to some memory address.作为概括,我们可以定义一个函数f(row,col)将每个(row, col)对映射到某个内存地址。 We now could write or matrix functions using f , and we could change f to adapt row-major, column-major or something completely else (like a Z order curve, if we want some fun).我们现在可以使用f编写或矩阵函数,我们可以更改f以适应行优先、列优先或完全其他的东西(例如 Z 阶曲线,如果我们想要一些乐趣)。

It doesn't matter what f we actually use (as long as the mapping is bijective), the operation C=A*B will always have the same result.不要紧什么f我们实际使用(只要映射是双射),操作C=A*B将始终具有相同的结果。 What changes is just the data in memory, but we have also to use f to interpet that data.改变的只是内存中的数据,但我们还必须使用f来插入这些数据。 We could just write a simple print function, also using f , to print the matrix as the 2D array in columns x rows as a typical human would expect.我们可以只编写一个简单的打印函数,也使用f ,将矩阵打印为列 x 行中的二维数组,就像典型的人类所期望的那样。

The confusion comes from this fact when you use a matrix in a different layout than the implementation of the matrix functions is designed on.当您在与矩阵函数的实现设计不同的布局中使用矩阵时,混淆来自这一事实。

If you have a matrix library which is internally assuimg colum-major layout, and pass in data in row-major format, it is as if you transformed that matrix before - and only at this point, things get screwed up.如果您有一个矩阵库,它在内部采用列主布局,并以行主格式传递数据,就好像您之前转换了该矩阵 - 只有在这一点上,事情才会搞砸。

To confuse things even more, there is another issue related to this: the matrix * vector vs vector * matrix issue.更令人困惑的是,还有另一个与此相关的问题:矩阵 * 向量 vs 向量 * 矩阵问题。 Some people like to write x' = x * M (with v' and v being row vectors), while others like to write y' = N *y (with column vectors).有些人喜欢写x' = x * Mv'v是行向量),而另一些人喜欢写y' = N *y (列向量)。 It is clear that mathematically, M*x = transpose((transpose(x) * transpose(M)) , so that people often also confuse this with row- vs column-major order effects - but it is also totally independent of that. It is just a matter of convention if you want to use the one or the other.很明显,从数学上讲, M*x = transpose((transpose(x) * transpose(M)) ,因此人们经常将其与行与列主序效应混淆 - 但它也完全独立于此。如果您想使用一个或另一个,这只是一个约定问题。

So, to finally answer your question:所以,最后回答你的问题:

The transformation matrices created there are written for the convention of multyplying matrix * vector, so that Mparent * Mchild is the correct matrix multiplication order.在那里创建的变换矩阵是为乘矩阵 * 向量的约定而编写的,因此Mparent * Mchild是正确的矩阵乘法顺序。

Up to this point, the actual data layout in memory does not matter at all .到目前为止,内存的实际数据布局根本无关紧要 It only begins to matter because now, we are interfacing a different API, with its own conventions.这才开始变得重要,因为现在,我们正在连接具有自己约定的不同 API。 GL's default order is column-major. GL 的默认顺序是 column-major。 The matrix class in use is written for row-major memory layout.使用的矩阵类是为行主内存布局而编写的。 So you just transpose at this point, so that GL's interpretation of that matrix matches your other library's.因此,此时您只需转置,以便 GL 对该矩阵的解释与您的其他库相匹配。

The alternative would be not convert them and account for that by incorporating the implicit operation created by this into the system - either by changing the multiplication order in the shader, or by adjusting the operations which created the matrix in the first place.另一种方法是不转换它们并通过将由此创建的隐式操作合并到系统中来解决这个问题——要么通过更改着色器中的乘法顺序,要么通过调整首先创建矩阵的操作。 However, I would not recommend going that path, because the resulting code will be totally unintuitive, because in the end, this would mean working with column-major matrices in a matrix class using a row-major interpretation.但是,我不建议走这条路,因为生成的代码将完全不直观,因为最终,这意味着使用行优先解释处理矩阵类中的列优先矩阵。

Yes, the memory layout is similar for glm and assimp : data.html是的,glm 和 assimp 的内存布局类似: data.html

But, according to the doc page : classai_matrix4x4t但是,根据文档页面: classai_matrix4x4t

The assimp matrix is always row-major whereas the glm matrix is always col-major meaning you need to create a transponse on conversion: 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: The abcd stands for row and 1234 stands for col in assimp. PS:abcd 代表行,1234 代表 assimp 中的 col。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM