Intermediate Tutorial 6         Projective Decals
Print

Projective Decals

 

Any problems you encounter during working with this tutorial should be posted in the Help Forum(external link).

 

Introduction

In this tutorial we will be covering how to add projective decals to an object in the scene. Projective texturing is useful when you want to do something like a selection indicator on the ground, an aiming sight that's projected on what you're aiming at, or some other type of decal that's projected onto something (but doesn't become a permanent part of the target like splatting). Here's a screenshot of an aiming site being projected onto everyone's favorite ogre head:

Decal_shot.png

You can find the code for this tutorial here. As you go through the tutorial you should be slowly adding code to your own project and watching the results as we build it.

Getting Started

New Textures

Before we get started on this project, we need to add two new images we will be using:
Decal.png
Decal_filter.png

The best place to put these files is in the media/materials/textures folder (for most people this should be located in the OgreSDK folder). Also note the character case by making sure the file names are all lower case as they are referenced as such in the tutorial.

The Initial Code

Set up your Intermediate Tutorial 6 project to look something like this:

IntermediateTutorial6.h

#ifndef __IntermediateTutorial6_h_
#define __IntermediateTutorial6_h_
 
#include "BaseApplication.h"
 
class IntermediateTutorial6 : public BaseApplication
{
public:
    IntermediateTutorial6(void);
    virtual ~IntermediateTutorial6(void);
 
protected:
    virtual void createScene(void);
	virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
	virtual void createProjector();
	virtual void makeMaterialReceiveDecal(const Ogre::String& matName);
 
	Ogre::SceneNode* mProjectorNode;
	Ogre::Frustum* mDecalFrustum;
	Ogre::Frustum* mFilterFrustum;
	float mAnim;
};
 
#endif // #ifndef __IntermediateTutorial6_h_

 
IntermediateTutorial6.cpp

#include "IntermediateTutorial6.h"
 
//-------------------------------------------------------------------------------------
IntermediateTutorial6::IntermediateTutorial6(void)
{
}
//-------------------------------------------------------------------------------------
IntermediateTutorial6::~IntermediateTutorial6(void)
{
}
 
//-------------------------------------------------------------------------------------
void IntermediateTutorial6::createScene(void)
{
	mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2f, 0.2f, 0.2f));
 
	Ogre::Light* light = mSceneMgr->createLight("MainLight");
	light->setPosition(20, 80, 50);
 
	mCamera->setPosition(60, 200, 70);
	mCamera->lookAt(0,0,0);
 
	Ogre::Entity* ent;
	for (int i = 0; i < 6; i++)
	{
		Ogre::SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
		ent = mSceneMgr->createEntity("head" + Ogre::StringConverter::toString(i), "ogrehead.mesh");
		headNode->attachObject(ent);
 
		Ogre::Radian angle(i + Ogre::Math::TWO_PI / 6);
		headNode->setPosition(75 * Ogre::Math::Cos(angle), 0, 75 * Ogre::Math::Sin(angle));
	}
}
 
bool IntermediateTutorial6::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
	return BaseApplication::frameRenderingQueued(evt);
}
 
void IntermediateTutorial6::createProjector()
{
}
 
void IntermediateTutorial6::makeMaterialReceiveDecal(const Ogre::String& matName)
{
}
 
 
 
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
 
#ifdef __cplusplus
extern "C" {
#endif
 
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
    INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
    int main(int argc, char *argv[])
#endif
    {
        // Create application object
        IntermediateTutorial6 app;
 
        try {
            app.go();
        } catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
            MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
            std::cerr << "An exception has occured: " <<
                e.getFullDescription().c_str() << std::endl;
#endif
        }
 
        return 0;
    }
 
#ifdef __cplusplus
}
#endif

 
Compile and run this program before continuing. You should see six Ogre heads.

Projecting Decals

Frustums

A frustum represents a pyramid capped at the near and far ends, which represents a visible area or a projection. Ogre uses this to represent cameras with (the Camera class derives directly from the Frustum class). In this tutorial, we will be using a frustum to project the decal onto the meshes in the scene.

The first thing we will do to create the projector is to create the frustum which represents it and attach it to a SceneNode. Find the createProjector() method and add the following code:

mDecalFrustum = new Ogre::Frustum();
mProjectorNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("DecalProjectorNode");
mProjectorNode->attachObject(mDecalFrustum);
mProjectorNode->setPosition(0,5,0);

This creates a projector which will grow the decal as you get farther and farther away from it, a lot like how a film projector works. If you want to create a projector which maintains a constant size and shape of decal at whatever distance we set, you should add the following code (but we do not for this tutorial):

// Do not add this to the project
mDecalFrustum->setProjectionType(Ogre::PT_ORTHOGRAPHIC);
mDecalFrustum->setOrthoWindowHeight(100);

setOrthoWindowHeight() is used together with aspect ratio to set the size of an orthographic frustum.

Before continuing, please take note of where our frustum is projecting the decal. In this application there is a ring of Ogre heads and the frustum sits in the dead center of them (though shifted up slightly, by 5 units), pointed in the -Z direction (which is the default since we did not change the orientation). This means that, eventually, when we run the application decals will be projected onto the back Ogre heads.

Modifying the Material

In order for the decal to actually show up on an object, the material that it uses has to receive the decal. We do this by creating a new pass which renders the decal on top of the regular texture. The frustum determines the location, size, and shape of the projected decal. In this demo we will be modifying the material itself to receive the decal, but for most real applications, you should probably create a clone of the material to modify so you can switch it off by setting the material back to the original one.

The first thing we will do is get the material and create a new pass for the material. Find makeMaterialReceiveDecal() and add the following code:

Ogre::MaterialPtr mat = (Ogre::MaterialPtr)Ogre::MaterialManager::getSingleton().getByName(matName);
Ogre::Pass *pass = mat->getTechnique(0)->createPass();

Now that we have created our pass, we need to set up blending and lighting. We will be adding a new texture which must be blended properly with the current texture already on the object. To do this we will set the scene blending to be transparent alpha, and the depth bias to be 1 (so that there is no transparency in the decal). Lastly we need to disable lighting for the material so that it always shows up no matter what the lighting of the scene is. If you want the decal in your application to be affected by the scene lighting you should not add that last function call:

pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
pass->setDepthBias(1);
pass->setLightingEnabled(false);

Now that we have our new pass we need to create a new texture unit state using our decal.png image. The second function call turns on projective texturing and takes in the frustum we have created. The final two calls set up the filtering and addressing modes:

Ogre::TextureUnitState *texState = pass->createTextureUnitState("decal.png");
texState->setProjectiveTexturing(true, mDecalFrustum);
texState->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
texState->setTextureFiltering(Ogre::FO_POINT, Ogre::FO_LINEAR, Ogre::FO_NONE);

We have set the texture addressing mode to clamp so that the decal doesn't "loop" itself on the object. For the filtering options, we have set the magnification of the object to use standard linear, but we have basically turned off filtering for minification (FO_POINT), and turned off mipmapping entirely. This prevents the border of the decal (which is transparent) from getting blurred into the rest of the texture when it is minimized. If we do not do that, there will be ugly smearing all over the outside of the place the decal is projected.

This is all you need to do to set up the material.

Calling the Functions

Now that we have built the functions, we need to call them to set up the projector and the material. Add the following code to the end of the createScene method:

createProjector();
for (unsigned int i = 0; i < ent->getNumSubEntities(); i++)
{
        makeMaterialReceiveDecal(ent->getSubEntity(i)->getMaterialName());
}

Note that the ent variable already has one of the ogre head entities stored in it from the previous loop. Since all the ogre heads use the same material, we only need to select a random one of them to grab the material names from.

Compile and run the application, you should see a few Ogre heads with a decal projected onto them.

Getting Rid of the Back Projection

Introduction

As you have probably noticed when running the application, there are actually two decals being projected. The first is projected in the -Z direction, which is where our frustum is facing, the other is projected in the +Z direction, onto the ogre heads behind the frustum we have created. The reason for this is when a decal is projected out of the front of the frustum, a corresponding (inverted) decal is projected out of the back of it.

This is obviously not what we want. To fix it we will introduce a filter that will remove the back projection.

Modifying the Projector

To filter the back projection, we need a new frustum for the filter which points in the direction we wish to filter. Add the following code to the createProjector() method:

mFilterFrustum = new Ogre::Frustum();
mFilterFrustum->setProjectionType(Ogre::PT_ORTHOGRAPHIC);
Ogre::SceneNode *filterNode = mProjectorNode->createChildSceneNode("DecalFilterNode");
filterNode->attachObject(mFilterFrustum);
filterNode->setOrientation(Ogre::Quaternion(Ogre::Degree(90),Ogre::Vector3::UNIT_Y));

This should all be familiar. The only difference is that we have rotated the node by 90 degrees to face backwards.

Modifying the Material

Now we need to add another texture state to the pass we added on the material. Add the following to makeMaterialReceiveDecal:

texState = pass->createTextureUnitState("decal_filter.png");
texState->setProjectiveTexturing(true, mFilterFrustum);
texState->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
texState->setTextureFiltering(Ogre::TFO_NONE);

This all should look familiar. Note that we are using the filter texture, the filter frustum, and the we have turned off filtering. Compile and run the application. You should now see only the forward projection of the decals.

Showing Off the Projection

Simple Rotation

To show off the projection, we will rotate the projector. To rotate the projector, simply add the following line of code to the beginning of the frameRenderingQueued method:

mProjectorNode->rotate(Ogre::Vector3::UNIT_Y, Ogre::Degree(evt.timeSinceLastFrame * 10));

Compile and run the application. You will now see the decal projected along the circle of ogre heads.

One Final Note

One last thing to note about decals, if you use decals in your application, be sure that the outer border pixels of the decals are completely transparent (zero alpha). If not, the decal will smear due to the way texture clamping works.

Work around

The above notice is true, but there's a way around it if your texture doesn't already have transparent borders:
As long as your texture is in a format that supports alpha channels (such as PNG), you can set the Texture Address Mode to TAM_BORDER (or border, if you're doing this in a script) and set the Texture Border Colour to (0, 0, 0, 0). This causes any texture coordinates outside the range 0, 1 to have the border color you specify, which is black with 0 alpha. So essentially, you just added a transparent border to your texture.

You can find a quick implementation in Projective Decals.


Alias: Intermediate_Tutorial_6


Contributors to this page: Latin11 points  , Spacegaier3733 points  , purdyjo389 points  , kr12259 points  and jacmoe111451 points  .
Page last modified on Wednesday 24 of August, 2011 22:11:25 GMT by Latin11 points .


The content on this page is licensed under the terms of the Creative Commons Attribution-ShareAlike License.
As an exception, any source code contributed within the content is released into the Public Domain.