Home > TutorialsComputer Graphics Essentials
2.1. Basic Lightning
In this tutorial you will learn how to add a light to a BetaCell scene. Here you will be introduced to the concept of effect composition.



Click here to go to the forum discussion of this tutorial


 

2.1.1. Prerequisites
default.jpg
Before you start this tutorial make sure that you have:
  • Finished tutorial 1.2.
   
 

2.1.2. The using statements
In this tutorial we will start from the end of tutorial 1.2, the new using statements are:

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

 

Advertisement
 

2.1.3. The Initialize method 
This tutorial introduces the concept of effect composition. There are many ways of composing effects in BetaCell, here we'll describe the one achieved via visitors.

First we create a light:

BCBasicLight light = new BCBasicLight();

The BetaCell.Environment.Light.BCBasicLight is a class that has 3 main purposes:
  • Encapsulate the properties of a light
  • Be the effect feeder for effects that have a lightning part
  • Group the different light types and return them in the form of effect composition parts
Then we apply a translation transform to the light:

light.transform = Matrix.CreateTranslation(10, 10, 10);

The transform has a different meaning depending on the light type, for point and spot, means the position, in the example, the point light is positioned at (10,10,10). For directional lights it describes the direction of the light.

After that we set some light properties, for more information on these check the documentation:

light.range = 200;
light.constAtten = 1;
light.linAtten = 0;
light.cubAtten = 0;
light.spotRange = 0.9f;
light.useShadows = false;


Now that we have the light, lets obtain the effect composition part that adds the point light feature, the parameter is the name of the desired light function. If you want to see other types of light supported by this feeder, take a look at the effect registry section of the config.xml file.

BCFunction lightEffectPart =
                light.getFunction("Shader.Light.Classic.PixelPointLight");


Finally we create a visitor capable of adding the point light feature to a mesh effect

BCDynamicEffectVisitor lightSetter =
                new BCDynamicEffectVisitor(lightEffectPart, light, 1);


Notice that we can create the color set visitor in a similar way:

BCColorSet colorSet = new BCColorSet(Color.Black);
BCFunction colorEffectPart = colorSet.getFunction("Shader.ColorSet");
BCDynamicEffectVisitor colorSetter =
                new BCDynamicEffectVisitor(colorEffectPart, colorSet, 0);


Also notice that since both BCBasicLight and BCColorSet are effect feeders, they have the ability to create effect composition parts of type BetaCell.Effects.Composer.BCFunction, using the getFunction method.

Now, lets discuss the parameters of the BetaCell.Environment.Mesh.Visitors.BCDynamicEffectVisitor constructor.

First it receives the effect composition part to add to each of the mesh's effects (one per mesh part) second it receives the feeder for that effect composition part. The third parameter is the suggested position at which the effect part should be put in the final effect, in this case we set the color to 0 and the lightning to 1, because the base color has to be set before the lightning calculations take place.

The final step is to visit each mesh with both visitors, the color set and the lightning.

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

...

sphere.visit(colorSetter);
sphere.visit(lightSetter);

...

cylinder.visit(colorSetter);
cylinder.visit(lightSetter);


And the rest is just the same as tutorial 1.2.
 

 2.1.4. The Draw method
The following lines from tutorial 1.2 were removed to draw the scene as solid objects:
device.RenderState.FillMode = FillMode.WireFrame;
device.RenderState.FillMode = FillMode.Solid;

 

 2.1.5. Conclusion
As you progress on BetaCell you will be using more composition, it's very important that you understand the concepts explained in this tutorial.

Click here to go to the forum discussion of this tutorial

 

Advertisement
 

1.1.10. 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.Effects.Composer;
using BetaCell.Environment.Light;
using BetaCell.Environment.Mesh;
using BetaCell.Behavior.Procedural;
using BetaCell.Environment;
using BetaCell.Environment.Mesh.Visitors;
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 sphere;
        BCMesh cylinder;
        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(0f, 0.0f, 0.1f, 0f, 1000.0f);

            base.Initialize();
        }

        private void recreateScene()
        {
            BCColorSet colorSet = new BCColorSet(Color.Black);
            //A color effect part
            BCFunction colorEffectPart = colorSet.getFunction("Shader.ColorSet");
            BCDynamicEffectVisitor colorSetter = new BCDynamicEffectVisitor(colorEffectPart, colorSet, 0);

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

            //Obtain a point light composition part
            BCFunction lightEffectPart = light.getFunction("Shader.Light.Classic.PixelPointLight");

            //Create the effect visitor
            BCDynamicEffectVisitor lightSetter = new BCDynamicEffectVisitor(lightEffectPart, light, 1);

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

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

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

            //-------------
            //sphere
            //-------------

            sphere = ProceduralModelers.SPHERE_MODELER.createSlicedSphere(5, 6, 1,
                new BCVertexPosNormalContainer());
            BCMeshGraphicUtil.init(sphere, device);

            sphere.visit(colorSetter);
            sphere.visit(lightSetter);

            //-------------
            //cylinder
            //-------------

            cylinder = ProceduralModelers.CYLINDER_MODELER.createCylinder(5, 6, 1, 1,
                new BCVertexPosNormalContainer());
            BCMeshGraphicUtil.init(cylinder, device);

            cylinder.visit(colorSetter);
            cylinder.visit(lightSetter);
        }


        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 OnActivated(object sender, EventArgs args)
        {
            buildViewMatrix();
            base.OnActivated(sender, args);
        }

        public void dispose()
        {
        }

        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);
            drawCylinders(rotMatrix, gameTime);
            drawSpheres(rotMatrix, gameTime);

            BCLogger.instance.printFPS(gameTime);
            BCLogger.instance.flush();

            base.Draw(gameTime);
        }

        public void drawCylinders(Matrix rotMatrix, GameTime gameTime)
        {
            Matrix r, t;
            //Creates the compose transformation described i the tutorial
            r = Matrix.CreateRotationX((float)Math.PI / 2) * Matrix.CreateTranslation(0, 6, 0);

            //Draws 12 the cylinders
            for (float z = -30; z <= 30; z += 10)
            {
                //Creates the positional translation of the actual cylinder
                t = Matrix.CreateTranslation(-10.0f, 0.0f, z);
                //Sets the cylinder transformation as the composed transformation described in
                //the tutorial multiplied by the
                //positional translation that was just calculated
                cylinder.transform = r * t * rotMatrix;
                cylinder.Draw(gameTime);

                t = Matrix.CreateTranslation(10.0f, 0.0f, z);
                cylinder.transform = r * t * rotMatrix;
                cylinder.Draw(gameTime);
            }
        }

        public void drawSpheres(Matrix rotMatrix, GameTime gameTime)
        {
            Matrix t;
            for (float z = -30; z <= 30; z += 10)
            {
                t = Matrix.CreateTranslation(-10.0f, 8.0f, z);
                sphere.transform = t * rotMatrix;
                sphere.Draw(gameTime);

                t = Matrix.CreateTranslation(10.0f, 8.0f, z);
                sphere.transform = t * rotMatrix;
                sphere.Draw(gameTime);
            }
        }
    }
}