Sign in to follow this  
Juliean

[DX9] Help with Terrain generation

Recommended Posts

Hi,

I've succesfully implemented this algorythm to load a Terrain out of a Heightmap:

[code]#include "Terrain.h"


CTerrain::CTerrain()
{
m_lpDevice = NULL;

m_VBuffer = NULL;
m_IBuffer = NULL;

m_Width = 0;
m_Length = 0;

m_Type = 1;
}


// Freigeben der Objekte
CTerrain::~CTerrain()
{
if(NULL != m_VBuffer)
{
m_VBuffer->Release();
m_VBuffer = NULL;
}

if(NULL != m_IBuffer)
{
m_IBuffer->Release();
m_IBuffer = NULL;
}
}



void CTerrain::Create(LPDIRECT3DDEVICE9 lpDevice, LPCSTR lpHeightmap)
{
// Zaehlvariablen fuer die Schleifen und Indizes
int i, j, k;

m_lpDevice = lpDevice;

LPDIRECT3DSURFACE9 lpSurface = NULL;

D3DXIMAGE_INFO ImgInfo;

// Informationen ueber die Heightmap holen
D3DXGetImageInfoFromFile(lpHeightmap, &ImgInfo);

// Breite und Laenge der Landschaft speichern
m_Width = ImgInfo.Width;
m_Length = ImgInfo.Height;

// Oberflaeche fuer die Heightmap anlegen
m_lpDevice->CreateOffscreenPlainSurface(m_Width,
m_Length,
D3DFMT_L8,
D3DPOOL_SCRATCH,
&lpSurface, 0);

// Heightmap laden
D3DXLoadSurfaceFromFile(lpSurface, 0, 0, lpHeightmap, 0, D3DX_DEFAULT, 0, 0);

// Anzahl der Vertices und Indizes berechnen
int SizeVertices = m_Width * m_Length;
int SizeIndices = (m_Width-1)*(m_Length-1)*2*3;

// Vertex und Index Buffer anlegen
m_lpDevice->CreateVertexBuffer(SizeVertices * sizeof(TERRAIN_VERTEX),
0, D3DFVF_TERRAINVERTEX,
D3DPOOL_DEFAULT,
&m_VBuffer,
0);

m_lpDevice->CreateIndexBuffer(SizeIndices * 2,
0,
D3DFMT_INDEX16,
D3DPOOL_DEFAULT,
&m_IBuffer,
0);

// Oberflaeche sperren
D3DLOCKED_RECT LockedRect;

lpSurface->LockRect(&LockedRect, 0, 0);

// und einen Zeiger auf die Pixel anlegen
BYTE* lpHeights = (BYTE*) LockedRect.pBits;

int nPitch = LockedRect.Pitch;

// die Vertices anlegen

TERRAIN_VERTEX* Vertices = new TERRAIN_VERTEX[SizeVertices];

// und mit Werten belegen
for(i=0;i<m_Length;i++)
{
// der Farbwert des Pixels bestimmt die Hoehe
for(j=0;j<m_Width;j++)
{
Vertices[i*m_Width+j].x = static_cast<float>(j)*0.5f;

Vertices[i*m_Width+j].y = static_cast<float>(lpHeights[i*nPitch+j])*0.1f;

Vertices[i*m_Width+j].z = static_cast<float>(m_Length - i)*0.5f;

// je hoeher der Vertex, umso heller die Farbe
Vertices[i*m_Width+j].Color = D3DCOLOR_XRGB(lpHeights[i*nPitch+j],
lpHeights[i*nPitch+j],
lpHeights[i*nPitch+j]);

}
}
// Array fuer die Indizes anlegen
unsigned short* Indices = new unsigned short[SizeIndices];


// und die Indizes der Dreiecke erzeugen
k = 0;

for(i=0;i<m_Length-1;i++)
{
for(j=0;j<m_Width-1;j++)
{
Indices[k++] = m_Width * (i + 1) + j;
Indices[k++] = m_Width * i + j;
Indices[k++] = m_Width * (i + 1) + j + 1;
Indices[k++] = m_Width * i + j;
Indices[k++] = m_Width * i + j + 1;
Indices[k++] = m_Width * (i + 1) + j + 1;
}
}

// Vertices in den Vertex Buffer kopieren
void* pVertices;

m_VBuffer->Lock(0, SizeVertices * sizeof(TERRAIN_VERTEX), &pVertices, 0);

memcpy(pVertices, Vertices, SizeVertices*sizeof(TERRAIN_VERTEX));

m_VBuffer->Unlock();

// Indizes in den Index Buffer kopieren
void* pIndices;

m_IBuffer->Lock(0, SizeIndices * sizeof(unsigned short), &pIndices, 0);

memcpy(pIndices, Indices, SizeIndices*sizeof(unsigned short));

m_IBuffer->Unlock();

// Speicher freigeben
delete[] Vertices;
delete[] Indices;

// Oberflaeche freigeben
lpSurface->Release();
}


void CTerrain::Render(D3DXVECTOR3 Position, D3DXVECTOR3 Rotation, D3DXVECTOR3 Scale)
{
// Vertex-Format setzen
m_lpDevice->SetFVF(D3DFVF_TERRAINVERTEX);

// Vertex Buffer angeben
m_lpDevice->SetStreamSource(0, m_VBuffer, 0, sizeof(TERRAIN_VERTEX));

//SetWorldTransformation(Position, Rotation, Scale);

// Index Buffer angeben
m_lpDevice->SetIndices(m_IBuffer);

// Landschaft rendern

m_lpDevice->SetRenderState(D3DRS_LIGHTING, FALSE);


m_lpDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
0, 0,
m_Length * m_Width,
0,
(m_Width-1)*(m_Length-1) * 2);

m_lpDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
}
[/code]

Thats my terrain class, the algorythim is in the Create-function. Everyting works fine for any heightmap under 256*256-size. Thats how it looks:

[URL=http://img852.imageshack.us/i/terraino.png/][IMG]http://img852.imageshack.us/img852/8107/terraino.png[/IMG][/URL]

Now i've got 2 problems:

1. If I use any map with a size greater than 512*512 it starts glitching up. First off all part of the terrain isn't shown, although the framerate is dropping to a very low amount (didn't do any render optimizations yet). This is how it looks:

[URL=http://img51.imageshack.us/i/terrain2.png/][IMG]http://img51.imageshack.us/img51/3666/terrain2.png[/IMG][/URL]

Can anyone please take a look and tell me if theres anything wrong with my algorythm? Here is the header file of my class:

[code]#pragma once
// Diese Datei enthaelt die Definition der Terrain-Klasse

#include <d3d9.h>
#include <d3dx9.h>


// Vertex-Format definieren
// momentan werden nur Koordinaten und Farben als
// Elemente fuer die Vertices verwendet
#define D3DFVF_TERRAINVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE)

struct TERRAIN_VERTEX
{
float x, y, z;
D3DCOLOR Color;
};

// Klasse Terrain
class CTerrain
{
public:

CTerrain();
virtual ~CTerrain();

// Terrain aus der uebergebenen Heightmap erzeugen
void Create(LPDIRECT3DDEVICE9 lpDevice, LPCSTR lpHeightmap);

// Terrain rendern
void Render(D3DXVECTOR3 Position, D3DXVECTOR3 Rotation, D3DXVECTOR3 Scale);

protected:

// Breite und Laenge des Terrain
int m_Width;
int m_Length;

// der Vertexbuffer enthaelt die Vertices
LPDIRECT3DVERTEXBUFFER9 m_VBuffer;

LPDIRECT3DDEVICE9 m_lpDevice;

// der Indexbuffer beschreibt die Dreiecke
LPDIRECT3DINDEXBUFFER9 m_IBuffer;
};[/code]

If you'd like to use it, just create an object of CTerrain class, call create, pass in a valid D3D-Device, and for render simply pass 3 vectors: (0, 0, 0), (0, 0,0), (1, 1, 1). Should be self-explaining but you never know.. hopefully someone figures out whats wrong!

2. I want to illuminate the terrain, and therefor I need the normals of the vertices. It would be no problem to change my FVF-declaration, but how do I calculate the normals? From what I read I need the 8 vertices nearby, but how do I get them, and how do I use these data to calculate the normals? Are there any tutorials/sample algorythm or can someone explain to me (with some pseudo-code-example would be best).

3. How do I "paint" that terrain? All my books just use a already drawn texture to lay over the terrain, but I'd like to do something a little more practical. What would be a "modern" approach to paint a terrain if I want to build my own level editor? Should I just create such a texture and modify the texture if I paint the terrain in the editor, or is there a better way to store the textures for terrain (using a whole texture for every terrain would cause a lot of hard disk space and probably memory too).

4. Is Heightmaps even a good approach to do terrain? For some parts I'd want to have terrain thats "over" another terrain (you know, like a cliff that doesn't go down vertically but a bit offset into the ground). Is this possible with heightmaps or do I have to take a differnet approach?

Share this post


Link to post
Share on other sites
I didn't read through all your code. However, I'm sure one of the problems you're running into is the type of numbers you're using for your index buffer. If you have width=512 and height=512, then you're trying to set indices in value from 0 to width*(height+1) + width +1 = 512*512 + 512 + 1 = 262657. You use D3DFMT_INDEX16 for the indices, so the index itself can only hold values from 0 to 65535.

Note: you probably have problems with 256x256 (your index range 0 to 256*256 + 256 +1 = 65793 ( which is > 65535 ). Those problems may just be hidden at the edges of your terrain.

Try using D3DFMT_INDEX32 and see what happens.

Note: Poor coding style. You don't check for errors [b]anywhere[/b]. Every DirectX function provides some indication of success or failure. Until your code is working properly, you should be checking for errors.

Share this post


Link to post
Share on other sites
[quote]I didn't read through all your code. However, I'm sure one of the problems you're running into is the type of numbers you're using for your index buffer. If you have width=512 and height=512, then you're trying to set indices in value from 0 to width*(height+1) + width +1 = 512*512 + 512 + 1 = 262657. You use D3DFMT_INDEX16 for the indices, so the index itself can only hold values from 0 to 65535.

Note: you probably have problems with 256x256 (your index range 0 to 256*256 + 256 +1 = 65793 ( which is > 65535 ). Those problems may just be hidden at the edges of your terrain.

Try using D3DFMT_INDEX32 and see what happens.[/quote]

Well what I get after changing to the 32-format I get an error at the DrawIndexedPrimitive-Call:

"unhandled exception at 0x4fe3f368 in Test.exe: 0xC0000005: access violation reading at position 0x05e30022." (translated from german)

Maybe I need to re-do some calculation when switching the format? DirectX-debug-runtime tells me: "Index stream does not have required number of indices. DrawIndexedPrimitive failed."

[quote]Note: Poor coding style. You don't check for errors [b]anywhere[/b]. Every DirectX function provides some indication of success or failure. Until your code is working properly, you should be checking for errors. [/quote]

Yeah I know, thats really one thing I'm lazy on and you'r right to critisize me. I'm so used to using the directx debug runtime that I don't even add those lines anywhere other than on the creation of the directx-device and stuff. But will it make any difference? I think its the same if I just switches on directx-debug mode and checked for any error, isn't it? Or are the HRESULTs of the functions telling me more?

Share this post


Link to post
Share on other sites
With regard to the unhandled exception, I don't know the cause off the top of my head. You'll have to do some debugging. If I had to guess: some index is beyond the end of the index buffer. For that you can try various things: e.g., draw just a few primitives (hopefully using the smaller values of the indices). If that works, keep drawing more until you get the error. Do some math at that point to determine where the index is being calculated incorrectly.

[quote]I'm so used to using the directx debug runtime that I don't even add those lines anywhere other than on the creation of the directx-device and stuff. But will it make any difference?[/quote]
Perhaps not when you're in debug mode. It's certainly possible to get errors in release mode that don't appear in debug mode. It's up to you, I suppose. I just have a preference for a program to handle errors gracefully even when debugging.

Share this post


Link to post
Share on other sites
Ok At least I noticed one thing, forgot that 32 bit indices are twice size so I gotta multiply with 4 instead of 2 on buffer creation. im not at home to test it, but ill do so in a few hours.

Nah, I didnt mean Im in debug mode all the tine but as soon as an error occours that I can solve right away I turn it on and its fine. But your right, there are certain bugs that dont happen in debug mode but in release mode. I just ran into such an issue yesterday. Had something to do with vertex buffer too..

Share this post


Link to post
Share on other sites
So I finally arrived at home and were able to solve the problem. I actually really had to change to SizeIndices * 4 in the index buffer creation, and I also had to change all short-values to int. Now it works at least up to 1024*1024., didn't test any huger heightmaps because performance is breaking in. As I plan huge landscapes with a great distance view I'll soon have to do some optimizations.

Anyway, no suggestions for my questions 2-4 in my first post?

Share this post


Link to post
Share on other sites
To calculate smooth normals for a terrain grid, you'll have to sharpen your math skills a bit. Assuming your grid looks something like this:
[img]http://www.veazie.org/Images/terrain.jpg[/img]
The process for calculating the normal for vertex v0 ( I leave determining how to run through the indices correctly up to you ):

1. Calculate the normal N[sub]A[/sub] for v0 with respect to triangle A = cross-product( vector( v2-v0 ), vector( v1-v0 ) )

2. Similarly, calculate the normal N[sub]B[/sub] for v0 with respect to triangle B = cross-product( vector( v3-v0 ), vector( v2-v0 ) )

3. Calculate normals for the remaining 4 triangles which share v0 as a vertex.

4. The smooth normal for v0 is ( N[sub]A[/sub] + N[sub]B[/sub] + N[sub]C[/sub] + N[sub]D[/sub] + N[sub]E[/sub] + N[sub]F[/sub] )/6 - the average of the normals for each of the 6 triangles adjacent to v0

5. Repeat for all vertices.

[quote]my books just use a already drawn texture to lay over the terrain, but I'd like to do something a little more practical ... build my own level editor?[/quote]
It depends greatly on just how you want to texture the terrain. I don't do much terrain "painting" in that regard ( I just overlay textures in a somewhat random fashion when I create terrains ).

Perhaps someone else knows of a general terrain "painter" application. If not, or you need certain features, you may have to write your own.

Share this post


Link to post
Share on other sites
Thanks for the explanation about the normals, this is helping a lot. Actually I do know how to generally calculate normals, and what dot product is and so on, but how to use that knowledge for vertices of a terrain was a mystery. Running through the vertices shouldn't be that problem as I I'm already running through all vertices in my heightmap to set the current height, so this will only need some modifications.

[quote]It depends greatly on just how you want to texture the terrain. I don't do much terrain "painting" in that regard ( I just overlay textures in a somewhat random fashion when I create terrains ).

Perhaps someone else knows of a general terrain "painter" application. If not, or you need certain features, you may have to write your own. [/quote]

Well I didn't really expect any programm for that. With editor I meant one I write on my own. Actually it is pretty much clear that I have to write my own routine for that, I just needed to know some techniques for achieving this. I think I've found one, its calles "Texture splatting" and seems to be pretty much what I'm looking for.

Anyway I think I'll have to use Meshes created in 3ds max for larger terrain. As I want to have huge, high mountains heightmaps really lose their purpose, because great increases in height will cause ugly textures, and I won't achieve a great distance view because the number of triangles will increase too much and there is too less control over it. I think I'll combine heightmaps for near, plain landscapes and meshes for far away parts of land.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this