Release/3.1.0 - #377
Merged
Merged
Conversation
Registers a postprocessing effect class as an r3f intrinsic and lets r3f's own reconciler handle args-driven reconstruction, live prop application, and disposal - no custom accessor scanning or fingerprinting. Also adds useLiveDefaults, the equivalent live-prop mechanism for effects that need real constructor args and can't use r3f's native reset-on-removal.
Passes are now derived from the r3f scene graph and only rebuilt when the resolved node list actually changes, not on every render. Fixes real GPU-resource bugs found along the way: composer-level prop changes (multisampling etc.) could dispose effects still in use by the new composer, discarded EffectPass wrappers leaked their own material and kept a stale change listener on the effect they wrapped, and a user's own EffectPass rendered as a child could be mistaken for one we generated.
Covers every effect whose postprocessing class constructs with zero arguments (Bloom, Noise, Vignette, FXAA, and ~20 others) - live props update the existing instance instead of reconstructing on every change, construction-only options move to explicit args. Also fixes a few bugs these effects had on top of the migration: opacity typing on nine of them, ChromaticAberration's radialModulation/modulationOffset incorrectly required, ColorDepth's bits not resetting on removal.
Outline, SelectiveBloom, ShockWave, GodRays, DepthOfField, SSAO, LUT, and N8AO all need real constructor args (scene/camera/etc.), so they stay hand-built with useMemo, but now apply live props through useLiveDefaults instead of reconstructing on every change. This is where nearly every real runtime bug from review surfaced: a first-apply bug where a still-correct value's setter fired anyway (Outline's multisampling disposing its render target before first use - the actual reason several of these didn't render at all), SSAO's color/fade/minRadiusScale/world* thresholds not resetting on removal, DepthOfField's depthTexture reconstructing instead of using the live setDepthTexture, and GodRays/N8AO not invalidating on live changes under frameloop="demand".
makeDisposeIdempotent guarded against depthPickingPass/copyPass getting disposed twice (once by the composer's own teardown, once by Autofocus's own cleanup) - unnecessary, since postprocessing/three dispose() is confirmed idempotent (event-fire or shallow property disposal, no internal state).
Removed redundant comments explaining ref behavior.
Passes are now derived from the r3f scene graph and only rebuilt when the resolved node list actually changes, not on every render. Fixes real GPU-resource bugs found along the way: composer-level prop changes (multisampling etc.) could dispose effects still in use by the new composer, discarded EffectPass wrappers leaked their own material and kept a stale change listener on the effect they wrapped, and a user's own EffectPass rendered as a child could be mistaken for one we generated.
Covers every effect whose postprocessing class constructs with zero arguments (Bloom, Noise, Vignette, FXAA, and ~20 others) - live props update the existing instance instead of reconstructing on every change, construction-only options move to explicit args. Also fixes a few bugs these effects had on top of the migration: opacity typing on nine of them, ChromaticAberration's radialModulation/modulationOffset incorrectly required, ColorDepth's bits not resetting on removal.
Outline, SelectiveBloom, ShockWave, GodRays, DepthOfField, SSAO, LUT, and N8AO all need real constructor args (scene/camera/etc.), so they stay hand-built with useMemo, but now apply live props through useLiveDefaults instead of reconstructing on every change. This is where nearly every real runtime bug from review surfaced: a first-apply bug where a still-correct value's setter fired anyway (Outline's multisampling disposing its render target before first use - the actual reason several of these didn't render at all), SSAO's color/fade/minRadiusScale/world* thresholds not resetting on removal, DepthOfField's depthTexture reconstructing instead of using the live setDepthTexture, and GodRays/N8AO not invalidating on live changes under frameloop="demand".
makeDisposeIdempotent guarded against depthPickingPass/copyPass getting disposed twice (once by the composer's own teardown, once by Autofocus's own cleanup) - unnecessary, since postprocessing/three dispose() is confirmed idempotent (event-fire or shallow property disposal, no internal state).
…rocessing into pr5/autofocus
Add createEffectComponent, a thin r3f-native effect factory
Passes are now derived from the r3f scene graph and only rebuilt when the resolved node list actually changes, not on every render. Fixes real GPU-resource bugs found along the way: composer-level prop changes (multisampling etc.) could dispose effects still in use by the new composer, discarded EffectPass wrappers leaked their own material and kept a stale change listener on the effect they wrapped, and a user's own EffectPass rendered as a child could be mistaken for one we generated.
Rewrite EffectComposer's pass lifecycle for correctness and cost
Covers every effect whose postprocessing class constructs with zero arguments (Bloom, Noise, Vignette, FXAA, and ~20 others) - live props update the existing instance instead of reconstructing on every change, construction-only options move to explicit args. Also fixes a few bugs these effects had on top of the migration: opacity typing on nine of them, ChromaticAberration's radialModulation/modulationOffset incorrectly required, ColorDepth's bits not resetting on removal.
Migrate simple effects to createEffectComponent
Outline, SelectiveBloom, ShockWave, GodRays, DepthOfField, SSAO, LUT, and N8AO all need real constructor args (scene/camera/etc.), so they stay hand-built with useMemo, but now apply live props through useLiveDefaults instead of reconstructing on every change. This is where nearly every real runtime bug from review surfaced: a first-apply bug where a still-correct value's setter fired anyway (Outline's multisampling disposing its render target before first use - the actual reason several of these didn't render at all), SSAO's color/fade/minRadiusScale/world* thresholds not resetting on removal, DepthOfField's depthTexture reconstructing instead of using the live setDepthTexture, and GodRays/N8AO not invalidating on live changes under frameloop="demand".
Migrate hand-rolled effects to useLiveDefaults
Simplify Autofocus's dispose handling, drop idempotency guard
They only have real setters on ssaoMaterial, not SSAOEffect itself, so the live props were silent no-ops.
…e toggle Groups effects into one EffectPass with a cheap enabled toggle, backed by a shared trailing CopyPass so disabling the last pass in a chain doesn't blank the canvas. Also fixes Autofocus's own passes racing that same mechanism, and gives N8AO a native enabled prop since it's a standalone Pass, not an Effect.
Documents the new grouping/enable-toggle API and its limitations (non-Effect passes like N8AO, convolution effects).
screenRes defaulted to a fresh Vector2(0,0) on each render, which r3f copies in place onto the uniform whenever a parent re-renders (e.g. a Leva control change) - wiping the viewport-synced value back to (0,0) and producing a fully black frame from the resulting divide-by-zero, until the next resize.
AutofocusProps intersected ComponentProps<typeof DepthOfField> (which already declares its own ref) with a differently-typed ref of its own, so TypeScript intersected the two ref types instead of the second overriding the first - making any RefObject<AutofocusApi> a type error.
useEffect(() => composer.setSize(size.width, size.height), [composer, size]) used r3f's size, which drei's View overrides per-portal only on View's own re-renders - not kept live, so a resize inside a View silently never reaches it. gl.getSize() isn't portal-scoped, and checking it every frame (cheap: no GPU sync, setSize itself only runs when the size actually changed) means this can't be missed regardless of whether this component ever re-renders. Fixes the composer being offset/wrong-sized inside View.
Mirrors postprocessing's own EffectComposer.autoRenderToScreen, set to false to render to a target of your own (e.g. via a trailing CopyPass) instead of the screen. Not a constructor option in postprocessing, but only ever read inside addPass()/removePass(), so it's applied the same way as depthBuffer/multisampling: changing it recreates the composer, rather than trying to patch it onto already-added passes. Closes #323
'auto' (default) merges effects into as few passes as possible while keeping at most one convolution effect per pass, matching what postprocessing itself allows. 'all' drops that limit (same no-guardrail contract as EffectGroup). 'none' gives every effect its own pass. Fixes #304.
@react-three/fiber, maath, and postprocessing itself all ship unminified - the consumer's bundler minifies again anyway, and this keeps stack traces and devtools readable.
tsconfig.json's exclude only matched *.test.* filenames, missing the test-utils.tsx helper - it was excluded from typecheck (never actually checked) yet still emitted into dist. Split into a build-only tsconfig.build.json that excludes src/tests entirely, and dropped the exclude from the base config so typecheck now covers every test file too.
# Conflicts: # docs/effects/outline.mdx
Silences Vite's configLoader: 'native' deprecation warning on every run.
Moved from docs/effects/ to docs/ alongside effect-composer/effect-group, since it's a guide, not a single effect's reference page. Content was still teaching the old manual useMemo+useDispose pattern with no mention of createEffectComponent or useLiveDefaults - now leads with those.
Extracted from Autofocus into a standalone, reusable primitive for reading world-space positions off the depth buffer. Also splits DepthPicking and N8AO into a new src/passes directory, since neither wraps a postprocessing Effect.
feat: add DepthPicking component and useDepthPicking hook
Removes yarn.lock in favor of pnpm-lock.yaml, updates CI/release workflows and CONTRIBUTING.md, and pins the pnpm version via packageManager in package.json.
chore: switch from yarn to pnpm
…d blend props Add useVector3, fix ShockWave/LensFlare position props to accept tuples not just Vector3. Also fixes LensFlare's occlusion default (no raycast hit now correctly shows the flare instead of hiding it). Remove blendFunction/opacity from ShockWave and Pixelation - both are mainUv-only effects with no color output to blend.
LensFlare was the only effect not forwarding a ref to its underlying instance. Extract the merge-ref logic (local + caller ref) shared with createEffectComponent into useMergeRefs.
N8AO gained enabled, ASCII's color widened to ColorRepresentation, ShockWave's position now accepts tuples. Moved n8ao.mdx to docs/passes/ to match its v4 source location and reworded it to say "pass" instead of "effect".
Both effects render internal extra passes that need it to work correctly. Exposes the resolved autoClear prop via EffectComposerContext and documents the requirement in their docs pages.
Reserves 4.0.0 for the next breaking rework, likely driven by postprocessing v7.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Internal rewrite of how effects apply live prop changes - public props are unchanged except for one cleanup (see Breaking below), but a lot of previously-broken live updates now actually work.
Highlights
##Breaking
Closes #68, closes #165, closes #182, closes #187 ,closes #257, closes #282, closes #285, closes #288, closes #292, closes #304, closes #323, closes #328, closes #336,