Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenGL ES 1.1: How to change texture color without losing luminance?

I have particles that I want to be able to change the color of in code, so any color can be used. So I have only one texture that basically has luminance.

I've been using glColor4f(1f, 0f, 0f, 1f); to apply the color.

Every blendfunc I've tried that has come close to working ends up like the last picture below. I still want to preserve luminance, like in the middle picture. (This is like the Overlay or Soft Light filters in Photoshop, if the color layer was on top of the texture layer.)

Any ideas for how to do this without programmable shaders? Also, since these are particles, I don't want a black box behind it, I want it to add onto the scene.

alt text

like image 238
Tenfour04 Avatar asked Dec 05 '10 20:12

Tenfour04


2 Answers

Here is a solution that might be close to what you're looking for:

glColor4f(1.0f, 0.0f, 0.0f, 1.0f);

glActiveTexture( GL_TEXTURE0 );
glEnable( GL_TEXTURE_2D );
glBindTexture(GL_TEXTURE_2D, spriteTexture);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

glActiveTexture( GL_TEXTURE1 );
glEnable( GL_TEXTURE_2D );
glBindTexture(GL_TEXTURE_2D, spriteTexture);    
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD );

What it does is multiply the original texture by the specified color and then adds the pixels values of the original texture on top:

final_color.rgba = original_color.rgba * color.rgba + original_color.rgba;

This will result in a brighter image than what you've asked for but might be good enough with some tweaking.

Should you want to preserve the alpha value of the texture, you'll need to use GL_COMBINE instead of GL_ADD (+ set GL_COMBINE_RGB and GL_COMBINE_ALPHA properly).

Here are some results using this technique on your texture. alt text

like image 133
Ozirus Avatar answered Nov 09 '22 03:11

Ozirus


NONSENSE! You don't have to use multi-texturing. Just premultiply your alpha.

If you premultiply alpha on the image after you load it in and before you create the GL texture for it then you only need one texture unit for the GL_ADD texture env mode.

If you're on iOS then Apple's libs can premultiply for you. See the example Texture2D class and look for the kCGImageAlphaPremultipliedLast flag.

If you're not using an image loader that supports premultiply then you have to do it manually after loading the image. Pseudo code:

uint8* LoadRGBAImage(const char* pImageFileName) {
    Image* pImage = LoadImageData(pImageFileName);
    if (pImage->eFormat != FORMAT_RGBA)
        return NULL;

    // allocate a buffer to store the pre-multiply result
    // NOTE that in a real scenario you'll want to pad pDstData to a power-of-2
    uint8* pDstData = (uint8*)malloc(pImage->rows * pImage->cols * 4);
    uint8* pSrcData = pImage->pBitmapBytes;
    uint32 bytesPerRow = pImage->cols * 4;

    for (uint32 y = 0; y < pImage->rows; ++y) {
        byte* pSrc = pSrcData + y * bytesPerRow;
        byte* pDst = pDstData + y * bytesPerRow;
        for (uint32 x = 0; x < pImage->cols; ++x) {
            // modulate src rgb channels with alpha channel
            // store result in dst rgb channels
            uint8 srcAlpha = pSrc[3];
            *pDst++ = Modulate(*pSrc++, srcAlpha);
            *pDst++ = Modulate(*pSrc++, srcAlpha);
            *pDst++ = Modulate(*pSrc++, srcAlpha);
            // copy src alpha channel directly to dst alpha channel
            *pDst++ = *pSrc++;
        }
    }

    // don't forget to free() the pointer!
    return pDstData;
}

uint8 Modulate(uint8 u, uint8 uControl) {
    // fixed-point multiply the value u with uControl and return the result
    return ((uint16)u * ((uint16)uControl + 1)) >> 8;
}

Personally, I'm using libpng and premultiplying manually.

Anyway, after you premultiply, just bind the byte data as an RGBA OpenGL texture. Using glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); with a single texture unit should be all you need after that. You should get exactly (or pretty damn close) to what you want. You might have to use glBlendFunc(GL_SRC_ALPHA, GL_ONE); as well if you really want to make the thing look shiny btw.

This is subtly different from the Ozirus method. He's never "reducing" the RGB values of the texture by premultiplying, so the RGB channels get added too much and look sort of washed out/overly bright.

I suppose the premultiply method is more akin to Overlay whereas the Ozirus method is Soft Light.

For more, see:

http://en.wikipedia.org/wiki/Alpha_compositing

Search for "premultiplied alpha"

like image 22
mikerochip Avatar answered Nov 09 '22 04:11

mikerochip