Ultramodernes OpenGL. Teil 2



Alles gute Laune und niedrigere Temperatur vor dem Fenster. Wie versprochen veröffentliche ich eine Fortsetzung des Artikels über den Super-Duper des modernen OpenGL. Wer hat den ersten Teil nicht gelesen - Ultramodern OpenGL. Teil 1

Vielleicht hast du Glück und ich kann das restliche Material in diesen Artikel schieben, das ist nicht sicher ...

Array-Textur


Textur-Arrays wurden bereits in OpenGL 3.0 hinzugefügt, aber aus irgendeinem Grund schreiben nur wenige Leute darüber (Informationen werden von Masons zuverlässig versteckt). Sie alle sind mit der Programmierung vertraut und wissen, was ein Array ist , obwohl ich mich besser von der anderen Seite „nähern“ sollte.

Um die Anzahl der Umschaltungen zwischen Texturen und damit die Umschaltvorgänge zu verringern, verwenden Benutzer Texturatlanten (eine Textur, in der Daten für mehrere Objekte gespeichert werden). Aber kluge Jungs von Khronos haben eine Alternative für uns entwickelt - Array-Textur. Jetzt können wir Texturen als Ebenen in diesem Array speichern, dh es ist eine Alternative zu Atlanten. Das OpenGL-Wiki hat eine etwas andere Beschreibung von Mipmaps usw., aber es scheint mir zu kompliziert ( Link ).

Die Vorteile dieses Ansatzes im Vergleich zu Atlanten bestehen darin, dass jede Schicht in Bezug auf Umhüllung und Mipmapping als separate Textur betrachtet wird.

Aber zurück zu unseren Widdern ... Das Texturarray hat drei Arten von Zielen:

  • GL_TEXTURE_1D_ARRAY
  • GL_TEXTURE_2D_ARRAY
  • GL_TEXTURE_CUBE_MAP_ARRAY

Code zum Erstellen eines Texturarrays:

GLsizei width = 512; GLsizei height = 512; GLsizei layers = 3; glCreateTextures(GL_TEXTURE_2D_ARRAY, 1, &texture_array); glTextureStorage3D(texture_array, 0, GL_RGBA8, width, height, layers); 

Die aufmerksamsten haben bemerkt, dass wir ein Repository für 2D-Texturen erstellen, aber aus irgendeinem Grund, wenn wir ein 3D-Array verwenden, gibt es hier keinen Fehler oder Tippfehler. Wir speichern 2D-Texturen, aber da sie sich in „Ebenen“ befinden, erhalten wir ein 3D-Array (tatsächlich werden Pixeldaten gespeichert, keine Texturen. Das 3D-Array enthält 2D-Ebenen mit Pixeldaten).

Hier ist es am Beispiel der 1D-Textur leicht zu verstehen. Jede Zeile in einem 2D-Pixelarray ist eine separate 1D-Schicht. Mipmap-Texturen können auch automatisch erstellt werden.

Damit enden alle Schwierigkeiten und das Hinzufügen eines Bildes zu einer bestimmten Ebene ist ganz einfach:

 glTextureSubImage3D(texarray, mipmap_level, offset.x, offset.y, layer, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 

Bei der Verwendung von Arrays müssen wir den Shader ein wenig ändern

 #version 450 core layout (location = 0) out vec4 color; layout (location = 0) in vec2 texture_0; uniform sampler2DArray texture_array; uniform uint diffuse_layer; float getCoord(uint capacity, uint layer) { return max(0, min(float(capacity - 1), floor(float(layer) + 0.5))); } void main() { color = texture(texture_array, vec3(texture_0, getCoord(3, diffuse_layer))); } 

Die beste Option wäre, die gewünschte Ebene außerhalb des Shaders zu berechnen. Hierfür können wir UBO / SSBO verwenden (es wird auch zum Übertragen von Matrizen und vielen anderen Daten verwendet, aber es ist irgendwie ein anderes Mal). Wenn jemand nicht auf tyk_1 und tyk_2 warten kann , können Sie lesen.

Was die Größen betrifft, d. H. GL_MAX_ARRAY_TEXTURE_LAYERS, die in OpenGL 3.3 256 und in OpenGL 4.5 2048 beträgt.

Es lohnt sich, etwas über das Sampler-Objekt zu erzählen (nicht in Bezug auf die Array-Textur, aber eine nützliche Sache) - dies ist ein Objekt, mit dem der Status einer Textureinheit angepasst wird, unabhängig davon, welches Objekt derzeit an die Einheit angehängt ist. Es hilft dabei, Sampler-Zustände von einem bestimmten Texturobjekt zu trennen, wodurch die Abstraktion verbessert wird.

 GLuint sampler_state = 0; glGenSamplers(1, &sampler_state); glSamplerParameteri(sampler_state, GL_TEXTURE_WRAP_S, GL_REPEAT); glSamplerParameteri(sampler_state, GL_TEXTURE_WRAP_T, GL_REPEAT); glSamplerParameteri(sampler_state, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(sampler_state, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glSamplerParameterf(sampler_state, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f); 

Ich habe gerade ein Sampler-Objekt erstellt, die lineare Filterung und die 16-fache anisotrope Filterung für jede Textureinheit aktiviert.

 GLuint texture_unit = 0; glBindSampler(texture_unit, sampler_state); 

Hier binden wir den Sampler einfach an die gewünschte Textureinheit, und wenn er nicht mehr das gewünschte Bindim 0 ist, an diese Einheit.

 glBindSampler(texture_unit, 0); 

Wenn wir den Sampler verknüpft haben, haben seine Einstellungen Vorrang vor den Einstellungen der Textureinheit. Ergebnis: Es ist nicht erforderlich, die vorhandene Codebasis zu ändern, um Sampler-Objekte hinzuzufügen. Sie können die Texturerstellung unverändert lassen (mit ihren eigenen Sampler-Zuständen) und einfach Code hinzufügen, um die Sampler-Objekte zu steuern und zu verwenden.

Wenn es Zeit ist, das Objekt zu löschen, rufen wir einfach diese Funktion auf:

 glDeleteSamplers(1, &sampler_state); 

Texturansicht


Ich werde dies als "Texturzeiger (er ist möglicherweise korrekter als der Link, ich bin xs)" übersetzen, da ich die beste Übersetzung nicht kenne.

Was sind Hinweise aus Sicht von OpenGL?

Alles ist sehr einfach, dies ist ein Zeiger auf die Daten einer unveränderlichen (nämlich veränderlichen) Textur, wie wir im Bild unten sehen.



Tatsächlich ist dies ein Objekt, das die Texeldaten eines bestimmten Texturobjekts gemeinsam nutzt. Analog können wir std :: shared_ptr aus C ++ verwenden . Solange mindestens ein Texturzeiger vorhanden ist, wird die ursprüngliche Textur vom Treiber nicht entfernt.

Das Wiki wird ausführlicher beschrieben und es lohnt sich, über die Arten der Textur und des Ziels zu lesen (sie müssen nicht übereinstimmen).

Um einen Zeiger zu erstellen, müssen wir einen Texturdeskriptor abrufen, indem wir glGenTexture aufrufen (keine Initialisierung erforderlich) und dann glTextureView .

 glGenTextures(1, &texture_view); glTextureView(texture_view, GL_TEXTURE_2D, source_name, internal_format, min_level, level_count, 5, 1); 

Texturzeiger können auf die N-te Ebene der Mipmap verweisen, was sehr nützlich und praktisch ist. Zeiger können entweder Texturarrays, Teile von Arrays, eine bestimmte Ebene in diesem Array oder ein Ausschnitt einer 3D-Textur als 2D-Textur sein.

Einzelpuffer für Index und Vertex


Nun, alles wird schnell und einfach gehen. Früher wurde in der OpenGL-Spezifikation für Vertex Buffer Object empfohlen, dass der Entwickler Vertex- und Indexdaten in verschiedene Puffer aufteilt. Jetzt ist dies jedoch nicht erforderlich (eine lange Geschichte, warum nicht).
Alles, was wir tun müssen, ist, die Indizes vor den Scheitelpunkten zu speichern und anzugeben, wo die Scheitelpunkte beginnen (genauer gesagt der Versatz). Dazu gibt es einen Befehl glVertexArrayVertexBuffer

So würden wir es machen:

 GLint alignment = GL_NONE; glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment); const GLsizei ind_len = GLsizei(ind_buffer.size() * sizeof(element_t)); const GLsizei vrt_len = GLsizei(vrt_buffer.size() * sizeof(vertex_t)); const GLuint ind_len_aligned = align(ind_len, alignment); const GLuint vrt_len_aligned = align(vrt_len, alignment); GLuint buffer = GL_NONE; glCreateBuffers(1, &buffer); glNamedBufferStorage(buffer, ind_len_aligned + vrt_len_aligned, nullptr, GL_DYNAMIC_STORAGE_BIT); glNamedBufferSubData(buffer, 0, ind_len, ind_buffer.data()); glNamedBufferSubData(buffer, ind_len_aligned, vrt_len, vrt_buffer.data()); GLuint vao = GL_NONE; glCreateVertexArrays(1, &vao); glVertexArrayVertexBuffer(vao, 0, buffer, ind_len_aligned, sizeof(vertex_t)); glVertexArrayElementBuffer(vao, buffer); 


Tessellation und Berechnungsschattierung


Ich werde Ihnen nichts über den Tessellation-Shader erzählen, da es in Google viel Material dazu gibt (auf Russisch). Hier sind einige Lektionen: 1 , 2 , 3 . Wir werden den Shader für Berechnungen betrachten (bliiin, auch viel Material, werde ich Ihnen kurz sagen).

Grafikkarten bieten den Vorteil von Grafikkarten in einer sehr großen Anzahl von Kernen und sind für eine große Anzahl kleiner Aufgaben ausgelegt, die parallel ausgeführt werden können. Der Berechnungs-Shader ermöglicht es, wie der Name schon sagt, Probleme zu lösen, die nicht mit Grafiken zusammenhängen (nicht erforderlich).

Ein Bild, ich weiß nicht, wie ich es nennen soll (wie Streams gruppiert sind).



Wofür können wir verwenden?

  • Bildverarbeitung
    1. Blüte
    2. Kachelbasierte Algorithmen (verzögerte Schattierung)
  • Simulationen
    1. Partikel
    2. Wasser

Außerdem sehe ich keinen Grund zum Schreiben, es gibt auch viele Informationen in Google, hier ein einfaches Anwendungsbeispiel:

 //     glUseProgramStages( pipeline, GL_COMPUTE_SHADER_BIT, cs); // ,    / glBindImageTexture( 0, tex, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); // 80x45   (  1280720) glDispatchCompute( 80, 45, 1); 


Hier ist ein Beispiel für einen leeren Compute-Shader:
 #version 430 layout(local_size_x = 1, local_size_y = 1) in; layout(rgba32f, binding = 0) uniform image2D img_output; void main() { // base pixel color for image vec4 pixel = vec4(0.0, 0.0, 0.0, 1.0); // get index in global work group ie x,y position ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); // // interesting stuff happens here later // // output to a specific pixel in the image imageStore(img_output, pixel_coords, pixel); } 


Hier sind einige Links für einen tieferen Blick auf 1 , 2 , 3 , 4 .

Pfadwiedergabe


Dies ist eine neue (nicht neue) Erweiterung von NVidia , deren Hauptziel das Vektor-2D-Rendering ist. Wir können es für Texte oder Benutzeroberflächen verwenden, und da die Grafiken Vektoren sind, hängt es nicht von der Auflösung ab, was zweifellos ein großes Plus ist und unsere Benutzeroberfläche wird großartig aussehen.

Das Grundkonzept ist eine Schablone, dann ein Cover (Cover im Original). Stellen Sie die Pfadschablone ein und visualisieren Sie die Pixel.

Für die Verwaltung wird Standard-GLuint verwendet, und die Funktionen zum Erstellen und Löschen haben eine Standard-Namenskonvention.

 glGenPathsNV //  glDeletePathsNV //  


Hier ist ein wenig darüber, wie wir den Weg finden können:
  • SVG oder PostScript in string'e
     glPathStringNV 
  • Array von Befehlen mit entsprechenden Koordinaten
     glPathCommandsNV 
    und zum Aktualisieren von Daten
     glPathSubCommands, glPathCoords, glPathSubCoords 
  • Schriftarten
     glPathGlyphsNV, glPathGlyphRangeNV 
  • lineare Kombinationen vorhandener Pfade (Interpolation von einem, zwei oder mehr Pfaden)
     glCopyPathNV, glInterpolatePathsNV, glCombinePathsNV 
  • lineare Transformation eines vorhandenen Pfades
     glTransformPathNV 

Liste der Standardbefehle:

  • Verschieben nach (x, y)
  • Nahweg
  • Linie zu (x, y)
  • quadratische Kurve (x1, y1, x2, y2)
  • kubische Kurve (x1, y1, x2, y2, x3, y3)
  • glatte quadratische Kurve (x, y)
  • glatte kubische Kurve (x1, y1, x2, y2)
  • Ellipsentrainer (rx, ry, x-Achsen-Rotation, Großbogen-Flagge, Sweep-Flagge, x, y)

So sieht die Pfadzeichenfolge in PostScript aus:

 "100 180 moveto 40 10 lineto 190 120 lineto 10 120 lineto 160 10 lineto closepath” // "300 300 moveto 100 400 100 200 300 100 curveto 500 200 500 400 300 300 curveto closepath” // 

Und hier in SVG:

 "M100,180 L40,10 L190,120 L10,120 L160,10 z” // "M300 300 C 100 400,100 200,300 100,500 200,500 400,300 300Z” // 

Es gibt immer noch alle Arten von Brötchen mit Arten von Füllungen, Kanten, Biegungen:



Ich werde hier nicht alles beschreiben, da es viel Material gibt und es einen ganzen Artikel braucht (wenn es interessant ist, werde ich es irgendwie schreiben).

Hier ist eine Liste der Rendering-Grundelemente

  • Kubische Kurven
  • Quadratische Kurven
  • Linien
  • Schriftzeichen
  • Bögen
  • Dash & Endcap Style

Hier ist ein Code, und dann gibt es viel Text:

 // SVG  glPathStringNV( pathObj, GL_PATH_FORMAT_SVG_NV, strlen(svgPathString), svgPathString); //  glStencilFillPathNV( pathObj, GL_COUNT_UP_NV, 0x1F); // //  ( ) glCoverFillPathNV( pathObj, GL_BOUNDING_BOX_NV); 

Das ist alles.

Es scheint mir, dass dieser Artikel weniger interessant und informativ herauskam, es war schwierig, die Hauptsache im Material herauszuarbeiten. Wenn jemand mehr darüber erfahren möchte, kann ich einige NVidia-Materialien und Links zu Spezifikationen verwerfen (wenn ich mich erinnere, wo ich sie gespeichert habe). Ich freue mich auch über jede Hilfe bei der Bearbeitung des Artikels.

Wie versprochen werde ich den folgenden Artikel über die Optimierung und Reduzierung von Draw Calls schreiben. Ich möchte Sie bitten, in den Kommentaren darüber zu schreiben, was Sie sonst noch lesen möchten und woran Sie interessiert sind:
  • Ein Spiel auf cocos2d-x schreiben (nur üben, kein Wasser)
  • Übersetzung einer Artikelserie über Vulkan
  • Einige Themen zu OpenGL (Quaternionen, neue Funktionen)
  • Computergrafik-Algorithmen (Beleuchtung, Raumbild-Umgebungsokklusion, Raumbildschirmreflexion)
  • Ihre Möglichkeiten


Vielen Dank für Ihre Aufmerksamkeit.

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


All Articles