代友转载,原译者:whistleofmysong@gmail.com博客www.singmelody.com
在这一章,我们将会添加后期处理的特效到场景之中,这可以提升场景的视觉效果并且使他们看起来更有趣。这一章将会展示给你如何创建合成器和如何结合它们创建新的特效。
这一章,我们将会:
创建合成器脚本并应用他们到我们的场景
使用视口来创建分屏
使用用户输入并使用合成器操作着色器参数,
那么,让我们开始吧….
【 准备一个场景 】
我们将会使用合成器特效。但是,在使用他们之前,我们将会准备一个场景,这样就可以展示出我们创建的不同特效了。
我们将会使用上一章的最后一个例子:
1. 删除改变模型材质的一行代码。我们想要它使用其本身的材质:
ent->setMaterial(Ogre::MaterialManager::getSingleton().getByName("MyMaterial18"));
2. 类中的代码现在应该看起来如下面一样:
class Example69 : public ExampleApplication { private: public: void createScene() { Ogre::SceneNode* node = mSceneMgr- >getRootSceneNode()->createChildSceneNode("Node1",Ogre::Vect or3(0,0,450)); Ogre::Entity* ent = mSceneMgr->createEntity("Entity1","Sinbad.mesh"); node->attachObject(ent); } };
3. 编译运行程序。你将会看到Sinbad本身材质的渲染实例。
接下来我们创建一个接下来要使用合成器的简单场景。
【 添加第一个合成器】
在解释什么是合成器之前,让我们先使用一个然后再讨论技术细节。
1. 我们需要一个新的材质,但是它目前不起作用。添加这个新的材质到之前使用过的材质文件中,并命名为Ogre3DBeginnersGuide/Comp1:
material Ogre3DBeginnersGuide/Comp1
{
technique
{
pass
{
texture_unit
{
}
}
}
}
2. 然后,创建一个新的文件来存储我们的合成器脚本。和材质文件是同一个目录,创建一个文件命名为Ogre3DBeginnersGuide.compositor :
3. 在这个文件里,使用和材质同样的方案来定义我们的合成器:
compositor Compositor1
{
technique
{
4. 接着,在修改之前定义一个目标以指示我们的渲染的场景:
texture scene target_width target_height PF_R8G8B8
5. 定义我们目标的内容。在当前的情况下,即为我们之前渲染的场景:
target scene
{
input previous
}
6. 合成器脚本的最后一步为定义输出:
target_output
{
7. 合成器不需要任何输入并把其结果渲染到一个覆盖整个窗口的四边形上。这个四边形使用材质Ogre3DBeginnersGuide/Comp1并需要我们的场景目标作为一个纹理输入:
input none pass render_quad { material Ogre3DBeginnersGuide/Comp1 input 0 scene }
8. 这样这个合成器就结束了。封闭所有的花括号:
}
}
}
9. 现在我们已经完成了一个合成器脚本,让我们添加它到场景中。为到达目地,我们使用CompositorManager(合成器管理器)和我们摄像机的视口。添加代码到createScene()函数中:
Ogre::CompositorManager::getSingleton().addCompositor(mCamera->getViewport(), "Compositor1"); Ogre::CompositorManager::getSingleton().setCompositorEnabled(mCamera->getViewport(), "Compositor1", true);
10. 编译运行程序,你将会看到和之前一样的场景。
刚刚我们使用一个包含合成器脚本的合成器文件添加了我们的第一个合成器。第一步仅创建了一个空的材质,这个材质得到渲染的信息,但并不添加任何特效。第二步创建了一个新的文件来存储我们的合成器脚本;就像材质文件一样,不过这个是合成器。第三步我们就比较熟悉了:我们命名我们第一个合成器为Compositor1,并如材质文件中定义的样式,使用一种technique,合成器对于不同的显卡有不同的techniques。从第四步开始就比较有趣了:这里我们创建了一个名为scene的新纹理,它和渲染的目标纹理有同样的大小,并且这个纹理对每个颜色分量使用8个位。这种格式化像素的方式由PF_R8G8B8来定义。
【 合成器是如何起作用的 】
但是为什么我们需要首先创建一个新的纹理呢?为理解这个,我们需要理解合成器的工作原理。合成器是在场景渲染之后来修改场景的外观。这个过程就好像电影中的后期处理,所谓后期处理就是电影杀青后添加电脑特效。为了实现这个任务,合成器需要把渲染后的场景作为一纹理,这样它就可以被修改了。我们在第四步创建了这个纹理,并且在第五步,我们告诉Ogre 3D以场景之前的渲染来填充这个纹理。这是由input previous 来完成的。现在我们有一个包含我们渲染后场景的纹理,我们下一个场景管理器需要做的就是创建一个新的图像,这个图像最终被在显示设备上显示。这一过程是在第六步使用关键字output和一个目标代码段来完成的。
在第七步定义这个输出。我们不需要任何输入,因为场景纹理已有场景图像。为渲染修改的纹理,我们需要一个四边形来覆盖整个显示设备,我们可以在这个设备渲染已经修改了的场景。这是用pass关键字后的render_quad 标识符来实现的。这里也有很多别的可以放在pass关键字后标识符。他们可以在文档地址(http://www.ogre3d.org/docs/manual/manual_32.html#SEC163)处找到。
在pass的代码段中,我们定义了用于渲染传递的几种不同的属性。第一个是我们四边形将要使用的材质;这里我们仅使用我们提前定义好的材质,这个材质无修改渲染四边形的纹理。下一个属性定义了如纹理类似的附加输入;这里我们想要输入的第一纹理作为场景纹理。实际上,这表示我们想要我们的场景渲染到一个纹理并应用至一个覆盖整个屏幕的四边形。在这里,我们将看不到加不加合成器对渲染的场景有什么不同。当我们添加一些代码到材质中就会有很大的改观了,添加的代码将会修改传入的纹理并添加附加的特效
第九步添加合成器到我们的视口并激活它;我们添加合成器到一个视口而非我们的场景是因为合成器通过摄像机修改了场景的景象,而摄像机的视图是与视口相关的。所以如果我们想要修改整个场景的景象,我们应添加合成器到对象,对象定义了场景本身的景象。
下面的简化图示显示出了合成器和一删简版的合成器脚本的工作流程,图示显示了代码每一步所代表的流程。
【 修改纹理 】
我们渲染我们的场景到一个纹理,但显示的效果却没有什么变化。这是相当无趣的。那么让我们把它变得有趣一点吧。
1. 我们将会使用一个片段着色器并修改纹理,所以修改材质使用片段着色器。同样的复制材质和合成器。这个合成器的名字应为Compositor2,材质名Ogre3DBeginnersGuide/Comp2:
fragment_program_ref MyFragmentShader5
{
}
2. 在使用引用之前不要忘记在纹理文件中定义片段着色器
fragment_program MyFragmentShader5 cg
{
source Ogre3DBeginnersGuideShaders.cg
entry_point MyFragmentShader5
profiles ps_1_1 arbfp1
}
3. 同样在着色器文件中创建一个新的片段着色器。就输入而言这个着色器有纹理坐标和纹理样例。当片段的颜色被计算的时,颜色将会返回:
void MyFragmentShader5(float2 uv : TEXCOORD0,out float4 color : COLOR, uniform sampler2D texture) {
4. 在纹理坐标系的位置获取纹理的颜色:
float4 temp_color = tex2D(texture, uv);
5. 转变颜色为灰度模式:
float greyvalue = temp_color.r * 0.3 + temp_color.g * 0.59 + temp_ color.b * 0.11;
6. 并使用这个值作为三个输出颜色分量的值:
color = float4(greyvalue,greyvalue,greyvalue,0);
7. 编译运行程序。你将会看到同样的模型,但是这次是在灰度模式以下的:
这样我们添加了一个片段着色器到我们的合成器中。在渲染过我们的场景之后,整个窗口四边形通过场景纹理使用我们的片段着色器得到渲染。片段着色器通过使用他的纹理坐标来查询坐标位置的颜色。然后程序用红色分量的0.3,绿色分量的0.59,蓝色分量的0.11转换RGB颜色值为灰度模式下的颜色。这些值代表每种颜色分量对于人眼接受亮度的影响。 使用这些值添加到颜色创建了和灰度显示比较接近的效果。在这个合成器特效之后,我们将会创建一种并非转变图像为黑白色的效果,但效果要比转化颜色更胜一筹。
【 转换图片】
现在是时候用另一个合成器来创建场景颜色转换的另一个版本了。
使用黑白模式合成器的代码,我们现在将要转换图片。
1. 复制着色器,材质和合成器因为稍后我们将会需要黑白模式着色器和当前的这个着色器。然后改变这个改变这个复制的片段着色器。这个新的片段着色器应命名为MyFragmentShader6,材质命名为Ogre3DBeginnersGuide/Comp3,作为合成器Compositor3。
2. 这时,获取纹理片段位置的颜色值,然用1.0减每个颜色分量来获取转换的颜色值:
color = float4( 1.0 - temp_color.r,1.0 - temp_color.g, 1.0 - temp_color.b,0);
3. 编译运行程序。这次,背景将会是白色的,Sinbad将会如下图显示的那样,其身上是比较奇怪的颜色:
刚刚我们改变了片段着色器以替换他们之前转换使用的黑白模式。程序的其他部分没有什么改变。转换RGB颜色值是比较简单的:仅用他们颜色分量的最大值减去当期颜色分量,在我们当前情况下是1.0。最终的颜色是原始颜色的反转。
【 结合合成器】
这么快变的略显无聊了,那么让我们结合两种着色器特效吧。
为结合两个合成器,我们需要创建一个新的合成器:
1. 为创建一个新的合成器,我们需要两个纹理——一个来存储场景并且一个来存储一些临时的结果:
compositor Compositor4
{
technique
{
texture scene target_width target_height PF_R8G8B8
texture temp target_width target_height PF_R8G8B8
2. 如之前一样填充场景纹理,然后使用场景纹理和我们的黑白模式的纹理来填充temp纹理。
target scene { input previous } target temp { pass render_quad { material Ogre3DBeginnersGuide/Comp2 input 0 scene } }
2. 然后使用临时材质和我们的反转材质来创建输出纹理:
target_output { input none pass render_quad { material Ogre3DBeginnersGuide/Comp3 input 0 temp } } } }
4. 编译和运行程序;你将会看到看到一个先颜色转换至黑白色,然后颜色反转的场景。
这样我们创建了第二个辅助纹理;这个纹理作为一个渲染目标设为黑白模式,然后这个纹理就会有我们场景的黑白图像作为反转材质的输入,处理之前图像才得以显示。
【 减少纹理总数 】
在之前的部分,我们使用两个纹理—— 一个是原始场景,另一个是在两步改变中的第一步完成之后来存储中间结果。现在让我们尝试仅使用一种纹理。
通过使用之前的代码,我们将会减少我们合成器的纹理总数。】
1. 我们需要一个新的合成器,这次只有一个纹理:
compositor Compositor5
{
technique
{
texture scene target_width target_height PF_R8G8B8
2. 然后用渲染的场景填充纹理:
target scene
{
input previous
}
3. 使用纹理作为输入纹理同时也为输出纹理:
target scene { pass render_quad { material Ogre3DBeginnersGuide/Comp2 input 0 scene } }
4. 同样的,使用使用纹理作为输入作为最终渲染:
target_output { input none pass render_quad { material Ogre3DBeginnersGuide/Comp3 input 0 scene } }
5. 添加缺少的括号:
}
}
6. 编译运行程序。效果是一样的,但是我们这次仅使用使用了一个纹理。
刚刚我们仅使用一个纹理改变了我们的合成器,并且发现在合成器中可以不止一次的使用纹理。我们发现可以同时使用纹理作为输入和输出。
【 让英雄动起来 —— 交换绿色和蓝色通道 】
创建一个合成器来交换场景的绿色和蓝色通道。结果将会看下来如下图:
【 一些更复杂的东西 】
目前位置,我们只看到了比较简单的合成器。那么让我们写一个更复杂的吧。
我们需要一个新的合成器,材质和片段着色器:
1. 合成器脚本本身并没有什么特别的。我们需要场景的一个纹理,然后直接使用这个纹理作为输入来关联输出,输出使用了一个材质:
compositor Compositor7 { technique { texture scene target_width target_height PF_R8G8B8 target scene { input previous } target_output { input none pass render_quad { material Ogre3DBeginnersGuide/Comp5 input 0 scene } } } }
2. 材质本身并没有什么特别;并如往常一样仅添加一个片段着色器:
material Ogre3DBeginnersGuide/Comp5
{
technique
{
pass
{
fragment_program_ref MyFragmentShader8
{
}
texture_unit
{
}
}
}
}
3. 不要忘记在使用材质之前添加片段着色器的定义:
fragment_program MyFragmentShader8 cg
{
source Ogre3DBeginnersGuideShaders.cg
entry_point MyFragmentShader8
profiles ps_1_1 arbfp1
}
4. 现在到比较有趣的部分了。在片段着色器中,自从最后一次算起函数头没有什么改变;只有函数体改变了。首先,我们需要两个变量,称为,num和stepsize。变量stepsize是1.0除以num的结果:
float num= 50; float stepsize = 1.0/ num;
5. 然后使用两个变量和纹理坐标来计算新的纹理坐标:
float2 fragment = float2(stepsize * floor(uv.x * num),stepsize * floor(uv.y * num));
6. 使用新的纹理坐标来从纹理中检索颜色:
color = tex2D(texture, fragment);
7. 改变程序仅使用这个着色器,而不结合别的合成器。然后编译运行程序。你应看到几种不同颜色的像素替代了Sinbad正常的实例。
(译注* 不同的显卡和渲染方式因为计算浮点的方式不同,很可能显示的效果不同,如果没有看到上图的效果,可以尝试用OpenGL的方式渲染程序。)
我们创建了另一个可以改变场景外观的合成器。这个效果几乎不可辨认模型。在第一步和第二步我们已经很熟悉了,应该理解起来没有什么困难。在第四步(译注:原文错为第三步,原文类似错误后面改正后不再提醒) ,设置了我们稍后需要使用的值。一是我们将要的像素尺寸的大小,所以我们设值为50.这表示每个轴,x和y,将会有50像素。第二个值是stepsize,这个值是用1除以像素的数量。我们需要这个值来计算纹理的坐标。
在第五步,我们使用旧的纹理坐标和我们两个之前定义的两个值来计算出新的纹理坐标。那么我们如何使用片段着色器来减少像素的数目呢?此种解决方式的显示结果将会为100*100分辨率。如果用这种分辨率来渲染场景,场景将会看起来很正常而不会看到在之前例子中的单个像素。为使用更少的显示像素以获取这种效果,我们需要几个相邻的像素为同样的颜色。我们在五步中使用了一个简单的算法实现了这种效果。第一步是用原始坐标乘以我们最后想要的像素数目,然后下一步把这个浮点型值四舍五入为低位整型。这将会给出和这个像素相同的最终数目。
比如说我们场景为4*4分辨率,我们想要最终的图像只有2*2。如果我们有一原始的纹理坐标(0.1,0.1),我们用最终的矩阵分辨率乘以他们,然后得到(0.2,0.2)。然后我们四舍五入这些值为(0,0)的低位整型。这告诉我们此像素的最终像素为(0,0)。如果有一像素的坐标为(0.75,0.75),结果将会为(1.5,1.5)并且四舍五入为(1,1)。通过这个简单的操作,我们可以计算出原始像素的最终像素。
在我们知道元素像素的最终像素后,我们需要计算纹理的坐标以从原始场景纹理上检索颜色值。为实现这个,我们需要我们第二个称为stepsize的值。我们例子的stepsize的值为0.5,因为我们用1除以我们第二步预定好的像素值。然后我们用stepsize乘以不同的纹理坐标值,以获得最终的纹理坐标。
我们使用这些值从场景纹理中检索颜色值并使用这些值作为新的颜色值。我们这样做是因为四个像素有同样的颜色值,这样看起来就好像一个大的像素一般。这使得一个场景比他实际的大小有更少的像素。当然,这种技术并不完美。一个更好的方法是计算原始像素的总体平均值以计算最终像素。
【 改变像素数量】
我们现在有一个合成器可以使我们的场景看起来比实际的像素要少,但是像素的数量在片段着色器中是硬编码。当我们想要以不同的像素来使用合成器时,我们需要创建一个新的合成器,材质和片段着色器,这不是有效率的做法。第一步我们要完成的是在材质中定义像素数量而非在片段着色器中。这样,我们至少可以重用不同的数量像素着色以完成我们最终想要的图像。
现在我们将会从材质来控制像素的数量而不是从着色器本身。
1. 创建一个新的有着所有旧参数的片段着色器,但多了一为uniform的新参数,称为numpixels的浮点型形参:
void MyFragmentShader9(float2 uv : TEXCOORD0, out float4 color : COLOR, uniform sampler2D texture, uniform float numpixels) {
2. 然后,在片段着色器内部,使用新的参数来设置num变量:
float num = numpixels;
3. 着色器的其他部分和之前的片段着色器相同
float stepsize = 1.0/num; float2 fragment = float2(stepsize * floor(uv.x * num),stepsize * floor(uv.y * num)); color = tex2D(texture, fragment); }
4. 此处的材质是一几乎和原来相同的副本。只有片段着色器的声明需要改变一下;或者更准确的说,需要添加一些新的东西到default_params 代码段。在这段中,定义一个numpixels参数,这个参数为float型并且值为500:
fragment_program MyFragmentShader9 cg { source Ogre3DBeginnerGuideShaders.cg entry_point MyFragmentShader9 profiles ps_1_1 arbfp1 default_params { param_named numpixels float 500 } }
5. 现在创建一个使用新材质的新合成器,并改变程序代码使用新的合成器。稍后,编译运行程序。你将会看到一个被渲染Sinbad的实例,这个实例比原来的模型的分辨率要小。
6. 现在改变numpixels的值为25并再次运行程序。不需要重新编译,因为我们改变的是一个脚本而不是代码文件。
刚刚我们把像素数目从片段着色器中放到了想要的材质中,这样就可以允许我们无需改变和复写片段着色器来改变像素数目。
为简单的实现这个目地;我们仅需要添加一个新的uniform变量到片段着色器中并添加一个default_params代码到材质中,原本我们在材质的代码段中是需要定义变量名,类型和param_named关键字的值的。对于Ogre 3D,为了能够映射材质中的参数到片段着色器中,保持型别和名字相同是十分必要的。
【 让英雄动起来 —— 尝试不同的像素数量 】
以numpixels 的值为50,100和1000来运行程序。
【 在代码中设置变量 】
我们把numpixels变量从片段着色器代码移动到材质脚本中。现在,让我们尝试从程序代码中设置值。
我们可以使用上个例子的合成器,材质和片段着色器。只有需要程序本身修改。
1. 我们不能直接作用于渲染的四边形材质,因为我们程序不知道那个四边形。监听是唯一可以影响合成器渲染过程的途径。Ogre 3D提供了一个合成器监听的接口。我们可以使用这个来创建一个新的监听:
class CompositorListener1 : public Ogre::CompositorInstance::Listener { public:
2. 覆写这个在当材质建立时调用的方法:
void notifyMaterialSetup (uint32 pass_id, MaterialPtr &mat) {
3. 使用获取到的材质指针来改变numpixels参数为125 :
mat->getBestTechnique()->getPass(pass_id)->getFragmentProgramParameters()->setNamedConstant("numpixels",125.0f); }
4. 在添加完激活合成器的代码后,在createScene()函数中添加下面的代码以获得合成器的实例:
Ogre::CompositorInstance* comp = Ogre::CompositorManager: :getSingleton().getCompositorChain(mCamera->getViewport())- >getCompositor("Compositor8");
5. 我们需要一个private变量来稍后存储监听实例:
private: CompositorListener1* compListener;
6. 当程序创建的时候,我们需要把这个指针设为 NULL :
Example78() { compListener = NULL; }
7. 我们创建的对象,我们也需要摧毁它。添加一个摧毁监听实例的析构函数到程序中:
~Example78()
{
delete compListener;
}
8. 现在创建一个监听并添加它到合成器实例:
compListener = new CompositorListener1(); comp->addListener(compListener);
9. 编译运行程序。 你将会看到有一个有些许像素的场景,如下图显示的一般:
刚刚我们改变了我们的程序,这样就可以从程序代码中修改像素数目而不是从材质脚本中。因为合成器创建了材质和渲染,当运行时,我们没有直接的权限去控制渲染的四边形纹理,所以我们需要改变如片段着色器的属性一类的材质的权限。为了仍然可以使用他们,Ogre 3D 提供了一从监听器本身继承的监听接口。当具体的四边形产生时,这个覆写的监听器被调用。这个函数获取他生成的pass的ID和一个材质本身的指针。
通过材质指针,我们可以选择将会使用的技术,我们获取到pass和它所附带的片段着色器的参数。只要我们有了参数,我们就可以改变像素数目。这是一个相当长的调用式,因为我们想要的参数是在类继承关系的底部。补充说明一下,我们可以定义参数,也可以在程序中使用合成器一类的材质来改变他们;我们甚至可以不需要监听器,因为当使用实体时,我们可以直接获取到实体的一个材质。
【 当程序运行的时候修改像素的数量 】
我们已经在程序代码中实现了改变像素的数量;那让我们走的更远一些并让我们可以通过用户输入改变像素数目。
我们将会用到一些知识,比如用户输入和第三章(摄像机,灯光,阴影)讲的帧监听:
1. 我们的程序需要FrameListener。添加一个新的private变量来存储这个指针:
Ogre::FrameListener* FrameListener;
2. 同样的,FrameListener 也应该初始化为NULL:
Example79() { FrameListener = NULL; compListener = NULL; }
3. 它也应该以同样的方式销毁:
~Example79() { if(compListener) { delete compListener; } if(FrameListener) { delete FrameListener; } }
4. 添加函数并声明。程序的其他部分不用改变:
void createFrameListener() { FrameListener = new Example79FrameListener(mWindow,compListener); mRoot->addFrameListener(FrameListener); }
5. 在创建FrameListener之前,我们需要修改合成器监听。它需要一个private变量来存储我们场景想要的像素数量:
class CompositorListener1 : public Ogre::CompositorInstance::Listener { private: float number;
6. 在构造函数中以125初始化变量:
public: CompositorListener1() { number = 125.0f; }
7. 现在从notifyMaterialSetup改变覆写函数的名称为notifyMaterialRender,并且使用num变量来替换一个既定的值以设置像素的数量:
void notifyMaterialRender(uint32 pass_id, MaterialPtr &mat) { mat->getBestTechnique()->getPass(pass_id)->getFragmentProgramParameters()->setNamedConstant("numpixels",number); }
8. 同时为number变量声明一个getter and setter函数:
void setNumber(float num) { number = num; } float getNumber() { return number; }
9. 现在添加FrameListener,它拥有三个私有变量——input manager,和我们已知的keyboard类,和一个指向合成器着色器的指针:
class Example79FrameListener : public Ogre::FrameListener
{ private: OIS::InputManager* _man; OIS::Keyboard* _key; CompositorListener1* _listener;
10. 在构造函数中,我们需要创建我们的输入系统并且保存合成器监听器的指针:
Example79FrameListener(RenderWindow* win,CompositorListener1* listener) { _listener = listener; size_t windowHnd = 0; std::stringstream windowHndStr; win->getCustomAttribute("WINDOW", &windowHnd); windowHndStr << windowHnd; OIS::ParamList pl; pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str())); _man = OIS::InputManager::createInputSystem( pl ); _key = static_cast<OIS::Keyboard*>(_man->createInputObject( OIS::OISKeyboard, false )); }
11. 并且,如之前一样,我们需要摧毁我们创建的输入系统:
~Example79FrameListener() { _man->destroyInputObject(_key); OIS::InputManager::destroyInputSystem(_man); }
12. 覆写frameStarted()方法,在这个函数中,如果用户按下Escape键时,获取键盘的输入并关闭程序:
bool frameStarted(const Ogre::FrameEvent &evt) { _key->capture(); if(_key->isKeyDown(OIS::KC_ESCAPE)) { return false; }
13. 如果用户按下了上方向键,获取我们现在使用的像素总数值并且以1增加它。然后设置并打印这个新的值:
if(_key->isKeyDown(OIS::KC_UP)) { float num = _listener->getNumber(); num++; _listener->setNumber(num); std::cout << num << std::endl; }
14. 完成对应的步骤如果下方向键按下的话:
if(_key->isKeyDown(OIS::KC_DOWN)) { float num = _listener->getNumber(); num--; _listener->setNumber(num); std::cout << num << std::endl; }
15. 这就是全部了。现在关闭frame started函数:
return true; }
16. 编译运行程序。同时尝试不同像素数目的效果。
我们扩展程序让它可以通过场景用户使用方向键来控制像素数量。第一步和第四步中添加和创建了帧监听。在第二步初始化FrameListener和CompositorListener为NULL,并且在第三步是负责摧毁这两个指针的。在第五和第六步插入了一个新的变量到合成器监听器中,这个变量存储了想要我们的场景有的像素数目。
在第四步,我们修改之前覆写的notifyMaterialSetup函数为notifyMaterialRender。这一步十分有必要,因为notifyMaterialSetup只在材质被创建后才会调用,但是notifyMaterialRender会在每次材质得到渲染时被调用。因为想要在运行时改变像素的数量,我们需要在每次画图函数调用之前修改像素的数量。当然,一个更好的办法是当像素的数量改变的时候才修改参数。这将会更节省CPU时间,但是我们在这里例子中不需要考虑那么多。
第八步为像素数量声明了一个the getter and setter方法,并且在第九步开始声明帧监听。我们需要合成器监听可以修改像素数量的值,因此我们添加一个private指针变量来存储它到帧监听器。
在第十步,获取了CompositorListener指针并在变量中存储它并在输入系统中初始化它,这部分知识我们之前已经讲过。在第十一步我们没有做什么新的事情。在十三和第十四步中使用了getter and setter来在合成器访问函数中来操作像素数目。在最后,第十五步,完成了帧监听并且这就是所有我们需要做的了。
【 让英雄动起来 —— 减少参数的改变 】
改变程序使它仅当像素的数目改变时,材质中的参数才设置为一个新的值。而且不要使用notifyMaterialRender;换而使用notifyMaterialSetup。
【 添加一个分屏 】
目前位置,我们已经看到过如何添加一个合成器到视口,但是视口还可以做很多别的有趣的事情,比如创建一个分屏。
在做了太多有关像素的话题之后,我们现在将会添加一个分屏
1. 我们不需要上一例子的所有代码。所以删除合成器监听器和帧监听器。
2. 我们需要第二个摄像机,所以创建一个指针来保存它
private: Ogre::Camera* mCamera2;
3. createScene()函数仅需要创建一个Sinbad.mesh的实例并关联它到场景结点:
void createScene() { Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()- >createChildSceneNode(); Ogre::Entity* ent = mSceneMgr->createEntity("Sinbad.mesh"); node->attachObject(ent); }
4. 现在我们需要一个createCamera()函数,这个函数来创建一个摄像机从(0,10,20)朝向(0,0,0):
void createCamera() { mCamera = mSceneMgr->createCamera("MyCamera1"); mCamera->setPosition(0,10,20); mCamera->lookAt(0,0,0); mCamera->setNearClipDistance(5);
5. 现在使用新的摄像机指针来存储另一个摄像机,虽和上一个摄像机看向同一点,但是这个是从位置(20,10,0)来看:
mCamera2 = mSceneMgr->createCamera("MyCamera2"); mCamera2->setPosition(20,10,0); mCamera2->lookAt(0,0,0); mCamera2->setNearClipDistance(5); }
6. 我们有摄像机,但是我们现在不需要视口,所以覆写createViewport()方法:
void createViewports() {
7. 使用第一个摄像机创建一个视口来覆盖渲染屏幕的左半部分:
Ogre::Viewport* vp = mWindow->addViewport(mCamera,0,0.0,0.0,0.5,1.0); vp->setBackgroundColour(ColourValue(0.0f,0.0f,0.0f));
8. 然后使用第二个摄像机创建第二个视口来覆盖渲染屏幕的右半部分:
Ogre::Viewport* vp2 = mWindow->addViewport(mCamera2,1,0.5,0.0,0.5,1.0); vp2->setBackgroundColour(ColourValue(0.0f,0.0f,0.0f));
9. 两个摄像机需要正确的纵横比;否则图像将会看起来很奇怪:
mCamera->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight()));
mCamera2->setAspectRatio(Real(vp2->getActualWidth()) / Real(vp2->getActualHeight()));
10. 编译运行程序。你将会从两个不同的视角看到相同的实例。
刚刚我们给程序创建了两个视口;每个视口都有一个摄像机从不同的地方来观察我们的模型实例。因为我们想要从不同的地方观察模型,每个视口都需要自己的摄像机。因此,我们在第二步创建了一个新指针来保存我们第二个摄像机。在第三步仅创建了一个包含一个模型的简单场景。在第四步重写了createCamera() 函数并且创建了我们第一个摄像机,这个摄像机在位置(0,10,20)朝向(0,0,0)。这意味着这个摄像机朝向沿着Z轴,对于例子来说,朝向模型的身前。在第五步在(20,10,0)处创建了一个摄像机,这个摄像机朝向沿着X轴。在第六步覆写了createViewports()函数,这个函数在稍后的步骤中才会插入代码。在第七步创建了第一个视口,并添加第一个摄像机到RenderWindow。这是通过addViewport()函数完成的。此函数的第一个参数是传递显示的图像的摄像机。第二个参数是哪个视口有更高的优先级,当视口重叠的时候这个就会起作用。第三个和第四个参数是定义视口的开始点,并且第五和第六个参数是定义高度和宽度,每个参数的范围为0到1.下面的图片显示了我们的渲染窗口的视口是如何建立的。
第九步仅设置了每个摄像机的纵横比,通过使用视口得到的宽度和高度信息。
同样,如果我们尝试用鼠标和键盘移动摄像机,我们可能会注意到我们仅可以控制左边的视口。这是因为只有摄像机的指针mCamera是由默认的帧监听来控制的。如果我们想要控制所有的摄像机,我们需要修改ExampleFrameListener。
【 让英雄动起来 —— 用视口做更多的事 】
创建一个有四个视口的程序 —— 一个是看前面,一个看后面,一个看左边,一个看右边。结果应该看起来如下图:
【 选择一个颜色通道 】
我们将会使用之前的代码,但是我们将会对其进行一些删改:
1. 首先创建一个片段着色器。除了正常的参数,添加一个float4类型的uniform参数来存储颜色通道的因子。使用这些因子来乘以颜色,我们将会从原始的场景纹理中检索颜色:
void MyFragmentShader10(float2 uv : TEXCOORD0, out float4 color : COLOR, uniform sampler2D texture, uniform float4 factors ) { color = tex2D(texture, uv); color *= factors; }
2. 使用片段着色器来创建一个新的材质并且用默认的值(1,1,1,0)来添加。这意味着如果没有改变的参数,场景将会正常渲染:
fragment_program MyFragmentShader10 cg { source Ogre3DBeginnerGuideShaders.cg entry_point MyFragmentShader10 profiles ps_1_1 arbfp1 default_params { param_named factors float4 1 1 1 0 } } material Ogre3DBeginnersGuide/Comp7 { technique { pass { fragment_program_ref MyFragmentShader10 { } texture_unit { } } } }
3. 然后使用这个材质添加一个合成器:
compositor Compositor9 { technique { texture scene target_width target_height PF_R8G8B8 target scene { input previous } target_output { input none pass render_quad { material Ogre3DBeginnersGuide/Comp7 input 0 scene } } } }
4. 我们有三个颜色通道,所以我们将会需要三个合成器监听来相应的改变参数。首先,添加一个红色通道的监听。仅设置颜色因子在材质建立时并且我们不需要在运行时改变它们:
class CompositorListener2 : public Ogre::CompositorInstance::Listener { public: void notifyMaterialSetup (uint32 pass_id, MaterialPtr &mat) { mat->getBestTechnique()->getPass(pass_id)->getFragmentProgramParameters()->setNamedConstant("factors",Ogre::Vector3(1,0,0)); } }
5. 现在,添加绿色和和蓝色颜色通道:
class CompositorListener3 : public Ogre::CompositorInstance::Listener { public: void notifyMaterialSetup (uint32 pass_id, MaterialPtr &mat) { mat->getBestTechnique()->getPass(pass_id)->getFragmentProgramParameters()->setNamedConstant("factors",Ogre::Vector3(0,1,0)); } }; class CompositorListener4 : public Ogre::CompositorInstance::Listener { public: void notifyMaterialSetup (uint32 pass_id, MaterialPtr &mat) { mat->getBestTechnique()->getPass(pass_id)->getFragmentProgramParameters()->setNamedConstant("factors",Ogre::Vector3(0,0,1)); } };
6. 替换摄像机指针,添加四个视口到程序中:
class Example83 : public ExampleApplication { private: Ogre::Viewport* vp; Ogre::Viewport* vp2; Ogre::Viewport* vp3; Ogre::Viewport* vp4;
7. 创建一个我们将会使用的摄像机并且把他放到可以看到Sinbad前面的位置:
void createCamera() { mCamera = mSceneMgr->createCamera("MyCamera1"); mCamera->setPosition(0,10,20); mCamera->lookAt(0,0,0); mCamera->setNearClipDistance(5); }
8. 修改createViewport() 函数仅使用一个摄像机并为两个新的视口添加必要的代码:
void createViewports() { vp = mWindow->addViewport(mCamera,0,0.0,0.0,0.5,0.5); vp->setBackgroundColour(ColourValue(0.0f,0.0f,0.0f)); vp2 = mWindow->addViewport(mCamera,1,0.5,0.0,0.5,0.5); vp2->setBackgroundColour(ColourValue(0.0f,0.0f,0.0f)); vp3 = mWindow->addViewport(mCamera,2,0.0,0.5,0.5,0.5); vp3->setBackgroundColour(ColourValue(0.0f,0.0f,0.0f)); vp4 = mWindow->addViewport(mCamera,3,0.5,0.5,0.5,0.5); vp4->setBackgroundColour(ColourValue(0.0f,0.0f,0.0f)); mCamera->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight())); }
9. 添加三个指针来存储我们上面创建的合成器监听:
CompositorListener2* compListener; CompositorListener3* compListener2; CompositorListener4* compListener3;
10. 在构造函数中初始化每个为NULL:
Example83() { compListener = NULL; compListener2 = NULL; compListener3 = NULL; }
11. 并且,同样,在析构函数中删除他们:
~Example83() { if(compListener) { delete compListener; } if(compListener2) { delete compListener2; } if(compListener3) { delete compListener3; } }
12. 在createScene()函数中,在创建模型实例和场景结点之后,添加必要的代码以添加合成器到我们第一个视口,激活它,并关联它到合成器监听,这个监听仅红色被渲染:
Ogre::CompositorManager::getSingleton().addCompositor(vp, "Compositor9"); Ogre::CompositorManager::getSingleton().setCompositorEnabled(vp, "Compositor9", true); Ogre::CompositorInstance*comp= Ogre::CompositorManager::getSingleton().getCompositorChain(vp)->getCompositor("Compositor9"); compListener = new CompositorListener2(); comp->addListener(compListener);
13. 给第二和第三个视口使用同样的办法,但是使用不同的绿色和蓝色合成器监听:
Ogre::CompositorManager::getSingleton().addCompositor(vp2, "Compositor9"); Ogre::CompositorManager::getSingleton().setCompositorEnabled(vp2, "Compositor9", true); Ogre::CompositorInstance* comp2 = Ogre::CompositorManager::getSin gleton().getCompositorChain(vp2)->getCompositor("Compositor9"); compListener2 = new CompositorListener3(); comp2->addListener(compListener2); Ogre::CompositorManager::getSingleton().addCompositor(vp3, "Compositor9"); Ogre::CompositorManager::getSingleton().setCompositorEnabled(vp3, "Compositor9", true); Ogre::CompositorInstance*comp3= Ogre::CompositorManager::getSingleton().getCompositorChain(vp3)->getCompositor("Compositor9"); compListener3 = new CompositorListener4(); comp3->addListener(compListener3);
14. 现在编译运行程序。你将会看到四个完全相似的图像,仅仅是渲染的颜色通道有区别。在左上角的那个,仅仅是红色通道可视;在右上角那个,仅仅是绿色可是;在左下角的那个,是蓝色的;在右下角的那个,是有所有颜色通道的:
刚刚我们把这章例子的知识综合到一起创建了一个应用程序,它使用四个视口和一个结合了三个合成器监听合成器以观察他们每个本身的颜色通道和结合后的效果。这个例子里其实没有什么新的不同;如果需要,查阅其他的例子以理解这个例子。
【 概要 】
这章我们接触到很多有关合成器和视口的知识。
具体来说,我们学习了:
‹ 1. 如何创建合成器脚本并且如何添加他们到我们的场景。
‹ 2. 如何使用合成器和片段着色器来操作我们的场景。
‹ 3. 着色器的参数和如何在材质脚本中改变他们的值或者直接在程序代码中修改。
‹ 4. 如何结合合成器来防止我们多次的重写代码。
‹ 5. 结合我们之前所学来创建了一个可以由用户控制的合成器。
我们现在已经学习了很多有关Ogre 3D的知识;仅有一个非常重要的话题还没有涉及。目前位置,我们一直依赖着ExampleApplication。在下一章,我们将会写我们自己的ExampleApplication。