Block Land 8

From DaveWiki

Jump to: navigation, search

Last Time we ended up creating some nice cave systems. Now we'll create a better lighting system so they aren't all fully lit.

Contents

Manual Lighting Model

Our first, and probably simplest, approach might be to just let Ogre and the graphics hardware handle all the lighting and shadowing. If I could get this to work at all I would test it, but my amateurish intuition leads me to believe that this won't work too well for performance reasons. We'd have to have our mesh chunks both cast and receive shadows in addition to self-shadowing. Also, if you look at MineCraft carefully you can see that they do some sort of lighting model on a per-block basis (the light on each block face is constant). This is particularly noticeable on blocks next to a torch on a flat wall.

For our custom block lighting we'll start with a very basic model. We'll define a 3D array the same size as our block array that will hold the light level of each block:

typedef unsigned char blocklight_t;
 
class TestBlockLandApplication : public BaseApplication
{
...
public:
	blocklight_t* m_BlockLight;
 
	block_t& GetBlockLight (const int x, const int y, const int z)
	{
		return m_BlockLight[x + y * WORLD_SIZE + z * WORLD_SIZE2];
	}
};
 
TestBlockLandApplication::TestBlockLandApplication(void)
{
...
	m_BlockLight = new blocklight_t[WORLD_SIZE3 + 16000];
	memset(m_BlockLight, 255, sizeof(blocklight_t) * WORLD_SIZE3);
}
 
TestBlockLandApplication::~TestBlockLandApplication(void)
{
...
	delete [] m_BlockLight;
}

For simplicity we'll just use a byte array but in the future we can reduce the number of light levels each block has to save some space (for example, MineCraft uses 16 light levels which can be stored in 4 bits). We'll initialize the block light array with a basic lighting model:

void TestBlockLandApplication::initWorldBlocksLight()
{
	int x, y, z;
	blocklight_t Light;
	blocklight_t DeltaLight = 16;
 
	for (z = 0; z < WORLD_SIZE; ++z)
	{
		for (x = 0; x < WORLD_SIZE; ++x)
		{
			Light = 255;
 
			for (y = WORLD_SIZE - 1; y >= 0; --y)
			{
				GetBlockLight(x, y, z) = Light;
 
				if (GetBlock(x, y, z) != 0) 
				{
					if (Light >= DeltaLight)
						Light -= DeltaLight;
					else
						Light = 0;
				}
 
			}
		}
	}
 
}

In this lighting model we examine blocks in a vertical column as if the light was coming from directly overhead. For each solid block we encounter we reduce the light level for all blocks underneath it. This isn't a particularly accurate model but will do fine as a starting point.

Now that we have our light levels for each block we have to figure out how to display them. The answer to this is vertex colors. When we create our mesh chunks we will assign a color to each vertex based on the block light level from 0 (darkest) to 1 (lightest). We when modify our block material to combine the texture with the vertex colors to result in a properly colored block.

This sounds a lot more complicated than it actually is. For our material definition we just have to add two lines in order to combine our vertex colors and texture:

void TestBlockLandApplication::createTexture (const TCHAR* pName, const TCHAR* pImageFilename)
{
...
	pass->setVertexColourTracking(Ogre::TVC_DIFFUSE);
	tex->setColourOperationEx(Ogre::LBX_MODULATE, Ogre::LBS_DIFFUSE, Ogre::LBS_TEXTURE);
}

In our mesh chunking method we have to assign a color to each vertex:

void TestBlockLandApplication::createChunkCombineMat (const int StartX, const int StartY, const int StartZ)
{
...
	BlockLight = GetBlockLight(x, y, z) / 255.0f;
 	MeshChunk->colour(BlockLight, BlockLight, BlockLight);
...
}

the last thing we have to do is tweak our scene's ambient color to make the caves darker:

    mSceneMgr->setAmbientLight(Ogre::ColourValue(0.02, 0.02, 0.02));

What we end up with is:

Caves With Our Simple Lighting System

While far from perfect it gives our caves depth and more character. The nice thing about this method is that we can easily tweak our light model to give different results.

Better Lighting

One issue with the previous example screen shot is that the surface blocks have far too much contrast between their top and side faces. This is due to the main light source (the sun) being overhead and having reduced the ambient light to almost 0 to make the caves darker. There are a few ways we can tackle this problem:

  1. Increase Ambient Light -- While simple this is a global setting and would also increase the darkest color of caves which is probably what we don't want.
  2. Complete Manual Light Modeling -- We could completely control light levels by using a more complex light modeling method and computing the light levels on all six sides of the block. This comes across as being too much effort, however, and only works for blocks and not any real meshes we may wish to use in the future (for items or mobs for example, although we could use both automatic and manual lighting).
  3. Manual Ambient Lighting -- We'll ignore the global ambient light parameter and manually add lights to simulate ambient lighting.

One thing to keep in mind is that at some point we will want to implement a day/night cycle in which case being able to use Ogre's built in lighting will make it much easier than having to do it all manually. The other thing to consider is the rough performance of our possible choices. We can make a few simple tests with our current block world engine and see how it affects the average FPS:

Type Average FPS
Lighting Off (Manual Lighting) 50 fps
One Directional Light 30 fps
7 Directional Lights (Manual Ambient Lighting) 7 fps

Unfortunately the easiest approach is also the slowest and the most complex is the fastest by a significant margin. There are most likely a variety of other options I haven't considered due to my lack of knowledge, for example making a 3D texture which is used by a shader program to dynamically calculate the lighting effect in real time rather than at mesh creation time.

Whatever the exact solution is some form of custom ambient lighting will be needed for improving the world's look:

Caves With Manual Ambient Lighting

Manual Lighting

Using manual lighting for the blocks looks to be a good option in terms of performance. In order to enable manual lighting for the blocks there are just a few things we need to change.

The first is a one line change in the texture creation method to disable lighting for the block material:

void TestBlockLandApplication::createTexture (const TCHAR* pName, const TCHAR* pImageFilename)
{
...
	tech->setLightingEnabled(false);
}

The next is to add some variety in the lighting of the blocks in our chunk creation method. If we keep each face of the block the exact same brightness then the world looks too bland. With lighting enabled the brightness variation is taken care of automatically by Ogre but now that we have disabled it we have to do it manually.

All we'll do for now is to create three slight brightness variations for faces in the X/Y/Z directions:

void TestBlockLandApplication::createChunkCombineMat (const int StartX, const int StartY, const int StartZ)
{
...
	BlockLight  = GetBlockLight(x, y, z) / 255.0f;  //For the two x faces
	float BlockLight1 = BlockLight * 0.9f;		//For the two z faces
	float BlockLight2 = BlockLight * 0.8f;		//For the two y faces
...
}

This brightness variation is enough to make our manually lite world much less bland:

Manually Lite Block World With Face Brightness Variation

One thing to note is that MineCraft appears to separate the amount of sunlight that hits the block and the amount of "block emitted" light (see NBT Structure). We haven't considered adding things like torches or lava light so perhaps our approach we've attempted here is incomplete or even wrong, at least compared to how MineCraft does it.

Next Time

Next Time

Personal tools