“材质(material)”,材质是一个很基本的术语,表示你的物体对光的反射方式。但是对它的实现并不像其解释这样简单,我们会用这一章节进行详细地介绍Ogre中相关的技术。

  我们刚才提及过,材质定义了物体对光线反射的处理方法。这里暗示了材质的表现与光源的类型相关:聚光(Spotlights)、点光源(point lights)以及有向光(directional lights)对材质的表现有着完全不同的影响。简单来讲,它们都是对物理世界真实光源类型的模拟,所以材质对它们的反射也遵守着其在物理世界中的规则。

  注意:虽然在Ogre里面,材质定义了物体如何反射光线的方法,但实际上并没有真的把光反射到附近的物体上。换句话说,场景中物体的辐射和反射属性并没有给场景增加任何光照。这是因为Ogre(以及目前绝大多数的即时渲染算法中)使用了局部辐射的算法来处理光照效果。

  为了帮助大家更好的理解局部辐射,在这里讲解一下与其相对的全局辐射算法的概念。所谓全局辐射光照模型,指的是通过光线跟踪等技术,计算场景中所有包括反射、辐射等所有光线的照明结果的算法模型。虽然全局光照模型可以产生和真实照片媲美的渲染结果,但因为其算法相对比较消耗时间,所以目前仍然无法应用于即时渲染领域(即时运算的3D应用程序,至少要每秒产生30帧画面)。所以全局光照模型现在只活跃于用来产生照片质量动画的“离线”渲染工具中,比如Pixar’s RenderMan(http://www.pixar.com),Mental ray(http://www.mentalp_w_picpaths.com,包括3D Studio Max 等商业软件所使用的工具),POV-Ray(http://www.povray.org,一个开源的光线跟踪渲染工具)以及Aqsis(一个免费开源的符合RenderMan标准的离线渲染器)中。

  材质的101[1]报告

  Ogre是一个硬件加速的事实3D渲染引擎。换句话说,它尽可能的使用硬件(GPU)来处理更多的渲染过程。而对于硬件来说,不论多么复杂的材质,最终渲染到屏幕的结果只有一个类型,那就是颜色。而Ogre作为一个典型的渲染引擎,把物体上的颜色分解成四种不同的光照作用:环境反射(Ambient)、漫反射(Diffuse)、放射(Emissive)以及镜面反射(Specular)。

  物体着色的基础

  在前一个章节中,我们介绍了Ogre中光源的类型。而在Ogre所采用的局部辐射度模型的着色算法中,对物体颜色产生影响的除了光源本身的属性之外,还有观察的角度以及物体本身的颜色(在实际处理中等同于的是摄像机的角度和物体所用材质属性)。

  下面列举了所有四种影响最终颜色的材质属性,以及它们的含义。

  ·环境反射:近似的模拟了场景中的全局辐射;也就是用来近似模拟所有光在场景中不断散射的结果。材质中有相应的属性来代表这种环境反射颜色。

  ·漫反射:这种颜色是接收到直接从光源发射的光之后产生的,“漫反射”这个词来源于现实世界,描述光被物体反射到各个方向的效果(换句话说就是散射)。

  ·放射:指的是自发光物体所拥有的颜色。这里有一个有趣的话题,因为在局部辐射光照模型中,放射光只能照亮自己却不能对周围任何物体产生影响,有时候这种效果却可以让人觉得极其虚幻(设想一下,在你的房间里有一个灼热的物体,却不会照亮周围任何东西,你就会了解这种感觉了)。

  ·镜面反射:描述了物体对被光照后的“高光”效果。设想一下假如你有一个红色皮球,而且擦得光亮。现在把它放在一个高瓦度的灯泡下面,你就会在上面看到一个被称为镜面高光的亮斑。这是因为光线被光滑的表面直接反射到你的眼睛中的缘故。

  在这里镜面反射的高光的颜色是可以被用户定义的。这是因为在现实世界中,和环境光、漫反射以及辐射光不同,镜面反射高光的颜色除了材质自身和光的颜色之外也受到周围其他的一些因素的影响。所以这里允许通过手动调整,使用较低的代价来达到对真实世界的简单模拟。另外我们也可以手动调整镜面高光的反射能量,简单的说就是可以用参数来控制红色皮球上“光斑”的大小,同时调节物体发亮的程度:能量大的时候,光斑变得又小又亮.

  3DMax中“高光级别”参数等同于Ogre中“specular power(镜面能量)”的概念,另外“光泽度”等同与Ogre中“shininess specular(镜面亮点)”参数。镜面能量决定能反射回来光的总量(表现为曲线的高度),另外的镜面亮度定义了高光如何被“展开”(越高的数值曲线越光滑)。

  这些基础的着色参数在Ogre中是总是可用的,不论你的硬件是否支持几何转换和光照处理(T&L),这些参数都是硬件加速所需要最基本的数据。如果在材质中提供了多种技术方案(Technique在我们后面的“技术和方案”章节中会具体介绍)以供选择的时候,至少对于其中使用固定功能着色(fixed-function shading)管线的来说这些参数仍然是必要的。

  纹理贴图(Texture Mapping)

  大部分的图形硬件都会至少支持一两个纹理处理单元(Texture units)。这些单元允许你的程序把纹理(Texture)通过一个独立的坐标系映射到一个物体上。这里的纹理不仅仅限于“2D图片”的概念,也可以有MIP-map 细节度的数据、立方体(Cube)纹理或者体积纹理(Volume)等等。在构造纹理的时候,可以使用的2D图片包括GIF,PNG或者网页上面使用的JPG图片格式,这些图片通常可以从屏幕截图或者数码相机以及拍照手机中得到。简单的说,使用了纹理贴图,就等于用一个2D图片包裹了3D物体,进而和物体表面的漫反射项进行混合(或者替代)来产生最终的颜色效果。

  提示:纹理贴图的制造通常可以被分成两个步骤。首先是艺术部分:美工使用3D模型工具来调整的纹理坐标(一般被称为UV坐标系)到模型的顶点上,进而产生一系列UV坐标。然后是程序部分:这些UV坐标被传递到图形硬件上把模型顶点和纹理坐标关联起来。

  前面提到的一些技术,除了图形硬件上的几何转换和光照处理(T&L),我们还提到了固定功能管道(fixed-function pipeline),这里面所谓的“固定(fixed)”的意思就是不可以编程的;你告知硬件物体的顶点和图片上像素的对应关系,然后就交给硬件就进行下面的工作:把物体的顶点信息映射到世界空间中去,然后结合纹理光栅化顶点的数据,最后绘制到屏幕上。

  可编程着色技术(Programmable Shading)

  可编程图形管线(programmable graphics pipeline)的出现,是到目前为止实时渲染3D图形加速技术中最大的飞跃。这意味着,简单的对每一顶点(顶点着色)和对每一像素(像素着色或者称为片断着色)的运算可以放到程序片段中来进行,这些程序既可以使用高级语言也可以通过GPU的汇编语言来实现,这样就能用我们所写的程序来取代之前固定功能管道所做的事情。注意,我在这里用到了“能取代”这个词,而不是“必须取代”。你仍然可以坚持使用固定功能管道,即便是在今天GEFORCE 7950 GTX2(在我写这文章时候市场上最强的图形卡)上面。如果决定了采用可编程着色语言,那么你就可以在渲染通路重处理任何顶点以及它们之间的片断数据。纹理在这个时候成为了程序的输入数据,需要说明一下,这里的输入数据可以是任何你需要的东西。因为如果你足够聪明,GPU对你而言只不过是一个高速的并行计算引擎而以,你甚至可以用它完成你任何希望的计算工作(比如人工智能的计算,在游戏编程精粹4中有相应的文章)。

  Ogre支持所有风格的GPU着色语言,其中包括低级的汇编和诸如Cg,GLSL和HLSL这种高级语言。然而“支持”这个词同时意味着Ogre并没有为你做什么具体的事情;你仍然要靠自己双手去写这些着色程序,不过还好Ogre提供了一个灵活并且可变的框架来帮助你在GPU上进行着色工作。你会在之后的“材质和可编程管线”章节中了解这个框架的具体信息。

  材质和程序设计

  在上一个章中,我们介绍了Ogre对物体中的定义的实体和几何模型之间的关系。同时我们也知道需要通过材质来让它们看起来更像现实世界中的样子。在更进一步了解Ogre材质机理之前,我们还要了解一些更高级的编程概念。

  批次(Batching)

  Ogre最基本的渲染单元被称为可渲染对象(Renderable),它们通过不同的渲染状态被分类传递到渲染队列中去。相同渲染状态的可渲染对象只需要一次的绘图操作,也就是说它们在一个绘图批次中完成了渲染过程。当渲染到不同的材质或者模型的时候都会导致渲染状态的改变,而每次改变都会引起新的绘图操作(新的绘图批次)。绘图操作都是一个耗费时间的过程,如同画家在绘制新作品前要清洗调色板一样,图形硬件也需要进行顶点和纹理的更新操作。为了渲染效率的提高,程序设计者要尽量减少渲染状态的改变,换句话说也就是减少绘图批次的数量。

  注意:通常而言,在你设定了渲染场景之后,所有的纹理都会被一次性的载入显存,除非你的纹理占用了过多的显存,否则不会在每一帧都传递纹理数据到显存中去。因此,你需要尽量的谨慎的处理纹理的使用:一旦确定纹理已经在显存,在它失效之前就不要再尝试去重载它。

  Ogre引擎在力所能及的范围内尽量把相同渲染状态的物体一起渲染,进而减少渲染状态改变。然而即便如此,Ogre仍然尊重用户设置的Renderable整体(也就是说Renderable是处理批次的最小原子结构),并不进行拆分。举例来说,如果你有一个拥有20个使用相同材质的片段组成的模型,你应该把它们合并成一个Renderable对象而不分割成二十个不同的小对象批次:这样就可以省略掉系统检查这些片断是否是一个相同的渲染状态的步骤,从而可以一次处理完毕。在渲染的每一帧中,都会有一个明确的可以处理批次数量的上限,但是对这方面的讨论超出了本书所介绍的范围。如果你希望得到更多的信息,可以去查阅NVIDIA的工程师Matthias Wloka在CGD(游戏开发者大会)上发表的相关论文[1]。在这本书的后面,你会学到如何在代码中控制渲染状态的改变。为了达到以上的目的,你可能会需要使用3D模型工具来修改你所用的模型。

  材质克隆

  在Ogre中材质是被所有引用所共享的。当你从材质管理器重得到了一个指向材质的指针的时候,其他的使用相同材质对象也在处理一样的指针。这意味着如果你希望改变某个物体上面材质的任何属性的话,你都要把这个材质为它单独克隆一份,否则这个改变就会同时影响到所有使用相同材质的物体。在渲染通道中都需要再产生一个完全不同的材质。不过这里也有一种可以替代的方法存在,把材质作为颜色数据,通过改变可编程GPU着色的参数来实现不同材质的效果。这样就可以避免产生一个新的批次。

  GPU着色

  GPU(图形处理器)在设计之初就有着明确的目标,提供高性能的并行向量计算以供针对3D图形处理使用。而具体如何使用和怎么使用的权力都交给了用户。GPU的存在是为了减少CPU(中央处理器)的工作压力,使用者负责分配具体的计算工作给它们。需要指出的是,GPU在其专署领域的运算速率大大的高于CPU,而且似乎在未来一个时期内这个差距仍然会增加。因此,你需要有计划的开始在你设计中的管线加入可编程着色技术,尤其是当你将要计划使用实时阴影技术或者更高级的渲染到纹理(Render-to-Texture)技术的时候更需要这方面的支援。

  技术(Technique) 和方案(Scheme)

  可以说技术和方案是Ogre引擎材质中最强大和活跃的两个特性。在Ogre中,每个材质中都至少包含了一种“技术”实现,这种实现允许你对不同性能显示卡和硬件平台使用不同的材质属性组合。简而言之,技术就是“一种对物体的渲染方法”。通常来说对具体适用哪个渲染技术是由Ogre引擎自动甄选出来的(根据硬件性能、方案以及细节等级等信息),但是如果你希望的话也可以在代码中完全控制这个过程。

  “方案”是Ogre使用的高级话题之一,事实上它是一个渲染技术集合的描述。举例来说,你可能有三个不同的技术方案:高质量,中等质量,低质量。在游戏运行的时候,允许用户通过选择这三个方案中的任意一个来确定在游戏中具体使用的渲染技术集合。

  Ogre在渲染的时候,会有一个自动甄选所需渲染技术的固定流程:首先过滤掉那些不在当前方案中的所有技术(默认情况下当前方案是“Default”);然后选择适配当前细节等级(LoD)的那些;最后在剩下的当中挑选当前硬件环境中可以执行的最优技术(最好效果的)。当Ogre找不到任何一个可以使用的渲染技术时,就会把物体渲染成单调的白色表面。换句话说,如果你看到了一片雪白,就要检讨一下你对材质的配置了。另外在默认的情况下,材质中所有技术的细节等级(LoD)都被设置成为0,也就是最高的细节等级。换句话说,Ogre总是在尽可能的帮助你选择最优材质技术。

  似乎技术和方案会带来很多复杂的处理细节。但在实际的执行过程中,你只要在材质脚本中提供了充足的内容,Ogre就会接替你来管理这些琐碎的细节。当然如果你喜欢,也可以用代码完成脚本所进行的工作。

  材质细节等级(Material LoD)

  计算机图形学中的细节等级(Level of detail)这个术语经常是用来描述几何体复杂度等级和摄像机距离的关系。同样的,这种描述也可以类似的用在材质上。例如说,你可以通过脚本把当前材质定义成多层纹理(用不同的方法来混合它们),并且同时拥有顶点和片断两种GPU着色程序。就算在一个很近的距离,也能在你的模型上面表现出“无与伦比”的漂亮材质细节。然而,当这个模型在屏幕上缩小到12像素的时候(就是说距离变远了),你认为这样做还值得么?

  答案明显是不!解决这个问题的方法就是使用材质细节等级(LoD)管理。继续我们上面的例子,好地解决方案是利用Ogre可以让你在不同的细节等级中使用不同的技术实现,进而更有效的利用当前GPU的资源。为了实现这个效果,你需要首先定义各个细节等级所对应的实际距离,然后把相应的技术索引到这些等级中去。这里鼓励你在每个细节等级中尽可能多的设置不同的技术实现,以供给不同的方案以及硬件选择出最适用的实现。

  材质的组成

  在下图6-2中,展示了Ogre的材质之中各种组成成分之间的关系。一份完整的材质至少有一种技术实现,每种技术实现中至少要有一个渲染通路。在图示中,我们看到材质中包含了N种的技术实现,而在真正的渲染时,只会有一种技术被激活并进入渲染过程(选择激活技术的工作一般交给Ogre自动完成)。如果我们激活了技术0,那么技术0中所有通路都会被传到图形硬件中去依次渲染(换句话说,Ogre渲染了激活技术中的所有通路)。

  通路(Pass)

  在Ogre中通路是最基本的渲染单位,同时也是可渲染对象(Renderable)用来标示自己渲染状态的基本单元。每个可渲染对象都会有自己的材质,Ogre在材质中甄选出最适合当前应用的技术实现。然后把当前技术中所有的“通路”依次放入图形硬件的渲染通路中。顾名思义,Ogre材质中的“通路”对应于图形硬件中“渲染通路”的概念。也就是说当前技术中如果包含了3个通路,那么在绘制是用这个材质的模型的时候,在每一帧就要进行3次渲染。

  在实际的使用中,通路里面还有“纹理单元(texture unit)”的定义,你可以在一个通路中定义任意数量的纹理单元,当然一个不用也是没问题的。

  纹理单元(Texture Unit)

  在Ogre对材质的定义中,纹理单元的概念对应于图形硬件中的纹理采样(texture sampler)。为了运行Ogre程序,至少需要一个硬件纹理采样支持。不过这并不是什么大问题,因为现代的图形硬件基本上都会有多个纹理采样,因此我们可以在一次渲染通路的执行中,同时处理多个纹理单元。

  顾名思义,纹理单元里都会包含一张纹理。你可以直接用硬盘中的图片文件,也可以通过实时的渲染来得到,甚至可以通过一个视频流来动态生成纹理图案。在Ogre中并没有对通路中纹理单元的数量进行限制,这是因为Ogre能根据图形硬件能力动态拆分通路(这里假设没有使用硬件着色程序)。具体点说,如果你的图形硬件只能同时处理4个纹理采样,但是应用程序却使用了一个6纹理单元的通路。这时候Ogre会自动的把这个6纹理通路拆分成两个分别两次进行渲染,不过虽然最后的渲染结果和预期的一样,但是仍然是通过两次渲染通路来实现的,对效率的影响不言自明。

  最后需要注意的是,纹理在被真正的抛弃之前,都会一直存在显存中,而并不是每帧从内存传递到显存。但是如果纹理数量太多或者体积过大的时候,硬件无法同时处理所有纹理,效率会严重降低。

  纹理压缩

  很多现代图形硬件都会支持压缩纹理(比如DXTC方案),不过Ogre只是简单的载入纹理然后把它传到图形硬件上,并没有执行纹理的任何压缩过程。如果需要纹理压缩的支持,就需要离线进行图片的压缩工作(多数情况是把图片转换成压缩的DDS格式),然后把这些预压缩的纹理交给程序使用。如果你的硬件不支持压缩图片格式,Ogre会帮助你在程序运行的时候进行解压缩,然后再交给硬件使用。

  视频流

  虽然Ogre并没有在纹理单元中内置对视频流的支持,不过因为纹理单元可以支持外部数据源的处理,所以通过这个这个简单的机理可以帮助实现对视频流的支持。Ogre社区已经提供了一个针对Theora(http://www.theora.org)这个视频流处理器为基础的插件。通过这个插件你可以把任何你所喜欢的流加入到你的Ogre程序中,甚至可以直接从实况电视转播中得到视频源:任何你能想象得到的应用都能帮你实现。

  实体(Entity)

  实体是Ogre中比较神奇和复杂的概念之一。在每个实体中都包含着一些子实体(SubEntity)的实现,这些子实体是真正的可渲染对象,它们维护着具体的材质特性。而每个子实体又和一个子模型(SubMesh)对应(通常来源于3D模型工具建立的模型)。概括的来说,实体和子实体是物体渲染特性的入口,而模型(Mesh)与子模型是物体结构特性(几何体数据)的入口。

  材质的例子

  为了便于大家更好的理解,我们会通过讲解一些简单的例子来继续这个章节。这里大多是从Ogre的演示程序中找到的例子,不过也有我写的的几个材质脚本的最简单实现。

  材质与固定功能管道(Fixed-Function Pipeline)

  代码6-1展示了一个最简单的材质脚本,里面包含一个最简单的技术实现,技术实现里只有一个通路。如果你用它对你的物体进行渲染,会得到一个灰色的结果。

  代码6-1:宇宙中最简单的Ogre材质

  material VerySimple

  {

  technique

  {

  pass

  {

  diffuse 0.5 0.5 0.5

  }

  }

  }

  上面代码6-1中定义的材质,并没有什么特殊漂亮的地方,但是对于我们用来介绍Ogre材质脚本层次和结构来说已经足够了。通过它你可以直观地了解到脚本内部的嵌套关系:材质包含了技术,而技术包含了通路。通路中定义了漫反射参数。

  现在让我们看一个更复杂一点的例子,在下面代码6-2中,我们在材质中建立了两个技术,而且提供了更多的固定功能着色(Fixed-function shading)的控制。

  代码 6-2:对代码6-1的简单扩充

  material NotQuiteAsSimple

  {

  technique

  {

  pass

  {

  diffuse 0.5 0.5 0.5

  ambient 0.1 0.2 0.3

  specular 0.8 0.8 0.8 68

  texture_unit

  {

  texture ReallyCool.jpg

  colour_op modulate

  }

  }

  }

  technique

  {

  pass

  {

  diffuse 0.5 0.5 0.5

  }

  }

  }

  代码6-2已经很接近在实际中使用的材质定义了。在其中第一个技术的通路里面使用了texture_unit标记来定义所使用的纹理贴图,这里假设有一张叫做ReallyCool.jpg的图片。在这个定义里面有趣的部分是定义如何把纹理的像素点混合到已经存在的颜色上:colour_op modulate这段脚本让Ogre把纹理上面的颜色数据和当前像素上的颜色数据(之前颜色混合的结果)相乘。在我们的例子中,所谓的当前颜色数据,其实指的就是程序中对局部光照模型混合的结果,具体点来说就是在这个相同的通路中对环境光,漫反射以及镜面反射采样的计算结果。

  代码6-2也是我们第一次在材质脚本中加入一个用来“垫底”的技术实现。如果程序不幸的运行在一个不能支持纹理数据的硬件环境上的时候,第二个备用的技术实现就会被系统拿出来工作,把你的模型渲染成单一的灰色,虽然不怎么好看,但至少保证了程序安全运行。因为我们定义的两个技术都被系统默认的设置成相同的“方案”(默认的“Default”方案)以及相同的LoD(细节等级:0),所以的这个“垫底”才能良好的被运行。

  注意:在你造成上面的问题之前,可能还有更严重的问题会出现。如果你的硬件真的很“老”,以至于无法支持纹理贴图属性,这时候甚至基本的3D API都不能运行(不论是DirectX还是OpenGL),进而导致Ogre不能启动。虽然Ogre是个强大的引擎,但是不能帮你升级硬件。

  代码6-3是一个真正在Ogre例子里面使用的材质脚本——Example.material。你在Ogre目录下面的Samples\Media\materials\scripts 子目录下面可以找到这个文件。

  代码6-3:一个定义纹理贴图的材质脚本

  material Examples/EnvMappedRustySteel

  {

  technique

  {

  pass

  {

  texture_unit Diffuse

  {

  texture RustySteel.jpg

  }

  texture_unit Environment

  {

  texture spheremap.png

  colour_op_ex add src_texture src_current

  colour_op_multipass_fallback one one

  env_map spherical

  }

  }

  }

  }

  上面代码和之前最大的不同就是增加了材质的命名:Examples/EnvMappedRustySteel。虽然看起来似乎好像存在着一个目录结构,但事实上并不真的存在任何层级关系,这样命名只不过是一个为了更方便处理的习惯罢了:Ogre系统只是简单的认为,在material标签右面相同行的所有字母都是这个材质的名字。这个名字在整个程序中必是唯一的。因为在Ogre中对于材质的名称并没有一个类似“命名空间”的概念:就算在不同的资源载入的材质,它们的名字也是同时存在一个空间的。如果你真的希望可以用一样的名字命名不同的材质,就最好遵从我们上面提到的习惯,用类似路径名的方法命名材质,其中路径就可以替代命名空间的实现。

  Ogre会给所有材质之中的元素(包括技术,通路以及纹理单元)提供一个默认的名字。还是用我们之前的脚本作为例子,在材质Examples/EnvMappedRustySteel中,里面的唯一的技术实现被命名为“0”,而通路因为一样的原因得到了“0”这个名字。两个纹理单元分别被命名为“0”和“1”。这个名称是和他在材质脚本中的顺序相匹配的。当你要通过“继承”的方式扩充脚本的时候(后面回讲到具体细节),有必要给这些元素手动命名,以便实现扩充功能的作用。相对于材质本身,Ogre并没有要求这些元素名称具有全局唯一性(当然,在它们自己的容器中,还是需要有不同的名字来辨别的)。

  在代码6-3中,第二个纹理单元被指定成为一个球体环境贴图(通过env_map标记来实现)。环境贴图是一种用很低的代价实现的模拟反射表面的效果,其中并没有真的使用光线跟踪算法。再加上前面使用的“生锈的钢铁”纹理,最后混合上环境贴图的效果。最后能产生什么样子呢?那就要参考下面的几幅截图和贴图来了解了。

  材质的继承(Material Inheritance)

  Ogre提供了体面的纹理继承机制,可以帮助我们更简单的改变纹理的某些部分。比如在前面我们定义了材质脚本Examples/EnvMappedRustySteel,如果我们需要换成压缩纹理。这时候既不用在原脚本上修改,也不用重新写另外一个。通过材质脚本的继承机制,我们可从原有的脚本中派生出新的纹理出来。

  不过在Ogre的代码和文档中把这种行为称为材质拷贝(material copying),这种称为揭示了“材质继承”与面向对象中的继承概念的区别。Ogre并没有对材质脚本实现面向对象的继承关系;也就是说在代码中Ogre系统名没有真正的使用原是材质进行继承,而只不过是简单的对原始脚本的属性进行了拷贝,其结果是:改变父脚本的属性不会影响子脚本。

  虽然不怎么地道,但是确实是如继承一样方便的方法。你可以建立一个新的材质,把已经存在的材质作为基础,拷贝所有旧材质的属性到新材质里面,并对其中的属性做轻微的调整。这样做既可以减少工作量也可以让脚本看起来更清晰简单。

  命名的重要性

  虽然大多数情况可以依赖系统对材质中成员的默认名称。但如果你使用了继承,并且准备在新的材质中增加技术,通道或者新的纹理单元。这时候就要考虑手动对程序进行命名了,这是因为需要明确的区分父材质和子材质(也就是原材质脚本和目标材质脚本)中各元素的名称。如果你在之前的材质中使用了称为“Primary”的技术,在扩充的拷贝材质中就不能增加一个被称为“Primary”的技术,这是因为相同的名称会覆盖原材质的属性。

  纹理覆盖(Texture Aliases)

  在很多情况下,派生材质的目的可能只是需要改变已有脚本中的纹理图片。这时候可以通过简单的纹理覆盖机制来实现。下面的代码片断展示了如何通过纹理覆盖机制把父材质中的图片换成相应的压缩格式。

  material Examples/EnvMappedCompressedRustySteel : Examples/EnvMappedRustySteel

  {

  set_texture_alias Diffuse rustySteel.dds

  set_texture_alias Environment aphereMap.dds

  }

  上面代码实现了我们需要的功能,现在Examples/EnvMappedCompressedRustySteel的使用的纹理已经不同于Examples/EnvMappedRustySteel,其他属性仍然继承了下来。一目了然,这种方法比麻烦的拷贝粘贴方法要好上很多。

  我们再来看一个稍微特别一点的材质实现,下面的材质脚本是Ogre演示程序中真实使用的例子。

  代码6-4:在Sampls/Media/materials/scripts/Example.material中使用的材质纹理效果

  material Examples/TextureEffect4

  {

  technique

  {

  pass

  {

  ambient 0.3 0.3 0.3

  scene_blend colour_blend

  cull_hardware none

  cull_software none

  texture_unit

  {

  texture Water02.jpg

  scrool_anim 0.01 0.01

  }

  }

  }

  }

  在默认的情况下,Ogre会使用硬件拣选来过滤掉逆时针的表面(也就是说图形硬件只渲染那些边是面向摄像机顺时针排列的三角形)。而有一些场景管理器只会渲染那些法线面向摄像机的三角形。当我们希望平面的两个面在场景中都可见的时候,就需要关掉这两种拣选方式,通过脚本中所用的cull_hardware none和cull_software none两行来实现。

  在图6-6中我们可以看到背景的蓝天白云透过半透明的材质被展现出来。这是材质和场景之中已经有的颜色进行混合的结果;因为纹理图片本身并没有透明效果(也就是没有自己的Alpha通道),所以使用了:scene_blend colour_blend来实现了简单的混合效果。如果使用了有Alpha通道的透明效果纹理,则可以使用:scene_blend alpha_blend来得到更好的效果。

  材质和可编程渲染管线

  在之前所提及的所有材质都使用了固定的渲染管线。而现在我们要接触更复杂一些的可编程渲染管线。虽然之前所有的脚本也都可以在可编程的管线中正常使用,但是当我们调用了着色程序之后,其中大部分的设置都会被忽略掉。

  在进行Ogre材质的GPU程序设计的时候有三件事情需要关心:程序本身、程序声明以及在材质中的使用。在这里首先让我们看一个简单一些GPU程序产生材质的例子:一个在Ogre的某个Demo程序中使用的硬铁皮表面材质。

  代码6-5:在文件Samples/Media/materials/programs/Example.cg中的Cg程序

  void hardwareSkinningOneWeight_vp(

  float4 position : POSITION,

  float3 normal : NORMAL,

  float2 uv : TEXCOORD0,

  float blendIdx : BLENDINDICES,

  out float4 oPosition : POSITION,

  out float2 oUv : TEXCOORD0,

  out float4 colour : COLOR,

  // Support up to 24 bones of float3x4

  // vs_1_1 only supports 96 params so more than this is not feasible

  uniform float3x4 worldMatrix3x4Array[24],

  uniform float4x4 viewProjectionMatrix,

  uniform float4 lightPos[2],

  uniform float4 lightDiffuseColour[2],

  uniform float4 ambient)

  {

  // transform by indexed matrix

  float4 blendPos = float4(mul(worldMatrix3x4Array[blendIdx], position).xyz, 1.0);

  // view / projection

  oPosition = mul(viewProjectionMatrix, blendPos);

  // transform normal

  float3 norm = mul((float3x3)worldMatrix3x4.Ogre 3D程序设计 Ogre材质1 .Array[blendIdx], normal);

  // Lighting - support point and directional

  float3 lightDir0 = normalize(

  lightPos[0].xyz - (blendPos.xyz * lightPos[0].w));

  float3 lightDir1 = normalize(

  lightPos[1].xyz - (blendPos.xyz * lightPos[1].w));

  oUv = uv;

  colour = ambient +

  (saturate(dot(lightDir0, norm)) * lightDiffuseColour[0]) +

  (saturate(dot(lightDir1, norm)) * lightDiffuseColour[1]);

  }

  就算你不了解6-5这个程序具体是什么意思也不要紧,现在你只要简单的注意一下用黑体字标明的函数名称就好了。另外,对于储存GPU程序的文件可以任意命名,如果你喜欢甚至可以改变扩展名。一会儿你就能了解到,这个文件的名字对于Ogre系统来说没有任何意义。

  注意:在这本书里面并没有要讲解GPU程序设计的计划。这里所列出的所有GPU程序代码都只是为了作为实际的例子,如果你不理解这些代码的意思又对其有兴趣,就需要找一些资料来学习一下相关的知识了。

  一旦你完成了上面的程序定义,你接下来需要做的工作是建立一个程序声明(declaration)。这种声名被用来给材质脚本中使用,下面代码6-6就是对上面程序的声明。