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

Terrain creation basics

At last, we've seen enough topics to start creating our terrain. Letís start small, by connecting 4x3 specified points. However, we will make our engine dynamic, so next chapter we can easily load a much larger number of points. To do this, we have to create 2 new variables in our class:

 private int terrainWidth = 4;
 private int terrainHeight = 3;

We will suppose the 4x3 points are equidistant. So the only thing we don't know about our points is the Z coordinate. We will use an array to hold this information, so we'll also add this line to the top of our class as well:

 private float[,] heightData;

For now, use this method to fill the array :

 private void LoadHeightData()
 {
     heightData = new float[4, 3];
     heightData[0, 0] = 0;
     heightData[1, 0] = 0;
     heightData[2, 0] = 0;
     heightData[3, 0] = 0;
 
     heightData[0, 1] = 0.5f;
     heightData[1, 1] = 0;
     heightData[2, 1] = -1.0f;
     heightData[3, 1] = 0.2f;
 
     heightData[0, 2] = 1.0f;
     heightData[1, 2] = 1.2f;
     heightData[2, 2] = 0.8f;
     heightData[3, 2] = 0;
 }

Donít forget to call it from within our LoadContent method. Make sure you call it before the SetUpVertices method, as that method will use the contents of the heightData.

 LoadHeightData();

With our height array filled, we can now create our vertices. Since we have a 4x3 terrain, 12 (=terrainWidth*terrainHeight) vertices will do. The points are equidistant (the distance between them is the same), so we can easily change our SetUpVertices method. To begin with, we will not be using the Z coordinate yet, to see the difference later on in this chapter.

 private void SetUpVertices()
 {
     vertices = new VertexPositionColor[terrainWidth * terrainHeight];
     for (int x = 0; x < terrainWidth; x++)
     {
         for (int y = 0; y < terrainHeight; y++)
         {
             vertices[x + y * terrainWidth].Position = new Vector3(x, 0, -y);
             vertices[x + y * terrainWidth].Color = Color.White;
         }
     }
 }

Nothing magical going on here, you simply define your 12 points and make them white. Note that the terrain will grow into the positive X direction (Right) and into the negative Z direction (Forward).

Next comes a more difficult part: defining the indices which define the required triangles to connect the 12 vertices. The best way to do this is by creating two sets of vertices:



We'll start by drawing the set of triangles drawn in solid lines. To do this, change our SetUpIndices method like this:

 private void SetUpIndices()
 {
     indices = new int[(terrainWidth - 1) * (terrainHeight - 1) * 3];
     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;
         }
     }
 }

Remember that terrainWidth and terrainHeight are the horizontal and vertical number of vertices in the terrain. We will need 2 rows of 3 triangles, giving 6 triangles. These will require 3*2 * 3 = 18 indices (=(terrainWidth-1)*(terrainHeight-1)*3), hence the first line creates an array capable of storing exactly this amount of integers.

Then, you fill your array with indices. You scan the X and Y coordinates row by row, and this time you create your triangles. During the first row, where y=0, you need to create 6 triangles based on vertices 0 (bottom-left) till 7 (middle-right). Next, y becomes 1 and 1*terrainWidth=4 is added to each index: this time we create our 6 triangles on vertices 4 (middle-left) till 11 (top-right).

To make things easier, Iíve first defined 4 shortcuts for the 4 corner indices of a quad. For each quad you store 3 indices, defining one triangle. Remember culling? It requires us to define the vertices in clockwise order. So first you define the top-left vertex, then the bottom-down vertex and the bottom-left vertex.

The counter variable is an easy way to store vertices to an array, as we increment it each time an index has been added to the array. When the method finishes, the array will contain all indices required to render all bottom-left triangles.

Weíve coded our Draw method in such a way that XNA draws a number of triangles specified by the length of our indices array, so you can immediately run this code!

You should note the triangles look tiny, so try positioning your camera at (0,10,0) and rerun the program. You should see 6 triangles in the right half of your window, every point of every triangle at the same Z coordinate. Now change the height of your points according to your heightData array:

 vertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x,y], -y);

Running this, you will notice the triangles are no longer positioned in the same plane.

Remember youíre still rendering only the bottom-left triangles. So when you would render the triangles with their solid colors instead of only their wireframes, 50% of your grid would not be covered. To fix this, letís define some more indices to render the top-right triangles also. We need the same vertices, so the only thing we have to change is the SetUpIndices method:

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

We will be drawing twice as much vertices now, that's why the *3 has been replaced by *6. You see the second set of triangles also has been drawn clockwise relative to the camera: first the top-left corner, then the top-right and finally the bottom-right.

Running this code will give you a better 3 dimensional view. We've especially taken care only to use the variables terrainWidth and terrainHeight, so these are the only things we need change to increase the size of our map, together with the contents of the heightData array. It would be nice to find a mechanism to fill this last one automatically, which we'll do in the next chapter.




DirectX Tutorial 7 - Terrain 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:
  • Scaling the Output Image?
          Hello, I want to add a scale factor for the output...
  • SetUpVertices Loop
          Hello, [Seconds time i gotta type this, might i...
  • Test
          Test because my last topic didnt work Sorry....
  • Question about Corners
          hi riemer, i studied this chapter a long time. ...
  • Indices image in the tutorial
          I was a bit confused with the following: ...
  • Flip the diagonal
          Hi, I am trying to flip the diagonal to create ...
  • Can someone explain the forloop ?
          How does it work? i mean like if x=1 and y=1 and t...
  • System.NullReferenceException
          Hi, I have a problem with declare some stuff. ...
  • Modify the approach
          The tutorial is great, Riemers. For this chapte...
  • understanding of SetUpIndices
          Hi Riemer, I was about to write a long text abo...
  • terrain that changes height
          Hi Riemer! Great tutorials! i wanted to change ...
  • IndexBuffer typeof(int) not excepted
          Hi, I have really been loving the tutorials. Sor...
  • Different SetUpIndicies() method
          I wrote the SetUpIndicies() method the following w...
  • ResourceUsage.WriteOnly?
          Hey these tutorials are great, however, I have run...
  • Runtime Error Fix for DrawIndex...
          Hello, If you are getting an error on this read...
  • Help with heightData
          Hi, I'm following along with the terrain tutorial...
  • Confusion in Indicies loop
          Hi Riemer, great tutorials about terrain. I got so...
  • Terrain Question?
          I'm sorry, I know it sounds stupid. But I do not ...
  • Empty Screen
          I've tried to get this tutorial to work, and up u...
  • Terrain Basics
          Hi Riemer, The image of the 12 vertices needs t...



    If you don't see any triangle when you run the code, see the note at the end of the previous chapter. Your problem should be solved by creating an array that can store shorts instead of ints and by filling it like this:

     private void SetUpIndices()
     {
         indices = new short[(terrainWidth - 1) * (terrainHeight - 1) * 6];
         int counter = 0;
         for (int y = 0; y < terrainHeight - 1; y++)
         {
             for (int x = 0; x < terrainWidth - 1; x++)
             {
                 short lowerLeft = (short)(x + y * terrainWidth);
                 short lowerRight = (short)((x + 1) + y * terrainWidth);
                 short topLeft = (short)(x + (y + 1) * terrainWidth);
                 short topRight = (short)((x + 1) + (y + 1) * terrainWidth);
     
                 indices[counter++] = topLeft;
                 indices[counter++] = lowerRight;
                 indices[counter++] = lowerLeft;
     
                 indices[counter++] = topLeft;
                 indices[counter++] = topRight;
                 indices[counter++] = lowerRight;
             }
         }
     }


    You can try these exercises to practice what you've learned:
  • Play around with the values of your heightData array.
  • Try to add an extra row to your grid.
    Our code so far:

     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
         {
             GraphicsDeviceManager graphics;
             SpriteBatch spriteBatch;
             GraphicsDevice device;
     
             Effect effect;
             VertexPositionColor[] 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();


                 LoadHeightData();
                 SetUpVertices();
                 SetUpIndices();            
             }
     
             protected override void UnloadContent()
             {
             }
     
             private void SetUpVertices()
             {
                 vertices = new VertexPositionColor[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);
                         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 LoadHeightData()
             {
                 heightData = new float[4, 3];
                 heightData[0, 0] = 0;
                 heightData[1, 0] = 0;
                 heightData[2, 0] = 0;
                 heightData[3, 0] = 0;
     
                 heightData[0, 1] = 0.5f;
                 heightData[1, 1] = 0;
                 heightData[2, 1] = -1.0f;
                 heightData[3, 1] = 0.2f;
     
                 heightData[0, 2] = 1.0f;
                 heightData[1, 2] = 1.2f;
                 heightData[2, 2] = 0.8f;
                 heightData[3, 2] = 0;
             }
     
             private void SetUpCamera()
             {
                 viewMatrix = Matrix.CreateLookAt(new Vector3(0, 10, 0), new Vector3(0, 0, 0), new Vector3(0, 0, -1));
                 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();
     
                 angle += 0.005f;
     
                 base.Update(gameTime);
             }
     
             protected override void Draw(GameTime gameTime)
             {
                 device.Clear(Color.DarkSlateBlue);
     
                 RasterizerState rs = new RasterizerState();
                 rs.CullMode = CullMode.None;
                 rs.FillMode = FillMode.WireFrame;
                 device.RasterizerState = rs;
     
                 effect.CurrentTechnique = effect.Techniques["ColoredNoShading"];
                 effect.Parameters["xView"].SetValue(viewMatrix);
                 effect.Parameters["xProjection"].SetValue(projectionMatrix);
                 Matrix worldMatrix = Matrix.Identity;
                 effect.Parameters["xWorld"].SetValue(worldMatrix);
     
                 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                 {
                     pass.Apply();
     
                     device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3, VertexPositionColor.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!