3.14 使用投影纹理将顶点转换到纹理空间
有了阴影贴图,现在我们就可以绘制相机视场中的场景了,并检测像素是否需要投下阴影,这可以通过从正确的位置对阴影贴图进行采样进行判断。本章的目标是绘制相机视场中的场景,添加每个像素存储在阴影贴图中的颜色。
首先,这意味着我们需要使用2个不同的technique绘制场景:第一个生成阴影贴图并存储在一张纹理中,第二个将场景绘制到屏幕。你已经在HLSL文件中添加了第二个:
technique ShadowedScene { pass Pass0 { VertexShader = compile vs_2_0 ShadowedSceneVertexShader(); PixelShader = compile ps_2_0 ShadowedScenePixelShader(); } }
因为我们需要2个新的shader方法,好的做法是定义两个新结构保存这两个方法的输出数据:
struct SSceneVertexToPixel { float4 Position : POSITION; float4 Pos2DAsSeenByLight : TEXCOORD0; }; struct SScenePixelToFrame { float4 Color : COLOR0; };
如你所见,顶点着色器需要将相机所见的2D位置传递到像素着色器 ,还要包括从光源所见的2D位置。这样,后面我们就可以获取对应当前像素的阴影贴图中的位置。像素着色器仍只输出像素颜色。
像素着色器使用阴影贴图,这个贴图可以作为一个标准的纹理:
exture xShadowMap; sampler ShadowMapSampler = sampler_state { texture =; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = clamp; AddressV = clamp;};
让我们首先编写顶点着色器代码。别忘了:第二个technique绘制的是相机视场中的场景。当调用第二个technique时,第一个technique已经完成了阴影贴图的绘制。注意:阴影贴图是从光源视场中绘制的,本例中就是位于屏幕左下角的汽车头灯。现在,对每个从相机视场中看到的像素,我们需要找到阴影贴图中的对应位置。换句话说,我们需要对应每个像素的在阴影贴图中的2D屏幕位置。因为这较难理解,所以我添加了一张图加以表示:
假设我们想知道墙上红点位置的颜色,我们如何获取在阴影贴图中对应的2D坐标。
第一步是将墙上红点像素的3D坐标投影到光源的2D相机空间中,做法与ShadowMap technique是一样的,即使用光源的WorldViewProjection矩阵转换3D位置,转换后的结果2D坐标存储在Output.Pos2DasSeenByLight:
SSceneVertexToPixel ShadowedSceneVertexShader( float4 inPos : POSITION) { SSceneVertexToPixel Output = (SSceneVertexToPixel)0; Output.Position = mul(inPos, xWorldViewProjection); Output.Pos2DAsSeenByLight= mul(inPos, xLightsWorldViewProjection); return Output; }
现在像素着色器就可以访问光源的齐次屏幕坐标了。第4个(齐次)坐标在第二章中已经讨论过了,但它仍是一个难点。我们需要第4个坐标,因为存储在Output.Pos2DasSeenByLightis中的这个值是与一个4*4矩阵相乘的结果。在可以使用X,Y和Z分量之前,我们需要将它们除以W分量。如果你想知道为什么,可以参考矩阵的基础知识。所以别忘了除以W分量。因为屏幕坐标是2D的,我们不使用Z分量。所以只需将X和Y除以W。
做完上述操作后,我们就获得了2D屏幕坐标,其中X和Y分量在[-1, 1]之间。坐标(-1,-1)表示屏幕的左上角,(0,1)表示右边中间。
但纹理坐标在[0, 1]区间,所以我们需要重新映射:将(-1,-1)变为(0,0),(1,1)仍是(1,1)不变。公式如下:
Xtexture=XScreenX/2+0.5
Ytexture=YScreenX/2+0.5
我们在像素着色器中使用了这个公式,计算第二个纹理坐标的代码中的“–”号是必须的,因为场景需要上下颠倒绘制到帧缓冲中,所以你需要进行调整。:
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; Output.Color = tex2D(ShadowMapSampler, ProjectedTexCoords); return Output; }
我们将齐次2D屏幕坐标作为像素着色器的输入,并从中获取2D纹理坐标。我们根据这个坐标采样阴影贴图并将此作为像素着色器的输出颜色。
以上就是HLSL代码,现在讨论XNA代码。因为此时我们有两个technique,所以需要绘制场景2次,需要在Draw方法底部添加一行代码:
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); }
首先激活并清除自定义渲染目标,使用ShadowMap technique将场景绘制到渲染目标中,这个场景是从汽车头灯看到的场景。然后重新将渲染目标设置为后备缓存并将它的内容保存在阴影纹理中。然后,清除后备缓存再次绘制场景,这次是使用ShadowedScene technique绘制相机看到的场景。
好了,现在运行代码,你会获得一个黑色的屏幕,这是因为你还没有设置effect的 xShadowMap变量!所以在DrawScene中添加以下代码:
effect.Parameters["xShadowMap"].SetValue(shadowMap);
在DrawModel方法中添加以下代码:
currentEffect.Parameters["xShadowMap"].SetValue(shadowMap);
现在运行代码,你会看到如下图所示的场景,但颜色不对:因为它们的颜色采样自阴影贴图。当你观察灯柱时,你会看到它的颜色和阴影贴图中的颜色是一样的。当你观察对应灯柱后面的墙时,它的颜色与灯柱的颜色相同!
所以,下一章我们就可以检测到阴影区域了。 投影纹理technique是本系列最后一个与数学相关的部分,根据本章的解释,你应该至少知道自己在干什么/为什么这样操作,以及理解背后的数学原理。
下面是完整的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; }; struct SScenePixelToFrame { float4 Color : COLOR0; }; SSceneVertexToPixel ShadowedSceneVertexShader( float4 inPos : POSITION) { SSceneVertexToPixel Output = (SSceneVertexToPixel)0; Output.Position = mul(inPos, xWorldViewProjection); Output.Pos2DAsSeenByLight = mul(inPos, xLightsWorldViewProjection); 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; Output.Color = tex2D(ShadowMapSampler, ProjectedTexCoords); return Output; } technique ShadowedScene { pass Pass0 { VertexShader = compile vs_2_0 ShadowedSceneVertexShader(); PixelShader = compile ps_2_0 ShadowedScenePixelShader(); } }
下面是XNA代码:
using System; using System.Collections.Generic; 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文件下载(已下载 1007 次)("OurHLSLfile"); streetTexture = Content.Load ("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, device.DisplayMode.Format); } 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, 1.0f, 200.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.2f; lightPos = new Vector3(-18, 5, -2); lightPower = 1.0f; Matrix lightsView = Matrix.CreateLookAt(lightPos, new Vector3(-2, 3, -10), new Vector3(0, 1, 0)); Matrix lightsProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, 1f, 5f, 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/27 下午3:02:50 阅读次数:6940