3

このチュートリアルに従って、リグモデルの出力アニメーションを期待どおりに取得しました。このチュートリアルでは、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

正しい出力を生成します。計算が成り立つためには、各行列を最初に転置して列の順序に変更し、次に上記の計算を行い、最後に再度転置して逆行順序の行列を取得する必要があると予想しました。これについては、このリンクでも説明しています。しかし、そうすると恐ろしい出力が生成されました。ここに欠けているものはありますか?

4

2 に答える 2

3

あなたは2つの異なることを混同しています:

  1. データがメモリ内に持っているレイアウト (行と列の主要な順序)
  2. 演算の数学的解釈 (乗算順序など)

行優先対列優先で作業する場合、物事を転置し、行列乗算の順序を逆にする必要があるとよく言われます。しかし、これは正しくありません

正しいのは、数学的にはtranspose(A*B) = transpose(B) * transpose(A). ただし、行列の格納順序は行列の数学的解釈とは独立しており、直交しているため、ここでは関係ありません。

つまり、数学では、行列の行と列が何であるかが正確に定義されており、各要素はこれら 2 つの「座標」によって一意にアドレス指定できます。すべての行列演算は、この規則に基づいて定義されています。たとえば、 ではC=A*B、 の最初の行と の最初の列の要素は、(列ベクトルに転置された)の最初の行と の最初の列Cの内積として計算されます。AB

ここで、行列の格納順序は、行列データがメモリ内でどのようにレイアウトされるかを定義するだけです。一般化として、各ペアをメモリアドレスにf(row,col)マッピングする関数を定義できます。(row, col)を使用して関数を記述または行列化できるようになり、行優先、列優先、またはまったく別のもの (楽しみが必要な場合は Z オーダー曲線など) に適応するようにf変更できます。f

実際に何を使用してfも (マッピングが全単射である限り)、操作C=A*Bは常に同じ結果になります。変化するのはメモリ内のデータだけですが、fそのデータを解釈するためにも使用する必要があります。同様に を使用して単純な印刷関数を記述しf、典型的な人間が期待するように、行列を列 x 行の 2D 配列として印刷することができます。

混乱は、マトリックス関数の実装が設計されているレイアウトとは異なるレイアウトでマトリックスを使用する場合に発生します。

列優先のレイアウトを内部的に想定している行列ライブラリがあり、行優先の形式でデータを渡すと、その行列を以前に変換したかのようになり、この時点でのみ、問題が発生します。

さらに混乱させるために、これに関連する別の問題があります: 行列 * ベクトル対ベクトル * 行列の問題です。x' = x * M(行ベクトルを使用v'して)書くのが好きな人もいれば、 (列ベクトルを使用して) 書くのが好きな人もいます。数学的には明らかであるため、人々はこれを行優先と列優先の順序効果と混同することがよくありますが、それとは完全に独立しています。どちらか一方を使用するかどうかは、慣習の問題です。vy' = N *yM*x = transpose((transpose(x) * transpose(M))

それで、最後にあなたの質問に答えるために:

そこで作成された変換行列は、行列 * ベクトルを乗算する規則に従って記述されているため Mparent * Mchild、正しい行列乗算順序です。

ここまでは、メモリ内の実際のデータ レイアウトはまったく問題になりません。今、独自の規則を持つ別の API とインターフェイスしているため、それは問題になり始めています。GL のデフォルトの順序は列優先です。使用中の行列クラスは、行優先のメモリ レイアウト用に記述されています。したがって、この時点で転置するだけで、そのマトリックスの GL の解釈が他のライブラリの解釈と一致するようになります。

別の方法は、それらを変換せず、これによって作成された暗黙的な操作をシステムに組み込むことによって、シェーダーで乗算順序を変更するか、最初に行列を作成した操作を調整することによって、それを説明することです。ただし、最終的には、行優先の解釈を使用して行列クラスで列優先の行列を操作することになるため、結果のコードはまったく直感的ではないため、その方法をお勧めしません。

于 2015-03-22T14:37:56.657 に答える