2
\$\begingroup\$

I know when drawing a rectangle, if we select red and draw two vertices, and then select yellow and draw the other two vertices, the entire rectangle will show a nice transition of color between those vertices and the middle of the rectangle will appear orange.

Suppose I have two textures, and I want to do the same kind of thing. On one rectangle, I want two vertices to use texture from one image and the other vertices to use the texture from the other image, and show a smooth transition in the middle.

Please show me how this can be done in OpenGL, thanks!

\$\endgroup\$
  • \$\begingroup\$ Are you talking about OpenGL 1.x or 2.x+ ? (There are different methods depending on whether you're using 1.x's fixed function pipeline, or 2.x's shader-based rendering) \$\endgroup\$ – Trevor Powell Feb 28 '14 at 5:10
  • \$\begingroup\$ Hi Trevor, I am a beginner, I think what I'm doing is OpenGL 1.x (I just put a bunch of vertices and texture coordinates between glBegin and glEnd). I wish there is a simple solution to this problem that is easy to understand for beginners like me. Thanks a lot! :) \$\endgroup\$ – noir Mar 1 '14 at 1:03
  • 1
    \$\begingroup\$ I'm surprised people don't think this was possible with GL 1.x -- in some ways, the ingenuity of little hacks and abusing pipelines feels lost when you can just do everything in shaders. Not that I want GL 1.x back :) \$\endgroup\$ – PatrickB Apr 10 '14 at 20:47
  • \$\begingroup\$ @noir: You're likely using the latest OpenGL your driver supports in the default compatibility mode, which allows you to use functions from OpenGL 1.0 but also features from newer versions. You can mix-and-match functionality as desired like glBegin and multi-texturing or shaders. \$\endgroup\$ – Sean Middleditch Apr 12 '14 at 0:23
2
\$\begingroup\$

This is possible using GL 1.x and here's how.

So as you've found using vertex colors, a smooth transition is where it blends the two colors. This is a called a linear interpolation, or a "lerp" for short. It can be generalized to any dimension by operating on the elements of the vector individually and indeed colors are treated like a lerp in 3D -- blend R1 with R2, G1 with G2, B1 with B2.

The lerp() function has an "alpha" parameter, such that alpha = 0.0 yields fully color 1 and alpha of 1.0 yields fully color 2, and somewhere in-between creates a mixture. So you'll see then that if you have color 1 (C1) and color 2 (C2) and some alpha value (A), then would do it as: lerp(C1,C2,A) = A*C1 + (1.0-A)*C2. As many people will tell you, you can simplify this formula, but keeping it as is will help you understand how to do this with GL 1.x.

To do this with two textures using GL 1.x, you will be drawing two rectangles on top of each other, one with texture 1, one with texture 2. However, you will be adding the two colors. If you do this as-is you'll end up with a final texture that looks like

final = C1 + C2

If you look at the lerp() function, you'll see that we're missing the multiplication by A and 1.0-A. So to do this, we're going to using colors.

In OpenGL 1.x, the default way that colors and textures are combined is like so:

C' = Ccolor * Ctexel

This is done by glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_TEXTURE_ENV_MODE). Again, this is the default mode, so you shouldn't need to do this.

If we specify a color of { 1, 1, 1, 1} (pure white) on side of the rectangle in the vertex data, and then { 1, 1, , 0} (transparent) on the other side, the colors will be smoothly interpolated by OpenGL as you've already noticed. Generalize that to alpha, which isn't "visible" per se, but can be used in graphics techniques such as this. So in effect, you'll end up with the alpha value of the color varying from 0..1 smoothly as desired. Now we have to use this value. Simply having the alpha value varying across the rectangle doesn't do anything, because alpha itself is not visible like red, green, or blue. So we have to somehow multiply this alpha value with each color.

Enter blending. Blending in the GL 1.x fixed function does just that. It takes the incoming pixel (call the source) and blends it with the destination pixel (i.e. what is already in the framebuffer) by some function, so you can look at mathematically as: Cblended = blend(Csrc, Cdest)

First, you must enable it: glEnable(GL_BLEND).

Then, you must give it the function glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) for the first rectangle.

The first parameter to glBlendFunc() is what gets multiplied with the source color. We're saying the alpha value of the color. The second parameter is what is multiplied with whatever is already in the framebuffer. We're saying 1.0-alpha. This effectively means we're doing a lerp().

When drawing the second rectangle, you must give it the function glBlendFunc(GL_SRC_ALPHA, GL_ONE). This preserves the previously calculated value (GL_ONE meaning multiply old value by 1.0) and then blends the incoming (new rectangle) value by its alpha, performing the lerp().

Let's go other all of this, mostly so you can appreciate shaders.

  1. Enable blending, possibly set up texture environment.
  2. Set up the blending function
  3. Draw rectangle 1 with left vertices having alpha of 1, right side alpha of 0
  4. Switch blend function to GL_SRC_ALPHA / GL_ONE (thanks Trevor)
  5. Draw rectangle 2 with right vertices having alpha of 1, left side alpha of 0
  6. Turn off blending and do other stuff.

This works because:

  1. When you specify vertex colors, the alpha value is smoothly interpolated across the rectangle.
  2. Using blending, we can multiply the color by that alpha value
  3. Since we swap 1.0/0.0 alpha values depending on which rectangle we're drawing, for each pixel where alpha is some value K for rectangle 1, it will be 1-K for rectangle 2.
\$\endgroup\$
  • \$\begingroup\$ Doesn't step 4 result in rectangle 2 completely overwriting and obscuring rectangle 1, since it uses a '1' alpha on both sides? With these settings, it seems like you would need to render rectangle 2 before rectangle 1, to make them composite correctly. \$\endgroup\$ – Trevor Powell Apr 10 '14 at 21:53
  • \$\begingroup\$ Hah, yeah, sorry, that was a typo. Hopefully the rest of the answer revealed that. \$\endgroup\$ – PatrickB Apr 11 '14 at 22:12
  • \$\begingroup\$ Eek. In that case, you're going to wind up with the middle of the rectangle having 25% transparency, aren't you? At the middle, the first rectangle will blend to 50% between the background and the first texture, and at that same spot the second rectangle will blend to 50% between that 50% blended value and the second texture, which still leaves 25% of the background showing through, right? \$\endgroup\$ – Trevor Powell Apr 11 '14 at 22:31
  • \$\begingroup\$ "25% transparency" is kind of a weird word for it. If you have destination alpha at all, it isn't being utilized in any computation -- hence SRC_ALPHA, ONE_MINUS_SRC_ALPHA. If you have black background, this will work perfectly, because what you're getting in the middle is this: black*0.5 + 0.5*C1 ==> 0.5*C1 -- then 0.5C1 + 0.5C2 = half of both in the middle as desired. If you don't believe me that this works, please try it out! :D \$\endgroup\$ – PatrickB Apr 13 '14 at 2:19
  • 1
    \$\begingroup\$ No. Your math is wrong -- the second blend operation isn't "0.5C1 + 0.5C2". 0.5C1 is the color written into the framebuffer in your first blend, so the second blend operation isn't blending 50% between C1 and C2, it's blending 50% between 0.5*C1 and 1.0*C2. Or in other words, 0.25*C1, 0.25*black, and 0.5*C2. \$\endgroup\$ – Trevor Powell Apr 13 '14 at 4:30
2
\$\begingroup\$

For the sake of easiness, use GLSL shaders. This is mandatory for modern openGL, anyway.

Now, what you have to do is to unwrap your object to each texture. So you must associate to each vertex two texture coordinates in your vertex shader. Plus, you may wish to add a [0-1] value X to each vertex that tells how much of the first and second texture you wish to use.

In the fragment shader, you can pick the color of both color, and alpha-blend them manually using X. Something like this (not tested) :

uniform gsampler2D texture1;
uniform gsampler2D texture2;
varying vec2 textureCoord1;
varying vec2 textureCoord2;
varying float thatMuchOfTexture2; //value X
void main(void)
{
    vec4 c1 = texture(texture1, textureCoord1.st);
    vec4 c2 = texture(texture2, textureCoord2.st);
    gl_FragColor = c1*(1-thatMuchOfTexture2) + c2*thatMuchOfTexture2
}
\$\endgroup\$
1
\$\begingroup\$

I do not believe that this is directly possible using OpenGL 1.x. If you're using OpenGL 1.3, you can use Texture Combiners to merge two textures together in various ways, and you can even specify different sets of texture coordinates for the two textures, but I do not believe it's possible to set different opacities for the different textures per vertex within a single draw.

Because of this, when coding to OpenGL 1.3, people generally use other, 'hacky' ways to achieve a similar effect.

The usual way to do it would be to draw the quad twice -- once using the first texture at full opacity, and then drawing the quad again using the second texture, but with vertex alpha set to fade out the second texture where you want the first one to show through.

Now, if you're using 3D, then this gets tricky, as the two draws will exactly overlap each other in the depth buffer. (If not, then ignore the rest of this answer)

In that tricky 3D situation, you probably want to have the first quad draw into a stencil buffer, to track which pixels it drew into on the screen, and then have the second draw disable its depth testing, and use that stencil buffer to clip which pixels it's allowed to draw into (so it doesn't draw on top of closer objects, with its depth testing disabled).

Alternately, you can use glPolygonOffset() to force the second quad draw to pretend to be slightly closer to the camera, so that it doesn't fight with the first one. This fix is simpler and easier, but is less predictable, as the parameters to glPolygonOffset() do not have standardised meanings, and may vary in their effect from platform to platform and from GPU to GPU.

\$\endgroup\$
  • \$\begingroup\$ I don't think the 3D situation is as tricky as you say. If you draw exactly the same vertices with exactly the same transformations and less-or-equal depth testing, you should draw exactly the same pixels on the second pass. \$\endgroup\$ – GuyRT Apr 11 '14 at 17:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.