OGRE的材质脚本 (三)
2010-12-15 13:18
316 查看
OGRE的材质脚本 (三)
材质拷贝技巧1: 一般来说,我们游戏中大量的材质是雷同性很强的,假若大段的复制材质渲染模式,实在是非常不值得的体力劳动,所以对于一些只有微小改变的材质设置,推荐使用材质拷贝。 材质拷贝技巧1: 当两个材质完全一致时。直接类似于C++的继承的写法即可: Material met1 { Technique { Pass 0 { … } Pass 1 { … } } } Material met2 : met1 { } 我们不需要做任何事情,met2就已经过去了met1的材质属性。 材质拷贝技巧2: 当我们向一个拷贝材质中添加新技术时。直接在新的材质脚本中声明新的技术即可。例: Material met2 : met1 { Technique new { } } 此时new这个技术就会默认的在其父类的默认命名为0的技术之后产生。不过值得注意的是,新创建的这个技术尽量命名,避免与父源类的技术名称发生冲突。 材质拷贝技巧3: 当我们想对拷贝材质已有的属性做一点点的改动时候,需要声明原有技术和通道,直接声明需要改动的属性即可。例: Material met2 : met1 { Technique 0 // 父类的技术并没有命名,默认是以索引为名,索引为0,所以这里填0,但是注意的是,一定要把这个0表示说明出来 { Pass 1 // 父类中的0号技术中的命名为0的一个通道 { max_lights 2 //修改该Pass中的最大光源属性为2 } } } 最常用的是修改一个渲染材质中的某一个纹理文件。我们可以这么做 Material met2 : met1 { Technique 0 // 父类的技术并没有命名,默认是以索引为名,索引为0,所以这里填0,但是注意的是,一定要把这个0表示说明出来 { Pass 1 // 父类中的0号技术中的命名为0的一个通道 { Texture_unit TreeTexture { Texture NewTreeTexture.png } } } } 这样我们就将treetexture纹理单元中的纹理图片替换了,而其他的一切渲染属性都没有更改。 材质拷贝技巧4: 记得我们之前说纹理单元时候有强调过纹理有一个属性叫纹理别名吧。texture_alias。 我们看上面的例子,假若我们材质met1,和met2仅差一个纹理的区别的话,那么上面的写法也比较麻烦,我们有个更简单的方法,就是,在初次定义的时候,为纹理定义一个别名,之后我们假若需要换纹理,仅告诉Ogre脚本解释器,别名现在代表另一张纹理便可以了。 例如:我们要渲染一张“漂亮的图“,在材质1中,“漂亮的图”代表 1.png,材质2中,“漂亮的图”代表2.png,我们只要告诉Ogre,“漂亮的图”是哪张便可以了。例子如下: Material met1 { Pass 0 { Texture_unit testTex { texture_alias DiffuseMap texture defaultDiff.png filtering trilinear tex_coord_set 1 } } } 那么我们假若需要使用同样的技术和通道,仅修改纹理图片,我们可以简单到如下: Material met2 { Set_texture_alias DiffuseMap NewChangedDiff.png } 一句话便更换了别名DiffuseMap所指代的对象。直接达到更换纹理贴图的效果。 所以,我们尽量可能的为技术,通道,纹理单元手动设置名字,纹理资源尽量设置别名。 字体定义脚本 Ogre中字体最终就是一个Meterial对象,我们要获得这个对象可以有2种方法: 1:利用一个字体生成工具自己设计字体纹理 2:让Ogre生成一个基于trueType字体的字体纹理 无论使用哪种,都需要在 .fontdef 文件中对字体进行定义。 当我们使用现有的一个字体纹理,那么该脚本声明格式为: MyFont // 字体名称,程序中调用时使用,自定义 { Type image // 告知Ogre,我们使用的是字体纹理,不是tureType Source XXX.png // 字体纹理文件名 Glyph A 1 3 14 14 // 告知Ogre,A这个字符在纹理中的位置是(1,3)到(14,14)之间。 …… // 对我们所需要的每一个字符都进行纹理位置的通知 } 从上面可以看出……对中文适用性之低。 当我们使用truetype生成一个字体纹理,那么脚本声明格式为 MyNewFont // 字体名称,程序中调用时使用,自定义 { Type truetype // 告知Ogre我们将从一个字体中生成纹理 Source XXX.ttf // 要加载的. ttf文件名 Size 16 // 生成字体纹理的大小,若过小,则贴到大的面上就显示很粗糙,若过大,则贴到小的面上就会模糊不清。 Resolution 96 // 每英寸计算的清晰度,一般是72或96 Antialias_colour true // 关闭默认的字体抗锯齿,这样的话我们就会在渲染时手动的对字体边进行抗锯齿处理。该项默认为false, 即不使用手动的抗锯齿,使用Ogre默认的抗锯齿功能。一般,false即可。 Code_pionts 33-166 // 该项是表示哪一段的unicode编码应当被生成字体纹理,默认是33-166的字符。 } Overlay覆盖层脚本 这个的作用重点表现在UI方面了,它就是将3D的按Z轴深度进行分割出的层 平面,这个脚本默认在Root初始化时会自动搜寻所有的 .overlay 层脚本并且装载分析。当然我们也可以手动去加载OverlayManager::GetSingleton().parseAllSource()或者手 动加载单独一个脚本OverlayManager::GetSingleton().parseSource(). 我们看一个样板式的层脚本 MyNewOverlays // 该层的唯一标识命名 { Zorder 200 // 该层的Z轴深度,越大代表越接近屏幕 Container Panel (MyNewOverlays/FirstPanel) // Container对应的是element,这两者都是对Panel的修饰词,当该面上有新的子面时就使用Container,若是一个完全无子面的面, 则可使用 element进行修饰。Panel是注册过的本面板元素的类型,Ogre提供了三种基本类型 Panel,BorderPanel,TextArea. 括号中的是该元素的唯一识别名称。我们在程序中就可以使用 OverlayManager::GetSingleton().getOverlayElement(唯一识别名称);来获得此元素的指针。在该句最后 我们还可以加入继承模版,接下来再说。 { Left 0 Top 0 Width 0.02 Height 0.3 Material ThisPanelMaterial // 这些都是该面板元素的属性 } } 这里我们遗留了三个问题: 1:Ogre提供的三个基本元素类型有什么区别和作用? 2:什么是面版元素继承的模版,作用有什么? 3:面板元素都有什么属性有什么作用? 答1:Ogre提供了三个基本表层元素类型。包括Panel,BorderPanel,TextArea。实际上,一般来说,这三种是完全不够的,是需要我们进行扩展的。扩展后的类型拥有独特的功能和属性。 例如: Panel 面版 它就是一个矩形的区域,重点作用就是做一个其他元素的容器。所以一般来说,它更多时候是没有背景的透明的。他的专有属性有: Transparent true / false 是否透明,若为true则代表本面板透明,自己是不参与渲染的。 Tiling 0 1 2 实现多重贴图。本例说明使用第0层材质纹理在面板上X轴方向重复贴一次,Y轴方向上重复贴两次。 Uv_coords topleft_u topleft_v bottomright_u bottomright_v 设置这个面版上的纹理UV坐标。 这三个属性是Panel专属的,这意味着其他的表层元素是不具有这些功能和属性的。 BorderPanel边框面版 它和Panel区别仅有一个,就是多个一套边框。这个边框会随着BorderPanel的大小自动的调节其纹理大小。它的构成由9部分:一个中心区,4个角,4个边。它的专属属性是 Border_size left right top bottom 边框按屏幕大小的比例尺寸。我们可以发现到Ogre在记录UI大小时是很喜欢记录UI与屏幕长度之间的比例,而非实际的象素大小。这样做的好处是,当屏幕 大小有变换时,是很容易实现缩放功能的。 Border_material materialName 因为边框面板有独特的边框,所以,我们需要对它指示出其边框使用的材质纹理。 Border_topleft_uv topleft_U uv_topleft_V buttomRight_U buttomRight_V Border_top_uv topleft_U uv_topleft_V buttomRight_U buttomRight_V …… 总共八块边缘都需要指定其UV信息。这里我们需要注意,我们美术资源制作时,要求左右两边的材质纹理应该允许被垂直拉伸,上下两块的材质纹理应该允许被水平拉伸。 TextArea文本区 用来渲染文本的一个表层元素。它转有属性包括: Font_name name 要使用的渲染字体名,我们必须保证这个字体在 .fontdef 里面并且可用。 Char_height 字母高度占用整个屏幕告诉的百分比。 Colour RGB 渲染字体的颜色,默认是使用一般的黑白字体。RGB是0.0-1.0之间的浮点数。 Colour_bottom RGB / colour_top RGB 实现文本从上到下的颜色渐变。 答2: 模板实际上就是类似于材质拷贝时的那个父类,实际作用是当几个比较类似属性的表层元素,我们可以直接定义一个模板,其他元素继承于该模板就节约了一些复制粘贴的操作而已。 我们看下例: Template container BordPanel ( MyTemplate/BasicBorderPanel ) // template表示该Panle是模板面版,本身它是不进行渲染的,类似于C++的抽象父类,制定一套属性样版提供其他渲染面板直接套用使用。 Container也是Panel的一个属性,表明它是允许有子表层元素的。后面括号里是模板面板名称。 { Left 0 …… Material MyPanelMaterialName Border_size 0.05 0.05 0.06 0.06 Border_material MyBorderPanelMaterialName …… } Template container Button (MyTemplate/BasicButton) : MyTemplate/BasicBorderPanel // 表明该Button是上面的层的子层,Button是开发人员注册的一个层元素类型 { Font_name BlackStyle Char_height 0.09 Color_top 1 1 0 Color_bottom 1 0.5 0.5 } 上面定义了一套模板边框面和一套模板按钮,下面我们将对其实现 MyOverlays // 层名称 { Zorder 490 // 层深度 Container BorderPanel (ChatBackPanel ) : MyTemplate/BasicBorderPanel // 实现一个进行渲染的背景面层元素,它套用模板边框面 { Container Button (JionButton) : MyTemplate/BasicButton // 实现一个进行渲染的按钮,它套用模板按钮的各项属性 { Left 0.8 Top 0.4 } Container Button (ExitButton) : MyTemplate/BasicButton // 实现另一个进行渲染的按钮,它套用模板按钮的各项属性 { Left 0.6 Top 0.4 } } } 这样,我们就得到了一个渲染出来的边框面板和上面的两个按钮,我们新创建的按钮就可以省去很多属性的设置了,仅对部分独特的属性进行设置即可。 可见,模板的作用和抽象Pass通道,技术,纹理单元 是一样的作用,减少我们属性设置而创建的。 答3: 层元素属性是对层的各项渲染指数进行调整的东西。我们也看到了,不同的元素因功能特性不同,会有独特的属性,但下列通用属性是每个元素必须有的: Metrics_mode pixels / relative 属性参数解释方式。默认是relative相对模式,这就意味着之后我们设置top 0.8 代表着这个0.8是针对屏幕宽高得到的比例值。若我们设置为pixels则代表我们设置 top 8 是从第8个象素开始的,是一个绝对的象素偏移量。显而易见,我们再设置为top 0.8 这样明显是不合理的了。 默认该项为 metrics_mode relative Horz_align left/center/right 设置此元素水平起点位置。例如,horz_align left 则代表本元素会自动的居左对齐,当然我们再设置left xxx可以再次对其位置进行调整。 默认该项为 horz_align left Vert_align top/center.button 同上不再解释。 默认该项为 vert_align top Left 0.3 设置元素相对于它上一层的水平位置。后面的参数跟metrics_mode属性参数解释方式挂钩。 默认该项为 left 0 Top 同上不再解释。 默认该项为 top 0 Width 0.2 设置元素大小。这里0.2是针对整个屏幕的大小而言,并非针对其父元素的大小。所以当我们设置width 0.5时则意味着这个元素会占屏幕的一半宽。 默认该项为 width 1 Height 同上不再解释 默认该项为height 1 Material XXX 设置该层元素使用的基本材质。值得注意的是:一个表层元素的材质会默认的禁止其上材质的光照和深度检测。所以,我们不应当在表层上使用与3D物体相同的材 质。另外,该项在不同的元素中解释意义也是不同的,在Panel中他是整个面版的背景材质,但是在BorderPanel中它仅仅是中心区域的材质。 Caption XXX 设置该层元素的标题。因为部分元素是没有标题属性的,所以有些元素可以忽略掉该元素。 Rotation 30 0 0 1 设置该层的旋转角度,第一个参数是旋转的角度,第2,3,4属性分别表示在x,y,z轴的旋转。本例就说明是该层元素需要围绕z轴旋转30度。 4:Mesh网格工具 Ogre自带的网格工具包括三种: 1:导出器Exporters 用于从绘图软件中导出固定格式的数据提供Ogre使用 Ogre这个导出插件能够导出两个文件,一个 .mesh 结尾的网格模型,一个是 .skeleton 后缀的骨骼模型。值得注意的是,当我们需要创建一个模型动画时候请注意: ·每个顶点必须没有超过4的加权骨骼赋值。 ·所有的顶点都必须被分配到至少一个骨骼点上,静态顶点就分配到根部骨骼点上 ·动画开始和结束时候每个骨骼点上最少要有一个关键桢 2:Xml转换器 能够将xml格式的数据转换为Mesh数据和骨骼数据 ·因为很多模型工具导出的Mesh都是xml格式的,这时使用该转换器就可以直接将 xml格式的mesh网格转换为 .mesh 文件。其中的语法非常简单。 格式为: OgreXMLConverter 文件名 这样就可以了。不过在转换时,你可以有一次机会对Mesh中的Lod信息进行处理。 3:Mesh网格更新器 能够对网格的数据进行更新修改功能。 未能顺利使用。故不做介绍。 5:硬件缓冲区(硬件缓存) ·定义 实际上这个缓冲区就是一块malloc出来的存储区域,不过它不如malloc是在内存中申请的区域,而这个缓冲区是在gpu/agp中,它的写读速度更快。通常硬件缓冲区作用有拿来做顶点缓冲区,索引缓冲区,和象素缓冲区。 ·使用 硬件缓冲区的管理是交由一个硬件缓存管理器负责的HardwareBufferManager,他负责缓冲区的创建和释放,它是几何体创建工厂,单键在Root初始化时就会被创建,所以,当我们需要一块内存的时候,一定不要直接New或malloc操作,而应当是这样 VerBuf = HardwareBufferManager::GetSingleton().CreateVertexBuffer() ·类型 我们在分配一块硬件缓冲区时,需要传一个参数,来指明这块缓冲区的类型,是否需要频繁读写?这样对底层的硬件缓存区域分配管理提供很大的便利。我们来看一 下硬件缓冲区的类型有哪些,我们分配它的时候应该做何选择。(HBU是HarewareBufferUsage简写) HBU_STATIC 静态硬件缓冲区,它意味着我们很少写入更新缓冲区,偶尔会从中进行数据读取。 HBU_STATIC_WRITE_ONLY 只写静态硬件缓冲区。它意味着我们很少更新缓冲区,并且绝对不从该缓冲区进行数据读取。但是,当我们创建了一个备份缓冲的话,我们依旧可以对其读取。 HBU_DYNAMIC 动态硬件缓冲区。它意味着我们会经常性的更新缓冲区中的数据,并且也希望能从其中读取数据,这一个效率最低的缓冲区使用方法。 HBU_DYNAMIC_WRITE_ONLY 只写动态硬件缓冲区,这个是个只许写入的硬件缓冲区,但当我们创建了一个备份缓冲的话,还是允许读取的。 HBU_DYNAMIC_WRITE_ONLY_DISCARDABLE 这个参数指明这个硬件缓冲区是一个需要频繁更新的缓冲区,大多数是每桢更新的数据存放在这里。但是需要注意的是,向该数据缓冲写入数据时记得加缓存锁。 建议:多使用WRITE_ONLY为后缀的缓冲区类型。即使必须进行读取,也建议使用备份缓冲,而非时刻可写的缓冲。 ·备份缓冲 当我们创建一个 WRITE_ONLY的硬件缓冲区后,我们有些时候假若非要从中读取数据,我们可以在创建缓冲区时传参,这样我们在内存中就会创建一个备份的缓冲区。每当 我们向显存中写入一份数据的时候,Ogre会自动的先将这份数据拷贝到内存缓冲区后,再将其更新到显存中的硬件缓冲区。当然,这个技术会带来更多的开销, 所以,非必要时不要用它。 ·缓存锁 当我们更新写入缓 冲或者在读取缓冲的时候,都应该先“锁”住它,以免它被修改。当然,之后记得解锁。pBuffer->Lock(begin , length , lockType)一般来说,锁的范围越小越便捷快速。但是锁的类型lockType也可以对读取的效率产生影响。 锁的类型包括: ·HBL_NORMAL 这种锁支持从缓冲区读取数据,但是效率很低下,因为它允许我们从硬件缓冲区进行数据读取。但是,当我们使用备份缓冲的话,这种影响会得到一些改善。 ·HBL_READ_ONLY 这种锁意味着我们只能从缓冲区中进行内容的读取,禁止写入。此时建议我们使用备份缓冲,会提高我们效率。而且,此时我们实际上读取到的并非硬件缓冲区,而是内存中的数据。 ·HBL_DISCARD 这种锁意味着我们每次操作都会将硬件缓冲区中的所有内容丢弃,一般这种操作也是会在每桢都处理的环境下才会使用,它是禁止读出的。但是一旦我们如此声明, 也就基本上是向引擎宣称我们不会对硬件缓冲区的内容感兴趣,那么就不应当创建备份缓冲区。在我们没有使用备份缓冲区时尽量使用这种锁,它的效率很高,但若 有了内存备份缓冲区的话,它就没有必要了。 ·HBL_NO_OVERWRITE 当我们有些时候需要更新部分缓冲区而非全部缓冲区时,使用HBL_DISCARD就显的不适合了。此时我们就需要使用这种锁了,它的效率依旧很高,但是也仅是没有备份缓冲的时候才有作用。 |