.heightMap IV

Shadowmap II
.zurück zu heightMap III

Heute habe ich das fehlende Phong Shading nachgesetzt - bzw. nur einen Teil davon. Nähere Informationen zum Phong Beleuchtungsmodell sind auf meinem zugehörigen Artikel zu finden: .phongBeleuchtungsmodell

Bisher wurde das Shading von OpenGL übernommen, was natürlich auch viel Zeit für ein sonst recht statisches Terrain in Anspruch nimmt.
Die zuvor vorgestellte Art des Shadowmappings wird auch hier wieder genutzt, ich erstelle eine Lightmap in die diesmal sowohl der Schatten als auch der diffuse gerichtete Lichtanteil entsprechend des Phongmodells eingearbeitet wird.
Dazu muss die Heightmap erweitert werden, um in jedem Punkt die jeweilige Oberflächennormale liefern zu können. Bevor ich das Ergebnis im einzelnen vorstelle: Hier nocheinmal ein Blick auf die nun völlig ungeshadete Szene:


 

 
Vorgehen
Wie bereits gesagt ist vom gesamten Phong Shading eigentlich nur der ambiente und der diffuse Lichtanteil interessant, da die Szene sonst glänzend und kunststoffähnlich aussehen würde. Die diffuse Lightmap ist vom Prinzip her auch recht einfach zu implementieren: Für jeden Punkt der zu erstellenden Lightmap wird der Normalenvektor errechnet und per Skalarprodukt ins Verhältnis zur Lichtquelle gesetzt - es ergibt sich graphisch interpretiert wieder eine Bitmap, die nun an den Stellen erhellt ist, an denen die Oberfläche zur Lichtquelle senkrecht steht.

Die so gewonnene diffuse Lightmap wird mit der zuvor beschriebenen Shadowmap multipliziert und ergibt die endgültige Lightmap, die wieder auf die Textur angewand werden kann. Alle involvierten Texturen und Maps sehen wie folgt aus:

 

 
Bei den gezeigten Maps wurde als Sonneneinstrahlrichtung - sowohl für die Ebenenrichtung als auch die Höhe - ein 45 Grad Winkel genutzt.
Deutlich erkennt man, dass nun auch der Boden am Rand grau und nicht mehr weiss ist. Die mit ca. 45 Grad Neigungswinkel zur Sonne hin gerichteten Berghänge leuchten auch deutlich heller auf, als der am Rand liegende ebene Untergrund.
Gut zu sehen ist auch die Verbindung der bereits eingeführten Shadowmap mit der diffusen Map, sowie deren Gesamtanwendung auf die Basistextur.
 

 
Traps
Probleme bei der Implementation traten nun allerdings wie folgt auf: Zunächst einmal muss man die Normale aus den Punkten der Heightmap berechnen. Das ist möglich, wenn man die Skalierung und die echten MapUnits per HeightMapPixel in die Rechnung einfließen lässt. Man kann dann die Höhendifferenzen in x und y Richtung der Heightmap zur Konstruktion zweier Spannvektoren und diese wiederum per Kreuzprodukt zur Normalenkonstruktion nutzen.

Allerdings entsteht an diesem Punkt mehr oder weniger ein Problem: Die beschränkte Genauigkeit der Heightmap. Wird nur ein Kanal der Heightmap zur Wertekodierung verwendet, so kann maximal zwischen 256 Höhenwerten unterschieden werden. Für das polygonale Netz war das bisher egal, da zwischenwerte zumindest linear interpoliert werden konnten und fehlende Höhengenauigkeit so relativ gut überbrückt wurde.
Bei den Normalen ergibt sich hierbei jedoch ein Problem, wie man an der folgenden Skizze erkennen kann:

Wie man sieht, kann man zwischen zwei vorhandenen Werten mit relativ wenig Aufwand Zwischenwerte Interpolieren, die zugehörigen Normalen bleiben aber stets die gleichen. Lineare Interpolation eben. Es Verhält sich wie mit der Ableitung einer konstanten Funktion. Gerade ist Gerade und die Ableitung bzw. hier die Richtung der Normalen bleibt konstant.
Natürlich steht es frei nur die richtigen (hier in der Skizze: die Weißen) Normalen zu berechnen und zwischen diesen linear zu interpolieren, allerdings sind benachbarte Originalpunkte durch die Ungenauigkeit ja wieder schrittweise ungenau... "diskrete Sprünge" ist hier das Schlagwort.

Zunächst einmal wie sich diese Problematik äußert (zum exakten Verständnis habe ich die Bilder so hochauflösend wie möglich...):


 

 
Man sieht deutlich - neben den Vertiefungen, die durch die Hügeliteration bei dieser Art Hightmap entstand - diskrete Sprünge speziell im Randbereich der Map. Diese Rillen entstehen faktisch überall, fallen aber besonders in flachen Gebieten unangenehm auf. Es ist im Prinzip nichts anderes wie der altbekannte "Treppenstufeneffekt" bei schwach steigenden/fallenden Linien. Nur das es diesmal 3D ist und genaugenommen noch in einer Textur. Der Effekt tritt also gar nicht "echt" auf - sprich als 3D Vertexverformung, sondern wird lediglich durch einen Bumpmappingeffekt suggeriert.
In unserem Fall ist es natürlich extrem unangenehm, dass Bumpmaps durch reines Lichtspiel optisch Strukturen hervorrufen. Das sie das auch wirklich tun, ist eindrucksvoll zu sehen:

Im höher gelegenen Gelände sieht dieser Bumpmap-Effekt eigentlich gar nicht mal schlecht aus, aber an flachen Stellen stört es durch die unrealistische Gestalt.
In diesem Fall haben wir jedoch vor einen Schatten zu werfen und keine Struktur zu erzeugen, eigentlich genau das Gegenteil.
Ich für meinen Teil vermerke mir Bumpmapping als weiteres interessantes Themengebiet, versuche den Effekt jetzt jedoch zu unterdrücken.


 

 
Versuch 1
Statt einer Normalen werden mehrere zusammen gemischt um so kleinere lokale Unebenheiten zu unterdrücken. Das Ergebnis wird besser, aber...

Bevor ich auf den Tradeoff eingehe kurz zur Info: Um den oben beschriebenen Bumpmapeffekt zu sehen, habe ich bereits Bilder mit 9 gemittelten Normalen genutzt. Mit einer einzelnen diskreten Normale sieht das Bild vollkommen verrauscht aus:

1 Normale

3x3 = 9 Normalen

4x4 = 16 Normalen

5x5 = 25 Normalen

...das Ergebnis wird zwar deutlich besser, aber über die benötigte Zeit brauche ich nichts zu sagen. Diese wächst unverhältnismässig an und ist einfach kein gangbarer Weg.
In meinen Quelltexten habe ich die Möglichkeit offengelassen dieses Feature zu nutzen, wenn man viel Zeit investieren will, aber ich habe eine relativ gute Lösung gefunden.
 

 
Versuch 2
Aus der Not eine Tugend machen. Geringe Qualität interpolieren, das entstehende Blurring als weichen Schatten verkaufen!
Die Rechenzeit wird gemindert und das Aussehen gesteigert. Das optische Maximum ist jedoch so ein Geheimnis. Ein echter Tradeoff. Bei obigen Bildern wurde jeweils mit einer 1024x1024 Pixel großen Textur und einer ebenfalls 1024x1024 Pixel Lightmap gearbeitet. Die Normalen geben aber ohne großen Zeitaufwand nicht genug für eine solche Lightmap her. Zudem ist das Ergebnis auch bei langer Rechenzeit nicht zufriedenstellend, da BMP-Heightmaps einfach zu wenige Werte unterscheiden können.

Die simple Lösung: Die Lightmap wird nur mit einem Bruchteil der Größe berechnet. Nach Abschluss der Berechnungen wird sie einfach vergrößert (ein bilinearer Filter genügt) und das Ergebnis ist dann auch ebenso mit einem Bruchteil der Zeit erzeugt worden.
Wir erinnern uns, dass die Heightmap keine BumpMap ist und wir keine optisch scharfen Konturen erzeugen wollen, sondern einen "smoothen" Schatten.

Die folgenden Bilder sind jeweils mit 3x3 Normalen, einer 1024x1024er Textur und einer gleich oder niedriger aufgelösten Lightmap entstanden.
(Der erste Zahlenwert gibt die Ausmaße der Textur, der zweite die Lightmap an)

 

 

 

 

 

 
Phong Light Koeffizienten
Die Koeffizienten für die Gewichtung des ambeinten und diffusen Lichtes sind natürlich auch einsetzbar um die Kontraste und Gesamthelligkeit der Oberfläche zu regeln:
Wie am letzten Bild gezeigt wird, sind während der Rechnung sogar negative Gewichte möglich.  

 

 

 


 

 

 

 


.zurück zu heightMap III zurück