|
|
Diese Lektion befasst sich mit einer recht einfachen und doch effektiven Beleuchtungstechnik aus der 3D-Welt: dem Gouraud-Shading. Sie erläutert die Mathematik hinter dem Verfahren (keine Angst, es wird nicht viel :) und macht einen kurzen Anriss von Objektmaterialien in OpenGL. Letztendlich soll dann versucht werden, selbst das Gouraud-Shading in Form eines Vertexshaders zu implementieren.
|
|
Gourad-Shading ist ein spezielles Rendering-Verfahren. Beim Gouraud Shading wird lediglich die Farbe der Vertizes einer Primitive berechnet, diese kann z.B. vom Material und auch von der Beleuchtung abhängen. Nach der Projektion der Vertizes werden die einzelnen Fragmente eingefärbt, indem zwischen den Farben der Eckpunkte des Polygons interpoliert wird. OpenGL setzt dieses Verfahren bei Benutzung der festen Funktionalität ein wenn glShadeModel auf GL_SMOOTH gesetzt wurde. Wir werden dieses Verfahren im Laufe dieser Lektion jedoch mit einem Shader umsetzen.
|
|
In welcher Farbe ein Objekt einem Betrachter erscheint hängt in der Realität von sehr vielen Faktoren ab. Um die ganze Angelegenheit nicht allzu kompliziert zu machen, wollen wir uns auf einige wenige beschränken:
| Faktor | GLSL Entsprechung |
|---|---|
| Die Farbe des Objektes | gl_FrontMaterial.diffuse |
| Die Farbe des Lichtes | gl_LightSource[0].diffuse |
| Die Position des Lichtes | gl_LightSource[0].position |
Wie man der Tabelle entnehmen kann, kann in einem GLSL-Shader auf das aktuelle Material über die vordefinierte Uniform Variable gl_FrontMaterial zugreifen. Genauer gesagt ist dieses das Material der Vorderseite der Faces, analog dazu existiert auch die vordefinierte Variable gl_BackMaterial über die auf das Material der Rückseite der Faces zugegriffen werden kann. Da wir aber ohnehin nur die Faces von der Vorderseite rendern interessiert uns dies nicht weiter.
Über die Material Variablen kann man grundsätzlich auf alle Materialeigenschaften zugreifen, die ein OpenGL-Material hat:
| Name | Datentyp | Beschreibung |
|---|---|---|
| gl_FrontMaterial.diffuse | vec4 | Der diffuse Lichtanteil, der vom Material reflektiert wird |
| gl_FrontMaterial.ambient | vec4 | Der ambiente Lichtanteil, der vom Material reflektiert wird |
| gl_FrontMaterial.specular | vec4 | Der Glanzlichtanteil, der vom Material reflektiert wird |
| gl_FrontMaterial.emission | vec4 | Der Lichtanteil, der vom Material ausgeht |
| gl_FrontMaterial.shininess | float | Der Glanzlichtexponent des Materials |
Auf sämtliche Lichtquellen kann über das vordefinierte Array gl_LightSource[] zugegriffen werden. Auch hier sind neben diffuse sämtliche anderen Eigenschaften einer Lichtquelle über GLSL zugreifbar.
Diese Angaben dienen nur zur Information, in dieser Lektion werden wir uns auf die in Tabelle 1 angegebenen Faktoren beschränken.
|
|
Bei der Bestimmung der Färbung einer von einer Lichtquelle beleuchteten Oberfläche, geht man davon aus, dass die Intensität proportional zur diffusen Komponente des Materials und zur diffusen Komponente des Lichtes ist. Außerdem ist die Intensität proportional zum Winkel zwischen dem Lichtvektor und der Normalen der Oberfläche. Demnach ergibt sich folgende Formel:

Diese Berechnung muss also in einem Gouraud-Shader für jede Vertex durchgeführt werden. Die Bestimmung der ersten beiden Komponenten der Formel ist dabei trivial: Ld entspricht gl_LightSource[0].diffuse und Md entspricht gl_FrontMaterial.diffuse.
Um die dritte Komponente zu bestimmen, muss man den Cosinus des Winkels zwischen Lichtvektor und Normale berechnen. Zum Glück kommt uns OpenGL da sehr entgegen, da es dem Shader für jede Vertex auch ihre Normale (gl_Normal im Vertexshader) übergibt. Der Cosinus des Winkels zwischen Normale und Lichtvektor lässt sich dann berechnen, indem man das Skalarprodukt des Normalenvektors und des Lichtvektors bildet. Hierfür müssen beide Vektoren die Länge 1 besitzen, also normalisiert sein. In GLSL heißt die Funktion zum Berechnen des Skalarproduktes dot(vec1, vec2) (vom englischen dot-product).
Die Folgende Graphik veranschaulicht den oben beschriebenen Vorgang:

|
|
Bei der Berechnung des Skalarproduktes mittels der Normalen muss einiges beachtet werden. Das Skalarprodukt kann nur funktionieren, wenn sich sowohl Normale als auch Lichtvektor im selben Korrdinatensystem befinden. Der Lichtvektor befindet sich im "Eye Space", das ist das Korrdinatensystem in dem die Szene von der Kamera (oder vom Auge) aus betrachtet wird. Die Normale hingegen befindet sich im lokalen Koordinatensystem ihres Modells. Wir müssen also einen der beiden Vektoren transformieren. Da es sich als umständlich erweisen wird den Lichtvektor in alle möglichen lokalen Modellkoordinatensysteme zu transformieren, entscheiden wir uns für die Normale. Da wir in einem Shader alles selbst in die Hand nehmen müssen, müssen wir auch diese Aufgabe selbst erledigen.
Die erste Idee, die man haben könnte ist die Normale mit der gl_ModelViewMatrix zu transformieren. Schließlich ist dies die Matrix die eine Vertex in den "Eye Space" transformiert. Dieses muss aber fehlschlagen, da die Normale nur drei Komponenten hat und die gl_ModelViewMatrix eine 4x4 Matrix ist. Die Reduzierung der gl_ModelVieMatrix auf ihre oberen 3x3 Komponenten ist eine Lösung die häufig funktioniert in einigen Fällen aber fehlschlägt (aus Gründen auf die wir hier nicht näher eingehen können). Um diesem Dilemma ein Ende zu bereiten bietet uns GLSL die gl_NormalMatrix, eine 3x3 Matrix, die die Normale in den "Eye Space" transformiert. Da diese Transformation die Länge der Normalen verändern kann muss sie danach wieder normalisiert werden. Die GLSL Funktion hierfür ist normalize(). Der vollständige Code zum transformieren der Normalen heißt also:
vec3 Normal = normalize(gl_NormalMatrix * gl_Normal);
|
|
Wie Eingangs bereits erwähnt, soll nun ein Gouraud-Shader programmiert werden.
|
|
Es soll das oben beschriebene Gouraud-Shading umgesetzt werden. Dabei müssen nur die diffusen Komponenten von Licht und Material beachtet werden. Der Vertexshader muss also neben der Transformation, die bereits aus Lektion 2 bekannt ist, einen Farbwert berechnen.
Der Pixelshader ist bereits vorgegeben und muss nicht weiter verändert werden, ein Blick in den Code schadet natürlich dennoch nicht. Er gibt nur die vom Vertexshader berechnete Farbe aus. Die für das Gouraudshading notwendige Interpolation wird automatisch von der Hardware erledigt.

|
|