Omnidirectional shadow mapping and wrong space?

I’ve been banging my head against the wall when dealing with point light shadow mapping, and a recent idea is that I might’ve been comparing two depth values from different spaces and I’d like some confirmation if thats the case.

The shadow pass draws all the geometry from the 6 different angles originating from the point light using its own view and projection matrices, while the directional vector (“positionDiff”) used in sampling the shadowmap and which is also converted to the comparison depth value (func “VectorToDepthValue”) is in world space. Is this a potential error?

const std::string gShadingFragmentShader = 
    "#version 420                                                                                                                            
                                                                                                                                             
    layout(std140) uniform UnifPointLight                                                                                                    
    {                                                                                                                                        
        mat4 mWVPMatrix;                                                                                                                     
        vec4 mLightColor;                                                                                                                    
        vec4 mLightPos;                                                                                                                      
        vec4 mGamma;                                                                                                                         
        vec2 mScreenSize;                                                                                                                    
                                                                                                                                             
        float mLightIntensity;                                                                                                               
        float mMaxDistance;                                                                                                                  
    } UnifPointLightPass;                                                                                                                    
                                                                                                                                             
    layout (binding = 2) uniform sampler2D unifPositionTexture;                                                                              
    layout (binding = 3) uniform sampler2D unifNormalTexture;                                                                                
    layout (binding = 4) uniform sampler2D unifDiffuseTexture;                                                                               
    layout (binding = 7) uniform samplerCube unifShadowmap;                                                                                  
                                                                                                                                             
    out vec4 fragColor;                                                                                                                      
                                                                                                                                             
    float VectorToDepthValue(vec3 Vec)                                                                                                       
    {                                                                                                                                        
        vec3 AbsVec = abs(Vec);                                                                                                              
        float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));                                                                           
                                                                                                                                             
        const float f = 100.0;                                                                                                               
        const float n = 0.1;                                                                                                                 
        float NormZComp = (f + n) / (f - n) - (2 * f * n) / (f - n) / LocalZcomp;                                                            
                                                                                                                                             
        return (NormZComp + 1.0) * 0.5;                                                                                                      
    }                                                                                                                                        
                                                                                                                                             
    void main()                                                                                                                              
    {                                                                                                                                        
        vec2 texcoord = gl_FragCoord.xy / UnifPointLightPass.mScreenSize;                                                                    
                                                                                                                                             
        vec3 worldPos = texture(unifPositionTexture, texcoord).xyz;                                                                          
        vec3 normal   = texture(unifNormalTexture, texcoord).xyz;                                                                            
        vec3 diffuse  = texture(unifDiffuseTexture, texcoord).xyz;                                                                           
        normal        = normalize(normal);                                                                                                   
                                                                                                                                             
        vec3 positionDiff = (UnifPointLightPass.mLightPos.xyz - worldPos);                                                                   
                                                                                                                                             
        float storedDepth = texture(unifShadowmap, positionDiff);                                                                            
        float visibility = 0.0;                                                                                                              
        if (storedDepth + 0.0001 > VectorToDepthValue(positionDiff))                                                                         
            visibility = 1.0;                                                                                                                
                                                                                                                                             
        float dist = length(positionDiff);                                                                                                   
        float attenuation = clamp(1.0 - dist*dist * (1 / (UnifPointLightPass.mMaxDistance * UnifPointLightPass.mMaxDistance)), 0.0, 1.0);    
        attenuation *= attenuation;                                                                                                          
                                                                                                                                             
        vec3 lightDir = normalize(positionDiff);                                                                                             
        float angleNormal = clamp(dot(normalize(normal), lightDir), 0, 1);                                                                   
                                                                                                                                             
        fragColor = vec4(diffuse, 1.0) * visibility * (angleNormal * attenuation *                                                           
                    UnifPointLightPass.mLightIntensity * UnifPointLightPass.mLightColor);                                                    
    }                                                                                                                                  

EDIT: also, is it “Lightposition - worldPosition” or is it “worldPosition - LightPosition”? The more I think about it, it feels like the latter.

bump! any insights?

At first glance, the VectorToDepthValue guts look correct. While the input to VectorToDepthValue() is light’s eye-space (it better be anyway; if not, that’s a bug), the guts basically run this Z coord through the light’s projection transform, does the perspective divide, and remaps -1…1 -> 0…1 to drop it into a window-space Z value for comparison with the shadow map depth values.

If positionDiff is in world-space and not light’s eye-space, then that’s a bug (it sounds like from what you’re saying is that it’s in world-space which is a problem).

Compare with the working point light source shadow code here:

It definately is in world space; I’ll try get it in light space instead. A couple of follow-up questions arise though :slight_smile:

  1. What I don’t understand though is how would you put it in light space? Since I got 6 different view matrices, one for each cube face? Looking at the code you linked, their “light_view_matrix” seems to only be a translation matrix - given that my lightPosition already is in world space, isn’t that the same thing?

  2. Looking at the shader source you posted though, why dosn’t they subtract the lights position with the vertex position? Won’t they get wrong cubemap coordinates otherwise? More like this:

	"	vec4 position_ls = light_view_matrix * camera_view_matrix_inv * position_cs;
"
        "       position_ls  -= light_view_matrix * light_position; 
"        <-----------
	// shadow map test
	"	vec4 abs_position = abs(position_ls);
"
	"	float fs_z = -max(abs_position.x, max(abs_position.y, abs_position.z));
"
	"	vec4 clip = light_projection_matrix * vec4(0.0, 0.0, fs_z, 1.0);
"
	"	float depth = (clip.z / clip.w) * 0.5 + 0.5;
"
	"	vec4 result = shadowCube(shadow, vec4(position_ls.xyz, depth));
"

If you look at that code I linked to, there’s a base light viewing matrix for the “forward” direction, and then there are 6 “face” matrices that are stacked on top of that when rendering the cube map faces to reorient that and point in the direction of the cube face.

However, in the shader when sampling the cube map, only the base light viewing matrix for the “forward” direction is used, and the vector in that space is what’s used as a direction vector for your cube map lookup.

The magic which grabs the appropriate value from this vector to use as the face-specific depth value (to use for the comparison once projected) is:


    vec4 abs_position = abs(position_ls);
    float fs_z = -max(abs_position.x, max(abs_position.y, abs_position.z));

[QUOTE=Dark Photon;1259961]If you look at that code I linked to, there’s a base light viewing matrix for the “forward” direction, and then there are 6 “face” matrices that are stacked on top of that when rendering the cube map faces to reorient that and point in the direction of the cube face.

However, in the shader when sampling the cube map, only the base light viewing matrix for the “forward” direction is used, and the vector in that space is what’s used as a direction vector for your cube map lookup.

The magic which grabs the appropriate value from this vector to use as the face-specific depth value (to use for the comparison once projected) is:


    vec4 abs_position = abs(position_ls);
    float fs_z = -max(abs_position.x, max(abs_position.y, abs_position.z));

[/QUOTE]

But what is the “forward” direction in this case? I find the term confusing; I can understand a forward vector in terms of a directional light, but what is it for omnidirectional lights? What is the purpose of the base light view matrix?

Looking at the code:

	// create the light view matrix (construct this as you would a camera matrix)
	glLoadIdentity();
	glTranslatef(-light_position_ws[0], -light_position_ws[1], -light_position_ws[2]);
	glGetFloatv(GL_MODELVIEW_MATRIX, light_view_matrix);

It seems to be a translation matrix?

EDIT: found a very similiar topic (point light - shadow mapping - OpenGL: Basic Coding - Khronos Forums) and seems to be stuck on the same issue

Yeah, for omni it seems arbitrary. But I think you agree that (for cube map face rendering purposes) you need viewing transforms for each face. You have a base viewing transform that gets you into the eye-space for one face. And then a “rotate” transform you can apply to that which moves you from that face to the other faces.

Turns out that since OpenGL eye-space is looking down the -Z axis with +Y up, then (I think) the CUBE_MAP_NEGATIVE_Z face ends up being your base transform here (the face matrix for it should be the identity). True, there’s no “real” forward direction on an omni light. So let’s call this your base direction.

The view matrix consists of a translation and a rotation component, right - so the “base matrix”, which we agree I need in the shader, is the translation matrix? Does the translation “put me in eye (light) space”?

Solved it!
Apparently the Up-vectors used when rendering the 6 cubemap faces were wrong. The correct ones for me where:

const Vec3 CUBEMAP_UP_VECTORS[CUBEMAP_NUM_FACES] = { Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f),
                                                         Vec3(0.0f, 0.0f, -1.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f) };

If anyone would chime in on why they would be negative to work I’d love to hear an explanation.

Also rather than using (LightPosition - FragmentPos) I used it the other way around:

vec3 cubemapDir   = (worldPos - UnifPointLightPass.mLightPos.xyz);                                                             
                                                                                                                                  
        float storedDepth = texture(unifShadowmap, cubemapDir);                                                                         
        float visibility = 0.0;                                                                                                           
        if (storedDepth + 0.0001 > VectorToDepthValue(cubemapDir))                                                                         
            visibility = 1.0;