December 28th, 2007
I want to try using XNA and I’ll make some project using it. Simple terrain engine should be challenging enough but not to hard.
Goal of the project is to generate some terrain similar to the one in Sim City 3000. If I succeed I’ll try to build something on it. Terrain as such is not my primary interest, I’m more concerned with using terrain as a map for building stuff on it.
I’ll try to keep a clean coding style which will allow me to share code between projects. Main class will be clean as possible and every custom function will be kept in separate class.
Project structure:
Project namespace: GAC
- First thing is to go to project options and change default namespace to GAC (like Game And Code). This will keep everything under one roof: GAC namespace.
References:
- Microsoft.Xna.Framework
- Microsoft.Xna.Framework.Game
Classes:
Program.cs
- Main entry point for the application – autogenerated
GameMain.cs
- Main class. Auto generated as Game1.cs
- Contains main loops: Update() and Draw()
- Holds device, camera and effect properties
xGame.cs
- Class where objects related to game are managed
- For now, only terrain is active
xTerrain.cs
- Class that keeps data about terrain and draws it
Main variables:
cameraPos, cameraAngle:
Initial position is set in SetUpCamera function. We’ll position it above the terrain and little towards user and at 45°angle looking on the terrain. From this values we’ll calculate view and projection matrix.
projMatrix:
How far and wide can camera see the world.
viewMatrix:
Where is camera located and where it points.
worldMatrix:
When rendering object on the world we can change this matrix so this objects is placed off from points defined on itself. Useful when you want to render one object on more places. Also, you don’t need to change every vertex to change it’s position.
Execution:
Main object will call Draw() on xGame. Depending on the game state and currently opened windows, xGame will decide what to draw and call appropriate object. For now we’ll draw only terrain so it will call ter.Draw().
Every class will have Init() function after which it should be able to draw itself. Before call to Init(), every required property (like size for terrain) should be already set. So, our terrain has SetSize function and Init() in which vertex and index buffer is set up. In Draw() function everything is drawn on the screen.
Only one concept remains. We need to set effects before we do drawing. Effects are required for HLSL (high-level shader language). That’s the kind of thing i was afraid of. I need to set and configure something I don’t use. Fortunately, there is BasicEffect class available for default effects. To set the effect for drawing we need to use one realy ugly loop. Every call for drawing to the device needs to be enclosed in such loop.
When everything is set up result is empty, somewhat boring grid. Not really accomplishment tself but project is now organized and ready for some fun things.
Adding height
To make the output more like a real terrain, we need some elevation data. This project will use heightmap. Heightmap is only a plain grayscale bitmap file where whiteness of a pixel is elevation value. I’ll set the program to load a hmap_64.png and use it for elevation, but also dimensions for the map. So, if i want 64×64 grid i need a 65×65 bmp file (65 is number of vertices in a row or column, that means there are 64 cells between).
I quickly drew some grayscale image somewhat reminding to relief and output looks like this:
Flat area which looks like water is – water level. Algorithm for height import is cutting every value below 55 to 0. Only values larger then 55 are considered for terrain heights. That leaves us with 200 height levels which should be enough for our needs.
Lightning
Now we’ll cover this skeleton and draw the image in solid instead of in wireframe mode. Unfortunately, result is only a big white blob. That’s because now any depth information is missing. Before, the wireframe produced an effect of the third dimension. With the wireframe gone, some light and shadows must be added to the terrain. For this purpose I’ll use the vertex normals. Every vertex have a Normal property in which I’ll store an average “flatness” of the polygons this vertex is participating in. This value will tell the graphics card how much light is reflected from the terrain.
This image shows how terrain looks with and without normals:


