顶点着色器基础
顶点着色器取代了图形管线中的顶点处理过程。当使用了定点着色器,有关变换和光照操作的状态信息就不再由固定的图形管线来处理了。当顶点着色被禁止,固定管线可以再次回来处理这些状态信息。
镶嵌技术,镶嵌高级图元的过程会发生在顶点着色之前。只有在dx11中该技术才能得到完全的硬件加速。
顶点着色器至少会输出具有相同性质的顶点顶点位置信息。作为可选项,顶点着色器还会输出纹理坐标、顶点颜色、顶点光照、雾效参数、等。
像素着色器基础
像素处理由像素着色器对每个像素进行计算。顶点着色器的输出作为像素着色器的输入。其他的像素操作(例如,雾混合、模板操作、渲染表面混合)会发生在像素着色之后。
纹理状态和采样状态
目前,多重纹理混合及相关操作完全有像素着色器来实现,纹理采样和过滤及纹理环绕已经可以在像素着色器中实现了。
像素着色输入
对像素着色器版本ps_1_1到ps_2_0,漫反射和镜面反射颜色在使用shader之前都被限制在0到1之间。
输入到像素着色器的颜色被认为是正确的,但是并不能完全保证。通过纹理坐标采样得到颜色也被限制在0到1之间。
像素着色输出
对像素着色器版本ps_1_1到ps_1_4,像素着色器的输出是寄存器r0的内容。不管该输出是什么,它都会在像素着色结束之后被发送给雾效处理阶段和渲染表面阶段。
对像素着色器版本ps_2_0和以后的版本,输出颜色从oC0到oC4中取出。
shader输入和shader变量类型
请参考sdk。
顶点着色器和像素着色器接收两种输入参数,varying 和 uniform 。varying 类型的输入对每个shader的多个执行实例都是独立的数据。对顶点着色器来说,varying 类型的数据就是position、normal等来自顶点数据流的信息。uniform数据(材质颜色和世界变换等信息),对每个shader的多个不同实例来说是常量。对那些熟悉汇编shader模型的人来说很容易理解,其实,uniform数据是由常量寄存器指定的数据,varying 数据是由v和t寄存器指定的数据。
unform数据可以由两种方式来指定,最常用的方式是声明成全局变量并在shader中使用它们。任何在shader中使用的全局变量都会在该shader的uniform数据链表中添加一个变量。另外一种方法是,在top-level的函数参数中标记uniform.该标记表示指定的变量应该被添加到uniform数据链表中。
uniform变量通过常量表与应用程序发生交互。常量表包含了所有uniform变量在常量寄存器中的位置,该表还包含了类型信息和指定的默认值等信息。
top-level的shader函数的varying输入参数必须使用semantic标记或uniform标记(表示该值对同一个shader的不同执行实例来说是常量)。如果top-level函数的参数没有被标记为semantic和uniform,则该shader会编译出错。
输入语法(semantic)的作用是连接给定输入参数与图形管线上一个阶段的输出参数。例如,输入语法POSITION0就是用来指定输入的位置信息的来源是顶点buffer中的位置信息。
顶点shader和像素shader位于不同的图形管线阶段,所以具有不同的输入语法集。顶点shader输入语法描述了从顶点buffer中加载的每个顶点的信息(位置、法线、纹理坐标、颜色、切线、次法线等),作为顶点shader的输入。输入语法直接映射顶点声明和用法索引。(在c++代码中使用的是FVF,但是在shader中FVF会被转换成顶点声明格式,也可以直接指定顶点声明格式,输入语法实际就是在顶点声明和输入字段之间建立了映射关系。)。像素shader的输入语法描述了由光栅化单元提供的每个像素的信息。这些输入数据是由顶点shader的输出进行差值产生的。基本的像素shader输入语法建立了上个阶段的每个像素的输出颜色和纹理坐标信息与该阶段的输入参数之间的映射。
顶点shader和像素shader都为图形管线中的下一个阶段提供了输出数据。输出语法集被用来指定shader生成的数据怎么与下一个阶段进行映射。例如,顶点shader的输出语法被用来创建光栅化差值器的输出与像素shader的输入之间的映射关系。像素shader的输出被提供给每个renderTarget的alpha混合单元或被写到深度buffer的深度值。
顶点shader输出语法被用来连接顶点shader和光栅化器,及连接顶点shader和像素shader。
像素shader输出语法被用来绑定shader输出颜色与正确的renderTarget。像素shader的输出颜色被用来连接到alpha混合阶段,该阶段决定了renderTarget将被如何修改。像素着色器的深度值输出被用来改变当前像素的深度值。深度值输出和多renderTarget只被某些shadermodel支持。
输入结构指定了哪些数据会从顶点buffer中提取出来提供给顶点shader。顶点shader把数据的位置、法线、混合权值等元素从顶点buffer中存储到顶点shader的寄存器中。输入数据的类型如果与shader中顶点声明中的数据类型不匹配则shader会自动的转换到HLSL的数据类型。如果顶点数据流中的数据的字段比shader顶点声明中的字段少,则多余的字段将被初始化为0,w将被初始化为1.
全局变量被编译器自动的赋值给寄存器。全局变量又被称作uniform变量,因为该变量的内容在每次shader被调用的时候所有像素的处理过程中是不会改变的。寄存器被包含在常量表中。可以通过ID3DXConstantTable 接口来访问和修改。
像素shader的输入语法指定了在顶点shader和像素shader之间起传输作用的硬件寄存器。每个寄存器的类型都有指定的属性。因为当前只有两种语法,颜色和纹理坐标。所以对那些非颜色和纹理坐标的信息,一般也可以通过标记为纹理坐标来进行传输。
注意,在顶点着色器中的输出语法中有时有位置信息,而这些信息对像素着色器来说是没有用的。HLSL允许无效的顶点shader输出数据。
shader是在dx8.0中才开始出现的。当时还是用汇编写的。shaderModel是vs_1_1和ps_1_1-ps_1_4.
在dx9.0时,有了HLSL语言,并且shaderModel升级到了vs_2_0,vs_3_0和ps_2_0,ps_3_0.
在写shader时必须要考虑版本问题,即要为好显卡,即支持高级着色模型的显卡写高级的shader,还要为那些次显卡即不支持高级着色模型的显卡写一些低级的基础的shader。HLSL要比汇编更容易,而且该语言会自动编译成汇编语言,自动生成的汇编总会比手动写的效率要高,所以建议大家以后尽量使用HLSL。建议离线编译shader,这不仅能保证代码安全,还能保证程序稳定,推荐工具fxc。
正如前面讲过的,编译器会自动的把全局变量赋值给寄存器,例:
sampler Environment;//对应s0 sampler SparkleNoise;//对应s1 float4 k_s;//对应c0
当然还能手动的绑定寄存器。例:
sampler Environment : register(s1); sampler SparkleNoise : register(s0); float4 k_s : register(c12);
渲染一个shader要经过以下几步:
1.把shader设置为当前shader。IDirect3DDevice9::SetVertexShader
2.初始化shader常量。IDirect3DDevice9::SetVertexShaderConstantF
3.设置数据源。IDirect3DDevice9::SetStreamSource
4.渲染。IDirect3DDevice9::DrawPrimitive
编译和渲染fragment请参阅文档,再次不赘述。