Costruiamo un motore grafico 3D Trasformazioni lineari

Benvenuti nella seconda parte della nostra serie di 3D Graphics Engine! Questa volta parleremo di trasformazioni lineari, che ci permetterà di modificare proprietà come la rotazione e il ridimensionamento dei nostri vettori, e di vedere come applicarli alle classi che abbiamo già costruito.

Se non hai già letto la prima parte di questa serie, ti suggerisco di farlo ora. Nel caso in cui non ricordi, ecco un breve riepilogo di ciò che abbiamo creato l'ultima volta:

Point Class Variables: num tuple [3]; // (x, y, z) Operatori: Punto AddVectorToPoint (Vettore); Point SubtractVectorFromPoint (Vector); SubtractPointFromPoint (Point); Funzioni: // disegna un punto nella sua posizione tupla con il tuo drawPoint grafico API preferito;  Classe vettoriale Variabili: num tuple [3]; // (x, y, z) Operatori: Vector AddVectorToVector (Vector); Vector SubtractVectorFromVector (Vector); 

Queste due classi saranno la base del nostro intero motore grafico, dove il primo rappresenta un punto (una posizione fisica all'interno del tuo spazio) e il secondo rappresenta un vettore (lo spazio / movimento tra due punti).

Per la nostra discussione sulle trasformazioni lineari, dovresti fare una piccola modifica alla classe Point: invece di esportare i dati su una linea di console come prima, usa la tua API grafica preferita e la funzione disegna il punto corrente sullo schermo.


Fondamenti di trasformazioni lineari

Solo un avvertimento: le equazioni di trasformazione lineare sembrano molto peggiori di quanto non siano in realtà. Ci sarà un po 'di trigonometria, ma non devi saperlo Come per fare quella trigonometria: ti spiegherò cosa devi dare a ciascuna funzione e cosa uscirai, e per le cose intermedie puoi semplicemente usare qualsiasi calcolatrice o libreria matematica che potresti avere.

Mancia: Se vuoi avere una migliore comprensione del funzionamento interno di queste equazioni, allora dovresti guardare questo video e leggere questo PDF.

Tutte le trasformazioni lineari prendono questa forma:

\ [B = F (A) \]

Questo indica che se hai una funzione di trasformazione lineare \ (F () \), e il tuo input è il vettore \ (A \), allora il tuo output sarà il vettore \ (B \).

Ognuno di questi pezzi - i due vettori e la funzione - può essere rappresentato come una matrice: il vettore \ (B \) come una matrice 1x3, il vettore \ (A \) come un'altra matrice 1x3 e la trasformazione lineare \ (F \) come matrice 3x3 (a matrice di trasformazione).

Ciò significa che, quando espandi l'equazione, assomiglia a questo:

\ [
\ Begin bmatrix
b_ 0 \\
b_ 1 \\
b_ 2
\ End bmatrix
=
\ Begin bmatrix
f_ 00 & f_ 01 & f_ 02 \\
f_ 10 & f_ 11 & f_ 12 \\
f_ 20 & f_ 21 & f_ 22
\ End bmatrix
\ Begin bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ End bmatrix
\]

Se hai mai preso una lezione di trigonometria o di algebra lineare, probabilmente stai iniziando a ricordare l'incubo che era la matematica delle matrici. Fortunatamente, c'è un modo più semplice per scrivere questa equazione per prendere la maggior parte del problema. Sembra questo:

\ [
\ Begin bmatrix
b_ 0 \\
b_ 1 \\
b_ 2
\ End bmatrix
=
\ Begin 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
\]

Tuttavia, queste equazioni possono essere alterate avendo un secondo input, come nel caso delle rotazioni, dove deve essere dato un vettore e la sua quantità di rotazione. Diamo un'occhiata a come funzionano le rotazioni.


rotazioni

Una rotazione è, per definizione, un movimento circolare di un oggetto attorno a un punto di rotazione. Il punto di rotazione del nostro spazio può essere una delle tre possibilità: o il piano XY, il piano XZ o il piano YZ (dove ciascun piano è costituito da due dei nostri vettori di base che abbiamo discusso nella prima parte della serie ).

I nostri tre punti di rotazione significano che abbiamo tre matrici di rotazione separate, come segue:

Matrice di rotazione XY:
\ [
\ Begin bmatrix
cos \ theta & -sin \ theta & 0 \\
sin \ theta & cos \ theta & 0 \\
0 & 0 & 1 \\
\ End bmatrix
\]

Matrice di rotazione XZ:

\ [
\ Begin bmatrix
cos \ theta & 0 & sin \ theta \\
0 & 1 & 0 \\
-sin \ theta & 0 & cos \ theta
\ End bmatrix
\]

Matrice di rotazione YZ:

\ [
\ Begin bmatrix
1 & 0 & 0 \\
0 & cos \ theta & -sin \ theta \\
0 e sin \ theta e cos \ theta
\ End bmatrix
\]

Quindi per ruotare un punto \ (A \) attorno al piano XY di 90 gradi (\ (\ pi / 2 \) radianti - la maggior parte delle librerie matematiche ha una funzione per convertire i gradi in radianti), segui questi passaggi:

\ [
\ Begin allineati
\ Begin bmatrix
b_ 0 \\
b_ 1 \\
b_ 2
\ End bmatrix
& =
\ Begin bmatrix
cos \ frac \ pi 2 & -sin \ frac \ pi 2 e 0 \\
sin \ frac \ pi 2 e cos \ frac \ pi 2 e 0 \\
0 e 0 e 1
\ End bmatrix
\ Begin bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ End \\ bmatrix
& =
\ Begin bmatrix
cos \ frac \ pi 2 a_ 0 + -sin \ frac \ pi 2 a_ 1 + 0a_ 2 \\
sin \ frac \ pi 2 a_ 0 + cos \ frac \ pi 2 a_ 1 + 0a_ 2 \\
0a_ 0 + 0a_ 1 + 1a_ 2
\ End \\ bmatrix
& =
\ Begin bmatrix
0a_ 0 + -1a_ 1 + 0a_ 2 \\
1a_ 0 + 0a_ 1 + 0a_ 2 \\
0a_ 0 + 0a_ 1 + 1a_ 2
\ End \\ bmatrix
& =
\ Begin bmatrix
-a_ 1 \\
a_ 0 \\
a_ 2
\ End bmatrix
\ End allineato
\]

Quindi se il tuo punto iniziale \ (A \) era \ ((3,4,5) \), allora il tuo punto di uscita \ (B \) sarebbe \ ((- - 4,3,5) \).

Esercizio: funzioni di rotazione

Come esercizio, prova a creare tre nuove funzioni per il Vettore classe. Uno dovrebbe ruotare il vettore attorno al piano XY, uno attorno al piano YZ e uno attorno al piano XZ. Le tue funzioni dovrebbero ricevere la quantità desiderata di gradi per la rotazione come input e restituire un vettore come output.

Il flusso di base delle tue funzioni dovrebbe essere il seguente:

  1. Crea un vettore di output.
  2. Converti l'input in gradi in forma radiante.
  3. Risolvi per ogni pezzo della tupla dei vettori di uscita usando le equazioni sopra.
  4. Restituisce il vettore di output.

scalata

Lo scaling è una trasformazione che ingrandisce o diminuisce un oggetto in base a una scala impostata.

L'esecuzione di questa trasformazione è abbastanza semplice (almeno rispetto alle rotazioni). Una trasformazione di ridimensionamento richiede due input: an vettore di input e a scalare 3-tupla, che definisce il modo in cui il vettore di input deve essere ridimensionato rispetto a ciascuno degli assi di base dello spazio.  

Ad esempio, nella tupla di ridimensionamento \ ((s_ 0, s_ 1, s_ 2) \), \ (s_ 0 \) rappresenta il ridimensionamento lungo l'asse X, \ (s_ 1 \) lungo l'asse Y e \ (s_ 2 \) lungo l'asse Z..

La matrice di trasformazione di ridimensionamento è la seguente (dove \ (s_ 0 \), \ (s_ 1 \) e \ (s_ 2 \) sono gli elementi della tupla a tre dimensioni):

\ [
\ Begin bmatrix
s0 & 0 & 0 \\
0 e s1 e 0 \\
0 & 0 e s2
\ End bmatrix
\]

Per rendere il vettore di input A \ ((a_ 0, a_ 1, a_ 2) \) due volte più grande lungo l'asse X (ovvero, utilizzando una tupla a 3 scaling \ (S = ( 2, 1, 1) \)), la matematica sarebbe simile a questa:

\ [
\ Begin allineati
\ Begin bmatrix
b_ 0 \\
b_ 1 \\
b_ 2
\ End bmatrix
& =
\ Begin bmatrix
s0 & 0 & 0 \\
0 e s1 e 0 \\
0 & 0 e s2
\ End bmatrix
\ Begin bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ End \\ bmatrix
& =
\ Begin bmatrix
2 & 0 & 0 \\
0 & 1 & 0 \\
0 e 0 e 1
\ End bmatrix
\ Begin bmatrix
a_ 0 \\
a_ 1 \\
a_ 2
\ End \\ bmatrix
& =
\ Begin bmatrix
2a_ 0 + 0a_ 1 + 0a_ 2 \\
0a_ 0 + 1a_ 1 + 0a_ 2 \\
0a_ 0 + 0a_ 1 + 1a_ 2
\ End \\ bmatrix
& =
\ Begin bmatrix
2a_ 0 \\
a_ 1 \\
a_ 2
\ End bmatrix
\ End allineato
\]

Quindi se viene dato il vettore di input \ (A = (3,4,0) \), allora il vettore di output \ (B \) sarebbe \ ((6,4,0) \).

Esercizio: funzioni di ridimensionamento

Come altro esercizio, aggiungi una nuova funzione alla tua classe vettoriale per il ridimensionamento. Questa nuova funzione dovrebbe incorporare una tupla in 3 livelli e restituire un vettore di output.  

Il flusso di base delle tue funzioni dovrebbe essere il seguente:

  1. Crea un vettore di output.
  2. Risolvi per ogni pezzo della tupla dei vettori di uscita usando l'equazione sopra (che può essere semplificata a) y0 = x0 * s0; y1 = x1 * s1; y2 = x2 * s2).
  3. Restituisce il vettore di output.

Costruiamo qualcosa!

Ora che hai trasformazioni lineari sotto la cintura, costruiamo un breve programma per mostrare le tue nuove abilità. Creeremo un programma che disegna un gruppo di punti sullo schermo e quindi ci consente di modificarli nel loro complesso eseguendo trasformazioni lineari su di essi.  

Prima di iniziare, vorremmo anche aggiungere un'altra funzione al nostro Punto classe. Questo sarà chiamato setPointToPoint (), e semplicemente imposterà la posizione del punto corrente a quella del punto che gli viene passato. Riceverà un punto come input e non restituirà nulla.

Ecco alcune specifiche rapide per il nostro programma:

  • Il programma terrà 100 punti in un array.
  • Quando il D premuto il tasto, il programma pulirà la schermata corrente e ridisegnerà i punti.
  • Quando il UN premuto il tasto, il programma scalerà tutte le posizioni dei punti di 0,5.
  • Quando il S premuto il tasto, il programma scalerà tutte le posizioni dei punti per 2.0.
  • Quando il R premuto il tasto, il programma ruoterà la posizione di tutti i punti di 15 gradi sul piano XY.
  • Quando il Fuga premuto il tasto, il programma uscirà (a meno che non lo stiate facendo con JavaScript o un altro linguaggio orientato al web).

Le nostre classi attuali:

Point Class Variables: num tuple [3]; // (x, y, z) Operatori: Punto AddVectorToPoint (Vettore); Point SubtractVectorFromPoint (Vector); Vector SubtractPointFromPoint (Point); // imposta la posizione del punto corrente su quella del punto immesso Null SetPointToPoint (Point); Funzioni: // disegna un punto nella sua posizione tupla con il tuo drawPoint grafico API preferito;  Classe vettoriale Variabili: num tuple [3]; // (x, y, z) Operatori: Vector AddVectorToVector (Vector); Vector SubtractVectorFromVector (Vector); Vector RotateXY (gradi); Vector RotateYZ (gradi); Vector RotateXZ (gradi); Scala vettoriale (s0, s1, s2); 

Con quelle specifiche, diamo un'occhiata a cosa potrebbe essere il nostro codice:

main // setup per la tua API grafica preferita qui // setup per input da tastiera (potrebbe non essere necessario) qui // creare una matrice di 100 punti Point Array pointArray [100]; per (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();   

Ora dovresti avere un piccolo programma per mostrare tutte le tue nuove tecniche! Puoi controllare la mia semplice demo qui.


Conclusione

Anche se certamente non abbiamo coperto tutte le possibili trasformazioni lineari disponibili, il nostro micromotore sta iniziando a prendere forma. 

Come sempre, ci sono alcune cose che sono state lasciate fuori dal nostro motore per semplicità (in particolare tosatura e riflessi in questa parte). Se vuoi saperne di più su questi due tipi di trasformazioni lineari, puoi saperne di più su di loro su Wikipedia e sui relativi collegamenti.

Nella parte successiva di questa serie, copriremo diversi spazi di visualizzazione e come selezionare gli oggetti che sono al di fuori della nostra vista.

Se hai bisogno di ulteriore aiuto, vai su Envato Studio, dove puoi trovare moltissimi fantastici servizi di design e modellazione 3D. Questi fornitori esperti possono aiutarti con una vasta gamma di progetti diversi, quindi basta consultare i fornitori, leggere le recensioni e le valutazioni e scegliere la persona giusta per aiutarti. 

Servizi di progettazione e modellazione 3D su Envato Studio