Geometrie (vector major de rând vs vector major de coloană)

Geometrie

Principal în această lecție, am explicat că vectorii (sau punctele) pot fi scriși ca matrici (un rând, trei coloane). Rețineți însă că am fi putut să le scriem și ca matrici (trei rânduri, o coloană). Din punct de vedere tehnic, aceste două moduri de exprimare a punctelor și vectorilor sub formă de matrici sunt perfect valabile, iar alegerea unui mod sau altuia este doar o chestiune de convenție.

Vector scris ca matrice: \( V=\begin{bmatrix}x & y & z\end{bmatrix}\)

Vector scris ca matrice: \(V=\begin{bmatrix}x\y\z\end{bmatrix}\)

În primul exemplu („matrice”) am exprimat vectorul sau punctul nostru în ceea ce numim ordinea rând-major: vectorul (sau punctul) este scris ca un rând de trei numere. În cel de-al doilea exemplu, spunem că punctele sau vectorii sunt scrise în ordinea coloană-major: scriem cele trei coordonate ale vectorului sau punctului pe verticală, ca o coloană.

Rețineți că exprimăm punctele și vectorii sub formă de matrice pentru a le înmulți cu matrici de transformare (de dragul simplității vom lucra cu mai degrabă cu matrici decât cu matrici). Am învățat, de asemenea, că putem multiplica matrici doar atunci când numărul de coloane din matricea din stânga și numărul de rânduri din matricea din dreapta sunt aceleași. Cu alte cuvinte, matricile și pot fi înmulțite între ele, dar matricile și nu pot fi înmulțite între ele. Rețineți că, dacă scriem un vector ca matrice, îl putem înmulți cu o matrice (presupunând că această matrice se află în interiorul drept al înmulțirii), dar dacă scriem acest vector ca matrice, atunci nu îl putem înmulți cu o matrice. Acest lucru este ilustrat în următoarele exemple. Dimensiunile interioare (3 și 3) ale matricelor implicate în înmulțire sunt aceleași (în verde), astfel încât această înmulțire este validă (iar rezultatul este un punct transformat scris sub forma unei matrice):

$$* = \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 dimensiunile interioare (1 și 3) ale matricelor implicate în înmulțire nu sunt aceleași (în roșu), astfel încât această înmulțire nu este posibilă:

$$* \rightarrow \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}$$$

Și atunci ce facem? Soluția la această problemă nu este de a înmulți vectorul sau punctul cu matricea, ci matricea M cu vectorul V. Cu alte cuvinte, mutăm punctul sau vectorul la dreapta în interiorul înmulțirii:

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

Rețineți că rezultatul acestei operații este un punct transformat scris sub forma unei matrice. Așadar, obținem un punct la început și terminăm cu un punct transformat, ceea ce este ceea ce ne dorim. Problema este rezolvată. Pentru a rezuma, atunci când prin convenție decidem să exprimăm vectori sau puncte în ordinea rândurilor majore (), trebuie să punem punctul în partea stângă a înmulțirii și punctul în partea dreaptă în interiorul semnului de înmulțire. Acest lucru se numește în matematică, o pre-multiplicare la stânga sau pre-multiplicare. Dacă, în schimb, decideți să scrieți vectorii în ordinea coloană-major (), matricea trebuie să se afle în partea stângă a înmulțirii, iar vectorul sau punctul în partea dreaptă. Aceasta se numește multiplicare la dreapta sau post-multiplicare.

Trebuie să fim atenți la modul în care sunt utilizați efectiv acești termeni. De exemplu, documentația Maya spune că „matricile sunt post-multiplicate în Maya. De exemplu, pentru a transforma un punct P din spațiul obiectelor în spațiul lumii (P’) ar trebui să faceți o post-multiplicare cu matricea worldMatrix. (P’ = P x WM)”, ceea ce este confuz, deoarece este de fapt o pre-multiplicare, dar se vorbește despre poziția matricei în raport cu punctul în acest caz particular. Aceasta este de fapt o utilizare incorectă a terminologiei. Ar fi trebuit să se scrie că, în Maya, punctele și vectorii sunt exprimați ca vectori majori de rând și că, prin urmare, ei sunt pre-multiplicați (ceea ce înseamnă că punctul sau vectorul apare înaintea matricei în multiplicare).

Tabelul următor rezumă diferențele dintre cele două convenții (unde P, V și M reprezintă punctul, vectorul și, respectiv, matricea).

Ordinea rândului major

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

Stânga sau preînmulțire

P/V * M

Columna-ordine majoră

\(P/V=\begin{bmatrix}x \\ y \\ z\end{bmatrix}\)

Dreapta sau post-înmulțire

M * P/V

Acum că am învățat despre aceste două convenții s-ar putea să vă întrebați „nu este vorba doar de a scrie lucruri pe hârtie?”. Știm cum să calculăm produsul a două matrici A și B: înmulțim fiecare coeficient din rândul curent al lui A cu elementele asociate din coloana curentă a lui B și însumăm rezultatul. Să aplicăm această formulă folosind cele două convenții și să comparăm rezultatele:

Ordinea rândului major

$${ \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\\y’ = x * b + y * e + z * h\\z’ = x * c + y * f + z * i\end{array} }$$

Ordinea majorității coloanei

${${ \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\\y’ = d * x + e * y + f * z\z’ = g * x + h * y + i * z\end{array} }$$

Înmulțirea unui punct sau a unui vector cu o matrice ar trebui să ne dea același rezultat, indiferent dacă folosim ordinea de mărime a rândurilor sau a coloanelor. Dacă utilizați o aplicație 3D pentru a roti un punct cu un anumit unghi în jurul axei z, vă așteptați ca punctul să se afle într-o anumită poziție după rotație, indiferent de convenția internă pe care dezvoltatorul a folosit-o pentru a reprezenta punctele și vectorii. Cu toate acestea, după cum puteți vedea din tabelul de mai sus, înmulțirea unui punct (sau a unui vector) cu aceeași matrice pe rând și pe coloană cu aceeași matrice nu ar da în mod clar același rezultat. Pentru a ne pune din nou pe picioare, ar trebui de fapt să transpunem matricea utilizată în înmulțirea coloană-major pentru a ne asigura că x’, y’ și z’ sunt identice (dacă trebuie să vă amintiți ce este transpunerea unei matrice, consultați capitolul Operații cu matrice). Iată ce obținem:

Ordinea coloană-major

$${ \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\\y’ = x * b + y * e + z * h\\z’ = x * c + y * f + z * i\end{array} }$$

Ordinea majorității coloanei

${${ \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\\y’ = b * x + e * y + h * z\z’ = c * x + f * y + i * z\end{array} }$$

În concluzie, trecerea de la ordinea majorată pe rând la ordinea majorată pe coloană implică nu numai schimbarea punctului sau a vectorului și a matricei în înmulțire, ci și transpunerea matricei, pentru a garanta că ambele convenții dau același rezultat (și invers).

Din aceste observații, putem vedea că orice serie de transformări aplicate unui punct sau unui vector atunci când se folosește convenția rând-major poate fi scrisă în ordine secvențială (sau ordine de citire). Imaginați-vă, de exemplu, că doriți să translatați punctul P cu matricea T, apoi să îl rotiți în jurul axei z cu Rz și apoi în jurul axei y cu Ry. Puteți scrie:

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

Dacă ar fi să folosiți o notație coloană-major ar trebui să apelați transformarea în ordine inversă (ceea ce s-ar putea găsi contra-intuitiv):

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

Așa că vă puteți gândi, „trebuie să existe un motiv pentru a prefera un sistem în locul altuia”. De fapt, ambele convenții sunt corecte și ne dau același rezultat, dar, din anumite motive tehnice, textele de matematică și fizică tratează în general vectorii ca vectori coloană.

Ordinea transformării atunci când folosim matrici de coloană major este mai asemănătoare în matematică cu modul în care scriem evaluarea și compunerea funcțiilor.

Convenția matricei de rând major face totuși ca matricile să fie mai ușor de predat, acesta fiind motivul pentru care o folosim pentru Scratchapixel (la fel ca și pentru Maya, DirectX. Ele sunt, de asemenea, definite ca standard în specificațiile RenderMan). Cu toate acestea, unele API-uri 3D, cum ar fi OpenGL, folosesc o convenție de tip column-major.

Implicație în codare: Are impact asupra performanței?

Există un alt aspect potențial foarte important de luat în considerare dacă trebuie să alegeți între row-major și column-major, dar acest aspect nu are nimic de-a face cu convențiile în sine și cu cât de practică este una față de cealaltă. Are mai mult de-a face cu calculatorul și cu modul în care acesta funcționează. Nu uitați că vom avea de-a face cu matrici. De obicei, implementarea unei matrice în C++ arată astfel:

class Matrix44{ … float m;};

După cum puteți vedea, cei 16 coeficienți ai matricei sunt stocați într-o matrice bidimensională de float (sau dublu, în funcție de precizia de care aveți nevoie. Clasa noastră Matrix din C++ este un șablon). Ceea ce înseamnă că în memorie cei 16 coeficienți vor fi dispuși în felul următor: c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33. Cu alte cuvinte, acestea sunt dispuse în mod contiguu în memorie. Să vedem acum cum sunt accesați acești coeficienți într-o înmulțire vector-matrice în care vectorii sunt scriși în ordinea rândului major:

// ordine rând-majorx’ = x * c00 + y * c10 + z * c20y’ = x * c01 + y * c11 + z * c21z’ = x * c02 + y * c12 + z * c22

După cum puteți vedea, elementele matricei pentru x’ nu sunt accesate secvențial. Cu alte cuvinte, pentru a calcula x’ avem nevoie de primul, al cincilea și al nouălea float din matricea 16 floats array. Pentru a calcula y’ trebuie să accesăm al 2-lea, al 6-lea și al 10-lea float din această matrice. În cele din urmă, pentru z’ avem nevoie de al 3-lea, al 7-lea și al 11-lea float din această matrice. În lumea calculatoarelor, accesarea elementelor dintr-o matrice într-o ordine nesecvențială nu este neapărat un lucru bun. De fapt, aceasta ar putea degrada performanța cache a procesorului. Nu vom intra în prea multe detalii aici, dar să spunem doar că cea mai apropiată memorie pe care o poate accesa procesorul se numește cache. Această memorie cache este foarte rapid de accesat, dar poate stoca doar un număr foarte limitat de date. Atunci când CPU trebuie să acceseze anumite date, verifică mai întâi dacă acestea există în memoria cache. În caz afirmativ, procesorul accesează imediat aceste date (cache hit), în caz contrar (cache miss), trebuie mai întâi să creeze o intrare în memoria cache pentru acestea, apoi să copieze în această locație datele din memoria principală. Acest proces consumă evident mai mult timp decât în cazul în care datele există deja în memoria cache, astfel încât, în mod ideal, dorim să evităm pe cât posibil ratarea memoriei cache. În plus față de copierea datelor specifice din memoria principală, procesorul copiază, de asemenea, o parte din datele care se află chiar lângă acestea (de exemplu, următorii 24 de octeți), deoarece inginerii hardware au considerat că, de exemplu, în cazul în care codul dvs. trebuie să acceseze un element dintr-o matrice, este probabil să acceseze elementele următoare la scurt timp după acesta. Într-adevăr, în programe, deseori facem buclă peste elementele unui array în ordine secvențială și, prin urmare, este probabil ca această presupunere să fie adevărată. Aplicată la problema matricei noastre, accesarea coeficienților matricei în ordine nesecvențială poate reprezenta, prin urmare, o problemă. Presupunând că procesorul încarcă în memoria cache flotantă flotantul solicitat plus cei 3 flotanți de lângă acesta, implementarea noastră actuală ar putea duce la multe ratări ale memoriei cache, deoarece coeficienții utilizați pentru a calcula x’ y’ și z’ sunt la o distanță de 5 flotanți în matrice. Pe de altă parte, în cazul în care se utilizează o notație de ordinul coloană-major, calcularea lui x’, de exemplu, necesită accesarea primului, celui de-al doilea și celui de-al treilea element al matricei.

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

Coeficienții sunt accesați în ordine secvențială, ceea ce înseamnă, de asemenea, că folosim bine mecanismul cache al CPU (doar 3 ratări ale cache-ului în loc de 9 în exemplul nostru). În concluzie, putem spune că, din punct de vedere al programării, implementarea înmulțirii matricei punctiforme sau vectoriale utilizând o convenție de ordine colum-major ar putea fi mai bună, din punct de vedere al performanței, decât versiunea care utilizează convenția de ordine row-major. În practică, însă, nu am reușit să demonstrăm că acesta este cazul (atunci când vă compilați programul folosind stegulețele de optimizare -O, -O2 sau -O3, compilatorul poate face treaba în locul dvs. prin optimizarea buclelor pe tablouri multidimensionale) și am reușit să folosim cu succes versiunea în ordinea rândului major fără a pierde din performanță în comparație cu o versiune a aceluiași cod care utilizează o implementare în ordinea coloanei major.

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, 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, „Clock time %f\n”, (clock() – start) / float(CLOCKS_PER_SEC)); return 0;}

Row-major and Column-Major Order in Computing

De dragul completării, să menționăm, de asemenea, că termenii row-major și column-major order pot fi, de asemenea, folosiți în calcul pentru a descrie modul în care sunt dispuse în memorie elementele din array-urile multidimensionale. În ordinea rândului major, elementele unei matrice multidimensionale sunt dispuse unul după altul, de la stânga la dreapta, de sus în jos. Aceasta este metoda utilizată de C/C++. De exemplu, matricea:

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

poate fi scrisă în C/C++ sub forma:

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

și elementele acestei matrice ar fi așezate în mod contiguu în memoria liniară ca:

1 2 3 3 4 5 5 6

În ordinea coloană-major, care este utilizată de limbaje precum FORTRAN și MATLAB, elementele matricei sunt stocate în memorie de sus în jos, de la stânga la dreapta. Folosind același exemplu de matrice, elementele matricei ar fi stocate (și accesate) în memorie în felul următor:

1 4 2 2 5 3 3 6

Cunoașterea modului în care elementele unei matrice sunt dispuse în memorie este importantă mai ales atunci când încercați să le accesați folosind offset-ul pointerilor și optimizarea buclelor for (am explicat anterior în acest capitol că aceasta ar putea afecta performanța cache-ului CPU). Cu toate acestea, din moment ce vom lua în considerare doar C/C++ ca limbaj de programare, ordonarea coloană-major (aplicată la calcul) nu prezintă un interes prea mare pentru noi. Menționăm doar ce înseamnă acești termeni în informatică, astfel încât să fiți conștienți de faptul că ei pot descrie două lucruri diferite în funcție de contextul în care sunt utilizați. Ar trebui să aveți grijă să nu le confundați. În contextul matematicii, ei descriu dacă tratați vectorii (sau punctele) ca rânduri de coordonate sau ca coloane și al doilea, iar în contextul informaticii, ei descriu modul în care un anumit limbaj de programare stochează și accesează elementele unei matrice multidimensionale (ceea ce sunt matricile) în memorie.

OpenGL este un caz interesant în această privință. Atunci când GL a fost creat inițial, dezvoltatorii au ales convenția vectorului de rând majorat. Dezvoltatorii care au extins OpenGL au considerat totuși că ar trebui să revină la vectorul coloană-major, ceea ce au și făcut. Cu toate acestea, din motive de compatibilitate, nu au vrut să modifice codul pentru înmulțirea matricei de puncte și au decis, în schimb, să schimbe ordinea în care coeficienții matricei sunt stocați în memorie. Cu alte cuvinte, OpenGL stochează coeficienții în ordinea majorității coloanelor, ceea ce înseamnă că coeficienții de translație m03, m13 și m23 dintr-o matrice care utilizează vectorul majorității coloanelor au indicii 13, 14, 15 în matricea float, la fel ca și coeficienții de translație m30, m31 și m32 dintr-o matrice care utilizează vectorul majorității rândurilor.

Rezumat

Diferențele dintre cele două convenții sunt rezumate în tabelul următor:

Vector rând-major (Matematică) Coluna-vector major (Matematică)

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

\(P/V=\begin{bmatrix}x \\ y \\\ z\end{bmatrix}\)

Pre-multiplicare \(vM\)

Post-multiplicare \(Mv\)

Ordinea de apelare și ordinea în care se aplică transformările sunt aceleași: „ia P, transformă cu T, transformă cu Rz, transformă cu Ry” se scrie ca \(P’=P*T*R_z*R_y\)

Ordinea de apel este inversă ordinii în care se aplică transformările: „ia P, transformă cu T, transformă cu Rz, transformă cu Ry” se scrie ca \(P’=R_y*R_z*T**P\)

API: Direct X, Maya

API: OpenGL, PBRT, Blender

Rândurile matricei reprezintă bazele (sau axele) unui sistem de coordonate (roșu: axa x, verde: axa y, albastru:axa 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} } $$

Coloanele matricei reprezintă bazele (sau axele) unui sistem de coordonate (roșu: axa x, verde: axa y, albastru:axa 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} }$$

Valorile de traducere sunt stocate în elementele c30, c31 și c32.

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

Valorile de traducere sunt stocate în elementele c03, c13 și c23.

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

Transpuneți matricea pentru a o folosi ca o matrice ordonată pe coloane

Transpuneți matricea pentru a o folosi ca o matrice ordonată pe rând.matrice ordonată majoritară

Matrice majoritară de rând (Calculatoare) Matrice majoritară de coloană (Calculatoare)

API: Direct X, Maya, PBRT

API: OpenGL

Un cititor a postat o întrebare pe Stackoverflow sugerând că tabelul de mai sus este confuz. Subiectul este confuz și, în ciuda celor mai bune încercări ale noastre de a face lumină în această privință, mulți oameni încă sunt confuzi în această privință. Ne-am gândit că răspunsul nostru de pe Stackoverflow ar putea, sperăm, să aducă o altă perspectivă asupra întrebării.

Aveți teoria (ceea ce faceți în matematică cu un creion și o hârtie) și ceea ce faceți cu implementarea (C++). Acestea sunt două probleme diferite.

Matematică: poți folosi două notații, fie coloană sau rând majoritar. Cu vectorul majorat pe rând, pe hârtie, trebuie să scrieți înmulțirea vector-matrice vM unde v este vectorul rând (1×4) și M matricea dvs. 4×4. De ce? Pentru că din punct de vedere matematic se poate scrie doar *, și nu invers. În mod similar, dacă folosiți coloana, atunci vectorul trebuie să fie scris pe verticală sau în notație (4 rânduri, 1 coloană). Astfel, înmulțirea cu o matrice poate fi scrisă doar în felul următor: . Rețineți că matricea este pusă în fața vectorului: Mv. Prima notație se numește înmulțire la stânga sau pre-multiplicare (deoarece vectorul se află în partea stângă a produsului), iar cea de-a doua (Mv) se numește înmulțire la dreapta sau post-multiplicare (deoarece vectorul se află în partea dreaptă a produsului). După cum vedeți, termenii derivă din faptul că vectorul se află în partea stângă (în fața sau „pre”) sau în partea dreaptă (după sau „post”) a matricei.

Acum, dacă aveți nevoie să transformați un vector (sau un punct), atunci trebuie să fiți atenți la ordinea înmulțirii, atunci când le scrieți pe hârtie. Dacă doriți să translatați ceva cu matricea T și apoi să rotiți cu R și apoi să scalați cu S, atunci, într-o lume majoritară pe coloane, trebuie să scrieți v’ = S * R * T * v. Într-o lume majoritară pe rând trebuie să scrieți v’ = v * T * R * S.

Aceasta este pentru teorie. Să o numim convenția vectorilor rând/coloană.

Computer: apoi vine momentul în care vă decideți să implementați acest lucru în C++ să zicem. Partea bună este că C++ nu-ți impune nimic despre nimic. Poți să mapezi valorile coeficienților matricei tale în memorie în modul în care dorești și poți scrie codul pentru a efectua o înmulțire a unei matrice cu o altă matrice în modul în care dorești. În mod similar, modul în care accesați coeficienții pentru o înmulțire vector-matrice depinde în totalitate de dumneavoastră. trebuie să faceți o distincție clară între modul în care vă cartografiați coeficienții în memorie și convențiile pe care trebuie să le utilizați din punct de vedere matematic pentru a vă reprezenta vectorii. Acestea sunt două probleme independente. Haideți să numim această parte dispunerea rândurilor/columnei-major.

De exemplu, puteți declara o clasă de matrice ca o matrice de, să zicem, 16 numere flotante contigue. Acest lucru este în regulă. În cazul în care coeficienții m14, m24, m34 reprezintă partea de translație a matricei (Tx, Ty, Tz), astfel încât să presupuneți că „convenția” dvs. este row-major, chiar dacă vi se spune să utilizați convenția matricei OpenGL care se spune că este column-major. Aici, posibila confuzie provine din faptul că cartografierea coeficienților în memorie este diferită de reprezentarea mentală pe care v-o faceți singuri despre o matrice „column-major”. Tu codifici „rând”, dar s-a spus că folosești (din punct de vedere matematic) „coloană”, de unde și dificultatea ta de a înțelege dacă faci lucrurile corect sau greșit.

Ceea ce este important este să vezi o matrice ca pe o reprezentare a unui sistem de coordonate definit de trei axe, și o translație. Unde și cum stocați aceste date în memorie este complet la latitudinea dumneavoastră. Presupunând că cei trei vectori care reprezintă cele trei axe ale sistemului de coordonate sunt numiți AX(x,y,z), AY(x,y,z), AZ(x,y,z), iar vectorul de translație este notat cu (Tx, Ty, Tz), atunci, din punct de vedere matematic, dacă folosiți vectorul coloană aveți:

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

Axele sistemului de coordonate se scriu pe verticală. Acum, dacă aveți dacă folosiți rând-major:

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

Axele sistemului de coordonate sunt scrise pe orizontală. Deci, problema acum, când vine vorba de lumea calculatoarelor, este cum să stocați acești coeficienți în memorie. Puteți la fel de bine să faceți:

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

Vă spune totuși ce convenție folosiți? Nu. De asemenea, puteți scrie:

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

sau:

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

Din nou, acest lucru nu vă oferă o indicație specială cu privire la convenția „matematică” pe care o utilizați. Pur și simplu stocați 16 coeficienți în memorie în moduri diferite și acest lucru este perfect în regulă atâta timp cât știți care este acel mod, astfel încât să îi puteți accesa în mod corespunzător mai târziu. Rețineți acum că un vector înmulțit cu o matrice ar trebui să vă dea același vector, indiferent dacă folosiți o notație matematică pe rând sau pe coloană. Astfel, ceea ce este cu adevărat important este să înmulțiți coordonatele (x,y,z) ale vectorului tău cu coeficienții corecți din matrice, ceea ce necesită cunoașterea modului în care „tu” ai decis să stochezi coeficientul matricei în memorie:

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}

Am scris această funcție pentru a sublinia faptul că, indiferent de convenția pe care o folosiți, rezultatul înmulțirii vectorului * matricei este doar o înmulțire și o adunare între coordonatele de intrare ale vectorului și coordonatele AX, AY și AZ ale axelor sistemului de coordonate (indiferent de notația folosită și indiferent de modul în care le stocați în memorie). Dacă utilizați:

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

Trebuie să apelați:

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

Dacă folosiți:

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

Trebuie să apelați:

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

Îți spune asta ce convenție folosești? Nu. Trebuie doar să apelați coeficienții potriviți în locurile potrivite atunci când faceți o înmulțire vec * mat. Și asta este tot, oricât de deconcertant ar părea.Acum, lucrurile sunt ușor diferite când vine vorba de înmulțirea mat * mat. Puteți presupune că ordinea în care înmulțiți matricile nu este aceeași. Așadar, R * S * T nu este același lucru cu T * S * R. Ordinea contează într-adevăr. Acum, din nou, dacă folosiți „row major”, atunci din punct de vedere matematic trebuie să scrieți:

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

unde ml este matricea din stânga și mr cea din dreapta: mt = ml * mr. Totuși, rețineți că nu am folosit paranteze pentru indicii de acces, deoarece nu dorim să sugerăm că accesăm aici elemente stocate într-o matrice 1D. Vorbim doar despre coeficienții matricelor așa cum sunt scrise pe hârtie. Dacă doriți să scrieți acest lucru în C++, atunci totul depinde de modul în care ați stocat coeficienții în memorie, așa cum am sugerat mai sus.

.

Lasă un răspuns

Adresa ta de email nu va fi publicată.