5 min read

The BasicEffect Class

Introduction

In the previous tutorial, we talked about the BasicEffect class, and how it is used. In this tutorial, we will look at how we can do lighting with a BasicEffect. We will be picking up where we left off with the last tutorial, but you can also start from the code we used in the tutorial on using 3D models.

A Little About Lighting

Light in real life is simply a collection of photons interacting with surfaces. However, in a game, we don’t have the time to model all of the interactions between many photons and all of the surfaces in our game. So instead, a simplified model is often used. With this standard model, there are four types of light that are used, which are discussed below.

Diffuse Light

Diffuse light is the basic kind of light. This is the kind of light that lights an object we are viewing, for the most part. The intensity of the light mostly comes from the angle the surface makes with the light itself, so surfaces that face away from the light don’t aren’t bright at all, while surfaces that face the light are lit up pretty well.

Specular Light

Specular light (or specular highlights) are the shiny spots that appear when an object is somewhat reflective. This light is based on how reflective the surface is, as well as the angle that is being made between the light source, the surface, and the viewer.

Ambient Light

Ambient light is light that doesn’t come from any particular light source, but instead is a kind of “background light” that comes from all over. In the real world, there is always a small amount of ambient light, and in our game, we will want to add a little bit to make our objects look more realistic.

Emissive Light

Emissive light is light that is coming from the surface itself. In games, however, emissive light doesn’t automatically light up nearby objects, so it often doesn’t have the same effect that we would like, but it still has its uses.

Lighting with the BasicEffect Class

Default Lighting

We are going to perform lighting with the BasicEffect class. It is actually pretty easy to do. So let’s go back to our DrawModel() method that we made, and inside the inner loop, where we set the world, view, and projection matrices, add the following line of code:

effect.EnableDefaultLighting();

This method does exactly what is says, and enables some default lighting in our effect, which actually does a lot. You should be able to run your game and see something like the image below:

Screenshot 1

Custom Lighting

While default lighting is pretty easy to do, you’ll probably want to customize the lighting. This is easy enough to do. For instance, the following code will change the diffuse color, direction, and specular color of one (there are three total) of the directional lights built in to the BasicEffect class:

effect.LightingEnabled = true; // turn on the lighting subsystem.
effect.DirectionalLight0.DiffuseColor = new Vector3(0.5f, 0, 0); // a red light
effect.DirectionalLight0.Direction = new Vector3(1, 0, 0);  // coming along the x-axis
effect.DirectionalLight0.SpecularColor = new Vector3(0, 1, 0); // with green highlights

The first line ensures that the lighting system is actually working. Without it, it will look as it did before.

Also, you can turn individual lights on and off with:

effect.DirectionalLight0.Enabled = false;

Also notice that you can set the effect’s ambient light color (remember that it is always a good idea to add a little of this) as well as the effect’s emissive light color:

effect.AmbientLightColor = new Vector3(0.2f, 0.2f, 0.2f);
effect.EmissiveColor = new Vector3(1, 0, 0);
using System;
using System.Collections.Generic;
using System.Linq;
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.Media;

namespace BasicEffectLighting
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        /// <summary>
        /// Stores the model that we are going to draw.
        /// </summary>
        private Model model;

        /// <summary>
        /// Stores the world matrix for the model, which transforms the
        /// model to be in the correct position, scale, and rotation
        /// in the game world.
        /// </summary>
        private Matrix world = Matrix.CreateTranslation(new Vector3(0, 0, 0));

        /// <summary>
        /// Stores the view matrix for the model, which gets the model
        /// in the right place, relative to the camera.
        /// </summary>
        private Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 10), new Vector3(0, 0, 0), Vector3.UnitY);

        /// <summary>
        /// Stores the projection matrix, which gets the model projected
        /// onto the screen in the correct way.  Essentially, this defines the
        /// properties of the camera you are using.
        /// </summary>
        private Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 480f, 0.1f, 100f);


        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            model = Content.Load<Model>("Ship");
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            DrawModel(model, world, view, projection);

            base.Draw(gameTime);
        }

        /// <summary>
        /// Does the work of drawing a model, given specific world, view, and projection
        /// matrices.
        /// </summary>
        /// <param name="model">The model to draw</param>
        /// <param name="world">The transformation matrix to get the model in the right place in the world.</param>
        /// <param name="view">The transformation matrix to get the model in the right place, relative to the camera.</param>
        /// <param name="projection">The transformation matrix to project the model's points onto the screen correctly.</param>
        private void DrawModel(Model model, Matrix world, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    // effect.EnableDefaultLighting();
                    effect.LightingEnabled = true; // Turn on the lighting subsystem.

                    effect.DirectionalLight0.DiffuseColor = new Vector3(1f, 0.2f, 0.2f); // a reddish light
                    effect.DirectionalLight0.Direction = new Vector3(1, 0, 0);  // coming along the x-axis
                    effect.DirectionalLight0.SpecularColor = new Vector3(0, 1, 0); // with green highlights

                    effect.AmbientLightColor = new Vector3(0.2f, 0.2f, 0.2f); // Add some overall ambient light.
                    effect.EmissiveColor = new Vector3(1, 0, 0); // Sets some strange emmissive lighting.  This just looks weird.

                    effect.World = world;
                    effect.View = view;
                    effect.Projection = projection;
                }

                mesh.Draw();
            }
        }

    }
}
Download the completed project