[翻译]XNA 3.0 Game Programming Recipes之forty-six

PS:自己翻译的,转载请著明出处格
                                                           6-10 添加多个灯到你的场景中使用缓慢的阴影
问题
                         你想要照亮你的3D场景同时从多个灯。
                         一个方法能绘制你的3D世界为每一个灯,并且混合每一个灯的影响。虽然,要求你的场景完全绘制你要添加的每一个灯。这个方法不能扩展,因为你的祯速率将除以灯的数量在你的场景中。
解决方案
                         在这节,你将学会完全不同的方法去绘制你的场景。绘制你的3D场景到2D纹理中只有一次。接下来,计算所有你的灯的光照为这个纹理的每个顶点。意思你准备做per-pixel处理在你的2D纹理中,它提供不得不绘制你的3D场景只有一次的好处。
                         但是做光照计算需要每个象素的原始3D位置,对吗?正确。继续阅读看如何实现。整个处理在三步中会被做,正如图6-12所显示。
                         在第一步,你绘制你的整个3D场景成纹理,而不是一个纹理,而是到三个纹理中一次。这里是三个纹理你想要的:
                         1。你绘制每个象素的基础颜色到第一个纹理中(图6-12的左上)。
                         2。在每个象素中转换法线到一个颜色和保存这个颜色在第二个纹理中(图6-12中上)。
                         3。储存每个象素的深度(摄象机和这象素之间的距离)在第三个纹理中(图6-12右上)。
                         作为说明,这整个操作发生只有一次,通过使用一个单一的效果。做为一个结果,这个操作获得和绘制你的场景普通方式不带光照计算(或者更少,由于效果非常简单)的相同的效果。
                         现在花点时间来理解接下来的几行。你场景的每个象素,已经存储深度在一个纹理中。每个象素,你同样知道它的2D场景坐标,因为它是和它的纹理坐标相同。也就是说一些方法或其他的方法,为场景中的每个象素,你应该能够重新创建原始3D位置和法线在一个象素中,你可以计算lighting contribution 到这个象素为任何一个灯!
                         所以,这就是你将要做的。在你已经产生你的三个纹理之后,在第二步期间你启用一个新的,干净的绘制目标。这新的目标每一个象素,重新创建它的原始3D位置和找回它的3D法线。这允许你计算第一个灯的lighting contribution在每一个象素中。你结束一个阴影地图包含你第一个灯的lighting contribution。
                         重复这个为你的每一个灯,添加它们的lighting contribution一起到阴影地图上。最后,阴影地图包含lighting contribution为所有的灯。这个处理为六个灯在图6-12的第二步。
                         在第三步也就是最后一步,结合颜色地图(第一步创建的)和阴影地图(第二步创建的)。图6-12这是第三步显示。
The Benefit of Deferred Rendering
                         如果你简单绘制你的3D世界为每一个灯和组合结果,你可能不得不转换你的3D世界到场景空间一次为每个灯在你的场景中。
                         这样一个操作把一个沉重负载两个在你的顶点着色器和你的象素着色器上。你的顶点着色器将不得不转换每个顶点的3D位置成一个2D场景坐标。因此,你的象素着色器将不得不计算许多象素。列如,如果一个对象A在背景中得首先被绘制,你的图形卡将使用象素着色器计算象素颜色。如果一个对象B稍后被绘制,这图形卡将再次需要去计算这些象素的正确的颜色。所以,图形卡已经计算一些象素不止一次。
                          总之,每一个灯,你的象素着色器需要去做大量工作,你的象素着色器需要处理更多的象素。
                          使用deferred rendering,执行这个操作只有一次,在第一步期间。在它之后,要做per-pixel处理在纹理中为每一个灯。每一步都象这样,处理每个象素正好一次。结合颜色和阴影地图的最后一步包括了另外的per-pixel处理步骤。
                          总之,你的3D场景需要被转换到场景空间中只有一次。每一个光照,你的图形卡需要处理场景中的每一个象素正好一次。你顶点着色器不得不做的少了很多,当使用多个灯时,你的象素着色器将同样不得不处理少许象素当使用deferred rendering。
它是如何工作的
                          deferred rendering发生在这三步中,这里可以找到说明。
准备
                          三步的每一个,我已经创建一个独立HLSL文件。创建这三个文件(Deferred1Scene.fx,Deferred2Lights.fx和Deferred3Final.fx),和添加它们的变量到你的XNA代码中:
1 Effect effect1Scene;
2 Effect effect2Lights;
3 Effect effect3Final;
                          不要忘记约束它们到这文件里在你的LoadContent方法中:
1 effect1Scene=Content.Load<Effect>("Deferred1Scence");
2 effect2Lights=Content.Load<Effect>("Deferred2Lights");
3 effect3Final=Content.Load<Effect>("Deferred3Final");
                          确保你加载任何几何要求去绘制你的场景在LoadContent方法中。在这个例子中,绘制一个简单房间从一个顶点数组中,它被初始化在InitSceneVertices方法中:
1 InitSceneVertices();
2 InitFullscreenVertices();
                          最后一行调用一个方法,它初始化第二个顶点数组,定义两个大三角形去覆盖整个场景。它们被用来在第二和第三步,你需要绘制全屏幕纹理使用你自己的象素着色器,允许你去处理全屏幕纹理在一个per-pixel基础上。这InitFullScreenVertices方法被执行如2-12节。
                          接下来,保持你的代码的干净,定义一个RenderScene方法,它接收一个效果和绘制整个屏幕使用这个效果。这个简单例子绘制只在三个纹理墙和一个地板从一个顶点数组中。如果你想有模型在你的场景中,确保你同样绘制它们用这个效果:
 1 private void RenderScene(Effect effect)
 2 {
 3      //Render room
 4      effect.Parameters["xWorld"].SetValue(Matrix.Identity);
 5      effect.Parameters["xTexture"].SetValue(wallTexture);
 6      effect.Begin();
 7      foreach(EffectPass pass in effect.CurrentTechnique.Passes)
 8      {
 9           pass.Begin();
10           device.VertexDeclaration=wallVertexDeclaration;
11           device.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip,wallVertices,0,14);
12           pass.End();
13      }
14      effect.End();
15 }
步骤1:绘制你的3D场景到三个纹理中
                           在这第一个步骤中,绘制你的场景到三个纹理中。这些纹理应该包含基础的颜色,3D法线,和场景中每个象素的深度。这深度是相机和对象到它所属的象素之间的距离。
                           这个被实现使用一个象素着色器。而不是绘制到一个单一绘制目标,你的象素着色器将绘制三个对象一次。
XNA代码
                           在你的XNA代码中定义这三个目标:
1 RenderTarget2D colorTarget;
2 RenderTarget2D normalTarget;
3 RenderTarget2D depthTarget;
                            初始化它们在LoadContent方法:
1 PresentationParameters pp=device.PresentationParameters;
2 int width=pp.BackBufferWidth;
3 int height=pp.BackBufferHeight;
4 colorTarget=new RenderTarget2D(device,width,height,1,SurfaceFormat.Color);
5 normalTarget=new RenderTarget2D(device,width,height,1,SurfaceFormat.Color);
6 depthTarget=new RenderTarget2D(device,width,height,1,SurfaceFormat.Single);
                            由于法线有三个组成部分,你保存它作为一个颜色。这深度是一个单一浮点型的值。当你的象素着色器同时写多个绘制对象,它们的格式必须有相同的大小。每一个颜色的组成部分使用8bit,所以一个颜色使用32bit.一个浮点型同样使用32bit,所以这是可行的。
                            随着绘制对象的设置,你已经准备去绘制了。这方法执行完整的第一步,应该被调用当第一行从你的Draw方法中:
 1 private void RenderSceneTo3RenderTargets()
 2 {
 3     //bind render targets to outputs of pixel shaders
 4     device.SetRenderTarget(0,colorTarget);
 5     device.SetRenderTarget(1,normalTarget);
 6     device.SetRenderTarget(2,depthTarget);
 7     //clear all render targets
 8     device.Clear(ClearOptions.Target|ClearOptions.DepthBuffer,Color.Black,1,0);
 9     //render the scene using custom effect writing to all targets simutaneously
10     effect1Secene.CurrentTechnique=effect1Scene.Techniques["MultipleTargets"];
11     effect1Secene.Parameters["xView"].SetValue(fpsCam.ViewMatrix);
12     effect1Secene.Parameters["xProjection"].SetValue(fpsCam.ProjectionMatrix);
13     RenderScene(effect1Scene);
14     //deactivate render targets to resolve them
15     device.SetRenderTarget(0,null);    
16     device.SetRenderTarget(1,null);  
17     device.SetRenderTarget(2,null);  
18     //copy contents of render targets into texture
19     colorMap=colorTarget.GetTexture();
20     normalMap=normalTarget.GetTexture();
21     depthMap=depthTarget.GetTexture();
22 }
                           首先,你启用三个绘制目标通过约束它们到COLOR0,COLOR1,和COLOR2输出象素着色器的语义。然后,你确保你清除它们的内容到黑色,设置它们的z-buffer到1。
                           虽然任何是初始化,你已经准备绘制你的场景。启用MultipleTaregets技术,你可以定义它在一分钟内。设置世界,视景和投影矩阵(世界矩阵应该被设置在RenderScene方法,因为这将会你的场景中的每个对象都不同)。绘制你的场景使用MultipleTargets技巧通过传递这个到RenderScene方法中。
                           一旦这RenderScene方法已经完整,这三个绘制目标将包含颜色,法线和深度值为每一个场景的象素。在你保存它们的内容到一个纹理之前,你需要停用它们。
HLSL代码
                           你仍然不得不定义MultipleTargets技术,它应该绘制屏幕到三个目标只有一次。开始定义XNA-to-HLSL变量:
1 float4*4 xWorld;
2 float4*4 xView;
3 float4*4 xProjection;
4 Texture xTexture;
5 sampler TextureSampler=sampler_state{textrue=<xTexture>;magfiler=LINEAR;minfilter=LINEAR;mipfilter=LINEAR;AddressU=wrap;AddressV=wrap;};
                           向往常一样当转化3D位置到2D场景坐标,你需要指定世界,视景和投影矩阵。直到房间的墙和地板都是纹理,你需要一个纹理从你的象素颜色中取样。这些颜色将会保存到第一个绘制对象中。
                           下面行中是你的顶点的输出结构和象素着色器:
 1 struct VertexToPixel
 2 {
 3     float4 Position :POSITION;
 4     float3 Normal :TEXCOORD0;
 5     float4 ScreenPos :TEXCOORD1;
 6     float2 TexCoords :TEXCOORD2;
 7 };
 8 struct PixelToFrame
 9 {
10     float4 Color :COLOR0;
11     float4 Normal:COLOR1;
12     float4 Depth:COLOR2;
13 };
                           接下来是所需的Position语义,你的顶点着色器需要传递法线到象素着色器中,这样它能保存这个在第二个绘制对象中。此外,因为这象素着色器需要保存深度到第三绘制目标中,你需要传递屏幕坐标到象素着色器中。它的X和Y组成部分包含当前象素的屏幕坐标,同时Z部分包含深度,从你的相机到所属于象素的对象之间的距离。最后,象素着色器需要纹理坐标这样它可以取样它的颜色从这纹理中在正确的位置。
                           象素着色器的输出结构十分重要。不象这本书别的地方的,你的象素着色器会生成更多的输出。而不是只能写到COLOR0语义,你的象素着色器同样写到COLOR1和COLOR2语义。也就是说,每一个输出相应你的三个绘制目标。
                          让我们继续前进,并讨论顶点着色:
 1 VertexToPixel MyVertexShader(float4 inPos:POSITION0,float3 inNormal:NORMAL0,float2 inTexCoords:TEXTCOORD0)
 2 {
 3      VertexToPixel Output=(VertexToPixel)0;
 4      float4*4 preViewProjection=mul(xView,xProjection);
 5      float4*4 preWorldViewProjection=mul(xWorld,preViewProjection);
 6      Output.Position=mul(intPos,preWorldViewProjection);
 7      float3*3 rotMatrix=(float3*3)xWorld;
 8      float3 rotNormal=mul(inNormal,rotMatrix);
 9      Output.Normal=rotNormal;
10      Output.ScreenPos=Output.Position;
11      Output.TexCoords=inTextCoords;
12      return Output;
13 }
                          这是非常基本的东西.3D位置被转化成2D屏幕坐标。法线通过世界矩阵旋转部分来被旋转的。纹理坐标立即发送输出。最后,2D场景坐标复制到ScreenPos变量,直到托管的Position变量不是从像素着色器内达到的。
                          是时候为你的象素着色器做点事了:
1 PixelToFrame MyPixelShader(VertexToPixel PSIn)
2 {
3      PixelToFrame Output=(PixelToFrame)0;
4      Output.Color.rgb=tex2D(TextureSampler,PSIn.TexCoords);
5      Output.Normal.xyz=PSIn.Normal/2.0f+5.0f;
6      Output.Depth=PSIn.ScreenPos.z/PSIn.ScreenPos.w;
7      return Output;
8 }
                          这非常容易。颜色从纹理中取样(这种情况,墙的砖块纹理),保存在第一个绘制目标中。下一个是法线。由于一个3D法线的每一部分被定义在[-1,1]范围内,你需要带这它到[0,1]范围,这样它能作为颜色部分被储存。通过用2除以它再加上0.5f到这个值。
                          最后,你需要保存深度值在第三个绘制目标中。这个深度保存在ScreenPos变量的Z部分中。由于ScreenPos是4*4的结果矩阵乘法,它是一个4*1同质向量。在你能使用第一个三个组成部份的任何一个,你需要分它们为4份,它在你的顶点着色器的最后一行实现。
                          最后但并非最不重要,这是技术的定义:
1 technique MultipleTargets
2 {
3     pass Pass0
4     {
5            VertexShader=compile vs_2_0 MyVertexShader();
6            PixelShader=complie ps_2_0 MyPixelShader();
7     }
8 }
步骤一个总结
                          在第一步的结束,你已经生成和保存了三个纹理:第一包含基础的颜色,第二个包含法线,最后一个包含你场景中每个象素的距离。
第二步:生成阴影地图
                          现在你知道这颜色,法线,和每个象素的深度,你可以执行光照计算。要做到这一点,你需要通知图形卡去绘制两个三角形来覆盖整个屏幕。它允许你去创建一个象素着色器,它被屏幕上每个象素正好一次调用。在象素着色器,计算lighting contribution 为一个灯一个确定的象素。
                          这个过程将重复每个灯在你的场景中。这些循环相应六个图象在图6-12的第二步,因为这个例子包含六个灯。
                          这个例子显示你如何计算一个聚光灯的影响。
注意:如果你想添加一个不同的灯,需要调整光照计算。这只是象素着色器的很小的部分;有趣的部分仍然相同。
HLSL代码
                         总之,这个效果取样每个象素的深度从深度地图中重建这象素的原始3D位置。一旦你知道了3D位置,你可以执行你的光照计算。                          
                         重新创建3D位置,需要逆反视景投影矩阵,和深度地图。而且,需要法线地图和一些变量,配置你的聚光灯。
 1 float4*4 xViewProjectionInv;
 2 float xLightStrength;
 3 float3 xLightPosition;
 4 float3 xConeDirection;
 5 float xConeAngle;
 6 float xConeDecay;
 7 Texture xNormalMap;
 8 sampler NormalMapSampler=sampler_state{texture=<xNormalMap>;magfilter=LINEAR;minfilter=LINEAR;mipfilter=LINEAR;AdrressU=mirror;AddressV=mirror;};
 9 Texture xDepthMap;
10 sampler DepthMapSampler=sampler_state{texture=<xDepthMap>;magfilter=LINEAR;minfilter=LINEAR;mipfilter=LINEAR;AdrressU=mirror;AddressV=mirror;};
                         接下来是你的顶点和象素着色器的输出结构。你的顶点着色器不得不产生必须的2D屏幕坐标。纹理坐标是必须的,这样每个象素能取样法线和深度地图在正确的地方。
                         这个时候,象素着色器不得不产生只有一次输出的值:当前灯的lighting contribution到当前的象素。
1 struct VertexToPixel
2 {
3      float4 Position:POSITION;
4      float2 TexCoord:TEXCOORD0;
5 };
6 struct PixelToFrame
7 {
8      float4 Color:COLOR0;
9 };
                          由于六个顶点定义在InitFullscreenVertices方法中是准备定义在场景空间坐标内(在[(-1,-1),(1,1)]范围),你的顶点着色器应该发送位置和纹理坐标到输出:
1 VertexToPixel MyVertexShader(float4 inPos:POSITION0,float2 texCoord:TEXCOORD0)
2 {
3      VertexToPixel Output=(VertexToPixel)0;
4      Output.Position=inPos;
5      Output.TexCoord=texCoord;
6      return Output;
7 }
                           象素着色器把所有时尚的东西。开始正常从相应的地图取样法线和深度:
1 PixelToFrame MyPixelShader(VertexToPixel):COLOR0
2 {
3     PixelToFrame Output=(PixelToFrame)0;
4     float3 normal=tex2D(NormalMapSampler,PSIn.TexCoord).rgb;
5     normal=normal*2.0f-1.0f;
6     normal=normalize(normal);
7     float depth=tex2D(DepthMapSampler,PSIn.TexCoord);
8 }
                           这深度能立即从深度地图里取样。这法线不得不重新绘制从[0,1]范围到[-1,-1]范围,它是相反的操作正如你第一步所做。
                           下一步是重建这象素的原始3D位置。为了实现这个,首先需要当前象素的屏幕的坐标。当前象素的纹理坐标对于这个非常完美,虽然它需要从[0,1]纹理坐标范围到[-1,1]屏幕坐标范围去映射。因为事情本来应该很容易,在Y屏幕坐标需要否定:
1 float4 screenPos;
2 screenPos.x=PSIn.TexCoord.x*2.0f-1.0f;
3 screenPos.y=-(PSIn.TexCoord.y*2.0f-1.0f);
                          然而,这个屏幕的位置有一个第三个组成部分:它是相机和象素之间的距离。这就是为什么你不得不产生第二个绘制目标。直到你知道深度,你知道第三个组成部分:
1 screenPos.z=depth;
2 screenPos.w=1.0f;
                          第四部分是必须的,以为你乘以这个向量用一个4*4矩阵。你可以改造一个Vector3成为一个类似的Vector4通过设置第四个部分为1。
                          在这点上,你有象素的屏幕坐标,但是你想有初始的3D位置。记住,你可以转换一个3D位置到一个2D场景坐标通过乘以3D位置用视景投影矩阵。这样,如果做相反的,转换一个2D屏幕位置到一个3D位置?它很简单:你乘以它用反转的这个视景投影矩阵:
1 float4 worldPos=mul(screenPos,xViewProjectionInv);
2 worldPos/=worldPos.w;
                         这个反转的视景投影矩阵需要被计算和设置通过你的XNA代码,它是非常容易的:
                         作为一个向量乘法用一个4*4矩阵的结果,它返回一个类似的向量,需要分第一个三个组成部分通过第一四个在你可以用它们之前。
                         最后!在这一点上,你知道象素的3D位置。你同样知道3D法线在这个象素。合起来,它们允许你执行任何类型的光照计算。象素着色器主要的计算光照聚光灯的影响,直接从6-8节采取的。这是完整的象素着色器:
 1 PixelToFrame MyPixelShader(VerctorToPixel PSIn):COLOR0
 2 {
 3     PixelToFrame Output=(PixelToFrame)0;
 4     float3 normal=tex2D(NormalMapSampler,PSIn.TexCoord).rgb;
 5     normal=normal*2.0f-1.0f;
 6     normal=normalize(normal);
 7     float depth=tex2D(DepthMapSampler,PSIn.TexCoord).r;
 8     float4 screenPos;
 9     screenPos.x=PSIn.TexCoord.x*2.0f-1.0f;
10     screenPos.y=-(PSIn.TexCoord.y*2.0f-1.0f);
11     screenPos.z=depth;
12     screenPos.w=1.0f;
13     float4 worldPos=mul(screenPos,xViewProjectionInv);
14     worldPos/=worldPos.w;
15     float3 lightDirection=normalize(worldPos-xLightPosition);
16     float coneDot=dot(lightDirection,normalize(xConeDirection));
17     bool coneCondition=coneDot>=xConeAngle;
18     float shading=0;
19     if(coneCondition)
20     {
21         float coneAttenuation=pow(coneDot,xConeDecay);
22         shading=dot(normal,-lightDirection);
23         shading*=xLightStrength;
24         shading*=coneAttenuation;
25     }
26     Output.Color.rgb=shading;
27     return Output;
28 }
                     这里是你的技巧定义:
1 technique DeferredSpotLight
2 {
3      pass Pass0
4      {
5           VertexShader=complie vs_2_0 MyVertexShader();
6           PixelShader=complie ps_2_0 MyPixelshader();
7      }
8 }
                     你已经创建一个效果,它开始从一个深度地图,一个法线图,和一个聚光灯,创建一个阴影地图包含聚光灯的影响在你的屏幕的部分中,它是相机可以看见的。
XNA代码
                     在你的XNA代码中,你调用这个效果为每一个灯绘制在你的场景中。想象一下你的灯,创建这个结构,它能保持所有关于一个聚光灯的细节:
1 public struct SpotLight
2 {
3      public Vector3 Position;
4      public float Strength;
5      public Vector3 Direction;
6      public float ConeAngle;
7      public float ConeDecay;
8 }   
                     添加这些对象的数组到你的工程中:
1 SpotLight[] spotLights;
                     初始化它这样它能保存一些灯:
1 spotLights=new SpotLight[NumberOfLights];
                     现在是完全由您来确定每个聚光灯。甚至,你可以改变它们的设置在Update方法中,这样它们围着你的场景移动!
                     接下来,你创建一个方法,它接收一个SpotLight对象。这方法将绘制聚光灯的lighting contribution在绘制目标的上面:
 1 private void AddLight(SpotLight spotLight)
 2 {
 3      effect2Lights.CurrentTechnique=effect2Lights.Techniques["DeferredSpotLight"];
 4      effect2Lights.Parameters["xNormalMap"].SetValue(normalMap);
 5      effect2Lights.Parameters["xDepthMap"].SetValue(depthMap);
 6      effect2Lights.Parameters["xLightPosition"].SetValue(spotLight.Position);
 7      effect2Lights.Parameters["xLightStrength"].SetValue(spotLight.Strength);
 8      effect2Lights.Parameters["xConeDirection"].SetValue(spotLight.Direction);
 9      effect2Lights.Parameters["xConeAngle"].SetValue(spotLight.ConeAngle);
10      effect2Lights.Parameters["xConeDecay"].SetValue(spotLight.ConeDecay);
11      Matrix viewProjInv=Matrix.Invert(fpsCam.ViewMatrix*fpsCam.ProjectionMatrxi);
12      effect2Light.Parameters["xViewProjectionInv"].SetValue(viewProjInv);
13      effect2Lights.Begin();
14      foreach(EffectPass pass in effect2Lights.CurrentTechnique.Passes)
15      {
16            pass.Begin();
17            device.VertexDeclaration=fsVertexDeclaration;
18            device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip,fsVertices,0,2);
19            pass.End(); 
20      }
21      effect2Lights.End();
22 }
                     你首先启用HLSL技术你刚定义完。接下来,你传递在法线地图和深度地图,它们在第一步产生。接下来的行传递通过设置聚光灯。最后一个变量是设置反向视景投影矩阵,它可以很容易的被创建使用Matrix.Invert方法。
                     一旦所有的变量已经设置,你的问题你的图形卡绘制两个三角形,横夸整个屏幕。作为一个结果,你场景中的每一个象素,你的图象卡计算当前聚光灯的lighting contribution。
                     这AddLight方法绘制单一灯的lighting contribution。调用这个方法为每一个聚光灯,添加所有它们的lighting contribution一起!通过使用添加的alpha混合。随着添加的alpha混合,每个lighting contribution被添加到相同的绘制目标中:
 1 private Texture2D GenerateShadingMap()
 2 {
 3      device.SetRenderTarget(0,shadingTarget);
 4      device.Clear(ClearOption.Target|ClearOptions.DepthBuffer,Color.Black,1,0);
 5      device.RenderState.AlphaBlendEnable=true;
 6      device.RenderState.SourceBlend=Blend.One;
 7      device.RenderState.DestinationBlend=Blend.One;
 8      for(int i=0;i<NumberOfLights;i++)
 9               AddLight(spotLight[i]);
10      device.RenderState.AlphaBlendEnable=false;
11      device.SetRenderTarget(0,null);
12      return shadingTarget.GetTexture();
13 }
                     这GenerateShadingMap方法首先启用一个新的绘制目标,调用shadingTarget.一如往常,它是首先清除的,所以前面的内容都被废弃。接下来,你启用添加alpha混合和添加所有灯的lighting contributions到这个绘制目标。关闭alpha混合器,这样你不能弄乱任何的以后透视图。最后,绘制目标的内容被保存成一个纹理,这个纹理被返回到调用的代码。
                     这方法应该被调用作为第二行从你的Draw方法中:
1 shadingMap=GenerateShadingMap();
                     两个shadingTarget和shadingMap变量仍然不得不添加你的工程:
1 RenderTarget2D shadingTarget;
2 Texture2D shadingMap;
                     初始绘制目标在LoadContent方法:
1 shadingTarget=new RenderTarget2D(device,width,height,1,SurfaceFormat.Color);
                     由于这个绘制目标需要包含lighting contribution为屏幕的每个象素,这绘制目标需要有同样的屏幕尺寸。
第二总结
                     在这一点上,你有一个阴影地图包含你的屏幕的每一个象素,多少象素应被照亮。
第三步:结合颜色地图和阴影地图
                     最后一步是非常容易的。每个象素的基本颜色被保存在colorMap在第一步。每个象素大量的光照被保存在shadingMap在第二步。而在第三步,你简单把它们两相乘得到最后的颜色。
HLSL代码
                     你的效果应该接受两个colorMap和shadingMap纹理。去照亮部分场景,它没有被任何的聚光灯遮盖着,你想要添加一点环境光照:
1 float xAmbient;
2 Texture xColorMap;
3 sampler ColorMapSampler=sampler_state{texture=<xColorMap>;magfilter=LINEAR;minfilter=LINEAR;mipfilter=LINEAR;AddressU=mirror;AddressV=mirror;};
4 Texture xShadingMap;
5 sampler ShadingMapSampler=sampler_state{texture=<xShadingMap>;magfilter=LINEAR;minfilter=LINEAR;mipfilter=LINEAR;AddressU=mirror;AddressV=mirror;};
                     顶点和象素着色器的输出结构,和顶点着色器它自己,是与前面的章节完全一致。这是因为顶点着色器将接收到六个顶点定义的两个三角形,它横跨整个屏幕。
 1 struct VertexToPixel
 2 {
 3     float4 Position :POSITION;
 4     float2 TexCoord :TEXCOORD0;
 5 };
 6 struct PixelToFrame
 7 {
 8     float4 Color:COLOR0;
 9 };
10 //--------------Technique:CombineColorAndShading--------------
11 VertexToPixel MyVertexShader(float4 inPos:POSITION0,float2 texCoord:TEXCOORD0)
12 {
13     VertexToPixel Output=(VertexToPixel)0;
14     Output.Position=inPos;
15     Output.TexCoord=texCoord;
16     return Output;
17 }
                     如所承落那样,象素着色器非常容易:
1 PixelToFrame MyPixelShader(VertexToPixel PSIn):COLOR0
2 {
3     PixelToFrame Output=(PixelToFrame)0;
4     float4 color=tex2D(ColorMapSampler,PSIn.TexCoord);
5     float shading=tex2D(ShadingMapSampler,PSIn.TexCoord);
6     Ouput.Color=color*(xAmbient+shading);
7     return Output;
8 }
                      你取样颜色和阴影的值。添加一些环境光照和把它们一起相乘。最终颜色被传递到绘制目标。
                      这里是技巧定义:
1 technique CombineColorAndShading
2 {
3     pass Pass0
4     {
5          VertexShader=compile vs_2_0 MyVertexShader();
6          PixelShader=complie ps_2_0 MyPixelShader();
7     }
8 }
XNA代码
                      这种效果需要被调用通过你的XNA代码在Draw方法的最后面。CombineColorAndShading方法列出下一个第一技巧,传递颜色和阴影地图,和设置环境光照值。最后,两个三角形跨越整个屏幕被绘制,使用你刚刚定义的技巧:
 1 private void CombineColorAndShading()
 2 {
 3      effect3Final.CurrentTechnique=effect3Final.Techniques["CombineColorAndShading"];
 4      effect3Final.Parameters["xColorMap"].SetValue(colorMap);
 5      effect3Final.Parameters["xShadingMap"].SetValue(shadingMap);
 6      effect3Final.Parameters["xAmbient"].SetValue(3.0f);
 7      effect3Final.Begin();
 8      foreach(EffectPass pass in effect3Final.CurrentTechnique.Passes)
 9      {
10            pass.Begin();
11            device.VertexDeclaration=fsVertexDeclaration;
12            device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip,fsVertices,0,2);
13            pass.End();
14      }
15      effect3Final.End();
16 }
                       这有两个三角形需要被绘制到场景中,而不是到一个自定义的绘制目标中。
第三步总结
                       你场景中的每一个象素,你必须结合基本颜色和光照强度。
代码
                       所有效果文件在前面章节被完全的列出来。主要方法涉及deferred shading.既然你已经分开你的代码成几个方法,你的Draw方法保持相当的整洁:
 1 protected override void Draw(GameTime gameTime)
 2 {
 3     //render color,normal and depth into 3 render targets
 4     RenderSceneTo3RenderTargets();
 5     //Add lighting contribution of each light onto shadingMap
 6     shadingMap=GenerateShadingMap();
 7     //Combine base color map and shading map
 8     CombineColorAndShading();
 9     base.Draw(gameTime);
10 }
Performance Tip
                      对于每个灯,你的象素着色器将计算光照影响为你的场景中的所有象素。减少一定数量的象素,需要在第2步里被处理,而不是绘制你的整个场景,你可以只绘制场景的部分,可能会被灯影响。你可以调整两个三角形的坐标,否则横跨整个场景。

posted on 2009-08-15 08:49  一盘散沙  阅读(364)  评论(0编辑  收藏  举报

导航