Sign in to follow this  

Shadow mapping on a Geforce 2 + Nvidia "Shadowcast" demo

This topic is 4236 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello, I just started trying to experiment with shadows of some sort, and I wanted to start with shadow mapping. I have a Geforce 4 MX and I just realised that you need a Geforce 3 or up for the depth comparison to be done in hardware (shadow mapping demos run at < 0.1 fps). How is the realtime shadow generated in a game like Hitman 3 (which I was playing today on my computer)? I don't know much about various shadow techniques, but from the aliasing it seems like it is shadow mapping. Take a look: Free Image Hosting at www.ImageShack.us /edit: changed the title [Edited by - deavik on May 2, 2006 2:58:21 AM]

Share this post


Link to post
Share on other sites
They use 8-bit shadowmapping or 16-bit register combiner dual texture shadowmapping. There's a tutorial on how to implement these techniques on:
http://www.paulsprojects.net/opengl/shadowmap/shadowmap.html

They're only practical if you apply them to very few objects in your scene (like only hitman) and the aliasing and artifacts will be severe for large distances. So, it's your call...

Hope I've been able to help you,
Jeroen

Share this post


Link to post
Share on other sites
Thanks, that was a great link!

When you say "They're only practical if you apply them to very few objects in your scene", do you mean because they are more expensive to do than regular shadowmapping?

I haven't had time to look through what's going on in the demo yet - just a disclaimer.

Share this post


Link to post
Share on other sites
Check out this. As long as you don't plan on continually updating the shadow maps, that method'll work fine. By the way, as long as you're rendering to the entire colour buffer (sky box/dome when outside, all 4 walls, a floor and a roof when inside, etc.), you can ignore the whole clearing the colour buffer bit.

Share this post


Link to post
Share on other sites
Gorax, thanks for the link. Your vertex shader looks very attractive, but I'll try it a bit later. Right now I'm trying to implement the "dual texture" method given in the later half of this paper by Mark Kilgard.

Firstly, does anyone have a copy of the "Shadowcast" demo with source code which is mentioned at the end of that paper? I searched and searched, but it seems like NV have taken it off the developer website. I'm having a hard time figuring out how to do the matrix math for the texgen modes.

Secondly, I have a little problem with copying the depth buffer with glReadPixels as mentioned in that paper. If I copy into an array of ubytes (as suggested in the paper) all the values are 255 (I checked). So, If I create a texture out of this data (call glTexImage2D with data format LUMINANCE and type UNSIGNED_BYTE) I get a white texture.

In a strange manner, it works as it should and I can see the depth buffer if I copy the depth buffer as 16bit ushorts, then supplying that to glTexImage2D as 2 ubytes per texel (luminance + alpha). ie:

GLushort data[SHADOW_MAP_SIZE * SHADOW_MAP_SIZE];
glReadPixels(0, 0, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, data);
glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY8, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);

Share this post


Link to post
Share on other sites
If you want Hitman style shadows, you don't actually need to do any depth rendering or depth comparison. The Hitman games just render the characters from the lights point of view into a normal texture (render the character without textures as a single black/grey colour) then use that texture to project from the lights point of view.

There's no self shadowing in the game so there is no depth comparing.

Share this post


Link to post
Share on other sites
Quote:
Original post by deavik
If I copy into an array of ubytes (as suggested in the paper) all the values are 255 (I checked).


This is because of the extremely limited depth buffer precision afforded by the 8-bit shadow map. In my shadow mapping demo (link already posted by godmodder), you can use the arrow keys to adjust the size of the frustum - note how the shadows go wrong once the near clip plane is closer to the camera than 0.3 or so. At this point, due to the non-linear nature of the depth buffer, many of the values are 255.

In other words, you need to push your near clip plane (when drawing from the light's POV) as far out as possible.

Share this post


Link to post
Share on other sites
Quote:
Original post by bakery2k1
This is because of the extremely limited depth buffer precision afforded by the 8-bit shadow map. In my shadow mapping demo (link already posted by godmodder), you can use the arrow keys to adjust the size of the frustum - note how the shadows go wrong once the near clip plane is closer to the camera than 0.3 or so. At this point, due to the non-linear nature of the depth buffer, many of the values are 255.

In other words, you need to push your near clip plane (when drawing from the light's POV) as far out as possible.

bakery2k1, I didn't know that website was yours! From the looks of it, you've got some great demos there! [smile]

Back to my problem, you were spot on with the depth precision - moving the near plane out solved the problem. Now, after that (and looking through your code) I've got the depth map in properly, but after that I'm having trouble projecting it.

OK depth map:


But if I try the projective texturing I get little "shadowed" streaks, and excepth that nothing:
Free Image Hosting at www.ImageShack.us

When you are multiplying with the bias matrix the light projection and view matrices, how do you obtain the latter two? At the moment I just save the matrices from when I capture the depth from the light's point of view:

// set up projection and look-at from light's point of view
glGetFloatv(GL_PROJECTION_MATRIX, light_proj);
glGetFloatv(GL_MODELVIEW_MATRIX, light_view);


Is this correct? Thanks for any help!

Share this post


Link to post
Share on other sites
Try performing the projective texturing with the depth map alone - without performing the shadow comparison. What does that look like?

Quote:

When you are multiplying with the bias matrix the light projection and view matrices, how do you obtain the latter two? At the moment I just save the matrices from when I capture the depth from the light's point of view:

// set up projection and look-at from light's point of view
glGetFloatv(GL_PROJECTION_MATRIX, light_proj);
glGetFloatv(GL_MODELVIEW_MATRIX, light_view);


That should work, but note that you need the light's "view" matrix, not the "modelview". You need to make sure that you are getting the matrices before performing any model transformations.

Share this post


Link to post
Share on other sites
bakery2k1, first of all thanks for sticking with me. After a lot of experimentation, I reversed the order of your matrix multiplies and it gives me this:

Free Image Hosting at www.ImageShack.us

Note that this is just with the depth texture (no tex_env_combine and no depth ramp texture either). It looks about alright, but then I am stuck at the next stage - combining with the depth ramp texture. This is what it looks like when I try it:

Free Image Hosting at www.ImageShack.us

With your wizard-like diagnostic skills [smile], can you suggest what I am doing wrong?

The paper by MJK states "save the depth map as INTENSITY8" which is what I am doing. I don't exactly yet know how tex_env_combine works - but it's specifying the texture as a source of ALPHA. Does that mean the texture's internal format has to be ALPHA?

If you suspect a particular portion of the code tell me I will post it.

Share this post


Link to post
Share on other sites
Quote:
Original post by deavik
bakery2k1, first of all thanks for sticking with me. After a lot of experimentation, I reversed the order of your matrix multiplies and it gives me this:


That looks promising!

Quote:
The paper by MJK states "save the depth map as INTENSITY8" which is what I am doing. I don't exactly yet know how tex_env_combine works - but it's specifying the texture as a source of ALPHA. Does that mean the texture's internal format has to be ALPHA?


INTENSITY8 textures have all 4 RGBA components with the same value, so are perfectly valid to be used as a source of ALPHA.

Quote:
If you suspect a particular portion of the code tell me I will post it.


If you're worried about your use of tex_env_combine, maybe we should have a look at that?

Share this post


Link to post
Share on other sites
OK, now I have set both the textures to INTENSITY8 internal format. The next 2 images are the scene projectively textured with *only* the: a) depth map, b) depth ramp texture:

Free Image Hosting at www.ImageShack.us

Free Image Hosting at www.ImageShack.us

Now from those 2, I can see that subtracting the second from the first should "separate" out the shadowed portions. The texenv code:


glActiveTexture(GL_TEXTURE0);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);

glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PRIMARY_COLOR);

glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);

glActiveTexture(GL_TEXTURE1);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);

glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);

glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_ADD_SIGNED);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glAlphaFunc(GL_GREATER, 0.5);
glEnable(GL_ALPHA_TEST);



TEXTURE0 holds the depth map texture, TEXTURE1 the 1D ramp texture. With that, the result is:

Free Image Hosting at www.ImageShack.us

However, another thing I am thinking about is that the bottom left portion of the "only depth ramp textured" scene is slightly darker than the "only depth map textured" scene - will subtracting them work? On the other hand, the bottom right hand corner of the combined textured scene looks OK (ie. not shadowed).

Thanks so much for your help so far!

Share this post


Link to post
Share on other sites
bakery2k1, I tried what you said, without success. However, I gott around to getting my head wrapped around texture combiners, and I think the condensed version below (which makes sense to me) is correct:

// set texenv to combine
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);

// color = primary color
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PRIMARY_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);

// alpha = TEXTURE0 alpha + (1 - TEXTURE1 alpha) - 0.5
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE0);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE1);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_ADD_SIGNED);

glAlphaFunc(GL_GREATER, 0.5);
glEnable(GL_ALPHA_TEST);



Now, the nett result of the alpha caluclation is (TEXTURE0 - TEXTURE1 + 0.5), and parts with alpha < 0.5 are rejected, right? Just to experiment I exchanged the the GL_SRC_ALPHA and GL_ONE_MINUS_ARC_ALPHA as operands 1 and 0 (so the new calculation is TEXTURE1 - TEXTURE0 + 0.5) and it gives me perfectly the result I expect: ie occluded from light parts unshadowed, and exposed parts shadowed. Take a look:

Free Image Hosting at www.ImageShack.us

You can even see where te self shadowing will happen! But I just can't figure out why just the opposite of this (which I want) is not happening ... arrrgh!

Share this post


Link to post
Share on other sites
Quote:
Original post by deavik
TEXTURE0 holds the depth map texture, TEXTURE1 the 1D ramp texture.


Are you sure of this? You may have the textures the wrong way round.

Quote:
I think the condensed version below (which makes sense to me) is correct


That appears correct, but note that you are accessing both TEXTURE0 and TEXTURE1 in the combiner unit for texture 0. This is OK, but GL_COMBINE alone does not allow this - you are using "crossbar" texturing.

Share this post


Link to post
Share on other sites
With my newfound knowledge of texture combiners [wink], I kept the alpha aources sign changed (as I wrote in the last post) and changed the AlphaFunc to GL_LEQUAL, and it works!

Free Image Hosting at www.ImageShack.us

BTW, is the texture combiner state maintained per texture unit? And what is "crossbar" texturing - accessing one texture unit from another?

bakery2k1, you practically tutored me to getting my first shadow map on screen! Thanks so much! [smile]

Share this post


Link to post
Share on other sites
Quote:
Original post by deavik
With my newfound knowledge of texture combiners [wink], I kept the alpha aources sign changed (as I wrote in the last post) and changed the AlphaFunc to GL_LEQUAL, and it works!


Well done! [smile]

Quote:
BTW, is the texture combiner state maintained per texture unit? And what is "crossbar" texturing - accessing one texture unit from another?


In the fixed function pipeline (this no longer applies when you get into fragment shaders) each texture unit can perform (IIRC) one combination operation on the RGB components and another on the alpha components.

Using texture m as an operand in the combination on texture unit n (m != n) is called "crossbar" texturing. Certain older cards could support GL_COMBINE, but not crossbar texturing. Just about anything should support it nowadays though.

Share this post


Link to post
Share on other sites
Thanks for the info!

Well I guess now that I've got the hang of my first shadowmap, like everyone else I'll try to improve on it. [smile]

Performance-wise of course I should try to cache the shadowmaps until the light / shadowcaster move, shouldn't I?

Apart from that - I know there are lots of shadowmap "improvement" methods out there - but what in your opinion is the best cost / quality wise that I should look at first? Without fragment shaders, that is ...

Share this post


Link to post
Share on other sites
I wouldn't settle for view-dependent techniques like PSM, Lispsm or TSM. These are all hard to implement and they're not really robust. Because they're view dependent, you can't cache them either so performance will be really slow especially for omni-lights. CSM's look promising though, knowing that pixel power of GPU's will increase rapidly, handling 3 shadowmaps of 2048x2048 shouldn't be a problem for DX10 hardware I think. The best thing about CSM's is that they ARE cachable (they're not view-dependent).
If you combine CSM's with PCF or even better VSM's, you can get some awesome results!

Jeroen

Share this post


Link to post
Share on other sites

This topic is 4236 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this