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 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 utføres en «modelltransformasjon» og 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 sylinderen, i figuren under, opprettes en translasjonsmatrise og en rotasjonsmatrise. Disse slås sammen til en matrise som så utgjør modelltransformasjonen for sylinderen. 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 |
«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 kuben også være sentrert rundt origo i det globale systemet.
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.
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å (Angel, E. et.al., 2015, side 187->) og (Cawood, S., McGee. P., 2009).
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.
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 minimum 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 minimum lik I.
Her følger noen eksempler med kode.
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).
Legg merke til at identitetsmatrisa (I) er tatt med for å indikere at M minimum bør settes lik I (alternativt kutte den ut og sette modelview-matrisa lik view-matrisa direkte).
Dette løses i WebGL vha. følgende kode:
. . .
// [cuon-utils]: Matrix4.prototype.setLookAt =
// function(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ)
viewMatrix.setLookAt(0, 100, 0, 0, 0, 0, 0, 0, -1); //Fra oven! Opp = -Z
// [cuon-utils]: Matrix4.prototype.setLookAt =
// function(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ)
viewMatrix.setLookAt(0, 100, 0, 0, 0, 0, 0, 0, -1); //Fra oven! Opp = -Z
// Projeksjonsmatrisa:
// [cuon-utils]: Matrix4.prototype.setPerspective =
// function(fovy, aspect, near, far)
// function(fovy, aspect, near, far)
projectionMatrix.setPerspective(45, canvas.width / canvas.height, 1, 100);
//M=I*T*R
modelMatrix.setIdentity();
modelMatrix.translate(0, 0, 20); //Flytt langs z-aksen.
modelMatrix.rotate(60, 0, 1, 0); //Roterer om y-aksen.
// Slår sammen modell & view til modelview-matrise:
modelviewMatrix = viewMatrix.multiply(modelMatrix); // NB! rekkefølge!
. . .
. . .
Parametrene til viewmatrisa settes slik; [eyeX, eyeY, eyeZ] = [0,100,0] og [centerX, centerY, centerZ] = [0,0,0] betyr at kameraet står i positiv y og ser ned mot origo. Opp-retninga på kameraet er satt til [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 den transleres 20 enheter langs z-aksen.
Merk at måten vi ser på koordinatsystemene tilsvarer det (Anyuru, A. 2012) betegner som «Using a Grand, Fixed Coordinate System».
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.
I høyre del av figuren (b) utføres først en translasjon og deretter en rotasjon; M = I * (R * T). Dette løses i WebGL ved å endre koden over slik:
. . .
//M=I*R*T
//M=I*R*T
modelMatrix.setIdentity();
modelMatrix.rotate(60, 0, 1, 0); //Roterer om y-aksen.
modelMatrix.translate(0, 0, 20); //Flytt langs z-aksen.
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.
Se eksempel /del6/CompositeAnim2.html/.js. Figuren under viser resultatet for de to tilfellene:
Rekkefølgen matrisene slås sammen på er altså avgjørende for resultatet.
Kombinasjon av egen rotasjon og «orbit»
Dersom rektanglet 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 |
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:
//M=I*O*R, der O=R*T
modelMatrix.setIdentity();
//Baneberegning / «Orbit»:
//Baneberegning / «Orbit»:
modelMatrix.rotate(orbAngle, 0, 1, 0); //Går i "bane" om y-aksen.
modelMatrix.translate(0, 0, orbTrans); //Flytt langs z-aksen.
//Roter først om «egen» y-akse:
modelMatrix.rotate(rotAngle, 0, 1, 0); //Roterer om origo. Y-aksen.
der orbTrans (orb=orbit, «omløpsbane») f.eks. settes lik 20 og bestemmer hvor 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 ved at det flyttes 20 enheter på z-aksen og, vha. orbAngle, deretter «går i bane»/roterer om 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.
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:
//M=I*O*R*S, der O=R*T
modelMatrix.setIdentity();
//Baneberegning / «Orbit»:
modelMatrix.rotate(orbAngle, 0, 1, 0); //Går i "bane" om y-aksen.
modelMatrix.translate(0, 0, orbTrans); //Flytt langs z-aksen.
//Roter om «egen» y-akse:
modelMatrix.rotate(rotAngle, 0, 1, 0); //Roterer om origo. Y-aksen.
//Skalerer først. Her; nedskalerer 50% i alle akser:
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.
Skalering, rotasjon om y-aksen, «orbit» (translasjon og en rotasjon) og translasjon (langs X). M = I*T*O*R*S |
WebGL-kode:
//M=I*T*O*R*S, der O=R*T
modelMatrix.setIdentity();
//Translerer hele modellen:
modelMatrix.translate(25,0,0);
//Baneberegning / «Orbit»:
modelMatrix.rotate(orbAngle, 0, 1, 0); //Går i "bane" om y-aksen.
modelMatrix.translate(0, 0, orbTrans); //Flytt langs z-aksen.
//Roter først om «egen» y-akse:
modelMatrix.rotate(rotAngle, 0, 1, 0); //Roterer om origo. Y-aksen.
//Skalerer likt i alle akser:
modelMatrix.scale(0.5, 0.5, 0.5);
Her er modellen flyttett 25 enheter langs x-aksen vha. translate(25,0,0) til slutt i «transformasjonsonssekvensen».
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.
Se (Angel, E. et.al., 2015, side 187->) og (Cawood, S., McGee. P., 2009) for mer informasjon.
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 φ).
Baneberegning; Jord-Måne
Anta at vi skal tegne en roterende jordklode (som roterer rundt egen akse) med en måne som roterer rundt jorda i tillegg til at den roterer rundt egen akse.
Man kan bruke samme modell (f.eks. en kule) for å tegne månen (evt. flere måner og satellitter). Definisjonen av modellen gjøres en gang men tegnes flere ganger, hver gang med ny og endret modellmatrise.
Her vil vi typisk starte med å tegne jorda og gjøre en enkel rotasjon rundt Y-aksen. Dette innebærer å lage en enkel rotasjonsmatrise og bruke denne til å tegne jorda.
Før månen tegnes må modellen nedskalerer noe samt rotere rundt egen akse i tillegg må man plassere den ”i bane” utenfor jorda og gjøre en ny rotasjon som vist i figuren. Dette innebærer å lage en ny modellmatrise som inneholder en rotasjon om y-aksen + en «orbit». Tilsvarende gjøres for ev. satellitter e.l.
Forslag til løsning:
Før jorda tegnes settes modellmatrisa til jorda lik en rotasjonsmatrise (y-aksen). Siden animate() og draw() metodene kjører kontinuerlig genereres det for hver runde en ny rotasjonsmatrise vha. en yRot-variabel.
Rotasjonshastigheten bestemmes av denne variabelen. Dette gjør at jorda kontinuerlig roterer om y-aksen. Variabelen yRot varierer mellom 0-360 grader.
Videre lages en ny modellmatrise for å kunne tegne månen. Månen må både nedskalere (månen er mindre enn jorda) , roteres om egen akse og sørger for at den går i bane rundt jorda. NB! Her er det viktig å slå sammen matrisene i korrekt rekkefølge.
Deretter tegnes, vha. samme buffer, månen.
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