Sombras de luz de ponto parabolóide duplo na configuração de iluminação adiada

10

Venho brincando com este tutorial / código de exemplo que demonstra uma implementação simples do light-pre-pass, que é um tipo de configuração de iluminação adiada.

Estou no processo de implementar sombras de luz pontual, usando mapas de sombra com parabolóide duplo. Estou seguindo esta descrição do DPM: http://gamedevelop.eu/en/tutorials/dual-paraboloid-shadow-mapping.htm

Eu sou capaz de criar os mapas de sombras, e eles parecem estar bem.

Acredito que o problema atual que estou tendo está no meu pixel shader que procura um valor de profundidade no mapa de sombras ao renderizar luzes de ponto.

Aqui está o meu código de sombreamento de ponto: http://olhovsky.com/shadow_mapping/PointLight.fx

A função de pixel shader de interesse é PointLightMeshShadowPS.

Alguém vê um erro flagrante nessa função?

Espero que alguém tenha resolvido esse problema antes :)

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Como você pode ver nas imagens acima, as sombras da postagem não coincidem com as posições das postagens; portanto, algumas transformações estão erradas em algum lugar ...

É assim que parece quando a luz do ponto está muito perto do chão (quase tocando o chão).

insira a descrição da imagem aqui

À medida que a luz pontual se aproxima do chão, as sombras se juntam e tocam ao longo da linha onde os dois mapas de sombras se encontram (ou seja, ao longo do plano em que a câmera de luz foi acionada para capturar os dois mapas de sombras).


Editar:

Outras informações:

insira a descrição da imagem aqui

Quando afasto a luz pontual da origem, há uma linha paralela ao vetor "certo" da câmera de luz que corta a sombra. A imagem acima mostra o resultado do movimento da luz indicadora para a esquerda. Se eu mover a luz indicadora para a direita, há uma linha de recorte equivalente à direita. Então, acho que isso indica que estou transformando algo incorretamente no pixel shader, como pensei.


Edit: Para tornar esta questão mais clara, aqui estão alguns pedaços de código.

Aqui está o código que atualmente uso para desenhar uma luz de ponto sombreada . Isso funciona e usa o mapeamento de sombra conforme o esperado.

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

Agora, aqui está o código de ponto de luz que estou usando, que tem algum tipo de erro na transformação em espaço de luz ao usar os mapas de sombra:

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
fonte
e você diz sombra mapas em si está tendo nenhum problema / (quero dizer, se você queimar a sombra mapeia para a textureMap eles vão escurecer pontos corretos?)
Ali1S232
Você tem 100% de certeza de que o FOV da renderização da câmera da posição da fonte de luz está correto?
Roy T.
A renderização da câmera a partir da posição da fonte de luz não possui uma matriz de projeção, porque a projeção é feita manualmente para obter a distorção parabolóide. Porém, vou verificar esse código, boa ideia Roy T. #
Olhovsky
Gajet: "Quero dizer, se você gravar os mapas de sombra no mapa de textura, eles escurecerão os pontos corretos?" Os mapas de sombras armazenam sombras no espaço claro; se eu olhar para o mapa, não há uma maneira fácil de saber com certeza se elas estão corretas porque eu as vejo no espaço da tela. O que é um "mapa de textura" - você quer dizer uma textura? Mapas de sombras são texturas.
precisa saber é o seguinte
Roy T .: Mover a luz ao redor revela que o mapa de sombras é cortado, então há um problema com a transformação ao usar a sombra, e não apenas ao criá-la.
precisa saber é o seguinte

Respostas:

2

Com o PIX, você pode depurar pixels isolados, talvez encontre o erro dessa maneira. FOV ou um erro de projeção é uma dica importante. Ou você esqueceu a transformação do mundo ?!

Christoph
fonte
você também pode tentar depurar com o NVidia-fxComposer
Ali1S232 4/11
Eu não acho que olhar para os valores do código de montagem vai me ajudar muito neste momento, porque estou tendo problemas para entender como a transformação deve ser feita em primeiro lugar. Portanto, ver qual é o valor no registro 10, ou em qualquer outro lugar, realmente não vai ajudar.
precisa saber é o seguinte
"Ou você esqueceu a transformação do mundo ?!" Na verdade, eu esqueci de aplicar a transformação do mundo ao criar os mapas de sombra - doh! Isso funciona agora, deixando todos os shaders como eu os tinha.
precisa saber é o seguinte
1

Ei Olhovsky, boa pergunta desafiadora. Conheço sua dor, implementei sombreamento diferido, iluminação inferida e sombras em meu último trabalho. Foi muito divertido, mas também muita dor quando não funcionou como o esperado.

Eu acho que o conselho com o PIX é realmente bom. Você não precisa mexer nas instruções do assembler do shader, mas pode ver os mapas de sombras e outros destinos de renderização, selecionar um pixel e chamar seu pixel shader e percorrê-lo e também seu vertex-shader.

Os truques gerais de depuração para esse tipo de situação incluem a simplificação da cena.

Um que me vem à mente é: coloque a câmera na mesma posição que a fonte de luz com os mesmos atributos fovy e outros que o passe de iluminação. Agora você pode comparar facilmente os valores no pixel-shader. O pixel-xy na passagem de renderização normal do seu objeto atual deve ser o mesmo que o pixel-xy calculado para a pesquisa no mapa de sombras, desde que tenha a mesma resolução.

Outro é mudar para a projeção ortográfica, tornar algo fácil, previsível e verificável. Quanto mais simples, melhor você pode verificar cada etapa do cálculo.

Fora isso, você pode mostrar como criar a matriz que calcula a posição no mapa de sombras do pixel atual, que é a transformação do espaço da tela no espaço da luz?

Maik Semder
fonte
Ver o mapa das sombras é um Parabloid, o que dificulta ainda mais a depuração e a idéia de colocar a câmera na posição das luzes para comparar a posição atual dos pixels e a posição no mapa das sombras não funcionará, deixa pra lá :)
Maik Semder
Se você estiver interessado, envie-me um e-mail para [email protected] e eu responderei com uma cópia do meu projeto. Caso contrário: a CameraTransformmatriz é na verdade a matriz mundial da câmera que está visualizando a cena. A LightViewProjmatriz é na verdade apenas a matriz mundial da luz, pois a matriz de visão da luz é apenas a matriz de identidade.
precisa
Você pode fazer um projeto C ++ simples com ele? Também deve haver a transformação parablóide envolvida, certo?
Maik Semder
A transformação parabolóide está no pixel shader que vinculei na pergunta. Minhas habilidades em C ++ são muito limitadas para criar um projeto rápido em C ++ que encapsule todo o pipeline de renderização adiada, eu acho :) No entanto, se você é proficiente em C ++, acho que não deve ser muito difícil ler meu código C #. Especialmente porque a maior parte da preocupação está realmente no pixel shader, e talvez com as matrizes passadas para ele.
precisa saber é o seguinte
Eu estava me referindo ao g_mDPView e ao g_mDPWorldView. Você pode mostrar como eles são calculados.
Maik Semder