PDA

View Full Version : brightening premultiplied sprites



Gerald
09-25-2011, 11:13 AM
Seeking the best way to brighten texture-mapped quads... I'm using glDrawArrays (fixed pipeline vs. shader) with color arrays included. My sprites are composed of multiple png-based textures that are premultiplied to get rid of halos, etc.

I want to highlight them when the user is supposed to touch them (for example). I can darken them using the color arrays, but I do not know how to brighten them without causing them to become semi-transparent where they should not (see image).

Since the color arrays didn't seem to work for this (seems like they should?) I tried a different approach, but it didn't work out quite right.

My first hack at this was to call glDrawArrays a 2nd time with a different blend function. The images above used the following code, but no matter how I change the blend function for the 2nd glDrawArrays I can't get both brightening without unwanted transparency where it should not be.

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glDrawArrays(GL_TRIANGLES, 0, 6 * (TextureArrayIndexEnd - TextureArrayIndexStart));
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glDrawArrays(GL_TRIANGLES, 0, 6 * (TextureArrayIndexEnd - TextureArrayIndexStart));

Oh, and this is in effect:

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

Each sprite is composed of between ~10 texture-mapped quads.

Before brightening attempt it looks like this:
http://i55.tinypic.com/10qgq37.jpg

After brightening attempt it looks like so (note the bodies becoming transparent where they shouldn't):
http://i53.tinypic.com/2q03uh0.jpg

My approach here seems like a hack and I'm not sure it gives me the control I'm seeking (ie. basically what the color arrays provide for darkening, but for brightening instead). What I'm hoping to hear is that there is some way to actually use the color arrays to brighten as well as darken my texture mapped quads that is also compatible with premultiplied textures..

Is this doable?

ZbuffeR
09-26-2011, 05:09 AM
What you are trying to do is very simple using GLSL shaders.

However with the fixed path it is a bit more complex.
Instead of using additive blending, you must additive use texture environment, to add the color to the texture :
http://www.opengl.org/sdk/docs/man/xhtml/glTexEnv.xml

Gerald
09-26-2011, 11:28 AM
Using the texture environment was where I began.

But glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE) doesn't seem to honor a color value (from my color array used by glDrawArrays) with a value > 1.0. And GL_ADD as the last param in glTexEnvi() adds the color to the transparent pixel thus showing the parts of the quad that should be transparent...

Suggestions? (not really looking to move to GLSL this release if avoidable)

EDIT: If GL_MODULATE could use color values greater than 1.0 then my problem is solved. Is this possible? If not, I'm eager to hear how to use glTexEnv to solve this.

_arts_
09-26-2011, 12:47 PM
Why not simply make them clearer/darker with color ?



vec4 normal(.5,.5,.5,1.);
vec4 bright(.9,.9,.9,1.);

// use modulate for texture mapping

if (selected)
glColor3fv (bright);
else
glColor3fv (normal);

drawModel();


This should be far enough for what you're trying to do.

PS: All floating-point color values are stuck between [0..1]. If a component becomes greater than one it is clamped to 1.

Alfonse Reinheart
09-26-2011, 12:51 PM
What exactly do you plan to do with color values greater than 1, even if TexEnv supported it? Your display certainly doesn't support it. Your framebuffer certainly doesn't support it. OpenGL will clamp any color value outside the [0, 1] range (note: simplification).

Even with shaders, this only would make sense in the context of an HDR rendering system. And you're not using HDR (are you?).

In any case, the problem with the transparency is that you're using pre-multiplied alpha. Pre-multiplication is fine, in the general case. But what you're doing with it breaks the math behind it.

Gerald
09-26-2011, 02:34 PM
I'm not proposing a "display" color value greater than 1.0.

What I was attempting to ask (by my EDIT: comment above) was whether the algorithm used to arrive at the display value for gl_modulate would allow one of the floats being multiplied (specifically those specified in the color array) to be greater than 1.0.

In other words, if my R (of RGB) was set to 0.7 and the R in my color array was allowed to be greater than 1.0, then I could add more red by multiplying by 1.2 (for example) to have a display R value of .84.

But, as I understand it, neither the display value can exceed 1.0 (makes sense) nor the color value (doesn't make sense to me).

It is obvious to me the final value should be clamped, just not the values used to calculate it.

Gerald
09-26-2011, 02:38 PM
@_arts_: The color values I'm dealing with are from textures that were created by an art team that I import and map onto quads. I can use the technique you describe (an am using it actually) to darken the textures.

So, setting a color value of 1.0 gives me the actual color from the image and since I cannot use a value greater than 1.0 in my color array (or the equivalent glColor3fv call) then this call doesn't work for brightening.

Gerald
09-26-2011, 02:43 PM
Btw, I'm using pre-multiplied alpha because not using it results in ugly halos around the color boundaries... using pre-multiplied alpha completely solves this issue.

Regardless, whether using pre-multiplied alpha or not, I still have RGB values that are completely transparent that I need to avoid becoming visible as I brighten the visible colors.

Alfonse Reinheart
09-26-2011, 02:57 PM
nor the color value (doesn't make sense to me)

Why? That's what the spec says. The per-vertex output color is clamped. If you don't want it to be clamped, you have to use some form of shader.


Regardless, whether using pre-multiplied alpha or not, I still have RGB values that are completely transparent that I need to avoid becoming visible as I brighten the visible colors.

Except that pre-multiplied alpha is exactly why your transparency fails.

The typical way to "brighten" colors in TexEnv is to add a value to the texture's color. The clamping will automatically take care of values outside the [0, 1] range. What you are computing is (sprite + brightColor).

The problem is that the colors stored in the texture are not the sprite's colors. They are the sprite's color multiplied by its alpha: (premult = (sprite * alpha)). Therefore, if you add a value to this, you get (premult + brightColor) or ((sprite * alpha) + brightColor), which is functionally meaningless. What you want is this: ((sprite + brightColor) * alpha).

You could do this in shaders easily enough, by taking the per-vertex brightColor and multiplying it by the alpha. Essentially, distribution:



((sprite + brightColor) * alpha)
((sprite * alpha) + (brightColor * alpha))
(premult + (brightColor * alpha))


But you're using TexEnv, where such a thing is rather difficult. There are ways to do it using crossbar, I think, where you can use two texture stages (with only one texture) and play games like that. But I don't know those techniques.

Gerald
09-26-2011, 03:24 PM
I understand that the values stored are the color * the alpha.

Because of this when I apply a color via the color arrays it is necessary to pass R*A, G*A and B*A vs. just RGB. This seems to work fine in some specific cases.

Not sure. Still learning. Thanks for the replies so far.

I would be happy to avoid using pre-multiplied alpha if I had another solution to the halos.

For that matter I would be open to using shaders if this conversion was simple enough. Back when I first considered this I was unable to find much in the way of guidance compared to the fixed pipeline. Given this is about the only unresolved issue I have with the fixed pipeline (and perf is great - kinda afraid of having to chase that again...) I assume it would be easier to address this?

(I thought "how hard can it be to brighten colors on a texture? I'm sure TONS of people have already hit this issue and solved it...")

carsten neumann
09-26-2011, 03:34 PM
How about you enable lighting for the selected sprite and use that to highlight it?
It may be enough to use only ambient lighting for this, but I'm also not 100% certain you won't run into similar issues as you do now...

Gerald
09-26-2011, 03:59 PM
@carsten: I thought of that, but have not tried it yet as I've seen a lot of recommendations to keep it off if possible for performance reasons.

I could try that if it becomes obvious that there isn't a simpler solution (I'm surprised that this wasn't answered immediately with a "Oh, that old issue. Here, do this." type of response).

Gerald
09-26-2011, 04:50 PM
OK, so I turned off premultiplied alpha and changed by my blend function from:

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
to
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

With this context, I can use gl_modulate to dim and gl_add to brighten as long as I set the appropriate color values in my color array and keep up with which one (gl_add or gl_modulate) I should use.

But, if I go this path I've reintroduced the halos that I used premultiplied alpha to eliminate.

Is there a solution that provides both for a fixed pipeline?

Alfonse Reinheart
09-27-2011, 12:04 AM
Is there a solution that provides both for a fixed pipeline?

I'm sure there is. I suggested looking into crossbar stuff (http://www.opengl.org/registry/specs/ARB/texture_env_crossbar.txt). However, the thing you have to understand is this.

Most people have moved on to shaders. In the evolution of graphics hardware, around the time people started to do the kind of stuff you're trying to do, shaders were available. So most of us used them. So this kind of advanced fixed-function stuff fell by the wayside.

I'm not saying that an answer doesn't exist. I'm sure that some combination of crossbar, combine, and so forth will be able to achieve the overall effect you want. The problem is that most of us never learned that stuff; we all moved on to shaders. The information is out there, but we didn't bother to learn how to do it, since we could just use shaders.

Gerald
09-27-2011, 12:15 AM
I am looking into the crossbar stuff. Thanks for the link. I do understand the facts about folks moving on to shaders, but I thought it didn't hurt to ask given how many people have used OpenGL all these years.

I am also looking into the shaders. I fully intend to go there. It's just a question of timing. To that end if there is a clear tutorial that demonstrates the equivalent of glDrawArrays using vectors, textures and colors in arrays (ie. aggressive batching to reduce draw calls to a minimum) done using shaders I would very much like to be directed to it.

I bought a book on OpenGL using shaders back when it first came out. The concepts were pretty simple, but I didn't find much in the way of examples / tutorials and the folks who wrote the book were quite unresponsive to questions. That and the decision to support a set of devices that (at the time) did not support shaders lead us to the fixed pipeline.

At this point, the hardware support issue is resolved, so I can go there when it makes sense.

Thanks.

Alfonse Reinheart
09-27-2011, 12:40 AM
To that end if there is a clear tutorial that demonstrates the equivalent of glDrawArrays using vectors, textures and colors in arrays (ie. aggressive batching to reduce draw calls to a minimum) done using shaders I would very much like to be directed to it.

How you do batching really has nothing to do with shaders. At least, not in the way you mean it.

Also "aggressive batching" is not really tutorial-level material.

Gerald
10-03-2011, 11:00 AM
Btw, one last post here from me before moving on in case others stumble across this thread in the future.

There were two things I was trying to achieve here:

1) causing my sprites to blink when it is time for the player to touch them
2) come up with a general solution for brightening my sprites

I've not come up with a solution for #2 combined with premultiplied alpha that works. Turning off premultiplied alpha and using GL_ADD is trivial but results in the "halos" that premulitplying famously solves. As Alfonse suggests I suspect this is doable using some of the more advanced techniques available with the fixed pipeline. However, for my current game I can live without the general solution.

However for #1 I needed a solution now and here's what I've come up with: I've found that using glColor (or the color array equivalent) to set a value for how much brightening is desired and then switching the blend func back and forth between (GL_ONE, GL_ONE_MINUS_SRC_ALPHA) and (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) works pretty nicely.

In my specific case I use glDrawArrays, so as I loop through my sprites and the quads that compose them I am constantly adding array entries to a "display array" that will eventually be used in a call to glDrawArrays. As I build this array, I check to see if the sprite I'm processing has been marked as the one to be highlighted and, if so, I store the starting and ending arrays that compose that sprite.

Later when I actually loop thru the display array and call glDrawArrays, the loop logic notices when I reach the beginning of the highlighted sprite's arrays (color, vertex, texture, etc.). At that point the logic issues a glDrawArrays call for everything preceding that point and then flips the blend function as stated above until done drawing the highlighted sprite, at which point it swtiches it back.

Here is a snippet of code from the display loop to help clarify (note that I'm also switching the texture environment between GL_MODULATE and GL_ADD) :

if (bTouchHighlightMode == false)
{
// normal or dimmed sprites (enable COLOR_ARRAY to dim)
glEnableClientState(GL_COLOR_ARRAY);
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}
else
{
// highlighted sprites
glDisableClientState(GL_COLOR_ARRAY);
glColor4f (0.3, 0.3, 0.3, 1.0);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
}
glDrawArrays(GL_TRIANGLES, 0, 6 * (TextureArrayIndexEnd - TextureArrayIndexStart));


Disabling the client state for the color array is convenient for me here because typically I'm using the color array to specify how much to darken the sprites. So, by just disabling this array and setting glColor I do not have to change the "normal" color array values or maintain a separate color array. And, obviously you can change the values passed to glColor() based on how bright you want the blinking to be.

I also wrote a cool little algorithm to control how many times the sprite blinks and how often it re-blinks. This is all controlled in the sprite's method that return a boolean value for whether the sprite is TouchHighlight()ed or not...

Anyway, this works great for highlighting. Hopefully it is useful for someone else who hasn't made the jump to shaders quite yet. (which I look forward to doing...)

All the best, -Gerald