Shadow-map for multiple omnidirectional lights

Started by
14 comments, last by Fnord42 12 years, 6 months ago
Hi there,

I'm thinking about how I can implement shadow-mapping for multiple point-lights with cube-maps.

My current idea is to bind a cube-map with only a depth-component to an FBO.

Then for each light I want to do the following:
1) Draw to the shadow-map-FBO using simple shaders, which render all triangles in lightspace simply black to trigger writing to the depth-buffer.
2) Calculate the light for all pixels using the shadowmap and add it to the default FBO's Colormap.

Then I want to render my objects for real, combining their texture-color with the light-color that is already in the default FBO.

Now my question is: Is this going to work?
I've read about people creating shadow-cube-maps with 6 rendering passes. Is there something that I haven't thought about, that's preventing me from doing this in only 1 pass? How I've understood a cube-map-depth-buffer, for each vertex-direction the depth-value is stored, so this should work, right?

greets & thanks for your time!

edit: I forgot to mention that at step 1) I thought about just using the lights view matrix without the projection matrix.
Advertisement
You need six projection matrices - one for each side of cube. And thus also 6 lights-pace matrices - each orienting negative Z to the direction of corresponding side.

You can still render shadow map in one pass using instancing or geometry shaders, but there is no way to avoid separate projection matrices. Except, of course, dual paraboloid shadow map, but this is quite different issue.

Which method (rendering in 6 passes or using geometry shaders) is actually faster depends AFAIK on GPU and driver versions. If you have frustum culling enabled the amount of geometry rendered is not too different. Doing 6 sides in one pass may actually be bad for memory locality, as all your triangles are each time guaranteed to touch all cubemap sides - but I do not know, how much this may be issue on modern GPU-s..

Lauris Kaplinski

First technology demo of my game Shinya is out: http://lauris.kaplinski.com/shinya
Khayyam 3D - a freeware poser and scene builder application: http://khayyam.kaplinski.com/

You need six projection matrices - one for each side of cube. And thus also 6 lights-pace matrices - each orienting negative Z to the direction of corresponding side.

You can still render shadow map in one pass using instancing or geometry shaders, but there is no way to avoid separate projection matrices. Except, of course, dual paraboloid shadow map, but this is quite different issue.

Which method (rendering in 6 passes or using geometry shaders) is actually faster depends AFAIK on GPU and driver versions. If you have frustum culling enabled the amount of geometry rendered is not too different. Doing 6 sides in one pass may actually be bad for memory locality, as all your triangles are each time guaranteed to touch all cubemap sides - but I do not know, how much this may be issue on modern GPU-s..


Thanks for your answer. I've read about the conventional way with a geometry shader, the 6 passes method and also paraboloid-mapping. But isn't there a way to skip the projection?
I thought, if I transform the vertices in light-space, i could use their direction as a cubemap coordinate and their distance from the light-space null-vector as depth-value.
Is there really no way to write from fragment-shader to a cubemap with directional texture-coordinates?

I've tried to set gl_Position in the vertex-shader of the shadow-map-calculation to the normalized vertex-position in lightspace and to use the vertex's distance to the light-space null-vector as fragment-color. But if I use this shader with an FBO which has cubemaps as depth- and color-attachments only one face of the cube is used, the other faces are static but seem to be undefined.

edit: I've read in the opengl.org/wiki that I can only attach one face of a cubemap to a framebuffer. Can I write only to framebuffers from a shader or are there other locations I can adress?

Thanks for your answer. I've read about the conventional way with a geometry shader, the 6 passes method and also paraboloid-mapping. But isn't there a way to skip the projection?
I thought, if I transform the vertices in light-space, i could use their direction as a cubemap coordinate and their distance from the light-space null-vector as depth-value.
Is there really no way to write from fragment-shader to a cubemap with directional texture-coordinates?

I've tried to set gl_Position in the vertex-shader of the shadow-map-calculation to the normalized vertex-position in lightspace and to use the vertex's distance to the light-space null-vector as fragment-color. But if I use this shader with an FBO which has cubemaps as depth- and color-attachments only one face of the cube is used, the other faces are static but seem to be undefined.

Okay, I probably understood what you meant.

Basically you want to replace standard projection (frustum) with custom calculation of gl_Position.

I think the biggest problem is, that your triangles will not be rasterized over cubemap. Rasterizer projects your triangle to XY viewport plane and then runs fragment shader for all pixels - if, for example, the triangle is parallel to XZ plane in clip coordinates (i.e. after perpective division) it will not be rasterized at all because it covers 0 viewport pixels.

Also do not forget, that single triangle can cover many sides of cubemap. The maximum is 5, although 2 and 3 are probably more common. Rasterization is bound to single layer/viewport I think. And I am pretty sure rasterizer will not select cube map side automatically - you have to do it in geometry shader.
Lauris Kaplinski

First technology demo of my game Shinya is out: http://lauris.kaplinski.com/shinya
Khayyam 3D - a freeware poser and scene builder application: http://khayyam.kaplinski.com/

[quote name='Fnord42' timestamp='1316798099' post='4865207']
Thanks for your answer. I've read about the conventional way with a geometry shader, the 6 passes method and also paraboloid-mapping. But isn't there a way to skip the projection?
I thought, if I transform the vertices in light-space, i could use their direction as a cubemap coordinate and their distance from the light-space null-vector as depth-value.
Is there really no way to write from fragment-shader to a cubemap with directional texture-coordinates?

I've tried to set gl_Position in the vertex-shader of the shadow-map-calculation to the normalized vertex-position in lightspace and to use the vertex's distance to the light-space null-vector as fragment-color. But if I use this shader with an FBO which has cubemaps as depth- and color-attachments only one face of the cube is used, the other faces are static but seem to be undefined.

Okay, I probably understood what you meant.

Basically you want to replace standard projection (frustum) with custom calculation of gl_Position.

I think the biggest problem is, that your triangles will not be rasterized over cubemap. Rasterizer projects your triangle to XY viewport plane and then runs fragment shader for all pixels - if, for example, the triangle is parallel to XZ plane in clip coordinates (i.e. after perpective division) it will not be rasterized at all because it covers 0 viewport pixels.

Also do not forget, that single triangle can cover many sides of cubemap. The maximum is 5, although 2 and 3 are probably more common. Rasterization is bound to single layer/viewport I think. And I am pretty sure rasterizer will not select cube map side automatically - you have to do it in geometry shader.
[/quote]

Thanks for the answer. Then I'll probably go with the 6-pass shadow-cube-mapping.
EDIT: Solved, see my post below.
Nevermind, my Mat4f::set_view()-Method was messed up.
I should seriously write unit-tests for my Math-library.
Hi again,

now my shadow-cube-map looks good but the shadows are looking really wrong (picture at the end of this post).
For test-reasons I'm currently rendering the shadow to a color-cube-map where all colors are the light-vertex-distance in worldspace scaled to a range between 0.0 and 1.0.
Then when I draw my scene, I'm calculating again the vector from light to vertex in worldspace and use this vector as the direction-vector of the texture-look-up of the shadow-cube-map and compare the shadow-texture-value with the scaled length of this vector. For scaling I divide through the far-plane of the projection which I use for creating the shadow-map.

I would really apreciate it if somebody could check what's wrong, I'm already messing with shadows for a few days and it just won't work ;)

Those are my shaders for rendering to a face of the shadow-map:
// ===== Vertex-Shader =============================================
in vec4 position; // In worldspace

uniform mat4 light_vp_matrix; // view-projection-matrix for the current face of the lights cubemap, far-plane: 40.0
uniform vec3 light_pos; // In worldspace

out vec4 var_light_to_vertex_ws;

void main(void)
{
var_light_to_vertex_ws = position - vec4(light_pos, 1.0);
gl_Position = light_vp_matrix * position;
}

// ===== Fragment-Shader =============================================
in vec4 var_light_to_vertex_ws;
out vec4 frag;

void main(void)
{
float dist_from_light = length(var_light_to_vertex_ws.xyz)/40.0;
frag = vec4(vec3(1.0, 1.0, 1.0) * dist_from_light, 1.0);
// gl_FragDepth = dist_from_light; // produces same strange results
}


Those are my shaders for drawing objects with light and shadow:
// ===== Vertex-Shader =============================================
in vec4 position;
uniform vec3 light_position_ws;
uniform mat4 mvp_matrix;
out vec4 var_light_to_vertex_ws;
...

void main(void)
{
var_light_to_vertex_ws = position - vec4(light_position_ws, 1.0);
gl_Position = mvp_matrix * position;
...
}

// ===== Fragment-Shader =============================================
in vec4 var_light_to_vertex_ws;
uniform samplerCube shadow_map;
out vec4 frag;
...

void main(void)
{
// Calculate shadow-map-depth of this fragment.
vec3 shadow_map_dir = normalize(var_light_to_vertex_ws).xyz;
shadow_map_dir.xz = -shadow_map_dir.xz; // For some reason it looks more right if I flip those.
float shadow_map_depth = texture(shadow_map, shadow_map_dir).r;

// Calculate depth of this fragment.
float frag_depth = length(var_light_to_vertex_ws.xyz)/40.0;

float is_in_light = 0.0;
if (shadow_map_depth < frag_depth + 0.0005)
is_in_light = 1.0;

...
}


Thanks for you time!

Light is flying over the terrains center @ (0.0, 6.0, 0.0)
terrain_buggy_shadows.png

The shadow-cube-map:
shadowcubemap.png
The problem lies definetly in the cubemap-texture-lookup.
This happens if I set the fragment color to vec3(1.0, 1.0, 1.0)*shadow_map_depth:
terrain_buggy_shadows2.png

I just can't figure out whats wrong with the lookup :(
When you write gl_Position, how does it know what face of the cubemap the triangle is going to write too?And the 1 pass sounds iffy to me, your going to try and draw 6 times the amount of models, effectively no culling then.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal

This topic is closed to new replies.

Advertisement