Open Source im professionellen Einsatz

Shader mit der OpenGL Shading Language programmieren

Schattenspiele

Programmierbare Grafikchips sind nichts Neues, doch inzwischen berechnen sie mit Shadern auch Lichteffekte. Mit der OpenGL Shading Language erledigt das nach Cg und HLSL nun auch eine freie Hochsprache.

Die Einführung der Geforce-3-Grafikkarten von Nvidia rief im Jahre 2001 vor allem bei vielen Programmierern von Computerspielen wahre Begeisterungsstürme hervor. Bis dato nahmen Grafikchips den Hauptprozessoren zwar bereits einige Arbeit ab, doch Entwicklern stand nur ein sehr begrenzter Befehlssatz zur Verfügung. Für darüber hinausgehende Effekte waren sie gezwungen, auf den frei programmierbaren Hauptprozessor auszuweichen.

Der Chip der Geforce 3 kannte erstmals Shader und somit programmierbare Lichteffekte. Entwickler schrieben in Assembler und steuerten die Hardware über herstellerspezifische Erweiterungen an. Doch mittlerweile etablierten sich Hochsprachen und es gibt auch außerhalb der Spielewelt Anwendungen in der Bild- und Videobearbeitung, zum Beispiel Core Image von Apple.

Nach Microsofts HLSL und C for Graphics (Cg) von Nvidia hat das OpenGL Architecture Board (ARB) mit der OpenGL Shading Language (GLSL, [1]) als Teil von OpenGL 2 ebenfalls eine C-artige Sprache ins Leben gerufen. Der neue Nvidia-Grafikkartentreiber für Linux unterstützt erstmals auch OpenGL 2 und ermöglicht es den Linux-Entwicklern damit, GLSL einzusetzen.

OpenGL-Pipeline

Auch programmierbaren Grafikchips übergibt man eine 3D-Szene aus Punkten im Raum, den so genannten Vertices (Singular: Vertex). Neben der Position enthalten Vertices Attribute wie die Farbe, Texturkoordinaten oder den Normalvektor. Der Weg von Vertexdaten aus der Anwendung in der Grafikkarte zu Pixeln auf dem Schirm heißt Pipeline (Abbildung 1).

Zuerst rechnet die GPU (Graphics Processing Unit) die Vertices eines Dreiecks von 3D-Weltkoordinaten auf 2D-Bildschirmkoordinaten um. Das geschieht über die Multiplikation des Ortsvektors eines Vertex mit einer Matrix. Die Bildschirmkoordinaten von drei Vertices spannen ein Dreieck auf dem Bildschirm auf. Beim Rastern stellt die GPU fest, welche Punkte auf dem Schirm zu diesem Dreieck gehören, und füllt sie gemäß der Vertexattribute. Gerade bei typischen Vektor- und Matrixoperationen ergeben sich durch programmierbare GPUs erhebliche Geschwindigkeitsvorteile gegenüber der CPU-Programmierung.

Die beim Rastern errechneten Punkte entsprechen aber noch nicht den Pixeln, die im fertigen Bild auf dem Schirm erscheinen: Dreiecke, die räumlich vor dem gezeichneten Dreieck liegen, können Pixel noch ganz oder teilweise übermalen, etwa bei Halbtransparenz oder an den Kanten bei eingeschalteter Kantenglättung. Das Zusammensetzen der endgültigen Pixelfarbe erfolgt erst während der Rasteroperationen. Ein Bildpunkt heißt deshalb vor den Rasteroperationen Fragment, weil er nicht unbedingt einem fertigen Pixel auf dem Bildschirm entspricht.

Herkömmliche Grafikchips gaben diese Pipeline fest vor, man spricht von einer Fixed Function Pipeline. Die neuen Grafikchips ersetzen in einer programmierbaren Pipeline erstmals einen Teil der Fixed Function Pipeline durch eigenen Programmcode. Der Entwickler kann an zwei Stellen eingreifen: Mit einem Vertexshader bei der Vertextransformation, die für jedes Vertex einzeln geschieht, oder mit einem Fragmentshader beim Einfärben und Texturieren, das pro Fragment ausgeführt wird. Fragmentshader heißen häufig auch Pixelshader; diese Bezeichnung ist zwar aus technischer Sicht ungenau, beschreibt die Funktion aber anschaulich.

GLSL und OpenGL 1.5

Die OpenGL Shading Language gehört erst ab Version 2 fest zum OpenGL-Standard. Als Extension gibt es sie aber bereits für OpenGL 1.5. Dort tragen die API-Funktionen noch leicht abweichende Namen und wie die meisten ARB-Extensions das Suffix ARB. Wer OpenGL 2 nicht zur Verfügung hat oder abwärtskompatibel entwickeln möchte, sollte zur Laufzeit abfragen, ob die GLSL-Extension vorhanden ist. Hier hilft die OpenGL Extension Wrangler Library [3].

Dieser Artikel verwendet die Namenskonvention von OpenGL 2. Die unterschiedlichen Bezeichnungen zwischen der GLSL-Implentierung in OpenGL 2 und der OpenGL-1.5-Extension nennt Hersteller ATI auf seiner Homepage [2]. Viele Tutorials benutzen jedoch die Konventionen von OpenGL 1.5, etwa das von Lighthouse 3D [4].

Abbildung 1: Ohne eine programmierbare GPU übergibt eine Anwendung Vertexdaten an die Fixed Function Pipeline, die daraus die einzelnen Pixel errechnet. Shader ersetzen die Vertextransformation oder das Einfärben und Texturieren durch Programmcode.

Abbildung 1: Ohne eine programmierbare GPU übergibt eine Anwendung Vertexdaten an die Fixed Function Pipeline, die daraus die einzelnen Pixel errechnet. Shader ersetzen die Vertextransformation oder das Einfärben und Texturieren durch Programmcode.

Elemente der Sprache

GLSL lehnt sich an C an. Sie kennt die Variablentypen »float«, »int« und »bool«, die sich auch zu Structures zusammenfassen lassen. Zur Verzweigung bietet GLSL nur »if« und »if-else« an, »switch« sieht sie nicht vor. Es gibt außerdem »for«- und »while«-Schleifen. Allerdings führt nur neuere Hardware, etwa die Geforce 6 und besser, Schleifen aus, die nicht vollständig ausrollbar sind. Also solche, deren Anzahl von Durchläufen nicht schon beim Kompilieren feststeht, sondern die eine Bedingung enthalten, die jeder Durchlauf überprüft. Auf älterer Hardware schlägt das Kompilieren einer nicht ausrollbaren Schleife fehl. Zum bedingten Kompilieren steht ein Präprozessor zur Verfügung, es gibt aber keine »#include«-Direktive.

Jeder Shader muss eine »main«-Methode enthalten, die keinen Rückgabewert ausgibt (»void«) und keine Argumente erwartet. Über die aus C übernommenen Datentypen hinaus bietet GLSL einige Erweiterungen für Grafikaufgaben. So gibt es eigene Datentypen für Texturen, Vektoren und Matrizen (Tabelle 1).

Die GLSL-Vektoren sind Datenstrukturen aus zwei, drei oder vier Elementen gleichen Typs. Sie eignen sich dazu, Koordinaten im Raum, Normalvektoren, Farben oder auch Texturkoordinaten zu speichern. Der Entwickler füllt Vektoren über Konstruktoren mit Daten. Diese erwarten Skalare, also einzelne Zahlenwerte, andere Vektoren oder eine Kombination aus beiden.

Tabelle 1: Neue
Datentypen GLSL

 

Datentyp

Beschreibung

vec2, vec3, vec4

Floatvector mit 2, 3 und 4 Elementen

ivec2, ivec3, ivec4

Integervector mit 2, 3 und 4 Elementen

bvec2, bvec3, bvec4

Boolvector mit 2, 3 und 4 Elementen

mat2, mat3, mat4

Floatmatrix mit 2x2, 3x3 und 4x4 Elementen

sampler1D, sampler2D, sampler3D

1D-, 2D- und 3D-Textur

samplerCube

Cubemap-Textur

Diesen Artikel als PDF kaufen

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook