Costruiamo un motore grafico 3D rasterizzazione di segmenti di linea e cerchi

Ciao, questa è la quarta parte della nostra serie sui motori di grafica 3D. Questa volta, copriremo rasterizzazione: il processo di prendere una forma descritta da formule matematiche e convertirla in un'immagine sullo schermo.

Mancia: Tutti i concetti contenuti in questo articolo sono ricavati dalle classi che abbiamo stabilito nei primi tre post, quindi assicurati di verificarli prima.


Ricapitolare

Ecco una recensione delle classi che abbiamo fatto finora:

 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 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); // riceve una tupla a 3 scale, restituisce il vettore scalato 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

Puoi controllare il programma di esempio dalla terza parte della serie per vedere come lavorano insieme.

Ora, diamo un'occhiata ad alcune cose nuove!


rasterizzazione

rasterizzazione (o rasterizzazione, se ti piace) è il processo di prendere una forma descritta in un formato di grafica vettoriale (o nel nostro caso, matematicamente) e convertirla in un'immagine raster (in cui la forma è adattata a una struttura di pixel).

Dato che la matematica non è sempre precisa quanto è necessario per la grafica del computer, dobbiamo utilizzare algoritmi per adattare le forme che descrive sullo schermo basato su numeri interi. Ad esempio, un punto può cadere sulla coordinata \ ((3.2, 4.6) \) in matematica, ma quando lo rendiamo, dobbiamo spingerlo su \ ((3, 5) \) in modo che possa adattarsi struttura dei pixel del nostro schermo.

Ogni tipo di forma che rasterizziamo avrà il proprio algoritmo per farlo. Iniziamo con una delle forme più semplici da rasterizzare: il segmento.


Segmenti di linea


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

I segmenti di linea sono una delle forme più semplici che possono essere disegnate e quindi sono spesso una delle prime cose coperte in qualsiasi classe geometrica. Sono rappresentati da due punti distinti (un punto iniziale e un punto finale) e la linea che collega i due. Viene chiamato l'algoritmo più comunemente utilizzato per rasterizzare un segmento di linea Algoritmo di Bresenham.

Passo dopo passo, l'Algoritmo di Bresenham funziona così:

  1. Ricevi come input i punti di inizio e fine di un segmento di linea.
  2. Identificare la direzione di un segmento di linea determinando le sue proprietà \ (dx \) e \ (dy \) (\ (dx = x_ 1 - x_ 0 \), \ (dy = y_ 1 - y_ 0 \)).
  3. Determinare sx, sy, e proprietà di cattura degli errori (mostrerò la definizione matematica per questi sotto).
  4. Arrotondare ogni punto nel segmento di linea sul pixel sopra o sotto.

Prima di implementare Algorithm di Bresenham, mettiamo insieme una classe di segmento di linea di base da utilizzare nel nostro motore:

 Classe LineSegment Variabili: int startX, startY; // il punto di partenza del nostro segmento di linea int endX, endY; // il punto finale del nostro segmento di linea Funzione: array returnPointsInSegment; // tutti i punti che si trovano su questo segmento di linea

Se vuoi eseguire una trasformazione sul nostro nuovo Segmento classe, tutto ciò che devi fare è applicare la tua trasformazione scelta ai punti di inizio e fine del Segmento e poi rimettili nella classe. Tutti i punti che cadono tra saranno elaborati quando il Segmento viene disegnato, poiché l'Algoritmo di Bresenham richiede solo i punti di inizio e di fine per trovare ogni punto successivo.

In ordine per il Segmento classe per adattarsi al nostro motore attuale, non possiamo effettivamente avere un disegnare() funzione integrata nella classe, motivo per cui ho optato per l'utilizzo di a returnPointsInSegment funzione invece. Questa funzione restituirà un array di ogni punto esistente all'interno del segmento di linea, consentendoci di disegnare e selezionare facilmente il segmento di linea in modo appropriato.

La nostra funzione returnPointsInSegment () sembra un po 'come questo (in JavaScript):

 function returnPointsInSegment () // crea una lista per memorizzare tutti i punti del segmento di linea in 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)); // 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));  return pointArray; 

Il modo più semplice per aggiungere il rendering dei nostri segmenti di linea nella nostra classe di fotocamere è aggiungere in un semplice Se struttura, simile alla seguente:

 // loop through array of if if (tipo di classe == Point) // esegue il nostro codice di rendering corrente else if (tipo di classe == LineSegment) var segmentArray = LineSegment.returnPointsInSegment (); // attraversa i punti dell'array, disegnandoli e selezionandoli come in precedenza

Questo è tutto ciò di cui hai bisogno per ottenere la prima classe di forma attiva! Se vuoi saperne di più sugli aspetti più tecnici dell'algoritmo di Bresenham (in particolare le sezioni di errore), puoi consultare l'articolo di Wikipedia su di esso.


Circles


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

Rasterizzare un cerchio è un po 'più difficile della rasterizzazione di un segmento di linea, ma non di molto. Useremo il Algoritmo del punto centrale fare tutto il nostro sollevamento pesante, che è un'estensione del già menzionato Algoritmo di Bresenham. Come tale, segue passi simili a quelli che sono stati elencati sopra, con alcune piccole differenze.

Il nostro nuovo algoritmo funziona così:

  1. Ricevi il punto centrale e il raggio di un cerchio.
  2. Impostare i punti in modo forzato in ogni direzione cardinale
  3. Attraversa ciascuno dei nostri quadranti, disegnando i loro archi

La nostra classe Circle sarà molto simile alla nostra classe di segmento di linea, con un aspetto simile al seguente:

 Circle Class Variabili: centro intXX, centroY; // il punto centrale del raggio circolare del nostro cerchio; // il raggio del nostro cerchio Funzione: array returnPointsInCircle; // tutti i punti all'interno di questo cerchio

Nostro returnPointsInCircle () la funzione si comporterà nello stesso modo in cui la nostra Segmento la funzione della classe fa, restituendo una serie di punti in modo che la nostra videocamera possa renderizzarli e selezionarli secondo necessità. Ciò consente al nostro motore di gestire una varietà di forme, con solo piccole modifiche necessarie per ciascuna.

Ecco cosa è il nostro returnPointsInCircle () la funzione assomiglierà (in JavaScript):

 function returnPointsInCircle () // memorizza tutti i punti del cerchio in un array var pointArray = new Array (); // imposta i valori necessari per l'algoritmo var f = 1 - radius; // usato per tracciare l'andamento del cerchio disegnato (dal suo semi-ricorsivo) var ddFx = 1; // passo x var ddFy = -2 * this.radius; // step y var x = 0; var y = this.radius; // questo algoritmo non tiene conto dei punti più lontani, // quindi dobbiamo impostarli manualmente pointArray.push (new Point (this.centerX, this.centerY + this.radius)); pointArray.push (new point (this.centerX, this.centerY - this.radius)); pointArray.push (new point (this.centerX + this.radius, this.centerY)); pointArray.push (new Point (this.centerX - this.radius, this.centerY)); while (x < y)  if(f >= 0) y--; ddFy + = 2; f + = ddFy;  x ++; ddFx + = 2; f + = ddFx; // crea il nostro arco attuale pointArray.push (new Point (x0 + x, y0 + y)); pointArray.push (new Point (x0 - x, y0 + y)); pointArray.push (new Point (x0 + x, y0 - y)); pointArray.push (new Point (x0 - x, y0 - y)); pointArray.push (new Point (x0 + y, y0 + x)); pointArray.push (new Point (x0 - y, y0 + x)); pointArray.push (new Point (x0 + y, y0 - x)); pointArray.push (new Point (x0 - y, y0 - x));  return pointArray; 

Ora, aggiungiamo un altro Se dichiarazione al nostro ciclo di disegno principale, e questi cerchi sono completamente integrati!

Ecco come può apparire il ciclo di disegno aggiornato:

 // loop through array of if if (tipo di classe == point) // esegue il nostro codice di rendering corrente else if (tipo di classe == LineSegment) var segmentArray = LineSegment.returnPointsInSegment (); // attraversa i punti dell'array, disegnandoli e selezionandoli come in precedenza else if (class type == Circle) var circleArray = Circle.returnPointsInCircle (); // attraversa i punti dell'array, disegnandoli e selezionandoli come in precedenza

Ora che abbiamo tolto le nostre nuove classi, facciamo qualcosa!


Master raster

Il nostro programma sarà semplice questa volta. Quando l'utente fa clic sullo schermo, disegneremo un cerchio il cui punto centrale è il punto su cui è stato fatto clic e il cui raggio è un numero casuale.

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 []; // 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; while (key! = esc) if (mouseClick) // crea un nuovo cerchio camera.objectsInWorld.push (new Circle (mouse.x, mouse.y, random (3,10)); // renderizza tutto nel scena camera.drawScene ();

Con un po 'di fortuna, ora dovresti essere in grado di utilizzare il tuo motore aggiornato per disegnare alcune fantastiche cerchie.


Conclusione

Ora che abbiamo alcune funzionalità di rasterizzazione di base nel nostro motore, possiamo finalmente iniziare a disegnare alcune cose utili sul nostro schermo! Niente di troppo complicato, ma se volessi, potresti mettere insieme delle figure stilizzate o qualcosa del genere.

!