高层着色语言(HLSL)是DirectX® 9最为强力的新组件之一。使用这种标准的高级语言, 在进行着色时编写者可以专注于算法而不用再去理会诸如寄存器的分配,寄存器读端口限制, 并行处理指令等等硬件细节. 除了把开发者从硬件细节中解放出来之外,HLSL 也具有高级语言所有的全部优势,诸如:代码重用容易, 可读性增强以及存在一个优化过的编译器。本书和 ShaderX2 - Shader Tips & Tricks 这本书的许多章节就用到了HLSL编写的着色器. 阅读完本章引言后,你会很容易理解那些着色器并在工作中用到它们。
这一章, 我们概述语言本身的基本结构以及将HLSL集成到你的应用程序中的方法。
一个简单的示例
在彻底描述HLSL之前, 让我们先看一下程序中HLSL顶点着色和HLSL像素着色的实现,这个程序渲染简单的木纹。下边显示的第一个HLSL着色是一个简单的顶点着色:
float4x4 view_proj_matrix;
float4x4 texture_matrix0;
struct VS_OUTPUT
{
float4 Pos : POSITION;
float3 Pshade : TEXCOORD0;
};
VS_OUTPUT main (float4 vPosition : POSITION)
{
VS_OUTPUT Out = (VS_OUTPUT) 0;
// Transform position to clip space
Out.Pos = mul (view_proj_matrix, vPosition);
// Transform Pshade
Out.Pshade = mul (texture_matrix0, vPosition);
return Out;
}
最开始两行声明了一对4×4的矩阵,分别命名为view_proj_matrix和texture_matrix0。在全局矩阵之后,声明了一个结构体。这个VS_OUTPUT有两个成员:Pos和Pshade。
该着色器的main函数接受一个单精度float4类型的输入参数并返回一个VS_OUTPUT结构体。float4类型的vPosition是着色器唯一的输入,返回的VS_OUTPUT结构体定义了 该顶点着色器的输出。目前不必关心在参数和结构体之后的关键字POSITION和TEXCOORD0。它们被称为语义,他们的含义将在本章后边讨论。
看一下main函数的实际代码部分,你可以看到一个内部函数mul,它被用来把输入的向量vPosition和矩阵view_proj_matrix相乘。在顶点着色器中这个函数最常被用来执行顶点-矩阵的乘法。就这样,vPosition被作为列向量,因为它是mul的第二个参数。如果向量vPosition是mul的第一个参数,它将被作为行向量。内部函数mul以及其他内部函数将在本章后边更详细的讨论。在把输入的vPosition的位置转换换到裁减空间之后,vPosition与另一个矩阵texture_matrix0相乘以产生3D纹理坐标。两次转换的结果写入返回的结构体VS_OUTPUT。一个顶点着色器必须总是以最小值输出到一个裁减空间位置。任何从顶点着色器输出的额外值都是 通过贯穿光栅化多边型插值得到的,也可用来输入到像素着色器。就这样,通过一个内插器, 三维的Pshade从顶点着色器被传递到像素着色器。
下边,我们看到一个简单的HLSL木纹像素着色器。这个像素着色器和刚才我们描述的顶点着色器一起工作,它将被编译成模型ps_2_0。
float4 lightWood; // xyz == Light Wood Color
float4 darkWood; // xyz == Dark Wood Color
float ringFreq; // ring frequency
sampler PulseTrainSampler;
float4 hlsl_rings (float4 Pshade : TEXCOORD0) : COLOR
{
float scaledDistFromZAxis = sqrt(dot(Pshade.xy, Pshade.xy)) * ringFreq;
float blendFactor = tex1D (PulseTrainSampler, scaledDistFromZAxis);
return lerp (darkWood, lightWood, blendFactor);
}最开始几行在全局范围内声明了一对浮点类型的四元数组和一个浮点变量。在这些变量之后,声明了一个被称为PulseTrainSampler的取样器。取样器将在章节后边讨论,目前你可以把它看成一个在显存中的窗口,它与过滤状态和纹理坐标寻址模式发生关联。在变量和取样器声明后边是着色器代码的主体部分。 你可以看到有一个输入参数Pshade,它是贯穿多边形插值得到的。它的值是由顶点着色器计算每一个顶点得出的。在像素着色器中,把着色空间Z轴上的笛卡尔距离作为一维纹理坐标来计算,衡量,使用,以存取绑定于PulseTrainSampler的纹理。tex1D()取样函数返回的颜色标量被用作混合因子,以混合在着色器全局范围内声明的两种相反颜色。像素着色器最终输出一个混合的四元向量结果。所有的像素着色器至少必须返回一个四元 RGBA 颜色。我们将在稍后章节中讨论像素着色器的附加选项。
汇编语言和编译对象
既然我们已经了解了一些HLSL着色器,这里简要讨论一下如何在代码中涉及Direct3D,D3DX,汇编着色器模型和你的程序。DirectX 8中第一次把着色器引入了Direct3D。在那个时候,这些虚拟着色器是这样定义的——每一个大致相当于一个特殊的3D硬件商生产的图形处理器。每个虚拟着色器都设计有汇编语言。在DirectX 8.0和DirectX 8.1中,编写这些着色器模型的程序(被命名为vs_1_1以及ps_1_1直到ps_1_4)相对短小并且一般由开发者直接用合适的汇编语言编写。如图1左所示,凭借D3DXAssembleShader()程序把人们可读的汇编语言代码传递给D3DX库并返回着色器的二进制表示,该二进制表示由CreatePixelShader()或CreateVertexShader()依次传递给Direct3D。更多传统汇编着色模型的细节,请参考在线和离线资源,包括Shader X 和DirectX SDK。
图1. Use of D3DX for Assembly and Compilation in DirectX 8 and DirectX 9
如图1右所示,在DirectX 9中的情形非常相似,凭借D3DXCompileShader() API,程序把HLSL着色器传递给D3DX并返回编译后着色器的二进制表示,该二进制表示由CreatePixelShader()或CreateVertexShader()轮流传递给Direct3D。生成的二进制汇编代码是一个函数,它只取决于选择的编译对象,而不是什么用户或开发者系统上的特殊图形设备。就是说,生成的二进制汇编程序与平台无关, 即可在任何地方编译或运行。事实上,Direct3D运行时本身并不知道HLSL的任何内容,除了二进制汇编着色器模型。这样做很有好处因为这就意味着HLSL编译器的更新不必依赖于Direct3D运行时。事实上,在2003年夏季末本书截稿与首印期之间,Microsoft开始计划发布含有更新过的HLSL编译器的DirectX SDK更新。
除了D3DX中HLSL编译器的开发之外,DirectX 9.0也提出了另外的汇编层着色器模型以展示最新的3D图形硬件的功能。 直接使用汇编语言为新的模型(vs_2_0,vs_3_0,ps_2_0和ps_3_0) 做开发,程序开发人员会感到自由 ,不过我们希望绝大多数开发人员都转移到HLSL从而专注于着色器的开发。
实际的硬件
当然,仅仅因为你可以写一个HLSL程序来表达一个特殊的着色算法不等于它能够在硬件上运行。前面已经讨论过,应用程序通过调用D3DX中的D3DXCompileShader() API把HLSL着色器编译成二进制汇编程序。这个API的入口参数之一是这样一个参数:它定义了HLSL编译器使用哪一个汇编语言模型(或编译对象)来表示最终着色器代码。如果一个程序在运行时执行HLSL着色器编译,程序会检测Direct3D设备的性能并选择匹配的编译对象。如果HLSL着色器中的算法太复杂以至于不能在选择的编译对象上执行,编译将会失败。这意味着尽管HLSL大大有利于着色器的开发,却不会把开发人员从这么一个现实中解放出来:把游戏封装后给拥有各种性能图形设备的用户。作为一个游戏开发人员,你仍然得为你的图像处理好一系列步骤,为更好的显示卡编写更好的着色器,为较老的卡编写更基本的。不过,有了编写完善的HLSL,负担可以大大减轻。
编译失败
如上所述, 给定的HLSL着色器编译特殊对象的失败说明对于编译对象来说着色器太过复杂。这就意味着着色器需要大量的资源或是需要一些诸如动态分支(不被所选编译对象所支持)的功能。例如,某个HLSL着色器可能被编写用于在一个着色器内存取所给定的六重纹理贴图。如果这个着色器被编译成ps_1_1, 编译将会失败,因为ps_1_1模型只支持四重纹理。其他编译失败的通常原因是超过了所选编译对象的最大指令计数器。某个HLSL中表示的算法也许仅仅需要大量指令而使得给定的编译对象不能被执行。
要重点注意的是所选编译对象不会限定编写人员所使用的HLSL语法。例如,着色器编写人员会使用'for'循环,子程序,'if-else'等等 语句,编译本身不支持循换,分支或'if-else'语句的对象。这种情况下,编译器将展开循环,内联函数调用并同时执行'if-else'语句的两个分支(译者注:即if与else后的语句全都执行),根据'if-else'语句中所使用的原始值选择合适的结果。当然, 如果最后所得到的着色器(程序)太长或相反超出了编译对象的资源,编译将失败。
命令行编译器: FXC
许多开发人员选择在着色器被封装之前把它从HLSL编译成二进制汇编语言,而不是在正在使用D3DX的客户机器上当程序载入时或首次运行时编译HLSL着色器。这保证了HLSL源代码不被窥视,同时也确保所有其程序能够永久运行的着色器已经通过其内部质量确认流程。在DirectX 9.0 SDK中提供了一个方便的命令行编译程序fxc允许开发人员脱机编译着色器。该程序有许多方便的选项,你不但可以以命令行方式编译你的着色器,也能产生指定编译对象的反汇编代码。如果你想优化你的着色器或只是想更详细的了解虚拟着色器的性能,在开发期间研究输出的反汇编代码是非常有用的。表1列出了这些命令行选项。
表1. FXC 命令行选项
-T target 编译对象 (默认: vs_2_0)
-E name 入口点 name (默认: main)
-Od 禁止优化
-Vd 禁止确认
-Zi 允许调试信息
-Zpr 按照行顺序挑选矩阵
-Zpc 按照列顺序挑选矩阵
-Fo file 输出目标文件
-Fc file 输出所生成代码的列表
-Fh file 输出含有生成代码的头部
-D id=text 定义宏
-nologo 没有版权信息
既然你了解了用于着色器开发的HLSL编译器的内容,我们就可以讨论实际的语言结构了。 在我们继续下面内容的时候,头脑里要一直保留着编译对象的概念以及潜在的汇编着色器模型的不同性能,这很重要。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/starflash2003/archive/2004/10/08/128688.aspx
现在你已经对什么是HLSL顶点和像素着色器以及他们如何与低层汇编着色器相互作用有了了解,我们将讨论一些语言本身的细节。
关键字
关键字是HLSL语言保留的预定义标识符,不能在你的程序中作为标识符使用。标有'*'的关键字不区分大小写。
表2. HLSL语言所保留的关键字
asm* bool compile const
decl* do double else
extern false float for
half if in inline
inout int matrix* out
pass* pixelshader* return sampler
shared static string* struct
technique* texture* true typedef
uniform vector* vertexshader* void
volatile while
下列关键字当前没有使用,不过保留给将来使用:
表3. 当前并没使用的保留关键字
auto break compile const
char class case catch
default delete const_cast continue
explicit friend dynamic_cast enum
mutable namespace goto long
private protected new operator
reinterpret_cast short public register
static_cast switch signed sizeof
throw try template this
typename unsigned using union
virtual
数据类型
HLSL支持各种数据类型,从简单的标量到较复杂的类型如向量和矩阵。
标量类型
语言支持以下标量数据类型:
表4. 标量数据类型
bool true or false
int 32-bit signed integer
half 16-bit floating point value
float 32-bit floating point value
double 64-bit floating point value
如果你已经熟悉了汇编层编程模型,就会知道并非所有图形处理器天生就支持这些数据类型。因此,整数也许需要由浮点硬件来仿真实现。这意味着并不保证超出整数(在这些平台上用浮点数表示的)范围的整数运算如期望那样运行。另外,并非所有对象平台支持半精度或双精度值。如果不支持,将使用单精度浮点数来仿真。
向量类型
常常会在HLSL着色器中声明向量变量。声明这些向量有许多方法,包括下列所示:
表5. 向量类型
vector 一个四维向量; 每一维是浮点类型
vector < type, size > 维数为size的向量; 每一维是type类型
声明向量最通常的方法是在类型后跟一个2-4的整数作为名字。例如,要声明一个4元组单精度浮点型,可以如下声明:
float4 fVector0;
float fVector1[4];
vector fVector2;
vector <float, 4> fVector3;
例如,要声明一个3元组布尔型,可以如下声明:
bool3 bVector0;
bool bVector1[3];
vector <bool, 3> bVector2;
一旦定义了一个向量,就可通过使用类似访问数组的语法或使用一个swizzle来访问其单独的维数。在这个swizzle例子中,维数必须来自于{x. y, z, w}或{r, g, b, a}命名空间 (不过不是两者都)。例如:
float4 pos = {3.0f, 5.0f, 2.0f, 1.0f};
float value0 = pos[0]; // value0 is 3.0f
float value1 = pos.x; // value1 is 3.0f
float value2 = pos.g; // value2 is 5.0f
float2 vec0 = pos.xy; // vec0 is {3.0f, 5.0f}
float2 vec1 = pos.ry; // INVALID because of bad swizzle
需要注意的是ps_2_0和更低的像素着色器模型并不支持arbitrary swizzles(译者注:Arbitrary swizzle实际上可以看作一个“修正器”(modifier),它用于修改指令和寄存器。其主要功能是减少在一个着色器中使用的指令数目,从而提高效率。)因此,当编译成这些对象时,原本简洁的高层代码(使用swizzle)可能变成相当难理解的二进制汇编代码。你应当熟知这些汇编模型中可以用到的swizzle。
矩阵类型
HLSL着色器中还常常会用到的变量类型是矩阵,它是二维数组。与标量和向量一样,矩阵由其他一些基本数据类型组成:布尔型,整型,半精度,单精度或双精度浮点型。矩阵可以是任意大小,不过一般都使用4行4列的矩阵。你可以再调用本章开头顶点着色器的例子,在全局范围声明两个4 × 4单精度浮点型矩阵:
float4x4 view_proj_matrix;
float4x4 texture_matrix0;
自然,也可使用其他维数的矩阵。例如,我们用不同方式声明一个3行4列的单精度浮点型矩阵:
float3x4 mat0;
matrix<float, 3, 4> mat1;
和向量一样,可以使用存取数组或结构体/swizzle的语法访问矩阵中的单一元素。例如,要访问矩阵view_proj_matrix的左上角元素可以使用如下面例子中数组下标的方法:
float fValue = view_proj_matrix[0][0];
也有用结构体的语法,定义结构体是由于要访问和swizzling of矩阵元素。从0开始的行列位置,如下:
_m00, _m01, _m02, _m03
_m10, _m11, _m12, _m13
_m20, _m21, _m22, _m23
_m30, _m31, _m32, _m33
从1开始的行列位置,如下:
_11, _12, _13, _14
_21, _22, _23, _24
_31, _32, _33, _34
_41, _42, _43, _44
也可以使用数组符号访问矩阵:例如:
float2x2 fMat = {3.0f, 5.0f, // row 1
2.0f, 1.0f}; // row 2
float value0 = fMat[0]; // value0 is 3.0f
float value1 = fMat._m00; // value1 is 3.0f
float value2 = fMat._12 // value2 is 5.0f
float value3 = fMat[1][1] // value3 is 1.0f
float2 vec0 = fMat._21_22; // vec0 is {2.0f, 1.0f}
float2 vec1 = fMat[1]; // vec1 is {2.0f, 1.0f}
在你的着色器中打算使用的HLSL中有几个可选的类型修饰符。通常把不想被着色器的代码修改的量设为const(常量)类型修饰符。在赋值符号左边使用常量(例如作为一个lval)会产生一个编译错误。
可以用row_major(行优先)类型修饰符与col_major(列优先)类型修饰符指定在存储常数硬件中的矩阵格式。row_major(行优先)类型修饰符表示矩阵中的每一行被存储在一个单个的常数寄存器中。同样地,使用col_major(列优先)表示矩阵中的每一列被存储在一个单个的常数寄存器中。默认为列优先。
存储类别修饰符
存储类别修饰符通知编译器给定变量的作用域和生存期。这些修饰符是可选的,可在变量类型前以任意次序出现。
像C语言一样,一个变量可以被声明为static(静态变量)或extern(外部变量)。(这两个修饰符是互斥的)在全局范围,static(静态)类别修饰符表示变量只能由着色器访问,而不能由应用程序通过API访问。任何在全局范围声明的非静态变量可以由应用程序通过API修改。像C语言一样,在局部范围使用static(静态)修饰符表示变量所含数据将在所声明函数内始终存在(译者注:即生存期为全局,作用域为函数内)。
在全局范围使用extern(外部)修饰符表示可由外部着色器通过API修改。不过这属于多此一举,因为在全局范围声明的变量默认就是这样。
使用shared(共享)修饰符设定将由两种效果共享的全局变量。
前缀为uniform的变量先在外部被初始化,然后进入HLSL着色器。(例如,通过Set*ShaderConstant*() API)。把全局变量当作被uniform声明。不过由于值在着色器中可以被修改,所以不可能是常数。
例如,假定你在全局范围声明了下列变量:
extern float translucencyCoeff;
const float gloss_bias;
static float gloss_scale;
float diffuse;
变量diffuse和translucencyCoeff可被Set*ShadercConstant*() API置位,也可被着色器本身修改。常量gloss_bias可被Set*ShadeConstant*() API置位,不过不能被着色器代码修改。最后,静态变量gloss_scale不能被Set*ShaderConstant*()API置位,不过可以也只能在着色器中被修改。
初始化
如前面例子显示的,和C语言中的习惯一样可以在变量声明时进行初始化。例如:
float2x2 fMat = {3.0f, 5.0f, // row 1
2.0f, 1.0f}; // row 2
float4 vPos = {3.0f, 5.0f, 2.0f, 1.0f};
float fFactor = 0.2f;
向量运算
在HLSL中,当执行关于向量的数学运算时需要留心一些程序陷阱(gotchas)。如果为3D图形编写着色器,绝大部分程序陷阱(gotchas)可以靠直觉发现。例如,定义标准的二元运算符以进行每一维的运算。
float4 vTone = vBrightness * vExposure;
假定vBrightness和vExposure都是float4类型,相当于:
float4 vTone;
vTone.x = vBrightness.x * vExposure.x;
vTone.y = vBrightness.y * vExposure.y;
vTone.z = vBrightness.z * vExposure.z;
vTone.w = vBrightness.w * vExposure.w;
要注意在4D向量vBrightness和vExposure间不是点乘。此外,用这种方式乘以矩阵变量不会引起矩阵相乘。点乘法和矩阵相乘法是通过内部函数mul()实现的,这将在后边讨论。
构造函数
常能在HLSL着色器中见到的属于其他语言特色的是构造函数,和C++中的类似不过增加了一些处理复杂数据类型的内容。构造函数使用的例子:
float3 vPos = float3(4.0f, 1.0f, 2.0f);
float fDiffuse = dot(vNormal, float3(1.0f, 0.0f, 0.0f));
float4 vPack = float4(vPos, fDiffuse);
构造函数通常用在:想要临时定义一个常量(如上边的dot(vNormal, float3(1.0f, 0.0f, 0.0f)))或想同时显式地压缩更小的数据类型。(如上边的float4(vPos, fDiffuse))。在这个例子中,构造函数float4接收一个float3类型和一个float类型同时返回一个数据被压缩的float4类型。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/starflash2003/archive/2004/10/12/132884.aspx
为了有助于着色器的编写和所产生代码的效率,最好熟悉一下HLSL的强制类型转换机制。强制类型转换常用于扩展或缩减选定的变量以匹配要赋值的变量。例如,在下列例子中,初始化vResult时把float型常量0.0f强制转换为float4型{0.0f , 0.0f , 0.0f , 0.0f }。
float4 vResult = 0.0f;
当把一个高维数据类型如向量或矩阵类型赋值给一个低维数据类型时就会发生类似的强制转换。这些情况下,额外数据都被有效省略。例如,编写下列代码:
float3 vLight;
float fFinal, fColor;
fFinal = vLight * fColor;
这个例子中,只是把float类型的标量fColor与vLight中的第一个成员相乘,从而把vLight强制转换为float类型。fFinal等于vLight.x * fColor。
最好先熟悉一下表4,HLSL的强制类型转换规则:
表6. HLSL的强制类型转换规则
Scalar-to-scalar 一直有效。当布尔型被强制转换为整数或浮点型,false变为0,true变为1。当整数或浮点型被强制转换为布尔型,0变为false,非0变为true。当浮点型被强制转换为整数类型,值被向0舍入,这与C语言的一样截断机制一样。
Scalar-to-vector 一直有效。 该强制转换操作通过复制标量并填充到向量。
Scalar-to-matrix 一直有效。 该强制转换操作通过复制标量并填充到矩阵。
Scalar-to-structure 该强制转换操作通过复制标量并填充到结构体。
Vector-to-scalar 一直有效。 选择向量的第一部分。
Vector-to-vector 目标向量必须不大于资源向量。该强制转换操作是通过保留最左边的值,去掉多余值。这样做的目的是可以把行矩阵,列矩阵和数字结构看成向量。
Vector-to-matrix 向量大小必须与矩阵大小相等。
Vector-to-structure 结构体不大于向量,且结构体各部分均为数字则有效。
Matrix-to-scalar 一直有效。 选择了矩阵的左上部分。
Matrix-to-vector 矩阵大小必须与向量大小相等。
Matrix-to-matrix 目标矩阵在任何一维都不大于源矩阵,该强制转换操作是通过保持左上值,去掉多余值。
Matrix-to-structure 结构体的大小等于矩阵的大小,结构体的所有成员都是数字。
Structure-to-scalar 结构体必须包含至少一个数字型成员
Structure-to-vector 结构体必须至少与向量的大小一样,第一个成员必须是数字,一直到向量的大小。(译者注:即成员数量与向量大小一样)
Structure-to-matrix 结构体必须至少与矩阵的大小一样。第一个成员必须是数字,一直到矩阵的大小。(译者注:即成员数量与矩阵大小一样)
Structure-to-object 结构体至少包含一个对象的成员。该成员的类型必须和对象类型完全相同。
Structure-to-structure 目标结构必须不大于源结构。一个有效的强制转换必定存在于所有相应的源成员与目的成员之间。
结构体
正如上边第一个着色器示例显示的,在HLSL着色器中定义一个结构体常常带来方便。例如,许多着色器编写者在他们的顶点着色器代码中会定义一个输出的结构体,使用该结构体作为他们的顶点着色器主函数的返回类型。(对于像素着色器很少这样做因为大多数像素着色器只有一个float4输出。)一个结构体的例子如下,来自于NPR Metallic着色器,我们将在后边讨论该着色器。(译者注:NPR(non-photo reality),是一种独特的二w维效果)
struct VS_OUTPUT
{
float4 Pos : POSITION;
float3 View : TEXCOORD0;
float3 Normal: TEXCOORD1;
float3 Light1: TEXCOORD2;
float3 Light2: TEXCOORD3;
float3 Light3: TEXCOORD4;
};
在HLSL着色器中也可以声明结构体作为普通使用。同样遵循上边所概括的强制类型转换规则。
取样器
要是想在像素着色器中对于每一个不同的纹理贴图进行取样,必须声明一个取样器。再调用一下前边描述的着色器中的hlsl_rings():
float4 lightWood; // xyz == Light Wood Color
float4 darkWood; // xyz == Dark Wood Color
float ringFreq; // ring frequency
sampler PulseTrainSampler;
float4 hlsl_rings (float4 Pshade : TEXCOORD0) : COLOR
{
float scaledDistFromZAxis = sqrt(dot(Pshade.xy, Pshade.xy)) * ringFreq;
float blendFactor = tex1D (PulseTrainSampler, scaledDistFromZAxis);
return lerp (darkWood, lightWood, blendFactor);
}
在这个着色器中,我们在全局范围声明了一个被称为PulseTrainSampler的取样器并把它作为第一个参数传递给内部函数tex1D()(将在下一部分讨论内部函数)。HLSL取样器 有一个非常直接的映射——在基于取样器概念的API与实际硅(在负责寻址纹理和过滤纹理的3D图形处理器中)之间轮换。在一个着色器中必须为每一个你计划访问的纹理贴图定义一个取样器,不过你可以在一个着色器中多次使用给定的取样器。这种处理方法在图像处理程序中非常普遍(在ShaderX2 - Shader Tips & Tricks的"Advanced Image Processing with DirectX 9 Pixel Shaders"章节有讨论),为了给由着色器代码表示的一个内部过滤器提供数据,输入的图像被以不同的纹理坐标多次取样。例如,下面的着色器使用光栅化引擎(rasterizer)通过一对Sobel滤波器把一个高度贴图(height map)转换为一个法线贴图(normal map)。
sampler InputImage;
float4 main( float2 topLeft : TEXCOORD0, float2 left : TEXCOORD1,
float2 bottomLeft : TEXCOORD2, float2 top : TEXCOORD3,
float2 bottom : TEXCOORD4, float2 topRight : TEXCOORD5,
float2 right : TEXCOORD6, float2 bottomRight : TEXCOORD7): COLOR
{
// Take all eight taps
float4 tl = tex2D (InputImage, topLeft);
float4 l = tex2D (InputImage, left);
float4 bl = tex2D (InputImage, bottomLeft);
float4 t = tex2D (InputImage, top);
float4 b = tex2D (InputImage, bottom);
float4 tr = tex2D (InputImage, topRight);
float4 r = tex2D (InputImage, right);
float4 br = tex2D (InputImage, bottomRight);
// Compute dx using Sobel operator:
//
// -1 0 1
// -2 0 2
// -1 0 1
float dX = -tl.a - 2.0f*l.a - bl.a + tr.a + 2.0f*r.a + br.a;
// Compute dy using Sobel operator:
//
// -1 -2 -1
// 0 0 0
// 1 2 1
float dY = -tl.a - 2.0f*t.a - tr.a + bl.a + 2.0f*b.a + br.a;
// Compute cross-product and renormalize
float4 N = float4(normalize(float3(-dX, -dY, 1)), tl.a);
// Convert signed values from -1..1 to 0..1 range and return
return N * 0.5f + 0.5f;
}
该着色器只使用了一个取样器:InputImage,不过示例中八次调用了内部函数tex2D()。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/starflash2003/archive/2004/10/13/135493.aspx