Block Land 6

From DaveWiki

Jump to: navigation, search

Last Time we made a relatively simple chunking optimization and discussed choices of world and chunk sizes. For this article we'll look at adding more block types and the issues it causes.

Contents

More Blocks

So far our world, as good as it might look, is relatively boring as there is only one type of block. For an actual playable world we're going to have to add and be able to display more block types. A simple structure definition will hold a few more block types:

struct blockinfo_t
{
	block_t ID;
	TCHAR	Name[64];
	TCHAR   Texture[64];
};
 
blockinfo_t g_BlockInfo[] =
{
	{ 0, "Air", "" },
	{ 1, "Grass", "grass01.png" },
	{ 2, "Soil", "soil01.png" },
	{ 3, "Rock", "rock01.png" },
	{ 4, "Lava", "lava01.png" },
	{ 255, "null", "" }           //Must be last
};

A quick change to our landscape generation code will let us use these new blocks (for simplicity in this example we'll the 5 block types are hard coded):

void TestBlockLandApplication::initWorldBlocksLandRnd()
{
	m_Landscape.SetSize(WORLD_SIZE, WORLD_SIZE);
	m_Landscape.CreateAltitude();
	infland::CMap& Map = m_Landscape.GetAltitudeMap();
 
	for (int z = 0; z < WORLD_SIZE; ++z)
	{
		for (int x = 0; x < WORLD_SIZE; ++x)
		{
			float Height = Map.GetValue(x, z) * WORLD_SIZE/4 + WORLD_SIZE/2;
 
			for (int y = 0; y < Height; ++y)
			{
				GetBlock(x, y, z) = (rand() % 4) + 1;  //1-4
			}
		}
	}
}

For displaying the new blocks we'll create materials using the texture filename given in the block info structure:

for (int i = 1; g_BlockInfo[i].ID != 255; ++i)
{
	createTexture(g_BlockInfo[i].Name, g_BlockInfo[i].Texture);
}

In order to use the block materials our first attempt might to be just modify the inner loop of the mesh chunk algorithm with something like:

if (Block != LastBlock)
{
	LastBlock = Block;
	MeshChunk->end();
	MeshChunk->begin(g_BlockInfo[Block].Name);
}

Now this works but the performance is simply terrible...as in 1 fps terrible. The reason is due to the number of "batches". When we constantly change the material in the manual mesh object it results in a lot of separate sub objects making the rendering a lot slower, especially with the number of sub objects caused by changing the material so frequently.

Better Batches

To reduce the number of batches we'll have to try to minimize the number of times we call begin/end in the manual object. One way to do this is for each chunk create all the block faces with the same block type at once. In the follow code we'll simply loop through each block in the chunk once for each block type (1-4). This probably won't work well, or at all, when we add more block types but for testing purposes will be fine for now.

void TestBlockLandApplication::createChunkMat (const int StartX, const int StartY, const int StartZ)
{
	block_t LastBlock = 0;
 
	Ogre::ManualObject* MeshChunk = new Ogre::ManualObject("MeshMatChunk" + Ogre::StringConverter::toString(m_ChunkID));
 
	int iVertex = 0;
	block_t Block;
	block_t Block1;
 
		/* Only create visible faces of chunk */
	block_t DefaultBlock = 1;
	int SX = 0;
	int SY = 0;
	int SZ = 0;
	int MaxSize = WORLD_SIZE; //*/
 
	for (int iBlock = 1; iBlock  <= 4; ++iBlock )
	{
		MeshChunk->begin(GetBlockMaterial(iBlock ));
		iVertex = 0;
 
		for (int z = StartZ; z < CHUNK_SIZE + StartZ; ++z)
		{
			for (int y = StartY; y < CHUNK_SIZE + StartY; ++y)
			{
				for (int x = StartX; x < CHUNK_SIZE + StartX; ++x)
				{
					Block = GetBlock(x,y,z);
					if (Block != iBlock ) continue;
 
					// Same chunking code as our previous version
				}
			}
		}
 
		MeshChunk->end();
	}
 
	mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(MeshChunk);
	++m_ChunkID;
}

The final result of all this is:

256 Block Sized World With 4 Block Types

With only 4 block types this change to mesh chunking hasn't impacted the world's performance very much.

Texture Atlas

Another method is combining multiple textures/materials into a single texture/material atlas. For example, right now our 4 block types have 4 individual texture files and material objects. This requires us to split the manual object into 4 separate sub-objects, each of which containing only one type of block. This not only slows down our mesh chunking but also slows down the chunk rendering due to having more batches.

Instead of having 4 separate materials we will combine them into a single texture/material. In this case it could be a 2x2, 1x4 or 4x1 but having a strip (1x4 or 4x1) will let us do some more complex optimizations later on in the mesh chunking. Now, since we only have one material we don't have to create 4 different mesh sub-objects any more but can go back to just one. All we need to do is to modify each face's texture UV coordinates to have it display the correct texture from the texture atlas.

For simplicities sake again we'll just manually combine our 4 textures into a 1x4 texture atlas (16x64 pixels) and modify our mesh chunking code to use this material:

void TestBlockLandApplication::createChunkCombineMat (const int StartX, const int StartY, const int StartZ)
{
	block_t LastBlock = 0;
 
	Ogre::ManualObject* MeshChunk = new Ogre::ManualObject("MeshMatChunk" + Ogre::StringConverter::toString(m_ChunkID));
	MeshChunk->begin("Combine4");
 
	int iVertex = 0;
	block_t Block;
	block_t Block1;
 
		/* Only create visible faces of chunk */
	block_t DefaultBlock = 1;
	int SX = 0;
	int SY = 0;
	int SZ = 0;
	int MaxSize = WORLD_SIZE; 
	float V1, V2;
 
	for (int z = StartZ; z < CHUNK_SIZE + StartZ; ++z)
	{
		for (int y = StartY; y < CHUNK_SIZE + StartY; ++y)
		{
			for (int x = StartX; x < CHUNK_SIZE + StartX; ++x)
			{
				Block = GetBlock(x,y,z);
				if (Block == 0) continue;
 
					//Compute the block's texture coordinates
				V1 = 0.25f * (float)(Block - 1);
				V2 = V1 + 0.25f;
 
					//x-1
				Block1 = DefaultBlock;
				if (x > SX) Block1 = GetBlock(x-1,y,z);
 
				if (Block1 == 0)
				{
					MeshChunk->position(x, y,   z+1); MeshChunk->normal(-1,0,0);	MeshChunk->textureCoord(0, V2);
					MeshChunk->position(x, y+1, z+1); MeshChunk->normal(-1,0,0);	MeshChunk->textureCoord(1, V2);
					MeshChunk->position(x, y+1, z);	  MeshChunk->normal(-1,0,0);	MeshChunk->textureCoord(1, V1);
					MeshChunk->position(x, y,   z);	  MeshChunk->normal(-1,0,0);	MeshChunk->textureCoord(0, V1);
 
					MeshChunk->triangle(iVertex, iVertex+1, iVertex+2);
					MeshChunk->triangle(iVertex+2, iVertex+3, iVertex);
 
					iVertex += 4;
				}
 
				//Snip...same chunking code with the V1/V2 texture coordinates as above
 
			}
		}
	}
 
	MeshChunk->end();
	mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(MeshChunk);
	++m_ChunkID;
}

The result of this code looks identical to our previous version and performs about the same, although this atlas code will work much better as the number of block types increases and is simpler to boot.

Next Time

Next Time we'll look at adding better block type layers in our world.

Personal tools