[翻译]XNA 3.0 Game Programming Recipes之fortyone

PS:自己翻译的,转载请著明出处格
                                                     6-5 添加HLSL顶点阴影
问题
                    BasicEffect将绘制你的场景刚好使用你定义的光照。尽管,如果你想定义你自己的一些奇异的效果,第一件事情,你的效果必须实施正确的照明。
                    在这一节,你了解如何写一个基本的HLSL效果,它执行per-vertex阴影。
解决方案
                    传递每个顶点的3D位置和法线到你的效果在图形卡里。这个顶点渲染器在你的图形卡需要为每个顶点做两件事。
                    第一件,总是当绘制一个3D世界时,它应该转换每个顶点的3D位置到相应的2D屏幕坐标通过使用世界,视景,投影矩阵。
                    第二件,具体到顶点照明,为每个顶点应该计算的大量的光照,这个顶点通过采取在光的方向和这顶点的法线之间的点乘积来接收的。
它是如何工作的
提示:这HLSL代码应该放在一个单独的文件里,用.fx结束。如果你不敢确定将代码放在这个文件的哪里,在这节稍后涉及"代码"这节会介绍,它例出整个.fx文件的内容在这节创建的。
                    首先你需要定义你的顶点在你的XNA工程中。很显然,你需要保存3D位置在你的每个顶点。允许你去计算正确的光照在你的顶点着色器,你同样需要提供每个顶点的法线。参看6-1节去了解为什么。
                    你可以使用同样的XNA代码在6-1节,它创建六个顶点包含在一个3D位置和一个法线(它们同样包含一个纹理坐标,它们不能在这里使用)。
                    创建一个新的.fx文件在你的XNA工程,添加下面的代码。如果包含HLSL变量,你能够改变从你的XNA程序。
1 float4*4 xWorld;
2 float4*4 xView;
3 float4*4 xProjection;
4 float xAmbient;
5 float3 xLightDirection;
                    象往常一样当转换一个3D坐标到一个2D场景中,你需要一个视景矩阵和一个投影矩阵.因为你同样可能想要移动3D对象在你的场景,你同样也需要一个世界矩阵。由于这节处理光照,你可能想去定义光照的方向。周围环境光值允许你去设置最小的光照;这种方法,即使一个对象不没有通过一个光源直接被点亮,它仍然微弱可见。
                    在分顶点着色器和象素着色器时,你应该首先定义它们的输出结构。首先,顶点着色器的输出(因此,象素着色器的输入)必须总是包含2D每个顶点场景坐标。其次,你的顶点着色器将同样计算每个顶点之间的大量光照。
                    在顶点和象素着色器之间,这些值将会以内插值替换,这样每个象素得到它的内插值替换值。
                    象素着色器应该只计算每个象素的最终的颜色。
1 struct VSVertexToPixel
2 {
3       float4 Position:POSITION;
4       float LightingFactor:TEXCOOR0;
5 };
6 struct VSPixelToFrame
7 {
8       float4 Color:COLOR0;
9 };
顶点着色器
                    你的顶点着色器将联合世界,视景和投影矩阵成一个矩阵,你使用它去转换每个3D顶点成2D场景坐标。
                    鉴于球形光照方向和法线方向在一个顶点内,你想要你的顶点着色器去计算大量的阴影,相应的图6-7。在光照和法线之间的角度越小,那么光照就越大。这个角度越大,这个光照就越少。
                    你可以取得这个值通过采取点乘积在两个方向之间。一个点乘积返回一个单一的值在0和1之间(如果两个向量的长度都为1).

                    不过,你应该否定它们之间其中的一个方向在采取点乘积之前,因为另外的这些方向是相反的。例如,在图6-7右边的图,你可以看见法线和光照方向有一个相反的方向,它将导致一个负的点乘积结果。
 1 VSVertexToPixel VSVertexShader(float4 inPos:POSITION0,float3 inNormal:NORMAL0)
 2 {
 3       VSVertexToPixel Output=(VSVertexToPixel)0;
 4       float4*4 preViewProjection=mul(xView,xProjection);
 5       float4*4 preWorldViewProjection=mul(xWorld,preViewProjection);
 6       Output.Position=mul(inPos,preWorldViewProjection);
 7       float3 normal=normalize(inNormal);
 8       Output.LightFactor=dot(rotNormal,-xLightDirection);
 9       return Output;
10 
                    点乘积的结果是一个单一的值,基于两个向量和两个向量长度之间的角度。在大多数情况下,你想你的光照仅仅基于在你的法线和光照方向之间的角度。意思是你需要确定所有你的法线(和光照方向)在你的整个3D世界有相同的长度;另一方面,顶点有更长的法线将会更亮。
注意:这个单词normalizing不会有任何事情用法线来做;它只是意味着使一个向量的长度正好是1,参看6-1节的注意。
当使用世界矩阵时确保正确的光照
                    前面的代码将正好使用,如果你设置你对象的世界矩阵到Identity矩阵,意思是对象应该被绘制围绕(0,0,0)3D原点。
                    在大多数情况下,虽然,你想设置另外一个世界矩阵,允许你去移动,旋转,和缩放你的对象在3D世界。
                    正如图6-1所示,如果你旋转你的对象,你想要你的法线被旋转和它们一起。这也就是说所有法线都应该同样被转换通过世界矩阵的内部旋转。
                    任何缩放操作包含在世界矩阵中,没有更多的影响光照的计算。你规格化你的法线在你的顶点着色器任何方法,确保结果向量的长度正好为1(统一长度)。
                    然而,如果你的世界矩阵包含一个转换,你已经在麻烦中了。这是因为一个法线是一个最大的向量长度是1。如一个方法,如果你转换如一个法线有一个矩阵包含一个转换大于两个单位,然后所有的法线指向这个方向。
                    如图6-8这样所示,这里是一个被绘制的对象使用一个世界矩阵包含一个少数单位的转化朝右边。作为一个结果,顶点的位置会被移动到右边。法线,尽管,当随着世界矩阵的转化,现在将所有的指向右边,同时它们应该保持不变!这样在您用世界矩阵转换你的法线之前,你需要去掉部分转化来自世界矩阵的。

                    一个矩阵是一个表包含4*4个数字。你需要转换法线只在旋转包含在世界矩阵里,而不是内部转换世界矩阵。你可以做这个通过剔除矩阵的部分旋转,它被储存在左上的3*3矩阵数字中。只需要通过4*4的世界矩阵到一个3*3的矩阵,你只需要提取转动信息,它正好是你所需要的!使用这个矩阵去旋转你的法线,如下面所示代码:
1 float3 normal=normalize(inNormal);
2 float3*3 rotMatrix=(float3*3)xWorld;
3 float3 rotNormal=mul(normal,rotMatrix);
4 Output.LightFactor=dot(rotNormal,-xLightDirection);
象素着色器
                    首先,一个三角形的顶点被处理通过顶点着色器,它们的光照值被计算。然后,每一个象素在三角形中,它需要被绘制,这个光照值是以内插值替换的在三个顶点之间。这个内插值替换光照值达到象素着色器。
                    在这些例子里,你会给你的对象一个蓝色作为基础颜色。去添加阴影到你的三角中,乘以基本颜色通过总和LightFactor(被计算在前面的顶点着色器中)和周围环境光(通过你的XNA程序设置通过xAmbient变量)。这个周围环境的因子确保没有对象会被完全的变黑,同时LightFactor导致光照到相应的光的方向:
1 VSPixelToFrame VSPixelShader(VSVertexToPixel PSIn):COLOR0
2 {
3      VSPixelToFrame Output=(VSPixelToFrame)0;
4      float4 baseColor=float4(0,0,1,1);
5      Output.Color=baseColor*(PSIn.LightFactor+xAmbient);
6      return Output;
7 }
技巧解说
                     最后,定义你的技巧和它的顶点,象素着色器它应该使用:
1 technique VertexShading
2 {
3     pass Pass0
4     {
5         VertexShader=compile vs_2_0 VSVertexShader();
6         PixelShader=compile ps_2_0 VSPixelShader();
7     }
8 }
XNA代码
                     在你的XNA工程中导入HLSL文件和保存它在一个Effect变量中,就象你在3-1节做的纹理那样。在我这个例子里,我称它为我的HLSL文件vertexshading.fx:
1 effect=content.Load<Effect>("vertexshading"); 
                                当涉及到绘制你的对象,你首先需要设置你的效果参数,并且绘制你的对象,十分一致的使用BasicEffect:
 1 effect.CurrentTechnique=effect.Technique["VertexShadin"];
 2 effect.Parameters["xWorld"].SetValue(Matrix.Identity);
 3 effect.Parameters["xView"].SetValue(fpsCam.ViewMatrix);
 4 effect.Parameters["xProjection"].SetValue(fpsCam.ProjectionMatrix);
 5 effect.Parameters["xLightDirection"].SetValue(new Vector3(1,0,0));
 6 effect.Begin();
 7 foreach(EffectPass pass in effect.CurrentTechnique.Passes)
 8 {
 9      pass.Begin();
10      device.VertexDeclaration=myVertexDeclaration;
11      device.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList,vertices,0,2);
12      pass.End();
13 }
14 effect.End();
原文地址:https://www.cnblogs.com/315358525/p/1544313.html