@react-three/postprocessing 3.0.4 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +80 -81
  3. package/dist/EffectComposer.d.ts +14 -6
  4. package/dist/EffectGroup.d.ts +9 -0
  5. package/dist/Selection.d.ts +8 -8
  6. package/dist/createEffectComponent.d.ts +30 -0
  7. package/dist/effects/ASCII.d.ts +29 -7
  8. package/dist/effects/Autofocus.d.ts +8 -7
  9. package/dist/effects/Bloom.d.ts +10 -5
  10. package/dist/effects/BrightnessContrast.d.ts +15 -5
  11. package/dist/effects/ChromaticAberration.d.ts +30 -7
  12. package/dist/effects/ColorAverage.d.ts +7 -8
  13. package/dist/effects/ColorDepth.d.ts +8 -5
  14. package/dist/effects/Depth.d.ts +13 -5
  15. package/dist/effects/DepthOfField.d.ts +7 -18
  16. package/dist/effects/DotScreen.d.ts +15 -5
  17. package/dist/effects/FXAA.d.ts +11 -5
  18. package/dist/effects/Glitch.d.ts +17 -29
  19. package/dist/effects/GodRays.d.ts +7 -19
  20. package/dist/effects/Grid.d.ts +8 -8
  21. package/dist/effects/HueSaturation.d.ts +15 -5
  22. package/dist/effects/LUT.d.ts +4 -3
  23. package/dist/effects/LensFlare.d.ts +52 -11
  24. package/dist/effects/Noise.d.ts +8 -5
  25. package/dist/effects/Outline.d.ts +9 -25
  26. package/dist/effects/Pixelation.d.ts +3 -2
  27. package/dist/effects/Ramp.d.ts +58 -10
  28. package/dist/effects/SMAA.d.ts +10 -5
  29. package/dist/effects/SSAO.d.ts +7 -30
  30. package/dist/effects/ScanlineEffect.d.ts +13 -5
  31. package/dist/effects/SelectiveBloom.d.ts +5 -10
  32. package/dist/effects/Sepia.d.ts +13 -5
  33. package/dist/effects/ShockWave.d.ts +12 -5
  34. package/dist/effects/Texture.d.ts +7 -8
  35. package/dist/effects/TiltShift.d.ts +10 -5
  36. package/dist/effects/TiltShift2.d.ts +44 -8
  37. package/dist/effects/ToneMapping.d.ts +9 -7
  38. package/dist/effects/Vignette.d.ts +19 -5
  39. package/dist/effects/Water.d.ts +17 -5
  40. package/dist/index.d.ts +15 -11
  41. package/dist/index.js +1841 -45
  42. package/dist/index.js.map +1 -1
  43. package/dist/passes/DepthPicking.d.ts +11 -0
  44. package/dist/{effects → passes}/N8AO.d.ts +5 -2
  45. package/dist/util.d.ts +19 -17
  46. package/dist/wrapEffect.d.ts +12 -0
  47. package/package.json +74 -67
  48. package/src/EffectComposer.tsx +379 -200
  49. package/src/EffectGroup.tsx +74 -0
  50. package/src/Selection.tsx +86 -46
  51. package/src/createEffectComponent.tsx +71 -0
  52. package/src/effects/ASCII.tsx +183 -135
  53. package/src/effects/Autofocus.tsx +122 -153
  54. package/src/effects/Bloom.tsx +34 -6
  55. package/src/effects/BrightnessContrast.tsx +7 -4
  56. package/src/effects/ChromaticAberration.tsx +22 -5
  57. package/src/effects/ColorAverage.tsx +4 -15
  58. package/src/effects/ColorDepth.tsx +25 -4
  59. package/src/effects/Depth.tsx +6 -4
  60. package/src/effects/DepthOfField.tsx +109 -89
  61. package/src/effects/DotScreen.tsx +7 -4
  62. package/src/effects/FXAA.tsx +6 -4
  63. package/src/effects/Glitch.tsx +32 -40
  64. package/src/effects/GodRays.tsx +104 -16
  65. package/src/effects/Grid.tsx +27 -21
  66. package/src/effects/HueSaturation.tsx +7 -4
  67. package/src/effects/LUT.tsx +25 -26
  68. package/src/effects/LensFlare.tsx +765 -611
  69. package/src/effects/Noise.tsx +16 -4
  70. package/src/effects/Outline.tsx +84 -117
  71. package/src/effects/Pixelation.tsx +20 -15
  72. package/src/effects/Ramp.tsx +239 -150
  73. package/src/effects/SMAA.tsx +20 -4
  74. package/src/effects/SSAO.tsx +152 -41
  75. package/src/effects/ScanlineEffect.tsx +7 -7
  76. package/src/effects/SelectiveBloom.tsx +98 -126
  77. package/src/effects/Sepia.tsx +6 -4
  78. package/src/effects/ShockWave.tsx +32 -4
  79. package/src/effects/Texture.tsx +28 -23
  80. package/src/effects/TiltShift.tsx +32 -4
  81. package/src/effects/TiltShift2.tsx +156 -90
  82. package/src/effects/ToneMapping.tsx +19 -6
  83. package/src/effects/Vignette.tsx +7 -4
  84. package/src/effects/Water.tsx +50 -35
  85. package/src/index.ts +43 -40
  86. package/src/passes/DepthPicking.tsx +63 -0
  87. package/src/passes/N8AO.tsx +105 -0
  88. package/src/tests/Autofocus.test.tsx +38 -0
  89. package/src/tests/Bloom.test.tsx +76 -0
  90. package/src/tests/ChromaticAberration.test.tsx +81 -0
  91. package/src/tests/ColorDepth.test.tsx +71 -0
  92. package/src/tests/DepthOfField.test.tsx +130 -0
  93. package/src/tests/DepthPicking.test.tsx +250 -0
  94. package/src/tests/EffectComposer.test.tsx +1404 -0
  95. package/src/tests/EffectGroup.test.tsx +337 -0
  96. package/src/tests/Glitch.test.tsx +56 -0
  97. package/src/tests/GodRays.test.tsx +101 -0
  98. package/src/tests/Grid.test.tsx +34 -0
  99. package/src/tests/LUT.test.tsx +64 -0
  100. package/src/tests/N8AO.test.tsx +92 -0
  101. package/src/tests/Outline.test.tsx +211 -0
  102. package/src/tests/SSAO.test.tsx +140 -0
  103. package/src/tests/Selection.test.tsx +324 -0
  104. package/src/tests/SelectiveBloom.test.tsx +166 -0
  105. package/src/tests/ShockWave.test.tsx +95 -0
  106. package/src/tests/TiltShift.test.tsx +76 -0
  107. package/src/tests/createEffectComponent.test.tsx +286 -0
  108. package/src/tests/effects.smoke.test.tsx +238 -0
  109. package/src/tests/test-utils.tsx +96 -0
  110. package/src/tests/wrapEffect.test.tsx +209 -0
  111. package/src/util.tsx +221 -55
  112. package/src/wrapEffect.tsx +123 -0
  113. package/src/EffectComposer.test.tsx +0 -126
  114. package/src/effects/N8AO.tsx +0 -81
@@ -1,4 +1,20 @@
1
- import { SMAAEffect } from 'postprocessing'
2
- import { wrapEffect } from '../util'
3
-
4
- export const SMAA = /* @__PURE__ */ wrapEffect(SMAAEffect)
1
+ import { SMAAEffect } from 'postprocessing'
2
+ import type { Ref } from 'react'
3
+ import { useMemo } from 'react'
4
+ import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
5
+
6
+ type SMAAOptions = EffectOptions<typeof SMAAEffect>
7
+
8
+ const SMAAImpl = /* @__PURE__ */ createEffectComponent<typeof SMAAEffect, SMAAOptions>(SMAAEffect)
9
+
10
+ export type SMAAProps = SMAAOptions & { opacity?: number; ref?: Ref<SMAAEffect> }
11
+
12
+ // preset/edgeDetectionMode/predicationMode have no live setter in
13
+ // postprocessing - routed through args so they still work as plain props.
14
+ export function SMAA({ preset, edgeDetectionMode, predicationMode, ...liveProps }: SMAAProps) {
15
+ const args = useMemo<[SMAAOptions]>(
16
+ () => [{ preset, edgeDetectionMode, predicationMode }],
17
+ [preset, edgeDetectionMode, predicationMode]
18
+ )
19
+ return <SMAAImpl args={args} {...liveProps} />
20
+ }
@@ -1,41 +1,152 @@
1
- import { Ref, forwardRef, useContext, useMemo } from 'react'
2
- import { SSAOEffect, BlendFunction } from 'postprocessing'
3
- import { EffectComposerContext } from '../EffectComposer'
4
-
5
- // first two args are camera and texture
6
- type SSAOProps = ConstructorParameters<typeof SSAOEffect>[2]
7
-
8
- export const SSAO = /* @__PURE__ */ forwardRef<SSAOEffect, SSAOProps>(function SSAO(
9
- props: SSAOProps,
10
- ref: Ref<SSAOEffect>
11
- ) {
12
- const { camera, normalPass, downSamplingPass, resolutionScale } = useContext(EffectComposerContext)
13
- const effect = useMemo<SSAOEffect | {}>(() => {
14
- if (normalPass === null && downSamplingPass === null) {
15
- console.error('Please enable the NormalPass in the EffectComposer in order to use SSAO.')
16
- return {}
17
- }
18
- return new SSAOEffect(camera, normalPass && !downSamplingPass ? (normalPass as any).texture : null, {
19
- blendFunction: BlendFunction.MULTIPLY,
20
- samples: 30,
21
- rings: 4,
22
- distanceThreshold: 1.0,
23
- distanceFalloff: 0.0,
24
- rangeThreshold: 0.5,
25
- rangeFalloff: 0.1,
26
- luminanceInfluence: 0.9,
27
- radius: 20,
28
- bias: 0.5,
29
- intensity: 1.0,
30
- color: undefined,
31
- // @ts-ignore
32
- normalDepthBuffer: downSamplingPass ? downSamplingPass.texture : null,
33
- resolutionScale: resolutionScale ?? 1,
34
- depthAwareUpsampling: true,
35
- ...props,
36
- })
37
- // NOTE: `props` is an unstable reference, so we can't memoize it
38
- // eslint-disable-next-line react-hooks/exhaustive-deps
39
- }, [camera, downSamplingPass, normalPass, resolutionScale])
40
- return <primitive ref={ref} object={effect} dispose={null} />
41
- })
1
+ import { BlendFunction, SSAOEffect } from 'postprocessing'
2
+ import { Ref, use, useMemo } from 'react'
3
+ import { EffectComposerContext } from '../EffectComposer'
4
+ import { applyPierced, readPierced, useDispose, useLiveDefaults } from '../util'
5
+
6
+ // first two args are camera and texture
7
+ type SSAOProps = ConstructorParameters<typeof SSAOEffect>[2] & { ref?: Ref<SSAOEffect> }
8
+
9
+ // Only resolutionScale/resolutionX/resolutionY/width/height and
10
+ // normalDepthBuffer have no live setter in postprocessing - everything else
11
+ // either has a real accessor directly on SSAOEffect, or on the nested
12
+ // ssaoMaterial (rangeThreshold/rangeFalloff are the constructor's names for
13
+ // what ssaoMaterial exposes as proximityThreshold/proximityFalloff).
14
+ // camera+normalBuffer being required constructor args also rule out
15
+ // createEffectComponent (needs `new Effect()` to work with zero args).
16
+ const LIVE_KEYS = [
17
+ 'blendMode-blendFunction',
18
+ 'normalBuffer',
19
+ 'samples',
20
+ 'rings',
21
+ 'radius',
22
+ 'depthAwareUpsampling',
23
+ 'color',
24
+ 'luminanceInfluence',
25
+ 'intensity',
26
+ 'ssaoMaterial-bias',
27
+ 'ssaoMaterial-fade',
28
+ 'ssaoMaterial-minRadiusScale',
29
+ 'ssaoMaterial-distanceThreshold',
30
+ 'ssaoMaterial-distanceFalloff',
31
+ 'ssaoMaterial-worldDistanceThreshold',
32
+ 'ssaoMaterial-worldDistanceFalloff',
33
+ 'rangeThreshold',
34
+ 'rangeFalloff',
35
+ 'ssaoMaterial-worldProximityThreshold',
36
+ 'ssaoMaterial-worldProximityFalloff',
37
+ ]
38
+
39
+ function get(effect: SSAOEffect, key: string): unknown {
40
+ if (key === 'rangeThreshold') return effect.ssaoMaterial.proximityThreshold
41
+ if (key === 'rangeFalloff') return effect.ssaoMaterial.proximityFalloff
42
+ return readPierced(effect, key)
43
+ }
44
+
45
+ function set(effect: SSAOEffect, key: string, value: unknown): void {
46
+ if (key === 'rangeThreshold') effect.ssaoMaterial.proximityThreshold = value as number
47
+ else if (key === 'rangeFalloff') effect.ssaoMaterial.proximityFalloff = value as number
48
+ else applyPierced(effect, key, value)
49
+ }
50
+
51
+ export function SSAO({
52
+ blendFunction = BlendFunction.MULTIPLY,
53
+ samples = 30,
54
+ rings = 4,
55
+ distanceThreshold = 1.0,
56
+ distanceFalloff = 0.0,
57
+ rangeThreshold = 0.5,
58
+ rangeFalloff = 0.1,
59
+ luminanceInfluence = 0.9,
60
+ radius = 20,
61
+ bias = 0.5,
62
+ intensity = 1.0,
63
+ color,
64
+ worldDistanceThreshold,
65
+ worldDistanceFalloff,
66
+ worldProximityThreshold,
67
+ worldProximityFalloff,
68
+ minRadiusScale,
69
+ fade,
70
+ depthAwareUpsampling = true,
71
+ resolutionScale,
72
+ resolutionX,
73
+ resolutionY,
74
+ width,
75
+ height,
76
+ ref,
77
+ }: SSAOProps) {
78
+ const { camera, normalPass, downSamplingPass, resolutionScale: composerResolutionScale } = use(EffectComposerContext)
79
+
80
+ const effect = useMemo<SSAOEffect | {}>(() => {
81
+ if (normalPass === null && downSamplingPass === null) {
82
+ console.error('Please enable the NormalPass in the EffectComposer in order to use SSAO.')
83
+ return {}
84
+ }
85
+
86
+ return new SSAOEffect(camera, normalPass && !downSamplingPass ? (normalPass as any).texture : null, {
87
+ blendFunction,
88
+ samples,
89
+ rings,
90
+ distanceThreshold,
91
+ distanceFalloff,
92
+ rangeThreshold,
93
+ rangeFalloff,
94
+ luminanceInfluence,
95
+ radius,
96
+ bias,
97
+ intensity,
98
+ // @ts-ignore
99
+ normalDepthBuffer: downSamplingPass ? downSamplingPass.texture : null,
100
+ resolutionScale: resolutionScale ?? composerResolutionScale ?? 1,
101
+ resolutionX,
102
+ resolutionY,
103
+ width,
104
+ height,
105
+ depthAwareUpsampling,
106
+ })
107
+ // color/worldDistanceThreshold/worldDistanceFalloff/worldProximityThreshold/
108
+ // worldProximityFalloff/minRadiusScale/fade are deliberately left out here
109
+ // even though they're valid constructor options: they have no JS-level
110
+ // default in this component's own signature, so useLiveDefaults' first
111
+ // snapshot must see SSAOEffect's own real default for them, not whatever
112
+ // value happened to be passed on the mounting render - otherwise removing
113
+ // the prop later "resets" to that first-render value instead of the
114
+ // effect's true default. They're still applied immediately below, live.
115
+ //
116
+ // Only the genuinely construction-only options belong here - everything
117
+ // else is applied live below via useLiveDefaults instead.
118
+ // eslint-disable-next-line react-hooks/exhaustive-deps
119
+ }, [camera, downSamplingPass, normalPass, resolutionScale, composerResolutionScale, resolutionX, resolutionY, width, height])
120
+
121
+ useLiveDefaults(
122
+ effect instanceof SSAOEffect ? effect : null,
123
+ {
124
+ 'blendMode-blendFunction': blendFunction,
125
+ samples,
126
+ rings,
127
+ radius,
128
+ depthAwareUpsampling,
129
+ color,
130
+ luminanceInfluence,
131
+ intensity,
132
+ 'ssaoMaterial-bias': bias,
133
+ 'ssaoMaterial-fade': fade,
134
+ 'ssaoMaterial-minRadiusScale': minRadiusScale,
135
+ 'ssaoMaterial-distanceThreshold': distanceThreshold,
136
+ 'ssaoMaterial-distanceFalloff': distanceFalloff,
137
+ 'ssaoMaterial-worldDistanceThreshold': worldDistanceThreshold,
138
+ 'ssaoMaterial-worldDistanceFalloff': worldDistanceFalloff,
139
+ rangeThreshold,
140
+ rangeFalloff,
141
+ 'ssaoMaterial-worldProximityThreshold': worldProximityThreshold,
142
+ 'ssaoMaterial-worldProximityFalloff': worldProximityFalloff,
143
+ },
144
+ LIVE_KEYS,
145
+ get,
146
+ set
147
+ )
148
+
149
+ useDispose(effect as SSAOEffect)
150
+
151
+ return <primitive ref={ref} object={effect} />
152
+ }
@@ -1,7 +1,7 @@
1
- import { ScanlineEffect, BlendFunction } from 'postprocessing'
2
- import { wrapEffect } from '../util'
3
-
4
- export const Scanline = /* @__PURE__ */ wrapEffect(ScanlineEffect, {
5
- blendFunction: BlendFunction.OVERLAY,
6
- density: 1.25,
7
- })
1
+ import { ScanlineEffect } from 'postprocessing'
2
+ import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
3
+
4
+ export const Scanline = /* @__PURE__ */ createEffectComponent<
5
+ typeof ScanlineEffect,
6
+ EffectOptions<typeof ScanlineEffect>
7
+ >(ScanlineEffect)
@@ -1,126 +1,98 @@
1
- import { SelectiveBloomEffect, BlendFunction } from 'postprocessing'
2
- import type { BloomEffectOptions } from 'postprocessing'
3
- import React, { Ref, RefObject, forwardRef, useMemo, useEffect, useContext, useRef } from 'react'
4
- import { Object3D } from 'three'
5
- import { useThree } from '@react-three/fiber'
6
- import { EffectComposerContext } from '../EffectComposer'
7
- import { selectionContext } from '../Selection'
8
- import { resolveRef } from '../util'
9
-
10
- type ObjectRef = RefObject<Object3D>
11
-
12
- export type SelectiveBloomProps = BloomEffectOptions &
13
- Partial<{
14
- lights: Object3D[] | ObjectRef[]
15
- selection: Object3D | Object3D[] | ObjectRef | ObjectRef[]
16
- selectionLayer: number
17
- inverted: boolean
18
- ignoreBackground: boolean
19
- }>
20
-
21
- const addLight = (light: Object3D, effect: SelectiveBloomEffect) => light.layers.enable(effect.selection.layer)
22
- const removeLight = (light: Object3D, effect: SelectiveBloomEffect) => light.layers.disable(effect.selection.layer)
23
-
24
- export const SelectiveBloom = /* @__PURE__ */ forwardRef(function SelectiveBloom(
25
- {
26
- selection = [],
27
- selectionLayer = 10,
28
- lights = [],
29
- inverted = false,
30
- ignoreBackground = false,
31
- luminanceThreshold,
32
- luminanceSmoothing,
33
- intensity,
34
- width,
35
- height,
36
- kernelSize,
37
- mipmapBlur,
38
-
39
- ...props
40
- }: SelectiveBloomProps,
41
- forwardRef: Ref<SelectiveBloomEffect>
42
- ) {
43
- if (lights.length === 0) {
44
- console.warn('SelectiveBloom requires lights to work.')
45
- }
46
-
47
- const invalidate = useThree((state) => state.invalidate)
48
- const { scene, camera } = useContext(EffectComposerContext)
49
- const effect = useMemo(() => {
50
- const effect = new SelectiveBloomEffect(scene, camera, {
51
- blendFunction: BlendFunction.ADD,
52
- luminanceThreshold,
53
- luminanceSmoothing,
54
- intensity,
55
- width,
56
- height,
57
- kernelSize,
58
- mipmapBlur,
59
- ...props,
60
- })
61
- effect.inverted = inverted
62
- effect.ignoreBackground = ignoreBackground
63
- return effect
64
- }, [
65
- scene,
66
- camera,
67
- luminanceThreshold,
68
- luminanceSmoothing,
69
- intensity,
70
- width,
71
- height,
72
- kernelSize,
73
- mipmapBlur,
74
- inverted,
75
- ignoreBackground,
76
- props,
77
- ])
78
-
79
- const api = useContext(selectionContext)
80
-
81
- useEffect(() => {
82
- // Do not allow array selection if declarative selection is active
83
- // TODO: array selection should probably be deprecated altogether
84
- if (!api && selection) {
85
- effect.selection.set(
86
- Array.isArray(selection) ? (selection as Object3D[]).map(resolveRef) : [resolveRef(selection) as Object3D]
87
- )
88
- invalidate()
89
- return () => {
90
- effect.selection.clear()
91
- invalidate()
92
- }
93
- }
94
- }, [effect, selection, api, invalidate])
95
-
96
- useEffect(() => {
97
- effect.selection.layer = selectionLayer
98
- invalidate()
99
- }, [effect, invalidate, selectionLayer])
100
-
101
- useEffect(() => {
102
- if (lights && lights.length > 0) {
103
- lights.forEach((light) => addLight(resolveRef(light), effect))
104
- invalidate()
105
- return () => {
106
- lights.forEach((light) => removeLight(resolveRef(light), effect))
107
- invalidate()
108
- }
109
- }
110
- }, [effect, invalidate, lights, selectionLayer])
111
-
112
- useEffect(() => {
113
- if (api && api.enabled) {
114
- if (api.selected?.length) {
115
- effect.selection.set(api.selected)
116
- invalidate()
117
- return () => {
118
- effect.selection.clear()
119
- invalidate()
120
- }
121
- }
122
- }
123
- }, [api, effect.selection, invalidate])
124
-
125
- return <primitive ref={forwardRef} object={effect} dispose={null} />
126
- })
1
+ import { useThree } from '@react-three/fiber'
2
+ import type { BloomEffectOptions } from 'postprocessing'
3
+ import { BlendFunction, SelectiveBloomEffect } from 'postprocessing'
4
+ import { Ref, RefObject, use, useEffect, useMemo } from 'react'
5
+ import { Object3D } from 'three'
6
+ import { EffectComposerContext } from '../EffectComposer'
7
+ import { EMPTY_ARRAY, resolveRef, useDispose, useLiveDefaults, useSelectionSync } from '../util'
8
+
9
+ type ObjectRef = RefObject<Object3D | null>
10
+
11
+ export type SelectiveBloomProps = BloomEffectOptions &
12
+ Partial<{
13
+ lights: Object3D[] | ObjectRef[]
14
+ selection: Object3D | Object3D[] | ObjectRef | ObjectRef[]
15
+ selectionLayer: number
16
+ inverted: boolean
17
+ ignoreBackground: boolean
18
+ ref?: Ref<SelectiveBloomEffect>
19
+ }>
20
+
21
+ const addLight = (light: Object3D, effect: SelectiveBloomEffect) => light.layers.enable(effect.selection.layer)
22
+ const removeLight = (light: Object3D, effect: SelectiveBloomEffect) => light.layers.disable(effect.selection.layer)
23
+
24
+ // BloomEffect (which SelectiveBloomEffect extends) only exposes real
25
+ // setters for these - luminanceThreshold/luminanceSmoothing/mipmapBlur/
26
+ // radius/levels/resolution* are construction-only in postprocessing itself.
27
+ // scene/camera being required constructor args also rules out
28
+ // createEffectComponent (needs `new Effect()` to work with zero args).
29
+ const LIVE_KEYS = ['width', 'height', 'kernelSize', 'intensity', 'inverted', 'ignoreBackground']
30
+
31
+ export function SelectiveBloom({
32
+ selection = EMPTY_ARRAY,
33
+ selectionLayer = 10,
34
+ lights = EMPTY_ARRAY,
35
+ luminanceThreshold,
36
+ luminanceSmoothing,
37
+ mipmapBlur,
38
+ radius,
39
+ levels,
40
+ resolutionScale,
41
+ resolutionX,
42
+ resolutionY,
43
+ ref,
44
+ ...liveProps
45
+ }: SelectiveBloomProps) {
46
+ const { scene, camera } = use(EffectComposerContext)
47
+
48
+ const invalidate = useThree((state) => state.invalidate)
49
+
50
+ const effect = useMemo(
51
+ () =>
52
+ new SelectiveBloomEffect(scene, camera, {
53
+ blendFunction: BlendFunction.ADD,
54
+ luminanceThreshold,
55
+ luminanceSmoothing,
56
+ mipmapBlur,
57
+ radius,
58
+ levels,
59
+ resolutionScale,
60
+ resolutionX,
61
+ resolutionY,
62
+ }),
63
+ [scene, camera, luminanceThreshold, luminanceSmoothing, mipmapBlur, radius, levels, resolutionScale, resolutionX, resolutionY]
64
+ )
65
+
66
+ useLiveDefaults(effect, liveProps as Record<string, unknown>, LIVE_KEYS)
67
+
68
+ // Must run before the lights effect below: addLight/removeLight read
69
+ // effect.selection.layer live, so it needs to already reflect the
70
+ // latest selectionLayer by the time lights get (re-)assigned to it.
71
+ useSelectionSync(effect, selection, selectionLayer)
72
+
73
+ useEffect(() => {
74
+ if (lights.length === 0) {
75
+ console.warn('SelectiveBloom requires lights to work.')
76
+ return
77
+ }
78
+
79
+ // Refs may not have attached yet - resolve and drop nullish entries
80
+ // rather than crashing addLight/removeLight on a null object.
81
+ const resolvedLights = lights.map((light) => resolveRef(light)).filter((light): light is Object3D => light != null)
82
+ if (resolvedLights.length === 0) return
83
+
84
+ resolvedLights.forEach((light) => addLight(light, effect))
85
+
86
+ invalidate()
87
+
88
+ return () => {
89
+ resolvedLights.forEach((light) => removeLight(light, effect))
90
+
91
+ invalidate()
92
+ }
93
+ }, [effect, invalidate, lights, selectionLayer])
94
+
95
+ useDispose(effect)
96
+
97
+ return <primitive ref={ref} object={effect} />
98
+ }
@@ -1,4 +1,6 @@
1
- import { SepiaEffect } from 'postprocessing'
2
- import { wrapEffect } from '../util'
3
-
4
- export const Sepia = /* @__PURE__ */ wrapEffect(SepiaEffect)
1
+ import { SepiaEffect } from 'postprocessing'
2
+ import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
3
+
4
+ export const Sepia = /* @__PURE__ */ createEffectComponent<typeof SepiaEffect, EffectOptions<typeof SepiaEffect>>(
5
+ SepiaEffect
6
+ )
@@ -1,4 +1,32 @@
1
- import { ShockWaveEffect } from 'postprocessing'
2
- import { wrapEffect } from '../util'
3
-
4
- export const ShockWave = /* @__PURE__ */ wrapEffect(ShockWaveEffect)
1
+ import type { ReactThreeFiber } from '@react-three/fiber'
2
+ import { ShockWaveEffect } from 'postprocessing'
3
+ import { Ref, use, useMemo } from 'react'
4
+ import { EffectComposerContext } from '../EffectComposer'
5
+ import { useDispose, useLiveDefaults, useVector3 } from '../util'
6
+
7
+ export type ShockWaveProps = {
8
+ position?: ReactThreeFiber.Vector3
9
+ speed?: number
10
+ maxRadius?: number
11
+ waveSize?: number
12
+ amplitude?: number
13
+ ref?: Ref<ShockWaveEffect>
14
+ }
15
+
16
+ const LIVE_KEYS = ['position', 'speed', 'maxRadius', 'waveSize', 'amplitude']
17
+
18
+ // ShockWaveEffect's constructor is (camera, position, options) - camera is
19
+ // a required arg, so it can't use createEffectComponent (needs
20
+ // `new Effect()` to work with zero args). Built by hand instead, like
21
+ // Outline/GodRays.
22
+ export function ShockWave(props: ShockWaveProps) {
23
+ const { speed, maxRadius, waveSize, amplitude, ref } = props
24
+ const { camera } = use(EffectComposerContext)
25
+ const effect = useMemo(() => new ShockWaveEffect(camera), [camera])
26
+ const position = useVector3(props, 'position')
27
+
28
+ useLiveDefaults(effect, { position, speed, maxRadius, waveSize, amplitude }, LIVE_KEYS)
29
+ useDispose(effect)
30
+
31
+ return <primitive ref={ref} object={effect} />
32
+ }
@@ -1,23 +1,28 @@
1
- import { TextureEffect } from 'postprocessing'
2
- import { Ref, forwardRef, useMemo, useLayoutEffect } from 'react'
3
- import { useLoader } from '@react-three/fiber'
4
- import { TextureLoader, SRGBColorSpace, RepeatWrapping } from 'three'
5
-
6
- type TextureProps = ConstructorParameters<typeof TextureEffect>[0] & {
7
- textureSrc: string
8
- /** opacity of provided texture */
9
- opacity?: number
10
- }
11
-
12
- export const Texture = /* @__PURE__ */ forwardRef<TextureEffect, TextureProps>(function Texture(
13
- { textureSrc, texture, opacity = 1, ...props }: TextureProps,
14
- ref: Ref<TextureEffect>
15
- ) {
16
- const t = useLoader(TextureLoader, textureSrc)
17
- useLayoutEffect(() => {
18
- t.colorSpace = SRGBColorSpace
19
- t.wrapS = t.wrapT = RepeatWrapping
20
- }, [t])
21
- const effect = useMemo(() => new TextureEffect({ ...props, texture: t || texture }), [props, t, texture])
22
- return <primitive ref={ref} object={effect} blendMode-opacity-value={opacity} dispose={null} />
23
- })
1
+ import { useLoader } from '@react-three/fiber'
2
+ import { TextureEffect } from 'postprocessing'
3
+ import type { Ref } from 'react'
4
+ import { useLayoutEffect } from 'react'
5
+ import { RepeatWrapping, SRGBColorSpace, TextureLoader } from 'three'
6
+ import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
7
+
8
+ const TextureImpl = /* @__PURE__ */ createEffectComponent<typeof TextureEffect, EffectOptions<typeof TextureEffect>>(
9
+ TextureEffect
10
+ )
11
+
12
+ export type TextureProps = EffectOptions<typeof TextureEffect> & {
13
+ textureSrc: string
14
+ /** opacity of provided texture */
15
+ opacity?: number
16
+ ref?: Ref<TextureEffect>
17
+ }
18
+
19
+ export function Texture({ textureSrc, texture, opacity = 1, ...props }: TextureProps) {
20
+ const t = useLoader(TextureLoader, textureSrc)
21
+
22
+ useLayoutEffect(() => {
23
+ t.colorSpace = SRGBColorSpace
24
+ t.wrapS = t.wrapT = RepeatWrapping
25
+ }, [t])
26
+
27
+ return <TextureImpl {...props} texture={texture ?? t} opacity={opacity} />
28
+ }
@@ -1,4 +1,32 @@
1
- import { TiltShiftEffect, BlendFunction } from 'postprocessing'
2
- import { wrapEffect } from '../util'
3
-
4
- export const TiltShift = /* @__PURE__ */ wrapEffect(TiltShiftEffect, { blendFunction: BlendFunction.ADD })
1
+ import { BlendFunction, TiltShiftEffect } from 'postprocessing'
2
+ import type { Ref } from 'react'
3
+ import { useMemo } from 'react'
4
+ import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
5
+
6
+ type TiltShiftOptions = EffectOptions<typeof TiltShiftEffect>
7
+
8
+ const TiltShiftImpl = /* @__PURE__ */ createEffectComponent<typeof TiltShiftEffect, TiltShiftOptions>(TiltShiftEffect)
9
+
10
+ export type TiltShiftProps = TiltShiftOptions & {
11
+ opacity?: number
12
+ ref?: Ref<TiltShiftEffect>
13
+ }
14
+
15
+ // kernelSize/resolutionScale/resolutionX/resolutionY have no live setter in
16
+ // postprocessing - routed through args so they still work as plain props
17
+ // (previously they were passed as plain props and silently never reached
18
+ // the effect at all, since there was no setter for diffProps to hit).
19
+ export function TiltShift({
20
+ blendFunction = BlendFunction.ADD,
21
+ kernelSize,
22
+ resolutionScale,
23
+ resolutionX,
24
+ resolutionY,
25
+ ...liveProps
26
+ }: TiltShiftProps) {
27
+ const args = useMemo<[TiltShiftOptions]>(
28
+ () => [{ kernelSize, resolutionScale, resolutionX, resolutionY }],
29
+ [kernelSize, resolutionScale, resolutionX, resolutionY]
30
+ )
31
+ return <TiltShiftImpl blendFunction={blendFunction} args={args} {...liveProps} />
32
+ }