Dienstag, 19. Juni 2012

Einfache SSIL-Implementierung (XNA 4.0)

Wozu SSIL?

Mein vorheriger Post zeigte SSAO als eine einfache Methode zur Bildverbesserung für euer Videospiel und sollte vor diesem hier gelesen werden. Auf Screen Space Indirect Lightning bin ich hier gestoßen - vorher habe ich davon auch noch nie etwas gehört. Aber die Idee ist nicht schlecht: Wenn wir beim SSAO sowieso für Bildpunkte Strahlen in zufällige Richtungen schicken und Tiefenwerte umliegender Pixel zur Verdeckung anschauen, wieso schauen wir dann nicht gleich noch, wie sie sich bezüglich Reflexion von Licht verhalten? Und das ist genau was passiert: Es werden Bildpunkte um einen Pixel gesucht und zusätzlich zum SSAO deren Albedo (Rückstrahlvermögen) betrachtet, um zusätzliches indirektes Licht auf den Bildpunkt zu übertragen wenn die Verdeckung vorhanden ist. So etwas wie indirektes Licht kennt ein lokales Beleuchtungsmodell nämlich nicht.

Schritt 1: Änderungen am SSAO-Shader

Der SSAO-Shader beinhaltet schon fast alles von dem, was wir brauchen. Zu Color-, Depth-, Normal- und Noise-Werten könnten wir noch den Licht-Wert brauchen. Ich habe hier den Vorteil, dass ich eine Deferred Rendering Enginge verwende, da habe ich den Licht-Wert sowieso in einem seperaten Rendertarget und er steht mir als Textur zur Verfügung. Im ersten Schritt kommt also hinzu:

float3 light = tex2D(lightSampler,input.TexCoord).rgb;

Vor der Schleife definieren wir noch eine Variable, wo wir die Information des indirekten Lichts speichern.

float3 resultRadiosity = 0;

In der Schleife kommen vier Zeilen Code hinzu. Zusätzlich zu den Fragmentkoordinaten brauchen wir noch den Lichtwert des verdeckenden Pixels und dessen Albedowert. Die Intensität der Rückstrahlung ist der Farbwert (Vektor), den wir mit Punktprodukt auf einen Skalar abbilden. Der Farbwert des Samples wird je nach Intensität (also Rückstrahlvermögen) auf den ursprünglichen Bildpunkt übertragen. 

float3 occluderLight = tex2D(lightSampler,se.xy);
float3 occluderAlbedo = tex2D(colorSampler,se.xy).rgb;
float intensity = dot(occluderAlbedo,1);

resultRadiosity += step(falloff,depthDifference)*normDiff*(smoothstep(color,occluderAlbedo,intensity));

Anschließend muss die Radiosity noch durch die Anzahl der Samples geteilt werden.

Bei der Betrachtung des Bildes ist mir aufgefallen, dass nun zwar Flächen indirekte Beleuchtung abbekommen, die Lichtfarbe dabei aber nicht berücksichtigt wird. Normalerweise reflektiert ein weißer Pixel, der mit rotem Licht beschienen aber kein weißes indirektes Licht, sondern leicht rötliches Zum Farbwert sollte also noch der Verdecker-Lichtwert (+occluderLight hinter der Klammer) dazurechnen werden. Ich habs bei mir mal so gemacht, die Szene wird dadurch aber merklich heller, das sollte man beachten. Eventuell muss man hier den Alphakanal hinzuziehen, und Bild und indirektes Licht hernach mit Alphablending zusammenführen, oder eine Abschwächung durchführen.

resultRadiosity += step(falloff,depthDifference)*normDiff*(smoothstep(color,occluderAlbedo,intensity)) + occluderLight;

Schritt 2: Fertigmachen

Eigentlich sind wir bereits fertig, da wir für jeden Pixel die Radiosity haben und diese einfach zurückgeben und über das Bild legen können. Jemand, der auch XNA 4.0 verwendet und damit auf SM 3.0 beschränkt ist, wird aber merken, dass die Samplerate sehr niedrig gestellt werden muss, damit er nicht über die Zahl der erlaubten Instruktionen kommt. Besonders da man SSAO- und SSIL-Anweisungen in einem Shader hat, hat man nicht viele Instruktionen übrig. Ich hab zu Demozwecken dafür kurz den SSIL-Shader ausgelagert und 13 Samples eingestellt. Das Ergebnis sieht folgendermaßen aus.

SSIL, kein Blur


Da die Körnung im finalen Bild erkennbar ist, wende ich wie beim SSAO einen Blur an. Lässt sich gut kombinieren. Das Ergebnis sieht schon ein wenig besser aus.

SSIL, mit Blur


Eventuell kann man noch etwas geschickter Normalen- und Tiefenwertvergleich einsetzen, sodass man ein besseres Ergebnis erhält. Ich wüsste im Augenblick aber nicht wie.

Der Vergleich zwischen dem Endergebnis mit und ohne SSIL seht ihr hier. Besonders gut sichtbar ist das Ergebnis am Bauch der Echse, wo ohne SSIL so gut wie überhaupt kein Licht hinkommen würde. Oder an der Unterseite des Raumschiffs. Die Lichtwirkung ist im untersten Bild vielleicht etwas heftig, vielleicht sollte man dies händisch abschwächen.

Bild ohne SSIL
Bild mit SSIL (nur Texturfarbe)
Bild mit SSIL (Texturfarbe und Lichtfarbe)
Bild mit SSIL (Texturfarbe und Lichtfarbe, Wirkung 25%)

Jetzt wo die Helligkeit nicht mehr all zu übertrieben ist, bleibt noch ein letzter Versuch, der allerdings wieder einige Instruktionen beansprucht: Anstatt die Intensität nur am Farbwert des Samples auszumachen, addieren wir noch dot(occluderLight,1), um die Intensität vom einfallenden Licht abhängig zu machen. Wo also wenig Licht hinfällt, wird auch wenig zurückgestrahlt, egal ob die Fläche jetzt sehr hell ist (und somit ohne Berücksichtigung ein hohes Rückstrahlvermögen hätte). Das Ergebnis ist das folgende. 

Bild mit SSIL (Texturfarbe und Lichtfarbe, Wirkung 25%), Rückstrahlvermögen berücksichtigt einfallendes Licht




Auch hier: Flächen an den Unterseiten kriegen normalerweise fast garkein Licht ab, mit SSIL das Umgebungslicht. Die Unterkante des Steins ist ebenfalls merklich anders beleuchtet. Ein sichtbarer Unterschied auch bei dem Pinüppel, dessen Unterseide nun nicht pechschwarz ist.

Bild mit SSIL (Texturfarbe und Lichtfarbe), Intensität berücksichtigt kein einfallendes Licht, keine Abschwächung

Bild ohne SSIL

Beurteilung und Einschränkungen

Ich will nicht erneut auf der hohen Instruktionszahl rumreiten, aber gerade wenn man zusätzliches Feintuning, wie die Intensität vom Lichtwert abhängig zu machen, reinbringt, bleibt nicht mehr viel übrig. Andere Einschränkungen dieser Implementierung sind, dass das indirekte Licht mit nur einem Lichtabprall berücksichtigt wird. Es wird also nur einmal reflektiert - und das funktioniert mit wenigen Samples auch nur dann gut, wenn der Radius der Strahlen für die Samples relativ klein bleibt. Außerdem wird nur berücksichtigt, was auf dem Screen zu sehen ist. Ist etwas, das stark abstrahlen würde, knapp aus dem Bild, ist dem Shader das völlig egal. Ist auch nichts dran zu ändern.

Wenn euch noch etwas auf- oder einfällt, wo ich vielleicht Denkfehler habe oder man Performance rausholen könnte, immer raus damit! Ansonsten gefällt mir das ganze schon ganz gut und ich werde weiter versuchen die Lichtfarbe etwas stärker mit in die Wirkung aufzunehmen.
Danke an alle, die irgendwas zum Thema online gestellt haben - wie immer gilt: Sie haben mich bestimmt beeinflusst.

Keine Kommentare:

Kommentar veröffentlichen