5
\$\begingroup\$

I'm working on a user interface system, and I want to be able to mix textured polies with frag-coloured polies. Here's my shader code, that doesn't work:

attribute vec2 vertex_coords;
attribute float texid;
attribute vec4 fragdetails;

varying float usingtex;
varying vec4 v_fragdetails;

void main(){
    gl_Position =  gl_ModelViewProjectionMatrix * vec4(vertex_coords,0.0,1.0);

    usingtex = texid;
    v_fragdetails = fragdetails;
}

Fragment:

varying float usingtex;
varying vec4 v_fragdetails;

uniform sampler2D thetexture;

void main() {
    if (usingtex != 0.0){
        thetexture = int(usingtex);
        gl_FragColor = texture2D(thetexture, vec2(v_fragdetails[0], v_fragdetails[1]));
    } else {
        thetexture;
        gl_FragColor = v_fragdetails;
    }
}

Frag_details consists of either [r, g, b, a] for a coloured poly, with texid set to 0, or [texture x, texture y, 0, 0] for a textured one.

However, samplers must be uniform, and uniforms can't be modified. So how on earth would I swap between textures in one draw? Do I have to set them all up as uniforms and then pick from those? I'd have to use an array, and know the length, which isn't really practical for a UI system where buttons will be clicked, tabs changed, and so on.

Is there a way to do this?

\$\endgroup\$
5
  • \$\begingroup\$ Do you have to switch textures inside of the Shader? Perhaps store a list of Textures, and inside of the object ( your objects have their own class, right? ) store an int relative to which Texture it uses, then use that to set the Texture during the draw call. \$\endgroup\$
    – Phill.Zitt
    Commented Aug 15, 2012 at 23:26
  • \$\begingroup\$ @Phill.Zitt I'd thought of that but the number of elements would be an issue. Things will be changing a lot in this system, but you need to know how many elements are in the list to start with. I suppose I could set it to a high number and pad it... \$\endgroup\$
    – bonzairob
    Commented Aug 16, 2012 at 8:08
  • \$\begingroup\$ I'm not sure what language you're using, but I know that most languages have some sort of managed List or Vector that will tell you it's .size or .length. Regardless, if the List is storing textures, wouldn't you know exactly how many textures are in it ( i.e. you have to load them all in anyways, right?) ? Member int or float that increments every time a new texture is loaded will fix that problem. \$\endgroup\$
    – Phill.Zitt
    Commented Aug 16, 2012 at 14:13
  • \$\begingroup\$ @Phill.Zitt The problem isn't counting the textures, it's being able to keep things flexible. My plan was to be able to add and remove UI elements as they're needed. Now it looks like that's not possible. \$\endgroup\$
    – bonzairob
    Commented Aug 16, 2012 at 15:48
  • \$\begingroup\$ Ah. That makes the question more clear. Are you targeting something that has an extremely limited amount of RAM? If not, storing all of the UI elements in an array, and 'turning' them on and off via a class variable ( bool, I'd think ) should save you draw calls. You might want to edit in what platform and programming language you are targeting ( as tags as well, perhaps? ) to give people a little more knowledge as to your situation. \$\endgroup\$
    – Phill.Zitt
    Commented Aug 16, 2012 at 16:26

3 Answers 3

4
\$\begingroup\$

One way to do this is to make a texture atlas: a single large image that contains all the icons or textures you'd ever want to draw. You'd have some data structure keeping track of where each icon is in the atlas, and set up UVs appropriately when you generate the geometry. Then you can do the whole thing with one texture/sampler and one draw call.

If you include glyphs in the texture atlas as well, you can do all your text rendering using this system too.

To handle solid-colored polygons, you could write the shader to multiply the texture by a vertex color. Include a 1x1 white pixel someplace in the atlas and set up the UVs to use that for polygons that should be solid-colored; similarly, ensure the vertex color is set to white for any textured polygons. This does mean your vertex data has to include both color and UVs for every vertex.

Another possibility is to split things into multiple draw calls. You'd have a solid-color shader and a textured shader, with one draw call for solid colors plus one for each texture. You'd have a vertex buffer for each of these cases and while generating geometry, add each polygon to the appropriate vertex buffer.

One issue with this is that you won't have full control over the draw order. You couldn't have a solid-colored poly over a textured poly over another solid-colored poly, for instance. For UI this is a pretty big issue, so I'd go with the atlasing approach, which will ensure everything gets drawn in the order you specify.

\$\endgroup\$
9
  • \$\begingroup\$ Also worth noting that for UIs, you should avoid scaling the texture at all if possible (create a texture atlas for the size of elements in pixels you plan to draw), and that you thus don't need mipmaps (which would just waste resources). You may also need to add a half pixel offset for GL and D3D10+, but not D3D9. \$\endgroup\$ Commented Aug 16, 2012 at 2:40
  • \$\begingroup\$ @Sean: "you should avoid scaling the texture at all if possible" I strongly disagree. The worst thing about many PC games is that their UIs are unscaleable, making them almost unplayable at large resolutions. I like Gratuitous Space Battles somewhat, but the fact that the text is microscopic and UI elements are tiny at 1080p or larger. \$\endgroup\$ Commented Aug 16, 2012 at 4:56
  • \$\begingroup\$ @SeanMiddleditch You can get better-looking magnified text and other two-color UI elements using distance fields. You're correct that there should be no need for mipmaps, though, as you should probably not be minifying text. (Although the distance field approach pretty much fixes minification, too, IIRC.) \$\endgroup\$ Commented Aug 16, 2012 at 6:32
  • \$\begingroup\$ The draw order would be an issue, so the atlas seems to be the way to go. However, I'm having a hard time thinking up ways to efficiently store things on there. It's easy enough with the rest of the game assets because they're mostly regular sizes and I can just append them to one another, but the UI parts will be so varied. Is there a secret to laying out an atlas automatically, efficiently? It seems like it would be something very useful to learn! \$\endgroup\$
    – bonzairob
    Commented Aug 16, 2012 at 8:42
  • \$\begingroup\$ @Nicol Bolas: this can be fixed by having a few different atlases for different screen sizes and form factors. doubling the size strictly also is acceptable, but that will generally mean you had ridiculously small elements to begin with. \$\endgroup\$ Commented Aug 16, 2012 at 9:18
3
\$\begingroup\$

For OpenGL 3.0+, an alternative to a texture atlas is a texture array, using sampler2DArray in GLSL. These are created similarly to 3D textures: glTexImage3D(GL_TEXTURE_2D_ARRAY...), followed by glTexSubImage3D(GL_TEXTURE_2D_ARRAY...) to actually fill the texture data. For pre-3.0 openGL, there is at least one extension of similar functionality (EXT_texture_array), but I don't know how good it is or on what GPUs it works.

Texture arrays have some advantages over atlases, mainly you don't have to pack them and you don't have interpolation issues at edges of textures. You can use multiple textures in the shader because your texture coordinates become three-dimensional (UV + texture id). A texture array will probably waste more space if your textures aren't of similar size, though.

\$\endgroup\$
1
  • \$\begingroup\$ That sounds really interesting, but unfortunately most of these elements will be different sizes :( \$\endgroup\$
    – bonzairob
    Commented Aug 17, 2012 at 12:16
3
\$\begingroup\$

I came up with the following solution, if you really need access to multiple samplers in a FOR loop. this even works with OpenGL ES 1.0! as you only can define 32 samplers maximum it is feasible i think.

uniform sampler2D tex[32];
vec4 c = vec4(0.0,0.0,0.0,1.0);

vec4 getColor(float i, float x, float y) {
    int ii = int(i);
    if(ii==0){c = texture2D(tex[0], vec2(x, y));}
    if(ii==1){c = texture2D(tex[1], vec2(x, y));}
    if(ii==2){c = texture2D(tex[2], vec2(x, y));}
    if(ii==3){c = texture2D(tex[3], vec2(x, y));}
    //...
    return c;
}

//in main in for loop with increment i:
//where as x and y are your coords.
vec4 color = getColor(i,x,y);
\$\endgroup\$

You must log in to answer this question.

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