Skip to content

v3.1.0

Latest

Choose a tag to compare

@kvvasuu kvvasuu released this 23 Aug 16:22
· 4 commits to master since this release

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 EffectPass with its own cheap enabled toggle, backed by a shared trailing CopyPass so 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 vanilla DepthPickingPass and exposes readDepth via ref - renders nothing, updates nothing automatically. useDepthPicking turns a screen position into a world-space point on demand, auto-detecting the composer's own camera when used inside its <EffectComposer>, with an explicit camera override for calling it from outside that tree. <Autofocus> is now built on both internally, same external behavior.
  • EffectComposer: new autoRenderToScreen?: boolean prop (default true) - construction-time option, same treatment as depthBuffer/multisampling.
  • EffectComposer: new renderPass?: (scene, camera) => Pass prop - lets consumers supply their own scene/camera render pass
  • EffectComposer: new mergeMode?: 'auto' | 'all' | 'none' prop (default 'auto'). 'auto' merges effects into as few EffectPasses as possible while keeping at most one convolution effect per pass - matching what postprocessing itself 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 postprocessing effect classes the same way this library's own simple effects do.
  • N8AO: new enabled prop, wired into the same shared trailing-CopyPass safety net as EffectGroup (N8AO isn't a mergeable postprocessing Effect, so it needed its own toggle rather than going through EffectGroup directly).
  • Grid, Glitch: opacity now works - these never went through wrapEffect (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/hiddenEdgeColor now typed as ColorRepresentation (number | string | Color) instead of just number - the runtime already wrapped values in new Color(value), so string colors ("red", "#ff0000", etc.) always worked, the type just didn't say so.
  • LensFlare, ShockWave: lensPosition/position now accept ReactThreeFiber.Vector3 (a tuple or a single number, not just a Vector3 instance), matching how position props work everywhere else in the r3f ecosystem.

Changed

  • Effects whose postprocessing class can be constructed with zero arguments are now built on createEffectComponent: live props update the existing effect instance in place instead of reconstructing it on every change, and a ref now 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 createEffectComponent similarly apply live props through useLiveDefaults instead 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: resolutionScale now falls back to the parent <EffectComposer>'s own resolutionScale prop when not set directly on <SSAO>, instead of always defaulting straight to 1.
  • Build: dist is 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 - useMemo only depended on [camera, downSamplingPass, normalPass, resolutionScale], and props were never re-applied to the effect any other way. Changing color, intensity, radius, samples, worldProximityThreshold/worldProximityFalloff, or anything else did nothing until one of those four values happened to change too. Now applies live via useLiveDefaults.
  • DepthOfField: changing depthTexture reconstructed the whole effect - render targets and passes included - despite DepthOfFieldEffect.setDepthTexture() existing specifically to update an existing instance in place. Now applied live.
  • N8AO: config/quality changes mutate effect.configuration directly (a plain object outside r3f's reconciler) without ever calling invalidate(), so changes didn't repaint under <Canvas frameloop="demand">.
  • EffectComposer: sized itself off r3f's size, which drei's <View> overrides per-portal only on View'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 reads gl.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/opacity props removed entirely. Both are mainUv-only shaders (no mainImage, verified against postprocessing'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

Full Changelog: v3.0.5...v3.1.0