I'm not sure it fits your deferred renderer but you might consider weighted, blended order-independent transparency. There's an older version w/o colored transmisson (web) and a newer version that supports colored transmission (web) and a lot of other stuff. It is quite fast because it only uses one opaque, one transparency, and one composition pass and it works with OpenGL 3.2+.
I implemented the first version and it works quite well, depending on your scene and a properly tuned weighting function, but has problems with high alpha-values. I didn't get good results with the weighting functions from the papers, but only after using linear, normalized eye-space z-values.
Note that when using OpenGL < 4.0 you can not specify a blending function per buffer (glBlendFunci), so you need to work around that (see the first paper).
- Set up frame buffer with these attachements:
- 0: RGBA8, opaque
- 1: RGBA16F, accumulation
- 2: R16F, revealage
- Clear attachment #0 to your screen clear color (r,g,b,1), #1 to (0,0,0,1) and #2 to 0.
Render opaque geometry to attachment #0 and depth buffer.
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
Render transparent geometry to attachment #1 and #2. Turn off depth buffer writes, but leave depth testing enabled.
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
The fragment shader part writing the accumulation and revealage targets looks like this:
uniform mat4 projectionMatrix;
layout (location = 1) out vec4 accum;
layout (location = 2) out float revealage;
/// @param color Regular RGB reflective color of fragment, not pre-multiplied
/// @param alpha Alpha value of fragment
/// param wsZ Window-space-z value == gl_FragCoord.z
void writePixel(vec3 color, float alpha, float wsZ) {
float ndcZ = 2.0 * wsZ - 1.0;
// linearize depth for proper depth weighting
//See: https://stackoverflow.com/questions/7777913/how-to-render-depth-linearly-in-modern-opengl-with-gl-fragcoord-z-in-fragment-sh
//or: https://stackoverflow.com/questions/11277501/how-to-recover-view-space-position-given-view-space-depth-value-and-ndc-xy
float linearZ = (projectionMatrix[2][2] + 1.0) * wsZ / (projectionMatrix[2][2] + ndcZ);
float tmp = (1.0 - linearZ) * alpha;
//float tmp = (1.0 - wsZ * 0.99) * alpha * 10.0; // <-- original weighting function from paper #2
float w = clamp(tmp * tmp * tmp * tmp * tmp * tmp, 0.0001, 1000.0);
accum = vec4(color * alpha* w, alpha);
revealage = alpha * w;
}
Bind attachment textures #1 and #2 and composite them to attachment #0 by drawing a quad with a composition shader.
glEnable(GL_BLEND);
glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
The fragment shader for composition looks like this:
uniform sampler2DMS accumTexture;
uniform sampler2DMS revealageTexture;
in vec2 texcoordVar;
out vec4 fragmentColor;
void main() {
ivec2 bufferCoords = ivec2(gl_FragCoord.xy);
vec4 accum = texelFetch(accumTexture, bufferCoords, 0);
float revealage = accum.a;
// save the blending and color texture fetch cost
/*if (revealage == 1.0) {
discard;
}*/
accum.a = texelFetch(revealageTexture, bufferCoords, 0).r;
// suppress underflow
if (isinf(accum.a)) {
accum.a = max(max(accum.r, accum.g), accum.b);
}
// suppress overflow
if (any(isinf(accum.rgb))) {
accum = vec4(isinf(accum.a) ? 1.0 : accum.a);
}
vec3 averageColor = accum.rgb / max(accum.a, 1e-4);
// dst' = (accum.rgb / accum.a) * (1 - revealage) + dst * revealage
fragmentColor = vec4(averageColor, revealage);
}