使用HLSL语言编写顶点着色器和像素着色器,
顶点着色器和像素着色器本质使我们自己编写的定制程序,这些程序可以帮助我们取代功能流水线中的某一块功能,
然后在图形卡的GPU中执行。
通过这种变换,我们在实现图像效果的时候就有了更大的的灵活性。
HLSL着色器程序可以以一个长字符串的形式出现在程序的源文件中,但是更模块化的做法是将着色器的代码和
源程序的代码分离。
龙书上面介绍是可以将着色器代码在记事本中编辑,然后保存为常规的ASCII文件,我是不太明白,感觉有点蒙,
但是还是先这么来吧
下面这个HLSL程序是将输入顶点变换之后加上颜色然后输出
//////////////////////////////////////////////////////////////////////////////////
//
//Desc: 顶点着色器,通过视图和投影变换转换顶点,并将顶点颜色设置为蓝色。
///////////////////////////////////////////////////////////////////////////////////
//
//用于存储组合视图和投影变换矩阵的全局变量。 我们从应用程序初始化此变量。
/*
该变量类型为matrix,这个类型是HLSL中的内置类型,表示维度为4×4的矩阵
取景变换矩阵:就是将观察坐标系转换到世界坐标系的矩阵
ViewProjMatrix矩阵就是取景变换矩阵和投影变换矩阵的乘积
主要有:在着色器的源代码中是找不到这些变量的初始化的源代码的,因为这些变量的初始化应该在应用程序的源代码中进行
应用程序与着色器程序之间要进行数据通信。
*/
matrix ViewProjMatrix;
//vector也是一个HLSL内置类型,表示一个4D向量,我们只是将他看作RGBA颜色向量,并将其初始化为蓝色
vector Blue={0.0f,0.0f,1.0f,1.0f);
//输入结构描述输入到着色器的顶点。 这里输入顶点仅包含位置分量。
struct VS_INPUT
{
//POSITION 是一种类似于顶点灵活格式的语法,POSITION是用来说明:变量position是描述顶点位置的
vector position: POSITION;
};
//输出结构描述从着色器输出的顶点。 这里输出顶点包含位置和颜色分量。
struct VS_oUTPUT
{
vector position: POSITION;
//COLOR是用来说明:变量diffus是描述颜色的
vector diffuse: COLOR;
);
//主入口点,观察主函数通过其参数接收输入顶点的副本,并返回它计算的输出顶点的副本。
VS_oUTPUT Main(VS_INPUT input)
{
//将输出结构体的各个成员初始化为0
VS_oUTPUT output=(VS_OUTPUT)0;
//将输入结构 的位置和矩阵相乘,然后将变换之后的点赋值给输出结构里面的位置成员
output.position =mul(input.position,ViewProjMatrix);
//将顶点漫反射颜色更改为蓝色
output.diffuse=Blue;
return output;
}
HLSL语法建立了变量和硬件寄存器之间的联系,输入变量总是和输入寄存器相关联,输出变量总是和输出寄存器相关联。
就像刚才的顶点输入结构,VS_INPUT里面的成员position,将会被连接到一个特殊的顶点输入位置寄存器,
同样的VS_OUTPUT里面的成员diffuse,将会被连接到顶点输出颜色寄存器。
入口函数为:Main,入口函数必须有一个可以接受输入顶点结构的参数,该参数用于把输入的顶点传给着色器,而且入口函数必须返回一个顶点结构的实例,用于将处理过的顶点结构输出
每一个着色器中都是用长量表来储存自己的常量,DX中也提供了相关的接口:ID3DXConstantTable来让我们的应用程序对着色器中的变量进行设置。
下面是ID3DXConstantTable部分函数的介绍:
1、获取常量的句柄
为了在应用程序中获取变量并对其进行设置,按照windows的尿性一定是获取句柄,当我们知道着色器中变量的名字的
时候,我们就可以使用下面的函数来获取该变量的句柄,返回的句柄类型为:D3DXHANDLE
D3DXHANDLE ID3DXConstantTable::GetConstantByName(
D3DXHANDLE hConstant,
LPCSTR pName
);
hConstant:一个D3DXHANDLE类型的句柄,这个句柄是我们所希望获取的数据的父结构的句柄,
比如:我们要获取VS_INPUT结构体里面的成员position,这是这个句柄就传VS_INPUT结构体的句柄
如果我们要获取的是最上层的数据成员,这个参数就传0
pName:我们希望获取句柄的那个数据在HLSL里面的名字
eg:
D3DXHANDLE h0;
h0=ConstTable->GetConstantByName(0,"ViewProjMatrix");
2、常量的设置
上一步中我们获取了数据的句柄,现在我们就来设置他。
我们在应用程序中就可以使用函数:ID3DXConstantTable::SetXXX进行设置
其中XXX对应的是即将设置的变量的类型,比如我们设置的是一个vector类型的数组,此时就是SetVectorArray
方法ID3DXConstantTable:SetXXX的通用签名如下:
HRESULT ID3DXConstantTable::SetXXX(
LPDIRECT3DDEVICE9 pDevice,
D3DXHANDLE hConstant,xXx value
);
·pDevice与常量表相关的设备指针。
·hConstant 我们想要设置的那个变量的句柄。
·value指定了我们引用的那个着色器中的变量应被赋为何值
如果要对数组进行设置,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
)
使用如下函数,对保存在文本中的着色器程序进行编译:
HRESULT D3DXCompileShaderFromFile(
LPCSTR pSrcFile,
CONST D3DXMACRO* pDefines,
LPD3DXINCLUDE pInclude,
LPCSTR pFunctionName,
LPCSTR pTarget,
DWORD Flags,
LPD3DXBUEFER* ppShader,
LPD3DXBUFFER* PPErrorMsgs,
LPD3DXCONSTANTTABLE* ppConstantTable
);
pSrcFile:保存HLSL文本的文件
pDefines:参数可选,现在先设置为NULL
pInclude:指向ID3DXInterface的指针,应用程序应该重载默认的inlcude行为,通常默认的include行为已经满足要求,所以讲该参数指定为NULL
pTarget:指定了要将HLSL源代码编译的着色器的版本,该参数为字符串。
合法的顶点着色器版本有:vS_l_1,vs_2_0,vs_2_sw。合法的像素着色器版本有:ps_1_1,ps_1_2,ps_1_3ps_1_4,ps_2_0,ps_2_sw。
·Flags 可选的编译选项。若该参数设为0,表示不使用任何选项。合法的选项包括:
D3DXSHADER_DEBUG 指示编译器写入调试信息。
D3DXSHADER_SKIPVALIDATION 指示编译器不要进行任何代码验证。仅当您正在使用一个已确定可用的着色器时,该参数才被使用。
D3DXSHADERSKIPOPTIMIZATION 指示编译器不要对代码做任何优化。实际上,仅在调试时该选项有用,因为调试时您不希望编译器对代码做任何改动。
ppShader:返回一个指向接口ID3DXBuffer的指针,该接口包含了编译后的着色器代码。然后,编译后的着色器代码就可作为另一个函数的参数来创建实际的顶点着色器或像素着色器。
ppErrorMsgs返回一个指向接口ID3DXBuffer的指针,该接口包含了一个存储了错误代码和消息的字符串。
ppConstantTable 返回一个指向接口ID3DXConstantTable的指针,该接口包含了该着色器的常量表数据。
bool 布尔值。注意,HLSL提供了关键词true和false。
int 32位的有符号整数。
half 16位的浮点数。
float 32位的浮点数。
double 64位的浮点数。
·vector一个4D向量,其中每个元素的类型都是float。
·vector
维数n必须介于1~4之间。
两个向量:u和v
HLSL里面有一种赋值方法:
v = u.xyzw
就是将u里面所有的分量赋值到v里面
v.xy = u
就是将v里面的x,y分量被u里面对应的分量进行赋值
matrix表示一个4×4矩阵,该矩阵中每个元素的类型均为float。
matrix
该矩阵的维数m和n必须介于1~4之间。
例如,要表示一个2×2的整型矩阵,可写作:matrix
我们可用与C++类似的语法声明一个特定类型的数组。例如:
float M[4][4];half p[4];vector v[12];
HLSL中的结构体定义方法与C++完全相同。但是,HL.SL中的结构体不允许有成员函数。
下面是一个HLSL中结构体的例子。
struct MyStruct
{
Matrix T;
vector n;
float f;
int x;
bool b;
};
MyStruct s;//instantiate
s.f=5.0f;//member access
和C++中一样
typedef vector
这样就不必写成:
vector
而是写成:
point myPoint;
几乎和C++中的一模一样,但还是有一些差别
首先:取模运算符适合用于整型和浮点型,使用取模运算符的时候,左右操作数必须同号
注意:因为有向量和矩阵等含有分量的内置类型,所以许多的计算都是在分量上进行的
不一样的还是比较运算符
比较运算符也是在分量上操作的,并将返回一个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)
和C++的语法基本相似
参数胡按值传递,不支持指针或者引用
不支持递归
函数总是内敛
HLSL还指定了一些专门在函数中使用的关键字
bool foo(
in const bool b,//input bool
out int r1, //output int
inout float r2) //input/output float
{
if(b)// test input value
{
r1=5;//output a value through r1
}
else
{
r1=1;// output a value through r1
}
// since r2 is inout we can use it as an input
// value and also output a value through it
r2=r2*r2*r2;
return true;
}
·in指定在该函数执行之前,必须对该形参传入实参的副本。
函数声明中形参可以不显式指定in,因为默认状态下每个形参都是in类型的。
例如,下面的两个函数完全等价。
float square(in float x)
{
return x*x;
}
不显式指定in:
float square(float x)
{
returnx*x;
}
·out指定当函数返回时,形参的值将复制给实参。这样我们就可将形参作为返回值。
关键字out是很必要的,因为HLSL不支持引用或指针。注意,如果一个形参是out类型的,则在函数开始执行时,实参值将不被复制到形参中。即out类型的参数仅用于输出数据,不可用作输入。
void square(in float x,out float y)
{
y=x*x;
}
在该函数中,我们通过乘法计算出输入参数x的平方,并用输出参数y返回该结果。
inout 表示一个参数同时兼有in和out类型参数的特点。
即如果您希望一个参数即可作为输入又可作为输出,可指定该关键字。
void square(inout float x)
{
x = x*x;
}