adziri
adziri

Reputation: 311

Texture mapping into a tile

I’m stuck on this task and do not know how to resolve this. I need to draw tile-grid with textures, my world represents a tiled grid, where the width and height of each tile equal 1. When I drawing I calculate a vertex buffer which contains vertices of the tiles which are visible for the camera, like on the screen:
enter image description here

(so I have one VBO for all these vertices)
Also, I have an element buffer which contains indexes and I draw them using GL_TRIANGLE_STRIP mode:
enter image description here
Real result:
enter image description here


And on this step all work just fine, but next I need to map on each square his texture, which I receive from the web, all textures differents. How I can do it? I’m using OpenGL ES 2.0 and C++.

Upvotes: 3

Views: 918

Answers (1)

Bartek Banachewicz
Bartek Banachewicz

Reputation: 39400

Well, if the other Q&As don't explain it well enough, let me give it a try.

The problem is indeed in the fact that you're using a triangle strip. The triangle strip has a number of uses, and the most important reason for using it is to reduce the amount of data for storing vertices:

The primary reason to use triangle strips is to reduce the amount of data needed to create a series of triangles. The number of vertices stored in memory is reduced from 3N to N+2, where N is the number of triangles to be drawn.

(Wikipedia)

The triangle strip achieves this miraculous property simply by reusing the data from previous triangles. It takes two vertices from a previous triangle and one additional one, which allows you to form a new triangle on that set of points. This works very well if the vertices are all forming one continuous surface, and each vertex makes sense both as a part of the first triangle and the next one.

e.g. for a sequence of vertices:

0:  (0,0)
1:  (0,1)
2:  (1,0)
3:  (1,1)

We end up with

1---3
|\  |
| \ |
|  \|
0---2

So indeed, the triangles are formed from indices (0,1,2) and (1,2,3).

Even when we add texturing, it still works. Assuming the texture is as big as four tiles, we might get:

0:  (0,0) (0  ,   0)
1:  (0,1) (0  , 0.5)
2:  (1,0) (0.5,   0)

3:  (1,1) (0.5, 0.5)
4:  (2,0) (1  ,   0)
5:  (2,1) (1  , 0.5)

With the result:

1---3---5
|\  |\  |
| \ | \ |
|  \|  \|
0---2---4

The key vertices to observe are 2 and 3. For the vertex 2, the texture coordinate is (0.5, 0), which is simultaneously the right edge of the 2nd triangle and the left edge of the 3rd one. This vertex naturally belongs to both of them, both position-wise and texture-wise.


Now, consider a tilemap where each square can be a different tile. Typically this can be achieved with a texture atlas with square tiles, each type of tile simply stored at a different offset.

Thus, the texture coordinates for the first pair of triangles might be the same, and the texture coordinates for the second pair might have an offset of say (+5, +5) (assume the texture is e.g. 100x100 for now, as that's easier to read).

So what happens with the vertices 2 and 3 now? They can't have the texture coordinates be both 0.5 and 5 at the same time. They are simply distinct vertices of two triangles that happen to lie next to each other position wise, but are completely separate texture-wise. The triangle strip reusing all attributes from previous vertices is the obstacle now.


And here's where the exploding kicks in. Instead of drawing the geometry as a triangle strip, you need individual triangles. You can still draw them in one drawcall, but you'll have to suffer some additional repetition of data:

-- triangle 0
0:  (0,0) (0  ,   0)
1:  (0,1) (0  , 0.5)
2:  (1,0) (0.5,   0)

-- triangle 1
3:  (0,1) (0  , 0.5)
4:  (1,0) (0.5,   0)
5:  (1,1) (0.5, 0.5)

-- triangle 2
6:  (1,0) (5  ,   5)
7:  (1,1) (5  , 5.5)
8:  (2,0) (5.5,   5)

-- triangle 3
9:  (1,1) (5  , 5.5)
10: (2,0) (5.5,   5)
11: (2,1) (5.5, 5.5) 

This is the raw data, but I understand that it's hard to follow, so let's use indices and look again. Assume positions go as they did before (0-5), the texture coordinates are t0 to t3 for the first triangle and u0 to u3 for the second one. Now:

0:  0 t0
1:  1 t1
2:  2 t2

3:  1 t1
4:  2 t2
5:  3 t3

6:  2 u0
7:  3 u1
8:  4 u2

9:  3 u1
10: 4 u2
11: 5 u3

Phew! Now it's a bit easier to spot the crucial difference: the position 2 appears in conjuctions with texcoord t2 in the first triangle, but with position u0 in the 2nd one. Similarly, position 3 interacts with t3 and u1, respectively. This is because the vertex 2 is the third vertex of the first triangle, but the first vertex of the second one and so on.

And that's it! Now you just need to write code to generate such a layout, set up your VBOs however you please (remembering that the vertex attribute for positions might be in a completely different VBO to the one for tiles to allow easier updates to just tile content without rewriting the tiles themselves) and you're done.

Remember that as I've mentioned before, this is all still drawn in one drawcall. The entire VBO is linearly processed by the GPU as fast as it can get, and it should result in a very good performance, the slightly higher memory use being rather negligible considering what kind of data we're working with here and the memory size of typical GPUs nowadays.


I have a few closing remarks that are sort of a post-scriptum

  1. In fact, if you indeed use the indexed rendering, only the indices get duplicated, not the actual vertex data. It's a good idea to use it here
  2. Remember about the winding order when generating the buffer. Since you're doing it programmatically, it's not hard to change, but might result in a few funny glitches if you don't get the order right.
  3. Keeping the texture indices in a separate buffer, or the same buffer, but not interleaved seems tempting because you can update them without touching the positions which will ultimately never change. This sounds tempting, but is not without drawbacks; the interleaved format is good precisely because it keeps data used together close, meaning the buffer fetches can be very, very efficient. If you split them up, you're forcing the gpu to stream from two distinct memory locations, potentially resulting in a worse rendering performance. Again, it probably won't matter in case of a simple 2D grid, but it's something to keep in mind.

Upvotes: 2

Related Questions