Velkommen til den andre delen av vår 3D Graphics Engine-serie! Denne gangen skal vi snakke om lineære transformasjoner, som vil la oss endre egenskaper som rotasjon og skalering av vektorer våre, og se på hvordan de skal brukes til klassene vi allerede har bygget.
Hvis du ikke allerede har lest den første delen av denne serien, foreslår jeg at du gjør det nå. Bare i tilfelle du ikke husker, her er en rask oversikt over hva vi opprettet forrige gang:
Point Class Variables: num tuple [3]; // (x, y, z) Operatører: Point AddVectorToPoint (Vector); Punkt SubtraherVectorFromPoint (Vector); SubtractPointFromPoint (punkt); Funksjoner: // tegne et punkt på sin posisjon tuple med din favoritt grafikk API drawPoint; Vector Class Variables: num tuple [3]; // (x, y, z) Operatører: Vector AddVectorToVector (Vector); Vector SubtractVectorFromVector (Vector);
Disse to klassene vil være grunnlaget for hele vår grafikkmotor, hvor den første representerer et punkt (en fysisk plassering i rommet ditt) og den andre representerer en vektor (mellomrom / bevegelse mellom to punkter).
For vår diskusjon om lineære transformasjoner, bør du gjøre en liten forandring til Point-klassen: i stedet for å sende data til en konsolllinje som før, bruk din favoritt grafikk-API og få funksjonen til å tegne det nåværende punktet på skjermen.
Bare en advarsel: Linjære transformasjonslikninger ser mye verre ut enn de egentlig er. Det vil være noen trigonometri involvert, men du trenger ikke å faktisk vite hvordan å gjøre det trigonometri: Jeg vil forklare hva du må gi hver funksjon og hva du vil komme deg ut, og for mellomliggende ting kan du bare bruke noen kalkulator eller matematikkbibliotek som du kanskje har.
Tips: Hvis du vil ha en bedre forståelse av de indre bevegelsene i disse ligningene, bør du se på denne videoen og lese denne PDF-filen.Alle lineære transformasjoner tar dette skjemaet:
\ [B = F (A) \]
Dette sier at hvis du har en lineær transformasjonsfunksjon \ (F () \), og din innføring er vektoren \ (A \), blir utgangen din vektoren \ (B \).
Hver av disse delene - de to vektorene og funksjonen - kan representeres som en matrise: vektoren \ (B \) som en 1x3-matrise, vektoren \ (A \) som en annen 1x3-matrise, og den lineære transformasjonen \ \) som en 3x3 matrise (a transformasjonsmatrise).
Dette betyr at når du utvider ligningen, ser det slik ut:
\ [
\ Begynne bmatrix
b_ 0 \\
b_ 1 \\
flaten b 2
\ End bmatrix
=
\ Begynne bmatrix
f_ 00 & f_ 01 & f_ 02 \\
f_ 10 & f_ 11 & f_ 12 \\
f_ 20 & f_ 21 & f_ 22
\ End bmatrix
\ Begynne bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ End bmatrix
\]
Hvis du noen gang har tatt en klasse i trigonometri eller lineær algebra, begynner du sannsynligvis å huske marerittet som var matematikkmatematikk. Heldigvis er det en enklere måte å skrive ut denne ligningen for å få mest mulig ut av trøbbelene ut av det. Det ser slik ut:
\ [
\ Begynne bmatrix
flaten b 0 \\
flaten b 1 \\
flaten b 2
\ End bmatrix
=
\ Begynne bmatrix
f_ 00 a_ 0 + f_ 01 a_ 1 + f_ 02 a_ 2 \\
f_ 10 a_ 0 + f_ 11 a_ 1 + f_ 12 a_ 2 \\
f_ 20 a_ 0 + f_ 21 a_ 1 + f_ 22 a_ 2 \\
\ End bmatrix
\]
Imidlertid kan disse ligningene endres ved å ha en andre inngang, slik som ved rotasjoner, hvor en vektor og dens rotasjonsmengde må begge gis. La oss se på hvordan rotasjoner fungerer.
En rotasjon er per definisjon en sirkulær bevegelse av en gjenstand rundt et rotasjonspunkt. Rotasjonspunktet for vårt rom kan være en av tre muligheter: enten XY-flyet, XZ-flyet eller YZ-flyet (hvor hvert fly består av to av våre grunnvektorer som vi diskuterte i første del av serien ).
Våre tre rotasjonspunkter betyr at vi har tre separate rotasjonsmatriser, som følger:
XY rotasjonsmatrise:
\ [
\ Begynne bmatrix
cos \ theta & -sin \ theta & 0 \\
synd \ theta & cos \ theta & 0 \\
0 & 0 & 1 \\
\ End bmatrix
\]
XZ rotasjonsmatrise:
\ [
\ Begynne bmatrix
cos \ theta & 0 & sin \ theta \\
0 & 1 & 0 \\
-synden \ theta & 0 & cos \ theta
\ End bmatrix
\]
YZ rotasjonsmatrise:
\ [
\ Begynne bmatrix
1 & 0 & 0 \\
0 & cos \ theta & -sin \ theta \\
0 & sin \ theta & cos \ theta
\ End bmatrix
\]
Så for å rotere et punkt \ (A \) rundt XY-planet med 90 grader (\ (\ pi / 2 \) radianer - de fleste mattebiblioteker har en funksjon for å konvertere grader til radianer), vil du følge disse trinnene:
\ [
\ Begin innrettet
\ Begynne bmatrix
flaten b 0 \\
flaten b 1 \\
flaten b 2
\ End bmatrix
& =
\ Begynne bmatrix
cos \ frac \ pi 2 & -sin \ frac \ pi 2 og 0 \\
synd \ frac \ pi 2 og cos \ frac \ pi 2 og 0 \\
0 & 0 & 1
\ End bmatrix
\ Begynne bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ End bmatrix \\
& =
\ Begynne bmatrix
cos \ frac \ pi 2 a_ 0 + -sin \ frac \ pi 2 a_ 1 + 0a_ 2 \\
synd \ frac \ pi 2 a_ 0 + cos \ frac \ pi 2 a_ 1 + 0a_ 2 \\
0a_ 0 + 0a_ 1 + 1a_ 2
\ End bmatrix \\
& =
\ Begynne bmatrix
0a_ 0 + -1a_ 1 + 0a_ 2 \\
1a_ 0 + 0a_ 1 + 0a_ 2 \\
0a_ 0 + 0a_ 1 + 1a_ 2
\ End bmatrix \\
& =
\ Begynne bmatrix
-a_ 1 \\
a_ 0 \\
a_ 2
\ End bmatrix
\ End innrettet
\]
Så hvis ditt opprinnelige punkt \ (A \) var \ ((3,4,5) \), vil utgangspunktet \ (B \) være \ ((- 4,3,5) \).
Som en øvelse, prøv å lage tre nye funksjoner for Vector
klasse. Man bør rotere vektoren rundt XY-flyet, en rundt YZ-flyet og en rundt XZ-flyet. Dine funksjoner bør motta ønsket antall grader for rotasjon som en inngang, og returnere en vektor som en utgang.
Den grunnleggende strømmen av dine funksjoner bør være som følger:
Skalering er en transformasjon som enten forstørrer eller reduserer et objekt basert på en angitt skala.
Å utføre denne transformasjonen er ganske enkel (i hvert fall i forhold til rotasjoner). En skaleringstransformasjon krever to innganger: an inngangsvektor og a skalering 3-tuple, som definerer hvordan inngangsvektoren skal skaleres i forhold til hvert av romets basisakser.
For eksempel representerer skaleringen i x-aksen, i scaling tuplen \ ((s_ 0, s_ 1, s_ 2) \), \ (s_ 0 \) \) langs Y-aksen, og \ (s_ 2 \) langs Z-aksen.
Skaleringstransformasjonsmatrisen er som følger (hvor \ (s_ 0 \), \ (s_ 1 \) og \ (s_ 2 \) er elementene i skalerings-3-tupelen):
\ [
\ Begynne bmatrix
s0 & 0 & 0 \\
0 & s1 & 0 \\
0 & 0 & s2
\ End bmatrix
\]
For å gjøre inngangsvektoren A \ ((a_ 0, a_ 1, a_ 2) \) dobbelt så stor langs X-aksen (det vil si ved å bruke en skalering 3-tuple \ (S = ( 2, 1, 1) \)), vil mattealen se slik ut:
\ [
\ Begin innrettet
\ Begynne bmatrix
flaten b 0 \\
flaten b 1 \\
flaten b 2
\ End bmatrix
& =
\ Begynne bmatrix
s0 & 0 & 0 \\
0 & s1 & 0 \\
0 & 0 & s2
\ End bmatrix
\ Begynne bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ End bmatrix \\
& =
\ Begynne bmatrix
2 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\ End bmatrix
\ Begynne bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ End bmatrix \\
& =
\ Begynne bmatrix
2a_ 0 + 0a_ 1 + 0a_ 2 \\
0a_ 0 + 1a_ 1 + 0a_ 2 \\
0a_ 0 + 0a_ 1 + 1a_ 2
\ End bmatrix \\
& =
\ Begynne bmatrix
2a_ 0 \\
a_ 1 \\
a_ 2
\ End bmatrix
\ End innrettet
\]
Så hvis gitt inngangsvektoren \ (A = (3,4,0) \), vil din utgangsvektor \ (B \) være \ ((6,4,0) \).
Som en annen øvelse, legg til en ny funksjon i vektorklassen for skalering. Denne nye funksjonen skal ta inn en skalering 3-tuple og returnere en utgangsvektor.
Den grunnleggende strømmen av dine funksjoner bør være som følger:
y0 = x0 * s0; y1 = x1 * s1; y2 = x2 * s2
).Nå som du har lineære transformasjoner under beltet ditt, la oss bygge et raskt lite program for å vise frem dine nye ferdigheter. Vi skal lage et program som trekker en gruppe poeng til skjermen, og lar oss da endre dem som helhet ved å utføre lineære transformasjoner på dem.
Før du starter, vil vi også legge til en annen funksjon til vår Punkt
klasse. Dette vil bli kalt setPointToPoint ()
, og vil ganske enkelt sette det nåværende punktets posisjon til det punktet som sendes til det. Det vil motta et poeng som en inngang, og vil ikke returnere noe.
Her er noen raske spesifikasjoner for vårt program:
Våre nåværende klasser:
Point Class Variables: num tuple [3]; // (x, y, z) Operatører: Point AddVectorToPoint (Vector); Punkt SubtraherVectorFromPoint (Vector); Vector SubtractPointFromPoint (punkt); // angir posisjonen til det nåværende punktet til det innmatede punktet Null SetPointToPoint (Point); Funksjoner: // tegne et punkt på sin posisjon tuple med din favoritt grafikk API drawPoint; Vector Class Variables: num tuple [3]; // (x, y, z) Operatører: Vector AddVectorToVector (Vector); Vector SubtractVectorFromVector (Vector); Vector RotateXY (grader); Vector RotateYZ (grader); Vector RotateXZ (grader); Vector Scale (s0, s1, s2);
Med disse spesifikasjonene, la oss se på hva vår kode kan være:
main // oppsett for din favoritt grafikk-API her // oppsett for tastaturinngang (kan ikke være nødvendig) her // opprett en rekke med 100 poeng Point Array pointArray [100]; for (int x = 0; x < pointArray.length; x++) //Set its location to a random point on the screen pointArray[x].tuple = [random(0,screenWidth), random(0,screenHeight), random(0,desiredDepth)); //this function clears the screen and then draws all of the points function redrawScreen() //use your Graphics API's clear screen function ClearTheScreen(); for (int x = 0; x < pointArray.length; x++) //draw the current point to the screen pointArray[x].drawPoint(); // while the escape is not being pressed, carry out the main loop while (esc != pressed) // perform various actions based on which key is pressed if (key('d') == pressed) redrawScreen(); if (key('a') == pressed) //create the space's origin as a point Point origin = new Point(0,0,0); Vector tempVector; for (int x = 0; x < pointArray.length; x++) //store the current vector address for the point, and set the point tempVector = pointArray[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added pointArray[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location pointArray[x].addVectorToPoint(tempVector.scale(0.5,0.5,0.5)); redrawScreen(); if(key('s') == pressed) //create the space's origin as a point Point origin = new Point(0,0,0); Vector tempVector; for (int x = 0; x < pointArray.length; x++) //store the current vector address for the point, and set the point tempVector = pointArray[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added pointArray[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location pointArray[x].addVectorToPoint(tempVector.scale(2.0,2.0,2.0)); redrawScreen(); if(key('r') == pressed) //create the space's origin as a point Point origin = new Point(0,0,0); Vector tempVector; for (int x = 0; x < pointArray.length; x++) //store the current vector address for the point, and set the point tempVector = pointArray[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added pointArray[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location pointArray[x].addVectorToPoint(tempVector.rotateXY(15)); redrawScreen();
Nå bør du ha et kult, lite program for å vise frem alle dine nye teknikker! Du kan sjekke ut min enkle demo her.
Selv om vi ikke sikkert dekker alle mulige lineære transformasjoner som er tilgjengelige, begynner vår mikromotor å ta form.
Som alltid er det noen ting som har gått ut av vår motor for enkelhet (nemlig skjæring og refleksjoner i denne delen). Hvis du vil finne ut mer om disse to typene lineære transformasjoner, kan du finne ut mer om dem på Wikipedia og dets relaterte lenker.
I neste del av denne serien skal vi dekke forskjellige visningsrom og hvordan vi kan kaste objekter som er utenfor vårt syn.
Hvis du trenger ekstra hjelp, gå over til Envato Studio, hvor du kan finne mange fantastiske 3D Design & Modeling-tjenester. Disse erfarne leverandørene kan hjelpe deg med et bredt spekter av forskjellige prosjekter, så bare bla gjennom leverandørene, les anmeldelser og rangeringer, og velg riktig person for å hjelpe deg.
3D Design & Modeling-tjenester på Envato Studio