The Complete Effect and HLSL Guide(十)
自定义函数
除了HLSL提供的大量内建函数以外,同样可以使用类似于C语言的方式定义自定义函数。下面就是声明函数的语法:
[ static inline target] [ const ] return_type id ( [ parameter_list] ) { [statement ] }
接下来的是定义函数原型的语法:
[ static inline target ] [ const ] return_type id ( [ paramter_list] );
你看,可以使用一系列修饰符作为函数的前缀,来控制编译器对待这些函数的行为。表 3-2列出了可能的自定义函数前缀,以及它们的含义。
表
3-2
自定义函数前缀
前缀 定义
|
static 这个前缀表示函数只存在于当前shader程序的作用域中,不能被多个shader共享。本书中大多数情况都不使用这个关键字。
inline 这个前缀表示把函数代码复制到调用代码之后执行,而不是真正按照函数调用的方法来执行代码。注意,对编译器来说这个前缀只起提示作用,并不能保证函数是内联的。还需要注意,这个前缀是当前HLSL编译器的默认行为。
target 这个前缀表示希望使用哪一个版本的顶点或像素着色器版本来编译代码。允许编译器对特定着色器版本进行优化。
const 这个前缀表示参数值在函数中不能改变。
记住,默认情况下所有函数都是内联的,因此不能递归调用。这是因为函数的处理,编译和执行都是由顶点和像素着色器硬件来完成的。着色器硬件只能以线性方式执行代码,不能跳转到代码的其他位置。这表示函数总是被内联到调用代码中。递归将导致代码路径是不确定的,所以被禁止。
此外,在parameter_list中定义的参数,也必须符合特定的声明语法:
[ uniform in out inout ] type id [ : semantic ] [ = default ]
同样可以使用修饰符和关键字作为参数前缀,控制编译器对待参数的行为。表3-3列出了参数前缀和它所表示的含义。
表
3-3
函数参数前缀
转换类型 描述
|
in 这是默认情况下的参数行为,对函数来说,这是个只读参数。
out 这个前缀表示参数是一个返回值,任何对它所做的改变都将返回给调用者。
inout 这个前缀是in和out行为的组合。
uniform 这个前缀和in前缀具有相同含义,但是特别指明参数来自于shader中的常量。
当为参数指定了语义标识符之后,他将会告诉编译器去哪里找输入数据源。举例来说,TEXCOORD0标识符将会告诉编译器把第一组纹理坐标作为这个参数的输入值。注意,标识符只对shader中的顶级函数才有意义,也就是顶点着色器或像素着色器的入口函数才能使用语义标识符。
目前为止,只剩下return_type参数没有讨论了,它用来定义函数的返回值类型。当函数没有返回值,或者通过out参数返回数据时,应该把返回值类型设置为void。
函数返回值可以是HLSL中定义的任何基本数据类型。此外,也可以是结构,允许函数同时返回一系列值。下面就是把结构作为返回值的例子:
struct VS_OUTPUT
{
float4 vPosition : POSITION;
float4 vDiffuse : COLOR;
};
VS_OUTPUT VertexShader_Tutorial ( flaot4 inPos : POSITION )
{
VS_OUTPUT Result;
//Do something…
return Result;
}
使用return关键字加变量名来从函数中返回值,这里变量类型必须和函数返回值类型一致。
这里,你可能在想如何使用带out前缀的参数。实际上,可以使用out参数来代替函数返回值。下面的代码展示了如何使用out参数来代替函数返回值。
struct VS_OUTPUT
{
float4 vPosition : POSITION;
float4 vDiffuse : COLOR;
};
void VertexShader_Tutorial ( float4 inPos : POSITION,
out VS_OUTPUT outReturn)
{
VS_OUTPUT Result;
//Do something
outReturn = Result;
}
你看,在HLSL中编写函数和使用其他高级语言几乎是一样的。在学习复杂函数和shader之前,先来看看如何用函数定义shader。
通过函数创建
Shader
编写自定义函数的主要目的之一就是通过它们定义shader。虽然讨论effect framework时我们才会详细学习如何声明shader,但是我希望先透露一点点内容给你。这里是使用自定义函数声明shader的例子。
Shader = compile shaderProfile FunctionName();
上面的语法中,shaderProfile可以是第一章中提到过的任意一个profile值,FunctionName元素则将被编译为shader。定义shader的过程实际上很简单,把希望的shader代码编写为一个函数,之后使用上面的语法把他编译并声明为shader。
为了让这个例子更具体一些,我们来看看如果如何编写一个简单的像素光照函数,并把它声明并编译为像素着色器。
flaot4 lighting ( in float3 normal, in float3 light, in float3 halfvector, in float4 color)
{
float4 color;
color = dot ( normal, light) * color;
color += dot ( light , halfvector) * color;
return color;
}
float4 myShader ( in float2 tex:TEXCOORD0, in float3 normal : TEXCOORD1,
in float3 light: TEXCOORD2, in float3 halfvector:TEXCOORD3,in float4 color:COLOR0)
{
//compute the lighting color
Float4 lightColor = lighting(normal,light,halfvector,color);
//Fetch the texture color
Float4 terColor = tex2D( texture_sampler, tex );
//Modulate the final color
Return lightColor * terColro;
}
PixelShader = compile ps_2_0 myShader();
小节以及接下来的内容
这一章,我们讨论了HLSL中的函数。HLSL语言本身提供了一个丰富的内置函数库,有大约70个内建函数供开发者调用。但更重要的是你可以通过编写自定函数定义shader,或把完成特定功能的代码打包到一起,以便复用。实际上,函数也许是HLSL中最重要的元素,每次定义shader时都必须用它。
现在我们已经学习了HLSL的大部分语法,可以开始学习effect framework了吗?暂时还不行!下一章我们将学习编写一系列基本的shader例子。这些例子不单用来告诉你HLSL能做什么,同时,也是我们继续学习所要用的基础代码。
~~~~~~~~~~~~~~第三章完~~~~~~~~~~