Your terrain grid should be the same size as your heightmap.
The following then can be used:
Code:
void CTerrain::PrepareTerrain(int iCellSize)
{
//Setup variables
m_vecPos=D3DXVECTOR3(0.0f,0.0f,0.0f);
m_iCellSize=iCellSize;
m_iMapSize=(m_iMapWidth)*(m_iMapDepth);
m_iNumVertsPerRow=m_iMapWidth;
m_iNumVertsPerCol=m_iMapDepth;
m_iNumCellsPerRow=m_iNumVertsPerRow-1;
m_iNumCellsPerCol=m_iNumVertsPerCol-1;
m_iWorldWidth=m_iNumCellsPerRow*m_iCellSize;
m_iWorldDepth=m_iNumCellsPerCol*m_iCellSize;
m_iWorldWidth2=m_iWorldWidth>>1;
m_iWorldDepth2=m_iWorldDepth>>1;
m_iNumVerts=m_iNumVertsPerRow*m_iNumVertsPerCol;
m_iNumTris=m_iNumCellsPerRow*m_iNumCellsPerCol*2;
m_iNumIndices=m_iNumTris*3;
//Create vertex buffer
HRESULT hr=m_pDevice->CreateVertexBuffer(m_iNumVerts * sizeof(TerrainVertex),
D3DUSAGE_WRITEONLY,
TerrainVertex::FVF,
D3DPOOL_MANAGED,
&m_pVB,
NULL);
if (FAILED(hr))
{
::MessageBox(0,"Failed to create vertex buffer",0,0);
}
//Create index buffer
hr=m_pDevice->CreateIndexBuffer(m_iNumIndices * sizeof(WORD),
D3DUSAGE_WRITEONLY,
D3DFMT_INDEX16,
D3DPOOL_MANAGED,
&m_pIB,
NULL);
if (FAILED(hr))
{
::MessageBox(0,"Failed to create index buffer",0,0);
}
//Compute vertices
TerrainVertex *pVerts=0;
m_pVB->Lock(0,0,(void **)&pVerts,0);
CreateVertices(pVerts);
m_pVB->Unlock();
//Compute indices
WORD *pIndices=0;
m_pIB->Lock(0,0,(void **)&pIndices,0);
CreateIndices(pIndices);
m_pIB->Unlock();
//Compute normals
CreateNormals();
}
void CTerrain::CreateVertices(TerrainVertex *pVerts)
{
int iStartX=-m_iWorldWidth2;
int iStartZ=m_iWorldDepth2;
int iEndX=m_iWorldWidth2;
int iEndZ=-m_iWorldDepth2;
int iNumVerts=0;
float fUInc=1.0f/(float)m_iNumCellsPerRow;
float fVInc=1.0f/(float)m_iNumCellsPerCol;
int r=0,c=0;
int iOffset=0;
for (int z=iStartZ;z>=iEndZ;z-=m_iCellSize)
{
c=0;
for (int x=iStartX;x<=iEndX;x+=m_iCellSize)
{
int index=r*m_iNumVertsPerCol+c;
float fHeight=(float)m_pHeightMap[index];
//fScaleHeight=128.0f;
pVerts[index]=TerrainVertex((float)x,fHeight,(float)z,(float)c*fUInc*64.0f,
(float)r*fVInc*64.0f);
//Detail texture coords
pVerts[index].du=(float)c*fUInc*256.0f;
pVerts[index].dv=(float)r*fVInc*256.0f;
c++;
}
r++;
}
}
void CTerrain::CreateIndices(WORD *pIndices)
{
int iOffset=0;
int iVert=0;
for (int i=0;i<m_iNumCellsPerCol;i++)
{
for (int j=0;j<m_iNumCellsPerRow;j++)
{
pIndices[iVert]=i*m_iNumVertsPerRow+j;
pIndices[iVert+1]=i*m_iNumVertsPerRow+j+1;
pIndices[iVert+2]=(i+1)*m_iNumVertsPerRow+j;
pIndices[iVert+3]=(i+1)*m_iNumVertsPerRow+j;
pIndices[iVert+4]=i*m_iNumVertsPerRow+j+1;
pIndices[iVert+5]=(i+1)*m_iNumVertsPerRow+j+1;
iVert+=6;
}
}
}
I highly recommend you place this into a quadtree and pass the constraints of each node of the tree to a similar CreateVertices() function which will create the vertices and compute the bounding volume for the patch. This will create patches of terrain. At render time you simply traverse the quadtree and do a frustrum cull down the tree. Here are the rules.
- If the bounding volume of the patch is out, all it's children are also out and do not need to be checked. This patch and all of it's children are not rendered.
- If the bounding volume of the patch is partially in, you must check it's children. If one of the children's bounding volume is out, then all of it's children are also out. Render only the patches that are in or partially in. At this point you may choose to also render the patches that are partially in. It will take more time for you to code an algo to split the meshes along the frustum than it will for the graphic card to simply cull those triangles that are out.
- If the bounding volume of the patch is completely in, then all of it's children are also completely in. Therefore you do NOT check the children and you simply render the entire patch and all of it's children.
This is an extremely fast process and rendered terrain with no LOD on my GeForce 3 64MB card at a whopping 90 FPS (good for that card) with over 3 million vertices in the grid. A quadtree structure may look like this:
Code:
struct TerrainPatch
{
CTerrain *pParent;
CTerrain *pUL; //Upper left
CTerrain *pUR; //Upper right
CTerrain *pLL; //Lower left
CTerrain *pLR; //Lower right
CBoundingVolume *pBounds; //Bounding volume for this patch
};
The parent node is so you can easily traverse the tree from anywhere in the tree. It does not have to be included for this to work as you will always be drilling down the tree and never up during rendering. I use it for other items in my system.
If you want to implement terrain LOD I recommend talking to Perspective as I do not yet have a working implementation.