

Eine Skybox ist eine Art der Environment Map, die eine Himmeltextur enthält. Skyboxen werden meist entweder in Cube Maps oder Longitude/Latitude Maps gespeichert.
Longitude / Latitude Maps
Hier können die Texturkoordinaten
Siehe auch
Eine mögliche Implementierung einer Skybox durch Longitude / Latitude Maps in GLSL könnte so aussehen:
Fragment Shader
#define PI 3.1415926535897932384626433832795
vec3 d = normalize(pos_wc - cam_pos);
vec2 uv = vec2(
0.5 + atan(d.z, d.x) / (2 * PI),
0.5 - asin(-d.y) / PI
);
out_col = vec4(texture(tex, uv).rgb, 1);Mit Normal Maps lassen sich Normalen auf einer Textur definieren. Das ist Hilfreich um z.B. mehr Details in der Beleuchtung zu erzielen ohne mehr Primitive zu rendern.
Normal Maps werden als reguläre Textur gespeichert und im Tangentialen Koordinatensystem interpretiert. Die
Berechnung
Normalen werden oft für Lighting benutzt (siehe z.B. Phong-Beleuchtungsmodell). Problem: Normalen sind im Tangent Space, andere Lighting-Vektoren (z.B. Lichtvektor
Siehe auch
Option 1: Im Worldspace bzw. Eyespace rechnen
Eine Option wäre, die Normale
Option 2: Im Tangent Space rechnen
Alternativ könnte man auch alle Vektoren in den Tangent Space zu überführen und das Lighting dort zu berechnen. Einen Lichtvektor
(Analog wird das auch für alle anderen für das Lighting relevanten Vektoren gemacht.)
Anmerkung
Da
ein Orthonormalsystem ist, ist die inverse Matrix gleich der transponierten Matrix:
Anmerkung: Performance
Aus Performance-Gründen wird meistens Option 2 gewählt, weil die Multiplikation mit der TBN-Matrix hier im Vertex-Shader berechnet werden kann. Bei Option 1 würde man die Normale brauchen, die nur im Fragment-Shader läuft. Und da der Fragment-Shader öfter läuft als der Vertex-Shader, ist es aus Performance-Sicht günstiger, die Berechnung im Vertex-Shader vorzunehmen. Die im Vertex-Shader berechneten Richtungsvektoren können dann an den Fragment-Shader weitergegeben werden und werden interpoliert.
Siehe auch
Mip-Mapping
Normal Maps lassen sich schlecht Mip-Mappen, weil wir hier nicht einfache Farben averagen, sondern Richtungsvektoren. Probleme, die auftreten können sind unter anderem:
- Normalen die in entgegenliegende Richtungen zeigen werden geaveraget, das Ergebnis zeigt in die Mitte. Wir haben auf einmal ein Specular Highlight, da wo eigentlich keins ist
- Der Average von zwei Normalen ist nicht normalisiert, heißt Lichter werden heller oder Dunkler
Eine mögliche Implementierung von Normal Maps in GLSL mit Lighting-Berechnung im Tangent Space könnte so aussehen:
Vertex Shader
// Normale
in vec3 in_norm;
// Tangente wird vor dem Vertex-Shader berechnet,
// weil hierfür alle drei Vertices bekannt sein müssen.
in vec3 in_tan; // Tangente
// Im Tangent Space
out vec3 direction_ts;
void main() {
vec3 bi_tan = cross(in_norm, in_tan); // Bi-Tangente
// TBN vom Model-Space in Worldspace rechnen
vec3 T = normalize(vec3(model_normal * vec4(in_tan, 0)));
vec3 B = normalize(vec3(model_normal * vec4(bi_tan, 0)));
vec3 N = normalize(vec3(model_normal * vec4(in_norm, 0)));
// Tangent Space aufsetzen
mat3 TS = transpose(mat3(T, B, N));
// Richtungen in den Tangent Space rechnen
direction_ts = normalize(TS * direction_ws);
/* ... */
}Fragment Shader
Normal Maps sind Texturen, die wie gehabt in den Shader geladen und ausgelesen werden können.
in vec2 tc;
uniform int has_normalmap;
uniform sampler2D normalmap;
void main() {
/* ... */
// Fallback für die Normale
vec3 N = vec3(0,0,1);
if (has_normalmap == 1) {
// Normalenrichtung aus der Textur holen
vec4 n = texture(normalmap, tc);
// Normale aus Richtung bauen.
// Wertebereiche müssen gemapped werden:
// rgb: [0, 1] --> xyz: [-1, 1]
// Umrechung mit x --> 2x - 1
N = normalize(vec3(
n.r * 2 - 1,
n.g * 2 - 1,
n.b * 2 - 1
));
}
// N kann jetzt für z.B. Phong-Lighting verwendet
// werden, vorausgesetzt alle anderen Richtungen
// sind auch im Tangent Space.
/* ... */
}Siehe auch





Mask Textures (auch Alpha-Maps) erlauben es uns, detailliertere Fragmente zu rendern als wir eigentlich Primitive haben. Das Funktioniert, indem wir Masken definieren, die bestimmen ob wir ein Fragment rendern oder nicht. Ist die Maske an, wird es wie gehabt gerendert. Ist die Textur aus, rendern wir es nicht. Das kann mit dem GLSL-Statement discard; gemacht werden.

Um die Texturen werden oft ähnliche Farben gelegt, weil heruntergesampelte Varianten (z.B. durch Mip-Mapping) die Randfarben der Textur miteinbeziehen. So wird vermieden, dass die Texturfarbe einen schlechten Durchschnitt macht.
Siehe auch
Eine mögliche Implementierung von Mask Textures in GLSL könnte so aussehen:
Fragment Shader
Die Alpha-Map wird als Uniform Texture importiert. Ein Flag kann helfen, ob die Map überhaupt existiert.
uniform int has_alphamap;
uniform sampler2D alphamap;Der eigentliche Check ist dann ein one-liner:
if (has_alphamap == 1 && texture(alphamap, tc).r < 0.5) discard;Mit discard können wir OpenGL sagen, dass er dieses Fragment nicht zeichnen soll.
Siehe auch
Das Texturmapping ist eineinfacher Ansatz, Details nicht nur auf Vertex-Ebene, sondern auch zwischen Vertices zu definieren. Dabei werden Bilder (Texturen) über die einzelnen Primitive gelegt.
Oft werden dabei für eine Textur Texturkoordinaten, meistens im Bereich

Siehe auch
Eine mögliche Implementierung von Texture Mapping in GLSL könnte so aussehen:
Vertex Shader
in vec3 in_pos; // Vertex-Position
in vec2 in_tc; // Texturkoordinaten
uniform mat4 mvp; // Transformation
out vec2 tc;
void main() {
// Textur-Koordinaten weiter an den
// Fragment Shader (werden dann interpoliert)
tc = in_tc;
gl_Position = mvp * vec4(in_pos, 1.0);
}Fragment Shader
Texturen kommen als Typ sampler2D in den Shader. Wir können den Wert an einer bestimmten Texturkoordinate mit der Funktion texture(sampler2D texture, vec2 uv) bekommen.
in vec2 tc; // Interpolierte Texturkoordinate
uniform sampler2D color; // Geladene Textur
out vec4 out_color;
void main() {
out_color = texture(diffuse_texture, tc).rgba;
}Das Phong-Beleuchtungsmodell (nicht zu verwechseln mit Phong Shading) ist ein einfaches lokales Beleuchtungsmodell. Es besteht aus 3 Komponenten:

Ambiente Komponente
Die Ambiente Komponente wird genutzt, um auch Objekte Leuchten zu lassen, an die kein direktes Licht kommt (da sie sonst schwarz wären).
Idee: Eine Konstante (abhängig vom Material) an jeder Stelle dazuaddieren.
Berechnung
Wir können einstellen:
- “Ambiente Farbe”
- mit welcher Farbe die Oberfläche ambient leuchten soll.
Gegeben dann noch wie viel Ambientes Licht es gibt
Diffuse Komponente
Mit der Diffusen Komponente lassen sich diffuse Reflexionen modellieren, z.B. die von matter Farbe, Stoff oder unpoliertem Holz.
Idee: Abhängig vom Winkel, in dem das Licht auf das Material einfällt, verstärkt oder weniger verstärkt reflektieren. Die Kameraposition ist dabei egal. Grundsätzlich wird bei kleinen Winkeln am meisten reflektiert. Je größer der Winkel wird, desto weniger wird reflektiert.
Warum ist das so?
Zwei Lichtstrahlen haben die selbe Intensität. Auf der Fläche hat ein Lichtstrahl wegen seines größeren Einfallswinkels eine größere Fläche. Dadurch hat das Licht da pro Fläche eine kleinere Intensität.
Die abfallende Intensität mit größer werdendem Einfallswinkel wird mit Hilfe des Lambertschen Gesetz modelliert. Dabei nutzen wir den Cosinus, der wachsendem Winkel abnimmt, also genau das Verhalten was wir brauchen.
Berechnung
Wir können einstellen:
- “Diffusionsfarbe”
- welche Farbe die Diffuse Reflexion haben soll.
Gegeben ist zusätzlich die eingehenden Lichtintensität
Im Shader wird aus effizienzgründen mit
Gleichzeitig müssen wir darauf achten, dass negative Einfallswinkel nicht in die Berechnung einfließen:
Notation
Statt
kann man auch schreiben, die beiden Ausdrücke sind äquivalent.
Spiegelnde Komponente
Mit dieser Komponente lassen sich glänzende Reflexionen modellieren, wenn die Lichtquelle direkt in der Reflexion sichtbar ist, z.B. auf Metall.
Anmerkung
Wir simulieren hier nur Reflexionen von Lichtquellen, nicht Reflexionen von anderen Objekten. Dafür brauche wir eine ganz andere Klasse von Algorithmen, siehe Raytracing.
Idee: Wir wollen das Licht nicht einfach nur spiegeln (dann würde man z.B. Punktlichtquellen nie sehen), sondern es auf der Oberfläche in einen kleinen Bereich streuen. Die Streuung nennt man auch Specular Highlight. Wir verringern also die Intensität der Reflexion um die Reflexionsrichtung, sodass in einem gewissen Bereich immer noch ein Anteil reflektiert wird.
Modelliert wird das durch einen potenzierten Cosinus von der Winkeldifferenz zur Reflexionsrichtung. Der Cosinus nimmt ja mit wachsender Winkeldifferenz ab. Durch die Potenz können wir zusätzlich einstellen, wie schnell der Cosinus abnimmt, also wie “breit” der Streubereich ist.
Berechnung
Wir können einstellen:
- “Shinyness exponent”
- wie breit das Licht gestreut werden soll. - Kleine Werte führen zu diffusen Reflexionen
- Große Were führen zu mehr glänzenden Reflexionen
- Reflexionsfarbe
- welche Farbe die Reflexion haben soll.
Gegeben ist dann die Winkeldifferenz zur Reflexionsrichtung
Mit
Anmerkung
Wir sehen, dass für diese Formel die Kameraposition gebraucht wird. Oft wird deshalb die Lichtberechnung im Eyespace gemacht, weil hier die Kamera ja der Nullpunkt ist. Damit fällt diese Variable weg.
Reflexionsrichtung bestimmen
Beim Rendern haben wir aber keinen direkten Zugriff auf die Reflexionsrichtung
Wir können die strecke, die die Lichtrichtung an der Normale
Multiplizieren wir die Strecke mit dem zweifachen der Normalen, kommen wir an die doppelte Strecke der Lichtrichtung an der Normalen entlang:
Wenn wir jetzt die Lichtrichtung abziehen, kommen wir genau an der Reflektionsrichtung raus:
Zusammenfassung
Nimmt man alle Komponenten zusammen und die
ist die ambiente Farbe am Fragment ist die Intensität der ambienten Beleuchtung
Pro Lichtquelle
ist die Anzahl der Lichtquellen in der Szene ist die Intensität der Lichtquelle
ist die diffuse Farbe am Fragment ist die Normale am Fragment ist die Richtung vom Fragment zur Lichtquelle
ist die spekulare Farbe am Fragment ist die Richtung von Fragment zur Kamera ist die Reflexionsrichtung der Lichtquelle, also ist der Shininess-Exponent am Fragment
Siehe auch
Eine mögliche Implementierung der diffusen und spekularen Komponente des Phong-Beleuchtungsmodell in GLSL könnte wie folgt aussehen:
Fragment Shader
// Helper: Skalarprodukt mit nur positiven Ergebnissen
float dot_p(vec3 a, vec3 b) {
return max(dot(a, b), 0);
}
// Phong-Shading
vec3 phong(
float I_in, // Lichtintensität
vec3 l, // Richtung zum Licht
vec3 v, // Richtung zur Kamera
vec3 n, // Normale
vec3 k_diff, // Diffuse Farbe
vec3 k_spec, // Spekulare Farbe
float n_s // Shininess-Exponent
) {
if (dot(n, l) < 0) return vec3(0);
vec3 diff = k_diff * dot_p(n, l);
vec3 r = 2 * n * dot(n, l) - l;
vec3 spec = k_spec * pow(dot_p(v, r), n_s);
return I_in * (diff + spec);
}Eine mögliche Implementierung in GLSL, um pro Model eine Farbe zu rendern, könnte so aussehen:
Fragment Shader
// Die Farbe kommt aus dem Model
uniform vec4 k_diff;
out vec4 out_col;
void main() {
// Direkt weiterleiten
out_col = k_diff;
}Hier ist eine Möglichkeit, um in GLSL jedes Primitiv in einer “zufälligen” Farbe zu rendern:
Fragment Shader
gl_PrimitiveID ist eine eindeutige Integer die innerhalb eines Primitiv gleich ist. Eine Farbpalette können wir im Voraus generieren und anschließend per Uniform hochladen.
uniform vec4 cols[100];
out vec4 out_col;
void main() {
out_col = cols[gl_PrimitiveID % 100];
}