3
\$\begingroup\$

I want to be able to have a big tilemap (e.g. like Terraria, or an infinite procedural generated one), with only a handful of tiles on my screen.

This means that I have a texture atlas (i.e. spritesheet) which contains the textures for the tiles. For the sake of simplicity, let's assume that I have a huge internal 2D array, containing the tileIDs and a lookup tablel/hasmap for the UV coords for each tile type. All the tiles have the same dimension (the texture AND the onscreen tiles). I know a couple of methods to render such a 2D grid/tilemap, but I don't know which one of them is a solid foundation for a game which heavily relies on modifiable tiles.

Additionally, I want to be able to dynamically change the tilesize (i.e. zooming in/out), modifying the tiles/tilemap (e.g. destroying, building) and fast loading (fast paced player actions).

  1. Using a single draw call. I create a VBO with all the quads to fill the screen (multiple overlapping vertices, because each quad needs a different texture) and have a persistent buffer to modify the texture buffer object. As soon as the camera moves, I update the UV coordinates in the TBO.

  2. I can use attributeless rendering. I make a glDrawArrays call on an empty VAO, and use gl_VertexID%6 and gl_VertexID/6 to get the vertex ID and the tileID. To map the UV coordinates, I pass a TBO, which is updated at every camera move. I don't know how expensive this calculation is for the GPU.

  3. Hoping on the train of attributeless rendering, I can pass a uniform or create a const array to hold the UV offsets of every tile type, and only pass at every camera move the updated tile type IDs to the shader (with a uniform?). With gl_VertexID/6 I know which index holds the current tiles type information, and then with this type, I can access my constant array to lookup the UV coordinates.

  4. Either using triangle strips, or indices to reduce the vertex count, and use provoking vertices to correctly map the UV coordinates (this can be combined with attributeless rendering). The vertices are all fixed for the window, and only the textures change. Here, once again, I have to map the TBO to a persistent buffer and update it at every camera move.

  5. Every tile gets a separate draw call (thus a separate VBO, IBO, TBO) and I can therefore render only the tiles which are seen by the camera. Because of the low amount of onscreen tiles, those separate draw calls shouldn't be too expensive.

  6. I put the whole map at the start of the game in the VAO and TBO, and modify the indices buffer, to only render the onscreen tiles (the IBO only holds enough indices to render the onscreen quads). My problem here is that I send too much data to the GPU every frame, and I have a really bad cache locality, because the information has to be accessed from different parts of the buffer objects.

  7. There might also be a way to use a geometric shader, although I have never used one.

My concrete question is, is there a "state of the art" method to render big tilemaps, is any of my above mentioned ideas a good solution, and, does it really matter which option I use?

Thank you!

\$\endgroup\$
2
\$\begingroup\$

does it really matter which option I use

Use the simplest method which is fast enough. Such method is described below.

is any of my above mentioned ideas a good solution

The #1 is unnecessarily slow.

The #5 is very slow too, probably a lot slower than you think.

Assume you have a 1920x1080 window, filled with 16x16 tiles. (These numbers are similar to what Terraria deals with.) It means that there are approximately 8000 visible tiles. You need at least two draw calls for each one: glUniform* and glDraw*. It means 16000 draw calls. It's a lot of draw calls. Not to mention the enormous amount of needed VBOs.

And honestly saying, all other solutions seem too complicated for my taste, especially since there is a much simpler option: Chunk-based rendering.

It means following:

  • Divide your map into chunks.
    (Small sized squares of tiles; something like 32x32 tiles would work.)
  • Make a separate VBO (possibly IBO) for each chunk.
  • Only draw visible chunks.
  • When a chunk is modified, update it's VBO accordingly.
  • If you want an infinite map, you'll have to create and destroy chunks on the fly. Otherwise it shouldn't be necessary.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.