Lassen Sie uns eine 3D-Grafik-Engine erstellen Leerzeichen und Culling

Herzlich willkommen! Dies ist der dritte Teil unserer Serie über 3D-Grafik-Engines. Wenn Sie es so weit in die Serie geschafft haben, werden Sie froh sein zu wissen, dass dieses Stück den mathematischen Aspekt von 3D-Engines viel leichter fassen wird, und sich stattdessen auf praktischere Dinge konzentrieren wird - insbesondere das Hinzufügen einer Kamera und ein grundlegendes Wiedergabesystem.

Spitze: Wenn Sie die ersten beiden Teile noch nicht gelesen haben, empfehle ich dringend, dies vor dem Fortfahren zu tun.

In Envato Studio erhalten Sie zusätzliche Hilfe. Dort können Sie aus einer breiten Palette hochwertiger 3D-Design- und Modellierungsdienste von erfahrenen Anbietern auswählen. 

3D-Design- und Modellierungsdienste auf Envato Studio

Rekapitulieren

Lassen Sie uns zunächst die Klassen betrachten, die wir bisher erstellt haben:

Punktklasse Variablen: Zahlentupel [3]; // (x, y, z) Operatoren: Punkt AddVectorToPoint (Vektor); Point SubtractVectorFromPoint (Vektor); Vektor SubtractPointFromPoint (Punkt); Null SetPointToPoint (Punkt); // Punkt zum angegebenen Punkt verschieben Funktionen: drawPoint; // Zeichne einen Punkt an seinem Positionstupel Vektorklasse Variablen: Anzahl Tupel [3]; // (x, y, z) Operatoren: Vector AddVectorToVector (Vector); Vector SubtractVectorFromVector (Vector); Vektor RotateXY (Grad); VektordrehzahlYZ (Grad); VektordrehzahlXZ (Grad); Vektorskala (s0, s1, s2); // Parameter: Skalierung entlang jeder Achse

Die Verwendung dieser beiden Klassen für sich alleine hat sich bisher als etwas unordentlich erwiesen. Wenn Sie jeden möglichen Punkt zeichnen, kann dies den Speicher Ihres Systems ziemlich schnell belasten. Um diese Probleme zu lösen, werden wir eine neue Klasse in unsere Spielengine einführen: die Kamera.

Unsere Kamera wird dort sein, wo ausschließlich unser Rendering stattfindet. es wird cull alle unsere Objekte auf dem Bildschirm, und es wird auch eine Liste aller unserer Punkte verwalten.

Aber bevor wir all das erreichen können, müssen wir zuerst ein wenig über das Keuchen sprechen.


London Culling

Nach der Definition werden Objekte aus einer größeren Gruppe von Objekten ausgewählt. Die kleine Auswahl, die wir ergreifen, sind die Punkte, die wir auf den Bildschirm ziehen möchten. Die größere Gruppe von Objekten wird jeder Punkt sein, der existiert.

Auf diese Weise wird der Speicher Ihres Systems drastisch reduziert, da nur das gezeichnet wird, was ein Spieler tatsächlich sehen kann, und nicht die Punkte einer ganzen Welt. In unserer Engine werden wir dies tun, indem Sie Parameter für a einstellen Raum ansehen.

Unser Ansichtsraum wird für alle drei traditionellen Achsen definiert: x, y und z. Seine x-Definition wird aus allem zwischen den linken und rechten Grenzen des Fensters bestehen, seine y-Definition wird aus allem zwischen den oberen und unteren Grenzen des Fensters bestehen, und seine z-Definition wird dazwischen liegen 0 (wo die Kamera eingestellt ist) und die Sichtweite unseres Spielers (für unsere Demonstration verwenden wir einen beliebigen Wert von 100).

Vor dem Zeichnen eines Punktes wird unsere Kameraklasse prüfen, ob dieser Punkt in unserem Ansichtsraum liegt. Wenn dies der Fall ist, wird der Punkt gezeichnet. sonst wird es nicht.


Können wir hier ein paar Kameras bekommen??

Mit diesem grundlegenden Verständnis von Keulung können wir ableiten, dass unsere Klasse bisher so aussehen wird:

Kameraklasse Vars: int minX, maxX; // minimale und maximale Grenzen von X int minY, maxY; // minimale und maximale Grenzen von Y int minZ, maxZ; // minimale und maximale Grenzen von Z

Wir werden auch unsere Kamera für das gesamte Rendering unserer Engine verantwortlich machen. Je nach Engine werden Sie feststellen, dass Renderer häufig von den Kamerasystemen getrennt sind. Dies geschieht in der Regel, um die Systeme gut gekapselt zu halten, da die beiden - je nach Umfang Ihrer Engine - ziemlich unordentlich werden könnten, wenn sie zusammengehalten werden. Für unsere Zwecke wird es jedoch einfacher sein, sie als eine Einheit zu behandeln.

Zunächst wollen wir eine Funktion, die extern von der Klasse aufgerufen werden kann, um die Szene zu zeichnen. Diese Funktion durchläuft jeden der vorhandenen Punkte, vergleicht sie mit den Auslöseparametern der Kamera und zeichnet sie gegebenenfalls.


Quelle: http://en.wikipedia.org/wiki/File:ViewFrustum.svg

Spitze: Wenn Sie Ihr Kamerasystem von Ihrem Renderer trennen möchten, können Sie einfach ein Renderer Klasse, lassen Sie das Kamerasystem die Punkte entfernen, speichern Sie die zu zeichnenden Punkte in einem Array und senden Sie dieses Array dann an das zeichnen() Funktion Ihres Renderers.


Punktverwaltung

Das letzte Stück unserer Kameraklasse wird das Punktverwaltungssystem sein. Je nach verwendeter Programmiersprache kann dies nur ein einfaches Array aller Objekte sein, die gezeichnet werden können (in späteren Teilen werden wir mehr als nur Punkte behandeln). Alternativ müssen Sie möglicherweise die übergeordnete Objektklasse der Sprache verwenden. Wenn Sie sehr unglücklich sind, müssen Sie Ihre eigene Objekt-übergeordnete Klasse erstellen, und jede Klasse (bis jetzt nur Punkte), die gezeichnet werden kann, muss ein Kind dieser Klasse sein.

Nach dem Hinzufügen in die Klasse würde eine grundlegende Übersicht unserer Kamera folgendermaßen aussehen:

Kameraklasse Vars: int minX, maxX; // minimale und maximale Grenzen von X int minY, maxY; // minimale und maximale Grenzen von Y int minZ, maxZ; // minimale und maximale Grenzen des Z-Arrays objectsInWorld; // ein Array aller vorhandenen Objekte Functions: null drawScene (); // zeichnet alle benötigten Objekte auf den Bildschirm, gibt nichts zurück

Mit diesen Ergänzungen wollen wir das Programm, das wir beim letzten Mal erstellt haben, ein wenig verbessern.


Größere und bessere Dinge

Wir werden ein einfaches Punktzeichnungsprogramm erstellen, mit dem zuletzt erstellten Beispielprogramm als Ausgangspunkt.

In dieser Iteration des Programms werden wir unsere neue Kameraklasse einsetzen. Wenn der D Wenn Sie die Taste drücken, zeichnet das Programm den Bildschirm neu, ohne zu löschen, und zeigt die Anzahl der Objekte an, die in der rechten oberen Ecke des Bildschirms gerendert wurden. Wenn der C Wenn Sie die Taste drücken, zeichnet das Programm den Bildschirm neu und zeigt die Anzahl der gerenderten Objekte an.

Schauen wir uns den Code an:

main // Setup für Ihre bevorzugte Grafik-API hier // Setup für die Tastatureingabe (möglicherweise nicht erforderlich) hier var camera = new Camera (); // eine Instanz der Kameraklasse erstellen camera.objectsInWorld [100]; // Erstellen Sie 100 Objektbereiche innerhalb des Kamera-Arrays. // Setzen Sie den Ansichtsbereich der Kamera. camera.minX = 0; camera.maxX = screenWidth; camera.minY = 0; camera.maxY = screenHeight; camera.minZ = 0; camera.maxZ = 100; für (int x = 0; x < camera.objectsInWorld.length; x++)  //Set its location to a random point on the screen camera.objectsInWorld[x].tuple = [random(-200,1000), random(-200,1000), random(-100,200));  function redrawScreenWithoutCulling() //this function clears the screen and then draws all of the points  ClearTheScreen(); //use your Graphics API's clear screen function for(int x = 0; x < camera.objectsInWorld.length; x++)  camera.objectsInWorld[x].drawPoint(); //draw the current point to the screen   while(esc != pressed) // the main loop  if(key('d') == pressed)  redrawScreenWithoutCulling();  if(key('c') == pressed)  camera.drawScene();  if(key('a') == pressed)  Point origin = new Point(0,0,0); Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++)  //store the current vector address for the point, and set the point tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added camera.objectsInWorld[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location camera.objectsInWorld[x].addVectorToPoint(tempVector.scale(0.5,0.5,0.5));   if(key('s') == pressed)  Point origin = new Point(0,0,0); //create the space's origin as a point Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++)  //store the current vector address for the point, and set the point tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added camera.objectsInWorld[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location camera.objectsInWorld[x].addVectorToPoint(tempVector.scale(2.0,2.0,2.0));   if(key('r') == pressed)  Point origin = new Point(0,0,0); //create the space's origin as a point Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++)  //store the current vector address for the point, and set the point tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added camera.objectsInWorld[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location camera.objectsInWorld[x].addVectorToPoint(tempVector.rotateXY(15));    

Jetzt können Sie aus erster Hand die Kraft des Keulens sehen! Wenn Sie den Beispielcode durchsehen, werden einige Dinge etwas anders gemacht, um die Demos webfähiger zu machen. (Sie können meine einfache Demo hier überprüfen.)


Fazit

Mit einer Kamera und einem Rendering-System können Sie technisch sagen, dass Sie eine 3D-Spiel-Engine erstellt haben! Es ist vielleicht noch nicht allzu beeindruckend, aber es ist auf dem Weg.

In unserem nächsten Artikel wollen wir einige geometrische Formen (dh Liniensegmente und Kreise) zu unserer Engine hinzufügen. Außerdem werden wir über die Algorithmen sprechen, mit denen ihre Gleichungen an die Pixel eines Bildschirms angepasst werden können.