Der Umgang mit Attributen im Vertex-Shader
Das Problem
OpenGL speichert alle seine Zustände, Daten, Einstellungen usw. in internen Tabellen bzw. Arrays; nur so ist der erforderliche schnelle Zugriff gewährleistet. Shader-Quelltexte aber verwenden wörtliche Bezeichner, damit der Code lesbar ist. Das führt zu einem gewissen Zuordnungsproblem, denn der Zugriff auf OpenGL erfordert die Kenntnis darüber. welche Zugriffsnummer welchem Variablennamen im Quelltext entspricht. Wenn beide nicht zusammenpassen, gibt es natürlich falsche Ergebnisse.
Als Beispiel nehmen wir die in fast jedem Vertexshader verwendeten Attribute für die Position und die Farbe der Vertices. Im Quelltext des Shaders könnten sie so bezeichnet werden:
in vec3 VertexPosition; in vec3 VertexColor;
Nun müssen diese Attribute von außen mit Daten versorgt werden, und dazu kann man nicht die Bezeichner "VertexPosition" und "VertexColor" gebrauchen, sondern muss die Zugriffsnummern verwenden, unter denen OpgenGL die Daten ablegt. Ob man eine solche Kennnummer nun "Index", "generisches Attribut", "Handle" oder wie auch immer nennt, ist Formsache. In der englischsprachigen Fachliteratur findet man häufig die Bezeichnung "name" für diese Zugriffsnummer, aber nach meiner Auffassung ist der Begriff sehr irreführend, weil "Name" eine sprachliche Festlegung beinhaltet, während eine laufende Nummerierung gemeint ist. Sei's drum, ich erwähne es nur deshalb, weil ein Leser mit deutschem Sprachgefühl stutzig werden könnte.
Die Lösung
Es gibt drei Lösungsansätze, vielleicht sogar vier, wobei ich die vierte nur als Behelf bewerten möchte.
1. Festlegung der Indices im Rahmenprogramm. Der Programmierer bestimmt, unter welcher Zugriffsnummer er auf die Attribute zugreifen will. Das geschieht mit folgender Funktion:
glBindAttribLocation (program_id, 0, "VertexPosition"); glBindAttribLocation (program_id, 1, "VertexColor");
Hiermit wird eindeutig festgelegt, dass Positionsdaten unter dem Index 0 übermittelt werden, Farbdaten unter dem Index 1. Es können auch andere, beliebige Indices verwenden werden, also 7 und 4 beispielsweise. Doch wo wird der Index verwendet? Um das herauszubekommen, kann man ja mal die beiden Nummern vertauschen, also 1 für die Position, 0 für die Farbe. Als Ergebnis wird wahrscheinlich eine andere Form mit anderen Farben herauskommen. Weiter unten im Rahmenprogramm tauchen zwei Funktionen auf, die sich auf die Indices beziehen:
glEnableVertexAttribArray (0); glEnableVertexAttribArray (1);
Hier werden nur die verwendeten Indices aktiviert, ohne Rücksicht auf die Reihenfolge. Da braucht also nichts dran verstellt zu werden. Dann kommen die entscheidenden Funktionen. Mit glBindBuffer werden nacheinander die Verbindungen zu den Buffer-Objekten hergestellt und anschließend die Daten mittels glVertexAttribPointer übertragen. Der erste Parameter ist der Index, um den es geht. Wenn wir die beiden Indices auch hier vertauschen, haben wir wieder das gewollte Ergebnis.
glBindBuffer (GL_ARRAY_BUFFER, PositionBuffer_ID); glVertexAttribPointer (1, 3, GL_FLOAT, GL_FALSE, 0, (GLubyte *)NULL ); glBindBuffer (GL_ARRAY_BUFFER, ColorBuffer_ID); glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 0, (GLubyte *)NULL );
Wichtig: Die Festlegung mittels glBindAttribLocation() muss erfolgen, bevor die beiden Shader zu einem Programm gelinkt werden. Beim Linken erzeugt OpenGL nämlich seine eigenen Indices, falls nicht vorher von außen etwas anderes bestimmt wurde.
2. Festlegung der Indices im Shader-Quelltext. Das geschieht durch den Layout-Zusatz:
layout (location = 0) in vec3 VertexPosition; layout (location = 1) in vec3 VertexColor;
Hier erübrigt sich der Aufruf von glBindAttribLocation(), ansonsten ist die Wirkung dieselbe wie im Lösungsbeispiel 1. Was nun günstiger ist, muss der Programmierer entscheiden. So oder so muss er wissen, was im Shader-Quelltext steht. Wie Blackboxes lassen sich Shader kaum behandeln, es sei denn, der Code wird strikt nach irgendwelchen Regeln formuliert. Fremdcode kann natürlich wieder alles durcheinander werfen.
3. Abfrage der von OpenGL verwendeten Indices. Dieses Verfahren geht einen anderen Weg: Man lässt OpenGL gewähren und beim Linken die erforderlichen Indices generieren ("generische Indices"). Wenn später ein Zugriff erforderlich ist, fragt man einfach nach, welchen Index ein bestimmtes Attribut bekommen hat:
GLint position_id = glGetAttribLocation (program_id, "VertexPosition");
Mit dem Ergebnis kann dann weiter gearbeitet werden. Mit den Nummern braucht man sich dabei nicht mehr auseinanderzusetzen, die werden ganz einfach in Variablen geführt. Wohl aber müssen auch in diesem Fall die im Quelltext verwendeten Attribut-Bezeichnungen bekannt sein.
4. Nun zu der vierten, schlechten Lösung: Nix tun, funktioniert auch. Na ja. Wenn wir keine der drei Maßnahmen treffen, werden wir möglicherweise verblüfft feststellen, dass das Erebnis trotzdem stimmt: Wie das? Wenn OpenGL die Indices generiert, dann spielt die Reihenfolge im Quelltext eine Rolle. Zuerst wird die 0 vergeben, dann die 1 usw. Wenn dann bei der Zuweisung der Daten noch die zur Reihenfolge passenden Indices angegeben werden, dann stimmt die Sache. Vieles spricht dafür, dass ungewollt eine "natürliche" Reihenfolge beachtet wird: Zuerst die Position als wichtigstes Attribut, dann die Farbe usw. Aber was ist, wenn jemand aus irgendeinem Grunde mal eine andere Reihenfolge wählt, was nicht mal auffallen muss?
in vec3 VertexColor; in vec3 VertexPosition;
Einfach mal ausprobieren. Eine saubere Lösung ist das also nicht.
Anmerkung: Einen vergleichbaren Befehlssatz gibt es auch für die out-Variablen im Fragment-Shader. Aber oft wird hier nur eine Variable verwendet, nämlich die Ausgangsvariable für die Fragmentfarbe. Da erübrigt sich die die explizite Verknüpfung von Variable und Index.