For quite some time I had this camp fire made of particles inside my demo scene. However, a camp fire without shadows looks a bit unnatural.
I have already implemented shadow mapping but only for a single projection. For the camp fire I need to map the shadows all around the camp fire, like a complete sphere.
There is a solution, it’s called cube texture.
In terms of topology a cube is isomorphic to a sphere, they both have a genus value of 0. This means that a cube can be stretched and deformed into a sphere without tearing the surface.
A torus(like a donut) has a genus 1 and cannot be stretched into a sphere.
DirectX11 and other APIs have intrinsic support for cube textures. In DirectX11 a cube texture is actually an array of 6 2D textures.
With a 2D texture we use a 2D vector to sample a Texel, namely the “UV coordinates”. This was sufficient because the 2D texture is like a bitmap in a sense.
With the cube texture we need a 3D vector to sample a Texel.
However, the length of the vector does not matter. This vector is “pointing” from the center of the cube texture and samples the Texels that it points to, or the Texel on the cube which the ray from the center with the direction of the vector intersects with.
Since we are doing shadow mapping, we need to compare the depth of our current pixel(in the pixel shader) to the depth in the shadow map.
With a single projection shadow mapping we achieved that by converting the world space position of the pixel into the screen space depth using the projection matrix and the view matrix of the light source.
float Depth = gLightProj._33+gLightProj._43/abs(toLight.z); // toLight here is in view space, the light acting as a camera.
In our cube map we have 6 projections, one for each side of the cube. We align the sides of the cube to the axes of the world space. Relying on the axes alignment and on the fact that the cube map is actually a cube and not a nonuniform box, we can calculate the depth value simply by taking the maximum absolute value of x, y, z. We also don’t need to translate the coordinate into the view space like we did with the single projection, because we assumed that the sides of the cube map are aligned to the world space.
float3 toLight = pin.PosW-gCynLights.Position; // Now toLight is in world space float Depth = gLightProj._33+gLightProj._43/(max(abs(toLight.x), max(abs(toLight.y), abs(toLight.z))));
Cube mapping can get pretty expensive. We have 6 textures instead of 1, and we need to render the scene from 6 different projections to fill the cube map.
There are optimizations we can do though. First of all, we can start off by not picking very high-resolution for the cube map.
Another thing is that we don’t have to render the whole scene 6 times, we can just render the objects which are visible to each of the 6 projections. In other words, we can do macro level culling. Macro level culling is done by not sending objects for drawing to the GPU which are not gonna be visible anyway. With a smart separation of the scene into different chunks and objects, we can ideally render every object no more than once or twice.
Another approach is to use the geometry shader to duplicate the level objects for each one of the 6 projections. This will save draw calls and some processing, but it has its own performance issues.
In my demo I used the first method and for the walls and floor of the dungeon I disabled shadow casting all together because the effect of shadows casted from the walls was barely visible.
One last thing, I added a small touch to the camp fire. I randomly changed the camp fire’s light source intensity and position over time. This is a small detail that made the camp fire look much more real and alive.