Intermediate Tutorial 6

From PyWiki

Jump to: navigation, search

Contents

[edit] Intermediate Tutorial 6: Projective Decals

Note: the following tutorial is unfortunately still a C++ tutorial, it has not yet been updated for python-ogre. A working python-version of the code has been included for further study.

[edit] 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:

http://www.ogre3d.org/wiki/images/7/79/Decal_shot.png

[edit] Getting started

[edit] New Textures

Before we get started on this project, we need to add two new images we will be using. Right click on the following two links and save them somewhere that Ogre can find them: http://www.ogre3d.org/wiki/images/1/19/Decal.png http://www.ogre3d.org/wiki/images/4/44/Decal_filter.png

The best place to put this is in the media/materials/textures folder (for most people this should be located in the OgreSDK folder).

[edit] Initial Code

Create a cpp file in the IDE of your choice and add the following code to it:

http://www.ogre3d.org/wiki/index.php/Intermediate_Tutorial_6

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

[edit] Projecting Decals

[edit] Frustums

A frustum represents a pyramid capped at a the near and far end, 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 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 do not do so now):

      // Do not add this to the project
      mDecalFrustum->setProjectionType(PT_ORTHOGRAPHIC);
      mDecalFrustum->setNearClipDistance(25);

By setting an orthographic frustum's field of view, aspect ratio, and near clip distance you will determine the size and shape of the decal which will be constant regardless of how far away the projector is.

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.

[edit] 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 and create a new pass for the material. Find the makeMaterialReceiveDecal and add the following code:

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

Now that we have created our pass, we need to setup 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(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 setup the filtering and addressing modes:

      TextureUnitState *texState = pass->createTextureUnitState("decal.png");
      texState->setProjectiveTexturing(true, mDecalFrustum);
      texState->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
      texState->setTextureFiltering(FO_POINT, FO_LINEAR, 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) doesn't get blurred into 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 setup the material.

[edit] Calling the Functions

Now that we have built the functions, we actually need to call them to setup 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.

[edit] Getting Rid of the Back Projection

[edit] 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.

[edit] 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 Frustum();
      mFilterFrustum->setProjectionType(PT_ORTHOGRAPHIC);
      SceneNode *filterNode = mProjectorNode->createChildSceneNode("DecalFilterNode");
      filterNode->attachObject(mFilterFrustum);
      filterNode->setOrientation(Quaternion(Degree(90),Vector3::UNIT_Y));

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

[edit] 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(TextureUnitState::TAM_CLAMP);
      texState->setTextureFiltering(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.

[edit] Showing Off the Projection

[edit] Simple Rotation

To show off the projection, we will rotate the projector and update the Field of View. To rotate the projector, simply add the following line of code to the frameStarted method:

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

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

[edit] Modifying the Field of View

The next thing we will do is modify the projection's field of view. Since we are not using an orthographic projector, we can modify the field of view to increase or decrease the size of the object we are projecting. To demonstrate this, we will set the FOVy (field of view Y) to be an angle between 15 and 25 degrees. The following code will increase and decrease the size of the decal (add this to the frameStarted method):

      mAnim += evt.timeSinceLastFrame / 2;
      if (mAnim >= 1)
          mAnim -= 1;
      mDecalFrustum->setFOVy(Degree(15 + Math::Sin(mAnim * Math::TWO_PI) * 10));

Compile and run the application.

[edit] 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.


[edit] Python version of the code

#!/usr/bin/env python 
# This code is Public Domain. 
"""Python-Ogre Intermediate Tutorial 06: Initial code """
 
import ogre.renderer.OGRE as ogre 
import SampleFramework as sf
import math
 
class ProjectiveDecalListener(sf.FrameListener):
   def __init__(self, win, cam, proj, decal):
 
      sf.FrameListener.__init__(self, win, cam, True, True)
 
      self.projectorNode = proj
      self.decalFrustum = decal
      self.anim = 0.0
 
   def frameStarted(self, evt):
      return sf.FrameListener.frameStarted(self, evt)
 
class TutorialApplication(sf.Application): 
   """Application class.""" 
 
   def _createScene(self):
      self.decalFrustum = ogre.Frustum()
      self.projectorNode = self.sceneManager.getRootSceneNode().createChildSceneNode("DecalProjectorNode")
      self.sceneManager.setAmbientLight((0.2, 0.2, 0.2))
 
      l = self.sceneManager.createLight("MainLight")
      l.setPosition(20, 80, 50)
 
      self.camera.setPosition(60, 200, 70)
      self.camera.lookAt(0, 0, 0)
 
      # 6 ogre heads, arranged in a circle
      for i in range(0, 6):
         headNode = self.sceneManager.getRootSceneNode().createChildSceneNode()
         ent = self.sceneManager.createEntity("head" + str(i), "ogrehead.mesh")
         headNode.attachObject(ent)
         angle = i * 2 * math.pi / 6
         headNode.setPosition(75 * math.cos(angle), 0, 75 * math.sin(angle))
 
   def createProjector(self):
      pass
 
   def makeMaterialReceiveDecal(self, matName):
      pass
 
   def _createFrameListener(self):
      self.frameListener = ProjectiveDecalListener(self.renderWindow, self.camera, self.projectorNode, self.decalFrustum)
      self.root.addFrameListener(self.frameListener)
      self.frameListener.showDebugOverlay(True)
 
if __name__ == '__main__': 
   ta = TutorialApplication() 
   ta.go()
#!/usr/bin/env python 
# This code is Public Domain. 
"""Python-Ogre Intermediate Tutorial 06: Final code """
 
import ogre.renderer.OGRE as ogre 
import SampleFramework as sf
import math
 
class ProjectiveDecalListener(sf.FrameListener):
   def __init__(self, win, cam, proj, decal):
 
      sf.FrameListener.__init__(self, win, cam)
 
      self.projectorNode = proj
      self.decalFrustum = decal
      self.anim = 0.0
 
   def frameStarted(self, evt):
      self.projectorNode.rotate(ogre.Vector3.UNIT_Y, ogre.Degree(evt.timeSinceLastFrame * 10))
      self.anim += evt.timeSinceLastFrame / 2
      if self.anim >= 1:
         self.anim -= 1
      self.decalFrustum.setFOVy(ogre.Degree(15 + math.sin(self.anim * 2 * math.pi) * 10))
      return sf.FrameListener.frameStarted(self, evt)
 
class TutorialApplication(sf.Application): 
   """Application class.""" 
 
   def _createScene(self):
      self.decalFrustum = ogre.Frustum()
      self.projectorNode = self.sceneManager.getRootSceneNode().createChildSceneNode("DecalProjectorNode")
 
      self.sceneManager.setAmbientLight((0.2, 0.2, 0.2))
 
      l = self.sceneManager.createLight("MainLight")
      l.setPosition(20, 80, 50)
 
      self.camera.setPosition(60, 200, 70)
      self.camera.lookAt(0, 0, 0)
 
      # 6 ogre heads, arranged in a circle
      for i in range(0, 6):
         headNode = self.sceneManager.getRootSceneNode().createChildSceneNode()
         ent = self.sceneManager.createEntity("head" + str(i), "ogrehead.mesh")
         headNode.attachObject(ent)
         angle = i * 2 * math.pi / 6
         headNode.setPosition(75 * math.cos(angle), 0, 75 * math.sin(angle))
 
      self.createProjector()
      for i in range(0, ent.getNumSubEntities()):
         self.makeMaterialReceiveDecal(ent.getSubEntity(i).getMaterialName())
 
   def createProjector(self):
      self.projectorNode.attachObject(self.decalFrustum)
      self.projectorNode.setPosition(0, 5, 0)
 
      self.filterFrustum = ogre.Frustum()
      self.filterFrustum.setProjectionType(ogre.PT_ORTHOGRAPHIC)
      filterNode = self.projectorNode.createChildSceneNode("DecalFilterNode")
      filterNode.attachObject(self.filterFrustum)
      filterNode.setOrientation(ogre.Quaternion(ogre.Degree(90), ogre.Vector3.UNIT_Y))
 
   def makeMaterialReceiveDecal(self, matName):
      mat = ogre.MaterialManager.getSingleton().getByName(matName)
      mPass = mat.getTechnique(0).createPass()
 
      mPass.setSceneBlending(ogre.SBT_TRANSPARENT_ALPHA)
      mPass.setDepthBias(1)
      mPass.setLightingEnabled(False)
 
      texState = mPass.createTextureUnitState("decal.png")
      texState.setProjectiveTexturing(True, self.decalFrustum)
      texState.setTextureAddressingMode(ogre.TextureUnitState.TAM_CLAMP)
      texState.setTextureFiltering(ogre.FO_POINT, ogre.FO_LINEAR, ogre.FO_NONE)
 
      texState = mPass.createTextureUnitState("decal_filter.png")
      texState.setProjectiveTexturing(True, self.filterFrustum)
      texState.setTextureAddressingMode(ogre.TextureUnitState.TAM_CLAMP)
      texState.setTextureFiltering(ogre.TFO_NONE)
 
   def _createFrameListener(self):
      self.frameListener = ProjectiveDecalListener(self.renderWindow, self.camera, self.projectorNode, self.decalFrustum)
      self.root.addFrameListener(self.frameListener)
      self.frameListener.showDebugOverlay(True)
 
if __name__ == '__main__': 
   ta = TutorialApplication() 
   ta.go()
Python-Ogre Tutorials

Python-Ogre Beginner Tutorials: Beginner 1 - Beginner 2 - Beginner 3 - Beginner 4 - Beginner 5 - Beginner 6 - Beginner 7 - Beginner 8

Intermediate Tutorials: Intermediate 1 - Intermediate 2 - Intermediate 3 - Intermediate 4 - Intermediate 5 - Intermediate 6

Advanced Tutorials: Advanced 1

See also: Artist Tutorials - Ogre Articles - Cookbook

Personal tools