Geometria (sor fő vs oszlop fő vektor)

Geometria

Ezzel a leckével kapcsolatban korábban elmagyaráztuk, hogy a vektorok (vagy pontok) felírhatók mátrixokként (egy sor, három oszlop). Megjegyezzük azonban, hogy akár mátrixokként (három sor, egy oszlop) is leírhattuk volna őket. Technikailag a pontok és vektorok mátrixokként való kifejezésének ez a két módja tökéletesen érvényes, és az egyik vagy másik mód választása csak konvenció kérdése.

Vektor mátrixként leírva: \( V=\begin{bmatrix}x & y & z\end{bmatrix}\)

Vektor mátrixként leírva: \(V=\begin{bmatrix}x\\\y\\z\end{bmatrix}\)

Az első példában ( mátrix) a vektorunkat vagy pontunkat úgy fejeztük ki, ahogyan azt a sor-major sorrendben nevezzük: a vektor (vagy pont) három számból álló sorként van leírva. A második példában azt mondjuk, hogy a pontokat vagy vektorokat oszlop-dúr sorrendben írjuk: a vektor vagy pont három koordinátáját függőlegesen, oszlopként írjuk.

Emlékezzünk arra, hogy a pontokat és vektorokat mátrixokként fejezzük ki, hogy azokat transzformációs mátrixokkal szorozzuk (az egyszerűség kedvéért inkább mátrixokkal fogunk dolgozni). Azt is megtanultuk, hogy csak akkor szorozhatunk mátrixokat, ha a bal oldali mátrix oszlopainak száma és a jobb oldali mátrix sorainak száma megegyezik. Más szóval a és mátrixok szorozhatók egymással, de a és mátrixok nem. Vegyük észre, hogy ha egy vektort mátrixként írunk le, akkor meg tudjuk szorozni egy mátrixszal (feltéve, hogy ez a mátrix a szorzás jobb oldalán belül van), de ha ezt a vektort mátrixként írjuk le, akkor nem tudjuk megszorozni egy mátrixszal. Ezt a következő példák szemléltetik. A szorzásban részt vevő mátrixok belső dimenziói (3 és 3) megegyeznek (zölddel), így ez a szorzás érvényes (és az eredmény egy mátrix formájában írt transzformált pont):

$$* = \begin{bmatrix}x & y & z\end{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 szorzásban részt vevő mátrixok belső dimenziói (1 és 3) nem azonosak (piros színnel), így ez a szorzás nem lehetséges:

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

Mit csináljunk tehát? A probléma megoldása az, hogy nem a vektort vagy a pontot szorozzuk meg a mátrixszal, hanem az M mátrixot a V vektorral. Más szóval a pontot vagy a vektort a szorzáson belül jobbra toljuk:

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

Megjegyezzük, hogy a művelet eredménye egy mátrix formájában kiírt transzformált pont. Tehát kapunk egy pontot, amivel kezdjük, és egy transzformált ponttal fejezzük be, ami az, amit szeretnénk. A probléma megoldva. Összefoglalva, amikor a konvenció szerint úgy döntünk, hogy a vektorokat vagy pontokat soros-dőrendi sorrendben () fejezzük ki, akkor a pontot a szorzás bal oldalára kell tennünk, a pontot pedig a szorzás előjelén belül jobbra. Ezt nevezik a matematikában baloldali vagy előszorzásnak. Ha úgy döntünk, hogy a vektorokat inkább oszlop-dúr sorrendben írjuk (), akkor a mátrixnak a szorzás bal oldalára kell kerülnie, a vektornak vagy a pontnak pedig a jobb oldalára. Ezt jobboldali vagy utólagos szorzásnak nevezzük.

Vigyáznunk kell, hogy ezeket a kifejezéseket ténylegesen hogyan használjuk. A Maya dokumentáció például azt mondja, hogy “a mátrixok utólagos szorzást végeznek a Mayában. Például egy P pont objektumtérből világtérbe (P’) való transzformálásához a worldMátrixszal kell utószorozni. (P’ = P x WM)”, ami azért zavaró, mert valójában ez egy elő-szorzási művelet, de ebben a konkrét esetben a mátrixnak a ponthoz viszonyított helyzetéről beszélnek. Ez valójában a terminológia helytelen használata. Azt kellett volna írni, hogy a Mayában a pontokat és a vektorokat soros nagyvektorokként fejezik ki, és ezért elő-szorzást végeznek (vagyis a pont vagy a vektor a szorzatban a mátrix előtt jelenik meg).

A következő táblázat a két konvenció közötti különbségeket foglalja össze (ahol a P, V és M a pontot, a vektort, illetve a mátrixot jelenti).

Sor-sorrend

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

Balra vagy előre.szorzás

P/V * M

oszlop-főrend

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

Jobb vagy poszt-szorzás

M * P/V

Most, hogy megismertük ezt a két konvenciót, megkérdezhetjük, hogy “ez nem csak a dolgok papírra írásáról szól?”. Tudjuk, hogyan számítsuk ki két A és B mátrix szorzatát: szorozzuk meg az A aktuális sorában lévő minden egyes együtthatót a B aktuális oszlopában lévő kapcsolódó elemekkel, és adjuk össze az eredményt. Alkalmazzuk ezt a képletet a két konvenciót használva, és hasonlítsuk össze az eredményeket:

sor-dőrend

$${ \begin{bmatrix}x & y & z\end{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} }$$

oszlop-dőrend

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

Egy pont vagy vektor mátrixszal való szorzása ugyanazt az eredményt kell, hogy adja, akár sor-, akár oszlopsorrendet használunk. Ha egy 3D-s alkalmazással elforgatunk egy pontot egy bizonyos szöggel a z tengely körül, akkor elvárjuk, hogy a pont a forgatás után egy bizonyos pozícióban legyen, függetlenül attól, hogy a fejlesztő milyen belső konvenciót használt a pontok és vektorok ábrázolására. Ahogy azonban a fenti táblázatot nézve láthatjuk, egy sor- és egy oszlop-durva pont (vagy vektor) megszorozása ugyanazzal a mátrixszal nyilvánvalóan nem ugyanazt az eredményt adná. Ahhoz, hogy talpra álljunk, valójában transzponálnunk kellene az oszlop-dúr szorzásban használt mátrixot, hogy biztosak lehessünk abban, hogy x’, y’ és z’ ugyanaz (ha emlékezned kell, mi egy mátrix transzponálása, nézd meg a Mátrixműveletek című fejezetet). Íme, amit kapunk:

Sor-dúr sorrend

$${ \begin{bmatrix}x & y & z\end{bmatrix}x & 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} }$$

oszlop-dőrend

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

Végeredményben a sor-dőrendről oszlop-dőrendre való áttérés nemcsak a pont vagy vektor és a mátrix felcserélését jelenti a szorzásban, hanem a mátrix transzponálását is, hogy mindkét konvenció ugyanazt az eredményt adja (és fordítva).

Ezekből a megfigyelésekből láthatjuk, hogy egy pontra vagy egy vektorra alkalmazott bármely transzformációsorozatot, amikor sor-dúr konvenciót használunk, fel lehet írni szekvenciális sorrendben (vagy olvasási sorrendben). Képzeljük el például, hogy a P pontot a T mátrixszal akarjuk elfordítani, majd a z tengely körül elforgatni az Rz, majd az y tengely körül az Ry mátrixszal. Leírhatod:

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

Ha oszlopos főjelölést használnál, akkor a transzformációt fordított sorrendben kellene meghívnod (amit az ember ellenkező értelműnek találhat):

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

Azt gondolhatod tehát, hogy “biztos van valami oka annak, hogy az egyik rendszert előnyben részesíted a másikkal szemben”. Valójában mindkét konvenció helyes, és ugyanazt az eredményt adja, de bizonyos technikai okokból a matematikai és fizikai szövegek általában oszlopvektorokként kezelik a vektorokat.

A transzformáció sorrendje, amikor oszlopos főmátrixokat használunk, jobban hasonlít a matematikában ahhoz, ahogyan a függvénykiértékelést és a kompozíciót írjuk.

A soros főmátrix konvenció azonban megkönnyíti a mátrixok tanítását, ami az oka annak, hogy ezt használjuk a Scratchapixel (valamint a Maya, DirectX. A RenderMan specifikációkban is szabványként vannak definiálva). Egyes 3D API-k, mint például az OpenGL, azonban oszlop-major konvenciót használnak.

Implikáció a kódolásban:

Van még egy potenciálisan nagyon fontos szempont, amit figyelembe kell venni, ha választani kell a sor-major és az oszlop-major között, de ennek valójában semmi köze magukhoz a konvenciókhoz és ahhoz, hogy mennyire praktikus az egyik a másikhoz képest. Ennek inkább a számítógéphez és annak működéséhez van köze. Ne feledje, hogy mátrixokkal fogunk foglalkozni. Tipikusan egy mátrix implementációja C++-ban így néz ki:

class Matrix44{ … float m;};

Amint láthatjuk, a mátrix 16 együtthatóját egy kétdimenziós lebegőértékekből álló tömbben tároljuk (vagy kétszeresekben, attól függően, hogy milyen pontosságra van szükségünk. A mi C++ Mátrix osztályunk egy sablon). Ami azt jelenti, hogy a memóriában a 16 együttható a következő módon lesz elrendezve: c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33. Más szóval, a memóriában egybefüggően helyezkednek el. Most nézzük meg, hogyan érjük el ezeket az együtthatókat egy vektor-mátrix szorzásban, ahol a vektorok soros-dúr sorrendben vannak felírva:

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

Amint látható, az x’ mátrix elemeihez nem szekvenciálisan férünk hozzá. Más szóval az x’ kiszámításához a mátrix 16 floats tömbjének 1., 5. és 9. lebegőjére van szükségünk. Az y’ kiszámításához a tömb 2., 6. és 10. lebegőjéhez kell hozzáférnünk. Végül pedig a z’ értékhez a tömb 3., 7. és 11. lebegőjére van szükségünk. A számítástechnika világában a tömb elemeinek nem szekvenciális sorrendben történő elérése nem feltétlenül jó dolog. Valójában potenciálisan rontja a CPU gyorsítótárának teljesítményét. Itt nem megyünk bele túl sok részletbe, de mondjuk csak annyit, hogy a CPU által elérhető legközelebbi memóriát cache-nek nevezzük. Ez a gyorsítótár nagyon gyorsan elérhető, de csak nagyon korlátozott számú adatot képes tárolni. Amikor a CPU-nak hozzá kell férnie valamilyen adathoz, először ellenőrzi, hogy az létezik-e a gyorsítótárban. Ha igen, akkor a CPU azonnal hozzáfér ehhez az adathoz (cache hit), ha nem (cache miss), akkor először létre kell hoznia egy bejegyzést a cache-ben a számára, majd erre a helyre kell másolnia az adatot a főmemóriából. Ez a folyamat nyilvánvalóan időigényesebb, mint amikor az adat már létezik a gyorsítótárban, ezért ideális esetben a lehető legjobban szeretnénk elkerülni a cache misseket. Az adott adatnak a főmemóriából történő másolása mellett a CPU a közvetlenül mellette lévő adatok egy darabját is lemásolja (például a következő 24 bájtot), mivel a hardvermérnökök úgy gondolták, hogy ha a kódunknak például egy tömb egy eleméhez kell hozzáférnie, akkor valószínűleg az azt követő elemeket is hamarosan eléri. A programokban valóban gyakran fordulunk elő, hogy egy tömb elemein szekvenciális sorrendben végighaladunk, ezért ez a feltételezés valószínűleg igaz. A mátrixproblémánkra alkalmazva tehát a mátrix együtthatóinak nem szekvenciális sorrendben történő elérése problémát jelenthet. Feltételezve, hogy a CPU betölti a kért lebegőszámot a gyorsítótárba, valamint a mellette lévő 3 lebegőszámot, a jelenlegi megvalósításunk sok gyorsítótár-kihagyáshoz vezethet, mivel az x’ y’ és z’ kiszámításához használt együtthatók 5 lebegőszámra vannak egymástól a tömbben. Másfelől, ha oszlop-major sorrendű jelölést használunk, az x’ kiszámításához például a mátrix 1., 2. és 3. eleméhez kell hozzáférni.

// oszlop-fő sorrendx’ = c00 * x + c01 * y + c02 * zy’ = c10 * x + c11 * y + c12 * zz’ = c20 * x + c21 * y + c22 * z

Az együtthatókat szekvenciális sorrendben érjük el, ami azt is jelenti, hogy jól kihasználjuk a CPU cache-mechanizmusát (példánkban 9 helyett csak 3 cache-kihagyás). Összefoglalva azt mondhatjuk, hogy programozási szempontból a pont- vagy vektor-mátrix szorzásunk oszlop-nagy sorrend konvencióval történő megvalósítása teljesítmény szempontjából jobb lehet, mint a sor-nagy sorrend konvenciót használó változat. Gyakorlatilag azonban nem tudtuk bizonyítani, hogy ez valóban így lenne (ha a programunkat a -O, -O2 vagy -O3 optimalizálási zászlók használatával fordítjuk le, a fordító elvégezheti helyettünk a munkát a többdimenziós tömbök feletti ciklusok optimalizálásával), és sikeresen használtuk a sor-major sorrendű változatot anélkül, hogy teljesítményveszteséget szenvedtünk volna el ugyanannak a kódnak az oszlop-major sorrendű implementációt használó változatához képest.

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

Row-major és Column-Major Order in Computing

A teljesség kedvéért említsük meg azt is, hogy a row-major és column-major order kifejezések a számítástechnikában is használhatók a többdimenziós tömbök elemeinek a memóriában való elhelyezésére. A többdimenziós tömb elemei soros sorrendben, balról jobbra, felülről lefelé haladva helyezkednek el egymás után. Ezt a módszert a C/C++ használja. Például a mátrixot:

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

C/C++ nyelven így lehetne leírni:

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

és ennek a tömbnek az elemeit a lineáris memóriában egybefüggően helyeznénk el a következőképpen:

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

és ennek a tömbnek az elemeit a lineáris memóriában a következőképpen helyeznénk el:

1 2 3 3 4 5 6

Az oszlopos sorrendben, amelyet olyan nyelvek használnak, mint a FORTRAN és a MATLAB, a mátrix elemei felülről lefelé, balról jobbra haladva kerülnek a memóriába. Ugyanezt a mátrixpéldát használva a mátrix elemei a következő módon lennének tárolva (és elérhetők a memóriában):

1 4 2 5 3 6

Az, hogy tudjuk, hogyan vannak elrendezve a mátrix elemei a memóriában, különösen akkor fontos, ha a mutatóeltolás és a for ciklus optimalizálásával próbáljuk elérni őket (ebben a fejezetben korábban már kifejtettük, hogy ez hatással lehet a CPU gyorsítótárának teljesítményére). Mivel azonban csak a C/C++-t tekintjük programozási nyelvnek, az oszlop-dúr rendezés (a számítástechnikában alkalmazva) nem nagyon érdekel bennünket. Csak azért említjük meg, hogy mit jelentenek a kifejezések a számítástechnikában, hogy tisztában legyenek azzal, hogy két különböző dolgot írhatnak le attól függően, hogy milyen kontextusban használják őket. Vigyázni kell, hogy ne keverjük össze őket. A matematika kontextusában azt írják le, hogy a vektorokat (vagy pontokat) koordinátasorokként vagy oszlopokként és a másodikat, a számítástechnikában pedig azt, hogy egy adott programozási nyelv hogyan tárolja a memóriában a többdimenziós tömbök (amilyenek a mátrixok is) elemeit és hogyan fér hozzá azokhoz.

Az OpenGL érdekes eset ebből a szempontból. Amikor a GL-t eredetileg létrehozták, a fejlesztők a sor-major vektor konvenciót választották. Az OpenGL-t bővítő fejlesztők azonban úgy gondolták, hogy vissza kellene térniük az oszlopos fővektorhoz, amit meg is tettek. Kompatibilitási okokból azonban nem akarták megváltoztatni a pont-mátrix szorzás kódját, és ehelyett úgy döntöttek, hogy megváltoztatják a sorrendet, amelyben a mátrix együtthatóit a memóriában tárolják. Más szóval az OpenGL az együtthatókat oszlopos főnévi sorrendben tárolja, ami azt jelenti, hogy az m03, m13 és m23 fordítási együtthatók egy oszlopos főnévi vektort használó mátrixból 13, 14, 15 indexszel rendelkeznek a float tömbben, ahogyan az m30, m31 és m32 fordítási együtthatók egy soros főnévi vektort használó mátrixból.

Összefoglaló

A két konvenció közötti különbségeket a következő táblázat foglalja össze:

Row-major vektor (matematika) Column-major vektor (matematika) Column-major vektor.fő vektor (matematika)

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

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

Pre-szorzás \(vM\)

Újabb szorzás \(Mv\)

A hívás sorrendje és a transzformációk alkalmazásának sorrendje megegyezik: “Vegyük P-t, transzformáljuk T-vel, transzformáljuk Rz-vel, transzformáljuk Ry-vel” a következőképpen írható: \(P’=P*T*R_z*R_y\)

A hívás sorrendje a fordítottja a transzformációk alkalmazásának sorrendjének: “vegyük P-t, transzformáljuk T-vel, transzformáljuk Rz-vel, transzformáljuk Ry-vel” az \(P’=R_y*R_z*T*P\)

API: X, Maya

API: Direct X, Maya

API: OpenGL, PBRT, Blender

A mátrix sorai egy koordinátarendszer alapjait (vagy tengelyeit) jelentik (piros: x-tengely, zöld: y-tengely, kék:z-tengely)

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

A mátrix oszlopai egy koordinátarendszer alapjait (vagy tengelyeit) jelölik (piros: x-tengely, zöld: y-tengely, kék:z-tengely)

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

A fordítási értékeket a c30, c31 és c32 elemekben tároljuk.

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

A fordítási értékeket a c03, c13 és c23 elemekben tároljuk.

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

Transzponáljuk a mátrixot úgy, hogy oszlopos nagyrendű mátrixként használjuk

Transzponáljuk a mátrixot úgy, hogy soros rendű mátrixként használjuk

.fő rendezett mátrix

Soros fő mátrix (Számítás) oszlopos fő mátrix (Számítás)

API: X, Maya, PBRT

API: Direct X, Maya, PBRT

API: OpenGL

Egy olvasó kérdést tett fel a Stackoverflow-n, mely szerint a fenti táblázat zavaros. A téma zavaros, és annak ellenére, hogy minden igyekezetünkkel igyekeztünk némi fényt deríteni a kérdésre, sokan még mindig összezavarodnak vele kapcsolatban. Úgy gondoltuk, hogy a Stackoverflow-n adott válaszunk remélhetőleg más megvilágításba helyezi a kérdést.

Van az elmélet (amit matematikában tollal és papírral csinálsz) és van az implementáció (C++). Ez két különböző probléma.

Matematika: kétféle jelölést használhatsz, vagy oszlop- vagy sormajor. Soros fővektorral, papíron a vektor-mátrix szorzást vM kell leírnod, ahol v a sorvektor (1×4) és M a 4×4-es mátrixod. Miért? Mert matematikailag csak *-ot lehet írni, és fordítva nem. Hasonlóképpen, ha oszlopot használsz, akkor a vektort függőlegesen kell leírni, vagy jelöléssel (4 sor, 1 oszlop). Így a mátrixszal való szorzást csak a következőképpen lehet leírni: . Figyeljük meg, hogy a mátrix a vektor elé kerül: Mv. Az első jelölést baloldali vagy elő-szorzásnak nevezzük (mert a vektor a szorzat bal oldalán van), a másodikat (Mv) pedig jobboldali vagy utólagos szorzásnak (mert a vektor a szorzat jobb oldalán van). Mint látod, a kifejezések abból erednek, hogy a vektor a mátrix bal oldalán (előtte, vagy “pre”) vagy jobb oldalán (utána, vagy “post”) van-e.

Most, ha egy vektort (vagy egy pontot) kell átalakítanod, akkor a szorzás sorrendjére kell figyelned, amikor papírra írod őket. Ha valamit T mátrixszal akarsz transzlálni, majd R mátrixszal forgatni, majd S mátrixszal skálázni, akkor oszlopos világban v’ = S * R * T * v. Soros világban v’ = v * T * R * S.

Ez az elméletről szól. Nevezzük ezt sor/oszlop vektor konvenciónak.

Számítógép: Aztán jön az a pont, amikor úgy döntesz, hogy ezt megvalósítod C++-ban mondjuk. Ebben az a jó, hogy a C++ nem ír elő neked semmit semmiről. Úgy képezheted le a mátrixod együtthatóinak értékeit a memóriában, ahogyan akarod, és úgy írhatod meg a kódot, hogy egy mátrix szorzását egy másik mátrixszal úgy végezd el, ahogyan akarod. Hasonlóképpen az, hogy miként éri el az együtthatókat egy vektor-mátrix szorzáshoz, teljesen Öntől függ.Világos különbséget kell tennie aközött, hogy miként képezi le az együtthatókat a memóriában, és milyen konvenciókat kell használnia matematikai szempontból a vektorok ábrázolásához. Ez két független probléma. Nevezzük ezt a részt sor/oszlop-fő elrendezésnek.

Egy mátrixosztályt például deklarálhatsz egy, mondjuk 16 egybefüggő lebegő számjegyből álló tömbként. Ez rendben is van. Ahol az m14, m24, m34 együtthatók a mátrix transzlációs részét képviselik (Tx, Ty, Tz), így feltételezed, hogy a “konvenciód” sor-major, annak ellenére, hogy azt mondják, hogy használd az OpenGL mátrix konvencióját, amelyről azt mondják, hogy oszlop-major. Itt a lehetséges zavart az okozza, hogy az együtthatók leképezése a memóriában eltér attól a mentális reprezentációtól, amit te magad készítesz egy “oszlop-dúr” mátrixról. Te “sor”-t kódolsz, de azt mondták, hogy (matematikai szempontból) “oszlop”-ot használsz, innen a nehézséged, hogy megértsd, jól vagy rosszul csinálod-e a dolgokat.

Az a fontos, hogy a mátrixot egy három tengely által meghatározott koordinátarendszer reprezentációjának tekintsd, és egy fordításnak. Hogy ezeket az adatokat hol és hogyan tárolod a memóriában, az teljesen rajtad múlik. Tegyük fel, hogy a koordinátarendszer három tengelyét reprezentáló három vektor neve AX(x,y,z), AY(x,y,z), AZ(x,y,z), és a transzlációs vektor neve (Tx, Ty, Tz), akkor matematikailag, ha oszlopvektort használunk, akkor:

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

A koordinátarendszer tengelyeit függőlegesen írjuk. Most ha ha a sor-duror-t használjuk:

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

A koordinátarendszer tengelyeit vízszintesen írjuk. A probléma most a számítógépes világban az, hogy hogyan tárolja ezeket az együtthatókat a memóriában. Akár azt is megteheted:

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

Mondja azonban, hogy milyen konvenciót használsz? Nem. Azt is írhatod:

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

vagy:

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

Ez megint nem ad különösebb támpontot arra, hogy melyik “matematikai” konvenciót használjuk. Egyszerűen csak 16 együtthatót tárolsz a memóriában különböző módon, és ez teljesen rendben van, amíg tudod, hogy mi az a mód, hogy később megfelelően hozzáférhess hozzájuk. Most tartsd észben, hogy egy mátrixszal megszorzott vektornak ugyanazt a vektort kell adnia, akár sor-, akár oszlop-matematikai jelölést használsz. Így igazából az a fontos, hogy a vektorod (x,y,z) koordinátáit megszorozd a mátrix megfelelő együtthatóival, amihez annak ismerete szükséges, hogy “te” hogyan döntöttél úgy, hogy a mátrix együtthatóját a memóriában tárolod:

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}

Ezt a függvényt azért írtuk, hogy kiemeljük azt a tényt, hogy függetlenül attól, hogy milyen konvenciót használunk, a vektor * mátrix szorzás eredménye csak egy szorzás és egy összeadás a vektor bemeneti koordinátái és a koordinátarendszer AX, AY és AZ tengelykoordinátái között (függetlenül attól, hogy milyen jelölést használunk, és függetlenül attól, hogy hogyan tároljuk őket a memóriában). Ha a:

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

Azt kell hívnunk:

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

Ha a:

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

Meg kell hívnod:

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

Ez megmondja, hogy milyen konvenciót használsz? Nem. Csak meg kell hívnod a megfelelő együtthatókat a megfelelő helyeken, amikor vec * mat szorzást végzel. És ez minden, bármennyire is zavarba ejtőnek tűnik.Most a dolgok kissé másképp állnak, amikor a mat * mat szorzásról van szó. Feltételezhetjük, hogy a sorrend, amelyben a mátrixokat szorozzuk, nem ugyanaz. Tehát R * S * T nem ugyanaz, mint T * S * R. A sorrend valóban számít. Most megint, ha a “sorfőmátrixot” használjuk, akkor matematikailag azt kell írnunk:

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

ahol ml a bal oldali mátrix és mr a jobb oldali: mt = ml * mr. Azonban vegyük észre, hogy nem használtunk zárójeleket a hozzáférési indexekhez, mert nem akarjuk azt sugallni, hogy itt egy 1D tömbben tárolt elemekhez férünk hozzá. Csak a papírra írt mátrixok együtthatóiról beszélünk. Ha ezt C++-ban akarod megírni, akkor minden attól függ, hogyan tároltad az együtthatókat a memóriában a fent javasolt módon.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.