Auf den Kopf gestellte Welt

Um ein neues Projekt zu entwickeln, hat Cube Art Project die Test Driven Development-Methodik übernommen. Bei diesem Ansatz wird zunächst ein Test für eine bestimmte Funktionalität der Anwendung durchgeführt und anschließend die spezifische Funktionalität implementiert. Den großen Vorteil dieses Ansatzes sehe ich darin, dass die finalen Schnittstellen möglichst unbeeinflusst von Implementierungsdetails implementiert werden, bevor mit der Entwicklung der Funktionalität begonnen wird. Bei diesem Ansatz bestimmt der Test die weitere Implementierung und bietet alle Vorteile der Vertragsprogrammierung, wenn es sich bei Schnittstellen um Verträge für eine bestimmte Implementierung handelt.
Würfelkunstprojekt – Ein 3D-Editor, in dem der Benutzer Figuren aus Würfeln baut; vor nicht allzu langer Zeit war dieses Genre sehr beliebt. Da es sich um eine grafische Anwendung handelt, habe ich beschlossen, Tests mit Screenshot-Validierung hinzuzufügen.
Um Screenshots zu validieren, müssen Sie sie aus dem OpenGL-Kontext abrufen. Dies geschieht mit der glReadPixels-Funktion. Die Beschreibung der Funktionsargumente ist einfach – Startposition, Breite, Höhe, Format (RGB/RGBA/usw.), Zeiger auf Ausgabepuffer; jeder, der mit SDL gearbeitet hat oder Erfahrung mit Datenpuffern in C hat, ersetzt einfach die notwendigen Argumente. Ich halte es jedoch für notwendig, eine interessante Funktion des glReadPixels-Ausgabepuffers zu beschreiben: Pixel werden darin von unten nach oben gespeichert, während in SDL_Surface alle grundlegenden Operationen von oben nach unten erfolgen.
Das heißt, nachdem ich einen Referenz-Screenshot aus einer PNG-Datei geladen hatte, konnte ich die beiden Puffer nicht direkt vergleichen, da einer von ihnen auf dem Kopf stand.
Um den Ausgabepuffer von OpenGL umzudrehen, müssen Sie ihn füllen, indem Sie die Screenshot-Höhe von der Y-Koordinate subtrahieren. Es ist jedoch zu bedenken, dass die Möglichkeit besteht, dass die Puffergrenzen überschritten werden, wenn Sie beim Füllen nichts subtrahieren zu Speicherbeschädigungen führen.
Da ich immer versuche, das OOP-Paradigma der „Programmierung durch Schnittstellen“ anstelle des direkten C-ähnlichen Speicherzugriffs durch Zeiger zu verwenden, hat mich das Objekt beim Versuch, Daten außerhalb des Puffers zu schreiben, dank der Grenzvalidierung in der Methode darüber informiert .
Der endgültige Code für die Methode zum Erstellen eines Screenshots im Top-Down-Stil:

    auto width = params->width;
    auto height = params->height;

    auto colorComponentsCount = 3;
    GLubyte *bytes = (GLubyte *)malloc(colorComponentsCount * width * height);
    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, bytes);

    auto screenshot = make_shared(width, height);

    for (auto y = 0; y < height; y++) {
        for (auto x = 0; x < width; x++) {
            auto byteX = x * colorComponentsCount;
            auto byteIndex = byteX + (y * (width * colorComponentsCount));
            auto redColorByte = bytes[byteIndex];
            auto greenColorByte = bytes[byteIndex + 1];
            auto blueColorByte = bytes[byteIndex + 2];
            auto color = make_shared(redColorByte, greenColorByte, blueColorByte, 255);
            screenshot->setColorAtXY(color, x, height - y - 1);
        }
    }

    free(bytes);

Quellen

https://community.khronos.org/ t/glreadpixels-fliped-image/26561
https://stackoverflow.com/questions/8346115/why-are-bmps-stored-upside-down

Quellcode

https://gitlab.com/demensdeum/cube- Kunstprojekt-Bootstrap

WebGL + SDL + Emscripten

Am Ende habe ich Mika mit SDL 1 und Emscripten auf WebGL portiert.

Als nächstes beschreibe ich, was im Code geändert werden musste, damit der Build in JavaScript erfolgreich abgeschlossen werden konnte.

  1. Verwenden Sie SDL 1 anstelle von SDL 2. Im Moment gibt es einen SDL 2-Port für Emscripten, aber ich fand es angemessener, den in Emscripten integrierten SDL 1 zu verwenden. Der Kontext wird nicht im Fenster initialisiert, sondern mithilfe von SDL_SetVideoMode und dem SDL_OPENGL-Flag. Der Puffer wird mit dem Befehl SDL_GL_SwapBuffers()
  2. gezeichnet

  3. Aufgrund der Art und Weise, wie JavaScript Schleifen durchführt – Das Rendering wird in einer separaten Funktion platziert und der regelmäßige Aufruf erfolgt über die Funktion emscripten_set_main_loop
  4. Die Montage muss ebenfalls mit dem Schlüssel “-s FULL_ES2=1
  5. erfolgen

  6. Ich musste die Assimp-Bibliothek aufgeben, das Modell aus dem Dateisystem laden und die Textur von der Festplatte laden. Alle notwendigen Puffer wurden auf die Desktop-Version geladen und zur Assemblierung mit emscripten.
  7. in die C-Header-Datei eingefügt

Code:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/9-sdl-gles-obj-textured-assimp-miku-webgl/mikuWebGL

Artikel:
http://blog.scottlogic.com/2014/03/12/native-code-emscripten-webgl-simmer-gently.html
https://kripken.github.io/emscripten-site/docs/porting/multimedia_and_graphics/OpenGL-support.html

Modell:
https://sketchfab.com/models/7310aaeb8370428e966bdcff414273e7

Es gibt nur Miku

Das Ergebnis der Arbeit an der FSGL-Bibliothek mit OpenGL ES und Code:

Als nächstes werde ich beschreiben, wie alles programmiert wurde und verschiedene interessante Probleme gelöst wurden.

Zuerst initialisieren wir den OpenGL ES-Kontext, wie ich im vorherigen Beitrag geschrieben habe. Im Folgenden betrachten wir nur das Rendern und eine kurze Beschreibung des Codes.

Die Matrix beobachtet dich

Diese Figur von Miku im Video besteht aus Dreiecken. Um in OpenGL ein Dreieck zu zeichnen, müssen Sie drei Punkte mit den Koordinaten x, y, z angeben. in 2D-Koordinaten des OpenGL-Kontexts.
Da wir eine Figur zeichnen müssen, die 3D-Koordinaten enthält, müssen wir eine Projektionsmatrix verwenden. Wir müssen das Modell auch drehen, vergrößern oder was auch immer wir tun möchten – Hierzu wird die Modellmatrix verwendet. In OpenGL gibt es kein Konzept einer Kamera; tatsächlich drehen sich Objekte um eine statische Kamera. Hierzu wird eine Ansichtsmatrix verwendet.

Um die Implementierung von OpenGL ES zu vereinfachen – es enthält keine Matrixdaten. Sie können Bibliotheken verwenden, die fehlende Funktionalität hinzufügen, zum Beispiel GLM.

Shader

Um es dem Entwickler zu ermöglichen, alles und in irgendeiner Weise zu zeichnen, muss OpenGL ES Vertex- und Fragment-Shader implementieren. Der Vertex-Shader muss Rendering-Koordinaten als Eingabe erhalten, Transformationen mithilfe von Matrizen durchführen und die Koordinaten an gl_Position übergeben. Fragment- oder Pixel-Shader – Zeichnet bereits Farbe/Textur, wendet Überlagerung an usw.

Ich habe Shader in GLSL geschrieben. In meiner aktuellen Implementierung sind Shader als C-Strings

direkt in den Hauptanwendungscode integriert

Puffer

Der Vertex-Puffer enthält die Koordinaten der Scheitelpunkte (Vertices); dieser Puffer enthält auch Koordinaten für die Texturierung und andere für Shader notwendige Daten. Nachdem Sie den Vertex-Puffer generiert haben, müssen Sie den Zeiger an die Daten für den Vertex-Shader binden. Dies erfolgt mit dem Befehl glVertexAttribPointer, bei dem Sie die Anzahl der Elemente, einen Zeiger auf den Anfang der Daten und die Schrittgröße angeben müssen, die zum Durchlaufen des Puffers verwendet wird. In meiner Implementierung erfolgt die Bindung von Scheitelpunktkoordinaten und Texturkoordinaten für den Pixel-Shader. Es ist jedoch anzumerken, dass die Übertragung der Daten (Texturkoordinaten) an den Fragment-Shader über den Vertex-Shader erfolgt. Um dies zu erreichen, werden die Koordinaten mit Varying angegeben.

Damit OpenGL weiß, in welcher Reihenfolge Punkte für Dreiecke gezeichnet werden müssen – Sie benötigen einen Indexpuffer (Index). Der Indexpuffer enthält die Scheitelpunktnummer im Array. Unter Verwendung dreier solcher Indizes wird ein Dreieck erhalten.

Texturen

Zuerst müssen Sie eine Textur für OpenGL laden/generieren. Hierzu habe ich SDL_LoadBMP verwendet, die Textur wird aus einer BMP-Datei geladen. Es ist jedoch zu beachten, dass nur 24-Bit-BMPs geeignet sind und die Farben darin nicht in der üblichen RGB-Reihenfolge, sondern in BGR gespeichert werden. Das heißt, nach dem Laden müssen Sie den roten Kanal durch einen blauen ersetzen.
Texturkoordinaten werden im Format angegeben UV< /a>, das heißt, Sie müssen nur zwei Koordinaten übertragen. Die Texturausgabe erfolgt im Fragment-Shader. Dazu müssen Sie die Textur in einen Fragment-Shader binden.

Nichts Besonderes

Da OpenGL gemäß unseren Anweisungen 3D bis 2D zeichnet – dann um die Tiefe zu implementieren und unsichtbare Dreiecke auszuwählen – Sie müssen Culling und einen Tiefenpuffer (Z-Puffer) verwenden. In meiner Implementierung ist es mir gelungen, die manuelle Generierung des Tiefenpuffers mithilfe von zwei Befehlen zu vermeiden: glEnable(GL_DEPTH_TEST); und Auswahlen glEnable(GL_CULL_FACE);
Stellen Sie außerdem sicher, dass die Nahebene für die Projektionsmatrix größer als Null ist, denn Die Überprüfung der Tiefe mit einer Null-Nahebene funktioniert nicht.

Rendering

Um den Vertex-Puffer und den Index-Puffer mit etwas Bewusstem zu füllen, zum Beispiel dem Miku-Modell, müssen Sie dieses Modell laden. Hierfür habe ich die Bibliothek assimp verwendet. Miku wurde in eine Wavefront-OBJ-Formatdatei eingefügt, mit assimp geladen und die Datenkonvertierung von assimp in Vertex- und Indexpuffer wurde implementiert.

Das Rendern erfolgt in mehreren Schritten:

  1. Rotieren Sie Miku mithilfe der Modellmatrixrotation
  2. Bildschirm und Tiefenpuffer löschen
  3. Zeichnen von Dreiecken mit dem glDrawElements-Befehl.

Nächste Stufe – Implementierung des Renderings in WebGL mit Emscripten.

Quellcode:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/8-sdl-gles-obj-textured-assimp-miku
Modell:
https://sketchfab.com/models/7310aaeb8370428e966bdcff414273e7

 

Projizieren Sie es

Nachdem ich eine rote Teekanne in 3D gezeichnet habe, betrachte ich es als meine Pflicht, kurz zu beschreiben, wie es gemacht wird.

Modernes OpenGL zeichnet nicht 3D, sondern nur Dreiecke, Punkte usw. in 2D-Bildschirmkoordinaten.
Um zumindest etwas mit OpenGL auszugeben, müssen Sie einen Vertex-Puffer bereitstellen, einen Vertex-Shader schreiben, alle erforderlichen Matrizen (Projektion, Modell, Ansicht) zum Vertex-Shader hinzufügen und alle Eingabedaten damit verknüpfen Rufen Sie für den Shader die Methode Rendering in OpenGL auf. Sieht es einfach aus?


Ok, was ist ein Vertex-Puffer? Liste der zu zeichnenden Koordinaten (x, y, z)
Der Vertex-Shader teilt der GPU mit, welche Koordinaten gezeichnet werden sollen.
Der Pixel-Shader sagt, was gezeichnet werden soll (Farbe, Textur, Mischung usw.)
Die Matrizen übersetzen 3D-Koordinaten in 2D-OpenGL-Koordinaten, die gerendert werden können

In den folgenden Artikeln werde ich Codebeispiele und Ergebnisse bereitstellen.

SDL2 – OpenGL ES

I love Panda3D game engine. But right now this engine is very hard to compile and debug on Microsoft Windows operation system. So as I said some time ago, I begin to develop my own graphics library. Right now it’s based on OpenGL ES and SDL2.
In this article I am going to tell how to initialize OpenGL ES context and how SDL2 helps in this task. We are going to show nothing.

King Nothing

First of all you need to install OpenGL ES3 – GLES 3 libraries. This operation is platform dependant, for Ubuntu Linux you can just type sudo apt-get install libgles2-mesa-dev. To work with OpenGL you need to initialize OpenGL context. There is many ways to do that, by using one of libraries – SDL2, GLFW, GLFM etc. Actually there is no one right way to initialize OpenGL context, but I chose SDL2 because it’s cross-platform solution, code will look same for Windows/*nix/HTML5/iOS/Android/etc.

To install sdl2 on Ubuntu use this command sudo apt-get install libsdl2-dev

So here is OpenGL context initialization code with SDL2:

    SDL_Window *window = SDL_CreateWindow(
            "SDL2 - OGLES",
            SDL_WINDOWPOS_UNDEFINED,
            SDL_WINDOWPOS_UNDEFINED,
            640,
            480,
            SDL_WINDOW_OPENGL
            );
	    

    SDL_GLContext glContext = SDL_GL_CreateContext(window);

After that, you can use any OpenGL calls in that context.

Here is example code for this article:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/3sdl-gles
https://github.com/demensdeum/OpenGLES3-Experiments/blob/master/3sdl-gles/sdlgles.cpp

You can build and test it with command cmake . && make && ./SDLGles