Czy ktoś może wyjaśnić (powody) implikacje colum vs. major rzędów w mnożeniu / konkatenacji?

11

Staram się nauczyć, jak konstruować matryce widokowe i rzutowe, i ciągle napotykam trudności w mojej implementacji z powodu mojego zamieszania na temat dwóch standardów matryc.
Wiem, jak pomnożyć macierz, i widzę, że transpozycja przed pomnożeniem całkowicie zmieni wynik, stąd potrzeba pomnożenia w innej kolejności.

Nie rozumiem jednak, co należy rozumieć jedynie przez „konwencję notacyjną” - z artykułów tu i tutaj wydaje się, że autorzy twierdzą, że nie ma to znaczenia, w jaki sposób macierz jest przechowywana lub przenoszona do GPU, ale na drugim strona ta macierz najwyraźniej nie jest równoważna z tym, jak byłaby ułożona w pamięci dla wiersza większego; a jeśli spojrzę na zapełnioną matrycę w moim programie, widzę, że składniki tłumaczenia zajmują 4, 8 i 12 element.

Jeśli się uwzględni:

„pomnożenie końcowe za pomocą macierzy głównych kolumn daje ten sam wynik, co pomnożenie wstępne przez macierzy głównych rzędów”.

Dlaczego w poniższym fragmencie kodu:

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

Czy r! = R2 i dlaczego pos3! = Pos dla :

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

Czy proces mnożenia zmienia się w zależności od tego, czy macierze są rzędami czy kolumnami , czy też jest to kolejność (dla równoważnego efektu?)

Jedną z rzeczy, która nie pomaga, aby stało się to jaśniejsze, jest to, że po dostarczeniu do DirectX moja główna główna kolumna WVP jest z powodzeniem używana do transformacji wierzchołków za pomocą wywołania HLSL: mul (wektor, macierz), co powinno skutkować traktowaniem wektora jako row-major , więc jak może działać matryca główna kolumny dostarczona przez moją bibliotekę matematyczną?

sebf
źródło

Odpowiedzi:

11

jeśli spojrzę na zapełnioną macierz w moim programie, widzę, że komponenty tłumaczenia zajmują 4, 8 i 12 element.

Zanim zacznę, ważne jest, aby zrozumieć: oznacza to, że macierze są wiersze główne . Dlatego odpowiadasz na to pytanie:

moja główna matryca WVP głównej kolumny jest z powodzeniem używana do transformacji wierzchołków za pomocą wywołania HLSL: mul (wektor, macierz), co powinno spowodować, że wektor będzie traktowany jako główny wiersz, więc jak może działać macierz główna kolumny dostarczona przez moją bibliotekę matematyczną?

jest dość proste: macierze są ważniejsze od rzędów.

Tak wiele osób używa matryc głównych lub transponowanych, że zapominają, że macierze nie są w ten sposób naturalnie zorientowane. Widzą więc macierz tłumaczeń w następujący sposób:

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

To jest transponowana macierz tłumaczeń . Nie tak wygląda normalna macierz tłumaczeń. Tłumaczenie znajduje się w czwartej kolumnie , a nie w czwartym wierszu. Czasami widać to nawet w podręcznikach, co jest kompletnym śmieciem.

Łatwo jest ustalić, czy macierz w tablicy jest rzędem czy kolumną. Jeśli jest to wiersz główny, tłumaczenie jest przechowywane w indeksach 3, 7 i 11. Jeśli jest to kolumna główna, tłumaczenie jest przechowywane w indeksach 12, 13 i 14. Oczywiście wskaźniki o zerowej podstawie.

Twoje zamieszanie wynika z przekonania, że ​​używasz macierzy głównych kolumn, podczas gdy w rzeczywistości używasz macierzy głównych wierszy.

Stwierdzenie, że wiersz kontra główna kolumna jest tylko konwencją notacyjną, jest całkowicie prawdziwe. Mechanika mnożenia macierzy i mnożenia macierzy / wektora jest taka sama, niezależnie od konwencji.

Jakie zmiany mają znaczenie wyników.

W końcu macierz 4x4 to po prostu siatka liczb 4x4. Nie musi odnosić się do zmiany układu współrzędnych. Jednak po przypisaniu znaczenia konkretnej macierzy musisz teraz wiedzieć, co jest w niej przechowywane i jak z niej korzystać.

Weź matrycę tłumaczeń, którą pokazałem ci powyżej. To ważna matryca. Możesz przechowywać tę matrycę float[16]na jeden z dwóch sposobów:

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

Powiedziałem jednak, że ta matryca tłumaczeń jest błędna, ponieważ tłumaczenie znajduje się w niewłaściwym miejscu. Powiedziałem konkretnie, że jest transponowany w stosunku do standardowej konwencji, jak budować macierze tłumaczeń, które powinny wyglądać następująco:

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

Spójrzmy, jak są one przechowywane:

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

Zauważ, że column_majorjest dokładnie taki sam jak row_major_t. Tak więc, jeśli weźmiemy odpowiednią macierz translacji i zachowamy ją jako kolumnę główną, będzie to to samo, co transponowanie tej macierzy i przechowywanie jej jako major wiersza.

To właśnie oznacza bycie tylko konwencją notacyjną. Tak naprawdę istnieją dwa zestawy konwencji: pamięć i transpozycja. Pamięć jest kolumna kontra główny wiersz, a transpozycja jest normalna vs. transponowana.

Jeśli masz macierz, która została wygenerowana w kolejności major-wiersz, możesz uzyskać ten sam efekt, transponując równoważnik główny kolumny tej matrycy. I wzajemnie.

Mnożenie macierzy można wykonać tylko w jeden sposób: biorąc pod uwagę dwie macierze, w określonej kolejności, mnożymy pewne wartości razem i przechowujemy wyniki. Teraz, A*B != B*Aale rzeczywisty kod źródłowy A*Bjest taki sam jak kod dla B*A. Oba uruchamiają ten sam kod, aby obliczyć dane wyjściowe.

Kod mnożenia macierzy nie dba o to, czy macierze będą przechowywane w porządku kolumnowym, czy rządowym.

Tego samego nie można powiedzieć o mnożeniu wektora / macierzy. I oto dlaczego.

Mnożenie wektora / macierzy jest fałszem; nie da się tego zrobić. Jednakże, można pomnożyć macierz przez inną matrycę. Jeśli więc udajesz, że wektor jest macierzą, możesz efektywnie zwielokrotniać wektor / macierz, po prostu mnożąc macierz / macierz.

Wektor 4D można uznać za wektor kolumnowy lub wektor rzędowy. Oznacza to, że wektor 4D można traktować jako macierz 4x1 (pamiętaj: w notacji macierzowej liczba wierszy jest na pierwszym miejscu) lub macierz 1x4.

Ale o to chodzi: biorąc pod uwagę dwie macierze A i B, A*Bjest definiowane tylko wtedy, gdy liczba kolumn A jest taka sama jak liczba wierszy B. Dlatego jeśli A jest naszą macierzą 4x4, B musi być macierzą z 4 rzędami w tym. Dlatego nie można wykonać A*x, gdzie x jest wektorem wiersza . Podobnie nie można wykonać, x*Agdzie x jest wektorem kolumny.

Z tego powodu większość bibliotek matematycznych przyjmuje takie założenie: jeśli pomnożysz wektor razy macierz, naprawdę masz zamiar wykonać mnożenie, które faktycznie działa , a nie takie, które nie ma sensu.

Zdefiniujmy, dla dowolnego wektora 4D x: Cma postać macierzy wektorów kolumnowych xi Rma postać macierzy wektorów rzędowych x. Biorąc to pod uwagę, dla dowolnej macierzy 4x4 A A*Creprezentuje macierz mnożącą A przez wektor kolumny x. I R*Areprezentuje macierz mnożącą wektor wiersza xprzez A.

Ale jeśli spojrzymy na to za pomocą ścisłej matematyki macierzowej, zobaczymy, że nieone równoważne . R*A nie może być taki sam jak A*C. Jest tak, ponieważ wektor wiersza to nie to samo, co wektor kolumnowy. Nie są tą samą matrycą, więc nie dają takich samych wyników.

Są one jednak powiązane w jeden sposób. To prawda R != C. Prawdą jest jednak również to , że tam, gdzie T jest operacją transpozycji. Dwie macierze są transponowane względem siebie.R = CT

Oto zabawny fakt. Ponieważ wektory są traktowane jako macierze, one również mają pytanie dotyczące przechowywania względem kolumny i wiersza. Problem polega na tym, że oba wyglądają tak samo . Tablica liczb zmiennoprzecinkowych jest taka sama, więc nie można odróżnić R i C tylko na podstawie danych. Tylko sposób odróżnić to od tego, jak są one wykorzystywane.

Jeśli masz jakieś dwie macierze A i B, a A jest przechowywane jako major-wiersz, a B jako major-kolumna, pomnożenie ich jest zupełnie bez znaczenia . W rezultacie dostajesz bzdury. Cóż, nie za bardzo. Matematycznie otrzymujesz ekwiwalent robienia . Lub ; są matematycznie identyczne.AT*BA*BT

Dlatego mnożenie macierzy ma sens tylko wtedy, gdy dwie macierze (i pamiętaj: mnożenie macierzy / macierzy to tylko mnożenie macierzy) są przechowywane w tym samym głównym porządku.

A więc, czy wektor jest kolumna główna czy rzędowa? Jest to jedno i drugie, jak stwierdzono wcześniej. Jest to kolumna główna tylko wtedy, gdy jest używana jako matryca kolumnowa, i jest rzędem głównym, gdy jest używana jako matryca wierszowa.

Dlatego jeśli masz macierz A, która jest kolumną główną, x*Aoznacza ... nic. Znów oznacza to , ale nie tego naprawdę chciałeś. Podobnie transponuje mnożenie, jeśli jest rzędem głównym.x*ATA*xA

Dlatego kolejność wektor / mnożenia macierzy robi zmianom, w zależności od głównego porządkowania danych (i czy używasz transpozycji macierzy).

Dlaczego w poniższym fragmencie kodu r! = R2

Ponieważ Twój kod jest uszkodzony i zawiera błędy. Matematycznie . Jeśli nie otrzymasz tego wyniku, oznacza to, że test równości jest nieprawidłowy (problemy z precyzją zmiennoprzecinkową) lub kod mnożenia macierzy jest uszkodzony.A * (B * C) == (CT * BT) * AT

dlaczego pos3! = pos for

Ponieważ to nie ma sensu. Jedynym sposobem na prawdziwość byłoby, gdyby . Dotyczy to tylko matryc symetrycznych.A * t == AT * tA == AT

Nicol Bolas
źródło
@Nicol, wszystko zaczyna teraz klikać. Nastąpiło zamieszanie z powodu rozbieżności między tym, co widziałem, a tym, co powinienem być, ponieważ moja biblioteka (wzięta z Axioma) deklaruje główną kolumnę (i wszystkie rzędy mnożenia itp. Są zgodne z tym), ale układ pamięci jest wierszem -major (sądząc po wskaźnikach translacji i fakcie, że HLSL działa poprawnie przy użyciu nietransponowanej matrycy); Teraz jednak widzę, jak nie jest to sprzeczne. Dziękuję Ci bardzo!
sebf
2
Prawie dałem ci -1 za powiedzenie rzeczy takich jak „To nie tak wygląda normalna matryca tłumaczenia” i „co jest kompletnym śmieciem”. Następnie kontynuujesz i ładnie wyjaśniasz, dlaczego są one całkowicie równoważne i dlatego żadne z nich nie jest bardziej „naturalne” niż drugie. Dlaczego po prostu nie usuniesz tego małego bzdury od samego początku? Reszta twojej odpowiedzi jest raczej dobra. (Również dla zainteresowanych: steve.hollasch.net/cgindex/math/matrix/column-vec.html )
imre
2
@imre: Ponieważ to nie nonsens. Konwencje są ważne, ponieważ mylące są dwie konwencje. Matematycy już dawno przyjęli konwencję dotyczącą matryc . „Transponowane macierze” (nazwane, ponieważ są transponowane ze standardu) stanowią naruszenie tej konwencji. Ponieważ są równoważne, nie dają rzeczywistej korzyści użytkownikowi. A ponieważ są różne i mogą być niewłaściwie używane, powoduje zamieszanie. Innymi słowy, gdyby transponowane matryce nie istniały, OP nigdy by o to nie zapytał. I dlatego ta alternatywna konwencja wprowadza zamieszanie.
Nicol Bolas
1
@Nicol: Macierz z tłumaczeniem w 12-13-14 może nadal być rzędem głównym - jeśli użyjemy z nią wektorów wierszy (i pomnożymy jako vM). Zobacz DirectX. LUB może być postrzegana jako główna kolumna, używana z wektorami kolumnowymi (Mv, OpenGL). To jest naprawdę to samo. I odwrotnie, jeśli macierz ma translację w 3-7-11, to można ją postrzegać jako macierz rzędów głównych z wektorami kolumnowymi, LUB kolumna główna z wektorami rzędowymi. Wersja 12-13-14 jest rzeczywiście bardziej powszechna, ale moim zdaniem 1) tak naprawdę nie jest standardem, i 2) nazywanie jej kolumną główną może wprowadzać w błąd, ponieważ niekoniecznie tak jest.
imre
1
@imre: To standard. Zapytaj dowolnego przeszkolonego matematyka, gdzie idzie tłumaczenie, a powiedzą ci, że idzie w czwartej kolumnie. Matematycy wymyślili matryce; to oni ustanawiają konwencje.
Nicol Bolas
3

Istnieją tutaj dwa różne rodzaje konwencji. Jednym z nich jest to, czy używasz wektorów wierszowych czy kolumnowych, a macierze dla tych konwencji są transponowane względem siebie.

Drugim jest to, czy macie macierze w pamięci w kolejności rzędów głównych, czy kolumnowych. Zauważ, że „major-wiersz” i „major-kolumna” nie są poprawnymi terminami do omawiania konwencji wektor-wiersz / wektor-kolumna ... nawet jeśli wiele osób niewłaściwie ich używa. Układy pamięci wierszy i wierszy również różnią się transpozycją.

OpenGL stosuje konwencję wektora kolumnowego i kolejność przechowywania głównych kolumn, a D3D wykorzystuje konwencję wektorów wierszowych i kolejność przechowywania głównych wierszy (cóż - przynajmniej D3DX, biblioteka matematyczna, robi), więc dwie transpozycje anulują się i okazuje się ten sam układ pamięci działa zarówno dla OpenGL, jak i D3D. Oznacza to, że ta sama lista 16 pływaków przechowywanych sekwencyjnie w pamięci będzie działać w ten sam sposób w obu interfejsach API.

Może to oznaczać, że ludzie twierdzą, że „nie ma znaczenia, w jaki sposób macierz jest przechowywana lub przenoszona do GPU”.

Jeśli chodzi o fragmenty kodu, r! = R2, ponieważ reguła transpozycji produktu to (ABC) ^ T = C ^ TB ^ TA ^ T. Transpozycja rozkłada się na mnożenie z odwróceniem kolejności. Więc w twoim przypadku powinieneś dostać r.Transpose () == r2, a nie r == r2.

Podobnie, pos! = Pos3, ponieważ dokonałeś transpozycji, ale nie odwróciłeś kolejności mnożenia. Powinieneś dostać wpvM * localPos == localPos * wvpM.Tranpose (). Wektor jest automatycznie interpretowany jako wektor wierszowy po pomnożeniu po lewej stronie macierzy oraz jako wektor kolumnowy po pomnożeniu po prawej stronie macierzy. Poza tym nie ma zmiany w sposobie przeprowadzania mnożenia.

Na koniec, re: „moja główna główna kolumna Macierz WVP jest z powodzeniem używana do transformacji wierzchołków za pomocą wywołania HLSL: mul (wektor, macierz)”, „Nie jestem tego pewien, ale może zamieszanie / błąd spowodował, że matryca wyszła z biblioteka matematyczna została już transponowana.

Nathan Reed
źródło
1

W grafice 3D używasz macierzy do przekształcania zarówno wektora, jak i punktów. Biorąc pod uwagę fakt, że mówisz o macierzy translacji, mówię tylko o punktach (nie możesz przetłumaczyć wektora za pomocą macierzy lub, mówiąc lepiej, możesz, ale uzyskasz ten sam wektor).

W mnożeniu macierzy liczba kolumn pierwszej macierzy powinna być równa liczbie wierszy drugiej (możesz pomnożyć macierz anxm dla mxk).

Punkt (lub wektor) jest reprezentowany przez 3 komponenty (x, y, z) i można go traktować zarówno jak wiersz lub kolumnę:

colum (wymiar 3 X 1):

| x |

| y |

| z |

lub

rząd (wymiar 1 X 3):

| x, y, z |

Możesz wybrać preferowaną konwencję, to tylko konwencja. Nazwijmy to T macierzą tłumaczenia. Jeśli wybierzesz pierwszą konwencję, aby pomnożyć punkt p dla macierzy, musisz użyć mnożenia po:

T * v (wymiar 3x3 * 3x1)

Inaczej:

v * T (wymiar 1x3 * 3x3)

autorzy wydają się twierdzić, że nie ma znaczenia, w jaki sposób macierz jest przechowywana lub przenoszona do GPU

Jeśli używasz zawsze tej samej konwencji, nie ma to znaczenia. Nie oznacza to, że macierz różnych konwencji będzie miała tę samą reprezentację pamięci, ale że przekształcając punkt za pomocą 2 różnych konwencji uzyskasz ten sam punkt przekształcony:

p2 = B * A * p1; // pierwsza konwencja

p3 = p1 * A * B; // druga konwencja

p2 == p3;

Heisenbug
źródło
1

Widzę, że składniki tłumaczenia zajmujące 4, 8 i 12 element oznaczają, że macierze są „złe”.

Komponenty translacji są zawsze określone jako wpisy # 13, # 14 i # 15 macierzy transformacji ( licząc pierwszy element tablicy jako element # 1 ).

Główna matryca transformacji rzędu wygląda następująco:

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

Macierz transformacji głównej kolumny wygląda następująco:

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

Główne macierze wierszy są określone w dół wierszy .

Deklarując powyższą macierz główną wiersza jako tablicę liniową, napisałbym:

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

To wydaje się bardzo naturalne. Ponieważ zauważono, angielski jest zapisany jako „wiersz-major” - matryca pojawia się w tekście powyżej dokładnie tak, jak będzie w matematyce.

I tu chodzi o zamieszanie.

Główne kolumny macierzy są określone w dół kolumn

Oznacza to, że aby określić macierz transformacji głównej kolumny jako tablicę liniową w kodzie, należy napisać:

    COLUMN_MAJOR = {R00, R10, R20, 0, // COLUMN # 1 // bardzo sprzeczne z intuicją
                     R01, R11, R21, 0,
                     R02, R12, R22, 0,
                     tx, ty, tz, 1};

Uwaga: jest to całkowicie sprzeczne z intuicją !! Macierz główna kolumny ma określone wpisy w dół kolumn podczas inicjowania tablicy liniowej, a więc w pierwszym wierszu

COLUMN_MAJOR = { R00, R10, R20, 0,

Określa pierwszą kolumnę macierzy:

 R00
 R10
 R20
  0 

a nie pierwszy wiersz , ponieważ uwierzyłbyś w prosty układ tekstu. Musisz mentalnie przetransponować matrycę główną kolumny, gdy zobaczysz ją w kodzie, ponieważ pierwsze 4 określone elementy faktycznie opisują pierwszą kolumnę. Podejrzewam, że właśnie dlatego wiele osób woli matryce z dużymi wierszami w kodzie (kaszel GO DIRECT3D !!).

Tak więc komponenty translacji zawsze mają indeksy tablic liniowych # 13, # 14 i # 15 (gdzie pierwszym elementem jest # 1), niezależnie od tego, czy używasz macierzy głównych wierszy czy kolumn głównych.

Co się stało z twoim kodem i dlaczego działa?

To, co dzieje się w twoim kodzie, jest takie, że masz kolumnę główną tak, ale umieściłeś składniki tłumaczenia w niewłaściwym miejscu. Po transponowaniu macierzy pozycja 4 przechodzi do pozycji 13, pozycji 8 do 13, a pozycji 12 do 15. I masz to.

Bobobobo
źródło
0

Mówiąc wprost, przyczyną tej różnicy jest to, że mnożenie macierzy nie jest przemienne . Przy regularnym mnożeniu liczb, jeśli A * B = C, oznacza to, że B * A także = C. Nie dotyczy to macierzy. Właśnie dlatego wybieram albo najważniejsze wiersze, albo najważniejsze kolumny.

Dlaczego nie ma znaczenia, że ​​we współczesnym interfejsie API (a konkretnie tutaj mówię o modułach cieniujących) możesz wybrać własną konwencję i pomnożyć macierze we właściwej kolejności dla tej konwencji we własnym kodzie modułu cieniującego. Interfejs API nie egzekwuje już ani jednego, ani drugiego.

Maximus Minimus
źródło