誰かが、乗法/連結における列と行の主な関係(の理由)の意味を説明できますか?


11

私はビューマトリックスと投影マトリックスの作成方法を学び、マトリックスの2つの標準について混乱しているため、実装が困難になり続けています。
私は行列を乗算する方法を知っています。乗算の前に転置すると結果が完全に変わるため、異なる順序で乗算する必要があることがわかります。

私が理解していないのは「表記規則」だけの意味です - ここここの記事から、著者はマトリックスの保存方法やGPUへの転送方法に違いはないと主張しているようですが、2番目その行列は明らかに、行優先のメモリでのレイアウト方法と同等ではありません。また、プログラム内の入力されたマトリックスを見ると、4番目、8番目、12番目の要素を占める翻訳コンポーネントが表示されています。

とすれば:

「列優先の行列で後乗算すると、行優先の行列で前乗算と同じ結果が得られます。」

なぜ次のコードスニペットで:

        Matrix4 r = t3 * t2 * t1;
        Matrix4 r2 = t1.Transpose() * t2.Transpose() * t3.Transpose();

DOESのR = R2!理由のためにPOS3 = POSを行います!

        Vector4 pos = wvpM * new Vector4(0f, 15f, 15f, 1);
        Vector4 pos3 = wvpM.Transpose() * new Vector4(0f, 15f, 15f, 1);

乗算プロセスは、行列が行メジャーか列メジャーかによって異なりますか、それとも次数のみですか(同等の効果を得るためですか?)

これがさらに明確になるのを助けていない1つのことは、DirectXに提供されると、列の主要なWVPマトリックスがHLSL呼び出しで頂点を正常に変換するために正常に使用されることです:mul(vector、matrix)これにより、ベクトルは次のように扱われます。 row-major、それで私の数学ライブラリによって提供される列のmajor行列はどのように機能しますか?



回答:


11

私のプログラムで入力された行列を見ると、4番目、8番目、12番目の要素を占める翻訳コンポーネントが表示されます。

始める前に、理解することが重要です。これは、行列が優先であることを意味します。したがって、この質問に答えます。

列メジャーWVP行列は、HLSL呼び出しで頂点を変換するために正常に使用されます:mul(vector、matrix)これにより、ベクトルが行優先として扱われるようになります。数学ライブラリによって提供される列メジャー行列はどのように機能しますか?

非常に単純です。行列は行優先です。

多くの人々が行優先または転置行列を使用しているため、行列がそのように自然に方向付けられていないことを忘れています。したがって、彼らは次のように翻訳マトリックスを見る:

1 0 0 0
0 1 0 0
0 0 1 0
x y z 1

これは転置された平行移動行列です。これは、通常の変換行列がどのように見えるかではありません。翻訳は第四に行くの列ではなく、4行目。時々、あなたはこれを完全にゴミである教科書で見ることさえします。

配列内の行列が行優先か列優先かを知るのは簡単です。行優先の場合、翻訳は3、7、11番目のインデックスに格納されます。列優先の場合、翻訳は12、13、および14番目のインデックスに格納されます。もちろんゼロベースのインデックス。

混乱は、実際には行優先の行列を使用しているのに、列優先の行列を使用していると信じていることに起因します。

行メジャーと列メジャーは表記規則であるという記述は完全に真です。行列の乗算と行列/ベクトルの乗算の仕組みは、規則に関係なく同じです。

変化するのは結果の意味です。

結局のところ、4x4マトリックスは、単なる4x4グリッドの数値です。座標系の変更を参照する必要はありません。ただし、特定のマトリックスに意味を割り当てたら、そこに何が格納され、どのように使用するかを知る必要があります。

上記で示した翻訳マトリックスをご覧ください。これは有効な行列です。次float[16]の2つの方法のいずれかでそのマトリックスを保存できます。

float row_major_t[16] =    {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1};
float column_major_t[16] = {1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1};

しかし、翻訳が間違った場所にあるため、この翻訳マトリックスは間違っていると言いました。具体的には、次のように変換行列を構築する方法の標準的な規則に関連して転置されると述べました。

1 0 0 x
0 1 0 y
0 0 1 z
0 0 0 1

これらがどのように保存されるか見てみましょう:

float row_major[16] =    {1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1};
float column_major[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1};

予告column_majorまったく同じようにrow_major_t。したがって、適切な変換行列を取得して列優先として保存すると、その行列を転置して行優先として保存するのと同じことになります。

これは、表記規則のみであることを意味します。実際には、メモリの格納と転置という2つの規則があります。メモリストレージは列と行が主ですが、転置は通常と転置です。

行優先の順序で生成された行列がある場合、その行列の列優先の等価物を転置することで同じ効果を得ることができます。およびその逆。

行列の乗算は1つの方法でのみ実行できます。2つの行列が特定の順序で与えられた場合、特定の値を乗算して結果を保存します。現在、A*B != B*Aですが、の実際のソースコードA*Bはのコードと同じですB*A。どちらも同じコードを実行して出力を計算します。

行列乗算コードは、行列が列優先順または行優先順のどちらで格納されているかを考慮しません。

同じことは、ベクトル/行列乗算についても言えません。そして、これが理由です。

ベクトル/行列の乗算は誤りです。それはできません。ただし、行列に別の行列を掛けることはできます。したがって、ベクトルが行列であると仮定すると、単純に行列/行列の乗算を実行するだけで、ベクトル/行列の乗算を効率的に実行できます。

4Dベクトルは、列ベクトルまたは行ベクトルと見なすことができます。つまり、4Dベクトルは4x1マトリックス(マトリックス表記では行カウントが最初に来る)または1x4マトリックスと考えることができます。

しかし、これが問題です。2つの行列AとBが与えられたA*B場合、Aの列数がBの行数と同じである場合にのみ定義されます。したがって、Aが4x4行列の場合、Bは4行の行列でなければなりません。初期化。したがって、A*xx は行ベクトルです。同様に、x*Axが列ベクトルの場合は実行できません。

このため、ほとんどの行列数学ライブラリはこの仮定を行っています。ベクトルに行列を掛けると、意味のないものではなく、実際に機能する乗算を実行することになります。

任意の4Dベクトルxについて、以下を定義します。C列ベクトルでなければならない行列の形x、及びR行ベクトルでなければならないマトリックスの形態x。これを考えると、4x4マトリックスAの場合、A*CAに列ベクトルを乗算したマトリックスを表しますx。またR*A、行ベクトルxにAを掛けた行列を表します。

しかし、厳密な行列計算を使用してこれを見ると、これらは同等でないことがわかります。と同じにR*A することはできませんA*C。これは、行ベクトルが列ベクトルと同じではないためです。それらは同じマトリックスではないため、同じ結果は生成されません。

ただし、それらは1つの方法で関連しています。それは事実ですR != C。ただし、Tが転置演算である場合も同様です。2つの行列は、互いに転置されます。R = CT

ここに面白い事実があります。ベクトルは行列として扱われるので、ベクトルにも列対行主記憶域の問題があります。問題は、どちらも同じように見えることです。floatの配列は同じであるため、データを見ただけではRとCの違いはわかりません。唯一の違いを見分けるための方法は、それらが使用されている方法です。

2つの行列AとBがあり、Aが行優先として、Bが列優先として格納されている場合、それらを乗算してもまったく意味がありません。結果としてナンセンスになります。まあ、そうでもない。数学的には、を実行することと同じです。または; それらは数学的に同一です。AT*BA*BT

したがって、行列の乗算は、2つの行列(およびベクトル/行列の乗算は行列の乗算のみ)が同じ主な順序で格納されている場合にのみ意味があります。

それで、ベクトルは列優先ですか、行優先ですか?前に述べたように、両方です。列マトリックスとして使用される場合にのみ列メジャーであり、行マトリックスとして使用される場合は行メジャーです。

したがって、列Aである行列Aがある場合、x*A何も意味しません。まあ、これも意味しますが、それはあなたが本当に望んでいたことではありません。同様に、が行優先の場合、転置乗算を行います。x*ATA*xA

したがって、ベクトル/行列乗算の順序はありませんデータのあなたの主要な順序に応じて、変更を(そして、あなたは転置行列を使用しているかどうか)。

次のコードスニペットでr!= r2を実​​行する理由

コードが壊れていてバグがあるからです。数学的には、。この結果が得られない場合は、等価テストが間違っている(浮動小数点の精度の問題)か、行列乗算コードが壊れています。A * (B * C) == (CT * BT) * AT

なぜpos3!= pos for

それは意味がないからです。真実であるための唯一の方法はifでしょう。そして、それは対称行列にのみ当てはまります。A * t == AT * tA == AT


@Nicol、すべてが今クリックし始めています。私のライブラリ(Axiomから取得)が列優先(およびすべての乗算順序などがこれに準拠)を宣言しているにもかかわらず、メモリレイアウトは行です-major(変換インデックスとHLSLが非転置行列を使用して正しく機能するという事実による判断); しかし、これがどのように矛盾していないのか、今はわかります。どうもありがとうございました!
sebf

2
「通常の変換行列はこんな感じではない」や「まったくゴミだ」などと言って、私はほぼ-1を出しました。次に、あなたは続けて、それらが完全に同等である理由をうまく説明し、したがってどちらもより「自然」ではありません。その小さなナンセンスを最初から削除してみませんか?あなたの答えの残りは実際にはかなり良いです。(また、興味がある人:steve.hollasch.net/cgindex/math/matrix/column-vec.html
imre

2
@imre:ナンセンスではないから。2つの規則があると混乱するので、規則は重要です。数学者たちはずっと昔に行列の慣習を決めた。「転置行列」(標準から転置されたために名前が付けられたもの)は、その規則に違反しています。これらは同等であるため、ユーザーに実際のメリットはありません。そして、それらは異なり、誤用される可能性があるため、混乱を招きます。言い換えると、転置行列が存在しない場合、OPはこれを要求しなかったでしょう。したがって、この代替規則は混乱を招きます。
Nicol Bolas、2011年

1
@Nicol:12-13-14で変換された行列は、行優先である可能性があります-行ベクトルを使用する場合(そしてvMとして乗算する場合)。DirectXを参照してください。または、列ベクトル(Mv、O​​penGL)で使用される列優先として表示できます。それは本当に同じです。逆に、行列に3-7-11の変換がある場合、列ベクトルを持つ行優先の行列、または行ベクトルを持つ列優先のいずれかとして表示できます。確かに12-13-14バージョンの方が一般的ですが、私の意見では、1)実際には標準ではなく、2)column-majorと呼ぶことは必ずしもそうではないため、誤解を招く可能性があります。
11

1
@imre:標準です。実際の訓練を受けた数学者に翻訳がどこに行くか尋ねると、彼らはそれが4列目に行くとあなたに言うでしょう。数学者行列を発明しました。彼らは慣習を定めたものです。
Nicol Bolas、2011年

3

ここでの作業には、2つの異なるコンベンションの選択肢があります。1つは、行ベクトルと列ベクトルのどちらを使用するかであり、これらの規則の行列は互いに転置されます。

もう1つは、行列を行優先順または列優先順でメモリに格納するかどうかです。「行メジャー」と「列メジャー」は、行ベクトル/列ベクトルの規則を説明するための正しい用語ではないことに注意してください。行優先と列優先のメモリレイアウトも転置によって異なります。

OpenGLは列ベクトルの規則と列優先の格納順序を使用し、D3Dは行ベクトルの規則と行優先の格納順序を使用します(少なくともD3DX、数学ライブラリはそうです)。そのため、2つの転置がキャンセルされ、結果がわかりますOpenGLとD3Dの両方で同じメモリレイアウトが機能します。つまり、メモリに順番に格納された16個のフロートの同じリストは、両方のAPIで同じように機能します。

これは、「マトリックスの格納方法やGPUへの転送方法に違いはない」と言っている人々の意図するところかもしれません。

コードスニペットについては、製品の転置のルールが(ABC)^ T = C ^ TB ^ TA ^ Tであるため、r!= r2です。転置は、順序を逆にして乗算を介して分散します。したがって、あなたの場合、r == r2ではなく、r.Transpose()== r2を取得する必要があります。

同様に、転置したが乗算の順序を逆にしなかったため、pos!= pos3。wpvM * localPos == localPos * wvpM.Tranpose()を取得する必要があります。ベクトルは、行列の左側で乗算されると行ベクトルとして、行列の右側で乗算されると列ベクトルとして自動的に解釈されます。それ以外は、乗算の実行方法に変更はありません。

最後に、「私の列のメジャーWVPマトリックスは、HLSL呼び出しで頂点を変換するために正常に使用されています:mul(vector、matrix)」、これについては不明ですが、おそらく混乱/バグが原因でマトリックスが出てきました数学ライブラリはすでに転置されています。


1

3Dグラフィックでは、マトリックスを使用してベクトルと点の両方を変換します。平行移動マトリックスについて話しているという事実を考慮して、ポイントについてのみ説明します(マトリックスを使用してベクトルを平行移動することはできません。または、より良く言えば、同じベクトルを取得できます)。

マトリックス乗算第1の行列の列の数は、第1(あなたがMXKためanxm行列を乗算することができる)の行の数に等しくなければなりません。

点(またはベクトル)は3つのコンポーネント(x、y、z)で表され、行または列のように考えることができます。

列(寸法3 X 1):

| x |

| y |

| z |

または

行(寸法1 X 3):

| x、y、z |

優先する規則を選択できます。これは単なる規則です。それをTを変換行列と呼びましょう。最初の規則を選択した場合、行列の点pを乗算するには、後置乗算を使用する必要があります。

T * v(寸法3x3 * 3x1)

さもないと:

v * T(寸法1x3 * 3x3)

作者は、マトリックスの保存方法やGPUへの転送方法に違いはないと主張しているようです。

常に同じ規則を使用しても、違いはありません。異なる規則の行列が同じメモリ表現を持つという意味ではありませんが、2つの異なる規則で点を変換すると、同じ変換点が得られます。

p2 = B * A * p1; //最初の規則

p3 = p1 * A * B; // 2番目の規則

p2 == p3;


1

4番目、8番目、12番目の要素を占める翻訳コンポーネントが表示されます。が、マトリックスが「間違っている」ことを意味します。

変換コンポーネントは、常に変換行列のエントリ#13、#14、および#15として指定されます(配列の最初の要素を要素#1として数えます))。

行の主要な変換行列は次のようになります。

[ 2 2 2 1 ]   R00 R01 R02 0  
              R10 R11 R12 0 
              R20 R21 R22 0 
              t.x t.y t.z 1 

列の主要な変換行列は次のようになります。

 R00 R01 R02 t.x   2  
 R10 R11 R12 t.y   2 
 R20 R21 R22 t.z   2 
  0   0   0   1    1 

行の主要な行列は行を下って行くように指定されます

上記の行の主行列を線形配列として宣言すると、次のようになります。

ROW_MAJOR = { R00, R01, R02, 0,  // row 1 // very intuitive
              R10, R11, R12, 0,  // row 2
              R20, R21, R22, 0,  // row 3
              t.x, t.y, t.z, 1 } ; // row 4

それは自然なことのようです。通知のため、英語は「行優先」と書かれています-行列は、数学の場合とまったく同じように、上のテキストに表示されます。

そして、ここが混乱のポイントです。

列の主要な行列は列を下って行くように指定されます

つまり、列の主変換行列をコードの線形配列として指定するには、次のように記述する必要があります。

    COLUMN_MAJOR = {R00、R10、R20、0、 // COLUMN#1 //非常に直感的ではない
                     R01、R11、R21、0、
                     R02、R12、R22、0、
                     tx、ty、tz、1};

これは完全に直観に反していることに注意してください!! 列の主行列には、線形配列を初期化するときに列の下にエントリが指定されているため、最初の行

COLUMN_MAJOR = { R00, R10, R20, 0,

マトリックスの最初の列を指定します

 R00
 R10
 R20
  0 

そして、最初の行はなく、テキストの単純なレイアウトがあなたを信じさせるでしょう 指定された最初の4つの要素は実際には最初の列を表すため、コードに表示される列の主行列を精神的に転置する必要があります。これが、多くの人々がコードで行優先行列を好む理由だと思います(GO DIRECT3D !!咳)。

したがって、行の主行列と列の主行列のどちらを使用しているかに関係なく、変換コンポーネントは常に線形配列インデックス#13、#14、および#15(最初の要素は#1)にあります。

あなたのコードで何が起こりましたか、そしてそれはなぜ機能しますか?

あなたのコードで起こっていることは、あなたは列major-matrix yesを持っていますが、翻訳コンポーネントを間違った場所に置いています。行列を転置すると、エントリ#4はエントリ#13、エントリ#8は#13、エントリ#12は#15に移動します。そして、そこにあります。


0

簡単に言えば、違いの理由は、行列の乗算が可換ではないためです。数値の通常の乗算​​では、A * B = Cの場合、B * Aも= Cになります。これは、行列の場合とは異なります。そのため、行優先または列優先のどちらを選択するかが重要になります。

それ重要ではない理由は、最新のAPI(ここでは特にシェーダーについて話している)では、独自の規則を選択し、独自のシェーダーコードでその規則の正しい順序でマトリックスを乗算できるためです。APIはどちらか一方を強制しなくなりました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.