3.15 将阴影添加到场景
原文地址:http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series3/Real_shadow.php
现在我们已经可以从阴影贴图中采样正确的位置了,就做好了绘制阴影的准备了。我们需要使用真实颜色绘制场景,所以顶点着色器需要将每个顶点的纹理坐标、法线和3D位置传递到插值器和像素着色器中。所以需要在SsceneVertexToPixel中添加3个变量:
float2 TexCoords : TEXCOORD1; float3 Normal : TEXCOORD2; float4 Position3D : TEXCOORD3;
在前面的章节中我们已经知道如何生成这些值了。
本章,我们还要根据阴影贴图中的值测试像素和光源间的真实距离。在创建阴影贴图的一章中,这个距离可以从由光源视场中看到的2D位置获取,只需将Z分量除以齐次坐标即可。像素着色器已经可以访问这个2D坐标,因此它存储在了Pos2DasSeenByLight变量中。
别忘了我们再次需要发送到顶点数据流中的所有信息,所以请特别注意第一行代码:
SSceneVertexToPixel ShadowedSceneVertexShader( float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0, float3 inNormal : NORMAL) { SSceneVertexToPixel Output = (SSceneVertexToPixel)0; Output.Position = mul(inPos, xWorldViewProjection); Output.Pos2DAsSeenByLight = mul(inPos, xLightsWorldViewProjection); Output.Normal = normalize(mul(inNormal, (float3x3)xWorld)); Output.Position3D = mul(inPos, xWorld); Output.TexCoords = inTexCoords; return Output; }
顶点着色器没有改变,所以看一下像素着色器。我们只绘制被头灯照亮的像素。这意味着首先必须检查像素是否在头灯光照范围内。换句话说:投影的x和y坐标是否在[0, 1]范围之内。要做到这点,我们可以使用HLSL saturate方法,这个方法可以将值截取到这个范围之内。所以如果截取后的值不等于截取前的值,我们就知道这个值不在[0, 1]范围之内,即它不在头灯视场之内。在HLSL代码中:
float diffuseLightingFactor = 0; if ((saturate(ProjectedTexCoords).x == ProjectedTexCoords.x) && (saturate(ProjectedTexCoords).y == ProjectedTexCoords.y)) { } float4 baseColor = tex2D(TextureSampler, PSIn.TexCoords); if (xSolidBrown == true) baseColor = float4(0.25f, 0.21f, 0.20f, 1); Output.Color = baseColor*(diffuseLightingFactor + xAmbient);
我们首先将光照因子设为0,if代码块检查当前像素是否在光照范围内,如果是就计算光照因子(下面会讨论到),如果不是,则光照因子保持为0,像素的颜色只有环境光颜色。
因此如果像素成功地进入了if代码块,即这个像素会被光照到。接下来,我们会检查这个像素是否被另一个物体遮挡。要做到这步,我们首先要获取像素和光源间的距离,这个距离存储在了阴影贴图中:
float depthStoredInShadowMap = tex2D(ShadowMapSampler, ProjectedTexCoords).r;
然后,我们计算像素和光源的真实距离,这和创建阴影贴图一章中的做法是一样的:
realDistance = PSIn.Pos2DAsSeenByLight.z/PSIn.Pos2DAsSeenByLight.w;
现在检查这个真实距离是否大于存储在阴影贴图中的值:
if ((realDistance - 1.0f/100.0f) <= depthStoredInShadowMap) { }
你可以看到我们减去了一个很小的偏移值(bias) 1/100。其原因是:我们在阴影贴图中将深度信息存储为一个颜色值,但此贴图的颜色通道只有8位,即最小容差是1/256。假设场景的最大距离时40.0f,那么最小的距离差为40/256 = 0.156f。这意味着在即使在最理想的情况中,位于距离在0.156f至0.234f之间的点都会被认为是0.156f。
例如一个点的真实距离是2.0f (对应存储在阴影贴图中的值为0.156f)。你想检查这个真实距离和存储在阴影贴图中的距离是否一样:if (2.0f == 0.156f)结果为FAIL,你会认为这个点是在另一个物体的阴影中。基于这个理由,我们将真实距离减去一个小值用于比较。
如果真实距离等于存储在阴影贴图中的距离,这个像素就应该被照亮,需要改变diffuseLightingFactor变量,这个变量的计算方法就是前几章提到的计算点乘值。下面是像素着色器代码:
SScenePixelToFrame ShadowedScenePixelShader(SSceneVertexToPixel PSIn) { SScenePixelToFrame Output = (SScenePixelToFrame)0; float2 ProjectedTexCoords; ProjectedTexCoords[0] = PSIn.Pos2DAsSeenByLight.x/PSIn.Pos2DAsSeenByLight.w/2.0f +0.5f; ProjectedTexCoords[1] = -PSIn.Pos2DAsSeenByLight.y/PSIn.Pos2DAsSeenByLight.w/2.0f +0.5f; float diffuseLightingFactor = 0; if ((saturate(ProjectedTexCoords).x == ProjectedTexCoords.x) && (saturate(ProjectedTexCoords).y == ProjectedTexCoords.y)) { float depthStoredInShadowMap = tex2D(ShadowMapSampler, ProjectedTexCoords).r; float realDistance = PSIn.Pos2DAsSeenByLight.z/PSIn.Pos2DAsSeenByLight.w; if ((realDistance - 1.0f/100.0f) <= depthStoredInShadowMap) { diffuseLightingFactor = DotProduct(xLightPos, PSIn.Position3D, PSIn.Normal); diffuseLightingFactor = saturate(diffuseLightingFactor); diffuseLightingFactor *= xLightPower; } } float4 baseColor = tex2D(TextureSampler, PSIn.TexCoords); if (xSolidBrown == true) baseColor = float4(0.25f, 0.21f, 0.20f, 1); Output.Color = baseColor*(diffuseLightingFactor + xAmbient); return Output; }
有了HLSL代码,下面是XNA代码。
因为我们只使用一个光源,所以增强了这个光源的强度: lightPower = 2f; 运行这个代码就会获得如下图相似的结果,但不是100%相同,你获得的图像有点粗糙,例如看一下左边灯柱的阴影。
这是因为你在阴影贴图中以Color的形式存储了深度值,这很好,因为这样做可以让我们以灰度值的形式可视化结果。但是,阴影贴图中4个颜色通道的值是一样的。每个像素占据32位,所以深度存储为8位,只有2^8=256级变化。
要获得更好的结果,你可以指定每个像素只使用一个通道而不是4个。这可以使用以下代码实现,这个代码可以让每个距离存储为完整的32位,具有2^32中可能的距离变化!
renderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, SurfaceFormat.Single);
从这个相机位置你可能看不到很大的不同,但如果将相机移动到靠近阴影的位置,就会发现效果有很大的改进!
最终,我们看到了第一个阴影效果!汽车投下了一个完美的阴影,你还能看到灯柱的阴影。注意:只有在头灯视场范围之内的像素被照亮,其他地方只有环境光颜色。
虽然我们获得了阴影,但光线本身的形状还不够漂亮:你可以看到视锥体的顶角。下一章我们会解决这个问题。
HLSL代码如下:
float4x4 xLightsWorldViewProjection; float4x4 xWorldViewProjection; float4x4 xWorld; bool xSolidBrown; float3 xLightPos; float xLightPower; float xAmbient; Texture xTexture; sampler TextureSampler = sampler_state { texture =; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;}; Texture xShadowMap; sampler ShadowMapSampler = sampler_state { texture = ; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = clamp; AddressV = clamp;}; struct VertexToPixel { float4 Position : POSITION; float2 TexCoords : TEXCOORD0; float3 Normal : TEXCOORD1; float3 Position3D : TEXCOORD2; }; struct PixelToFrame { float4 Color : COLOR0; }; VertexToPixel SimplestVertexShader( float4 inPos : POSITION0, float3 inNormal: NORMAL0, float2 inTexCoords : TEXCOORD0) { VertexToPixel Output = (VertexToPixel)0; Output.Position =mul(inPos, xWorldViewProjection); Output.TexCoords = inTexCoords; Output.Normal = normalize(mul(inNormal, (float3x3)xWorld)); Output.Position3D = mul(inPos, xWorld); return Output; } float DotProduct(float3 lightPos, float3 pos3D, float3 normal) { float3 lightDir = normalize(pos3D - lightPos); return dot(-lightDir, normal); } PixelToFrame OurFirstPixelShader(VertexToPixel PSIn) { PixelToFrame Output = (PixelToFrame)0; float diffuseLightingFactor = DotProduct(xLightPos, PSIn.Position3D, PSIn.Normal); diffuseLightingFactor = saturate(diffuseLightingFactor); diffuseLightingFactor *= xLightPower; float4 baseColor = tex2D(TextureSampler, PSIn.TexCoords); if (xSolidBrown == true) baseColor = float4(0.25f, 0.21f, 0.20f, 1); Output.Color = baseColor*(diffuseLightingFactor + xAmbient); return Output; } technique Simplest { pass Pass0 { VertexShader = compile vs_2_0 SimplestVertexShader(); PixelShader = compile ps_2_0 OurFirstPixelShader(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// struct SMapVertexToPixel { float4 Position : POSITION; float4 Position2D : TEXCOORD0; }; struct SMapPixelToFrame { float4 Color : COLOR0; }; SMapVertexToPixel ShadowMapVertexShader( float4 inPos : POSITION) { SMapVertexToPixel Output = (SMapVertexToPixel)0; Output.Position = mul(inPos, xLightsWorldViewProjection); Output.Position2D = Output.Position; return Output; } SMapPixelToFrame ShadowMapPixelShader(SMapVertexToPixel PSIn) { SMapPixelToFrame Output = (SMapPixelToFrame)0; Output.Color = PSIn.Position2D.z/PSIn.Position2D.w; return Output; } technique ShadowMap { pass Pass0 { VertexShader = compile vs_2_0 ShadowMapVertexShader(); PixelShader = compile ps_2_0 ShadowMapPixelShader(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// struct SSceneVertexToPixel { float4 Position : POSITION; float4 Pos2DAsSeenByLight : TEXCOORD0; float2 TexCoords : TEXCOORD1; float3 Normal : TEXCOORD2; float4 Position3D : TEXCOORD3; }; struct SScenePixelToFrame { float4 Color : COLOR0; }; SSceneVertexToPixel ShadowedSceneVertexShader( float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0, float3 inNormal : NORMAL) { SSceneVertexToPixel Output = (SSceneVertexToPixel)0; Output.Position = mul(inPos, xWorldViewProjection); Output.Pos2DAsSeenByLight = mul(inPos, xLightsWorldViewProjection); Output.Normal = normalize(mul(inNormal, (float3x3)xWorld)); Output.Position3D = mul(inPos, xWorld); Output.TexCoords = inTexCoords; return Output; } SScenePixelToFrame ShadowedScenePixelShader(SSceneVertexToPixel PSIn) { SScenePixelToFrame Output = (SScenePixelToFrame)0; float2 ProjectedTexCoords; ProjectedTexCoords[0] = PSIn.Pos2DAsSeenByLight.x/PSIn.Pos2DAsSeenByLight.w/2.0f +0.5f; ProjectedTexCoords[1] = -PSIn.Pos2DAsSeenByLight.y/PSIn.Pos2DAsSeenByLight.w/2.0f +0.5f; float diffuseLightingFactor = 0; if ((saturate(ProjectedTexCoords).x == ProjectedTexCoords.x) && (saturate(ProjectedTexCoords).y == ProjectedTexCoords.y)) { float depthStoredInShadowMap = tex2D(ShadowMapSampler, ProjectedTexCoords).r; float realDistance = PSIn.Pos2DAsSeenByLight.z/PSIn.Pos2DAsSeenByLight.w; if ((realDistance - 1.0f/100.0f) <= depthStoredInShadowMap) { diffuseLightingFactor = DotProduct(xLightPos, PSIn.Position3D, PSIn.Normal); diffuseLightingFactor = saturate(diffuseLightingFactor); diffuseLightingFactor *= xLightPower; } } float4 baseColor = tex2D(TextureSampler, PSIn.TexCoords); if (xSolidBrown == true) baseColor = float4(0.25f, 0.21f, 0.20f, 1); Output.Color = baseColor*(diffuseLightingFactor + xAmbient); return Output; } technique ShadowedScene { pass Pass0 { VertexShader = compile vs_2_0 ShadowedSceneVertexShader(); PixelShader = compile ps_2_0 ShadowedScenePixelShader(); } }
在XNA代码中只有一些小变化:
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace XNAseries3 { public class Game1 : Microsoft.Xna.Framework.Game { struct MyOwnVertexFormat { private Vector3 position; private Vector2 texCoord; private Vector3 normal; public MyOwnVertexFormat(Vector3 position, Vector2 texCoord, Vector3 normal) { this.position = position; this.texCoord = texCoord; this.normal = normal; } public static VertexElement[] VertexElements = { new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0), new VertexElement(0, sizeof(float)*3, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0), new VertexElement(0, sizeof(float)*5, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0), }; public static int SizeInBytes = sizeof(float) * (3 + 2 + 3); } GraphicsDeviceManager graphics; GraphicsDevice device; Effect effect; Matrix viewMatrix; Matrix projectionMatrix; VertexBuffer vertexBuffer; VertexDeclaration vertexDeclaration; Vector3 cameraPos; Texture2D streetTexture; Model lamppostModel; Texture2D[] lamppostTextures; Model carModel; Texture2D[] carTextures; Vector3 lightPos; float lightPower; float ambientPower; Matrix lightsViewProjectionMatrix; RenderTarget2D renderTarget; Texture2D shadowMap; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } protected override void Initialize() { graphics.PreferredBackBufferWidth = 500; graphics.PreferredBackBufferHeight = 500; graphics.IsFullScreen = false; graphics.ApplyChanges(); Window.Title = "Riemer's XNA Tutorials -- Series 3"; base.Initialize(); } protected override void LoadContent() { device = GraphicsDevice; effect = Content.Load effect> ("OurHLSLfile"); streetTexture = Content.Load文件下载(已下载 1147 次)("streettexture"); carModel = LoadModel("racer", out carTextures); lamppostModel = LoadModel("lamppost", out lamppostTextures); SetUpVertices(); SetUpCamera(); PresentationParameters pp = device.PresentationParameters; renderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, SurfaceFormat.Single); } private Model LoadModel(string assetName, out Texture2D[] textures) { Model newModel = Content.Load (assetName); textures = new Texture2D[7]; int i = 0; foreach (ModelMesh mesh in newModel.Meshes) foreach (BasicEffect currentEffect in mesh.Effects) textures[i++] = currentEffect.Texture; foreach (ModelMesh mesh in newModel.Meshes) foreach (ModelMeshPart meshPart in mesh.MeshParts) meshPart.Effect = effect.Clone(device); return newModel; } private void SetUpVertices() { MyOwnVertexFormat[] vertices = new MyOwnVertexFormat[18]; vertices[0] = new MyOwnVertexFormat(new Vector3(-20, 0, 10), new Vector2(-0.25f, 25.0f), new Vector3(0, 1, 0)); vertices[1] = new MyOwnVertexFormat(new Vector3(-20, 0, -100), new Vector2(-0.25f, 0.0f), new Vector3(0, 1, 0)); vertices[2] = new MyOwnVertexFormat(new Vector3(2, 0, 10), new Vector2(0.25f, 25.0f), new Vector3(0, 1, 0)); vertices[3] = new MyOwnVertexFormat(new Vector3(2, 0, -100), new Vector2(0.25f, 0.0f), new Vector3(0, 1, 0)); vertices[4] = new MyOwnVertexFormat(new Vector3(2, 0, 10), new Vector2(0.25f, 25.0f), new Vector3(-1, 0, 0)); vertices[5] = new MyOwnVertexFormat(new Vector3(2, 0, -100), new Vector2(0.25f, 0.0f), new Vector3(-1, 0, 0)); vertices[6] = new MyOwnVertexFormat(new Vector3(2, 1, 10), new Vector2(0.375f, 25.0f), new Vector3(-1, 0, 0)); vertices[7] = new MyOwnVertexFormat(new Vector3(2, 1, -100), new Vector2(0.375f, 0.0f), new Vector3(-1, 0, 0)); vertices[8] = new MyOwnVertexFormat(new Vector3(2, 1, 10), new Vector2(0.375f, 25.0f), new Vector3(0, 1, 0)); vertices[9] = new MyOwnVertexFormat(new Vector3(2, 1, -100), new Vector2(0.375f, 0.0f), new Vector3(0, 1, 0)); vertices[10] = new MyOwnVertexFormat(new Vector3(3, 1, 10), new Vector2(0.5f, 25.0f), new Vector3(0, 1, 0)); vertices[11] = new MyOwnVertexFormat(new Vector3(3, 1, -100), new Vector2(0.5f, 0.0f), new Vector3(0, 1, 0)); vertices[12] = new MyOwnVertexFormat(new Vector3(13, 1, 10), new Vector2(0.75f, 25.0f), new Vector3(0, 1, 0)); vertices[13] = new MyOwnVertexFormat(new Vector3(13, 1, -100), new Vector2(0.75f, 0.0f), new Vector3(0, 1, 0)); vertices[14] = new MyOwnVertexFormat(new Vector3(13, 1, 10), new Vector2(0.75f, 25.0f), new Vector3(-1, 0, 0)); vertices[15] = new MyOwnVertexFormat(new Vector3(13, 1, -100), new Vector2(0.75f, 0.0f), new Vector3(-1, 0, 0)); vertices[16] = new MyOwnVertexFormat(new Vector3(13, 21, 10), new Vector2(1.25f, 25.0f), new Vector3(-1, 0, 0)); vertices[17] = new MyOwnVertexFormat(new Vector3(13, 21, -100), new Vector2(1.25f, 0.0f), new Vector3(-1, 0, 0)); vertexBuffer = new VertexBuffer(device, vertices.Length * MyOwnVertexFormat.SizeInBytes, BufferUsage.WriteOnly); vertexBuffer.SetData(vertices); vertexDeclaration = new VertexDeclaration(device, MyOwnVertexFormat.VertexElements); } private void SetUpCamera() { cameraPos = new Vector3(-25, 13, 18); viewMatrix = Matrix.CreateLookAt(cameraPos, new Vector3(0, 2, -12), new Vector3(0, 1, 0)); projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 5.0f, 100.0f); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); UpdateLightData(); base.Update(gameTime); } private void UpdateLightData() { ambientPower = 0.5f; lightPos = new Vector3(-18, 5, -2); lightPower = 2.0f; Matrix lightsView = Matrix.CreateLookAt(lightPos, new Vector3(-2, -3, -10), new Vector3(0, 1, 0)); Matrix lightsProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, 1f, 1f, 100f); lightsViewProjectionMatrix = lightsView* lightsProjection; } protected override void Draw(GameTime gameTime) { device.SetRenderTarget(0, renderTarget); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawScene("ShadowMap"); device.SetRenderTarget(0, null); shadowMap = renderTarget.GetTexture(); device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0); DrawScene("ShadowedScene"); base.Draw(gameTime); } private void DrawScene(string technique) { effect.CurrentTechnique = effect.Techniques[technique]; effect.Parameters["xWorldViewProjection"].SetValue(Matrix.Identity * viewMatrix * projectionMatrix); effect.Parameters["xTexture"].SetValue(streetTexture); effect.Parameters["xWorld"].SetValue(Matrix.Identity); effect.Parameters["xLightPos"].SetValue(lightPos); effect.Parameters["xLightPower"].SetValue(lightPower); effect.Parameters["xAmbient"].SetValue(ambientPower); effect.Parameters["xLightsWorldViewProjection"].SetValue(Matrix.Identity * lightsViewProjectionMatrix); effect.Parameters["xShadowMap"].SetValue(shadowMap); effect.Begin(); foreach (EffectPass pass in effect.CurrentTechnique.Passes) { pass.Begin(); device.VertexDeclaration = vertexDeclaration; device.Vertices[0].SetSource(vertexBuffer, 0, MyOwnVertexFormat.SizeInBytes); device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 18); pass.End(); } effect.End(); Matrix car1Matrix = Matrix.CreateScale(4f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateTranslation(-3, 0, -15); DrawModel(carModel, carTextures, car1Matrix, technique, false); Matrix car2Matrix = Matrix.CreateScale(4f) * Matrix.CreateRotationY(MathHelper.Pi * 5.0f / 8.0f) * Matrix.CreateTranslation(-28, 0, -1.9f); DrawModel(carModel, carTextures, car2Matrix, technique, false); Matrix lamp1Matrix = Matrix.CreateScale(0.05f) * Matrix.CreateTranslation(4.0f, 1, -35); DrawModel(lamppostModel, lamppostTextures, lamp1Matrix, technique, true); Matrix lamp2Matrix = Matrix.CreateScale(0.05f) * Matrix.CreateTranslation(4.0f, 1, -5); DrawModel(lamppostModel, lamppostTextures, lamp2Matrix, technique, true); } private void DrawModel(Model model, Texture2D[] textures, Matrix wMatrix, string technique, bool solidBrown) { Matrix[] modelTransforms = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(modelTransforms); int i = 0; foreach (ModelMesh mesh in model.Meshes) { foreach (Effect currentEffect in mesh.Effects) { Matrix worldMatrix = modelTransforms[mesh.ParentBone.Index] * wMatrix; currentEffect.CurrentTechnique = currentEffect.Techniques[technique]; currentEffect.Parameters["xWorldViewProjection"].SetValue(worldMatrix * viewMatrix * projectionMatrix); currentEffect.Parameters["xTexture"].SetValue(textures[i++]); currentEffect.Parameters["xSolidBrown"].SetValue(solidBrown); currentEffect.Parameters["xWorld"].SetValue(worldMatrix); currentEffect.Parameters["xLightPos"].SetValue(lightPos); currentEffect.Parameters["xLightPower"].SetValue(lightPower); currentEffect.Parameters["xAmbient"].SetValue(ambientPower); currentEffect.Parameters["xLightsWorldViewProjection"].SetValue(worldMatrix * lightsViewProjectionMatrix); currentEffect.Parameters["xShadowMap"].SetValue(shadowMap); } mesh.Draw(); } } } }
发布时间:2010/5/28 上午8:06:52 阅读次数:15096