DirectX11 高级着色器语言HLSL入门

来源:互联网 发布:2017微信用户数据报告 编辑:程序博客网 时间:2024/05/20 06:28

高级着色器语言HLSL入门

1. 数据类型简介

与CPU不同,在显卡芯片中,最小的数据吞吐单元是一个由32位浮点数组成的四元组。这一点很有道理不是,想想你在渲染过程中所有涉及到的数据,最复杂的不外乎四维坐标(x,y,z,w)或颜色(r,g,b,a),这样GPU可以一次性处理一个四元组。而整数什么的在显卡中被放到四元组的一个分量里使用,而很多显卡中,整数、布尔值都不被直接支持,而是转为浮点数使用。至于矩阵,通常用4个四元组表示一个4x4矩阵(默认情况一个四元组存储一行,也可以指定按列存储,属于细节问题,goto:细节问题)其他尺寸的以此类推。
反映到程序上,一个四维向量就被声明为float4,4维方矩阵被声明为float4x4等等。当然,你也可以使用任意不超过4的维度的向量或矩阵,如int3,float3x3,double1。这个double1实际上就是标量了,1可以省略不写。

2. 纹理(Texture)&取样器(Sampler)简介

这俩东西可以看作特殊类型变量。纹理就是Shader中用到的贴图资源,这我想没什么好说的。来解释一下取样器:实际上每张贴图在使用的时候都要用一个取样器。取样器相当于这样一个结构,除了保存贴图本身数据之外,还包括过滤参数等取样信息。通常,读取贴图这样的指令接收的都是取样器类型的参数而并非直接接收纹理贴图。声明及使用纹理或取样器跟使用普通变量一样。这里有一些初始化取样器的方法,还是等到后面的实例中讲述吧。

3. 数据类型大全

数据类型有值类型、向量、矩阵、采样器、和结构体。
1.值类型
  bool 布尔变量
  half 16为整形
  int 32位整形
  float 单精度浮点数
  double 双精度浮点数
  声明方式:float f;
  赋值方式:f = 1;
2.向量
  声明方式:float4 f;
  赋值方式:f = {1,2,3,4};
  取值方式:float3 ff = f.rgb;
  说明:向可以通过xyzw或者rgba访问向量中的指定字段,x或者r就是代表0号字段。不仅可以单独操作一个字段,还可以对多个字段同时操作,例如3*f.xyz,就是将f中的xyz都乘以个3。
3.矩阵
  声明方式:float2x4 f; 先行后列。
  赋值方式:f = {1,1,2,2,3,3,4,4};
  取值方式:float ff = f[0][0];
  说明:如果要对矩阵做乘法运算,请使用mul函数,如mul(ff,f)。
4.采样器
  声明方式:
    texture Texture; //纹理变量
    sampler TextureSampler = sampler_state //纹理采样器
    {
    Texture = ; //纹理采样器使用的纹理对象
    MinFilter = Linear; //缩小图形使用线性滤波
    MagFilter = Linear; //放大图形使用线性滤波
    MipFilter = Linear; //Mipmap使用线性滤波
    AddressU = Wrap; //U方向上的纹理寻址模式采用Wrap方式
    AddressV = Wrap; //V方向上的纹理寻址模式采用Wrap方式
    };
  赋值方式:在C#中对Texture赋值,effect.Parameters[“Texture”].SetValue(Game.Content.Load(“*”));
  取值方式:tex2D(TextureSampler, TEXCOORD0);
  说明:MinFilter、MagFilter、MipFilter、AddressU、AddressV是可选项,如果不写将会使用默认值,也就是上面赋予的值。
5.结构体
  声明方式:
    struct VertexShaderInput
    {
      float4 Position : POSITION;
      float2 TextureCoordinates : TEXCOORD0;
      float3 Normal: NORMAL;
    };
    VertexShaderInput input;
    此处与C#语法有些区别,直接这么写,不需要再写个new什么的。
  赋值方式:与C#语法一致。
  取值方式:与C#语法一致。

4. 控制流

控制流,就是if…else,for,while什么的。在CPU中,这些控制流造成的实际上是指令跳转。但在GPU中指令跳转并不被广泛的支持,以往的大部分显卡只懂得按顺序一句一句执行指令。因此HLSL的编译器可能会做出诸如展开循环、遍历分支等等莽撞的事来适应显卡。所以使用时要特别小心,而且不是所有情况的控制流语句都被支持。具体的很多规则还是在细节问题里。

5. 函数

HLSL中提供了很多函数可供调用,在Direct3D 文档 -> DirectX Graphics -> Reference -> HLSL Shader Reference -> HLSL Intrinsic Functions中有这些函数的详细列表。也可自己写函数用,但是在较早的Shader版本中,就像内联函数一样编译时最终要将函数展开插入到函数调用处。还有一点我想你一定会想到的就是主函数会是什么。Vertex Shader和Pixel Shader各自需要一个主函数,由程序员来指定!没错,程序员在Shader外部指定。
HLSL 中的函数定义与其它编程语言中的完全一样:

ReturnValue FunctionName( parameterName : semantic ){// function code goes here}

函数的返回值可以是任何 HLSL 中定义的类型,包括组合类型和 void 空类型。 当你定义一个着色器函数的参数列表时,需要完全指定跟随变量之后的语义标识。 当定义函数参数时,还有一些事项需要注意,因为 HLSL 没有具体的方式用于返回参数列表中的值的引用,这需要通过定义少量的关键字来达成同样的结果。
在参数声明之前,使用关键字 out 可以让编译器知道该变量可以用于输出。另外地,关键字 inout 可以允许变量既作为输入也作为输出:

void GetColor( out float3 color ){    color = float3( 0.0f, 1.0f, 1.0f );}

6. 顶点着色器

当物体通过管线传输需要绘制时,它们的顶点会发送到
你的顶点着色器中处理。 如果你不想对传入的顶点做任何处理,可以直接将它们传送给像素着色器进行绘制。 大多数情况,你至少需要使用一个世界或投影变换作用这些顶点,以使得它们在正确的空间位置被渲染。
使用顶点着色器,你可以对顶点做诸多的控制,而不仅仅是进行简单的变换。可以将顶点平移到任何坐标轴,改变它的颜色,或任何其它性质的控制。

PS_Input VS_Main( VS_Input vertex ){    PS_Input vsOut = ( PS_Input )0;    vsOut.pos = vertex.pos;    vsOut.tex0 = vertex.tex0;    return vsOut;}

这个顶点着色器看起来像C语言的函数。HLSL使用类似C语法,对C/C++了解后,学习HLSL更容易。我们可以看到顶点渲染器,名字叫做VS,以float4为参数,返回值为float4。在HLSL中,float4是又4个浮点数组成的。那个冒号后面定义的是参数的semantics,返回值也是如此。就像之前提到的,semantics在HLSL中描述的是数据的属性。在上面的渲染器中,我们选择POSITION作为输入参数Pos的语义,因为这个参数包含了顶点坐标信息。返回的语义是SV_POSITION(SV是System Value的缩写)。SV_POSITION是事先已经定义好的语义,像素着色器的顶点语义不是POSITION,而是SV_POSITION,这两种语义的区别在于SV表示硬件会进行插值。SV_POSITION带入PS的,和你自己在像素着色器里做透视除法的结果一样。见顶点着色器的输入结构和像素着色器的输入结构:

struct VS_Input{    float4 pos : POSITION;    float2 tex0 : TEXCOORD0;};struct PS_Input{    float4 pos : SV_POSITION;    float2 tex0 : TEXCOORD0;};

7. 像素着色器

现代计算机的显示器通常显示速度很快,屏幕的最小单位就是像素。每个像素都有一个颜色,并且每个像素相互独立。当我们要在屏幕上渲染一个三角形时,我们并不是把整个三角形当做一个实体来画。其实是,我们把三角形区域的像素绘制出来。
光栅化
将三角形的三个顶点所覆盖的一串像素绘制出来的操作叫做光栅化。GPU首先要判断哪些像素被三角形区域覆盖。然后GPU调用激活的像素渲染器渲染这些像素。一个像素渲染器的主要目标就是计算每个像素的颜色。渲染器根据输入来计算顶点颜色,或者,如果没有使用几何渲染器,就像在这个教程中,像素渲染器的输入将直接来自顶点渲染器的输出。
像素着色器可以让你访问任何经过管线输出之前的像素。 在像素被绘制到屏幕之前,你有机会改变每个像素的颜色。 某些情况,你只需要简单的返回由顶点或几何着色器传入的像素颜色,但是大多数情况,你需要处理光照或贴图对像素颜色的影响。

float4 PS_Main( PS_Input frag ) : SV_TARGET{return colorMap_.Sample( colorSampler_, frag.tex0 );}

上面的函数中,SV_TARGET是返回值得语义,它表示一个输出语义,用于指定像素着色器的用于渲染目标的输出。

8. 语义(semantic)

语义名字(semantic name),是一个描述元素目的的字符串。例如元素作为顶点的位置,则它的语义就是“ POSITION”。我们可以使该元素通过语义“ COLOR”用于顶点颜色,通过“ NORMAL”用于法线向量等等。语义使得元素绑定一个 HLSL 着色器作为它的输入或输出变量。
另外,我们必须更新顶点着色器的输入结构和像素着色器的输入结构来允许使用贴图坐标。顶点着色器将会获得来自于顶点缓存块的贴图坐标并且直接将它们传递给像素着色器,使得像素着色器来访问它们。

struct VS_Input{    float4 pos : POSITION;    float2 tex0 : TEXCOORD0;};

一些公共的语义包括:
SV_POSITION——一个具体的变换位置的 float4 值
NORMAL0——定义一个法线向量
COLOR0——定义一个颜色值
还有一些其他的语义见于 DirectX SDK 文档的 HLSL 章节的完全列表。其中大量的语义末尾跟随一个数字,因为可能定义多个这样的语义类型。

9. register

Texture2D colorMap_ : register( t0 );SamplerState colorSampler_ : register( s0 );

对象 colorMap_是Texture2D 类型,因为它用于 2D 贴图,而 colorSampler_是 HLSL 高级着色语言的一个类型 SamplerState。为了在我们提供的渲染函数中的着色器输入中绑定这些对象,我们必须使用 HLSL 注册关键字 register。 为了绑定第一个输入贴图我们使用 t0,这里他表示贴图类型, 0 表示使用第一个索引贴图。对于采用状态对象使用 s0 出于同样的原因。因为我们使用函数 PSSetSamplers 和 PSSetShaderResource 来传递一个数组元素给我们的着色器使用,所以必须将我们使用的数据索引绑定给每一个 HLSL 变量。因为我们只有一张贴图和一个采样状态,我们只需要使用 t0 和 s0 即可。
需要注意的是,这些注册的缓存必须符合我们在渲染函数 Render 中指定的那样,才能将缓存正确的注册到 HLSL 对象中。

10. 细节问题

你会觉得前面说的太过粗略,还有很多问题没有叙述,但相对来讲这些都算是细枝末节了。例如HLSL中保留关键字有哪些;变量的作用域;数据类型的详细信息;四元组分量的使用法则等等,这些在Direct3D文档 -> DirectX Graphics -> Programming Guide -> The Programmable Pipeline -> Programmable HLSL Shaders -> HLSL Language Basics中讲得比我清楚,我也不再多余翻译了。

0 0