3.13 将场景绘制到纹理
原文地址:http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series3/Render_to_texture.php。
如上一章所述,阴影映射的第二步需要比较深度贴图中的值。要实现这一步,我们需要将深度贴图保存到一张纹理中,这样就可以在最后一步中使用这个存储在纹理中的数据。
所以我们要将场景绘制到一张纹理中,而不是绘制到屏幕。这在创建一面镜子时是非常有用的:首先你将镜子中看到的场景绘制到纹理中,然后使用覆盖有这个纹理的两个三角形绘制场景。
借助于XNA的某些辅助类,将场景绘制到纹理很简单,我们需要在代码中添加以下变量:
RenderTarget2D renderTarget; Texture2D shadowMap;
第一行代码是渲染目标,默认渲染目标是屏幕,它是可以绘制其上的一片内存区域。第二个变量是存储渲染目标结果的纹理。
要学习更多细节可参见3.8 将场景绘制到纹理。
我们现在只需初始化渲染目标,所以将以下代码放置在LoadContent方法中:
PresentationParameters pp = device.PresentationParameters; renderTarget = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, 1, device.DisplayMode.Format);
这些代码创建了一个和后备缓冲相同大小和数据格式的渲染目标。第4个参数表示纹理的mipmap level,可参见教程3.7 创建一张纹理,定义每个像素的颜色,将纹理保存到一个文件理解mipmap。
有了这些变量,接下来需要调整Draw方法。非常简单:只需在绘制前激活渲染目标,所以在Draw方法顶部添加以下代码:
device.SetRenderTarget(0, renderTarget);
从这行代码开始,渲染目标会被清除,阴影贴图会绘制到这个渲染目标上。当你现在运行代码,屏幕上不会绘制任何东西。如果有什么东西被绘制的话,也是因为它们当时仍保存在后备缓冲中的缘故。
在Draw方法最后,当绘制完所有东西后,我们需要将自定义渲染目标中的内容保存到一张纹理中。在可以访问这些内容前,还需要关闭自定义渲染目标。这可以通过激活另一个渲染目标做到,本例中将渲染目标重新设置为后备缓存:
device.SetRenderTarget(0, null); shadowMap = renderTarget.GetTexture();
最后一行代码将渲染目标中的内容放置到一张纹理中,这非常简单。
下面我们想通过代码看到纹理的结果,虽然在本章的最后会移除这个代码。可以有两种不同的方法:第一个方法非常简单:你只需将纹理保存到一个文件!这也是游戏截屏使用的方法,只需在Draw方法最后添加以下代码:
shadowMap.Save("shadowmap.bmp", ImageFileFormat.Bmp);
当你运行代码后,你可以在项目的debug文件夹中找到一个叫做shadowmap.bmp 的图像文件!因为这个文件会在每帧后被覆盖,这非常花费时间,所以请再次移除这行代码。
第二个方法是将纹理显示在屏幕上。要做到这点,我们首先要将后备缓冲作为当前渲染目标,然后使用一个SpriteBatch绘制2D图像。这个代码非常丑陋,因为它每帧都要创建一个新SpriteBatch!你无需每帧都创建这个对象,你可以在类中定义这个对象然后每帧重用它。我在这里使用它的原因是因为我2分钟后就要移除这个代码:
device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0); using (SpriteBatch sprite = new SpriteBatch(device)) { sprite.Begin(SpriteBlendMode.None, SpriteSortMode.Texture, SaveStateMode.SaveState); sprite.Draw(shadowMap, new Vector2(0, 0), null, Color.White, 0, new Vector2(0, 0), 0.4f, SpriteEffects.None, 1); sprite.End(); }
第一行代码清除后备缓存,然后创建一个SpriteBatch对象用来绘制纹理。
我们使用了SaveStateMode.SaveState,否则图形设备会继续保持原有的渲染状态(例如默认的alpha)。现在,渲染状态在spritebatch激活前被保存,然后在纹理绘制后被恢复。在3D应用程序中,当你用到SpriteBatch类时你必须使用SaveStateMode.SaveState,除非你很清楚渲染状态究竟发生了什么变化。
要绘制一张纹理,你需要设定这些参数:纹理,屏幕位置,绘制纹理的哪一部分(null表示整张纹理),施加在纹理上的光线颜色,旋转,从纹理的何处开始绘制,缩放等。
要学习更多SpriteBatch的高级功能和性能优化,可参见教程3.1 使用SpriteBatch类显示2D图像:加载和绘制图像,3.2 旋转,缩放和镜像一张图像,3.3 使用层绘制透明图像,3.4 使用SpriteBatch类时的性能考虑。
好了,现在运行代码,你会看到缩放了0.4倍的纹理,如下图所示。
比起上一章并没有漂亮多少,但你学习了:将屏幕绘制到纹理,将2D纹理绘制到屏幕!现在移除显示纹理的代码进入下一章。 HLSL代码没有改变,所以以下只列出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 effect> ("OurHLSLfile"); streetTexture = Content.Load文件下载(已下载 1017 次)("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.DarkSlateBlue, 1.0f, 0); using (SpriteBatch sprite = new SpriteBatch(device)) { sprite.Begin(SpriteBlendMode.None, SpriteSortMode.Texture, SaveStateMode.SaveState); sprite.Draw(shadowMap, new Vector2(0, 0), null, Color.White, 0, new Vector2(0, 0), 0.4f, SpriteEffects.None, 1); sprite.End(); } 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.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); } mesh.Draw(); } } } }
发布时间:2010/5/26 上午8:19:05 阅读次数:6314