使用渲染到纹理例子



使用渲染到纹理例子

       在Ogre的演示程序中提供了一些渲染到纹理的例子。在接下来的部分,我们将重点介绍这些例程的实现细节,以便让我们进一步了解纹理渲染目标的使用方法。

 

Demo_RenderToTexture

       这是一个使用渲染到纹理技术的最基本的例子,代码也比较容易理解。我们首先在原点处放置一个平面,然后布置相应的摄像机把场景中的内容映射到平面所使用的材质纹理上。如图8-1所展示的结果,为了便于观察,例子中在场景里面放置了一个食人魔的头和几个麻花体的模型。最后在平面上产生了实时反射的渲染特效。整个过程不需要任何的GPU编程,并且渲染效率远远高于传统中通过光线追踪来产生反射的效果。这种实时反射效果可以自动依照视点的变化而正确的变化。在图中可能看不出来,在摄像机移动的同时,天空盒的倒影会正确的在反射表面投影出来。

 

图8-1:Ogre渲染到纹理演示程序效果

 

       在演示程序RenderToTexture.cpp文件中,RenderToTextureApplicaiton类中的createScene()方法中实现了大部分关键的操作。在这里我们重点介绍如何设置纹理渲染目标。

和创建其他对象一样,纹理的创建也有相应的工厂方法。代码8-3中展示了纹理对象的创建过程。其中PF_R8G8B8描述了一个24位无Alpha通道的颜色格式。方法返回了一个指向纹理资源的智能指针。

 

扫描二维码关注公众号,回复: 2980748 查看本文章

代码8-3:创建一个512x512,24-bit,名字为RttTex的纹理渲染目标

TexturePtr texture = TextureManager::getSingleton().createManual( "RttTex",

         ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D,

         512, 512, 0, PF_R8G8B8, TU_RENDERTARGET );

   

在代码8-4中,我们设置了一个摄象机和视口用于把场景内容渲染到目标纹理上。

 

代码8-4:创建一个摄象机和视口用来把场景内容渲染到纹理

RenderTarget *rttTex = texture->getBuffer()->getRenderTarget();

        {

            mReflectCam = mSceneMgr->createCamera("ReflectCam");

            mReflectCam->setNearClipDistance(mCamera->getNearClipDistance());

            mReflectCam->setFarClipDistance(mCamera->getFarClipDistance());

            mReflectCam->setAspectRatio(

                (Real)mWindow->getViewport(0)->getActualWidth() /

                (Real)mWindow->getViewport(0)->getActualHeight());

            Viewport *v = rttTex->addViewport( mReflectCam );

            v->setClearEveryFrame( true );

            v->setBackgroundColour( ColourValue::Black );

在这里我们需要注意的,为了实现反射效果,我们把反射摄像机(纹理渲染目标使用)和主摄像机(渲染整个场景使用)设置到相同的位置。我们并不需要做任何特殊的处理,只要保证两个摄像机的位置和方向相同,就能很好的在表面上实现倒影的效果。

 

       代码8-4中创建了相应的视口并设置为每帧清理。如果你忘了做每帧清理的工作,之前的渲染结果都会保留下来(听起来似乎是一种实现残影特效的方法,但这里并不需要)。视口的背景被设置为黑色,这样就能正确地在上面添加渲染结果(黑色是没有光线的颜色)。

 

       在下面代码8-5中(接着上面的代码),我们创建了相应的材质对象,这个材质之后被用在createScene()方法所创建的反射平面上。

 

代码8-5:创建使用渲染纹理的材质

MaterialPtr mat = MaterialManager::getSingleton().create("RttMat",

                ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

            TextureUnitState* t = mat->getTechnique(0)->getPass(0)->createTextureUnitState("RustedMetal.jpg");

            t = mat->getTechnique(0)->getPass(0)->createTextureUnitState("RttTex");

            // Blend with base texture

            t->setColourOperationEx(LBX_BLEND_MANUAL, LBS_TEXTURE, LBS_CURRENT,

ColourValue::White,  ColourValue::White, 0.25);

                            t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);

                            t->setProjectiveTexturing(true, mReflectCam);

            rttTex->addListener(this);

 

       上面代码中创建了名字为“RttMat”的材质,其中包含一个技术实现(Technique)和其中的一个渲染通路(Pass),在渲染通路中含有两个纹理单元,其中一个是静态图片(图片文件RustedMetal.jpg),而另外一个就是使用我们之前创建的纹理渲染目标“RttTex”。两层纹理单元以25%系数混合到一起(换句话说,我们的纹理渲染目标在表面上拥有25%可见度)。最后的混合效果就像前面图8-1所展示的一样。

 

       这个投影纹理可以良好的用于对世界地图的反射纹理的应用。注意在这里代码中纹理单元的寻址方式被设置为TAM_CLAMP,这可以有效的让渲染纹理附着在物体表面上。

 

在代码的后面,RenderToTextureApplication类的实例被作为一个“监听者”添加到纹理渲染目标对象上(接下来我们会讲具体的回调内容)。

 

接下来的代码8-6中,展示了这个演示程序中关键的步骤。

 

代码8-6:设置投影摄像机,并把代码-83中创建的纹理应用到平面

            // set up linked reflection

            mReflectCam->enableReflection(mPlane);

            // Also clip

            mReflectCam->enableCustomNearClipPlane(mPlane);

        }

        // Give the plane a texture

        mPlaneEnt->setMaterialName("RttMat");

 

       在代码8-6中,我们首先把摄像机设置为镜像状态。并且我们在参数中指定了作为“镜子” 的平面。如你所见,这个平面即用来渲染反射纹理,同时也用来作为反射表面的分界。平面会如同镜子般反射所有平面上面的内容。这种方法比较常用于模拟水面的反射,你可以想象这样一个画面,碧绿的湖面映射着周围美丽的风景。这和本演示程序的基本原理是相同的。

 

       对摄像机近截面(Near clip plane)的设置突然出现在这里可能会让人感觉有一些突兀。但是这里确实需要一个“截面”,否则你会把反射表面下面的内容(那些你不希望被反射的东西)也渲染到表面,在这里我们同样用镜面所在的平面作为近截面来截去这些物体。但是这里没有使用“用户自定义截面”,而是通过改变视截体的近截面来达到同样的效果。这是因为并不能保证“用户自定义截面”在所有硬件上都能够正 确的实现,而视截体的“近截面”可以。并且就算在能实现“用户自定义截面”的硬件上,视截体的“近截面”会有更高的渲染效率。虽然拉远近截面会让深度缓存的精度有所下降,不过在大多数时候并不会对显示效果有明显的影响(毕竟我们只是在纹理上作渲染工作)。

 

       代码8-6最后一行把我们所用平面的实体和反射材质绑定到一起。

 

       另外一部分对渲染的处理工作发生在每一帧渲染的时候。也就是在场景内容渲染到纹理的时候。请参看代码8-7。

 

代码8-7:在渲染前隐藏平面,然后在渲染结束后让它可见

// render target events

    void preRenderTargetUpdate(const RenderTargetEvent& evt)

    {

        // Hide plane

        mPlaneEnt->setVisible(false);

    }

    void postRenderTargetUpdate(const RenderTargetEvent& evt)

    {

        // Show plane

        mPlaneEnt->setVisible(true);

    }

 

       在每次纹理被渲染的时候,我们希望它能完整的反射所有场景中存在的物体,但却不希望它连自己都反射了(就如同镜子不会反射镜子本身一样)。因此,我们不得不在每次渲染反射画面的时候“关闭”镜子,然后再渲染完整个纹理的之后再“开启”它。这就是为什么之前我们要用纹理渲染目标的addListener方法的原因。RenderToTextureApplication类实现了RenderTargetListener回调接口,纹理渲染目标会在每次渲染前后调用相应的回调函数。

 

       最后需要注意的是,为了让纹理渲染出来的结果和我们的视点相同,在运行的每一帧都要用更新反射摄像机的位置。

 

  // Make sure reflection camera is updated too

        mReflectCam->setOrientation(mCamera->getOrientation());

        mReflectCam->setPosition(mCamera->getPosition());

 

       如果用户更新了主摄像机的位置,我们的反射摄像机也会完全的跟随,这样就能保证反射的结果对应于我们当前的视点。

 

Demo_Fresnel

       Demo_Fresnel演示程序同样使用了渲染到纹理技术,不过他更主要的作用是展示通过GPU实时着色技术来渲染水面的折射效果,在这里是用了菲涅耳折射的算法。在这个演示程序中,把水面的反射效果和菲涅耳折射效果混合在一起,并放置在GPU着色语言中进行处理(包括顶点和片断程序,这里将略过具体的实现细节)。

      

图8-2:Ogre中Demo_Fresnel 演示程序渲染效果

 

观察水下的物体时,你所看到的光线会偏离一个角度。这种现象就是折射,相应的定律最早由19世纪的菲涅耳提出。Demo_Fresnel演示程序重点展示了相应的效果,和之前的演示程序一样,它也是用了纹理渲染目标技术来实现。在这个例子中使用了三个摄像机,除了作为渲染场景本身的主摄像机之外,还有渲染水面上反射效果的摄像机以及渲染水面下折射效果的摄像机,它们把渲染结果绘制到纹理渲染目标并应用于水面。

 

       在水下被折射过的物体,在水面上是看不到本来面貌的。折射和反射纹理都被应用到水面之后,主摄像机就不会再次渲染水下的物体了。通过GPU程序中的“噪声函数”,水面上产生了相应的波纹效果。另外提及一点,关于Demo_Fresnel 演示中“Fresnel(菲涅耳定律)”的话题,反射和折射两个纹理之间的混合并不是固定的,在菲涅耳定律中揭示它们在不同观察角度相互的关系。如果你有机会经过哪个湖面(最好是比较平静的时候),可以留意一下不同角度观察水面的反射效果和折射效果之间的关系,会有助于你了解是菲涅耳定律。

使用渲染到纹理例子

       Ogre的演示程序中提供了一些渲染到纹理的例子。在接下来的部分,我们将重点介绍这些例程的实现细节,以便让我们进一步了解纹理渲染目标的使用方法。

 

Demo_RenderToTexture

       这是一个使用渲染到纹理技术的最基本的例子,代码也比较容易理解。我们首先在原点处放置一个平面,然后布置相应的摄像机把场景中的内容映射到平面所使用的材质纹理上。如图8-1所展示的结果,为了便于观察,例子中在场景里面放置了一个食人魔的头和几个麻花体的模型。最后在平面上产生了实时反射的渲染特效。整个过程不需要任何的GPU编程,并且渲染效率远远高于传统中通过光线追踪来产生反射的效果。这种实时反射效果可以自动依照视点的变化而正确的变化。在图中可能看不出来,在摄像机移动的同时,天空盒的倒影会正确的在反射表面投影出来。

 

8-1Ogre渲染到纹理演示程序效果

 

       在演示程序RenderToTexture.cpp文件中,RenderToTextureApplicaiton类中的createScene()方法中实现了大部分关键的操作。在这里我们重点介绍如何设置纹理渲染目标。

和创建其他对象一样,纹理的创建也有相应的工厂方法。代码8-3中展示了纹理对象的创建过程。其中PF_R8G8B8描述了一个24位无Alpha通道的颜色格式。方法返回了一个指向纹理资源的智能指针。

 

代码8-3创建一个512x51224-bit,名字为RttTex的纹理渲染目标

TexturePtr texture = TextureManager::getSingleton().createManual( "RttTex",

         ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D,

         512, 512, 0, PF_R8G8B8, TU_RENDERTARGET );

   

在代码8-4中,我们设置了一个摄象机和视口用于把场景内容渲染到目标纹理上。

 

代码8-4创建一个摄象机和视口用来把场景内容渲染到纹理

RenderTarget *rttTex = texture->getBuffer()->getRenderTarget();

        {

            mReflectCam = mSceneMgr->createCamera("ReflectCam");

            mReflectCam->setNearClipDistance(mCamera->getNearClipDistance());

            mReflectCam->setFarClipDistance(mCamera->getFarClipDistance());

            mReflectCam->setAspectRatio(

                (Real)mWindow->getViewport(0)->getActualWidth() /

                (Real)mWindow->getViewport(0)->getActualHeight());

            Viewport *v = rttTex->addViewport( mReflectCam );

            v->setClearEveryFrame( true );

            v->setBackgroundColour( ColourValue::Black );

在这里我们需要注意的,为了实现反射效果,我们把反射摄像机(纹理渲染目标使用)和主摄像机(渲染整个场景使用)设置到相同的位置。我们并不需要做任何特殊的处理,只要保证两个摄像机的位置和方向相同,就能很好的在表面上实现倒影的效果。

 

       代码8-4中创建了相应的视口并设置为每帧清理。如果你忘了做每帧清理的工作,之前的渲染结果都会保留下来(听起来似乎是一种实现残影特效的方法,但这里并不需要)。视口的背景被设置为黑色,这样就能正确地在上面添加渲染结果(黑色是没有光线的颜色)。

 

       在下面代码8-5中(接着上面的代码),我们创建了相应的材质对象,这个材质之后被用在createScene()方法所创建的反射平面上。

 

代码8-5创建使用渲染纹理的材质

MaterialPtr mat = MaterialManager::getSingleton().create("RttMat",

                ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

            TextureUnitState* t = mat->getTechnique(0)->getPass(0)->createTextureUnitState("RustedMetal.jpg");

            t = mat->getTechnique(0)->getPass(0)->createTextureUnitState("RttTex");

            // Blend with base texture

            t->setColourOperationEx(LBX_BLEND_MANUAL, LBS_TEXTURE, LBS_CURRENT,

ColourValue::White,  ColourValue::White, 0.25);

                            t->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);

                            t->setProjectiveTexturing(true, mReflectCam);

            rttTex->addListener(this);

 

       上面代码中创建了名字为“RttMat”的材质,其中包含一个技术实现(Technique)和其中的一个渲染通路(Pass),在渲染通路中含有两个纹理单元,其中一个是静态图片(图片文件RustedMetal.jpg),而另外一个就是使用我们之前创建的纹理渲染目标“RttTex”。两层纹理单元以25%系数混合到一起(换句话说,我们的纹理渲染目标在表面上拥有25%可见度)。最后的混合效果就像前面图8-1所展示的一样。

 

       这个投影纹理可以良好的用于对世界地图的反射纹理的应用。注意在这里代码中纹理单元的寻址方式被设置为TAM_CLAMP,这可以有效的让渲染纹理附着在物体表面上。

 

在代码的后面,RenderToTextureApplication类的实例被作为一个“监听者”添加到纹理渲染目标对象上(接下来我们会讲具体的回调内容)。

 

接下来的代码8-6中,展示了这个演示程序中关键的步骤。

 

代码8-6设置投影摄像机,并把代码-83中创建的纹理应用到平面

            // set up linked reflection

            mReflectCam->enableReflection(mPlane);

            // Also clip

            mReflectCam->enableCustomNearClipPlane(mPlane);

        }

        // Give the plane a texture

        mPlaneEnt->setMaterialName("RttMat");

 

       在代码8-6中,我们首先把摄像机设置为镜像状态。并且我们在参数中指定了作为“镜子” 的平面。如你所见,这个平面即用来渲染反射纹理,同时也用来作为反射表面的分界。平面会如同镜子般反射所有平面上面的内容。这种方法比较常用于模拟水面的反射,你可以想象这样一个画面,碧绿的湖面映射着周围美丽的风景。这和本演示程序的基本原理是相同的。

 

       对摄像机近截面(Near clip plane)的设置突然出现在这里可能会让人感觉有一些突兀。但是这里确实需要一个“截面”,否则你会把反射表面下面的内容(那些你不希望被反射的东西)也渲染到表面,在这里我们同样用镜面所在的平面作为近截面来截去这些物体。但是这里没有使用“用户自定义截面”,而是通过改变视截体的近截面来达到同样的效果。这是因为并不能保证“用户自定义截面”在所有硬件上都能够正 确的实现,而视截体的“近截面”可以。并且就算在能实现“用户自定义截面”的硬件上,视截体的“近截面”会有更高的渲染效率。虽然拉远近截面会让深度缓存的精度有所下降,不过在大多数时候并不会对显示效果有明显的影响(毕竟我们只是在纹理上作渲染工作)。

 

       代码8-6最后一行把我们所用平面的实体和反射材质绑定到一起。

 

       另外一部分对渲染的处理工作发生在每一帧渲染的时候。也就是在场景内容渲染到纹理的时候。请参看代码8-7

 

代码8-7在渲染前隐藏平面,然后在渲染结束后让它可见

// render target events

    void preRenderTargetUpdate(const RenderTargetEvent& evt)

    {

        // Hide plane

        mPlaneEnt->setVisible(false);

 

    }

    void postRenderTargetUpdate(const RenderTargetEvent& evt)

    {

        // Show plane

        mPlaneEnt->setVisible(true);

    }

 

       在每次纹理被渲染的时候,我们希望它能完整的反射所有场景中存在的物体,但却不希望它连自己都反射了(就如同镜子不会反射镜子本身一样)。因此,我们不得不在每次渲染反射画面的时候“关闭”镜子,然后再渲染完整个纹理的之后再“开启”它。这就是为什么之前我们要用纹理渲染目标的addListener方法的原因。RenderToTextureApplication类实现了RenderTargetListener回调接口,纹理渲染目标会在每次渲染前后调用相应的回调函数。

 

       最后需要注意的是,为了让纹理渲染出来的结果和我们的视点相同,在运行的每一帧都要用更新反射摄像机的位置。

 

  // Make sure reflection camera is updated too

        mReflectCam->setOrientation(mCamera->getOrientation());

        mReflectCam->setPosition(mCamera->getPosition());

 

       如果用户更新了主摄像机的位置,我们的反射摄像机也会完全的跟随,这样就能保证反射的结果对应于我们当前的视点。

 

Demo_Fresnel

       Demo_Fresnel演示程序同样使用了渲染到纹理技术,不过他更主要的作用是展示通过GPU实时着色技术来渲染水面的折射效果,在这里是用了菲涅耳折射的算法。在这个演示程序中,把水面的反射效果和菲涅耳折射效果混合在一起,并放置在GPU着色语言中进行处理(包括顶点和片断程序,这里将略过具体的实现细节)。

      

8-2OgreDemo_Fresnel 演示程序渲染效果

 

观察水下的物体时,你所看到的光线会偏离一个角度。这种现象就是折射,相应的定律最早由19世纪的菲涅耳提出。Demo_Fresnel演示程序重点展示了相应的效果,和之前的演示程序一样,它也是用了纹理渲染目标技术来实现。在这个例子中使用了三个摄像机,除了作为渲染场景本身的主摄像机之外,还有渲染水面上反射效果的摄像机以及渲染水面下折射效果的摄像机,它们把渲染结果绘制到纹理渲染目标并应用于水面。

 

       在水下被折射过的物体,在水面上是看不到本来面貌的。折射和反射纹理都被应用到水面之后,主摄像机就不会再次渲染水下的物体了。通过GPU程序中的“噪声函数”,水面上产生了相应的波纹效果。另外提及一点,关于Demo_Fresnel 演示中“Fresnel(菲涅耳定律)”的话题,反射和折射两个纹理之间的混合并不是固定的,在菲涅耳定律中揭示它们在不同观察角度相互的关系。如果你有机会经过哪个湖面(最好是比较平静的时候),可以留意一下不同角度观察水面的反射效果和折射效果之间的关系,会有助于你了解是菲涅耳定律。

猜你喜欢

转载自blog.csdn.net/wang371372/article/details/44672281