Opengl: Use single channel texture as alpha channel to display text

Kacper Kuryllo picture Kacper Kuryllo · Mar 24, 2012 · Viewed 11.1k times · Source

What I'm trying to do is load a texture into hardware from a single channel data array and use it's alpha channel to draw text onto an object. I am using opengl 4.

If I try to do this using a 4 channel RGBA texture it works perfectly fine but for whatever reason when I try and load in a single channel only I get a garbled image and I can't figure out why. I create the texture by combing texture bitmap data for a series of glyphs with the following code into a single texture:

int texture_height = max_height * new_font->num_glyphs;
int texture_width = max_width;

new_texture->datasize = texture_width * texture_height;
unsigned char* full_texture = new unsigned char[new_texture->datasize];

// prefill texture as transparent
for (unsigned int j = 0; j < new_texture->datasize; j++)
    full_texture[j] = 0;

for (unsigned int i = 0; i < glyph_textures.size(); i++) {
    // set height offset for glyph
    new_font->glyphs[i].height_offset = max_height * i;
    for (unsigned int j = 0; j < new_font->glyphs[i].height; j++) {
        int full_disp = (new_font->glyphs[i].height_offset + j) * texture_width;
        int bit_disp = j * new_font->glyphs[i].width;
        for (unsigned int k = 0; k < new_font->glyphs[i].width; k++) {
            full_texture[(full_disp + k)] =
                    glyph_textures[i][bit_disp + k];
        }
    }
}

Then I load the texture data calling:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->x, texture->y, 0, GL_RED, GL_UNSIGNED_BYTE, reinterpret_cast<void*>(full_texture));

My fragment shader executes the following code:

#version 330

uniform sampler2D texture;

in vec2 texcoord;
in vec4 pass_colour;

out vec4 out_colour;

void main()
{
float temp = texture2D(texture, texcoord).r;
out_colour = vec4(pass_colour[0], pass_colour[1], pass_colour[2], temp);
}

I get an image that I can tell is generated from the texture but it is terribly distorted and I'm unsure why. Btw I'm using GL_RED because GL_ALPHA was removed from Opengl 4. What really confuses me is why this works fine when I generate a 4 RGBA texture from the glyphs and then use it's alpha channel??

Answer

Nicol Bolas picture Nicol Bolas · Mar 24, 2012
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->x, texture->y, 0, GL_RED, GL_UNSIGNED_BYTE, reinterpret_cast<void*>(full_texture));

This is technically legal but never a good idea.

First, you need to understand what the third parameter to glTexImage2D is. That's the actual image format of the texture. You are not creating a texture with one channel; you're creating a texture with four channels.

Next, you need to understand what the last three parameters do. These are the pixel transfer parameters; they describe what the pixel data you're giving to OpenGL looks like.

This command is saying, "create a 4 channel texture, then upload some data to just the red channel. This data is stored as an array of unsigned bytes." Uploading data to only some of the channels of a texture is technically legal, but almost never a good idea. If you're creating a single-channel texture, you should use a single-channel texture. And that means a proper image format.

Next, things get more confusing:

new_texture->datasize = texture_width * texture_height*4;

Your use of "*4" strongly suggests that you're creating four-channel pixel data. But you're only uploading one-channel data. The rest of your computations agree with this; you don't seem to ever fill in any data pass full_texture[texture_width * texture_height]. So you're probably allocating more memory than you need.

One last thing: always use sized internal formats. Never just use GL_RGBA; use GL_RGBA8 or GL_RGBA4 or whatever. Don't let the driver pick and hope it gives you a good one.

So, the correct upload would be this:

glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, texture->x, texture->y, 0, GL_RED, GL_UNSIGNED_BYTE, full_texture);

FYI: the reinterpret_cast is unnecessary; even in C++, pointers can implicitly be converted into void*.