2
\$\begingroup\$

Currently I'm trying to render a TileMap using OpenGL 2.1, GLSL 1.2. I would like to draw every tile in just one draw call. I use a single texture with all tiles, identifying each one by an index.

The vertex data per tile is:

vec2 worldPos; // the position to transform the tile Quad in world coordinates
vec2 texCoord; // the uv coordinates, calculated using the tile index, by CPU (top-left corner).

But I can't find a way to draw everything with one draw call: - The uv coordinates can't be calculated in shader because the vertex shader don't know which corner of the quad it is processing. - Can't draw by element because my quad vertex data only contains 4 vertices, store repeated vertices is a memory waste. Only if I could use a separate element buffer just for the vertex (0, 1, 2, 3, 0, 1, ...).

Does someone have any suggestion about how should I proceed? Thanks!

\$\endgroup\$

3 Answers 3

3
\$\begingroup\$

The uv coordinates can't be calculated in shader because the vertex shader don't know which corner of the quad it is processing.

int corner = gl_VertexID % 4;

There; now your shader knows what corner it is. This requires GL 3.x and a #version declaration of 130 or above.

store repeated vertices is a memory waste

Yes. That's why it's called a "memory vs. performance tradeoff." You spend memory to make things faster.

You're doing 2D rendering. Do you honestly think that making a proper array is going to tax your GPU's memory significantly?

Even if your tiles are 4x4 pixels in size, and you're rendering at 2560x1600, that's only 256,000 tiles for a single screen. With each tile being 32 bytes in size (four vertices, where each vertex has 2 signed shorts for the position, 2 signed shorts for the texture coordinate), that means your vertex data takes up 8,192,000 bytes of memory.

Is ~8MB of storage really going to be a problem for you? Even on cards with 128MB of graphics memory, that's less than 10% of the space. And that's the worst-case scenario; for more reasonable tile sizes like 8x8 or 16x16, you're looking at 2MB or 512KB, respectively

Also, instancing for tiles is more likely to kill your performance than to help it. Instancing works most optimally when:

  • The number of instances is large. 500+.
  • The size of each instance's data is between 100 and 2,000 vertices or so.

Your case fails #2.

\$\endgroup\$
4
  • \$\begingroup\$ I think even the built-in variable is not available, at least not guaranteed by GLSL 1.2 Spec. \$\endgroup\$
    – felipe
    Commented Aug 1, 2013 at 15:11
  • \$\begingroup\$ @Felipe: I don't know why I even bother looking at the man pages sometimes. They're just dangerously inaccurate. \$\endgroup\$ Commented Aug 1, 2013 at 17:14
  • \$\begingroup\$ GLSL 1.2 does not provide any type smaller than 'float'. If I use 'short' to load the buffer I still use less bandwidth? \$\endgroup\$
    – felipe
    Commented Aug 2, 2013 at 4:08
  • \$\begingroup\$ @Felipe: If you're talking about vertex data, GLSL doesn't care. GLSL only sees the post-conversion data. The conversion is that specified by your vertex format. That has nothing to do with GLSL. \$\endgroup\$ Commented Aug 2, 2013 at 4:23
2
\$\begingroup\$

Here´s an alternate approach. which I used for something slighty diffrent, but i think it might help you too, it´s super low memory, one draw call and very fast:

What you need:

  • Your View and Projection Matrix
  • A vertexbuffer holding a fullscreen quad
  • A texture where each pixels represents a tile and stores the tile´s texture ID (needs to be float or integer texture
  • A texture atlas containing all possible tile textures
  • the following shader ( i didn´t test it, you might have to do some minor corrections)

Vertexshader ( you can avoid alot of this if you use orthographic projection):

varying vec3 pos3D; //position of Vertex in 3D

uniform mat4 invWVP; //Inverse of (Projection * View)
uniform vec3 CPos; // Camera Position

void main()
{
    vec4 pos = invWVP * gl_Vertex; //You defined a fullscreen quad in Screen coordinates, this brings it back to 3D

    pos /= pos.w;   //homogenous coordinate correction

    vec3 dir = pos.xyz - CPos; //you don´t exactly know how far the plane your tiles are on is from the camera, so we do a ray plane intersection

    float t = -CPos.z / (dir.z); //assumes XY-Plane as your world plane

    pos3D = CPos + t * dir; // final 3D position of the corners

    gl_Position = gl_Vertex; // just render the screen coordniates, we need it to fill the whole screen
}

Fragment Shader:

varying vec3 pos3D; //pixel´s 3D position

uniform sampler2D tiles; //tile-map
uniform sampler2D atlas; // texture atlas

void main()
{
 vec2 tile = vec2(floor(pos3D.x) / 512.0,floor(pos3D.y) / 512.0); // figure out which tile we´re in from the 3D coordinates
 float tex = texture2D(tiles,tile).r; // get tile´s texture ID

 vec2 UV = vec2(0.0625 * (tex + fract(pos3D.x)), fract(pos3D.y)); // calculate UV within the texture|. The factor 0.0625 is if you have 16 textures next to each other from left to right in your atlas, this entirely depends on your layout for it

 gl_FragColor = vec4(texture2D(atlas,UV)); //fetch texel for fragment   
}

All this assumes that you use the XY-Plane as your world and display everything on that one, you have to change a little in case you use a diffrent plane. It can be simplified if you use orthographic projection and no tilt in you view matrix. The advantage of this is that you don´t need more than 4 vertices and 2 textures, the shaders are not too heavy so it should run pretty fast any one drawcall is enought for everything and as long as you keep the matrices and CPos up-to-date camera movement will be rendered correctly.

\$\endgroup\$
0
\$\begingroup\$

It sounds like you're using vertex instancing with an inner vertex buffer representing a single quad, and an outer vertex buffer with one entry per tile. If that's the case, you specify the UV of one corner of the tile in the per-tile vertex buffer (as it sounds like you're already doing), and a per-corner UV offset in the inner vertex buffer. Just add the two together in the vertex shader to generate the UVs for the tile.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .