Geometri (radmajor vs kolumnmajorvektor)

Geometri

Förr i den här lektionen har vi förklarat att vektorer (eller punkter) kan skrivas ner som matriser (en rad, tre kolumner). Observera dock att vi också kunde ha skrivit ner dem som matriser (tre rader, en kolumn). Tekniskt sett är dessa två sätt att uttrycka punkter och vektorer som matriser helt giltiga och att välja det ena eller det andra sättet är bara en fråga om konvention.

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

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

I det första exemplet ( matris) har vi uttryckt vår vektor eller punkt i vad vi kallar rad-major ordning: vektorn (eller punkten) är skriven som en rad av tre tal. I det andra exemplet säger vi att att punkterna eller vektorerna är skrivna i kolumn-major ordning: vi skriver vektorns eller punktens tre koordinater vertikalt, som en kolumn.

Håll dig i minnet att vi uttrycker punkter och vektorer som matriser för att multiplicera dem med transformationsmatriser (för enkelhetens skull kommer vi att arbeta med istället för matriser). Vi har också lärt oss att vi bara kan multiplicera matriser när antalet kolumner från den vänstra matrisen och antalet rader från den högra matrisen är desamma. Med andra ord kan matriserna och multipliceras med varandra men inte matriserna och. Observera att om vi skriver en vektor som en matris kan vi multiplicera den med en matris (förutsatt att denna matris står till höger inne i multiplikationen), men om vi skriver denna vektor som en matris kan vi inte multiplicera den med en matris. Detta illustreras i följande exempel. De inre dimensionerna (3 och 3) för de matriser som är involverade i multiplikationen är desamma (i grönt) så denna multiplikation är giltig (och resultatet är en transformerad punkt skriven i form av en matris):

$$* = \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 inre dimensioner (1 och 3) för de matriser som ingår i multiplikationen är inte desamma (i rött) så denna multiplikation är inte möjlig:

$$$* \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å vad gör vi? Lösningen på detta problem är inte att multiplicera vektorn eller punkten med matrisen, utan matrisen M med vektorn V. Med andra ord flyttar vi punkten eller vektorn till höger inne i 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 att resultatet av denna operation är en transformerad punkt skriven i form av en matris. Vi får alltså en punkt till att börja med och vi slutar med en transformerad punkt, vilket är vad vi vill ha. Problemet är löst. För att sammanfatta, när vi av konvention bestämmer oss för att uttrycka vektorer eller punkter i rad-major ordning (), måste vi placera punkten till vänster om multiplikationen och punkten till höger innanför multiplikationstecknet. Detta kallas i matematiken för en vänstermultiplikation eller förmultiplikation. Om du bestämmer dig för att skriva vektorerna i kolumn-major ordning istället () måste matrisen stå på vänster sida av multiplikationen och vektorn eller punkten på höger sida. Detta kallas för en höger- eller postmultiplikation.

Vi måste vara försiktiga med hur dessa termer faktiskt används. Till exempel står det i Maya-dokumentationen att ”matriserna postmultipliceras i Maya. För att till exempel transformera en punkt P från objektrymd till världsrymd (P’) måste du eftermultiplicera med worldMatrix. (P’ = P x WM)”, vilket är förvirrande eftersom det egentligen är en förmultiplikation, men de talar om matrisens position i förhållande till punkten i det här specifika fallet. Det är faktiskt en felaktig användning av terminologin. Det borde ha skrivits att i Maya uttrycks punkter och vektorer som radmajorvektorer och att de därför förmultipliceras (vilket innebär att punkten eller vektorn förekommer före matrisen i multiplikationen).

I följande tabell sammanfattas skillnaderna mellan de två konventionerna (där P, V och M står för punkt, vektor respektive matris).

Radmajorordning

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

Vänster eller pre-multiplikation

P/V * M

Kolumn-större ordning

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

Högre eller senaremultiplikation

M * P/V

När vi nu har lärt oss om dessa två konventioner kanske du frågar dig ”handlar det inte bara om att skriva saker på papper?”. Vi vet hur man beräknar produkten av två matriser A och B: multiplicera varje koefficient i A:s aktuella rad med de tillhörande elementen i B:s aktuella kolumn och summera resultatet. Låt oss tillämpa denna formel med de två konventionerna och jämföra resultaten:

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

Kolonnmajorordning

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

Multiplicering av en punkt eller en vektor med en matris bör ge oss samma resultat oavsett om vi använder rad- eller kolumnmajorordning. Om du använder ett 3D-program för att rotera en punkt med en viss vinkel runt z-axeln förväntar du dig att punkten ska vara i ett visst läge efter rotationen, oavsett vilken intern konvention utvecklaren använde för att representera punkter och vektorer. Som du kan se genom att titta på tabellen ovan skulle multiplikation av en rad-major och kolumn-major punkt (eller vektor) med samma matris helt klart inte ge oss samma resultat. För att komma på fötter igen skulle vi faktiskt behöva transponera den matris som används i kolumn-major multiplikationen för att vara säkra på att x’, y’ och z’ är samma (om du behöver komma ihåg vad transponering av en matris är kan du läsa kapitlet om matrisoperationer). Här är vad 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} }$$$

Kolonnmajorordning

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

Slutsatsen är att om man går från rad-major ordning till kolumn-major ordning måste man inte bara byta ut punkten eller vektorn och matrisen i multiplikationen utan också transponera matrisen för att garantera att båda konventionerna ger samma resultat (och vice versa).

Utifrån dessa observationer kan vi se att alla serier av transformationer som tillämpas på en punkt eller en vektor när en rad-major-konvention används kan skrivas i sekventiell ordning (eller läsordning). Tänk dig till exempel att du vill översätta punkten P med matrisen T, sedan rotera den runt z-axeln med Rz och sedan runt y-axeln med Ry. Du kan skriva:

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

Om du skulle använda en kolumn-major-notation skulle du behöva kalla transformationen i omvänd ordning (vilket man kan tycka är kontraintuitivt):

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

Då kan du kanske tänka att ”det måste finnas en anledning att föredra ett system framför ett annat”. I själva verket är båda konventionerna korrekta och ger oss samma resultat, men av vissa tekniska skäl behandlar matematiska och fysikaliska texter i allmänhet vektorer som kolonnvektorer.

Transformationsordning när vi använder kolumma-major matriser liknar mer i matematik det sätt på vilket vi skriver funktionsutvärdering och komposition.

Radmajor matriskonvention gör dock matriser lättare att lära ut, vilket är anledningen till att vi använder den för Scratchapixel (liksom Maya, DirectX. De är också definierade som standard i RenderMan-specifikationerna). Vissa 3D-API:er, t.ex. OpenGL, använder dock en kolumn-major-konvention.

Implikation i kodning:

Det finns en annan potentiellt mycket viktig aspekt att ta hänsyn till om du måste välja mellan rad-major och kolumn-major, men den har egentligen inget att göra med själva konventionerna och hur praktisk den ena är jämfört med den andra. Det har mer att göra med datorn och hur den fungerar. Kom ihåg att vi kommer att hantera matriser. Typiskt sett ser implementeringen av en matris i C++ ut så här:

class Matrix44{ … float m;};

Som du kan se lagras matrisens 16 koefficienter i en tvådimensionell matris av floats (eller dubbletter beroende på vilken precision du behöver. Vår C++ Matrix-klass är en mall). Vilket innebär att i minnet kommer de 16 koefficienterna att läggas ut på följande sätt: c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33. Med andra ord är de placerade i minnet på ett sammanhängande sätt. Låt oss nu se hur dessa koefficienter nås i en vektor-matrixmultiplikation där vektorerna skrivs i radmajorordning:

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

Som ni kan se är elementen i matrisen för x’ inte åtkomliga sekventiellt. För att beräkna x’ behöver vi med andra ord den första, femte och nionde floaten i matrisen 16 floats array. För att beräkna y’ måste vi få tillgång till den andra, sjätte och tionde floaten i denna matris. Och slutligen för z’ behöver vi den 3:e, 7:e och 11:e floaten från matrisen. I beräkningsvärlden är det inte nödvändigtvis bra att få tillgång till element från en matris i en icke-sekventiell ordning. Det kan faktiskt försämra processorns cacheprestanda. Vi ska inte gå in på alltför många detaljer här, men låt oss bara säga att det närmaste minne som CPU:n kan komma åt kallas för en cache. Denna cache är mycket snabb att komma åt men kan bara lagra ett mycket begränsat antal data. När processorn behöver komma åt vissa data kontrollerar den först om de finns i cacheminnet. Om så är fallet får processorn tillgång till uppgifterna direkt (cache hit), men om så inte är fallet (cache miss) måste den först skapa en post i cacheminnet och sedan kopiera uppgifterna från huvudminnet till denna plats. Denna process är naturligtvis mer tidskrävande än när uppgifterna redan finns i cacheminnet, så vi vill helst undvika cachemissar så mycket som möjligt. Förutom att kopiera data från huvudminnet kopierar processorn också en del av de data som finns precis bredvid (t.ex. de närmaste 24 bytes), eftersom maskinvaruingenjörerna tänkte att om koden behöver få tillgång till ett element i en matris, är det troligt att den kommer att få tillgång till de efterföljande elementen strax efteråt. I program loopar vi ofta över element i en array i sekventiell ordning och detta antagande är därför sannolikt sant. Tillämpat på vårt matrisproblem kan det därför vara ett problem att få tillgång till matrisens koefficienter i icke sekventiell ordning. Om man antar att processorn laddar den begärda floaten i cacheminnet plus de tre floaten bredvid den, kan vårt nuvarande genomförande leda till många cache missar, eftersom de koefficienter som används för att beräkna x’ y’ och z’ ligger fem floater ifrån varandra i matrisen. Om man å andra sidan använder en kolumn-major-ordningsnotering krävs det till exempel för att beräkna x’ att man får tillgång till det första, andra och tredje elementet i matrisen.

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

Koefficienterna nås i sekventiell ordning, vilket också innebär att vi utnyttjar CPU:s cachemekanism på ett bra sätt (endast 3 cachemissar istället för 9 i vårt exempel). Sammanfattningsvis kan vi säga att från en programmeringssynpunkt kan det vara bättre att genomföra vår punkt- eller vektormatrismultiplikation med hjälp av en colum-major ordningskonvention än den version som använder en row-major ordningskonvention. I praktiken har vi dock inte kunnat visa att detta verkligen var fallet (när du kompilerar ditt program med optimeringsflaggorna -O, -O2 eller -O3 kan kompilatorn göra jobbet åt dig genom att optimera slingor över flerdimensionella matriser) och vi har framgångsrikt använt versionen med rad-major ordning utan att förlora i prestanda jämfört med en version av samma kod som använder ett genomförande med kolumn-major ordning.

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

Row-major och Column-major Order in Computing

För fullständighetens skull kan vi också nämna att termerna row-major och column-major order också kan användas inom datateknik för att beskriva hur element i flerdimensionella matriser är placerade i minnet. I rad-major ordning läggs elementen i en flerdimensionell matris ut efter varandra, från vänster till höger, uppifrån och ner. Detta är den metod som används av C/C++. Till exempel matrisen:

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

kan skrivas i C/C++ som:

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

och elementen i denna matris skulle läggas ut i linjärt minne som:

1 2 3 4 5 5 6

I kolumn-major ordning, som används av språk som FORTRAN och MATLAB, lagras matrisens element i minnet uppifrån och ner, från vänster till höger. Om vi använder samma matrixexempel skulle matrisens element lagras (och nås) i minnet på följande sätt:

1 4 2 2 5 3 6

Att veta hur elementen i en matris är placerade i minnet är viktigt, särskilt när du försöker få tillgång till dem med hjälp av pekarförskjutning och optimering av for-slingor (vi har tidigare i det här kapitlet förklarat att det kan påverka CPU:s cache-prestanda). Men eftersom vi endast kommer att betrakta C/C++ som vårt programmeringsspråk, är kolumn-majorordning (tillämpad på databehandling) inte av något större intresse för oss. Vi nämner bara vad termerna betyder inom databehandling, så att du är medveten om att de kan beskriva två olika saker beroende på i vilket sammanhang de används. Du bör vara försiktig så att du inte blandar ihop dem. I matematiska sammanhang beskriver de om man behandlar vektorer (eller punkter) som rader av koordinater eller som kolumner och det andra, och i datorsammanhang beskriver de hur ett visst programmeringsspråk lagrar och har tillgång till element i flerdimensionella matriser (som matriser är) i minnet.

OpenGL är ett intressant fall i det avseendet. När GL ursprungligen skapades valde utvecklarna en rad-major vektorkonvention. Utvecklare som utökade OpenGL tyckte dock att de borde gå tillbaka till column-major vector vilket de också gjorde. Av kompatibilitetsskäl ville de dock inte ändra koden för punktmatrismultiplikationen och beslutade i stället att ändra den ordning i vilken matrisens koefficienter lagrades i minnet. Med andra ord lagrar OpenGL koefficienterna i kolumn-major ordning, vilket innebär att översättningskoefficienterna m03, m13 och m23 från en matris som använder kolumn-major vektor har index 13, 14 och 15 i float arrayen, liksom översättningskoefficienterna m30, m31 och m32 från en matris som använder rad-major vektor.

Sammanfattning

Skillnaderna mellan de två konventionerna sammanfattas i följande tabell:

Row-major vector (matematik) Column-större vektor (matematik)

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

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

Pre-multiplikation \(vM\)

Postmultiplikation \(Mv\)

Ansökningsordningen och ordningen i vilken transformationerna tillämpas är densamma: ”ta P, transformera med T, transformera med Rz, transformera med Ry” skrivs som \(P’=P*T*R_z*R_y\)

Ansamlingsordningen är den omvända ordningen av den ordning i vilken transformationerna tillämpas: ”ta P, omvandla med T, omvandla med Rz, omvandla med Ry” skrivs som \(P’=R_y*R_z*T*P\)

API: Direct X, Maya

API: OpenGL, PBRT, Blender

Matrisens rader representerar baserna (eller axlarna) i ett koordinatsystem (rött: x-axel, grönt: y-axel, blått: z-axel)

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

Matrisens kolumner representerar baserna (eller axlarna) i ett koordinatsystem (rött: x-axel, grönt: y-axel, blått: z-axel)

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

Översättningsvärdena lagras i elementen c30, c31 och c32.

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

Översättningsvärdena lagras i elementen c03, c13 och c23.

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

Transponera matrisen för att använda den som en kolumnmajoriserad ordnad matris

Transponera matrisen för att använda den som en rad-major ordered matrix

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

API: Direct X, Maya, PBRT

API: OpenGL

En läsare ställde en fråga på Stackoverflow och menade att tabellen ovan var förvirrande. Ämnet är förvirrande och trots våra bästa försök att kasta lite ljus över frågan är det fortfarande många som blir förvirrade. Vi trodde att vårt svar på Stackoverflow förhoppningsvis kunde ge en annan insikt om frågan.

Du har teorin (det du gör i matematik med penna och papper) och det du gör med din implementering (C++). Detta är två olika problem.

Matematik: Du kan använda två notationer, antingen kolumn- eller radmajor. Med radmajor vektor måste du på papper skriva vektor-matrixmultiplikationen vM där v är radvektorn (1×4) och M din 4×4-matris. Varför? För att du matematiskt sett bara kan skriva *, och inte tvärtom. På samma sätt om du använder kolumn måste vektorn skrivas vertikalt, eller i notation (4 rader, 1 kolumn). Multiplikationen med en matris kan alltså bara skrivas på följande sätt: . Observera att matrisen sätts framför vektorn: Mv. Den första notationen kallas vänster- eller förmultiplikation (eftersom vektorn står på vänster sida av produkten) och den andra (Mv) kallas höger- eller eftermultiplikation (eftersom vektorn står på höger sida av produkten). Som du ser härstammar termerna från huruvida vektorn befinner sig på vänster sida (framför, eller ”pre”) eller på höger sida (efter, eller ”post”) av matrisen.

Nu, om du behöver transformera en vektor (eller en punkt) måste du vara uppmärksam på multiplikationens ordning, när du skriver ner dem på papper. Om du vill översätta något med matrisen T och sedan rotera med R och sedan skala med S, så måste du i en kolumnmajorvärld skriva v’ = S * R * T * v. I en radmajorvärld måste du skriva v’ = v * T * R * S.

Det är för teorin. Låt oss kalla detta för rad/kolumnvektorkonvention.

Dator: Sedan kommer punkten när du bestämmer dig för att implementera detta i C++, låt oss säga. Det som är bra med detta är att C++ inte ålägger dig någonting om någonting. Du kan mappa värdena för din matris koefficienter i minnet på det sätt du vill, och du kan skriva koden för att utföra en matrismultiplikation med en annan matris på det sätt du vill. På samma sätt är det helt upp till dig hur du får tillgång till koefficienterna för en vektor-matrixmultiplikation, men du måste göra en tydlig åtskillnad mellan hur du mappar dina koefficienter i minnet och vilka konventioner du behöver använda ur matematisk synvinkel för att representera dina vektorer. Detta är två oberoende problem. Låt oss kalla den här delen för rad/kolumn-major layout.

Till exempel kan du deklarera en matrisklass som en matris med till exempel 16 sammanhängande floats. Det går bra. Där koefficienterna m14, m24, m34 representerar translationsdelen av matrisen (Tx, Ty, Tz), så du antar att din ”konvention” är rad-major trots att du blir tillsagd att använda OpenGL:s matriskonvention som sägs vara kolumn-major. Här kommer den möjliga förvirringen från det faktum att mappningen av koefficienterna i minnet skiljer sig från den mentala representation som du gör dig av en ”kolumnmajor”-matris. Du kodar ”rad” men det sades att du använder (ur matematisk synvinkel) ”kolumn”, därav din svårighet att förstå om du gör saker och ting rätt eller fel.

Det viktiga är att se en matris som en representation av ett koordinatsystem som definieras av tre axlar, och en translation. Var och hur du lagrar dessa data i minnet är helt upp till dig. Om vi antar att de tre vektorerna som representerar koordinatsystemets tre axlar heter AX(x,y,z), AY(x,y,z), AZ(x,y,z), och att translationsvektorn betecknas med (Tx, Ty, Tz), så har vi matematiskt sett följande om vi använder oss av kolumnvektorer:

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

Axlarna i koordinatsystemet skrivs vertikalt. Om du nu har om du använder row-major:

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

Koordinatsystemets axlar skrivs horisontellt. Problemet när det gäller datorvärlden är hur man ska lagra dessa koefficienter i minnet. Du kan lika gärna göra:

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

Säger det dig dock vilken konvention du använder? Nej. Du kan också skriva:

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

eller:

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

Alltså, det ger dig ingen särskild indikation på vilken ”matematisk” konvention du använder. Du lagrar bara 16 koefficienter i minnet på olika sätt och det är helt okej så länge du vet vilket sätt det är, så att du kan få tillgång till dem på lämpligt sätt senare. Kom nu ihåg att en vektor multiplicerad med en matris bör ge dig samma vektor oavsett om du använder en rad- eller kolumnmatematisk notation. Det viktiga är alltså egentligen att du multiplicerar vektorns (x,y,z)-koordinater med rätt koefficienter från matrisen, vilket kräver kunskap om hur ”du” har bestämt dig för att lagra matriskoefficienten i minnet:

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 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 skrev denna funktion för att understryka det faktum att oavsett vilken konvention du använder är resultatet av vektor- * matrismultiplikationen bara en multiplikation och en addition mellan vektorns inmatningskoordinater och koordinatsystemets axelkoordinater AX, AY och AZ (oavsett vilken notation du använder och oavsett hur du lagrar dem i minnet). Om du använder:

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

Du måste anropa:

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

Om du använder:

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

Du måste anropa:

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

Säger det dig vilken konvention du använder? Nej, du behöver bara anropa rätt koefficienter på rätt ställen när du gör en vec * mat multiplikation. Och det är allt, hur förvirrande det än kan verka. nu är saker och ting lite annorlunda när det gäller mat * mat multiplikation. Du kan anta att ordningen i vilken du multiplicerar matriserna inte är densamma. Så R * S * T är inte samma sak som T * S * R. Ordningen spelar faktiskt roll. Om du återigen använder ”row major” måste du matematiskt sett skriva:

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

där ml är den vänstra matrisen och mr den högra: mt = ml * mr. Observera dock att vi inte har använt parenteser för åtkomstindexen, eftersom vi inte vill ge sken av att vi får åtkomst till element lagrade i en 1D-matris här. Vi talar bara om koefficienterna i matriserna som de är skrivna på papper. Om du vill skriva detta i C++ beror allt på hur du har lagrat dina koefficienter i minnet enligt förslaget ovan.

Lämna ett svar

Din e-postadress kommer inte publiceras.