HLSL初级教程

本文是一篇针对HLSL初学者的教程,详细介绍了HLSL(High Level Shading Language)的基本概念和使用方法,包括着色器的编写、顶点着色器和像素着色器的应用,以及如何使用HLSL Effect框架。教程通过实例展示了如何创建和使用顶点着色器实现顶点变换和颜色设定,以及如何利用像素着色器实现多纹理效果。最后,通过Effect框架简化了着色器的管理和渲染过程,使读者能更好地理解和掌握HLSL编程。
摘要由CSDN通过智能技术生成
前言

         本教程针对HLSL(High LevelShading Language)初学者,从应用的角度对HLSL、顶点着色器、像素着色器和Effect效果框架进行了介绍,教程中去掉了对HLSL语法等一些细节内容的讨论,力求帮助读者尽可能快地理解HLSL编程的概念,掌握HLSL编程的方法。

         教程中部分阐述直接引用了其他文档,这是因为这些文档表述之精要,已经达到了不能更改的地步,这里表示感谢。

1.HLSL入门

1.1什么是着色器

DirectX使用管道技术(pipeline)进行图形渲染,其构架如下:


图1.1 Direct3D Graphics Pipeline

之前我们使用管道的步骤如下:

1.       设定顶点、图元、纹理等数据信息;

2.       设定管道状态信息;

²       渲染状态

通过SetRenderState方法设定渲染状态;

另外,使用以下方法设置变换、材质和光照:

                   SetTransform

                   SetMaterial

SetLight

                   LightEnable

²       取样器状态

通过SetSamplerState方法设定取样器状态;

²       纹理层状态

通过SetTextureStageState设定纹理层状态;

3.       渲染;

这部分交由D3D管道按照之前的设定自行完成,这部分操作是D3D预先固定的,所以这种管道技术被称为固定功能管道(fixed function pipeline);

 

固定功能管道给我们编程提供了一定的灵活性,但是仍有很多效果难以通过这种方式实现,比如:

1.       在渲染过程中,我们要求y坐标值大于10的顶点要被绘制到坐标值(0,0,0)的地方,在之前的固定功能管道中,顶点被绘制的位置是在第1步即被设定好的,不可能在渲染过程中进行改变,所以是不可行的;

2.       谋顶点在纹理贴图1上映射为点A,在纹理贴图2上映射为点B,我们要求该顶点颜色由A、B共同决定,即:

定点颜色 = A点色彩值*0.7 + B点色彩值*0.3

            这在固定管道编程中也是不可行的。

以上两个问题都可以由可编程管道(pragrammable pipeline)来解决。

         可编程管线允许用户自定义一段可以在GPU上执行的程序,代替固定管道技术中的Vertex Processing和Pixel Processing阶段(参照图1.1),从而在使我们在编程中达到更大的灵活性。其中替换Vertex Processing的部分叫做Vertex Shader(顶点着色器),替换Pixel Proccessing的部分叫做Pixel Shader(像素着色器),这就是我们所说的着色器Shader。

1.2什么是HLSL

Direct8.x中,着色器是通过低级着色汇编语言来编写的,这样的程序更像是汇编式的指令集合,由于其效率低、可读性差、版本限制等缺点,迫切要求出现一门更高级的着色语言。到了Direct3D9,HLSL(High Level Shading Language,高级渲染语言)应运而生了。

HLSL的语法非常类似于C和C++,学习起来是很方便的。

 

1.3怎么写HLSL着色器

我们可以直接把HLSL着色器代码作为一长串字符串编写进我们的应用程序源文件中,但是,更加方便和模块化的方法是把着色器的代码从应用程序代码中分离出来。因此,我们将着色器代码单独保存为文本格式,然后在应用程序中使用特定函数将其加载进来。

下面是一个完整的HLSL着色器程序代码,我们把它保存在BasicHLSL.txt中。该着色器完成顶点的世界变换、观察变换和投影变幻,并将顶点颜色设定为指定的颜色。

//

// BasicHLSL.txt

//

 

//

// Global variable

//

 

matrix WVPMatrix;

vector color;

 

//

// Structures

//

 

struct VS_INPUT

{

         vectorposition : POSITION;

};

 

struct VS_OUTPUT

{

         vectorposition : POSITION;

         vectorcolor : COLOR;

};

 

//

// Functions

//

 

VS_OUTPUT SetColor(VS_INPUT input)

{

         VS_OUTPUToutput = (VS_OUTPUT)0;

        

         output.position= mul(input.position, WVPMatrix);       

         output.color= color;

        

         returnoutput;

}

 

下面就针对上述代码讲解一下HLSL着色器程序的编写:

1.3.1全局变量

         代码中声明了两个全局变量:

matrix WVPMatrix;

vector color;

         变量WVPMatrix是一个矩阵类型,它包含了世界、观察、投影的合矩阵,用于对顶点进行坐标变换;

         变量color是一个向量类型,它用于设定顶点颜色;

         代码中并没有对全局变量进行初始化,这是因为我们对全局变量的初始化过程将在应用程序中进行,全局变量在应用程序中赋值而在着色器程序中使用,这是应用程序和着色器通信的关键所在。具体赋值过程将在后续部分讲述。

1.3.2输入输出

²       输入输出结构

程序中定义了两个输入输出结构VS_INPUT和VS_OUTPUT

struct VS_INPUT

{

         vectorposition : POSITION;

};

 

struct VS_OUTPUT

{

         vectorposition : POSITION;

         vectorcolor : COLOR;

};

自定义的结构可以采用任意名称,结构不过是一种组织数据的方式,并不是强制的,你也可以不使用,而将本程序的输入改为:

         vectorposition : POSITION;

²       标志符

用于输入输出的变量采用用一种特殊的声明方式:

Type VariableName : Semantic

         这个特殊的冒号语法表示一个语义,冒号后面的标志符用来指定变量的用途,如

vector position : POSITION;

         其中,POSITION标志符表明该变量表示顶点位置,另外还有诸如COLOR、NORMAL等很多表示其他意义的标志符。

本节所说的输入输出其实是指着色器代码和编译器、GPU之间的通信,和应用程序是无关的,所以这些变量不需要在应用程序中进行赋值,标志符告诉编译器各个输入输出变量的用途(顶点位置、法线、颜色等),这是着色器代码和编译器、GPU之间通信的关键。

1.3.3入口函数

         程序中还定义了一个函数SetColor:

OUTPUT SetColor(INPUT input)

{

         VS_OUTPUToutput = (VS_OUTPUT)0;

        

         output.position= mul(input.position, WVPMatrix);       

         output.color= color;

        

         returnoutput;

}

1.       该函数以input和output类型作为输入输出;

2.       使全局变量WVPMatrix和input.position相乘,以完成顶点的世界、观察、投影变换,并把结果赋值到output.position;

output.position = mul(input.position,WVPMatrix);

3.       将全局变量color的值赋给output.color;

output.color = color;

4.       在同一个着色器代码文件中,可以有多个用户自定义函数,因此在应用程序中需要指定一个入口函数,相当于windows程序的WinMain函数,本程序只包含SetColor一个函数而且它将被做为入口函数使用。

 

1.3.4总结

         至此,一个HLSL着色器编写完毕,渲染过程中,当一个顶点被送到着色器时:

1.       全局变量WVPMatrix、color将在应用程序中被赋值;

2.       入口函数SetColor被调用编译器根据标志符将顶点信息填充到VS_INPUT中的各个字段;

3.       SetColor函数中,首先定义一个VS_OUTPUT信息,之后根据WVPMatrix和color变量完成顶点的坐标变换和颜色设定操作,最后函数返回VS_OUTPUT结构;

4.       编译器将会再次根据标志符把返回的VS_OUTPUT结构中的各字段映射为顶点相应的信息。

5.       顶点被送往下一个流程接受进一步处理。

上述过程中,全局变量在应用程序中赋值而在着色器程序中使用,这是应用程序和着色器通信的关键所在;标志符告诉编译器各个输入输出变量的用途(顶点位置、法线、颜色等),这是着色器代码和编译器、GPU之间通信的关键。个人认为这是着色器中最为精义的地方:)

1.4怎么用HLSL着色器

应用程序中对HLSL着色器的使用分为以下步骤:

1.       加载(称为编译更为妥当)着色器代码;

2.       创建(顶点/像素)着色器;

3.       对着色器中的变量进行赋值,完成应用程序和着色器之间的通信。

4.       把着色器设定到渲染管道中;

本例使用的着色器是一个顶点着色器,因此我们将通过顶点着色器的使用来讲解着色器的使用过程,像素着色器的使用过程与此大同小异,二者之间仅有些微差别。

 

1.4.1声明全局变量

IDirect3DVertexShader9* BasicShader = 0; //顶点着色器指针

 

ID3DXConstantTable* BasicConstTable = 0; //常量表指针

D3DXHANDLE WVPMatrixHandle          = 0;

D3DXHANDLE ColorHandle              = 0;

 

ID3DXMesh* Teapot                   = 0; //指向程序中D3D茶壶模型的指针

 

1.4.2编译着色器

通过D3DXCompileShaderFromFile函数从应用程序外部的文本文件BasicHLSL.txt中编译一个着色器:

//编译后的着色器代码将被放在一个buffer中,可以通过ID3DXBuffer接口对其进行访问,之后的着色器将从这里创建

         ID3DXBuffer*shaderBuffer      = 0;

         //用于接受错误信息

         ID3DXBuffer*errorBuffer       = 0;

 

         //编译着色器代码

         D3DXCompileShaderFromFile("BasicHLSL.txt",//着色器代码文件名

                                                                   0,

                                                                   0,

                                                                   "SetColor", //入口函数名称

                                                                   "vs_1_1", //顶点着色器版本号

                                                                   D3DXSHADER_DEBUG,// Debug模式编译          

                                                                   &shaderBuffer, //指向编译后的着色器代码的指针

                                                                   &errorBuffer,

                                                                   &BasicConstTable); //常量表指针

1.4.3创建着色器

         应用程序通过CreateVertexShader创建一个顶点着色器,注意使用了上一步得到的shaderBuffer:

         g_pd3dDevice->CreateVertexShader((DWORD*)shaderBuffer->GetBufferPointer(),&BasicShader);

1.4.3对着色器中的变量进行赋值

1.3.4节说到着色器的全局变量在应用程序中赋值而在着色器程序中使用,这是应用程序和着色器通信的关键所在,这里就具体说明赋值过程。

着色器中的全局变量在编译后都被放在一个叫常量表的结构中,我们可以使用ID3DXConstantTable接口对其进行访问,参照1.4.1中编译着色器函数D3DXCompileShaderFromFile的最后一个参数,该参数即返回了指向常量表的指针。

对一个着色器中变量进行赋值的步骤如下:

1.       通过变量名称得到指向着色器变量的句柄;

还记得在BasicHLSL.x着色器文件中我们声明的两个全局变量吗:

                   matrixWVPMatrix;

vector color;

                   我们在应用程序中相应的声明两个句柄:

D3DXHANDLE WVPMatrixHandle          = 0;

D3DXHANDLE ColorHandle              = 0;

                   然后通过变量名得到分别得到对应的两个句柄:

                            WVPMatrixHandle= BasicConstTable->GetConstantByName(0, "WVPMatrix");

                            ColorHandle= BasicConstTable->GetConstantByName(0, "color");

2.       通过句柄对着色器变量进行赋值;

我们可以先设置各变量为默认值:

                   BasicConstTable->SetDefaults(g_pd3dDevice);

之后,可以使用ID3DXConstantTable::SetXXX函数对各个变量进行赋值:

HRESULT SetXXX(

 LPDIRECT3DDEVICE9 pDevice,

 D3DXHANDLE hConstant,

  XXXvalue

);

其中XXX代表变量类型,例如Matrix类型的变量就要使用SetMatrix函数赋值,而Vector类型的则要使用SetVector来赋值。

1.4.4把着色器设定到渲染管道中

         这里我们使用SetVertexShader方法把顶点着色器设定到渲染管道中:

                   g_pd3dDevice->SetVertexShader(BasicShader);

1.4.5整个渲染过程如下

在渲染过程中,我们设定顶点的变换坐标和颜色值,渲染代码如下:

         g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,

D3DCOLOR_XRGB(153,153,153), 1.0f, 0 );

         //开始渲染

         g_pd3dDevice->BeginScene();

 

         //得到世界矩阵、观察矩阵和投影矩阵

         D3DXMATRIXmatWorld, matView, matProj;

         g_pd3dDevice->GetTransform(D3DTS_WORLD,&matWorld);

         g_pd3dDevice->GetTransform(D3DTS_VIEW,&matView);

         g_pd3dDevice->GetTransfo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值