1、1X N A S h a d e r编程教程1环境光照S h a d e r简史在D i r e c t X 8之前,G P U使用固定方式变换像素和顶点,即所谓的“固定管道”。这使得开发者不可能改变像素和顶点转化和处理的进程,使大多数游戏的图像表现看起来非常相似。D i r e c t X 8提出了顶点和像素着色器,这让开发者可以在管道中决定如何处理顶点和像素,使他们获得了很强的灵活性。一开始s hade r编程使用汇编语言程序使用的着色器,这对s hader开发者来说相当困难,S h a der Model 1.0是唯一支持的版本。但D i r e c t X 9发布后这一切改变了,开发者
2、能够使用高级着色语言(H L S L)取代了汇编语言,H L S L语法类似C语言,这使s hader更容易编写,阅读和学习。D i r e c t X 10.0提出了一个新的s hader G e om e t r y S h a der作为S h a der Model 4. 0的组成部分。但这需要一个最先进的显卡和Wi ndows V i s t a才能支持。X N A支持S h a der Model 1.0至3.0,可以在X P,V i s t a和X B ox360!上运行。S h a d e r?嗯,历史已经说得够多了那么什么是s hader?正如我所说的,s hader可以用来
3、定制管道的步骤,使开发者能够决定如何处理像素/顶点。如下图所示,应用程序在渲染时启动并使用s hader,顶点缓冲区通过向pixel s hader发送所需的顶点数据与pixel s hader协同工作,并在帧缓冲中创建了一个图像。但请注意许多G P U不支持所有的s hader模式,在开发s hader时应引起足够重视。一个s hader最好要有一个类似/简单的效果,使程序在较旧的计算机上也能工作正常。V e r t e x s h a d e rV e r t e x s h a de r s用来逐顶点地处理顶点数据。例如可以通过将模型中的每个顶点沿着法线方向移动到一个新位置使一个模型变“
4、胖”(这称之为def orm s hader s)。V e r t e x s h a de r s从应用程序代码中定义的一个顶点结构获取数据,并从顶点缓冲区加载这个结构传递到s hader。这个结构描述了每个顶点的属性:位置,颜色,法线,切线等。2接着V e r t e x s h a de r将输出传递到pixel s hader。可以通过在s hader中定义一个结构包含你想要存储的数据,并让V e r t e x s hader返回这个实例来决定传递什么数据,或通过在s hader中定义参数,使用out关键字来实现。输出可以是位置,雾化,颜色,纹理坐标,切线,光线位置等。s t r u
5、 c t VS _OUTP UTf l o a t 4 P o s : P OS I TI ON; ;VS _OUTP UT VS ( f l oa t 4 P os : P OS I TI ON )VS _OUTP UT Ou t = ( VS _OUTP UT) 0 ;. . .r et u r n Ou t ;/ / o rf l o a t 3 VS ( o u t f l o a t 2 t ex : TEX C OORD0 ) : P OS I TI ONt ex = f l oa t 2 ( 1 . 0 , 1 . 0 ) ;r et u r n f l o a t 3 ( 0
6、. 0 , 1 . 0 , 0 . 0 ) ;P i x e l S h a d e rP i xel S h a der对给定的模型/对象/一组顶点处理所有像素(逐像素)。这可能是一个金属盒,我们要自定义照明的算法,色彩等等。P i xel S h a der从v e r t e x s h a de r s的输出值获取数据,包括位置,法线和纹理坐标:f l o a t 4 P S ( f l oa t v P os : VP OS, f l o a t 2 t ex : TEX C OOR D0 ) : C OL OR. . .r et u r nf l o a t 4 ( 1 . 0 f
7、 , 0 . 3 f , 0 . 7 f , 1 . 0 f ) ;p i x el s h a d er可以有两个输出值:颜色和深度。H L S LH H HH L S L是用来开发s hader的。在H L S L中,您可以声明变量,函数,数据类型,测试(i f / e l s e / f or / do/w hil e +)以及更多功能以建立一个顶点和像素的处理逻辑。下面是一些H L S L的关键字。这不是全部,但是最重要的。数据类型:boolt r ue or f a l s ei n t 32-bit i nte ge r3hal f 16bit i nte gerf l oat 3
8、2bit f l oatdouble 64bit double向量:f l oat 3 vec t orT e s tf l oat vec t orT e s t 3vec t or vec t orT e s tf l oat 2 vec t orT e s tbool3 v e c t orT e s t矩阵:f l oat 3x3:3x3矩阵,f l oat类型f l oat 2x2:2x2矩阵,f l oat类型还有很多辅助函数处理复杂的数学表达式:c os( x )返回x的余弦值s i n( x)返回x的正弦值c r oss ( a , b )返回向量a和向量b的叉乘dot( a
9、,b )返回向量a和向量b的点乘norm a l i z e ( v )返回一个归一化的向量v(v / | v|)完整列表请看:htt p:/ / m s dn2.mi c r osoft .c om / e n- u s / l i b r a r y/ bb509611.as px(译者:推荐看c l a ym a n的博客中的T he C om ple t e E f f e c t a nd H L S L G uide连载)H S L S提供了大量的函数让你使用!它们能帮助你解决不同的问题。E f f e c t文件E f f e c t文件(.f x)让开发s hader变得更容易
10、,你可以在.f x文件中存储几乎所有关于着色的东西,包括全局变量,函数,结构,ver t e x s hader,pixel s hader,不同的t e c hniques / pas s e s,纹理等等。我们前面已经讨论了在s hader中声明变量和结构,但什么是t e c hnique/ pas s e s?这很简单。一个S h a der可以有一个或一个以上的t e c hnique。每个t e c hniq ue都有一个唯一的名称,我们可以通过设置E f f e c t类中的C u r r e ntT e c hniq ue属性选择使用哪个t e c hnique。ef f ec
11、t . C u r r en t Tec h ni q ue = ef f ec t . Tec h n i qu es “ Amb i en t L i g ht “ ;在这里,我们设置“e f f e c t”使用t e c hnique“A m bie ntL i ght”。一个t e c hniq ue可以有一个或多个pas s e s,但请确保处理所有pas s e s以获得我们希望的结果。这个例子包含一个t e c hniq ue和一个pas s:t ec h n i qu e S h a der4p a s s P 0Ver t ex S h a d er = c o mpi l
12、 e v s _1 _1 VS ( ) ;P i xel Sh a d er = c o mp i l e p s _1 _1 P S ( ) ;这个例子包含一个t e c hniq ue和两个pas s:t ec h n i qu e S h a derp a s s P 0Ver t ex S h a der = c omp i l e v s _1 _1 VS ( ) ;P i xel S h a d er = c omp i l e p s _1 _1 P S( ) ;p a s s P 1Ver t ex S h a der = c omp i l e v s _1 _1 VS _O
13、t h er ( ) ;P i xel S h a d er = c omp i l e p s _1 _1 P S_Ot h er ( ) ;这个例子包含二个t e c hniq ue和一个pas s:t ec h n i qu e S h a der _1 1p a s s P 0Ver t ex S h a d er = c o mpi l e v s _1 _1 VS ( ) ;P i xel Sh a d er = c o mp i l e p s _1 _1 P S ( ) ;t ec h n i qu e S h a der _2 ap a s s P 0Ver t ex S h
14、 a der = c omp i l e v s _1 _1 VS 2 ( ) ;P i xel S h a d er = c omp i l e p s _2 _a P S 2 ( ) ;我们可以看到,一个t e c hnique有两个函数,一个是pixel s hader,另一个是v e r t e x s hader。Ver t ex S h a d er = c o mpi l e v s _1 _1 VS 2 ( ) ;5P i xel Sh a d er = c o mpi l e p s _1 _1 P S 2 ( ) ;这告诉我们,这个t e c hniq ue将使用V S 2
15、()作为ver t e x s hader,P S 2()作为pixel s h a de r,并且支持S h a der Model 1.1或更高版本。这就让G P U支持更高版本的s hader变得可能。在X N A中实现S h a der很简单。事实上,只需几行代码就可以加载和使用s hader。下面是步骤:1.编写s hader2.把s hader文件(.f x)导入到“C ont e nts”3.创建一个E f f e c t类的实例4.初始化E f f e c t类的实例。5.选择使用的t e c hniq ue6.开始s hader7.传递不同的参数至s hader8.绘制场景9
16、.结束s hader更详细的步骤:1.记事本和V i s ual S t udio等都可以用来编写s h a de r。也有一些s hader的I D E可用,我个人喜欢使用nVi d i a s的F X C om pose r:htt p:/ / devel oper .nvidia .c om / obje c t / f x_ c om pose r _home .htm l。(译者:还推荐一个s hader的I D E:A MD公司的R e nder Monkey,可在htt p:/ / a t i . a m / devel o per / r e nder m onkey/ do
17、wnlo a ds.htm l下载最新版本1.81(93.9MB,2008年4月8日),个人用下来的感觉好像nvidi a实力更强一些,文档也很详实,而R e nder Monkey上手更容易。)2当s hader建立后,将其拖放到“C ont e nt”目录,自动生成素材名称。3X N A框架有一个E f f e c t类用于加载和编译s hader。要创建这个类的实例可用以下代码:Ef f ec t ef f ec t ;E f f e xt属于Mi c r osoft .Xna.F r a m e w ork.Gr a phic s类库,因此,记得添加usi ng语句块:u s i n
18、g Mi c r o s of t . X n a . F r a mewo r k . Gr a p h i c s4要初始化s hader,我们可以使用C ont e nt从项目或文件中加载:6ef f ec t = C o n t en t . L o a d ( “ S ha d er “ ) ;“S h a der”是你添加到C ont e nt目录的s hader名称。5选择使用何种t e c hniq ue:ef f ec t . C u r r en t Tec h ni q ue = ef f ec t . Tec h n i qu es “ Amb i en t L i g
19、 h t “ ;6要使用E f f e c t,请调用B e gin( )函数:ef f ec t . Beg i n( ) ;此外,您必须启动所有pas s e s。f o r ea c h ( Ef f ec t Pa s s p a s s i n ef f ec t . C u r r en t Tec h n i qu e. P a s s es )/ / Beg i n c ur r en t p a s sp a s s . Beg i n ( ) ;7有很多方法可以设置s h a der的参数,但对这个教程来说下列方法够用了。注:这不是最快的方法,在以后的教程中我将回到这里:e
20、f f ec t . P a r a met er s “ ma t Wor l dVi ewP r oj “ . S et Va l u e( wo r l d Ma t r i x * v i ewMa t r i x*p r oj Ma t r i x) ;其中“m a t W orl dVi e w P r oj”是在s hader中定义的:f l o a t 4 x4 m a t Wo r l d Vi e w P r o j ;将m a t W orl dVi e w P r oj设置为w orl d Ma t r i x * vie w Ma t r i x * projMa t
21、 r i x。S e t V a l ue设置参数并将它传递到s h a der,G e t V a l ue从s hader获取值,T ype是获取的数据类型。例如,G e t V a l ue I nt 32()得到一个整数。8渲染你想要这个s hader处理/转换的场景/对象。9要停止pas s,调用pas s .End(),要停止s h a de r,调用E f f e c t的E nd()方法:pa s s . En d( ) ;ef f ec t . En d ( ) ;为了更好地理解步骤,可参见源代码。环境光照(A mb i e n t l i g h t)7O K,我们终于到了
22、最后一步,实现s hader!不坏吧?首先,什么是“A m b i e nt l i ght”?环境光是场景中的基本光源。如果你进入一个漆黑的屋子,环境光通常是零,但走到外面时,总是有光能让你看到。环境光没有方向(译者:所以也将其称为“全局光照模型”),在这里应确保对象不会自己发光,它有一个基本的颜色。环境光的公式是:I = A i nt e nsi t y* A c olo r其中I是光的实际颜色,A i nt e nsi t y是光的强度(通常在0.0和1.0之间),A c olo r环境光的颜色,这个颜色可以是固定值,参数或纹理。好吧,现在开始实现S h a der。首先,我们需要一个矩
23、阵表示世界矩阵:f l o a t 4 x4 ma t Wo r l d Vi ewP r o j ;在s hader顶端声明这个矩阵(作为全局变量)然后,我们需要知道v e r t e x s hader向pixels hader传递了哪些值。这可以通过建立一个结构(可以命名为任何值)实现:s t r u c t OUTf l o a t 4 P o s : P OS I TI ON; ;我们创建了一个名为O U T的结构,其中包含一个f l o a t 4类型的名叫P os的变量。“:”后面的P O S I T I O N告诉G P U在哪个寄存器(r e gis t e r)放置这个值?
24、嗯,什么是寄存器?寄存器是G P U中保存数据的一个容器。G P U使用不同的寄存器保存位置,法线,纹理坐标等数据,当定义一个s hader传递到pixel s hader的变量时,我们必须决定在G P U的何处保存这个值。看一下ver t e x s hader:OUT Ver t ex S h a der ( f l o a t 4 P o s : P OS I TI ON )OUT Ou t = ( OUT) 0 ;Ou t . P os = mu l ( P os , ma t Wo r l d Vi ewP r o j ) ;r et u r n Ou t ;我们创建了一个O U T
25、类型的函数,它的参数是f l oat 4类型的P os:P O S I T I O N。这是模型文件/应用程序/游戏中定义的顶点位置。然后,我们建立一个名叫O U T的O U T结构实例。这个结构必须被填充并从函数返回,以便后继过程处理。输入参数中的P os不参与后继过程的处理,但需要乘以w orl d vie w proj e c t i o n矩阵使之以正确放置在屏幕上。由于P os是O U T中的唯一变量,我们已经返回它并继续前进。现在开始处理pixel s hader s,我们声明为一个f l oat 4类型的函数,返回存储在G P U中的C O L O R寄存器上的f l oat 4
26、值。我们在pixel s hader中进行环境光的算法:8f l o a t 4 P i xel Sh a d er ( ) : C OL ORf l o a t Ai = 0 . 8 f ;f l o a t 4 Ac = f l o a t 4 ( 0 . 0 7 5 , 0 . 0 7 5 , 0 . 2 , 1 . 0 ) ;r et u r n Ai * Ac ;这里我们使用上面的公式计算目前像素的颜色。A i是环境光强度,A c是环境光颜色。最后,我们必须定义t e c hniq ue并将pixel s hader和ver t e x s hader函数绑定到t e c hniqu
27、e上:t ec h n i qu e Amb i en t L i g htp a s s P 0Ver t ex S h a der = c omp i l e v s _1 _1 Ver t ex S h a d er ( ) ;P i xel S h a d er = c omp i l e p s _1 _1 P i xel S h a der ( ) ;好了,完成了!现在,我建议你看看源代码,并调整各个参数更好地理解如何使用X N A实现s hader。环境光满足下列公式:I = A i nt e nsi t y * A c olo r漫反射光的公式以此为基础,在方程中添加了一个定向
28、光:I = A i nt e nsi t y*Ac olor + D i nt e nsi t y*Dc olo r *N.L从这个公式可以看到我们仍然使用环境光,但需要额外两个变量描述漫反射的的颜色和强度,两个向量N描述表面的法线,L描述光线的方向。我们可以将漫反射光线作为表示表面反射光线的多少。光线反射的强度随着N和L夹角的变小而变得更强。如果L与N平行则反射最强烈,如果L平行于表面,则反射最弱。9要计算L和N的夹角,我们可以使用点乘或标量乘积。这条规则可用来计算给定两个向量间的夹角,可以定义如下:N .L = | N | * | L | * c os ( a)(译者注:这个公式实际上是L
29、 a m b e r t定理的简化形式,若归一化N和L,则这个公式可简化为N .Lc os( a ))这里| N |是向量N的长度,| L |是向量L的长度,c os( a )是两个向量之间夹角的余弦。实现s h a d e r我们需要三个全局变量:f l oat 4x4 m a t W orl dVi e w P r oj ;f l oat 4x4 m a t I nver s e Worl d ;f l oat 4 vLi ghtD i r e c t i o n ;我们仍然要教程1中的w orl dvie w proje c t i o n矩阵,但除此之外,我们还需要I n ve r s
30、 e Wor l d矩阵计算出与世界矩阵相关的正确法线,而vLi ghtD i r e c t i o n表示光线的方向。我们还需要在ver t e x s hader中定义O U T结构,这样才能在pixel s h a der中获得正确的光线方向:s t r u c t OUTf l o a t 4 P o s : P OS I TI ON;f l o a t 3 L : TEX C OOR D0 ;f l o a t 3 N : TEX C OOR D1 ; ;这里定义了位置P os,光线方向L和法线方向N存储在不同的寄存器中。T E X C O O R D n可用于任何值,现在我们还没
31、有使用任何纹理坐标,所以我们可以用这些寄存器储存这两个向量。10O K,现在处理ver t e x s hader:OUT Ver t ex S h a der ( f l o a t 4 P o s : P OS I TI ON, f l o a t 3 N : NOR MAL )OUT Ou t = ( OUT) 0 ;Ou t . P os = mu l ( P os , ma t Wo r l d Vi ewP r o j ) ;Ou t . L = n o r ma l i z e( v L i g h t Di r ec t i o n) ;Ou t . N = n o r ma
32、l i z e( mu l ( ma t I nv er s eWo r l d , N) ) ;r et u r n Ou t ;我们从模型文件获取位置和法线并通过它们传递到s hader。根据这些值和全局变量我们可以转换位置,法线和光线方向,并转换和归一化表面的法线。然后,在P i xel S h a der中获取T E X C O O R D 0中的的值并把它放在L中,T E X C O O R D 1中的值放入N。这些寄存器的数据是由v e r t e x s h a de r添加的。然后,我们在pixel s hader执行上面的漫反射方程:f l o a t 4 P i xel S
33、h a d er ( f l o a t 3 L : TEX C OOR D0 , f l o a t 3 N : TEX C OOR D1 ) : C OL ORf l o a t Ai = 0 . 8 f ;f l o a t 4 Ac = f l o a t 4 ( 0 . 0 7 5 , 0 . 0 7 5 , 0 . 2 , 1 . 0 ) ;f l o a t Di = 1 . 0 f ;f l o a t 4 Dc = f l o a t 4 ( 1 . 0 , 1 . 0 , 1 . 0 , 1 . 0 ) ;r et u r n Ai * Ac + Di * Dc * s a
34、 t u r a t e( d o t ( L , N) ) ;译者注:因为dot( L , N )的范围在(1,1)之间,所以需要s a t ur a t e将它截取到(0,1)之间。使用的t e c hniq ue如下:t ec h n i qu e Di f f us eL i g htp a s s P 0Ver t ex S h a der = c omp i l e v s _1 _1 Ver t ex S h a d er ( ) ;P i xel S h a d er = c omp i l e p s _1 _1 P i xel S h a der ( ) ;好了,这就是漫反
35、射光照!可以下载源码更好地理解原理,希望你能感受到s hader的威力并知道如何在程序中实现。译者注:还看过一个例子中不使用I nver s e Worl d矩阵,而是使用O u t .N =norm a l i z e ( m ul( N ,m a t Worl d) ) ;而本例使用的是O u t .N = n or m a l i z e ( m ul( m a t I nver s e Worl d , N ) ) ;11另外本例中的L向量对应的vLi ghtD i r e c t i o n在程序中设置为:V e c t or4 v L i ghtD i r e c t i on =
36、 n e w V e c t or 4(0.0f, 0.0f, 1.0f, 1.0f) ;这表示指向z轴正方向,即垂直屏幕向外,这里实际是指“顶点指向光源的方向”,也就是说光线的方向是垂直屏幕向里的,教程里使用“光线方向”容易引起误解,至少我是一开始就搞错了。习惯上光线的方向是指“从光源指向顶点的方向”,这时应该是用s a t ur a t e ( dot(L ,N) ) ;而不是本例中的s a t ur a t e ( dot( L , N ) ) ;因为根据L a m be r t定理,光线的方向是指从顶点指向光源的方向,而导入的L是指光源指向顶点的方向,所以要L。X N A S h a
37、d e r编程教程3镜面反射光照这次我们将实现一个叫做镜面反射的光线算法。该算法是建立在前面环境光照和漫反射光照的基础上的,所以,如果你前面没有弄懂,现在是时候了。:)在这个教程中,您需要一些s hader编程的基本知识,矢量和矩阵的数学知识。镜面反射光照迄今为止,我们已经实现了一个很好的照明模式。但是,如果我们想绘制一个抛光或闪耀的物体该怎么办?比如说金属表面,塑料,玻璃,瓶子等。为了模拟这种情况,我们需要在照明算法中加入一个新的向量:e ye向量。您可能会想什么是“ e ye ”向量?这很容易回答:“ e ye”向量是从相机位置指向观察目标的向量。在程序代码中已经有了这个向量:v i ew
38、Ma t r i x= Ma t r i x. C r ea t eL o o k At ( n ew Vec t o r 3 ( x, y , z Hei g h t ) , Vec t o r 3 . Z er o, Vec t o r 3 . Up ) ;“ T he e ye “的位置在这:Vec t o r 3 ( x, y , z Hei g h t )让我们把这个向量存储在一个变量中:V e c t or4 v e c E ye = n e w V e c t or 4(x, y, z H e i ght,0);让我们深入讨论如何在创建这个向量后使用s hader。镜面高光的公式
39、是I = A i A c + D i *Dc *N.L+ S i *Sc *(R .V) n12其中R = 2*(N .L) *NL如我们所见,我们需要新的E ye向量和一个反射向量R。为了计算镜面光,我们需要将R点乘V并进行n次幂的运算,而指数n表示光泽属性,n越大说明物体表面越光滑,反光越强。实现S h a d e r如前面的截图可见,现在这个对象看起来很有光泽,只需通过s hader就能实现!很酷吧!首先声明一些变量:f l o a t 4 x4 ma t Wo r l d Vi ewP r o j ;f l o a t 4 x4 ma t Wo r l d ;f l o a t 4 v
40、 ec L i g h t Di r ;f l o a t 4 v ec Ey e;13f l o a t 4 v Di f f us eC o l o r ;f l o a t 4 v S p ec u l a r C o l or ;f l o a t 4 v Amb i en t ;然后是O U T结构。S h a der将返回经过变换的位置P os,光线向量L,法线向量N和观察向量V(E ye向量)。s t r u c t OUTf l o a t 4 P o s : P OS I TI ON;f l o a t 3 L : TEX C OOR D0 ;f l o a t 3 N :
41、TEX C OOR D1 ;f l o a t 3 V : TEX C OOR D2 ; ;除了V向量,在v e r t e x s h a der没有新的东西。V可以通过将E ye向量减去经变换后的位置向量得到。由于V是O U T结构的一部分,而且我们已经定义了O U T out,所以可以通过下列代码计算V:f l o a t 4 P o s Wo r l d = mu l ( P os , ma t Wo r l d ) ;Ou t . V= v ec Ey e - P o s Wo r l d这里的vec E ye向量是通过参数(相机位置)传递到s hader中的。OUT VS ( f
42、l oa t 4 P os : P OS I TI ON, f l o a t 3 N : NOR MAL )OUT Ou t = ( OUT) 0 ;Ou t . P os = mu l ( P os , ma t Wo r l d Vi ewP r o j ) ;Ou t . N = mu l ( N, ma t Wo r l d ) ;f l o a t 4 P o s Wo r l d = mu l ( Po s , ma t Wo r l d ) ;Ou t . L = v ec L i g h t Di r ;Ou t . V = v ec Ey e - P o s Wo r l
43、d ;r et u r n Ou t ;然后处理pixel s hader。首先归一化N orm a l,L i ghtD i r和V i e w D i r简化计算。P i xel s hader会根据前面镜面反射公式返回f l oat 4代表当前像素的最终颜色和光强。然后使用教程2同样的方法计算漫反射光线的方向。P i xel S h a der中新的东西是通过L和N计算反射向量R,并使用这个向量计算镜面反射光。因此,首先计算反射向量R:R = 2 * ( N .L) * N L14在前面我们已经在计算漫反射光时计算了N和L的点乘。所以可以使用如下代码:f l o a t 3 R ef l
44、 ec t = n o r ma l i z e( 2 * Di f f * No r ma l - L i gh t Di r ) ;译者注:也可以使用H L S L内置的函数r e f l e c t计算R e f l e c t向量,注意在L i ghtD i r前有个负号,想想为什么:f l o a t 3 R ef l ec t = n o r ma l i z e( r ef l ec t ( - L i g ht Di r , No r ma l ) ) ;现在只剩下计算镜面反射光了。我们知道,这需要计算反射向量R和观察向量V的n次幂:( R .V) nn表示物体的光泽程度,n越
45、大,反光区域越小。您可能注意到,我们使用了一个新的H L S L函数pow( a ,b),它返回a的b次方。f l o a t S p ec u l a r = p o w( s a t u r a t e( d o t ( R ef l ec t , Vi ewDi r ) ) , 1 5 ) ;我们终于准备将一切整合在一起并计算出最终的像素颜色:r et u r n v Amb i en t + v Di f f u s eC o l or * Di f f + v S pec u l a r C ol o r * S p ec ul a r ;这个公式你应该很熟悉了,对不对?我们首先计算
46、环境光和漫反射光并把它们相加。然后将镜面反光的颜色乘以刚才算出的反光强度S pec ula r,并和环境光颜色和漫反射颜色相加。本教程的pixel s hader如下所示:f l o a t 4 P S ( f l oa t 3 L : TEX C OOR D0 , f l oa t 3 N : TEX C OOR D1 , f l o a t 3 V : TEX C OOR D2 ) : C OL ORf l o a t 3 No r ma l = n o r ma l i z e( N) ;f l o a t 3 L i gh t Di r = n o r ma l i z e( L )
47、;f l o a t 3 Vi ewDi r = n o r ma l i z e( V) ;f l o a t Di f f = s a t u r a t e( d ot ( No r ma l , L i g ht Di r ) ) ;/ / R = 2 * ( N. L ) * N L f l o a t 3R ef l ec t = n o r ma l i z e( 2 * Di f f * No r ma l - L i g ht Di r ) ;f l o a t S p ec ul a r = p ow( s a t u r a t e( d ot ( R ef l ec t
48、 , Vi ewDi r ) ) , 1 5 ) ; / / R . Vn/ / I = A + Dc ol o r * Di n t en s i t y * N . L + S c ol o r * S i n t en s i t y * ( R . V) nr et u r n v Amb i en t + v Di f f us eC o l o r * Di f f + v S p ec u l a r C o l or * S p ec u l a r ;当然,我们还要指定一个t e c hniq ue并编译V e r t e x和P i xel s hader:t ec h n
49、 i qu e S p ec u l a r L i g htp a s s P 015/ / c o mpi l e s h a d er s Ver t ex S h a d er = c omp i l e v s _1 _1 VS ( ) ;P i xel S h a d er = c omp i l e p s _2 _0 P S( ) ;s hader ( .f x )文件的完整代码如下:f l o a t 4 x4 ma t Wo r l d Vi ewP r o j ;f l o a t 4 x4 ma t Wo r l d ;f l o a t 4 v ec L i g h t Di r ;f l o a t 4 v ec Ey e;f l o a t 4 v Di f f us eC o l o r ;f l o a t 4 v S p ec u l a r C o l or ;f l o a t 4 v Amb i en t ;s t r u c t OUTf l o a t 4 P o s : P OS I TI ON;f l o a t 3 L :