15.1 Ogre中的“二维”
早在第一章,我们就曾经向读者介绍过,Ogre是一个3D图形渲染引擎,通过Ogre可以制作出各种各样的三维动画和游戏。但是,不要以为Ogre中只能渲染跟3D相关的物体,Ogre同样可以渲染跟2D相关的物体,比如:图形、图片、文字等等。另外,在开发一些小型2D游戏(而非大型3D游戏)的时候,可能只是需要Ogre进行“二维”的编程,而无处理与“三维”有关的操作。
15.2 “表层(Overlay)”的概念
在Ogre 中,“二维”的处理总是与“表层(Overlay)”的概念密切相关,这是因为Ogre中将所有的与“二维”相关的处理都被放置在称为“表层(Overlay)”中。顾名思义,表层是实质上是一个层,这个层覆盖在场景中所有其他的物体之上,而且,还有一点需要注意的是,表层在渲染队列的最后,也就是说,表层是场景中最后被渲染的。
单纯从文字上讲,读者可能会对Ogre中“二维”、“表层”这些概念感到不好理解,其实,在我们之前章节的实例中,几乎每次都会看到Ogre中的“表层”。下面我们来看一个实例运行时的界面:
上图是我们在第五章,运行一个实例的界面,可以看到,在界面中有一个三维的“食人魔”怪兽,另外在界面的左下角和右下角,分别有两个“层”(已用红框标出),它们是“二维”的,这就是Ogre中的“表层”。
笔者注: Ogre中的表层(Overlay):一般是用来制作Gui界面的,但是这个Gui界面不具备用户交互的功能。这些表层位于整个“屏幕”的最上层,就像上图所示的那样。当然,读者可以自己设置“表层”的属性,如形状、大小、颜色和透明度等。 当然,Ogre中的表层不一定非得应用于Gui界面,如果在三维游戏中需要使用二维的画面,也可以利用表层来实现。比如,在一款三维游戏中有一面墙,上面挂着一幅风景画,那么就可以利用表层来实现这个这个二维的“墙壁加风景画”的效果。
|
还有一点要特别注意,Ogre中的二维常常通过表层来处理,但是,不是说表层只能处理与二维有关的物体,事实上,表层也可以处理与三维有关的物体。
15.3 表层的框架结构
15.3.1 基本概念
所谓的表层框架,就是一个表层整体的布局和结构,因为不同的表层上的元素都有不同的布局样式和结构层次。下面的这段脚本代码,就可以生成一个完整的表层框架:
MyOverlay { zorder 500
container Panel(MyContainer1) { metrics_mode pixels left 0 top 0 width 50 height 50
container Panel(MyContainer2) { metrics_mode pixels left 20 top 20 width 30 height 30
element TextArea(MyTextAreaOverlayElement) { metrics_mode pixels left 15 top 3 width 15 height 15
font_name StarWars char_height 0.06 colour 0.9 0.9 0.9 caption MyTextAreaOverlayElement } } } } |
通过使用这段脚本,生成的表层框架如下图所示:
从上图可以看出,表层框架中有两种不同的“物品”:Container(容器)和Element(元素),我们可以把他们统称为表层对象。顾名思义,表层对象就是存在于表层上的实实在在的对象。表层Container(容器)用来“盛放”表层Element(元素)。对于表层对象,有如下几点需要注意:
1.表层的外层必须是一个Container(容器)类型的表层对象,正如上图中的Container1。
2.表层中的Container(容器)可以相互嵌套,也就是说,Container(容器)中可以再包含一个Container(容器)。正如上图中的Container1中又包含了Container2、3、4。
那么,如何对表层中的对象进行定位呢? 我们可以在脚本的代码中看到其中的端倪。一般来说,对于每一个Element和Container,都是相对于其父容器进行定位,也就是说,看这个Element或Container盛放在哪一个容器中,比如,上图中的TextAreaOverlayElement2盛放于Container3中,那么Container3就是TextAreaOverlayElement2的父容器。
在表层对象TextAreaOverlayElement2的定义脚本中有如下一行代码:
metrics_mode relative
可以看出,TextAreaOverlayElement2表层对象使用了relative即相对定位的方法,而其他表层对象的脚本代码中使用的代码是:metrics_modepixels,即采用的是像素定位的方法。
笔者注: 在我们选择表层对象的布局方式时,只能选择相对定位法或是元素定位法中的一种,不能同时选择两种。而且,容器本身与容器中的元素可以采用不同的定位方法。 在实际应用中,多数是采用 “相对定位” 的方式对表层元素进行定位,这样做的好处是,在我们改变窗口的大小(或是表层的大小)时,表层对象的位置不会因此而发生相对的改变,另外,使用相对定位法时,我们也无需关心用户使用电脑屏幕的分辨率,因为相对定位不会因为屏幕分辨率的不同而使表层的布局结构发生改变。
|
例如,上图中的底层容器Container1被放置到了屏幕绝对位置(0,0)处,Container2被放置到了相对于Container1(也就是Container2的父容器)的(2,5)处,而元素TextAreaOverlayElement1被放置在了相对于其父容器Container2的(15,3)位置处,但如果相对于Container1来说(即电脑屏幕(0,0)处),TextAreaOverlayElement1是被放置在了(17,8)位置处。(17,8)由它本身的相对位置(15,3)和它的父容器(2,5)相加得到。
笔者注: 1.在Ogre中,表层Element类下有非常多的派生类,也就是说,表层中的元素有非常多的种类,这些派生类都包括:PanelOverlayElement,BorderPanelOverlayElement以及TextAreaOverlayElement等。但是有一点需要注意,由于表层框架中不包括文本资源的类,因此如果我们需要在表层中渲染并显示文字,需要使用到表层之外的Font与FontManager类等来处理文本的渲染与显示。 2.Ogre中的表层对象可以简单的分为两类:第一类是由Ogre进行管理的,上面的表层脚本就是其中的一个例子,这类的脚本由Ogre自身对其进行管理,多数用来进行一些简单的纹理输出与显示;第二类不由Ogre对其进行管理,比如我们在接下来会讲到的Gui界面等。这两类表层对象都可以被Ogre支持。 |
15.3.2 表层对象的属性
Ogre的表层元素类和表层容器类是可扩展的,也就是说,程序开发人员可以在基类已有功能的基础上,按照自己的需要对其功能进行扩展。
不过,也有一部分表层的属性是Ogre中自带的,这些属性包括:Panel、BorderPanel、TextArea,我们可以在脚本中改变这些属性的值,以达到改变表层的目的。下面我们就对着几个属性一一介绍:
(1)Panel(面板)
我们可以把Panel(面板)看作是一个矩形区域,这个矩形区域可以包含其他的表层对象(表层元素和表层容器)。我们可以改变面板的透明度、面板的材质等不同的属性,下面我们就来一一介绍:
· Transparent属性:该属性用来设置面板是否为透明。它有两个取值(true和false),当取值为true时表示,该面板为透明背景,且面板自己无须渲染。
· tiling(layer,x_tile,y_tile)属性: 该属性用来设置材质的纹理在整个面板上X和Y方向贴图的次数。layer是指纹理层,取值为0一直到总纹理层数-1,比如,一共有5个纹理层,那么layer的取值就是0到4,我们可以利用layer设置多个纹理层相互叠加的背景效果。x_tile,y_tile分别是纹理在整个面板上X和Y方向贴图的次数。
·uv_coords (topleft_u,topleft_v,bottomright_u,bottomright_v)属性:该属性用来设置面板上的纹理的坐标位置。
(2)BorderPanel(边缘面板)
这种面板同第一种Panel面板相比显得更“高级”,它比Panel多了一个“边框”,这个边框是独立于面板的,也就是说,这个边框的属性可以独立于面板本身单独的设定。如果读者有过HTML编程的经验,相信对HTML中表格的操作不会陌生。BorderPanel的操作方法就跟HTML中的表格边框的操作方法类似,下面我们就详细的讲解一下它具有的属性:
·border_size(left,right,top,bottom),这个属性用来设置边框的尺寸,有left,right,top,bottom四个属性值,可以为表格的“左、右、上、下”等设置不同的尺寸。
·border_material(name),这个属性用来设置边框的材质,属性通过将材质的名称(name)作为一个参数传递进去,是边框具备一种材质。
笔者注: 边框的材质信息和面板中心区域的材质信息不同,因为中心区域里的材质通常只是平铺的,而边框上的材质往往就不能这样处理,因此,边框上面的材质通常不同于中心区域的材质。 |
·border_topleft_uv(u1,v1,u2,v2),这个属性用来设置边框顶部左侧的纹理坐标,与之类似的还有border_topright_uv,border_bottomleft_uv,border_bottomright_uv三个属性值,这里就不一一介绍了。
·border_left_uv(u1,v1,u2,v2) ,这个属性用来设置边框整体的纹理坐标,与之类似的还有border_right_uv,border_top_uv,border_bottom_uv三个属性,这里也就不一一介绍了。
(3)TextArea(元素)
通过它,可以渲染并显示文本。我们可以使用Ogre中的FontManager和Font类来定义字体,或者直接使用事先编写好的.fontdef文件。TextArea(元素)有如下的几个属性:
·font_name (name),这个属性用来设置要使用字体的名字。这个字体的名字通常被定义在一个.fontdef文件中,而且,在使用它之前要脚本调用这个“名字”时,它出在可用的状态。
·char_height(height),这个属性用来设置要显示的文字占屏幕高度的百分比。
·colour (red,green,blue),这个属性用来设置要显示的文字的颜色,有红、绿、蓝三个值。这三个值的取值范围都是0到1,通过这个属性,可以让我们要显示的文字有不同的颜色,使整个画面更加生动起来。不过,如果我们使用的字体已经实现定义好了颜色,那么就不必再使用这个属性了。
·colour_bottom(red,green,blue) 和 colour_top(red,green,blue),从这个属性的名称就可以看出来,它是用来定义文字上部和底部的颜色的,通过这两个属性,可以使文字的上部和下部显示不同的颜色,从而达到一种渐变的效果。
(4)元素布局
在上文中曾经介绍过,表层对象在其父容器中的定位方式有两种:相对定位法和元素定位法。每一个表层对象在其父容器中都有两个布局属性:垂直属性和水平属性。我们可以vert_align标记来设置垂直属性,标记的取值可以是top,center或者bottom中的一个;同样的,我们可以用horiz_align标记来设置水平属性,标记的取值可以是left,middle或者right中的一个。在默认的情况下,系统会自动将表层对象定位在其父容器的top和left处,也就是父容器的左上方。
笔者注: 下面,我们要向读者介绍一个深度(Z-Order)的概念,有过HTML编程的读者都会对Div中的层深度非常熟悉,每一个Div都有一个Z-Order的属性值,Z-Order是一个层深度的概念,它用来区分不同的Div层的叠放顺序,Z-Order值小的层放置在下面,Z-Order值大的层放置在上面。同理,在Ogre中,表层也有一个Z-Order的属性。这里的Z-Order的作用就是:意义在于: z-order值较的层会被绘制在底层,而 z-order值较大的层被绘制在上层,覆盖下面的层,因此这个Z-Order值,实质上是用来决定表层的绘制顺序的。 |
15.4 有关表层的例子
15.4.1 表层的简单使用
在通过示例介绍模板的使用之前,我们先来通过一些简单的示例回顾一下表层元素的一些常用属性。需要说明一点:表层元素(OverlayElement)属性可以用在表层脚本中的container或element代码块中,它们必须各自占用一行。
第一步,在media目录下新建一个文件夹名叫MyOverlays,然后在MyOverlays文件夹下新建一个文件名叫MyOverlay1.overlay,然后在这个文件内填入以下代码:
MyOverlayTest1 { zorder 500 container Panel(MyOverlayTest1/ImagePanel) { metrics_mode pixels horz_align right vert_align bottom top -640 left -650 width 640 height 640 material MyOverlayMaterial1 } } |
这里MyOverlayTest1即为表层名,它的zorder值设置为了500,接着定义了一个container对象,类型名为Panel,它是一块能包含其它元素(或容器)的矩形区域,这里我们定义的实例名为“MyOverlayTest1/ImagePanel”。紧接着就是定义了一系列表层元素的属性:
·metrics_mode用来设置元素的大小和位置的单位,格式是metrics_mode<pixels|relative>。当采用像素模式的时候,元素的大小和位置以像素为单位进行衡量,在这种模式下,元素的位置和大小会随着电脑屏幕分辨率的变换而变换。例如:800*600分辨率下,某个元素的左上角坐标为20,20,大小为60,60。那么它将显示在屏幕左上方,但当分辨率为1024*768的时候,它的大小会发生变换,而且位置也会朝着左上方移动一下。当采用相对模式是,元素的位置和尺寸与显示分辨率无关,也就是说,元素的显示结果可以适应任何的屏幕分辨率。
·horz_align用来设置元素的水平对齐方式,其格式为:horz_align<left|center|right>。从属性的取值上可以猜想到,这个属性有可以设置3种元素的水平对齐方式:居左、居中和居右。
·left用来设置元素相对于它上一层的水平位置,其格式为:left <value>,在这里,元素的位置是相对于上一层的位置而言的。同理,top就是设置元素相对于它上一层的垂直位置。
·width/height用来设置元素的宽度和高度,它的大小是相对于屏幕大小而言的。
·material用来设置用于此元素的材质名。
第二步,由于我们在第一步中使用到了MyOverlayMaterial1这个材质,所以我们还需要自己定义这个材质。打开media/materials/scripts这个文件夹,打开文件夹下的MyTest.material这个文件(如果没有这个文件,我们就手动新建一个),然后添加一个新材质,如下所示:
material MyOverlayMaterial1 { technique { pass { lighting off scene_blend alpha_blend depth_check off texture_unit { texture leaf.png } } } } |
定义材质的内容我们这里就不多做介绍,如果读者哪里不明白可以参照材质介绍的相应章节温习一下。
第三步,需要在程序中通过代码调用相应的Overlay,同样,我们使用我们在前面章节保存的模板代码,修改createScene函数中的内容如下:
void createScene() { Ogre::Overlay*pOverlay =Ogre::OverlayManager::getSingleton().getByName("MyOverlayTest1"); pOverlay->show(); Ogre::Entity*ogreHead =mSceneMgr->createEntity("Head","ogrehead.mesh"); Ogre::SceneNode*headNode =mSceneMgr->getRootSceneNode()->createChildSceneNode(); headNode->attachObject(ogreHead); // Set ambient light mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5)); // Create a light Ogre::Light*l =mSceneMgr->createLight("MainLight"); l->setPosition(20,80,50); } |
在程序中调用Overlay的方式很简单,通过OverlayManager::getSingleton().getByName()函数获取到overlay的指针,再通过show函数打开显示开关即可。
到此为止,大部分工作我们都已完成,但是如果现在编译并运行我们的程序还不行,因为我们还忘了一个重要的步骤,就是把我们的Overlay文件的路径告诉Ogre,要不然它是找不到我们的这个文件的,还记得resource.cfg这个文件吗,到我们的工作目录下打开resource_d.cfg(如果编译的是Release版本则打开resource.cfg文件),然后在[popular]部分 添加下面一行:
FileSystem=media/MyOverlays |
保存,编译并运行我们的程序,我们会看到如下效果:
通过上图,我们可以看到在“最上面”的一层,我们贴上了一张图,这片树叶无论我们怎样移动那个Ogre头,总是被这片树叶遮挡着,这也正是我们前面所要达到的效果,因为Overlay的渲染队列的渲染操作在所有其它渲染队列之后进行。
15.4.2 模板的概念及其应用
你可以使用模板创建各种有相同属性的元素,一个模板就是一个抽象元素,而且它并不增加到某一层中,它扮演的就是一个基类,元素可以继承并取得默认属性。要创建一个模板,关键字template必须是元素定义的第一个词(在container或element之前),模板元素被创建在最外层——它并不在某一层里指定。建议你在一个单独的层中定义模板,尽管这么做并不是必要的。在单独文件中定义模板,允许有不同的外观,方便替代。
元素可以通过类似于C++继承的方式继承一个模板——通过在元素定义上使用:操作符。:操作符被放置在名字的右括号之后(以空格分隔)。要继承的模板名放在:操作符之后(也以空格分隔)。
一个模板中可以包含子模板,子模板在模板被实例化时被创建,虽然使用template关键字对于子模板来说是可选的,但是为了清楚起见还是建议使用,因为子模板本身也是要做模板的。
介绍完了模板,那么现在我们就再来通过一个示例向大家介绍一下模板的使用。
第一步,打开MyOverlay1.overlay文件,继续添加内容,我们首先定义一个模板:
template element TextArea(Overlay2/Basic2) { metrics_mode pixels left 150 top 5 width 90 height 30 font_name fangsong char_height 19 caption IMedia } |
这里有几个我们没见过的属性:font_name、char_height、caption,其中font_name <name>表示要使用的字体名,这个字体必须被定义在一个*.fontdef文件中并确保在脚本使用时它是可用的。Ogre中默认自带了一个.fontdef的文件,我们可以打开media/fonts目录查看一下是否有个sample.fontdef文件,为了我们使用的方便,我们在这个目录下也自定义一个我们自己的文件MyFont.fontdef,然后按照sample.fontdef文件里的内容写一个新的字体名,如下所示:
fangsong { type truetype source simfang.ttf size 32 resolution 96 } |
这里我们定义了一个叫做仿宋的字体,但是我们可以发现我们现在并没有simfang.ttf这个字体文件,因此我们需要到我们系统字体的安装目录把这个字体文件拷贝到我们当前这个文件夹下,以便Ogre在使用的时候能够找到,到我们系统的安装盘:\Windows\Fonts目录下,查找仿宋字体,然后拷贝到media/fonts目录下即可。这样一个新的字体名我们就创建好了。char_height <height>表示字幕的高度,虽然因为Ogre支持比例字体字母可能不尽相同,但是它会基于这个常量高度。caption是设置元素的文本标题。
第二步,定义我们的Overlay,并让其中的元素继承刚才定义的模板:
MyOverlayTest2 { zorder 200 container Panel(MyOverlayTest2/Panel2) { element TextArea (Overlay2/NewText2) : Overlay2/Basic2 { } } } |
这里我们可以发现,在实例Overlay2/NewText2中我们没有定义任何东西,而是让它继承自模板Overlay2/Basic2,这样就可以继承模板的属性。
第三步,修改应用程序代码中createScene函数中的内容如下:
void createScene() { Ogre::Overlay*pOverlay2=Ogre::OverlayManager::getSingleton().getByName("MyOverlayTest2"); pOverlay2->show(); Ogre::Entity*ogreHead =mSceneMgr->createEntity("Head","ogrehead.mesh"); Ogre::SceneNode*headNode =mSceneMgr->getRootSceneNode()->createChildSceneNode(); headNode->attachObject(ogreHead); // Set ambient light mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5)); // Create a light Ogre::Light*l =mSceneMgr->createLight("MainLight"); l->setPosition(20,80,50); } |
这样,我们的工作基本算是完成了,编译并运行程序,我们应该是可以看到屏幕左上角有一行文字显示出来的,如果你能看到这些,说明你的程序已经没问题了,但是,或许读者朋友可能会和笔者当时遇到同一个问题,程序写得明明没有问题,但是就是看不到有一行文字出现,是的,工作做到现在,我们很可能还是看不到我们想看到的那一行文字,其实读者朋友不用担心,如果你是按照前面的要求一步一步坐下来的,那么的确你写的程序时没有什么问题的,经过我们在各大论坛上的查找,确实发现了这个问题:Ogre1.7.2 text rendering有个bug,如果不resize窗口大小,文字是不会显示的。不显示字体的原因是因为我们在使用的时候其实是没有加载font的关系,我们的程序运行时,如果我们的鼠标没有被捕获,也就是说如果我们的程序可以移出程序窗口的话,可以稍微拖拽缩放一下窗口试试,字体立刻就出现了。
那么怎么解决这个问题呢?由于目前为止读者朋友可能还不太清楚怎样修改源代码,所以这里我们提供一种比较简单的解决方法,我们在创建Overlay元素之前如果能先读取一下字体,这样就可以显示文字了。在createScene函数体的最开始我们加入下面的代码:
Ogre::FontManager *pFontManager =Ogre::FontManager::getSingletonPtr(); Ogre::ResourceManager::ResourceCreateOrRetrieveResultres =pFontManager->createOrRetrieve("fangsong",Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); res.first->load(); |
这样,我们通过手动先读取一次字体的方式,就可以看到文字显示的效果了,编译并运行程序,我们应该就可以看到如下效果了:
或许读者朋友会问直接把要显示的内容写在脚本文件里了,那么我们在程序中还能实时改变吗?是的,完全可以,我们在程序代码中可以完全获取整个Overlay上的所有元素,比如,我们要在程序中把刚才显示在屏幕上的“Imedia”修改为“Hello,IMedia”,那么我们只需在createScene函数体中再加下面两行代码即可:
OverlayElement* gui=OverlayManager::getSingleton().getOverlayElement("Overlay2/NewText2"); gui->setCaption(" Hello,IMedia"); |
这里通过getOverlayElement函数即可获取到对应元素名的指针,然后即可对其修改相应内容。
15.4.3 利用表层显示中文
我们先来了解一下如何在Ogre中创建一个表层及如何在表层中添加表层对象。我们可以调用SceneManager::createOverlay函数来创建一个表层,当然我们也可以将表层定义在一个脚本中,然后通过Ogre调用这个脚本。实际应用中,多数是将表层定义在脚本中, 然后通过Ogre调用这个脚本。
笔者注: 我们可以一次定义很多个表层,这些表层默认都是不显示的。我们可以调用show()函数来设置其显示;当然,我们也可以一次显示多个表层,这就要用到Overlay::setZOrder()函数。 |
在上一个小节中,我们曾向读者介绍过表层对象的概念,那么,我们要如何在Ogre中的表层上添加表层对象呢?
在Ogre中添加一个表层对象需要调用OverlayManager::createOverlayElement函数。例如,如果我们要在表层上添加一个Panel面板,可以通过下面的代码实现:
OverlayManager::getSingleton().createOverlayElement("MyPanel","myPanel");
笔者注: 有一点需要注意,只有OverlayContainers可以直接添加进Ogre的表层中去,而且内部容器的Z-Order值要大于外部容器的Z-Order值。如果我们想在表层上添加一个面板(Panel),可以直接调用Overlay::add2D函数。 如果要在Container(容器)里面再添加表层元素或容器,可以调用OverlayContainer::addChild函数。 |
好了,了解了上面这些基本的概念之后,我们就可以利用表层,在Ogre中实现文字的显示了。在Ogre中,文字是不能被单独渲染的,它必须作为纹理,投射到表层TextAreaOverlayElement中,用这样的方法渲染并显示。我们既可以使用微软为我们提供的TrueType类型字体文件(.ttf文件)来完成对文字的操作,也可以用“图片”的方式,即将带有文字的图片作为纹理,投射到表层中进行渲染。若我们使用微软为我们提供的.ttf字体文件来显示文字,那么我们就省下了制作纹理的时间(因为微软已经为我们创建好了纹理),不过,这种方法也有弊端,那就是Ogre在渲染这些纹理的时候要花费非常多的时间,从而会影响渲染效率。若我们使用了“图片”的方式,来显示文本,Ogre渲染纹理的时间就会大大缩短,不过,这些“图片”都需要用户自己手动制作,这就增加了前期处理的时间。在实际应用中,读者要充分权衡这两种方法的优点和缺点,以选择最适合自己程序的那种方法。
说了这么多,到底怎样在Overlay上显示中文呢?或许读者朋友尝试过,但是总是显示不正确或是出错,这里笔者也把网友提供的一种显示中文的技巧和大家分享,希望对大家有用。例如,我们想在窗口上显示这么一句话:“你好,IMedia”。
第一步,打开前面我们新建的Myfont.fontdef文件,修改为如下:
fangsong { type truetype source simfang.ttf size 32 resolution 96 code_points 20320-20320 22909-22909 65292-65292 73-73 77-77 101-101 100-100 105-105 97-97 } |
第二步,修改应用程序代码中的gui->setCaption(" Hello,IMedia");为:gui->setCaption(L"你好,IMedia"); 注意字符串前加上一个大写的L,表示将ANSI字符串转换成unicode的字符串,就是每个字符占用两个字节。
编译并运行程序,你应该可以看到中文显示出来了:
相信读者朋友最大的疑问在于MyFont.fontdef文件里新加的那几行内容了。我们新加的code_points就是要使用的Unicode字符编码,例如,‘你‘这个字的编码用16进制表示是4F60,转换成10进制就是20320,在Ogre字体定义中使用的是10进制,根据Ogre字体定义文档的描述,这个’你‘字需要在文件中描述成20320-20320这样的形式。你可以到http://www.chi2ko.com/tool/CJK.htm查看每个字符对应的编码。当然为了自动生成Ogre字体code_points,网上热心的网友自己动手写了一个自动转换的软件,大家可以到这里去下载:
http://files.cnblogs.com/gogoplayer/Ogre/UnicodeViewer.rar
http://files.cnblogs.com/gogoplayer/Ogre/UnicodeViewer.rar
15.4.4 在表层中加入3D
介绍了关于Overlay的这些内容,相信读者朋友应该对Overlay的基本概念有了一定的了解,更多的技巧还需读者朋友更多的实践经验。下面我们来分析本节的最后一个问题,也是读者朋友一直关心的,就是如何向Overlay中加入3D。
虽然Overlay一般是用来处理2D界面的,如GUI界面。但有些时候,我们想做些特别的界面,比如左上角加入一个人物状态框,上面可以有人物动作,人物可以旋转观察等。那么我们该怎样向Overlay中添加3D实体呢?下面我们分几个步骤来讲:
第一步,移出前面我们在程序中向createScene函数中增加的所有代码,增加如下内容:
void createScene() { Ogre::Overlay*pOverlay1=Ogre::OverlayManager::getSingleton().getByName("MyOverlayTest1"); pOverlay1->show(); Ogre::Overlay*pOverlay =Ogre::OverlayManager::getSingleton().getByName("MyOverlayTest3"); Ogre::Entity*ogreHead =mSceneMgr->createEntity("Head","ogrehead.mesh"); headNode = new Ogre::SceneNode(mSceneMgr,"PointerNode"); headNode->attachObject(ogreHead); headNode->setPosition(30,0,-160); ogreHead->setMaterialName("Examples/DepthCheck1"); pOverlay->add3D(headNode); pOverlay->show(); Ogre::Entity*ogreHead2 =mSceneMgr->createEntity("Head2","ogrehead.mesh"); Ogre::SceneNode*headNode2 =mSceneMgr->getRootSceneNode()->createChildSceneNode(); headNode2->attachObject(ogreHead2); headNode2->scale(3,3,3); // Set ambient light mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5)); // Create a light Ogre::Light*l =mSceneMgr->createLight("MainLight"); l->setPosition(20,80,50); } |
在这里得注意:headNode这个场景结点不能使用SceneNode::createChildSceneNode方法来创建结点(程序运行会报错),也不能使用SceneManager::createSceneNode方法来创建结点(这样做其实是可以显示出来的,但当对此结点进行旋转、缩放等操作时,实体会显示异常)。也就是说,我们在程序结束时要手动释放掉mPointerNode。
第二步,添加第一步中用到的材质“Examples/DepthCheck1”:
material Examples/DepthCheck1 { technique { pass { depth_check off texture_unit { texture RustySteel.jpg } texture_unit { texture spheremap.png colour_op_ex add src_texture src_current colour_op_multipass_fallback one one env_map spherical } } } } |
这里要注意,在材质中应该把深度检查关闭,不然可能被场景中的其它3D物体遮住。除了这种方式外,如果我们不想修改原来实体的材质,可以在程序中通过遍历实体各个子实体对应的材质通道关闭其深度检查的方式,也可以达到相同的效果。
第三步,添加一个protected成员变量:
Ogre::SceneNode*headNode; |
第四步,在构造函数中初始化成员变量:
headNode = NULL; |
第五步,在析构函数中添加如下内容:
delete headNode; |
编译并运行程序,我们将看到如下效果,我们的实体模型在Overlay上面了:
读者朋友可以再添加一些正常情况的实体,通过移动鼠标和键盘的方式观察,可以发现无论怎么移动,我们在Overlay上定义的这个实体都是在最前面的。我们还可以通过旋转或移动headNode改变这个实体的状态。
15.4.5 为场景添加背景
前面部分我们一直在讨论的都是Overlay,但是有时我们的要求并不仅限于像使用Overlay那样一直把需要展示的东西放在窗口的最前面,也就是说,有的时候我们可能想把二维的一些显示效果放在我们要渲染的模型的背后,就上前面图中的那样,树叶在第一个Ogre头的后面,或许读者会问,这样的效果我们不是已经做到了,其实读者朋友不妨细细回味一下,之所以出现这样的效果是因为第一个Ogre头和树叶的渲染,我们都把它们放在了Overlay里面,而我们的不可能把所有的3D模型都放在Overlay里面,如果全部都采用这样的方式,无异于弄巧成拙,增加自己的工作量。或许读者朋友又会想到另一种方式:我们不是可以设置视口的背景吗,或者像添加天空盒那样都可以实现啊,但是读者朋友想象一下,如果我们想在背景和3D模型之间再放一些东西该怎么实现,如下图所示:
如果我们想达到上图中所示的效果,我们可以Ogre中的另一个类Rectangle2D,它相当于一个Overlay一样的二维的平面,不过它可以灵活改变自己的渲染队列次序,我们可以把它放到模型后面渲染。
接着前面章节的代码,我们继续添加内容,在createScene函数体中继续添加以下内容:
1 2 3 4 5 6 7 8 9 |
mSceneMgr->setSkyBox(true,"Examples/SpaceSkyBox");//设置天空盒 Ogre::Rectangle2D *rect2d =newOgre::Rectangle2D(true); rect2d->setCorners(-0.8f, 0.5f, 0.2f, -0.5f); rect2d->setMaterial("MyOverlayMaterial1"); rect2d->setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY+1); rect2d->setBoundingBox(Ogre::AxisAlignedBox(-100000.0*Ogre::Vector3::UNIT_SCALE, 100000.0*Ogre::Vector3::UNIT_SCALE)); Ogre::SceneNode*node =mSceneMgr->getRootSceneNode()->createChildSceneNode(); node->attachObject(rect2d); |
代码分析:
第1行:设置天空盒,也就是上图中我们看到的天空背景;
第2行:定义一个Rectangle2D对象,构造函数我们传递一个参数true,表示包含纹理坐标;
第3行:setCorners是设置二维矩形的相对大小,主要由四个参数,分别为左、上、右、下坐标,坐标范围如下图所示:
第4行:设置二维矩形所使用的材质。注意,在我们定义的这个材质中应该把深度检查关闭,不然可能被场景中的其它3D物体遮住(在Material的Pass中设置depth_check off来关闭深度检查),前面我们也提到过除了这种方式外,如果我们不想修改原来实体的材质,可以在程序中通过遍历实体各个子实体对应的材质通道关闭其深度检查的方式也可以达到相同的目的。比如说,我们这里使用的MyOverlayMaterial1材质中没有在pass通道中加上depth_check off这句,那么我们完全可以在程序代码中修改材质,如下所示:
MaterialPtr material=MaterialManager::getSingleton().getByName("MyOverlayMaterial1"); material->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); |
通过这样的设置同样可以达到禁用深度检查的目的。
第5行:这里就是我们前面提到过的Rectangle2D可以灵活改变自己要渲染的时机,我们可以查看一下Ogre中渲染队列中各个不同层次,setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY+1);的意思就是让我们的二维矩形放在下图中红色箭头处的次序进行渲染,正是因为这样,我们的二维矩形才会在其它3D模型之前渲染:
enum RenderQueueGroupID { /// Use this queue for objects which must be rendered first e.g. backgrounds RENDER_QUEUE_BACKGROUND = 0, /// First queue (after backgrounds), used for skyboxes if rendered first RENDER_QUEUE_SKIES_EARLY = 5, RENDER_QUEUE_1 = 10, RENDER_QUEUE_2 = 20, RENDER_QUEUE_WORLD_GEOMETRY_1 = 25, RENDER_QUEUE_3 = 30, RENDER_QUEUE_4 = 40, /// The default render queue RENDER_QUEUE_MAIN = 50, RENDER_QUEUE_6 = 60, RENDER_QUEUE_7 = 70, RENDER_QUEUE_WORLD_GEOMETRY_2 = 75, RENDER_QUEUE_8 = 80, RENDER_QUEUE_9 = 90, /// Penultimate queue(before overlays), used for skyboxes if rendered last RENDER_QUEUE_SKIES_LATE = 95, /// Use this queue for objects which must be rendered last e.g. overlays RENDER_QUEUE_OVERLAY = 100, /// Final possible render queue, don't exceed this RENDER_QUEUE_MAX = 105 }; |
而我们再来看一下Overlay的渲染次序在后面,因此才会在3D模型渲染之后进行。
第6行:设置边界盒,这里我们设置边界盒的范围很大的目的是希望二维矩形总是保持可见的。
其它几行的代码都是我们前面介绍过的,这里就不再赘述。
15.5 合成器与合成器链
15.5.1 合成器
合成器(Compositor framework)是Ogre新加入的一个特性,它是Ogre API的一部分,用户可以通过它轻松的实现后加工处理效果。简单的说,Ogre中的合成器是用来处理Ogre中的特效的,通过它,可以用来完成多种多样的特效。例如,我们可以通过合成器实现让一个表面暗淡的金属周围发光的特效;也可以通过合成器实现场景中的某个位置的朦胧效果等等。
在Ogre中使用合成器,通常也是通过“脚本”来实现的,就像上一节中讲到的表层的使用方法一样,我们可以预先定义一种合成器脚本,然后在程序中调用它,以实现某种特效。Ogre中有一些自带的合成器脚本文件,我们可以在Samples/Common/Media/materials/scripts目录中找到这些脚本文件。进入上述目录,我们打开其中的脚本文件,看看其中的代码(注意:代码很长,这里只列出了一部分,读者可以在自己的电脑上打开这个文件并查阅其中的代码):
//Dark Sylinc's Bloom compositor Bloom { technique { // Temporary textures texture rt_output target_width target_height PF_R8G8B8 texture rt0 target_width_scaled 0.25 target_height_scaled 0.25 PF_R8G8B8 texture rt1 target_width_scaled 0.25 target_height_scaled 0.25 PF_R8G8B8
target rt_output { // Render output from previous compositor (or original scene) input previous }
target rt0 { // Start with clear texture input none // Horizontal blur pass pass render_quad { // Renders a fullscreen quad with a material material Ogre/Compositor/BrightPass2 input 0 rt_output } }
target rt1 { // Start with clear texture input none // Horizontal blur pass pass render_quad { // Renders a fullscreen quad with a material material Ogre/Compositor/BlurV input 0 rt0 } }
target rt0 { // Start with clear texture input none // Horizontal blur pass pass render_quad { // Renders a fullscreen quad with a material material Ogre/Compositor/BlurH input 0 rt1 } }
target_output { // Start with clear output input none // Draw a fullscreen quad pass render_quad { // Renders a fullscreen quad with a material material Ogre/Compositor/BloomBlend2 input 0 rt_output input 1 rt0 } } } }
compositor Glass { technique { texture rt0 target_width target_height PF_R8G8B8
target rt0 { input previous }
target_output { // Start with clear output input none
pass render_quad { material Ogre/Compositor/GlassPass input 0 rt0 } } } }
compositor "Old TV" { technique { texture rt0 target_width target_height PF_R8G8B8
// render scene to a texture target rt0 { input previous }
target_output { // Start with clear output input none
pass render_quad { // convert the previous render target to a black and white image, add some noise, distort it, // then render to scene aligned quad material Ogre/Compositor/OldTV input 0 rt0 } } } } |
上面的代码中列出了三个合成器脚本,它们分别是compositor "Old TV"、compositor Glass和compositor Bloom。在代码中compositor "OldTV"的定义中,“inputnone”这句代码的作用是使使用合成器中的目标将存在于视口中的内容忽略;“pass render_quad”这句代码的作用是高速合成器将通路渲染到整个目标区域中来,在这里,与render_quad同级的取值有如下几种:
·render_quad:将通路渲染到整个目标区域中来。
·render_scene:只将屏幕显示的内容渲染到目标区域中来。
·tencil:将模板缓存中的内容进行渲染。
·clear:将这个值传入使用合成器的目标的缓存中。
15.5.2 合成器应用实例1
那么,了解了这么多关于合成器的基本概念,合成器在Ogre中是如何实现的呢?Ogre中和合成器的运作流程是什么样子的呢?下面我们通过一个实例来详细看一下。
首先,我们准备一个场景,用来展示不同的合成特效。
同样,我们使用我们在前面章节保存的模板代码,修改createScene函数中的内容如下:
void createScene() { Ogre::Entity*ogreHead =mSceneMgr->createEntity("Head","ogrehead.mesh"); Ogre::SceneNode*headNode =mSceneMgr->getRootSceneNode()->createChildSceneNode(); headNode->attachObject(ogreHead); // Set ambient light mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5)); // Create a light Ogre::Light*l =mSceneMgr->createLight("MainLight"); l->setPosition(20,80,50); } |
为了更好的理解合成器中的各个概念,我们还是像以前一样,先通过一个简单的示例来逐步讲解,让大家熟悉整个的添加过程。
第一步,在media/materials/scripts目录下在新键一个材质文件名叫MyTest3.material,并添加一个新材质,如下所示:
material MyCompositorMaterial1 { technique { pass { texture_unit { } } } } |
这里我们仅仅定义了一个空的材质,没有附加任何特别的东西。
第二步,在media/materials/scripts目录下,新建一个文件名叫MyCompsitors.compositor,然后增加如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
compositor MyCompositor1 { technique { texture scene target_width target_height PF_R8G8B8 target scene { input previous } target_output { input none pass render_quad { material MyCompositorMaterial1 input 0 scene } } } } |
第三步,在应用程序代码中的createScene函数中继续增加如下代码:
Ogre::CompositorManager::getSingleton().addCompositor(mCamera->getViewport(),"MyCompositor1"); Ogre::CompositorManager::getSingleton().setCompositorEnabled(mCamera->getViewport(),"Myompositor1",true); |
至此,所有的工作已经完成了,编译并运行程序,你会发现和原来没有添加这些时的效果是一样的,读者朋友不用担心,其实我们的合成器框架已经开始工作了,下面我们就结合上面的内容逐步给大家讲解各部分的概念及整个的运作流程。
笔者注: 合成器脚本是在资源组被初始化时装载的,Ogre会在所有与组相关的资源位置搜索“*.compositor”后缀的文件,并解析它。我们可以像上面第二步中那样定义合成器脚本,多个合成器也可能被定义在单独的一个脚本里。 脚本中的每个合成器必须有一个名字,而且这个名字必须是唯一的,它可以包括路径字符以便逻辑上区分你的合成器,也为了避免重名,但是引擎并不将这个名字分级对待,而仅作为字符串处理。名字里也可以包含空格,但是如果这样的话,就必须用双引号括起来,如compositor "My Name"。 |
15.5.3 合成器应用实例2
前面虽然我们把场景渲染成了一张纹理,但是并没有修改它,现在我们对它做一些工作,来看到不同的效果。
这里我们使用shader着色程序来修改我们的纹理。
第一步,修改MyCompositorMaterial1材质为如下内容:
material MyCompositorMaterial1 { technique { pass { fragment_program_ref MyCompFragmentShader1 { } texture_unit { } } } } |
这里我们添加了对片断程序MyCompFragmentShader1的引用。
第二步,定义我们的片断程序,注意,要放在上面材质定义的内容之前,因为我们在材质中引用它的时候要保证以及定义:
fragment_program MyCompFragmentShader1 cg { source Ogre3DCompShaders.cg entry_point MyCompFragmentShader1_Main profiles ps_2_0 arbfp1 } |
第三步,我们可以看到第二步中我们使用的cg文件名叫Ogre3DCompShaders.cg,因此在media\materials\programs目录下,新建一个文件Ogre3DCompShaders.cg,然后填入以下内容:
void MyCompFragmentShader1_Main(float2 uv : TEXCOORD0, out float4 color : COLOR, uniform sampler2D texture) { float4 temp_color = tex2D(texture,uv); float greyvalue = temp_color.r * 0.3 + temp_color.g * 0.59 + temp_color.b * 0.11; color = float4(greyvalue,greyvalue,greyvalue,0); } |
编译并运行程序,你会看到如下效果:
代码分析:
这里我们可以发现由于我们增加了一个片断着色到我们的合成器中,最终渲染出来的效果变成了灰度图的效果,shader着色编程的知识我们这里就不多做赘述,如果读者哪里不明白可以参考GPU编程一章。回去查看前面合成器的工作流程我们可以发现,我们在修改纹理阶段通过在我们的材质中加入一个片段着色程序,的确达到了修改的目的,而片段着色程序中所完成的工作就是查询相应纹理坐标处对应的颜色,然后通过相应的公式把对应的RGB通道颜色转换成最终的灰度颜色,而我们计算时乘以的那些系数表示的是各种颜色通道的贡献值,研究发现程序这几个系数最终呈现的灰度图将会是一种比较好的效果。
如果我们把片断着色程序中最后一行输出颜色的公式改为如下:
color = float4( 1.0 - temp_color.r,1.0 - temp_color.g, 1.0 - temp_ color.b,0); |
我们然后运行程序会发现整个屏幕窗口中的颜色和最开始的相比都翻转过来了。
15.5.4 合成器链
合成器链,顾名思义,是一个由若干合成器组成的链,读者可以想成将这些合成器收尾相连形成一个链,前一个的输出是后一个的输入,这样一来,我们可以利用这个合成器链生成更复杂的特效,因为用合成器链处理的特效是所有链上的合成器的特效的叠加。例如合成器脚本1→2→3→4→5,当我们利用这个脚本实现某种3D特效的时候,分别经由1、2、3、4、5这五个合成器,然后将各自的效果叠加形成最终的效果,其中,各个合成器之间是通过“纹理传送”的方式进行通信的。另外,合成器可以通过调用脚本的方式实现。而且,合成器效果也可以从代码中直接创建。
在15.2.1小节中,我们曾向读者介绍过,合成器的设计是脚本的形式,即独立于Ogre单独设计,最后通过Ogre调用就可以。在Ogre中,合成器是通过CompositorManager类来管理的,既然合成器链有很多合成器组成,那么合成器链本质上来说也是一个合成器。在合成器链中包含很多以脚本的形式载入的CompositorInstance类的实例,我们可以从这些实例中选择一个,并将其与某一个“视口”绑定。也就是说,绑定之后的实例就被挂接你可以把其中一个CompositorChain类的对象中,而且这个实例被CompositorManager类来管理。与有关合成器的操作息息相关的,还有以下几个类:CompositionTechnique(技术)、CompositionPass(通路)、CompositionTargetPass(目标通路)等,这些类都负责参与对合成器的管理和操作。
15.5.5 合成器链使用的实例
读者朋友或许会想这样的效果是不是太单调了,其实合成器的功能远不止这些,下面我们再深入一些,介绍一些怎么组合两个合成器效果。
现在我们想让前面我们实现的灰度效果和翻转颜色效果进行结合,最终呈现一种新的效果,因此为了达到这个效果,我们先做一些重复性的工作:
第一步,在MyTest3.material中复制MyCompFragmentShader1材质MyCompositorMaterial1的内容创建一个新的MyCompFragmentShader2和材质MyCompositorMaterial2(除了名称外,其它内容基本一样):
fragment_program MyCompFragmentShader2 cg { source Ogre3DCompShaders.cg entry_point MyCompFragmentShader2_Main profiles ps_2_0 arbfp1 }
material MyCompositorMaterial2 { technique { pass { fragment_program_ref MyCompFragmentShader2 { } texture_unit { } } } } |
第二步,再Ogre3DTestShaders.cg中恢复MyCompFragmentShader1_Main中的内容,把翻转颜色的内容恢复为原来的灰度图内容:
color = float4(greyvalue,greyvalue,greyvalue,0); |
新建一个函数MyCompFragmentShader2_Main使其内容为计算翻转颜色的着色程序:
void MyCompFragmentShader2_Main(float2 uv : TEXCOORD0, out float4 color : COLOR, uniform sampler2D texture) { float4 temp_color = tex2D(texture,uv); color = float4( 1.0 - temp_color.r,1.0 - temp_color.g, 1.0 - temp_color.b,0); } |
第三步,在MyCompositors.compositor文件中新定义一个合成器名叫MyCompositor2,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
compositor MyCompositor2 { technique { texture scene target_width target_height PF_R8G8B8 texture temp target_width target_height PF_R8G8B8 target scene { input previous } target temp { pass render_quad { material MyCompositorMaterial1 input 0 scene } } target_output { input none pass render_quad { material MyCompositorMaterial2 input 0 temp } } } } |
第四步,把应用程序代码中使用MyCompositor1的两处地方修改为MyCompositor2,编译并运行程序,你将会看到如下黑白效果和翻转颜色结合的效果:
代码分析:
在这个新的合成器中,我们需要两个纹理,一个用来存储场景,另一个用来存储一些临时结果(第5、6行)。对于场景纹理,我们和以前的内容一样,对于临时纹理,我们用来填充场景纹理和我们定义的黑白材质,接着使用临时材质和翻转颜色材质创建我们最后的输出纹理。从场景渲染的流程中可以看出,这一次我们首先创建了一个“中间纹理”,将其作为渲染目标作为接受程序中的“黑白纹理”,然后将这两次纹理的叠加结果输出,作为最终的显示效果。
好了,了解了上面这些关于合成器的基础知识,现在让我们来看一些更复杂的操作。
首先,为了思路的清晰,我们还是重新写脚本(虽然大部分内容还是一样),之所以不在原来的脚本上修改,是为了帮读者朋友理清思路,如果大家对这整个过程都已十分了解,不妨在原来的脚本基础上修改。
第一步,我们需要在MyCompositors.compositor文件中定义新的合成器脚本名叫MyCompositor3,和原来的内容基本一样:
compositor MyCompositor3 { technique { texture scene target_width target_height PF_R8G8B8 target scene { input previous } target_output { input none pass render_quad { material MyCompositorMaterial3 input 0 scene } } } } |
第二步,在我们需要在MyTest3.material文件中中定义一个片断着色程序MyCompFragmentShader3和一个新的材质名叫MyCompositorMaterial3,和原来的内容基本一样:
fragment_program MyCompFragmentShader3 cg { source Ogre3DCompShaders.cg entry_point MyCompFragmentShader3_Main profiles ps_2_0 arbfp1 } material MyCompositorMaterial3 { technique { pass { fragment_program_ref MyCompFragmentShader3 { } texture_unit { } } } } |
第三步,在Ogre3DCompShaders.cg中定义一个新的函数名叫MyCompFragmentShader3_Main,填入以下内容:
void MyCompFragmentShader3_Main(float2 uv : TEXCOORD0, out float4 color : COLOR, uniform sampler2D texture) { float num = 50; float stepsize = 1.0/num; float2 fragment = float2(stepsize * floor(uv.x * num),stepsize * floor(uv.y * num)); color = tex2D(texture,fragment); } |
第四步,修改应用程序中的代码,把对合成器名的两处调用换成MyCompositor3即可,最后编译并运行程序,你将会看到如下效果:
代码分析:
我们可以看到,最终看到的画面基本无法识别了,其实原因很简单,因为我们在片段着色程序中对纹理做了修改,从而影响到了合成器的最终效果。由于其它部分的脚本内容我们都已经见过,所以这里我们主要分析一下MyCompFragmentShader3_Main函数中的内容,正是因为这几行代码才影响了我们程序最终的显示效果。
函数体中第一行代码,我们定义了一个变量num,它代表了我们想要显示的像素个数,这里设置为50意味着每一个轴(x、y轴)上将由50个像素;第二行代码定义了第二个变量stepsize,我们用1除以像素的数量给它赋值,后面我们需要用这个值来计算纹理坐标。在这个函数中,我们要达到的功能时减少最终要显示的像素数量,原理很简单,我们是让一些相邻的像素具有同样的颜色。(注意:floor函数是对输入参数向下取整。例如floor(float(1.3))返回的值为1.0)。
如果我们想在程序中手动改变最终想要显示的像素的数量,我们可以把对应的参数修改写到我们的程序中,修改MyCompFragmentShader3_Main函数的参数列表及其函数体中的内容如下:
void MyCompFragmentShader3_Main(float2 uv : TEXCOORD0, out float4 color : COLOR, uniform sampler2D texture, uniform float numpixels //新添加 ) { float num = numpixels; //修改部分 float stepsize = 1.0/num; float2 fragment = float2(stepsize * floor(uv.x * num),stepsize * floor(uv.y * num)); color = tex2D(texture,fragment); } |
然后,到MyTest3.material中修改片段着色部分的代码,增加一个默认参数块:
fragment_program MyCompFragmentShader3 cg { source Ogre3DCompShaders.cg entry_point MyCompFragmentShader3_Main profiles ps_2_0 arbfp1 //默认参数块开始 default_params { param_named numpixels float 50 } //默认参数块结束 } |
现在问题来了,我们该怎么在程序代码里修改“numpixels”这个参数呢?当然通过Entity我们也可以获取对应的材质从而达到修改着色器变量的目的,不过,这里我们介绍在合成器中使用的一种新的方法。因此,这里我们还需要在做一些工作才能达到我们的目的。
第一步,让我们的程序继承一个新的类Ogre::CompositorInstance::Listener如下所示:
class Example1 : public ExampleApplication,Ogre::CompositorInstance::Listener |
这是一个合成器监听器类,该类有两个主要的函数notifyMaterialSetup和 notifyMaterialRender,notifyMaterialSetup函数在编译渲染目标操作所包含的材质时激活;notifyMaterialRender 函数在编译渲染目标操作所包含的材质前激活,因此材质参数是可以变化的。
第二步,这里我们重写notifyMaterialSetup函数,如下所示:
void notifyMaterialSetup(uint32 pass_id, MaterialPtr &mat) { mat->setcu mat->getBestTechnique()->getPass(pass_id)->getFragmentProgramParameters()->setNamedConstant("numpixels",125.0f); } |
在这个函数体中我们通过传递过来的材质指针就可以取到材质中使用的着色器变量,从而对其进行修改。
第三步,修改createScene函数体中关于合成器调用部分的代码,如下所示:
Ogre::CompositorManager::getSingleton().addCompositor(mCamera->getViewport(),"MyCompositor3"); Ogre::CompositorManager::getSingleton().setCompositorEnabled(mCamera->getViewport(),"MyCompositor3",true); Ogre::CompositorInstance*comp =Ogre::CompositorManager::getSingleton().getCompositorChain(mCamera->getViewport())->getCompositor("MyCompositor3"); comp->addListener(this);//注册监听器 |
由于我们继承了合成器实例的监听器类,因此这里注册之后,我们对着色器参数做出的修改(numpixels已经由原来的50修改修改为了125),就会影响到最终显示的效果,编译并运行程序,你会看到如下效果,由于数量增加了,所以比原来稍微清楚了一下,你可以修改这个数量,值越大最终显示的效果越清晰:
笔者注: 由于我们可以在程序中修改我们的着色器变量,因此如果读者感兴趣可以增加帧监听和键盘响应,这样就可以达到在程序运行的时候实时修改这个变量的目的,这样我们就能在程序运行时看到不同变量的显示效果了。 |
PS:很久以前就打算把学习Ogre中遇到的知识分享给大家(虽然只是一些皮毛),但是说来惭愧,一直很懒,直到最近才抽出点时间写一些自己的理解(Ogre入门级的东西),所以难免会有很多不足之处,分享是一种快乐,同时也希望和大家多多交流!
(由于在Word中写好的东西发布到CSDN页面需要重新排版(特别是有很多图片时),所以以后更新进度可能会比较慢,同时每章节发布的时间可能不一样(比如说我首选发布的是第二章,其实第一章就是介绍下Ogre的前世今生神马的,相信读者早就了解过~~~),但是我会尽量做到不影响大家阅读,还望大家谅解。)
上述内容很多引用了网上现有的翻译或者内容,在此一并谢过(个人感觉自己有些地方写得或者翻译的不好),还望见谅,转载请注明此项!