Pythonstuff GLSL in English Pythonstuff GLSL auf Deutsch Pythonstuff GLSL Pythonstuff
PythonStuff Home
 

 

Anisotrope Reflexe für realistisches Haar mit GLSL

Durch Unebenheiten mit einer bestimmten Vorzugsrichtung (gebürstetes Metall, Haare, Seide usw.) wird das Licht richtungsabhängig reflektiert. Für den theoretischen Hintergrund gibt es z.B. hier Unterlagen.

Zuerst mal das Programm:

und die Texturen dazu (das Verzeichnis auf “textures” umbenennen):

Für jene, die das “mal eben” ausprobieren wollen, einfach auch die folgenden Python-Dateien ins gleiche Verzeichnis kopieren:

Die Bibliothek "Pylget" muss natürlich auch installiert sein.

Das Ergebnis sieht etwa so aus:

(von links nach rechts: Blinn/Phong, Phong, Blinn/Phong auf Bumpmap)

Anisotrope Glanzlichter - Blinn/Phong Anisotrope Glanzlichter - Phong Anisotropes Glanzlicht mit Bumpmap

Anisotrope Glanzlichter - Wolle Anisotrope Glanzlichter - Wolle Anisotrope Glanzlichter - Haar

Anisotropie

Für dieses Programm habe ich die relevanten lokalen Vektoren umbenannt:

  • E - der Vektor zum Auge
  • L - der Vektor zur Lichtquelle (das kann L1, L2,.. für mehrere Lichtquellen sein)
  • H - der “Halb-Vektor”: das Mittel zwischen E und L (norm(norm(E)+norm(L))
  • N - der Normalen-Vektor - standardmässig (0,0,1), aber durch die Bumpmap modifiziert

Das folgende Bild ist aus der Arbeit Ashikhmin,Shirley: An Anisotropic BRDF Model (2000) entnommen, diese Arbeit sollte man sich jedenfalls angesehen haben.

Lokale Geometrie und die relevanten Vektoren

Die Vektoren E,L,H liegen in einer Ebene - im allgemeinen Fall liegt N nicht in dieser.

Um meinen Anisotropie-Algorithmus zu erklären (ist der neu (August 2011) ? ich bin mir nicht sicher), führe ich noch einen Vektor ein:

  • T - Faser-Richtung (in der Basis-Ebene)

Vektoren für die anisotrope Oberfläche

Betrachten wir die grüne Ebene, die durch T und N aufgespannt ist. Wenn E,L (und daher auch H) in dieser Ebene liegen, ist die Reflektion am hellsten. Sie verschwindet, wenn die Vektoren orthogonal dazu liegen. Um das zu erreichen, projiziere ich (rote Linien) die Vektoren auf diese Ebene und führe die Glanzlicht-Berechnungen (Phong oder Blinn-Phong) mit diesen projizierten Vektoren E',L',H' (blau) durch.

(Beachte, dass für Phong-Shading nur E' und L' benötigt werden, für Blinn-Phong-Shading nur H', dass also nur eine Projektion berechnet werden muss).

Ok ? Also weiter zur Implementierung - um die beiden Shading-Algorithmen vergleichen zu können, habe ich die Taste 'M' (Model) eingeführt, um zwischen beiden umzuschalten.

Anisotropie - Coding

Zur Erklärung des Quellcodes: wir erweitern das Beispiel 5, das sich mit “Parallax Mapping” beschäftigt hat.

Zu Beginn wird der Hilfe-Text erweitert:

I = Isotropic/Anisotropic Lighting
M = Model (Phong/Blinn-Phong)

Die Standard-Werte werden ganz unten im Programm gesetzt, wir beginnen mit “anisotropisch” und “Phong”-Shading.

toggleaniso = True
togglemodel = False # Phong

Die “import” Statements habe ich etwas verändert, weil ich in der Zwischenzeit auf Pydev mit Eclipse als Entwicklungsumgebung umgestellt habe und dort werden unqualifizierte (“*”)-Importe ausführlich angemeckert:

from math import pi, sin, cos, sqrt
from euclid import Vector3
from pyglet import resource
from pyglet.window import * #@UnusedWildImport
from pyglet.gl import *     #@UnusedWildImport
from shader import Shader  

So zeige ich im Vorübergehen, wie man diese Pydev-Warnungen unterdrückt :-)

Übrigens - einige Leute haben die Frage gestellt, wo denn die Libraries sind, mit denen ich die ganze Zeit arbeite. Ich habe daher die relevanten py-Files hier nochmal angeführt, damit die Site-Dependencies klein bleiben:

“Pyglet.py” sollte ordentlich installiert werden, sonst gibt's später Schwierigkeiten bei Upgrades.

Weiter gehts: die beiden neuen Schalter können mit den Tasten 'I' und 'M' verändert werden und ihr aktueller Zustand wird an das Shader-Programm weitergereicht:

shader.uniformi('toggleaniso', toggleaniso)
shader.uniformi('togglemodel', togglemodel)

Ich habe ein paar Zeilen für die Positionierung des Hilfe-Textes verändert, aber die sind nicht so besonders interessant. Das Wichtigste ist natürlich das

Shader Programm für Anisotrope Glanzlichter

Wie beim vorigen Beispiel lese ich die Farbe jedes Pixels (nachdem ich im “Parallax-Shading”-Teil die Position berechnet habe) und die Normale aus den entsprechenden Texturen:

vec4 texColor = vec4(texture2D(my_color_texture[0], newCoords).rgb, 1.0);
vec3 norm     = normalize( texture2D(my_color_texture[1], newCoords).rgb - 0.5);
vec3 gloss    = texture2D(my_color_texture[3], newCoords).rgb; // Glossmap: r=brightness, g=specular exp, b=strand-dirx

Neu ist die “gloss”-Textur, dieses RGB-Bild liefert

  • (R)ed - Helligkeit des Glanzlichtes
  • (G)reen - den Specular Exponent (genauer: dessen Wurzel)
  • (B)lue - Die Faser-Richtung des Haarbündels oder Bürst-Richtung bei Metall

Der (B)lue-Kanal ist folgendermassen codiert:

  • 0 bedeutet “isotrop”
  • 3..255 = Faser-Richtung 1..180 Grad zur Tangente

Beachte, dass die Farbwerte bei “jpg”-kodierten Texturen speziell an Kanten kräftig überschiessen können - daher habe ich einen Sicherheitsabstand vom “0”-Wert eingehalten.

Zur Berechnung der “Phong”-Formel für das Glanzlicht verwende ich etwas wie

specular = pow ( max(0.0, R.E), gloss.g * gloss.g * 128.0)

wobei “R.E” das Innere Produkt von “R” (der Reflektierte Licht-Vektor) mit “E” der Augen-Vektor.

Der Exponent ist 32 für einen “Grün”-Wert von 128 (das kommt von gloss.g = 0.5, so 0.5*0.5*128.0 = 32.0) und maximal 128.

Im isotropen Fall ist die Phong Berechnung nicht allzu überraschend, aber der anisotrope Fall ist erstaunlicherweise genau das Gleiche:

R•E for Phong
N•H for Blinn-Phong

Das liegt daran, dass die “Anisotropie” schon vor der “Phong” oder “Blinn/Phong” Berechnung erledigt wird, indem die Augen- und Licht-Vektoren manipuliert werden.

Ich projiziere die beiden Vektoren in eine Ebene, die parallel zum Faserrichtung liegt. Dazu wird der Normalvektor auf diese Ebene berechnet:

vec3 tplane = cross( N, vec3( straindir, 0.0 )); // normal vector of T-Plane 
E  = normalize( E  - dot(E,  tplane) * tplane ); // project eye   to texture plane

Weil die Faserrichtung “straindir” in der Tangentenebene liegt, liegt auch dieser Orthogonalvektor in der Tangentenebene, wenn die Normale N nicht durch die Bumpmap verändert wird. Dann wird der “E”-Vektor und der “R”-Vektor in diese Ebene projiziert. Auf diese Weise erhält man eine Standard-“Phong”-Reflektion, wenn man entlang der Faserrichtung schaut.

Wie man aus Wikipedia lernen kann, ist die Berechnung der “Blinn-Phong” Reflektion in einigen (häufigen) Spezialfällen deutlich einfacher. Insbesondere ist der Fall interessant, bei dem die Lichtquelle(n) und das Auge sehr weit von der Oberfläche entfernt sind - dann werden “L” und “E” konstant.

“Blinn-Phong” scheint für viele Materialien die bessere Annäherung an die Wirklichkeit zu liefern als “Phong” (siehe Experimental Validation of Analytical BRDF Functions), daher habe ich mir das auch angesehen: Das nette Ergebnis ist, dass nur ein einziger Vektor (“H”) in die Faser-Ebene projiziert werden muss - also ist auch hier die Blinn-Phong-Berechnung einfacher, schneller und (meist) realistischer.

Hier ein direkter Vergleich von Blinn/Phong (links) mit Phong (rechts) im isotropen Fall:

Isotropes Glanzlicht - Blinn/Phong Isotropes Glanzlicht - Phong

Der Unterschied wird bei Streiflicht recht augenscheinlich.

Im Quellcode sind einige Berechnungen redundant, damit beide Beleuchtungsmodelle jeweils isotrop und anisotrop demonstriert werden können. Diese Berechnungen können natürlich entfallen, wenn man sich für das eine oder andere Berechnungsmodell entschieden hat.

Das war's..

Und jetzt zu etwas völlig anderem: Beispiel 8 - Environmental Bump Mapping mit GLSL Cube Mapping !

Schon das Beispiel 6 - Vertex Offset Shader für deformierbare Objekte gesehen ?

Literatur:


English version, Start
Impressum & Disclaimer