第十六章
高级着色语言:High-Level Shading Language,HLSL
顶点着色器和像素着色器就是自行编写的规模较小的定制程序(custom programs),这些定制程序取代固定功能流水线中的某一功能模块,并在图形卡的GPU(Graphics Processing Unit,图形处理单元)中执行,通过这种功能替换,便在实现各种图形效果时获得了巨大的灵活性,也就是说,不再受制于那些预定义的"固定"运算
在Dirext 8.0x系列版本中,着色器程序是用底层的汇编语言来编写的
在Dirext 9.0x系列版本中,提供了一种专门用来编写着色器程序的高级着色语言HLSL
HLSL和汇编相比的好处:
1 提高生产力,更快捷,更容易
2 增强可读性,可读性好,易于调式和维护
3 编译器生成的汇编代码比手工编写的汇编代码更高效
4 借助HLSL编译器,可将着色器代码编译为任何可用的着色器版本
5 学习HLSL语言大大缩短学习曲线
如果图形卡不支持顶点着色器和像素着色器,需要切换REF设备
顶点着色器可用软件顶点运算(software vertex processing)方式来模拟,即在创建设备时,将设备行为标记设定为D3DCREATE_SOFTWARE_VERTEXPROCESSING
HLSL着色器程序的编制
HLSL着色器程序可以用一个长字符串的形式出现在应用程序的源文件中,但更方便也更模块化的做法是将着色器代码与应用程序代码分离,基于上述考虑,可在记事本中编写着色器代码并将其保存为常规的ASCII文本文件,接下来级可以使用函数D3DXCompileShaderFromFile对着色器文件进行编译了
例:HLSL编写的顶点着色程序,对顶点实施了取景变换(view transformation)和投影变换(projection transformation),并将顶点的漫反射颜色分量设为蓝色
// Global variable to store a combined view and projection transformation matrix .We initialize this variable
// from the application
matrix ViewProjMatrix;
//Initialize a global blue color vector
vector Blue = {0.0f,0.0f,1.0f,1.0f};
// Input structure describes the vertex that is input into the shader .Here the input vertex contains a position //componment only
struct VS_INPUT
{
vector position : POSITION;
};
// Output structure describes the vertex that is Output from the shader .Here the output vertex contains a position //and color componment
struct VS_OUTPUT
{
vector position :POSITION;
vector diffuse :COLOR;
};
// Main Entry Point ,observe the main function receives a copy of the input vertex through its parameter and returns //a copy of the output vertex in computes
VS_OUTPUT Main(VS_INPUT input)
{
// zero out members of output
VS_OUTPUT output = (VS_OUTPUT)0;
// transform to view space and project
output.position = mul(input.position,ViewProjMatrix);
// set vertex diffuse color to blue
output.diffuse = Blue;
return output;
}
ViewProjMatrix 是一个matrix类型的实例,该类型是HLSL中的内置类型,表示维数4*4,该变量存储了取景变换矩阵和投影变换矩阵的乘积,这样它就同时描述了两种变换,所以,只需进行一次向量矩阵乘法就可实现上述两种变换,注意,在着色器代码中时找不到这些变量的初始化代码的,因为这些变量的初始化应在应用程序源代码中进行而非着色器程序代码中,
Blue是HLSL内置类型vector的一个实例,该类型表示一个4D向量,只将其视为RGBA颜色向量,并将其初始化为蓝色
VS_INPUT 和VS_OUTPUT出现的很特别的冒号(colon)语法(syntax)表达了一种语义(semantic),用来指定变量的用途,这与顶点结构中的灵活顶点格式(FVF)非常相似.
例
vector position:POSITION:
语法":POSITION"的意思是说向量position用于描述输入顶点的位置信息
例
vector diffuse :COLOR
":COLOR"意思是说向量diffuse用于描述输出顶点的颜色信息
每个HLSL程序也应该有一个入口点,但该名称并非强制性的,在遵循函数命名的规则的前提下,着色器的入口函数的命名可自由选择,入口函数必须有一个可接收输入结构的参数,该参数将用于把输入顶点传给着色器,而入口函数必须返回一个输出结构实例,用来将经过处理的顶点自着色器输出
HLSL着色器程序的编译
每个着色器都用常量表来存储其变量,为了使应用程序能够访问着色器的常量表(Constant Table),D3DX库提供了接口ID3DXConstantTable,借助该接口,可在应用程序源代码中对着色器代码中的变量进行设置
获取常量句柄:
当给定着色器中期望引用的那个变量的名称时,该函数返回一个引用了该变量的D3DXHANDLE类型的句柄(Handle)
D3DXHANDLE ID3DXConstantTable::GetConstantByName(
D3DXHANDLE hConstant,// scope of constant
LPCSTR pName // name of constant
);
#hConstant 一个D3DXHANDLE类型的句柄,标识了那个包含了希望获取句柄的变量的父结构,例如,希望得到某个特定结构实例(instance)的一个单个数据成员的句柄,可为该参数传入该结构实例的句柄,如果想要获取指向顶级(top - level)变量的句柄,应该将该参数指定为0
#pName希望获取句柄的那个着色器源代码中的变量名称
例:如果引用的着色器中的变量名称为ViewProjMatrix,且该变量为顶级参数,可以这样实现引用:
// Get a handle to the ViewProjMatrix variable in the shader
D3DXHANDLE h0;
h0 = ConstTable -> GetConstantByName(0,"ViewProjMatrix");
常量的设置:
获取了变量D3DXHANDLE类型的句柄,就可在应用程序中使用方法ID3DXConstantTable::SetXXX对该变量进行设置,XXX表示被设置变量的类型名称,实际调用时只有用类型名将其替换即可
例:设置变量为一个vector类型的数组,该方法对应SetVectorArray
方法ID3DXConstantTable::SetXXX的通用签名:
HRESULT ID3DXConstantTable::SetXXX(
LPDIRECT3DDEVICE9 pDevice,//与常量表相关的设备指针
D3DXHANDLE hConstant,//要设置的那个变量的句柄
XXX value //指定引用了那个着色器的变量应被赋何值,XXX替换该变量名 //称,对某些类型(bool,int float)出入的是该值的副本,而对 //另外一些类型(向量,矩阵结构体),传入的是指向该值的指针
);
如果对数组进行设置,SetXXX方法还增加了一个参数,以接收该数组的维数,例,设置一个4D向量的数组的方法原型为:
HRESULT ID3DXConstantTable::SetVectorArray(
LPDIRECT3DDEVICE9 pDevice,// associated device
D3DXHANDLE hConstant,// handle to shader variable
CONST D3DXVECTOR4* pVector,// pointer to array
UINT Count// number of elements in array
);
该列表描述了那些可用接口ID3DXConstantTable进行设置的类型,假定已经拥有了一个合法的设备指针以及所要设置的变量的句柄
#SetBool: 设置一个布尔值
bool b = true
ConstTable->SetBool(Device,handle,b);
#SetBoolArray 设置布尔数组
bool b[3] = {true,false,true};
ConstTable->SetBoolArray(Device,handle,b,3);
#SetFloat 设置浮点数
float f = 3.14f;
ConstTable->SetFloat(Device,handle,f);
类似的还有:
#SetFloatArray
#SetInt
#SetIntArray
#SetMatrix设置一个4*4矩阵
D3DXMATRIX M(..);
ConstTable->SetMatrix(Device,handle,&M);
#SetMatrixArray设置4*4矩阵数组
D3DXMATRIX M[4];
ConstTable->SetMatrixArray(Device,handle,M,4);
#SetMatrixPointerArray 设置一个4*4矩阵指针数组
D3DXMATRIX* M[4];
ConstTable->SetMatrixPointerArray(Device,handle,M,4);
#SetMatrixTranspose设置4*4转置矩阵
D3DXMATRIX M(...);
D3DXMatrixTranspose(&M,&M);
ConstTable->SetMatrixTranspose(Device,handle,&M);
#SetMatrixTransposeArray设置4*4转置矩阵数组
D3DXMATRIX M[4];
ConstTable->SetMatrixTransposeArray(Device,handle,M,4);
#SetMatrixTransposePointerArray设置4*4转置矩阵指针数组
D3DXMATRIX* M[4];
ConstTable->SetMatrixTransposePointerArray(Device,handle,M,4);
#SetVector设置一个D3DXVECTOR4类型的变量
D3DXVECTOR4 v(1.0f,2.0f,3.0f,4.0f);
ConstTable->SetVector(Device,handle,&v);
#SetVectorArray设置向量数组类型的变量
D3DXVECTOR4[3];
ConstTable->SetVectorArray(Device,handle,v,3);
#SetValue设置大小任意的类型,
例,结构体,用该函数对D3DXMATRIX类型的变量进行设置
D3DXMATRIX M(...);
ConstTable->SetValue(Device,handle,(void*)&M,sizeof(M);
设置常量的默认值:即在变量声明时被赋予的初值,该方法在应用程序的设置过程中,应调用一次
HRESULT ID3DXConstantTable::SetDefaults(LPDIRECT3DDEVICE9 pDevice);
HLSL着色器程序编译:
该函数对保存在文本文件中的着色器程序进行编译
HRESULT D3DXCompileShaderFromFile(
LPCSTR pSrcFile,//保存了着色器源代码的那个文本文件的名称
CONST D3DXMACRO* pDefines,//该参数可选,可设为NULL
LPD3DXINCLUDE pInclude,//指向ID3DXInterface接口的指针,应用程序实现该接口,以重 //载默认的include行为(include behavior),通常默认的include行为已足够 //满足要求,可将该参数指定为NULL而将其忽略
LPCSTR pFunctionName,//一个指定了着色器入口函数名称的字符串,例,如果着色 //器的入口函数为Main,该参数应赋为"Main"
LPCSTR pTarget,//指定了要将HLSL源代码编译成的着色器版本,该参数为一字符串 //,合法的顶点着色器版本有:vs_1_1,vs_2_0,vs_2_sw,合法的像素着色器 //版本有:ps_1_1,ps_1_2,ps_1_3,_ps_2_sw,例,将顶点着色器译为2.0版本, //则需要将该参数指定为vs_2_0,能将着色器编译为不同着色器版本的能力是 //HLSL与汇编语言相比的一个主要优势,借助HLSL,几乎可以即时地将一个 //着色器移植到不同的版本中
DWORD Flags,可选的编译选项,若该参数为0,表示不使用任何选项
#D3DXSHANER_DEBUG 指示编译器写入调试信息
#D3DXSHADER_SKIPVALIDATION指示编译器不要进行任何代码验证,仅当正在 //使用一个以确定可用的着色器时,该参数才被使用
#D3DXSHADER_SKIPOPTIMIZATION指示编译器不要对代码做任何优化,实际上 //,仅在调试时该选项有用,因为调试时不希望编译器对代码做任何改动
LPD3DXBUFFER* ppShader,//返回指向接口ID3DXBuffer的指针,该接口包含了编译后的着色 //器代码,编译后的着色器代码就可作为另一个函数的参数来创建实际的顶点着色器 //或像素着色器
LPD3DXBUFFER* ppErrorMsgs,//返回指向接口ID3DXBuffer指针,包含了错误代码和消息的 //字符串
LPD3DXCONSTANTTABLE* ppConstantTable//返回指向接口ID3DXConstantTable的指针,该接口 //包含了该着色器的常量表数据
);
例:
ID3DXConstantTable* TransformConstantTable = 0 ;
ID3DXBuffer* shader = 0;
ID3DXBuffer* errorBuffer = 0 ;
hr = D3DXCompileShaderFromFile(
"transform.txt",// shader filename
0,
0,
"Main",
"vs_2_0",
D3DXSHADER_DEBUG,
&shader,
&errorBuffer,
&TransformConstantTable);
// output any error message
if (errorBuffer)
{
::MessageBox(0,(char*)errorBuffer->GetBufferPointer(),0,0);
d3d::Release<ID3DXBuffer*>(errorBuffer);
}
if (FAILED(hr))
{
::MessageBox(0,"D3DXCreateEffectFromFile() - FAILED",0,0);
return false;
}
变量类型(scalar type)
#bool 布尔值,true,false
#int 32位有符号整数
#half 16位浮点数
#float 32位浮点数
#double 64位浮点数
注意,有些平台可能不支持int ,half和double,这些类型将用float来模拟
HLSL内置向量类型(vector type)
#vector 一个4D向量,其中每个元素的类型都是float
#vector<T,n>一个n维向量,其中每个元素的类型均为标量类型T,维数n必须介于1-4之间
例,一个二维double型向量例子
vector<double,2>vec2;
HLSL特殊语法--"替换调配(swizzles)"来专门用来完成这类不关心顺序的复制操作
vector u = {1.0f,2.0f,3.0f,4.0f};
vector v = {0.0f,0.0f,0.0f,0.0f};
v = u.xyyw
有选择地复制
例:可仅复制x和y分量
vector u = {1.0f,2.0f,3.0f,4.0f};
vector v = {0.0f,0.0f,0.0f,0.0f};
v.xy = u//v = {1.0f,2.0f,0.0f,0.0f};
HLSL内置矩阵类型(matrix type)
#matrix 表示一个4*4矩阵,该矩阵中每个元素类型均为float
#matrix<T,m,n>表示一个m*n矩阵,其中的每个元素都为标量类型T,该矩阵的维数m和n必须介于1-4之间
例,表示一个2*2的整型矩阵
matrix<int ,2,2>m2x2;
还可用该语法来定义m*n矩阵,其中m和n必须介于1-4之间
floatmxn matmxnl
例:
float2x2 mat2x2;
float3x3 mat3x3;
float4x4 mat4x4;
float2x4 mat2x4;
整型矩阵
int2x2 i2x2;
可用数组的双下标语法来访问矩阵的各项(entry,元素),例如,要对矩阵M中第i行,第j列的项进行设置
M[i][j] = value;
还可用像访问结构体中的成员那样访问矩阵M中的项,HLSL已定义了下列项名称
下标从1开始的情形
M._11=M._12=M._13=M._14 = 0.0f;
M._21=M._22=M._23=M._24 = 0.0f;
M._31=M._32=M._33=M._34 = 0.0f;
M._41=M._42=M._43=M._44 = 0.0f;
下标从0开始的情形
M._m00=M._m01=M._m02=M._m03 = 0.0f;
M._m10=M._m11=M._m12=M._m13 = 0.0f;
M._m20=M._m21=M._m22=M._m23 = 0.0f;
M._m30=M._m31=M._m32=M._m33 = 0.0f;
引用矩阵中某一特定行,可通过数组单下标语法来实现
例,引用矩阵M的第i行
vector ithRow =M[i];
对HLSL中变量进行初始化
vector u ={0.6f,0.3f,1.0f,1.0f};
vector v ={1.0f,5.0f,0.2f,1.0f};
也可用构造函数语法:
vector u = vector(0.6f,0.3f,1.0f,1.0f);
vector v = vector(0.6f,0.3f,1.0f,1.0f);
float2x2 f2x2 = float2x2(0.6f,0.3f,1.0f,1.0f);
int2x2 m = {1,2,3,4};
int a = {5};
float3 x = float3{0,0,0};
HLSL的数组;
float M[4][4];
half p[4];
vector v[12];
HLSL结构体定义与c++相同,但是HLSL中结构体不允许有成员函数
HLSL关键字typedef 与c++的功能完全一样
例,可用语法类型vector<float,3>赋予另一个名称point:
typedef vector<float,3> point;
vector<float,3>myPoint 等价于point myPoint;
对常量类型和数组类型运用关键字typedef
typedef const float CFLOAT;
typedef float point2[2];
HLSL变量的前缀:
#staitc 如果全局变量在声明时使用了关键字static,该变量在该着色器程序外部可见
#uniform 如果变量声明时使用了关键字uniform,表明该变量将在着色器之外进行初始化,例如,在c++程序中对该变量进行初始化,然后再作为输入传给该着色器
#extern 表明该变量可在该着色器程序之外进行访问,只有全局变量可使用关键字extern,非静态的全局变量在默认状态下都是extern类型的。
#shared提示效果框架该变量可在多个效果之间共享,只有全局变量方可使用
#volatile提示效果框架该变量将经常被修改,只有全局变量方可使用
const HLSL中和C++中含义完全相同
HLSL支持许多与c++类似的语句,如选择,循环和一般的顺序流程
HLSL支持一种灵活的类型转换机制,HLSL中的类型转换语法与c语言的完全相同
例将float类型转换为matrix类型
float f = 5.0f
matrix m = (matrix)f;
HLSL支持许多与c++类似的运算符,但仍有一些差别,首先,取模运算符%适用于整型和浮点型数据,适用取模运算符时,左操作数和右操作数必须同号,其次,许多HLSL运算都是在变量的分量级(component basis)上进行的,这是由于向量和矩阵都是HLSL的内置类型,而这些类型都由若干分量组成,由于有了能够在分量级上进行运算的运算符,如向量/矩阵加法,向量/矩阵加法,向量/矩阵的相等测试等运算级可使用与标量类型相同的运算符来进行
例:
vector u = {1.0f,0.0f,-3.0f,1.0f};
vector u = {-4.0f,2.0f,1.0f,0.0f};
//adds corresponding components
vector sum = u+v;// sum = (-3.0f,2.0f,-2.0f,1.0f)
向量自增也是每个向量自增
sum++;//sum = (-2.0f,3.0f,-1.0f,2.0f)
向量的逐分量相乘
vector u = {1.0f,0.0f,-3.0f,1.0f};
vector v = {-4.0f,2.0f,1.0f,0.0f};
vector sum = u*v;// sum = (-4.0f,0.0f,-3.0f,0.0f)
比较运算符也是在分量上操作的,并将返回一个bool型的向量或矩阵(其每个分量的类型均为bool),返回"bool"向量包含了两个分量的比较结果
vector u = {1.0f,0.0f,-3.0f,1.0f};
vector v = {-4.0f,0.0f,1.0f,1.0f};
vector b = (u == v);//b = (false ,true,false ,true)
最后,讨论双目运算中的变量类型提升(variable promotion)
#对于双目运算,如果左操作数与右操作数的维数不同,则维数较小的操作数得到提升(类型转换),使得其维数与原先维数较大的操作数相同
#对于双目运算,如果左右操作数类型不同,则具有较低类型的操作数得到提升(类型转换),使得其类型的精度与原先具有较高类型的精度的操作数相同
HLSL中的函数具有下列性质:
#函数使用与C++类似的语法
#参数总是按值传递的
#不支持递归
#函数总是内联的(inline)
HLSL还额外增加了一些专门在函数中使用的关键字
in //指定在该函数执行之前,必须对该形参传入实参的副本,可以不显式指定in,默认状态下每个形参都是in类型
out//指定当函数返回时,形参的值将复制给实参,out仅用于输出数据
inout//该参数即可作为输入又可作为输出
HLSL拥有一个丰富的内置函数集
注意如果为一个"标量"函数(即一般只对标量进行运算的函数,如cos(x))传入一个非标量类型的参数,该函数将针对该传入参数的每个分量进行计算。
例:
float3 v = float3(0.0f,0.0f,0.0f);
v = cos(v);// v = (cos(x),cos(y),cos(z))
总结 :
1 HLSL程序一般保存在ASCII文本文件中,其编译应由应用程序调用函数D3DXCompileShaderFromFile来完成
2 接口ID3DXConstantTable允许在应用程序中对着色器程序中的变量进行设置,这种数据通信时必要的,因为着色器所使用的数据在程序绘制的每一帧中都有可能发生变化,例如,应用程序的取景变换矩阵发生了改变,就需要用新的取景变换矩阵来更新着色器中的取景变换矩阵,借助ID3DXConstantTable接口,就可实现数据更新
3 对于每个着色器,必须定义两个能够分别描述着色器输入数据和输出数据格式的输入结构和输出结构
4 每个着色器都有一个入口函数,该函数接收一个用于将输入数据传递给着色器的输入结构类型的参数,此外,每个着色器都返回一个输出结构实例,以便将处理结果输出.