现在的位置: 首页 > 综合 > 正文

Ogre 3D程序设计 Ogre材质2

2013年09月03日 ⁄ 综合 ⁄ 共 12351字 ⁄ 字号 评论关闭

代码 6-6:在Samples/Media/materials/scripts/Examples.program中的程序声明

//Basic hardware skinning using one indexed weight per vertex

vertex_program Ogre/HardwareSkinningOneWeight cg

{

source Example_Basic.cg

entry_point hardwareSkinningOneWeight_vp

profiles vs_1_1 arbvp1

includes_skeletal_animation true

}

代 码6-6中列出了叫做Ogre/HardwareSkinningOneWeight的顶点程序声明。首先它声明了这个程序使用的是cg语言,进而告知 Ogre需要调用Cg运行时库通过来硬件渲染管线来处理程序。并且这个声明也告诉了Ogre从Example_Base.cg(我们之前定义的代码 6-5)中可以找到程序源代码。对于一个Cg程序来说,它需要一个程序入口点,这样Cg就可以从它来运行整个程序了(Cg是一种类似C的语言,它支持结构 化的代码设计)。我们通过entry_point标签来把它定义到hardwareSkinningOneWeight_vp函数上(在真实的代码中可能 同时有很多函数)。(另外:HLSL程序也需要声明一个入口点来进入程序,而汇编和GLSL语言就没有这个需要:其中GLSL使用main函数作为入口 点,汇编程序的运行只是简单的从顶部运行到底部。)

同时在代码中也提供了profiles(描述)属性,用来把这个程序 的profiles信息交给Ogre和Cg以便运行时使用。Ogre通过这个属性信息来做技术的匹配工作:如果特殊的硬件集合无法支持现在所提供的 profiles信息,Ogre就会再选择下一个要求较低的技术来匹配,直到找到能用的为止。Cg程序也使用这个profiles信息;汇编程序使用一个 类似的机制来处理,被称为syntax;DirectX HLSL使用target来定义GPU性能需求。这些不同的名词其实都讲的一样的东西,而且可以使用一样的值(举例来说,现在的设置对汇编程序的 syntax同样有效)。GLSL程序直接编译成本地GPU机器码,所以不需要这种参数。

最后,在这个例子中,声明了在 Ogre中硬件加速骨骼动画的支持,Ogre将要使用这个程序代替软件蒙皮通路来提供给蒙皮使用(我们将要在第九章了解到更多的关于Ogre动画的相关细 节)。只有当你确定需要这个功能的时候再打开它,否则就把这行从程序里面去掉。

代码6-7:就是使用上面提到的硬件蒙皮 程序的材质,你可以在文件中找到

material Examples/Robot

{

// Hardware skinning techniique

technique

{

pass

{

vertex_program_ref Ogre/HardwareSkinningOneWeight

{

param_named_auto worldMatrix3x4Array[0] world_matrix_array_3x4

param_named_auto viewProjectionMatrix viewproj_matrix

param_named_auto lightPos[0] light_position 0

param_named_auto lightPos[1] light_position 1

param_named_auto lightDiffuseColour[0] light_diffuse_colour 0

param_named_auto lightDiffuseColour[1] light_diffuse_colour 1

param_named_auto ambient ambient_light_colour

}

// alternate shadow caster program

shadow_caster_vertex_program_ref Ogre/HardwareSkinningOneWeightShadowCaster

{

param_named_auto worldMatrix3x4Array[0] world_matrix_array_3x4

param_named_auto viewProjectionMatrix viewproj_matrix

param_named_auto ambient ambient_light_colour

}

texture_unit

{

texture r2skin.jpg

}

}

}

// Software blending technique

technique

{

pass

{

texture_unit

{

texture r2skin.jpg

}

}

}

}

代码6-7 中主要介绍了GPU程序中常用的参数。整个材质脚本中包含了两个不同的技术实现,第一个就是硬件蒙皮用的技术;当硬件无法支持上面的例程的时候将会载入第 二个使用固定功能管线的技术,在这里Ogre把固定管线的软件蒙皮作为整个材质的备用“垫底”的技术实现。

注意:虽然两 种技术都把r2skin.jpg作为纹理使用。不过其中的区别是,当使用可编程渲染管线的技术实现,这个纹理可被GPU程序作为参数使用(通过 TEXCOORDO输入参数语义),而固定渲染管线忽略了这项功能。

而上面的材质中到底处理了哪些事情呢?在第 一个技术中的第一个通路里面,我们告诉了Ogre我们希望使用shado_caster_vertex_program_ref这个GPU顶点程序(我们 之前在程序中声明的,当你阅读完第九章节之后可以了解更多细节)。后面7行告诉Ogre自动分配五个“uniform”参数到顶点着色程序(基于程序参数 的语义,硬件来自动处理这些参数的输入和输出)。

GPU程序参数类型

为 了进一步了解Ogre材质,我们必须在这里介绍一些Ogre中GPU程序所使用的不同的参数类型,在GPU程序里面这些参数通常被称为 Constant(常量);这是因为从GPU程序的角度来看,这些参数对它们而言事实上就是起到了常量的作用。所以在Ogre程序中谈及GPU程序时候经 常会交替使用Parameter(参数)和Constant(常量)两个术语。

我们首先要知道GPU程序中分为两种不同 的参数类型,依据不同的索引方式,分别是位置索引(Indexed)和名称定位(Named)。而每种参数根据不同的设置方法又分为自动和手动两种。所以 就像我们之前脚本中所使用的一样,细分下来一共有四种不同的参数,它们分别是param_indexed(手动索引)、param_name(手动名 称)、param_indexed_auto(自动索引)以及param_named_auto(自动名称)。

在汇编图 形着色语言中,唯一能设置的参数种类就是位置索引,所以提到位置索引的参数设置方法十有八九也是在谈论汇编语言的程序。其实位置索引依赖于一个很简单的前 提:所有参数在硬件上的存储地址都是固定的,而所谓索引就是直接从硬件地址中找出相应的参数。在这里举个简单的例子,如果我们有一个以四个浮点数(16字 节)为最小储存单位的硬件,第一个参数的索引值默认是0,而第二个和后面的参数的索引值则和前面的参数长度有关,如果我们在所引为0的地方放了一个16字 节的float4类型(四个float)数据,那么下一个参数的索引就是1,我们在所引为0的地方放入一个64字节的matrix4x4类型数据(相当于 4个float4),那么下一个参数的索引值就是4。

图6-7:分别展示了在同一块储存区域中5块相同大小 数据和两块不同大小数据的储存方式和索引地址

相对于位置索引的参数,名称定位的设置方式要简单且清晰很多。只需要在材质 和GPU程序中使用相同的参数名称,Ogre就会帮你把它们关联起来。虽然方面,但也有其局限性,因为这些通过命名定位的参数只能在高级着色语言中使用, 即Cg,HLSL和GLSL。而在汇编程序中无法支持这种参数方式。

对于上面提到的两种参数,Ogre在传递值的时候又 会分为自动和手动两种处理方式。它们的区别在于Ogre是否自动为你寻找并匹配参数所对应的值。Ogre可以帮你处理大量不类型的参数,并把它们的“自动 值”传递到图形硬件中的着色程序中。如果你希望了解所Ogre所提供所有“自动值”的类型,请参考本书附录B的列表。

不 过仍然后很多时候需要你通过手动的方式向着色程序传递参数,对于名称定位的参数类型(param_named),可以通过 GpuProgramParameters::setConstant()方法来传递参数。而对于位置索引的类型(param_indexed),则可以 通过GpuProgramParameters::setNamedConstant()方法,例如:

GpuProgramParametersSharedPtr params = entity->getSubEntity(0)->getMaterial()->

getTechnique(0)->getPass(0)->getVertexProgram().createParameters();

params->setNamedConstant(“ambient”, ColourValue(0.5, 0.5 0.5 1.0));

虽然上面只写了顶点着色程序的方法,不过对 于片断着色程序(也有人称之为像素着色程序)而言,也有类似的方法和机制帮助你完成参数的传值过程。

定制的自动参数

通 过Renderable(可渲染对象)的接口,你可以实现把CPU应程序中的参数变量,绑定到相应的GPU程序参数上面;这么做的好处就是当这些参数的值 改变的时候,不需要你手动通过setNameConstant()方法去通知GPU程序,取而代之的是这个过程将交给程序自动完成。为了实现这种神奇的功 能,首先要在材质脚本中进行相应的配置(参照附录B),然后在程序中通过setCustomParameter()这个Renderable类型的方法来 把参数绑定到具体变量。

举例来说,假如我们要在之前的代码6-7中绑定一个程序中的变量到CPU程序中投影算法的环境光 (ambient)参数上。我们首先需要做的是在材质脚本中把自动参数的指定从ambient_light_colour改成custom(下面的黑体 字)。

//改变投影参数

shadow_caster_vertex_program_ref Ogre/HardwareSkinningOneWeightShadowCaster

{

param_named_auto worldMatrix3x4Array[0] world_matrix_array_3x4

param_named_auto viewProjectionMatrix viewproj_matrix

param_named_auto ambient custom 12

}

后 面额外的参数(“12”)是用来传递给Ogre程序来辨认到底是绑定到哪个参数上面。被绑定到GPU程序中的变量类型只能是4-vector(四元向量) 类型(换句话说,就是Ogre中的Vector类),但是从CPU到GPU传递数据具体的含义可以任你确定,比如在我们现在的例子中,我们认为这个定制的 参数的意义是RGBA各式的颜色值。

Vector4 myColor;

entity->getSubEntity(0)->setCustomParameter(12, myColor);

现在,当GPU程序开始执行渲染到这个通路的时候,Ogre会自动的提供当前的 myColor变量的值作为参数。而我们在脚本中使用的索引(“12”)的唯一目的就是为了查询绑定参数,需要注意的是,这个索引值不可以为0。

比 较复杂的例子:视差帖图

我将要通过一个高级一点的例子作为这个章节的结尾,整个程序中既包含顶点程序也包含片断程序。视差帖图 (Offset Mapping)是一种用来在不影响模型结构的情况下,在3D空间中加入贴图表面视觉凹凸度的方法。图片6-8以及图片6-9展示了实际的效果。注意,这 些在纹理表面上增加的深度效果并不会受到观察角度的影响而产生变化。

图6-8:视差贴图在Demo_Dot3Bump演示 中的效果

图6-9:从另外的角度观察视差贴图

代 码6-8提供了Demo_Dot3Bump中使用的视差贴图例子的材质脚本。

代码6-8:在Samples/Media /materials/scripts/OffsetMapping.material中的GPU程序声明

// Bump map with Parallax offset vertex program, support for this is required

vertex_program Examples/OffsetMappingVP cg

{

source OffsetMapping.cg

entry_point main_vp

profiles vs_1_1 arbvp1

}

// Bump map with parallax fragment program

fragment_program Examples/OffsetMappingFP cg

{

source OffsetMapping.cg

entry_point main_fp

profiles ps_2_0 arbfp1

}

// Bump map with parallax fragment program

fragment_program Examples/OffsetMappingPS asm

{

source OffsetMapping_specular.asm

// sorry, only for ps_1_4 and above:)

syntax ps_1_4

}

material Examples/OffsetMapping/Specular

{

// This is the preferred technique which uses both vertex and

// fragment programs, supports coloured lights

technique

{

// do the lighting and bump mapping with parallax pass

pass

{

// Vertex program reference

vertex_program_ref Examples/OffsetMappingVP

{

param_named_auto lightPosition light_position_object_space 0

param_named_auto eyePosition camera_position_object_space

param_named_auto worldViewProj worldviewproj_matrix

}

// Fragment program

fragment_program_ref Examples/OffsetMappingFP

{

param_named_auto lightDiffuse light_diffuse_colour 0

param_named_auto lightSpecular light_specular_colour 0

// Parallax Height scale and bias

param_named scaleBias float4 0.04 -0.02 1 0

}

// Normal + height(alpha) map

texture_unit

{

texture rockwall_NH.tga

tex_coord_set 0

}

// Base diffuse texture map

texture_unit

{

texture rockwall.tga

tex_coord_set 1

}

}

}

// This is the preferred technique which uses both vertex and

// fragment programs, supports coloured lights

technique

{

// do the lighting and bump mapping with parallax pass

pass

{

// Vertex program reference

vertex_program_ref Examples/OffsetMappingVP

{

param_named_auto lightPosition light_position_object_space 0

param_named_auto eyePosition camera_position_object_space

param_named_auto worldViewProj worldviewproj_matrix

}

// Fragment program

fragment_program_ref Examples/OffsetMappingPS

{

param_indexed_auto 0 light_diffuse_colour 0

param_indexed_auto 1 light_specular_colour 0

// Parallax Height scale and bias

param_indexed 2 float4 0.04 -0.02 1 0

}

// Normal + height(alpha) map

texture_unit

{

texture rockwall_NH.tga

tex_coord_set 0

}

// Base diffuse texture map

texture_unit

{

texture rockwall.tga

tex_coord_set 1

}

}

}

// Simple no-shader fallback

technique

{

pass

{

// Base diffuse texture map

texture_unit

{

texture rockwall.tga

}

}

}

}

在 上面代码中不仅有材质脚本本身,同时提供了被材质脚本所引用的所有着色程序的声明。其中包含了一个顶点着色程序声明 (Examples/PffsetMappingVP)和两个片断着色程序声明(Examples/OffsetMappingFP和Examples /OffsetMappingPS)。在这里需要注意一点,我们在同一个材质脚本中使用了两个不同的片断着色程序声明,这是为了适配不同性能的图形硬件。 对于高于2.0顶点着色版本的图形硬件可以很顺利的执行Examples/OffsetMappingFP这个片断着色程序。而对于低于这个版本且高于 1.4顶点渲染版本的图形硬件只能是用Examples/OffsetMappingPS来实现类似的效果。这两个顶点程序被不同的技术所引用,在具体的 执行中Ogre会帮助我们根据硬件的能力作出选择。在下面的代码6-9种我们可以看到这些程序的Cg语言版本。而对于 Examples/OffsetMappingPS程序,你可以在6-10里面看到它的汇编代码。

代码 6-9:Cg语言实现的顶点和片断着色程序,可以在Samples/Media/materials/programs /OffsetMapping.cg文件中找到相应的代码

/* Bump mapping with Parallax offset vertex program

In this program, we want to calculate the tangent space light end eye vectors

which will get passed to the fragment program to produce the per-pixel bump map

with parallax offset effect.

*/

/* Vertex program that moves light and eye vectors into texture tangent space at vertex */

void main_vp(float4 position : POSITION,

float3 normal : NORMAL,

float2 uv : TEXCOORD0,

float3 tangent : TEXCOORD1,

// outputs

out float4 oPosition : POSITION,

out float2 oUv : TEXCOORD0,

out float3 oLightDir : TEXCOORD1, // tangent space

out float3 oEyeDir : TEXCOORD2, // tangent space

out float3 oHalfAngle : TEXCOORD3, //

// parameters

uniform float4 lightPosition, // object space

uniform float3 eyePosition, // object space

uniform float4x4 worldViewProj)

{

// calculate output position

oPosition = mul(worldViewProj, position);

// pass the main uvs straight through unchanged

oUv = uv;

// calculate tangent space light vector

// Get object space light direction

float3 lightDir = normalize(lightPosition.xyz - (position * lightPosition.w));

float3 eyeDir = eyePosition - position.xyz;

// Calculate the binormal (NB we assume both normal and tangent are

// already normalised)

// NB looks like nvidia cross params are BACKWARDS to what you'd expect

// this equates to NxT, not TxN

float3 binormal = cross(tangent, normal);

// Form a rotation matrix out of the vectors

float3x3 rotation = float3x3(tangent, binormal, normal);

// Transform the light vector according to this matrix

lightDir = normalize(mul(rotation, lightDir));

eyeDir = normalize(mul(rotation, eyeDir));

oLightDir = lightDir;

oEyeDir = eyeDir;

oHalfAngle = normalize(eyeDir + lightDir);

}

// General functions

// Expand a range-compressed vector

float3 expand(float3 v)

{

return (v - 0.5) * 2;

}

void main_fp(float2 uv : TEXCOORD0,

float3 lightDir : TEXCOORD1,

float3 eyeDir : TEXCOORD2,

float3 halfAngle : TEXCOORD3,

uniform float3 lightDiffuse,

uniform float3 lightSpecular,

uniform float4 scaleBias,

uniform sampler2D normalHeightMap,

uniform sampler2D diffuseMap,

out float4 oColor : COLOR)

{

// get the height using the tex coords

float height = tex2D(normalHeightMap, uv).a;

// scale and bias factors

float scale = scaleBias.x;

float bias = scaleBias.y;

// calculate displacement

float displacement = (height * scale) + bias;

float3 uv2 = float3(uv, 1);

// calculate the new tex coord to use for normal and diffuse

float2 newTexCoord = ((eyeDir * displacement) + uv2).xy;

// get the new normal and diffuse values

float3 normal = expand(tex2D(normalHeightMap, newTexCoord).xyz);

float3 diffuse = tex2D(diffuseMap, newTexCoord).xyz;

float3 specular = pow(saturate(dot(normal, halfAngle)), 32) * lightSpecular;

float3 col = diffuse * saturate(dot(normal, lightDir)) * lightDiffuse + specular;

oColor = float4(col, 1);

}

代 码6-10:为了配合低等级图形硬件而实现的汇编片断着色语言,可以在Samples/Media/materials/programs /OffsetMapping_specular.asm文件中找到相应代码

// Pixel Shader for doing bump mapping with parallax plus diffuse and specular lighting by nfz

// uv TEXCOORD0

// lightDir TEXCOORD1

// eyeDir TEXCOORD2

// half TEXCOORD3

// lightDiffuse c0

// lightSpecular c1

// Parallax scale and bias c2

// normal/height map texunit 0 - height map in alpha channel

// diffuse texture texunit 1

ps.1.4

texld r0, t0 // get height

texcrd r2.xyz, t0 // get uv coordinates

texcrd r3.xyz, t2 // get eyedir vector

mad r0.xyz, r0.a, c2.x, c2.y // displacement = height * scale + bias

mad r2.xyz, r3, r0, r2 // newtexcoord = eyedir * displacement + uv

phase

texld r0, r2.xyz // get normal N using newtexcoord

texld r1, r2.xyz // get diffuse texture using newtexcoord

texcrd r4.xyz, t1 // get lightdir vector

texcrd r5.xyz, t3 // get half angle vector

dp3_sat r5.rgb, r0_bx2, r5 // N dot H - spec calc

dp3_sat r4.rgb, r0_bx2, r4 // N dot L - diffuse calc

+ mul r5.a, r5.r, r5.r

mul r0.rgb, r4, r1 // colour = diffusetex * N dot L

+ mul r5.a, r5.a, r5.a

mul r5.rgb, r5.a, r5.a

mul r5.rgb, r5, r5

mul r5.rgb, r5, r5

mul r5.rgb, r5, c1 // specular = (N dot H)^32 * specularlight

mad r0.rgb, r0, c0, r5 // colour = diffusetex * (N dot L)* diffuselight + specular

+ mov r0.a, c2.b

这 里的可编程通路中只支持了一个光源的处理(通常指的事场景中第一个光源)。顶点程序支持三个自动提交参数:光源位置,眼睛位置,和世界-视点-投影变换矩 阵(这三个参数都传给了main_vp函数)。切线数据被作为顶点的数据元素(被编码成3D 纹理的UVW格式),与顶点位置、法线以及标准的UV贴图坐标被作为前四个参数传递给顶点程序。切线数据通常在离线产生的文件中得到,比如通过导出的模型 文件或者转换之后的XML文件;你也可以通过调用Mesh::buildTangentVectors()在程序中动态生成这些数据(在自动生成模型的算 法中经常用到)。main_vp函数提供相应的返回值会作为片断程序的输入参数提供,其中包括顶点坐标和四个贴图坐标(包括贴图UV坐标,灯光数据,眼睛 数据以及切线空间的半三维向量(half 3-vectors))。

Ogre自动提供给片断程序的参数包括环境光和镜面 光的颜色,并且你的应用程序可以把高光信息和偏移数据放在一个四元向量中传递给片断程序(如果你没有传入,将使用默认数据)。每个片段的实例还能得到之前 在顶点程序中输出的数据作为额外参数(这些参数通过之前的main_vp输出得到)。最终从判断程序中输出的结果就是最后计算出来的像素颜色,并把这个结 果储存在相应的帧缓存中。

而其中的汇编和Cg的片断程序执行了相同的工作;这么做的原因是在把纹理坐标通过另外一张纹理 调整的时候,早期版本的像素着色技术不能支使用持附属纹理读取。所以我们需要在PS1.4版本使用汇编程序来代替Cg。这样做的好处是可以让程序在支持 DirectX8.1级别的硬件上运行(比如Radeon 8500等)。

注意:你可能注意到了通过纹理传递任意数据类 型到片断程序。这种使用方法并不只是Ogre所专有的;对于GPU编程来说这是一个很常用的储存传递匿名数据的方法(不论是从CPU转递数据到顶点程序还 是从顶点程序传递到片断程序,或者是我们这样从CPU传递到片断程序)。

结语

Ogre的材质系统是非常灵活的,会有很多 种不同的方法来管理这些在你的场景中渲染属性。这一章节的目标就是让你熟悉Ogre如何管理自己的材质,以及在你的程序中如何创建并管理者这些。不过这仍 然仍无法在这一个章节中把所有Ogre材质的属性和指令的细节介绍清楚。虽然这是一项很费劲并让人厌烦的工作,但是这本书仍然在附录B中提供了材质脚本中 所有的指令和参数的列表。你可以用这章节学过的知识来尝试构建你需要的材质效果,当你需要知道所有材质脚本的属性和指令的时候,附录B就是你最好的快速参 考手册。

在下一章节中,你会学到关于Ogre的一个子系统,一个可以用来帮助你的程序来找到可以使用的材质脚本的系统:Ogre 的资源管理系统。

抱歉!评论已关闭.