XNA for C#
DirectX 9 for C#
DirectX 9 for C++
DirectX 9 for VB
Forum
   July 19: Series4 - 2 Pass Renderstates
My Book: Out Now!
      
       Go to section on this site

Additional info


Latest Forum posts

 WinForm not found
  Posted by: riemer
  When: 20/07/2008 at 14:30:03

 Bounding Boxes for Maya Data
  Posted by: riemer
  When: 20/07/2008 at 14:27:31

 QuadTree
  Posted by: riemer
  When: 20/07/2008 at 14:23:48

 Bounding Boxes for Maya Data
  Posted by: vijay
  When: 20/07/2008 at 12:25:40

 Error on compiling
  Posted by: libia
  When: 20/07/2008 at 02:36:16

 Terrain From RAW
  Posted by: reiko
  When: 19/07/2008 at 20:36:39

 WinForm not found
  Posted by: kapil.rajak
  When: 19/07/2008 at 16:24:52

 human skin colors
  Posted by: aguacate
  When: 19/07/2008 at 13:34:55

 Bounding Boxes for Maya Data
  Posted by: Archenon
  When: 19/07/2008 at 09:19:16

 Volunteer to write VB.net tutorial
  Posted by: riemer
  When: 19/07/2008 at 07:00:19


Ads

Experimenting with Lights in XNA

Even when using colors and a Z buffer, your terrain seems to miss some depth detail when you turn on the Solid FillMode. By adding some lighting, it will look much better. This chapter we will see the impact of a light on 2 simple triangles, so we can have a better understanding of how lights work in XNA. We will be using the code from the ‘World space’ chapter, so reload that code now.

This chapter, we will be using a directional light. Imagine this as the sunlight: the light will travel in one particular direction. To calculate the effect of light hitting a triangle, XNA needs another input: the 'normal' in every vertex. Consider next figure:



If you have a light source a), and you shine it on the shown 3 surfaces, how is XNA supposed to know that surface 1 should be lit more intensely than surface 3? If you look at the thin red lines in figure b), you'll notice that their length is a nice indication of how much light you would want to be reflected (and thus seen) on every surface. So how can we calculate the length of these lines? Actually, XNA does the job for us. All we have to do is give the blue arrow perpendicular (with an angle of 90 degrees, the thin blue lines) to every surface and XNA does the rest (a simple cosine projection) for us!

This is why we need to add normals (the perpendicular blue lines) to our vertex data. The VertexPositionColor will no longer do, and unfortunately, XNA does not offer a structure that can contain a position, a color and a normal. But that’s no problem, we can easily create one of our own. Let’s put this code at the top of our class, immediately above our variable declarations:

 public struct VertexPositionNormalColored
 {
     public Vector3 Position;
     public Color Color;
     public Vector3 Normal;
 
     public static int SizeInBytes = 7 * 4;
     public static VertexElement[] VertexElements = new VertexElement[]
     {
         new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
         new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0 ),
         new VertexElement( 0, sizeof(float) * 4, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
     };
 }

This might look complicated, but I’m sure you understand the first 3 lines: this new struct can hold a postion, a color and a normal; exactly what we need! The bottom of the struct is a bit more complex, and we’ll discuss it in detail in Series 3.

This allows us to change our vertex variable declaration:

 VertexPositionNormalColored[] vertices;

Now we could start defining triangles with normals. But first, let’s have a look at the next picture, where the arrows at the top represent the direction of the light and the color bar below the drawing represents the color of every pixel along our surface:



If we simply define the perpendicular vectors, it is easy to see there will be an 'edge' in the lighting (see the bar directly above the a)). This is because the right surface is lit more then the left surface. So it will be easy to see the surface is made of separate triangles. However, if we place in the shared top vertex a 'normal' as shown in figure b), XNA automatically interpolates the lighting in every point of our surface! This will give a much smoother effect, as you can see in the bar above the b). This vector is the average of the 2 top vectors of a).

As always, the average of 2 vectors can be found by summing them and by dividing them by two.

To demonstrate this example, we will first reset the camera position:

 viewMatrix = Matrix.CreateLookAt(new Vector3(0, -40, 100), new Vector3(0, 50, 0), new Vector3(0, 1, 0));

Next, we will update our SetUpVertices method with 6 vertices that define the 2 triangles of the example above:

 private void SetUpVertices()
 {
     vertices = new VertexPositionNormalColored[6];
 
     vertices[0].Position = new Vector3(0f, 0f, 50f);
     vertices[0].Color = Color.Blue;
     vertices[0].Normal = new Vector3(1, 0, 1);
     vertices[1].Position = new Vector3(50f, 0f, 00f);
     vertices[1].Color = Color.Blue;
     vertices[1].Normal = new Vector3(1, 0, 1);
     vertices[2].Position = new Vector3(0f, 50f, 50f);
     vertices[2].Color = Color.Blue;
     vertices[2].Normal = new Vector3(1, 0, 1);
 
     vertices[3].Position = new Vector3(-50f, 0f, 0f);
     vertices[3].Color = Color.Blue;
     vertices[3].Normal = new Vector3(-1, 0, 1);
     vertices[4].Position = new Vector3(0f, 0f, 50f);
     vertices[4].Color = Color.Blue;
     vertices[4].Normal = new Vector3(-1, 0, 1);
     vertices[5].Position = new Vector3(0f, 50f, 50f);
     vertices[5].Color = Color.Blue;
     vertices[5].Normal = new Vector3(-1, 0, 1);
 
     for (int i = 0; i < vertices.Length; i++)
         vertices[i].Normal.Normalize();
             
     myVertexDeclaration = new VertexDeclaration(device, VertexPositionNormalColored.VertexElements);
 }

This defines the 2 surfaces of the picture above. By adding a Z coordinate (other than 0), the triangles are now 3D. You can notice that I’ve defined the normal vectors perpendicular to the triangles, as to reflect example a) of the image above.

At the end, we ‘normalize our normals’ . These 2 words have absolutely nothing to do with each other. It means we scale our normal vectors so their lengths become exactly one. This is required for correct lighting, as the length of the normals has an impact on the amount of lighting.

Also note that we have to update the VertexDeclaration, as the vertices we’re sending over to our graphics card now contains extra data.

All there's left to do is change the Draw method a bit:

 device.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 2);

Which draws the 2 triangles defined in our vertices array. When you run this code, you should see an arrow (our 2 triangles), but you don’t see any depth because we haven’t yet defined the light! We can define this by setting these additional parameters of our effect:

 effect.Parameters["xEnableLighting"].SetValue(true);
 Vector3 lightDirection = new Vector3(0.5f, 0, -1.0f);
 lightDirection.Normalize();
 effect.Parameters["xLightDirection"].SetValue(lightDirection);

This instructs our technique to enable lighting calculations (now the technique needs the normals), and we set the direction of our light. Note again that you need to normalize this vector, so its lengths becomes one. You might also want to change the background color to black, so you get a better view.

Now run this code and you'll see what I mean with 'edged lighting': the light shines brightly on the left panel and the right panel is darker. You can clearly see the difference between the two triangles! This is what was shown in the left part of the example image above.

Now it's time to combine the vectors on the edge that is shared by the 2 triangles from (-1,0,1) and (1,0,1) to (-1+1,0,1+1)/2 = (0,0,1):

 vertices[0].Position = new Vector3(0f, 0f, 50f);
 vertices[0].Color = Color.Blue;
 vertices[0].Normal = new Vector3(0, 0, 1);
 vertices[1].Position = new Vector3(50f, 0f, 00f);
 vertices[1].Color = Color.Blue;
 vertices[1].Normal = new Vector3(1, 0, 1);
 vertices[2].Position = new Vector3(0f, 50f, 50f);
 vertices[2].Color = Color.Blue;
 vertices[2].Normal = new Vector3(0, 0, 1);
 
 vertices[3].Position = new Vector3(-50f, 0f, 0f);
 vertices[3].Color = Color.Blue;
 vertices[3].Normal = new Vector3(-1, 0, 1);
 vertices[4].Position = new Vector3(0f, 0f, 50f);
 vertices[4].Color = Color.Blue;
 vertices[4].Normal = new Vector3(0, 0, 1);
 vertices[5].Position = new Vector3(0f, 50f, 50f);
 vertices[5].Color = Color.Blue;
 vertices[5].Normal = new Vector3(0, 0, 1);

When you run this code, you'll see that the reflection is nicely distributed from the dark right tip to the brighter left panel. It's not difficult to imagine that this effect will give a much nicer and smoother effect on a large number of triangles, such as our terrain.




DirectX Tutorial 12 - Lighting basics

If you appreciate the amount of time I spend creating and updating
these pages, feel free to donate -- any amount is welcome !



Click here to go to the forum on this chapter!

Or click on one of the topics on this chapter to go there:
  • human skin colors
          I have some human 3D models in my game. I'm using...
  • Size of color data type?
          Hi, From the vertice structure and SizeInBytes ...



    Also, you can see that since the middle vertices use the SAME normal, we could again combine the 2x2 shared vertices to 2x1 vertices using an index buffer. Notice however, that in the case you really WANT to create an edge, you need to specify the 2 separate normal vectors.

    You can try these exercises to practice what you've learned:
  • Try moving the normals a bit to the left and right in both the separate and shared case.
    Your 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;
     
     namespace XNAtutorial
     {
         public class Game1 : Microsoft.Xna.Framework.Game
         {
             public struct VertexPositionNormalColored
             {
               public Vector3 Position;
                 public Color Color;
                 public Vector3 Normal;
     
                 public static int SizeInBytes = 7 * 4;
                 public static VertexElement[] VertexElements = new VertexElement[]
                 {
                     new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
                     new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0 ),
                     new VertexElement( 0, sizeof(float) * 4, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
                 };
             }
     
             GraphicsDeviceManager graphics;
             SpriteBatch spriteBatch;
             GraphicsDevice device;
             Effect effect;
             VertexPositionNormalColored[] vertices;
             VertexDeclaration myVertexDeclaration;
     
             Matrix viewMatrix;
             Matrix projectionMatrix;
     
             public Game1()
             {
                 graphics = new GraphicsDeviceManager(this);
                 Content.RootDirectory = "Content";
             }
     
             protected override void Initialize()
             {
                 graphics.PreferredBackBufferWidth = 500;
                 graphics.PreferredBackBufferHeight = 500;
                 graphics.IsFullScreen = false;
                 graphics.ApplyChanges();
                 Window.Title = "Riemer's XNA Tutorials -- Series 1";
     
                 base.Initialize();
             }
     
             protected override void LoadContent()
             {
                 device = graphics.GraphicsDevice;
                 spriteBatch = new SpriteBatch(GraphicsDevice);
     

                effect = Content.Load<Effect> ("effects");
                SetUpVertices();
                SetUpCamera();
            }

            private void SetUpVertices()
            {

                 vertices = new VertexPositionNormalColored[6];
     
                 vertices[0].Position = new Vector3(0f, 0f, 50f);
                 vertices[0].Color = Color.Blue;
                 vertices[0].Normal = new Vector3(0, 0, 1);
                 vertices[1].Position = new Vector3(50f, 0f, 00f);
                 vertices[1].Color = Color.Blue;
                 vertices[1].Normal = new Vector3(1, 0, 1);
                 vertices[2].Position = new Vector3(0f, 50f, 50f);
                 vertices[2].Color = Color.Blue;
                 vertices[2].Normal = new Vector3(0, 0, 1);
     
                 vertices[3].Position = new Vector3(-50f, 0f, 0f);
                 vertices[3].Color = Color.Blue;
                 vertices[3].Normal = new Vector3(-1, 0, 1);
                 vertices[4].Position = new Vector3(0f, 0f, 50f);
                 vertices[4].Color = Color.Blue;
                 vertices[4].Normal = new Vector3(0, 0, 1);
                 vertices[5].Position = new Vector3(0f, 50f, 50f);
                 vertices[5].Color = Color.Blue;
                 vertices[5].Normal = new Vector3(0, 0, 1);
     
                 for (int i = 0; i < vertices.Length; i++)
                     vertices[i].Normal.Normalize();
     
                 myVertexDeclaration = new VertexDeclaration(device, VertexPositionNormalColored.VertexElements);
             }
     
             private void SetUpCamera()
             {
                 viewMatrix = Matrix.CreateLookAt(new Vector3(0, -40, 100), new Vector3(0, 50, 0), new Vector3(0, 1, 0));
                 projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 1.0f, 300.0f);
             }
     
             protected override void UnloadContent()
             {
             }
     
             protected override void Update(GameTime gameTime)
             {
                 if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                     this.Exit();
     
                 base.Update(gameTime);
             }
     
             protected override void Draw(GameTime gameTime)
             {
                 device.Clear(Color.Black);
                 
                 device.RenderState.CullMode = CullMode.None;
     
                 effect.CurrentTechnique = effect.Techniques["Colored"];
                 effect.Parameters["xView"].SetValue(viewMatrix);
                 effect.Parameters["xProjection"].SetValue(projectionMatrix);
                 effect.Parameters["xWorld"].SetValue(Matrix.Identity);
                 effect.Parameters["xEnableLighting"].SetValue(true);
                 Vector3 lightDirection = new Vector3(0.5f, 0, -1.0f);
                 lightDirection.Normalize();
                 effect.Parameters["xLightDirection"].SetValue(lightDirection);
                 
                 effect.Begin();
                 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                 {
                     pass.Begin();
     
                     device.VertexDeclaration = myVertexDeclaration;
                     device.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 2);
     
                     pass.End();
                 }
                 effect.End();
     
                 base.Draw(gameTime);
             }
         }
     }
     


    Google
     
    Webwww.riemers.net


    If you appreciate the amount of time I spend creating and updating
    these pages, feel free to donate -- any amount is welcome !



    - Website design & XNA + DirectX code : Riemer Grootjans -
    ©2003 - 2008 Riemer Grootjans
  • Translations

    This site in English
    This site in Korean
    This site in Czech

    Microsoft MVP Award



    2007 - 2008 MVP Award
    DirectX - XNA

    Contents

    News
    Home
    Forum
    XNA 2.0 Recipes Book (8)
    Downloads
    Extra Reading (3)
    Matrices: geometrical
    Matrix Mathematics
    Homogenous matrices
    Tutorials (136)
    XNA 2.0 using C# (65)
    Series 1: Terrain (13)
    Starting a project
    The effect file
    The first triangle
    World space
    Rotation - translation
    Indices
    Terrain basics
    Terrain from file
    Keyboard
    Adding colors
    Lighting basics
    Terrain lighting
    VertexBuffer & IndexBuffer
    Series 2:Flightsim (14)
    Series 3: HLSL (18)
    Series 4: Advanced terrain (17)
    Short Tuts (3)
    DirectX using C# (54)
    DirectX using C++ (15)
    DirectX using VB (2)
    -- Expand all --


    Thank you!

    Support this site --
    any amount is welcome !

    Stay up-to-date

    I don't have the time to keep a News section, so stay informed about the updates by clicking on this RSS file!