I'm trying to draw regular 2D tiles to the screen from a single texture but there are a few ways to accomplish this and I don't feel like I know enough about rendering to assess the trade-offs properly.

First of all, I have potentially a lot of tiles to render. Each tile is 8-32px (depending on zoom factor) and on a Full HD monitor that's over half a million tiles (and over a million triangles) per layer, and I have two layers. While I'm not going to go out of my way to support massive screens that are zoomed all the way out, I do want a solid reliable experience for players with screens up to 2048x2048px at any zoom, which ends up being about a quarter of a million triangles (before non-tile sprites, effects, and HUD elements are added on top).

I've been getting a lot of conflicting advice on what is good and what is bad to do with regard to tilemapping... What's been the nicest method so far to work with is instancing 6 points, two triangles in a square configuration, and giving each instance a position and texture position. But now I'm told instancing with less than 100 vertices creates a lot of overhead... Using a geometry shader to produce vertices is also crazy expensive and impracticable for hundreds of thousands of triangles, apparently. Sometimes I'm told triangle strips are the say to go, but you have to add in messy degenerate triangles when you have tilemap gaps. Other times triangles are said to be better.

Do all roads lead back to a static baked in array of all corners of each tile for efficiency?

Also, when you move left, right, up, down... you need to add in more tiles to the side... but how? Is there a way to do this that isn't messy and complicated?

  • \$\begingroup\$ Have you actually tried to implement any of the aforementioned methods yet? It might be useful to know how well they actually perform before coming up with crazy ideas. \$\endgroup\$ Sep 30 '15 at 4:19
  • \$\begingroup\$ I've got instancing over 6 verticies running in my code now. Before that I was creating two triangles in a geometry shader from a single vertex. Before that I had arrays of sets of vertices for triangle strip squares. I haven't stress tested any of them though and I fear even if I did I'd only know what works for my hardware, not the general case. \$\endgroup\$
    – NovaCrist
    Sep 30 '15 at 4:36
  • \$\begingroup\$ You aren't going to know how well it works on other hardware regardless of what method you're using. I'd recommend trying out what you had running but draw everything and see how well it performs. \$\endgroup\$ Sep 30 '15 at 4:39

How about clustering them into "super tiles"? You could draw a group of tiles into a "super tile" that's, for example, 512x512px. You'd just fill each super tile once when it first comes on-screen, and then all you have to do is draw the super tiles to the screen.

To keep performance super smooth, you could prepare regular tile (not super tile) meshes for each super tile that's not quite on screen, and then draw them to a render target when needed. You could prepare those regular tile meshes over several frames to reduce the chance of "hiccups" in performance when you draw to a new super tile.

These regular tile meshes could be done any of the ways you mentioned -- instancing, geometry shader, or a static mesh. I'd recommend the latter since instancing and geometry shaders aren't really helping you any in this kind of situation, but they're also not really hurting in this kind of situation, either, since the overall performance is not really dependant on occasionally preparing a new super tile.

A variant on this "super tile" technique is to render individual regular tiles into their respective super tile only as needed -- so if only the edge of a super tile is on-screen, only those regular tiles on that edge will have been drawn into the super tile's texture. In this situation, since you're drawing relatively few new tiles at a time, the technique you use for rendering the regular tiles would be near-inconsequential, so this would especially be worth exploring if you do experience hiccups when creating new super tiles as per my first suggestion. The complication here is that you need to keep track of which regular tiles have already been drawn to their super tile, so you don't redraw regular tiles into their super tiles unnecessarily. That shouldn't be a huge problem, though.

  • \$\begingroup\$ This could work... but are there any complications to adding lighting effects with this method? If I had a flashlight in game and wanted the light to be less intense after hitting a tile, are super tiles going to make a difference in how I approach that? \$\endgroup\$
    – NovaCrist
    Oct 9 '15 at 7:16
  • \$\begingroup\$ Good point -- if you are changing lighting on a per-tile basis, that'll complicate things in this case, as each tile might need to be re-drawn when its lightness changes. Or, perhaps it'd work to have a low-resolution lighting mask drawn over the super-tile, with each pixel corresponding to a tile. You'll need to be creative with it. If you want per-pixel lighting, you could do a kind of deferred-lighting technique, where fully-lit tiles are drawn to the super tile, and then the shader that draws the super tile lights each pixel using information about light position and occluders. \$\endgroup\$
    – Jibb Smart
    Oct 13 '15 at 1:56

My first thought is that you're rendering too many tiles. Are you really getting any kind of detail out of an 8x8px tile at 2048x2048 resolution? But that's probably not relevant to the question, unless you want to consider maybe reducing the number of tiles you're rendering to ease the load on the shader.

My second thought is that you should probably use a static mesh representing the entire visible portion of the map, with a few rows and columns extending outside of the viewport to eliminate visible redraw, then just texture each triangle with the visible tiles. You can figure out which tiles are visible by calculating which tile your camera is currently pointing at, but I'm sure you already know how to do that. Keep track of the offset within whatever tile you're pointing at (IE, "My camera is pointing at tile 3,2 with an offset of -2, -14") and you'll never need to actually generate any vertices at all.

Which means that yes, in terms of raw "least number of calculations" you can't get less than zero. You'll still have to apply a transformation matrix, but you'd be doing that regardless.

Another trick I learned back in the day (and similar to Jibb's "super tile" concept) is to render the entire screen to a single texture, and then just render that texture plus whatever's changed/been added since the camera last moved. At the time this didn't produce any noticeable performance improvements on the platform I was working on, but it may be worth considering.

As for how to add new tiles to the view, you are always going to be rendering WxH's worth of tiles. you just just change the starting tile. So if your screen is 100 tiles' worth wide, and your camera is pointing at tile 0, you render tiles 0-99. When the camera moves and is now over tile 1, you render tiles 1-100. And so on.


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.