Modul 3.2: Animasjoner, forts.

Sammensatte transformasjoner

Hittil har modeller blitt animert vha. enkle transformasjoner, dvs. enten en skalering, en rotasjon eller en translasjon (forflytning). I dette kapitlet skal vi se mer på sammensatte transformasjoner og se at rekkefølgen man multipliserer de ulike matrisene på har betydning. Fra matematikken vet vi at matrisemultiplikasjon generelt ikke er kommutativ: Ma * Mb ≠ Mb * Ma.

Innledning

Når modeller defineres i et tenkt lokalt koordinatsystem er det ofte lurt å sentrere modellen om origo. Dette gjør det blant annet lettere å håndtere rotasjoner. Når modellen er definert kan man bruke en modellmatrise til å utføre en "modelltransformasjon". Vi tenker oss da at modellen plasseres i det globale koordinatsystemet eller «world space». Dette gjøres ved å multiplisere modellens vertekser med modellmatrisa. Dersom man f.eks. skal gjøre både en translasjon og en rotasjon av en modell (f.eks. sylinderen i figuren under) opprettes en translasjonsmatrise og en rotasjonsmatrise. Disse slås sammen til en matrise som så utgjør modelltransformasjonen for modellen. Modellmatrisa slås deretter sammen med view-matrisa slik at vi får en modelview-matrise (som igjen sendes til verteksshaderen). Dersom man definerer en sylinder som vi ønsker skal flyttes litt langs x-aksen for så å rotere om z-aksen kan man forestille seg at følgende skjer:

Modelltransformasjon: Forflytning og rotasjon
Modelltransformasjon: Forflytning og rotasjon

«M» indikerer at det utføres en modelltransformasjon. Sylinderen defineres her i utgangspunktet sentrert om origo. Til høyre ser vi sylinderen i «world space» etter at modelltransformasjonen er utført. Som vi vil se etter hvert har rekkefølgen translasjonsmatrisa og rotasjonsmatrisa multipliseres sammen på avgjørende betydning. Dersom man setter modellmatrisa lik identitetsmatrisa vil modellen plasseres direkte inn i det globale koordinatsystemet («world space») uten noen form for transformasjon. Dersom f.eks. en sylinder sentreres om origo i det lokale koordinatsystemet vil denne også være sentrert rundt origo i det globale systemet.

Modelltransformasjon: Modellmatrisa = Identitetsmatrisa
Modelltransformasjon: Modellmatrisa = Identitetsmatrisa

I dette tilfellet vil modell- og world-koordinater (dvs. koordinater etter modelltransformasjonen) være det samme.

Translere, Rotere, Skalere (TRS)

En sammensatt transformasjon kan være en kombinasjon av rotasjon (R), translasjon (T) og skalering (S). Flere slike transformasjoner slås sammen, vha. matrisemultiplikasjon, til en modellmatrise. Rekkefølgen disse slås sammen på har stor betydning og avhenger av hva man ønsker å oppnå. Dersom modellen både skal skaleres, roteres og transleres må den først skaleres, deretter roteres og til slutt transleres.

Utføringsrekkefølge: skaler, roter, transler. Matriserekkefølge: M = T*R*S
Utføringsrekkefølge: skaler, roter, transler. Matriserekkefølge: M = T*R*S.

For å få til dette må matrisene multipliseres sammen i motsatt rekkefølge, slik: T*R*S. Modellmatrisa beregnes med andre ord slik:

modellMatrise = translasjonsMatrise * rotasjonsMatrise * skaleringsMatrise

Se også [1, s. 187 ff.] og [4]. Dersom modellen kun skal flyttes og skaleres vil rekkefølgen T*S gi ønsket resultat (dvs. vi bruker ingen rotasjonsmatrise). Skal modellen kun flyttes og roteres brukes rekkefølgen T*R. Poenget er at man bruker de matrisene i TRS-rekkefølgen man trenger og utelater resten. Ønsker man at modellen skal «gå i bane» om et punkt kan man lage en «orbit» matrise. Dette er egentlig en kombinasjon av en rotasjonsmatrise og en translasjonsmatrise som slås sammen slik: O = R*T. Se figuren under.

En orbit.
Orbit = Rotasjon * Translasjon.

Skal «orbit» matrisa kombineres med de andre matrisene vil rekkefølgen se slik ut: T*O*R*S. Normalt vil vi starte med å sette modellmatrisa lik Identitetsmatrisa og deretter føye til aktuelle transformasjoner. Komplett rekkefølge blir da: (I*)T*O*R*S. Dersom det ikke er behov for noen transformasjoner settes modellmatrisa lik I.

Rotasjon etterfulgt av translasjon (T * R)

I påfølgende eksempler brukes et kvadrat for å illustrere sammensatte transformasjoner. Kvadratet er sentrert om origo i lokale koordinater. Vi ønsker å translere og rotere kvadratet. Dette løses ved å opprette to matriser; en rotasjonsmatrise (R) og en translasjonsmatrise (T). For å oppnå ønsket effekt må modellmatrisa (M) settes lik produktet av disse. Dersom vi følger (I)TORS-rekkefølgen settes M = I * T * R. (Her brukes ikke skalering og/eller Orbit).

translasjon_rotasjon
Rotasjon om y-aksen etterfulgt av translasjon langs z-aksen. M = I*T*R.

Koden under viser hvordan view- og projeksjonsmatrisene initialiseres i initCamera(), som typisk kalles ved oppstart i main(). Eksemplet er ikke komplett, en del funksjoner er utelatt av plasshensyn. Du finner komplett kode i kodeeksemplene som følger disse notatene. I draw() funksjonen er det vist hvordan modellmatrisa settes opp med translasjon og rotasjon. Legg merke til at renderInfo.modelMatrix i utgangspunktet settes lik identitetsmatrisa (I). Modell-matrisa slås sammen med view-matrisa til modelview-matrise, som så sendes inn til shaderen. Legg merke til at kvadratet er tegnet i XZ-planet.

Parametrene til viewmatrisa settes slik; [camPosX, camPosY, camPosZ] = [0,100,0] og [lookAtX, lookAtY, lookAtZ] = [0,0,0] betyr at kameraet står i positiv y og ser ned mot origo. Opp-retninga på kameraet er satt til [upX, upY,upZ] = [0,0,-1] for å se kvadratet som vist i figuren over. Det viktige her er imidlertid måten modellmatrisa beregnes på. Denne settes først lik identitetsmatrisa vha. setIdentity(). Deretter genereres matrisa ved å utføre metodekallene i motsatt rekkefølge i forhold til hva man ønsker utført. Her brukes metodene translate(0,0,20) og rotate(60, 0,1,0) i den rekkefølgen, som betyr at kvadratet roteres 60 grader om y-aksen før det transleres 20 enheter langs z-aksen.

Her kunne man alternativt brukt setTranslate() etterfulgt av rotate() og dermed utelatt kall på setIdentity() først, altså slik:

. . .
// MODEL:
renderInfo.modelMatrix.setTranslate(0, 0, 20);     //Flytt langs z-aksen.
renderInfo.modelMatrix.rotate(60, 0, 1, 0);        //Roterer om y-aksen.
. . .

Bruker man transformasjonsfunksjoner som IKKE starter med set, kombineres ny transformasjon (f.eks. rotate()) med transformasjonen i gjeldende modellmatrise (her modelMatrix). Bruker man transformasjonsfunksjoner som starter med set, som f.eks. setTranslate(), overskriver den eksisterende matrisa med en ren translasjonsmatrise og forkaster tidligere innhold.

Translasjon etterfulgt av rotasjon (R * T)

Alternativt ønsker vi å gjøre det motsatte, dvs. utføre en «orbit», dvs. først translere og deretter rotere, slik:

M = I * O = I * (R * T)

NB! Husk at transformasjonsmatrisene multipliseres sammen i motsatt rekkefølge i forhold til hva man ønsker utført. Alternativt kan vi lese fra høyre mot venstre for å se hva som skjer.

M_IRT
Translasjon langs z-aksen etterfulgt av rotasjon om y-aksen. M = I*R*T.

Her utføres først en translasjon og deretter en rotasjon; M = I * (R * T). Dette løses i WebGL ved å endre koden over slik:

. . .
// MODEL: M= I*R*T
renderInfo.modelMatrix.setIdentity();
renderInfo.modelMatrix.rotate(60, 0, 1, 0);        //Roterer om y-aksen.
renderInfo.modelMatrix.translate(0, 0, 20);        //Flytt langs z-aksen.
. . .

Her utføres først en translasjon langs z-aksen og deretter en rotasjon om y-aksen.

Kombinasjon av egen rotasjon og «orbit»

Dersom kvadratet både skal rotere om egen y-akse/origo og deretter gå i «bane» rundt y-aksen trengs det to rotasjoner. Se figuren under:

Hvordan få kvadratet til å rotere om egen akse OG gå i bane
Hvordan få kvadratet til å rotere om egen akse OG gå i bane.

Figuren illustrerer en rotasjon om y-aksen og en «orbit» (translasjon etterfulgt av en rotasjon). Modellmatrisa beregnes slik: M = I * O * R der O = R * T. Dette kan f.eks. løses i WebGL-kode slik:

. . .
// MODEL: M=I*O*R, der O=R*T
renderInfo.modelMatrix.setIdentity();
//Baneberegning / «Orbit»: 
renderInfo.modelMatrix.rotate(orbAngle, 0, 1, 0);   //Går i bane om y-aksen.
renderInfo.modelMatrix.translate(0, 0, orbTrans);   //Flytt langs z-aksen.
//Roter først om egen y-akse:
renderInfo.modelMatrix.rotate(rotAngle, 0, 1, 0);         //Roterer egen Y-akse.
. . .

der orbTrans (orb=orbit, «omløpsbane») f.eks. settes lik 20 og bestemmer hvilken «bane» kvadratet skal gå i. Variablene orbAngle og rotAngle kan f.eks. oppdateres kontinuerlig og variere mellom 0 og 360 for å gi en animasjon. Dette vil føre til at kvadratet kontinuerlig roterer om egen y-akse samtidig som det går i bane rundt y-aksen.

Skalering, rotasjon om egen akse og «orbit»

Anta videre at vi har behov for å skalere modellen før den tegnes. Dette gjøres ved å bruke en skaleringsmatrise.

IORS
Orbit, Rotasjon, Skalering.

Her utføres først en skalering, deretter rotasjon om y-aksen og til slutt en «orbit». M = I*O*R*S der O=R*T. Dette kan løses i WebGL-koden slik:

. . .
// MODEL: M=I*O*R*S, der O=R*T
renderInfo.modelMatrix.setIdentity();
//Baneberegning / «Orbit»: 
renderInfo.modelMatrix.rotate(orbAngle, 0, 1, 0);
renderInfo.modelMatrix.translate(0, 0, orbTrans);
//Roter om egen y-akse:
renderInfo.modelMatrix.rotate(rotAngle, 0, 1, 0);
//Skalerer først. Her; nedskalerer 50% i alle akser:
renderInfo.modelMatrix.scale(0.5, 0.5, 0.5);
. . .

Her utføres først en skalering. Deretter kombineres dette med de samme transformasjonene som i eksemplene over.

Legger til translasjon

Anta nå at vi ønsker å kombinere de foregående transformasjonene med en translasjon (forflytning). Figuren under illustrerer dette. Etter skalering, rotasjon og «orbit» transleres modellen et stykke langs X-aksen.

ITORS
Skalering, rotasjon om y-aksen, «orbit» (translasjon og en rotasjon) og translasjon (langs X). M = I*T*O*R*S

WebGL-kode:

. . .
// MODEL: M=I*T*O*R*S, der O=R*T
renderInfo.modelMatrix.setIdentity();
//Translerer hele modellen: 
renderInfo.modelMatrix.translate(25,0,0);
//Baneberegning / «Orbit»: 
renderInfo.modelMatrix.rotate(orbAngle, 0, 1, 0);
renderInfo.modelMatrix.translate(0, 0, orbTrans);
//Roter om egen y-akse:
renderInfo.modelMatrix.rotate(rotAngle, 0, 1, 0);
//Skalerer først. Her; nedskalerer 50% i alle akser:
renderInfo.modelMatrix.scale(0.5, 0.5, 0.5);
. . .

Her er modellen flyttet 25 enheter langs x-aksen vha. translate(25,0,0) til slutt i «transformasjonssekvensen». Denne sekvensen av transformasjoner, dvs. M = I*T*O*R*S, tar høyde for det meste av vanlige transformasjoner og sørger for at ting skjer i korrekt rekkefølge.

Høyrehåndsregelen for rotasjon

Legg merke til at rotasjon rundt eksempelvis y-aksen er definert i forhold til høyrehåndsregelen: La høyre hånds tommel peke i positiv retning av aksen og krum fingrene "rundt aksen" (dvs. mot klokka). Dette vil være en positiv rotasjon rundt aksen (dvs. når vi angir en positiv rotasjonsvinkel φ).

Eksempel; Planet-Måne

Anta at vi skal animere en planet som roterer om egen akse med en eller flere måner, som roterer rundt planeten. Månen(e) skal også rotere om egen akse. Her bør man bruke samme kulemodell/figur for å tegne både planeten og månen(e). Definisjonen av modellen gjøres en gang men modellen tegnes flere ganger, hver gang med ny og endret transformasjon, i form av en modellmatrise.

Forslag til løsning - forenklet:
Start med å lage et verteksbuffer som inneholder vertekser for en kule. Før planeten tegnes settes modellmatrisa lik en rotasjonsmatrise, dvs. en rotasjon om y-aksen. Dersom det er nødvendig å skalere planeten opp eller ned kombineres rotasjonsmatrisa med en skaleringsmatrise (i rett rekkefølge). Siden animate() og draw() metodene kjører kontinuerlig genereres det, for hver runde, en ny rotasjonsmatrise vha. en yRot-variabel. Dette gjør at planeten kontinuerlig roterer om y-aksen. Variabelen yRot bør variere mellom 0-360 grader og settes tilbake til 0 når den når 360 (yRot += rotationSpeed * elapsed; yRot %= 360). Videre lages en ny modellmatrise for å kunne tegne månen. Modellmatrisa til månen må inneholde en (ned)skalering, en rotasjon om egen akse og en "orbit" som sørger for at den går i bane rundt planeten. Her er det viktig å slå sammen matrisene i korrekt rekkefølge. Deretter tegnes månen vha. samme verteksbuffer som ble brukt til planeten.

1 kommentar:

  1. Det går også ann å gjøre "orbit" ved å sette koordinatene til sin(rotasjonsvinkel) ganger radius i x-retning og cos(rotasjonsvinkel) ganger radius i y-retning. Eventuelt xz eller yz retning. Da blir det en ren translasjon, og man slipper å bruke rotasjonsmatrisen.

    SvarSlett