渲染到纹理用途:游戏中水的倒影,汽车的反光镜,gpgpu必备。
实现参见红皮书的
至于OGRE中的渲染到纹理可以参考翻译文档第八章《OGRE渲染目标》
“渲染到纹理”技术,本质上来说就是一次对场景中几何体的渲染过程。它本身要花费一些执行时间,进而导致帧速的下降。当你渲染复杂耗时的内容的时候,你不得不考虑相关的效率问题。不过,为了实现一些特殊的效果,又不能不使用“渲染到纹理”技术。比如我们将要在12章中介绍的实时阴影以及动态反射之类的特效都需要纹理渲染目标的支持。虽然在前面材质章节时候介绍过一种简单的静态反射材质,但它却不能反射动态的物体的“倒影”。如果这时候使用了纹理渲染目标来表现反射,就能很好的处理诸如水面上划行的船在水面的“倒影”此类的动态效果。另外一种纹理渲染目标比较擅长的领域是3D GUI的实现。你可以选择把2D的GUI画面渲染到3D的空间几何体上面,或者把3D的物体渲染到2D的GUI上面。纹理渲染目标都可以良好的帮你实现。
代码举例 :纹理对象的创建过程。其中PF_R8G8B8描述了一个24位无Alpha通道的颜色格式。方法返回了一个指向纹理资源的智能指针。注意最后一个参数必须为 TU_RENDERTARGET ,这个纹理是一个渲染目标。换句话说,你把一个目标渲染到这个纹理上。
代码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:创建一个摄象机和视口用来把场景内容渲染到纹理
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());
如果用户更新了主摄像机的位置,我们的反射摄像机也会完全的跟随,这样就能保证反射的结果对应于我们当前的视点。