@react-three/postprocessing 3.0.5 → 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.
- 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 +4 -3
- 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
package/src/effects/Texture.tsx
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { useLoader } from '@react-three/fiber'
|
|
2
2
|
import { TextureEffect } from 'postprocessing'
|
|
3
|
-
import { Ref
|
|
3
|
+
import type { Ref } from 'react'
|
|
4
|
+
import { useLayoutEffect } from 'react'
|
|
4
5
|
import { RepeatWrapping, SRGBColorSpace, TextureLoader } from 'three'
|
|
5
|
-
import {
|
|
6
|
+
import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
const TextureImpl = /* @__PURE__ */ createEffectComponent<typeof TextureEffect, EffectOptions<typeof TextureEffect>>(
|
|
9
|
+
TextureEffect
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
export type TextureProps = EffectOptions<typeof TextureEffect> & {
|
|
8
13
|
textureSrc: string
|
|
9
14
|
/** opacity of provided texture */
|
|
10
15
|
opacity?: number
|
|
11
16
|
ref?: Ref<TextureEffect>
|
|
12
17
|
}
|
|
13
18
|
|
|
14
|
-
export function Texture({ textureSrc, texture, opacity = 1,
|
|
19
|
+
export function Texture({ textureSrc, texture, opacity = 1, ...props }: TextureProps) {
|
|
15
20
|
const t = useLoader(TextureLoader, textureSrc)
|
|
16
21
|
|
|
17
22
|
useLayoutEffect(() => {
|
|
@@ -19,9 +24,5 @@ export function Texture({ textureSrc, texture, opacity = 1, ref, ...props }: Tex
|
|
|
19
24
|
t.wrapS = t.wrapT = RepeatWrapping
|
|
20
25
|
}, [t])
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
useDispose(effect)
|
|
25
|
-
|
|
26
|
-
return <primitive ref={ref} object={effect} blendMode-opacity-value={opacity} />
|
|
27
|
+
return <TextureImpl {...props} texture={texture ?? t} opacity={opacity} />
|
|
27
28
|
}
|
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import { BlendFunction, TiltShiftEffect } from 'postprocessing'
|
|
2
|
-
import {
|
|
2
|
+
import type { Ref } from 'react'
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
|
|
3
5
|
|
|
4
|
-
|
|
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
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BlendFunction, Effect, EffectAttribute } from 'postprocessing'
|
|
2
|
+
import type { Ref } from 'react'
|
|
2
3
|
import { Uniform } from 'three'
|
|
3
|
-
import {
|
|
4
|
+
import { createEffectComponent } from '../createEffectComponent'
|
|
4
5
|
|
|
5
6
|
const TiltShiftShader = {
|
|
6
7
|
fragmentShader: /* glsl */ `
|
|
@@ -62,20 +63,22 @@ const TiltShiftShader = {
|
|
|
62
63
|
`,
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
type Vec2Tuple = [number, number]
|
|
67
|
+
|
|
65
68
|
export class TiltShiftEffect extends Effect {
|
|
66
69
|
constructor({
|
|
67
70
|
blendFunction = BlendFunction.NORMAL,
|
|
68
71
|
blur = 0.15, // [0, 1], can go beyond 1 for extra
|
|
69
72
|
taper = 0.5, // [0, 1], can go beyond 1 for extra
|
|
70
|
-
start = [0.5, 0.0], // [0,1] percentage x,y of screenspace
|
|
71
|
-
end = [0.5, 1.0], // [0,1] percentage x,y of screenspace
|
|
73
|
+
start = [0.5, 0.0] as Vec2Tuple, // [0,1] percentage x,y of screenspace
|
|
74
|
+
end = [0.5, 1.0] as Vec2Tuple, // [0,1] percentage x,y of screenspace
|
|
72
75
|
samples = 10.0, // number of blur samples
|
|
73
|
-
direction = [1, 1], // direction of blur
|
|
76
|
+
direction = [1, 1] as Vec2Tuple, // direction of blur
|
|
74
77
|
} = {}) {
|
|
75
78
|
super('TiltShiftEffect', TiltShiftShader.fragmentShader, {
|
|
76
79
|
blendFunction,
|
|
77
80
|
attributes: EffectAttribute.CONVOLUTION,
|
|
78
|
-
uniforms: new Map<string, Uniform<number |
|
|
81
|
+
uniforms: new Map<string, Uniform<number | Vec2Tuple>>([
|
|
79
82
|
['blur', new Uniform(blur)],
|
|
80
83
|
['taper', new Uniform(taper)],
|
|
81
84
|
['start', new Uniform(start)],
|
|
@@ -85,6 +88,69 @@ export class TiltShiftEffect extends Effect {
|
|
|
85
88
|
]),
|
|
86
89
|
})
|
|
87
90
|
}
|
|
91
|
+
|
|
92
|
+
private u<T>(name: string): T {
|
|
93
|
+
return this.uniforms.get(name)!.value
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private setU(name: string, value: unknown): void {
|
|
97
|
+
this.uniforms.get(name)!.value = value
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get blur(): number {
|
|
101
|
+
return this.u('blur')
|
|
102
|
+
}
|
|
103
|
+
set blur(value: number) {
|
|
104
|
+
this.setU('blur', value)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get taper(): number {
|
|
108
|
+
return this.u('taper')
|
|
109
|
+
}
|
|
110
|
+
set taper(value: number) {
|
|
111
|
+
this.setU('taper', value)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get start(): Vec2Tuple {
|
|
115
|
+
return this.u('start')
|
|
116
|
+
}
|
|
117
|
+
set start(value: Vec2Tuple) {
|
|
118
|
+
this.setU('start', value)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
get end(): Vec2Tuple {
|
|
122
|
+
return this.u('end')
|
|
123
|
+
}
|
|
124
|
+
set end(value: Vec2Tuple) {
|
|
125
|
+
this.setU('end', value)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
get samples(): number {
|
|
129
|
+
return this.u('samples')
|
|
130
|
+
}
|
|
131
|
+
set samples(value: number) {
|
|
132
|
+
this.setU('samples', value)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get direction(): Vec2Tuple {
|
|
136
|
+
return this.u('direction')
|
|
137
|
+
}
|
|
138
|
+
set direction(value: Vec2Tuple) {
|
|
139
|
+
this.setU('direction', value)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export type TiltShift2Props = {
|
|
144
|
+
blendFunction?: BlendFunction
|
|
145
|
+
blur?: number
|
|
146
|
+
taper?: number
|
|
147
|
+
start?: Vec2Tuple
|
|
148
|
+
end?: Vec2Tuple
|
|
149
|
+
samples?: number
|
|
150
|
+
direction?: Vec2Tuple
|
|
151
|
+
ref?: Ref<TiltShiftEffect>
|
|
88
152
|
}
|
|
89
153
|
|
|
90
|
-
export const TiltShift2 = /* @__PURE__ */
|
|
154
|
+
export const TiltShift2 = /* @__PURE__ */ createEffectComponent<typeof TiltShiftEffect, TiltShift2Props>(
|
|
155
|
+
TiltShiftEffect
|
|
156
|
+
)
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { ToneMappingEffect } from 'postprocessing'
|
|
2
|
-
import
|
|
2
|
+
import type { Ref } from 'react'
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
type ToneMappingOptions = EffectOptions<typeof ToneMappingEffect>
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
const ToneMappingImpl = /* @__PURE__ */ createEffectComponent<typeof ToneMappingEffect, ToneMappingOptions>(
|
|
9
|
+
ToneMappingEffect
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
export type ToneMappingProps = ToneMappingOptions & { opacity?: number; ref?: Ref<ToneMappingEffect> }
|
|
13
|
+
|
|
14
|
+
// minLuminance/maxLuminance have no live setter in postprocessing - routed
|
|
15
|
+
// through args so they still work as plain props.
|
|
16
|
+
export function ToneMapping({ minLuminance, maxLuminance, ...liveProps }: ToneMappingProps) {
|
|
17
|
+
const args = useMemo<[ToneMappingOptions]>(() => [{ minLuminance, maxLuminance }], [minLuminance, maxLuminance])
|
|
18
|
+
return <ToneMappingImpl args={args} {...liveProps} />
|
|
19
|
+
}
|
package/src/effects/Vignette.tsx
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { VignetteEffect } from 'postprocessing'
|
|
2
|
-
import {
|
|
2
|
+
import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
|
|
3
3
|
|
|
4
|
-
export const Vignette = /* @__PURE__ */
|
|
4
|
+
export const Vignette = /* @__PURE__ */ createEffectComponent<
|
|
5
|
+
typeof VignetteEffect,
|
|
6
|
+
EffectOptions<typeof VignetteEffect>
|
|
7
|
+
>(VignetteEffect)
|
package/src/effects/Water.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BlendFunction, Effect, EffectAttribute } from 'postprocessing'
|
|
2
|
+
import type { Ref } from 'react'
|
|
2
3
|
import { Uniform } from 'three'
|
|
3
|
-
import {
|
|
4
|
+
import { createEffectComponent } from '../createEffectComponent'
|
|
4
5
|
|
|
5
6
|
const WaterShader = {
|
|
6
7
|
fragmentShader: /* glsl */ `
|
|
@@ -10,7 +11,7 @@ const WaterShader = {
|
|
|
10
11
|
vec2 vUv = uv;
|
|
11
12
|
float frequency = 6.0 * factor;
|
|
12
13
|
float amplitude = 0.015 * factor;
|
|
13
|
-
float x = vUv.y * frequency + time * 0.7;
|
|
14
|
+
float x = vUv.y * frequency + time * 0.7;
|
|
14
15
|
float y = vUv.x * frequency + time * 0.3;
|
|
15
16
|
vUv.x += cos(x + y) * amplitude * cos(y);
|
|
16
17
|
vUv.y += sin(x - y) * amplitude * cos(y);
|
|
@@ -25,11 +26,25 @@ export class WaterEffectImpl extends Effect {
|
|
|
25
26
|
super('WaterEffect', WaterShader.fragmentShader, {
|
|
26
27
|
blendFunction,
|
|
27
28
|
attributes: EffectAttribute.CONVOLUTION,
|
|
28
|
-
uniforms: new Map<string, Uniform<number
|
|
29
|
+
uniforms: new Map<string, Uniform<number>>([['factor', new Uniform(factor)]]),
|
|
29
30
|
})
|
|
30
31
|
}
|
|
32
|
+
|
|
33
|
+
get factor(): number {
|
|
34
|
+
return this.uniforms.get('factor')!.value
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
set factor(value: number) {
|
|
38
|
+
this.uniforms.get('factor')!.value = value
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type WaterEffectProps = {
|
|
43
|
+
blendFunction?: BlendFunction
|
|
44
|
+
factor?: number
|
|
45
|
+
ref?: Ref<WaterEffectImpl>
|
|
31
46
|
}
|
|
32
47
|
|
|
33
|
-
export const WaterEffect = /* @__PURE__ */
|
|
34
|
-
|
|
35
|
-
|
|
48
|
+
export const WaterEffect = /* @__PURE__ */ createEffectComponent<typeof WaterEffectImpl, WaterEffectProps>(
|
|
49
|
+
WaterEffectImpl
|
|
50
|
+
)
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export * from './createEffectComponent'
|
|
1
2
|
export * from './EffectComposer'
|
|
3
|
+
export * from './EffectGroup'
|
|
2
4
|
export * from './Selection'
|
|
3
5
|
export * from './util'
|
|
4
6
|
export * from './wrapEffect'
|
|
@@ -37,5 +39,5 @@ export * from './effects/ToneMapping'
|
|
|
37
39
|
export * from './effects/Vignette'
|
|
38
40
|
export * from './effects/Water'
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
export * from './
|
|
42
|
+
export * from './passes/DepthPicking'
|
|
43
|
+
export * from './passes/N8AO'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useThree } from '@react-three/fiber'
|
|
2
|
+
import { CopyPass, DepthPickingPass as DepthPickingPassImpl } from 'postprocessing'
|
|
3
|
+
import { use, useCallback, useEffect, useImperativeHandle, useState, type Ref } from 'react'
|
|
4
|
+
import type { Camera } from 'three'
|
|
5
|
+
import { Vector2, Vector3 } from 'three'
|
|
6
|
+
import { EffectComposerContext } from '../EffectComposer'
|
|
7
|
+
import { useDispose } from '../util'
|
|
8
|
+
|
|
9
|
+
export type DepthPickingApi = {
|
|
10
|
+
readDepth: (ndc: Vector2 | Vector3) => Promise<number>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type DepthPickingProps = {
|
|
14
|
+
ref?: Ref<DepthPickingApi>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Mounts a vanilla `postprocessing` DepthPickingPass and exposes its
|
|
18
|
+
// `readDepth` via ref - nothing else. Renders nothing. Pair with
|
|
19
|
+
// `useDepthPicking` for a world-space position instead of raw depth.
|
|
20
|
+
export function DepthPicking({ ref }: DepthPickingProps) {
|
|
21
|
+
const { composer } = use(EffectComposerContext)
|
|
22
|
+
|
|
23
|
+
const [depthPickingPass] = useState(() => new DepthPickingPassImpl())
|
|
24
|
+
const [copyPass] = useState(() => new CopyPass())
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
composer.addPass(depthPickingPass)
|
|
27
|
+
composer.addPass(copyPass)
|
|
28
|
+
return () => {
|
|
29
|
+
composer.removePass(depthPickingPass)
|
|
30
|
+
composer.removePass(copyPass)
|
|
31
|
+
}
|
|
32
|
+
}, [composer, depthPickingPass, copyPass])
|
|
33
|
+
|
|
34
|
+
useDispose(depthPickingPass)
|
|
35
|
+
useDispose(copyPass)
|
|
36
|
+
|
|
37
|
+
useImperativeHandle(ref, () => ({ readDepth: (ndc) => depthPickingPass.readDepth(ndc) }), [depthPickingPass])
|
|
38
|
+
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useDepthPicking(pass: React.RefObject<DepthPickingApi | null>, camera?: Camera) {
|
|
43
|
+
const composerCamera = use(EffectComposerContext)?.camera
|
|
44
|
+
const defaultCamera = useThree((state) => state.camera)
|
|
45
|
+
const resolvedCamera = camera ?? composerCamera ?? defaultCamera
|
|
46
|
+
const [ndc] = useState(() => new Vector3())
|
|
47
|
+
|
|
48
|
+
return useCallback(
|
|
49
|
+
async (x: number, y: number): Promise<Vector3 | false> => {
|
|
50
|
+
if (!pass.current) return false
|
|
51
|
+
ndc.x = x
|
|
52
|
+
ndc.y = y
|
|
53
|
+
ndc.z = await pass.current.readDepth(ndc)
|
|
54
|
+
ndc.z = ndc.z * 2.0 - 1.0
|
|
55
|
+
const hit = 1 - ndc.z > 0.0000001 // missed if ndc.z is close to 1
|
|
56
|
+
// clone - unproject mutates in place, and ndc is reused across calls,
|
|
57
|
+
// so returning it directly would hand out a reference that changes
|
|
58
|
+
// under the caller on the next pick.
|
|
59
|
+
return hit ? ndc.clone().unproject(resolvedCamera) : false
|
|
60
|
+
},
|
|
61
|
+
[pass, resolvedCamera, ndc]
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -5,9 +5,11 @@ import { ReactThreeFiber, applyProps, useThree } from '@react-three/fiber'
|
|
|
5
5
|
import { Ref, useLayoutEffect, useMemo } from 'react'
|
|
6
6
|
/* @ts-ignore */
|
|
7
7
|
import { N8AOPostPass } from 'n8ao'
|
|
8
|
+
import { groupPasses } from '../EffectComposer'
|
|
8
9
|
import { useDispose } from '../util'
|
|
9
10
|
|
|
10
11
|
export type N8AOProps = {
|
|
12
|
+
enabled?: boolean
|
|
11
13
|
aoRadius?: number
|
|
12
14
|
distanceFalloff?: number
|
|
13
15
|
intensity?: number
|
|
@@ -24,6 +26,7 @@ export type N8AOProps = {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export function N8AO({
|
|
29
|
+
enabled = true,
|
|
27
30
|
halfRes,
|
|
28
31
|
screenSpaceRadius,
|
|
29
32
|
quality,
|
|
@@ -38,12 +41,26 @@ export function N8AO({
|
|
|
38
41
|
renderMode = 0,
|
|
39
42
|
ref,
|
|
40
43
|
}: N8AOProps) {
|
|
41
|
-
const { camera, scene } = useThree()
|
|
42
|
-
const effect = useMemo(() =>
|
|
44
|
+
const { camera, scene, invalidate } = useThree()
|
|
45
|
+
const effect = useMemo(() => {
|
|
46
|
+
const instance = new N8AOPostPass(scene, camera)
|
|
47
|
+
// N8AOPostPass isn't a postprocessing Effect, so it's not mergeable via
|
|
48
|
+
// EffectGroup - it's already a standalone Pass, hence its own `enabled`
|
|
49
|
+
// prop below instead. Registering it here still gets it the shared
|
|
50
|
+
// trailing CopyPass safety net (see EffectComposer.tsx) so disabling it
|
|
51
|
+
// while it's the last pass in the chain doesn't blank the canvas.
|
|
52
|
+
groupPasses.add(instance)
|
|
53
|
+
return instance
|
|
54
|
+
}, [camera, scene])
|
|
43
55
|
|
|
44
56
|
// TODO: implement dispose upstream; this effect has memory leaks without
|
|
45
57
|
useDispose(effect)
|
|
46
58
|
|
|
59
|
+
useLayoutEffect(() => {
|
|
60
|
+
effect.enabled = enabled
|
|
61
|
+
invalidate()
|
|
62
|
+
}, [effect, enabled, invalidate])
|
|
63
|
+
|
|
47
64
|
useLayoutEffect(() => {
|
|
48
65
|
applyProps(effect.configuration, {
|
|
49
66
|
color,
|
|
@@ -58,6 +75,9 @@ export function N8AO({
|
|
|
58
75
|
halfRes,
|
|
59
76
|
depthAwareUpsampling,
|
|
60
77
|
})
|
|
78
|
+
// effect.configuration is a plain object, never r3f-managed - applyProps'
|
|
79
|
+
// own invalidate (gated behind object.__r3f) never fires for it.
|
|
80
|
+
invalidate()
|
|
61
81
|
}, [
|
|
62
82
|
screenSpaceRadius,
|
|
63
83
|
color,
|
|
@@ -71,11 +91,15 @@ export function N8AO({
|
|
|
71
91
|
halfRes,
|
|
72
92
|
depthAwareUpsampling,
|
|
73
93
|
effect,
|
|
94
|
+
invalidate,
|
|
74
95
|
])
|
|
75
96
|
|
|
76
97
|
useLayoutEffect(() => {
|
|
77
|
-
if (quality)
|
|
78
|
-
|
|
98
|
+
if (quality) {
|
|
99
|
+
effect.setQualityMode(quality.charAt(0).toUpperCase() + quality.slice(1))
|
|
100
|
+
invalidate()
|
|
101
|
+
}
|
|
102
|
+
}, [effect, quality, invalidate])
|
|
79
103
|
|
|
80
104
|
return <primitive ref={ref} object={effect} />
|
|
81
105
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DepthPickingPass, EffectComposer as EffectComposerImpl, EffectPass, RenderPass } from 'postprocessing'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import { EffectComposer } from '../EffectComposer'
|
|
5
|
+
import { Autofocus } from '../effects/Autofocus'
|
|
6
|
+
import { flush, root, strict, waitForComposer } from './test-utils'
|
|
7
|
+
|
|
8
|
+
describe('Autofocus', () => {
|
|
9
|
+
it('inserts depthPickingPass right after RenderPass, not appended at the end', async () => {
|
|
10
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
11
|
+
|
|
12
|
+
await React.act(async () => root.render(<EffectComposer ref={composerRef}>{<Autofocus />}</EffectComposer>))
|
|
13
|
+
const composer = await waitForComposer(composerRef)
|
|
14
|
+
await flush()
|
|
15
|
+
|
|
16
|
+
expect(composer.passes[0]).toBeInstanceOf(RenderPass)
|
|
17
|
+
expect(composer.passes[1]).toBeInstanceOf(DepthPickingPass)
|
|
18
|
+
|
|
19
|
+
await React.act(async () => root.render(null))
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('never lets depthPickingPass own renderToScreen, regardless of StrictMode double-invocation', async () => {
|
|
23
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
24
|
+
|
|
25
|
+
await React.act(async () => root.render(strict(<EffectComposer ref={composerRef}>{<Autofocus />}</EffectComposer>)))
|
|
26
|
+
const composer = await waitForComposer(composerRef)
|
|
27
|
+
for (let i = 0; i < 10; i++) await flush()
|
|
28
|
+
|
|
29
|
+
const depthPickingPass = composer.passes.find((p) => p instanceof DepthPickingPass)!
|
|
30
|
+
expect(depthPickingPass.renderToScreen).toBe(false)
|
|
31
|
+
|
|
32
|
+
// The real, visible output (DepthOfField's own EffectPass) must own it instead.
|
|
33
|
+
const effectPass = composer.passes.find((p) => p instanceof EffectPass)!
|
|
34
|
+
expect(effectPass.renderToScreen).toBe(true)
|
|
35
|
+
|
|
36
|
+
await React.act(async () => root.render(null))
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { BloomEffect, EffectComposer as EffectComposerImpl } from 'postprocessing'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import { EffectComposer } from '../EffectComposer'
|
|
5
|
+
import { Bloom } from '../effects/Bloom'
|
|
6
|
+
import { flush, root } from './test-utils'
|
|
7
|
+
|
|
8
|
+
describe('Bloom', () => {
|
|
9
|
+
it('applies intensity live, without reconstructing the effect', async () => {
|
|
10
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
11
|
+
const ref = React.createRef<BloomEffect>()
|
|
12
|
+
|
|
13
|
+
const render = (intensity: number) =>
|
|
14
|
+
root.render(
|
|
15
|
+
<EffectComposer ref={composerRef}>
|
|
16
|
+
<Bloom ref={ref} intensity={intensity} />
|
|
17
|
+
</EffectComposer>
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
await React.act(async () => render(1))
|
|
21
|
+
await flush()
|
|
22
|
+
const first = ref.current
|
|
23
|
+
expect(first!.intensity).toBe(1)
|
|
24
|
+
|
|
25
|
+
await React.act(async () => render(2))
|
|
26
|
+
await flush()
|
|
27
|
+
|
|
28
|
+
expect(ref.current).toBe(first)
|
|
29
|
+
expect(ref.current!.intensity).toBe(2)
|
|
30
|
+
|
|
31
|
+
await React.act(async () => root.render(null))
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('applies mipmapBlur (a construction-only option) as a plain prop, reconstructing under the hood', async () => {
|
|
35
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
36
|
+
const ref = React.createRef<BloomEffect>()
|
|
37
|
+
|
|
38
|
+
const render = (mipmapBlur: boolean) =>
|
|
39
|
+
root.render(
|
|
40
|
+
<EffectComposer ref={composerRef}>
|
|
41
|
+
<Bloom ref={ref} mipmapBlur={mipmapBlur} />
|
|
42
|
+
</EffectComposer>
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
await React.act(async () => render(true))
|
|
46
|
+
await flush()
|
|
47
|
+
const first = ref.current
|
|
48
|
+
expect(first!.mipmapBlurPass.enabled).toBe(true)
|
|
49
|
+
|
|
50
|
+
await React.act(async () => render(false))
|
|
51
|
+
await flush()
|
|
52
|
+
|
|
53
|
+
expect(ref.current).not.toBe(first)
|
|
54
|
+
expect(ref.current!.mipmapBlurPass.enabled).toBe(false)
|
|
55
|
+
|
|
56
|
+
await React.act(async () => root.render(null))
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('accepts opacity, as documented in the README (#opacity narrower than createEffectComponent allows)', async () => {
|
|
60
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
61
|
+
const ref = React.createRef<BloomEffect>()
|
|
62
|
+
|
|
63
|
+
await React.act(async () =>
|
|
64
|
+
root.render(
|
|
65
|
+
<EffectComposer ref={composerRef}>
|
|
66
|
+
<Bloom ref={ref} opacity={0.02} />
|
|
67
|
+
</EffectComposer>
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
await flush()
|
|
71
|
+
|
|
72
|
+
expect(ref.current!.blendMode.opacity.value).toBe(0.02)
|
|
73
|
+
|
|
74
|
+
await React.act(async () => root.render(null))
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -28,4 +28,54 @@ describe('ChromaticAberration', () => {
|
|
|
28
28
|
|
|
29
29
|
await React.act(async () => root.render(null))
|
|
30
30
|
})
|
|
31
|
+
|
|
32
|
+
it('applies offset live without reconstructing the effect', async () => {
|
|
33
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
34
|
+
const ref = React.createRef<ChromaticAberrationEffect>()
|
|
35
|
+
|
|
36
|
+
const render = (x: number) =>
|
|
37
|
+
root.render(
|
|
38
|
+
<EffectComposer ref={composerRef}>
|
|
39
|
+
<ChromaticAberration ref={ref} offset={[x, 0.001]} />
|
|
40
|
+
</EffectComposer>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
await React.act(async () => render(0.01))
|
|
44
|
+
await flush()
|
|
45
|
+
const first = ref.current
|
|
46
|
+
expect(first!.offset.x).toBeCloseTo(0.01)
|
|
47
|
+
|
|
48
|
+
await React.act(async () => render(0.02))
|
|
49
|
+
await flush()
|
|
50
|
+
|
|
51
|
+
expect(ref.current).toBe(first)
|
|
52
|
+
expect(ref.current!.offset.x).toBeCloseTo(0.02)
|
|
53
|
+
|
|
54
|
+
await React.act(async () => root.render(null))
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('applies radialModulation live, without reconstructing the effect', async () => {
|
|
58
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
59
|
+
const ref = React.createRef<ChromaticAberrationEffect>()
|
|
60
|
+
|
|
61
|
+
const render = (radialModulation: boolean) =>
|
|
62
|
+
root.render(
|
|
63
|
+
<EffectComposer ref={composerRef}>
|
|
64
|
+
<ChromaticAberration ref={ref} radialModulation={radialModulation} />
|
|
65
|
+
</EffectComposer>
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
await React.act(async () => render(false))
|
|
69
|
+
await flush()
|
|
70
|
+
const first = ref.current
|
|
71
|
+
expect(first!.radialModulation).toBe(false)
|
|
72
|
+
|
|
73
|
+
await React.act(async () => render(true))
|
|
74
|
+
await flush()
|
|
75
|
+
|
|
76
|
+
expect(ref.current).toBe(first)
|
|
77
|
+
expect(ref.current!.radialModulation).toBe(true)
|
|
78
|
+
|
|
79
|
+
await React.act(async () => root.render(null))
|
|
80
|
+
})
|
|
31
81
|
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ColorDepthEffect, EffectComposer as EffectComposerImpl } from 'postprocessing'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import { ColorDepth } from '../effects/ColorDepth'
|
|
5
|
+
import { EffectComposer } from '../EffectComposer'
|
|
6
|
+
import { flush, root } from './test-utils'
|
|
7
|
+
|
|
8
|
+
describe('ColorDepth', () => {
|
|
9
|
+
it('applies bits live via the differently-named bitDepth setter, without reconstructing the effect', async () => {
|
|
10
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
11
|
+
const ref = React.createRef<ColorDepthEffect>()
|
|
12
|
+
|
|
13
|
+
const render = (bits: number) =>
|
|
14
|
+
root.render(
|
|
15
|
+
<EffectComposer ref={composerRef}>
|
|
16
|
+
<ColorDepth ref={ref} bits={bits} />
|
|
17
|
+
</EffectComposer>
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
await React.act(async () => render(4))
|
|
21
|
+
await flush()
|
|
22
|
+
const first = ref.current
|
|
23
|
+
expect(first!.bitDepth).toBe(4)
|
|
24
|
+
|
|
25
|
+
await React.act(async () => render(8))
|
|
26
|
+
await flush()
|
|
27
|
+
|
|
28
|
+
expect(ref.current).toBe(first)
|
|
29
|
+
expect(ref.current!.bitDepth).toBe(8)
|
|
30
|
+
|
|
31
|
+
await React.act(async () => root.render(null))
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('resets bitDepth to its constructor default when bits is removed', async () => {
|
|
35
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
36
|
+
const ref = React.createRef<ColorDepthEffect>()
|
|
37
|
+
|
|
38
|
+
await React.act(async () =>
|
|
39
|
+
root.render(
|
|
40
|
+
<EffectComposer ref={composerRef}>
|
|
41
|
+
<ColorDepth ref={ref} />
|
|
42
|
+
</EffectComposer>
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
await flush()
|
|
46
|
+
const defaultBitDepth = ref.current!.bitDepth
|
|
47
|
+
|
|
48
|
+
await React.act(async () =>
|
|
49
|
+
root.render(
|
|
50
|
+
<EffectComposer ref={composerRef}>
|
|
51
|
+
<ColorDepth ref={ref} bits={4} />
|
|
52
|
+
</EffectComposer>
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
await flush()
|
|
56
|
+
expect(ref.current!.bitDepth).toBe(4)
|
|
57
|
+
|
|
58
|
+
await React.act(async () =>
|
|
59
|
+
root.render(
|
|
60
|
+
<EffectComposer ref={composerRef}>
|
|
61
|
+
<ColorDepth ref={ref} />
|
|
62
|
+
</EffectComposer>
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
await flush()
|
|
66
|
+
|
|
67
|
+
expect(ref.current!.bitDepth).toBe(defaultBitDepth)
|
|
68
|
+
|
|
69
|
+
await React.act(async () => root.render(null))
|
|
70
|
+
})
|
|
71
|
+
})
|