Låt oss bygga en 3D-grafikmotor Radera linjesegment och cirklar

Hej, det här är den fjärde delen av serien på 3D grafikmotorer. Den här gången kommer vi att täcka rastrering: processen att ta en form som beskrivs av matematiska formler och konvertera den till en bild på din skärm.

Tips: Alla begrepp i den här artikeln är byggda av klasser som vi har etablerat i de tre första inläggen, så var noga med att kolla in dem först.


Recap

Här är en översyn av de klasser som vi har gjort hittills:

 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 (); // drar alla nödvändiga objekt till skärmen

Du kan kolla in provprogrammet från den tredje delen av serien för att se hur de fungerar tillsammans.

Nu, låt oss ta en titt på några nya saker!


rastrering

rastrering (eller rastrering, om du vill) är processen att ta en form som beskrivs i ett vektorgrafikformat (eller i vårt fall, matematiskt) och konvertera det till en rasterbild (där formen passar på en pixelstruktur).

Eftersom matte inte alltid är så exakt som vi behöver det för datorgrafik, måste vi använda algoritmer för att passa de former som beskrivs på vår heltalsbaserade skärm. Till exempel kan en punkt falla in i koordinaten \ ((3.2, 4.6) \) i matematiken, men när vi gör det måste vi knyta den till \ ((3, 5) \) så att den kan passa in i pixelstruktur på vår skärm.

Varje typ av form som vi rasteriserar kommer att ha sin egen algoritm för att göra det. Låt oss börja med en av de enklare formerna att rasterisera: linjesegmentet.


Linjesegment


Källa: http://en.wikipedia.org/wiki/File:Bresenham.svg

Linjesegment är en av de enklaste formerna som kan dras, och det är ofta en av de första sakerna i någon geometriklass. De representeras av två separata punkter (en startpunkt och en slutpunkt) och linjen som förbinder de två. Den vanligaste algoritmen som rasteriserar ett linjesegment kallas Bresenhams algoritm.

Steg för steg fungerar Bresenhams algoritm så här:

  1. Ta emot ett linjesegments start- och slutpunkter som inmatning.
  2. Identifiera ett linjessegments riktning genom att bestämma dess egenskaper (\ xx) och \ (dy \) \ (dx = x_ 1 - x_ 0 \), \ (dy = y_ 1 - y_ 0 \)).
  3. Bestämma sx, sy, och felfångande egenskaper (jag visar den matematiska definitionen för dessa nedan).
  4. Runda varje punkt i linjesegmentet till antingen pixeln över eller under.

Innan vi implementerar Bresenhams algoritm kan vi sätta ihop en baslinjesegmentsklass som ska användas i vår motor:

 LineSegment Class Variabler: 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

Om du vill göra en omvandling på vårt nya Linjesegmentet klass, allt du behöver göra är att tillämpa din valda omvandling till början och slutpunkterna i Linjesegmentet och lägg sedan tillbaka dem i klassen. Alla punkter som faller mellan kommer att behandlas när Linjesegmentet själv dras, eftersom Bresenhams algoritm endast kräver början och slutpunkter för att hitta varje efterföljande punkt.

För att Linjesegmentet klass för att passa med vår nuvarande motor, kan vi inte faktiskt ha en dra() funktion inbyggd i klassen, varför jag har valt att använda a returnPointsInSegment funktion istället. Denna funktion kommer att returnera en uppsättning av alla punkter som finns inom linjesegmentet, så att vi enkelt kan dra och slänga linjesegmentet, beroende på vad som är lämpligt.

Vår funktion returnPointsInSegment () ser lite ut så här (i JavaScript):

 funktionen returnPointsInSegment () // skapa en lista för att lagra alla linjesegmentets punkter i var pointArray = new Array (); // ställa in den här funktionens variabler baserat på klassens start- och slutpunkter var x0 = this.startX; var y0 = this.startY; var x1 = this.endX; var y1 = this.endY; // definiera vektorskillnader och andra variabler som krävs för Bresenhams algoritm var dx = Math.abs (x1-x0); var dy = Math.abs (y1-y0); var sx = (x0 & x1)? 1: -1; // steg x var sy = (y0 & y1)? 1: -1; // steg y var err = dx-dy; // få det ursprungliga felvärdet // ställa in den första punkten i array pointArray.push (ny punkt (x0, y0)); // Huvudbehandlingsslinga medan (! ((X0 == x1) && (y0 == y1))) var e2 = err * 2; // hålla felvärdet // använd felvärdet för att bestämma om punkten ska rundas upp eller ner om (e2 => -dy) err - = dy; x0 + = sx;  om (e2 < dx)  err += dx; y0 += sy;  //add the new point to the array pointArray.push(new Point(x0, y0));  return pointArray; 

Det enklaste sättet att lägga till rendering av våra linjesegment i vår kamera klass är att lägga till i en enkel om struktur som liknar följande:

 // loop genom array av objekt om (klass typ == Point) // gör vår nuvarande renderingskod annars om (klass typ == LineSegment) var segmentArray = LineSegment.returnPointsInSegment (); // loop genom punkter i matrisen, rit och dölja dem som vi tidigare har

Det är allt du behöver för att få din första formklass igång! Om du vill lära dig mer om de mer tekniska aspekterna av Bresenhams algoritm (särskilt felavsnitten) kan du kolla Wikipedia-artikeln om det.


cirklar


Källa: http://en.wikipedia.org/wiki/File:Bresenham_circle.svg

Rasterizing en cirkel är lite svårare än rasterizing ett linjesegment, men inte mycket. Vi kommer att använda mittpunkts cirkelalgoritm att göra all vår tunga lyftning, vilket är en förlängning av den tidigare nämnda Bresenhams algoritm. Som sådan följer det liknande steg till de som listades ovan, med några mindre skillnader.

Vår nya algoritm fungerar så här:

  1. Ta emot en cirkels mittpunkt och radie.
  2. Tvinga tvinga punkterna i varje kardinalriktning
  3. Cykla genom var och en av våra kvadranter och dra deras bågar

Vår cirkelklass kommer att vara mycket lik vår linjesegmentklass, ser något ut så här:

 Circle Class Variabler: int centerX, centerY; // mittpunkten för vår cirkel intradie // radien av vår cirkel Funktion: array returnPointsInCircle; // alla poäng inom denna cirkel

Vår returnPointsInCircle () funktionen kommer att verka på samma sätt som vår Linjesegmentet klassens funktion gör att vi returnerar en rad punkter så att vår kamera kan göra och kasta dem efter behov. Detta låter vår motor hantera en mängd olika former, med bara mindre ändringar som behövs för varje.

Här är vad vårt returnPointsInCircle () funktionen kommer att se ut (i JavaScript):

 funktion returnPointsInCircle () // lagra alla cirkels punkter i en array var pointArray = new Array (); // ställa in de värden som behövs för algoritmen var f = 1 - radie; // används för att spåra framstegen i den ritade cirkeln (eftersom den är semi-recursiv) var ddFx = 1; // steg x var ddFy = -2 * this.radius; // steg y var x = 0; var y = this.radius; // den här algoritmen tar inte hänsyn till de längsta punkterna, // så vi måste ställa in dem manuellt punktArray.push (ny punkt (this.centerX, this.centerY + this.radius)); pointArray.push (ny punkt (this.centerX, this.centerY - this.radius)); pointArray.push (ny punkt (this.centerX + this.radius, this.centerY)); pointArray.push (ny punkt (this.centerX - this.radius, this.centerY)); medan (x < y)  if(f >= 0) y--; ddFy + = 2; f + = ddFy;  x ++; ddFx + = 2; f + = ddFx; // bygga vår nuvarande bågpunktArray.push (ny punkt (x0 + x, y0 + y)); pointArray.push (ny punkt (x0 - x, y0 + y)); pointArray.push (ny punkt (x0 + x, y0 - y)); pointArray.push (ny punkt (x0 - x, y0 - y)); pointArray.push (ny punkt (x0 + y, y0 + x)); pointArray.push (ny punkt (x0 - y, y0 + x)); pointArray.push (ny punkt (x0 + y, y0 - x)); pointArray.push (ny punkt (x0 - y, y0 - x));  returpunktArray; 

Nu lägger vi bara till i en annan om uttalande till vår huvudsakliga ritningsling, och dessa cirklar är fullt integrerade!

Så här ser den uppdaterade ritslingan ut:

 // loop genom array av objekt om (klass typ == punkt) // gör vår nuvarande renderingskod annars om (klass typ == LineSegment) var segmentArray = LineSegment.returnPointsInSegment (); // loop genom punkter i arrayen, dra och släng dem som vi tidigare annars om (klass typ == Cirkel) var circleArray = Circle.returnPointsInCircle (); // loop genom punkter i matrisen, rit och dölja dem som vi tidigare har

Nu när vi har våra nya klasser ur vägen, låt oss göra något!


Raster Master

Vårt program kommer att vara enkelt den här gången. När användaren klickar på skärmen kommer vi att rita en cirkel vars mittpunkt är den punkt som klickades och vars radie är ett slumptal.

Låt oss ta en titt på koden:

 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 kameraklassen camera.objectsInWorld []; // skapa 100 objektutrymmen i kamerans array // sätt kamerans bildrymd camera.minX = 0; camera.maxX = screenWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; medan (key! = esc) if (mouseClick) // skapa en ny cirkel camera.objectsInWorld.push (ny cirkel (mouse.x, mouse.y, slumpmässig (3,10)); // gör allt i scenkamera.drawScene ();

Med någon tur bör du nu kunna använda din uppdaterade motor för att rita några fantastiska cirklar.


Slutsats

Nu när vi har några grundläggande rasteriseringsfunktioner i vår motor, kan vi äntligen börja dra några användbara saker till vår skärm! Ingenting alltför komplicerat just än, men om du ville, kan du koppla ihop några pinnen figurer, eller något av det slaget.

!