Implementierung einer DirectX11 basierenden 3D Engine in C++

Hier könnt ihr euch selbst, eure Homepage, euren Entwicklerstammtisch, Termine oder eure Projekte vorstellen.
Forumsregeln
Bitte Präfixe benutzen. Das Präfix "[Projekt]" bewirkt die Aufnahme von Bildern aus den Beiträgen des Themenerstellers in den Showroom. Alle Bilder aus dem Thema Showroom erscheinen ebenfalls im Showroom auf der Frontpage. Es werden nur Bilder berücksichtigt, die entweder mit dem attachement- oder dem img-BBCode im Beitrag angezeigt werden.

Die Bildersammelfunktion muss manuell ausgeführt werden, die URL dazu und weitere Details zum Showroom sind hier zu finden.

This forum is primarily intended for German-language video game developers. Please don't post promotional information targeted at end users.
Antworten
Benutzeravatar
gombolo
Establishment
Beiträge: 161
Registriert: 26.01.2011, 20:33

Implementierung einer DirectX11 basierenden 3D Engine in C++

Beitrag von gombolo »

Liebe Community,

in den letzten Tagen habe ich mich mit der Implementierung einer 3D-Engine auf Basis von DirectX 11 beschäftigt. Die Idee dazu hatte ich bereits vor einigen Jahren, genauer so 2002/20004 rum, als ich eine 2D-Version umsetzte. Viel später wagte ich mich dann an eine 3D-Version unter Verwendung von DirectX 9. Allerdings habe ich diese Version nie veröffentlicht, da ich mit der Qualität des Codes nicht zufrieden war.

Ich selbst bin ein Hobbyprogrammierer und widme meine Freizeit der Entwicklung von Software. Das können Spiele sein oder Software um Lotto zu spielen :) oder eben 2D/3D Engines...

Ich möchte jeden einladen der Lust hat sich die 3D Engine anzuschauen. Aktuell kann die Engine nicht viel bzw. es kann schon was, aber man sieht nicht viel. Was es kann ist bunte Rechtecke zu rendern : )

Ihr könnte auf dieser Seite im Entwicklungslog etwas über die Entwicklung lesen. Ist in englisch + deutsch

Und auf github könnte ihr Euch den Code anschauen. Ich freue mich über Feedback und hoffe das neue Ideen oder Vorschläge kommen.

Bild

und das ist der Code dazu:

Code: Alles auswählen

#include "gidx.h"

int main()
{
    Engine::Graphics(1024, 768);

    LPMESH camera;
    Engine::CreateCamera(&camera);

    // Brush erstellen / Jedes Brush ist nach dem erstellen ein Child vom Standard-Shader
    LPBRUSH brush;
    Engine::CreateBrush(&brush);

    // Vertexdaten werden hier gespeichert
    LPMESH mesh;
    Engine::CreateMesh(&mesh, brush);

    // Vertexdaten werden hier gespeichert
    LPSURFACE surface;
    Engine::CreateSurface(&surface, mesh);

    Engine::AddVertex(surface, -1.0f, -1.0f, 0.0f); Engine::VertexColor(surface, 0  , 255, 0);
    Engine::AddVertex(surface, -1.0f,  1.0f, 0.0f); Engine::VertexColor(surface, 0  ,   0, 255);
    Engine::AddVertex(surface,  1.0f, -1.0f, 0.0f); Engine::VertexColor(surface, 255,   0, 0);
    Engine::AddVertex(surface,  1.0f,  1.0f, 0.0f); Engine::VertexColor(surface, 255, 255, 0);

    Engine::AddTriangle(surface, 0, 1, 2);
    Engine::AddTriangle(surface, 1, 3, 2);
    
    Engine::FillBuffer(surface);

    while (gdx::MainLoop() && !(GetAsyncKeyState(VK_ESCAPE) & 0x8000))
    {
        Engine::Cls(32, 64, 128);

        Engine::engine->RenderWorld();

        Engine::Flip();
    }

    // Shutdown the engine
    return(gdx::ShutDown());
}
Zuletzt geändert von gombolo am 06.04.2024, 20:37, insgesamt 1-mal geändert.
Benutzeravatar
Lord Delvin
Establishment
Beiträge: 597
Registriert: 05.07.2003, 11:17

Re: Implementierung einer DirectX11 basierenden 3D Engine in C/C++

Beitrag von Lord Delvin »

Erinnert mich ein bisschen an das, was ich mit Tyr/SDL/OpenGL gebaut habe. Was genau ist dein Ziel?
Ich meine du baust was auf DirectX drauf, d.h. du willst irgendwelche Freiheiten beschneiden, damit irgendwas schneller und viel einfacher geht. Was its das?

Dir fehlt eine Lizenz. Ich würde Apache V2 nehmen, damit es tatsächlich ohne Bedenken nutzbar ist. Alles andere ist irgendwie schwierig.

C++. Nicht C/C++ ;)

Mein Plan mit meiner war eigentlich, sowas wie das, was man so Mitte-Ende-Neunziger hatte zu bauen. Aber selbst das ist zumindest unter meinen Rahmenbedingungen zu aufwändig.
XML/JSON/EMF in schnell: OGSS
Keine Lust mehr auf C++? Versuche Tyr: Get & Get started
Benutzeravatar
gombolo
Establishment
Beiträge: 161
Registriert: 26.01.2011, 20:33

Re: Implementierung einer DirectX11 basierenden 3D Engine in C++

Beitrag von gombolo »

Lord Delvin hat geschrieben: 01.04.2024, 09:02 Erinnert mich ein bisschen an das, was ich mit Tyr/SDL/OpenGL gebaut habe. Was genau ist dein Ziel?
Ich meine du baust was auf DirectX drauf, d.h. du willst irgendwelche Freiheiten beschneiden, damit irgendwas schneller und viel einfacher geht. Was its das?
Ich kopiere mal einfach den Text von meiner Beschreibung von der itch.io-Seite :)

Ziele und Grenzen des Projekts
Was eine 3D Engine jedoch sicherlich auch ist, ist, dass sie aufwendig zu entwickeln ist. Es ist sehr wichtig, sich realistische Ziele zu setzen, um die Motivation aufrechtzuerhalten und ein Ziel zu haben, das auch erreicht werden kann. Deshalb sind die Ziele und Grenzen des neuen Projekts fest definiert und können später erweitert werden, aber vorerst stehen sie fest.
Die Nutzung der Engine soll, wie bei den Vorgängerversionen, eine einfache Handhabung ermöglichen. Das Rendern von Primitiven sollte einfach sein. Es sollte möglich sein, Licht und Texturen zu verwenden. Positionieren, Rotieren und Skalieren von 3D-Objekten sollten umgesetzt werden, und einfache Kenntnisse in C sollten ausreichen, um mit der Engine arbeiten zu können. Was ich nicht umsetzen kann sind zusätzliche Funktionalitäten wie Physiksimulation, Partikeleffekte oder Netzwerkunterstützung.


...und ein ganz wichtiger Grund ist wie immer das erlernen und experimentieren :)
Lord Delvin hat geschrieben: 01.04.2024, 09:02 C++. Nicht C/C++ ;)
Habe ich mal geändert ;)
Lord Delvin hat geschrieben: 01.04.2024, 09:02 Dir fehlt eine Lizenz. Ich würde Apache V2 nehmen, damit es tatsächlich ohne Bedenken nutzbar ist. Alles andere ist irgendwie schwierig.
Danke für den Hinweis. Ich habe gar nicht darauf geachtet.
Benutzeravatar
TomasRiker
Beiträge: 99
Registriert: 18.07.2011, 11:45
Echter Name: David Scherfgen
Wohnort: Hildesheim

Re: Implementierung einer DirectX11 basierenden 3D Engine in C/C++

Beitrag von TomasRiker »

Da du um Feedback gebeten hast: Ich finde deine Namensgebung verwirrend. Sie entspricht nicht dem, was man normalerweise im Kontext von 3D-Engines erwarten würde: Was hat eine Kamera mit einem Mesh zu tun? Was hat eine Surface mit Vertices zu tun? Unter einer Kamera versteht man normalerweise ein abstraktes Ding, das selbst keine sichtbare Geometrie hat, und unter einer Surface versteht man normalerweise ein 2D-Array aus Pixeln. Was ist bei dir ein Brush? Darunter versteht man normalerweise Optionen zum Füllen von 2D-Formen.
Warum sind die Methoden überwiegend statisch, bis auf "RenderWorld"?
Benutzeravatar
gombolo
Establishment
Beiträge: 161
Registriert: 26.01.2011, 20:33

Re: Implementierung einer DirectX11 basierenden 3D Engine in C/C++

Beitrag von gombolo »

F: Was hat eine Kamera mit einem Mesh zu tun?

Du hast Recht. Die Kamera ist ein abstraktes "Ding". Meine Idee ist es später die Kamera, aber auch das Licht wie ein "Mesh" zu nutzen.

Doch "Mesh" ist in diesem Zusammenhang die falsche Bezeichnung. Es wird noch ein Objekt geben das übergeordnet alle diese Informationen beinhaltet. Das habe ich noch nicht implementiert, aber es wird dann keinen Unterschied machen ob ich ein Licht, eine Kamera oder ein 3D Objekt im Raum bewege...mit den selben Funktionen.

Mein erstes Ziel ist es auf einfacher Weise texturierte Primitive zu render.

F: unter einer Surface versteht man normalerweise ein 2D-Array aus Pixeln.

Das Surface beinhaltet Die Vertex- und Indexbuffer.

F: Warum sind die Methoden überwiegend statisch, bis auf "RenderWorld"?

Das Projekt ist noch in der Entwicklung und ich habe das einfach so gemacht...wird noch geändert.


Hier eine Erklärung zum Objektmanager:

SHADER: Enthält Informationen über Vertex- und Pixelshader, einschließlich des Binärdaten-Bytecodes. Es verwaltet auch das Input-Layout und enthält eine Liste von Brushes, die diesen Shader verwenden.

BRUSH: Beschreibt das Aussehen eines Meshes mit Eigenschaften wie Glanz, Transparenz und Farbe. Es verwaltet Texturen und Materialien, die auf das Mesh angewendet werden. Ein Pointer auf eine Liste von Meshes, die denselben Brush verwenden, wird ebenfalls gespeichert.

MESH: Repräsentiert ein 3D-Objekt im Raum. Es speichert Transformationen wie Translation, Rotation und Skalierung sowie 3D Daten in Surface, die dem Mesh zugeordnet sind. Ein Pointer auf den konstanten Puffer für das Mesh und den zugehörigen Shader wird ebenfalls gespeichert.

SURFACE: Stellt eine Oberfläche dar, die aus einer Reihe von Vertices und Farben besteht. Diese Struktur verwaltet auch die DirectX-Buffer für die Positionen, Farben und Indizes der Oberfläche. Ein Pointer auf einen Shader, der für die Darstellung der Oberfläche verwendet wird, wird ebenfalls gespeichert.

Bild

Die ObjectManager-Klasse fungiert als Schnittstelle zur Erstellung, Verwaltung und Löschung von Oberflächen, Meshes, Brushes und Shadern. Sie ermöglicht auch das Hinzufügen und Entfernen von Oberflächen von Meshes, Meshes von Brushes und Brushes von Shadern.

Wer soll das alles nur rendern?
Eigentlich könnte der Objektmanager auch alles gleich rendern. Doch im Interesse einer besseren Übersicht macht es Sinn, Aufgaben zu teilen. Der Objektmanager übernimmt bereits viele Aufgaben, daher ist es sinnvoll, eine separate Klasse zu erstellen, die sich ausschließlich um das Rendering der von ihm verwalteten Primitiven kümmert. Diese Klasse, beispielsweise als RenderManager bezeichnet, hat die Hauptaufgabe, die Objekte zu rendern, die vom Objektmanager verwaltet werden. Durch diese Trennung bleibt der Objektmanager auf die Verwaltung von Objekten spezialisiert, während der RenderManager sich auf das Rendering konzentriert. Dies erleichtert das Debuggen, Testen und Erweitern der Software, da Änderungen in einem Bereich die anderen nicht beeinflussen sollten, solange die Schnittstellen zwischen den Klassen erhalten bleiben.

Trotzdem muss ich gestehen, dass ich mich für einen Weg entschieden habe, der beide Klassen eng miteinander verbindet. Über die friend class RenderManager;-Deklaration in der ObjectManager-Klasse ermögliche ich dem RenderManager den Zugriff auf private und geschützte Mitglieder der ObjectManager-Klasse. Dies kann in bestimmten Situationen sinnvoll sein, insbesondere wenn der RenderManager spezielle Zugriffe benötigt, um beispielsweise Rendering-Operationen durchzuführen oder Ressourcen zu verwalten. Durch diese Entscheidung reduziere ich sicher nicht die Abhängigkeit beider Klassen, aber ich denke, dass es in diesem Fall gerechtfertigt ist, insbesondere weil beide Klassen eng zusammenarbeiten.

Bild
Benutzeravatar
TomasRiker
Beiträge: 99
Registriert: 18.07.2011, 11:45
Echter Name: David Scherfgen
Wohnort: Hildesheim

Re: Implementierung einer DirectX11 basierenden 3D Engine in C/C++

Beitrag von TomasRiker »

Dein "Surface" nennt man normalerweise Mesh (= Vertices, Indices und Verweise auf Materialien).
Deinen "Brush" würde ich als Material bezeichnen (= Verweis auf Shader und Shader-Parameter).
Dein "Mesh" wäre eher ein Objekt (= Verweis auf ein Mesh sowie Position, Rotation, Skalierung).
Nur als Anregung, um potenzielle Nutzer nicht zu verwirren.
Benutzeravatar
gombolo
Establishment
Beiträge: 161
Registriert: 26.01.2011, 20:33

Re: Implementierung einer DirectX11 basierenden 3D Engine in C/C++

Beitrag von gombolo »

TomasRiker hat geschrieben: 01.04.2024, 15:23 Dein "Surface" nennt man normalerweise Mesh (= Vertices, Indices und Verweise auf Materialien).
Deinen "Brush" würde ich als Material bezeichnen (= Verweis auf Shader und Shader-Parameter).
Dein "Mesh" wäre eher ein Objekt (= Verweis auf ein Mesh sowie Position, Rotation, Skalierung).
Nur als Anregung, um potenzielle Nutzer nicht zu verwirren.
Deine Vorschläge sind wichtig um Begriffe zu verwenden die geläufig sind und von allen verstanden werden. Sicher wird es im laufe der Zeit ein paar Anpassungen geben.

In meiner Vorstellung ist eine Surface ein Container für Daten, insbesondere für Vertex- und Indexdaten. Diese Daten gehören zu einem übergeordneten Objekt, das ich als Mesh bezeichne. Dieses Mesh vereint die verschiedenen Komponenten zu einem 3D-Objekt. Die Entscheidung, Surfaces und Meshes zu trennen, war eine Überlegung für mögliche spätere Optimierungen. Zum Beispiel könnte das Mesh basierend auf seiner Position zur Kamera oder im 3D-Raum sortiert werden, um die Renderleistung zu verbessern. Oder die maximale Ausdehnung im Raum speichern, aber so weit bin ich noch nicht.
Benutzeravatar
gombolo
Establishment
Beiträge: 161
Registriert: 26.01.2011, 20:33

Re: Implementierung einer DirectX11 basierenden 3D Engine in C/C++

Beitrag von gombolo »

{UPDATE]
Ich weiß nicht, ob sich jemand den Text antut. Softwareentwicklung, insbesondere wenn es um etwas so Komplexes wie die Entwicklung einer 3D-Engine geht, bei der viele Aspekte berücksichtigt werden müssen, ist es unmöglich, die Funktionen und die Arbeitsweise in wenigen Worten zu beschreiben. Sollte sich doch jemand das antun, dann danke ich für Eure Zeit.

In meinem ersten Lösungsansatz habe ich die Objekte, die ich zur Verwaltung der Daten benötige, als Strukturen (struct) gespeichert, und der Objektmanager hat diese Strukturkonstruktionen verwaltet. Jedoch musste ich feststellen, dass es hinsichtlich der Organisation und Lesbarkeit sinnvoller ist, diese Strukturen in Klassen zu ändern. Im Grunde ändert sich nichts an der Verwaltung durch die Objektmanager-Klasse, aber die Klassen haben jetzt ihre eigenen Methoden. Dadurch wird die Pflege und Wartung einfacher, denke ich. Ein einfaches Beispielprogramm verdeutlicht dies besser.
Bilder sagen mehr als tausend Worte. Man könnte auch sagen, dass ein Programmcode mehr aussagt als tausend Worte. Zur Testung der Engine habe ich natürlich ein Testprogramm entwickelt, das ich hier kurz vorstellen möchte und auf die einzelnen Befehle eingehe.

Code: Alles auswählen

 LPMESH camera;
 Engine::CreateCamera(&camera);

 // Brush erstellen / Jedes Brush ist nach dem erstellen ein Child vom Standard-Shader
 LPBRUSH brush;
 Engine::CreateBrush(&brush);

 // Vertexdaten werden hier gespeichert
 LPMESH mesh;
 Engine::CreateMesh(&mesh, brush);

 LPMESH mesh2;
 Engine::CreateMesh(&mesh2, brush);

 LPMESH mesh3;
 Engine::CreateMesh(&mesh3, brush);

 // Vertexdaten werden hier gespeichert
 LPSURFACE wuerfel;
 Engine::CreateSurface(&wuerfel, mesh);

 LPSURFACE pyramide;
 Engine::CreateSurface(&pyramide, mesh3);

 Engine::engine->GetMM().addSurfaceToMesh(mesh2, wuerfel);
In diesem Abschnitt wird gezeigt, wie man einen Brush erstellt. Anschließend werden drei Meshes erstellt, die zu diesem Brush gehören, sowie ein Surface, das später die Vertexdaten für einen Würfel speichern soll, und ein Surface für die Vertexdaten einer Pyramide. Es sollte nicht auf die Aufrufe der Funktionen geachtet werden, da die Engine sich noch in der Entwicklung befindet. Bei genauerer Betrachtung ist erkennbar, dass ein Surface, das vom Würfel stammt, an zwei Meshes übergeben wird. Dies ist auch in Ordnung, da dadurch vermieden wird, dass die gleichen Daten mehrfach im Speicher vorhanden sind. Das Mesh kann auf den Speicherbereich zugreifen, in dem die Vertices für den Würfel abgelegt sind.

Bild

Sobald alles soweit fertig ist, können die Objekte positioniert und rotiert werden, wie in diesem Codeausschnitt zu sehen ist. Auch hierbei sollte nicht auf die Funktionsaufrufe geachtet werden.

Code: Alles auswählen

    camera->PositionEntity(0.0f, 10.0f, -15.0f);
    camera->RotateEntity(35.0f, 0.0f, 0.0f);

    mesh->RotateEntity(0.0f, 30.0f, 0.0f);
    mesh->PositionEntity(-5.0f, 0.0f, 0.0f);

    mesh2->RotateEntity(0.0f, 0.0f, 0.0f);
    mesh2->PositionEntity(0.0f, 0.0f, 5.0f);

    mesh3->RotateEntity(-90.0f, 0.0f, 0.0f);
    mesh3->PositionEntity(5.0f, 0.0f, 0.0f);

    mesh3->RotateEntity(0, -90, 0);
In einer Schleife können nun die Objekte beispielsweise rotiert oder bewegt werden. Ein weiterer Hinweis: Die Methode 'TurnEntity' addiert bei jedem Aufruf den Winkel zur aktuellen Rotation hinzu, während 'RotateEntity' einen definierten Winkel einstellt.

Code: Alles auswählen

    while (gdx::MainLoop() && !(GetAsyncKeyState(VK_ESCAPE) & 0x8000))
    {
        Engine::Cls(32, 64, 128);

        mesh->TurnEntity(0.0f, 0.0f, 1.0f);

        mesh2->TurnEntity(1.0f, 0.0f, 0.0f);
        mesh3->TurnEntity(0.0f, 0.0f, 1.0f);
        mesh3->MoveEntity(0, 0, 0.1);
        mesh3->TurnEntity(0.0f, 0.0f, 0.5f, Space::World);

        Engine::RenderWorld();

        Engine::Flip();
    }
Weil für die Engine alle Objekte im 3D-Raum gleich sind, unabhängig davon, ob es sich um Licht oder die Kamera handelt, ist es auch möglich, mit einem einfachen Befehlsaufruf eines dieser Objekte zu interagieren. Zum Beispiel kann die Pyramide zur Kamera gemacht werden. Nach dem Aufruf dieser Funktion sieht der Betrachter die Welt aus der Perspektive der Pyramide entlang der positiven Z-Achse.

Bild

Bild
Benutzeravatar
gombolo
Establishment
Beiträge: 161
Registriert: 26.01.2011, 20:33

Re: Implementierung einer DirectX11 basierenden 3D Engine in C++

Beitrag von gombolo »

[UPDATE]

Ich muss leider mein kleines Projekt ruhen lassen, weil die Arbeit ruft, aber ich habe schon einiges umgesetzt und es funktioniert meiner Meinung nach nicht schlecht :) hoffe ich kann bald weiter machen. Macht viel Spass und man lernt wirklich viel dazu :)

Ich möchte hier einen C++ Code zeigen, der demonstrieren soll wie einfach man ein Würfel erstellen und rotieren kann.

Code: Alles auswählen

#include "gidx.h"

// OYNAME Engine
// 
// Here's a simple example demonstrating how to create vertices and connect 
// them to form a cube. Additionally, it shows how to load another shader to 
// the standard shader, which is automatically generated each time the engine 
// starts, and link it to one of the created cubes.

// Function declaration
void CreateCube(LPMESH* mesh, BRUSH* brush = nullptr);

int main()
{
    Engine::Graphics(1024, 768);

    // Creating camera object
    LPCAMERA camera;
    Engine::CreateCamera(&camera);
    // Positioning the camera
    Engine::PositionEntity(camera, 0.0f, 0.0f, -10.0f);

    // Creating light
    LPLIGHT light;
    Engine::CreateLight(&light);
    // Positioning the light
    Engine::PositionEntity(light, 0.0f, -5.0f, 0.0f);

    // The engine automatically creates a default shader and a 
    // default brush upon startup.In this example, I'm not creating 
    // either of these objects. Consequently, every mesh created 
    // is automatically attached to the default brush, which 
    // is linked to the default shader.

    // Create cube
    LPMESH cube;
    CreateCube(&cube);
    // Positioning the cube
    Engine::PositionEntity(cube, 0.0f, 0.0f, 0.0f);


    while (gdx::MainLoop() && !(GetAsyncKeyState(VK_ESCAPE) & 0x8000)) // Main loop
    {
        Engine::Cls(0, 64, 128);

        // Rotate cube
        Engine::TurnEntity(cube, 0.5f, 0.0f, 0.5f);

        Engine::RenderWorld();

        Engine::Flip();
    }

    // Shutdown the engine
    return gdx::ShutDown();
}

----------------------------------------------

Mit Licht wird es schöner

Bild
der kleine Punkt ob stellt die Position des Lichts dar. Bild links das Licht ist noch weiter hinten. Bild zwei das Bild bewegt sich und leuchtet die Objekt von vorne an.

​Eine 3D-Szene wird erst richtig interessant mit Licht. Unser Auge ist daran gewöhnt, dass Objekte mit dem Licht ihrer Umgebung interagieren. Dadurch entstehen Schatten und unterschiedliche Schattierungen des Lichts, obwohl das Objekt nur eine Farbe hat. Die Vielfalt an Licht und Schatten sucht unser Gehirn, wenn es sich eine 3D-Szene anschaut, oft unbewusst. Manchmal suchen wir sogar bewusst nach diesen Effekten in der simulierten Realität.

Überraschenderweise war es aufwendiger als gedacht, ein einfaches Licht zu implementieren. Mit "einfach" meine ich jetzt ein Licht, das von einer bestimmten Seite gerade auf das Objekt scheint. Das ist sozusagen die Sonne in unserer Szene. Schatten gibt es noch nicht, aber wir können das Licht bewegen, und das beeinflusst die Beleuchtung und damit auch die Schattierung der Objekte.


Was hat sich noch getan
Neben den Experimenten mit Licht und der Implementierung von gerichtetem Licht habe ich die Verwaltung der Objekte umgestellt. Ursprünglich war der Objektmanager neben der Erstellung der Objekte auch für deren Rotation und Bewegung verantwortlich. Diese Verantwortung habe ich nun auf die einzelnen Objekte verschoben. Das bedeutet, dass ein Mesh-Objekt, das auch die Daten für die Position und Rotation beinhaltet, auch für die Bewegung verantwortlich ist. Das Mesh besteht außerdem aus dem Surface-Objekt, das einen Speicherplatz repräsentiert und die Informationen über die Vertices und den Index speichert. Jetzt ist das Surface-Objekt verantwortlich für das Rendern. Diese Umgestaltung hat dazu geführt, dass der Rendermanager jetzt aufgeräumter ist und der Code im Rendermanager besser lesbar ist. Konkret bedeutet das, dass der Rendermanager sich vom Objektmanager das erste Objekt holt und durch alle Objekte iteriert, um deren Funktionen aufzurufen, bis zum letzten Objekt der Surface-Klasse, die dann die DirectX-Methode Draw aufruft.
Benutzeravatar
gombolo
Establishment
Beiträge: 161
Registriert: 26.01.2011, 20:33

[Projekt] Implementierung einer DirectX11 basierenden 3D Engine in C++

Beitrag von gombolo »

[UPDATE] jetzt hatte ich doch etwas Zeit um mich meinem kleinen Projekt zu widmen.
Quellcode auf: github

Bild anklicken [GIF]
2024-04-2723-09-37-ezgif.com-optimize.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

Code: Alles auswählen

Engine::Graphics(x,y,windowed);
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

Code: Alles auswählen

Engine::AddVertex(quadData, -1.0f, -1.0f, -1.0f);
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.

Code: Alles auswählen

Engine::VertexNormal(quadData, 0.0f, 0.0f, -1.0f);
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.

Code: Alles auswählen

Engine::VertexColor(quadData, 224, 224, 224);
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.

Code: Alles auswählen

Engine::VertexTexCoord(quadData, 0.0f, 1.0f);
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.

Code: Alles auswählen

Engine::AddTriangle(quadData, 0, 1, 2);
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.

Code: Alles auswählen

Engine::FillBuffer(quadData);
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.
Zuletzt geändert von gombolo am 29.04.2024, 09:00, insgesamt 4-mal geändert.
NytroX
Establishment
Beiträge: 389
Registriert: 03.10.2003, 12:47

Re: Implementierung einer DirectX11 basierenden 3D Engine in C++

Beitrag von NytroX »

Sieht gut aus, Glückwunsch dass du so weit gekommen bist.
Jetzt kannst du dir überlegen, was du als nächstes damit machen willst :-)
Benutzeravatar
gombolo
Establishment
Beiträge: 161
Registriert: 26.01.2011, 20:33

Re: Implementierung einer DirectX11 basierenden 3D Engine in C++

Beitrag von gombolo »

NytroX hat geschrieben: 28.04.2024, 12:34 Sieht gut aus, Glückwunsch dass du so weit gekommen bist.
Jetzt kannst du dir überlegen, was du als nächstes damit machen willst :-)
Bohaa..eigentlich gibt es so viel zu machen. 3D Model laden will ich noch implementieren. Ich denke ich nehme dafür assimp und Schatten und noch Lichter, dann kann man den Renderprozess optimieren, Kollision, Physik, Animation....es gibt so viel :D

Zu viel für einen einzelnen.
Benutzeravatar
**Achilles**
Beiträge: 7
Registriert: 01.04.2024, 19:25
Benutzertext: Blender Modellierer
Echter Name: Sven

Re: Implementierung einer DirectX11 basierenden 3D Engine in C++

Beitrag von **Achilles** »

Nun ich nutze gerne freie Engines oder teste es eben gerne an. Die letzte die ich angetestet hatte, war die Softpixel Engine davor die Irrlicht und OGRE. Nun es ist schade das Studenten etwas erstellen , aber komischerweise es nie weiter pflegen oder gar wieder einstellen. Schade jedenfalls. Zu deiner Engine kann ich leider nichts zu sagen. Es fehlt mir eben die Zeit diese erst zu lesen wie man es nutzt / anwendet / implementiert. Ich teste gerne meine 3D Modelle. Mir persönlich wäre es lieber du erstellst ein 3D Viewer mit deiner Engine. Vielleicht noch kleine Extras dazu.
Hier ein 10 Jahre Rückblick, und heute? Rockt Blender 4.0 :D
https://youtu.be/LEvoq38JJiY
Benutzeravatar
gombolo
Establishment
Beiträge: 161
Registriert: 26.01.2011, 20:33

Re: Implementierung einer DirectX11 basierenden 3D Engine in C++

Beitrag von gombolo »

Meine Engine ist nicht vergleichbar mit Irrlicht oder anderen weiter entwickelten Engines. Ich wollte nur ein paar Ideen umsetzen, einfach so... just for fun.

In Bezug auf einen 3D-Viewer: Mein Projekt ist auf GitHub und kann mit Visual Studio geklont werden. Es enthält auch eine Testapplikation, die ein paar sich drehende Würfel zeigt.

Dass vielversprechende Projekte nicht weitergeführt werden können, kann viele Gründe haben, von Zeitmangel bis hin zu anderen Prioritäten im Leben.

Edit: hier noch mal die Ziele und Grenzen dieses kleinen Projekts: ...Das Rendern von Primitiven sollte einfach sein. Es sollte möglich sein, Licht und Texturen zu verwenden. Positionieren, Rotieren und Skalieren von 3D-Objekten sollten umgesetzt werden, und einfache Kenntnisse in C sollten ausreichen, um mit der Engine arbeiten zu können. Was ich nicht umsetzen kann sind zusätzliche Funktionalitäten wie Physiksimulation, Partikeleffekte oder Netzwerkunterstützung.
Antworten