In dem folgenden Beitrag gehen wir ein wenig näher auf die offizielle OpenGL Shading Language GLSL ein.

Einführung in GLSL

Was ist GLSL?

GLSL ist eine C/C++-ähnliche Programmiersprache für mehrere Teile der Grafikkarte. Mit GLSL können Sie kurze Programme, sogenannte Shader, codieren, die auf dem Grafikprozessor ausgeführt werden.

Warum Shader?

Bis zur DirectX 8 Hardware (GeForce 2 und niedriger, Radoen 7000 und niedriger) konnte die Grafikpipeline nur konfiguriert, aber nicht programmiert werden. So gibt es zum Beispiel das OpenGL-Lichtmodell mit ambienter, diffuser, spiegelnder und emittierender Beleuchtung. Dieses Modell wird hauptsächlich verwendet, aber es gibt viele andere Modelle für die Beleuchtung. In OpenGL mit fester Funktion konnte nur dieses Beleuchtungsmodell verwendet werden, kein anderes. Mit Shadern können Sie ihr eigenes Lichtmodell schreiben. Aber das ist nur eine Eigenschaft von Shadern. Es gibt tausende andere wirklich schöne Möglichkeiten: Schatten, Environment Mapping, Per-Pixel Beleuchtung, Bump Mapping, Parallax Bump Mapping, HDR und vieles mehr.

Warum GLSL?

Shader sind in OpenGL bis 2002 über ARB_vertex_program und ARB_fragment_program extension verfügbar. Aber mit diesen Erweiterungen können Sie nur Assembly Shader verwenden. Aufgrund der zunehmenden Komplexität von Beleuchtungs- und Beschattungsmodellen sind Montageschatten nur noch schwer zu handhaben. GLSL ist eine High-Level-Shading-Sprache, was bedeutet, dass Sie ihren Shader im C/C++-Stil schreiben können. Das macht die Shader-Entwicklung wesentlich einfacher.

Was ist der Unterschied zwischen Fixed Function Pipeline und GLSL?

Es gibt zwei Arten von Shadern in GLSL: Vertex- und Fragment-Shader.

Vertex-Shader.

Ein Vertex-Shader arbeitet auf jedem Vertex. Wenn Sie also glVertex oder glDrawArrays) aufrufen, wird der Vertex-Shader für jeden Vertex ausgeführt. Wenn Sie einen Vertex-Shader verwenden, haben Sie fast die volle Kontrolle darüber, was mit jedem Vertex passiert. Wenn Sie jedoch einen Vertex-Shader verwenden, werden alle Per-Vertex-Operationen der festen Funktion OpenGL-Pipeline ersetzt.

Fragment-Shader.

Ein Fragment-Shader arbeitet auf jedem Fragment, das durch Rasterung erzeugt wird. Mit dem Fragment-Shader haben Sie fast die volle Kontrolle über das, was mit jedem Fragment passiert. Aber genau wie ein Vertex-Shader ersetzt ein Fragment-Shader alle Per-Fragment Operationen der festen Funktion OpenGL-Pipeline.

Wie sieht GLSL aus?

Wie oben erwähnt gibt es 2 Arten von Shadern, einen Vertex- und einen Fragment-Shader. Jeder Shadertyp hat andere In- und Outputs.

Datentypen in GLSL.

Es gibt vier Haupttypen: float, int, bool und sampler. Für die ersten drei Typen stehen Vektortypen zur Verfügung:

Copy to Clipboard

Für Floats gibt es hier auch Matrixtypen:

Copy to Clipboard

Sampler sind Typen, die Texturen darstellen. Sie werden für Texturproben verwendet. Probenehmertypen müssen einheitlich sein. Sie dürfen nicht als unheitlicher Typ deklariert werden. Hier sind die verschiedenen Samplertypen:

Copy to Clipboard

Über Attribute, Uniforms und Varyings.

Es gibt drei Arten von Inputs und Outputs in einem Shader. Attribute, Uniforms und Varyings.

Uniforms sind Werte, die sich während eines Renderings nicht ändern, z.B. die Lichtposition und -farbe. Uniforms sind in Vertex- und Fragmentshadern erhältlich. Uniforms sind schreibgeschützt.

Attribute stehen nur im Vertex Shader zur Verfügung und sind Eingabewerte, die jeden Vertex verändern, z.B. die Vertex-Position oder Normals. Attribute sind schreibgeschützt.

Varyings werden verwendet, um Daten von einem Vertex- an einen Fragment-Shader zu übergeben. Varyings werden über das Primitiv interpoliert und sind schreibgeschützt im Fragment-Shader, sind aber im Vertex-Shader lesbar und beschreibbar. Wenn Sie Variationen verwenden wollen, müssen Sie diese in ihrem Vertex- und in ihrem Fragment-Shader deklarieren.

Alle einheitlichen, attributiven und unterschiedlichen Typen müssen global sein. Es ist nicht erlaubt, in einer Funktion oder einem Leerzeichen einen einheitlichen/attributartigen/variierenden Typ anzugeben.

Build-In Typen.

GLSL hat einige eingebaute Attribute in einem Vertex-Shader:

Copy to Clipboard

Es gibt noch weitere In-Build Attribute.

GLSL hat uach einige In-Build Uniforms:

Copy to Clipboard

Es gibt einige andere In-Build Uniforms, wie z.B. Beleuchtungszustände.

GLSL Build-In Varyings.

Copy to Clipboard

Es gibt noch einige andere In-Build Varyings.

Last but not least gibt es einige In-Build Typen, die für die Shaderausgabe verwendet werden:

Copy to Clipboard

Die Bedeutung der In-Build Typen besteht darin, dass sie auf die OpenGL-Zustände abgebildet werden. Wenn Sie beispielsweise glLightfv (GL_LIGHT0, GL_POSITION, my_light_position) aufrufen, steht dieser Wert als Uniform mit gl_LightSource[0].position in einem Vertex- und/oder Fragment-Shader zur Verfügung.

Allgemeine Typen.

Sie können auch ihre eigenen Attribute, Uniforms und Variationen festlegen. Wenn Sie z.B. einen 3D-Tangentenvektor für jeden Vertex aus ihrer Anwendung an den Vertex-Shader übergeben wollen, können Sie ein Tangenten-Attribut angeben:

Copy to Clipboard

Details zu GLSL.

GLSL ist C/C++ sehr ähnlich, aber es gibt einige kleine Unterschiede.

Zudem hat GLSL die folgenden Features/Einschränkungen:

GLSL ist 100% typsicher. Es ist nicht erlaubt, einem Float ohne Casting eine ganze Zahl zuzuweisen:

Copy to Clipboard

Die Gussteile müssen mit Hilfe von Konstruktoren bereitgestellt werden. Zum Beispiel funktioniert das nicht:

Copy to Clipboard

Vektoren und Matrizen können nur mit Hilfe von Konstruktoren mit Benutzerdaten gefüllt werden:

Copy to Clipboard

Die Vektor-Multiplikation erfolgt komponentenweise:

Copy to Clipboard

Vektor mit Matrix-Multiplikation ist ebenfalls verfügbar.

Matrix* Vektor bedroht den Vektor als Spalten-Vektor (OpenGL-Standard).

Vektor* Matrix bedroht den Vektor als Zeilenvektor (DirectX-Standard).

Zum Beispiel wird ein Scheitelpunkt normalerweise auf diese Weise transformiert.

Copy to Clipboard

Dadurch wird die Vertex-Position einfach durch die Modell-View-Projection Matrix transformiert.

Es gibt auch viele In-Build Funktionen, die verwendet werden können (und sollten):

Copy to Clipboard

Jeder Shader muss einen main() void haben. Diese Leere wird aufgerufen, wenn der Shader ausgeführt wird.

Shader-Beispiele.

So haben wir bisher viel über GLSL gehört. Aber wie sieht ein Shader aus? Hier sind einige einfache Beispiele:

Ambient Shader.

Der Ambient Shader ist sicherlich der einfachste Shader auf dem Markt. Jedes gerenderte Pixel hat eine bestimmte Farbe:

Vertex-Shader:

Copy to Clipboard

Fragment-Shader:

Copy to Clipboard

Diffuse Shader.

Das Modell der diffusen Beleuchtung ist ein gängiges Beleuchtungsmodell. Es ist etwas schwieriger umzusetzen:

Vertex-Shader:

Copy to Clipboard

Fragment-Shader:

Copy to Clipboard

Texture Mapping Shader.

Dies ist ein einfacher Shader für das Texture-Mapping.

Vertex-Shader.

Copy to Clipboard

Fragment-Shader.

Copy to Clipboard

GLSL API: So verwenden Sie GLSL in ihrer OpenGL-Anwendung.

Wir wissen nun, wie man Shader schreibt und wofür sie gut sind. Aber wie verwenden wir sie in unserer OpenGL-Anwendung?

GLSL ist ab sofort über 4 Erweiterungen erhältlich:

Copy to Clipboard

GLSL ist C/C++ sehr ähnlich. Wenn Sie also einen GLSL-Shader verwenden wollen, müssen Sie die folgenden Schritte durchführen:

  • Übergabe der Shader-Source an ein Shader-Objekt.
  • Kompilieren der Shader-Quelle.
  • Shader mit einem Programm-Objekt verknüpfen.

Ein Shader-Objekt repräsentiert ihren Quellcode. Sie können ihren Quellcode an ein Shader-Objekt übergeben und das Shader-Objekt kompilieren.

Ein Programmobjekt stellt einen nutzbaren Teil der Renderpipeline dar.

Wie erstellt man diese Objekte?

Copy to Clipboard

Zum Beispiel: Wenn Sie ein Vertex-Shader-Objekt erstellen wollen, müssen Sie folgendes tun:

Copy to Clipboard

Wie übergibt man den Shader-Quellcode an ein Shader-Objekt?

Dies kann durch die Verwendung von

Copy to Clipboard

Mit diesem Befehl können Sie mehr als einen String an ein Shader-Objekt übergeben.

  • Shader ist das Zielobjekt des Shaders.
  • number_strings ist die Anzahl der Strings, die Sie an den GL übergeben wollen.
  • Strings ist ein Zeiger auf einen Zeiger von Zeichen, in dem der Quelltext gespeichert ist.
  • Lenght ist ein Zeiger auf ints, wo die Länge jedes Strings gespeichert wird. Wenn die Länge gleich Null ist, werden die Strings als null terminiert angenommen.

Hier ein Beispiel, wie man eine Shader-Quelle an einen Vertex-Shader übergibt:

Copy to Clipboard

Wie kompiliert man ein Shader-Objekt?

Dies kann durch das folgende Code-Snippet gelöst werden:

Copy to Clipboard

Shader ist unser Shader-Objekt.

Wie verknüpfe ich meine Shader-Objekte mit einem Programmobjekt?

Zuerst müssen wir ein Programmobjekt erstellen. Dann hängen wir alle Shader-Objekte, die wir verwenden wollen, an dieses Programmobjekt an:

Copy to Clipboard

Programm ist unser Programmobjekt und Shader ist unser Shaderobjekt.

Dann verknüpfen wir unser Programmobjekt mit

Copy to Clipboard

Programm ist unser Programmobjekt.

Wie verwendet man ein Programmobjekt?

Programmobjekte können verwendet werden mit

Copy to Clipboard

Programm ist unser Programmobjekt

Wenn das Programm 0 ist, wird die Standardfunktion OpenGL verwendet.

Alles zusammenfügen.

Hier ist ein Code-Beispiel zum Laden, Kompilieren, Verknüpfen und Verwenden von Shadern:

Copy to Clipboard

Wenn alle Shader erfolgreich kompiliert wurden und das Programmobjekt erfolgreich gelinkt wurde, werden alle Renderings nach glUseProgramObjectARB mit unserem Shader durchgeführt.

Einige weitere wichtige Funktionen.

Natürlich können Sie auch Objekte (Shader und Programm) löschen mit

Copy to Clipboard

Ein weiterer sehr wichtiger Befehl ist

Copy to Clipboard

Objekt ist ein Shader oder Programmobjekt.

MaxLenght ist die maximale Anzahl der Zeichen, die in infoLog geschrieben werden.

Länge ist die Anzahl der Zeichen, die in InfoLog geschrieben wurden.

InfoLog ist ein Zeiger auf Zeichen, in denen das Inforprotokoll gespeichert wird.

Mit dieser Funktion erhalten Sie Informationen über Shader- und Programmobjekte.

Beispiel: Wenn Sie diese Funktion nach einer fehlgeschlagenen Kompilierung aufrufen, erhalten Sie Informationen, warum die Kompilierung fehlgeschlagen ist.

Uniforms.

Uniforms können an den GL übergeben werden, indem man

Copy to Clipboard

Location ist der Ort der Uniform.

Count ist die Anzahl der Werte von Typ TYPE, die übergeben werden sollen.

Val ist ein Wert aus TYPE.

Vals ist ein Zeiger auf Werte von TYPE (für Matrizen sind nur float verfügbar)

Transpose gibt an, ob die übergebenen Matrizen transponiert werden sollen.

Der einheitliche Standort lässt sich leicht erreichen mit

Copy to Clipboard

Programm ist unser Programmobjekt.

Name ist der Name der Uniform, die wir bekommen wollen.

Also hier ist ein Beispiel, wie man Uniforms benutzt:

Copy to Clipboard

Beachten Sie, dass Sie die In-Build Uniform nicht über glUniform passieren können.

Allgemeine Attribute.

Allgemeine Attribute sind wie Uniforms:

Copy to Clipboard

Index ist die Location, an dem sich das Attribut befindet.

Val ist ein Wert aus TYPE.

Vals ist ein Zeiger auf Werte von TYPE (für Matrizen stehen nur floats zur Verfügung).

Die Ermittlung der Attributposition kann auch ganz einfach mit

Copy to Clipboard

Programm ist unser Programmobjekt.

Name ist der Name des Attributs, das wir erhalten wollen.

Ein Beispiel:

Copy to Clipboard

Verwendung von Texturen mit GLSL.

Da viele GLSL-Einsteiger Probleme mit der Texturierung haben, möchte ich jetzt etwas dazu sagen.

Wie gesagt… Texturen sind in GLSL über Sampler verfügbar, die einheitlich sein müssen. Aber wie teilen wir GLSL mit, welches Texturbild für welchen Sampler verwendet werden soll?

Texturen werden nicht direkt an GLSL übergeben, aber wir übergeben die Textureinheit, in der unsere Textur an OpenGL gebunden ist. Dies funktioniert folgendermaßen:

  • Bringen Sie den Probenehmer an einen einheitlichen Ort.
  • Binden Sie die Textur an die Textureinheit i.
  • Übergeben Sie i als ganze Zahl mit glUniform.

Hier ein Beispiel:

Copy to Clipboard

I ist die Textureinheit, in der wir unsere Textur binden möchten.

Es gibt viele weitere nützliche Funktionen für die Verwendung von GLSL.

Shader Hardware/Treiber Übersicht.

Gut, wir haben schon viel über Shader und GLSL gehört. Aber welche Hardware ist in der Lage, Shader zu betreiben.

GLSL ist dem DirectX Shader Model 3.0 sehr ähnlich.

Zur Zeit ist GLSL auf den folgenden Grafikkarten verfügbar:

ATI Radeon 9500, 9600, 9700 und 9800 mit Catalyst 4.5 treiber (der jetzt der fehlerfreieste ATI GLSL Treiber ist).

Nvidia GeForce FX 5200, 5600, 5700, 5800, 5900, 5950 mit Forceware 60 Serie.

Da diese Karten nur Shader Model 2.0 unterstützen, sind einige Funktionen von GLSL nicht verfügbar, wie z.B. Loops oder Vertex Shader Sampler.

Wir glauben nicht, dass etwas älteres als diese Karten GLSL Fragment Shader unterstützt. Ich denke jedoch, dass es eine Vertex-Shader-Emulation in der Treibersoftware für diese Karten geben könnte.

GLSL Shader Entwicklungswerkzeuge.

Es gibt auch einige schöne Shader Entwicklungswerkzeuge für GLSL.

Typhoon Labs hat ein wirklich nettes Shader-Entwicklungswerkzeug mit der Bezeichnung Shader Designer. Sie erhalten es unter http://www.typhoonlabs.com

ATI und 3Dlabs arbeiten auch an einer GLSL Version von RenderMonkey. Dieses Tool ist noch nicht verfügbar, sollte aber bald erscheinen.

Vielen Dank fürs Lesen.