Pamācības

Mazliet matemātikas

GiGa, 07.02.2010

Lejupielādēt: šeit

Komentāri: 3

Mazliet matemātikas

Sveiki! 

            Šodien parunāsim mazliet par 3d spēļu programmēšanai nepieciešamajām matemātikas, lielākoties ģeometrijas, zināšanām. Bez tām nu nekādi neiztikt, tās ir ļoti svarīgas – uz to balstās visas tālākās spēles sastāvdaļas, tāpēc ir svarīgi labi iepazīties ar turpmāk aprakstītajām darbībām un jēdzieniem, jo to pielietojums spēlēs ir ļoti plašs: sākot no grafikas, fizikas un sadursmju pārbaudes līdz pareizām un ērtām spēlētāja un pretinieku kustībām, un daudz kur vairāk. Šis tutoriālis ir domāts kā turpinājums šim tutoriālim: „Vienkāršš OpenGL logs ar SDL.”. Tāpat kā iepriekšējais, tas ir domāts lielākoties iesācējiem, tāpēc, ja vakar pabeidzi kodēt kaulu animācijas exporteri no 3dmax un hw akselerētu bibliotēku tās atspēlēšanai un renderēšanai ar bumpmapēm un 1-3 gaismām – te neko jaunu neatradīsi :) Bet, ja viss, ko māki ir mazliet programmēt un uzzīmēt kaut ko uz ekrāna ar OGL vai DX, tad šis būs tieši laikā. Tālāk aprakstītajām darbībām došu pseudo-code paraugus, lai tas būtu viegli saprotams visu valodu programmētājiem. Sourcē ir pievienota neliela C++ bibliotēka ar šeit aprakstītajām klasēm un funkcijām. Centīšos pārāk neizplūst matemātikas teorijā, bet koncentrēties uz vienkāršu un visiem saprotamu tieši programmētājiem svarīgo lietu izklāstu. Ar to ievadam laikam pietiks, ķeramies pie lietas.

Vektori 

            Vienkāršāk saprast, kas ir vektors, laikam ir ar zīmējuma palīdzību. Turpmākajos zīmējumos pieņemsim, ka viena rūtiņa apzīmē vienu vienību.

  1. zīmējums.

            Vektori savā būtība apzīmē pārvietojumu. Piemēram, vektors A apzīmē pārvietojumu divas vienības pa labi un divas vienības uz augšu. Mēs varam teikt, ka vektora A vērtība ir (2;2). Savukārt vektors D apzīmē pārvietojumu divas vienības pa kreisi un trīs vienības uz leju, tā vērtība ir (-2;-3). Pirmā vērtība vektora vērtības pierakstā ir pārvietojums pa horizontālo (x) asi, bet otrā pa vertikālo (y) asi. Mēs varam teikt, ka šie vektori sastāv no divām komponentēm – x un y. Un pierakstam mēs to šādi (x;y). Svarīgi ir atcerēties, ka vektors pats par sevi norāda tikai pārvietojumu, nevis savu atrašanos telpā. Piemēram, vektors A=B, jo abu vērtība ir (2;2). D=C, jo abu vērtība ir (-2;-3). Būtībā, par vektoriem A un B mēs varam teikt, ka tas ir viens un tas pats vektors uzzīmēts divās dažādās vietās. To pašu mēs varam teikt par C un D.

            Tātad, mēs esam noskaidrojuši, ka vektors apzīmē pārvietojumu, ko var apzīmēt ar diviem skaitļiem: (x;y). Bet tik pat labi mēs varam teikt, ka vektoram ir virziens un garums. Jo pārvietojums taču būtībā ir kustība noteiktā virzienā, noteiktā attālumā. Tātad A=B, jo tiem ir vienādi virzieni un garumi. To pašu varam teikt par C un D. A ir paralēls F, bet tie nav vienādi, jo to garumi ir dažādi. Savukārt E ir inverss B, jo to virzieni ir tieši pretēji, bet garumi vienādi. Savukārt F ir pretējs E, jo to virzieni pretēji, bet garumi dažādi. Viss tas taču ir vienkārši, to taču māca jau 9. klasē laikam, bet ne visi jau klausās, ko tā skolotāja tur runā :) Šie visi piemēri ir 2 dimensiju vektori, bet tieši tie paši noteikumi attiecas arī uz 3 un vairāk dimensiju vektoriem, tikai attiecīgi tiek pievienotas vēl komponentes „z” un citas. Piemēram, 3 dimensiju vektoru parasti apzīmē: (x;y;z). Kodu paraugos vērsīsimies pie individuālām vektora komponentēm lūk šādi:

Vector.x=OtherVector.z;
Vector.y=....;
Utt.

            Vēl vektors var apzīmēt kādu noteiktu punktu telpā, un notiek tas šādi.

  1. zīmējums. 

            Kādas ir punkta A koordinātes? (-5;4) Kāda ir vektora C vērtība? (-5;4) Kāda starpība? Nekāda :) Tas pats ar punktu B(6;4) un vektoru D(6;4). Tātad vektors var apzīmēt pārvietojumu un arī noteiktu punktu, viss atkarīgs no tā, kā mēs interpretējam vektora vērtību. Bet svarīgi ir atcerēties, ka, kad vektors apzīmē pārvietojumu, tam nav noteikta atrašanās vieta – mēs varam veikt šo pārvietojumu no jebkura brīvi izvēlēta punkta. Bet, kad vektors apzīmē punktu, tas apzīmē tikai un vienīgi vienu konkrētu punktu ar noteiktu atrašanās vietu, kurā mēs nonākam veicot ar šo vektoru apzīmēto pārvietojumu no kāda atskaites punkta, parasti (0;0) – „pasaules centra”. Tas varbūt izklausās pārāk matemātiski un sarežģīti, bet tā nav. Jāsaprot vienkārši, ka ar vektoru var apzīmēt pārvietojumu, kā arī konkrētu punktu.

            Tālāk, dažas darbības, ko var veikt ar vektoriem. 

Vektoru saskaitīšana 

            Grafiski tas izskatās šādi:

  1. zīmējums. 

            Nekā sarežģīta te nav. C=A+B, un skaitliski tas notiek tā: (9;8)=(2;6)+(7;2); katru pirmā vektora komponentu saskaitām ar attiecīgo otra vektora komponentu, jeb:

C.x=A.x+B.x;
C.y=A.y+B.y;
C.z=A.z+B.z;

   Kā redzams, šī darbība ir komutatīva, t.i. mainīgo secības izmaiņa neizmaina rezultātu, jeb A+B=B+A. 

Vektoru invertēšana 

            Vektoru invertēšana arī ir pavisam vienkārša darbība, izskatās tā šādi:

4. zīmējums.

            Apzīmēt to var šādi: A=-B; (-2;-4)=-(2;4); Šī darbība samaina vektora virzienu uz pretēju sākuma virzienam, bet nemaina vektora garumu. Var teikt, ka A ir inverss B. Viss, kas ir jādara – jāsamaina zīme katrai vektora vērtības komponentei, jeb:

B.x=-A.x;
B.y=-A.y;
B.z=-A.z;

 

Vektoru atņemšana 

            Vēl viena vienkārša darbība ir vektoru atņemšana, kas īstenībā ir viena vektora saskaitīšana ar otra vektora inverso vektoru.

5. zīmējums.

            Kā redzams, C=A-B=A+(-B); (6;0)=(8;4)-(2;4)=(8;4)+(-2;-4); No katra pirmā vektora komponentes atņemam attiecīgo otrā vektora komponenti, jeb:

C.x=A.x-B.x;
C.y=A.y-B.y;
C.z=A.z-B.z;

            Saprotams, ka šī darbība nav komutatīva: A-B nav vienāds ar B-A, bet gan A-B=-(B-A).

            Ja ar vektoriem ir apzīmēti punkti telpā, tad šo vektoru starpība būs pārvietojums no tā punkta, kuru atņem, uz to punktu, no kura atņem.

6. zīmējums.

Redzams, ka C=A-B; (9;7)=(3;5)-(-6;-2). Tieši tāpat, kā tas notiktu, ja A un B būtu vektori. 

Vektoru reizināšana un dalīšana ar skaitli 

            Reizinot vai dalot vektoru ar skaitli, vienkārši vajag reizināt/dalīt katru vektora komponenti ar šo skaitli. Rezultātā, iegūstam vektoru, kura garums ir mainījies, bet kurš vērsts tajā pašā virzienā, vai arī tieši pretējā virzienā – ja esam reizinājuši/dalījuši ar negatīvu skaitli. Vieglāk to laikam saprast pētot sakarības šajā zīmējumā:

7. zīmējums.

            Redzams, ka A=2*C jeb (4;0)=2*(2;0); B=3*C jeb (6;0)=3*(2;0); B=A*1.5 jeb (6;0)=1.5*(3;0); Arī apgrieztās sakarības ir patiesas: C=A/2 jeb (2;0)=(4;0)/2; C=B/3 jeb (2;0)=(6;0)/3; A=B*2/3 jeb (4;0)=(6;0)*2/3; Bet, ja reizinām/dalām ar negatīvu skaitli, iegūstam pretēji vērstus vektorus: E=-1*A jeb (-4;0)=-1*(4;0); D=B/(-2) jeb (-3;0)=(6;0)/(-2); E=C*(-2) jeb (-4;0)=(2;0)*(-2);

//reizināšana
B.x=A.x*Scalar;
B.y=A.y*Scalar;
B.z=A.z*Scalar;
//dalīšana
B.x=A.x/Scalar;
B.y=A.y/Scalar;
B.z=A.z/Scalar;

 

Vektora garuma noteikšana 

            Vektora garumu var noteikt gluži kā skolā mācīja – pēc Pitagora teorēmas. Vektora garumu apzīmē šādi: |A| - vektora A garums. Aprēķina to šādi |A|=sqrt(A.x*A.x+A.y*A.y), un 3 dimensijās tas notiek līdzīgi:

len=sqrt(A.x*A.x+A.y*A.y+A.z*A.z);

            To mēs tik vienkārši varam darīt, jo, kā redzams šajā zīmējumā:

8. zīmējums.

katru vektoru var sadalīt divās (3 dimensijām - trijās) komponentēs, kuras tad arī ir tieši tās vērtības, ko apzīmē katra vektora komponente, un starp šīm komponentēm veidojas 90 grādu leņķis. Kopā ar sākotnējo vektoru tās veido taisnleņķa trīsstūri, kura hipotenūzu, zinot katetes, mēs, protams, varam izrēķināt pēc Pitagora teorēmas.

Vektora normalizēšana 

            Normāl vektors ir tāds vektors, kura garums ir 1, jeb |N|=1. Lai normalizētu vektoru, jāpielieto divas iepriekšējās funkcijas – jāizrēķina tā garums un jāizdala ar to. Tādā veidā mēs iegūstam vektoru, kura virziens nav mainījies, bet garums ir 1. Citiem vārdiem: B.x=A.x/|A|; B.y=A.y/|A|; Jeb:

len=sqrt(A.x*A.x+A.y*A.y+A.z*A.z);
B.x=A.x/len;
B.y=A.y/len;
B.z=A.z/len;

 

Vektoru skalārais reizinājums – dot product 

            Vektoru skalārajam reizinājumam īstenībā nemaz nav ērta grafiskā attēlojuma, tāpēc iztiksim šoreiz bez zīmējuma. Divu vektoru skalārais reizinājums ir skaitlis, kuru aprēķina šādi: A dot B=A.x*B.x+A.y*B.y; vai arī: A dot B=|A|*|B|*cos a; kur a – leņķis starp vektoriem A un B. Trīs dimensijām tas izskatās šādi:

dot=A.x*B.x+A.y*B.y+A.z*B.z;

Šīs funkcijas noderīgums slēpjas faktā, ka, ja abi vektori ir normalizēti (to garums ir 1), tad dot products dod kosinusu leņķim starp vektoriem. Kas ir noderīgi daudzos aprēķinos, piemēram, lai izrēķinātu gaismas ietekmi uz virsmu atkarībā no leņķa starp gaismas vektoru un virsmas normāl vektoru. 

Vektoru vektoriālais reizinājums – cross product 

            Vektoriālais reizinājums dod vektoru, kurš perpendikulārs abiem pirmajiem vektoriem. Līdz ar to vektoriālais reizinājums iespējams tikai 3 dimensiju vektoriem, jo divās dimensijās nav iespējams atrast perpendikulāru vektoru diviem jebkuriem brīvi izvēlētiem vektoriem, ja tie nav paralēli. Bet 4 un vairāk dimensijās būs bezgalīgi daudz vektoru, kuri ir perpendikulāri diviem citiem brīvi izvēlētiem vektoriem. Pārāk daudz nesatraucies, ja nevari īsti iedomāties, kā tie 4d vektori varētu izskatīties :) Galvenais ir saprast, ka 3 dimensijās jebkuriem diviem brīvi izvēlētiem vektoriem (izņemot paralēliem vai pretēji vērstiem) pastāv divi vektori, kuri ir perpendikulāri pirmajiem diviem. Cross productu apzīmē ar „x”.

9. zīmējums.

            Kā redzams šajā attēlā, diviem brīvi izvēlētiem vektoriem A un B, ir divi vektori – C un D, kuri perpendikulāri gan A, gan B. Cross products dos tikai vienu no šiem vektoriem, tāpēc argumentu secība cross produktam ir svarīga. AxB=C, bet BxA=D. Protams, ka D=-C. Šī funkcija ar skaitļiem izskatās šādi:

C.x=A.y*B.z-B.y*A.z;
C.y=A.z*B.x-B.z*A.x;
C.z=A.x*B.y-B.x*A.y;

 

            Un rezultējošā vektora garums ir |AxB|=|A|*|B|*sin a; kur a – leņķis starp A un B. 

Matricas 

            Kas tad īsti ir matrica? Diemžēl Morpheus’a skaidrojums ir diezgan neprecīzs, un filmu vien noskatoties mēs nekļūsim par labākiem programmētājiem. Priekš mums matrica būs divdimensionāls skaitļu masīvs, un tā kā šie tutoriāļi ir domāti 3d spēlēm, tad mūs interesēs 3x3 un 4x4 matricas. Izskatās tās šādi:

1.0

0.0

0.0

0.0

1.0

0.0

0.0

0.0

1.0


un

1.0

0.0

0.0

0.0

0.0

1.0

0.0

0.0

0.0

0.0

1.0

0.0

0.0

0.0

0.0

1.0

            Protams, katrā no elementiem var būt jebkāds skaitlis, ne tikai 0.0 vai 1.0, bet šīs divas parādītās matricas ir diezgan īpašas – tās ir identitātes matricas, bet par tām sīkāk mazliet vēlāk.

            Kam mums tādas matricas ir vajadzīgas? Vienkārši – lai izdarītu darbības ar vektoriem. Matricas var apzīmēt dažādas darbības ar vektoriem: rotācijas, pārbīdes (translations) un mēroga maiņas (scales), vai visu kopā. 3x3 matricas 3d telpā var apzīmēt tikai rotācijas, bet 4x4 matricas var apzīmēt gan rotācijas (par ko rūpējas kreisais augšējais 3x3 stūrītis, gluži kā to dara 3x3 matrica), gan pārbīdi, kur tiek lietotas 3 apakšējās kreisās ailes, gan mēroga maiņu, kur tiek lietota visa labā kolonna.

            Bet kur tieši tas tiek spēlē izmantots? Iedomājieties šādu situāciju: mums zināms, ka noteiktās koordinātēs (relatīvi pret punktu (0;0;0) - „pasaules centru”) atrodas noteikta objekta vertexi (punkti, kuri nosaka objekta poligonu stūru atrašanās vietu, tos apzīmē ar vektoriem), un mums zināms, ka noteiktā punktā atrodas spēlētāja „acs”, pavērsta noteiktā leņķī. Tagad, ja mēs gribam uzzīmēt šo objektu uz spēlētāja ekrāna, kā lai mēs uzzinām kurā vietā tieši uz spēlētāja ekrāna jāzīmē katrs objekta vertexs? Te mums palīgā nāk matricas, jo tas ir pats ērtākais un efektīvākais veids, kā paveikt šo un vēl daudzus citus uzdevumus.

            Lai saprastu tālāk stāstīto, ir jāiemācās vēl viens jēdziens – telpa (space). Tas apzīmē, relatīvi pret kuru punktu tiek saglabātas konkrēta vektora koordinātes. Piemēram, ja vektors tiek glabāts atmiņā pēc koordinātēm, kādās tas atrodas relatīvi pret pasaules centru, tad tas atrodas pasaules telpā (world space). Ja vektors tiek glabāts relatīvi pret kāda noteikta objekta centru un orientāciju, tad tas atrodas tā objekta jeb modeļa telpā (object space, jeb model space). Ja vektors tiek glabāts relatīvi pret „aci” un tās orientāciju, tad tas atrodas acs telpā (eye space).

            Zinot „acs” atrašanās vietu un skata virzienu, un skata leņķi, mēs varam izveidot tādu matricu, ar kuru pareizinot katru objekta vertexu mēs iegūsim tā atrašanās vietu uz spēlētāja ekrāna. 3d renderēšanas bibliotēkās, kā OpenGL vai Direct3D, tas parasti notiek sekojoši. Ir divas matricas: projekcijas matrica (projection matrix) un modeļa matrica (modelview matrix). Modelview matricas uzdevums ir pārvērst vektorus no object space’a uz eye space. Savukārt projection matrica pārvērš vertexus tālāk screen space’ā – koordinātēs relatīvās pret ekrānu. Bet tas tā – tikai, lai dotu nojausmu par to, cik svarīgas ir matricas spēlē. Šī tutoriāļa tēma būs matricas un ar tām saistītās matemātikas noderīgas funkcijas. Kādā nākamajā tutoriālī apskatīsim to, kā šī matemātika tiek pielietota OpenGL programmā. 

Matricas datora atmiņā 

            Lai mēs varētu pielietot matricas savās programmās mums no sākuma ir jāizdomā veids, kā mēs 2d masīvu, kas izskatās šādi:

M00

M10

M20

M30

M01

M11

M21

M31

M02

M12

M22

M33

M03

M13

M23

M33

saglabāsim atmiņā, kur elementi veido 1d masīvu:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Šeit arī rodas pirmās domstarpības, jo daži nolemj, ka labāk to saglabāt ir šādi:

M00

M01

M02

M03

M10

M11

M12

M13

M20

M21

M22

M23

M30

M31

M32

M33

bet citi, ka šādi:

M00

M10

M20

M30

M01

M11

M21

M31

M02

M12

M22

M32

M03

M13

M23

M33

OpenGL izvēlējās pirmo variantu, bet D3D otro.

            Līdz ar to arī viss turpmākais kods, ko es došu, būs attiecināms tikai uz OGL matricām, kas ir „collumn-major” matricas, nevis uz D3D, kas ir „row-major” matricas. Pāriet no viena tipa uz otru gan ir vienkārši – matricu vajag vienkārši transponēt (šī darbība tiks paskaidrota vēlāk). Vai arī var vienkārši visā kodā samainīt indeksus, ar kuriem notiek vēršanās pie konkrēta matricas elementa.

            Parasti matricas programmas atmiņā glabā šādi:

float M[9];
float M[16];

 

kā 1dimensionālus masīvus, kurā matrica ir izklāta kā pirmajā paraugā dots, vai, ja gribam ar D3D savienojamās row major matricas, tad kā otrajā paraugā dots. Jebkurā gadījumā – gan OGL gan D3D izmanto 4x4 matricas, jo tās var apzīmēt visu iespējamo darbību spektru, bet mēs priekš sevis uztaisīsim arī 3x3 matricas, lai dažās vietās, kur nepieciešamas tikai rotācijas, netērētu lieki resursus turot atmiņā un izdarot darbības ar 4x4 matricām. 

Identitātes matricas 

            Tās ir īpašas matricas, un īpašas tās ir ar to, ka nedara neko :) Izskatās tās kā parādīts pašā tutoriāļa sākumā, un, pareizinot jebkuru vektoru ar identitātes matricu, mēs iegūsim to pašu vektoru, kas mums bija sākumā. Tālāk, kādas darbības mēs īsti varam veikt ar matricām? 

Matricas transponēšana 

            Šī ir pavisam vienkārša darbība, kur no vienas matricas mēs dabūsim citu matricu, kas būs tās attiecīgi row/collumn major versija. Tas nozīmē, ja mēs transponēsim row major matricu, mēs iegūsim collumn major matricu, ar tām pašām īpašībām, un otrādāk.

//3x3 matricai
M1[0]=M2[0]; M1[1]=M2[3]; M1[2]=M2[6];
M1[3]=M2[1]; M1[4]=M2[4]; M1[5]=M2[7];
M1[6]=M2[2]; M1[7]=M2[5]; M1[8]=M2[8];
//4x4 matricai
M1[0]=M2[0]; M1[1]=M2[4]; M1[2]=M2[8]; M1[3]=M2[12];
M1[4]=M2[1]; M1[5]=M2[5]; M1[6]=M2[9]; M1[7]=M2[13];
M1[8]=M2[2]; M1[9]=M2[6]; M1[10]=M2[10]; M1[11]=M2[14];
M1[12]=M2[3]; M1[13]=M2[7]; M1[14]=M2[11]; M1[15]=M2[15];

 

Vektora reizināšana ar matricu 

            Šī tad arī ir laikam pati svarīgākā matricas funkcija, ar to mēs izdarām ar matricu aprakstītos pārveidojumus (rotāciju, pārbīdi...) kādam vektoram. Ar 3x3 matricu viss ir vienkārši:

rx=M[0]*x+M[3]*y+M[6]*z;
ry=M[1]*x+M[4]*y+M[7]*z;
rz=M[2]*x+M[5]*y+M[8]*z;

Bet ar 4x4 matricu ir mazliet savādāk. Jo īstenībā, lai pilnībā aprakstītu vektoru 3d telpā ir nepieciešami četri elementi – x,y,z un w; kur w ir „mērogs”, ar kuru šis vektors ir jādala. Piemēram, vektora (2;4;6;2) īstā vērtība būtu (1;2;3;1). Un pilna 3d vektora reizināšana ar 4x4 matricu nozīmē:

rx=M[0]*x+M[4]*y+M[8]*z+M[12]*w;
ry=M[1]*x+M[5]*y+M[9]*z+M[13]*w;
rz=M[2]*x+M[6]*y+M[10]*z+M[14]*w;
rw=M[3]*x+M[7]*y+M[11]*z+M[15]*w;

Bet tā kā šis mērogs lielākoties nav nepieciešams, jo gandrīz visiem vektoriem tas vienmēr būtu 1 (un kāda jēga lieku reizi dalīt ar 1?), un gandrīz neviena matrica neietekmētu šo mērogu, un mēs negribam palielināt nepieciešamo atmiņu par 33% glabājot 4 vērtības 3 vietā, mūsu vektors sastāv no 3 elementiem un šajā reizinājuma w vērtība tiek pieņemta par 1:

rx=M[0]*x+M[4]*y+M[8]*z+M[12];
ry=M[1]*x+M[5]*y+M[9]*z+M[13];
rz=M[2]*x+M[6]*y+M[10]*z+M[14];

 

Divu matricu reizināšana 

            Šī arī ir diezgan svarīga operācija, jo tā ļauj apvienot divu matricu apzīmētās operācijas vienā matricā. Piemēram, ja ir zināma viena matrica, kura apzīmē rotāciju, un ir zināma otra matrica, kura apzīmē pārvietojumu, un nepieciešams daudzus vektorus gan pagriezt ar pirmo matricu, un pabīdīt ar otro, tad tā vietā, lai katru vektoru reizinātu ar divām matricām, mēs varam apvienot šīs matricas vienā, tās savstarpēji sareizinot tikai vienreiz, un tad katru vektoru reizināt tikai ar iegūto matricu, izdarot mazāk aprēķinus.

//M – rezultāts, m1 un m2 – reizināmās matricas
//3x3 matricām
M[0]=m1[0]*m2[0]+m1[1]*m2[3]+m1[2]*m2[6];
M[1]=m1[0]*m2[1]+m1[1]*m2[4]+m1[2]*m2[7];
M[2]=m1[0]*m2[2]+m1[1]*m2[5]+m1[2]*m2[8];
M[3]=m1[3]*m2[0]+m1[4]*m2[3]+m1[5]*m2[6];
M[4]=m1[3]*m2[1]+m1[4]*m2[4]+m1[5]*m2[7];
M[5]=m1[3]*m2[2]+m1[4]*m2[5]+m1[5]*m2[8];
M[6]=m1[6]*m2[0]+m1[7]*m2[3]+m1[8]*m2[6];
M[7]=m1[6]*m2[1]+m1[7]*m2[4]+m1[8]*m2[7];
M[8]=m1[6]*m2[2]+m1[7]*m2[5]+m1[8]*m2[8];
 
//4x4 matricām
M[0] = m1[0]*m2[0] + m1[1]*m2[4] + m1[2]*m2[8] + m1[3]*m2[12];
M[1] = m1[0]*m2[1] + m1[1]*m2[5] + m1[2]*m2[9] + m1[3]*m2[13];
M[2] = m1[0]*m2[2] + m1[1]*m2[6] + m1[2]*m2[10] + m1[3]*m2[14];
M[3] = m1[0]*m2[3] + m1[1]*m2[7] + m1[2]*m2[11] + m1[3]*m2[15];
M[4] = m1[4]*m2[0] + m1[5]*m2[4] + m1[6]*m2[8] + m1[7]*m2[12];
M[5] = m1[4]*m2[1] + m1[5]*m2[5] + m1[6]*m2[9] + m1[7]*m2[13];
M[6] = m1[4]*m2[2] + m1[5]*m2[6] + m1[6]*m2[10] + m1[7]*m2[14];
M[7] = m1[4]*m2[3] + m1[5]*m2[7] + m1[6]*m2[11] + m1[7]*m2[15];
M[8] = m1[8]*m2[0] + m1[9]*m2[4] + m1[10]*m2[8] + m1[11]*m2[12];
M[9] = m1[8]*m2[1] + m1[9]*m2[5] + m1[10]*m2[9] + m1[11]*m2[13];
M[10] = m1[8]*m2[2] + m1[9]*m2[6] + m1[10]*m2[10] + m1[11]*m2[14];
M[11] = m1[8]*m2[3] + m1[9]*m2[7] + m1[10]*m2[11] + m1[11]*m2[15];
M[12] = m1[12]*m2[0] + m1[13]*m2[4] + m1[14]*m2[8] + m1[15]*m2[12];
M[13] = m1[12]*m2[1] + m1[13]*m2[5] + m1[14]*m2[9] + m1[15]*m2[13];
M[14] = m1[12]*m2[2] + m1[13]*m2[6] + m1[14]*m2[10] + m1[15]*m2[14];
M[15] = m1[12]*m2[3] + m1[13]*m2[7] + m1[14]*m2[11] + m1[15]*m2[15];

 

Tikai atkal jāņem vērā tas, ka šajā darbība ir svarīga mainīgo secība. M1*M2 nav tas pats, kas M2*M1. Atgriežoties pie piemēra ar M1=matrica ar rotāciju, M2=matrica ar pārbīdi, tad, ja mēs aprēķināsim kopējo matricu šādi: MK=M1*M2, tad visi vektori, kas tiek reizināti ar MK tiks no sākuma pagriezti un pēc tam pārbīdīti, bet ja MK=M2*M1, tad tie no sākuma tiks pārbīdīti, un pēc tam pagriezti. Kā redzams, 3x3 matricu ar 4x4 matricu sareizināt nevar. Bet ja mums ļoti gribās, mēs varam izveidot jaunu 4x4 matricu, kuras augšējais kreisais stūris būs vienāds ar šo 3x3 matricu un pārējie elementi būs kā identitātes matricā, un tad izmantot šo jauno 4x4 matricu reizinājumā ar pirmo 4x4 matricu. 

Matricas invertēšana 

            Un pēdējā, bet ne mazāk svarīgā darbība, par ko šodien pastāstīšu sakarā ar matricām, būs matricas invertēšana. Ar tās palīdzību mēs iegūstam – jā, uzminēji, matricas inverso matricu :) Tā ir matrica, kas dara tieši pretējo sākotnējai matricai. Ja matrica veic rotāciju x grādus pa labi, tad inversā veiks rotāciju x grādus pa kreisi. Tas ir, ja M2=Invert(M1); un Vector2=M1*Vector1; tad Vector1=M2*Vector2;

//3x3 matricai
//M – matrica, kurai gribam uzzināt inverso matricu, MR – rezultāts
//MD – 3x3 matrica pagaidu vērtību glabāšanai
//DetM – pagaidu vērtība – matricas determinants

MD[0]=M[4]*M[8]-M[5]*M[7];
MD[1]=M[3]*M[8]-M[5]*M[6];
MD[2]=M[3]*M[7]-M[4]*M[6];

MD[3]=M[1]*M[8]-M[2]*M[7];
MD[4]=M[0]*M[8]-M[2]*M[6];
MD[5]=M[0]*M[7]-M[1]*M[6];

MD[6]=M[1]*M[5]-M[2]*M[4];
MD[7]=M[0]*M[5]-M[2]*M[3];
MD[8]=M[0]*M[4]-M[1]*M[3];

DetM=M[0]*M[4]*M[8]+M[1]*M[5]*M[6]+M[2]*M[3]*M[7]-M[0]*M[5]*M[7]-M[1]*M[3]*M[8]-M[2]*M[4]*M[6];

MR[0]=MD[0]/DetM;
MR[3]=-MD[1]/DetM;
MR[6]=MD[2]/DetM;

MR[1]=-MD[3]/DetM;
MR[4]=MD[4]/DetM;
MR[7]=-MD[5]/DetM;

MR[2]=MD[6]/DetM;
MR[5]=-MD[7]/DetM;
MR[8]=MD[8]/DetM;

//4x4 matricai
//mat - matrica, kurai gribam uzzināt inverso matricu
//dst - rezultāts
//src – 4x4 matrica, tmp – 12 elementu masīvs pagaidu vērtību glabāšanai
//det – pagaidu vērtība – matricas determinants

src[0] = mat[0]; src[4] = mat[1]; src[8] = mat[2]; src[12] = mat[3];
src[1] = mat[4]; src[5] = mat[5]; src[9] = mat[6]; src[13] = mat[7];
src[2] = mat[8]; src[6] = mat[9]; src[10] = mat[10]; src[10] = mat[11];
src[3] = mat[12]; src[7] = mat[13]; src[11] = mat[14]; src[15] = mat[15];

tmp[0] = src[10] * src[15];
tmp[1] = src[11] * src[14];
tmp[2] = src[9] * src[15];
tmp[3] = src[11] * src[13];
tmp[4] = src[9] * src[14];
tmp[5] = src[10] * src[13];
tmp[6] = src[8] * src[15];
tmp[7] = src[11] * src[12];
tmp[8] = src[8] * src[14];
tmp[9] = src[10] * src[12];
tmp[10] = src[8] * src[13];
tmp[11] = src[9] * src[12];

dst[0] = tmp[0]*src[5] + tmp[3]*src[6] + tmp[4]*src[7];
dst[0] -= tmp[1]*src[5] + tmp[2]*src[6] + tmp[5]*src[7];
dst[1] = tmp[1]*src[4] + tmp[6]*src[6] + tmp[9]*src[7];
dst[1] -= tmp[0]*src[4] + tmp[7]*src[6] + tmp[8]*src[7];
dst[2] = tmp[2]*src[4] + tmp[7]*src[5] + tmp[10]*src[7];
dst[2] -= tmp[3]*src[4] + tmp[6]*src[5] + tmp[11]*src[7];
dst[3] = tmp[5]*src[4] + tmp[8]*src[5] + tmp[11]*src[6];
dst[3] -= tmp[4]*src[4] + tmp[9]*src[5] + tmp[10]*src[6];
dst[4] = tmp[1]*src[1] + tmp[2]*src[2] + tmp[5]*src[3];
dst[4] -= tmp[0]*src[1] + tmp[3]*src[2] + tmp[4]*src[3];
dst[5] = tmp[0]*src[0] + tmp[7]*src[2] + tmp[8]*src[3];
dst[5] -= tmp[1]*src[0] + tmp[6]*src[2] + tmp[9]*src[3];
dst[6] = tmp[3]*src[0] + tmp[6]*src[1] + tmp[11]*src[3];
dst[6] -= tmp[2]*src[0] + tmp[7]*src[1] + tmp[10]*src[3];
dst[7] = tmp[4]*src[0] + tmp[9]*src[1] + tmp[10]*src[2];
dst[7] -= tmp[5]*src[0] + tmp[8]*src[1] + tmp[11]*src[2];

tmp[0] = src[2]*src[7];
tmp[1] = src[3]*src[6];
tmp[2] = src[1]*src[7];
tmp[3] = src[3]*src[5];
tmp[4] = src[1]*src[6];
tmp[5] = src[2]*src[5];
tmp[6] = src[0]*src[7];
tmp[7] = src[3]*src[4];
tmp[8] = src[0]*src[6];
tmp[9] = src[2]*src[4];
tmp[10] = src[0]*src[5];
tmp[11] = src[1]*src[4];

dst[8] = tmp[0]*src[13] + tmp[3]*src[14] + tmp[4]*src[15];
dst[8] -= tmp[1]*src[13] + tmp[2]*src[14] + tmp[5]*src[15];
dst[9] = tmp[1]*src[12] + tmp[6]*src[14] + tmp[9]*src[15];
dst[9] -= tmp[0]*src[12] + tmp[7]*src[14] + tmp[8]*src[15];
dst[10] = tmp[2]*src[12] + tmp[7]*src[13] + tmp[10]*src[15];
dst[10]-= tmp[3]*src[12] + tmp[6]*src[13] + tmp[11]*src[15];
dst[11] = tmp[5]*src[12] + tmp[8]*src[13] + tmp[11]*src[14];
dst[11]-= tmp[4]*src[12] + tmp[9]*src[13] + tmp[10]*src[14];
dst[12] = tmp[2]*src[10] + tmp[5]*src[11] + tmp[1]*src[9];
dst[12]-= tmp[4]*src[11] + tmp[0]*src[9] + tmp[3]*src[10];
dst[13] = tmp[8]*src[11] + tmp[0]*src[8] + tmp[7]*src[10];
dst[13]-= tmp[6]*src[10] + tmp[9]*src[11] + tmp[1]*src[8];
dst[14] = tmp[6]*src[9] + tmp[11]*src[11] + tmp[3]*src[8];
dst[14]-= tmp[10]*src[11] + tmp[2]*src[8] + tmp[7]*src[9];
dst[15] = tmp[10]*src[10] + tmp[4]*src[8] + tmp[9]*src[9];
dst[15]-= tmp[8]*src[9] + tmp[11]*src[10] + tmp[5]*src[8];

det=src[0]*dst[0]+src[1]*dst[1]+src[2]*dst[2]+src[3]*dst[3];
det = 1/det;

dst[0] = dst[0]*det;
dst[1] = dst[1]*det;
dst[2] = dst[2]*det;
dst[3] = dst[3]*det;
dst[4] = dst[4]*det;
dst[5] = dst[5]*det;
dst[6] = dst[6]*det;
dst[7] = dst[7]*det;
dst[8] = dst[8]*det;
dst[9] = dst[9]*det;
dst[10] = dst[10]*det;
dst[11] = dst[11]*det;
dst[12] = dst[12]*det;
dst[13] = dst[13]*det;
dst[14] = dst[14]*det;
dst[15] = dst[15]*det;

 

Quaternioni 

            Kas tad īsti ir quaternions? (Angļu – quaternion, nav ne mazākās nojausmas, kā to īstenība sauc latviski.) Tas ir 4dimensiju vektors, tātad sastāv no četrām komponentēm – x,y,z,w. Pēdējo komponenti mēdz dēvēt arī par „q”. Vai arī, vēl visas četras komponentes mēdz apzīmēt ar a,i,j,k. Es pieturēšos pie x,y,z,w.

Ko tad īsti ar to var iesākt? Ar to var apzīmēt rotācijas 3d telpā, gluži kā ar 3x3 matricām, bet quaternioniem ir viena priekšrocība: starp tiem ir iespējams ātri interpolēt. Ja nezini, ko nozīmē interpolēt – tas nozīmē „iestarpināt vērtības”. Jā, diez ko labs skaidrojums tas nav, bet neko labāku nevaru izdomāt, un vārdnīca arī nepalīdz. Varbūt piemērs palīdzēs. Ja quaternions A apzīmē rotāciju par 30 grādiem pa labi, un quaternions B par 90 grādiem pa labi, tad interpolējot starp šiem quaternioniem ar faktoru/daļu 0.5 (jeb par 50%), iegūsim quaternionu, kurš apzīmēs rotāciju par 60 grādiem. Jeb citiem vārdiem sakot, interpolējot no A uz B un palēnām palielinot interpolācijas faktoru/daļu no 0.0 līdz 1.0, mēs iegūsim quaternionus, kuri apzīmēs rotācijas no 30 līdz 90 grādiem. Parasti quaternioni tiek lietoti šādi: quaternioni tiek uztaisīti no 3x3 matricām, tiek veikta interpolācija un iegūtais quaternions tiek atkal pārvērsts par 3x3 matricu, lai ar to veiktu tālākās darbības ar vektoriem.

            Pielietojumi tiem ir iespējami daudz un dažādi, bet paši populārākie laikam ir kaulu animācijā, kur, piemēram, ja zināms, kādos leņķos kauls pagriezts, teiksim, piektajā un desmitajā kadrā (keyframe’os), un nepieciešams uzzināt tā rotāciju jebkurā starp kadrā. Otrs populārs pielietojums ir trešās personas kameru gludas rotācijas nodrošināšanā, kad kamera nevis tiek uzreiz nolikta vēlamajā virzienā, bet katru kadru palēnām tuvojas tam. Bet, protams, tos var pielietot jebkur, kur nepieciešams nodrošināt interpolāciju starp rotācijām.  

3x3 matrica -> quaternions 

            Iegūt quaternionu no 3x3 matricas mēs varam šādi:

//M – dotā 3x3 matrica
w = sqrt(1.0 + M[0] + M[4] + M[8]) * 2.0;
x = (M[7] - M[5]) / w ;
y = (M[2] - M[6]) / w ;
z = (M[3] - M[1]) / w ;
w=w/4.0;

 

quaternions -> 3x3 matrica

             Iegūt 3x3 matricu no quaterniona mēs varam šādi:

sqw = w*w;   
sqx = x*x;   
sqy = y*y;   
sqz = z*z;

M[0] =  sqx - sqy - sqz + sqw;
M[4] = -sqx + sqy - sqz + sqw;   
M[8] = -sqx - sqy + sqz + sqw;

tmp1 = x*y;   
tmp2 = z*w;
M[3] = 2.0 * (tmp1 + tmp2);  
M[1] = 2.0 * (tmp1 - tmp2);

tmp1 = x*z;
tmp2 = y*w;   
M[6] = 2.0 * (tmp1 - tmp2);   
M[2] = 2.0 * (tmp1 + tmp2);

tmp1 = y*z;
tmp2 = x*w;
M[7] = 2.0 * (tmp1 + tmp2);
M[5] = 2.0 * (tmp1 - tmp2);

 

SLERP 

SLERP ir saīsinājums no Spherical Linear intERPolation. Neprasiet man kāpēc tas tiek saīsināts tieši šādi, nezinu, tā tas vienkārši pieņemts. Slerp tad arī ir pati noderīgākā quaternionu funkcija – ar to notiek interpolācija starp diviem quaternioniem.

//Ax,Ay,Az,Aw – quaterniona, no kura interpolēsim, sastāvdaļas

//Bx,By,Bz,Bw – quaterniona, uz kuru interpolēsim, sastāvdaļas

//t – interpolācijas daļa

//pārējie – dažādas pagaidu vērtības

   

    Rez = (Ax * Bx) + (Ay * By) + (Az * Bz) + (Aw * Bw);

   

    if(Rez < 0.0)

    {

        Bx=-Bx;

        By=-By;

        Bz=-Bz;

        Bw=-Bw;

        Rez = -Rez;

    }

   

    Scale0 = 1.0 - t, Scale1 = t;

 

            if(1.0 - Rez > 0.1)

            {

                        theta = acos(Rez);

                        sinTheta = sin(theta);

 

                        Scale0 = sin( ( 1.0 - t ) * theta) / sinTheta;

                        Scale1 = sin( ( t * theta) ) / sinTheta;

            }         

 

            x = (Scale0 * Ax) + (Scale1 * Bx);

            y = (Scale0 * Ay) + (Scale1 * By);

            z = (Scale0 * Az) + (Scale1 * Bz);

            w = (Scale0 * Aw) + (Scale1 * Bw);

 

Quaternionu reizināšana 

            Arī quaternionus, tāpat kā matricas, var sareizināt, lai apvienotu to apzīmētas rotācijas.

w=Q1w*Q2w-Q1x*Q2x-Q1y*Q2y-Q1z*Q2z;
x=Q1w*Q2x+Q1x*Q2w+Q1y*Q2z-Q1z*Q2y;
y=Q1w*Q2y+Q1y*Q2w+Q1z*Q2x-Q1x*Q2z;
z=Q1w*Q2z+Q1z*Q2w+Q1x*Q2y-Q1y*Q2x;

 

End of transmission 

            Tas arī šodienai viss. Pārāk dziļu ieskatu apakšā esošajā matemātikā es acīmredzot nesniedzu :) bet tas arī nebija tutoriāļa mērķis. Cerams, ka vismaz esmu pastāstījis pietiekami, lai Tev nerastos problēmas augstāk minēto funkciju pielietojumā, kad tas būs nepieciešams. Ja rodas jautājumi, meklē palīdzību dev.gamez.lv vai meilo man: giga86@tvnet.lv

Kods

Jūs varētu interesēt arī šādi raksti:

Aptauja

Vai Tu apmeklēji gamedev.lv konferenci [0]



Citas aptaujas

Autorizācija

Lietotājs

Parole


Reģistrēties Aizmirsi paroli?