LandscapeA landscape displayed from a heightmap and single diffuse texture.  Landscapes were from 4-8 million vertices.  Regions consisted of 4096 vertices and could be drawn as anywhere from 2 tris to 8 thousand tris.  Distance to camera and intrinsic detail bias based determination of which level-of-detail to use.  A normal map was also calculated at first load to show consistent lighting despite changing LOD.

LOD-Lighting

Lighting Demonstration

LOD-Stitching

Level-Of-Detail and Stitching

LOD-Landscape

A Second Landscape

I created a stitching system to allow connected regions to differ in LOD.  This required 16 different stitch patterns for each level of detail based on which touching regions were at a different LOD.  This code follows:

void LODLandscape::CreateIndexBuffers()
{
	/** 
	 * Create space for all the index buffers.  And 
	 * the ids/lengths for talking to the GPU.
	 * 
	 * iBuffers gets enough memory for the largest 
	 * possible index buffer.
	 * 
	 * VERTCHUNKWIDTH - Number of heightvals across in each chunk
	 * NUMSUBSAMPLES  - Number of LOD levels being used
	 * 
	 * NORTH, SOUTH, EAST, WEST: 1, 2, 4, 8
	 */
	unsigned short *iBuffers = new unsigned short[VERTCHUNKWIDTH*VERTCHUNKWIDTH*NUMSUBSAMPLES];
	iBuffIds = new size_t*[NUMSUBSAMPLES+1];
	iBuffLengths = new size_t*[NUMSUBSAMPLES+1];

	for( int i=1 ; i<=NUMSUBSAMPLES ; ++i )
	{
		iBuffIds[i] = new size_t[16];
		iBuffLengths[i] = new size_t[16];

		for( int dir=0 ; dir <= (NORTH|EAST|SOUTH|WEST) ; ++dir )
		{
			int currIndexSize=0;

#define HEIGHT						  (VERTCHUNKWIDTH+1)
#define STEP						    (1<<(NUMSUBSAMPLES-i))
#define PUSHBACK(n)					iBuffers[ currIndexSize++ ] = n

			for( int y=0 ; y < VERTCHUNKWIDTH ; y+=STEP )
				for( int x=0 ; x < VERTCHUNKWIDTH ; x+=STEP )
				{
					if( !(( x/STEP+y/STEP )%2) )
					{
						if( y==(VERTCHUNKWIDTH-STEP) && dir&NORTH )
						{
							PUSHBACK( (y+STEP)*HEIGHT + x+STEP );
							PUSHBACK( (y     )*HEIGHT + x );
							PUSHBACK( (y+STEP)*HEIGHT + x-STEP );
						}
						else if( !(x==0 && dir&WEST)  )
						{
							PUSHBACK( (y     )*HEIGHT + x );
							PUSHBACK( (y+STEP)*HEIGHT + x );
							PUSHBACK( (y+STEP)*HEIGHT + x+STEP );
						}

						if( x==(VERTCHUNKWIDTH-STEP) && dir&EAST )
						{
							PUSHBACK( (y+STEP)*HEIGHT + x+STEP );
							PUSHBACK( (y-STEP)*HEIGHT + x+STEP );
							PUSHBACK( (y     )*HEIGHT + x );
						}
						else if( !(y==0 && dir&SOUTH))
						{
							PUSHBACK( (y+STEP)*HEIGHT + x+STEP );
							PUSHBACK( (y     )*HEIGHT + x+STEP );
							PUSHBACK( (y     )*HEIGHT + x );
						}
					}
					else
					{
						if( y==0 && dir&SOUTH )
						{
							PUSHBACK( (y     )*HEIGHT + x-STEP );
							PUSHBACK( (y+STEP)*HEIGHT + x );
							PUSHBACK( (y     )*HEIGHT + x+STEP );
						}
						else if( (x==0 && dir&WEST) )
						{
							PUSHBACK( (y-STEP)*HEIGHT + x );
							PUSHBACK( (y+STEP)*HEIGHT + x );
							PUSHBACK( (y     )*HEIGHT + x+STEP );
						}
						else
						{
							PUSHBACK( (y     )*HEIGHT + x );
							PUSHBACK( (y+STEP)*HEIGHT + x );
							PUSHBACK( (y     )*HEIGHT + x+STEP );
						}

						if( !( y==(VERTCHUNKWIDTH-STEP) && dir&NORTH ) && !( x==(VERTCHUNKWIDTH-STEP) && dir&EAST ))
						{
							PUSHBACK( (y+STEP)*HEIGHT + x+STEP );
							PUSHBACK( (y     )*HEIGHT + x+STEP );
							PUSHBACK( (y+STEP)*HEIGHT + x );
						}
					}
				}

			iBuffIds[i][dir] = Renderer::GetInstance()->CreateIndexId( iBuffers, currIndexSize );
			iBuffLengths[i][dir] = currIndexSize;
		}
	}

	delete [] iBuffers;

#undef HEIGHT
#undef STEP
#undef PUSHBACK

}