Home > TutorialsTechniques
4.9. Projective Texturing 
In this tutorial you will learn how to apply projective texturing using BetaCell.



Click here to go to the forum discussion of this tutorial


 

4.9.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
 

4.9.2. Game Assets 
Game assets [2.3.2] for this tutorial:

 

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

using BetaCell.Environment.Mesh;
using BetaCell.Util.Procedural;
using BetaCell.Common.Vertex;
using BetaCell.Environment.Light.Projection;
using BetaCell.Environment.Mesh.Visitors;
using BetaCell.Effects;
using BetaCell.Environment.Light;
using BetaCell.Util.RenderStates;
using BetaCell.Environment.Texture.Feeder;
using BetaCell.Environment;


 

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

BCMesh plane;
BCFirstPersonHumanCamera lightCamera;
BCProjection projection;

 

4.9.5. Projective Texturing
default.jpg
The objective of this tutorial is to project a texture into a plane like a movie is projected into a screen in a theater.

In a movie theater, you can see a beam of light coming out of the projection chamber, that light creates a volume as shown in the upper part of the figure. Anything inside that volume presents the content of the projection; ideally, in a movie theater, the only object intersecting this volume should be the screen.

The projection volume can easily be represented by a frustum [3.4.3], for example, the camera on the lower part of the figure has a frustum with the same shape as the projection volume of the upper part.


default.jpg
We are going to project the texture in 2 over the plane as shown in 1. In 1 you can see the how the texture is projected.

In 3 you can see the plane with the texture projected on it. The idea of projective texturing is to find texture coordinates for each of the vertices of a mesh.

The texture coordinates shown in 2 can be obtained by taking each of the vertices in 1, identify the pixel of the texture to which they correspond and assign the texture coordinate of that pixel. For example, in 1 and 3, we can see that v1 is mapped to the upper left corner of the beta symbol of the image.
 

default.jpg
Of course the computer doesn't know about the upper left corner of the beta symbol, so what we do is to see what the projector sees.

For that, we pretend that the camera is the projector (lower part of the figure) and find where in the 2D screen will a vertex end up after it is transformed from 3D world (upper part of the figure) space to 2D screen space (lower part of the figure).

Assuming that the 2D screen space is defined in the [0,0] to [1,1] interval, that [0,0] is the upper left corner and that [1,1] is the lower right corner, we can use the 2D screen coordinate of the vertex as the texture coordinate. 

 

Advertisement
 

4.9.6. The Initialize method 
First we define a camera [1.1.7][3.4.3] that will represent the projection volume:

lightCamera = new BCFirstPersonHumanCamera(new Vector3(0, 0, -1),
    new Vector3(0, 0, 25),

    MathHelper.PiOver4 / 2, 1, 1, 30);

Then, we will create the projection effect:

projection = new BCProjection(
    content.Load<Texture2D>("Content/gameAssets/betaCell"),
    new BCSampler("LINEAR", "LINEAR", "LINEAR",
    "4", "WRAP", "WRAP", "0xffffff"),

    1, lightCamera.getViewMatrix(-1) * lightCamera.getProjectionMatrix(-1)
);

The first parameter is the texture being projected, the second is a sampler, the third is the projection factor; you can think of it as how strong is the projection, the last one is the projection matrix; this is obtained from the light camera as shown above.

Finally we create the effect visitor, the function is BCProjection.PROJECTOR and the feeder is the projector itself:

BCDynamicEffectVisitor projectionSetter = new BCDynamicEffectVisitor(
    projection.getFunction("Shader.Light.Projection.Projection"), projection,
    BCDynamicEffect.MAX_FUNCTIONS

);


 

4.9.7. The Update method
The moveCamera method was modified so that when the left shift is pressed, you will see what the projector sees. You will also be able to move the projector by updating it's projection matrix with the light camera.

KeyboardState keys = Keyboard.GetState();
if (keys.IsKeyDown(Keys.LeftShift))
{
    moveCamera(gameTime, lightCamera);
    projection.lightProjection =
        lightCamera.getViewMatrix(-1) * lightCamera.getProjectionMatrix(-1);
}
else
{
    moveCamera(gameTime, camera);
}


 

4.9.8. The Draw method
We are going to draw the light camera (that represents the projector) as a wireframe render strategy:
 
BCWireFrameRenderStrategy drawWire = new BCWireFrameRenderStrategy();

plane.Draw(gameTime);

drawWire.push(device);
lightCamera.Draw(device, gameTime);
drawWire.pop(device);

 

4.9.9. Conclusion
This tutorial is another example of the benefits of using BetaCell on your XNA project. You added the projective texturing effect with a mesh visitor without rewriting your shaders.

Click here to go to the forum discussion of this tutorial
 
 

Advertisement
 

4.9.10. Complete source code
using System;
using System.Collections.Generic;
using System.Threading;

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.Mesh;
using BetaCell.Util.Procedural;
using BetaCell.Common.Vertex;
using BetaCell.Environment.Mesh.Visitors;
using BetaCell.Effects;
using BetaCell.Environment.Light;
using BetaCell.Util.RenderStates;
using BetaCell.Environment.Texture.Feeder;
using BetaCell.Environment;
using BetaCell.Environment.Texture.Projection;

namespace Starter
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        //Game attributes
        GraphicsDeviceManager graphics;
        GraphicsDevice device;
        ContentManager content;

        BCFirstPersonHumanCamera camera;
        //end game attributes


        BCMesh plane;
        BCFirstPersonHumanCamera lightCamera;
        BCProjection projection;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8;
            content = new ContentManager(Services);

#if(XBOX360)
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 768;
#else
            graphics.PreferredBackBufferWidth = 852;
            graphics.PreferredBackBufferHeight = 480;
#endif
        }

        protected override void Initialize()
        {
            device = graphics.GraphicsDevice;
            BCLogger.instance.init(device, content);
            BCInitializationManager.initialize(content);

            OnActivated(null, null);

            //----------------------
            //Creating Plane Mesh
            //----------------------

            plane = ProceduralModelers.PLANE_MODELER.createPlane(2, 2, 8, 5, Vector3.Zero,
                new BCVertexPosNorTexContainer(), 1);
            BCMeshGraphicUtil.init(plane, device);
            plane.transform = Matrix.CreateRotationX((float)(Math.PI / 10));

            //---------------------------------
            //Setting the base color effect
            //---------------------------------

            //Base black color for the lines
            BCColorSet blackBase = new BCColorSet(Color.Black);
            //Effect representation
            BCDynamicEffectVisitor colorSetter =
                new BCDynamicEffectVisitor(blackBase.getFunction("Shader.ColorSet"), blackBase, 0);

            plane.visit(colorSetter);

            //---------------------------------
            //Assigning material to the plane
            //---------------------------------

            BCMaterialBase white = new BCMaterialBase();
            byte hCol = 155;
            byte lCol = 64;

            white.diff = new Color(hCol, hCol, hCol, 1);
            white.spec = new Color(hCol, hCol, hCol, 1);
            white.amb = new Color(lCol, lCol, lCol, 1);
            white.specRange = 0.1f;
            white.emit = Color.Black;

            BCMaterialVisitor materialSetter = new BCMaterialVisitor(white);

            plane.visit(materialSetter);

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

            BCBasicLight light = new BCBasicLight();
            light.range = 3000;
            light.transform = Matrix.CreateTranslation(12.5f, 12.5f, 0);
            light.cubAtten = 0;
            light.constAtten = 0.4f;
            light.linAtten = 0.05f;

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

            plane.visit(lightSetter);

            //----------------------------
            //Preparing projection effect
            //----------------------------

            lightCamera = new BCFirstPersonHumanCamera(new Vector3(0, 0, -1), new Vector3(0, 0, 25),
                MathHelper.PiOver4 / 2, 1, 1, 30);

            projection = new BCProjection(
                content.Load<Texture2D>("Content/gameAssets/betaCell"),
                new BCSampler("LINEAR", "LINEAR", "LINEAR", "4", "WRAP", "WRAP", "0xffffff"),
                1,
                lightCamera.getViewMatrix(-1) * lightCamera.getProjectionMatrix(-1)
            );

            BCDynamicEffectVisitor projectionSetter = new BCDynamicEffectVisitor(
                projection.getFunction("Shader.Texture.Projection"), projection, BCDynamicEffect.MAX_FUNCTIONS
            );

            plane.visit(projectionSetter);

            base.Initialize();
        }

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

        void buildViewMatrix()
        {
            Vector3 pos = new Vector3(35, 10, 40);
            Vector3 look = new Vector3(-3, -1, -3);
            look.Normalize();

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

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

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

        protected override void Update(GameTime gameTime)
        {
#if (XBOX360)
            GamePadState gamepad = GamePad.GetState(PlayerIndex.One);
            if (gamepad.IsButtonDown(Buttons.A))
            {
                moveCamera(gameTime, lightCamera);
                projection.lightProjection = lightCamera.getViewMatrix() * lightCamera.getProjectionMatrix();
            }
            else
            {
                moveCamera(gameTime, camera);
            }
#else
            KeyboardState keys = Keyboard.GetState();
            if (keys.IsKeyDown(Keys.LeftShift))
            {
                moveCamera(gameTime, lightCamera);
                projection.lightProjection = lightCamera.getViewMatrix(-1) * lightCamera.getProjectionMatrix(-1);
            }
            else
            {
                moveCamera(gameTime, camera);
            }
#endif
        }

        private void moveCamera(GameTime gameTime, BCFirstPersonHumanCamera camera)
        {

            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)
        {
            graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,
                Color.LightGray, 1.0f, 0);

            BCWireFrameRenderStrategy drawWire = new BCWireFrameRenderStrategy();

            plane.Draw(gameTime);

            drawWire.push(device);
            lightCamera.Draw(device, gameTime);
            drawWire.pop(device);

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