This release is an internal rewrite of how effects apply prop changes - most of it is invisible unless you hit one of the bugs it fixes, but it puts every effect on the same reliable, live-updating foundation instead of each one reconstructing itself in ad hoc ways.
Added
- EffectGroup: new component that groups effects into a single
EffectPasswith its own cheapenabledtoggle, backed by a shared trailingCopyPassso disabling the last pass in a chain doesn't blank the canvas. - DepthPicking, useDepthPicking: new component + hook for reading world-space positions off the depth buffer, extracted from
<Autofocus>into a reusable, standalone primitive.<DepthPicking>just mounts the vanillaDepthPickingPassand exposesreadDepthvia ref - renders nothing, updates nothing automatically.useDepthPickingturns a screen position into a world-space point on demand, auto-detecting the composer's own camera when used inside its<EffectComposer>, with an explicitcameraoverride for calling it from outside that tree.<Autofocus>is now built on both internally, same external behavior. - EffectComposer: new
autoRenderToScreen?: booleanprop (defaulttrue) - construction-time option, same treatment asdepthBuffer/multisampling. - EffectComposer: new
renderPass?: (scene, camera) => Passprop - lets consumers supply their own scene/camera render pass - EffectComposer: new
mergeMode?: 'auto' | 'all' | 'none'prop (default'auto').'auto'merges effects into as fewEffectPasses as possible while keeping at most one convolution effect per pass - matching whatpostprocessingitself actually allows. This is strictly better than the old unconditional "any convolution effect gets fully isolated" behavior.'all'drops that safety limit entirely.'none'gives every effect its own pass. - createEffectComponent: the internal effect-wrapping factory is now part of the public API, for wrapping your own zero-arg-constructible
postprocessingeffect classes the same way this library's own simple effects do. - N8AO: new
enabledprop, wired into the same shared trailing-CopyPasssafety net asEffectGroup(N8AO isn't a mergeablepostprocessingEffect, so it needed its own toggle rather than going throughEffectGroupdirectly). - Grid, Glitch:
opacitynow works - these never went throughwrapEffect(the only place that handled it) before, so it was silently inert regardless of what the (missing) types suggested. - Outline, GodRays: warn in the console when mounted without
<EffectComposer autoClear={false}>- both render an internal extra pass that silently produces wrong output (outlines don't render at all; GodRays occlusion looks wrong) without it. - Outline:
visibleEdgeColor/hiddenEdgeColornow typed asColorRepresentation(number | string | Color) instead of justnumber- the runtime already wrapped values innew Color(value), so string colors ("red", "#ff0000", etc.) always worked, the type just didn't say so. - LensFlare, ShockWave:
lensPosition/positionnow acceptReactThreeFiber.Vector3(a tuple or a single number, not just aVector3instance), matching how position props work everywhere else in the r3f ecosystem.
Changed
- Effects whose
postprocessingclass can be constructed with zero arguments are now built oncreateEffectComponent: live props update the existing effect instance in place instead of reconstructing it on every change, and arefnow points to one stable instance across prop updates instead of a new one every render. - Effects that need real constructor args and can't use
createEffectComponentsimilarly apply live props throughuseLiveDefaultsinstead of reconstructing the whole effect on every change. - EffectComposer: pass rebuild lifecycle rewritten - passes are now only rebuilt when the resolved effect/pass list actually changed, not on every unrelated React render.
- SSAO:
resolutionScalenow falls back to the parent<EffectComposer>'s ownresolutionScaleprop when not set directly on<SSAO>, instead of always defaulting straight to1. - Build:
distis no longer minified - the consumer's own bundler minifies anyway in a real production build, and this keeps stack traces/devtools readable when debugging into the library.
Fixed
- SSAO: almost none of its props updated live on change -
useMemoonly depended on[camera, downSamplingPass, normalPass, resolutionScale], and props were never re-applied to the effect any other way. Changingcolor,intensity,radius,samples,worldProximityThreshold/worldProximityFalloff, or anything else did nothing until one of those four values happened to change too. Now applies live viauseLiveDefaults. - DepthOfField: changing
depthTexturereconstructed the whole effect - render targets and passes included - despiteDepthOfFieldEffect.setDepthTexture()existing specifically to update an existing instance in place. Now applied live. - N8AO: config/
qualitychanges mutateeffect.configurationdirectly (a plain object outside r3f's reconciler) without ever callinginvalidate(), so changes didn't repaint under<Canvas frameloop="demand">. - EffectComposer: sized itself off r3f's
size, which drei's<View>overrides per-portal only onView's own re-renders - not kept live - so a composer nested in a<View>could go arbitrarily long without picking up a resize (in practice: staying offset/wrong-sized indefinitely). Now readsgl.getSize()directly instead, which isn't portal-scoped and can't be missed regardless of whether the component itself ever re-renders.
Removed
- Pixelation, ShockWave:
blendFunction/opacityprops removed entirely. Both aremainUv-only shaders (nomainImage, verified againstpostprocessing's own pass-composition logic) - there's no color output to blend, so the props never did anything; they're gone rather than wired up. If you were passing either to<Pixelation>/<ShockWave>, remove them - they were already silent no-ops.
What's Changed
- Add createEffectComponent, a thin r3f-native effect factory by @kvvasuu in #363
- Rewrite EffectComposer's pass lifecycle for correctness and cost by @kvvasuu in #364
- Migrate simple effects to createEffectComponent by @kvvasuu in #366
- Migrate hand-rolled effects to useLiveDefaults by @kvvasuu in #367
- Simplify Autofocus's dispose handling, drop idempotency guard by @kvvasuu in #368
- docs: Update Custom Effects docs for React 19 by @MaximeHeckel in #337
- Fix/docs workflow v3 by @kvvasuu in #370
- docs(examples): migrate examples to @pmndrs/examples by @kvvasuu in #371
- feat: add DepthPicking component and useDepthPicking hook by @kvvasuu in #372
- chore: switch from yarn to pnpm by @kvvasuu in #373
- docs: add missing effect pages by @kvvasuu in #374
- docs: link effect examples to pmndrs/examples by @kvvasuu in #376
New Contributors
- @MaximeHeckel made their first contribution in #337
Full Changelog: v3.0.5...v3.1.0