Click here to Skip to main content
15,868,141 members
Articles / Multimedia / DirectX
Article

3D Terrain Visualisation in Managed DirectX 9 and C#

Rate me:
Please Sign up or sign in to vote.
4.86/5 (70 votes)
18 Sep 20056 min read 353.8K   4.7K   164   78
In this project I demonstrate how to write a simple 3D rendering application in a relatively small amount of code.

Point, Wireframe and Solid (Textured) Mode

Introduction

GIS (Geographical Information System) is a computer support system that represents data using maps. It helps people access, display and analyse data that has geographic content and meaning. For those not familiar with GIS, it used to be a niche IT market dominated by the traditional GIS and CAD companies such as Intergraph, Bentley, MapInfo, Autodesk and ESRI. Nowadays global IT giants such as Microsoft, Google and Oracle are competing for their share of the pie through products such as Virtual Earth, Google Earth and Oracle Spatial. NASA has also recently released a free, open source GIS viewer application called World Wind.

In this article, I will demonstrate how to build a standalone 3D terrain visualisation tool from scratch using C# and Managed DirectX 9.0c. The application will allow the user to rotate the point of view using the arrow keys and to change the rendering mode to (P) Point, (W) Wire frame and (S) Solid.

Background

I recently completed a GIS system implementation for a local City Council. During that project I developed a proof of concept application to demonstrate the technical feasibility of 3D visualisation using the available spot heights and aerial photography textures. The aim of this article is to share my knowledge and experience with all developers interested in GIS and .NET.

Requirements

Before we start, I would like to specify the software requirements for this project:

  • Visual Studio .NET IDE (I used 2005 beta 2)
  • Managed DirectX 9.0c SDK (I used August 2005 update)
  • .NET framework (I used v2.0 but v1.1 will work as well)

3D rendering concepts

First of all I will need to explain the general 3D programming concepts behind my code. Unfortunately, entire books are written on this topic and I won't be able to give a full explanation for every single line in my code, but instead will attempt to present the most important ideas behind 3D visualisation.

In order to generate any 3D terrain model, you will need some grid based data with X, Y and Z values for each grid point. A very important consideration is how the Z value is stored, as DirectX uses left-handed coordinate system while OpenGL uses right-handed coordinate system (to learn about different coordinate systems, please search the Internet). I have chosen a grid size of 79x88 simply because that is how my source data is stored, but you can change this to any arbitrary grid size. Likewise, my data uses 20m resolution which means that the real distance between two adjacent points is 20 meters.

Point Mode

Once you read in all the points you will need to generate a "mesh". The mesh is an array of triangles constructed from the points you loaded in the previous step. All rendering in 3D is based on triangles and arrays of triangles.

Wireframe Mode

Even the meanest video cards on today's market have limited rendering capability in terms of how many triangles can be drawn per second. Therefore, the less work your video card needs to perform the faster your application runs. This is where optimisation algorithms come into play, such as ROAM or PLOD (the latter is built-in DirectX 9). These and other similar algorithms are aimed at reducing the level of detail and number of triangles located furthest from the view point. The other way to look at this is to say that we are reducing the level of detail where it matters least, while we are preserving the highest possible level of detail where it matters most. We won't be using these algorithms here, however you should be aware of what they are and what they are used for.

Finally, textures are used to provide a more realistic look of the scene. Textures use their own coordinate system, with top left representing 0,0 and bottom right representing 1,1. Any texture point within this range (0,0 - 1,1) is referred to as a "texel".

Textured Mode

As an exercise left to the reader, further enhancements could include the SkyBox, Lighting, Shading or even Physics engine with collision detection etc. Managed DirectX also includes support for DirectPlay and DirectSound with advanced networking and sound APIs. Using your imagination, the sky is the limit!

Using the code

I have built a sample WinForm application using the Visual Studio 2005 IDE. You will need to have Managed DirectX 9.0c SDK installed on your PC for the project to compile and run correctly (I used the August 2005 update). However you can use a much smaller DirectX 9.0c redistributable if you wish to distribute your code to users who don't have Managed DirectX SDK installed on their PC.

OK, let's dive into the code.

First of all, we will import the necessary libraries:

C#
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX.DirectInput;

Then, we will declare our grid width and height, screen and keyboard devices, VertexBuffer and IndexBuffer, Texture, Vertex and Triangle structs and a few other variables used throughout the project:

C#
private int GRID_WIDTH = 79;     // grid width
private int GRID_HEIGHT = 88;    // grid height
private Microsoft.DirectX.Direct3D.Device device = null;  // device object
private Microsoft.DirectX.DirectInput.Device keyb = null; // keyboard
private float angleZ = 0f;       // POV Z
private float angleX = 0f;       // POV X
private float[,] heightData;     // array storing our height data
private int[] indices;           // indices array
private IndexBuffer ib = null; 
private VertexBuffer vb = null;
private Texture tex = null;
//Points (Vertices)
public struct dVertex
{
  public float x;
  public float y;
  public float z;
}
//Created Triangles, vv# are the vertex pointers
public struct dTriangle
{
  public long vv0;
  public long vv1;
  public long vv2;
}
private System.ComponentModel.Container components = null;

Now we're ready to initialise our Direct3D device object:

C#
// define parameters for our Device object
PresentParameters presentParams = new PresentParameters();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.EnableAutoDepthStencil = true;
presentParams.AutoDepthStencilFormat = DepthFormat.D16;
// declare the Device object
device = new Microsoft.DirectX.Direct3D.Device(0, 
             Microsoft.DirectX.Direct3D.DeviceType.Hardware, this, 
             CreateFlags.SoftwareVertexProcessing, presentParams);
device.RenderState.FillMode = FillMode.Solid;
device.RenderState.CullMode = Cull.None;
// Hook the device reset event
device.DeviceReset += new EventHandler(this.OnDeviceReset);
this.OnDeviceReset(device, null);
this.Resize += new EventHandler(this.OnResize);

As you can see we have wired up the OnDeviceReset event which fires up every time the user resizes the application window. Our points are stored in a VertexBuffer:

C#
// create VertexBuffer to store the points
vb = new VertexBuffer(typeof(CustomVertex.PositionTextured), 
         GRID_WIDTH * GRID_HEIGHT, device, Usage.Dynamic | Usage.WriteOnly, 
         CustomVertex.PositionTextured.Format, Pool.Default);
vb.Created += new EventHandler(this.OnVertexBufferCreate);
OnVertexBufferCreate(vb, null);

Then, we need to instantiate an IndexBuffer, from which our triangular mesh is constructed. IndexBuffer stores an ordered list into the Vertex data:

C#
ib = new IndexBuffer(typeof(int), (GRID_WIDTH - 1) * 
     (GRID_HEIGHT - 1) * 6, device, Usage.WriteOnly, Pool.Default);
ib.Created += new EventHandler(this.OnIndexBufferCreate);
OnIndexBufferCreate(ib, null);

Also pay attention to InitialiseIndices() and LoadHeightData() functions in the source code attached, where we load and "triangulate" our data. Next, we initialise the keyboard device:

C#
public void InitialiseKeyboard()
{
  keyb = new Microsoft.DirectX.DirectInput.Device(SystemGuid.Keyboard);
  keyb.SetCooperativeLevel(this, CooperativeLevelFlags.Background |
                           CooperativeLevelFlags.NonExclusive);
  keyb.Acquire();
}

Then, we position our camera:

C#
private void CameraPositioning()
{
  device.Transform.Projection = 
     Matrix.PerspectiveFovLH((float)Math.PI/4,   
     this.Width/this.Height, 1f, 350f);
  device.Transform.View = 
     Matrix.LookAtLH(new Vector3(0, -70, -35), new Vector3(0, -5, 0), 
     new Vector3(0, 1, 0));
  device.RenderState.Lighting = false;
  device.RenderState.CullMode = Cull.None;
}

Almost done, only a few steps left. Now we will override the OnPaint event and provide our own event handling code:

C#
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
  device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.LightBlue , 1.0f, 0);
  // set the camera position
  CameraPositioning();
  // draw the scene     
  device.BeginScene();
  device.SetTexture(0, tex);
  device.VertexFormat = CustomVertex.PositionTextured.Format;
  device.SetStreamSource(0, vb, 0);
  device.Indices = ib;
  device.Transform.World = 
         Matrix.Translation(-GRID_WIDTH/2, -GRID_HEIGHT/2, 0) *   
         Matrix.RotationZ(angleZ)*Matrix.RotationX(angleX);
  device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, GRID_WIDTH * 
                               GRID_HEIGHT, 0, indices.Length/3);
  device.EndScene(); 
  device.Present();
  this.Invalidate();
  ReadKeyboard();
}

Finally, we need to write our main procedure and we're done:

C#
static void Main() 
{
  using (WinForm directx_form = new WinForm())
  {
    directx_form.LoadHeightData();
    directx_form.InitialiseIndices();
    directx_form.InitialiseDevice();
    directx_form.InitialiseKeyboard();
    directx_form.CameraPositioning();
    directx_form.Show();
    Application.Run(directx_form);
  }
}

Now compile and run. You should get the results as shown in the pictures at the top. Use the arrow keys on your keyboard to drive the application, and press P, W and S to switch between different rendering modes: Point, Wire frame and Solid. Cool, huh?

Points to note

One thing you'll quickly learn to appreciate is how time consuming 3D programming can be. Even the smallest detail or effect you wish to implement could take many painful days; however, the entire experience is very rewarding once you overcome the hurdles. My suggestion to everyone is to use the Internet in the first instance and search about the problem you're trying to solve. The chances are, someone has already done what you're trying to do, and better still, the problem may have been documented and solved. If you're lucky, a step-by-step tutorial or code snippets may be readily available showing you how to solve your problem.

References

  • For those people who have some spare cash in their wallets, there is an excellent book available for Managed DirectX programming in C#:
    • Managed DirectX 9 - Graphics and Game Programming, by Tom Miller.
  • Also, as a starting point there is an excellent DirectX 9 tutorial using C# available here.
  • Another great site with lots of practical examples in DX9 and C# is available here.

History

  • 19th September 2005: First release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Founder GIS People
Australia Australia
I have filled variety of roles ranging from Junior Software Engineer to GIS Team Leader. My background is in real-time spatial solutions, including mobile data capture and advanced road network data modelling.

In my projects I have utilised and integrated technologies such as GPS, Oracle Spatial, FME, MapInfo and ESRI suite of tools.

I'm proficient in all aspects of the software development lifecycle and I'm holding a degree in Computer Science from Queensland University of Technology.

Comments and Discussions

 
QuestionSuper-impose vector data Pin
Member 370746116-Apr-15 22:06
Member 370746116-Apr-15 22:06 
QuestionC++ support Pin
Member 1155085224-Mar-15 1:27
Member 1155085224-Mar-15 1:27 
QuestionCompilation with Visual Studio 2010 Pin
Gagnon Claude29-Jun-11 16:36
Gagnon Claude29-Jun-11 16:36 
AnswerRe: Compilation with Visual Studio 2010 Pin
schubert0125-Aug-11 17:49
schubert0125-Aug-11 17:49 
GeneralRe: Compilation with Visual Studio 2010 [modified] Pin
Igor Stjepanovic27-Aug-11 22:21
Igor Stjepanovic27-Aug-11 22:21 
Generalsize limitation on overlay bitmap Pin
Aladar6413-May-09 14:11
Aladar6413-May-09 14:11 
GeneralRe: size limitation on overlay bitmap Pin
Igor Stjepanovic13-Aug-09 15:32
Igor Stjepanovic13-Aug-09 15:32 
QuestionFullScreen? Is the Windows.Form neccessary? Pin
Maxi Ng @ TW22-Oct-08 21:00
Maxi Ng @ TW22-Oct-08 21:00 
GeneralManaged DirectX Pin
newspicy16-Sep-08 2:30
newspicy16-Sep-08 2:30 
QuestionBlue screen Pin
Taher Hassan25-Nov-07 12:37
Taher Hassan25-Nov-07 12:37 
Generalgrid size Pin
jtby230-Aug-07 12:21
jtby230-Aug-07 12:21 
GeneralRe: grid size Pin
Igor Stjepanovic13-Aug-09 15:37
Igor Stjepanovic13-Aug-09 15:37 
GeneralZ-Buffer Pin
sithira7730-Jul-07 23:27
sithira7730-Jul-07 23:27 
GeneralRe: Z-Buffer Pin
Igor Stjepanovic15-Aug-07 15:00
Igor Stjepanovic15-Aug-07 15:00 
Generalfloat.Parse Pin
Lutosław1-Jul-07 2:38
Lutosław1-Jul-07 2:38 
GeneralRe: float.Parse Pin
Lutosław2-Jul-07 0:02
Lutosław2-Jul-07 0:02 
GeneralData type Pin
muzzaukuk24-Nov-06 11:06
muzzaukuk24-Nov-06 11:06 
GeneralVirus Pin
Murray Roke11-Jul-06 1:11
Murray Roke11-Jul-06 1:11 
GeneralRe: Virus Pin
Igor Stjepanovic11-Jul-06 1:13
Igor Stjepanovic11-Jul-06 1:13 
GeneralPlease take a minute to vote Pin
Igor Stjepanovic9-Jul-06 22:44
Igor Stjepanovic9-Jul-06 22:44 
GeneralRe: Please take a minute to vote Pin
giloutho10-Jul-06 1:10
giloutho10-Jul-06 1:10 
GeneralRe: Please take a minute to vote Pin
Darren Ashenden19-Feb-07 14:31
Darren Ashenden19-Feb-07 14:31 
GeneralProblems in localized Windows [modified] Pin
ivandasch20-Jun-06 8:59
ivandasch20-Jun-06 8:59 
GeneralRe: Problems in localized Windows Pin
giloutho9-Jul-06 0:59
giloutho9-Jul-06 0:59 
GeneralZoom In/Out .. Pin
sahiljain2223-May-06 20:06
sahiljain2223-May-06 20:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.