Ombres lumineuses à double point paraboloïde dans une configuration d'éclairage différé

10

J'ai joué avec ce tutoriel / exemple de code qui montre une implémentation simple de light-pre-pass, qui est un type de configuration d'éclairage différé.

Je suis en train de mettre en œuvre des ombres ponctuelles, en utilisant des cartes d'ombre à double paraboloïde. Je suis cette description de DPM: http://gamedevelop.eu/en/tutorials/dual-paraboloid-shadow-mapping.htm

Je suis capable de créer des cartes d'ombre, et elles semblent bien paraître.

Je crois que le problème actuel que j'ai est dans mon pixel shader qui recherche une valeur de profondeur dans la carte d'ombre lors du rendu des lumières ponctuelles.

Voici mon code de shader de lumière ponctuelle: http://olhovsky.com/shadow_mapping/PointLight.fx

La fonction de pixel shader d'intérêt est PointLightMeshShadowPS.

Quelqu'un voit-il une erreur flagrante dans cette fonction?

J'espère que quelqu'un a déjà résolu ce problème :)

entrez la description de l'image ici

entrez la description de l'image ici

Comme vous pouvez le voir dans les images ci-dessus, les ombres du message ne correspondent pas à la position des messages, donc une transformation est incorrecte quelque part ...

Voici à quoi cela ressemble lorsque la lumière ponctuelle est très proche du sol (touchant presque le sol).

entrez la description de l'image ici

À mesure que la lumière ponctuelle se rapproche du sol, les ombres se rejoignent et se touchent le long de la ligne de rencontre des deux cartes d'ombre (c'est-à-dire le long du plan où la caméra lumineuse a été inversée pour capturer les deux cartes d'ombre).


Éditer:

Plus d'informations:

entrez la description de l'image ici

Lorsque j'éloigne la lumière ponctuelle de l'origine, il y a une ligne parallèle au vecteur "droit" de la caméra lumineuse qui coupe l'ombre. L'image ci-dessus montre le résultat du déplacement de la lumière ponctuelle vers la gauche. Si je déplace le point lumineux vers la droite, il y a une ligne de détourage équivalente à la place. Je pense donc que cela indique que je transforme quelque chose de mal dans le pixel shader, comme je le pensais.


Edit: Pour rendre cette question plus claire, voici quelques morceaux de code.

Voici le code que j'utilise actuellement pour dessiner un spot ombré . Cela fonctionne et utilise le mappage d'ombre comme vous vous en doutez.

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;
}

Maintenant, voici le code de lumière ponctuelle que j'utilise, qui a une sorte de bogue dans la transformation en espace clair lors de l'utilisation des cartes d'ombre:

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
la source
et vous dites que les cartes d'ombres elles-mêmes n'ont aucun problème / (je veux dire si vous gravez les cartes d'ombres sur le texturemap, elles assombriront les endroits corrects?)
Ali1S232
Êtes-vous sûr à 100% que le FOV du rendu de la caméra à partir de la position de la source lumineuse est correct?
Roy T.
Le rendu de la caméra à partir de la position de la source de lumière n'a pas de matrice de projection, car la projection est effectuée manuellement afin d'avoir la chaîne paraboloïde. Je vais vérifier ce code, bonne idée Roy T.
Olhovsky
Gajet: "Je veux dire que si vous gravez les textures d'ombres sur le texturemap, elles assombriront les zones correctes?" Les cartes ombrées stockent les ombres dans l'espace lumineux, si je regarde la carte, il n'y a pas de moyen facile de savoir avec certitude qu'elles sont correctes car je les vois dans l'espace d'écran. Qu'est-ce qu'un "texturemap" - vous voulez dire une texture? Les cartes d'ombres sont des textures.
Olhovsky
Roy T.: Déplacer la lumière révèle que la texture de l'ombre est tronquée, il y a donc un problème avec la transformation lors de l'utilisation réelle de l'ombre, pas seulement lors de sa création.
Olhovsky

Réponses:

2

Avec PIX, vous pouvez déboguer des pixels isolés, peut-être trouvez-vous l'erreur de cette manière. FOV ou une erreur de projection est un indice chaud. Ou avez-vous oublié la transformation du monde?!

christoph
la source
vous pouvez également essayer le débogage avec NVidia-fxComposer
Ali1S232
Je ne pense pas que regarder les valeurs du code d'assemblage va m'aider beaucoup à ce stade, car j'ai du mal à comprendre comment la transformation doit être effectuée en premier lieu. Donc, voir quelle valeur est dans le registre 10, ou n'importe où, ne va pas vraiment aider.
Olhovsky
"Ou avez-vous oublié la transformation du monde?!" En fait, j'ai oublié d'appliquer la transformation du monde lors de la création des cartes d'ombre - doh! Cela fonctionne maintenant, laissant tous les shaders comme je les avais.
Olhovsky
1

Salut Olhovsky, belle question stimulante. Je connais votre douleur, j'ai implémenté l'ombrage différé, l'éclairage inféré et les ombres dans mon dernier travail. C'était vraiment très amusant, mais beaucoup de douleur aussi quand cela ne fonctionnait pas comme prévu.

Je pense que le conseil avec PIX est en fait un bon. Vous n'avez pas à vous soucier des instructions de l'assembleur du shader, mais vous pouvez regarder les shadow maps et autres cibles de rendu, sélectionner un pixel et appeler son pixel shader et le parcourir ainsi que son vertex-shader.

Les astuces de débogage générales pour ce type de situations incluent la simplification de la scène.

Celui qui me vient à l'esprit est le suivant: placez l'appareil photo à la même position que la source de lumière avec les mêmes attributs fovy et autres que dans la passe d'éclairage. Maintenant, vous pouvez facilement comparer les valeurs dans le pixel shader. Le pixel-xy dans la passe de rendu normale pour votre objet actuel doit être le même que le pixel-xy calculé pour la recherche dans le shadowmap, tant qu'il a la même résolution.

Un autre est de passer à la projection orthographique, de rendre quelque chose de facile, de prévisible et de vérifiable. Le plus simple est le mieux vous pouvez vérifier chaque étape de calcul.

À part cela, pouvez-vous montrer comment vous créez la matrice qui calcule la position dans l'ombre-carte pour le pixel actuel, c'est-à-dire la transformation de l'espace écran en espace lumière?

Maik Semder
la source
Le simple fait de voir la carte des ombres est un paraboïde, ce qui rend encore plus difficile le débogage et l'idée de mettre la caméra à la position des lumières pour comparer la position actuelle des pixels et la position dans la carte des ombres ne fonctionnera pas, peu importe :)
Maik Semder
Si vous êtes intéressé, envoyez-moi un e-mail à [email protected], et je vous répondrai avec une copie de mon projet. Sinon: la CameraTransformmatrice est en fait la matrice mondiale de la caméra qui visualise actuellement la scène. La LightViewProjmatrice n'est en fait que la matrice mondiale de la lumière, comme la matrice de vue de la lumière n'est que la matrice d'identité.
Olhovsky
Pouvez-vous faire un simple projet C ++ avec lui? Il devrait également y avoir la transformation parabloïde impliquée non?
Maik Semder
La transformation paraboloïde est dans le pixel shader que j'ai lié dans la question. Mes compétences en C ++ sont trop limitées pour créer un projet C ++ rapide qui encapsule l'ensemble du pipeline de rendu différé, je pense :) Cependant, si vous maîtrisez le C ++, je pense que cela ne devrait pas être trop difficile de lire mon code C #. D'autant plus que la plupart des inquiétudes sont vraiment dans le pixel shader, et peut-être avec les matricies qui lui sont transmises.
Olhovsky
Je faisais référence à g_mDPView et g_mDPWorldView. Pouvez-vous montrer comment ils sont calculés.
Maik Semder