Costruiamo un motore grafico 3D spazi e abbattimento

Benvenuto! Questa è la terza parte della nostra serie sui motori di grafica 3D. Se sei arrivato così lontano nella serie, sarai felice di sapere che questo pezzo sarà molto più leggero sull'aspetto matematico dei motori 3D, e invece si concentrerà su aspetti più pratici - in particolare, aggiungendo una fotocamera e un sistema di rendering di base.

Mancia: Se non hai ancora letto le prime due parti, ti consiglio vivamente di farlo prima di continuare.

Puoi inoltre ottenere ulteriore assistenza su Envato Studio, dove puoi scegliere tra una vasta gamma di servizi di progettazione e modellazione 3D di alta qualità da fornitori esperti. 

Servizi di progettazione e modellazione 3D su Envato Studio

Ricapitolare

Prima di tutto, diamo un'occhiata alle classi che abbiamo creato finora:

Point Class Variables: num tuple [3]; // (x, y, z) Operatori: Punto AddVectorToPoint (Vettore); Point SubtractVectorFromPoint (Vector); Vector SubtractPointFromPoint (Point); Null SetPointToPoint (Point); // sposta il punto nel punto specificato Funzioni: drawPoint; // disegna un punto nella sua posizione tuple Vector Class Variables: 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); // parametri: ridimensionamento lungo ciascun asse

L'utilizzo di queste due classi da solo si è rivelato un po 'disordinato fino ad ora, e disegnando ogni possibile punto può drenare la memoria del sistema abbastanza velocemente. Per risolvere questi problemi, introdurremo una nuova classe nel nostro motore di gioco: il telecamera.

La nostra videocamera sarà dove tutto il nostro rendering avverrà, in esclusiva; sta per cogliere tutti i nostri oggetti sullo schermo e gestirà anche un elenco di tutti i nostri punti.

Ma prima di arrivare a tutto questo, dobbiamo prima parlare un po 'di abbattimento.


Culling di Londra

L'abbattimento, per definizione, è la selezione di oggetti da un gruppo più ampio di oggetti. Per il nostro motore di gioco, la piccola selezione che prendiamo saranno i punti che vogliamo disegnare sullo schermo. Il gruppo più grande di oggetti sarà ogni punto che esiste.

In questo modo si riduce drasticamente il consumo del motore nella memoria di un sistema, disegnando solo ciò che un giocatore è effettivamente in grado di vedere, piuttosto che un valore di punti dell'intero mondo. Nel nostro motore, lo faremo impostando i parametri per a guarda lo spazio.

Il nostro spazio di visualizzazione sarà definito su tutti e tre gli assi tradizionali: x, yez. La sua definizione x consisterà in tutto ciò che si trova tra i limiti sinistro e destro della finestra, la sua definizione y sarà costituita da tutto ciò che si trova tra i limiti superiore e inferiore della finestra e la sua definizione z sarà compresa tra 0 (dove è impostata la fotocamera) e la distanza di visione del nostro giocatore (per la nostra dimostrazione, useremo un valore arbitrario di 100).

Prima di disegnare un punto, la nostra classe di fotocamere controllerà se quel punto si trova nel nostro spazio di visualizzazione. Se lo fa, allora il punto sarà disegnato; altrimenti, non lo farà.


Possiamo avere alcune macchine fotografiche qui?

Con questa comprensione di base dell'abbattimento, possiamo dedurre che la nostra classe apparirà così, finora:

Camera Class Vars: int minX, maxX; // limiti minimi e massimi di X int minY, maxY; // limiti minimi e massimi di Y int minZ, maxZ; // limiti minimi e massimi di Z

Avremo anche la nostra fotocamera per gestire tutto il rendering per il nostro motore. A seconda del motore, scoprirai che i renderer sono spesso separati dai sistemi di telecamere. Questo è tipicamente fatto per mantenere i sistemi incapsulati bene, poiché - a seconda della portata del tuo motore - i due potrebbero diventare abbastanza disordinati se tenuti insieme. Per i nostri scopi, tuttavia, sarà più semplice trattarli come uno.

Per prima cosa, vorremmo una funzione che possa essere chiamata esternamente dalla classe che disegnerà la scena. Questa funzione scorrerà ciclicamente tra i punti esistenti, li confronta con i parametri di selezione della videocamera e li disegna se necessario.


Fonte: http://en.wikipedia.org/wiki/File:ViewFrustum.svg

Mancia: Se volevi separare il tuo sistema fotografico dal tuo renderer, potresti semplicemente creare un Renderer classe, fare in modo che il sistema della videocamera scelga i punti, memorizzi quelli da disegnare in un array, quindi invia tale array al disegnare() funzione del tuo renderer.


Gestione dei punti

L'ultimo pezzo della nostra classe di fotocamere sarà il suo sistema di gestione dei punti. A seconda del linguaggio di programmazione che stai usando, questo potrebbe essere semplicemente un array di tutti gli oggetti che possono essere disegnati (manterremo più di un semplice punto nelle parti successive). In alternativa, potrebbe essere necessario utilizzare la classe genitore dell'oggetto predefinito della lingua. Se sei super sfortunato, dovrai creare la tua classe genitore di oggetti e avere ciascuna classe drawable (fino ad ora solo punti) essere un figlio di quella classe.

Dopo averlo aggiunto alla classe, una panoramica di base della nostra fotocamera sarebbe simile a questa:

Camera Class Vars: int minX, maxX; // limiti minimi e massimi di X int minY, maxY; // limiti minimi e massimi di Y int minZ, maxZ; // limiti minimi e massimi degli oggetti array ZInWorld; // una matrice di tutti gli oggetti esistenti Funzioni: null drawScene (); // disegna tutti gli oggetti necessari sullo schermo, non restituisce nulla

Con queste aggiunte, miglioriamo un po 'il programma che abbiamo realizzato l'ultima volta.


Cose più grandi e migliori

Creeremo un semplice programma di disegno a punti, con il programma di esempio che abbiamo creato l'ultima volta come punto di partenza.

In questa iterazione del programma, aggiungeremo l'uso della nostra nuova classe di telecamere. Quando il D premuto il tasto, il programma ridisegna lo schermo senza selezionare, visualizzando il numero di oggetti che sono stati renderizzati nell'angolo in alto a destra dello schermo. Quando il C premuto il tasto, il programma ridisegna lo schermo con il culling, visualizzando anche il numero di oggetti renderizzati.

Diamo un'occhiata al codice:

main // setup per la tua API grafica preferita qui // setup per l'input da tastiera (potrebbe non essere necessario) qui var camera = new Camera (); // crea un'istanza della classe telecamera camera.objectsInWorld [100]; // crea 100 spazi oggetto all'interno dell'array della telecamera // imposta lo spazio di visualizzazione della telecamera camera.minX = 0; camera.maxX = screenWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; per (int x = 0; x < camera.objectsInWorld.length; x++)  //Set its location to a random point on the screen camera.objectsInWorld[x].tuple = [random(-200,1000), random(-200,1000), random(-100,200));  function redrawScreenWithoutCulling() //this function clears the screen and then draws all of the points  ClearTheScreen(); //use your Graphics API's clear screen function for(int x = 0; x < camera.objectsInWorld.length; x++)  camera.objectsInWorld[x].drawPoint(); //draw the current point to the screen   while(esc != pressed) // the main loop  if(key('d') == pressed)  redrawScreenWithoutCulling();  if(key('c') == pressed)  camera.drawScene();  if(key('a') == pressed)  Point origin = new Point(0,0,0); Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++)  //store the current vector address for the point, and set the point tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added camera.objectsInWorld[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location camera.objectsInWorld[x].addVectorToPoint(tempVector.scale(0.5,0.5,0.5));   if(key('s') == pressed)  Point origin = new Point(0,0,0); //create the space's origin as a point Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++)  //store the current vector address for the point, and set the point tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added camera.objectsInWorld[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location camera.objectsInWorld[x].addVectorToPoint(tempVector.scale(2.0,2.0,2.0));   if(key('r') == pressed)  Point origin = new Point(0,0,0); //create the space's origin as a point Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++)  //store the current vector address for the point, and set the point tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added camera.objectsInWorld[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location camera.objectsInWorld[x].addVectorToPoint(tempVector.rotateXY(15));    

Ora puoi vedere, in prima persona, il potere di abbattere! Tieni presente che se stai osservando il codice di esempio, alcune cose sono fatte in modo un po 'diverso per rendere le demo più web-friendly. (Puoi controllare la mia semplice demo qui.)


Conclusione

Con una telecamera e un sistema di rendering sotto la cintura, puoi dire tecnicamente che hai creato un motore di gioco 3D! Potrebbe non essere ancora troppo impressionante, ma è in arrivo.

Nel nostro prossimo articolo, cercheremo di aggiungere alcune forme geometriche al nostro motore (ovvero segmenti di linea e cerchi) e parleremo degli algoritmi che possono essere utilizzati per adattare le loro equazioni ai pixel di uno schermo.