Home > TutorialsComputer Graphics Essentials
2.5. Channel Blend Effect
In this tutorial you will learn how to apply a multi-texture effect that let you organize tillable textures in order to apply them in specific parts of the scene.



Click here to go to the forum discussion of this tutorial


 

2.5.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
  • For a better understanding, is preferred to have finished 2.3.
   
 

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

 

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

 

2.5.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 attributes:

BCMesh plane;
BCProceduralRotator rotator;

 

Advertisement
 

2.5.5. The Initialize method 
Most of the concepts of this initialization method where covered in [2.3.5]; this tutorial focuss in a new texture effect.

default.jpg
The problem that we are trying to solve is how to apply different textures to the same surface to recreate some dirt in the grass or to pass from the ground to a river; generally, rivers don't have grass in the bottom.

These are the textures that we are going to be using in this tutorial, the objective is to have a grass base with the texture in the upper left part, a road with the texture in the lower part and some stones with the texture in the upper right part.

The way to do this is specify areas where each texture has to be applied; notice that there are some areas where the transition from one texture to the other requires more than one texture to be applied at the same time.

default.jpg
The idea is to define a fourth texture that specifies how to apply the other three; we will call it the reference texture. In our particular case, we are going to apply the grass to the red part of the reference texture, the stone to the green part and the road to the blue part. Since the reference texture has a transition from one color to another, for example red -> purple -> blue, the change from grass to road will also have a transition, which is desired.

The code in the tutorial up to the definition of the sampler is the same as 2.3 and 2.4. First the texture feeder is defined:

BCTextureFeeder texFeeder =
                new BCTextureFeeder();




The next step is to add the sampler for the different textures to the texture feeder; notice that the names are gBlendMap for the reference texture and gTex0, gTex1 and gTex2 for the grass, the road and the stone respectively, also remember [2.3.4] that we DON'T SET THE TEXTURES TO THE FEEDER, ONLY THE SAMPLERS, nevertheless, the name of the samplers have to coincide with the names of the textures that we'll define later in the mesh.
 
texFeeder.addSampler("gBlendMap", sampler);
texFeeder.addSampler("gTex0", sampler);
texFeeder.addSampler("gTex1", sampler);
texFeeder.addSampler("gTex2", sampler);

Now we must obtain the effect composition part that represent the channel blend effect:

BCFunction channelBlendPart =
                texFeeder.getFunction("Shader.Texture.ChanelBlend");


Now we create the visitor [1.2.4] that will add this effect part part to the mesh's effect:

int endOfEffect = BCDynamicEffect.MAX_FUNCTIONS;
BCDynamicEffectVisitor textureEffectSetter =
        new BCDynamicEffectVisitor(channelBlendPart, texFeeder, endOfEffect);

Notice the endOfEffect variable; in BetaCell we can choose the order of the parts in the final effect; the BCDynamicEffect.MAX_FUNCTIONS is a constant of the BetaCell.Effects.BCDynamicEffect that indicates "this is the last part to apply to the final effect", which makes sense because we would like the lightning calculations to take place before the texturing ones.

We are now ready to load the textures and set them to the plane, for that we create a visitor per texture:

Texture2D reference = content.Load<Texture2D>("Content/gameAssets/channelsR");
BCMemoryTextureStrategy referenceStrategy =
                    new BCMemoryTextureStrategy(reference);

BCTextureVisitor blendMapTex =
                    new BCTextureVisitor("gBlendMap", referenceStrategy);


Another way to define a visitor in a single statement (more confusing but faster):

BCTextureVisitor grassTex = new BCTextureVisitor(
    "gTex0",
    new BCMemoryTextureStrategy(
                    content.Load<Texture2D>("Content/gameAssets/grass"))

);

Notice that both definitions are the same code; just that the last one defines everything inline.

Also, the scale of each texture in the effect can be set with the following line.

gassTex.strategy.setScale(new Vector2(5, 5));

After all the preparations, the only thing left is to visit the plane:

plane.visit(blendMapTex);
plane.visit(grassTex);
plane.visit(roadTex);
plane.visit(stoneTex);

plane.visit(materialVisitor);
plane.visit(colorSetter);
plane.visit(lightSetter);
plane.visit(textureEffectSetter);

 

 2.5.6. The Draw method
Nothing new, just draw the plane:

plane.Draw(gameTime);

 

 2.5.7. Conclusion
This tutorial is almost the same as 2.3 and 2.4, it's purpose is to show the blend map effect. This technique is very useful when you have a large terrain.

Click here to go to the forum discussion of this tutorial

 

Advertisement
 

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

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 plane;
        BCProceduralRotator rotator;

        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();
            rotator = new BCProceduralRotator(0, 0.0f, 0.1f, 0f, 1000.0f);

            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
            //Adding a sampler per expected texture of the channel blend effect part
            BCTextureFeeder texFeeder = new BCTextureFeeder();
            texFeeder.addSampler("gBlendMap", sampler);
            texFeeder.addSampler("gTex0", sampler);
            texFeeder.addSampler("gTex1", sampler);
            texFeeder.addSampler("gTex2", sampler);

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

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

            //-------------
            //plane
            //-------------

            plane = ProceduralModelers.PLANE_MODELER.createPlane(4, 4, 25, 25, new Vector3(0, 0, 0),
                new BCVertexPosNorTexContainer(), 1);
            BCMeshGraphicUtil.init(plane, device);

            //Setting texture
            Texture2D reference = content.Load<Texture2D>("Content/gameAssets/channelsR");
            BCMemoryTextureStrategy referenceStrategy = new BCMemoryTextureStrategy(reference);
            BCTextureVisitor blendMapTex = new BCTextureVisitor("gBlendMap", referenceStrategy);

            //Alternate way to initiate the texture in a single definition
            BCTextureVisitor grassTex = new BCTextureVisitor(
                "gTex0",
                new BCMemoryTextureStrategy(content.Load<Texture2D>("Content/gameAssets/grass"))
            );
            grassTex.strategy.setScale(new Vector2(5, 5));

            BCTextureVisitor roadTex = new BCTextureVisitor(
                "gTex1",
                new BCMemoryTextureStrategy(content.Load<Texture2D>("Content/gameAssets/stone"))
            );
            roadTex.strategy.setScale(new Vector2(5, 5));

            BCTextureVisitor stoneTex = new BCTextureVisitor(
                "gTex2",
                new BCMemoryTextureStrategy(content.Load<Texture2D>("Content/gameAssets/road"))
            );
            stoneTex.strategy.setScale(new Vector2(5, 5));

            plane.visit(blendMapTex);
            plane.visit(grassTex);
            plane.visit(roadTex);
            plane.visit(stoneTex);

            plane.visit(materialVisitor);
            plane.visit(colorSetter);
            plane.visit(lightSetter);
            plane.visit(textureEffectSetter);
        }

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

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

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

            Matrix rotMatrix = rotator.getRotationMatrix(gameTime);

            plane.transform = rotMatrix;
            plane.Draw(gameTime);

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