Meetkunde (Rij-majoor vs Kolom-majoorvector)

Gemeetkunde

Eerder in deze les hebben we uitgelegd dat vectoren (of punten) kunnen worden opgeschreven als matrices (één rij, drie kolommen). Merk echter op dat we ze ook als matrices (drie rijen, één kolom) hadden kunnen schrijven. Technisch zijn deze twee manieren om punten en vectoren als matrices uit te drukken volkomen geldig en de keuze voor de ene of de andere wijze is slechts een kwestie van conventie.

Vector geschreven als matrix: \V=begin{bmatrix}x & y & zeind{bmatrix})

Vector geschreven als matrix: \V=Begin{bmatrix}x & y & z:einde{bmatrix})

In het eerste voorbeeld ( matrix) hebben we onze vector of punt uitgedrukt in wat we noemen de rij-majeur volgorde: de vector (of punt) is geschreven als een rij van drie getallen. In het tweede voorbeeld zeggen we dat punten of vectoren worden geschreven in kolom-majoorvolgorde: we schrijven de drie coördinaten van de vector of het punt verticaal, als een kolom.

Houd in gedachten dat we punten en vectoren uitdrukken als matrices om ze te vermenigvuldigen met transformatiematrices (omwille van de eenvoud zullen we werken met in plaats van matrices). We hebben ook geleerd dat we matrices alleen kunnen vermenigvuldigen als het aantal kolommen van de linker matrix en het aantal rijen van de rechter matrix gelijk zijn. Met andere woorden de matrices en kunnen met elkaar vermenigvuldigd worden maar de matrices en niet. Merk op dat als we een vector schrijven als een matrix we deze kunnen vermenigvuldigen met een matrix (in de veronderstelling dat deze matrix rechts staat in de vermenigvuldiging), maar als we deze vector schrijven als een matrix dan kunnen we deze niet vermenigvuldigen met een matrix. Dit wordt geïllustreerd in de volgende voorbeelden. De binnenmaten (3 en 3) van de matrices die betrokken zijn bij de vermenigvuldiging zijn gelijk (in groen) dus deze vermenigvuldiging is geldig (en het resultaat is een getransformeerd punt geschreven in de vorm van een matrix):

$$* = \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 binnenste dimensies (1 en 3) van de bij de vermenigvuldiging betrokken matrices zijn niet gelijk (in rood), zodat deze vermenigvuldiging niet mogelijk is:

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

Wat doen we dan? De oplossing van dit probleem is niet de vector of het punt met de matrix te vermenigvuldigen, maar de matrix M met de vector V. Met andere woorden, we verplaatsen het punt of de vector naar rechts binnen de vermenigvuldiging:

$$* \begin{bmatrix} c_{00}&c_{01}&{c_{02}} c_{10}&c_{11}&{c_{12}} c_{20}&c_{21}&{c_{22}} c_{00}&{c_{02}} c_{10}&c_{11}&{c_{12}} c_{20}&{c_{22}} Dus we hebben een punt om mee te beginnen en we eindigen met een getransformeerd punt en dat is wat we willen. Probleem opgelost. Samenvattend, wanneer we volgens afspraak vectoren of punten willen uitdrukken in rij-majoorvolgorde (), moeten we het punt links van de vermenigvuldiging zetten en het punt rechts binnen het vermenigvuldigingsteken. Dit wordt in de wiskunde een linker- of voormultiplicatie genoemd. Als je besluit om de vectoren in kolom-majoor volgorde te schrijven (), dan moet de matrix links van de vermenigvuldiging staan en de vector of punt rechts. Dit wordt een rechter- of post-multiplicatie genoemd.

We moeten voorzichtig zijn met hoe deze termen daadwerkelijk worden gebruikt. In de Maya-documentatie staat bijvoorbeeld “de matrices worden in Maya post-multiplicated. Bijvoorbeeld, om een punt P te transformeren van object-ruimte naar wereld-ruimte (P’) zou je moeten post-vermenigvuldigen met de wereldMatrix. (P’ = P x WM)”, wat verwarrend is omdat het eigenlijk een voor-vermenigvuldiging is maar ze spreken over de positie van de matrix ten opzichte van het punt in dit specifieke geval. Dat is eigenlijk een onjuist gebruik van de terminologie. Er had moeten staan dat in Maya punten en vectoren worden uitgedrukt als rij-majeur vectoren en dat ze dus voorvermenigvuldigd worden (wat betekent dat het punt of de vector in de vermenigvuldiging vóór de matrix komt).

De volgende tabel vat de verschillen tussen de twee conventies samen (waarbij P, V en M respectievelijk staan voor Punt, Vector en Matrix).

Rij-majeurvolgorde

(P/V=x & y & z eind{bmatrix})

Linkse of voorvermenigvuldiging

P/V * M

Kolom-hoofdvolgorde

(P/V=begin{bmatrix}x y y y zeind{bmatrix})

Rechter- of na-vermenigvuldiging

M * P/V

Nu we over deze twee conventies hebben geleerd, zou je kunnen vragen “gaat dat niet gewoon over dingen op papier schrijven?”. We weten hoe we het product van twee matrices A en B kunnen berekenen: vermenigvuldig elke coëfficiënt in de huidige rij van A met de bijbehorende elementen in de huidige kolom van B en tel het resultaat bij elkaar op. Laten we deze formule toepassen met de twee conventies en de resultaten vergelijken:

$$

Rij-majeurvolgorde

$${ \begin{bmatrix}x & y & z\eind{bmatrix} * \begin{bmatrix}a & b & c \d & e & f \g & h & i\eind{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}einde{array} }$

Kolom-majeurvolgorde

$${ \begin{bmatrix} a & b & c \d & e & f \g & h & i \eind{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 * zend{array} }$

Vermenigvuldiging van een punt of een vector met een matrix zou hetzelfde resultaat moeten opleveren, of we nu een rij- of een kolommagrootheid gebruiken. Als je een 3D applicatie gebruikt om een punt met een bepaalde hoek rond de z-as te draaien, verwacht je dat het punt na de rotatie in een bepaalde positie ligt, ongeacht welke interne conventie de ontwikkelaar gebruikt om punten en vectoren voor te stellen. Maar zoals je kan zien in de tabel hierboven, zou het vermenigvuldigen van een rij-majoor en kolom-majoor punt (of vector) met dezelfde matrix duidelijk niet hetzelfde resultaat geven. Om weer op de been te komen, moeten we de matrix die gebruikt wordt in de kolom-majoor vermenigvuldiging transponeren om er zeker van te zijn dat x’, y’ en z’ hetzelfde zijn (als je je moet herinneren wat de transpositie van een matrix is, kijk dan in het hoofdstuk Matrixbewerkingen). Dit is wat we krijgen:

$$

Rij-majeurvolgorde

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

$$& }$

$${\begin{array}{l}x’ = x * a + y * d + z * g }x’ = x * b + y * e + z * h }$

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

Kolom-majeurvolgorde

$${ \begin{bmatrix} a & d & g \b & e & h \c & f & iend{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 * zend{array} }$

Van rij- naar kolom-volgorde gaan betekent dus niet alleen dat het punt of de vector en de matrix in de vermenigvuldiging verwisseld moeten worden, maar ook dat de matrix getransponeerd moet worden, om te garanderen dat beide conventies hetzelfde resultaat opleveren (en omgekeerd).

Uit deze waarnemingen kunnen we afleiden dat elke reeks transformaties toegepast op een punt of een vector bij gebruik van een rij-majeur conventie in sequentiële volgorde (of afleesvolgorde) kan worden geschreven. Stel je bijvoorbeeld voor dat je het punt P wilt transleren met matrix T, het dan wilt roteren om de z-as met Rz en dan om de y-as met Ry. U kunt schrijven:

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

Als u een kolom-majeur notatie zou gebruiken, zou u de transformatie in omgekeerde volgorde moeten oproepen (wat men contra-intuïtief zou kunnen vinden):

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

Dus u zou kunnen denken, “er moet een reden zijn om het ene systeem boven het andere te verkiezen”. In feite zijn beide conventies correct en geven ze hetzelfde resultaat, maar om technische redenen worden vectoren in wis- en natuurkundige teksten meestal als kolomvectoren behandeld.

De volgorde van transformatie wanneer we colum-major matrices gebruiken, lijkt in de wiskunde meer op de manier waarop we functiewaardering en samenstelling schrijven.

De row-major matrixconventie maakt matrices echter gemakkelijker te onderwijzen, wat de reden is dat we deze gebruiken voor Scratchapixel (en ook voor Maya, DirectX. Ze zijn ook gedefinieerd als de standaard in de RenderMan specificaties). Sommige 3D API’s, zoals OpenGL, gebruiken echter een kolom-majeur conventie.

Implicatie in codering: Does it Impact Performance?

Er is nog een potentieel zeer belangrijk aspect om in overweging te nemen als u moet kiezen tussen rij-majeur en kolom-majeur, maar dit heeft niets echt te maken met de conventies zelf en hoe praktisch de een is boven de ander. Het heeft meer te maken met de computer en de manier waarop die werkt. Onthoud dat we te maken zullen hebben met matrices. Typisch ziet de implementatie van een matrix in C++ er als volgt uit:

class Matrix44{ … float m;};

Zoals u kunt zien worden de 16 coëfficiënten van de matrix opgeslagen in een tweedimensionale array van floats (of dubbels, afhankelijk van de precisie die u nodig heeft. Onze C++ Matrix klasse is een template). Dit betekent dat in het geheugen de 16 coëfficiënten op de volgende manier worden geordend: c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33. Met andere woorden, ze liggen aaneengesloten in het geheugen. Laten we nu eens kijken hoe deze coëfficiënten worden benaderd in een vector-matrix vermenigvuldiging waar vectoren worden geschreven in rij-majoor volgorde:

// rij-majoor volgordex’ = x * c00 + y * c10 + z * c20y’ = x * c01 + y * c11 + z * c21z’ = x * c02 + y * c12 + z * c22

Zoals u ziet worden de elementen van de matrix voor x’ niet opeenvolgend benaderd. Met andere woorden, om x’ te berekenen hebben we de 1ste, 5de en 9de float van de matrix 16 floats array nodig. Om y’ te berekenen hebben we de 2e, 6e en 10e float van deze matrix nodig. En voor z’ tenslotte hebben we de 3de, 7de en 11de float van de matrix nodig. In de wereld van computers is het niet noodzakelijkerwijs een goede zaak om elementen uit een array in een niet-volgorde te benaderen. Het kan zelfs de cache prestaties van de CPU aantasten. We zullen hier niet te veel in detail treden, maar laten we zeggen dat het dichtstbijzijnde geheugen waartoe de CPU toegang heeft, een cache wordt genoemd. Deze cache is zeer snel te benaderen, maar kan slechts een zeer beperkt aantal gegevens opslaan. Wanneer de CPU toegang wil tot bepaalde gegevens, controleert hij eerst of die in de cache aanwezig zijn. Als dat het geval is, krijgt de CPU meteen toegang tot deze gegevens (cache hit), maar als dat niet het geval is (cache miss), moet hij eerst een entry in de cache aanmaken en vervolgens de gegevens uit het hoofdgeheugen naar deze locatie kopiëren. Dit proces is uiteraard tijdrovender dan wanneer de gegevens al in de cache aanwezig zijn, dus idealiter willen we cache misses zoveel mogelijk voorkomen. Naast het kopiëren van de specifieke gegevens uit het hoofdgeheugen, kopieert de CPU ook een stuk van de gegevens die er vlak naast staan (bijvoorbeeld de volgende 24 bytes), omdat hardware-ingenieurs bedachten dat als je code bijvoorbeeld een element van een array moest benaderen, het waarschijnlijk was dat het snel daarna de volgende elementen zou benaderen. In programma’s maken we inderdaad vaak een lus over elementen van een matrix in opeenvolgende volgorde, en deze aanname is dus waarschijnlijk waar. Toegepast op ons matrixprobleem kan de toegang tot de coëfficiënten van de matrix in niet-sequentiële volgorde dus een probleem zijn. Ervan uitgaande dat de CPU de gevraagde float in de cache laadt plus de 3 floats ernaast, zou onze huidige implementatie kunnen leiden tot veel cache misses, omdat de coëfficiënten die gebruikt worden om x’ y’ en z’ te berekenen 5 floats uit elkaar liggen in de array. Aan de andere kant, als je een kolom-majoor-notatie gebruikt, moet je voor het berekenen van x’ bijvoorbeeld de 1e, 2e en 3e elementen van de matrix benaderen.

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

De coëfficiënten worden in sequentiële volgorde benaderd, wat ook betekent dat we goed gebruik maken van het CPU cache mechanisme (slechts 3 cache misses in plaats van 9 in ons voorbeeld). Concluderend kunnen we zeggen dat, vanuit een programmeer standpunt, het implementeren van onze punt- of vector-matrix vermenigvuldiging met behulp van een kolom-majoor volgorde conventie beter zou kunnen zijn, wat betreft prestaties, dan de versie die gebruik maakt van de rij-majoor volgorde conventie. In de praktijk hebben we echter niet kunnen aantonen dat dit het geval was (als je je programma compileert met de optimalisatievlaggen -O, -O2 of -O3, kan de compiler het werk voor je doen door lussen over multidimensionale arrays te optimaliseren) en we hebben met succes de rij-majoor-volgorde versie gebruikt zonder enig verlies van prestatie ten opzichte van een versie van dezelfde code met een kolom-majoor-volgorde implementatie.

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<typenaam T>klasse 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, “Kloktijd %f\n”, (clock() – start) / float(CLOCKS_PER_SEC)); return 0;

Rij-majeur en kolom-majeur volgorde in berekeningen

Voor de volledigheid vermelden we ook nog even dat de termen rij-majeur en kolom-majeur volgorde ook gebruikt kunnen worden in berekeningen om de manier te beschrijven waarop elementen van multidimensionale arrays in het geheugen worden geplaatst. In rij-majeur volgorde worden de elementen van een multidimensionale array achter elkaar gezet, van links naar rechts, van boven naar beneden. Dit is de methode die door C/C++ wordt gebruikt. Bijvoorbeeld de matrix:

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

zou in C/C++ geschreven kunnen worden als:

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

en de elementen van deze array zouden contigu in het lineaire geheugen worden geplaatst als:

1 2 3 4 5 6

In kolom-majeur volgorde, die wordt gebruikt in talen als FORTRAN en MATLAB, worden de elementen van de matrix van boven naar beneden, van links naar rechts in het geheugen opgeslagen. In hetzelfde matrixvoorbeeld zouden de elementen van de matrix op de volgende manier in het geheugen worden opgeslagen (en benaderd):

1 4 2 5 3 6

Weten hoe de elementen van een matrix in het geheugen staan is belangrijk, vooral als je ze probeert te benaderen met pointer offset en for loop optimalisatie (we hebben eerder in dit hoofdstuk uitgelegd dat het de CPU cache prestatie kan beïnvloeden). Maar omdat we alleen C/C++ als onze programmeertaal beschouwen, is kolom-majeur ordening (toegepast op computergebruik) niet van groot belang voor ons. We vermelden alleen wat de termen betekenen in de informatica, zodat je je ervan bewust bent dat ze twee verschillende dingen kunnen beschrijven, afhankelijk van de context waarin ze worden gebruikt. Je moet oppassen dat je ze niet door elkaar haalt. In de context van de wiskunde beschrijven ze of je vectoren (of punten) behandelt als rijen coördinaten of als kolommen en de tweede, en in de context van de informatica beschrijven ze de manier waarop een bepaalde programmeertaal elementen van multidimensionale arrays (wat matrices zijn) in het geheugen opslaat en benadert.

OpenGL is in dat opzicht een interessant geval. Toen GL in eerste instantie werd gemaakt, kozen de ontwikkelaars voor de rij-majeur vector conventie. Ontwikkelaars die OpenGL uitbreidden vonden echter dat ze terug moesten gaan naar de kolom-majeur vector, wat ze ook deden. Maar om compatibiliteitsredenen wilden zij de code voor de punt-matrix vermenigvuldiging niet veranderen en besloten zij in plaats daarvan de volgorde te veranderen waarin de coëfficiënten van de matrix in het geheugen werden opgeslagen. Met andere woorden, OpenGL slaat de coëfficiënten op in kolom-majeur volgorde, wat betekent dat de vertaalcoëfficiënten m03, m13 en m23 van een matrix die een kolom-majeur vector gebruikt indices 13, 14, 15 in de float array hebben, net als de vertaalcoëfficiënten m30, m31 en m32 van een matrix die een rij-majeur vector gebruikt.

Samenvatting

De verschillen tussen de twee conventies worden in de volgende tabel samengevat:

Rij-majeurvector (Wiskunde) Kolom-hoofdvector (wiskunde)

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

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

Voor-vermenigvuldiging (vM)

Post-vermenigvuldiging (Mv)

De volgorde van de oproep en de volgorde waarin de transformaties worden toegepast is dezelfde: “neem P, transformeer door T, transformeer door Rz, transformeer door Ry” wordt geschreven als P’=P*T*R_z*R_y)

De oproepvolgorde is het omgekeerde van de volgorde waarin de transformaties worden toegepast: “neem P, transformeer door T, transformeer door Rz, transformeer door Ry” wordt geschreven als

API: Direct X, Maya

API: OpenGL, PBRT, Blender

De rijen van de matrix vertegenwoordigen de grondslagen (of assen) van een coördinatenstelsel (rood: x-as, groen: y-as, blauw: z-as)

$${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} } $$

De kolommen van de matrix stellen de grondslagen (of assen) van een coördinatenstelsel voor (rood: x-as, groen: y-as, blauw: z-as)

$${ \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} }$$

De vertaalwaarden worden opgeslagen in de elementen c30, c31 en c32.

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

De vertaalwaarden worden opgeslagen in de elementen c03, c13 en c23.

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

Transponeer de matrix om hem te gebruiken als een kolom-majoor geordende matrix

Transponeer de matrix om hem te gebruiken als een rij-majoor geordende matrix

majeur geordende matrix

Rij-majeur matrix (Computing) Kolom-majeur matrix (Computing)

API: Direct X, Maya, PBRT

API: OpenGL

Een lezer plaatste een vraag op Stackoverflow waarin hij suggereerde dat de bovenstaande tabel verwarrend was. Het onderwerp is verwarrend en ondanks onze beste poging om wat licht op de zaak te werpen, raken veel mensen er nog steeds van in de war. We dachten dat ons antwoord op Stackoverflow hopelijk wat meer inzicht in de vraag zou kunnen brengen.

Je hebt de theorie (wat je in de wiskunde doet met pen en papier) en wat je doet met je implementatie (C++). Dat zijn twee verschillende problemen.

Wiskunde: je kunt twee notaties gebruiken, kolom- of rij-majeur. Met rij-majeur vector, op papier, moet je de vector-matrix vermenigvuldiging vM schrijven waar v de rij vector is (1×4) en M je 4×4 matrix. Waarom? Omdat je wiskundig alleen * kunt schrijven, en niet andersom. Evenzo als je kolom gebruikt, dan moet de vector verticaal worden opgeschreven, of in notatie (4 rijen, 1 kolom). De vermenigvuldiging met een matrix kan dus alleen als volgt geschreven worden: . Merk op dat de matrix voor de vector gezet wordt: Mv. De eerste notatie heet een linker- of voorvermenigvuldiging (omdat de vector links van het product staat) en de tweede (Mv) heet een rechter- of na-vermenigvuldiging (omdat de vector rechts van het product staat). Zoals u ziet vloeien de termen voort uit het feit of de vector zich aan de linkerkant (voor, of “pre”) of aan de rechterkant (na, of “post”) van de matrix bevindt.

Nu, als u een vector (of een punt) moet transformeren, dan moet u letten op de volgorde van de vermenigvuldigingen, wanneer u ze op papier zet. Als je iets wilt vertalen met matrix T en dan roteren met R en dan schalen met S, dan moet je in een kolom-majeur wereld, schrijven v’ = S * R * T * v. In een rij-majeur wereld moet je schrijven v’ = v * T * R * S.

Dat is voor de theorie. Laten we dat de rij/kolom vector conventie noemen.

Computer: dan komt het punt dat je besluit om dit te implementeren in C++ zeg maar. Het goede hiervan is dat C++ je niets oplegt. U kunt de waarden van de coëfficiënten van uw matrix in het geheugen mappen zoals u wilt, en u kunt de code schrijven om een matrixvermenigvuldiging met een andere matrix uit te voeren zoals u wilt. Op dezelfde manier hoe je de coëfficiënten voor een vector-matrix vermenigvuldiging benadert is volledig aan jou. Je moet een duidelijk onderscheid maken tussen hoe je je coëfficiënten in het geheugen mapt en welke conventies je wiskundig gezien moet gebruiken om je vectoren weer te geven. Dit zijn twee onafhankelijke problemen. Laten we dit deel de rij/kolom-major layout noemen.

U kunt bijvoorbeeld een matrixklasse declareren als een array van zeg 16 aaneengesloten floats. Dat is prima. Waar coëfficiënten m14, m24, m34 het translatie deel van de matrix vertegenwoordigen (Tx, Ty, Tz), dus u neemt aan dat uw “conventie” rij-majeur is, ook al wordt u verteld om OpenGL matrix conventie te gebruiken die kolom-majeur zou zijn. Hier komt de mogelijke verwarring voort uit het feit dat de toewijzing van de coëfficiënten in het geheugen anders is dan de mentale voorstelling die u zich maakt van een “kolom-majeur” matrix. U codeert “rij” maar u zou (wiskundig gezien) “kolom” gebruiken, vandaar uw moeilijkheid om te begrijpen of u het goed of fout doet.

Wat belangrijk is, is een matrix te zien als een voorstelling van een coördinatenstelsel gedefinieerd door drie assen, en een translatie. Waar en hoe je deze gegevens in het geheugen opslaat is geheel aan jou. Veronderstel dat de drie vectoren die de drie assen van het coordinaten systeem voorstellen AX(x,y,z), AY(x,y,z), AZ(x,y,z) heten, en de translatie vector wordt aangeduid met (Tx, Ty, Tz), dan heb je wiskundig als je een kolom vector gebruikt:

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

De assen van het coördinatenstelsel worden verticaal geschreven. Als je nu rij-majeur gebruikt:

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

De assen van het coördinatenstelsel worden horizontaal geschreven. Het probleem in de computerwereld is nu, hoe sla je deze coëfficiënten op in het geheugen. Je kunt net zo goed doen:

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

Gaat het je dan vertellen welke conventie je gebruikt? Nee. Je kunt ook schrijven:

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

of:

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

Wederom geeft dat niet bepaald aan welke “wiskundige” conventie je gebruikt. Je slaat gewoon 16 coëfficiënten op verschillende manieren op in het geheugen en dat is perfect in orde, zolang je maar weet wat die manier is, zodat je ze later op de juiste manier kunt benaderen. Bedenk dat een vector vermenigvuldigd met een matrix dezelfde vector oplevert, of je nu een rij- of een kolom-wiskundige notatie gebruikt. Wat dus echt belangrijk is, is dat je de (x,y,z) coördinaten van je vector vermenigvuldigt met de juiste coëfficiënten uit de matrix, waarvoor je moet weten hoe “jij” besloten hebt de matrixcoëfficiënt in het geheugen op te slaan:

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}

We schreven deze functie om het feit te onderstrepen dat, ongeacht welke conventie je gebruikt, het resultaat van de vector * matrix vermenigvuldiging gewoon een vermenigvuldiging en een optelling is tussen de ingangscoördinaten van de vector en de ascoördinaten AX, AY en AZ van het coördinatensysteem (ongeacht de notatie die je gebruikt, en ongeacht de manier waarop je ze in het geheugen opslaat). Als u:

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

U moet aanroepen:

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

Als u:

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

U moet aanroepen:

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

Zegt dat je welke conventie je gebruikt? Nee. Je hoeft alleen maar de juiste coëfficiënten op de juiste plaats aan te roepen als je een vec * mat vermenigvuldiging doet. En dat is alles, hoe onthutsend het ook mag lijken.Nu liggen de zaken iets anders als het gaat om mat * mat vermenigvuldiging. Je kunt ervan uitgaan dat de volgorde waarin je de matrices vermenigvuldigt niet dezelfde is. Dus R * S * T is niet hetzelfde als T * S * R. De volgorde doet er inderdaad toe. Als je nu weer “rij-majoor” gebruikt, dan moet je wiskundig schrijven:

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

waar ml de linkse matrix is en mr de rechtse: mt = ml * mr. Merk echter op dat we geen haakjes gebruiken voor de toegangsindices omdat we niet willen suggereren dat we hier elementen benaderen die in een 1D matrix zijn opgeslagen. We hebben het gewoon over de coëfficiënten van matrices zoals ze op papier staan. Als u dit in C++ wilt schrijven, dan hangt alles af van hoe u uw coëfficiënten in het geheugen hebt opgeslagen zoals hierboven gesuggereerd.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.