Home > TutorialsTechniques
4.8. Render to Texture 
This tutorial shows how to render to a texture in XNA, the relevant parts of it doesn't have anything to do with BetaCell



Click here to go to the forum discussion of this tutorial

 

4.8.1. Prerequisites
default.jpg
Before you start this tutorial make sure that you have:
  • Installed XNA Game Studio 3.1
  • Downloaded the starter project from the downloads section
  • Successfully run the starter project
  • This tutorial starts from [3.5], it's desired to have completed it.
 

4.8.2. Game Assets 
 

4.8.3. The using statements
Besides the using statements from the starter project, this tutorial needs the following using statements:

using BetaCell.Environment.Terrain;
using BetaCell.Environment.Mesh;
using BetaCell.Util.Procedural;
using BetaCell.Common.Vertex;
using BetaCell.Environment.Mesh.Util;
using BetaCell.Environment;
using BetaCell.Environment.Mesh.Visitors;
using BetaCell.Environment.Light;
using BetaCell.Util.RenderStates;
using BetaCell.Environment.Texture.Feeder;
using BetaCell.Environment.Animation;
using BetaCell.Environment.Mesh.Collision;
using BetaCell.Util;
using BetaCell.Environment.Mesh.Content;
using BetaCell.Common.Bounding;
using BetaCell.Dynamic.Skinned;


 

4.8.4. Game Attributes 
The attributes that come with the starter project are explained in tutorial section 1.1.4.

For this tutorial we are going to be needing the following attribute:

RenderTarget2D pip;

 

4.8.5. The Initialize method 
The relevant code in the initialize method for this tutorial constructs a texture drawer which is an object that draws a texture in a specific area of the screen.

//Picture in Picture initialization
textureDrawer = new BCTextureDrawer(device, BCSampler.DEFAULT);

 
 
4.8.6. The OnActivated method
A render target is an object that encapsulates a canvas where you draw. In the buildViewMatrix we create the render target with the following call:

pip = new RenderTarget2D(
    device,
    (int)(this.Window.ClientBounds.Width / 2),
    (int)(this.Window.ClientBounds.Height / 2),
    1,
    SurfaceFormat.Color
);

The parameters of the constructor are:
  1. The graphics device
  2. Half the width of the screen
  3. Half the height of the screen (half because the 'picture in picture'  ocupies a fourth of the screen)
  4. Mipmap levels
  5. The surface type of the canvas


  
Advertisement
 

4.8.7. The Draw method
First we prepare the device to draw in the render target defined in [4.8.6]

device.SetRenderTarget(0, pip);

Then we clear the render target to black and draw the surrounding box and the collision model on it.

graphics.GraphicsDevice.Clear(
    ClearOptions.Target | ClearOptions.DepthBuffer | ClearOptions.Stencil,
    Color.Black, 1.0f, 0);


BCDrawUtil.drawBoundings(evilMesh, device, gameTime);

BCWireFrameRenderStrategy drawWire = new BCWireFrameRenderStrategy();
BCDrawOverGeometryRenderStrategy drawOver = new BCDrawOverGeometryRenderStrategy(15);

drawWire.push(device);
drawOver.push(device);
BCDrawUtil.drawCollisionModel(
    device, evilMesh.transform, Color.LightGreen, evilColisionModel);

drawOver.pop(device);
drawWire.pop(device);

if (showFace)
{
    drawOver.push(device);
    BCDrawUtil.drawTriangle(
        device,
        currentColisionFace[0],
        currentColisionFace[1],
        currentColisionFace[2],
        Color.Yellow
    );
    drawOver.pop(device);
}

After that we set the render target to the back buffer again (the screen)

device.SetRenderTarget(0, null);

And store the render target content in a texture:

Texture2D pipTex = pip.GetTexture();

After that we draw the mesh and the cursor on the screen as we have done before:

graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,
    Color.White, 1.0f, 0);


evilMesh.Draw(gameTime);
cursor.draw(gameTime);

And finally we draw the texture in the lower right part of the screen:

textureDrawer.drawTexture(
    pipTex,
    new Vector2(0.5f, -0.5f),
    new Vector2(0.5f, 0.5f)
);


The parameters are:
  1. The texture to draw
  2. The offset from the center (in screen percentage; -1 to 1) at which the texture is going to be drawn
  3. The size of the texture (in screen percentage; 0 to 1)

 

4.8.8. Conclusion
This tutorial is not really about BetaCell, is about rendering to a texture in XNA. Since the code to render to the texture and to draw the texture is pure XNA, you will find appropriate documentation to understand it.
 
Click here to go to the forum discussion of this tutorial

 

Advertisement
 

4.8.9. Complete source code
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;

using BetaCell.Debug;
using BetaCell.Util.GlobalInfo.Content;
using BetaCell.Environment.Camera;
using BetaCell.Util.GlobalInfo;


using BetaCell.Environment.Animation;
using BetaCell.Environment.Mesh;
using BetaCell.Environment.Light;
using BetaCell.Environment.Mesh.Content;
using BetaCell.Environment;
using BetaCell.Environment.Mesh.Visitors;
using BetaCell.Dynamic.Skinned;
using BetaCell.Environment.Mesh.Collision;
using BetaCell.Util;
using BetaCell.Util.RenderStates;
using BetaCell.Environment.Texture.Feeder;

namespace Starter
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        //Game attributes
        GraphicsDeviceManager graphics;
        GraphicsDevice device;
        ContentManager content;

        BCFirstPersonHumanCamera camera;
        //end game attributes

        //This tutorial's attributes
        BCMesh evilMesh;

        BCAnimationSetBase animationSet;

        BCAnimationBase idle;
        BCAnimationBase run;
        BCAnimationBase idleToRun;
        BCAnimationBase runToIdle;

        BCAnimationController controller;

        BCAddSequenceListener animationSequence;

        //----------------------------
        //Animation Control Attributes
        //----------------------------

        double timeToChange = 5;
        double lastTime = 0;
        bool isIdle = true;

        //---------------------------
        //Animated collision model
        //---------------------------

        BCCollisionModel evilCollisionModel;
        bool showFace = false;
        Vector3[] currentCollisionFace = new Vector3[3];

        BCCursor cursor;

        RenderTarget2D pip;

        BCTextureDrawer textureDrawer;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8;
            content = new ContentManager(Services);
#if(XBOX360)
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
#else
            graphics.PreferredBackBufferWidth = 852;
            graphics.PreferredBackBufferHeight = 480;
#endif
        }

        protected override void Initialize()
        {
            //base initialization
            device = graphics.GraphicsDevice;

            BCLogger.instance.init(device, content);
            BCInitializationManager.initialize(content);

            OnActivated(null, null);

            //-----
            //MESHES
            //-----

            evilMesh =
                BCMeshReader.readMesh("Content/gameAssets/evilAnimatedTexturedB", null, device, content);

            //-----
            //Light Effect
            //-----

            BCBasicLight light = new BCBasicLight();
            light.range = 3000;
            light.transform = Matrix.CreateTranslation(-1f, -1f, -1f);
            light.cubAtten = 0;
            light.constAtten = 1;
            light.linAtten = 0;
            light.useShadows = false;

            BCDynamicEffectVisitor lightSetter = new BCDynamicEffectVisitor(
                light.getFunction("Shader.Light.Classic.PixelDirectionLight"), light, 1
            );

            evilMesh.visit(lightSetter);

            //---------
            //ANIMATION
            //---------
            animationSet = content.Load<BCAnimationSetBase>("Content/gameAssets/evilAnimatedTexturedBAnim");

            animationSet.animations.TryGetValue("Idle", out idle);
            animationSet.animations.TryGetValue("Run", out run);
            animationSet.animations.TryGetValue("IdleToRun", out idleToRun);
            animationSet.animations.TryGetValue("RunToIdle", out runToIdle);

            controller = new BCAnimationController(evilMesh.skeleton.boneCount);

            evilMesh.controller = controller;

            BCSkinnedMeshFeeder skinnedFeeder = new BCSkinnedMeshFeeder();
            BCDynamicEffectVisitor skinnedSetter = new BCDynamicEffectVisitor(
                skinnedFeeder.getFunction("Actuator.Skinned.SkinnedMesh"),
                skinnedFeeder, 0
            );
            evilMesh.visit(skinnedSetter);

            animationSequence = new BCAddSequenceListener();
            animationSequence.setNextAnimation(null, idle);
            controller.addSingle(idle, animationSequence, 0);

            //Prepare boundings of the mesh for drawing
            BCDrawableBoundingPrepareVisitor boundingPrepare =
                new BCDrawableBoundingPrepareVisitor(device, Color.Red);
            evilMesh.visit(boundingPrepare);

            //Collision model
            evilCollisionModel = new BCCollisionModel(evilMesh);

            //Cursor
            cursor = new BCCursor(
                device,
                content.Load<Texture2D>("Content/gameAssets/cursor")
            );

            cursor.position = new Vector2(device.Viewport.Width / 2, device.Viewport.Height / 2);

            //Texture Drawer
            textureDrawer = new BCTextureDrawer(device, BCSampler.DEFAULT);

            base.Initialize();
        }

        protected override void OnActivated(object sender, EventArgs args)
        {
            buildViewMatrix();
            base.OnActivated(sender, args);
        }

        void buildViewMatrix()
        {
            Vector3 pos = new Vector3(6f, 2, 6f);
            Vector3 look = new Vector3(-7, -3.0f, -7);
            look.Normalize();

            camera = new BCFirstPersonHumanCamera(look, pos,
                MathHelper.PiOver4,
                (float)this.Window.ClientBounds.Width / (float)this.Window.ClientBounds.Height,
                1f, 200);

            //Picture in Picture initialization
            pip = new RenderTarget2D(
                device,
                (int)(this.Window.ClientBounds.Width / 2),
                (int)(this.Window.ClientBounds.Height / 2),
                1,
                SurfaceFormat.Color
            );

            BCGlobalInfo.instance.setMatrix(
                BCGlobalInfo.VIEW_INDEX,
                camera.getViewMatrix(-1)
            );

            BCGlobalInfo.instance.setMatrix(
                BCGlobalInfo.PROJECTION_INDEX,
                camera.getProjectionMatrix(-1)
            );
        }

        protected override void Update(GameTime gameTime)
        {
            double time = gameTime.TotalGameTime.TotalSeconds;

            if (time - lastTime > timeToChange)
            {
                lastTime = time;
                if (isIdle)
                {
                    if (animationSequence.setNextAnimation(idleToRun, run))
                    {
                        isIdle = false;
                    }
                }
                else
                {
                    if (animationSequence.setNextAnimation(runToIdle, idle))
                    {
                        isIdle = true;
                    }
                }
            }

            controller.update(time);

            moveCamera(gameTime);

            //Collision
            Ray currentRay = new Ray(camera.getPosition(), camera.look);
            float? distance = evilMesh.bounding.Intersects(currentRay);

            if (distance != null && distance > 0)
            {
                List<Vector3> intersectedFaces = new List<Vector3>();
                List<float> distances = new List<float>();

                BCPooledLinkedList<BCBonePose> currentPoses = controller.popBonePoses();
                Matrix[] currentPosesTranforms = controller.popFinalMatrices(currentPoses);

                evilCollisionModel.animate(currentPosesTranforms, evilMesh.getTransform());

                controller.pushFinalMatrices(currentPosesTranforms);
                controller.pushBonePoses(currentPoses);

                evilCollisionModel.intersects(currentRay, intersectedFaces, distances);

                showFace = BCCollisionModel.getClosestFace(
                    currentRay,
                    intersectedFaces,
                    distances,
                    currentCollisionFace
                );
            }
        }

        private void moveCamera(GameTime gameTime)
        {

            float df = 0;
            float ds = 0;
            float dy = 0;
            float pitch = 0;
            float angleY = 0;

            float speed = 5f;
#if (XBOX360)
            GamePadState gamepad = GamePad.GetState(PlayerIndex.One);
         
            df = gamepad.ThumbSticks.Left.Y;
            ds = gamepad.ThumbSticks.Left.X;
            pitch = - gamepad.ThumbSticks.Right.Y * 0.05f;
            angleY = gamepad.ThumbSticks.Right.X * 0.05f;

            if (gamepad.IsButtonDown(Buttons.RightShoulder))
            {
                dy += 0.25f;
            }
            if (gamepad.IsButtonDown(Buttons.LeftShoulder))
            {
                dy -= 0.25f;
            }

            speed = new Vector3(df, ds, dy).Length()*10;
#else

            KeyboardState keys = Keyboard.GetState();
            MouseState mouse = Mouse.GetState();

            if (keys.IsKeyDown(Keys.W))
            {
                df += 1;
            }
            if (keys.IsKeyDown(Keys.S))
            {
                df -= 1;
            }
            if (keys.IsKeyDown(Keys.D))
            {
                ds += 1;
            }
            if (keys.IsKeyDown(Keys.A))
            {
                ds -= 1;
            }
            if (keys.IsKeyDown(Keys.Q))
            {
                dy += 1;
            }
            if (keys.IsKeyDown(Keys.E))
            {
                dy -= 1;
            }
            speed = 5f;
            if (keys.IsKeyDown(Keys.LeftControl))
            {
                speed = 10f;
            }

            angleY = ((float)this.Window.ClientBounds.Width / 2.0f - (float)mouse.X) * -0.05f;
            pitch = ((float)this.Window.ClientBounds.Height / 2.0f - (float)mouse.Y) * -0.05f;

            Mouse.SetPosition(this.Window.ClientBounds.Width / 2, this.Window.ClientBounds.Height / 2);

#endif
            camera.update(df, dy, ds, pitch, angleY, speed, (float)gameTime.ElapsedGameTime.TotalSeconds);

            BCGlobalInfo.instance.setMatrix(BCGlobalInfo.VIEW_INDEX, camera.getViewMatrix(-1));
        }

        protected override void Draw(GameTime gameTime)
        {
            //---------------------
            //Draw on Texture
            //---------------------

            device.SetRenderTarget(0, pip);

            graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer | ClearOptions.Stencil,
                Color.Black, 1.0f, 0);

            BCDrawUtil.drawBoundings(evilMesh, device, gameTime);

            BCWireFrameRenderStrategy drawWire = new BCWireFrameRenderStrategy();
            BCDrawOverGeometryRenderStrategy drawOver = new BCDrawOverGeometryRenderStrategy(15);

            drawWire.push(device);
            drawOver.push(device);
            BCDrawUtil.drawCollisionModel(device, evilMesh.transform, Color.LightGreen, evilCollisionModel);
            drawOver.pop(device);
            drawWire.pop(device);

            if (showFace)
            {
                drawOver.push(device);
                BCDrawUtil.drawTriangle(
                    device,
                    currentCollisionFace[0],
                    currentCollisionFace[1],
                    currentCollisionFace[2],
                    Color.Yellow
                );
                drawOver.pop(device);
            }

            device.SetRenderTarget(0, null);

            Texture2D pipTex = pip.GetTexture();

            //---------------------
            //Draw on Back Buffer
            //---------------------

            graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.White, 1.0f, 0);

            evilMesh.Draw(gameTime);
            cursor.draw(gameTime);

            textureDrawer.drawTexture(
                pipTex,
                new Vector2(0.5f, -0.5f),
                new Vector2(0.5f, 0.5f)
            );

            BCLogger.instance.printFPS(gameTime);
            BCLogger.instance.flush();
            base.Draw(gameTime);
        }
    }
}