
So finally I had some freetime to crunch up my PSSM implementation into both a sample and a library. You can easily use the library in .NET based languages,
without having to re-code it to the language of your choice! The package I provide contains the following:
- Source code for both the sample
- Source code for the PSSM library
- Source code for the shaders
Basicaly, my implementation has the following properties:
1. It uses four frustum splits
2. All four frustum splits are saved in only ONE texture, each depth map is saved in a separated ARGB channel. Thus you can NOT use Variance Shadow Mapping or every other probability shadow mapping method, since they require at least two channels per depth map.
3. It uses a five-pass model, instead of the eight-pass model mentioned in the original PSSM papers. My implementation can render the final scene in only a single pass!
4. It does NOT require dynamic branching on the GPU, as most implementation does.
5. It uses orthogonal camera matrices, since they make more sense than the perspective ones used in the original PSSM papers.
The basic render flow of my implementation is the following:
You just pass the light and camera information and your shaders into the library.
The library itself does not know the shadow casting/receiving geometry at all - and it doesn't need to do. To get the shadows working, you apply your shaders on your geometry (
which can be TVTerrain, TVMesh, TVActor, TVMiniMesh, TVParticleSystem, in other words: Every imaginable geometry! and ensure that they have two techniques implemented: "shadowed" and "depth" (More on this later on.) The library does switch between the techniques while generating the depth maps. This way, there is no need to re-apply the shaders to your geometry and the library doesn't need to know where the shaders are applied on. To get the occluders onto the depht map, you hook up a delegate in the library that asks YOU to render the geometry - Thats it.
The eStudioz.PSSM library contains one main class called CParallelSplitShadowMapping, which you need to instanciate. While doing so, you need to pass the
following information to its constructor:
// 256, 512, 1024, 2048
int nShadowMapSize
// FLOAT16 or FLOAT32. The more precision, the better. Needs at least four channels!
CONST_TV_RENDERSURFACEFORMAT eRenderSurfaceFormat
// The render interval in seconds. There is definately no need to render the depth maps every frame. Every 1/100th
float fRenderInterval second for example is enough at all. Can be limited to every other interval, e.g. on slower computers. 0 will be every frame rendering.
// Since it can only use one directional light, you must pass it into. However, you can change this direction later on in the LightDirection attribute of the
CParallelSplitShadowMapping class
TV_3DVECTOR vLightDirection
After that, you call the AddShader() method to add every shader you want to add. The only thing you need to take care of, is the AddMeshToSceneBoundingBox() method. This method contructs the scenes bounding box volume to let it be able to care about the worlds borders. The simplest ways would be to pass in only one large box, that is never rendered. Of course you could skip this if you want to, but I advice you not to, because it can optimize the scenes frustum to ensure the best included shadow areas. Then, you hook up the RenderGeometry() delegate and you're ready. To render the depth maps and calculate all neccessary stuuf, call the Update() method BEFORE your final scene rendering pass and that's it. Easy, isn't it?
I think five lines of code is easy enough though 
On the shader side, you need to include the following parameters and one texture:
float3 g_vLightDirection;
float4x4 g_mShadowMap[4];
float4x4 g_mLightViewProj[4];
float4 g_fSplitDistances;
texture texShadowMap;
sampler2D sampShadowMap =
sampler_state
{
Texture = (texShadowMap);
MinFilter = Point;
MagFilter = Point;
MipFilter = None;
AddressU = Border;
AddressV = Border;
AddressW = Border;
BorderColor = 0xFFFFFF;
};
In addition, I provide highly optimized functions for the shadow test:
half GetSplitByDepth(float fDepth)
{
float4 fTest = fDepth > g_fSplitDistances;
return dot(fTest, fTest);
}
float GetShadow(half fSplitIndex, float2 faSplitUV[4], float4 faLightDepths)
{
float fShadow = faLightDepths[fSplitIndex] > tex2D(sampShadowMap, faSplitUV[fSplitIndex].xy)[fSplitIndex];
return 1.0f - fShadow * SHADOW_OPACITY;
}
Refer to my blog at
http://www.e-studioz.de/wordpress/?p=14 to see why this is optimized as possible. For more information, take a look at my code. It's not the best code, but clean, readable and effective. Oh, and keep in mind, that the project is a MSVS 2008 one, but the code itself is .NET 2.0 (as I don't like 3.0). Ah, and in addition, there is no MTV3D65.dll included, as usual.
Here's the download for you:
http://www.e-studioz.de/pssm/PSSM4u.zipTo move the camera in the sample, hold your mouse button 2 and use WASD to move.
Overall, this is open source! All I ask for is:
- Please don't change the library name (eStudioz.PSSM.ddl) if you use it "as is".
- Show me all the freaking stuff you made with it!
- A credit might be cool, but being a beta-tester of your stuff might be even better!
Please do me this favor, since I'm sharing ~150 hours of work here! Thanks!Hope you enjoy it!