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ą?
źródło
Odpowiedzi:
Zanim zacznę, ważne jest, aby zrozumieć: oznacza to, że macierze są wiersze główne . Dlatego odpowiadasz na to pytanie:
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:
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: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:
Spójrzmy, jak są one przechowywane:
Zauważ, że
column_major
jest dokładnie taki sam jakrow_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*A
ale rzeczywisty kod źródłowyA*B
jest taki sam jak kod dlaB*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*B
jest 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*A
gdzie 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:
C
ma postać macierzy wektorów kolumnowychx
iR
ma postać macierzy wektorów rzędowychx
. Biorąc to pod uwagę, dla dowolnej macierzy 4x4 AA*C
reprezentuje macierz mnożącą A przez wektor kolumnyx
. IR*A
reprezentuje macierz mnożącą wektor wierszax
przez A.Ale jeśli spojrzymy na to za pomocą ścisłej matematyki macierzowej, zobaczymy, że nie są one równoważne .
R*A
nie może być taki sam jakA*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*B
A*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*A
oznacza ... nic. Znów oznacza to , ale nie tego naprawdę chciałeś. Podobnie transponuje mnożenie, jeśli jest rzędem głównym.x*AT
A*x
A
Dlatego kolejność wektor / mnożenia macierzy robi zmianom, w zależności od głównego porządkowania danych (i czy używasz transpozycji macierzy).
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
Ponieważ to nie ma sensu. Jedynym sposobem na prawdziwość byłoby, gdyby . Dotyczy to tylko matryc symetrycznych.
A * t == AT * t
A == AT
źródło
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.
źródło
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ę:
lub
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:
Inaczej:
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:
źródło
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:
Macierz transformacji głównej kolumny wygląda następująco:
Główne macierze wierszy są określone w dół wierszy .
Deklarując powyższą macierz główną wiersza jako tablicę liniową, napisałbym:
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ć:
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
Określa pierwszą kolumnę macierzy:
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.
źródło
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.
źródło