1
votes

I am having a huge problem with texture mapping in OpenGL, mainly with transparency. I am trying to replicate a multi-layered background by displaying a simple texture first and then a more detailed texture second (for example, the first one would be blue, and the second one would contain mountains and stuff). I have this almost working, but I have a weird result and I'm not sure how to fix it.

I basically want the black in my second texture to not appear. I have this working, but the brown of my mountains seems to blend with the first background texture (or add to it). My brown mountains show up as pale red. My glBlendFunc is currently at GL_ONE but I have tried with GL_ONE_MINUS_SRC_ALPHA to have nothing change; the black is still there, the mountains are still brown.

I have tried the image in three different formats with no difference (BMP, JPG, and PNG).

Below is the code that I have for both textures:

TEXTURE 1:

if (buf.load("images/background-layer1.png"))
{
    tex1 = QGLWidget::convertToGLFormat( buf );
    glPushAttrib(GL_CURRENT_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT);

    glGenTextures(1, &texture[0]);
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, 4, tex1.width(), tex1.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex1.bits());

    glEnable(GL_TEXTURE_2D);

    glBegin(GL_QUADS);
        glTexCoord2f(0, 0);
        glVertex2d(m_pBottomLeft.x, m_pBottomLeft.y); //Bottom Left

        glTexCoord2f(1, 0);
        glVertex2d(m_pBottomRight.x, m_pBottomRight.y); //Bottom Right

        glTexCoord2f(1, 1);
        glVertex2d(m_pTopRight.x, m_pTopRight.y); //Top Right

        glTexCoord2f(0, 1);
        glVertex2d(m_pTopLeft.x, m_pTopLeft.y); //Top Left
    glEnd();

    glDisable(GL_TEXTURE_2D);
    glPopAttrib();
}

TEXTURE 2:

if (buf2.load("images/background-layer2.png"))
{
    tex2 = QGLWidget::convertToGLFormat( buf2 );

    glPushAttrib(GL_CURRENT_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    glEnable(GL_DEPTH_TEST);

    glEnable(GL_TEXTURE_2D);

    glGenTextures(2, &texture[1]);
    glBindTexture(GL_TEXTURE_2D, texture[1]);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex2.width(), tex2.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex2.bits());

    glBegin(GL_QUADS);
        glColor4f(1, 1, 1, 1);
        glTexCoord2f(0, 0);
        glVertex2d(m_pBottomLeft.x, m_pBottomLeft.y); //Bottom Left

        glTexCoord2f(1, 0);
        glVertex2d(m_pBottomRight.x, m_pBottomRight.y); //Bottom Right

        glTexCoord2f(1, 1);
        glVertex2d(m_pTopRight.x, m_pTopRight.y); //Top Right

        glTexCoord2f(0, 1);
        glVertex2d(m_pTopLeft.x, m_pTopLeft.y); //Top Left
    glEnd();

    glDisable(GL_BLEND);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_TEXTURE_2D);
    glPopAttrib();
}

Screenshot: Screenshot

1
Does your mountain texture contain a valid alpha channel ?rotoglup
This is going to sound stupid as hell but how can I figure that out or add one?Starforsaken101
Ouch, vast question... alpha channel allows to make parts of your image transparent, like you'd like for your black parts. You can use an image editing software to check/create one. See GIMP for example : docs.gimp.org/en/gimp-using-web-transparency.htmlrotoglup
Well, I do understand what that means, but if I want to the black colors of my image to be transparent, why would I need to save with transparency? Why not make my transparent areas black?Starforsaken101
Okay, for some reason, I thought you absolutely had to replace some color with transparency in OpenGL. What I did was saved the image properly, with true alpha/transparent areas. Then I changed the mode to GL_ONE_MINUS_SRC_ALPHA! This is so cool. Thanks rotoglup :)!Starforsaken101

1 Answers

1
votes

You have your solution already, but hey, it is possible to go without the alpha channel and make a certain color transparent. While participating in Global Game Jam (48 hours limited game competition), we needed to make a lot of sprites for different objects quickly, without using any complicated tools.

We actually ended up using a digital camera and the windows paint (mspaint). We set up a rule that the upper left corner of the image must always contain the transparent color (so the transparent color could be pretty much any color the artist chose). When the image was loaded, the alpha channel was set accordingly to the occurence of the transparent color. While that worked well, it still left some of the transparent color leak into the image (thanks to texture filtering).

/**
 *  @brief a simple raster image with fixed RGBA8 storage
 *
 *  The image data are always RGBA8. Alpha is stored in the most significant byte,
 *  followed by red, green and blue with decreasing significance.
 *
 *  The storage is very simple, each 32 bits in the buffer contains a single pixel,
 *  the first pixel is in top left corner, there is no scanline padding.
 */
struct TBmp {
    char n_former_bpp; /**< @brief former bpp, before conversion to RGBA8 */
    bool b_grayscale; /**< @brief grayscale flag (if set, the bitmap is assumed
        to contain grayscale image, stored as RGBA8) */
    bool b_alpha; /**< @brief alpha channel flag (if set, the alpha channel is significant;
        otherwise it's expected to be 0xff in all image pixels) */
    int n_width; /**< @brief image width, in pixels */
    int n_height; /**< @brief image height, in pixels */
    uint32_t *p_buffer; /**< @brief pointer to image data */
};

void TransparentColor_to_Alpha(TBmp *p_sprite, bool b_force_alpha_recalc = false)
{
    if(b_force_alpha_recalc || !p_sprite->b_alpha) {
        uint32_t n_transparent_color = p_sprite->p_buffer[0] & 0xffffff;
        // get transparent color from lower left corner

        for(int i = 0, n = p_sprite->n_width * p_sprite->n_height; i < n; ++ i) {
            uint32_t n_color = p_sprite->p_buffer[i];
            if(n_color == n_transparent_color)
                ;//p_sprite->p_buffer[i] = n_color; // do nothing, color is transparent and alpha is zero
            else if((n_color & 0xffffff) == n_transparent_color)
                p_sprite->p_buffer[i] = n_color & 0xffffff; // clear alpha
            else
                p_sprite->p_buffer[i] = n_color | 0xff000000U; // set alpha
        }
        // calculate alpha based on transparent color (binary only)

        p_sprite->b_alpha = true;
    }
    // build alpha channel using "transparent color"
}

In order to remove the transparent color from the image, we wrote additional function that would duplicate color of the boundary pixels, effectively erasing the transparent color from the image (that can be done because the transparency is now in alpha channel).

bool Sprite_FloodEdgeColor(TBmp *p_sprite, int n_max_grow_step_num = 0)
{
    {
        uint32_t n_transparent_color = p_sprite->p_buffer[0] & 0xffffff;
        // get transparent color from lower left corner

        TBmp *p_clone;
        if(!(p_clone = p_sprite->p_Clone()))
            return false;
        // clone the bitmap

        uint32_t *p_buffer = p_sprite->p_buffer;
        uint32_t *p_buffer_pong = p_clone->p_buffer;
        for(int i = 0; !n_max_grow_step_num || i < n_max_grow_step_num; ++ i) {
            bool b_change = false;
            for(int y = 0, w = p_sprite->n_width, h = p_sprite->n_height; y < h; ++ y) {
                for(int x = 0; x < w; ++ x) {
                    if(p_buffer[x + w * y] == n_transparent_color) {
                        int n_neigh_rb = 0, n_neigh_g = 0;
                        int n_neigh_num = 0;

                        for(int sy = max(1, y) - 1, ey = min(y + 1, h - 1); sy <= ey; ++ sy) {
                            for(int sx = max(1, x) - 1, ex = min(x + 1, w - 1); sx <= ex; ++ sx) {
                                if(sx == x && sy == y)
                                    continue; // skip self (it's transparent anyway)
                                uint32_t n_neigh = p_buffer[sx + w * sy];
                                if(n_neigh != n_transparent_color) {
                                    n_neigh_rb += n_neigh & 0xff00ff;
                                    n_neigh_g += n_neigh & 0xff00;
                                    ++ n_neigh_num;
                                }
                            }
                        }
                        // gather neighbour colors

                        if(n_neigh_num > 2) {
                            int r = (n_neigh_rb & 0xffff0000) / n_neigh_num;
                            int g = n_neigh_g / n_neigh_num;
                            int b = (n_neigh_rb & 0xffff) / n_neigh_num;
                            uint32_t n_color = (0xff0000 & min(0xff0000, r)) |
                                (0xff00 & min(0xff00, g)) | (0xff & min(0xff, b));
                            // calculate average neighbor color

                            p_buffer_pong[x + w * y] = n_color;
                            b_change = true;
                        }
                    } else
                        p_buffer_pong[x + w * y] = p_buffer[x + w * y]; // just copy
                }
            }
            // grow 1px into transparent color

            if(b_change || p_buffer != p_sprite->p_buffer)
                std::swap(p_buffer, p_buffer_pong);
            // swap the buffers ...

            if(!b_change)
                break;
        }

        if(p_buffer != p_sprite->p_buffer) {
            memcpy(p_sprite->p_buffer, p_buffer,
                p_sprite->n_width * p_sprite->n_height * sizeof(uint32_t));
        }
        // in case the last result is not in

        p_clone->Delete();
        // cleanup
    }
    // bleed colors on edge into the transparent space (to enable hifi blending)

    return true;
}

That was almost it, but the pictures of the objects we took using the digital camera often had brighter pixels at the edge which were particularly disturbing for the player to look at. So we wrote one more function that would use the median filter to remove the bright pixels from the boundary (while the rest of the image is unaffected).

bool SpriteEdge_MedianFilter(TBmp *p_sprite,
    bool b_prefer_darker = true, bool b_5x5_median = true)
{
    {
        uint32_t n_transparent_color = p_sprite->p_buffer[0] & 0xffffff;
        // get transparent color from lower left corner

        TBmp *p_clone;
        if(!(p_clone = p_sprite->p_Clone()))
            return false;
        // clone the bitmap

        uint32_t *p_buffer = p_sprite->p_buffer;
        uint32_t *p_buffer_pong = p_clone->p_buffer;
        {
            const int n_off = (b_5x5_median)? 2 : 1;
            const int n_thresh = (b_5x5_median)? 25 : 9;

            bool b_change = false;
            for(int y = 0, w = p_sprite->n_width, h = p_sprite->n_height; y < h; ++ y) {
                for(int x = 0; x < w; ++ x) {
                    if(p_buffer[x + w * y] != n_transparent_color) {
                        uint32_t p_neigh_color[25];
                        int n_neigh_num = 0;

                        for(int sy = max(n_off, y) - n_off,
                           ey = min(y + n_off, h - 1); sy <= ey; ++ sy) {
                            for(int sx = max(n_off, x) - n_off,
                               ex = min(x + n_off, w - 1); sx <= ex; ++ sx) {
                                uint32_t n_neigh = p_buffer[sx + w * sy];
                                if(n_neigh != n_transparent_color) {
                                    p_neigh_color[n_neigh_num] = n_neigh;
                                    ++ n_neigh_num;
                                }
                            }
                        }
                        // gather neighbour colors (including self)

                        if(n_neigh_num < n_thresh) { // if the pixel is on the edge ...
                            uint32_t r[25], g[25], b[25];
                            for(int i = 0; i < n_neigh_num; ++ i) {
                                r[i] = p_neigh_color[i] & 0xff0000;
                                g[i] = p_neigh_color[i] & 0xff00;
                                b[i] = p_neigh_color[i] & 0xff;
                            }
                            std::sort(r, r + n_neigh_num);
                            std::sort(g, g + n_neigh_num);
                            std::sort(b, b + n_neigh_num);
                            // calculate median neighbor color

                            uint32_t n_self = p_buffer[x + w * y];
                            int mr, mg, mb;
                            if(b_prefer_darker) {
                                mr = min(r[n_neigh_num / 2], n_self & 0xff0000);
                                mg = min(g[n_neigh_num / 2], n_self & 0xff00);
                                mb = min(b[n_neigh_num / 2], n_self & 0xff);
                            } else {
                                mr = r[n_neigh_num / 2];
                                mg = g[n_neigh_num / 2];
                                mb = b[n_neigh_num / 2];
                            }
                            int a = n_self & 0xff000000U;

                            p_buffer_pong[x + w * y] = mr | mg | mb | a;
                            b_change = true;
                        }
                    } else
                        p_buffer_pong[x + w * y] = p_buffer[x + w * y]; // just copy
                }
            }
            // grow 1px into transparent color

            if(b_change || p_buffer != p_sprite->p_buffer)
                std::swap(p_buffer, p_buffer_pong);
            // swap the buffers ...
        }

        if(p_buffer != p_sprite->p_buffer) {
            memcpy(p_sprite->p_buffer, p_buffer,
                p_sprite->n_width * p_sprite->n_height * sizeof(uint32_t));
        }
        // in case the last result is not in

        p_clone->Delete();
        // cleanup
    }

    return true;
}

We actually wrote one more function that would erode the opaque parts of the image, effectively making the sprite a selected amount of pixels smaller and removing the problematic regions in case they could not be removed using the median function. That's pretty much it, although it was written in about an hour, it is pretty much ultimate tool for creating quick'n'dirty sprites.

Get full source code.