Sombras de luz de punto paraboloide dual en configuración de iluminación diferida

10

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 :)

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

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).

ingrese la descripción de la imagen aquí

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:

ingrese la descripción de la imagen aquí

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;
}
Olhovsky
fuente
y se dice a sí mismos mapas de sombras están teniendo ningún problema / (me refiero a si se quema la sombra se asigna a textureMap que van a oscurecen lugares correctos?)
Ali1S232
¿Está 100% seguro de que el FOV de la representación de la cámara desde la posición de la fuente de luz es correcto?
Roy T.
El renderizado de la cámara desde la posición de origen de la luz no tiene una matriz de proyección, porque la proyección se realiza manualmente para tener la deformación paraboloide. Sin embargo, comprobaré ese código, buena idea Roy T.
Olhovsky
Gajet: "Quiero decir, si quemas los mapas de sombras en el mapa de texturas, ¿oscurecerán los puntos correctos?" Los mapas de sombras almacenan sombras en el espacio de luz, si miro el mapa, no hay una manera fácil de saber con certeza que son correctas porque las veo en el espacio de la pantalla. ¿Qué es un "mapa de textura"? ¿Te refieres a una textura? Los mapas de sombras son texturas.
Olhovsky
Roy T .: Mover la luz revela que el mapa de sombras se recorta, por lo que hay un problema con la transformación cuando se usa la sombra, no solo al crearla.
Olhovsky

Respuestas:

2

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?

christoph
fuente
también puede intentar depurar con NVidia-fxComposer
Ali1S232
No creo que mirar los valores del código de ensamblaje me vaya a ayudar mucho en este momento, porque tengo problemas para entender cómo se debe hacer la transformación en primer lugar. Por lo tanto, ver qué valor tiene el registro 10, o donde sea, realmente no va a ayudar.
Olhovsky
"¡¿O has olvidado la transformación del mundo ?!" De hecho, olvidé aplicar la transformación del mundo al crear los mapas de sombras, ¡sí! Esto funciona ahora, dejando todos los sombreadores como los tenía.
Olhovsky
1

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?

Maik Semder
fuente
Solo ver el mapa de sombras es un parabloide, eso hace que sea aún más difícil de depurar y la idea de colocar la cámara en la posición de luces para comparar la posición actual del píxel y la posición en el mapa de sombras no funcionará, no importa :)
Maik Semder
Si está interesado, envíeme un correo electrónico a [email protected], y le responderé con una copia de mi proyecto. De lo contrario: la CameraTransformmatriz es en realidad la matriz mundial de la cámara que actualmente está viendo la escena. La LightViewProjmatriz 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.
Olhovsky
¿Puedes hacer un proyecto simple de C ++ con él? También debería haber una transformación parabloide involucrada, ¿verdad?
Maik Semder
La transformación paraboloide está en el sombreador de píxeles que vinculé en la pregunta. Mis habilidades en C ++ son demasiado limitadas para crear un proyecto rápido de C ++ que encapsula todo el canal de renderizado diferido, creo :) Sin embargo, si eres experto en C ++, entonces creo que no debería ser demasiado difícil leer mi código C #. Especialmente porque la mayor parte de la preocupación está realmente en el sombreador de píxeles, y quizás con las matrículas que se le pasaron.
Olhovsky
Me refería a g_mDPView y g_mDPWorldView. ¿Puedes mostrar cómo se calculan?
Maik Semder