5
\$\begingroup\$

I want to draw some very-semi-transparent objects on top of my scene and have them blend nicely with the other pixels in the scene, but not blend with the background; if I draw a semi-transparent object over the (black) background, I don't want it tainted and drawn dark.

enter image description here

The top scene is what I get - the semi-transparent orange triangle on top is blended with the black background. The bottom scene is what I want - the semi-transparent orange triangle is blended only with the blue triangle, but not with the black background.

How can this best be achieved? Is there a simple blend func that does this? I would like to avoid deferred rendering and such if I possibly can.

Is there some way that gl.blendFunc and perhaps gl.blendFuncSeparate can be used to achieve this?

\$\endgroup\$
  • 1
    \$\begingroup\$ Would a two-pass rendering \$\endgroup\$ – sam hocevar Jul 14 '13 at 19:36
3
\$\begingroup\$

To build upon Sean's answer, you don't need to make a separate FBO in order to render the effect you're looking for. Unfortunately, you cannot do it in a single pass because you're asking for two different blending operations for the pixels depending on whether or not your orange triangle is overlapping the light blue one. Fortunately, this is an excellent example of a place where the stencil buffer can be used.

In order to generate the effect that you want you need to do the following:

  1. Clear the stencil and color bits for your framebuffer.
  2. Render the blue triangle into both the color buffer and the stencil buffer.
  3. Render the orange triangle once with regular alpha blending testing whether or not the stencil value is non-zero
  4. Render the orange triangle again without alpha blending (painter's algorithm) wherever the stencil value is zero.

You will need to make sure that your OpenGL context has a stencil buffer, which you can usually do during creation of the context. I believe that WebGL asks for it explicitly.

Here is some (untested) pseudocode as well:

// Enable both color and stencil buffers
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_STENCIL_TEST);
glStencilMask(0xFF);

// Tell OpenGL to never pass the stencil test, and set the reference value to one.
glStencilFunc(GL_NEVER, 1, 0xFF);

// Tell OpenGL to replace the value in the stencil buffer with the reference value
// whenever the stencil test fails (which is always)
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);

// Clear your framebuffer
glClearColor(0, 0, 0, 0);
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

DrawBackgroundTriangle();

// Set the stencil function to only draw against non-zero values and to not modify
// the stencil buffer on failure
glStencilFunc(GL_NOTEQUAL, 0, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

// Enable alpha blending as usual
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

DrawForegroundTriangle();

// Set the stencil buffer to only draw against zero values and to not modify the
// stencil buffer on failure. We do not need to call glStencilOp again because
// the arguments will be the same.
glStencilFunc(GL_EQUAL, 0, 0xFF);

// Disable blending
glDisable(GL_BLEND);

DrawForegroundTriangle();
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I've been waiting for you to answer so I can accept :) You mentioned it in another comment and it lit the lightbulb and I got it working very easily. \$\endgroup\$ – Will Jul 16 '13 at 9:04
3
\$\begingroup\$

Turning Sam's comment into an answer, you need to do all your rending in two passes.

For your first pass, draw only the foreground objects into an off-screen buffer/FBO. If you're using multiplicative blending you might get your desired effect with a white background; with additive blending, you may want a black background. Remember to clear to and update the alpha channel appropriately; you probably want to clear it to 0.

You can then do a second pass to render your background and the completed foreground scene. The foreground buffer should have alpha of 0 wherever there was no foreground object, so you can do a simple blend (or even just an alpha test) when rendering the foreground buffer /texture over the background.

In GL, the psueodocode would be something like (I don't recall the names or parameters of the GL functions; correct as necessary):

setup:
  fg_buffer = glGenTexture()
  glBindTexture(fg_buffer)
  glTexImage2D(size_and_format_crud)
  glBindTexture(0)

  fg_fbo = glGenFBO()
  glBindFrameBuffer(fg_fbo)
  glAttachColorBuffer(fg_texture)
  glBindFrameBuffer(0)


render_foreground:
  glBindFrameBuffer(fg_fbo)
  glClearColor(0, 0, 0, 0)
  glClear()

  setup_blend_and_draw()

  glBindFrameBuffer(0)

render_scene:
  render_background()

  render_foreground()     
  glBindTexture(fg_buffer)
  glDraw(full_screen_quad)
  glBindTexture(0)
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ You don't need to use a separate FBO, you can just use the stencil buffer. \$\endgroup\$ – Mokosha Jul 14 '13 at 21:24
  • 1
    \$\begingroup\$ @Mokosha: Interesting, clever idea. You should make an answer for it. \$\endgroup\$ – Sean Middleditch Jul 14 '13 at 21:53

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.