Creative Tinkering

I have been thinking about building a RPG 2D game with retro aesthetics. Appropriately I want to make tilemap graphics the main visual feature as it was the most widely used method for world building in the old days and even lives on today in 3D games.

screenshot of Terreria

When looking at the image above of terraria’s world, one can clearly perceive a pixellation at a coarser level than the pixels in the image. I think that it is descriptive to say that tilemap graphics is a metabitmap: it is a bitmap that consists of other bitmaps.

Tilemapping has two main components: tilemap and tileset

Tilemap

Tilemap is a bitmap that contains information about which tiles must be where. Here is an example of a simple tilemap with the size on 7×7:


var mapDataFlat = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];

var mapVisuallyFormatted = [
	0,0,0,0,0,0,0,
	0,1,0,0,0,1,0,
	0,1,0,0,0,1,0,
	0,0,0,0,0,0,0,
	0,1,1,1,1,1,0,
	0,0,1,1,1,0,0,
	0,0,0,0,0,0,0
];

var mapAsciiRendered = [
	.......
	.H...H.
	.H...H.
	.......
	.HHHHH.
	..HHH..
	.......
];

Tileset

Tileset is an atlas map of all possible tiles. It is kind of an palette. The tilemap contains indices that correspond to a tile in the tileset.


tileset texture from minecraft

Rendering

As tilemaps are in essence bitmaps the obvious step would be to move the rendering(and processing in the future) of tiles to the GPU instead of CPU. GPU is designed for parallel processing: doing many similar things at once while the CPU will generally do things one after another. So in a nutshell a GPU can iterate tiles in a tilemap all at once while a CPU will have to iterate them one after another.

I have chosen to use WebGL to render the tilemap in my game. It is common practice to render individual tiles with individual quads with individual UV mappings. This is very flexible and allows for fancy effects and all, but if the tilemap is for example 1024×1024 tiles, there would have to be: 1024 x 1024 x 6 = 6 291 456 vertices to describe the geometry. This would mean that this grid mesh must be broken into smaller chunks which means more management and is still rather high poly for a simplistic 2D tile rendering.

As an avid experimenter I have put together a method that does not rely on massive amounts of quad meshes, but instead uses bitmaps and the Fragment Shader to draw different tiles in predetermined positions. The live demo below has a tilemap with the size of 1024×1024 and the tilemap data is changed 1024 times per frame


Go to Demo

The entire 1024×1024 tilemap is rendered on a single quad. And the magic takes place in the fragment shader which is quite short as fragment shaders go:


uniform vec2 tilemapSize;
uniform sampler2D tileset;
uniform sampler2D tileindex;
varying vec2 vuv0;
varying vec2 vuv1;
void main(){
	float index = floor(texture2D( tileindex, vuv0 ).x * 256.0);
	if( index == 0.0){ discard; }
	vec2 offset = vec2(
		index - floor(index / 16.0),
		floor(index / 16.0)
	) / 16.0;
	vec4 color = texture2D( tileset, ( fract(vuv1 * 16.0) + offset * 16.0)/16.0 );
	gl_FragColor = color;
}

Main thing here is the calculation of uv coordinates per fragment from index value. This example uses a single channel uint8 texture for tile indices and a 16×16 cells tileset texture. As the indices can only be in range of 0 to 255 the tileset can be maximum of 16×16 tiles (square root of 256). Larger tileset can be used if index texture uses more channels. For example 3 channel uint8 texture could have indices in range of 0 to 255*255*255 (0 – 16581375) if encoded as a base 255 number. This would yield a tileset of about 4000×4000 which would absolutely be more than enough. In reality these extra channels would be better used for other purposes such as tile ‘health’ or other properties.

Leave a Reply

Your email address will not be published. Required fields are marked *