Shadow class derived from SceneObject
From TDN
Shadow class from SceneObject, how it works
This data is in relation to this resource
but is relevant to the standard shadow creation in TGE as well.
To create a shadow of an object, you first need to create a new shadow instance.
Shadow *mShadow = new Shadow();
but you can just use the mShadow pointer defined in SceneObject.h for this purpose.
mShadow = new Shadow();
The initShadow method
After creating the object, you need to initialize it by calling initShadow and passing 2 parameters, the 1st being the address of the shadow casting object which will own the shadow and the 2nd being the shapeInstance pointer of the shadow casting object.
It sounds complex but every object which exists in the game is part of the “scene†and is therefore a sceneObject. Every sceneObject which has a mesh, like players or vehicles will have a pointer to that mesh class which is known as the shapeInstance.
Logical deduction will show that to have a shadow, you need a sceneobject to “attach†the shadow to and that sceneObject needs a mesh (shapeInstance) which will block a portion of light and cast a shadow.
SceneObjects such as triggers don't have a shapeInstance pointer because they don't have a mesh and therefore can't cast shadows in theory. The initShadow method therefore checks the validity of the above parameters and also does some basic graphic card checks. We don't do it in the shadow constructor because the above two pointers are not available the 1st time TGE creates the shadow object at game start.
Thus far we have the following:
mShadow = new Shadow(); bool result = mShadow->initShadow(this, mShapeInstance);
Calling the registerObject() method
Next we need to register the shadow as a sceneObject itself so that the sceneGraph can determine whether or not it's visible on the screen. The client scenegraph is the code responsible for checking where you camera is in the game, then checking what part of the game is visible to your camera and from that it can determine what game objects should be drawn on your screen. To do that it checks if your sceneObject's object-box falls within that section of the game which is visible to the camera, also called the camera's view-frustum.
The shadow is given its own object-box but it is a client side box, meaning the server does not know about or need to care about looking after the shadow. It's the clients problem.
So now we have the following
mShadow = new Shadow();
bool result = mShadow->initShadow(this, mShapeInstance);
if(!(result && mShadow->registerObject())
{
Con::warnf( ConsoleLogEntry::General, "ShapeBase::initShadows() - Could not register shadow " );
delete mShadow;
mShadow = NULL;
mGenerateShadow=false;
return false;
}
The Shadow::onAdd() event
For those of you who don't know, when we call mShadow->registerObject() the shadow object's ::onAdd method is called, so we will look at that method next.
In bool Shadow::onAdd(), we need to create a default object box for the shadow. For a start, we just make a box of 5m square and position it at the location of the sceneObject which is going to own this shadow. We use the mOwner pointer which was setup in the initShadow method described earlier.
mObjBox.max.set(5,5,5); mObjBox.min.set(-5,-5,-5); resetWorldBox(); setTransform(mOwner->getRenderTransform());
Next we check what type of object the mOwner is and set up some default flags accordingly. Finally we register the shadow as a client side sceneObject by calling the following.
gClientContainer.addObject(this); gClientSceneGraph->addObjectToScene(this); return true;
A quick look at SceneGraph
Ok, thats all we can do for now. It's out of our hands because the shadow will now only be drawn if scenegraph decides the shadow object-box falls within the camera view-frustum. So if you are chasing missing shadows, the above object-box position is the the first thing you should check. If you got the position wrong, no shadow will be drawn where you expect it to be.
So lets step over to scengraph for a minute and see what it does. TGE performs its actions in a specific sequence, i.e, it checks for keyboard events, network events, moves objects in the scene, draws objects and so on, not necessarily in that order though. Which means sceneGraph gets a slice of the game cycle found in main.cc.
During that time-slice, it goes though the complete list of game objects and calls that objects prepRenderImage method which will give that object a chance to insert itself in the next sceneState or “frame†drawn on the screen. It's not drawn automatically because that object may have its own reasons for not wanting to be drawn.
Calling prepRenderImage therefore alerts the object as in “hey! do you want to be drawn on screen or not?â€
Calling Shadow::prepRenderImage
So in our Shadow::prepRenderImage method we want to check first of all if we fall within the camera view-frustum and we do that by calling
state->isObjectRendered(this)
which will return true if our object-box is visible to the camera.
Next we can check some distance values, are we too far from the camera to bother, are we obscured by another object etc. You can add any checks you want at this point and return if you don't want the shadow drawn.
If you do want the shadow drawn then you need to do
SceneRenderImage* image = new SceneRenderImage; image->obj = this; image->isTranslucent = true; image->sortType = SceneRenderImage::BeginSort; state->insertRenderImage(image);
What happens next? Well if you DID call state->insertRenderImage(image); then sceneState will call Shadow::renderObject when the next frame is due to be drawn.
So next we will look at the Shadow::renderObject method.
Calling Shadow::renderObject
1st we setup opengl and obtain a distance and fog amount for later use. 2nd we call the prepare method and if successful we go ahead and render the shadow. Prior to rendering we may have needed to create a shadow bitmap, depending on what prepare() determined was necessary.
So what is in prepare()?
1st we call updateLightInfo(); which gets/checks the current sun direction and ambient light values. We also use the light vector to create a more accurate object-box, done in the moveSceneBox(); method. The reason we use a castray method to determine the shadow length is explained in detail in this thread. http://www.garagegames.com/mg/forums/result.thread.php?qt=37971
To summarize the relevant portion of that thread, all objects which fall within the shadow's world box volume will be asked to create a polylist of the overlapped area, which takes time. The more accurate the object-box, hence the world-box, the more efficient/faster the shadow production will be.
Back to the Shadow::prepare() method.
The next to happen is some basic performance checks used to determine which shadow production method to use.
After that, we call findPixelSizeDetail(pixelSize,&psd); which will select the correct bitmap size to use based on distance from camera.
The buildPartition() method
Then we call buildPartition(); which basically defines a volume which is likely to contain objects upon which a shadow must be cast. The original TGE method defined a HUGE space which caused many unnecessary buildpolly calls, repeated once for each shadow casting object etc. It was a mess. In this new shadow class, we use the shadows world-box which is based on the VERY accurate object-box created for the shadow using the castray method.
The above volume is searched and all objects which it overlaps have to produce a list of polygons which fall within the above volume. This poly-list is then used as co-ordinates onto which the shadow bitmap is mapped. Simple in concept... To improve performance, various checks have been put in place to reduce the amount of searches. Example, a TStatic does not move so we only need to search the volume once per mission which is a tremendous performance boost.
But, if you REALLY want that tree shadow to fall on your vehicles as it drives under some trees, then you will have to make a small mod to this area to allow the TSstatic volume search to happen on every frame, and pay for it in a performance drop. An idea would be to create a trigger of exact dimensions to the shadow object box and link it to the buildpartion method. Then you can set it up so that if a new object enters the trigger, then whilst in the trigger, the builpartion searches on every frame. Remember we are talking about a static tree here. ShapeBase objects will do a partition search on every frame by default. After we build and search a partition, we do some checks to determine if a new shadow bitmap is required or if the old one is still valid.
The prepare() method is now done and if it returned true, then a shadow bitmap might need to be created before we can actually render a shadow. The old shadow class only used a software rasterizer for shadow bitmap creation. The new shadow class still retains that method but adds in some new bitmap creation methods.
Using the new methods
The concept is simple, render the object to a bitmap, as seen from the sun's(or other light source) viewpoint,
then use that bitmap as a shadow texture and map it onto the poly-list.
Pbuffers and Aux buffers
Most of the newer nVidia cards have whats known as Aux buffers. It's an axillary hardware color buffer used for writing off-screen data which you do not want to immediately see on screen. It was created for special effects use and in the new shadow class, we also use it as a convenient place to render the shadow object prior to processing.
Pbuffers (pixel buffers) are an older concept which works on most video cards, even as far back as the Rivia TNT. They are also supported by ATI drivers. They work by having the driver software create/allocate a portion of computer memory for use as a video buffer. The graphic card hardware writes to this buffer in place of the frame buffer and you still get the advantage of hardware acceleration. The disadvantage is that the context switching from frame buffer to pbuffer is rather slow, much slower than Aux buffers. On older computers, the memory itself might be slow compared to video memory.
The pBuffer.cc file included in this resource is very robust as is the manner in which the shadow.cc file implements it.
FBO
As of version 8, this resource fully implements "render to texture" and the texture is then used directly as the shadow texture. No more glReadPixels which was a much slower approach. Version 8 also added FBO support (Frame Buffer Objects) which are currently the fastest Opengl method of rendering straight to a texture and unlike pBuffers, they are supported on Mac and Linux as well.
In the new shadow render method, we therefore 1st try and use FBO, failing that we try pbuffers and then AUX buffers then stencil buffer and finally the software rasterizer.
The main attraction of using the above method is that the buffers have alpha channels which allow you to get leaf details in tree shadows. Semi-transparent objects such as windows, vehicle canopies, fairy wings etc will now all cast realistic shadows with this approach.
Have a look at the vehicle body below, the windows are tinted but the shadow correctly shows them as semi-transparent.
The reason the shadow is clipped front and back is because the object box (shown as a red box) is much too small for the vehicle. The object box is created using the bounds box which in this case, was made too small by the designer.
The createAdvancedStencilShadow method
This method uses the graphic card's stencil buffer as a off screen render area for shadow casting objects. It's fast but it has no alpha channel which means that semi-transparent objects such as tinted windows will cast a black or solid shadow rather than a semi-transparent shadow as the FBO, pbuffer or aux buffer shadow would do.
Multiple Light Sources
Some people have modified the old shadow class to cast shadows from multiple light sources. This could be useful when your game is based mainly on interiors and you have many light sources in and around your buildings.
This new shadow class currently only uses the sun as a light source, even if you are indoors. This was done because that was all my game required.
If you want to add additional light sources you would need to modify the updateLightInfo() method so that you could specify which light source to use for this shadow at this particular moment in time.
You would ideally need to create a shadow manager class which attaches to the shadow casting object (the owner) and scans the surroundings for the 3 strongest or closest light sources. The shadow manager would then need to create 3 shadow instances and manage them by constantly providing the updateLightInfo(); method with the correct light source for that shadow instance. Of course you could use more than 3 shadows at any one time, depending on how many other shadow casting objects are on screen etc. It just requires a good shadow manager.
Duncan Gray




