Orthogonal Style Shadow Maps
Posted: Thu Jul 03, 2008 4:51 pm
Hello and welcome to one of my (very rare) tutorials. This week I'll be introducing you to the handy world of combining orthogonal projections with shadow maps. The cool thing about using an orthogonally projected shadow map along with an orthogonal view is that you very high detailed shadows whereever you go at a fairly low performance cost. This is due to the fact that the shadow camera can easily follow the main camera around, shadowing things in a directional manner as necessary.
Well lets get started, first grab a copy of XEffects from here. Please get the latest version because I made some changes and bug fixes that are necessary for this tutorial code to function properly. From here on, I'm going to go through every little bit about the example, even the obvious stuff, so that you can just copy paste every code snippet and you will have the entire CPP file. Please bear with me on that.
Open up a new project, and type down the basic boilerplate code we need at the start of every Irrlicht tutorial. Here I am also including "effectWrapper.h" from XEffects, and a custom Diablo/Dungeon Siege style character controller I made to use along with this project:
In traditional Irrlicht tutorial fashion, we are too lazy to actually make use of namespaces, and so we make them obsolete using the follow few lines:
Int main, and Irrlicht crew:
Here I am creating a cube scene node, then stretching it to form a plane. Following that I use the mesh manipulator to alter the texcoords so that the texture tiles along the ground plane. An alternative method would be to create a HillPlaneMesh and specify upon its creation the texture repetitions we desire. We will also set the texture, and in this one case, set lighting to true. The ground mesh is the only one that looks better with lighting set to true, all the other meshes are better off with it set to false, you will see why later:
Here we add an ICameraSceneNode, "orthoCam", and set its projection matrix to an orthogonal one. Note how easy it is to create an orthogonal matrix. The first two parameters are the height and width, and the last two are the near and far plane Z-positions. Note that there is a LH (Left hand) method and a RH (Right hand) method. In most cases in Irrlicht we would want to use the Left Hand method. We also set the camera's "isOrthogonal" property to true, this has nothing to do with the view, but merely informs the scene manager's culling and collision algorithms to treat it differently:
Here we create our dwarf IAnimatedMeshScene node. I used an ms3d version that can be found on Psionic's (The original creator) site, mainly because the animation keyframes are easier to handle. I've also included this version in the download package for you to use. Following that I add my special "Dungeon Siege style" character controller and set the animator as my device's event reciever, because it will be recieving mouse input and moving the camera/dwarf character. I set the lighting to false on the dwarf, because this more closely resembles an outdoor feel, and makes the shadows cast on the dwarf more distinguished:
Now we create the effectHandler instance. We pass the current Irrlicht device and a nice square resolution of say 1024x1024. (Feel free to up this for more quality.). When you don't specify the shaders folder in the constructor, it will assume they that the shaders are located in a folder called "shaders" in the same directory as the executable, so be sure to place them there. We will also set a shadow darkness of 1.0f:
Here I'm applying shadow mapping to the ground and dwarf scene nodes. The floor need only receive shadows, so we will give it the ESM_RECEIVE shadow mode:
Here we will generate another orthographic projection matrix and apply it to light's camera. When a light is set up in an orthographic manner, it behaves as a directional light source, similar to that of the sun. This makes it great for outdoor scenes. Note that we are setting the width and height here a little bigger than last time, to make sure that we are capturing everything within the viewport. Depending on how tall the objects in your levels are, you may have to increase these values. We also set the maximum shadow distance from the light to 1. This is because when using orthographic projection, the depth values behave slightly differently. Make sure that the near and far value of your projection matrix enclose the visible area as tightly as possible, to get the best depth resolution:
Add a few houses, position and rotate them. I changed the culling mode because I found the default culling mode can make them disappear while they are on screen when using this style of projection, I suppose telling the smgr that the camera is orthographic didn't do much
. We will also set the lighting to false on these houses, as the lighting will be taken care of mostly by the shadow maps, also since the actual lighting in the shadow mapping shaders is of a point light source, it doesn't make much sense to have a light source like that follow the camera around. The end result looks better to me with no diffuse lighting applied. We will use the shadow mode "ESM_BOTH" as the houses will cast shadows on themselves from the chimneys/window extrusions:
Basic run loop with frame rate display in the window caption. The main reason that the Irrlicht tutorials do the whole "if lastFps != FPS" thing is because changing the window title can be quite a costly operation to do every frame, so we only want to do it when necessary:
We make the view camera and the shadow camera follow the dwarf around, the shadow camera is facing the opposite direction of the view camera, this allows us to see the shadows coming in a downward direction. You could try a different orientation, but make sure that the shadow's frustum (View area) fully encloses what the view camera is seeing, by scaling and positioning it correctly. We must also remember to update the effectHandler, this operation replaces "smgr->drawAll()" incase we want to do post processing effects:
Last but not least, we drop the device and return 0:
End result:

A few nice things to note about this screenshot:
- The quality is consistant throughout, the shadow camera follows the view where ever it goes, and the shadowmap is uniformally distributed because of the orthographic projection.
- The chimneys cast soft shadows on the roofs of the houses.
- The dwarf is partly shadowed by the house in front of him.
- The dwarf and house shadows blend seamlessly together to form a single shadow, the dwarf isn't unrealistically casting a shadow of its own when it's already inside a house shadow.
About the character controller:
When the user clicks on a position on the screen, the intersection is found between the screen ray and the ground plane. The dwarf then orients itself towards the direction of the point where the user clicked, and runs there until he is a certain distance away. The animation frames specified are specific to the dwarf, so be sure to set these to something else if you plan to use the character controller in your own projects with a different model. Regarding that, the IOrthoControllerAnimator is free to use, no restrictions.
Here is an archive containing the demo binary/media and full sources:
http://irrlichtirc.g0dsoft.com/BlindSid ... Shadow.zip
(Remember: To move the dwarf just click somewhere and he will run there.)
That about settles it for today, have fun.
Cheers
Well lets get started, first grab a copy of XEffects from here. Please get the latest version because I made some changes and bug fixes that are necessary for this tutorial code to function properly. From here on, I'm going to go through every little bit about the example, even the obvious stuff, so that you can just copy paste every code snippet and you will have the entire CPP file. Please bear with me on that.
Open up a new project, and type down the basic boilerplate code we need at the start of every Irrlicht tutorial. Here I am also including "effectWrapper.h" from XEffects, and a custom Diablo/Dungeon Siege style character controller I made to use along with this project:
Code: Select all
#include <irrlicht.h>
#include "effectWrapper.h"
#include "IOrthoControllerAnimator.h"
Code: Select all
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
Code: Select all
int main()
{
IrrlichtDevice* device = createDevice(EDT_OPENGL, dimension2di(800,600), 32);
ISceneManager* smgr = device->getSceneManager();
IVideoDriver* driver = device->getVideoDriver();
Code: Select all
IMeshSceneNode* ground = smgr->addCubeSceneNode();
ground->setScale(vector3df(200,1,200));
smgr->getMeshManipulator()->makePlanarTextureMapping(ground->getMesh(),2);
ground->getMaterial(0).setTexture(0,driver->getTexture("media/rockwall.bmp"));
ground->getMaterial(0).Lighting = true;
Code: Select all
ICameraSceneNode* orthoCam = smgr->addCameraSceneNode();
orthoCam->setPosition(vector3df(300,400,0));
orthoCam->setTarget(vector3df(0,0,0));
matrix4 projMat;
projMat.buildProjectionMatrixOrthoLH(650,500,10,1000);
orthoCam->setProjectionMatrix(projMat);
orthoCam->setIsOrthogonal(true);
Code: Select all
IAnimatedMeshSceneNode* dwarf = smgr->addAnimatedMeshSceneNode(smgr->getMesh("media/dwarf.ms3d"));
dwarf->setPosition(vector3df(0,8,0));
dwarf->getMaterial(0).Lighting = false;
IOrthoControllerAnimator* orthoAnimator = new IOrthoControllerAnimator(smgr);
dwarf->addAnimator(orthoAnimator);
device->setEventReceiver(orthoAnimator);
Code: Select all
effectHandler* effect = new effectHandler(device,dimension2di(1024,1024));
effect->setShadowDarkness(1.0f);
Code: Select all
effect->addShadowToNode(dwarf, EFT_8PCF, ESM_BOTH);
effect->addShadowToNode(ground, EFT_8PCF, ESM_RECEIVE);
Here we will generate another orthographic projection matrix and apply it to light's camera. When a light is set up in an orthographic manner, it behaves as a directional light source, similar to that of the sun. This makes it great for outdoor scenes. Note that we are setting the width and height here a little bigger than last time, to make sure that we are capturing everything within the viewport. Depending on how tall the objects in your levels are, you may have to increase these values. We also set the maximum shadow distance from the light to 1. This is because when using orthographic projection, the depth values behave slightly differently. Make sure that the near and far value of your projection matrix enclose the visible area as tightly as possible, to get the best depth resolution:
Code: Select all
effect->setMaxShadowDistanceFromLight(1.0f);
projMat.buildProjectionMatrixOrthoLH(800,850,10,600);
effect->getLightCamera()->setProjectionMatrix(projMat);

Code: Select all
for(u32 i = 0;i < 3;++i)
{
IMeshSceneNode* house = smgr->addMeshSceneNode(smgr->getMesh("media/house.3ds"));
house->setScale(vector3df(0.2f,0.2f,0.2f));
house->setPosition(vector3df(200 * i,0,-200));
house->setRotation(vector3df(0,90,0));
house->getMaterial(0).setTexture(0,driver->getTexture("media/fw12b.jpg"));
house->setAutomaticCulling(EAC_FRUSTUM_BOX);
house->getMaterial(0).Lighting = false;
effect->addShadowToNode(house,EFT_8PCF,ESM_BOTH);
}
for(u32 i = 0;i < 3;++i)
{
IMeshSceneNode* house = smgr->addMeshSceneNode(smgr->getMesh("media/house.3ds"));
house->setScale(vector3df(0.2f,0.2f,0.2f));
house->setPosition(vector3df(200,0,200 * i));
house->getMaterial(0).setTexture(0,driver->getTexture("media/fw12b.jpg"));
house->setAutomaticCulling(EAC_FRUSTUM_BOX);
house->getMaterial(0).Lighting = false;
effect->addShadowToNode(house,EFT_8PCF,ESM_BOTH);
}
Code: Select all
int lastFPS = 0;
while(device->run())
{
driver->beginScene(true,true,SColor(0,0,0,0));
if(driver->getFPS() != lastFPS)
{
stringw title = "Orthographic shadow test FPS: ";
title += driver->getFPS();
device->setWindowCaption(title.c_str());
}
Code: Select all
orthoCam->setPosition(dwarf->getPosition() + vector3df(200,400,0));
orthoCam->setTarget(dwarf->getPosition());
effect->setLightPosition(dwarf->getPosition() + vector3df(-200,400,0));
effect->setLightTarget(dwarf->getPosition());
effect->update();
driver->endScene();
}
Code: Select all
device->drop();
device = NULL;
return 0;
}

A few nice things to note about this screenshot:
- The quality is consistant throughout, the shadow camera follows the view where ever it goes, and the shadowmap is uniformally distributed because of the orthographic projection.
- The chimneys cast soft shadows on the roofs of the houses.
- The dwarf is partly shadowed by the house in front of him.
- The dwarf and house shadows blend seamlessly together to form a single shadow, the dwarf isn't unrealistically casting a shadow of its own when it's already inside a house shadow.
About the character controller:
When the user clicks on a position on the screen, the intersection is found between the screen ray and the ground plane. The dwarf then orients itself towards the direction of the point where the user clicked, and runs there until he is a certain distance away. The animation frames specified are specific to the dwarf, so be sure to set these to something else if you plan to use the character controller in your own projects with a different model. Regarding that, the IOrthoControllerAnimator is free to use, no restrictions.
Here is an archive containing the demo binary/media and full sources:
http://irrlichtirc.g0dsoft.com/BlindSid ... Shadow.zip
(Remember: To move the dwarf just click somewhere and he will run there.)
That about settles it for today, have fun.
Cheers