In the last tutorial of this series, I will try to explain how to create your own shadow mapping shaders. We’ve already seen the shadows of truevision3d, now it’s time to create our own system. This is by no means the best type of shadowing, it is just good to get an understanding of what is actually happening. The system we’ll make is for a point light. The basic idea behind it is to place 6 camera’s on the place of the light, each facing outwards, so you’ve got a complete view from the camera. This is a cubemap. We’ll render the depth of the object the camera sees to the cubemaps’ faces. This then allows us to do a lookup in the direction of the pixel we’re rendering, to see if the distance of the cubemap is lower than the real distance. If it is, we have a shadow.
What do we need?
- First of all we’ll need a cubemap rendersurface.
- We will also need 2 shaders to render with. 1 shader will just output the depth, the other will sample the depth and return the colors.
How do we set things up?
- The cubemap rendersurface is created like this:
The first parameter is the size of the render surface (per face, so in this case we have 6 256×256 pixel textures).
The second parameter enables the depth buffer, so an object behind another object can never be rendered on top.
Then there’s the format and the string identifier, which you’ve already seen before.
- Another thing to set up is the cube map properties:
RSDeep.GetCamera().SetViewFrustum(90, 1500, 1);
In this case, we set the cube map to automatically generate the camera’s for us, so we don’t have to do all that math any more (first line, 1st parameter). The position of the camera’s will be (0,15,0).
The last line tells the render surface to use a camera with a field of view of 90 degrees, which means we’ll get full coverage.
How do we render this?
- First of all, lets render the scene to the depth cube map.
In this 6 iteration loop, the StartCubeRender function will setup the right cameras. Then the room is rendered and the cuberender for that face will stop. Keep in mind that before we do this, we have to make sure we have the room render its depth by calling:
- To render the room with the shadows on it, we set the shader to the lighting shader and render it:
- Now for some explanation of the shaders. The vertex shader of the depth shader:
OUT.Position = mul(float4(IN.Position, 1), World);
OUT.Depth = length(OUT.Position.xyz – LightPos.xyz);
OUT.Position = mul(OUT.Position, ViewProj);
OUT.UV = IN.UV;
You should be able to figure out what it does, but just to make sure; we calculate the vertex positions in world space, calculate the distance between the vertex and the light position, transform the vertex into the view projection space and forward the UV coordinates.
- The vertex shader of the depth shader just outputs the depth:
OUT.Colour = float4(IN.Depth, 0, 0, 1);
- The vertex shader of the lighting shader isn’t too fancy, it does about the same as the ones from this and the previous tutorial combined, so it will calculate depth as well. This depth is going to compare to the one of the cube map. This might look odd, it’s the same as before right? No, we’re in another camera now, so we see different vertices now, so there can be a difference.
- The pixel shader of the shadow shader is really complicated at first glance, but I’ll extract the important parts for this tutorial from it.
if(Deep > Depth) sFactor.rgb -= SubFactor;
rsFactor = saturate(sFactor);
This is all there’s happening that’s important. The depth variable will contain a lookup from the cubemap. You can sample from a cubemap by supplying a direction vector that points from within the middle of the cube to the outside. It has to be normalized to have a correct result.
Now that we have this depth, we can compare it to the depth we calculated for the vertex. If there’s a difference, we need to subtract some light from the pixel. That’s done by the second line.
We can’t have negative light, so the final result will be saturated (clamped to 0 if below zero).
There is a lot more going on, but this is the most important part.
Things you can add/change yourself
- There’s not much to add here, Geoff Wilson did a great job writing these shaders, so they are pretty much complete. You can however add more objects and play around with the biases in the shader to get a feeling for them.