Ogre中级教程(六): 投影贴图

      中级教程六
        投影贴图 目录
              [隐藏]
                1 介绍
                2 准备开始
                3 起始代码
                4 投影贴图
                  4.1 平截头体(Frustums)
                  4.2 修改材质
                  4.3 调用函数
                5 消除反向投影
                  5.1 介绍
                  5.2 修改投影器
                  5.3 修改材质
                6 炫耀一下投影
                  6.1 简单的旋转
                  6.2 修改视线范围
                7 最后要注意的


        [编辑] 介绍
        在这一课里,我们将介绍如何为场景中的一个物体添加投影贴图。投影纹理是非常有用的,比如你想要一个类似地面上的选择指示器,或者你正在瞄准的瞄准镜,或者投射在某个物体上的其它贴图。下面的截图是一个大家都喜爱的ogre头像,它被投影了一个瞄准器:
        [[1]]
        你能在这里找到本课的代码。当你学习本课时,你应该逐个地往你的工程里添加代码,编译并观察结果。
        [编辑] 准备开始
        开始之前,我们需要两张新图。右击这两个链接,并保存到Ogre可以找到的地方:[decal.png] [decal_filter.png]
        最好放在media/materials/textures目录下(对于大多数人,是在OgreSDK目录里)。
        [编辑] 起始代码
        用你喜爱的IDE创建一个cpp文件,并添加如下代码:
   #include "ExampleApplication.h"
  
   // A FrameListener that gets passed our projector node and decal frustum so they can be animated
   class ProjectiveDecalListener : public ExampleFrameListener
   {
   public:
       ProjectiveDecalListener(RenderWindow* win, Camera* cam, SceneNode *proj, Frustum *decal)
           : ExampleFrameListener(win, cam), mProjectorNode(proj), mDecalFrustum(decal), mAnim(0)
       {
       }
  
       bool frameStarted(const FrameEvent& evt)
       {
           return ExampleFrameListener::frameStarted(evt);
       }
  
   protected:
       SceneNode *mProjectorNode;
       Frustum *mDecalFrustum;
       float mAnim;
   };
  
   class ProjectiveDecalApplication : public ExampleApplication
   {
   protected:
       SceneNode *mProjectorNode;
       Frustum *mDecalFrustum;
       Frustum *mFilterFrustum;
  
       void createScene()
       {
           // Set ambient light
           mSceneMgr->setAmbientLight(ColourValue(0.2, 0.2, 0.2));
  
           // Create a light
           Light* l = mSceneMgr->createLight("MainLight");
           l->setPosition(20,80,50);
  
           // Position the camera
           mCamera->setPosition(60, 200, 70);
           mCamera->lookAt(0,0,0);
  
           // Make 6 ogre heads (named head0, head1, etc.) arranged in a circle
           Entity *ent;
           for (int i = 0; i < 6; i++)
           {
               SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
               ent = mSceneMgr->createEntity("head" + StringConverter::toString(i), "ogrehead.mesh");
               headNode->attachObject(ent);
               Radian angle(i * Math::TWO_PI / 6);
               headNode->setPosition(75 * Math::Cos(angle), 0, 75 * Math::Sin(angle));
           }
       }
  
       // The function to create our decal projector
       void createProjector()
       {
       }
  
       // A function to take an existing material and make it receive the projected decal
       void makeMaterialReceiveDecal(const String &matName)
       {
       }
  
       // Create new frame listener
       void createFrameListener(void)
       {
           mFrameListener= new ProjectiveDecalListener(mWindow, mCamera, mProjectorNode, mDecalFrustum);
           mRoot->addFrameListener(mFrameListener);
       }
   };
  
   #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
           ProjectiveDecalApplication app;
  
           try {
               app.go();
           } catch(Exception& e) {
   #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
               MessageBoxA(NULL, e.getFullDescription().c_str(),
                   "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
   #else
               std::cerr << "An exception has occurred: " << e.getFullDescription();
   #endif
           }
  
           return 0;
       }
  
   #ifdef __cplusplus
   }
   #endif
编者并运行这个程序,你应该可以看见六个Ogre头颅。
        [编辑] 投影贴图
        [编辑] 平截头体(Frustums)
        一个平截头体(Frustums)表示一个头部被截取的棱椎,代表了一个可视区域或是一个投影。Ogre使用它来表示一个camera(Camera类直接继承了Frustum类)。在这一课里,我们将使用一个平截头体来把贴花投影到场景中的网格(mesh)上。

        首先我们要创建一个投影器(projector),即创建代表它的平截头体,并绑定到场景节点上。找到createProjector方法并添加以下代码:

      mDecalFrustum = new Frustum();
      mProjectorNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("DecalProjectorNode");
      mProjectorNode->attachObject(mDecalFrustum);
      mProjectorNode->setPosition(0,5,0);
这样一个投影器就创建好了,当你离得越远时,这个贴图会变得更大,就像电影机一样。若你要创建一个投影器,使得不论多远它总是保持恒定的大小和形状,你就要添加如下代码(但现在不要这样做):

      // 别把这段加到工程里
      mDecalFrustum->setProjectionType(PT_ORTHOGRAPHIC);
      mDecalFrustum->setNearClipDistance(25);
通过设置正射投影的视线范围、横纵比、近截取距离,你决定了这个帖图的大小和形状,使得无论投影器有多远,它总是保持恒定。
        继续之前,请先搞清楚我们的平截头体将要把贴图投影到的地点。在这个程序中,有一圈Ogre头颅,而这个平截头体处于它们的正中心(虽然微微向上抬起了5个单位),并指向-Z方向(由于我们没有改变朝向,是一个默认值)。这就意味着,最终我们运行程序时,贴图将投向后面的Ogre人头。

        [编辑] 修改材质
        为了让贴图最终显示在物体上,它使用的材质必须能够接收贴图。为此我们创建一个新的通路,在常规纹理上面渲染贴图。这个平截头体决定了这个投影贴图的位置、大小和形状。在这个Demo里面,我们将直接修改这个材质来接收贴图,但在实际的应用中,你应该创建这个材质的一个拷贝,再修改它,这样你就可以将材质切换回原来的。

        首先获得这个材质,并为它创建一个新的通路。找到makeMaterialReceiveDecal并添加以下代码:
      MaterialPtr mat = (MaterialPtr)MaterialManager::getSingleton().getByName(matName);
      Pass *pass = mat->getTechnique(0)->createPass();
现在我们创建了通路,我们需要设置混合和光照。我们将添加新的纹理,它必须与当前的纹理正确地混合。为此我们将把场景混合(scene
        blending)设置成alpha透明,并把深度偏移(depth
        bias)设置成1(也就是贴图不透明)。最后我们把材质的光照关闭,这样不论场景使用何种光照,它总是显示的。如果你想在程序里让这个贴图受光照的影响,你就不必加上最后的函数调用:

      pass->setSceneBlending(SBT_TRANSPARENT_ALPHA);
      pass->setDepthBias(1);
      pass->setLightingEnabled(false);
接下来,新通路需要用我们的decal.png图像来创建一个新的纹理单元状态。第二个函数调用打开了投影纹理,并接收我们创建的平截头体。最后两个调用设置过滤和寻址模式:

      TextureUnitState *texState = pass->createTextureUnitState("decal.png");
      texState->setProjectiveTexturing(true, mDecalFrustum);
      texState->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
      texState->setTextureFiltering(FO_POINT, FO_LINEAR, FO_NONE);
我们已经把纹理的寻址模式设置成clamp,这样贴图就不会在物体上不停地循环了。对于过滤选项,当对象放大时,使用标准线性滤波,但对于缩小的情况,我们只是关闭过滤,并且完全关闭mip贴图。这样避免了当缩小时,贴图的边缘不能很好地融入纹理的其它部分。如果我们不这样做的话,贴图投影区域的边缘会非常的难看。

        对于设置材质,以上是所有你要做的。
        [编辑] 调用函数
        我们已经建立好了函数,为了设置投影器和材质还必须调用它们。在createScene方法的最后面,加上以下代码:
      createProjector();
      for (unsigned int i = 0; i < ent->getNumSubEntities(); i++)
          makeMaterialReceiveDecal(ent->getSubEntity(i)->getMaterialName());
注意,在前面的循环里,ent变量已经保存有了Ogre头颅。由于所有的Ogre头颅使用相同的材质,我们只需要随机选择他们中的一个来获取材质名称。
        [编辑] 消除反向投影
        [编辑] 介绍
        也许你已经注意到了,当运行程序里,存在两个投影贴图。一个是投射在-Z方向,也就是我们的平截头体的朝向,另一个投射在+Z方向,在我们创建的平截头体的后面的Ogre人头上。这个的原因是,当一个贴图从平截头体投影出去时,一个相应的(反向)贴图从它的背面投影出去。

        这显然不是我们想要的。为了修正它,我们将引了一个过滤器来除去反向投影。
        [编辑] 修改投影器
        为了过滤掉反向投影,我们需要一个新的平截头体,让它指向我们想要过滤的方向。在createProjector方法中添加以下代码:
      mFilterFrustum = new Frustum();
      mFilterFrustum->setProjectionType(PT_ORTHOGRAPHIC);
      SceneNode *filterNode = mProjectorNode->createChildSceneNode("DecalFilterNode");
      filterNode->attachObject(mFilterFrustum);
      filterNode->setOrientation(Quaternion(Degree(90),Vector3::UNIT_Y));
这应该比较熟悉了。唯一的区别就是我们把这个节点旋转了90度以朝向背面。
        [编辑] 修改材质
        下面我们添加另一个纹理状态到材质的通路上。把以下代码加到makeMaterialReceiveDecal:
      texState = pass->createTextureUnitState("decal_filter.png");
      texState->setProjectiveTexturing(true, mFilterFrustum);
      texState->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
      texState->setTextureFiltering(TFO_NONE);
这都是非常熟悉的了。注意,我们正在使用过滤纹理,过滤平截头体,并关闭了过滤。编译并运行程序。你应该可以看到只有朝向前面的投影贴图。
        [编辑] 炫耀一下投影
        [编辑] 简单的旋转
        为了炫耀一下这个投影,我们将旋转这个投影并更新它的视线范围(Field of
        View)。为了旋转这个投影器,只需要把下面几行代码添加到frameStarted方法里:
      mProjectorNode->rotate(Vector3::UNIT_Y, Degree(evt.timeSinceLastFrame * 10));
编译并运行程序。你将会看到贴图沿着一个圈被投影到Ogre人头。
        [编辑] 修改视线范围
        接下来,我们将修改这个投影器的视线范围(field of
        view)。由于我们不使用正射投影器,我们可以修改视力范围来增加或缩小投影的大小。作为演示,我们将把FOVy(field of view
        Y)设置成15到25度的夹角。下面的代码将增加或缩小贴图的大小(添加到frameStarted方法):
      mAnim += evt.timeSinceLastFrame / 2;
      if (mAnim >= 1)
          mAnim -= 1;

      mDecalFrustum->setFOVy(Degree(15 + Math::Sin(mAnim * Math::TWO_PI) * 10));
编译并运行程序。
        [编辑] 最后要注意的
        最后要注意到是,如果你在程序里使用贴图,必须保证贴图的边缘像素都是完全透明的(alpha为0)。否则,出于纹理clamping的工作机制,贴图会拖泥带水。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值