Primary implementation surface
These routes are generated from canonical source. Their exact status remains separate from implementation availability.
Design workload-selected bloom in Three.js r185 WebGPU/TSL, choosing physical HDR scene bloom versus selective MRT contribution, with exact BloomNode pyramid costs, transparent blending rules, PSF limits, and mobile/tile-GPU gates.
These routes are generated from canonical source. Their exact status remains separate from implementation availability.
This skill contributes to the following cross-skill owner graphs.
Bloom is a camera response to bright HDR signal: energy above a threshold scatters into a wide kernel. The signal chain is the contract — bloom samples scene-linear HDR before tone mapping:
$$L_{bloom} = K_{blur} * \max(L_{scene} - T, 0), \qquad L_{final} = \operatorname{tonemap}(L_{scene} + s\,L_{bloom})$$The blur is a mip pyramid: progressive downsample + upsample accumulation approximates a large Gaussian at a fraction of the cost, with radius scaling per mip level. Selective bloom routes emissive contribution through an MRT channel so only authored emitters glow:
$$L_{bloom} = K * (E_{mrt}), \qquad E = \text{emissiveNode output, scene-relative units}$$The skill's core rule: an object's brightness hierarchy is authored in scene units (sun ≫ plasma ≫ LED); bloom reveals that hierarchy, it must never be the object.
Every image identifies what it proves. Page screenshots demonstrate the published presentation only; generated inputs demonstrate asset channels only; canonical acceptance still requires render-target readback and a schema-v2 bundle.
1 published image
concept proxy preview
The complete SKILL.md as loaded by agents — verbatim, rendered.
Bloom approximates the broad tail of an imaging point-spread function (PSF). The physically motivated input is scene-linear HDR radiance. A bright-pass and selective emissive membership are real-time/art-direction approximations, not optical laws. The base scene must remain readable with bloom disabled.
Use $threejs-choose-skills for renderer/budget preflight,
$threejs-exposure-color-grading for exposure and output ownership, and
$threejs-image-pipeline when the scene pass/MRT is shared.
Release numbers and list ordering are identifiers, not tuning claims.
| Visual contract | Input | Decision threshold |
|---|---|---|
| Optical glare: bright direct lighting, reflections, transmission, sky/sun, and emission should respond to one exposure-relative luminance rule | bloom(sceneColor) |
Default. It needs no bloom-membership MRT and includes final transparent/reflected radiance. |
| Art-directed selective glow: named surfaces must bloom differently from equally bright radiance | bloom(emissiveContribution) from the existing scene-pass MRT |
Use only when no single bright-pass threshold can meet the contributor false-positive/false-negative contract [Gated] and the measured MRT delta fits. |
| Selective boost plus optical highlights | bloom(sceneColor + authoredBoost) |
Use only when both mechanisms are required and the extra attachment is charged once [Gated]. |
| Base form disappears without bloom | neither | Repair geometry/material/lighting first. Bloom is deferred. |
Selective MRT is not automatically higher quality. It omits bright reflected and transmitted radiance unless those signals are authored into the contribution target. Full-scene bloom is usually both more physical and cheaper on bandwidth-limited devices.
When the route declares a physics-to-render boundary, consume its immutable
PhysicsPresentationCandidate -> CameraViewPublication ->
ViewPreparationPublication ->
PhysicsPresentationSnapshot chain and bind LightingTransportSnapshot
through a provider-wide PresentedStatePair (entityId: typed-absence) in the
Candidate whose binding ID is referenced by the Snapshot, from the
physics domain and interaction contract.
Validate the exact central presentation and lighting channel descriptors; do
not redeclare a bloom-local lighting record. Bloom input is scene-linear
radiance in its declared render-local basis. Match the pair's context/provider/
signal IDs, descriptor/state/resource generations, PresentationStateHandle,
each state's requested presentation instant, mapped source instant, clock-map
revision/error, and the lighting bundle sampleInstant; validate channel
actualPhysicsTime, filter/age, maximum staleness, validity, and error.
Irradiance transport values
must pass through the lighting/BRDF contract before bloom. Selective emission
cannot use an unrelated arbitrary intensity scale.
Basis, quantity, SI unit, bundle sampleInstant, channel actualPhysicsTime,
state/resource version, validity, and error are checked per canonical channel.
Canonical lighting-provider channels
remain SI-valued. A normalized RGB bloom input is a separately named
render-local signal produced by a versioned SI-to-render conversion with
reference scale, provenance, and error; it is not a normalized canonical
lighting channel. One route-wide label
cannot make incidentRadiance, surfaceIrradiance,
directSolarIrradiance, skyIrradiance, transmittance, or material emission
dimensionally compatible; applied attenuation uses attenuationFactorIds.
A nonphysical route leaves the router physics fields not used and declares
only its render-local color contract.
Declare threshold domain explicitly:
Do not tune one numeric threshold across incompatible lighting bases. A change
to basis/calibration, working primaries, quantity convention, or exposure-key
policy emits the relevant ViewPreparationPublication.reactivePublications
entry and recomputes/reseeds
threshold state before bloom. Shadow commits and discontinuous foam,
emissive, or optical changes produce a versioned radiance-reactive mask for any
upstream temporal history. BloomNode itself has no history to reset, but it
must consume the source after required rejection/reseed and publish the same
epoch in diagnostics.
ViewPreparationPublication.resetDependencies is an immutable plan; append the performed threshold
conversion, upstream history action, graph rebuild, and submission to
FrameExecutionRecord. Device loss appends a FrameExecutionRecord with
overallStatus: device-lost, affected target execution statuses
device-lost, cancelled dependent actions, and lost-generation entries in
leaseDispositionById; it invalidates bloom resources and timing evidence without
mutating the sealed snapshot. Rebuild under the new backend/resource generation.
Verified against installed three@0.185.1 [Measured]:
| Fact | Consequence |
|---|---|
bloom(node, strength, radius, threshold) and BloomNode.setResolutionScale() exist. |
Use the built-in baseline; do not invent stale pass wrappers. |
The default internal linear scale is 0.5 [Derived]. |
It is a source default, not a device-quality decision. |
High-pass weight is smoothstep(threshold, threshold + smoothWidth, luminance(input.rgb)) [Derived]. |
Thresholding occurs in the input's scene-linear domain. smoothWidth is a soft knee, not blur radius. |
The node uses 5 mip levels with separable kernel radii [6, 10, 14, 18, 22] [Derived]. |
It is a multi-Gaussian approximation, not an arbitrary physical PSF. |
radius mixes fixed cross-mip weights; it does not change kernel support [Derived]. |
Call it a mip-spread control, not a physical radius or sigma. |
| Internal bright/blur targets are RGBA16F [Derived]. | BloomNode itself cannot become an R11/RG compact path through a quality flag. |
Blur stages write alpha 1 and the composite carries nonzero alpha [Derived]. |
Add bloom RGB while preserving scene alpha when canvas/compositor alpha matters. |
Only MRT output named output uses material blending by default; other outputs default to no blending [Derived]. |
Transparent emissive contributions overwrite unless the MRT output receives an explicit blend mode. |
r185 MRTNode.merge() assigns merged modes to blendings, not the operative blendModes map [Derived]. |
A material-level mrtNode can drop the scene emissive blend mode; do not use that combination in the canonical transparent path. |
PassNode.compileAsync() compiles the scene pass, not BloomNode's fullscreen materials [Derived]. |
Warm and time the complete RenderPipeline; scene compilation alone is insufficient. |
Initialize and hard-gate WebGPU:
await renderer.init();
if ( renderer.backend.isWebGPUBackend !== true ) {
throw new Error( 'threejs-bloom requires WebGPU.' );
}
There is no non-WebGPU implementation in this skill.
Optical/full-scene path:
one HDR scene pass -> BloomNode(scene color) -> HDR add -> one output transform
Selective path:
one HDR scene pass with output + emissive MRT
-> BloomNode(emissive contribution)
-> add to scene color
-> one output transform
Both paths use one scene traversal. The selective path adds an HDR attachment; it does not add a selection scene render.
Treat the HDR add as vec4(scene.rgb + bloom.rgb, scene.a), not an unchecked
vec4 sum. An opaque swapchain can hide alpha corruption that later appears in
DOM/video compositing.
Read references/hdr-bloom-system.md for verified code, transparent MRT blending, exact pass/fetch/memory equations, adaptation rules, and validation.
For selective bloom, inspect the emissive texture before tuning bloom. r185
MRT outputs other than output do not inherit material blending by default.
Configure the emissive MRT output with BlendMode(MaterialBlending). Author
transparent contribution through the regular emissiveNode, already weighted
for the material's premultiplied/straight-alpha policy. Avoid material-level
mrtNode overrides in r185 because the stock MRT merge loses the configured
non-output blend map.
Use additive contribution for additive optical energy; use alpha compositing only when the visible surface uses the same ordered model. Charge transparent cost by measured covered fragments/overdraw, not object count. A screen-filling layer can cost more than thousands of tiny contributors.
If visible emission and bloom contribution must diverge per transparent material, use a separately measured contribution pass or a source-verified custom MRT fix. This forfeits the stock one-scene-pass guarantee and must be budgeted explicitly.
| Tier | Authored start | Gate |
|---|---|---|
| Full | scale 0.5, strength 0.35-0.75, mip-spread 0.25-0.45, soft knee 0.05-0.12 [Authored] |
HDR signal, PSF footprint, and full marginal time pass at maximum target DPR. |
| Balanced | scale 0.33-0.5, narrower contribution/threshold range [Authored] |
Fixed-view halo error is acceptable and measured bloom delta fits. |
| Constrained WebGPU | scale 0.25-0.33, reduced transparent screen coverage, optional bloom disable [Authored] |
Tile bandwidth, thermal run, and minimum-mip dimension pass; otherwise disable bloom. |
Threshold has no portable numeric default. Derive it from the stabilized exposure and a false-color pre-tone luminance view. For selective input, threshold still controls energy within membership; it is not the membership mechanism.
The deepest r185 level is valid only when
floor(scale * min(width, height)) >= 16 [Derived/Gated]. Below that, the
fixed 5-level chain reaches a zero-sized target. Reduce mip count in a custom
node or disable bloom for that surface size; do not rely on undefined tiny-
target behavior.
deltaBloomFull = time(scene + full-scene bloom) - time(scene) [Measured]
deltaMRT = time(scene with contribution MRT) - time(scene) [Measured]
deltaBloomSelective = time(scene + contribution MRT + bloom) - time(scene)
[Measured]
route valid iff charged delta <= declaredMarginalBloomBudget [Gated]
If another effect already owns the same emissive attachment, charge its
measured attachment delta once in the unique-work ledger. Use the complete
paired deltaBloomSelective for acceptance; do not add independently sampled
MRT and BloomNode percentiles or assume their interaction is zero.
Let A = scale^2 * width * height bloom pixels [Derived]. The fixed r185
node performs 12 fullscreen draws [Derived]: one high-pass, two blur
directions across 5 levels, and one composite. Its allocated internal target
storage approaches 29.3125 * A bytes before integer-size floors [Derived]
and its shader work is about
42.3047 * A texture samples plus 4.6641 * A pixel writes [Derived].
At 1920x1080, scale 0.5, internal targets occupy 14.49 MiB; a selective
RGBA16F emissive attachment adds 15.82 MiB, for 30.31 MiB incremental live
image storage before depth, output, MSAA, alignment, and tile scratch
[Derived].
Do not seed the search with adapter-class millisecond tables. They obscure DPR, HDR sparsity, MRT stores, browser state, and thermal behavior. Declare one product marginal budget [Gated], measure the complete charged delta with matched warmed A/B traces [Measured], and reject every tier that misses it.
For a pixel-bound miss, estimate
nextScale = currentScale * sqrt(budgetMs / measuredMs) [Derived], clamp
to the tier and minimum-mip gates, then remeasure. The square-root estimate is
invalid when MRT stores, fixed pass overhead, or transparent overdraw dominate.
8 * width * height resolved bytes [Derived] and may add multisample tile
storage before resolve.| Signature | Cause / decision |
|---|---|
| Gray, clipped highlight cores | HDR was clamped or output-converted before high-pass. |
| Uniform milky veil | Threshold/knee admits broad midtones, or strength compensates for a bad source signal. |
| Bright reflections stay razor-sharp while emitters glow | Selective MRT omitted optical radiance; use scene-color or hybrid input. |
| Transparent contributors disappear or pop by draw order | Emissive MRT blending/alpha is wrong. |
| Halo changes with DPR or resize | Fixed mip/kernel footprint changed in output-pixel terms; validate endpoints or use a custom PSF. |
| Blocky staircase around small highlights | Bloom scale is too low for the feature; raise scale or reject bloom for that tier. |
| Tiny hot pixels create enormous halos | Unbounded HDR fireflies; repair sampling/exposure or robustly cap the source before bloom. |
| Scene form vanishes when bloom is disabled | Bloom is carrying silhouette/lighting; repair the base scene. |
| Bloom-off timing is unchanged | Bloom remains reachable in the active graph; replace output node and set needsUpdate. |
| Transparent canvas gains an opaque rectangle or wrong edge alpha | Bloom alpha was added to scene alpha; preserve base alpha explicitly. |
This skill owns source-signal selection, BloomNode controls, selective MRT contribution, transparent blending, PSF diagnostics, and bloom marginal cost. Exposure/color grading owns metering, adaptation, tone mapping, and display conversion. Image-pipeline owns shared scene passes and global ordering.
Preserved concept proxies and generated-asset previews. They are excluded from primary completion counts and link to the canonical lab through the schema-v2 registry.