XNA for C#
DirectX 9 for C#
DirectX 9 for C++
DirectX 9 for VB
Forum
   2D Series finished!
My Book: Out Now!
      
       Go to section on this site

Additional info


Latest Forum posts

 Texture on a 3d Object
  Posted by: ToastbrotX
  When: 10/09/2010 at 10:23:08

 Unable to Build
  Posted by: Rich_Zap
  When: 09/09/2010 at 15:39:33

 HLSL calculating normals
  Posted by: Rich_Zap
  When: 09/09/2010 at 15:34:56

 Texture on a 3d Object
  Posted by: ToastbrotX
  When: 09/09/2010 at 11:14:19

 DRIVER ERROR XBOX 360
  Posted by: Anonymous
  When: 09/09/2010 at 01:12:47

 Reflection problem in corners ...
  Posted by: Anonymous
  When: 08/09/2010 at 21:03:35

 Texture on a 3d Object
  Posted by: radulph
  When: 08/09/2010 at 17:34:01

 InvalidDataException???
  Posted by: Anonymous
  When: 08/09/2010 at 09:10:36

 Unable to Build
  Posted by: Anonymous
  When: 08/09/2010 at 07:49:36

 Suggestion to change a few lines
  Posted by: Insomnica
  When: 06/09/2010 at 14:37:05


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;
         }
     }
 
     myVertexDeclaration = new VertexDeclaration(device, VertexPositionColor.VertexElements);
 }

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:
  • 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 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
         {
             GraphicsDeviceManager graphics;
             SpriteBatch spriteBatch;
             GraphicsDevice device;
           Effect effect;
             VertexPositionColor[] vertices;
             VertexDeclaration myVertexDeclaration;
             int[] indices;
     
             Matrix viewMatrix;
             Matrix projectionMatrix;
     
             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 -- Series 1";
     
                 base.Initialize();
             }
     
             protected override void LoadContent()
             {
                 device = graphics.GraphicsDevice;
                 spriteBatch = new SpriteBatch(GraphicsDevice);

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

                 LoadHeightData();
                 SetUpVertices();
                 SetUpCamera();
                 SetUpIndices();
             }
     
             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;
                     }
                 }
     
                 myVertexDeclaration = new VertexDeclaration(device, VertexPositionColor.VertexElements);
             }
     
             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 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);
             }
     
             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;
             }
     
             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.DarkSlateBlue);
                 device.RenderState.CullMode = CullMode.None;
                 device.RenderState.FillMode = FillMode.WireFrame;
     
                 effect.CurrentTechnique = effect.Techniques["Colored"];
                 effect.Parameters["xView"].SetValue(viewMatrix);
                 effect.Parameters["xProjection"].SetValue(projectionMatrix);
                 effect.Parameters["xWorld"].SetValue(Matrix.Identity);
     
                 effect.Begin();
                 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                 {
                     pass.Begin();
     
                     device.VertexDeclaration = myVertexDeclaration;
                     device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3);
     
                     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 - 2009 MVP Award
    DirectX - XNA

    Contents

    News
    Home
    Forum
    XNA 2.0 Recipes Book (8)
    Chapter 1
    Chapter 2
    Chapter 3
    Chapter 4
    Chapter 5
    Chapter 6
    Chapter 7
    Chapter 8
    XNA 3.0 Recipes Book (8)
    Chapter 1
    Chapter 2
    Chapter 3
    Chapter 4
    Chapter 5
    Chapter 6
    Chapter 7
    Chapter 8
    Downloads
    Extra Reading (3)
    Matrices: geometrical
    Matrix Mathematics
    Homogenous matrices
    Community Projects (1)
    Team Project (1)
    News
    Tutorials (160)
    XNA 3.0 using C# (89)
    2D Series: Shooters! (22)
    Starting a project
    Drawing fullscreen images
    Positioning images
    SpriteBatch.Draw()
    Rotation
    Keyboard input
    Writing text
    Angle to Direction
    Direction to Angle
    Smoke trail
    Manual texture creation
    Random terrain
    Texture to Colors
    Coll Detection Overview
    Coll Detection Matrices
    Putting CD into practice
    Particles
    Additive alpha blending
    Particle engine
    Adding craters
    Sound in XNA
    Resolution independency
    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)
    Starting point
    Textures
    Loading the floorplan
    Creating the 3D city
    Loading a Model
    Ambient and diffuse
    Quaternion camera
    Flight kinematics
    Collision detection
    Adding targets
    Point sprites
    Alpha blending
    Skybox
    Camera delay
    3D Series 3: HLSL (18)
    Starting point
    HLSL introduction
    Vertex format
    Vertex shader
    Pixel shader
    Per-pixel colors
    Textured triangle
    Triangle strip
    World transform
    World normals
    Per-pixel lighting
    Shadow map
    Render to texture
    Projective texturing
    Real shadow
    Shaping the light
    2D screen processing
    Preshaders
    3D Series 4: Adv. terrain (19)
    Starting code
    Mouse camera
    Textured terrain
    Multitexturing
    Adding detail
    Skydome
    The water technique
    Refraction map
    Reflection map
    Perfect mirror
    Ripples
    The Fresnel term
    Moving water
    Specular highlights
    Billboarding
    Region growing
    Billboarding renderstates
    Perlin noise
    Gradient skybox
    Short Tuts (3)
    Run XNA on older pcs
    MessageBox in XNA
    Normal generation
    DirectX using C# (54)
    Series 1:Terrain (14)
    Opening a window
    Linking to the Device
    Drawing a triangle
    Camera
    Rotation - Translation
    Indices
    Terrain creation
    Terrain from file
    DirectInput
    Importing bmp files
    Colored vertices
    DirectX Light basics
    Mesh creation
    Mesh lighting
    Series 2: Flightsim (19)
    Starting code
    Textures
    The floorplan
    Creating the 3D City
    Meshloading from file
    Ambient light
    Action
    Flight kinematics
    Collision detection
    Skybox
    Texture filtering
    Adding targets
    Point sprites
    Alpha blending
    DirectSound
    Sounds in 3D
    Playing MP3 files
    Displaying text
    Going fullscreen
    Series 3: HLSL (19)
    Starting point
    HLSL Introduction
    Vertex Shader
    Shaded triangle
    Pixel Shader
    Textured Triangle
    Triangle Strip
    World transform
    Adding normals
    The first light
    Shadow mapping
    Render To Texture
    Projective texturing
    The first shadow
    Shaping the light
    Preshaders
    Multiple lights
    Adjusting Z values
    Finishing touch
    Short Tuts (2)
    Resizing problem
    Checking Device caps
    DirectX using C++ (15)
    Series 1: Terrain (15)
    Opening a window
    Ending the game loop
    Linking to the Device
    Clearing your window
    Drawing a triangle
    Culling
    Camera
    Rotation - Translation
    Indices
    Terrain creation
    Terrain from file
    DirectInput
    Importing .bmp files
    Adding colors
    DirectX Light basics
    DirectX using VB (2)
    Series 1: Intro (2)
    The first triangle
    Rotation - translation
    -- Tree view --


    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!