Orthogonal Style Shadow Maps

A forum to store posts deemed exceptionally wise and useful
Post Reply
BlindSide
Admin
Posts: 2821
Joined: Thu Dec 08, 2005 9:09 am
Location: NZ!

Orthogonal Style Shadow Maps

Post by BlindSide »

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:

Code: Select all

#include <irrlicht.h>
#include "effectWrapper.h"
#include "IOrthoControllerAnimator.h"
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:

Code: Select all

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
Int main, and Irrlicht crew:

Code: Select all

int main()
{
	IrrlichtDevice* device = createDevice(EDT_OPENGL, dimension2di(800,600), 32);
	ISceneManager* smgr = device->getSceneManager();
	IVideoDriver* driver = device->getVideoDriver();
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:

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;
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:

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);
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:

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);
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:

Code: Select all

	effectHandler* effect = new effectHandler(device,dimension2di(1024,1024));
	effect->setShadowDarkness(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:

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);
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 :P . 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:

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);
}
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:

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());
		}
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:

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();
	}
Last but not least, we drop the device and return 0:

Code: Select all

	device->drop();
	device = NULL;

	return 0;
}
End result:

Image
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
ShadowMapping for Irrlicht!: Get it here
Need help? Come on the IRC!: #irrlicht on irc://irc.freenode.net
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

u da man, again.

i'll check it out later.
Image
Virion
Competition winner
Posts: 2148
Joined: Mon Dec 18, 2006 5:04 am

Post by Virion »

this is cool. this can be a small rpg demo for irrlicht.
Muhaha
Posts: 17
Joined: Wed Feb 13, 2008 10:07 am

Post by Muhaha »

hello,
i'm trying to compile but i have this errors http://pastebin.ca/1064564
BlindSide
Admin
Posts: 2821
Joined: Thu Dec 08, 2005 9:09 am
Location: NZ!

Post by BlindSide »

Interesting, can you tell me what compiler you are using?

You seem to be using an obsolete Irrlicht version I think, try updating to the latest SVN trunk, and tell me if you still get those errors. I was sure that CubeSceneNodes were made MeshSceneNodes a long time ago, but maybe they didn't make it into the bug fixing branch revision.

Cheers
ShadowMapping for Irrlicht!: Get it here
Need help? Come on the IRC!: #irrlicht on irc://irc.freenode.net
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Yes, such changes usually don't go into the 1.4 branch. It's only in SVN trunk.
Muhaha
Posts: 17
Joined: Wed Feb 13, 2008 10:07 am

Post by Muhaha »

BlindSide wrote:Interesting, can you tell me what compiler you are using?

You seem to be using an obsolete Irrlicht version I think, try updating to the latest SVN trunk, and tell me if you still get those errors. I was sure that CubeSceneNodes were made MeshSceneNodes a long time ago, but maybe they didn't make it into the bug fixing branch revision.

Cheers
the compiler is visual studio 2005 and the irricht version is 1.4.1 from http://irrlicht.sourceforge.net/downloads.html
with devc++ i have same errors,

tomorrow i try to download the latest svn.
gbox
Posts: 37
Joined: Mon May 01, 2006 3:41 am
Location: jeonju, korea
Contact:

Post by gbox »

cool~ :D
is 1.4.1 patch version? or orignal version?
http://cafe.naver.com/jcga

professor of Jelabukdo Game Engine Academy
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

1.4.1 is an official release. However, it differs from the latest development version found in SVN trunk/, even though also 1.4.1 can be found in SVN under branches/releases/1.4
The 1.4 branch is a bug-fixing maintenance branch, new features usually only go into the trunk development version, which will become Irrlicht version 1.5 later on.
andres
Competition winner
Posts: 78
Joined: Tue Jul 08, 2008 5:18 pm
Location: Guarapuava/Brazil
Contact:

Post by andres »

i have same errors than Muhaha. :(
visual c++ 2008 and irrlicht 1.4.1
ps: more errors when i try to compile with devcpp.
Muhaha
Posts: 17
Joined: Wed Feb 13, 2008 10:07 am

Post by Muhaha »

thanks now works,
i compile it with svn trunk revision 1412
andres
Competition winner
Posts: 78
Joined: Tue Jul 08, 2008 5:18 pm
Location: Guarapuava/Brazil
Contact:

Post by andres »

Muhaha wrote:thanks now works,
i compile it with svn trunk revision 1412
i'm trying to compile svn trunk 1412 here but i have some errors. can you explain me how to do this?
Prof. Andres Jessé Porfirio
Federal Technological University of Parana (UTFPR)
www.andresjesse.com
http://irrrpgbuilder.sourceforge.net

Image
Muhaha
Posts: 17
Joined: Wed Feb 13, 2008 10:07 am

Post by Muhaha »

nothing special,
i only downloaded the svn and compiled it, whitout error :D
torleif
Posts: 188
Joined: Mon Jun 30, 2008 4:53 am

Post by torleif »

To get this working in 1.4.2, replace DefineMap[driver->getVendorInfo()] = ""; with DefineMap[driver->getName()] = ""; in CShaderPre.h
MasterGod
Posts: 2061
Joined: Fri May 25, 2007 8:06 pm
Location: Israel
Contact:

Post by MasterGod »

Nice work, Good job.
Image
Dev State: Abandoned (For now..)
Requirements Analysis Doc: ~87%
UML: ~0.5%
Post Reply