Weighted Blended Order-Independent Transparency GLSL
Weighted Blended Order-Independent Transparency (WBOIT) approximates the transmission and blending of overlapping transparent surfaces in a single geometry pass without sorting polygons. By utilizing Multiple Render Targets (MRT) and additive blending hardware, WBOIT separates the rendering equation into color accumulation and visibility transmission. A depth-dependent weighting function dynamically scales each surface's contribution based on camera distance and opacity, allowing complex, intersecting transparent geometry to be composited realistically over an opaque background in a subsequent fullscreen resolve step.
The Challenge of Real-Time Transmission
Standard alpha blending requires rendering primitives in strict back-to-front order using the formula:
\[C = C_{src} \cdot \alpha_{src} + C_{dst} \cdot (1 - \alpha_{src})\]
Sorting geometry on the CPU per frame is computationally expensive, fails with intersecting geometry, and cannot handle non-convex cyclic overlaps. WBOIT bypasses sorting by making the blending operations commutative, reformulating the composite so that all transparent fragments can be rasterized in arbitrary order.
The Accumulation Pass Architecture
During the transparent geometry pass, depth writing is disabled while depth testing remains enabled to discard surfaces hidden behind opaque objects. Two render targets are bound simultaneously using OpenGL or Vulkan MRT:
- Accumulation Buffer (
accumTexture, typically RGBA16F or RGBA32F): Stores the weighted sum of pre-multiplied transparent colors in RGB channels and the sum of weights in the Alpha channel. - Revealage Buffer (
revealTexture, typically R8 or R16F): Tracks total light transmittance (how much the background remains visible).
Hardware blend states are configured independently for each target:
- Accumulation Target: Configured with additive
blending (
GL_ONE,GL_ONE). - Revealage Target: Configured to multiply incoming
transmittance values (
GL_ZERO,GL_ONE_MINUS_SRC_COLOR).
Depth Weighting Function
The core heuristic of WBOIT is the weighting function \(w(z, \alpha)\), which assigns higher significance to fragments closer to the camera to approximate occlusion. A standard formulation proposed by Morgan McGuire and Louis Bavoil is:
float computeWeight(float z, float alpha) {
// Clamp to avoid floating-point extremes
return clamp(pow(min(1.0, alpha * 10.0) + 0.01, 3.0) * 1e8 *
pow(1.0 - z * 0.9, 3.0), 1e-2, 3e3);
}A common linear view-space variant simplifies this calculation:
float computeWeight(float linearZ, float alpha) {
return clamp(0.03 / (1e-5 + pow(linearZ / 200.0, 4.0)), 1e-2, 3e3) * alpha;
}Fragment Shader Implementation
In the geometry pass, the fragment shader computes the surface color, alpha, and corresponding weight before outputting to the two targets.
#version 330 core
layout (location = 0) out vec4 accumOutput;
layout (location = 1) out float revealOutput;
in vec3 vNormal;
in vec3 vViewPosition;
uniform vec4 uColor; // Base transparent material color (RGBA)
void main() {
vec4 color = uColor;
// Linearize view depth or calculate normalized distance
float linearDepth = abs(vViewPosition.z);
// Compute heuristic weight
float weight = computeWeight(linearDepth, color.a);
// Accumulation: premultiplied weighted color and weighted alpha
accumOutput = vec4(color.rgb * color.a, color.a) * weight;
// Revealage: tracks how much background light transmits through (1 - alpha)
revealOutput = color.a;
}Because of the GL_ZERO, GL_ONE_MINUS_SRC_COLOR blend
state, the revealage target updates as:
\[\text{Revealage}_{new} = \text{Revealage}_{old} \cdot (1 - \alpha)\]
Initialized to 1.0, this target continuously multiplies
transmission factors regardless of draw order.
The Composite Pass
Once all transparent objects have rendered into the accumulation and revealage buffers, a fullscreen quad resolves the final image over the opaque background:
#version 330 core
out vec4 fragColor;
in vec2 uv;
uniform sampler2D opaqueTexture;
uniform sampler2D accumTexture;
uniform sampler2D revealTexture;
void main() {
float revealage = texture(revealTexture, uv).r;
// Skip composition if no transparent surfaces were rasterized
if (revealage >= 1.0) {
discard;
}
vec4 accum = texture(accumTexture, uv);
// Prevent divide-by-zero errors
vec3 averageColor = accum.rgb / max(accum.a, 0.00001);
vec3 opaqueColor = texture(opaqueTexture, uv).rgb;
// Blend accumulated transparent color with transmitted background color
fragColor = vec4(averageColor * (1.0 - revealage) + opaqueColor * revealage, 1.0);
}By computing transmittance as a product and average color via weighted normalization, WBOIT produces a plausible transmission approximation that entirely removes geometry sorting overhead.