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 ("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();
}
}
}
}
文件下载(已下载 1017 次)
发布时间:2010/5/26 上午8:19:05 阅读次数:7027
