XNA for C#
DirectX 9 for C#
DirectX 9 for C++
DirectX 9 for VB
Forum
   
My XNA Book
      
       Go to section on this site

Additional info


Latest Forum posts

 Account settings
  Posted by: Anonymous
  When: 07/05/2014 at 09:48:39

 forced subtitle
  Posted by: Applefly
  When: 07/05/2014 at 06:00:48

 convert DVD into PMS
  Posted by: Applefly
  When: 07/05/2014 at 05:55:25

 DVD to Digital Copy easily
  Posted by: VIKIVannessa
  When: 05/05/2014 at 06:52:29

 DVD on Xbox 360/Xbox One Console
  Posted by: VIKIVannessa
  When: 05/05/2014 at 06:51:47

 Extract .Srt Subtitles
  Posted by: Applefly
  When: 04/05/2014 at 03:54:38

 Encode Movie collection
  Posted by: Applefly
  When: 04/05/2014 at 03:52:41

 Convert DVD to WMV
  Posted by: Applefly
  When: 29/04/2014 at 05:53:50

 rip DVDs into digital files
  Posted by: Applefly
  When: 29/04/2014 at 05:51:20

 iTunes movies/music to Kindle Fire
  Posted by: ciciyu80
  When: 29/04/2014 at 05:10:20


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 as it does not allow us to store a normal for each vertex, 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 VertexPositionColorNormal
 {            
     public Vector3 Position;
     public Color Color;
     public Vector3 Normal;
 
     public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
     (
         new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
         new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
         new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, 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 its full detail in Series 3. For now, think of it as a manual for the graphics card to understand what kind of data is contained inside each vertex.

This allows us to change our vertex variable declaration:

 VertexPositionColorNormal[] 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 than 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 VertexPositionColorNormal[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();
 }

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.

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

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

And let the graphics card know that from now on it has to use the Colored technique. This technique works exactly the same as the ColoredNoShading technique, but also adds correct lighting (in case you provided correct normals!):

 effect.CurrentTechnique = effect.Techniques["Colored"];
 


When you run this code, you should see an arrow (our 2 triangles), but you don’t see any shading 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 (otherwise the length of this vector influences the strength of the shading, while you want the shading to depend solely on the direction of the incoming light). 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:
  • Lighting basics
          -position (sizeof(float) * 3) -color (sizeof(f...
  • Trying to convert to XNA 4.0
          Ok, so Ive done well so far with converting all of...
  • Doesnt work right on xbox
          I tried running this code on both my pc and my xbo...
  • normals - what?
          I don't understand this chapter. In the pictures...
  • normals - what?
          I don't understand this chapter. In the pictures...
  • normals - what?
          I don't understand this chapter. In the pictures...
  • 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 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 Series3D1
     {
         public class Game1 : Microsoft.Xna.Framework.Game
         {
             public struct VertexPositionColorNormal
             {
                 public Vector3 Position;
                 public Color Color;
                 public Vector3 Normal;
     
                 public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
                 (
                     new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                     new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
                     new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
                 );
             }
     
             GraphicsDeviceManager graphics;
             SpriteBatch spriteBatch;
             GraphicsDevice device;
     
             Effect effect;
             VertexPositionColorNormal[] vertices;
     
             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 -- 3D Series 1";
     
                 base.Initialize();
             }
     
             protected override void LoadContent()
             {
                 spriteBatch = new SpriteBatch(GraphicsDevice);
     
                 device = graphics.GraphicsDevice;

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

            protected override void UnloadContent()
            {
            }

            private void SetUpVertices()
            {

                 vertices = new VertexPositionColorNormal[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();
             }
     
             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 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);
     
                 RasterizerState rs = new RasterizerState();
                 rs.CullMode = CullMode.None;
                 device.RasterizerState = rs;
     
                 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);
     
                 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                 {
                     pass.Apply();
     
                     device.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 2, VertexPositionColorNormal.VertexDeclaration);
                 }
     
                 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 - 2011 Riemer Grootjans
  • Translations

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

    Microsoft MVP Award



    2007 - 2011 MVP Award
    DirectX - XNA

    Contents

    News
    Home
    Forum
    XNA 2.0 Recipes Book (8)
    XNA 3.0 Recipes Book (8)
    Downloads
    Extra Reading (3)
    Matrices: geometrical
    Matrix Mathematics
    Homogenous matrices
    Community Projects (1)
    Tutorials (160)
    XNA 4.0 using C# (89)
    2D Series: Shooters (22)
    3D 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
    3D Series 2: Flightsim (14)
    3D Series 3: HLSL (18)
    3D Series 4: Adv. terrain (19)
    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!