[翻译]Oreilly.Learning.XNA.3.0之five

PS:自己翻译的,转载请著明出处
                                                                    第十三章   HLSL基础
                       让我们先离开游戏的开始一分钟,谈谈有关于Hight Level Shader Language(HLSL)的内容。Pre-XNA,DirectX允许开发者传送指令在图形设备上,通过一个机制称为固定功能管道(FFP).这个工作暂时的工作不错,直到图形卡和硬件开始成为难以置信的复杂。更多的性能,它被添加到硬件中,更详细和复杂的FFP需要去成为允许开发者去充分利用硬件。
注意:即使在现在的卡上,FFP被作为着色器执行-就象在场景后面的一个操作。这是非常相似的方式,这种方式是BasicEffect为开发商提供了一个简单的存取版本的FFP。               
                       相反不断增加功能到FFP,扩展它,微软决定改变,并允许开发者可以直接与硬件设备对话,这个语言是专为这些设备指定的语言。
                       第一次尝试解决这个问题,允许开发者直接编写汇编语言给硬件。虽然这种方法是功能性,开发还需要更高级别的语言去开发。HLSL的引入。HLSL开始作为一个连接项目在微软和NVIDIA之间。在某一点上,然而, 开发的成果被分开了,NVIDIA的语言(为图形称为C,或者Cg)走一条路线,并且微软的语言(称为HLSL)走了另一条。
                       HLSL允许开发者编写的语言,类似于C和转化到汇编语言的图形卡本身。用HLSL ,开发人员可以访问所有的功能,图形卡,而不必使用的API 就象FFP。
                       正如这本书前面章节中提到的,任何东西绘制在XNA3D上使用HLSL。XNA开发团队在微软非常仁慈的添加上了BaiscEffect类,你使用它在例子上到现在,为了使开发者能够学会使用XNA而不用一开始就学习HLSL。然而,甚至BasicEffect类简单传递数据到一个内在的HLSL处理器。
注意:决定添加BasicEffect类到XNA框架是一个事实,FFP不被支持在Xbox360的GPU上(图形处理单元)。也就是说开发者在微软面对一个进退两难的局面-难道它们应该支持FFP在PC上,强迫代码重写为所有的Xbox360游戏,或发明一种巨大的阴影,实现FFP?最终,它们决定采取一个中间的办法,它给了开发者足够去站起来并且运行不用处理阴影代码(BasicEffect类)。
                       在HLSL ,开发人员编写的着色器可以访问的最复杂的图形硬件是一个特色。图形卡能支持两个不同的着色器类型:顶点着色器和象素着色器。顶点着色器运行一次为所有的顶点在viewing frustum或者视景区域。象素着色器运行一次在所有可见的象素在所有可见的对象绘制在场景中。
                       为了了解更多点关于阴影处理在HLSL中,看图13-1。

                       阴影的处理首先运行任何顶点着色器,为每一个顶点着色器在场景中的。顶点着色器的目标是设置顶点的位置基于world和相机的设置。一旦位置数据从顶点着色器中接收到,一个光栅化进程发生。光栅是处理转换一个三角形成一组象素被绘制到场景中。在场景已经被光栅,象素着色器运行在每个象素上,它们被绘制在场景中。象素着色器的目标是每个定义可见象素的颜色。
注意:在象素着色器运行之前,一个深度检查运行在每个象素上。这个可以识别的像素,它们在视景区域内,但是被隐藏是因为另外一个对象在它们的前面,所以象素着色器不必要去绘制它们。
                       在象素着色器运行之后,数据输出这个屏幕。
HLSL语法
                       HLSL是一种语言,类似于C.我会提醒你在我们进入这节用HLSL编写阴影之前,它是非常的困难比编写代码在XNA中。如果你不能理解它并且感觉迷茫,别让它给你带来太大的压力。事实上,在很多游戏开发中,它不是罕有的为这些到一个游戏开发小组,游戏开发团队分一个人,谁写阴影,谁用DirectX或者XNA,主要是因为它们实在是两种不同的技能组合。
                       尽管如此, HLSL是XNA开发的一个重要组成部分,这本书可能不完整,如果它(译者:本书)不包含一些扩展的话。在这章,我们会了解HLSL基础,复习一些例子代码,但是这本书不意味着要结束了,成为所有的HLSL资源。整本书专门讨论这个问题;如果您正在寻找获得真正深入HLSL ,您可以拿起,然后在此基础深化你的知识运用这些资源。
                       好了,这是我的免责声明。让我们的行动吧。
                       变量在HLSL被声明是与在C#中相同的方式。事实上,很多变量使用同样的关键字象用C#(HLSL的关键字如int,float
,bool,string,true,false,return,void,etc...)
                       更复杂的对象作为代表性的是structs,它同样用和C#相同的方式定义。一个十分常见的方式,代表数据在3D图形中是使用一个向量。向量能定义使用一个vector关键字,但是它们更普通定义作为float3s代表向量,有三个元素同时float4代表向量有四个元素。
                       float3和float4基本上数组变量的浮点值。向量定义这种方式很普通的代表既可以是颜色也可以是位置。作为一个例子,你可以定义一个新float4变量,并且初始化它的值使用下面的行:
1 float4 color = float4(1001);
                       这里定义一个float4变量称为color,你可以通过单个元素在float4变量中通过使用数组符号,如下:
1 float red = color[0];
                       除了数组符号,你可以使用特殊的命名空间访问float4数据类型。在前面提到过,一个float4能代表一个颜色或者一个位置。例如,一个四元素在这个数组中能相对于任何的r,g,b和一个或者x,y,z和w.为了访问一个变量的元素使用这些命名空间,你可以使用类似于下面的代码:
1 float blue = color.b;
2 float z = color.z;
                       请注意,你可以使用rgba或者xyzw去访问相同的变量。事实上,你可以结合数组的元素成一个新数组,访问元素用任何的命令。下面的代码使数组颠倒通过访问元素用颠倒次序:
1 float4 reverse = color.abgr;
                       一次性的访问多个元素,正如前面的代码行,被称为swizzling(译者:任意组合)可以swizzle中的内容的任何命令,但您不能swizzle 在颜色和位置的命名空间。例如,此代码将导致汇编错误:
1 // This won't compile
2 float4 reverse = color.axgy;
                       另一个重要的变量类型值得注意的是,矩阵使用floatRxC关键字创建(R相当于行的数量,C相当于列的数量)。你同样可以使用matrix关键字去创建矩阵变量。下面两行代码将创建4*4矩阵:
1 float4x4 matrixFun;
2 matrix <float44> matrixMadness;
                       最后,如前所述, structs中创建HLSL以同样的方式与C#相似,所以你可以使用下面的代码去创建一个struct在HLSL中:
1 struct myStruct
2 {
3     float4 position;
4 };
Dissecting a Sample HLSL Effect File
                       在XNA中,effects被创建用effect文件,它有一个.fx扩展。它们通过内容管道被加载,并且被编译就象另外的内容资源。因此,编译器将检测编译的错误在你的HLSL代码中,就象它在你的C#代码中,即使你的effect文件被与其他内容一起保存。
注意:虽然你的HLSL代码将会被编译通过内容管道,不幸运的,你不会有机会获得工作时智能感知当工作在HLSL effect文件。
                       现在你已经有了HLSL语言的基础了,让我们看下样本effect文件,使用HLSL创建的。在这节,我们将仔细分析样本文件的不同地方:
 1 float4x4 World;
 2 float4x4 View;
 3 float4x4 Projection;
 4 struct VertexShaderInput
 5 {
 6     float4 Position : POSITION0;
 7 };
 8 struct VertexShaderOutput
 9 {
10     float4 Position : POSITION0;
11 };
12 VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
13 {
14      VertexShaderOutput output;
15      float4 worldPosition = mul(input.Position, World);
16      float4 viewPosition = mul(worldPosition, View);
17      output.Position = mul(viewPosition, Projection);
18      return output;
19 }
20 float4 PixelShaderFunction( ) : COLOR0
21 {
22      return float4(1001);
23 }
24 technique Technique1
25 {
26      pass Pass1
27      {
28           VertexShader = compile vs_1_1 VertexShaderFunction( );
29           PixelShader = compile ps_1_1 PixelShaderFunction( );
30      }
31 }
                        不要过于害怕此代码。它看起来有点不同,你已经习惯了在XNA中,但我们会探讨在这里的每一部分的代码,您可以找出它的做了什么事情。
                        首先,让我们看这前面三行:
1 float4x4 World;
2 float4x4 View;
3 float4x4 Projection;
                        这三行代表全局变量在这个文件中,所有三个变量是4*4的矩阵;它们对应world,相机的view,和相机的projection.到现在这些条件你应该很熟悉,因为它们与world,view,,和projection相同,这些在用3D绘制的你前面例子用到过。
看上去相似?
                        请回忆下,在过去哪里你使用过的world,view,和projection变量?
                        你使用它们去设置BasicEffect类的world,View,和Projection属性。下面的代码设置这些变量,它应该与在你编译的12章的源代码中你的BasciModel类中的Draw方法很类似。
1 foreach (BasicEffect be in mesh.Effects)
2 {
3     be.EnableDefaultLighting( );
4     be.Projection = camera.Projection;
5     be.View = camera.View;
6     be.World = GetWorld( ) *
7     Mesh.ParentBone.Transform;
8 }
                        你会看到同一个World,Projection,和View变量在这个HLSL文件中。它制造了个场景,当你考虑它时。记住当用3D绘制时,任何事情在XNA中使用HLSL。直到现在,你已经使用了BasicEffect类,它提供你一个方法去绘制,好,基础effects无需要知道HLSL底层的工程。在场景的后台,甚至BasicEffect类也使用HLSL。
                        现在,您正在寻找拓展和使用HLSL而不是BasicEffect类,它可能不会使你惊奇看到了同样的属性,你已经用在BasicEffect类显示在HLSL文件它自己中。
                        变量在这个全局空间中可以设置从你的XNA代码中。(稍后你看见更多如何去做,当你使用这个代码到一个实际的对象。)当使用一个HLSL文件在XNA中,首先要做的一件事是寻找什么变数需要被设置在XNA代码中。
                        就象当你用C或C++编程一样,在HLSL,变量和功能需要被定义在他们被使用之前。也就是说一个典型的流程为一个HLSL文件将有变量在上面,其次是struct,功能,最后着色器调用它们自己。
                        当读一个HLSL文件,它通常是用一个正常的顺序去执行。这样,让我们跳过下面的代码在样本文件中。这里是文件的一部分,使用technique关键字:
1 technique Technique1
2 {
3    pass Pass1
4    {
5            VertexShader = compile vs_1_1 VertexShaderFunction( );
6            PixelShader = compile ps_1_1 PixelShaderFunction( );
7    }
8 }
                        每一个HLSL effect文件将有一个或者多个技巧。在你的XNA代码中,你指定哪个技巧去运行当应用一个HLSL文件通过相关技巧名称,它在这种情况下是Technique1.
                        在技巧这部分的内部,注意到这里是一个小部分调用一个pass.每个技巧有一个或者更多的passes.当你绘制在XNA中使用一个自定义的HLSL effect文件,你要设置技巧正如前面所提到的,然后通过循环所有的passes去绘制它们中的每一个。这种特殊的技术只有一个pass.
                        每个pass可以包含一个顶点或者象素着色器。为了执行一个着色器,你的HLSL文件必须设置VertexShader或者PixelShader的值,相当于去编译顶点或者象素着色器的功能。
                         让我们来更深入的看这两行:
1 VertexShader = compile vs_1_1 VertexShaderFunction( );
2 PixelShader = compile ps_1_1 PixelShaderFunction( );
                         VertexShader和PixelShader对象是HLSL对象,它们的名字必须被正确的拼写,并且区分大小写。关键字compile告诉XNA去编译下面的这个方法,这个方法使用一个确定顶点或象素着色器固定的版本(在这种情况下,你正在编译使用顶点着色器的1.1版本,通过vs_1_1指定,象素着色器的1.1版本通过ps_1_1被指定)。这个功能(VertexShaderFunction()和PixelShaderFunction())在前面被定义在这个文件中。这里面首先被调用的是顶点着色器,这样下一个我们回看到顶点着色器的功能。
                         顶点着色器功能在文件中被列出:
1 VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
2 {
3      VertexShaderOutput output;
4      float4 worldPosition = mul(input.Position, World);
5      float4 viewPosition = mul(worldPosition, View);
6      output.Position = mul(viewPosition, Projection);
7      return output;
8 }
                          首先的事情,你可能注意到,作为一个输入类型的功能接受一个VertexShaderInput类的结构(在这个文件中在前面定义过)。返回的类型同样是一个struct,并且是VertexShaderOutput类的(同样在前面定义过)。让我们另外看下两个结构:
1 struct VertexShaderInput
2 {
3     float4 Position : POSITION0;
4 };
5 struct VertexShaderOutput
6 {
7     float4 Position : POSITION0;
8 };
                          这些结构真的没有什么不普通的地方,除了一个事情,你可以在前面没有见过:一个HLSL句法。POSITION0代码出现在每个变量定义的后面,是一个HLSL句法。
                          HLSL代码使用句法去连接数据从XNA游戏中,它使用HLSL代码。当一个句法被指定到一个变量上,HLSL将自动的分配一个值到那个变量,基于给定的句法。从本质上讲一个方式 去连接变量在HLSL带着某些数据,其中的XNA游戏访问。
                          在这种情况下,顶点着色方法接受一个参数的类型VertexShaderInput ,其中包含一个变量的语义POSITION0 。指定POSITION0句法在Position变量上,导致了HLSL去自动设置变量代表一个位置值,这是通过XNA游戏被支持的。
                          为什么值会被自动的分配呢?记住,一个顶点着色器运行一次为每个可见的顶点,所以位置通过POSITION0句法被给予在一个顶点着色器中,是顶点的位置。
                          这里有几个别的可能的顶点着色器的输入句法,除了POSITION0之外,其他的被列在表13-1中。

                          您可能注意到顶点着色器返回一个struct,这个struct同样有一个POSITION0句法结合它。虽然语义是 同样的,但是意思在这种情况下是不同的,它被用来作为顶点着色器的输出而不是输入。不同的句法的意义变化依赖变量是否使用一个特殊的语意,(语意)它被用来给一个象素着色器或者一个顶点着色器,并且是否被那个着色器指定为输出或者输入。
                          例如,这个句法可以被用于顶点着色器的输出,这不同于被用于顶点着色器的输入。顶点着色器的输出句法被列在表单13-2中。

                          因此,当POSITION0语义适用于输入参数中的顶点着色器,HLSL自动分配顶点位置到那个参数中,但是相反,一个指定的句法在一个输出参数被用来标记一个变量作为包含确定的数据。
                          早些时候,你读的是任何的顶点着色器的最低工作,是去设置所有顶点的位置在场景中,通过指定一个输出参数用POSITION[n]句法来实现。从本质上讲,你的顶点输出需要去设置每个顶点的位置。但是你如何做这个呢,假定那里没有vertex.position变量或类似的设置?这就是输出语义用武之地。通过返回一个值用一个变量,它有一个POSITION的句法,你指定一个返回值,它表示一个位置。
                          这个很奇怪吗?不用担心。让我们看下实际的顶点着色器功能。为了你的方便,这个功能和structs,它的用途再次被列出:
 1 struct VertexShaderInput
 2 {
 3      float4 Position : POSITION0;
 4 };
 5 struct VertexShaderOutput
 6 {
 7      float4 Position : POSITION0;
 8 };
 9 VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
10 {
11      VertexShaderOutput output;
12      float4 worldPosition = mul(input.Position, World);
13      float4 viewPosition = mul(worldPosition, View);
14      output.Position = mul(viewPosition, Projection);
15      return output;
16 }
                        首先这个代码做的是创建一个VertexShaderOutput类型的变量,所以它可以返回在程序结束时的数据类型。注意,struct有一个参数称为Position的,它有一个POSITION0的句法。再一次,一个顶点着色器的最基本的责任是去设置每一个顶点的的位置。你可以通过返回一个值依靠一个POSITION0的句法设置通位置。所以,通过返回一个VertexShaderOutput类型的对象,你将会设置顶点的位置,假定你设置VertexShaderOutput结构的Position成员。
                        下一步,顶点着色功能创建一个变量它称为worldPosition,并且设置这个变量的值到mul函数的结果。什么是mul函数?这是被称为一个intrinsic function在HLSL中:它是一个函数,HLSL提供给开发者去操作数据。一个内函数表单将会被提供在本章的后面。这个特殊机能(mul)把两个矩阵相乘。
                        首先,顶点着色器的功能是把顶点(input.Position)和World矩阵相乘。你是怎么知道input.Position代表顶点的位置?好,再来一次,回到语句中。这输入变量是VertexShaderInput类型的,它是一个定义在struct前面的顶点着色器功能的说明。这个VertxShaderInput struct有一个数字:Position。struct的Position数字有一个POSITION0的句法。当POSIION0句法依赖一个使用的变量为顶点的输入,顶点的位置是自动的分配到这个变量中。
                        下一步,顶点着色器功能是乘通过相机的view得到的结果矩阵。然后乘以通过相机的projection得到的结果矩阵,并分配值到输出参数,参数被返回。所有的矩阵乘积设置顶点到正确地方的位置,都来自于相机的位置和方向。
注意:我知道,你们在想什么:"哇...等一下,你告诉我,如果你乘以一个顶点的位置通过对象的world矩阵,然后乘以通过相机的View矩阵得到的矩阵,最后乘以通过相机的projection矩阵得到的矩阵,它太神奇了设置顶点的位置到正确的位置?"好,也是。"神奇"对于它是一个很好的术语。你不需要了解这些是如何工作的,象这样一个讨论矩阵的乘积将比我们这本书更深入。但是,这些矩阵的互相乘是为了得到顶点的正确的位置。这只是表明如何迷人和多么强大矩阵乘法。
                        你可以同样迷惑代码如何设置每个顶点的位置在场景中,当只有一个位置变量从这个功能中被返回。记住,顶点着色器将会为每个顶点运行在场景中。例如,如果你绘制一儿歌三角形,然后运行这个HLSL文件去绘制那个三角形,你的顶点着色器将会运行一次为每个顶点(在一个三角形里的顶点,所以顶点着色器可以运行三次)。
                        参数传递到顶点着色器有一个POSITION0的句法,它将导致变量去自动的填满这个值,代表顶点的位置。所以,如果三角型的顶点你绘制的是(0,1,0),(1,0,0),和(-1,0,0),你的顶点着色器将运行三次:首先输入参数的值会是(0,1,0),其次输入的参数的值会是(1,0,0),最后的值会是(-1,0,0).
                        这看起来象一团混乱的代码,句法,着色器,等等。你的头部可以旋转。但是这行。这里关键点需要记住:
        1。全局变量,是没有语意或者初始值需要由XNA代码来设置。
        2。技巧的名称运行在你的HLSL文件中,需要从XNA代码中去设置。
        3。输入参数的语义将自动接收数据通过句法当HLSL effect被执行时来表示(例如,一个输入参数在一个顶点着色器有一个POSITION0的句法将自动被设置到一个值,代表顶点的位置)。
        4。一个输出变量用一个句法标记这个变量作为指定的数据类型为处理在effect中。这是如何你用HLSL"设置"数据。如果你需要"set"一个顶点的位置在一个顶点着色器中,你返回一个变量以一个POSITION0的句法。
        5。一个顶点着色器有不同的输入和输出句法。
        6。在最低限度,一个顶点着色器需要去指定一个POSITION[n]句法为了输出。
                         一旦顶点着色器完成了,象素着色器将会运行。让我们看看象素着色器被创建在effect文件中:
float4 PixelShaderFunction( ) : COLOR0
{
     
return float4(1001);
}
                         首先,请注意,没有输入参数。虽然顶点着色器输出将最终使它到象素着色器中输入,数据类型用用在每一种情况不一定必须是完全相同的。事实上,由于顶点着色器输出句法是不同于象素着色器输入句法,他们往往不能是同一类型。有时(在这种情况下),你根本不需要去指定任何的输入。表13-3显示一个有效像素着色器输入的语义表。

                         最终,注意这个奇怪的句法在这个功能本身:
1 float4 PixelShaderFunction( ) : COLOR0
                         这从另一个角度说明语义的返回类型。您还可以指定输入语义的参数列表,而不是使用结构。例如,下列方法将返回一个颜色和接受一个:
1 float myShader(float COLOR0) : COLOR0
                         语义有效输出像素着色起列于表13-4

                         如前所述,一个象素着色器的最低工作是去设置颜色为每个单独的象素。在这种情况下,这个象素着色器功能返回一个float以COLOR0句法去完成这个。这个功能包含只有一行代码,这行代码返回的是红颜色:
1 return float4(1001);
                          由于这个功能它自己有一个COLOR0句法,返回一个float4(它可以代表颜色)将"设置"颜色为这个象素。你也许很疑惑这个功能是如何设置每个象素的颜色在场景中。记住一个象素着色器是运行为每个象素在场景中。如果你绘制一个三角形,然后使用这个HLSL effect文件去绘制那个三角形,这个象素着色器可以运行为每个象素,它们是组成这一部分的三角形。根据三角形的大小,屏幕的大小,它可能是10次,100次,1000次,或者更多。每一次象素着色器返回一个值,它返回的值以一个COLOR0的句法,表明这个值被返回代表指定的象素的新的颜色。在这个例子中,返回值总是红色(RGBA1,0,0,1),所以每个象素将会被着成红色。你可以改变这颜色根据象素的位置或者基于一些纹理文件或任何其他您可能会想要做的,这将让你着色每个象素在不同的场景上。    
                          在你运行象素着色器之后,传递完成。由于这里是唯一传入在此代码中,HLSL文件现在被完成了,数据将会被发送到屏幕上。顶点着色器设置所有的顶点的位置在world,象素着色器着色所有的象素为红色,所以应用这个effect文件到一个场景中,可以导致任何东西在场景中的,都会变成红色。在下一节,我们应用这个文件到一些primitives去看看它的实际情况。
Applying an HLSL Effect in C#
                           在本节中,我们将使用资源代码为纹理的矩形项目来自第9章。你可以回忆下,这个项目作为一个创建很酷的矩形以一个数的图象纹理。运行这个项目将导致相似的树矩形,你可以回顾一下当你第一开始3D节的时候(看图13-2)
                           当前,矩形使用BasicEffect类被绘制。你准备改变,所以它使用一个effect创建从一个HLSL文件,这文件是你所生成的。首先要做的是创建一个子文件夹在Content节点在你的工程中通过右击Content节点在解决方案中,并且Add-New Folder.命名这个新的文件夹Effects.
                           然后,右键单击Content\Effects文件夹在解决方案中,选择Add-New Item....选择Effect File作为模板在右边,并命名这个文件为Red.fx如图13-3所显示的。


                           可能你的模板effect文件与前面所列出来的代码相同,但是同样为了安全起见,确保你的effect文件包含下面的代码:
 1 float4x4 World;
 2 float4x4 View;
 3 float4x4 Projection;
 4 struct VertexShaderInput
 5 {
 6     float4 Position : POSITION0;
 7 };
 8 struct VertexShaderOutput
 9 {
10     float4 Position : POSITION0;
11 };
12 VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
13 {
14      VertexShaderOutput output;
15      float4 worldPosition = mul(input.Position, World);
16      float4 viewPosition = mul(worldPosition, View);
17      output.Position = mul(viewPosition, Projection);
18      return output;
19 }
20 float4 PixelShaderFunction( ) : COLOR0
21 {
22      return float4(1001);
23 }
24 technique Technique1
25 {
26    pass Pass1
27    {
28           VertexShader = compile vs_1_1 VertexShaderFunction( );
29           PixelShader = compile ps_1_1 PixelShaderFunction( );
30    }
31 }
                          你可以 检查你的effect代码通过编译你的解决方案变的完整。如果你没有得到任何的编译错误,你知道你的代码至少语法是正确的。(这是一个好兆头)
                          为了使用你的effect在代码中,你需要去创建一个Effect类型的变量去保存effect在内存中。创建一个类别的Effect变量在你的Game1类中:
1 Effect effect;
                          接下来,你需要加载effect到你的Effect对象通过内容管道。添加下面代码到你的Game1类的LoadContent方法中:
1 effect = Content.Load<Effect>(@"effects\red");
                          最后,你需要删除下面的方法在你的Draw方法中,它使用BasicEffect 类去绘制:
 1 BasicEffect effect = new BasicEffect(GraphicsDevice, null);
 2 effect.World = worldRotation * worldTranslation * worldRotation;
 3 effect.View = camera.View;
 4 effect.Projection = camera.Projection;
 5 effect.Texture = texture;
 6 effect.TextureEnabled = true;
 7 effect.Begin( );
 8 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
 9 {
10     pass.Begin( );
11     GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, verts, 02);
12     pass.End( );
13 }
14 effect.End( );
注意:代码的第二行在这个表中的,可以不同在你的项目中。这是因为在第9章的结束,我鼓励你去试不同的worldRotations和worldTranslation的结合在effect.World变量的设置中。不要担心这个;只需替换上述代码部分用下面列出的。                       
                          替换那部分代码用下面的代码,它使用你的新effect文件而不是BasicEffect类:
 1 effect.CurrentTechnique = effect.Techniques["Technique1"];
 2 effect.Parameters["World"].SetValue(Matrix.Identity);
 3 effect.Parameters["View"].SetValue(camera.view);
 4 effect.Parameters["Projection"].SetValue(camera.projection);
 5 effect.Begin( );
 6 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
 7 {
 8     pass.Begin( );
 9     GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, verts, 02);
10     pass.End( );
11 }
12 effect.End( );
                          您会发现,在核心绘制foreach循环被使用,是相同的-你循环所有当前技巧的passes,绘制你的对象。代码中的主要区别在于,你不再使用BasicEffect类去绘制。
                          BasicEffect类有几个属性,你分配它,对象的world矩阵,相机的view矩阵,和相机的projection矩阵。当使用HLSL时,你仍然使用这个数据,但你而不是将它分配给上述全局变量,在你的HLSL effect文件通过使用effect.Paramters[].SetValue方法。
传递数据从XNA到HLSL
                          这里有两个不同的方法去得到数据从XNA到你的HLSL effect文件。一个方法是通过使用句法。正如这章前面所讨论过的,如果你创建一个变量,它被用来作为输入变量到一个顶点着色器或者象素着色器,并且分配一个句法到这个变量中,适当的数据将自动被设置到这个变量中当功能函数被运行时。
                          另一个方法去得到数据从XNA到HLSL是手动设置参数。正如前面你所看到的,只有固定的数据类型能被传递通过句法(就象位置,颜色,纹理坐标,等等)。虽然,你可以传递任何你想要的到一个HLSL文件,通过手动参数。为了实现这个,你定义个全局变量在你的HLSL文件,并且设置参数使用effect.Paramters[].SetValue方法如前面代码所示的。
                          如果你已经玩你的第9的代码或者调整它通过这本书的练习,你的代码可能比我本章使用的代码有很大的不同。为了阐述,这里是你的Game1类应该看起来和这相同:
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using Microsoft.Xna.Framework;
 5 using Microsoft.Xna.Framework.Audio;
 6 using Microsoft.Xna.Framework.Content;
 7 using Microsoft.Xna.Framework.GamerServices;
 8 using Microsoft.Xna.Framework.Graphics;
 9 using Microsoft.Xna.Framework.Input;
10 using Microsoft.Xna.Framework.Media;
11 using Microsoft.Xna.Framework.Net;
12 using Microsoft.Xna.Framework.Storage;
13 namespace _3D_Madness
14 {
15        public class Game1 : Microsoft.Xna.Framework.Game
16        {
17               GraphicsDeviceManager graphics;
18               SpriteBatch spriteBatch;
19               Camera camera;
20               //Vertex array
21               VertexPositionTexture[] verts;
22               //World matrices
23               Matrix worldTranslation = Matrix.Identity;
24               Matrix worldRotation = Matrix.Identity;
25               //Texture
26               Texture2D texture;
27               //HLSL
28               Effect effect;
29               public Game1()
30               {
31                  graphics = new GraphicsDeviceManager(this);
32                  Content.RootDirectory = "Content";
33                }
34                protected override void Initialize()
35                {
36                   // Initialize camera
37                   camera = new Camera(thisnew Vector3(005),Vector3.Zero, Vector3.Up);
38                   Components.Add(camera);
39                   base.Initialize();
40                 }
41                 protected override void LoadContent()
42                 {
43                      // Create a new SpriteBatch, which can be used to draw textures.
44                      spriteBatch = new SpriteBatch(GraphicsDevice);
45                      //Load texture
46                      texture = Content.Load<Texture2D>(@"Texturesrees");
47                       // Initialize vertices
48                       verts = new VertexPositionTexture[4];
49                       verts[0= new VertexPositionTexture(new Vector3(-110), new Vector2(00));
50                       verts[1= new VertexPositionTexture(new Vector3(110), new Vector2(10));
51                       verts[2= new VertexPositionTexture(new Vector3(-1-10), new Vector2(01));
52                       verts[3= new VertexPositionTexture(new Vector3(1-10), new Vector2(11));
53                       //Load effect
54                       effect = Content.Load<Effect>(@"effectsed");
55                   }
56                    protected override void UnloadContent()
57                    {
58                       // TODO: Unload any non ContentManager content here
59                    }
60                    protected override void Update(GameTime gameTime)
61                    {
62                       // Allows the game to exit
63                       if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==ButtonState.Pressed)
64                       this.Exit();
65                       // Translation
66                       KeyboardState keyboardState = Keyboard.GetState();
67                       if (keyboardState.IsKeyDown(Keys.Left))
68                            worldTranslation *= Matrix.CreateTranslation(-.01f, 00);
69                       if (keyboardState.IsKeyDown(Keys.Right))
70                             worldTranslation *= Matrix.CreateTranslation(.01f, 00);
71                       // Rotation
72                       // Rotation
73                       worldRotation *= Matrix.CreateFromYawPitchRoll(MathHelper.PiOver4 / 60,0,0);
74                       base.Update(gameTime);
75                      }
76                      protected override void Draw(GameTime gameTime)
77                      {
78                          GraphicsDevice.Clear(Color.CornflowerBlue);
79                          GraphicsDevice.VertexDeclaration =new VertexDeclaration(GraphicsDevice,VertexPositionTexture.VertexElements);
80                          //Set effect parameters
81                          effect.CurrentTechnique = effect.Techniques["Technique1"];
82                          effect.Parameters["World"].SetValue(Matrix.Identity);
83                          effect.Parameters["View"].SetValue(camera.view);
84                          effect.Parameters["Projection"].SetValue(camera.projection);
85                          //Start drawing with the effect
86                          effect.Begin();
87                          foreach (EffectPass pass in effect.CurrentTechnique.Passes)
88                          {
89                               pass.Begin();
90                               GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, verts, 02);
91                               pass.End();
92                           }
93                           effect.End();
94                           base.Draw(gameTime);
95                    }
96         }
97 }
                        编译并运行这个项目,你可以看见相同的矩形和前面一样,但是现在是红色的了(如图13-4)

                        现在你也许会想:“嗯...矩形看起来比我们好,当我使用BasicEffect。 HLSL的是真棒!"我不得不承认矩形比以前好看了,但是这只是HLSL可以做的一小部分。让我们了解下更多更详细的HLSL代码去看是否能使东西变的更好。
Applying HLSL Using Textures
                        一个矩形着成红色只需要最简单的HLSL着色器,它是某种你很少能找到的最新的视频游戏。通常,你会应用一个纹理到一个对象,然后调整纹理的显示方式通过应用发光或者烟雾或者别的效果。
                        在这节,你可以应用一个自定义的HLSL文件到你的矩形同时应用颜色从树纹理中。
                        打开你的Red.fx文件再一次,用这里显示的代码替换文件中的代码:
 1 float4x4 xWorldViewProjection;
 2 Texture xColoredTexture;
 3 sampler ColoredTextureSampler = sampler_state{ texture = <xColoredTexture> ;magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR;AddressU = mirror; AddressV = mirror;};
 4 struct VertexIn
 5 {
 6     float4 position : POSITION;
 7     float2 textureCoordinates : TEXCOORD0;
 8 };
 9 struct VertexOut
10 {
11     float4 Position : POSITION;
12     float2 textureCoordinates : TEXCOORD0;
13 };
14 VertexOut VertexShader(VertexIn input)
15 {
16     VertexOut Output = (VertexOut)0;
17     Output.Position =mul(input.position, xWorldViewProjection);
18     Output.textureCoordinates = input.textureCoordinates;
19     return Output;
20 }
21 float4 PixelShader(VertexOut input) : COLOR0
22 {
23     float4 output = tex2D(ColoredTextureSampler,input.textureCoordinates);
24     return output;
25 }
26 technique Textured
27 {
28    pass Pass0
29    {
30      VertexShader = compile vs_1_1 VertexShader( );
31      PixelShader = compile ps_1_1 PixelShader( );
32    }
33 }
                        虽然,你对HLSL还是初步的了解,这个文件不十分复杂,它的大部分对你来说是有意义的。首先注意并不是使用三个变量分别去表示world,view和projection矩阵,你只有一个变量,叫做xWorldViewProjection.在你的XNA代码中,你需要去一起乘world,view和projection矩阵去设置顶点位置,没有关系是你在XNA代码还是用HLSL文件去做这个。做这个乘法的优势在XNA代码中比在HLSL文件要好,如果你传入它到HLSL代码中,这个乘法式每个场景做一次,尽管做这个乘法式在HLSL中将导致它为每个顶点只做一次(译者:这里的句很长很奇怪,无法理解请对照原文)。
                        另外,注意新的变量xColoredTexture。这个变量是一个纹理对象,你需要给它分配您的纹理对象的值从您的XNA代码中。
                        还有一个ColoredTextureSampler对象,允许你抽取数据资料从一个纹理到纹理的特定部分的确定的颜色。当象素着色器运行,它将映射在对象上每一个象素到纹理上的相应位置,通过使用取样器,并且将返回象素应该有的颜色。
                        请注意,像素着色器使用一个tex2D方法去完成这个。tex2D是一个函数,它被内置在HLSL中,就象mul,你在前面使用过它去乘以两个矩阵。tex2D查询一个颜色在特定纹理上的点,使用一个取样器。从本质上讲,你将会运行你的象素着色器为每个象素在屏幕上,对吗?每一次象素着色器运行,你准备去接收一个纹理坐标为那个象素(它被传入到象素着色器中,通过VertexOut struct的textureCoorinates元素)。tex2D函数将接收这些纹理坐标,查找相应象素在这纹理在每个坐标,返回那个象素的颜色。使用这个方法,整个三角形将会被着色,看上去就象像你的纹理传递给HLSL effect的文件.
                        除了mul和tex2D之外,这里有一个其他的一些内建的函数(所谓的内部函数)在HLSL中。这些函数被列出在微软的MSDN library为了帮助DirectX和XNA的开发。为了你的方便,他们显示在表13-5中。



                        所以从本质上来说,新的effect文件,你刚刚创建的将会去设置顶点位置,然后着色对象通过拖象素的坐标离开相应的纹理,使用取样器。换句话说,它将映射纹理到绘制的矩形到屏幕上。
                        接下来,您需要更改您的绘图方法的代码使用新的自定义effect,而不是前面所用的红色自定义effect.你还记得你所需要去设置的数据吗,为了去使用一个HLSL effec在XNA中?
                        你需要设置effect的名字去运行,同样你需要设置所有的全局变量在你的effect文件中。当前,你使你的Game1类的Draw方法的下面代码去实现它,但是你要设置数据为red.fx文件:
1 effect.CurrentTechnique = effect.Techniques["Technique1"];
2 effect.Parameters["World"].SetValue(Matrix.Identity);
3 effect.Parameters["View"].SetValue(camera.View);
4 effect.Parameters["Projection"].SetValue(camera.Projection);
                        首先,你需要去改变为了使用新的effect,它是技巧的名称。这个技巧在red.fx文件被称做Technique1,同时新的effect使用一个技巧称为Textured.
                        改变前面的代码的头一行,它是在你的Game1类的Draw方法中:
1 effect.CurrentTechnique = effect.Techniques["Textured"];
                        接下来,您可能已经注意到,全局变量是不同的。在此之前,您曾在你的HLSL定义3个全局变量:World,View和Projection.
                        你的新effect只有两个全局变量:xWorldViewProjection(它应该被设置到world matrix multiplied通过view matri multiplied 通过projection矩阵)和xColoredTexture(它应该被设置到Texture2D对象你想应用到的矩形)。
                        从你的Game1类中的Draw方法删除下面三行代码:
1 effect.Parameters["World"].SetValue(Matrix.Identity);
2 effect.Parameters["View"].SetValue(camera.View);
3 effect.Parameters["Projection"].SetValue(camera.Projection);
                       用下面的代码去取代它们:
1 effect.Parameters["xWorldViewProjection"].SetValue(Matrix.Identity * camera.view * camera.projection);
2 effect.Parameters["xColoredTexture"].SetValue(texture);
                       这些新代码应该看起来很熟悉-它从本质上来讲与你前面使三角形变红的代码相同。这里关键的不同,在前面所提到,你设置world,view,和projection变量只有一次(译者:一起设置),而不是分别的。当它们互相相乘,它们给你同样的结果,所以就象前面使用的方法一样好。

                       现在编译执行这个游戏,你会看见会原来一样的纹理矩形,但是这次它的绘制使用了一个自定义HLSL着色器(正如图13-5所示)
                       有一件事你可能已经注意到的是,您的矩形用来旋转,但现在 它停滞不前。这是为什么?答案就在于world矩阵。还记得一个对象的world矩阵代表位置,旋转,缩放,等等。给一个特定对象。因此,如果对象不能适当的旋转,不能正确的缩放,或者在错误的位置,那很可能对象的world矩阵有一个问题。
                       在这种特殊情况下,你应该设置xWorldViewProjection HLSL变量到world matrix mulitplied通过viewmultiplied 通过projection矩阵(译者:这里不好翻译,大概是说矩阵的乘积)。这里列出的这段代码你可以拿去用:
1 effect.Parameters["xWorldViewProjection"].SetValue(Matrix.Identity * camera.view * camera.projection);
                       注意这个值,你为乘法式子的world部分使用的:Matrix.Identity.回忆一下单位矩阵是干什么用的?当你乘以矩阵A通过单位矩阵,这个积矩阵是矩阵A。所以在这种情况下,你乘以单位矩阵通过view和projection.这是完全一样的把view和projection一起乘了-换句话说,你不用特别为对象的world矩阵指定特别地方(不用移动,不用旋转,什么都不用)。这就是为什么对象不旋转。

                       所以,你如何解决?当你创建这个代码在第9章,你使用两个类别矩阵变量去旋转矩形:worldTranslation和worldRotation.为了使对象旋转,创建一个world矩阵从worldTranslation和worldRotation矩阵,并使用world矩阵代替Matrix.Identity当设置xWorldViewProjection HLSL变量时。
                       也就是说,用你的Game1类的Draw方法来替换下面的行:
1 effect.Parameters["xWorldViewProjection"].SetValue(Matrix.Identity * camera.view * camera.projection);
                       用这个:
1 Matrix world = worldRotation * worldTranslation;effect.Parameters["xWorldViewProjection"].SetValue(world * camera.view * camera.projection);
                       现在旋转和创建代码在第9章你写的,应用到这个矩形上。运行这个游戏在这点上,你可以看见一个自旋的矩形,如图13-6所示。

                       这个矩形会移动当你压下左右方向键,就象你在第9章最初的矩形那样。再一次,感觉很自由去不同的旋转,应用它们用不同的命令,就象你在第9章做的。而不是设置BasicEffect类的world属性,你现在创建一个临时的world矩阵变量,并分配值到这个变量中在HLSL effect文件中,但最终的结果是完全一样的。
                       嗯...可能HLSL并非如此惊人。你只是把一个需要大量的工作结束了,具有完全相同的结果!不过,让我们来看看一些不同的事情,你可以使用HLSL,做这种纹理矩形。
HLSL Effects: Creating a Negative
                       现在你有一个effect文件,使用一个纹理,这里有any number of things你可以做为你的HLSL文件去得到一些真正有趣和酷的效果。例如,改变在象素着色器中的代码如下,会得到一个负图象被绘制:
1 float4 PixelShader(VertexOut input) : COLOR0
2 {
3     float4 output = 1-tex2D(ColoredTextureSampler, input.textureCoordinates);
4     return output;
5 }
                       效果如13-7所示。

HLSL Effects: Blur
                       另一个非常简单的效果是模糊图象。为了实现这个,你摄取每个象素的颜色在纹理中,并且添加它的颜色从邻近的像素到目标的像素。为了尝试下,用下面的代码替换在你游戏中的象素着色器。
1 float4 PixelShader(VertexOut input) : COLOR0
2 {
3     float4 Color;
4     Color = tex2D(ColoredTextureSampler, input.textureCoordinates.xy);
5     Color += tex2D(ColoredTextureSampler, input.textureCoordinates.xy + (0.01));
6     Color += tex2D(ColoredTextureSampler, input.textureCoordinates.xy - (0.01));
7     Color = Color / 3;
8     return Color;
9 }
                        这个效果要求象素着色器1.4或者更高的,所以改变这行在你的技巧定义在象素着色器中的:
1 PixelShader = compile ps_1_4 PixelShader( );
注意:难道你总是希望去使用最新最强大的象素着色器?为什么甚至不屑使用像素着色1.1? 事实上,你应该经常使用低版本的象素着色器,依靠这个你将要去执行的功能。什么原因?图形卡只支持确定的象素着色器和顶点着色器的版本。如果你不需要使用1.4版本,但是你告诉HLSL无论如何去使用该版本,一些用户可能无法运行你的游戏,因为他们的图形卡可能只支持像素着色器1.1(即使他们应该能够运行您的游戏,由于你不能实际使用任何的象素着色器的1.4的功能)。
                       其结果将是一个模糊的图像,如图13-8所示。
HLSL Effects: Grayscale
                       绘制一个灰度图像是另一种非常简单的效果,它添加起来没有什么困难。通过应用一个标准的灰度方程,你可以转换每个象素的颜色到一个灰度的阴影,使用dot的功能。用下面的代码代替你的象素着色器:
1 float4 PixelShader(VertexOut input) : COLOR0
2 {
3      float4 color;
4      color = tex2D( ColoredTextureSampler, input.textureCoordinates.xy);
5      return dot(color, float3(0.30.590.11));
6 }
 
                      他做了些什么?首先,阴影的功能重新得到象素的颜色在象素的坐标上。然后dot功能重新得到一个dot product使用两个向量:象素的颜色来自纹理,并使用一个向量生成(0.3,0.59,0.11).为什么是这些数字呢?好,你刚才使用了0.33给它们中的每一个,得到一个平均颜色,它将是非常好看的灰度样式。虽然,很久以前,某人有人比我聪明得多想通了,这些数字更接近于灰度为肉眼看到的。我讨厌在这本书象这样去引用Wikipedia,但它是一个伟大的资源,以阅读这些
numbers(见http://en.wikipedia.org/wiki/Grayscale)。
                        由此产生的图像将被绘制成灰色,如图13-9。
                        正如你所看到的,这里有无限的可能性当你处理HLSL效果时。这些只不过是很少的事情你可以做的。就象在本章一开头说提到的,本书不会是权威资源在HLSL方面。但是, 如果你对这些感兴趣的话,在网上有很多资源和其它的书,它们可以让你对HLSL和effects有更深入的了解。

源代码:http://shiba.hpe.cn/jiaoyanzu/WULI/soft/xna.aspx?classId=4
(完)

posted on 2009-08-23 20:39  一盘散沙  阅读(474)  评论(0编辑  收藏  举报

导航