verve.gl post-processing
Guide: Image quality & post-processing · WebGL · Advanced WebGL.
The post-processing API is part of the low-level gl core (src/core/gl/command.zig). It is driven from island code via the encoder — beginPostProcess / endPostProcess and the Encoder.run* passes — not through the declarative ctx.glScene builder. All shaders ship as WGSL + GLSL twins (WebGPU / WebGL2).
PostProcess struct
Configuration for beginPostProcess / endPostProcess.
pub const PostProcess = struct {
bloom: ?Bloom = .{}, // null → skip bright-pass + blur
fxaa: bool = true, // FXAA on the final blit
webgpu: bool = false, // emit WGSL modules vs GLSL
tonemap: ToneMap = .aces, // composite tone-mapping operator
vignette: ?Vignette = null, // null → off
ao_tex: u32 = 0, // SSAO blurred-AO handle (composite tex2); 0 → 1×1 white (AO=1)
scene_src: u32 = 0, // scene-source override; 0 → h_scene_hdr (240)
};| Field | Default | Notes |
|---|---|---|
bloom | .{} | null skips the bright-pass + blur chain |
fxaa | true | FXAA anti-aliasing on the final canvas blit |
webgpu | false | true emits the WGSL post modules; default emits GLSL |
tonemap | .aces | Composite tone-mapping operator (see ToneMap) |
vignette | null | Optional corner darkening after tone-mapping |
ao_tex | 0 | SSAO blurred-AO render target bound at composite tex2; 0 → 1×1 white dummy (no-op) |
scene_src | 0 | Render target the bright-pass + composite read as the scene; 0 → h_scene_hdr. SSR/DOF/OIT set it to their resolved output |
ToneMap enum
pub const ToneMap = enum(u8) {
linear = 0, // pow(clamp(hdr,0,1), 1/2.2) — clips highlights
reinhard = 1, // hdr/(1+hdr), gamma 2.2
reinhard_ext = 2, // hdr*(1+hdr/W²)/(1+hdr), W=4, gamma 2.2
aces = 3, // Hill ACES fit, NO gamma (default; pre-slice-2 output)
agx = 4, // minimal AgX (Sobotka/Filament); basis for three.js AgXToneMapping
uncharted2 = 5, // Hable filmic, bias 2.0, W=11.2, gamma 2.2
};The integer value is the wire contract with the composite shader (op = i32(P.tonemap + 0.5) selects the branch).
Vignette struct
pub const Vignette = struct {
intensity: f32 = 0.0, // 0 = off, 1 = full effect
radius: f32 = 0.75, // distance from center where falloff begins (0.5–0.8)
};Applied after tone-mapping via smoothstep(radius, radius − 0.45, d).
Bloom struct
pub const Bloom = struct {
threshold: f32 = 1.0, // luminance above which pixels bloom
intensity: f32 = 0.6, // bloom blended into the composite
};Composite params packing
The composite stage packs into one 16-byte slot — no separate buffer:
p_comp = [intensity, tonemap, vig_intensity, vig_radius]Render-target & shader handles
Each effect reserves a contiguous handle block (h_ = render target, sh_ = shader). Handles 240–266 are reserved for the post path so they never clash with app handles.
| Ctx | Handle | ID | Role |
|---|---|---|---|
PostCtx | h_scene_hdr | 240 | Offscreen scene HDR target |
h_bloom_a / h_bloom_b | 241 / 242 | Bloom blur ping-pong | |
h_ldr | 243 | LDR composite output | |
sh_bright | 244 | Bright-pass shader | |
sh_blur | 245 | Separable Gaussian (shared by DOF) | |
sh_composite | 246 | Tone-map + vignette composite | |
sh_fxaa | 247 | FXAA | |
PrepassCtx | h_gbuffer | 248 | rgba16f depth + view-normal G-buffer (public consume handle) |
sh_prepass | 249 | variant_prepass shader | |
sh_gdebug | 250 | G-buffer debug-viz shader | |
SsaoCtx | h_ao_raw / h_ao_blur | 251 / 252 | AO accumulation / blurred AO (composite tex2) |
sh_ssao / sh_ssao_blur | 253 / 254 | SSAO pass / 4×4 box blur | |
SsrCtx | h_scene_ssr | 255 | Scene + reflections RT (drop-in scene_src) |
sh_ssr | 256 | SSR fullscreen shader | |
DofCtx | h_dof_a / h_dof_b | 257 / 258 | Blur ping-pong (H) / fully blurred (V) |
h_scene_dof | 259 | Sharp + blur CoC composite (drop-in scene_src) | |
sh_dof | 260 | CoC combine shader (vec4 params → 32B) | |
OitCtx | h_accum / h_reveal | 261 / 262 | Additive accum / multiplicative revealage |
h_scene_oit | 263 | Resolved scene + transparency (drop-in scene_src) | |
sh_oit | 264 | Transparent-geometry shader (WGSL MRT / GLSL accum-out) | |
sh_oit_reveal | 265 | GLSL revealage-out shader (WebGL2 only) | |
sh_oit_resolve | 266 | Fullscreen resolve (accum + reveal + opaque) |
Post Params layout (SSAO / SSR)
SSAO and SSR share one 144-byte Params block so the bridge's binding-size path is reused:
[0..4) = params (vec4) // SSAO: (radius, bias, intensity, _)
// SSR: (strength, max_distance, thickness, fresnel_power)
[4..20) = inv_proj (mat4, column-major)
[20..36) = proj (mat4, column-major)DOF needs no matrices — its combine params are a single vec4 (focus_distance, focal_range, max_blur, _) → 32 bytes.
Encoder passes
| Method | Effect |
|---|---|
beginPostProcess(PostProcess) | Redirect scene rendering into h_scene_hdr |
endPostProcess(...) | Bright-pass → blur → composite (tone-map + vignette) → FXAA → canvas |
Encoder.runSsao(...) | SSAO pass + 4×4 box blur → h_ao_blur |
Encoder.runSsr(...) | 32-step screen-space reflection pass → h_scene_ssr |
Encoder.runDof(...) | 2 blur passes + CoC combine → h_scene_dof |
Encoder.runOit(ctx, webgpu, w, h, draws) | Weighted-blended OIT; draws = per-object OitDraw list → h_scene_oit |
OIT wire tags & blend state
runOit emits backend-specific structure; the resolve and weight/blend math are identical, so both backends produce the same image.
| Constant | Value | Role |
|---|---|---|
variant_oit | 1 << 17 | Standalone WBOIT geometry shader (mvp + mv + color UBO; not a PBR add-on) |
begin_mrt_pass | 40 | WebGPU MRT pass open {accum, reveal, depth_src} |
draw_oit | 41 | Transparent draw {vbuf, ibuf, idx_off, idx_count, mvp_ptr, mv_ptr, color_ptr} |
state_blend_add | 1 << 4 | WebGL2 additive accum blend (ONE/ONE) |
state_blend_mult | 1 << 5 | WebGL2 revealage blend (ZERO/ONE_MINUS_SRC_COLOR) |
On WebGPU per-target blend is baked into the MRT pipeline, so the WebGL2 blend bits only steer the fallback (two single-target passes).
Backend divergence
| Backend | Transparent accum + reveal |
|---|---|
| WebGPU | One MRT pass — 2-target pipeline, per-target blend, depth-write off, opaque depth read-only |
| WebGL2 | Two single-target passes over the same geometry (accum ONE/ONE, reveal ZERO/ONE_MINUS_SRC_COLOR) |