Geometri (række- og kolonnevektor)

Geometri

Førre i denne lektion har vi forklaret, at vektorer (eller punkter) kan skrives ned som matricer (en række og tre kolonner). Bemærk dog, at vi også kunne have skrevet dem op som matricer (tre rækker, en kolonne). Teknisk set er disse to måder at udtrykke punkter og vektorer som matricer på fuldt ud gyldige, og valget af den ene eller den anden måde er blot et spørgsmål om konvention.

Vektor skrevet som matrix: \( V=\begin{bmatrix}x & y & y & z\end{bmatrix}\)

Vektor skrevet som matrix: \(V=\begin{bmatrix}x\y\\z\end{bmatrix}\)

I det første eksempel ( matrix) har vi udtrykt vores vektor eller punkt i det, vi kalder række-major orden: vektoren (eller punktet) er skrevet som en række af tre tal. I det andet eksempel siger vi, at at punkter eller vektorer er skrevet i kolonne-major orden: vi skriver vektorens eller punktets tre koordinater lodret, som en kolonne.

Husk, at vi udtrykker punkter og vektorer som matricer for at gange dem med transformationsmatricer (for enkelhedens skyld vil vi arbejde med i stedet for matricer). Vi har også lært, at vi kun kan gange matricer, når antallet af kolonner fra den venstre matrix og antallet af rækker fra den højre matrix er det samme. Med andre ord kan matricerne og multipliceres med hinanden, men det kan matricerne og ikke. Bemærk, at hvis vi skriver en vektor som en matrix, kan vi gange den med en matrix (forudsat at denne matrix er til højre inde i multiplikationen), men hvis vi skriver denne vektor som en matrix, kan vi ikke gange den med en matrix. Dette illustreres i de følgende eksempler. De indre dimensioner (3 og 3) af de matricer, der er involveret i multiplikationen, er de samme (i grønt), så denne multiplikation er gyldig (og resultatet er et transformeret punkt skrevet i form af en 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 indre dimensioner (1 og 3) af de matricer, der er involveret i multiplikationen, er ikke de samme (i rødt), så denne multiplikation er ikke mulig:

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

Så hvad gør vi nu? Løsningen på dette problem er ikke at multiplicere vektoren eller punktet med matricen, men matricen M med vektoren V. Med andre ord flytter vi punktet eller vektoren til højre inden for multiplikationen:

$$$* \rightarrow \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} = \begin{bmatrix}x’\\y’\\z’\end{bmatrix}$$$

Bemærk, at resultatet af denne operation er et transformeret punkt, skrevet i form af en matrix. Vi får altså et punkt til at starte med, og vi slutter med et transformeret punkt, hvilket er det, vi ønsker. Problemet er løst. For at opsummere, når vi ved konvention beslutter os for at udtrykke vektorer eller punkter i række-major orden (), skal vi sætte punktet på venstre side af multiplikationen og punktet på højre side inden for multiplikationstegnet. Dette kaldes i matematikken en venstre- eller præ-multiplikation. Hvis man i stedet vælger at skrive vektorerne i kolonne-major-orden (), skal matrixen stå på venstre side af multiplikationen og vektoren eller punktet på højre side. Dette kaldes en højre eller post-multiplikation.

Vi skal være forsigtige med, hvordan disse udtryk faktisk bruges. For eksempel siger Maya-dokumentationen “matricerne er post-multipliceret i Maya. For at transformere et punkt P fra objekt-rum til verdens-rum (P’) skal du f.eks. eftermultiplicere med worldMatrix. (P’ = P x WM)”, hvilket er forvirrende, fordi det faktisk er en præ-multiplikation, men de taler om matrixens position i forhold til punktet i dette særlige tilfælde. Det er faktisk en forkert brug af terminologien. Der skulle have stået, at i Maya udtrykkes punkter og vektorer som række-major-vektorer, og at de derfor er præ-multiplicerede (hvilket betyder, at punktet eller vektoren optræder før matricen i multiplikationen).

Den følgende tabel opsummerer forskellene mellem de to konventioner (hvor P, V og M står for henholdsvis punkt, vektor og matrix).

Række-major orden

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

Links eller pre-multiplikation

P/V * M

Kolonne-større orden

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

Ret eller post-multiplikation

M * P/V

Nu, hvor vi har lært om disse to konventioner, vil du måske spørge “handler det ikke bare om at skrive ting på papir?”. Vi ved, hvordan man beregner produktet af to matricer A og B: multiplicer hver koefficient inden for A’s aktuelle række med de tilhørende elementer inden for B’s aktuelle kolonne og summer resultatet. Lad os anvende denne formel ved hjælp af de to konventioner og sammenligne resultaterne:

Række-major orden

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

Søjle-major orden

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

Multiplikation af et punkt eller en vektor med en matrix bør give os det samme resultat, uanset om vi bruger række- eller kolonne-majororden. Hvis du bruger et 3D-program til at rotere et punkt med en bestemt vinkel omkring z-aksen, forventer du, at punktet er i en bestemt position efter rotationen, uanset hvilken intern konvention udvikleren har brugt til at repræsentere punkter og vektorer. Men som du kan se ved at se på tabellen ovenfor, ville det klart ikke give det samme resultat at gange et punkt (eller en vektor) med samme matrix, hvis man multiplicerer et punkt (eller en vektor) med række-major og et punkt (eller vektor) med kolonne-major. For at komme på fode igen ville vi faktisk være nødt til at transponere den matrix, der anvendes i kolonne-major-multiplikationen, for at sikre os, at x’, y’ og z’ er de samme (hvis du skal huske, hvad transponering af en matrix er, kan du læse i kapitlet om matrixoperationer). Her er, hvad vi får:

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

Søjle-major orden

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

Det kan konkluderes, at man ved at gå fra række-major orden til kolonne-major orden ikke kun skal bytte om på punktet eller vektoren og matrixen i multiplikationen, men også transponere matrixen for at sikre, at begge konventioner giver det samme resultat (og omvendt).

Fra disse observationer kan vi se, at enhver serie af transformationer, der anvendes på et punkt eller en vektor, når der anvendes en række-major-konvention, kan skrives i sekventiel rækkefølge (eller læserækkefølge). Forestil dig f.eks. at du ønsker at translatere punkt P med matrix T og derefter rotere det rundt om z-aksen med Rz og derefter rundt om y-aksen med Ry. Du kan skrive:

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

Hvis du skulle bruge en kolonne-major notation ville du være nødt til at kalde transformationen i omvendt rækkefølge (hvilket man kan finde kontra-intuitivt):

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

Så du tænker måske: “Der må være en grund til at foretrække det ene system frem for det andet”. Faktisk er begge konventioner korrekte og giver os det samme resultat, men af visse tekniske årsager behandler matematiske og fysiske tekster generelt vektorer som kolonnevektorer.

Transformationsrækkefølge, når vi bruger kolum-major matricer, svarer i matematik mere til den måde, vi skriver funktionsvurdering og komposition på.

Række-major matrixkonvention gør dog matricer lettere at lære, hvilket er grunden til, at vi bruger den til Scratchapixel (samt Maya, DirectX. De er også defineret som standard i RenderMan-specifikationerne). Nogle 3D-API’er, såsom OpenGL, bruger dog en kolonne-major-konvention.

Implikation i kodning:

Der er et andet potentielt meget vigtigt aspekt at tage i betragtning, hvis du skal vælge mellem row-major og column-major, men det har ikke rigtig noget at gøre med selve konventionerne, og hvor praktisk den ene er i forhold til den anden. Det har mere at gøre med computeren og den måde, den fungerer på. Husk, at vi skal arbejde med matricer. Typisk ser implementeringen af en matrix i C++ således ud:

class Matrix44{ … float m;};

Som du kan se, er de 16 koefficienter i matrixen gemt i et todimensionelt array af floats (eller doubles, afhængigt af den præcision, du har brug for. Vores C++ Matrix-klasse er en skabelon). Hvilket betyder, at i hukommelsen vil de 16 koefficienter blive lagt ud på følgende måde: c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33. De er med andre ord anbragt sammenhængende i hukommelsen. Lad os nu se, hvordan disse koefficienter tilgås i en vektor-matrixmultiplikation, hvor vektorerne er skrevet i række-major-orden:

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

Som du kan se, er der ikke adgang til elementerne i matrixen for x’ i rækkefølge. Med andre ord skal vi for at beregne x’ bruge den 1., 5. og 9. float i matrixens 16 floats array. For at beregne y’ har vi brug for adgang til den 2., 6. og 10. float i dette array. Og endelig skal vi til z’ bruge den 3., 7. og 11. float fra arrayet. I computerverdenen er det ikke nødvendigvis en god ting at få adgang til elementer fra et array i en ikke-sekventiel rækkefølge. Det kan faktisk potentielt forringe CPU’ens cache-ydelse. Vi vil ikke gå for meget i detaljer her, men lad os bare sige, at den nærmeste hukommelse, som CPU’en kan få adgang til, kaldes en cache. Denne cache er meget hurtig at få adgang til, men kan kun lagre et meget begrænset antal data. Når CPU’en har brug for at få adgang til nogle data, kontrollerer den først, om de findes i cachen. Hvis det er tilfældet, får CPU’en adgang til dataene med det samme (cache hit), men hvis det ikke er tilfældet (cache miss), skal den først oprette en post i cachen til dataene og derefter kopiere dataene fra hovedhukommelsen til dette sted. Denne proces er naturligvis mere tidskrævende, end når dataene allerede findes i cachen, så ideelt set ønsker vi at undgå cache misses så meget som muligt. Ud over at kopiere de pågældende data fra hovedhukommelsen kopierer CPU’en også en del af de data, der ligger lige ved siden af (f.eks. de næste 24 bytes), fordi hardwareingeniørerne har regnet ud, at hvis din kode f.eks. skal have adgang til et element i et array, vil den sandsynligvis få adgang til de efterfølgende elementer kort efter. I programmer sløjfer vi faktisk ofte over elementer i et array i sekventiel rækkefølge, og denne antagelse er derfor sandsynligvis sand. Anvendt på vores matrixproblem kan det derfor være et problem at få adgang til matrixens koefficienter i en ikke-sekventiel rækkefølge. Hvis vi antager, at CPU’en indlæser den ønskede float i cachen plus de 3 floats ved siden af, kan vores nuværende implementering føre til mange cache-misses, da de koefficienter, der anvendes til at beregne x’ y’ og z’, ligger 5 floats fra hinanden i matrixen. På den anden side, hvis man bruger en kolonne-major-ordningsnotation, kræver beregningen af x’ f.eks. adgang til matrixens første, anden og tredje element.

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

Koefficienterne tilgås i sekventiel rækkefølge, hvilket også betyder, at vi gør god brug af CPU’ens cache-mekanisme (kun 3 cache-misses i stedet for 9 i vores eksempel). Afslutningsvis kan vi sige, at ud fra et programmeringssynspunkt kan det være bedre at gennemføre vores punkt- eller vektor-matrixmultiplikation ved hjælp af en kolum-major-ordenskonvention, hvad angår ydeevne, end den version, der anvender en række-major-ordenskonvention. I praksis har vi dog ikke kunnet påvise, at dette faktisk var tilfældet (når du kompilerer dit program ved hjælp af optimeringsflagene -O, -O2 eller -O3, kan compileren gøre arbejdet for dig ved at optimere loops over flerdimensionale arrays), og vi har med succes anvendt row-major order-versionen uden nogen form for tab af ydeevne sammenlignet med en version af den samme kode, der anvender en kolonne-major order-implementering.

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.z * m, v.x * m + v.y * m + v.z * m);#endif }}}; #include <cmath>#include <cstdlib>#include <cstdio>#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 og Column-Major Order in Computing

For fuldstændighedens skyld skal vi også lige nævne, at udtrykkene row-major og column-major order også kan bruges i computerbrug til at beskrive den måde, hvorpå elementer i flerdimensionale arrays er lagt ud i hukommelsen. I række-major rækkefølge er elementerne i et flerdimensionalt array placeret efter hinanden, fra venstre mod højre og fra top til bund. Dette er den metode, der anvendes i C/C++. F.eks. kan matricen:

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

skulle kunne skrives i C/C++ som:

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

og elementerne i dette array ville blive lagt ud sammenhængende i lineær hukommelse som:

1 2 3 4 5 5 6

I kolonne-major-orden, som anvendes af sprog som FORTRAN og MATLAB, lagres matrixens elementer i hukommelsen fra top til bund, fra venstre til højre. Hvis vi bruger det samme matrixeksempel, ville matrixens elementer blive lagret (og tilgås) i hukommelsen på følgende måde:

1 4 4 2 5 5 3 6

Det er vigtigt at vide, hvordan elementerne i en matrix er placeret i hukommelsen, især når du forsøger at få adgang til dem ved hjælp af pointer offset og for loop-optimering (vi har tidligere i dette kapitel forklaret, at det kan påvirke CPU-cacheydelsen). Da vi imidlertid kun vil betragte C/C++ som vores programmeringssprog, er kolonne-major-ordnering (anvendt på databehandling) ikke af stor interesse for os. Vi nævner kun, hvad begreberne betyder inden for datalogi, så du er opmærksom på, at de kan beskrive to forskellige ting afhængigt af den sammenhæng, hvori de anvendes. Du bør være forsigtig med ikke at blande dem sammen. I matematisk sammenhæng beskriver de, om man behandler vektorer (eller punkter) som rækker af koordinater eller som kolonner og det andet, og i forbindelse med databehandling beskriver de den måde, hvorpå et bestemt programmeringssprog gemmer og tilgår elementer af flerdimensionale array (som matricer er) i hukommelsen.

OpenGL er et interessant tilfælde i den henseende. Da GL oprindeligt blev skabt, valgte udviklerne den række-major vektorkonvention. Udviklere, der udvidede OpenGL, mente dog, at de skulle gå tilbage til column-major vector, hvilket de også gjorde. Af kompatibilitetshensyn ønskede de imidlertid ikke at ændre koden til punkt-matrixmultiplikationen og besluttede i stedet at ændre rækkefølgen, i hvilken matrixens koefficienter blev lagret i hukommelsen. OpenGL lagrer med andre ord koefficienterne i kolonne-major-orden, hvilket betyder, at translationskoefficienterne m03, m13 og m23 fra en matrix, der anvender kolonne-major-vektor, har indeks 13, 14 og 15 i float arrayet, ligesom translationskoefficienterne m30, m31 og m32 fra en matrix, der anvender række-major-vektor.

Summarum

Forskellene mellem de to konventioner er opsummeret i følgende tabel:

Row-major vektor (Matematik) Kolonne-hovedvektor (Matematik)

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

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

Pre-multiplikation \(vM\)

Post-multiplikation \(Mv\)

Opkaldsrækkefølgen og den rækkefølge, transformationerne anvendes, er den samme: “Tag P, transformér med T, transformér med Rz, transformér med Ry” skrives som \(P’=P*T*R_z*R_y\)

Ansøgningsrækkefølgen er den omvendte af den rækkefølge, i hvilken transformationerne anvendes: “tag P, transformér med T, transformér med Rz, transformér med Ry” skrives som \(P’=R_y*R_z*T*P\)

API: Direct X, Maya

API: OpenGL, PBRT, Blender

Rækkerne i matricen repræsenterer baserne (eller akserne) i et koordinatsystem (rød: x-akse, grøn: y-akse, blå:z-akse)

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

Kolonnerne i matricen repræsenterer baserne (eller akserne) i et koordinatsystem (rød: x-akse, grøn: y-akse, blå:z-akse)

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

Oversættelsesværdierne er gemt i elementerne c30, c31 og c32.

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

Oversættelsesværdierne er gemt i elementerne c03, c13 og c23.

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

Transponere matrixen for at bruge den som en kolonne-major-orderet matrix

Transponere matrixen for at bruge den som en række-major ordered matrix

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

API: Direct X, Maya, PBRT

API: OpenGL

En læser stillede et spørgsmål på Stackoverflow, hvori han antydede, at ovenstående tabel var forvirrende. Emnet er forvirrende, og på trods af vores bedste forsøg på at kaste lidt lys over sagen, er der stadig mange mennesker, der bliver forvirrede over det. Vi tænkte, at vores svar på Stackoverflow forhåbentlig kunne bringe en anden indsigt i spørgsmålet.

Du har teorien (det du gør i matematik med pen og papir) og det du gør med din implementering (C++). Det er to forskellige problemer.

Matematik: Du kan bruge to notationer, enten kolonne- eller række-major. Med row major vektor skal du på papiret skrive vektor-matrixmultiplikationen vM, hvor v er rækkevektoren (1×4) og M din 4×4-matrix. Hvorfor? Fordi man matematisk set kun kan skrive *, og ikke omvendt. Tilsvarende hvis du bruger kolonne, så skal vektoren skrives lodret nedad, eller i notation (4 rækker, 1 kolonne). Multiplikationen med en matrix kan således kun skrives således: . Bemærk, at matrixen sættes foran vektoren: Mv. Den første notation kaldes en venstre- eller før-multiplikation (fordi vektoren er på venstre side af produktet) og den anden (Mv) kaldes en højre- eller efter-multiplikation (fordi vektoren er på højre side af produktet). Som du kan se, stammer udtrykkene fra, om vektoren er på venstre side (foran, eller “pre”) eller på højre side (efter, eller “post”) af matricen.

Nu, hvis du skal transformere en vektor (eller et punkt), så skal du være opmærksom på rækkefølgen af multiplikationerne, når du skriver dem ned på papiret. Hvis du vil translatere noget med matrixen T og derefter rotere med R og derefter skalere med S, så skal du i en kolonne-major-verden skrive v’ = S * R * T * v. I en række-major-verden skal du skrive v’ = v * T * R * S.

Det er for teoriens skyld. Lad os kalde det for row/column vektorkonvention.

Computer: Så kommer det punkt, hvor du beslutter dig for at implementere dette i C++ f.eks. Det gode ved dette er, at C++ ikke pålægger dig noget om noget som helst. Du kan kortlægge værdierne af dine matrixkoefficienter i hukommelsen på den måde, du ønsker, og du kan skrive koden til at udføre en matrixmultiplikation med en anden matrix på den måde, du ønsker. På samme måde er det helt op til dig, hvordan du får adgang til koefficienterne for en vektor-matrixmultiplikation, men du skal skelne klart mellem, hvordan du kortlægger dine koefficienter i hukommelsen, og hvilke konventioner du skal bruge ud fra et matematisk synspunkt for at repræsentere dine vektorer. Der er tale om to uafhængige problemer. Lad os kalde denne del for række/kolonne-major-layoutet.

For eksempel kan du deklarere en matrixklasse som et array af f.eks. 16 sammenhængende floats. Det er fint nok. Hvor koefficienterne m14, m24, m34 repræsenterer translationsdelen af matricen (Tx, Ty, Tz), så du antager, at din “konvention” er row-major, selv om du får besked på at bruge OpenGL-matrixkonvention, som siges at være column-major. Her kommer den mulige forvirring fra det faktum, at afbildningen af koefficienterne i hukommelsen er forskellig fra den mentale repræsentation, du selv laver af en “column-major”-matrix. Du koder “row”, men det blev sagt, at du bruger (fra et matematisk synspunkt) “column”, deraf dine vanskeligheder med at finde mening i, om du gør tingene rigtigt eller forkert.

Det vigtige er at se en matrix som en repræsentation af et koordinatsystem defineret af tre akser, og en translation. Hvor og hvordan du gemmer disse data i hukommelsen, er helt op til dig. Hvis man antager, at de tre vektorer, der repræsenterer koordinatsystemets tre akser, hedder AX(x,y,z), AY(x,y,z), AZ(x,y,z), og at translationsvektoren betegnes med (Tx, Ty, Tz), så har man matematisk set, hvis man bruger kolonnevektor:

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

Koordinatsystemets akser er skrevet lodret. Hvis du nu har hvis du bruger row-major:

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

Koordinatsystemets akser er skrevet vandret. Så problemet, når det kommer til computerverdenen, er nu, hvordan man lagrer disse koefficienter i hukommelsen. Du kan lige så godt gøre:

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

Siger det dig dog hvilken konvention du bruger? Nej. Du kan også skrive:

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

eller:

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

Agennem det får man ikke nogen særlig indikation af, hvilken “matematisk” konvention man bruger. Du lagrer bare 16 koefficienter i hukommelsen på forskellige måder, og det er helt fint, så længe du ved, hvilken måde det er, så du kan få adgang til dem på passende vis senere. Husk nu på, at en vektor ganget med en matrix bør give dig den samme vektor, uanset om du bruger en række- eller kolonne-matematisk notation. Det vigtige er altså i virkeligheden, at du multiplicerer (x,y,z)-koordinaterne for din vektor med de rigtige koefficienter fra matricen, hvilket kræver viden om, hvordan “du” har besluttet at lagre matrixkoefficienten i hukommelsen:

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 AZzy, 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}

Vi har skrevet denne funktion for at understrege, at uanset hvilken konvention man anvender, er resultatet af vektor- * matrixmultiplikationen blot en multiplikation og en addition mellem vektorens indgangskoordinater og koordinatsystemets asekoordinater AX, AY og AZ (uanset hvilken notation man anvender, og uanset hvordan man lagrer dem i hukommelsen). Hvis du bruger:

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

Du skal kalde:

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

Hvis du bruger:

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

Du skal kalde:

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

Hvis det fortæller dig, hvilken konvention du bruger? Nej. Du skal bare kalde de rigtige koefficienter de rigtige steder, når du foretager en vec * mat multiplikation. Og det er det hele, hvor forvirrende det end kan virke. nu er tingene lidt anderledes, når det drejer sig om mat * mat multiplikation. Du kan gå ud fra, at rækkefølgen, som du multiplicerer matricerne i, ikke er den samme. Så R * S * T er ikke det samme som T * S * R. Rækkefølgen betyder faktisk noget. Nu igen, hvis du bruger “row major”, skal du matematisk set skrive:

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

hvor ml er den venstre matrice og mr den højre: mt = ml * mr. Bemærk dog, at vi ikke har brugt parenteser til adgangsindeksene, fordi vi ikke ønsker at antyde, at vi har adgang til elementer, der er gemt i et 1D array her. Vi taler blot om koefficienterne i matricer, som de er skrevet på papiret. Hvis du ønsker at skrive dette i C++, så afhænger det hele af, hvordan du har gemt dine koefficienter i hukommelsen som foreslået ovenfor.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.