The random ramblings of a French programmer living in Norway...
2012
← Tilemap tutorial (part 4)Welcome to L3314N! →
  Tilemap tutorial (part 5)
Sun 9th December 2012   
Welcome to the fifth and final part of the Tilemap tutorial.

As promised in the previous article I will finally explain how to render the tilemap using a special shader that allows you to draw the whole tilemap with only two triangles.


Compatibility woes

My main machine at home is a Core I7 with a NVidia™ 560ti.

It took me about 3 hours to get the first version of my shader up and running starting from the "Pixelate" sample that came with SFML: I was very happy!

When I tried it on my girlfriend's machine (an ATI™ 5850) it did not work at all :(

It took me about 2 days of work, and the usage of some tools like the AMD Gpu Shader Analyser
1before I finally managed to get my shader working at all on ATI.

The lesson I learnt there is that the NVidia drivers and cards are extremely tolerant, accept non valid shaders, non matching parenthesis, invalid type conversions, etc... whatever crap you write will probably work.
The ATI drivers on the other hand seems to be a lot more strict, and will refuse to compile your shaders if they are wrong.
2
The bottom line is: The current shader runs perfectly on all the tested NVidia and Intel cards, has some small accuracy problems on ATI, and does not work at all on S3 Chrome.

I will try to fix the issues, but the concept rests valid.

Here is what the final result looks like3:


Ok, it's now time to explain how the final code works!

Structure of the code

The files are available for download in a compressed archive at the end of the article, including a pre-built binary for Windows® system4.

Most of the work is done in tilemap.frag, the setup is done in main.cpp and some minor work is performed by tilemap.vert.

The vertex shader was not absolutely necessary, but more recent versions of OpenGL don't propagate some of the shader parameters automatically anymore, so you need to have a vertex shader just to do that. Go figure.

I commented the two most important files as much as I could, but let's detail a bit.

The inner loop

Here is the actual drawing code used in the CPP file to draw the whole tilemap:
// Draw the tilemap// Nothing special there, just don't forget  to set the shader.sf::RenderStates tilemapStates;
tilemapStates.shader=&tilemapShader;
renderWindow.draw(tileMapSprite,tilemapStates);
Yes, that's all.

Of course there's some more code, like for example the calculations for zooming and rotating the tilemap on screen:

// Display the tilemap centered in the windowtileMapSprite.setPosition(sf::Vector2f(renderWindow.getSize())/2.0f);

// Update rotationtileMapSprite.setRotation(tilemapAngle);
tilemapAngle+=0.1f;

// Update zoomfloat scale=2.0f+1.0f*cosf(tilemapZoom);
tileMapSprite.setScale(scale,scale);
tilemapZoom+=0.01f;
Just by changing the value passed to tileMapSprite.setPosition you can move the entire tilemap all over the screen.

As far as the inner loop is concerned, it's all there is.

So as you can see from a CPU point of view this is very minimalistic since all the work is done inside the pixel shader.

The setup code

Of course, there's a little bit of set-up done up-front which makes all that possible:

// This is where the magic happens:
//
// We tell the sf::Sprite engine it will be using the tilemap texture which is
// 256 by 256 pixels, but then we will tell it to use texture coordinates that
// would a 4096 by 4096 pixel texture use.
//
// The trick is that we want our pixel shader to draw the actual final texture
// as if it had always be present in memory. This makes it possible to use the
// tilemap very easy, no need to do complex tricks to clip it or rescale it:
// Just pretend it has the size you wanted it to be in first place :)
sf::Sprite tileMapSprite;
tileMapSprite.setTexture(tileMapTexture);
tileMapSprite.setTextureRect(sf::IntRect(0,0,256*16,256*16));
tileMapSprite.setOrigin(128*16,128*16);
I guess the comment says it all.

The tileMapSprite.setOrigin is only there to make the tilemap rotate around its center, which is less stomach turning inducing that having it turn around a corner :)

That's about it for the C++ code, let's look at the shader code now.

The GPU code5

Without the comments, the whole shader code looks like that:
#version 130

uniform sampler2D tilemap;
uniform sampler2D tileGraphics;

void main()
{
vec2 tilePos=gl_TexCoord[0].xy/16;
float index = floor(texture2D(tilemap,tilePos).r*256);
vec2 baseTilePos=0.5*floor(vec2(mod(index,2),index/2));
vec2 internalPos=0.5*mod(gl_TexCoord[0].xy*16,1);
gl_FragColor=texture2D(tileGraphics,baseTilePos+internalPos);
}
If you download the archive you will get the fully commented code, so I will just briefly explain what it does.

When the graphic card renders the sprite, it will for each visible pixel on the screen call the shader and asks it to compute this one particular pixel value.

The only input we get are:
  • tilemap - the texture that contains our 256x256 pixels tilemap
  • tileGraphics - the 32x32 texture atlas containing four textures
  • gl_TexCoord[0] - the texture coordinates of the pixel being drawn
The only thing we return is:
  • gl_FragColor - the color of the pixel as computed by the shader
So basically the trick is to do what I explained in the previous article: From the given texture coordinates deduce both the index of the texture to use and the texture coordinates of the pixel to return

The only difficulty really is that you need to clearly layout the size of all your textures and primitives to be able to convert from one system to another.

gl_TexCoord contains a value in the 0 to 1.0 range which maps to our 4096x40966 pixel 'virtual' tilemap texture

And since tileGraphics is a 32 by 32 pixel texture containing 16 by 16 pixels images, we have to use fractional coordinates to access the various images in the atlas.

Hopefully the comments in the code will be enough for you to understand the details, in case of problem just use the comment box!

Final words and the files

This was the final episode of the tilemap tutorial.

Here is the complete archive, containing the executable, source code and textures:

Tilemap.zip (105 kilobytes)
If you have any comment about this series of tutorial, if you don't like them, if you have suggestions, if you want me to write more of these, if you find the style terrible, or too simple, or too difficult, please tell me :)

This is going to be the last article for the year 2012, I'm now going to take a break and work on my game!


1. The tool works even if you have a non ATI card. You can download it on AMD's website.
2. I would not be surprised if that was some dark magic added by NVidia to make crappy games run on their cards, a bit like earlier web browsers that accepted totally broken web pages.
3. The original capture ran at 60 frames per second with the framerate limiter enabled. Without limits I had about 1100 frames per seconds reported by FRAPS in 1920x1200 resolution.
4. I've not setup sfml on my Linux machine, and I don't have a Mac, sorry for that.
5. I admit I've been thinking about doing some bad title pun based on Fifty Shades of Grey, but like Gangnam Style and Nyan Cat it's a bit over-done these days.
6. The tilemap contains 256 by 256 tiles, each tile is 16 by 16 pixels, that's a 4096 by 4096 texture.
comments powered by Disqus