Låt oss bygga en 3D Graphics Engine Rasterizing Triangles och Quads

Välkommen till den femte delen av vår Låt oss bygga en 3D Graphics Engine-serie! Den här gången kommer vi att bygga två nya klasser för rasterisering: en för trianglar och en för grundläggande fyrhjulingar. Sedan ska vi ta bitar från de två klasserna och sätta ihop en slutgiltig, allmäktig polygonklass.

Tips: Det här är en del av en serie, så om du vill få ut det mesta, se till att du läser de andra handledningarna som leder fram till den här.


Recap

Vi har hittills byggt in lite i vår motor! Här är vad vi har:

  • Punkt och vektorgrupper (byggnadsblocken i vår motor).
  • Transformationsfunktioner för våra poäng.
  • En kameraklass (sätter vår visningsport, och culls pekar utanför skärmen).
  • Två klasser för rasterisering (linjesegment och cirklar).

Här är en snabb referens för alla klasser som vi har byggt:

 Point Class Variables: num tuple [3]; // (x, y, z) Operatörer: Peka AddVectorToPoint (Vector); Punkt SubtraherVectorFromPoint (Vector); Vector SubtractPointFromPoint (Punkt); Null SetPointToPoint (Point); Funktioner: drawPoint; // rita en punkt i sin position tuple Vector Class Variables: num tuple [3]; // (x, y, z) Operatörer: Vector AddVectorToVector (Vector); Vector SubtraherVectorFromVector (Vector); Vector RotateXY (grader); Vector RotateYZ (grader); Vector RotateXZ (grader); Vektorskala (s0, s1, s2); // mottar en skalering 3-tuple, returnerar den skalade vektorn Kameraklass Vars: int minX, maxX; int minY, maxY; int minZ, maxZ; array objectsInWorld; // en grupp av alla existerande objekt Funktioner: null drawScene (); // ritar alla nödvändiga objekt till skärmen LineSegment Class Variables: int startX, startY; // utgångspunkten för vårt linjesegment int endX, endY; // slutpunkten för vårt linjesegment Funktion: array returnPointsInSegment; // alla punkter ligger på detta linjesegment

Vi kommer att lita tungt på Linjesegmentet klass för att skapa vår Triangel och Quad klasser, så se till att bli omhändertagna med det innan du går vidare.


Rasterizing Triangles

Att sätta ihop en Triangel Klassen för motorn är ganska enkel, särskilt sedan Linjesegmentet klassen är där all vår rasterisering faktiskt kommer att äga rum. Denna klass tillåter tre punkter att ställas in, och kommer att rita ett linjesegment mellan dem för att göra den färdiga triangeln.  

En grundläggande översikt av klassen kan se ut så här:

 Triangle Class Variables: // koordinerar de tre punkterna i våra trianglar int Point1X, Point1Y; int punkt2x, punkt2y; int punkt3X, punkt3Y; Funktion: array returnPointsInTriangle; // alla punkter inom triangelns omkrets

För standardernas skull kommer vi att anta att de tre punkterna som deklarerats i vår triangel är i ett urmönster.  

Använda vår Linjesegmentet klass, då kan vi ställa in vår returnPointsInTriangle () funktion så här:

 funktion returnPointsInTriangle () array PointsToReturn; // skapa en tillfällig grupp för att hålla triangeln poäng // Skapa tre rader och lagra poängen i arrayen PointsToReturn.push (nytt LineSegment (this.Point1X, this.Point1Y, this.Point2X, this.Point2Y)); PointsToReturn.push (nytt LineSegment (this.Point2X, this.Point2Y, this.Point3X, this.Point3Y)); PointsToReturn.push (nytt LineSegment (this.Point3X, this.Point3Y, this.Point1X, this.Point1Y)); retur (PointsToReturn); 

Inte så illa, eller hur? Eftersom vi redan har mycket arbete som görs inom vår Linjesegmentet klass måste vi bara fortsätta stränga dem tillsammans för att skapa mer komplexa former. Detta gör det enkelt att skapa allt mer komplicerade polygoner på skärmen, helt enkelt genom att lägga till på mer LineSegments (och lagra fler poäng inom klassen själv).

Låt oss då titta på hur vi kan lägga till fler poäng till det här systemet genom att skapa en fyrkantig klass.


Kommer att kvadras bort

Att sätta ihop en klass för att hantera fyrhjulingar innebär bara att lägga till några extra saker till vårt Triangel klass. Med en annan uppsättning punkter skulle vår fyrfärgsklass se ut så här:

 Quad Class Variables: int Point1X, Point1Y; // koordinater för de fyra punkterna i vår fyrkantiga int Point2X, Point2Y; int punkt3X, punkt3Y; int Point4X, Point4Y; Funktion: array returnPointsInQuad; // returnera alla poäng inom fyrsidan

Sedan lägger vi bara till i det extra linjesegmentet till returnPointsInQuad funktion, som så:

 funktion returnPointsInQuad () array PointsToReturn; // skapa en tillfällig matris för att hålla quads poängen // Skapa fyra linjesegment och lagra sina poäng i arrayen PointsToReturn.push (nytt LineSegment (this.Point1X, this.Point1Y, this.Point2X, this.Point2Y)); PointsToReturn.push (nytt LineSegment (this.Point2X, this.Point2Y, this.Point3X, this.Point3Y)); PointsToReturn.push (new LineSegment (this.Point3X, this.Point3Y, this.Point4X, this.Point4Y)); PointsToReturn.push (new LineSegment (this.Point4X, this.Point4Y, this.Point1X, this.Point1Y)); retur (PointsToReturn); 

Medan man bygger nya klasser som detta är ganska rakt framåt, finns det ett mycket enklare sätt att inkapslera alla våra polygoner i en klass. Genom att använda magiken på slingor och arrays kan vi sätta ihop en polygonklass som kan göra nästan vilken storlek som helst du vill!


Var har alla Polys-Gon?

För att skapa en ständigt växande polygonklass måste vi göra två saker. Det första är att flytta alla våra poäng till en matris, vilket skulle ge oss en klassbeskrivning som liknar något sådant:

 Polygon Class Variables: array Points; // håller alla polygonens punkter i en array Funktion: array returnPointsInPolygon; // en array som håller alla polygonens punkter

Den andra är att använda en slinga för att låta ett namnlöst antal linjesegment passera i vår returnPointsInPolygon () funktion, vilket kan se ut så här:

 funktionen returnPointsInPolygon array PointsToReturn; // en tillfällig grupp för att hålla polygonens punkter // loop genom alla punkter i polygonen, flytta ett koordinatpar åt gången (med ett steg av två) för (int x = 0; x < this.Points.length; x+=2)  if(this is not the last point)  //create a line segment between this point and the next one in the array PointsToReturn.push(new LineSegment(this.Points[x], this.Points[x+1], this.Points[x+2], this.Points[x+3]));  else if(this is the last point)  //create a line segment between this point and the first point in the array PointsToReturn.push(new LineSegment(this.Points[x-2], this.Points[x-1], this.Points[0], this.Points[1]));   //return the array of points return PointsToReturn; 

Med denna klass som läggs till i vår motor kan vi nu skapa allt från en triangel till en viss 39-sidig avsky med samma kod.


Polygon Creator

För att spela med vår nya polygonklass, låt oss göra ett program som visar omfattningen av sin räckvidd. Vårt program ska tillåta användaren att lägga till eller ta bort sidor från den visade polygonen med tangenttryckningar. Naturligtvis måste vi sätta gränser för antalet sidor som vår polygon kan ha, eftersom det med mindre än tre sidor inte längre kommer att göra det till en polygon. Vi behöver inte riktigt hålla ögonen på polygons övre gränser för att de ska skala snyggt. Vi kommer emellertid att begränsa polygoner till att ha tio sidor maximalt eftersom vi kommer att ställa in sina nya punkter från koden.

Våra programspecifikationer kan brytas ner i dessa mindre delar:

  • Rita en polygon till skärmen initialt.
  • När "a" -tangenten trycks ned sänker du antalet sidor på polygonen med 1.
  • När 's' -tangenten trycks, öka antalet sidor på polygonen med 1.
  • Förhindra att polygons antal sidor faller under 3.
  • Förhindra att polygons antal sidor ökar över 10.

Låt oss se hur vår kod kan se ut:

 main // setup för ditt favoritgrafik API här // inställning för tangentbordsinmatning (kanske inte krävs) här var kamera = ny kamera (); // skapa en förekomst av vår kamera klass kamera.objectsInWorld []; // initiera kamerans objektmatris // ställa in kamerans bildrymds-kamera.minX = 0; camera.maxX = screenWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; // skapa en rad punkter för varje polygonstorlek var threeSides = new Array (100,100,100,50,50,50); var fourSides = ny Array (poäng här); var fiveSides = new Array (poäng här); var sixSides = ny Array (poäng här); var sevenSides = new Array (poäng här); var eightSides = new Array (poäng här); var nineSides = new Array (poäng här); var tenSides = ny Array (poäng här); // lagra alla arrays i en annan array för enklare åtkomst var sidesArray = new Array (threeSides, fourSides, fiveSides, sixSides, sevenSides, eightSides, nineSides, tenSides); // hålla reda på hur många punkter polygonen har för närvarande var polygonPoäng = 3; // skapa den första polygonen som ska visas var polygon = ny Polygon (sidesArray [0] [0], sidesArray [0] [1], sidesArray [0] [2], sidesArray [0] [3], sidesArray [0 ] [4], sidorArray [0] [5],); // rita den ursprungliga polygonen till skärmkamera.drawScene (); // medan användaren inte har tryckt på flyktangenten medan (tangent! = esc) if (tangenttryckt == 'a') // om polygonen inte riskerar att falla under 3 om (polygonPoints! = 3) // minska antalet punkter polygonPoints--; // ändra polygonen för att få rätt antal poäng // redraw scenkamera.drawScene ();  annars om (tangenttryckt == 's') // om polygonen inte riskerar att gå över 10 om (polygonPoints! = 10) // öka antalet punkter polygonPoäng ++; // ändra polygonen för att få rätt antal poäng // redraw scenkamera.drawScene (); 

Vårt lilla program ska låta dig justera en polygon på skärmen nu! Kolla in demoen. Om du skulle vilja nötla upp det här programmet, kanske du vill försöka sätta polygonändringsavsnittet i någon form av en algoritm för att göra skalan lättare på dig själv. Jag är osäker på om man redan existerar, men om det gör det, kan du enkelt ha en oändligt skalande polygon på dina händer!


Slutsats

Vi har en ganska stor mängd rasterisering inbyggd i vår motor nu, så att vi skapar nästan vilken form som vi kan behöva (även om vissa endast genom kombination). Nästa gång kommer vi att flytta bort från teckningsformer och prata mer om sina egenskaper. Om du är intresserad av att få lite färg på din skärm, var noga med att kolla in nästa del!