The gsplat fragment shader premultiplies intensity into RGB and (only
in this mode) emits a clamped coverage alpha, so the source factor is
One, not SrcAlpha (which would multiply the contribution by
coverage a second time). This replaces the generic normal entry for
gsplats, whose alpha-always-1 output made normal degenerate
("opaque dimmed by opacity") — see GSPLAT_DEPTH_SORTING_SPEC.md §3.
Deliberate choices (each load-bearing):
CustomBlending with SYMMETRIC alpha channel (the material's
blendEquationAlpha/blendSrcAlpha/blendDstAlpha stay null):
separate alpha-channel blend state is exactly what trips a
gl.getError() flag under WebGPURenderer's WebGL2 bridge (see
materials/gsplat/material-tsl.ts). Alpha then composites as
a_src + (1 - a_src)·a_dst — correct coverage accumulation.
NEVER set material.premultipliedAlpha instead: on the TSL path
NodeMaterial.setup() auto-injects an output RGB×alpha transform
when that flag is set, double-premultiplying a shader that already
premultiplies (and desyncing GLSL vs TSL).
depthWrite: false unconditionally (unlike the generic normal
entry's opacity >= 0.99 flip): a coverage-alpha splat fragment
with alpha as low as ~1e-4 survives the shader's discards, and
letting it write depth punches occlusion halos across everything
behind the splat's footprint. Sorted transparency never
depth-writes. Trade-off: a gsplat normal layer does not occlude
additive layers behind it.
No opacity parameter — the state is opacity-independent by
construction (depthWrite never flips).
GSplat-specific
normal-mode blending state: premultiplied alpha-over.The gsplat fragment shader premultiplies intensity into RGB and (only in this mode) emits a clamped coverage alpha, so the source factor is
One, notSrcAlpha(which would multiply the contribution by coverage a second time). This replaces the genericnormalentry for gsplats, whose alpha-always-1 output madenormaldegenerate ("opaque dimmed by opacity") — see GSPLAT_DEPTH_SORTING_SPEC.md §3.Deliberate choices (each load-bearing):
CustomBlendingwith SYMMETRIC alpha channel (the material'sblendEquationAlpha/blendSrcAlpha/blendDstAlphastay null): separate alpha-channel blend state is exactly what trips agl.getError()flag under WebGPURenderer's WebGL2 bridge (see materials/gsplat/material-tsl.ts). Alpha then composites asa_src + (1 - a_src)·a_dst— correct coverage accumulation.material.premultipliedAlphainstead: on the TSL pathNodeMaterial.setup()auto-injects an output RGB×alpha transform when that flag is set, double-premultiplying a shader that already premultiplies (and desyncing GLSL vs TSL).depthWrite: falseunconditionally (unlike the genericnormalentry'sopacity >= 0.99flip): a coverage-alpha splat fragment with alpha as low as ~1e-4 survives the shader's discards, and letting it write depth punches occlusion halos across everything behind the splat's footprint. Sorted transparency never depth-writes. Trade-off: a gsplatnormallayer does not occlude additive layers behind it.No
opacityparameter — the state is opacity-independent by construction (depthWrite never flips).