Sign in to follow this  
xynapse

RTS Game - The terrain approach

Recommended Posts

Dear GameDev's,

i am up to create a new engine from scratch for a typical strategy game like "Settlers V" - where camera can go 'around' a unit, zoom in, zoom out, but never sees the horizon.

My terrain (heightmap) shall be around 5km x 5km - which is 5120x5120 RAW heightmap that is to be loaded when engine is initializing and we are talking about PC platform here.

This is exactly what i need to achieve in terms of Camera view, no more of the terrain ahead will be visible.

[img]http://www.deafgamers.com/05screenshots/thesettlers5pic3.jpg[/img]




Camera is limited to see 50 meters around player when max zoomed out - and that's the limit, no more can be seen.

[img]http://extraordinaire.pl/gd/approach0.jpg[/img]




I need to render only this part that is visible to the camera frustum as fast as possible, so i can have more power for frag shaders / eventual post processing effect to add (which is obvious).




There is only one small question at the start i would like to ask before i go further:

a) What kind of approach should i take to store such big terrain when loading to the engine ( should i cut this terrain into pieces - how do i handle them, separate vbos, how big shall be the one 'piece' )

b) After successful load, how do i know which part of the terrain should i render ? ( how is this connected to the terrain VBO / VBOs )





Is there anything that covers point a/b in a single well known solution?


Somebody told me that i should use QuadTree Frustum Culling for this type of game, can i ask you for an advise please?




Thank you for your help.

Share this post


Link to post
Share on other sites
Personally, I wouldn't use a quadtree for this. Given the constraints on the camera, you can achieve faster results using a simpler grid partition, and just calculating the intersection of the frustum against the grid.

Consider that you split your terrain into a grid of chunks. The terrain has a minimum Y and a maximum Y bound; that is, there are values of Y for which the terrain will lie between, forming two upper and lower bounding planes on the world. You can reverse project the top-left and top-right corners of the screen against the Lower Y plane, and reverse project the bottom-left and bottom-right corners of the screen against the Upper Y plane, to calculate four points of a box-shape that outline the visible area. Using the X/Z values of the reverse-projected area, you can determine which grid squares the points lie within, then use some sort of polygon rasterization algorithm to iterate and draw just the grid units covered by the frustum box. Remember to extend the corners of the box outward from the center of the box by some arbitrary amount (determined by testing, as it depends upon the size of your grid units relative to the screen) in order to provide sufficient padding at the edges of the screen so that no blank areas are shown, where the view box crosses just the corner of a grid unit, leaving that grid unit undrawn.

Calculating the visible grids directly in this fashion eliminates the recursion of the quadtree, which can result in a significant number of redundant tests.

The grid unit chunks could be separate VBOs, or you could just have one large VBO and use separate index buffers for the grid units.

Share this post


Link to post
Share on other sites
JTippetts, thank you again for your help - i will go without quad-tree rendering for this engine.



As soon as i manage to load and divide the terrain into cells, and get this thing going on a single VBO with separate index buffers

I will come back here to ask again about the procedure to compute which cells to render in regards to current view.




Time to get to work.




Share this post


Link to post
Share on other sites
Hidden
(LOUD THINKING)




OK have my terrain loaded, time to setup the Indexes as my VBO is constructed at this moment.




I use a 1024x1024 raw height field for start.

I split this 1024x1024 terrain into let's say 32x32 chunks which i call [b]Cells[/b].

That means :




[quote]1024 / 32 = [b]32 cells in X - width[/b]

1024 / 32 = [b]32 cells int Z - height[/b]

[/quote]

which is 1024 cells in total ( Index buffers ).


If there's something I'm doing wrong at this step, please let me know.




After i load the vertexes into a VBO buffer i need to create those per-cell Index Buffers and store them in a vector for later enumerator usage.

- First thing is to calculate index value for each vertex within a Cell.

- iterate through the starting X and Z till ending X and Z values for each cell

(0,0 -> 32,32)

(32,32 -> 64,64)

(64,64 -> 96,96)

(.............................)

- Calculate the index values for this cell push'ing_back each index value to a:

[code] std::vector<unsigned short> m_vIndices;[/code]




- After we calculate them, generate the Index buffer for each cell and get rid of the m_vIndices to free the memory.




This is exactly where i am at right now and don't want to make any mistake.

Having VBO ready i need to find a good mechanism to handle those Index Buffers for later usage.




[b]0[/b]) Do we see a potential failure at the start with such a large number of index buffers ?

1) Is it ok to hold Cells Index Buffers in a std::vector<GLuint> ? - how do i access them later on while looking for a cell by having the X,Z values - possible?

[b]2[/b]) How do i generate indexes per Cell to render them efficiently - i believe strips should do the thing, any good ready - to - go pseudo code so i could have a look please?




Hopefully i managed to write it clear.



















Share this post


Link to post
Hidden
Should i go with with:

glDrawElements([b]GL_TRIANGLE_STRIP[/b]


or

glDrawElements(GL_[b]TRIANGLES[/b]


For the terrain rendering?




Quick notes:

[quote]


<::CTerrain::Create> [2011/10/5 19:14:11] Loading "terrain.raw" terrain file, width=1024 height=1024.

<::CTerrain::Create> [2011/10/5 19:14:11] Terrain debug: Cells after splitting: 64x64
<::CTerrain::Create> [2011/10/5 19:14:12] Terrain debug: Indexes : 2097150
<::CTerrain::Create> [2011/10/5 19:14:12] Terrain debug: Vertexes: 1048576



[/quote]


Share this post


Link to post
As promised i'm back with the news and a source code listing - so someone might find it useful.

First of all i'll drop the terrain class here so we all know what i am talking about.





Terrain.h

[code]

namespace XYNAPSE
{

// --[ Struct ]---------------------------------------------------------------
//
// - Class :
// - Prototype : struct tTerrainCell
//
// - Purpose : This holds index buffer data per cell
//
// -----------------------------------------------------------------------------
struct tTerrainCell
{
GLuint m_uiIBO;
int m_iIndexesCount;
// more to come.
};


class CTerrain
{

public:
CTerrain();
bool Create(const std::string& strFileName, int iWidth, int iHeight);
void Destroy();
void Render(const CMatrix4x4& mViewMatrix,const CMatrix4x4& mProjectionMatrix);
void Update(const CVector3& vOriginPosition);

private:
int m_iHeight;
int m_iWidth;
int m_iVertexCount;
int hLOD;

std::vector<XYN_vbo_vert> m_vVertices;
std::vector<tTerrainCell> m_vCells;

GLuint m_uiVAO;
GLuint m_uiVBOVertices;
GLuint m_usIndices;
CMatrix4x4 m_matModel;

XYNAPSE::CShader *m_pShader;

BYTE hHeightField[1024][1024];


};

}

[/code]




Terrain.cpp ( creation / render methods )

[code]

// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : bool CTerrain::Create(const std::string& strFileName, int iWidth, int iHeight)
//
// - Purpose : This reads in the height data from a raw file and splits the map into defined cells
// The cell size is called: iTerrainSplitCellSize
//
// We generate ONE single VBO that holds vertexes for the whole map
// And separate per-cell IBOs
//
// So after loading we get:
// Index buffers = ( m_iHeight / iTerrainSplitCellSize ) * ( m_iWidth / iTerrainSplitCellSize )
// Vertex buffer = 1
//
// -----------------------------------------------------------------------------
bool CTerrain::Create(const std::string& strFileName, int iWidth, int iHeight)
{
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Loading \"%s\" terrain file, width=%i height=%i.",strFileName.data(),iWidth,iHeight);

// Load the heights to a byte array
// If fp failed, return false.
m_iHeight = iHeight;
m_iWidth = iWidth;

FILE *fp=NULL;
fp = fopen(strFileName.data(), "rb");
if(!fp)
{
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"ERROR, Couldn't load \"%s\" terrain file, file not found.",strFileName.data());
return false;
}
// Sanity check
size_t result;
result = fread(hHeightField, 1, m_iWidth * m_iHeight, fp);
fclose(fp);

if(result != (m_iWidth * m_iHeight) )
{
return false;
}


// Ok time to split the terrain into iTerrainSplitCellSize cells
// Here we define the Cell Size
int iTerrainSplitCellSize = 16;
int iCellsAfterSplittingHeight = m_iHeight/iTerrainSplitCellSize;
int iCellsAfterSplittingWidth = m_iWidth/iTerrainSplitCellSize;

// Generate Vertex buffer
for(int x=0;x<m_iWidth;x++)
{
for(int z=0;z<m_iHeight;z++)
{

XYN_vbo_vert newVertexData;
newVertexData.x=x;
newVertexData.y=0;
newVertexData.z=z;
m_vVertices.push_back(newVertexData);
}
}

// Generate separate IBO's for each cell
for(int iCellX=0;iCellX<iCellsAfterSplittingWidth;iCellX++)
{
for(int iCellZ=0;iCellZ<iCellsAfterSplittingHeight;iCellZ++)
{
int iCellXEnd=(iCellX*iTerrainSplitCellSize)+iTerrainSplitCellSize;
int iCellZEnd=(iCellZ*iTerrainSplitCellSize)+iTerrainSplitCellSize;

std::vector<unsigned short> vIndices;

for(int x=iCellX*iTerrainSplitCellSize; x < ( iCellXEnd ); x++)
{
for(int z=iCellZ*iTerrainSplitCellSize; z < ( iCellZEnd ); z++)
{
vIndices.push_back( x * m_iWidth + z );
vIndices.push_back( (x + 1) * m_iWidth + z);
}

}

// Create new Cell objet
tTerrainCell newCell;

// Store the number of indexes for this cell
newCell.m_iIndexesCount=vIndices.size();

// Generate the IBO
glGenBuffers( 1, &newCell.m_uiIBO);
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newCell.m_uiIBO );
glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * vIndices.size(), &vIndices[0], GL_STATIC_DRAW );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );

// Push back newly created cell to our list
m_vCells.push_back(newCell);

// Pop some debug note
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: Cell[%i][%i] Indexes: %i\n",iCellX,iCellZ,vIndices.size());
}
}

// Generate the VBO for the whole mesh
glGenBuffers(1, &m_uiVBOVertices);
glBindBuffer(GL_ARRAY_BUFFER, m_uiVBOVertices);
glBufferData(GL_ARRAY_BUFFER, m_vVertices.size() * sizeof(XYN_vbo_vert), &m_vVertices[0], GL_STATIC_DRAW_ARB);
glBindBuffer( GL_ARRAY_BUFFER, 0 );

// Set the Model matrix to 0,0,0
m_matModel.Translate(0,0,0);

// Create terrain shader for rendering tests purposes
m_pShader = new XYNAPSE::CShader;
if(m_pShader->loadShader("data/shaders/terrain.vert","data/shaders/terrain.frag")==false)
return false;

// All clear!
return true;
}





// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : void CTerrain::Render(const CMatrix4x4& mViewMatrix,const CMatrix4x4& mProjectionMatrix)
//
// - Purpose : Renders visible cells of the terrain that were previously
// calculated by Update method.
//
// -----------------------------------------------------------------------------
void CTerrain::Render(const CMatrix4x4& mViewMatrix,const CMatrix4x4& mProjectionMatrix)
{

// Turn the shader on
m_pShader->TurnOn();

// Inject matrices to the shader
m_pShader->SetMatrix4x4(m_pShader->getUniform("m_Model"), m_matModel);
m_pShader->SetMatrix4x4(m_pShader->getUniform("m_View"), mViewMatrix);
m_pShader->SetMatrix4x4(m_pShader->getUniform("m_Projection"), mProjectionMatrix);

// Bind the VBO
glBindBuffer(GL_ARRAY_BUFFER, m_uiVBOVertices);

//Setup the VertexPosition
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),(char*)0);

//Setup the VertexNormal
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*5));

//Setup the VertexTexCoord
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*3));

//Setup the VertexTangent
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*8));

// Bind the selected Cell IBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vCells[0].m_uiIBO);

// Draw Triangle strip
glDrawElements(GL_TRIANGLE_STRIP,m_vCells[0].m_iIndexesCount,GL_UNSIGNED_SHORT,0);

// Boil out with the bindings
glBindBuffer( GL_ARRAY_BUFFER, 0 );
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);

// Turn the shader off
m_pShader->TurnOff();


}
[/code]




This works as it is intended to, the index buffers consist of a :


[quote]

<XYNAPSE::CTerrain::Create> [2011/10/6 10:43:31] Terrain debug: Cell[0][0] Indexes: 512


[/quote]

512 entries for 16x16 Cell.




Rendering with strips looks promising too

[img]http://extraordinaire.pl/gd/approach2.jpg[/img]




The next thing i want to ask is the data-holding approach.

Having in mind a map like 256x256 would create :


[quote]<XYNAPSE::CTerrain::Create> [2011/10/6 11:12:15] Terrain debug: Cells created: 256[/quote]

[b]256[/b] Index buffers.




- Now is this something that i should be worried about in future ( for example when speaking of a map like 2048 x 2048 ) ?










Share this post


Link to post
Share on other sites
Ok i have switched from rendering triangle strips to indexed triangles as strips were causing 'known' problems when a connection between few cells was made.

Now it's working fine and it is time now to find out which Cells to render by known the player position.




Knowing that my cells are stored in a



[code]

struct tTerrainCell
{
GLuint m_uiIBO;
int m_iIndexesCount;
};


std::vector<tTerrainCell> m_vCells;

[/code]

and my player is located let's say :

[b]x: 120, z: 46[/b]




I need to calculate the number that will point to the cell he is currently standing in.




[img]http://extraordinaire.pl/gd/approach3.jpg[/img]




Anyone has any good idea ?







Ps. as promissed, updated terrain class below.

[code]


namespace XYNAPSE
{

// --[ Struct ]---------------------------------------------------------------
//
// - Class :
// - Prototype : struct tTerrainCell
//
// - Purpose : This holds index buffer data per cell
//
// -----------------------------------------------------------------------------
struct tTerrainCell
{
GLuint m_uiIBO;
int m_iIndexesCount;
};


class CTerrain
{

public:
CTerrain();
bool Create(const std::string& strFileName, int iSize);
void Destroy();
void Render(const CMatrix4x4& mViewMatrix,const CMatrix4x4& mProjectionMatrix);
void Update(const CVector3& vOriginPosition);

private:
int m_iSize;
int m_iVertexCount;

unsigned int HeightIndexAt(int x, int z);
float HeightAtPixel(float x, float z);
CVector3 NormalAtPixel(int x, int z);



std::vector<tTerrainCell> m_vCells;

GLuint m_uiVAO;
GLuint m_uiVBOVertices;
GLuint m_usIndices;
CMatrix4x4 m_matModel;

XYNAPSE::CShader *m_pShader;

BYTE pHeightMap[1024*1024];

};

}

[/code]

[code]


namespace XYNAPSE
{
CTerrain::CTerrain()
{

}
void CTerrain::Destroy()
{
// Delete the shader object
if(m_pShader!=NULL)
{
m_pShader->Delete();
SAFE_DELETE(m_pShader);
}

// Delete the VBO buffer
if(m_uiVBOVertices!=NULL)
{
glDeleteBuffers(1,&m_uiVBOVertices);
}

// Delete the Index Buffers from the Cells
for(int a=0;a<m_vCells.size();a++)
{
if(m_vCells[a].m_uiIBO)
{
glDeleteBuffers(1, &m_vCells[a].m_uiIBO);
}

}

}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : bool CTerrain::Create(const std::string& strFileName, int iWidth, int iHeight)
//
// - Purpose : This reads in the height data from a raw file and splits the map into defined cells
// The cell size is called: iTerrainSplitCellSize
//
// We generate ONE single VBO that holds vertexes for the whole map
// And separate per-cell IBOs
//
// So after loading we get:
// Index buffers = ( m_iHeight / iTerrainSplitCellSize ) * ( m_iWidth / iTerrainSplitCellSize )
// Vertex buffer = 1
//
// -----------------------------------------------------------------------------
bool CTerrain::Create(const std::string& strFileName, int iSize)
{

// Load the heights to a byte array
// If fp failed, return false.
m_iSize = iSize;

XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Loading \"%s\" terrain file, size=%ix%i.",strFileName.data(),m_iSize,m_iSize);

FILE *fp=NULL;
fp = fopen(strFileName.data(), "rb");
if(!fp)
{
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"ERROR, Couldn't load \"%s\" terrain file, file not found.",strFileName.data());
return false;
}
// Sanity check
size_t result;
result = fread(pHeightMap, 1, m_iSize * m_iSize, fp);
fclose(fp);

if(result != (m_iSize * m_iSize) )
{
return false;
}


// Ok time to split the terrain into iTerrainSplitCellSize cells
// Here we define the Cell Size
int iTerrainSplitCellSize = 16;
int iCellsAfterSplittingHeight = m_iSize/iTerrainSplitCellSize;
int iCellsAfterSplittingWidth = m_iSize/iTerrainSplitCellSize;




// Generate Vertex buffer
std::vector<XYN_vbo_vert> m_vVertices;
for(int x=0;x<m_iSize;x++)
{
for(int z=0;z<m_iSize;z++)
{

XYN_vbo_vert newVertexData;
newVertexData.vPosition=CVector3(x,0,z);
m_vVertices.push_back(newVertexData);
}
}
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: VBO %i entries.",m_vVertices.size());
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: Cells after splitting %i x %i",iCellsAfterSplittingHeight,iCellsAfterSplittingWidth);


// Generate separate IBO's for each cell
for(int iCellX=0;iCellX<iCellsAfterSplittingWidth;iCellX++)
{
for(int iCellZ=0;iCellZ<iCellsAfterSplittingHeight;iCellZ++)
{


std::vector<unsigned short> vIndices;

int startX=iCellX*iTerrainSplitCellSize;
int startZ=iCellZ*iTerrainSplitCellSize;
int endX=(startX)+iTerrainSplitCellSize;
int endZ=(startZ)+iTerrainSplitCellSize;
for (int x = startX; x < endX ; ++x)
{
for (int z = startZ; z < endZ ; ++z)
{

// CCW


//
// *2
// | \
// *1 _*3
////////////
vIndices.push_back(x*m_iSize + z);
vIndices.push_back((x+1)*m_iSize + z);
vIndices.push_back(x*m_iSize + z + 1);


//
// *1-*2
// |
// *3
/////////////
vIndices.push_back((x+1)*m_iSize + z);
vIndices.push_back((x+1)*m_iSize + z + 1);
vIndices.push_back(x*m_iSize + z + 1);

}

}


// Create new Cell objet
tTerrainCell newCell;

// Store the number of indexes for this cell
newCell.m_iIndexesCount=vIndices.size();

// Generate the IBO
glGenBuffers( 1, &newCell.m_uiIBO);
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newCell.m_uiIBO );
glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * vIndices.size(), &vIndices[0], GL_STATIC_DRAW );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );

// Push back newly created cell to our list
m_vCells.push_back(newCell);

}
}


XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: Cells created: %i\n",m_vCells.size());


// Generate the VBO for the whole mesh
glGenBuffers(1, &m_uiVBOVertices);
glBindBuffer(GL_ARRAY_BUFFER, m_uiVBOVertices);
glBufferData(GL_ARRAY_BUFFER, m_vVertices.size() * sizeof(XYN_vbo_vert), &m_vVertices[0], GL_STATIC_DRAW_ARB);
glBindBuffer( GL_ARRAY_BUFFER, 0 );

// Set the Model matrix to 0,0,0
m_matModel.Clear();

// Create terrain shader for rendering tests purposes
m_pShader = new XYNAPSE::CShader;
if(m_pShader->loadShader("data/shaders/terrain.vert","data/shaders/terrain.frag")==false)
return false;

// All clear!
return true;
}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : void CTerrain::Update(const CVector3& vOriginPosition);
//
// - Purpose : This generates a list of terrain cells to render according to
// The position in vOriginPosition vector.
//
// -----------------------------------------------------------------------------
void CTerrain::Update(const CVector3& vOriginPosition)
{

}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : void CTerrain::Render(const CMatrix4x4& mViewMatrix,const CMatrix4x4& mProjectionMatrix)
//
// - Purpose : Renders visible cells of the terrain that were previously
// calculated by Update method.
//
// -----------------------------------------------------------------------------
void CTerrain::Render(const CMatrix4x4& mViewMatrix,const CMatrix4x4& mProjectionMatrix)
{

// Turn the shader on
m_pShader->TurnOn();

// Inject matrices to the shader
m_pShader->SetMatrix4x4(m_pShader->getUniform("m_Model"), m_matModel);
m_pShader->SetMatrix4x4(m_pShader->getUniform("m_View"), mViewMatrix);
m_pShader->SetMatrix4x4(m_pShader->getUniform("m_Projection"), mProjectionMatrix);

// Bind the VBO
glBindBuffer(GL_ARRAY_BUFFER, m_uiVBOVertices);

//Setup the VertexPosition
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),(char*)0);

//Setup the VertexNormal
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*5));

//Setup the VertexTexCoord
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*3));

//Setup the VertexTangent
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*8));

// Bind the selected Cell IBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vCells[0].m_uiIBO);

// Draw Triangle strip
glDrawElements(GL_TRIANGLES,m_vCells[0].m_iIndexesCount,GL_UNSIGNED_SHORT,0);


// Boil out with the bindings
glBindBuffer( GL_ARRAY_BUFFER, 0 );
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);

// Turn the shader off
m_pShader->TurnOff();


}
}

[/code]










Share this post


Link to post
Share on other sites
Ok, coming back with the results once more.




I convert given X,Z position of some point to find a Cell by calling Update method before i render the terrain.

The given X,Z values are scaled to a number of cells created.




This is how it is happening and works as expected.

[code]

// Find out which cell array belongs to our current camera position
int iCellX = (vOriginPosition.x/m_iTerrainSplitCellSize);
int iCellZ = (vOriginPosition.z/m_iTerrainSplitCellSize);

// Do some minimals fix
if(iCellX<0) iCellX = 0;
if(iCellZ<0) iCellZ = 0;

// Do some maximals fix
if(iCellX>m_iSize/m_iTerrainSplitCellSize) iCellX = m_iSize/m_iTerrainSplitCellSize;
if(iCellZ>m_iSize/m_iTerrainSplitCellSize) iCellZ = m_iSize/m_iTerrainSplitCellSize;

// This is the index to our vector holding cells
// We render m_iCurrentCell index buffer to render
// the cell that belongs to position vOriginPosition
m_iCurrentCell = iCellX * (m_iSize/m_iTerrainSplitCellSize) + iCellZ;

[/code]



[media]http://www.youtube.com/watch?v=KwwWap2qJ9U[/media]



[b]Next step.[/b]

[i][u]Render cells that are visible within the viewport. [/u][/i]




And here comes the question in regards to:

[quote]

You can reverse project the top-left and top-right corners of the screen against the Lower Y plane, and reverse project the bottom-left and bottom-right corners of the screen against the Upper Y plane, to calculate four points of a box-shape that outline the visible area. Using the X/Z values of the reverse-projected area, you can determine which grid squares the points lie within, then use some sort of polygon rasterization algorithm to iterate and draw just the grid units covered by the frustum box.

[/quote]




JTippetts - can i ask you to shed a bit of light on this - maybe some example ?












As always, dropping here the updated Terrain Class for anyone who is interested.




[code]


namespace XYNAPSE
{

// --[ Struct ]---------------------------------------------------------------
//
// - Class :
// - Prototype : struct tTerrainCell
//
// - Purpose : This holds index buffer data per cell
//
// -----------------------------------------------------------------------------
struct tTerrainCell
{
GLuint m_uiIBO;
int m_iIndexesCount;
};

// --[ Class ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Purpose : This class is responsible for generating the VBO / Cells IBO's
// : From the heightmap file.
// : Also this class handles all updates and rendering of the terrain
// -----------------------------------------------------------------------------
class CTerrain
{

public:
CTerrain();

bool Create(const std::string& strFileName, int iSize); // Creates the terrain from heightmap file
void Destroy(); // Proper way of destructing the Terrain object
void Render(const CMatrix4x4& mViewMatrix,const CMatrix4x4& mProjectionMatrix); // Renders the terrain cells we're in
CVector2 Update(const CVector3& vOriginPosition); // Call from the engine before rendering the frame

private:
int m_iSize; // This is the map size m_iSize x m_iSize
int m_iTerrainSplitCellSize; // Cell size
int m_iCurrentCell; // Current cell we're in - temporary for tests

std::vector<tTerrainCell> m_vCells; // This holds our cells with index buffers

GLuint m_uiVAO; // Not used
GLuint m_uiVBOVertices; // VBO for the whole heightmap
CMatrix4x4 m_matModel; // Model matrix

XYNAPSE::CShader *m_pShader; // Shader for rendering purposes

BYTE pHeightMap[1024*1024]; // This is where our heightmap is stored

};

}

[/code]




[code]



namespace XYNAPSE
{
CTerrain::CTerrain()
{

}
void CTerrain::Destroy()
{
// Delete the shader object
if(m_pShader!=NULL)
{
m_pShader->Delete();
SAFE_DELETE(m_pShader);
}

// Delete the VBO buffer
if(m_uiVBOVertices!=NULL)
{
glDeleteBuffers(1,&m_uiVBOVertices);
}

// Delete the Index Buffers from the Cells
for(int a=0;a<m_vCells.size();a++)
{
if(m_vCells[a].m_uiIBO)
{
glDeleteBuffers(1, &m_vCells[a].m_uiIBO);
}

}

}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : bool CTerrain::Create(const std::string& strFileName, int iWidth, int iHeight)
//
// - Purpose : This reads in the height data from a raw file and splits the map into defined cells
// The cell size is called: iTerrainSplitCellSize
//
// We generate ONE single VBO that holds vertexes for the whole map
// And separate per-cell IBOs
//
// So after loading we get:
// Index buffers = ( m_iHeight / iTerrainSplitCellSize ) * ( m_iWidth / iTerrainSplitCellSize )
// Vertex buffer = 1
//
// -----------------------------------------------------------------------------
bool CTerrain::Create(const std::string& strFileName, int iSize)
{

// Load the heights to a byte array
// If fp failed, return false.
m_iSize = iSize;

XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Loading \"%s\" terrain file, size=%ix%i.",strFileName.data(),m_iSize,m_iSize);

FILE *fp=NULL;
fp = fopen(strFileName.data(), "rb");
if(!fp)
{
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"ERROR, Couldn't load \"%s\" terrain file, file not found.",strFileName.data());
return false;
}
// Sanity check
size_t result;
result = fread(pHeightMap, 1, m_iSize * m_iSize, fp);
fclose(fp);

if(result != (m_iSize * m_iSize) )
{
return false;
}


// Ok time to split the terrain into iTerrainSplitCellSize cells
// Here we define the Cell Size
m_iTerrainSplitCellSize = 16;
int iCellsAfterSplittingHeight = m_iSize/m_iTerrainSplitCellSize;
int iCellsAfterSplittingWidth = m_iSize/m_iTerrainSplitCellSize;




// Generate Vertex buffer
std::vector<XYN_vbo_vert> m_vVertices;
for(int x=0;x<m_iSize;x++)
{
for(int z=0;z<m_iSize;z++)
{

XYN_vbo_vert newVertexData;
newVertexData.vPosition=CVector3(x,0,z);
m_vVertices.push_back(newVertexData);
}
}
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: VBO %i entries.",m_vVertices.size());
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: Cells after splitting %i x %i",iCellsAfterSplittingHeight,iCellsAfterSplittingWidth);


// Generate separate IBO's for each cell
for(int iCellX=0;iCellX<iCellsAfterSplittingWidth;iCellX++)
{
for(int iCellZ=0;iCellZ<iCellsAfterSplittingHeight;iCellZ++)
{


std::vector<unsigned short> vIndices;

int startX=iCellX*m_iTerrainSplitCellSize;
int startZ=iCellZ*m_iTerrainSplitCellSize;
int endX=(startX)+m_iTerrainSplitCellSize;
int endZ=(startZ)+m_iTerrainSplitCellSize;
for (int x = startX; x < endX ; ++x)
{
for (int z = startZ; z < endZ ; ++z)
{

// CCW


//
// *2
// | \
// *1 _*3
////////////
vIndices.push_back(x*m_iSize + z);
vIndices.push_back((x+1)*m_iSize + z);
vIndices.push_back(x*m_iSize + z + 1);


//
// *1-*2
// |
// *3
/////////////
vIndices.push_back((x+1)*m_iSize + z);
vIndices.push_back((x+1)*m_iSize + z + 1);
vIndices.push_back(x*m_iSize + z + 1);

}

}


// Create new Cell objet
tTerrainCell newCell;

// Store the number of indexes for this cell
newCell.m_iIndexesCount=vIndices.size();

// Generate the IBO
glGenBuffers( 1, &newCell.m_uiIBO);
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newCell.m_uiIBO );
glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * vIndices.size(), &vIndices[0], GL_STATIC_DRAW );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );

// Push back newly created cell to our list
m_vCells.push_back(newCell);

}
}


XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: Cells created: %i\n",m_vCells.size());


// Generate the VBO for the whole mesh
glGenBuffers(1, &m_uiVBOVertices);
glBindBuffer(GL_ARRAY_BUFFER, m_uiVBOVertices);
glBufferData(GL_ARRAY_BUFFER, m_vVertices.size() * sizeof(XYN_vbo_vert), &m_vVertices[0], GL_STATIC_DRAW_ARB);
glBindBuffer( GL_ARRAY_BUFFER, 0 );

// Set the Model matrix to 0,0,0
m_matModel.Clear();

// Create terrain shader for rendering tests purposes
m_pShader = new XYNAPSE::CShader;
if(m_pShader->loadShader("data/shaders/terrain.vert","data/shaders/terrain.frag")==false)
return false;

// All clear!
return true;
}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : void CTerrain::Update(const CVector3& vOriginPosition);
//
// - Purpose : This generates a list of terrain cells to render according to
// The position in vOriginPosition vector.
//
// -----------------------------------------------------------------------------
CVector2 CTerrain::Update(const CVector3& vOriginPosition)
{

// Find out which cell array belongs to our current camera position
int iCellX = (vOriginPosition.x/m_iTerrainSplitCellSize);
int iCellZ = (vOriginPosition.z/m_iTerrainSplitCellSize);

// Do some minimals fix
if(iCellX<0) iCellX = 0;
if(iCellZ<0) iCellZ = 0;

// Do some maximals fix
if(iCellX>m_iSize/m_iTerrainSplitCellSize) iCellX = m_iSize/m_iTerrainSplitCellSize;
if(iCellZ>m_iSize/m_iTerrainSplitCellSize) iCellZ = m_iSize/m_iTerrainSplitCellSize;

// This is the index to our vector holding cells
// We render m_iCurrentCell index buffer to render
// the cell that belongs to position vOriginPosition
m_iCurrentCell = iCellX * (m_iSize/m_iTerrainSplitCellSize) + iCellZ;

// For debug usage, so we know in which cell vOriginPosition is placed
return CVector2(iCellX,iCellZ);
}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : void CTerrain::Render(const CMatrix4x4& mViewMatrix,const CMatrix4x4& mProjectionMatrix)
//
// - Purpose : Renders visible cells of the terrain that were previously
// calculated by Update method.
//
// -----------------------------------------------------------------------------
void CTerrain::Render(const CMatrix4x4& mViewMatrix,const CMatrix4x4& mProjectionMatrix)
{

// Turn the shader on
m_pShader->TurnOn();

// Inject matrices to the shader
m_pShader->SetMatrix4x4(m_pShader->getUniform("m_Model"), m_matModel);
m_pShader->SetMatrix4x4(m_pShader->getUniform("m_View"), mViewMatrix);
m_pShader->SetMatrix4x4(m_pShader->getUniform("m_Projection"), mProjectionMatrix);

// Bind the VBO
glBindBuffer(GL_ARRAY_BUFFER, m_uiVBOVertices);

//Setup the VertexPosition
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),(char*)0);

//Setup the VertexNormal
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*5));

//Setup the VertexTexCoord
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*3));

//Setup the VertexTangent
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*8));

// Bind the selected Cell IBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vCells[m_iCurrentCell].m_uiIBO);

// Draw Triangle strip
glDrawElements(GL_TRIANGLES,m_vCells[m_iCurrentCell].m_iIndexesCount,GL_UNSIGNED_SHORT,0);


// Boil out with the bindings
glBindBuffer( GL_ARRAY_BUFFER, 0 );
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);

// Turn the shader off
m_pShader->TurnOff();


}
}

[/code]

Share this post


Link to post
Share on other sites
The four points that you obtain by reverse-projecting the screen against the planes bounding the terrain on Y will form a quadrilateral shape. You can calculate which cell each of the points is in by dividing the coordinate by the cell-size, and taking the floor to get the integral CellX, CellZ coordinates. Once you have these cells, you can use a rasterization technique to draw the cells. If you consider that your grid of cells is very much like a pixelated surface, where each cell is a pixel, then you can see how a basic software rasterization algorithm could be handy for drawing all of the visible cells. A real quick Google pulls up [url="http://joshbeam.com/articles/triangle_rasterization/"]some[/url] [url="http://chrishecker.com/Miscellaneous_Technical_Articles#Perspective_Texture_Mapping"]articles[/url] on software rasterization that could come in handy as references. The basics of rasterization, though, are that you iterate the rows, or scan lines, of a triangle primitive. The visible quad is split into triangles, and the triangles are rendered by calling each rasterized cell's draw routine.

Share this post


Link to post
Share on other sites
Thanks JTippetts - i am right now looking into this,

meanwhile - a working class update again, this time i have added few methods for 'getting' the height and normal values out of the raw heightmap file.




Here are the results of rendering one cell.

Terrain size: [b]1024x1024[/b]

VBO vertices:[b] 1048576[/b]


Terrain spitted into: [b] 64x64 Cells[/b]

Cells created: [b]16x16 which is 256 cells in total[/b][b]
[/b]

Each cell vertices: [b]64x64 = 4096[/b]

Indices per cell: [b]23814[/b][b]
[/b]

Triangles per cell: [b]23814 / 3 = 7938[/b]








[img]http://extraordinaire.pl/gd/approach4.jpg[/img]




[media]http://www.youtube.com/watch?v=lFFIGZsXbL8[/media]




Moving on to implement JTippetts method, will keep this updated - maybe somebody will find it useful.







Promised class updates.


[code]
namespace XYNAPSE
{

// --[ Struct ]---------------------------------------------------------------
//
// - Class :
// - Prototype : struct tTerrainCell
//
// - Purpose : This holds index buffer data per cell
//
// -----------------------------------------------------------------------------
struct tTerrainCell
{
GLuint m_uiIBO;
int m_iIndexesCount;
};

// --[ Class ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Purpose : This class is responsible for generating the VBO / Cells IBO's
// : From the heightmap file.
// : Also this class handles all updates and rendering of the terrain
// -----------------------------------------------------------------------------
class CTerrain
{

public:
CTerrain();

bool Create(const std::string& strFileName, int iSize, int iCellSize); // Creates the terrain from heightmap file
void Destroy(); // Proper way of destructing the Terrain object
void Render(const CMatrix4x4& m_mat4ViewMatrix,const CMatrix4x4& m_mat4ProjectionMatrix); // Renders the terrain cells we're in
CVector3 Update(const CVector3& vOriginPosition); // Call from the engine before rendering the frame

float HeightAt(float x, float z);
CVector3 NormalAt(float x, float z);
private:
int m_iHeightMapSize; // This is the map size m_iHeightMapSize x m_iHeightMapSize
int m_iCellSize; // Cell size
int m_iCurrentCell; // Current cell we're in - temporary for tests
GLuint m_uiVAO; // Not used
GLuint m_uiVBOVertices; // VBO for the whole heightmap
CMatrix4x4 m_mat4Model;
std::vector<tTerrainCell> m_vCells; // This holds our cells with index buffers
XYNAPSE::CShader *m_pShader; // Shader for rendering purposes
BYTE pHeightMap[1024*1024]; // This is where our heightmap is stored


unsigned int HeightIndexAt(int x, int z);
CVector3 NormalAtPixel(int x, int z);
float HeightAtPixel(int x, int z);
};

}[/code]




[code]


namespace XYNAPSE
{
CTerrain::CTerrain()
{

}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : void CTerrain::Destroy()
//
// - Purpose : Call this before deleting the terrain object
//
// -----------------------------------------------------------------------------
void CTerrain::Destroy()
{
// Delete the shader object
if(m_pShader!=NULL)
{
m_pShader->Delete();
SAFE_DELETE(m_pShader);
}

// Delete the VBO buffer
if(m_uiVBOVertices!=NULL)
{
glDeleteBuffers(1,&m_uiVBOVertices);
}

// Delete the Index Buffers from the Cells
for(int a=0;a<m_vCells.size();a++)
{
if(m_vCells[a].m_uiIBO)
{
glDeleteBuffers(1, &m_vCells[a].m_uiIBO);
}

}

}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : bool CTerrain::Create(const std::string& strFileName, int iWidth, int iHeight)
//
// - Purpose : This reads in the height data from a raw file and splits the map into defined cells
// The cell size is called: iTerrainSplitCellSize
//
// We generate ONE single VBO that holds vertexes for the whole map
// And separate per-cell IBOs
//
// So after loading we get:
// Index buffers = ( m_iHeight / iTerrainSplitCellSize ) * ( m_iWidth / iTerrainSplitCellSize )
// Vertex buffer = 1
//
// -----------------------------------------------------------------------------
bool CTerrain::Create(const std::string& strFileName, int iSize, int iCellSize)
{

// Load the heights to a byte array
// If fp failed, return false.
m_iHeightMapSize = iSize;
m_iCellSize = iCellSize;
int iCellsCount = m_iHeightMapSize/m_iCellSize;

XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Loading \"%s\" terrain file, size=%ix%i.",strFileName.data(),m_iHeightMapSize,m_iHeightMapSize);

FILE *fp=NULL;
fp = fopen(strFileName.data(), "rb");
if(!fp)
{
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"ERROR, Couldn't load \"%s\" terrain file, file not found.",strFileName.data());
return false;
}
// Sanity check
size_t result;
result = fread(pHeightMap, 1, m_iHeightMapSize * m_iHeightMapSize, fp);
fclose(fp);

if(result != (m_iHeightMapSize * m_iHeightMapSize) )
{
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"ERROR, failed to load %i x %i bytes.",m_iHeightMapSize*m_iHeightMapSize);
return false;
}

// Generate Vertex buffer
std::vector<XYN_vbo_vert> m_vVertices;
for(int x = 0; x < m_iHeightMapSize; x++)
{
for(int z = 0; z < m_iHeightMapSize; z++)
{

XYN_vbo_vert newVertexData;
newVertexData.vPosition=CVector3(x, HeightAt(x,z) ,z);
newVertexData.vNormals= NormalAt(x,z);
m_vVertices.push_back(newVertexData);
}
}
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: VBO %i entries.",m_vVertices.size());
XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: Cells after splitting %i x %i",iCellsCount,iCellsCount);

// Generate separate IBO for each cell
for(int iCellX=0;iCellX<iCellsCount;iCellX++)
{
for(int iCellZ=0;iCellZ<iCellsCount;iCellZ++)
{

// We need <unsigned int> to store the indices correctly
// As the last vertex number would for example: 1024x1024 = 1048576
// Unsigned short won't handle that.
std::vector<unsigned int> vIndices;

int startX = iCellX * m_iCellSize;
int startZ = iCellZ * m_iCellSize;

int endX = startX + m_iCellSize;
int endZ = startZ + m_iCellSize;
vIndices.clear();
for (int x = startX; x < endX -1 ; ++x)
{
for (int z = startZ; z < endZ -1 ; ++z)
{

// CCW
//
// *2
// | \
// *1 _*3
////////////
vIndices.push_back( x * m_iHeightMapSize + z);
vIndices.push_back( ( x + 1 ) * m_iHeightMapSize + z);
vIndices.push_back( x * m_iHeightMapSize + z + 1);
// CCW
//
// *1-*2
// \ |
// *3
/////////////
vIndices.push_back((x+1)*m_iHeightMapSize + z);
vIndices.push_back((x+1)*m_iHeightMapSize + z + 1);
vIndices.push_back(x*m_iHeightMapSize + z + 1);

}

}


// Create new Cell objet
tTerrainCell newCell;

// Store the number of indexes for this cell
newCell.m_iIndexesCount=vIndices.size();

XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: Cell[%i][%i] Indices: %i\n",iCellX,iCellZ,vIndices.size());

// Generate the IBO
bool bGLError=false;
glGenBuffers( 1, &newCell.m_uiIBO);
if(glGetError()) bGLError=true;

glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newCell.m_uiIBO );
if(glGetError()) bGLError=true;

glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * newCell.m_iIndexesCount, &vIndices[0], GL_STATIC_DRAW );
if(glGetError()) bGLError=true;

glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
if(glGetError()) bGLError=true;

// Error handling
if(bGLError==true)
{

XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"ERROR, glGenBuffers for Cell[%i][%i]\n",iCellX,iCellZ);
this->Destroy();
return false;
}


// Push back newly created cell to our list
m_vCells.push_back(newCell);


}
}




XYNAPSE::CLogger::Instance()->Write(XLOGEVENT_LOCATION,"Terrain debug: Cells created: %i\n",m_vCells.size());

// Generate the VBO for the whole mesh
glGenBuffers(1, &m_uiVBOVertices);
glBindBuffer(GL_ARRAY_BUFFER, m_uiVBOVertices);
glBufferData(GL_ARRAY_BUFFER, m_vVertices.size() * sizeof(XYN_vbo_vert), &m_vVertices[0], GL_STATIC_DRAW_ARB);
glBindBuffer( GL_ARRAY_BUFFER, 0 );

// Set the Model matrix to 0,0,0
m_mat4Model.Clear();

// Create terrain shader for rendering tests purposes
m_pShader = new XYNAPSE::CShader;

if(m_pShader->loadShader("data/shaders/terrain.vert","data/shaders/terrain.frag")==false)
return false;

// All clear!
return true;
}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : void CTerrain::Update(const CVector3& vOriginPosition);
//
// - Purpose : This generates a list of terrain cells to render according to
// The position in vOriginPosition vector.
//
// -----------------------------------------------------------------------------
CVector3 CTerrain::Update(const CVector3& vOriginPosition)
{
// Find out which cell array belongs to our current camera position
int iCellX = (vOriginPosition.x/m_iCellSize);
int iCellZ = (vOriginPosition.z/m_iCellSize);

// Do some minimals fix
if(iCellX<0) iCellX = 0;
if(iCellZ<0) iCellZ = 0;

// Do some maximals fix
if(iCellX>(m_iHeightMapSize/m_iCellSize)-1) iCellX = (m_iHeightMapSize/m_iCellSize)-1;
if(iCellZ>(m_iHeightMapSize/m_iCellSize)-1) iCellZ = (m_iHeightMapSize/m_iCellSize)-1;

// This is the index to our vector holding cells
// We render m_iCurrentCell index buffer to render
// the cell that belongs to position vOriginPosition,
m_iCurrentCell = iCellX * (m_iHeightMapSize/m_iCellSize) + iCellZ;

// For debug usage, so we know in which cell vOriginPosition is placed
return CVector3(iCellX,iCellZ,m_iCurrentCell);
}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : void CTerrain::Render(const CMatrix4x4& m_mat4ViewMatrix,const CMatrix4x4& m_mat4ProjectionMatrix)
//
// - Purpose : Renders visible cells of the terrain that were previously
// calculated by Update method.
//
// -----------------------------------------------------------------------------
void CTerrain::Render(const CMatrix4x4& m_mat4ViewMatrix,const CMatrix4x4& m_mat4ProjectionMatrix)
{
// Bind the VBO
glBindBuffer(GL_ARRAY_BUFFER, m_uiVBOVertices);

//Setup the in_VertexPosition
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),(char*)0);

//Setup the in_VertexNormal
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*5));

//Setup the in_VertexTexCoord
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*3));

//Setup the in_VertexTangent
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT,GL_FALSE,sizeof(XYN_vbo_vert),BUFFER_OFFSET(sizeof(GLfloat)*8));

// Bind the selected Cell IBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vCells[m_iCurrentCell].m_uiIBO);

// Turn the shader on
m_pShader->TurnOn();

// Inject matrices to the shader
m_pShader->SetMatrix4x4(m_pShader->getUniform("mat4_Model"), m_mat4Model);
m_pShader->SetMatrix4x4(m_pShader->getUniform("mat4_View"), m_mat4ViewMatrix);
m_pShader->SetMatrix4x4(m_pShader->getUniform("mat4_Projection"), m_mat4ProjectionMatrix);

// Draw indexed triangles
glDrawElements(GL_TRIANGLES,m_vCells[m_iCurrentCell].m_iIndexesCount,GL_UNSIGNED_INT,0);

// Turn the shader off
m_pShader->TurnOff();

// Boil out with the bindings
glBindBuffer( GL_ARRAY_BUFFER, 0 );
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);


}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : float CTerrain::HeightAt(float x, float z)
//
// - Purpose : Given a (x, z) position on the rendered height map this method
// calculates the exact height of the height map at that (x, z)
// position using bilinear interpolation.
//
// -----------------------------------------------------------------------------
float CTerrain::HeightAt(float x, float z)
{

int iGridSpacing = 1;
int iHeightScale = 1;
x /= static_cast<float>(iGridSpacing);
z /= static_cast<float>(iGridSpacing);

long ix = FloatToLong(x);
long iz = FloatToLong(z);
float topLeft = pHeightMap[HeightIndexAt(ix, iz)] * iHeightScale;
float topRight = pHeightMap[HeightIndexAt(ix + 1, iz)] * iHeightScale;
float bottomLeft = pHeightMap[HeightIndexAt(ix, iz + 1)] * iHeightScale;
float bottomRight = pHeightMap[HeightIndexAt(ix + 1, iz + 1)] * iHeightScale;
float percentX = x - static_cast<float>(ix);
float percentZ = z - static_cast<float>(iz);

return bilerp(topLeft, topRight, bottomLeft, bottomRight, percentX, percentZ);
}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : float CTerrain::HeightAt(float x, float z)
//
// - Purpose : Given a 2D height map coordinate, this method returns the index
// into the height map. This method wraps around for coordinates larger
// than the height map size.
//
// -----------------------------------------------------------------------------
unsigned int CTerrain::HeightIndexAt(int x, int z)
{
return (((x + m_iHeightMapSize) % m_iHeightMapSize) + ((z + m_iHeightMapSize) % m_iHeightMapSize) * m_iHeightMapSize);
}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : float CTerrain::HeightAtPixel(int x, int z)
//
// - Purpose : Returns a single float (raw) height value, without more complex calculations
//
// -----------------------------------------------------------------------------
float CTerrain::HeightAtPixel(int x, int z)
{
return pHeightMap[z * m_iHeightMapSize + x];
}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : CVector3 CTerrain::NormalAt(float x, float z)
//
// - Purpose : Given a (x, z) position on the rendered height map this method
// calculates the exact normal of the height map at that (x, z) position
// using bilinear interpolation.
//
// -----------------------------------------------------------------------------
CVector3 CTerrain::NormalAt(float x, float z)
{

int iGridSpacing = 1;
int iHeightScale = 1;

x /= static_cast<float>(iGridSpacing);
z /= static_cast<float>(iGridSpacing);

long ix = FloatToLong(x);
long iz = FloatToLong(z);

float percentX = x - static_cast<float>(ix);
float percentZ = z - static_cast<float>(iz);

CVector3 topLeft;
CVector3 topRight;
CVector3 bottomLeft;
CVector3 bottomRight;
CVector3 normal;

topLeft=NormalAtPixel(ix, iz);
topRight=NormalAtPixel(ix + 1, iz);
bottomLeft=NormalAtPixel(ix, iz + 1);
bottomRight=NormalAtPixel(ix + 1, iz + 1);

CVector3 vNormal;
vNormal = bilerp(topLeft, topRight, bottomLeft, bottomRight, percentX, percentZ);
vNormal.Normalize();
return vNormal;
}
// --[ Method ]---------------------------------------------------------------
//
// - Class : CTerrain
// - Prototype : CVector3 CTerrain::NormalAtPixel(int x, int z)
//
// - Purpose : Returns the normal at the specified location on the height map.
// The normal is calculated using the properties of the height map.
// This approach is much quicker and more elegant than triangulating the
// height map and averaging triangle surface normals.
//
// -----------------------------------------------------------------------------
CVector3 CTerrain::NormalAtPixel(int x, int z)
{
int iGridSpacing = 1;
int iHeightScale = 1;

CVector3 vNormal;

if (x > 0 && x < m_iHeightMapSize - 1)
vNormal.x = HeightAtPixel(x - 1, z) - HeightAtPixel(x + 1, z);
else if (x > 0)
vNormal.x = 2.0f * (HeightAtPixel(x - 1, z) - HeightAtPixel(x, z));
else
vNormal.x = 2.0f * (HeightAtPixel(x, z) - HeightAtPixel(x + 1, z));

if (z > 0 && z < m_iHeightMapSize - 1)
vNormal.z = HeightAtPixel(x, z - 1) - HeightAtPixel(x, z + 1);
else if (z > 0)
vNormal.z = 2.0f * (HeightAtPixel(x, z - 1) - HeightAtPixel(x, z));
else
vNormal.z = 2.0f * (HeightAtPixel(x, z) - HeightAtPixel(x, z + 1));

vNormal.y = 2.0f * iGridSpacing;
vNormal.Normalize();
return vNormal;
}
}

[/code]

Share this post


Link to post
Share on other sites
Updating after the weekend with a new Util function that does the gluUnproject without calling OpenGL functions.

This operates on matrices and vectors only, so somebody can find it usefull.

I am going to use it to continue with JTippetts's approach - for finding the cells to render, so here's what will be needed next in order to run the terrain rendering.

[code]

// --[ Function ]---------------------------------------------------------------
//
// - Prototype : static CVector4 UnProject(const CVector2& vMousePosition, const CVector2& vScreenSize, const CMatrix4x4& mViewMatrix, const CMatrix4x4& mModelMatrix, const CMatrix4x4& mProjectionMatrix, float zValue)
//
// - Purpose : Converts Screen Space coordinates to World Space coordinates
//
// -----------------------------------------------------------------------------
CVector4 UnProject(const CVector2& vMousePosition, const CVector2& vScreenSize, const CMatrix4x4& mViewMatrix, const CMatrix4x4& mProjectionMatrix, float zValue)
{
CVector4 vPositionWorldSpace;

CMatrix4x4 matMVP = mViewMatrix * mProjectionMatrix;

CMatrix4x4 matInverse = matMVP.inverse();


float in[4];
float winZ = 1.0;

CVector4 vIn = CVector4(
(2.0f*((float)(vMousePosition.x-0)/(vScreenSize.x-0)))-1.0f,
1.0f-(2.0f*((float)(vMousePosition.y-0)/(vScreenSize.y-0))),
2.0* zValue -1.0,
1.0);



//Multiply the vector by inversed matrix
vPositionWorldSpace = vIn * matInverse;


vPositionWorldSpace.w = 1.0 / vPositionWorldSpace.w;

//Divide result vector by it's w component after matrix multiplication ( perspective division )
vPositionWorldSpace.x *= vPositionWorldSpace.w;
vPositionWorldSpace.y *= vPositionWorldSpace.w;
vPositionWorldSpace.z *= vPositionWorldSpace.w;

return vPositionWorldSpace;
}

[/code]




Time to move on, will update when i get that frustum and cells rendering with rev projection done.

Share this post


Link to post
Share on other sites
[quote name='hupsilardee' timestamp='1318107036' post='4870587']
Cut down on the memory use by only storing X and Y, then use VTF to get the height in a vertex shader. Also, texture coords can be generated with a vertex shader if you use standard splatting
[/quote]



hupsilardee thanks for replying, what's VTF and how fast is it comparing to stored values within VBO ?

that looks promising..


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