He estado jugando con este tutorial / código de muestra que demuestra una implementación simple de light-pre-pass, que es un tipo de configuración de iluminación diferida.
Estoy en el proceso de implementar sombras de luz puntuales, utilizando mapas de sombra de doble paraboloide. Estoy siguiendo esta descripción de DPM: http://gamedevelop.eu/en/tutorials/dual-paraboloid-shadow-mapping.htm
Soy capaz de crear los mapas de sombras, y parecen verse bien.
Creo que el problema actual que tengo está en mi sombreador de píxeles que busca un valor de profundidad en el mapa de sombras cuando renderizo luces de puntos.
Aquí está mi código de sombreador de luz de punto: http://olhovsky.com/shadow_mapping/PointLight.fx
La función de sombreador de píxeles de interés es PointLightMeshShadowPS
.
¿Alguien ve un error evidente en esa función?
Esperemos que alguien haya abordado este problema antes :)
Como puede ver en las imágenes de arriba, las sombras de la publicación no coinciden con las posiciones de las publicaciones, por lo que alguna transformación es incorrecta en alguna parte ...
Esto es lo que parece cuando el punto de luz está muy cerca del suelo (casi tocando el suelo).
A medida que la luz puntual se acerca al suelo, las sombras se juntan y se tocan a lo largo de la línea donde se encuentran los dos mapas de sombras (es decir, a lo largo del plano donde se volteó la cámara de luz para capturar los dos mapas de sombras).
Editar:
Más información:
Cuando muevo el punto de luz lejos del origen, hay una línea paralela al vector "derecho" de la cámara de luz que recorta la sombra. La imagen de arriba muestra el resultado de mover el punto de luz hacia la izquierda. Si muevo el punto de luz hacia la derecha, hay una línea de recorte equivalente a la derecha. Así que creo que esto indica que estoy transformando algo incorrectamente en el sombreador de píxeles, como pensé.
Editar: para aclarar esta pregunta, aquí hay algunas piezas de código.
Aquí está el código que actualmente uso para dibujar un foco de luz sombreado . Esto funciona y utiliza el mapeo de sombras como era de esperar.
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;
}
Ahora aquí está el código de luz de punto que estoy usando, que tiene algún tipo de error en la transformación en espacio de luz cuando uso los 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;
}
fuente
Respuestas:
Con PIX puede depurar píxeles aislados, tal vez encuentre el error de esta manera. FOV o un error de proyección es una pista caliente. ¿O has olvidado la transformación del mundo?
fuente
Hola Olhovsky, buena pregunta desafiante. Conozco tu dolor, implementé Sombreado diferido, Iluminación inferida y sombras en mi último trabajo. Fue muy divertido, pero también mucho dolor cuando no funcionó como se esperaba.
Creo que el consejo con PIX es realmente bueno. No tiene que meterse con las instrucciones del ensamblador del sombreador, pero puede mirar los mapas de sombras y otros objetivos de renderizado, seleccionar un píxel y llamar a su sombreador de píxeles y recorrerlo y también su sombreador de vértices.
Los trucos generales de depuración para este tipo de situaciones incluyen la simplificación de la escena.
Una que me viene a la mente es: coloque la cámara en la misma posición que la fuente de luz con los mismos fovy y otros atributos que en el pase de iluminación. Ahora puede comparar fácilmente los valores en el sombreador de píxeles. El pixel-xy en el paso de renderizado normal para su objeto actual debe ser el mismo que el pixel-xy calculado para la búsqueda en el mapa de sombras, siempre que tenga la misma resolución.
Otro es cambiar a proyección ortográfica, hacer algo fácil, predecible y comprobable. Cuanto más simple, mejor puede verificar cada paso de cálculo.
Aparte de eso, ¿puede mostrar cómo crea la matriz que calcula la posición en el mapa de sombras para el píxel actual, que es la transformación del espacio de la pantalla al espacio de la luz?
fuente
CameraTransform
matriz es en realidad la matriz mundial de la cámara que actualmente está viendo la escena. LaLightViewProj
matriz es en realidad solo la matriz mundial de la luz, ya que la matriz de vista de la luz es solo la matriz de identidad.