User Tools

Site Tools

Culling & Population

Note that the terrain system in the SpeedTree SDK does not support any specific file format for terrain population. It will simply provide the client application with blank cells to populate as needed. The client is free to populate those cells based on any data or algorithm available. Note that the reference application does use an example-level class called CTerrainData that IDV uses to load terrain data. Feel free to use this as a basis for your own population data system if helpful.


Once the CTerrain object has been properly initialized, it can be called on to provide a list of visible terrain cells for any given view. The example below shows how to cull the terrain for a given view and then traverse both the newly created cells and the overall visible cells.

using namespace SpeedTree;
//  TerrainCullExample
void TerrainCullExample(void)
    // assume properly initialized terrain object
    CTerrain cTerrain;
    assert(cTerrain.IsEnabled( ));
    // ... your code here (updating view, etc.) ...
    CView cView;
    // perform cull
    STerrainCullResults sVisibleTerrainCells;
    cTerrain.CullAndComputeLOD(cView, sVisibleTerrainCells);
    // populate the new cells
    for (size_t i = 0; i < sVisibleTerrainCells.m_aCellsToUpdate.size( ); ++i)
        CTerrainCell* pNewCell = sVisibleTerrainCells.m_aCellsToUpdate[i];
        // populate pNewCell
    // run through visible cells (example basis for render loop)
    for (size_t i = 0; i < sVisibleTerrainCells.m_aVisibleCells.size( ); ++i)
        CTerrainCell* pCell = sVisibleTerrainCells.m_aVisibleCells[i];
        // render or otherwise process the cell

Culling results are returned through the STerrainCullResults structure:

//  Structure STerrainCullResults
struct STerrainCullResults
    TTerrainCellArray   m_aVisibleCells;    // cells that are visible from this view
    TTerrainCellArray   m_aCellsToUpdate;   // cells that need to be populated
    TTerrainVboArray    m_aFreedVbos;       // internal use

The TTerrainCellArray type is just a typedef for CArray. CArray is SpeedTree's lightweight version of STL's std::vector<>.


To populate the new cells, the client application will need to provide a height for any (x,y) position that the terrain system requests. Additionally, the application could be configured to include texture coordinates, per-vertex normals, ambient occlusion hints, colors, splat mixtures, etc. Note that this data is being passed directly into a CGeometryBuffer object, not a generic structure.

IDV has set up an example terrain rendering system, but there is no reason your application cannot be modified to upload different vertex attributes, use a modified shader, etc. The following code illustrates a simplified example of how to populate a single terrain cell (as returned in STerrainCullResults::m_aCellsToUpdate). A working example can be found in MyApplication.cpp of the reference application in the function CMyTerrain::Populate() in MyTerrain.cpp.

using namespace SpeedTree;
//  TerrainPopulationExample 
// assume external terrain object
CTerrain cTerrain;
// assume vertex format that matches the VBs
struct STerrainVertex
    Vec3       m_vCoords;
    st_float32 m_afTexCoords[3]; // texcoord_s, texcoord_t, ambient occlusion
void TerrainPopulationExample(CTerrainCell* pCell)
    // gather some working parameters
    const st_int32 c_nCellRes = cTerrain.GetMaxTileRes( );
    const st_float32 c_fCellSize = cTerrain.GetCellSize( );
    const st_float32 c_fVertexSpacing = c_fCellSize / (c_nCellRes - 1);
    const st_int32 c_nCellRow = pCell->Row( );
    const st_int32 c_nCellCol = pCell->Col( );
    // need an intermediate buffer to hold our vertex data before it's 
    // copied to the vertex buffer
    CArray<STerrainVertex> aVertices(c_nCellRes * c_nCellRes);
    STerrainVertex* pVertex = &aVertices[0];
    // run through the cell's points, column by row
    float x = c_nCellCol * c_fCellSize;
    for (st_int32 nCol = 0; nCol < c_nCellRes; ++nCol)
        float y = c_nCellRow * c_fCellSize;
        for (st_int32 nRow = 0; nRow < c_nCellRes; ++nRow)
            // compute height based on any criteria
            float z = cos(x) * sin(y); // silly example
            // fill out vertex data
            pVertex->m_vCoords.Set(x, y, z);
            // s texcoord (example: based on x)
            pVertex->m_afTexCoords[0] = x / 10.0f;
            // t texcoord (example: based on y)
            pVertex->m_afTexCoords[1] = x / 10.0f;
            // ambient occlusion (example: off)
            pVertex->m_afTexCoords[2] = 1.0f;
            y += c_fVertexSpacing;
        x += c_fVertexSpacing;
    // update the vertex buffer
    CGeometryBuffer* pVbo = (CGeometryBuffer*) pCell->GetVbo( );
    pVbo->OverwriteVertices(&aVertices[0], st_uint32(aVertices.size( )), 0);

Note that speed is a key issue in this function. While it's only called when the camera roams into a new area, if this function is too slow, it will cause lagging as the camera roams as several cells can be created in one frame. Terrain resolution also has a strong effect, especially since it has to move in powers-of-two+1 sequence: 17, 33, 65, 129, etc. Each move up in resolution will actually quadruple the amount of work done by this function (as well as the vertex shader).

If your application requires a minimum of Shader Model 3.0, then it could be possible to only populate a vertex buffer with (x,y) locations and pull the height values from a texture lookup, greatly reducing the CPU impact of this function.