Costruiamo un motore grafico 3D i colori

Benvenuto! Questa è la sesta parte della nostra serie Let's Build a 3D Graphics Engine che copre le basi dei sistemi di grafica 3D. Questa volta parleremo del colore e di come aggiungerlo alle nostre classi esistenti. Creeremo anche alcune funzioni utili per semplificare la gestione dell'illuminazione, che è la parte successiva e finale della nostra attività.


Ricapitolare

Aggiungere colori ai nostri oggetti non sarà un'impresa troppo grande, quindi le uniche due lezioni su cui ci concentreremo pesantemente sono la nostra point-class e la nostra classe di fotocamere. Come aggiornamento, ecco come sono:

 Point Class Variables: num tuple [3]; // (x, y, z) Operatori: Punto AddVectorToPoint (Vettore); Point SubtractVectorFromPoint (Vector); Vector SubtractPointFromPoint (Point); Null SetPointToPoint (Point); Funzioni: drawPoint; // disegna un punto nella sua posizione tupla Camera Class Vars: int minX, maxX; int minY, maxY; int minZ, maxZ; array objectsInWorld; // una matrice di tutti gli oggetti esistenti Funzioni: null drawScene (); // disegna tutti gli oggetti necessari sullo schermo

Finora, il nostro motore teorico ha quasi tutte le basi in atto, tra cui:

  • Punto e Vettore classi (i mattoni del nostro motore).
  • Funzioni di trasformazione per i nostri punti.
  • UN telecamera classe (imposta la nostra finestra e elimina i punti all'esterno dello schermo).
  • Tre classi per rasterizzare (segmenti di linea, cerchi e poligoni).

Ora aggiungiamo un po 'di colore!


Colore per tutti!

Il nostro motore gestirà i colori memorizzando i loro valori al suo interno Punto classe. Ciò consente a ciascun punto di avere il proprio colore individuale, rendendo i calcoli di illuminazione e ombreggiatura molto più semplici (per le persone, almeno - a volte è meno efficiente codificare un motore in questo modo). Quando capiamo l'illuminazione o l'ombreggiatura di una scena, possiamo facilmente fornire la funzione con una lista di punti, e quindi sfogliarli ognuno di essi, usando la loro distanza dalla luce per modificare il loro colore di conseguenza..

Uno dei metodi più comuni per archiviare il colore nella programmazione consiste nell'utilizzare i valori rosso, verde e blu per creare il colore desiderato effettivo (in genere viene chiamato miscelazione colore additiva). Memorizzando un valore di 0-255 in ciascuno di questi segmenti di colore, è possibile creare facilmente un'ampia varietà di colori. (Questo è il modo in cui la maggior parte delle API determina il colore, quindi per ragioni di compatibilità ha senso usare questo metodo). 

Quindi, a seconda dell'API di grafica che si sta utilizzando, è possibile passare questi valori in forma decimale (255,0,0), o in forma esadecimale (0xFF0000 o # FF0000). Useremo il formato decimale nel nostro motore poiché è un po 'più facile da lavorare. Inoltre, se la tua API grafica utilizza valori esadecimali, probabilmente ha una funzione per la conversione da decimale a esadecimale, quindi questo non dovrebbe essere un problema.

Per iniziare l'implementazione del colore, aggiungeremo tre nuove variabili alla nostra classe Point: rosso, blu, e verde.  Non c'è ancora niente di troppo stravagante, ma ecco cosa è il nostro Punto il nuovo schema della classe potrebbe avere il seguente aspetto:

 Point Class Variables: num tuple [3]; // (x, y, z) num rosso, verde, blu; // può essere abbreviato in r, g, b se desiderato Operatori: Point AddVectorToPoint (Vector); Point SubtractVectorFromPoint (Vector); Vector SubtractPointFromPoint (Point); Null SetPointToPoint (Point); Funzioni: drawPoint; // disegna un punto nella sua posizione tupla

Questo è tutto ciò di cui abbiamo bisogno per memorizzare il colore del nostro punto. Ora dobbiamo solo regolare la funzione di disegno della nostra fotocamera in modo che utilizzi il colore specificato.

Questo cambierà drasticamente a seconda dell'API grafica che stai usando, ma dovrebbero avere tutti una funzione simile a questa:

 object.setColor (rosso, verde, blu)

Se la tua API grafica utilizza valori esadecimali per il colore anziché i decimali, la tua funzione sarà simile a questa:

 object.setColor (toHex (rosso, verde, blu))

Quest'ultimo bit usa a toHex () funzione (di nuovo, i nomi delle funzioni differiscono da API a API) per convertire un valore RGB in un valore esadecimale in modo da non dover.  

Dopo aver apportato queste modifiche, ora dovresti essere in grado di avere punti colorati all'interno della scena. Per fare un ulteriore passo avanti, modificheremo ciascuna delle nostre classi di rasterizzazione in modo che la nostra intera forma possa essere colorata.

Per aggiungere questo alle nostre classi, dobbiamo semplicemente aggiungere la gestione del colore alle loro funzioni di costruzione. Questo potrebbe sembrare:

 lineSegment :: constructor (startX, startY, endX, endY, red, green, blue) this.startX = startX; questo.startY = startY; this.endX = endX; this.endY = endY; this.red = red; this.green = verde; this.blue = blu; 

Quindi, abbiamo solo bisogno di modificare la sua funzione di punti di ritorno in modo tale da impostare ogni punto della sua matrice in modo che abbia il colore specificato. La nuova funzione sarà simile a questa:

 function returnPointsInSegment () // crea una lista per memorizzare tutti i punti del segmento di linea var pointArray = new Array (); // imposta le variabili di questa funzione in base ai punti iniziale e finale della classe var x0 = this.startX; var y0 = this.startY; var x1 = this.endX; var y1 = this.endY; // definisce le differenze vettoriali e le altre variabili richieste per Algorithm di Bresenham var dx = Math.abs (x1-x0); var dy = Math.abs (y1-y0); var sx = (x0 & x1)? 1: -1; // passo x var sy = (y0 & y1)? 1: -1; // step y var err = dx-dy; // ottiene il valore di errore iniziale // imposta il primo punto nell'array pointArray.push (new Point (x0, y0, this.red, this.green, this.blue)); // Ciclo di elaborazione principale while (! ((X0 == x1) && (y0 == y1))) var e2 = err * 2; // mantiene il valore dell'errore // usa il valore dell'errore per determinare se il punto deve essere arrotondato in alto o in basso se (e2 => -dy) err - = dy; x0 + = sx;  if (e2 < dx)  err += dx; y0 += sy;  //add the new point to the array pointArray.push(new Point(x0, y0,this.red,this.green,this.blue));  return pointArray; 

Ora, ogni punto all'interno del segmento di linea dovrebbe essere dello stesso colore che è stato passato nel segmento di linea. È possibile utilizzare questo metodo per impostare i colori anche nelle altre classi di rasterizzazione, e presto la scena si animerà di colori!  

Mettiamo in azione le nostre nuove funzionalità creando un programma per mostrarle.


Giocando con 16,7 milioni di colori

Usando il mixaggio additivo dei colori, possiamo facilmente creare oltre 16,7 milioni di colori diversi usando semplicemente il semplice (r, g, bnotazione. Creeremo un programma che sfrutta questo vasto numero di colori.

Usando la pressione dei tasti, consentiremo all'utente di controllare individualmente i valori rosso, verde e blu di un oggetto, consentendo loro di trasformarlo in qualsiasi colore che vorrebbero.

Le specifiche per il nostro programma sono le seguenti:

  • Disegna un oggetto sullo schermo.
  • Se l'utente preme UN quindi abbassa il valore rosso dell'oggetto; se premono Q quindi alzalo.
  • Se l'utente preme S quindi abbassa il valore verde dell'oggetto; se premono W quindi alzalo.
  • Se l'utente preme D quindi abbassa il valore blu dell'oggetto; se premono E quindi alzalo.
  • Ridisegna l'oggetto dopo che il colore è stato aggiornato.
  • Assicurati di limitare i valori del colore, impedendo loro di scendere sotto 0 o salire sopra 255.

Tenendo tutto questo in mente, diamo un'occhiata a quale potrebbe essere il profilo di base del nostro programma:

 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 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; // memorizza i nostri colori in modo che possano essere manipolati var red, green, blue; // disegna l'oggetto iniziale e impostalo su una variabile while (key! = esc) if (key press = 'a') if (red> 0) red -; oggetto.rosso = rosso; // ridisegna oggetto if (tasto preme = 'q') if (rosso < 255)  red ++; object.red = red; //redraw object   if(key press = 's')  if(green > 0) verde -; object.green = verde; // ridisegna oggetto if (tasto preme = 'w') if (verde < 255)  green ++; object.green = green; //redraw object   if(key press = 'd')  if(blue > 0) blu -; oggetto.blu = blu; // ridisegna oggetto if (tasto preme = 'e') if (blu < 255)  blue ++; object.blue = blue; //redraw object    

Ora possiamo giocare con il nostro oggetto e trasformarlo in qualsiasi colore che desideri!

Controlla qui la demo - premi ripetutamente il tasto Q, W, E, UN, S, e D i tasti per cambiare il colore del quadrato.


Conclusione

Con il colore aggiunto nel nostro motore, abbiamo tutto ciò che ci serve per gestire finalmente l'illuminazione. Nel prossimo articolo, cercheremo di creare fonti di illuminazione e di creare alcune funzioni che consentano a tali fonti di influenzare i colori dei nostri punti. La profondità che l'illuminazione aggiunge a un motore è estremamente soddisfacente, quindi assicurati di verificarla!