original address:
http://www.aougu.net/html/70/n-1170.html 写过Direct3D程序的朋友们可能还记得,在以往,大家常为如何表现更多真实的材质(如玻璃、金属等)而发愁。最新的Direct3D中,HLSL把程序员从复杂的Shader指令集中解放出来,着力于更重要的算法。
首先,什么是Shader&什么是HLSL?
简要地说,Shader就是一种脚本程序,相对独立于D3D主程序,并且被编译成显卡的GPU指令序列在显示芯片上跑。(你肯定想知道更多,比如这种程序用什么来写,都要写什么,怎么让GPU跑这种程序等等,别着急慢慢来),这里有必要先了解一下:
AGP显卡的渲染流程:
首先来根据下面这张图粗略说明一下当前最普遍流行的AGP显卡的渲染流程,甭管是nVidia还是ATI哪一边的。
每次渲染过程(例如,在一帧画面中画一个馒头的过程)都包括顶点处理(Vertex Processing)和像素处理(Pixel Processing)两个主要功能模块的执行。首先,显卡从AGP总线接收这个馒头的顶点数据。这些数据包括位置、法线、贴图坐标(如果是面包可能更需要贴图,也就是说贴图坐标不是必需的)等等,这些都是未经过任何变换,也就是在物体本地空间(Object Space)下的原始坐标。每个顶点依次被送入顶点处理单元,在这里进行坐标变换、光照计算(如果是每顶点光照)等工作,变换的结果是把每个三角形变换置屏幕空间(Screen Space)下直接可用。这里用到的变换矩阵、灯光等信息都是处理每一批顶点时一次性传给显卡的,作为显卡的资源。顶点处理圈定了三角形的范围,接下来就要逐像素地填充这个三角形了。填充哪些像素是靠对顶点屏幕坐标的线性插值来决定的。像素的其他一些必要参数,如颜色,贴图坐标等也是通过对上一步计算出来的顶点的这些属性进行插值得到的。另外每个像素还要通过深度检测和模板检测决定最终是否绘制。需要绘制的像素被送进像素处理模块,进行贴图像素取值,贴图混合等工作,必要的话每像素光照也在这里完成。这里贴图等信息也是作为显卡的资源。像素最终的处理结果被放进后备缓冲。
以往显卡在顶点处理和像素处理过程中执行的是一套布在硬件上的固定的程序,D3D程序员只能设置一些参数,实际上就是你调用 IDirect3DDevice::SetRenderState()时做的事,而这样的程序在IDirect3DDevice:: DrawPrimitive()中自动执行。那么有些事情就很难办到,如渲染一个水晶馒头。应为参数再多,其渲染所用到的光照公式也跑不出石膏这种东西。现在的显卡,确切的说是现在的Direct3D允许你写这么一段程序替代固定的顶点处理过程和像素处理过程(记住,只是这两个过程,跟插值什么的没关系)。其中替换顶点处理的就叫Vertex Shader(暂时还没有确切的中文名),替换像素处理的就叫Pixel Shader。就是开篇所说的Shader。
这样你应该想到Shader中大概应该写些什么了。如果还不行的话建议复习一下D3D。用什么来写呢?
三。GPU自有GPU的指令集,以往的Shader就是用这种汇编式的指令集组成,例如:
vs_1_1
dcl_position v0
dcl_normal v1,r0, v0.x, c0
mad r2, v0.y, c1, r0
就如同汇编用多了必然出现C一样,自Direct3D9.0后,一种叫HLSL(High Level Shading Language,高级渲染语言)的面向过程的Shader语言应运而生.
HLSL基础就像每一本编程语言的教材一样,介绍一门语言,首先从它的数据类型,表达式,控制流这些东西说起。HLSL的这些基本语法很像C/C++,不再赘述。有些常见问题还是要说明一下,是为了让你不会被这些牵制了全面了解Shader的脚步。
数据类型
与CPU不同,在显卡芯片中,最小的数据吞吐单元是一个由32位浮点数组成的四元组。这一点很有道理不是,想想你在渲染过程中所有涉及到的数据,最复杂的不外乎四维坐标(x,y,z,w)或颜色(r,g,b,a),这样GPU可以一次性处理一个四元组。而整数什么的在显卡中被放到四元组的一个分量里使用,而很多显卡中,整数、布尔值都不被直接支持,而是转为浮点数使用。至于矩阵,通常用4个四元组表示一个4x4矩阵(默认情况一个四元组存储一行,也可以指定按列存储)其他尺寸的以此类推。
反映到程序上,一个四维向量就被声明为float4,4维方矩阵被声明为float4x4等等。当然,你也可以使用任意不超过4的维度的向量或矩阵,如int3,float3x3,double1。这个double1实际上就是标量了,1可以省略不写。
纹理(Texture)&取样器(Sampler)这俩东西可以看作特殊类型变量。纹理就是Shader中用到的贴图资源,这我想没什么好说的。来解释一下取样器:实际上每张贴图在使用的时候都要用一个取样器。取样器相当于这样一个结构,除了保存贴图本身数据之外,还包括过滤参数等取样信息。通常,读取贴图这样的指令接收的都是取样器类型的参数而并非直接接收纹理贴图。声明及使用纹理或取样器跟使用普通变量一样。这里有一些初始化取样器的方法,还是等到后面的实例中讲述吧。
Semantic & Annotation
任何类型的变量(包括纹理和取样器),我们都可以用Semantic或Annotation修饰来起到一些特殊作用。Semantic暂时翻译成语义; Annotation暂时翻译成注解,这是HLSL中独特的东西。下面这两行中,第一个变量冒号后面的POSITION就是Semantic,第二个变量后面用一对尖括号<>圈起来的表达式就是Annotation,一组<>中可以有很多个表达式。
float3 OmniPos : POSITION;
texture TexMap < string name = "test.dds"; >;
一般来讲,
Semantic是
告诉应用程序或D3D这个被修饰的变量是做什么用的,
Annotation是
告诉程序这个变量怎么用。很云山雾罩是吗,是这样,在应用程序代码中,是可以调用D3D的API认出Semantic和Annotation的。例如上面这两行,程序的逻辑就可能是这样:首先写主程序的甲和写Shader的乙约定好POSITION标识该变量代表灯泡A的位置,甲在程序里写:{灯泡A.位置 = XXX; 找到Shader中带POSITION的变量; 给该变量赋值为灯泡A.位置; return;} 那么甲可以不知道乙在Shader中给这个要用灯泡A位置的变量起了什么名,而且乙可以在好几个Shader中给用这个数据的变量起不同的名。然后,甲和乙再约定遇到Annotation中的“name”就将后面的字符串作为文件名建立贴图。于是甲的程序就从Shader中读出了一个文件名,建立了一个贴图以供这个texture变量使用。Semantic和Annotation大概就这么用,首先要约定好各个Semantic和Annotation都是什么意思,这是up to you的,然后就是通过它们的标识来给变量赋值或作其他辅助性工作了。
既然都是做辅助说明的为什么还要分成Semantic和Annotation,我的想法是Semantic简单方便而Annotation能干的事更多。不说这个了,无关大局。要说的是,D3D也跟我们约定了一套Semantic,它们大体上都能顾名思义,控制流
控制流,就是if…else,for,while什么的。在CPU中,这些控制流造成的实际上是指令跳转。但在GPU中指令跳转并不被广泛的支持,以往的大部分显卡只懂得按顺序一句一句执行指令。因此HLSL的编译器可能会做出诸如展开循环、遍历分支等等莽撞的事来适应显卡。所以使用时要特别小心,而且不是所有情况的控制流语句都被支持。
函数HLSL中提供了很多函数可供调用,在Direct3D 文档 -> DirectX Graphics -> Reference -> HLSL Shader Reference -> HLSL Intrinsic Functions中有这些函数的详细列表。也可自己写函数用,但是在较早的Shader版本中,就像内联函数一样编译时最终要将函数展开插入到函数调用处。还有一点我想你一定会想到的就是主函数会是什么。Vertex Shader和Pixel Shader各自需要一个主函数,由程序员来指定!没错,程序员在Shader外部指定。
posted on 2007-11-13 01:08
七星重剑 阅读(2075)
评论(0) 编辑 收藏 引用 所属分类:
Game Graphics 、
HLSL&ShaderMonkey