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

 Backup DVD Escape Plan
  Posted by: Applefly
  When: 17/04/2014 at 06:00:12

 Disney DVDs to MP4
  Posted by: Applefly
  When: 17/04/2014 at 05:56:07

 DVD movies through media streamer
  Posted by: VIKIVannessa
  When: 16/04/2014 at 02:25:09

 DVD movies to play on Galaxy Tab 3 10.1
  Posted by: VIKIVannessa
  When: 16/04/2014 at 02:20:51

 Blu-ray to Apple ProRes
  Posted by: Applefly
  When: 12/04/2014 at 00:47:46

 HD H.264 MP4 format
  Posted by: Applefly
  When: 12/04/2014 at 00:43:23

 DVD collection to computer
  Posted by: VIKIVannessa
  When: 04/04/2014 at 07:13:33

 DVD movies to play on Galaxy Tab 3 10.1
  Posted by: VIKIVannessa
  When: 04/04/2014 at 07:11:27

 DVD movies to play on Galaxy Tab 3 10.1
  Posted by: VIKIVannessa
  When: 04/04/2014 at 07:11:27

 DVD Ripper to populate
  Posted by: Applefly
  When: 04/04/2014 at 05:01:35


Ads

Adding normals to our Terrain – Intuitive approach

We’ll be adding normal data to all vertices of our terrain, so our graphics card can perform some lighting calculations on it. As seen in the previous chapter, we will need to add a normal to each of our vertices.

The remainder of this chapter is explained in much more detail in Recipe 5-7

This normal should be perpendicular to the triangle of the vertex. In cases where the vertex is shared among multiple triangles (as in our terrain), you should find the normal of all triangles that use the vertex, and store the average of those normals in the vertex.
First we have to reload our code from the ‘Adding colors’ chapter, after which we need to add the struct (which allows normal data to be added to our vectors) to the top of our class:

 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)
     );
 }

Don’t forget to update our vertices declaration:

 VertexPositionColorNormal[] vertices;

During the instantiation of this array:

 vertices = new VertexPositionColorNormal[terrainWidth * terrainHeight];

As well as the line that actually renders your triangles in our Draw method:

 device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3, VertexPositionColorNormal.VertexDeclaration);

Now we’re ready to calculate the normals, which will be done in the CalculateNormals method. Start by clearing the normals in each of your vertices:

 private void CalculateNormals()
 {
     for (int i = 0; i < vertices.Length; i++)
         vertices[i].Normal = new Vector3(0, 0, 0);
 }

Next, you’re going to scroll through each of your triangles. For each triangle, you will calculate its normal. Finally, you will add this normal to the normal each of the triangle’s 3 vertices:

 for (int i = 0; i < indices.Length / 3; i++)
 {
     int index1 = indices[i * 3];
     int index2 = indices[i * 3 + 1];
     int index3 = indices[i * 3 + 2];
 
     Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
     Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
     Vector3 normal = Vector3.Cross(side1, side2);
 
     vertices[index1].Normal += normal;
     vertices[index2].Normal += normal;
     vertices[index3].Normal += normal;                
 }

You look up the indices for the 3 vertices of the triangle. If you know 2 sides of a triangle, you can find its normal by taking the cross product of these 2 sides. Given any 2 vectors, their cross product gives you’re the vector that is perpendicular to both vectors.

So first you find 2 sides of the triangle, by subtracting the position from one corner from the position of another. Once you know the normal for the triangle, you add it to the normal of each of the 3 vertices.

At this point, all vertices contain huge normal vectors, while they need to be of unity length. So end by normalizing all of them:

 for (int i = 0; i < vertices.Length; i++)
     vertices[i].Normal.Normalize();

That’s it for the normals! Don’t forget to call this method at the end of our LoadContent method:

 CalculateNormals();

In your Draw method, tell your graphics card to use the correct effect:

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

And turn on the light:

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

The last line turns on ambient lighting, so even the parts of the terrain that are not lit at all by the directional light still receive some lighting. When you run this code, you should see the image below:




DirectX Tutorial 13 - Terrain lighting

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:
  • Patchwork Quilt Effect when using XNA4VB
          Hi, I am following these tutorials, except I am...
  • VertexPositionColorNormal : IVertexType
          I posted on a previous chapter that generics can b...
  • Converting to XNA 4.0
          Hi, Thanks for these tutorials, they really are...
  • Order of Vertex Elements
          When I created my VertexPositionNormalColored type...
  • Lighting not working / dark
          Up till the lighting parts everything was working ...
  • Displaying Normal Problem
          hey, riemer, I am currently using your code for so...
  • Strange light's movements
          I followed the series 1 tutorials and I created a ...
  • Error in Terrain Lighting
          Following the tutorial and when I got to terrain l...
  • Problems after applying updated tutorial
          Hi reimer, The normals of my multitextured terr...
  • Problem loading bigger heightmap
          Hi, I copy/pasted the code provided in the "Te...
  • Fix @ commented out some effects.fx
          Hey guys, I had to comment out the following lines...
  • Lighting
          I can't see the lighting I tried to copy the code...
  • Lighting incompatible on DX8.1
          Thanks Reimer. I read somewhere that someone said...
  • Question of Calculate Normal
          hi, in the 3-picture illustration, i understand t...
  • normals
          hi i am new to XNA GSE and i have been following...


    As you can see, adding correct lighting dramatically adds more 3D feeling to your scene.

    Chapter 6 is completely dedicated to lighting a 3D scene.


     The final code of our project:
     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;
             int[] indices;
     
             private float angle = 0f;
             private int terrainWidth = 4;
             private int terrainHeight = 3;
             private float[,] heightData;
     
             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();


                Texture2D heightMap = Content.Load<Texture2D> ("heightmap");            LoadHeightData(heightMap);
                SetUpVertices();
                SetUpIndices();

                 CalculateNormals();
             }
     
             protected override void UnloadContent()
             {
             }
     
             private void SetUpVertices()
             {
                 float minHeight = float.MaxValue;
                 float maxHeight = float.MinValue;
                 for (int x = 0; x < terrainWidth; x++)
                 {
                     for (int y = 0; y < terrainHeight; y++)
                     {
                         if (heightData[x, y] < minHeight)
                             minHeight = heightData[x, y];
                         if (heightData[x, y] > maxHeight)
                             maxHeight = heightData[x, y];
                     }
                 }
     
                 vertices = new VertexPositionColorNormal[terrainWidth * terrainHeight];
                 for (int x = 0; x < terrainWidth; x++)
                 {
                     for (int y = 0; y < terrainHeight; y++)
                     {
                         vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y);
     
                         if (heightData[x, y] < minHeight + (maxHeight - minHeight) / 4)
                             vertices[x + y * terrainWidth].Color = Color.Blue;
                         else if (heightData[x, y] < minHeight + (maxHeight - minHeight) * 2 / 4)
                             vertices[x + y * terrainWidth].Color = Color.Green;
                         else if (heightData[x, y] < minHeight + (maxHeight - minHeight) * 3 / 4)
                             vertices[x + y * terrainWidth].Color = Color.Brown;
                         else
                             vertices[x + y * terrainWidth].Color = Color.White;
                     }
                 }
             }
     
             private void SetUpIndices()
             {
                 indices = new int[(terrainWidth - 1) * (terrainHeight - 1) * 6];
                 int counter = 0;
                 for (int y = 0; y < terrainHeight - 1; y++)
                 {
                     for (int x = 0; x < terrainWidth - 1; x++)
                     {
                         int lowerLeft = x + y * terrainWidth;
                         int lowerRight = (x + 1) + y * terrainWidth;
                         int topLeft = x + (y + 1) * terrainWidth;
                         int topRight = (x + 1) + (y + 1) * terrainWidth;
     
                         indices[counter++] = topLeft;
                         indices[counter++] = lowerRight;
                         indices[counter++] = lowerLeft;
     
                         indices[counter++] = topLeft;
                         indices[counter++] = topRight;
                         indices[counter++] = lowerRight;
                     }
                 }
             }
     
             private void CalculateNormals()
             {
                 for (int i = 0; i < vertices.Length; i++)
                     vertices[i].Normal = new Vector3(0, 0, 0);
     
                 for (int i = 0; i < indices.Length / 3; i++)
                 {
                     int index1 = indices[i * 3];
                     int index2 = indices[i * 3 + 1];
                     int index3 = indices[i * 3 + 2];
     
                     Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                     Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                     Vector3 normal = Vector3.Cross(side1, side2);
     
                     vertices[index1].Normal += normal;
                     vertices[index2].Normal += normal;
                     vertices[index3].Normal += normal;
                 }
     
                 for (int i = 0; i < vertices.Length; i++)
                     vertices[i].Normal.Normalize();
             }
     
             private void LoadHeightData(Texture2D heightMap)
             {
                 terrainWidth = heightMap.Width;
                 terrainHeight = heightMap.Height;
     
                 Color[] heightMapColors = new Color[terrainWidth * terrainHeight];
                 heightMap.GetData(heightMapColors);
     
                 heightData = new float[terrainWidth, terrainHeight];
                 for (int x = 0; x < terrainWidth; x++)
                     for (int y = 0; y < terrainHeight; y++)
                         heightData[x, y] = heightMapColors[x + y * terrainWidth].R / 5.0f;
             }
     
             private void SetUpCamera()
             {
                 viewMatrix = Matrix.CreateLookAt(new Vector3(60, 80, -80), new Vector3(0, 0, 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();
     
                 KeyboardState keyState = Keyboard.GetState();
                 if (keyState.IsKeyDown(Keys.Delete))
                     angle += 0.05f;
                 if (keyState.IsKeyDown(Keys.PageDown))
                     angle -= 0.05f;
     
                 base.Update(gameTime);
             }
     
             protected override void Draw(GameTime gameTime)
             {
                 device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);
     
                 RasterizerState rs = new RasterizerState();
                 rs.CullMode = CullMode.None;
                 device.RasterizerState = rs;
     
                 Matrix worldMatrix = Matrix.CreateTranslation(-terrainWidth / 2.0f, 0, terrainHeight / 2.0f) * Matrix.CreateRotationY(angle);
                 effect.CurrentTechnique = effect.Techniques["Colored"];
                 effect.Parameters["xView"].SetValue(viewMatrix);
                 effect.Parameters["xProjection"].SetValue(projectionMatrix);
                 effect.Parameters["xWorld"].SetValue(worldMatrix);
                 Vector3 lightDirection = new Vector3(1.0f, -1.0f, -1.0f);
                 lightDirection.Normalize();
                 effect.Parameters["xLightDirection"].SetValue(lightDirection);
                 effect.Parameters["xAmbient"].SetValue(0.1f);
                 effect.Parameters["xEnableLighting"].SetValue(true);            
     
                 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                 {
                     pass.Apply();
     
                     device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3, 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!