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  阅读次数:6314

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号