Skip to main content
gamedev.net
🔒 Locked

shadow maps question

Started by McZ 23 replies 4.3k views
Original Post
McZ
McZ
I have been reading alot of different tutorials and stuff here on this forum, but one thing I havn't understand yet is if I need one depthmap for each light that cast shadows? but then I need to activate all of them at the same time when doing the test against the depthmap?
Yann L
Yann L
Yes, you need a separate depth map for each lightsource. When rendering the shadowed geometry, you perform as many tests as there are lights, and somehow combine the results. I said "somehow", because different people use different methods, it's more often than not an artistic decision. The physically most correct way is to add the light contribution of all sources together, each one multiplied with the shadow comparison factor of its depth map.

1) Start by rendering each light to its individual depth surface

2) Bind each surface to a separate texture (imaging) unit.

3) Set up a depth compare for each unit, making sure that the generated projected texture coordinates for each unit match the one of the respective lightsource.

4) Use the following equation per pixel:

c(final_light) = c(light0) * depthCompareResult(0) + c(light1) * depthCompareResult(1) + ...

5) Multiply the c(final_light) with whatever diffuse texture or colour you might need.
Mezz
Mezz
EDIT Beaten by Yann

-Mezz
MikeyO
MikeyO
I have a question relating to Shadow maps.. I think I sort of understand how the process of setting up the depth map works, but what I dont understand is the rendering process.. (basically) how does the texture create these shadows on the screen, and do you use a textured quad in front of the camera? I know a quad is involved somehow, but.. I just don't see how it all fits together at the moment. Maybe someone could direct me to a nice explanation of shadow mapping.
Mezz
Mezz
A couple of shadow map links:

nVidia paper

Paul's Projects tutorial

To provide a bit of answer to your question Mikey, shadow mapping makes use of projective texturing, to project the shadow map onto the geometry into the scene. Using this you can easily figure out which texel in the shadow map applies to the particular part of the geometry you are rendering, and thus know if it is in shadow or not WRT the light source.

-Mezz
MikeyO
MikeyO
Do shadow casters/reciever meshes need to have UV coordinates?
Jason Z
Jason Z
The depth map is projected onto the shadow receivers, so the uv coords are generated. The nVidia paper that Mezz posted is a very good reference, especially when you first start to learn about shadow maps.
Jason Zink :: DirectX MVP   Direct3D 11 engine on CodePlex: Hieroglyph 3 Direct3D Books: 
McZ
McZ
ok, I'll look into the nVidia paper... but so far I can't really understand how the shadow maps can be valid at the same time.. this will cost alot of texunits? or do I create an extra pass that project all of them into one big depthmap and use that one only?
Mezz
Mezz
Quote:
Original post by McZ
I can't really understand how the shadow maps can be valid at the same time..


I'm not sure I understand this question. If you have one shadow map for each light, then render the scene from each light's POV into their shadow maps, then all the shadow maps contain valid information. After you've done this, you can use these shadow maps when drawing your actual scene from the viewer's perspective, to tell whether certain objects are in shadow or not.

Quote:
Original post by McZ
this will cost alot of texunits?


It will if you do multiple lights in one pass, but you can do a pass per light and only bind one of your shadow map textures at a time, hence only using one texture unit.

Quote:
Original post by McZ
or do I create an extra pass that project all of them into one big depthmap and use that one only?


I've never heard of this as a technique, so I couldn't recommend it.

-Mezz
McZ
McZ
ahh thank you! now I understand :)

I use up all tex units I have free for a pass.. but what if my texunits isn't enough for one pass? how do I split it into several passes? do the extra passes also need depthmap testing?

and what to do if I have more depthmaps then tex units?
Mezz
Mezz
If you don't have enough texture units to do one light in a single pass... well, that's some complex lighting equation you have going on there :)

If you were able to split one light into multiple passes, then yes you'd still need a shadow test for each pass, remember that in the end all your shadow test does is effectively multiply the light's value either by 1.0 (i.e. full brightness) if it's not in shadow, or 0.0 (i.e. no brightness at all) if it is. Sometimes you might multiply it somewhere in between if it's on a boundary but that's a more advanced case.

If you have more depth maps than texture units... well, like has been said previously, you need to do a pass per light, or whatever you can afford. You only need the depth map for the light you are rendering, so if you do a single pass per light, you would use at most 1 texture unit with the depth map.

Hope that clears some stuff up.

-Mezz
MikeyO
MikeyO
I tried to implement Nvidia's HardwareShadowMap sample, but I am definitely not seeing the right results.
(http://download.nvidia.com/developer/SDK/Individual_Samples/DEMOS/Direct3D9/HardwareShadowMap.zip)

-set the render target to the color surface
-set the depth stencil surface to the new one
-clear the target & depth buffer
-set the new viewport
-set up matrices from lights POV.
-draw each mesh with the 2nd technique (GenHardwareShadowMap)
-reset the viewport
-reset the render target/depth buffers
-clear the target/depthbuffer
-set up texScaleBias Matrix
-set "ShadowMap" to the depth texture
-set "spotlight" texture
-set view's worldviewproj and worldIT for each mesh
-Draw each mesh with 1st technique (UseHardwareShadowMap)
-Rinse, lather, repeat

And this is what it looks like: (Top in wireframe, bottom solid, gray = color render target rendered to a sprite)
So anybody know what many things I might be doing wrong?
MikeyO
MikeyO
Heres what it should look like (this is with stencil volumes though):
cignox1
cignox1

I know a quad is involved somehow
[\quote]
I think you were meaning a volume shadows bad version, in wich you use a black quad over the whole screen to create the shadows, using the stencil generated mask to render it.
MikeyO
MikeyO
No, I mean shadow mapping.. I realized some time ago that the quad they(nvidia) are creating in their sample is only used for the shadow casting object's shadow to be projected onto (:D).
Mezz
Mezz
Well there isn't really anything wrong with the algorithm you've written there MikeyO, but I can't tell what the problem is just from those shots. Are you sure you're doing the texture projection correctly in the UseHardWareShadowMap phase?
I assume you're just using nVidia's shaders, in which case they will be correct.
Hmm... dunno.

-Mezz
MikeyO
MikeyO
Ok, here's the relevent source code... I'm sure it's either some fundamental error, something I'm doing completely wrong, or its a few small things I'm forgetting. - Captain Obvious

///////INITIALIZATION CODE//////shadowFX.SetValue("LightVec", Vector4.Scale(Vector4.Normalize(new Vector4(lightVector.X, lightVector.Y, lightVector.Z, 1.0f)), 100));backBuffer = device.GetRenderTarget(0); //I assume this is a reference..zBuffer = device.DepthStencilSurface;colShadowTex = new Texture(device, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 1, Usage.RenderTarget, Format.A8R8G8B8, Pool.Default); //Color texturezShadowTex = new Texture(device, SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 1, Usage.DepthStencil, Format.D24S8, Pool.Default); //Z depth textureshadowColSurface = colShadowTex.GetSurfaceLevel(0); //Color surfaceshadowZSurface = zShadowTex.GetSurfaceLevel(0); //Z depth surfacelightPosition = new Vector3(1000, 1000, 0);///////MAKE SHADOW CODE//////////Matrix World;Vector3 lookAt = Vector3.Subtract(lightPosition, lightVector);Matrix lightView, lightProj;lightView = Matrix.LookAtLH(lightPosition, lookAt, new Vector3(0, 1, 0));lightProj = Matrix.PerspectiveFovLH((float)Math.PI / 3, 1, 1, 1000);lightViewProj = lightView * lightProj;device.SetRenderTarget(0, shadowColSurface);device.DepthStencilSurface = shadowZSurface; Viewport oldViewport = device.Viewport;Viewport newViewport = new Viewport();newViewport.X = 0; newViewport.Y = 0;newViewport.Width = SHADOW_MAP_SIZE;newViewport.Height = SHADOW_MAP_SIZE;newViewport.MinZ = 0.0f;newViewport.MaxZ = 1.0f;device.Viewport = newViewport;shadowFX.Technique = shadowFX.GetTechnique(1);device.RenderState.DepthBias = 0.0002f;device.RenderState.SlopeScaleDepthBias = 2.0f;device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Gray/*0x00FFFFFF*/, 1.0f, 0);device.VertexDeclaration = defDeclare;Matrix tempIdent = Matrix.Identity;int passes;for(int i = 0; i < balls.Count; i++){	Ball b = (Ball)balls;	///////RENDER CODE HERE /////	World = device.Transform.World = Matrix.Scaling(1, 1, 1) * Matrix.RotationYawPitchRoll(0,0,0) * Matrix.Translation(b.position);	device.Transform.World = World;	//Set up matrices	Matrix WorldViewProj = World * lightViewProj;	Matrix WorldITMat = Matrix.TransposeMatrix(Matrix.Invert(World));	shadowFX.SetValue("WorldViewProj", WorldViewProj);	shadowFX.SetValue("WorldIT", WorldITMat);	shadowFX.SetValue("TexTransform", tempIdent);	passes = shadowFX.Begin(0);	for(int j = 0; j < passes; j++)	{		shadowFX.BeginPass(j);		ballMesh.DrawSubset(0);		shadowFX.EndPass();	}	shadowFX.End();}device.Transform.World = World = Matrix.Translation(0, -4, 0);shadowFX.SetValue("WorldViewProj", World * lightViewProj);shadowFX.SetValue("WorldIT", Matrix.TransposeMatrix(Matrix.Invert(World)));passes = shadowFX.Begin(0);for(int j = 0; j < passes; j++){	shadowFX.BeginPass(j);	plane.DrawSubset(0);	shadowFX.EndPass();}shadowFX.End();//Reset render settingsdevice.Viewport = oldViewport;device.RenderState.DepthBias = 0.0f;device.RenderState.SlopeScaleDepthBias = 0.0f;//////////USE SHADOW CODE///////////////////Generate Shadow MapRenderShadowMap(device, View, Proj); //Calls above code (MAKE SHADOW CODE)//Render SceneshadowFX.Technique = shadowFX.GetTechnique(0); //Use shadow techniquedevice.SetRenderTarget(0, backBuffer); //reset to original render targetdevice.DepthStencilSurface = zBuffer;device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Blue, 1.0f, 0);shadowFX.SetValue("ShadowMap", zShadowTex);shadowFX.SetValue("SpotLight", decalShadowModel);float fOffset = 0.5f + (0.5f / (float)SHADOW_MAP_SIZE);int range = 1;float fbias = 0f;Matrix texScaleBiasMat = new Matrix();texScaleBiasMat.M11 = 0.5f;texScaleBiasMat.M22 = -0.5f;texScaleBiasMat.M33 = (float)range;texScaleBiasMat.M12 = texScaleBiasMat.M13 = texScaleBiasMat.M14 = 	texScaleBiasMat.M21 = texScaleBiasMat.M23 = texScaleBiasMat.M24 = 	texScaleBiasMat.M31 = texScaleBiasMat.M32 = texScaleBiasMat.M34 = 0.0f;texScaleBiasMat.M41 = texScaleBiasMat.M42 = fOffset;texScaleBiasMat.M43 = fbias;texScaleBiasMat.M44 = 1.0f;int passes;for(int i = 0; i < balls.Count; i++){	Ball b = (Ball)balls;	///////RENDER CODE HERE /////	World = device.Transform.World = Matrix.Scaling(1, 1, 1) * Matrix.RotationYawPitchRoll(0,0,0) * Matrix.Translation(b.position);	device.Transform.World = World;	//Set up Matrices	Matrix WorldViewProj = World * View * Proj;	Matrix WorldIT = Matrix.TransposeMatrix(Matrix.Invert(World));	shadowFX.SetValue("WorldViewProj", WorldViewProj);	shadowFX.SetValue("WorldIT", WorldIT);	shadowFX.SetValue("TexTransform", World * lightViewProj * texScaleBiasMat);	passes = shadowFX.Begin(0);	for(int j = 0; j < passes; j++)	{		shadowFX.BeginPass(j);		ballMesh.DrawSubset(0);		shadowFX.EndPass();	}	shadowFX.End();}shadowFX.SetValue("SpotLight", decalShadowSurf);device.Transform.World = World = Matrix.Translation(0, -4, 0);shadowFX.SetValue("WorldViewProj", World * View * Proj);shadowFX.SetValue("WorldIT", Matrix.TransposeMatrix(Matrix.Invert(World)));passes = shadowFX.Begin(0);for(int j = 0; j < passes; j++){	shadowFX.BeginPass(j);	plane.DrawSubset(0);	shadowFX.EndPass();}shadowFX.End();


The shader can be found here: http://pk.dras.us/users/Orion/ShadowMap.fx

Anything that is just glare-in-your-face obvious? subtle errors? Help thus far has been greatly appreciated.
MikeyO
MikeyO
I finally got it working by removing the new viewport code.
McZ
McZ
Quote:
Original post by Mezz
If you don't have enough texture units to do one light in a single pass... well, that's some complex lighting equation you have going on there :)

If you were able to split one light into multiple passes, then yes you'd still need a shadow test for each pass, remember that in the end all your shadow test does is effectively multiply the light's value either by 1.0 (i.e. full brightness) if it's not in shadow, or 0.0 (i.e. no brightness at all) if it is. Sometimes you might multiply it somewhere in between if it's on a boundary but that's a more advanced case.

If you have more depth maps than texture units... well, like has been said previously, you need to do a pass per light, or whatever you can afford. You only need the depth map for the light you are rendering, so if you do a single pass per light, you would use at most 1 texture unit with the depth map.

Hope that clears some stuff up.

-Mezz


lets say I have 4 texture units and 2 of them is texture but I have 4 shadowcasting lights(depthmaps) so then I need to do it in 2 passes?

first make sure the depthmaps is updated.

bind the textures for the mesh
bind the first 2 depthmaps
draw

blend scene with the new pass
bind the last 2 depthmaps
draw

or is it some other way?
Mezz
Mezz
Not that I'm aware of (well, you could split it into 4 passes if you wanted) but that 2-pass approach looks fine McZ.

-Mezz

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.