Geometria (wektor główny wierszowy vs wektor główny kolumnowy)

Geometria

Wcześniej w tej lekcji wyjaśniliśmy, że wektory (lub punkty) mogą być zapisane jako macierze (jeden wiersz, trzy kolumny). Zauważmy jednak, że mogliśmy je również zapisać jako macierze (trzy wiersze, jedna kolumna). Technicznie rzecz biorąc, te dwa sposoby wyrażania punktów i wektorów jako macierzy są całkowicie poprawne, a wybór jednego lub drugiego sposobu jest tylko kwestią konwencji.

Wektor zapisany jako macierz: \( V=begin{bmatrix}x & y & z\end{bmatrix})

Wektor zapisany jako macierz: \(V=begin{bmatrix}x & y & zend{bmatrix}})

W pierwszym przykładzie ( macierz) wyraziliśmy nasz wektor lub punkt w tym, co nazywamy porządkiem rząd-major: wektor (lub punkt) jest zapisany jako rząd trzech liczb. W drugim przykładzie mówimy, że punkty lub wektory są zapisane w porządku kolumna-major: piszemy trzy współrzędne wektora lub punktu pionowo, jako kolumna.

Pamiętaj, że wyrażamy punkty i wektory jako macierze, aby pomnożyć je przez macierze transformacji (dla uproszczenia będziemy pracować z raczej niż macierze). Nauczyliśmy się również, że możemy mnożyć macierze tylko wtedy, gdy liczba kolumn z lewej macierzy i liczba wierszy z prawej macierzy są takie same. Innymi słowy, macierze i mogą być mnożone przez siebie, ale macierze i nie mogą. Zauważ, że jeśli zapiszemy wektor jako macierz to możemy go pomnożyć przez macierz (zakładając, że ta macierz jest po prawej stronie w środku mnożenia), ale jeśli zapiszemy ten wektor jako macierz to nie możemy go pomnożyć przez macierz. Jest to zilustrowane na poniższych przykładach. Wymiary wewnętrzne (3 i 3) macierzy biorących udział w mnożeniu są takie same (na zielono), więc to mnożenie jest poprawne (a wynikiem jest przekształcony punkt zapisany w postaci macierzy):

$$* = \begin{bmatrix}x & y & z\end{bmatrix} * \begin{bmatrix}c_{00}&c_{01}&{c_{02}}\\c_{10}&c_{11}&{c_{12}}\\c_{20}&c_{21}&{c_{22}}\\\end{bmatrix} =\begin{bmatrix}x’&y’&z’\end{bmatrix}$$

The wymiary wewnętrzne (1 i 3) macierzy biorących udział w mnożeniu nie są takie same (na czerwono), więc to mnożenie nie jest możliwe:

$$* ^rightarrow ^begin{bmatrix}x{bmatrix} y^end{bmatrix} * ^begin{bmatrix} c_{00}&c_{01}&{c_{02}} c_{10}&c_{11}&{c_{12}} c_{20}&c_{21}&{c_{22}} ^end{bmatrix}$$

Co więc robimy? Rozwiązaniem tego problemu nie jest mnożenie wektora czy punktu przez macierz, ale macierzy M przez wektor V. Innymi słowy, przesuwamy punkt lub wektor w prawo wewnątrz mnożenia:

$$* \prawidłowo \begin{bmatrix} c_{00}&c_{01}&{c_{02}} c_{10}&c_{11}&{c_{12}} c_{20}&c_{21}&{c_{22}} * \begin{bmatrix}x’\z’\end{bmatrix} = \begin{bmatrix}x’\z’\end{bmatrix}$$

Zauważmy, że wynikiem tej operacji jest przekształcony punkt zapisany w postaci macierzy. Tak więc dostajemy punkt na początek i kończymy z przekształconym punktem, który jest tym, czego chcemy. Problem rozwiązany. Podsumowując, gdy umownie zdecydujemy się na wyrażenie wektorów lub punktów w kolejności rząd-major (), musimy umieścić punkt po lewej stronie mnożenia, a punkt po prawej wewnątrz znaku mnożenia. Nazywa się to w matematyce mnożeniem lewym lub przed mnożeniem. Jeśli zdecydujesz się zapisać wektory w kolejności kolumna-major zamiast (), macierz musi być po lewej stronie mnożenia, a wektor lub punkt po prawej stronie. Nazywa się to mnożeniem prawym lub post-multiplikacją.

Musimy uważać na to, jak te terminy są faktycznie używane. Na przykład dokumentacja Maya mówi, że „macierze są post-multiplikowane w Maya”. Na przykład, aby przekształcić punkt P z przestrzeni obiektu do przestrzeni świata (P’), musisz pomnożyć go przez worldMatrix. (P’ = P x WM)”, co jest mylące, ponieważ w rzeczywistości jest to mnożenie wstępne, ale w tym konkretnym przypadku mówi się o pozycji macierzy w stosunku do punktu. To jest właściwie błędne użycie terminologii. Powinno być napisane, że w Maya, punkty i wektory są wyrażone jako wektory główne rzędu i że są one zatem wstępnie mnożone (co oznacza, że punkt lub wektor pojawia się przed macierzą w mnożeniu).

Poniższa tabela podsumowuje różnice między tymi dwoma konwencjami (gdzie P, V i M odpowiednio oznaczają punkt, wektor i macierz).

Row-major order

(P/V=początek{bmatrix}x & y &zkońcówka{bmatrix}})

Lewy lub przedmnożenia

P/V * M

Kolumna- lub przedmajor order

(P/V= początek{bmatrix}x \ y \ z \ end{bmatrix}})

Mnożenie w prawo lub pomnożenie

M * P/V

Teraz, gdy poznaliśmy te dwie konwencje, możesz zapytać „czy to nie jest po prostu zapisywanie rzeczy na papierze?”. Wiemy, jak obliczyć iloczyn dwóch macierzy A i B: pomnóż każdy współczynnik w bieżącym wierszu macierzy A przez odpowiadające mu elementy w bieżącej kolumnie macierzy B i zsumuj wynik. Zastosujmy tę formułę, używając dwóch konwencji i porównajmy wyniki:

Row-major order

${ \begin{bmatrix}x & y & z\end{bmatrix} * \begin{bmatrix}a & b & c \d & e & f \g & h & i \end{bmatrix} }$$

$${ ^begin{array}{l}x’ = x * a + y * d + z * g}x’ = x * b + y * e + z * h = x * c + y * f + z * i}end{array} }$$

Column-major order

$${ \begin{bmatrix} a & b & c \d & e & f \g & h & i \end{bmatrix} * \begin{bmatrix}x\\y\\z\end{bmatrix} }$$

$${begin{array}{l}x’ = a * x + b * y + c * z}x’ = d * x + e * y + f * z}x’ = g * x + h * y + i * z}end{array} }$$

Mnożenie punktu lub wektora przez macierz powinno dać nam ten sam wynik niezależnie od tego, czy używamy porządku wierszowego czy kolumnowego. Jeśli używasz aplikacji 3D, aby obrócić punkt o pewien kąt wokół osi z, oczekujesz, że punkt będzie w pewnej pozycji po obrocie, bez względu na to, jaką wewnętrzną konwencję deweloper użył do reprezentacji punktów i wektorów. Jednak jak widać na powyższej tabeli, pomnożenie punktu (lub wektora) o rzędzie większym i o kolumnie większej przez tę samą macierz nie da nam tego samego wyniku. Aby stanąć na nogi, musielibyśmy dokonać transpozycji macierzy użytej w mnożeniu kolumnowo-współrzędnym, aby upewnić się, że x’, y’ i z’ są takie same (jeśli musisz sobie przypomnieć, czym jest transpozycja macierzy, sprawdź rozdział Operacje na macierzach). Oto, co otrzymamy:

Row-major order

${ \begin{bmatrix}x & y & z\end{bmatrix} * \begin{bmatrix}a & b & c \d & e & f \g & h & i \end{bmatrix} }$$

$${begin{array}{l}x’ = x * a + y * d + z * g = x * b + y * e + z * h = x * c + y * f + z * i}end{array} }$$

Column-major order

$${ ^begin{bmatrix} a & d & g ^b & e & h ^c & f & i ^end{bmatrix} * \begin{bmatrix}x\\y\\z\end{bmatrix} }$$

$${begin{array}{l}x’ = a * x + d * y + g * z}x’ = b * x + e * y + h * z}x’ = c * x + f * y + i * z}end{array} }$$

Podsumowując, przejście z porządku wiersz-major do porządku kolumna-major wiąże się nie tylko z zamianą punktu lub wektora i macierzy w mnożeniu, ale także z transpozycją macierzy, aby zagwarantować, że obie konwencje dają ten sam wynik (i na odwrót).

Z tych obserwacji widzimy, że każda seria przekształceń zastosowanych do punktu lub wektora, gdy używana jest konwencja rząd-major, może być napisana w kolejności sekwencyjnej (lub kolejności odczytu). Wyobraźmy sobie na przykład, że chcemy przetłumaczyć punkt P za pomocą macierzy T, następnie obrócić go wokół osi z za pomocą Rz, a następnie wokół osi y za pomocą Ry. Możesz napisać:

$P’=P * T * R_z * R_y$$

Gdybyś użył notacji kolumna-major musiałbyś wywołać transformację w odwrotnej kolejności (co mogłoby się wydać sprzeczne z intuicją):

$P’=R_y * R_z * T * P$$

Możesz więc pomyśleć, „musi być powód, aby preferować jeden system od drugiego”. W rzeczywistości, obie konwencje są poprawne i dają nam ten sam wynik, ale z pewnych technicznych powodów, teksty matematyczne i fizyczne generalnie traktują wektory jako wektory kolumnowe.

Konwencja przekształceń, gdy używamy macierzy kolumnowo-majorowych jest bardziej podobna w matematyce do sposobu pisania oceny funkcji i kompozycji.

Konwencja macierzy rzędowo-majorowych sprawia jednak, że macierze są łatwiejsze do nauczenia, co jest powodem, dla którego używamy jej w Scratchapixel (jak również Maya, DirectX. Są one również zdefiniowane jako standard w specyfikacji RenderMan). Jednak niektóre API 3D, takie jak OpenGL, używają konwencji kolumna-major.

Implication in Coding: Does it Impact Performance?

Jest jeszcze jeden potencjalnie bardzo ważny aspekt, który należy wziąć pod uwagę, jeśli musisz wybrać pomiędzy row-major i column-major, ale to nie ma nic wspólnego z konwencjami i jak praktyczny jest jeden nad drugim. Ma to więcej wspólnego z komputerem i sposobem jego działania. Pamiętaj, że będziemy mieli do czynienia z macierzami. Zazwyczaj implementacja macierzy w C++ wygląda tak:

class Matrix44{ … float m;};

Jak widzisz, 16 współczynników macierzy jest przechowywanych w dwuwymiarowej tablicy float (lub double, w zależności od precyzji, jakiej potrzebujesz. Nasza klasa C++ Matrix jest szablonem). Oznacza to, że w pamięci 16 współczynników będzie ułożonych w następujący sposób: c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33. Innymi słowy, są one ułożone równolegle w pamięci. Zobaczmy teraz, jak te współczynniki są dostępne w mnożeniu wektorowo-matrycowym, gdzie wektory są zapisane w kolejności wiersz-major:

// row-major orderx’ = x * c00 + y * c10 + z * c20y’ = x * c01 + y * c11 + z * c21z’ = x * c02 + y * c12 + z * c22

Jak widać elementy macierzy dla x’ nie są dostępne sekwencyjnie. Innymi słowy, aby obliczyć x’ potrzebujemy 1, 5 i 9 zmiennoprzecinkowych elementów macierzy 16 float. Aby obliczyć y’ musimy uzyskać dostęp do 2, 6 i 10 elementu float tej tablicy. I w końcu, aby obliczyć z’ potrzebujemy 3, 7 i 11 float z tej tablicy. W świecie obliczeń, dostęp do elementów z tablicy w kolejności innej niż sekwencyjna, niekoniecznie jest dobrą rzeczą. W rzeczywistości może to potencjalnie pogorszyć wydajność pamięci podręcznej procesora. Nie będziemy tutaj wchodzić w zbyt wiele szczegółów, ale powiedzmy, że najbliższa pamięć, do której procesor może uzyskać dostęp, nazywana jest pamięcią podręczną (cache). Ta pamięć podręczna jest bardzo szybko dostępna, ale może przechowywać tylko bardzo ograniczoną liczbę danych. Kiedy procesor potrzebuje uzyskać dostęp do pewnych danych, najpierw sprawdza, czy istnieją one w pamięci podręcznej. Jeśli tak, CPU uzyskuje dostęp do tych danych od razu (cache hit), jeśli nie (cache miss), musi najpierw utworzyć wpis w pamięci podręcznej dla niego, a następnie skopiować do tej lokalizacji dane z pamięci głównej. Proces ten jest oczywiście bardziej czasochłonny niż w przypadku, gdy dane już istnieją w cache’u, więc idealnie byłoby, gdybyśmy chcieli uniknąć misses w cache’u tak bardzo, jak to tylko możliwe. Oprócz kopiowania konkretnych danych z pamięci głównej, procesor kopiuje również fragment danych, które znajdują się tuż obok (na przykład następne 24 bajty), ponieważ inżynierowie sprzętowi doszli do wniosku, że jeśli nasz kod potrzebuje dostępu na przykład do elementu tablicy, to prawdopodobnie wkrótce po tym uzyska dostęp do elementów znajdujących się za nim. W istocie, w programach często wykonujemy pętle nad elementami tablicy w kolejności sekwencyjnej i dlatego to założenie jest prawdopodobnie prawdziwe. Zastosowane do naszego problemu z macierzą, dostęp do współczynników macierzy w kolejności innej niż sekwencyjna może być zatem problemem. Zakładając, że CPU ładuje żądany zmiennoprzecinek do pamięci podręcznej plus 3 zmiennoprzecinki obok niego, nasza obecna implementacja może prowadzić do wielu pominięć pamięci podręcznej, ponieważ współczynniki używane do obliczania x’ y’ i z’ są oddalone od siebie o 5 zmiennoprzecinków w tablicy. Z drugiej strony, jeśli używasz notacji kolejności kolumna-major, obliczanie x’ na przykład wymaga dostępu do 1, 2 i 3 elementu macierzy.

// column-major orderx’ = c00 * x + c01 * y + c02 * zy’ = c10 * x + c11 * y + c12 * zz’ = c20 * x + c21 * y + c22 * z

Współczynniki są dostępne w kolejności sekwencyjnej, co oznacza również, że dobrze wykorzystujemy mechanizm cache procesora (tylko 3 misses cache zamiast 9 w naszym przykładzie). Podsumowując, można powiedzieć, że z programistycznego punktu widzenia implementacja mnożenia punktowego lub wektorowo-macierzowego z wykorzystaniem konwencji porządku kolumnowo-majorowego może być lepsza wydajnościowo niż wersja z wykorzystaniem konwencji porządku wierszowo-majorowego. Praktycznie jednak nie byliśmy w stanie tego wykazać (gdy kompilujesz swój program używając flag optymalizacyjnych -O, -O2 lub -O3, kompilator może wykonać pracę za Ciebie, optymalizując pętle nad wielowymiarowymi tablicami) i z powodzeniem używaliśmy wersji row-major order bez utraty wydajności w porównaniu z wersją tego samego kodu używającą implementacji column-major order.

template<typename T>class Vec3{public: Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} T x, y, z, w;}; template<typename T>class Matrix44{public: T m; Vec3<T> multVecMatrix(const Vec3<T> &v) {#ifdef ROWMAJOR return Vec3<T>( v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m);#else return Vec3<T>( v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m);#endif }}; #include <cmath>#include <cstdlib>#include <cstdio>#include <ctime> #define MAX_ITER 10e8 int main(int argc, char **argv){ clock_t start = clock(); Vec3<float> v(1, 2, 3); Matrix44<float> M; float *tmp = &M.m; for (int i = 0; i < 16; i++) *(tmp + i) = drand48(); for (int i = 0; i < MAX_ITER; ++i) { Vec3<float> vt = M.multVecMatrix(v); } fprintf(stderr, „Czas zegara %f\n”, (clock() – start) / float(CLOCKS_PER_SEC)); return 0;}

Row-major and Column-Major Order in Computing

Dla pełnego obrazu wspomnijmy, że terminy row-major i column-major order mogą być również używane w informatyce do opisania sposobu, w jaki elementy tablic wielowymiarowych są ułożone w pamięci. W kolejności wiersz-major elementy tablicy wielowymiarowej są ułożone jeden za drugim, od lewej do prawej, od góry do dołu. Jest to metoda używana przez C/C++. Na przykład macierz:

$M = \begin{bmatrix}1&2&3\4&5&6\end{bmatrix}$$

można zapisać w C/C++ jako:

float m={{1, 2, 3}, {4, 5, 6}};

a elementy tej tablicy byłyby ułożone przylegle w pamięci liniowej jako:

1 2 3 4 5 6

W porządku kolumna-major, który jest używany przez takie języki jak FORTRAN i MATLAB, elementy macierzy są przechowywane w pamięci od góry do dołu, od lewej do prawej. Używając tego samego przykładu macierzy, elementy macierzy byłyby przechowywane (i dostępne) w pamięci w następujący sposób:

1 4 2 5 3 6

Wiedza o tym, jak elementy macierzy są rozmieszczone w pamięci, jest ważna, zwłaszcza gdy próbujesz uzyskać do nich dostęp przy użyciu przesunięcia wskaźnika i optymalizacji pętli for (wyjaśniliśmy wcześniej w tym rozdziale, że może to wpłynąć na wydajność pamięci podręcznej procesora). Ponieważ jednak naszym językiem programowania będzie tylko C/C++, uporządkowanie kolumna-major (stosowane w informatyce) nie ma dla nas większego znaczenia. Przytaczamy jedynie znaczenie tych terminów w informatyce, abyś był świadomy, że mogą one opisywać dwie różne rzeczy w zależności od kontekstu, w którym są używane. Powinieneś być ostrożny, aby ich nie mieszać. W kontekście matematyki opisują one, czy traktujesz wektory (lub punkty) jako rzędy współrzędnych lub jako kolumny i drugie, a w kontekście informatyki opisują sposób, w jaki dany język programowania przechowuje i uzyskuje dostęp do elementów wielowymiarowej tablicy (którą są macierze) w pamięci.

OpenGL jest interesującym przypadkiem w tym względzie. Kiedy GL został początkowo stworzony, programiści wybrali konwencję wektora rząd-major. Programiści, którzy rozszerzyli OpenGL pomyśleli jednak, że powinni wrócić do konwencji column-major vector, co też uczynili. Jednak ze względu na kompatybilność, nie chcieli zmieniać kodu mnożenia macierzy punktów i zamiast tego zdecydowali się zmienić kolejność, w jakiej współczynniki macierzy były przechowywane w pamięci. Innymi słowy, OpenGL przechowuje współczynniki w kolejności kolumna-major, co oznacza, że współczynniki translacji m03, m13 i m23 z macierzy używającej wektora kolumna-major mają indeksy 13, 14, 15 w tablicy float, tak samo jak współczynniki translacji m30, m31 i m32 z macierzy używającej wektora rząd-major.

Podsumowanie

Różnice między tymi dwoma konwencjami są podsumowane w poniższej tabeli:

.

Row-major vector (Mathematics) Column-.wektor główny (Matematyka)

(P/V= początkowy{bmatrix}x & y & zkońcowy{bmatrix}})

\(P/V=begin{bmatrix}x & y & zend{bmatrix}\)

Pre-mnożenie

Post-multiplikacja

Kolejność wywoływania i kolejność stosowania przekształceń jest taka sama: „weź P, przekształć przez T, przekształć przez Rz, przekształć przez Ry” zapisujemy jako ὄ(P’=P*T*R_z*R_y)

Kolejność wywoływania jest odwrotna do kolejności stosowania przekształceń: „weź P, przekształć przez T, przekształć przez Rz, przekształć przez Ry” jest zapisane jako \(P’=R_y*R_z*T*P\)

API: Direct X, Maya

API: OpenGL, PBRT, Blender

Rzędy macierzy reprezentują podstawy (lub osie) układu współrzędnych (czerwony: oś x, zielony: oś y, niebieski:oś z)

$${begin{bmatrix} \color{red}{c_{00}}& \color{red}{c_{01}}&\color{red}{c_{02}}&0\\ \color{green}{c_{10}}& \color{green}{c_{11}}&\color{green}{c_{12}}&0\\ \color{blue}{c_{20}}& \color{blue}{c_{21}}&\color{blue}{c_{22}}&0\\0&0&0&1 \end{bmatrix} } $$

Kolumny macierzy reprezentują podstawy (lub osie) układu współrzędnych (czerwony: oś x, zielony: oś y, niebieski:oś z)

${ \begin{bmatrix} \color{red}{c_{00}}& \color{green}{c_{01}}&\color{blue}{c_{02}}&0\\ \color{red}{c_{10}}& \color{green}{c_{11}}&\color{blue}{c_{12}}&0\\ \color{red}{c_{20}}& \color{green}{c_{21}}&\color{blue}{c_{22}}&0\\0&0&0&1\end{bmatrix} }$$

Wartości translacji są przechowywane w elementach c30, c31 i c32.

$${\begin{bmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\Tx&Ty&Tz&1\end{bmatrix} }$$

Wartości translacji są przechowywane w elementach c03, c13 i c23.

${begin{bmatrix}1&0&0&Tx&1&0&Ty&0&1&Tz&0&0&1}end{bmatrix} }$$

Transpozycja macierzy w celu użycia jej jako macierzy uporządkowanej kolumnami

Transpozycja macierzy w celu użycia jej jako macierzy uporządkowanej rzędamimajor ordered matrix

Row-major matrix (Computing) Column-major matrix (Computing)

API: Direct X, Maya, PBRT

API: OpenGL

Czytelnik zamieścił pytanie na Stackoverflow, sugerując, że powyższa tabela jest myląca. Temat jest mylący i pomimo naszych najlepszych prób rzucenia trochę światła na sprawę, wiele osób nadal jest zdezorientowanych w tym temacie. Pomyśleliśmy, że nasza odpowiedź na Stackoverflow może, miejmy nadzieję, przynieść inny wgląd w pytanie.

Masz teorię (to, co robisz w matematyce z piórem i papierem) i to, co robisz z implementacją (C ++). Są to dwa różne problemy.

Matematyka: możesz użyć dwóch notacji, albo kolumny, albo row major. W przypadku wektora rzędowego, na papierze musisz napisać mnożenie wektorowo-macierzowe vM, gdzie v jest wektorem rzędowym (1×4), a M twoją macierzą 4×4. Dlaczego? Ponieważ matematycznie możesz napisać tylko *, a nie na odwrót. Podobnie jeśli używasz kolumny, wtedy wektor musi być zapisany pionowo, lub w notacji (4 wiersze, 1 kolumna). Tak więc, mnożenie z macierzą może być zapisane tylko w następujący sposób: . Zauważ, że macierz jest umieszczona przed wektorem: Mv. Pierwszy zapis jest nazywany mnożeniem lewym lub przed-mnożeniem (ponieważ wektor jest po lewej stronie iloczynu), a drugi (Mv) jest nazywany mnożeniem prawym lub po-mnożeniem (ponieważ wektor jest po prawej stronie iloczynu). Jak widzisz terminy wynikają z tego, czy wektor jest po lewej stronie (przed, lub „pre”) lub po prawej stronie (po, lub „post”) macierzy.

Teraz, jeśli potrzebujesz przekształcić wektor (lub punkt) to musisz zwrócić uwagę na kolejność mnożenia, kiedy zapisujesz je na papierze. Jeśli chcesz przetłumaczyć coś za pomocą macierzy T, a następnie obrócić za pomocą R, a następnie skalować za pomocą S, to w świecie kolumnowym musisz napisać v’ = S * R * T * v. W świecie rzędowym musisz napisać v’ = v * T * R * S.

To dla teorii. Nazwijmy to konwencją wektora rząd/kolumna.

Komputer: wtedy przychodzi punkt, w którym decydujesz się zaimplementować to w C++ powiedzmy. Dobrą rzeczą jest to, że C++ nie narzuca ci niczego. Możesz mapować wartości współczynników swojej macierzy w pamięci tak, jak chcesz, i możesz napisać kod, aby wykonać mnożenie macierzy przez inną macierz tak, jak chcesz. Podobnie sposób, w jaki uzyskujesz dostęp do współczynników dla mnożenia wektorowo-macierzowego, jest całkowicie zależny od ciebie. Musisz dokonać wyraźnego rozróżnienia między tym, jak mapujesz swoje współczynniki w pamięci, a tym, jakich konwencji musisz użyć z matematycznego punktu widzenia, aby reprezentować swoje wektory. Są to dwa niezależne problemy. Nazwijmy tę część układem wiersz/kolumna-major.

Na przykład możesz zadeklarować klasę macierzy jako tablicę powiedzmy 16 contiguous floats. To jest w porządku. Gdzie współczynniki m14, m24, m34 reprezentują część translacyjną macierzy (Tx, Ty, Tz), więc zakładasz, że twoja „konwencja” jest row-major, mimo że powiedziano ci, abyś użył konwencji macierzy OpenGL, która jest powiedziana, że jest column-major. Tutaj możliwe zamieszanie wynika z faktu, że mapowanie współczynników w pamięci różni się od mentalnej reprezentacji, którą tworzysz sobie z macierzy „kolumna-major”. Kodujesz „rząd”, ale powiedziano ci, że używasz (z matematycznego punktu widzenia) „kolumny”, stąd twoja trudność w zrozumieniu, czy robisz rzeczy dobrze, czy źle.

Ważne jest, aby zobaczyć macierz jako reprezentację układu współrzędnych zdefiniowanego przez trzy osie i translację. Gdzie i jak przechowujesz te dane w pamięci jest całkowicie zależne od Ciebie. Zakładając, że trzy wektory reprezentujące trzy osie układu współrzędnych są nazwane AX(x,y,z), AY(x,y,z), AZ(x,y,z), a wektor translacji jest oznaczany przez (Tx, Ty, Tz), to matematycznie, jeśli używasz wektora kolumnowego, masz:

$M = \begin{bmatrix} AXx & AYx & AZx & Tx \\ AXy & AYy & AZy & Ty \ AXz & AYz & AZz & Tz \ 0 & 0 & 1 & 1 \end{bmatrix}$

Osie układu współrzędnych są zapisane pionowo. Teraz jeśli masz, jeśli używasz row-major:

$$M = \begin{bmatrix} AXx & AXy & AXz & 0 \a AYx & AYy & AYz & 0 \a AZx & AZy & AZz & 0 \a Tx & Ty & Tz & 1 \end{bmatrix}$

Osie układu współrzędnych są zapisane poziomo. Więc problem teraz, gdy przychodzi do świata komputerów, jest jak przechowywać te współczynniki w pamięci. Możesz równie dobrze zrobić:

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

Czy to jednak mówi ci, której konwencji używasz? Nie. Możesz również napisać:

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

lub:

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

Znowu, to nie daje ci konkretnej wskazówki, której „matematycznej” konwencji używasz. Po prostu przechowujesz 16 współczynników w pamięci na różne sposoby i to jest całkowicie w porządku, o ile wiesz, jaki jest ten sposób, abyś mógł uzyskać do nich odpowiedni dostęp później. Teraz pamiętaj, że wektor pomnożony przez macierz powinien dać ci ten sam wektor, niezależnie od tego, czy używasz notacji matematycznej wiersza czy kolumny. Tak więc to, co jest naprawdę ważne, to to, że mnożysz współrzędne (x,y,z) swojego wektora przez odpowiednie współczynniki z macierzy, co wymaga wiedzy o tym, jak „ty” zdecydowałeś się przechowywać współczynnik macierzy w pamięci:

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 * AZz + v.z * AZz + Tz}

Pisaliśmy tę funkcję, aby podkreślić fakt, że bez względu na to, jakiej konwencji używasz, wynik mnożenia wektora * macierzy jest po prostu mnożeniem i dodawaniem między współrzędnymi wejściowymi wektora i współrzędnymi osi układu współrzędnych AX, AY i AZ (niezależnie od notacji, której używasz, i niezależnie od sposobu przechowywania ich w pamięci). Jeśli używasz:

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

Trzeba wywołać:

vecMatMult(v, m, m, m, m, m, m, m, m, …

Jeśli używasz:

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

Trzeba wywołać:

vecMatMult(v, m, m, m, m, m, m, m, m, …

Czy to mówi ci, której konwencji używasz? Nie. Musisz tylko wywołać właściwe współczynniki we właściwych miejscach, gdy wykonujesz mnożenie vec * mat. I to wszystko, jakkolwiek może się to wydawać niepokojące.Teraz rzeczy są nieco inne, jeśli chodzi o mnożenie mat * mat. Możesz założyć, że kolejność, w której mnożysz matryce, nie jest taka sama. Więc R * S * T nie jest taki sam jak T * S * R. Kolejność rzeczywiście ma znaczenie. Teraz znowu, jeśli używasz „row major” wtedy matematycznie musisz napisać:

mt11 = ml11 * mr11 + ml12 * mr21 + ml13 * mr31 + ml14 * mr41

gdzie ml jest lewą macierzą a mr prawą: mt = ml * mr. Zauważ jednak, że nie używaliśmy nawiasów dla indeksów dostępu, ponieważ nie chcemy sugerować, że mamy dostęp do elementów przechowywanych w tablicy 1D. Mówimy po prostu o współczynnikach macierzy zapisanych na papierze. Jeśli chcesz to napisać w C++, to wszystko zależy od tego, jak przechowywałeś współczynniki w pamięci, jak zasugerowano powyżej.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.