[UPDATE] jetzt hatte ich doch etwas Zeit um mich meinem kleinen Projekt zu widmen.
Quellcode auf:
github
Bild anklicken [GIF]
Projektstatus
Nach mehreren Wochen regelmäßiger Entwicklung ist es Zeit ein Fazit zu ziehen. Das Ursprüngliche Ziel war:
• Rendern von Primitiven sollte so einfach wie möglich sein.
• Es sollte möglich sein, Licht und Texturen zu verwenden.
• Positionieren, Rotieren und Skalieren von 3D-Objekten sollten umgesetzt werden.
• Einfache Kenntnisse in C/C++ sollten ausreichen, um mit der Engine arbeiten zu können.
Im aktuellen Stand der Engine habe ich diese Ziele erreicht. Hier möchte ich nun auf die einzelnen Ziele eingehen. Ich denke die Engine ist einfach anzuwenden. Man kann sehr einfach Primitive erstellen und texturieren. Zu Zeit ist nur gerichtetes Licht möglich. Schatten gibt es noch nicht, vielleicht als nächstes Ziel? Ich möchte noch 3D Modelle laden können. Da denke ich das assimp ganz gut geeignet ist. Mal sehen.
Die API Spezifikationen
Doch erst einmal etwas Grundlegendes zur Verwendung der API. Grundsätzlich läuft jede Engine nach einem bestimmten Muster ab. Es gibt eine Hauptfunktion, es gibt eine Initalisierungsfunktion und eine Hauptschleife in der die Engine geupdatet wird. Das kann dann z.B. so aussehen:
Code: Alles auswählen
int main()
{
init();
while (!exit)
{
update();
render();
}
shutdown();
}
Diese Grundlegende Struktur ist auch in der oyname-Engine zu finden. Jede oyname-Anwendung muss mit einem int main() beginnen. Um die Funktionen der Engine verwenden zu muss noch gidx.h eingebunden werden. Die Initalisierung erfolgt mit der Funktionen
In der Hauptschleife wird die Funktion Engine::RenderWorld(); aufgerufen um die Objekte zu rendern.
Eine Vollständige oyname-Anwendung sieht so aus:
Code: Alles auswählen
int main()
{
Engine::Graphics(x,y,windowed);
while (gdx::MainLoop() && !(GetAsyncKeyState(VK_ESCAPE) & 0x8000))
{
Engine::RenderWorld();
Engine::Flip();
}
return gdx::ShutDown();
}
Primitive Rendern
Die Daten für die Vertices werden in einem Speicherort gespeichert die ich SURFACE nenne. Weil eine SURFACE immer zu einem MESH gehört muss als erstes ein MESH erstellt werden. Ein MESH speichert Daten über die Rotation und Position.
Kamera und Licht in der Szene
Doch als erstes braucht man eine Kamera damit man die Szene anschauen kann und Licht damit auch was zu sehen ist.
Code: Alles auswählen
#include "gidx.h"
int main()
{
Engine::Graphics(1024, 768);
// Creating camera object
LPCAMERA camera;
Engine::CreateCamera(&camera);
// Positioning the camera
Engine::MoveEntity(camera, 0.0f, 0.0f, -7.0f);
// Creating light
LPLIGHT light;
Engine::CreateLight(&light, D3DLIGHTTYPE::D3DLIGHT_DIRECTIONAL);
Engine::RotateEntity(light, 0.0f, 0.0f, 0.0f);
Nachdem man ein Fenster mit der Funktion Graphics erstellt hat, verwendet man die Funktion CreateCamera(), um eine Kamera zu erstellen. Durch die Funktion MoveEntity() bewegen wir die Kamera entlang der Z-Achse um 7 Einheiten nach hinten. Alternativ könnte man die Kamera auch direkt an eine definierte Stelle mit der Funktion PositionEntity() platzieren.
Damit die Objekte sichtbar werden, benötigen wir Licht. Derzeit ist nur gerichtetes Licht möglich, das als Sonne in unserer Szene agiert und aus dem gleichen Winkel auf alle Objekte scheint. Das Licht wird mit der Funktion CreateLight() erstellt. Durch die Funktion RotateEntity() könnte man das Licht auch rotieren lassen und somit aus verschiedenen Winkeln auf die Objekte in der Szene scheinen lassen.
Um einen höheren Detailgrad zu simulieren, werde ich noch eine Textur laden. Auch hier ist das Vorgehen vergleichbar mit dem der Kamera oder des Lichts. Man definiert eine Textur und kann sie mit LoadTexture() in den Speicherbereich laden, um sie später zu verwenden
Code: Alles auswählen
LPTEXTURE texture = nullptr
Engine::LoadTexture(&texture, L"..\\oyname\\Texture\\test4.bmp");
Als nächstes benötigen wir ein MESH-Objekt, das Informationen über die Position, Rotation und Skalierung speichert. Wenn man ein MESH erstellt und keine weiteren Parameter bei der Funktion CreateMesh() angibt, wird das MESH dem (Standard)BRUSH-Objekt angefügt. Das BRUSH-Objekt ist für die Textur und das Material zuständig, wobei der aktuelle Stand der Engine kein Material zur Verfügung stellt. Wenn man nun das MESH erstellt, kann man mit der Funktion EntityTexture() dem MESH eine Textur zuweisen. Tatsächlich wird die Textur mit der Funktion EntityTexture() im BRUSH gespeichert, das das MESH verwaltet. Ebenso ist es möglich, die Textur direkt dem BRUSH zuzuweisen. Diese Änderung wirkt sich auf alle Objekte aus, die zum selben BRUSH gehören.
Code: Alles auswählen
// Speichert Position, Rotation und Skalierung
LPMESH quad = NULL;
Engine::CreateMesh(&quad);
Engine::EntityTexture(quad, texture);
// Speicherplatz für die Vertices
LPSURFACE quadData = NULL;
Engine::CreateSurface(&quadData, quad);
Nach dem man ein MESH erstellt hat kann man das SURFACE erstellen und es dem MESH anhängen. Wurde erfolgreich eine SURFACE erstellt beginnt man mit dem Befüllen mit Daten. Dafür gibt es vier Funktionen und ein die aus den Vertices ein Triangel zusammenbaute.
Vertexdaten erstellen
Die Funktion AddVertex fügt einen Vertex zu einem Mesh hinzu. Ein Vertex repräsentiert einen Punkt im dreidimensionalen Raum. Die Parameter (-1.0f, -1.0f, -1.0f) geben die X-, Y- und Z-Koordinaten des Vertex an. Diese Funktion wird verwendet, um die Geometrie von Objekten innerhalb der Engine zu definieren.
VertexNormal setzt die Normalen eines Vertex. Normale sind Vektoren, die die Ausrichtung oder Richtung eines Polygons im Raum definieren. In diesem Fall zeigt die Normale in Richtung der negativen Z-Achse (-1.0f), was darauf hinweist, dass das Polygon nach hinten gerichtet ist. Normale sind wichtig für Beleuchtungsberechnungen und Oberflächenreflexionen.
Die Funktion VertexColor setzt die Farbe eines Vertex. Die Parameter 224, 224, 224 repräsentieren die Rot-, Grün- und Blaukomponenten der Farbe. Diese Funktion wird verwendet, um die visuelle Darstellung von Objekten zu definieren. Die Farbe wird oft in Kombination mit Beleuchtungseffekten und Texturen verwendet, um realistische Grafiken zu erzeugen.
VertexTexCoord setzt die Texturkoordinaten eines Vertex. Texturkoordinaten bestimmen, wie eine Textur auf die Oberfläche eines Objekts projiziert wird. Die Parameter (0.0f, 1.0f) könnten beispielsweise angeben, dass die Textur an der unteren linken Ecke des Polygons beginnt. Texturierung ist ein wichtiger Aspekt der visuellen Darstellung von Objekten in einer 3D-Umgebung.
Die Funktion AddTriangle fügt ein Dreieck zu einem Mesh hinzu, indem sie die Indizes der Vertices angibt, aus denen das Dreieck besteht. Die Parameter (0, 1, 2) geben die Indizes der Vertices an, die dieses Dreieck bilden. Diese Funktion wird verwendet, um die Oberflächenstruktur von Objekten zu definieren und komplexe Formen aus einfachen Polygonen aufzubauen. Dreiecke sind grundlegende Bausteine für die Darstellung von 3D-Objekten und ermöglichen die Modellierung verschiedener Formen und Strukturen.
Zum Schluss müssen die Daten noch in den Buffer verschoben und werden und sind somit bereit von der Engine verwendet zu werden.
Die Funktion FillBuffer befüllt einen Buffer mit den Daten des Meshes, die zuvor definiert wurden. Ein Buffer ist ein Speicherbereich, der die geometrischen und anderen Eigenschaften von Objekten enthält, die von der Grafikkarte gerendert werden sollen. Diese Funktion wird normalerweise am Ende der Objekterstellung verwendet, um die Daten für die weitere Verarbeitung durch die Grafikkarte vorzubereiten.
Schleifenbedingung:
Code: Alles auswählen
while (gdx::MainLoop() && !(GetAsyncKeyState(VK_ESCAPE) & 0x8000))
{
Engine::RenderWorld();
Engine::Flip();
}
return gdx::ShutDown();
Die Schleife wird so lange ausgeführt, wie die Funktion gdx::MainLoop() true zurückgibt und die Escape-Taste nicht gedrückt wurde.
Engine-Rendering:
Innerhalb der Schleife wird die Funktion Engine::RenderWorld() aufgerufen. Diese Funktion ist verantwortlich für das Rendern der 3D-Welt oder Szene, die in der Engine definiert wurde. Hier wird die visuelle Darstellung der Szene erzeugt, basierend auf den Objekten, die zuvor in der Engine festgelegt wurden.
Bildschirmausgabe aktualisieren:
Nachdem die Welt gerendert wurde, wird die Funktion Engine::Flip() aufgerufen. Diese Funktion aktualisiert die Ausgabe auf dem Bildschirm.
Programmende:
Wenn die Schleife beendet ist (entweder weil gdx::MainLoop() false zurückgibt oder die Escape-Taste gedrückt wurde), wird die Funktion gdx::ShutDown() aufgerufen, um das Programm sauber zu beenden. Diese Funktion führt Aufräumarbeiten wie das Freigeben von Ressourcen durch.