Exponential Shadow Maps sind nicht weich zu kriegen

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Exponential Shadow Maps sind nicht weich zu kriegen

Beitrag von Schrompf »

Guten Tag, verehrtes Fachpublikum.

Ich bin aktuell mal wieder dabei, die Splitterwelten zu optimieren. Gegen die CPU-Last wird demnächst hoffentlich ein Multicore-Renderer kommen, aber vorerst will ich noch die Schatten für gelegentlich auftretenden Stellen mit massivem Overdraw optimieren. Meine Wahl viel dabei auf Exponential Shadow Maps, da die einzige mir bekannte praktikable Alternative Variance Shadow Mapping bei den üblichen Tiefenkomplexitäten einer Splitterwelt mehr Light leaked als ein chaotisch-guter Paladin.

Also Exponential Shadow Mapping. Sieht auf den Bildern, die man im Netz dazu findet, wirklich gut aus. Aber nicht bei mir. Ich habe als Vergleich immernoch meine 32xPCF-Implementierung hier, die auch sauber gegen Selbstschattierung geschützt wurde. Aber ESM bleibt bei mir knochenhart, selbst mit 11x11 Box- oder Gauss-Weichzeichner auf der Shadow Map, logarithmisch oder linear. Es ist alles wurscht, ich kriege Penumbra Regions von bestenfalls einem Texel Breite. Folgende Bilder für maximale Aufmerksamkeitserhaschung:

ESM, ohne Filtering. Sieht größtenteils wie normales Shadow Mapping aus, aber man erkennt die Wirkung bei Kontaktschatten, z.B. beim Charakter.
spl_schiff_esmf.jpg
spl_draussen_esmf.jpg
ESM, mit 11x11 Gauss-Filter. Schon besser, aber immernoch knochenhart. Erneuter Hinweis: exponentieller Filter sieht praktisch genauso aus, aber dazu später mehr.
spl_schiff_esm.jpg
spl_draussen_esm.jpg
Referenzbild: 32 PCF-Samples emulieren einen ungefähr 5x5-Boxfilter. Man bemerke die trotzdem weiter reichenden Penumbra Regions.
spl_schiff_pcf.jpg
spl_draussen_pcf.jpg
Jetzt meine Frage an die Experten: hat hier jemand schonmal Erfahrungen mit Exponential Shadow Mapping gesammelt? Was kann ich hier tun, um die Weite der Schatten-Licht-Übergänge so breit zu bekommen, wie die Filtergröße es verspricht? Zur Vollständigkeit noch der Schatten-Code:

Code: Alles auswählen

lichtStaerke *= saturate( exp( 10000.0f * (tex2D( TexSchatten, rein.mSchattenTexKoords.xy).x - rein.mLichtEntfernung)));
und beispielhaft der Quer-Filter, die lineare Variante. Die logarithmische Variante sieht mehr oder minder exakt gleich aus. Außer ich baue einen Multiplikator ein wie in den Beispielen im Netz, dann habe ich gar keinen Schatten mehr.

Code: Alles auswählen

float4 main( const PixelEingabe rein) : COLOR0
{
  float tiefe;
        
  // Mitte samplen als Startwert
  tiefe = tex2D( TexBild, rein.mTexKoords).x * 0.24609375f;
               
  // Texturkoords für die linken 5 Samples berechnen. 
  float2 t1 = saturate( rein.mTexKoords + float2( -5.0f, 0.0f) * gBild.zw);
  float2 t2 = saturate( rein.mTexKoords + float2( -4.0f, 0.0f) * gBild.zw);
  float2 t3 = saturate( rein.mTexKoords + float2( -3.0f, 0.0f) * gBild.zw);
  float2 t4 = saturate( rein.mTexKoords + float2( -2.0f, 0.0f) * gBild.zw);
  float2 t5 = saturate( rein.mTexKoords + float2( -1.0f, 0.0f) * gBild.zw);
        
  // linke Seite in den Durchschnitt einfließen lassen
  tiefe += tex2D( TexBild, t1).x * 0.0009765625f;
  tiefe += tex2D( TexBild, t2).x * 0.009765625f;
  tiefe += tex2D( TexBild, t3).x * 0.0439453125f;
  tiefe += tex2D( TexBild, t4).x * 0.1171875f;
  tiefe += tex2D( TexBild, t5).x * 0.205078125f;

  // Texturkoords der rechten Seite
  t1 = min( rein.mTexKoords + float2( 1.0f, 0.0f) * gBild.zw, gBild.xy);
  t2 = min( rein.mTexKoords + float2( 2.0f, 0.0f) * gBild.zw, gBild.xy);
  t3 = min( rein.mTexKoords + float2( 3.0f, 0.0f) * gBild.zw, gBild.xy);
  t4 = min( rein.mTexKoords + float2( 4.0f, 0.0f) * gBild.zw, gBild.xy);
  t5 = min( rein.mTexKoords + float2( 5.0f, 0.0f) * gBild.zw, gBild.xy);
        
  // rechte Seite samplen
  tiefe += tex2D( TexBild, t1).x * 0.205078125f;
  tiefe += tex2D( TexBild, t2).x * 0.1171875f;
  tiefe += tex2D( TexBild, t3).x * 0.0439453125f;
  tiefe += tex2D( TexBild, t4).x * 0.009765625f;
  tiefe += tex2D( TexBild, t5).x * 0.0009765625f;
             
  return tiefe.xxxx;
}
Danke im Voraus für eure Zeit!
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Exponential Shadow Maps sind nicht weich zu kriegen

Beitrag von Schrompf »

Nachtrag: den Gausfilter kann man auch einfacher schreiben, ohne sich bei jeder Änderung die Finger zu brechen:

Code: Alles auswählen

static const float gFaktor[11] = 
{ 
  0.0009765625f, 0.009765625f, 0.0439453125f, 0.1171875f, 0.205078125f, 
  0.24609375f,
  0.205078125f, 0.1171875f, 0.0439453125f, 0.009765625f, 0.0009765625f
};
      
float4 main( const PixelEingabe rein) : COLOR0
{
  float tiefe = 0.0f;
  
  for( float a = 0; a < 11.0f; ++a )
  {
    float2 texk = clamp( rein.mTexKoords + float2( a - 5.0f, 0.0f) * gBild.zw, float2( 0.0f, 0.0f), gBild.xy);
    tiefe += exp( tex2D( TexBild, texk).x) * gFaktor[a];
  }
           
  return log( tiefe).xxxx;
}
Ändert gar nix, ist aber freundlicher für die Augen des geneigten Betrachters. Ist hier im Beispiel jetzt ein exponentieller Filter, aber wie schon geschrieben: es ändert gar nix.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Exponential Shadow Maps sind nicht weich zu kriegen

Beitrag von Schrompf »

Erneuter Nachtrag, und erneut zum Thema "ändert genau gar nix". Der Erfinder von ESMs, Marco Salvi, hat am logarithmischen Filter umhergeformelt und eine Convolution Function abgeleitet, die so lautet:

Code: Alles auswählen

float log_conv( float x0, float X, float y0, float Y )
{
  return (X + log(x0 + (y0 * exp(Y - X))));
}
Ich habe ebenso ne Weile rumgeformelt und festgestellt, dass diese Formel im Gegensatz zum einfachen gewichteten Aufaddieren genau 0 Unterschied macht. Außer dass der Filter-Code etwas mühsamer wird. Schade. Ich hatte auf eine versteckte Intelligenz gehofft, die meine Probleme magisch löst :-(
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Exponential Shadow Maps sind nicht weich zu kriegen

Beitrag von Schrompf »

Da ich den Thread bisher eh alleine bestreite, kann ich hier auch die neuesten Erkenntnisse posten. Und die lauten (powered by Kleine Einkauf-Wanderung und Mett-Brötchen):

Ich kann mir über die geschriebene Tiefe und die festgelegte Funktion des Helligkeitsverlauf doch jede beliebige Schattenintensität rausschreiben! Die Erkenntnis musste ich erstmal verdauen. Die Formel für die Verdeckung lautet ja wie folgt:

[edit] Formel wieder plump geschrieben, bis ich den Latex-Kram mal nachgelesen habe
h = exp( k*(d - d0))

Mit h als Helligkeit, d als Abstand der aktuellen Oberfläche zum Licht und d0 als gelesene Schattenentfernung aus der ShadowMap. Das kann man einfach nach h umstellen, muss aber aufpassen, weil in der Formel ja üblicherweise noch ein max() oder saturate() drin ist, um Helligkeiten > 1 zu verhindern.

Das heißt dann ja auch, dass ich im ShadowMap-Filter für einen Texel anhand der umliegenden Pixel eine Verdeckung mit beliebigen Methoden berechnen kann. Ich könnte also auch die nahesten 2 Texel in jede Richtung zum reinen Weichzeichnen und die weiteren 5 oder so anteilig verdunkeln lassen, wenn sie eine gewisse Mindestentfernung zum zentralen Texel überschreiten, um eine sich mit Distanz öffnende Penumbra Region zu bekommen. Und die ganze Bastelei musste, da in der Exponentialform sauber interpolierbar, auch noch prima achsenseparieren lassen. Der einzige Nachteil ist, dass wir alle die Spielchen nur für unverdeckte Texel auf der Shadow Map machen können, also nur den äußeren Rand von Schattenkanten bearbeiten können. Für den inneren Rand wissen wir vorab nicht, für welche Tiefe wir die Helligkeit berechnen werden, und können demzufolge keine Verdeckungswerte gezielt berechnen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Artificial Mind
Establishment
Beiträge: 802
Registriert: 17.12.2007, 17:51
Wohnort: Aachen

Re: Exponential Shadow Maps sind nicht weich zu kriegen

Beitrag von Artificial Mind »

Bei unserem Projekt haben wir ja auch Soft-Shadows und wir haben das wie folgt gemacht:

Das Erstellen der Shadowmap berechnet folgendes:

Code: Alles auswählen

    float depth = length(vWorldPosition - uCameraPosition) / uShadowRange;
    depth = min(1, depth);

    fFragDataColor = exp(80 * depth);
Es wird also 80 * depth rausgeschrieben, wobei uShadowRange die maximale "Reichweite" des Schattens angibt bei uns.
Die Texture ist 32bit, 1 Channel.

In einem zweiten Schritt wird die Textur mit einem 5x5 Gauss-Filter geblurt. (Wir hatten vorher 9x9, aber das war uns zu weich.)

Die eigentliche Schattenberechnung der Objekte ist dann folgende:

Code: Alles auswählen

        float depth = length(vWorldPosition - uLightPosition) / uShadowRange;

        vec4 shadowPos = uShadowProjection * uShadowView * vec4(vWorldPosition, 1);
        vec2 shadowTex = shadowPos.xy / shadowPos.w * .5 + .5;
        float shadowDepth = texture(uShadow, shadowTex).r;

        float p = clamp(exp(-80 * depth) * shadowDepth, 0, 1);

        finalColor.rgb = mix(vec3(0.0), finalColor.rgb, p);
Es wird die Tiefe depth vom aktuellen Objekt berechnet. Dann wird mit entsprechender Transformation aus der ShadowMap ausgelesen (shadowDepth) und clamp(exp(-80 * depth) * shadowDepth, 0, 1) ergibt dann den Abdunklungsfaktor.

Vielleicht hilft dir das etwas weiter - oder dein Problem liegt ganz woanders.


Die 80 beim Potenzieren ist vom Paper vorgeschlagen worden als guter Kompromiss zwischen numerischer Stabilität und Sprungfunktionsapproximation.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Exponential Shadow Maps sind nicht weich zu kriegen

Beitrag von Schrompf »

Danke für die Details! Ich bin aber nicht sicher, ob mir das nützen wird... das "length(bla - blubb)" sieht für mich aus, als würdest Du da die Schatten für ein Pointlight oder Spotlight berechnen... und die haben nunmal recht geringe Wertebereiche. Und für geringe Wertebereiche funktionieren meine Tricks auch alle schon ganz gut. Ich fürchte, meine Probleme da stammen nur von der Tatsache, dass sich beim Frustum über 2000m Tiefe erstreckt und Occluder und Reciever auch gern mal 200m auseinanderliegen. Ich benutze als Overdarkening Factor z.B. 10000, damit Kontaktschatten schon nach einem halben Meter oder so stabil sind, während Du den Faktor 80 nimmst. Tja. Ich werde das nochmal durchformeln, wie der Filter aussehen müsste, wenn ich lineare Tiefen anstatt exponentielle Tiefen lesen müsste.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Artificial Mind
Establishment
Beiträge: 802
Registriert: 17.12.2007, 17:51
Wohnort: Aachen

Re: Exponential Shadow Maps sind nicht weich zu kriegen

Beitrag von Artificial Mind »

Meinst Du nicht, dass die Probleme eher auftreten, wenn du 2km Frustum hast und Occluder/Receiver nur wenige cm auseinander liegen?

Unser Pointlight ist in der Tat nicht so weit weg. Aber ein 1:10 Verhältnis (2km - 200m) konnte der gut abbilden.

Hast Du schonmal versucht mit dem Faktor (bei mir 80, bei Dir 10.000) zu experimentieren? Mal von wirklichen kleinen Werten bis richtig große?
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Exponential Shadow Maps sind nicht weich zu kriegen

Beitrag von Schrompf »

Ja, hab ich :-) Das Problem ist, dass ich den Faktor so gewählt habe, dass ein menschliches Charakter-Modell bereits ab Hüfthöhe einen brauchbaren Schatten wirft. Das wäre ein Tiefenverhältnis von ~1:2000. Daher der astronomisch hohe Faktor, und daher kann ich auch keine exponentiellen Tiefen schreiben, wie Du das tust. Und ich *vermute*, dass die ganze Weichzeichnerei dann bei höheren Entfernungsverhältnissen prinzipiell nicht mehr funktionieren kann. Ich bekomme ja vom Weichzeichnen zumindest prächtig geglättete Kanten :-) Man sieht fast keine Texel-Formen mehr, die Silhouette des Schattens ist nahezu perfekt. Nur der Übergang zwischen Licht und Schatten ist immernoch sehr schmal.

Daher mein Gedanke, dass mal mit exponentieller Gewichtung der Tiefendifferenzen zu probieren. Ich dachte, damit könnte ich dann für eine definierte Ziel-Tiefe (die ich ja in der ShadowMap habe) einen mir genehmen Helligkeitsverlauf berechnen. Geht so leider auch nicht, aber ich weiß noch nicht, warum.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Zudomon
Establishment
Beiträge: 2253
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: Exponential Shadow Maps sind nicht weich zu kriegen

Beitrag von Zudomon »

Nun bin ich auch bei dem Thema gelandet und habe das gleiche Problem wir du (Schrompf).
Aber ich glaube, dass wir da einen kleinen Denkfehler hatten. Denn Artificial Mind führt ja noch diesen Filter Prozess an. Ich dachte auch, dass die stärke von EMS vor allem darin liegt, dass diese quadratischen Artefakte verschwinden... und wenn man die Shadowmap filtert, sollte das auch funktionierren... hab es aber noch nicht probiert.

Habt ihr denn schon neue Erkenntnisse, wie man vielleicht noch bessere Schatten erzeugt? Bevor ich mich jetzt auf EMS usw. stürze? :D
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Exponential Shadow Maps sind nicht weich zu kriegen

Beitrag von Schrompf »

Ich hab das Thema nicht mehr verfolgt, als sich mir die Erkenntnis einstellte, dass ESM und Konsorten prinzipiell genau so funktionieren, wie ich es oben als Problem beschrieben habe. Es sind im Endeffekt Interpolationen der Tiefenwerte. Und wenn Du die Interpolation so zurechtformelst, dass der Schatten für ne Tiefendifferenz von einem Tausendstel ordentlich aussieht, wird der zwangsweise degenerieren, wenn Du eine Tiefendifferenz von nem Zehntel hast. Oder anders formuliert: wenn Dein Charakter auf 20cm Abstand weiche Schatten werfen soll, wird die selbe Formel zwangsweise knochenharten Quatsch berechnen, wenn Du Bergschatten auf 200m Abstand Schatten werfen lässt.

Ich wollte irgendwann mal das Gegenteil schreiben, also nicht "Je weiter auseinander, desto härter", sondern "je weiter auseinander, desto weiter die Interpolation", aber das kollidiert auch an ein paar Ecken und ich hatte das Interesse verloren, bevor ich die gelöst hatte. Alternativ könnte man irgendwie pro Schatten-Texel den Gradienten der Tiefe bestimmen und aus dem was ableiten... sind alles Hirnfürze, die noch zu nichts Konkreten geführt habe. Meine Zockabende meinen, dass selbst die aktuellsten Engines wie Division, Battlefield und Konsorten alle noch eine schlichte Cascaded Shadow Map mit Variance Shadow Mapping und festen Kerneln benutzen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Antworten