@react-three/postprocessing 3.0.5 → 3.1.1

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 (106) hide show
  1. package/README.md +6 -7
  2. package/dist/EffectComposer.d.ts +9 -2
  3. package/dist/EffectGroup.d.ts +9 -0
  4. package/dist/createEffectComponent.d.ts +30 -0
  5. package/dist/effects/ASCII.d.ts +28 -7
  6. package/dist/effects/Autofocus.d.ts +1 -1
  7. package/dist/effects/Bloom.d.ts +9 -1
  8. package/dist/effects/BrightnessContrast.d.ts +14 -1
  9. package/dist/effects/ChromaticAberration.d.ts +26 -3
  10. package/dist/effects/ColorAverage.d.ts +7 -7
  11. package/dist/effects/ColorDepth.d.ts +7 -1
  12. package/dist/effects/Depth.d.ts +12 -1
  13. package/dist/effects/DotScreen.d.ts +14 -1
  14. package/dist/effects/FXAA.d.ts +10 -1
  15. package/dist/effects/Glitch.d.ts +16 -11
  16. package/dist/effects/GodRays.d.ts +1 -1
  17. package/dist/effects/Grid.d.ts +7 -6
  18. package/dist/effects/HueSaturation.d.ts +14 -1
  19. package/dist/effects/LUT.d.ts +1 -1
  20. package/dist/effects/LensFlare.d.ts +46 -5
  21. package/dist/effects/Noise.d.ts +7 -1
  22. package/dist/effects/Outline.d.ts +6 -3
  23. package/dist/effects/Pixelation.d.ts +1 -1
  24. package/dist/effects/Ramp.d.ts +58 -6
  25. package/dist/effects/SMAA.d.ts +9 -1
  26. package/dist/effects/SSAO.d.ts +1 -1
  27. package/dist/effects/ScanlineEffect.d.ts +12 -1
  28. package/dist/effects/SelectiveBloom.d.ts +1 -1
  29. package/dist/effects/Sepia.d.ts +12 -1
  30. package/dist/effects/ShockWave.d.ts +11 -1
  31. package/dist/effects/Texture.d.ts +4 -4
  32. package/dist/effects/TiltShift.d.ts +9 -1
  33. package/dist/effects/TiltShift2.d.ts +44 -4
  34. package/dist/effects/ToneMapping.d.ts +9 -3
  35. package/dist/effects/Vignette.d.ts +18 -1
  36. package/dist/effects/Water.d.ts +17 -1
  37. package/dist/index.d.ts +4 -1
  38. package/dist/index.js +2205 -713
  39. package/dist/index.js.map +1 -1
  40. package/dist/passes/DepthPicking.d.ts +11 -0
  41. package/dist/{effects → passes}/N8AO.d.ts +2 -1
  42. package/dist/util.d.ts +9 -15
  43. package/dist/wrapEffect.d.ts +3 -3
  44. package/package.json +6 -5
  45. package/src/EffectComposer.tsx +143 -42
  46. package/src/EffectGroup.tsx +74 -0
  47. package/src/createEffectComponent.tsx +71 -0
  48. package/src/effects/ASCII.tsx +75 -28
  49. package/src/effects/Autofocus.tsx +24 -77
  50. package/src/effects/Bloom.tsx +32 -4
  51. package/src/effects/BrightnessContrast.tsx +5 -2
  52. package/src/effects/ChromaticAberration.tsx +13 -21
  53. package/src/effects/ColorAverage.tsx +3 -16
  54. package/src/effects/ColorDepth.tsx +23 -2
  55. package/src/effects/Depth.tsx +4 -2
  56. package/src/effects/DepthOfField.tsx +48 -26
  57. package/src/effects/DotScreen.tsx +5 -2
  58. package/src/effects/FXAA.tsx +4 -2
  59. package/src/effects/Glitch.tsx +26 -31
  60. package/src/effects/GodRays.tsx +90 -6
  61. package/src/effects/Grid.tsx +17 -18
  62. package/src/effects/HueSaturation.tsx +5 -2
  63. package/src/effects/LUT.tsx +9 -11
  64. package/src/effects/LensFlare.tsx +209 -55
  65. package/src/effects/Noise.tsx +14 -2
  66. package/src/effects/Outline.tsx +55 -56
  67. package/src/effects/Pixelation.tsx +11 -8
  68. package/src/effects/Ramp.tsx +96 -7
  69. package/src/effects/SMAA.tsx +18 -2
  70. package/src/effects/SSAO.tsx +130 -22
  71. package/src/effects/ScanlineEffect.tsx +6 -6
  72. package/src/effects/SelectiveBloom.tsx +26 -44
  73. package/src/effects/Sepia.tsx +4 -2
  74. package/src/effects/ShockWave.tsx +30 -2
  75. package/src/effects/Texture.tsx +10 -9
  76. package/src/effects/TiltShift.tsx +30 -2
  77. package/src/effects/TiltShift2.tsx +72 -6
  78. package/src/effects/ToneMapping.tsx +16 -3
  79. package/src/effects/Vignette.tsx +5 -2
  80. package/src/effects/Water.tsx +21 -6
  81. package/src/index.ts +4 -2
  82. package/src/passes/DepthPicking.tsx +63 -0
  83. package/src/{effects → passes}/N8AO.tsx +28 -4
  84. package/src/tests/Autofocus.test.tsx +38 -0
  85. package/src/tests/Bloom.test.tsx +76 -0
  86. package/src/tests/ChromaticAberration.test.tsx +50 -0
  87. package/src/tests/ColorDepth.test.tsx +71 -0
  88. package/src/tests/DepthOfField.test.tsx +130 -0
  89. package/src/tests/DepthPicking.test.tsx +250 -0
  90. package/src/tests/EffectComposer.test.tsx +501 -8
  91. package/src/tests/EffectGroup.test.tsx +337 -0
  92. package/src/tests/Glitch.test.tsx +56 -0
  93. package/src/tests/GodRays.test.tsx +101 -0
  94. package/src/tests/Grid.test.tsx +34 -0
  95. package/src/tests/LUT.test.tsx +64 -0
  96. package/src/tests/N8AO.test.tsx +92 -0
  97. package/src/tests/Outline.test.tsx +122 -0
  98. package/src/tests/SSAO.test.tsx +140 -0
  99. package/src/tests/SelectiveBloom.test.tsx +48 -0
  100. package/src/tests/ShockWave.test.tsx +95 -0
  101. package/src/tests/TiltShift.test.tsx +76 -0
  102. package/src/tests/createEffectComponent.test.tsx +286 -0
  103. package/src/tests/effects.smoke.test.tsx +46 -42
  104. package/src/util.tsx +148 -23
  105. package/src/wrapEffect.tsx +3 -2
  106. package/dist/tests/test-utils.d.ts +0 -12
@@ -0,0 +1,250 @@
1
+ import { useThree } from '@react-three/fiber'
2
+ import { DepthPickingPass as DepthPickingPassImpl, EffectComposer as EffectComposerImpl, EffectPass, RenderPass } from 'postprocessing'
3
+ import * as React from 'react'
4
+ import * as THREE from 'three'
5
+ import { describe, expect, it } from 'vitest'
6
+ import { EffectComposer } from '../EffectComposer'
7
+ import { Noise } from '../effects/Noise'
8
+ import { DepthPicking, useDepthPicking, type DepthPickingApi } from '../passes/DepthPicking'
9
+ import { flush, root, strict, waitForComposer } from './test-utils'
10
+
11
+ describe('DepthPicking', () => {
12
+ it('adds its pass after RenderPass', async () => {
13
+ const composerRef = React.createRef<EffectComposerImpl>()
14
+
15
+ await React.act(async () => root.render(<EffectComposer ref={composerRef}>{<DepthPicking />}</EffectComposer>))
16
+ const composer = await waitForComposer(composerRef)
17
+ await flush()
18
+
19
+ expect(composer.passes[0]).toBeInstanceOf(RenderPass)
20
+ expect(composer.passes.slice(1)).toContainEqual(expect.any(DepthPickingPassImpl))
21
+
22
+ await React.act(async () => root.render(null))
23
+ })
24
+
25
+ // Depth reads are position-independent (postprocessing's stable depth
26
+ // texture is populated once per frame off RenderPass, not off wherever
27
+ // DepthPicking's own pass happens to land) - verified against real
28
+ // geometry in test-env, not just structurally here. enableNormalPass adds
29
+ // a NormalPass right after RenderPass too, shifting what "index 1" used
30
+ // to mean back when this was added at a fixed index.
31
+ it('still keeps its trailing CopyPass owning renderToScreen with enableNormalPass and other effects around it', async () => {
32
+ const composerRef = React.createRef<EffectComposerImpl>()
33
+
34
+ await React.act(async () =>
35
+ root.render(
36
+ <EffectComposer ref={composerRef} enableNormalPass>
37
+ <Noise />
38
+ <DepthPicking />
39
+ <Noise />
40
+ </EffectComposer>
41
+ )
42
+ )
43
+ const composer = await waitForComposer(composerRef)
44
+ await flush()
45
+
46
+ const depthPickingPass = composer.passes.find((p) => p instanceof DepthPickingPassImpl)!
47
+ expect(depthPickingPass.renderToScreen).toBe(false)
48
+ expect(composer.passes.at(-1)!.renderToScreen).toBe(true)
49
+
50
+ await React.act(async () => root.render(null))
51
+ })
52
+
53
+ it('never lets its pass own renderToScreen when a real effect follows it, regardless of StrictMode', async () => {
54
+ const composerRef = React.createRef<EffectComposerImpl>()
55
+
56
+ await React.act(async () =>
57
+ root.render(
58
+ strict(
59
+ <EffectComposer ref={composerRef}>
60
+ <DepthPicking />
61
+ <Noise />
62
+ </EffectComposer>
63
+ )
64
+ )
65
+ )
66
+ const composer = await waitForComposer(composerRef)
67
+ for (let i = 0; i < 10; i++) await flush()
68
+
69
+ const pass = composer.passes.find((p) => p instanceof DepthPickingPassImpl)!
70
+ expect(pass.renderToScreen).toBe(false)
71
+
72
+ // The real, visible output (Noise's own EffectPass) must own it instead.
73
+ const effectPass = composer.passes.find((p) => p instanceof EffectPass)!
74
+ expect(effectPass.renderToScreen).toBe(true)
75
+
76
+ await React.act(async () => root.render(null))
77
+ })
78
+
79
+ // DepthPickingPass.render() is conditional on a pending readDepth() call -
80
+ // unlike a normal pass, a frame with nothing pending renders nothing at
81
+ // all. If it ever owned renderToScreen (e.g. as the structurally-last
82
+ // pass with no other effects present), those frames would leave the
83
+ // screen showing whatever was already in the framebuffer. Its own
84
+ // trailing CopyPass (always unconditional) must own renderToScreen
85
+ // instead, even with no other effects around.
86
+ it('never owns renderToScreen even with no other effects - its own trailing CopyPass does instead', async () => {
87
+ const composerRef = React.createRef<EffectComposerImpl>()
88
+
89
+ await React.act(async () => root.render(<EffectComposer ref={composerRef}>{<DepthPicking />}</EffectComposer>))
90
+ const composer = await waitForComposer(composerRef)
91
+ await flush()
92
+
93
+ expect(composer.passes).toHaveLength(3)
94
+ const depthPickingPass = composer.passes.find((p) => p instanceof DepthPickingPassImpl)!
95
+ expect(depthPickingPass.renderToScreen).toBe(false)
96
+ expect(composer.passes.at(-1)!.renderToScreen).toBe(true)
97
+ expect(composer.passes.at(-1)).not.toBeInstanceOf(DepthPickingPassImpl)
98
+
99
+ await React.act(async () => root.render(null))
100
+ })
101
+
102
+ it('exposes readDepth via ref and renders nothing itself', async () => {
103
+ const ref = React.createRef<DepthPickingApi>()
104
+
105
+ await React.act(async () =>
106
+ root.render(
107
+ <EffectComposer>
108
+ <DepthPicking ref={ref} />
109
+ </EffectComposer>
110
+ )
111
+ )
112
+ await flush()
113
+
114
+ expect(ref.current).toBeTruthy()
115
+ expect(typeof ref.current!.readDepth).toBe('function')
116
+ })
117
+ })
118
+
119
+ describe('useDepthPicking', () => {
120
+ function Picker({
121
+ passRef,
122
+ camera,
123
+ onReady,
124
+ }: {
125
+ passRef: React.RefObject<DepthPickingApi | null>
126
+ camera?: THREE.Camera
127
+ onReady: (getHit: ReturnType<typeof useDepthPicking>) => void
128
+ }) {
129
+ const getHit = useDepthPicking(passRef, camera)
130
+ onReady(getHit)
131
+ return null
132
+ }
133
+
134
+ it('unprojects a picked depth into a world-space point using an explicitly passed camera', async () => {
135
+ const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 1000)
136
+ camera.position.set(0, 0, 5)
137
+ camera.updateMatrixWorld()
138
+ camera.updateProjectionMatrix()
139
+
140
+ // A stand-in for the mounted pass - readDepth resolves to a fixed,
141
+ // known depth so the resulting world position is fully predictable.
142
+ const fakePass: DepthPickingApi = { readDepth: async () => 0.5 }
143
+ const passRef = { current: fakePass }
144
+
145
+ let getHit: ReturnType<typeof useDepthPicking> = null!
146
+
147
+ await React.act(async () =>
148
+ root.render(
149
+ <EffectComposer camera={camera}>
150
+ <Picker passRef={passRef} camera={camera} onReady={(fn) => (getHit = fn)} />
151
+ </EffectComposer>
152
+ )
153
+ )
154
+ await flush()
155
+
156
+ const hit = await getHit(0, 0)
157
+ expect(hit).not.toBe(false)
158
+
159
+ const expected = new THREE.Vector3(0, 0, 0.5 * 2 - 1).unproject(camera)
160
+ expect((hit as THREE.Vector3).toArray()).toEqual(expected.toArray())
161
+ })
162
+
163
+ it("falls back to r3f's own default camera when none is passed explicitly", async () => {
164
+ const fakePass: DepthPickingApi = { readDepth: async () => 0.5 }
165
+ const passRef = { current: fakePass }
166
+
167
+ let getHit: ReturnType<typeof useDepthPicking> = null!
168
+ let defaultCamera: THREE.Camera | null = null
169
+
170
+ function CaptureDefaultCamera() {
171
+ defaultCamera = useThree((state) => state.camera)
172
+ return null
173
+ }
174
+
175
+ await React.act(async () =>
176
+ root.render(
177
+ <EffectComposer>
178
+ <CaptureDefaultCamera />
179
+ <Picker passRef={passRef} onReady={(fn) => (getHit = fn)} />
180
+ </EffectComposer>
181
+ )
182
+ )
183
+ await flush()
184
+
185
+ const hit = await getHit(0, 0)
186
+ expect(hit).not.toBe(false)
187
+
188
+ const expected = new THREE.Vector3(0, 0, 0.5 * 2 - 1).unproject(defaultCamera!)
189
+ expect((hit as THREE.Vector3).toArray()).toEqual(expected.toArray())
190
+ })
191
+
192
+ // The point of taking `pass` as a plain ref (rather than reading
193
+ // DepthPicking's own context) - the hook itself never touches
194
+ // EffectComposerContext, so it works from anywhere under <Canvas>, not
195
+ // just from inside the <EffectComposer> the pass happens to live in.
196
+ it('works when called outside the <EffectComposer> the pass is mounted in', async () => {
197
+ const fakePass: DepthPickingApi = { readDepth: async () => 0.5 }
198
+ const passRef = { current: fakePass }
199
+ let getHit: ReturnType<typeof useDepthPicking> = null!
200
+
201
+ await React.act(async () =>
202
+ root.render(
203
+ <>
204
+ <EffectComposer>
205
+ <DepthPicking />
206
+ </EffectComposer>
207
+ <Picker passRef={passRef} onReady={(fn) => (getHit = fn)} />
208
+ </>
209
+ )
210
+ )
211
+ await flush()
212
+
213
+ expect(await getHit(0, 0)).not.toBe(false)
214
+ })
215
+
216
+ it('returns false when depth is at the far plane (nothing hit)', async () => {
217
+ const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 1000)
218
+ const fakePass: DepthPickingApi = { readDepth: async () => 1 }
219
+ const passRef = { current: fakePass }
220
+
221
+ let getHit: ReturnType<typeof useDepthPicking> = null!
222
+
223
+ await React.act(async () =>
224
+ root.render(
225
+ <EffectComposer camera={camera}>
226
+ <Picker passRef={passRef} camera={camera} onReady={(fn) => (getHit = fn)} />
227
+ </EffectComposer>
228
+ )
229
+ )
230
+ await flush()
231
+
232
+ expect(await getHit(0, 0)).toBe(false)
233
+ })
234
+
235
+ it('returns false when the pass ref is not attached yet', async () => {
236
+ const passRef = { current: null }
237
+ let getHit: ReturnType<typeof useDepthPicking> = null!
238
+
239
+ await React.act(async () =>
240
+ root.render(
241
+ <EffectComposer>
242
+ <Picker passRef={passRef} onReady={(fn) => (getHit = fn)} />
243
+ </EffectComposer>
244
+ )
245
+ )
246
+ await flush()
247
+
248
+ expect(await getHit(0, 0)).toBe(false)
249
+ })
250
+ })