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 :)
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).
À 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:
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;
}
fonte
Respostas:
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 ?!
fonte
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?
fonte
CameraTransform
matriz é na verdade a matriz mundial da câmera que está visualizando a cena. ALightViewProj
matriz é na verdade apenas a matriz mundial da luz, pois a matriz de visão da luz é apenas a matriz de identidade.