5

私は次の機能を持っています:

void Matrix::Scale(const float xScale, const float yScale, const float zScale)
{
    Matrix scaleMatrix;
    scaleMatrix.m_data[M11] = xScale;
    scaleMatrix.m_data[M22] = yScale;
    scaleMatrix.m_data[M33] = zScale;
    *this *= scaleMatrix;
}

void Matrix::Translate(const float xTranslation, const float yTranslation, const float zTranslation)
{
    Matrix translationMatrix;
    translationMatrix.m_data[M14] = xTranslation;
    translationMatrix.m_data[M24] = yTranslation;
    translationMatrix.m_data[M34] = zTranslation;
    *this *= translationMatrix;
}

そして、両方の関数の最後の行についてはわかりません。事前乗算または事後乗算 (つまり、現在行っていること) を行う必要があります。このクラスの使用にはどのような意味がありますか? 私はOpenGLでクラスを使用しているので、それとの類似点はおそらく役に立ちます。

編集:

私のシェーダーコードは次のようになります。

void main()
{
    gl_Position = vec4(v_xy, 0.0, 1.0) * v_ModelMatrix * v_ViewMatrix * v_ProjectionMatrix;
    f_uv = v_uv;
}

私の行列乗算関数は次のようになります。

// Row 1
result[M11] = lhs[M11] * rhs[M11]   +   lhs[M12] * rhs[M21]   +   lhs[M13] * rhs[M31]   +   lhs[M14] * rhs[M41];    // Column 1
result[M12] = lhs[M11] * rhs[M12]   +   lhs[M12] * rhs[M22]   +   lhs[M13] * rhs[M32]   +   lhs[M14] * rhs[M42];    // Column 2
result[M13] = lhs[M11] * rhs[M13]   +   lhs[M12] * rhs[M23]   +   lhs[M13] * rhs[M33]   +   lhs[M14] * rhs[M43];    // Column 3
result[M14] = lhs[M11] * rhs[M14]   +   lhs[M12] * rhs[M24]   +   lhs[M13] * rhs[M34]   +   lhs[M14] * rhs[M44];    // Column 4

// Row 2
result[M21] = lhs[M21] * rhs[M11]   +   lhs[M22] * rhs[M21]   +   lhs[M23] * rhs[M31]   +   lhs[M24] * rhs[M41];    // Column 1
result[M22] = lhs[M21] * rhs[M12]   +   lhs[M22] * rhs[M22]   +   lhs[M23] * rhs[M32]   +   lhs[M24] * rhs[M42];    // Column 2
result[M23] = lhs[M21] * rhs[M13]   +   lhs[M22] * rhs[M23]   +   lhs[M23] * rhs[M33]   +   lhs[M24] * rhs[M43];    // Column 3
result[M24] = lhs[M21] * rhs[M14]   +   lhs[M22] * rhs[M24]   +   lhs[M23] * rhs[M34]   +   lhs[M24] * rhs[M44];    // Column 4

// Row 3
result[M31] = lhs[M31] * rhs[M11]   +   lhs[M32] * rhs[M21]   +   lhs[M33] * rhs[M31]   +   lhs[M34] * rhs[M41];    // Column 1
result[M32] = lhs[M31] * rhs[M12]   +   lhs[M32] * rhs[M22]   +   lhs[M33] * rhs[M32]   +   lhs[M34] * rhs[M42];    // Column 2
result[M33] = lhs[M31] * rhs[M13]   +   lhs[M32] * rhs[M23]   +   lhs[M33] * rhs[M33]   +   lhs[M34] * rhs[M43];    // Column 3
result[M34] = lhs[M31] * rhs[M14]   +   lhs[M32] * rhs[M24]   +   lhs[M33] * rhs[M34]   +   lhs[M34] * rhs[M44];    // Column 4

// Row 4
result[M41] = lhs[M41] * rhs[M11]   +   lhs[M42] * rhs[M21]   +   lhs[M43] * rhs[M31]   +   lhs[M44] * rhs[M41];    // Column 1
result[M42] = lhs[M41] * rhs[M12]   +   lhs[M42] * rhs[M22]   +   lhs[M43] * rhs[M32]   +   lhs[M44] * rhs[M42];    // Column 2
result[M43] = lhs[M41] * rhs[M13]   +   lhs[M42] * rhs[M23]   +   lhs[M43] * rhs[M33]   +   lhs[M44] * rhs[M43];    // Column 3
result[M44] = lhs[M41] * rhs[M14]   +   lhs[M42] * rhs[M24]   +   lhs[M43] * rhs[M34]   +   lhs[M44] * rhs[M44];    // Column 4

行列を事後乗算する場合 (つまりviewMatrix = transform * viewMatrix;)、シェーダー コードは現在とは逆の順序で MVP を適用する必要があるという印象を受けました。

編集2:

http://scratchapixel.com/lessons/3d-basic-lessons/lesson-4-geometry/conventions-again-row-major-vs-column-major-vector/の要約表は、 OpenGL (列優先を示す) で事後乗算を使用していますが、私の行列は行優先としてメモリに配置されていますか?

4

3 に答える 3

21

ここで 2 つの問題が混在しているようです。これは、scratchapixel の Web ページが説明しようとしていることだと思います。あなたが参照しているページにある情報から、物事はかなり明確に見えますが、この種のものを頭に入れるのは難しいです. 理論 (ペンと紙を使って数学で何をするか) と、実装 (C++) で何をするかがあります。これらは 2 つの異なる問題です。

数学: 列優先または行優先の 2 つの表記法を使用できます。このWebページでもGraphicsMuncherが述べているように、紙に行主ベクトルを使用して、ベクトルと行列の乗算vMを記述する必要があります。ここで、vは行ベクトル(1x4)で、Mは4x4行列です。なぜなら、数学的にしか書けないからです[1x4]*[4x4]、その逆ではありません。同様に、列を使用する場合、ベクトルを垂直に書き下すか、[4x1] (4 行 1 列) の表記にする必要があります。したがって、行列を使用した乗算は [4x4][4x1] とのみ記述できます。行列はベクトルの前に置かれます: Mv. 最初の表記はポスト乗算と呼ばれ、2 番目の (Mv) はプリ乗算と呼ばれます (行列が前にある)。さて、GraphicsMuncher が述べたように、ベクトル(または点)を変換する必要がある場合は、紙に書き留めるときに、乗算の順序に注意を払う必要があります。行列 T で何かを変換し、R で回転してから S でスケーリングする場合、列優先の世界では、v' = S * R * T * v と書く必要があります。行優先の世界では、 v' = v * T * R * S と書きます。

それは理論のためです。

コンピューター: 次に、これを C++ で実装することを決定するポイントが来ます。これの良いところは、C++ が何も強制しないことです。メモリ内の行列の係数の値を好きなようにマップできます。また、コードを記述して、別の行列で行列の乗算を実行することもできます。同様に、ベクトル行列乗算の係数にアクセスする方法は完全にあなた次第です。

係数をメモリにマッピングする方法と、ベクトルを表すために数学的な観点から使用する必要がある規則について明確に区別する必要があります。これらは 2 つの独立した問題です。たとえば、あなたの場合、おそらくマトリックスクラスを16個の連続した浮動小数点数の配列として宣言します。それはいいです。ここで、係数 m14、m24、m34 は行列の変換部分 (Tx、Ty、Tz) を表すため、列と呼ばれる OpenGL 行列規則を使用するように指示されていても、「規則」は行優先であると想定します。選考科目。ここでの混乱は、メモリ内の係数のマッピングが、「列優先」行列を自分で作成している精神的な表現とは異なるという事実から生じています。「行」をコーディングします

重要なことは、行列を 3 つの軸と平行移動によって定義される座標系の表現と見なすことです。このデータをメモリ内のどこにどのように保存するかは、完全にあなた次第です。座標系の 3 つの軸を表す 3 つのベクトルが AX(x,y,z)、AY(x,y,z)、AZ(x,y,z) と名付けられ、並進ベクトルが (Tx 、Ty、Tz)、次に数学的に、列ベクトルを使用する場合 (ラテックスはサポートされていないと思います):

    AXx AYx AZx Tx 
M = AXy AYy AZy Ty
    AXz AYz AZz Tz
     0   0   0  1 

座標系の軸は縦書きです。行優先を使用する場合:

    AXx AXy AXz 0 
M = AYx AYy AYz 0
    AZx AZy AZz 0
     Tx  Ty  Tz 1

座標系の軸は横書きです。したがって、コンピュータの世界に関して言えば、これらの係数をメモリに格納する方法が問題になります。次のこともできます。

float m[16] = { AXx, AXy, AXz, 0, AYx, AYy, AYz, 0, AZx, AZy, AZz, 0, Tx, Ty, Tz, 1}; 

どの規則を使用しているかがわかりますか?いいえ。次のように書くこともできます。

float m[16] = { AXx, AXy, AXz, Tx, AYx, AYy, AYz, Ty, AZx, AZy, AZz, Tz, 0, 0, 0, 1}; 

また

float m[16] = { AXx, AYx, AZx, Tx, AXy, AYy, AZy, Ty, AXz, AYz, AZz, Tz, 0, 0, 0, 1};

繰り返しますが、これは、使用する「数学的」規則を特定するものではありません。16 個の係数をさまざまな方法でメモリに保存しているだけで、後で適切にアクセスできるように、その方法が何であるかを知っていれば問題ありません。ここで、ベクトルを行列で乗算すると、行または列の数学表記を使用しても同じベクトルが得られることに注意してください。したがって、実際に重要なことは、ベクトルの (x,y,z) 座標に行列の正しい係数を掛けることです。これには、「あなた」がどのように行列係数をメモリに格納することを決定したかを知る必要があります。

Vector3 vecMatMult (Vector3 v,
    float AXx, float AXy, float AXz, float Tx,
    float AYx, float AYy, float AYz, float Ty,
    float AZz, float AZy, float AZz, float Tz) {
    return Vector3(
        v.x * AXx + v.y * AYx + v.z * AZx + Tx,
        v.x * AXy + v.y * AYy + v.z * AZy + Ty,
        v.x * AXz + v.y * AYz + v.z * AZz + Tz
}

編集: 上記のコードは間違っていました。修正しました。

この関数を書いたのは、どの規則を使用しても、ベクトル * 行列の乗算の結果は、ベクトルの入力座標と座標系の軸座標 AX、AY、AZ (使用する記法、およびそれらをメモリに保存する方法に関係なく)。

使用する場合:

float m[16] = { AXx, AXy, AXz, 0, AYx, AYy, AYz, 0, AZx, AZy, AZz, 0, Tx, Ty, Tz, 1};

電話する必要があります:

vecMatMult(v, m[0], m[1], m[2], m[12], m[4], m[5], m[6], m[13], ...

使用する場合:

float m[16] = { AXx, AYx, AZx, Tx, AXy, AYy, AZy, Ty, AXz, AYz, AZz, Tz, 0, 0, 0, 1};

電話する必要があります:

vecMatMult(v, m[0], m[4], m[8], m[3], m[1], m[5], m[9], m[10], ...

それはあなたが使用する規則を教えてくれますか?いいえ。vec * mat 乗算を実行するときに、適切な場所で適切な係数を呼び出す必要があるだけです。そして、それがすべてであり、当惑するかもしれません。

mat * mat 乗算に関しては、少し異なります。行列を乗算する順序は同じではないと想定できます。したがって、R * S * T は T * S * R と同じではありません。順序は実際に重要です。「行優先」を使用する場合は、数学的に次のように記述する必要があります (表記法を使用)。

mt11 = ml11 * mr11 + ml12 * mr21 + m13 * m31 + ml14 * mr41

ここで、ml は左側の行列で、mr は右側の行列です: mt = ml * mr. ただし、ここでは 1D 配列に格納されている要素にアクセスしているとは言いたくないので、アクセス インデックスに角かっこ [] を使用していないことに注意してください。行列の係数について話しているだけです。これを C++ で記述したい場合は、上記のように係数をメモリに保存する方法にすべて依存します。

それが役に立てば幸い。

于 2013-07-27T11:54:35.640 に答える
2

行列は連想的です。つまり、

ABC = (AB)C = A(BC)

(AB vs BC)したがって、順序を同じに保つ限り、最初に実際に乗算する行列は関係ありません。前と後の乗算はABvsの問題ですBA

そうは言っても、この質問は、変換を希望どおりに実行したい場合に、スケーリング/回転/移動を乗算する順序を説明しています。列行列の代わりに行行列を使用する場合は、順序を逆にして各要素を転置します。これは、より良い説明を提供するスライドショーです (スライド 19 にジャンプ)。

行列に関して厳密に言えば、列行列はT * R * Sまたは Translate * Rotate * Scale の順序になります。したがって、恒等Iから始めると、事後乗算 (あなたがしていること) は正しいです。前乗算に変更すると、 XのLによる変換はL * Xになるため、T * R * Sを取得するには、呼び出しを実行する順序を反転させます。

于 2013-07-22T14:04:59.913 に答える
0

クラス、matrix を作成して、4x1 サイズの行列の実数を保持します。数値を配列に保持します。主に、ユーザーが入力する A と B の 2 つのオブジェクトを作成します。別のオブジェクト C を作成します。C は実際には A と B の合計です。2 つの行列オブジェクトを受け取り、それらの合計を別の行列として返す関数 add が必要です。行列 C=add(A,B); 画面上に 3 つのオブジェクトを適切な形式で表示します。C++ で

于 2014-04-06T17:24:34.057 に答える