@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.
- package/README.md +6 -7
- package/dist/EffectComposer.d.ts +9 -2
- package/dist/EffectGroup.d.ts +9 -0
- package/dist/createEffectComponent.d.ts +30 -0
- package/dist/effects/ASCII.d.ts +28 -7
- package/dist/effects/Autofocus.d.ts +1 -1
- package/dist/effects/Bloom.d.ts +9 -1
- package/dist/effects/BrightnessContrast.d.ts +14 -1
- package/dist/effects/ChromaticAberration.d.ts +26 -3
- package/dist/effects/ColorAverage.d.ts +7 -7
- package/dist/effects/ColorDepth.d.ts +7 -1
- package/dist/effects/Depth.d.ts +12 -1
- package/dist/effects/DotScreen.d.ts +14 -1
- package/dist/effects/FXAA.d.ts +10 -1
- package/dist/effects/Glitch.d.ts +16 -11
- package/dist/effects/GodRays.d.ts +1 -1
- package/dist/effects/Grid.d.ts +7 -6
- package/dist/effects/HueSaturation.d.ts +14 -1
- package/dist/effects/LUT.d.ts +1 -1
- package/dist/effects/LensFlare.d.ts +46 -5
- package/dist/effects/Noise.d.ts +7 -1
- package/dist/effects/Outline.d.ts +6 -3
- package/dist/effects/Pixelation.d.ts +1 -1
- package/dist/effects/Ramp.d.ts +58 -6
- package/dist/effects/SMAA.d.ts +9 -1
- package/dist/effects/SSAO.d.ts +1 -1
- package/dist/effects/ScanlineEffect.d.ts +12 -1
- package/dist/effects/SelectiveBloom.d.ts +1 -1
- package/dist/effects/Sepia.d.ts +12 -1
- package/dist/effects/ShockWave.d.ts +11 -1
- package/dist/effects/Texture.d.ts +4 -4
- package/dist/effects/TiltShift.d.ts +9 -1
- package/dist/effects/TiltShift2.d.ts +44 -4
- package/dist/effects/ToneMapping.d.ts +9 -3
- package/dist/effects/Vignette.d.ts +18 -1
- package/dist/effects/Water.d.ts +17 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2205 -713
- package/dist/index.js.map +1 -1
- package/dist/passes/DepthPicking.d.ts +11 -0
- package/dist/{effects → passes}/N8AO.d.ts +2 -1
- package/dist/util.d.ts +9 -15
- package/dist/wrapEffect.d.ts +3 -3
- package/package.json +6 -5
- package/src/EffectComposer.tsx +143 -42
- package/src/EffectGroup.tsx +74 -0
- package/src/createEffectComponent.tsx +71 -0
- package/src/effects/ASCII.tsx +75 -28
- package/src/effects/Autofocus.tsx +24 -77
- package/src/effects/Bloom.tsx +32 -4
- package/src/effects/BrightnessContrast.tsx +5 -2
- package/src/effects/ChromaticAberration.tsx +13 -21
- package/src/effects/ColorAverage.tsx +3 -16
- package/src/effects/ColorDepth.tsx +23 -2
- package/src/effects/Depth.tsx +4 -2
- package/src/effects/DepthOfField.tsx +48 -26
- package/src/effects/DotScreen.tsx +5 -2
- package/src/effects/FXAA.tsx +4 -2
- package/src/effects/Glitch.tsx +26 -31
- package/src/effects/GodRays.tsx +90 -6
- package/src/effects/Grid.tsx +17 -18
- package/src/effects/HueSaturation.tsx +5 -2
- package/src/effects/LUT.tsx +9 -11
- package/src/effects/LensFlare.tsx +209 -55
- package/src/effects/Noise.tsx +14 -2
- package/src/effects/Outline.tsx +55 -56
- package/src/effects/Pixelation.tsx +11 -8
- package/src/effects/Ramp.tsx +96 -7
- package/src/effects/SMAA.tsx +18 -2
- package/src/effects/SSAO.tsx +130 -22
- package/src/effects/ScanlineEffect.tsx +6 -6
- package/src/effects/SelectiveBloom.tsx +26 -44
- package/src/effects/Sepia.tsx +4 -2
- package/src/effects/ShockWave.tsx +30 -2
- package/src/effects/Texture.tsx +10 -9
- package/src/effects/TiltShift.tsx +30 -2
- package/src/effects/TiltShift2.tsx +72 -6
- package/src/effects/ToneMapping.tsx +16 -3
- package/src/effects/Vignette.tsx +5 -2
- package/src/effects/Water.tsx +21 -6
- package/src/index.ts +4 -2
- package/src/passes/DepthPicking.tsx +63 -0
- package/src/{effects → passes}/N8AO.tsx +28 -4
- package/src/tests/Autofocus.test.tsx +38 -0
- package/src/tests/Bloom.test.tsx +76 -0
- package/src/tests/ChromaticAberration.test.tsx +50 -0
- package/src/tests/ColorDepth.test.tsx +71 -0
- package/src/tests/DepthOfField.test.tsx +130 -0
- package/src/tests/DepthPicking.test.tsx +250 -0
- package/src/tests/EffectComposer.test.tsx +501 -8
- package/src/tests/EffectGroup.test.tsx +337 -0
- package/src/tests/Glitch.test.tsx +56 -0
- package/src/tests/GodRays.test.tsx +101 -0
- package/src/tests/Grid.test.tsx +34 -0
- package/src/tests/LUT.test.tsx +64 -0
- package/src/tests/N8AO.test.tsx +92 -0
- package/src/tests/Outline.test.tsx +122 -0
- package/src/tests/SSAO.test.tsx +140 -0
- package/src/tests/SelectiveBloom.test.tsx +48 -0
- package/src/tests/ShockWave.test.tsx +95 -0
- package/src/tests/TiltShift.test.tsx +76 -0
- package/src/tests/createEffectComponent.test.tsx +286 -0
- package/src/tests/effects.smoke.test.tsx +46 -42
- package/src/util.tsx +148 -23
- package/src/wrapEffect.tsx +3 -2
- 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
|
+
})
|