Să construim un motor grafic 3D Rasterizarea segmentelor de linie și a cercurilor

Bună ziua, aceasta este a patra parte a seriei noastre despre motoarele grafice 3D. De data aceasta, vom acoperi rasterizarea: procesul de a lua o formă descrisă de formule matematice și de ao transforma într-o imagine pe ecran.

Bacsis: Toate conceptele din acest articol sunt construite din clase pe care le-am stabilit în primele trei posturi, deci asigurați-vă că le verificați mai întâi.


Recapitulare

Iată o revizuire a claselor pe care le-am făcut până acum:

 Clasa de puncte Variabile: număr [3]; // (x, y, z) Operatori: Point AddVectorToPoint (Vector); Punctul SubtractVectorFromPoint (Vector); Vector SubtractPointFromPoint (punct); Null SetPointToPoint (punct); Funcții: drawPoint; // desenați un punct la tupla sa de poziție Clasa Vector Variables: num tuple [3]; // (x, y, z) Operatori: Vector AddVectorToVector (Vector); Vector SubtractVectorFromVector (Vector); Vector RotateXY (grade); Vector RotateYZ (grade); Vector RotateXZ (grade); Vector Scară (s0, s1, s2); // primește o scalare 3-tuple, returnează vectorul scalat Clasa camerei Vars: int minX, maxX; int minY, maxY; int minZ, maxZ; obiecte arrayInWorld; // o matrice a tuturor obiectelor existente Funcții: null drawScene (); // atrage toate obiectele necesare pe ecran

Puteți verifica programul de probă din a treia parte a seriei pentru a vedea cum funcționează împreună.

Acum, să aruncăm o privire la unele lucruri noi!


rasterizarea

rasterizarea (sau rasterisation, dacă doriți) este procesul de a lua o formă descrisă într-un format vectorial grafic (sau, în cazul nostru, matematic) și transformându-l într-o imagine raster (unde forma se potrivește pe o structură de pixeli).

Deoarece matematica nu este întotdeauna la fel de precisă cât trebuie să fie pentru grafica computerizată, trebuie să folosim algoritmi pentru a se potrivi formelor pe care le descrie pe ecranul nostru bazat pe întreg. De exemplu, un punct poate cădea pe coordonatul \ ((3.2, 4.6) \) în matematică, dar atunci când îl facem, trebuie să-l îndreptăm spre \ ((3, 5) \) astfel încât să se poată încadra în structura pixel a ecranului nostru.

Fiecare tip de formă pe care îl rasterizăm va avea propriul algoritm pentru a face acest lucru. Să începem cu una dintre cele mai simple forme de rasterizare: segment de linie.


Segmente de linie


Sursă: http://ro.wikipedia.org/wiki/File:Bresenham.svg

Segmentele de linie sunt una dintre cele mai simple forme care pot fi desenate și astfel sunt adesea unul dintre primele lucruri acoperite în orice clasă de geometrie. Acestea sunt reprezentate de două puncte distincte (un punct de început și un punct final) și linia care leagă cele două. Se numește cel mai frecvent utilizat algoritm de rasterizare a unui segment de linie Algoritmul lui Bresenham.

Pas cu pas, algoritmul lui Bresenham funcționează astfel:

  1. Primiți punctele de început și de sfârșit ale segmentului de linie ca intrări.
  2. Identificați direcția unui segment de linie prin determinarea proprietăților lui \ (dx \) și \ (dy \) (\ (dx = x_ 1 - x_ 0 \), \ (dy = y_ 1 \)).
  3. A determina s x, sy, și eroare de captură proprietăți (Voi arăta definiția matematică pentru aceste mai jos).
  4. Rotiți fiecare punct din segmentul de linie la pixelul deasupra sau dedesubtul.

Înainte de a implementa algoritmul lui Bresenham, vă permitem să puneți împreună o clasă de segment de bază care să fie utilizată în motorul nostru:

 Clasa segmentului de linie Variables: int startX, startY; // punctul de plecare al segmentului nostru de linie int endX, endY; // punctul de sfarsit al segmentului nostru de linie Functie: matrice returnPointsInSegment; // toate punctele situate pe acest segment de linie

Dacă doriți să efectuați o transformare pe noul nostru Segment de linie clasa, tot ce trebuie sa faci este sa aplici transformarea aleasa la punctele de inceput si sfarsit ale Segment de linie și apoi plasați-i înapoi în clasă. Toate punctele care se încadrează între ele vor fi procesate atunci când Segment de linie ea însăși este desenată, deoarece Algoritmul lui Bresenham cere ca punctele de început și de sfârșit să găsească fiecare punct ulterior.

Pentru a Segment de linie clasa pentru a se potrivi cu motorul nostru actual, nu putem avea de fapt un a desena() funcția integrată în clasă, motiv pentru care am optat pentru utilizarea a returnPointsInSegment funcția. Această funcție va returna o matrice a fiecărui punct care există în segmentul de linie, permițându-ne să desenăm și să tragem cu ușurință segmentul de linie, după caz.

Funcția noastră returnPointsInSegment () arată cam așa (în JavaScript):

 funcția returnPointsInSegment () // a crea o listă pentru a stoca toate punctele segmentului de linie în var pointArray = new Array (); // setați variabilele acestei funcții pe baza punctelor de început și de sfârșit ale clasei var x0 = this.startX; var y0 = acest început; var x1 = acest.endX; var y1 = acest lucru; // definește diferențele vectoriale și alte variabile necesare pentru algoritmul lui Bresenham var dx = Math.abs (x1-x0); var dy = Math.abs (y1-y0); var sx = (x0 & x1)? 1: -1; // pas x var sy = (y0 & y1)? 1: -1; // pas y var err = dx-dy; // obține valoarea inițială de eroare // setați primul punct în array pointArray.push (nou punct (x0, y0)); // Bucla principală de procesare în timp ce (! ((X0 == x1) && (y0 == y1))) var e2 = err * 2; // păstrați valoarea erorii // utilizați valoarea erorii pentru a determina dacă punctul trebuie rotunjit în sus sau în jos dacă (e2 => -dy) err - = dy; x0 + = sx;  dacă (e2 < dx)  err += dx; y0 += sy;  //add the new point to the array pointArray.push(new Point(x0, y0));  return pointArray; 

Cea mai ușoară modalitate de a adăuga randarea segmentelor de linie în clasa camerei noastre este de a adăuga o simplă dacă structură, similară cu următoarele:

 / / loop prin array de obiecte dacă (tip de clasă == Point) // face codul nostru actual de redare altceva dacă (tipul de clasă == LineSegment) var segmentArray = LineSegment.returnPointsInSegment (); // introduceți buclă prin puncte în matrice, desenând și tăindu-le așa cum am făcut anterior

Asta e tot ce ai nevoie pentru a vă face prima clasă de formare! Dacă doriți să aflați mai multe despre aspectele mai tehnice ale algoritmului lui Bresenham (în special secțiunile de eroare), puteți consulta articolul Wikipedia despre el.


cerc


Sursă: http://ro.wikipedia.org/wiki/File:Bresenham_circle.svg

Rasterizarea unui cerc este un pic mai dificil decat rasterizarea unui segment de linie, dar nu foarte mult. Vom folosi mijlocul cercului algoritm să facem toate ridicările grele, ceea ce reprezintă o extensie a algoritmului lui Bresenham menționat anterior. Ca atare, urmează pași asemănători cu cei menționați mai sus, cu unele diferențe minore.

Noul nostru algoritm funcționează astfel:

  1. Primiți centrul și raza unui cerc.
  2. Stabiliți cu forța punctele în fiecare direcție cardinală
  3. Treceți prin fiecare din cadrele noastre, trasându-le arcurile

Clasa noastră de cercuri va fi foarte asemănătoare cu clasa noastră de segment de linie, care arată în felul următor:

 Clasa cercurilor Variabile: int centerX, centerY; // punctul central al cercului nostru int; // raza cercului nostru Funcție: matrice returnPointsInCircle; // toate punctele din acest Cerc

Al nostru returnPointsInCircle () funcția se va comporta în același mod ca și noi Segment de linie funcția de clasă face, returnând o gamă de puncte, astfel încât camera noastră să le poată reda și să le curețe după cum este necesar. Acest lucru permite motorului nostru să se ocupe de o varietate de forme, cu doar mici modificări necesare pentru fiecare.

Iată ce ne returnPointsInCircle () funcția va arăta ca în JavaScript:

 function returnPointsInCircle () // memorarea tuturor punctelor cercului într-un array var pointArray = new Array (); // setați valorile necesare pentru algoritmul var f = 1 - raza; // folosit pentru a urmări progresul cercului tras (de la semi-recursiv) var ddFx = 1; // pas x var ddFy = -2 * this.radius; // pas y var x = 0; var y = this.radius; // acest algoritm nu ține cont de cele mai îndepărtate puncte, // deci trebuie să le setăm manual pointArray.push (nou punct (this.centerX, this.centerY + this.radius)); pointArray.push (punct nou (acest centru, acest centru - acest.radius)); pointArray.push (punct nou (acest.centerX + acest.radius, acest.centerY)); pointArray.push (nou punct (this.centerX - this.radius, this.centerY)); în timp ce (x < y)  if(f >= 0) y-; ddFy + = 2; f + = ddFy;  x ++; ddFx + = 2; f + = ddFx; // construiți punctul curent arcArray.push (noul punct (x0 + x, y0 + y)); pointArray.push (noul punct (x0 - x, y0 + y)); pointArray.push (nou punct (x0 + x, y0 - y)); pointArray.push (nou punct (x0 - x, y0 - y)); pointArray.push (nou punct (x0 + y, y0 + x)); pointArray.push (nou punct (x0 - y, y0 + x)); pointArray.push (nou punct (x0 + y, y0 - x)); pointArray.push (punctul nou (x0 - y, y0 - x));  return pointArray; 

Acum, noi adăugăm în altul dacă declarație către bucla principală de desenare și aceste cercuri sunt pe deplin integrate!

Iată cum poate arăta bucla actualizată de desen:

 / / loop prin array de obiecte dacă (tip de clasă == punct) // face codul nostru actual de redare altceva dacă (tip de clasă == LineSegment) var segmentArray = LineSegment.returnPointsInSegment (); // bucle prin puncte în matrice, desen și culling-le cum am făcut anterior else if (class == Circle) var circleArray = Circle.returnPointsInCircle (); // introduceți buclă prin puncte în matrice, desenând și tăindu-le așa cum am făcut anterior

Acum, când ne-am împiedicat noile clase, să facem ceva!


Raster Master

Programul nostru va fi simplu de data asta. Când utilizatorul face clic pe ecran, vom trasa un cerc al cărui punct central este punctul pe care a fost făcut clic și a cărui rază este un număr aleatoriu.

Să aruncăm o privire la cod:

 main // setup pentru grafica API preferată aici // configurare pentru introducerea tastaturii (poate să nu fie necesară) aici var camera = new Camera (); // a crea o instanță a camerei de cameră camera.objectsInWorld []; // creați 100 de spații de obiecte în interiorul matricei camerei // setați camera de vizualizare a camerei camera.minX = 0; camera.maxX = ecranWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; în timp ce (key! = esc) if (mouseClick) // a crea un nou cerc camera.objectsInWorld.push (nou Cerc (mouse.x, mouse.y, aleator (3,10) scena camera.drawScene ();

Cu orice noroc, ar trebui acum să puteți folosi motorul actualizat pentru a desena câteva cercuri minunate.


Concluzie

Acum că avem câteva caracteristici de rasterizare de bază în motorul nostru, putem începe în sfârșit să desenăm câteva lucruri utile pe ecranul nostru! Nimic încă prea complicat, dar dacă ați fi vrut, ați putea să îmbrățișați niște figuri de tip stick sau ceva de genul ăsta.

!