Exponential Varianz Shadow Mapping - Implementierung

7

Ich verwende eine Varianzschatten-Zuordnung mit einem "Standard" -Lichtblutungs-Fix in meiner Grafik-Engine, der auf verzögertem Rendern basiert. Ich habe eine einzelne Schattenzuordnung für ein gerichtetes Licht, da eine Testszene relativ klein ist. Das Speichern von Tiefe sieht folgendermaßen aus:

float moment1 = gl_FragCoord.z;
float moment2 = moment1 * moment1;
outColor = vec2(moment1, moment2);

Dann führe ich eine trennbare Gaußsche Unschärfe an dieser Textur durch.

Schattentest:

float Chebyshev(vec2 moments, float mean, float minVariance)
{
    float shadow = 1.0f;
    if(mean <= moments.x)
    {
        shadow = 1.0f;
        return shadow;
    }
    else
    {
        float variance = moments.y - (moments.x * moments.x);
        variance = max(variance, minVariance);
        float d = mean - moments.x;
        shadow = variance / (variance + (d * d));
        float amount = 0.3f;
        shadow =  clamp((shadow - amount) / (1.0f - amount), 0.0f, 1.0f); // standard light bleeding fix
        return shadow;
    }
}

vec4 shadowCoord = shadowMatrix * vec4(viewSpacePosition, 1.0f); 
vec2 moments = texture(ShadowMap, shadowCoord.xy).xy;
float minVariance = 0.000001f;
float shadow = Chebyshev(moments, shadowCoord.z, minVariance);

Ich fand eine interessante Technik - Exponential Varianz Shadow Mapping, die eine bessere Lösung für leichte Blutungen bieten sollte. Hier sparen Sie Tiefe für diese Technik:

float positiveExponent = 40.0f;
float negativeExponent = 5.0f
float depth = gl_FragCoord.z;
vec2 exponents = vec2(positiveExponent, negativeExponent);
depth = 2.0f * depth - 1.0f;
float pos = exp(exponents.x * depth);
float neg = -exp(-exponents.y * depth);
vec2 warpDepth = (pos, neg);
outColor = vec4(warpDepth, warpDepth * warpDepth);

Wie implementiere ich einen Schattentest für diese Technik? Hier ist mein Versuch:

float positiveExponent = 40.0f;
float negativeExponent = 5.0f;
vec2 exponents = vec2(positiveExponent, negativeExponent);

vec2 warpDepth(float depth)
{
    depth = 2.0f * depth - 1.0f;
    float pos = exp(exponents.x * depth);
    float neg = -exp(-exponents.y * depth);
    vec2 wDepth = vec2(pos, neg);
    return wDepth;
}

float Chebyshev(vec2 moments, float mean, float minVariance)
{
    float shadow = 1.0f;
    if(mean <= moments.x)
    {
        shadow = 1.0f;
        return shadow;
    }
    else
    {
        float variance = moments.y - (moments.x * moments.x);
        variance = max(variance, minVariance);
        float d = mean - moments.x;
        shadow = variance / (variance + (d * d));
        return shadow;
    }
}

vec4 shadowCoord = shadowMatrix * vec4(viewSpacePosition, 1.0f);
vec4 moments = texture(ShadowMap, shadowCoord.xy).xyzw;
vec2 posMoments = vec2(moments.x, moments.z);
vec2 negMoments = vec2(moments.y, moments.w);
vec2 wDepth = warpDepth(shadowCoord.z);
//float minVariance = 0.000001f;
//Edit
vec2 depthScale = 0.0001f * exponents * wDepth;
vec2 minVariance = depthScale * depthScale;
float posResult = Chebyshev(posMoments, wDepth.x, minVariance.x);
float negResult = Chebyshev(negMoments, wDepth.y, minVariance.y);
shadow = min(posResult, negResult);

Leider entfernt es keine leichten Blutungen. Habe ich etwas falsch gemacht ? Vielleicht sollte ich zwei verschiedene minVariance basierend auf den positiven und negativen Exponenten berechnen?

Edit1: MinVariance auf folgende Weise berechnen:

vec2 depthScale = 0.0001f * exponents * wDepth;
vec2 minVariance = depthScale * depthScale;
float posResult = Chebyshev(posMoments, wDepth.x, minVariance.x);
float negResult = Chebyshev(negMoments, wDepth.y, minVariance.y);

liefert bessere Ergebnisse, aber leichte Blutungen sind immer noch gut sichtbar.

Edit2: Leichte Blutung: EVSM mit minVariance, wie oben berechnet, liefert ein besseres Ergebnis als VSM ohne Standard-Fix für leichte Blutungen, aber um das beste Ergebnis zu erzielen, verwende ich auch Standard-Fix für leichte Blutungen mit EVSM. In der Chebyshev-Funktion wird eine leichte Blutungskorrektur durchgeführt, aber vielleicht sollte ich sie nach folgender Berechnung hinzufügen: shadow = min (posResult, negResult)?

EVSM verursacht kleine Artefakte auf der beleuchteten Seite der Objekte, daher füge ich beim Speichern der EVSM-Tiefe etwas glPolygonOffset hinzu.

Irbis
quelle
Sieht so aus, als hätten Sie hier einen Tippfehler: vec2 warpDepth = (pos, neg)- sollte sein vec2(pos, neg)
Nathan Reed
Danke, aber ich habe das sofort behoben, weil ich glsl-Compiler-Fehler habe. Ein Problem liegt woanders.
Irbis
Ich würde mich freuen, einen Screenshot zu sehen, Irbis!
Danijar

Antworten:

5

Das Varianzschatten-Mapping leidet einfach unter diesen leichten Blutungsproblemen.

Ich persönlich bevorzuge die Implementierung von ESM (siehe Seite 257 von ShaderX6), da der Speicherdruck die Hälfte der VSM-Karte beträgt und die Artefakte für mich viel weniger abrasiv sind: (Der Anfang des Schattens ist etwas zu hell.)

Vor diesem Hintergrund finden Sie hier ein (ziemlich altes) PDF mit großartigen Techniken, die Sie zum Nachdenken anregen. (oder nur, um Ihnen den Algorithmus zu zeigen, wenn Sie ShaderX6 nicht haben) http://developer.download.nvidia.com/presentations/2008/GDC/GDC08_SoftShadowMapping.pdf

In meiner aktuellen Engine habe ich einen Hybrid, der im Grunde genommen ESM ist, aber die 2-Moment-Schattenkarte (oder höher) verwendet, um die Varianz zu berechnen und die ESM-Artefakte an den Stellen zu reduzieren, an denen sich der Okkluder zu nahe am Empfänger befindet.

MickLH
quelle