Lichtschatten mit zwei Paraboloidpunkten in verzögerter Beleuchtung

10

Ich habe mit diesem Tutorial / Beispielcode herumgespielt , der eine einfache Implementierung von Light-Pre-Pass demonstriert, einer Art verzögerter Beleuchtungskonfiguration.

Ich bin dabei, Punktlichtschatten mithilfe von Dual-Paraboloid-Schattenkarten zu implementieren. Ich folge dieser Beschreibung von DPM: http://gamedevelop.eu/en/tutorials/dual-paraboloid-shadow-mapping.htm

Ich kann die Schattenkarten erstellen und sie scheinen gut auszusehen.

Ich glaube, dass das aktuelle Problem in meinem Pixel-Shader liegt, der beim Rendern von Punktlichtern einen Tiefenwert in der Schattenkarte nachschlägt.

Hier ist mein Point Light Shader Code: http://olhovsky.com/shadow_mapping/PointLight.fx

Die interessierende Pixel-Shader-Funktion ist PointLightMeshShadowPS.

Hat jemand einen krassen Fehler in dieser Funktion gesehen?

Hoffentlich hat jemand dieses Problem schon einmal angegangen :)

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Wie Sie in den obigen Bildern sehen können, stimmen die Schatten des Posts nicht mit den Positionen der Posts überein, sodass irgendwo eine Transformation falsch ist ...

So sieht es aus, wenn sich das Punktlicht sehr nahe am Boden befindet (fast den Boden berührt).

Geben Sie hier die Bildbeschreibung ein

Wenn sich das Punktlicht dem Boden nähert, kommen die Schatten zusammen und berühren sich entlang der Linie, auf der sich die beiden Schattenkarten treffen (dh entlang der Ebene, auf der die Lichtkamera gedreht wurde, um die beiden Schattenkarten zu erfassen).


Bearbeiten:

Weitere Informationen:

Geben Sie hier die Bildbeschreibung ein

Wenn ich das Punktlicht vom Ursprung weg bewege, gibt es eine Linie parallel zum "rechten" Vektor der Lichtkamera, die den Schatten abschneidet. Das obige Bild zeigt das Ergebnis der Bewegung des Punktlichts nach links. Wenn ich das Punktlicht nach rechts bewege, befindet sich rechts eine entsprechende Schnittlinie. Ich denke, dies deutet darauf hin, dass ich etwas im Pixel-Shader falsch transformiere, wie ich dachte.


Bearbeiten: Um diese Frage klarer zu machen, hier ein paar Codeteile.

Hier ist der Code, mit dem ich derzeit ein schattiertes Punktlicht zeichne . Dies funktioniert und verwendet die erwartete Schattenzuordnung.

VertexShaderOutputMeshBased SpotLightMeshVS(VertexShaderInput input)
{
    VertexShaderOutputMeshBased output = (VertexShaderOutputMeshBased)0;    
    output.Position = mul(input.Position, WorldViewProjection);

    //we will compute our texture coords based on pixel position further
    output.TexCoordScreenSpace = output.Position;
    return output;
}

//////////////////////////////////////////////////////
// Pixel shader to compute spot lights with shadows
//////////////////////////////////////////////////////
float4 SpotLightMeshShadowPS(VertexShaderOutputMeshBased input) : COLOR0
{
    //as we are using a sphere mesh, we need to recompute each pixel position into texture space coords
    float2 screenPos = PostProjectionSpaceToScreenSpace(input.TexCoordScreenSpace) + GBufferPixelSize;
    //read the depth value
    float depthValue = tex2D(depthSampler, screenPos).r;

    //if depth value == 1, we can assume its a background value, so skip it
    //we need this only if we are using back-face culling on our light volumes. Otherwise, our z-buffer
    //will reject this pixel anyway

    //if depth value == 1, we can assume its a background value, so skip it
    clip(-depthValue + 0.9999f);

    // Reconstruct position from the depth value, the FOV, aspect and pixel position
    depthValue*=FarClip;

    //convert screenPos to [-1..1] range
    float3 pos = float3(TanAspect*(screenPos*2 - 1)*depthValue, -depthValue);

    //light direction from current pixel to current light
    float3 lDir = LightPosition - pos;

    //compute attenuation, 1 - saturate(d2/r2)
    float atten = ComputeAttenuation(lDir);

    // Convert normal back with the decoding function
    float4 normalMap = tex2D(normalSampler, screenPos);
    float3 normal = DecodeNormal(normalMap);

    lDir = normalize(lDir);

    // N dot L lighting term, attenuated
    float nl = saturate(dot(normal, lDir))*atten;

    //spot light cone
    half spotAtten = min(1,max(0,dot(lDir,LightDir) - SpotAngle)*SpotExponent);
    nl *= spotAtten;

    //reject pixels outside our radius or that are not facing the light
    clip(nl -0.00001f);

    //compute shadow attenuation

    float4 lightPosition = mul(mul(float4(pos,1),CameraTransform), MatLightViewProjSpot);

    // Find the position in the shadow map for this pixel
    float2 shadowTexCoord = 0.5 * lightPosition.xy / 
                            lightPosition.w + float2( 0.5, 0.5 );
    shadowTexCoord.y = 1.0f - shadowTexCoord.y;
    //offset by the texel size
    shadowTexCoord += ShadowMapPixelSize;

    // Calculate the current pixel depth
    // The bias is used to prevent floating point errors 
    float ourdepth = (lightPosition.z / lightPosition.w) - DepthBias;

    nl = ComputeShadowPCF7Linear(nl, shadowTexCoord, ourdepth);

    float4 finalColor;

    //As our position is relative to camera position, we dont need to use (ViewPosition - pos) here
    float3 camDir = normalize(pos);

    // Calculate specular term
    float3 h = normalize(reflect(lDir, normal));
    float spec = nl*pow(saturate(dot(camDir, h)), normalMap.b*50);
    finalColor = float4(LightColor * nl, spec); 

    //output light
    return finalColor * LightBufferScale;
}

Hier ist der Punktlichtcode , den ich verwende, der einen Fehler bei der Umwandlung in den Lichtraum aufweist, wenn die Schattenkarten verwendet werden:

VertexShaderOutputMeshBased PointLightMeshVS(VertexShaderInput input)
{
    VertexShaderOutputMeshBased output = (VertexShaderOutputMeshBased)0;    
    output.Position = mul(input.Position, WorldViewProjection);

    //we will compute our texture coords based on pixel position further
    output.TexCoordScreenSpace = output.Position;
    return output;
}

float4 PointLightMeshShadowPS(VertexShaderOutputMeshBased input) : COLOR0
{
    // as we are using a sphere mesh, we need to recompute each pixel position 
    // into texture space coords
    float2 screenPos = 
        PostProjectionSpaceToScreenSpace(input.TexCoordScreenSpace) + GBufferPixelSize;

    // read the depth value
    float depthValue = tex2D(depthSampler, screenPos).r;

    // if depth value == 1, we can assume its a background value, so skip it
    // we need this only if we are using back-face culling on our light volumes. 
    // Otherwise, our z-buffer will reject this pixel anyway
    clip(-depthValue + 0.9999f);

    // Reconstruct position from the depth value, the FOV, aspect and pixel position
    depthValue *= FarClip;

    // convert screenPos to [-1..1] range
    float3 pos = float3(TanAspect*(screenPos*2 - 1)*depthValue, -depthValue);

    // light direction from current pixel to current light
    float3 lDir = LightPosition - pos;

    // compute attenuation, 1 - saturate(d2/r2)
    float atten = ComputeAttenuation(lDir);

    // Convert normal back with the decoding function
    float4 normalMap = tex2D(normalSampler, screenPos);
    float3 normal = DecodeNormal(normalMap);

    lDir = normalize(lDir);

    // N dot L lighting term, attenuated
    float nl = saturate(dot(normal, lDir))*atten;

    /* shadow stuff */

    float4 lightPosition = mul(mul(float4(pos,1),CameraTransform), LightViewProj);

    //float4 lightPosition = mul(float4(pos,1), LightViewProj);
    float posLength = length(lightPosition);
    lightPosition /= posLength;

    float ourdepth = (posLength - NearClip) / (FarClip - NearClip) - DepthBias;
    //float ourdepth = (lightPosition.z / lightPosition.w) - DepthBias;

    if(lightPosition.z > 0.0f)
    {
        float2 vTexFront;
        vTexFront.x =  (lightPosition.x /  (1.0f + lightPosition.z)) * 0.5f + 0.5f; 
        vTexFront.y =  1.0f - ((lightPosition.y /  (1.0f + lightPosition.z)) * 0.5f + 0.5f);    

        nl = ComputeShadow(FrontShadowMapSampler, nl, vTexFront, ourdepth);
    }
    else
    {
        // for the back the z has to be inverted        
        float2 vTexBack;
        vTexBack.x =  (lightPosition.x /  (1.0f - lightPosition.z)) * 0.5f + 0.5f; 
        vTexBack.y =  1.0f - ((lightPosition.y /  (1.0f - lightPosition.z)) * 0.5f + 0.5f); 

        nl = ComputeShadow(BackShadowMapSampler, nl, vTexBack, ourdepth);
    }

    /* shadow stuff */

    // reject pixels outside our radius or that are not facing the light
    clip(nl - 0.00001f);

    float4 finalColor;
    //As our position is relative to camera position, we dont need to use (ViewPosition - pos) here
    float3 camDir = normalize(pos);

    // Calculate specular term
    float3 h = normalize(reflect(lDir, normal));
    float spec = nl*pow(saturate(dot(camDir, h)), normalMap.b*100);
    finalColor = float4(LightColor * nl, spec);

    return finalColor * LightBufferScale;
}
Olhovsky
quelle
und Sie sagen, dass Schattenkarten selbst kein Problem haben / (Ich meine, wenn Sie die Schattenkarten auf Texturkarten brennen, verdunkeln sie die richtigen Stellen?)
Ali1S232
Sind Sie zu 100% sicher, dass das Sichtfeld des Kamera-Renderings von der Position der Lichtquelle korrekt ist?
Roy T.
Das Kamera-Rendering von der Position der Lichtquelle hat keine Projektionsmatrix, da die Projektion manuell erfolgt, um die Paraboloid-Verzerrung zu erzielen. Ich werde diesen Code überprüfen, gute Idee Roy T.
Olhovsky
Gajet: Ich meine, wenn Sie die Schattenkarten auf eine Texturkarte brennen, verdunkeln sie die richtigen Stellen? Die Schattenkarten speichern Schatten im hellen Raum. Wenn ich mir die Karte anschaue, gibt es keine einfache Möglichkeit, sicher zu sein, dass sie korrekt sind, da ich sie im Bildschirmbereich sehe. Was ist eine "Texturkarte" - du meinst eine Textur? Schattenkarten sind Texturen.
Olhovsky
Roy T.: Das Bewegen des Lichts zeigt, dass die Schattenkarte abgeschnitten wird. Daher gibt es ein Problem mit der Transformation, wenn der Schatten tatsächlich verwendet wird, nicht nur beim Erstellen.
Olhovsky

Antworten:

2

Mit PIX können Sie Pixel isoliert debuggen. Vielleicht finden Sie den Fehler auf diese Weise. FOV oder ein Projektionsfehler ist ein heißer Hinweis. Oder hast du die Welttransformation vergessen?!

Christoph
quelle
versuchen Sie können auch mit NVidia-fxComposer debug
Ali1S232
Ich denke nicht, dass es mir an dieser Stelle sehr helfen wird, auf die Assembler-Code-Werte zu starren, da ich Probleme habe zu verstehen, wie die Transformation überhaupt durchgeführt werden sollte. Zu sehen, welcher Wert in Register 10 ist oder wo auch immer, wird also nicht wirklich helfen.
Olhovsky
"Oder hast du die Welttransformation vergessen?!" Ich habe tatsächlich vergessen, die Welttransformation beim Erstellen der Schattenkarten anzuwenden - doh! Dies funktioniert jetzt und lässt alle Shader so, wie ich sie hatte.
Olhovsky
1

Hey Olhovsky, schöne herausfordernde Frage. Ich kenne deinen Schmerz, ich habe in meinem letzten Job Deferred Shading, Inferred Lighting und Shadows implementiert. Es hat wirklich großen Spaß gemacht, aber auch viel Schmerz, wenn es nicht wie erwartet funktioniert hat.

Ich denke, der Rat mit PIX ist tatsächlich gut. Sie müssen sich nicht mit den Assembler-Anweisungen des Shaders herumschlagen, sondern können sich die Schattenkarten und andere Renderziele ansehen, ein Pixel auswählen und seinen Pixel-Shader aufrufen und durch ihn sowie seinen Vertex-Shader gehen.

Zu den allgemeinen Debug-Tricks für diese Art von Situationen gehört die Vereinfachung der Szene.

Eines, das mir in den Sinn kommt, ist: Stellen Sie die Kamera an die gleiche Position wie die Lichtquelle mit den gleichen Fovy- und anderen Attributen wie im Beleuchtungsdurchgang. Jetzt können Sie die Werte im Pixel-Shader einfach vergleichen. Das Pixel-xy im normalen Render-Pass für Ihr aktuelles Objekt sollte mit dem berechneten Pixel-xy für die Suche in der Schattenkarte übereinstimmen, sofern es dieselbe Auflösung hat.

Eine andere Möglichkeit ist die Umstellung auf orthografische Projektion, um etwas einfach, vorhersehbar und überprüfbar zu machen. Je einfacher desto besser können Sie jeden Berechnungsschritt überprüfen.

Können Sie ansonsten zeigen, wie Sie die Matrix erstellen, die die Position in der Schattenkarte für das aktuelle Pixel berechnet, dh die Transformation vom Bildschirmraum zum Lichtraum?

Maik Semder
quelle
Nur die Schattenkarte zu sehen, ist ein Parabloid, was das Debuggen noch schwieriger macht und die Idee, die Kamera an die Lichtposition zu bringen, um die aktuelle Pixelposition und Position in der Schattenkarte zu vergleichen, funktioniert nicht, egal :)
Maik Semder
Wenn Sie interessiert sind, senden Sie mir eine E-Mail an [email protected], und ich werde mit einer Kopie meines Projekts antworten. Andernfalls: Die CameraTransformMatrix ist tatsächlich die Weltmatrix der Kamera, die gerade die Szene betrachtet. Die LightViewProjMatrix ist eigentlich nur die Weltmatrix des Lichts, da die Ansichtsmatrix des Lichts nur die Identitätsmatrix ist.
Olhovsky
Können Sie damit ein einfaches C ++ - Projekt erstellen? Es sollte auch die parabloide Transformation geben, oder?
Maik Semder
Die Paraboloidtransformation befindet sich in dem Pixel-Shader, den ich in der Frage verlinkt habe. Meine C ++ - Kenntnisse sind zu begrenzt, um ein schnelles C ++ - Projekt zusammenzustellen, das die gesamte verzögerte Rendering-Pipeline enthält, denke ich :) Wenn Sie jedoch mit C ++ vertraut sind, sollte es meiner Meinung nach nicht zu schwierig sein, meinen C # -Code zu lesen. Zumal der größte Teil der Sorge wirklich im Pixel-Shader liegt und vielleicht mit den Matricies, die an ihn übergeben werden.
Olhovsky
Ich bezog mich auf g_mDPView und g_mDPWorldView. Können Sie zeigen, wie sie berechnet werden?
Maik Semder