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 ("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();
}
}
}
}
文件下载(已下载 1147 次)
发布时间:2010/5/28 上午8:06:52 阅读次数:15629
