hellOGL: OpenGL hallo Welt

Heute werde ich zeigen, wie man ein Fenster öffnet und einen OpenGL-Kontext erstellt. Dies ist eine überraschend schwierige Aufgabe. OpenGL verfügt noch nicht über offizielle plattformübergreifende Tools zum Erstellen von Kontexten. Daher werden wir uns auf Bibliotheken von Drittanbietern verlassen (in diesem Fall GLFW und froh). Es gibt bereits viele ähnliche Hallo-Welten im Internet, aber ich mag nicht alles, was ich gesehen habe: Entweder ist es sehr raffiniert oder die Bilder in den Beispielen sind sehr primitiv ( oder beides !). Vielen Dank an alle Autoren, aber ich werde ein weiteres Tutorial herunterladen :)

Heute werden wir dies zeichnen:



Dieses Modell wurde vom Künstler Samuel Sharit (arshlevon) gezeichnet. Vielen Dank, dass ich es als Teil meines Vorlesungskurses über Computergrafik verwenden durfte!

Stufe 0: Lesen von tinyrenderer


Im Allgemeinen ist es am besten (wenn auch nicht notwendig), diese Vorlesung zu halten, nachdem ich meinen gesamten tinyrenderer- Kurs gelesen habe. Für diejenigen, die kein Englisch sprechen, ist dieser Vorlesungskurs im Hub verfügbar, obwohl ich die russische Version nicht mehr unterstütze. Im Rahmen dieser Vorlesung habe ich gezeigt, wie Sie genau dieses Bild in nur fünfhundert Codezeilen und sogar mit einem vollständigen Verbot von Bibliotheken von Drittanbietern zeichnen können:



Überraschenderweise verstehen viele meiner Schüler nicht, dass dieser Software-Rasterizer nicht nur ein Spielzeug ist, sondern eine echte Einführung in die Funktionsweise von OpenGL. Daher werde ich heute zeigen, wie man Diabetes mit Hardwarebeschleunigung rendert, und ich werde den Code aus dem Software-Rasterizer-Repository in vielerlei Hinsicht verwenden.

Achtung, ich habe mir nicht die Aufgabe gestellt, jede Codezeile zu erklären, da ich mich darauf verlasse, dass das Lesen der Dokumentation der beste Weg ist, alles zu verstehen. Mein Code wird nur benötigt, um zu wissen, was genau in der Dokumentation zu lesen ist und in welcher Reihenfolge. Außerdem werde ich nicht erklären, was Shader sind , und ich werde nicht erklären, wie man normale Karten liest . Ich habe viel Zeit mit tinyrenderer verbracht, wo alles geregelt ist.

Stufe eins, die schwierigste: ein Fenster erstellen


Das gesamte Repository lebt hier ; Für jeden Schritt des Tutorials wurde ein Commit erstellt, da der Github einen sehr praktischen Überblick über alle vorgenommenen Änderungen bietet. Wir fangen hier an , unser Ziel ist es, dieses Fenster zu bekommen:



Der Code wird mit CMake kompiliert. Ich habe unter Linux (g ++) und Windows (Visual Studio 2017) nachgesehen. Unter Linux wird die neueste Version des Codes wie folgt kompiliert:

git clone --recurse-submodules https://github.com/ssloy/hellOGL.git cd hellOGL mkdir build cd build cmake .. make 

Verwenden Sie "git checkout", wenn Sie ein separates Commit kompilieren möchten, nicht die neueste Version. Dieser Code lädt Glad und GLFW, erstellt ein Fenster mit dem erforderlichen Tastaturrückruf und lädt leere Vertex- und Pixel-Shader von der Festplatte.

Stufe zwei: Laden eines 3D-Modells


Zu diesem Zeitpunkt vorgenommene Änderungen im Projekt finden Sie hier . In dieser Phase ist es unser Ziel, die 3D-Modelldatei zu analysieren und die ersten Dreiecke zu zeichnen, ohne sich im Moment um die Beleuchtung zu kümmern:



Bitte beachten Sie, dass sowohl das Modell selbst als auch die Bibliothek für die Arbeit mit Vektoren und ich den Modellparser vollständig von tinyrenderer übernommen haben. Vielleicht ist Software-Renderer nicht so nutzlos?

Die Grundidee in modernem OpenGL ist sehr einfach. Wir haben zuerst ein 3D-Modell hochgeladen und dann ein Scheitelpunktarray der Größe 3 * 3 * (die Anzahl der Dreiecke) erstellt. Jedes Dreieck hat drei Eckpunkte, oder? Jeder Scheitelpunkt wird durch drei Zahlen (x, y, z) beschrieben. Insgesamt reicht 3 * 3 * model.nfaces () aus, um das gesamte Modell zu beschreiben:

  std::vector<GLfloat> vertices(3*3*model.nfaces(), 0); for (int i=0; i<model.nfaces(); i++) { for (int j=0; j<3; j++) { for (int k=0; k<3; k++) vertices[(i*3+j)*3 + k] = model.point(model.vert(i, j))[k]; } } 

Und dann sagen wir OpenGL, dass hier ein Array ist, zeichnen, native!

  while (!glfwWindowShouldClose(window)) { [...]  glDrawArrays(GL_TRIANGLES, 0, vertices.size()); [...] } 

Der Vertex-Shader macht nichts Interessantes, er übergibt die Daten einfach so wie sie sind an den Fragment-Shader:

 #version 330 core // Input vertex data, different for all executions of this shader layout(location = 0) in vec3 vertexPosition_modelspace; void main() { gl_Position = vec4(vertexPosition_modelspace, 1); // Output position of the vertex, in clip space } 

Nun, der Fragment-Shader ist auch unprätentiös. Es zeichnet einfach die aktuelle Gurke in rot:

 #version 330 core // Output data out vec3 color; void main() { color = vec3(1,0,0); } 

Das Schwierigste ist erledigt, jetzt ist es eine Frage der Technologie!

Stufe drei: Diffuse Beleuchtung


Zu diesem Zeitpunkt vorgenommene Änderungen im Projekt finden Sie hier . Wir sollten dieses Bild bekommen:



Wie Sie wissen, ist die diffuse Beleuchtung im Phong-Modell ein einfaches Skalarprodukt zwischen
normaler Vektor und Beleuchtungsvektor. Daher habe ich zusätzlich zum Vertices-Array ein weiteres Normalen-Array hinzugefügt. Sagen Sie mir, ohne auf den Code zu schauen, wie groß er ist.

Das Interessanteste ist, dass im Fragment-Shader in der CPP-Hauptdatei nur Daten geladen werden:

 #version 330 core // Output data out vec3 color; // Interpolated values from the vertex shaders in vec3 Normal_cameraspace; in vec3 LightDirection_cameraspace; void main() { vec3 n = normalize(Normal_cameraspace); // Normal of the computed fragment, in camera space vec3 l = normalize(LightDirection_cameraspace); // Direction of the light (from the fragment to the light) float cosTheta = clamp(dot(n,l), 0, 1); // Cosine of the angle between the normal and the light direction, color = vec3(1,0,0)*(0.1 + // ambient lighting 1.3*cosTheta); // diffuse lighting } 

Stufe vier: Transformationsmatrizen


Zu diesem Zeitpunkt vorgenommene Änderungen im Projekt finden Sie hier . Zu diesem Zeitpunkt habe ich die Modell-, Ansichts- und Projektionsmatrizen codiert. Ganz am Anfang sind sie nur einzeln, aber wenn Sie die Leertaste drücken, beginnt sich das Modell zu drehen: Jedes Mal, wenn ich ein Bild zeichne, drehe ich die Modellmatrix um 0,01 Radiant um die z-Achse:

  { // rotate the model around the z axis with each frame Matrix R = rot_z(0.01); if (animate) M = R*M; } 

Hier gibt die Funktion rot_z () die Rotationsmatrix um die z-Achse um einen bestimmten Winkel zurück. Da OpenGL nichts über meine Matrixklasse weiß, musste ich den Matrixexport void export_row_major () zu einem einfachen Zeiger auf ein Float hinzufügen.



Fünfter Schritt: Normale Karten


Zu diesem Zeitpunkt vorgenommene Änderungen im Projekt finden Sie hier . An dieser Stelle lernen wir, wie man Texturen überlagert. Da die übliche diffuse Textur langweilig ist, werde ich sofort die normale Karte anwenden, und sogar im Tangentenraum. Normale Karten sehen ungefähr so ​​aus:



Die entsprechenden Berechnungen sind, gelinde gesagt, nicht offensichtlich. Lesen Sie daher noch einmal die Erklärungen in tinyrenderer . In Bezug auf Daten müssen Sie mehrere Puffer hinzufügen: UV-Koordinaten und Arrays von Tangenten- und Bi-Tangenten-Vektoren.



Stufe fünf: Diffuse Textur


Wenn wir bereits wissen, wie man normale Karten zählt, ist das Anwenden einer normalen diffusen Textur einfach trivial. Zu diesem Zeitpunkt vorgenommene Änderungen im Projekt finden Sie hier .



Stufe sechs: Blendung


Zu diesem Zeitpunkt vorgenommene Änderungen im Projekt finden Sie hier . Fügen Sie im letzten Schritt eine weitere Textur hinzu, mit der wir die Blendung der Beleuchtung von glänzenden Oberflächen simulieren können:



Fazit


In diesem Code kann vieles verbessert werden, und die visuellen Effekte können endlos verdreht werden. Dies ist jedoch nicht mein Ziel, sondern mein Ziel ist es zu zeigen, dass absolut alle Techniken, die ich beim Rendern von Software angesprochen habe, im aktuellen OpenGL-Kontext anwendbar sind. Und ich persönlich denke immer noch, dass Sie sich mit 3D-Grafiken vertraut machen müssen, indem Sie Bilder zeichnen, ohne die Magie der Grafikbibliotheken zu nutzen.

Versuchen Sie als Erweiterung beispielsweise, Schatten hinzuzufügen , die globale Beleuchtung zu zählen oder schließlich eine Leuchtkarte zu erstellen: Schließlich sollten die Augen und der Kristall in Diablos Stirn leuchten!

Source: https://habr.com/ru/post/de443174/


All Articles