Ogre渲染目标
在Ogre中,并没有要求你一定要把最终的显示画面渲染到图形显示设备上。换句话说,Ogre最终渲染到帧缓存的结果(颜色数据),既可以在显示器上面展示也可以输送到其他渲染目标。在这里,渲染目标的概念是一个用来保存渲染结果(二维信息)的存储区域,它既可以在显存中也可以在AGP[1]的共享储存区域中。
最基本和普通的渲染目标当然是程序的主窗口。就是那个可以被你在操作系统中改变大小并通过Alt+Tab切换的那个窗口。如果你按照前面章节所介绍的那样初始化了Ogre渲染系统之后,你就拥有了这个渲染目标。
不过,相当多的游戏需要额外渲染一些特殊的场景部分来实现有趣的效果。比如把一些看不见的部分渲染到纹理上,再拿到场景中使用。很普遍的例子中包括:赛车游戏中的后视镜;监视敌人的监视器;镜子或者水面里面的倒影;把预先渲染的数据在之后的渲染通路中再使用;或者实现一些很炫目的后处理效果。这些特殊效果都可以通过Ogre众多渲染目标中的“纹理渲染目标”类型来实现,这种做法被称为渲染到纹理(RTT)技术。虽然你也可以把渲染的结果放到其他的地方,但在实际应用中并不常见。所以在这个章节接下去的部分,将要重点介绍如何渲染到纹理。
概念总览
顾名思义,渲染目标就是渲染结果的显示的地方。其中既包括硬件的缓存(包括帧缓存以及其他),也包括诸如纹理一样的储存区域。在渲染到硬件缓存的时候,渲染目标的物理表示就是你应用程序使用的窗口(不论是主窗口还是辅助窗口)。当渲染到纹理的时候,你可以在程序的任何地方如同普通纹理一样使用它。对Ogre而言,一个纹理也不过只是一个2D的标满而已,和渲染窗口的帧缓存没有什么本质的区别。
你也可以向渲染目标对象中注册自己的监听者实例,这时候渲染目标就是一个事件源(可以参考设计模式中的监听者)。不论是渲染开始或者结束,以及渲染设置和对象可见性的改变都会让渲染目标向你的监听者实例中调用相应的事件处理。同时它也可以把视口层的变化事件提供给用户,其中包括视口的增加和移除事件。
在Ogre中提供了得到渲染目标性能的方法:它帮助你计算了各种渲染状态的参数。当然,可能这些计算结果对你没有什么价值。不过当你希望知道渲染一幅画面消耗的时间(平均时间或者最差时间),或者想要了解当前画面有多少个三角形被渲染的时候。你可以通过这个方法来得到这些数字。你可以把他们显示在界面上让使用者了解其性能(就像演示程序中所做的一样),也可以根据这些数据来调整或者限制帧率。有些开发者喜欢降低帧率,把节省下来的CPU性能用于系统其他的应用之上(换句话说,就是不让3D渲染耗干你的机器性能)。
渲染窗口
Ogre引擎中的渲染窗口并没有什么神秘的特性,它不过是系统GUI界面中的一个普通窗口,和其他的窗口一样被系统创建和管理。在Windows操作系统下,它是一个普通得Win32窗口。而在Linux系统下,再默认的情况下是GLX窗口,也可能是GTK或者SDL窗口(虽然在Ogre1.2版本中并不支持SDL窗口)。
一般来说Ogre的渲染窗口只具备了最少的配置,通常只有尺寸和标题栏。这个窗口并不包含完整地GUI配置,比如下拉菜单或者工具条。对于一般的游戏来说这样就足够了(很多游戏只要全屏显示就算OK了)。但如果你真的有需要,也可以自己配制下拉菜单等GUI界面,Ogre会提供给你渲染窗口的句柄用来操作。你也可以自己创建窗口,然后把句柄反过来提供给Ogre使用,Ogre会把这个窗口作为父窗口,在其中的客户区域中创建所需渲染窗口。有经验的开发者(特别是那些工具的开发者),通常喜欢自己管理窗口系统。
当时用多个窗口的时候,只有第一个窗口被设置为Ogre的渲染主窗口。它会和Direct3D或者OpenGL设备绑定。其他所有窗口都被称为辅助窗口,它们并没有向主窗口那么被系统重视。例如,只有在决定关闭程序时候你才能销毁主窗口,否则程序会崩溃。这是因为在释放主窗口的时候渲染内容和设备都会被一同释放。而辅助窗口却可以在程序运行期间随时创建和释放。
视口
视口是这样一个矩形区域,它把从摄像机得到的内容映射到渲染目标上面。在同一个渲染目标中可以包含一个或者更多的视口。视口在初始化的时候需要得到一个摄像机对象的实例,但是它们并没有被静态绑定,所以你可以在任何时候把视口和另外的摄像机重新绑定到一起。
在渲染目标中,每一个视口都占有一个独立的z-order(用来决定哪个是视口在哪个视口前面)。更高的z-order决定在堆栈中“更高”的位置(意味着当z-order值为0的时候,这个视口就在最下面)。通过管理同一个渲染目标的多个视口可以实现很多有趣的效果。
警告:虽然多视口能带来很多有趣的效果,但同时也能带来潜在的问题。再默认的情况下,Ogre会用视口的颜色清空渲染部分的深度缓存。然而你也能关闭这个功能,不过这就在多视口的情况下引入了潜在的问题:当你没有开启深度缓存的清理功能的时候,多个视口会共享一个相同的深度缓存,这将会导致无法与色的深度缓存冲突问题。
视口是渲染目标和摄像机唯一的通道。换句话说,它是渲染系统和场景管理器与其内容的唯一入口。视口可以被称为你“场景中的窗户”。
虽然大多数时候,视口会占据你渲染目标的全部空间。但这并不是必要的,你可以用一些非占据全部空间的“小”视口实现相当有趣的效果。比如你可以在一个赛车游戏中,用一个小视口代表赛车中后视的效果,“镜子”里面的视口包含面向后方或者的摄像机的渲染结果。另外的例子是,在一些战争模拟游戏中,通过一个更接近目标的摄像来机实现画中画放大的视口效果。
注意:这里可能有一个认识上的普遍错误。在现实生活中使用摄像机的时候,你可以通过调整摄像机的焦距等数据来拉近目标距离。而这种操作在3D渲染中却行不通,虽然你可以看到放大的物体,不过却比正常的简陋很多。这是因为系统仍然根据摄像机的真实距离进行LoD(细节等级)计算,导致提供给你低精度的模型。当你遇到这种问题的时候,解决办法是创建另外一个摄像机,然后把摄像机的位置贴近目标来实现放大(虽然听上去有些愚蠢)。
渲染到纹理
用纹理作为渲染目标是一件极其有趣的事情。简单来说,它的概念就是把当前场景的一部分或者全部渲染成一个可以使用的纹理上面,然后再另外一次渲染中把它当作普通的纹理使用。
并且这个工作也非常简单明了:首先需要创建一个渲染目标纹理并设置其属性,之后把这个纹理添加到渲染目标列表,最后把这个纹理设置到相应的材质中。Ogre都会帮助你处理之后所有的工作,它会优先更新(渲染)纹理渲染目标,这就能保证你在场景中使用的动态纹理都是当前场景的画面。如果你不需要每帧自动更新功能,也可以关掉自动更新换成手动更新的方式。你可以通过操作纹理的方法管理纹理渲染目标,包括混合在内的所有纹理操作都可以在纹理渲染目标上实现。
性能影响
其实纹理目标只不过是一种硬件缓存的表现形式。为了得到更高的性能,需要把缓存设置为静态并且只读的(大多数情况下,你也不能直接从CPU写纹理)。因为从缓存中读取数据是一种耗时的工作,所以不要放纵自己从硬件设备中读取纹理的习惯。
“渲染到纹理”技术,本质上来说就是一次对场景中几何体的渲染过程。它本身要花费一些执行时间,进而导致帧速的下降。当你渲染复杂耗时的内容的时候,你不得不考虑相关的效率问题。不过,为了实现一些特殊的效果,又不能不使用“渲染到纹理”技术。比如我们将要在12章中介绍的实时阴影以及动态反射之类的特效都需要纹理渲染目标的支持。虽然在前面材质章节时候介绍过一种简单的静态反射材质,但它却不能反射动态的物体的“倒影”。如果这时候使用了纹理渲染目标来表现反射,就能很好的处理诸如水面上划行的船在水面的“倒影”此类的动态效果。另外一种纹理渲染目标比较擅长的领域是3D GUI的实现。你可以选择把2D的GUI画面渲染到3D的空间几何体上面,或者把3D的物体渲染到2D的GUI上面。纹理渲染目标都可以良好的帮你实现。
如果你希望把相同场景或物体同时渲染到多个纹理表面上(举例来说,假如你想把你场景中的法线渲染到一个纹理上面,而另外一个纹理上面渲染这个场景的深度信息)。可以通过MultiRenderTarget类型的实例把多个纹理渲染目标绑定到一起,唯一需要注意的是这些纹理渲染目标的大小要一样。在不超过硬件能力的前提下你可以创建任意数量的纹理渲染目标并捆定到一起,实际运行时这时候这些渲染目标会有类似的行为表现。(这段文字并不容易理解,请读者自行参照演示程序Demo_DeferredShading中对MultiRenderTarget类型的使用。)
渲染目标相关的类型
在Ogre中,大部分和渲染目标相关的类型都可以简单的管理和操作。其中渲染目标的抽象接口“RenderTarget”是所有渲染目标的基类,由它派生出三种不同的渲染目标类型,包括:窗口渲染目标 “RenderWindow”、纹理渲染目标“RenderTexture”以及用于绑定多个渲染目标的“MultiRenderTarget”类型。
使用窗口渲染目标的例子
针对与不同系统的各种类型窗口,Ogre也提供了多个不同窗口渲染目标的实现,其中包括:D3DRenderWindow,GLXWindow,GTKWindow,SDLWindow以及Win32Window。因为Linux下只支持OpenGL硬件加速接口,并且默认情况下使用GLX框架,所以大多数时候你在Linux会选择GLXWindow实现。在Windows下,因为同时支持D3D9RenderWindow和OpenGL两种不同的硬件加速接口,所以你可以使用D3D9RenderWindow或者Win32Window来完成你的程序。其中Win32Window是OpenGL在Windows下面的实现版本。虽然GLX是X Windows下面专有的平台,但是因为XFree86(http://www.x.org)系统可以通过Cygwin(http://www.cywin.com)运行到Windows平台之上,所以理论上Windows也可以实现GLX框架的支持。
虽然在程序中通常使用Root对象的实例来建立窗口,不过这里它只不过起到外观模式(设计模式Fa?ade)的作用,真正的创建工作仍然是转交给具体的渲染系统RenderSystem类型来实现。
RenderWindow* createRenderWindow(const String& name, unsigned int width,
unsigned int height,bool fullScreen,
const NameValuePairList* miscParams = 0);
在调用这个方法之后,系统会为你创建一个包含渲染表面的系统窗口。其中miscParams参数是一个std::map类型的指针,中可以加入你对渲染窗口的具体配置,而这些通常是平台相关的窗口设置:
·窗口标题:在窗口标题栏以及系统的任务栏出现的文本。在默认的情况下显示“Ogre Render Windows”。值得注意的是,这不同于方法中第一个参数“name”,“name”参数是系统用来标示渲染窗口的唯一ID,和标题并没有任何关系。参数的索引值是:“title”。
?窗口边框样式:设置渲染窗体边框的样式(指在窗口模式下,且没有被父窗体包含时候有效)。值可以设置为“none”,“fixed”以及“resize”之一。在默认的情况下被设置称为“resize”。参数的索引值是:“border”。
?渲染窗口尺寸:用来确定createRenderWindow()方法中的width和height两个参数是应用于整个窗口(包括边框等修饰部分)还是应用于实际渲染区域(内部尺寸实际渲染尺寸)。可以设置为“true”(包括修饰部分)和“false”(只有内部区域)。默认的是“false”。参数的索引值是:“outerDimensions”。
?颜色深度:定义渲染窗口的颜色深度,可以是16位或者32位(值分别为“16”和“32”)。这个参数只在Windows系统下有效;在Linux和Mac系统下会使用当前颜色深度“visual”。只有在Win32环境下系统才能处理相应的参数,默认情况是桌面的颜色深度(“通过显示属性中设置”)。参数的索引值是:“colourDepth”。
?窗口位置:设置窗体在桌面上的具体位置(从左上角算的偏移位置)。默认是放置在桌面中间。参数的索引值是:“top”与“left。”
?深度缓存:控制窗口是否使用深度缓存(可用值为true或者false,默认为true)。这是DX9渲染系统独有的参数。参数的索引值是:“depthBuffer”。
?外部窗口句柄:把Ogre的渲染内容绘制到外部的窗口上(非Ogre自身创建的)。在Windows下,这个值是字符串形式的窗口句柄(HWND)。在X Windows(GLX,Linux,Mac)下,可以选者使用 “A:B:C”或者 “A:B:C:D”两种格式之一,其中A代表显示指针(XDisplay*),B表示屏幕数量(通常是0),C代表窗口句柄,D是XVisualInfo*的值。在这里虽然不同的窗口类型需要不同的代码,但是确实简单而又直观的解决平台无关性的问题(可能在你调用的代码中需要用宏来区分多种平台的处理)。你需要把整型的数字转换成字符串类型传入。这里的值默认为“0”。参数的索引值是:“externalWindowHandle”。
?父窗口句柄:虽然和外部窗体句柄有同样的格式,但是它包含的是父窗体的信息,Ogre会在提供的窗口内创建相应的子窗口。参数的索引值是:“parentWindowHandle”。
?FSAA:用于指定一个的全屏反混淆因子。可能的值是“0”,“2”,“4”,“6”…中的任何一个。默认情况下是“0”。参数的索引值是:“FSAA”。
?刷新率:为渲染窗口指定新的刷新率。可以是任何显示器能接受的数字,并且只适用于全屏模式。默认时为桌面刷新率。参数的索引值是:“displayFrequency”。
?垂直同步:关闭(或开启)显示器的垂直同步。只适用于全屏模式。值可以为“true”或者“false”。默认为“false”。参数的索引值是:“vsync”。
?NVPerfHUD:启用/禁用使用NVIDIA 公司提供的“监测和可视化软件调试状态显示界面”(也就是我们所熟悉的NVPerfHUD)。只有当使用者安装了NVIDIA公司提供的相应工具驱动(http://developer.nvidia.com/object/nvperfhud_home.html)之后这个参数才有用处。且只能在Director3D和NVIDIA加速的图形硬件下使用。值可以为“true”或者“false”。默认为“false”。参数的索引值是:“useNVPerfHUD”。
在代码8-1种展示了如何创建渲染窗口的过程。这个窗口采用了全屏模式,75Hz的刷新率和2倍FSAA(反混淆因子),并开启垂直同步。
代码8-1:创建全屏的渲染窗口
NameValuePairList params; // 这只是一个typedef std::map
// 设置2倍反混淆
params[“FSAA”] = “2”;
// 开启垂直同步
params[“vsync”] = “true”;
// 设置75Hz刷新率
params[“displayFrequency”] = “75”;
RenderWindow* window = createRenderWindow(“MyWindow”, 800, 600 ,true, ?ms);
工具开发者通常希望将Ogre渲染窗口包含在另外的窗口控件中。这也可以在创建窗口的时候很容易的实现,请参看代码8-2。
代码8-2:创建被包含的渲染窗口
NameValuePairList params; // 这只是一个typedef std::map
// 设置外部窗体句柄。
// 我们假设你已经创建了父窗口,用于包含Ogre的渲染窗口。
// 并把它的句柄储存在一个无符号整型变量“mParent”中。
params[“externalWindowHandle”] = StringConverter::toString(mParent);
// 窗口可以根据需要重新设置大小,从而忽略了下面参数中的大小。
RenderWindow* window = createRenderWindow(“MyWindow”, 800, 600 ,true, ?ms);
使用渲染到纹理例子
在Ogre的演示程序中提供了一些渲染到纹理的例子。在接下来的部分,我们将重点介绍这些例程的实现细节,以便让我们进一步了解纹理渲染目标的使用方法。
Demo_RenderToTexture
这是一个使用渲染到纹理技术的最基本的例子,代码也比较容易理解。我们首先在原点处放置一个平面,然后布置相应的摄像机把场景中的内容映射到平面所使用的材质纹理上。如图8-1所展示的结果,为了便于观察,例子中在场景里面放置了一个食人魔的头和几个麻花体的模型。最后在平面上产生了实时反射的渲染特效。整个过程不需要任何的GPU编程,并且渲染效率远远高于传统中通过光线追踪来产生反射的效果。这种实时反射效果可以自动依照视点的变化而正确的变化。在图中可能看不出来,在摄像机移动的同时,天空盒的倒影会正确的在反射表面投影出来。
图8-1:Ogre渲染到纹理演示程序效果
在演示程序RenderToTexture.cpp文件中,RenderToTextureApplicait
和创建其他对象一样,纹理的创建也有相应的工厂方法。代码8-3中展示了纹理对象的创建过程。其中PF_R8G8B8描述了一个24位无Alpha通道的颜色格式。方法返回了一个指向纹理资源的智能指针。
代码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
t->setProjectiveTexturing(true, mReflectCam);
rttTex->addListener(this);
上面代码中创建了名字为“RttMat”的材质,其中包含一个技术实现(Technique)和其中的一个渲染通路(Pass),在渲染通路中含有两个纹理单元,其中一个是静态图片(图片文件RustedMetal.jpg),而另外一个就是使用我们之前创建的纹理渲染目标“RttTex”。两层纹理单元以25%系数混合到一起(换句话说,我们的纹理渲染目标在表面上拥有25%可见度)。最后的混合效果就像前面图8-1所展示的一样。
这个投影纹理可以良好的用于对世界地图的反射纹理的应用。注意在这里代码中纹理单元的寻址方式被设置为TAM_CLAMP,这可以有效的让渲染纹理附着在物体表面上。
在代码的后面,RenderToTextureApplicati
接下来的代码8-6中,展示了这个演示程序中关键的步骤。
代码8-6:设置投影摄像机,并把代码-83中创建的纹理应用到平面
// set up linked reflection
mReflectCam->enableReflection(mPlane);
// Also clip
mReflectCam->enableCustomNearClipPlan
}
// 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方法的原因。RenderToTextureApplicati
最后需要注意的是,为了让纹理渲染出来的结果和我们的视点相同,在运行的每一帧都要用更新反射摄像机的位置。
// 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中的相关细节。