Home > TutorialsComputer Graphics Essentials
2.3. Creating a box with a texture
In this tutorial you will create a box with a crate texture, not the most original tutorial but a very useful one.



Click here to go to the forum discussion of this tutorial


 

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

2.3.2. Game Assets 
This is a new section of the tutorials; the game assets are files that the application needs in order to give the user the desired experience. For example, in this tutorial, the crate texture is a game asset.

First download the crate texture (right click + save as): Crate Texture Download

default.jpg
Remember the location where you saved the crate texture; now in the solution explorer of the starter project (left side of the starter tutorial), you will find the starter project, inside you will find the Content of the project, inside the content you will find a folder named "gameAssets".

Right click the game assets > click Add > click Existing Item.

Browse to the place where you saved the crate texture, select it and click the Add button.

You can see that the texture is now one of the assets of the project, there are many interesting things you can explore in the content options, for example you can make XNA create the mip maps of the texture.



 

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

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

 

2.3.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:

BCMesh cube;

 

Advertisement
 

2.3.5. The Initialize method 
This Initialize method can get a little long, also to do this kind of initialization to all the objects in a scene is not practical. In an advanced application you will create the objects in a content creation tool like Blender and load the objects ready to be drawn (which is exactly what will be done in a later tutorial!).

You can take a look at the initialization of the base color and lightning effect composition parts at [1.1.6] and [2.1.3]. To learn about the creation of a cube, see [1.3.4].

default.jpg
Now we will discuss what a material is and how to create it. The main components of a material are diffuse, specular and ambient. The diffuse component of a material reacts to light that 'hit' the object directly, in the figure the diffuse component of the material is blue. The specular component of a material is that shinny little spot that reflects light directly to the camera, in the figure, this component is red. Finally, the ambient component is the light that has bounced in the scene so many times, that is impossible to tell where it came from; in the figure, this component is green. Notice that the part of the sphere not illuminated by the light is green, the directly illuminated is blue and the one that reflects the light most directly to the camera is red.

A material like the one in the figure is very rare; it was created to explain what a material is. 

In general, all three components of a material tend to have the same color. The call that constructs the material in this tutorial is as follows:

BCMaterialBase material = new BCMaterialBase();
material.specRange = 0.8f;
material.diff = Color.White;
material.spec = Color.White;
material.amb = Color.LightGray;

It's a nice white base to apply the texture on. In order to apply the material, the following visitor [1.2.4] is created:

BCMaterialVisitor materialVisitor = new BCMaterialVisitor(material);

After the material visitor, the color setter visitor [1.2.4] and the light setter visitor [2.1.3] are created.

Now let's talk about texture coordinates.

default.jpg
Remember that a vertex can have multiple information? well, a kind of information that a vertex can have are texture coordinates.

In the upper left part of the figure we have a 2D image, this is the texture that we are willing to apply. Notice that the positions of the vertices won't work for us to describe how to apply the texture to the face because they are in 3D (X,Y and Z) and the texture is in 2D, so we need to specify how to apply the texture in 2D.

The way to do that is to specify a new set of information per vertex called the texture coordinate; that has a U and a V component. The U component goes from left to right and the V component goes from up to down. Both U and V range from 0 to 1. A pixel in a texture described by a UV pair is called a texel.   

In the figure, the UV coordinates for vertex 0 are (0.5,0) because is in the middle of the image from left to right (U=0.5) and in the beginning of the image from top to bottom (V=0). vertex 1 and 2 have (0,1) and (1,1) texture coordinates respectively.

Now that we understand what texture coordinates are, let's discuss the sampler object.

default.jpg
The sampler is the responsible of returning a color given a texture coordinate. What you need to know is that depending on how you construct the sampler, the end results will vary.

Once a triangle with a texture is drawn in the screen, we can have the scenario where the drawn triangle is bigger than the texture that is applied to it; creating ugly artifacts like the ones shown in the upper left part of the figure.

That's where the definition of the sampler can help us; we can create a sampler that returns a color by averaging the neighbor texels of the resultant texel, creating a smooth transition like the one shown in the lower right part of the figure.

The code that creates the sampler is:

BCSampler sampler = new BCSampler(
    "LINEAR", "LINEAR", "LINEAR", "4",
    "WRAP", "WRAP", "0xffffffff"
);

A detailed discussion of the parameters is out of the scope of this tutorial and can be found in "Introduction to 3D Game Programming, A Shader Approach" by Frank Luna, for the experienced reader, the first parameter is the minification filter, the second is the magnification filter, the third is the mipmap filter, the fourth is the anisotropic level, the fifth is the address mode in the U coordinate, the sixth is the address mode in the V coordinate and the last one is the color of the border. 

A more casual reader should know that minification means when the texture in greater than the resultant triangle, magnification means the opposite, also that the filters are POINT (no filtering, fast), LINEAR normal filtering and Anisotropic which is a filter that helps viewing the ground but requires more computation (slow).

Now, we must create the texture effect. The effect feeder for texture effects is created with the following line:

BCTextureFeeder texFeeder = new BCTextureFeeder();

Now we need to associate the sampler with the effect; this is done with this line:

texFeeder.addSampler("tex", sampler);

Notice that we add the sampler with the name 'tex', that's because the  basic texture effect, which is the one that we will be using, expects the texture to have that name.

The next step is to create the effect composition part that represents basic texturing, which is obtained from the feeder [1.1.6] in the following way:

BCFunction texturePart = texFeeder.getFunction("Shader.Texture.Texture");

Now we create the visitor [1.2.4] to apply the texture to a mesh's effect.

BCDynamicEffectVisitor textureEffectSetter = new BCDynamicEffectVisitor(
        texturePart, texFeeder, BCDynamicEffect.MAX_FUNCTIONS);
 

Now we are going to create the cube [1.3.4].

cube = ProceduralModelers.CUBE_MODELER.createCube(
                            10, 10, 10, new BCVertexPosNorTexContainer());

BCMeshGraphicUtil.init(cube, device);

Now we are going to load the texture. In BetaCell the mesh has the responsibility of holding it's textures, so instead of setting the texture to the effect, we are going to set a texture in the mesh with the name 'tex' which is the name expected by the effect and the feeder.

First we must load the texture:

Texture2D crateTexture = content.Load<Texture2D>("Content/gameAssets/crate");

This is the way textures are loaded in XNA, note that the path starts relative to the content folder and that there's no extension at the end of the file name (crate instead of crate.jpg).

Then we create a texture strategy with the texture; texture strategies are an advanced topic that are not going to be covered by this particular tutorial, you can think of them as a generic representation of a texture or take a look at the documentation.

BCTextureStrategy memory = new BCMemoryTextureStrategy(crateTexture);

After that, we create a visitor [1.2.4] that sets the texture to all the parts of the mesh. Notice that the constructor of the visitor takes the name that the texture will have in the mesh and the texture strategy.

BCTextureVisitor crateTex = new BCTextureVisitor("tex", memory);

At this point we have a bunch of visitors, all that's left is apply them all to the cube.

cube.visit(crateTex);
cube.visit(materialVisitor);
cube.visit(colorSetter);
cube.visit(lightSetter);
cube.visit(textureEffectSetter);

 

2.3.6. The Draw method
Nothing new, just draw the cube:

cube.Draw(gameTime);

 

2.3.7. Conclusion
This tutorial covered a lot of ground; it explained some generic concepts of texturing, as well as XNA and BetaCell specific texture code. When you create a mesh with a content creation tool, all the work that we performed in this tutorial will have already been done.

Click here to go to the forum discussion of this tutorial

 

Advertisement
 

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

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 cube;

        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);

            recreateScene();

            base.Initialize();
        }

        private void recreateScene()
        {
            //Creating a base material for the cube
            BCMaterialBase material = new BCMaterialBase();
            material.specRange = 0.8f;
            material.diff = Color.White;
            material.spec = Color.White;
            material.amb = Color.LightGray;
            BCMaterialVisitor materialVisitor = new BCMaterialVisitor(material);

            //Color set effect composition part
            BCColorSet colorSet = new BCColorSet(Color.Black);
            BCFunction baseColor = colorSet.getFunction("Shader.ColorSet");
            BCDynamicEffectVisitor colorSetter = new BCDynamicEffectVisitor(baseColor, colorSet, 0);

            //Light effect composition part
            BCBasicLight light = new BCBasicLight();
            light.range = 200;
            light.transform = Matrix.CreateTranslation(20, 20, 10);
            light.constAtten = 1;
            light.linAtten = 0;
            light.cubAtten = 0;
            light.spotRange = 0.9f;
            light.useShadows = false;

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

            //Creating a sampler for the texture
            BCSampler sampler = new BCSampler(
                "LINEAR", "LINEAR", "LINEAR", "4",
                "WRAP", "WRAP", "0xffffffff"
            );

            //Creating a texture feeder for the texture
            BCTextureFeeder texFeeder = new BCTextureFeeder();
            texFeeder.addSampler("tex", sampler);

            //Getting the texture effect part function from the texture feeder
            BCFunction texturePart = texFeeder.getFunction("Shader.Texture.Texture");

            //Creating the texture effect setter visitor
            BCDynamicEffectVisitor textureEffectSetter =
                new BCDynamicEffectVisitor(texturePart, texFeeder, BCDynamicEffect.MAX_FUNCTIONS);

            //-------------
            //cube
            //-------------

            cube = ProceduralModelers.CUBE_MODELER.createCube(10, 10, 10, new BCVertexPosNorTexContainer());
            BCMeshGraphicUtil.init(cube, device);

            //Loading the texture
            Texture2D crateTexture = content.Load<Texture2D>("Content/gameAssets/crate");

            //Creating a texture strategy from the texture
            BCTextureStrategy memory = new BCMemoryTextureStrategy(crateTexture);

            //Setting texture to the mesh
            BCTextureVisitor crateTex = new BCTextureVisitor("tex", memory);

            //Applying visitors
            cube.visit(crateTex);
            cube.visit(materialVisitor);
            cube.visit(colorSetter);
            cube.visit(lightSetter);
            cube.visit(textureEffectSetter);

        }

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

        void buildViewMatrix()
        {
            Vector3 pos = new Vector3(20, 10, 20);
            Vector3 look = new Vector3(-2, -1, -2);
            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)
        {
        }

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.White, 1.0f, 0);

            cube.Draw(gameTime);

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