@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,74 @@
|
|
|
1
|
+
import { Effect, EffectPass } from 'postprocessing'
|
|
2
|
+
import { use, useImperativeHandle, useLayoutEffect, useRef, useState, type ReactNode, type Ref } from 'react'
|
|
3
|
+
import type { Group } from 'three'
|
|
4
|
+
import { disposePassWithoutEffects, EffectComposerContext, groupPasses } from './EffectComposer'
|
|
5
|
+
import { readGroupChildren, updateIfChanged } from './util'
|
|
6
|
+
|
|
7
|
+
export type EffectGroupProps = {
|
|
8
|
+
/** Toggles the whole pass, like Pass.enabled in vanilla postprocessing - cheap, no reconstruction. */
|
|
9
|
+
enabled?: boolean
|
|
10
|
+
children: ReactNode
|
|
11
|
+
ref?: Ref<EffectPass>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Groups its children into one EffectPass instead of relying on
|
|
15
|
+
// EffectComposer's automatic consecutive-effect merging. Renders children
|
|
16
|
+
// into a hidden inner <group> (invisible to EffectComposer's own top-level
|
|
17
|
+
// walk) and renders the resulting pass back out via <primitive> at this
|
|
18
|
+
// component's own JSX position, so EffectComposer's existing bare-Pass
|
|
19
|
+
// passthrough places it correctly relative to sibling effects.
|
|
20
|
+
export function EffectGroup({ enabled = true, children, ref }: EffectGroupProps) {
|
|
21
|
+
const { camera, requestRebuild } = use(EffectComposerContext)
|
|
22
|
+
|
|
23
|
+
const group = useRef<Group>(null!)
|
|
24
|
+
const effectsRef = useRef<Effect[]>([])
|
|
25
|
+
const [pass, setPass] = useState<EffectPass | null>(null)
|
|
26
|
+
|
|
27
|
+
// Mirrors EffectComposer's own buildPasses: always constructs a fresh
|
|
28
|
+
// EffectPass when the effect list changes, rather than updating one in
|
|
29
|
+
// place - so composer.addPass() runs its normal setSize/initialize dance
|
|
30
|
+
// for every effect, with nothing to replicate by hand here.
|
|
31
|
+
useLayoutEffect(() => {
|
|
32
|
+
const effects = readGroupChildren(group.current, (object): object is Effect => object instanceof Effect)
|
|
33
|
+
if (!updateIfChanged(effectsRef, effects)) return
|
|
34
|
+
|
|
35
|
+
if (!effects.length) {
|
|
36
|
+
setPass(null)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const newPass = new EffectPass(camera, ...effects)
|
|
41
|
+
groupPasses.add(newPass)
|
|
42
|
+
setPass(newPass)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// useLayoutEffect (not useEffect) so `enabled` is applied before
|
|
46
|
+
// EffectComposer's own effect adds this pass to the composer.
|
|
47
|
+
useLayoutEffect(() => {
|
|
48
|
+
if (pass) pass.enabled = enabled
|
|
49
|
+
}, [pass, enabled])
|
|
50
|
+
|
|
51
|
+
// A local state update above doesn't re-render EffectComposer, so its own
|
|
52
|
+
// tree walk would never notice this pass appearing/disappearing.
|
|
53
|
+
useLayoutEffect(() => {
|
|
54
|
+
requestRebuild()
|
|
55
|
+
}, [pass, requestRebuild])
|
|
56
|
+
|
|
57
|
+
// Disposes whichever pass this replaces (or the last one, on unmount) -
|
|
58
|
+
// the only place a pass gets disposed, so there's no double-dispose risk
|
|
59
|
+
// from also doing it inline above where `pass` is replaced.
|
|
60
|
+
useLayoutEffect(() => {
|
|
61
|
+
return () => {
|
|
62
|
+
if (pass) disposePassWithoutEffects(pass)
|
|
63
|
+
}
|
|
64
|
+
}, [pass])
|
|
65
|
+
|
|
66
|
+
useImperativeHandle(ref, () => pass as EffectPass, [pass])
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<>
|
|
70
|
+
<group ref={group}>{children}</group>
|
|
71
|
+
{pass && <primitive object={pass} />}
|
|
72
|
+
</>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { extend, useThree } from '@react-three/fiber'
|
|
2
|
+
import type { BlendFunction, Effect, Pass } from 'postprocessing'
|
|
3
|
+
import type { ExoticComponent, JSX, Ref } from 'react'
|
|
4
|
+
import { useRef } from 'react'
|
|
5
|
+
import { useLiveDefaults, useMergeRefs } from './util'
|
|
6
|
+
|
|
7
|
+
export type EffectConstructor = new (...args: any[]) => Effect | Pass
|
|
8
|
+
|
|
9
|
+
// The effect's own options type, straight off its constructor - postprocessing
|
|
10
|
+
// already types every effect's sole options object precisely (either inline or,
|
|
11
|
+
// like BloomEffect, as a named exported type); this just strips the `| undefined`
|
|
12
|
+
// that comes from the parameter being optional.
|
|
13
|
+
export type EffectOptions<T extends EffectConstructor> = NonNullable<ConstructorParameters<T>[0]>
|
|
14
|
+
|
|
15
|
+
const components = new WeakMap<EffectConstructor, ExoticComponent<any> | string>()
|
|
16
|
+
let i = 0
|
|
17
|
+
|
|
18
|
+
const BLEND_KEYS = ['blendMode-blendFunction', 'blendMode-opacity-value']
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Registers `effect` as a JSX intrinsic once per class and returns a
|
|
22
|
+
* component that renders it. Everything else - construction from `args`,
|
|
23
|
+
* live prop application (with the same Color/Vector coercion and reset-
|
|
24
|
+
* to-default on removal any r3f element gets), disposal - is r3f's own
|
|
25
|
+
* reconciler, same rules as `<mesh>`/`<meshStandardMaterial>`. Only fits
|
|
26
|
+
* effects whose constructor works with zero arguments (`new Effect()`) -
|
|
27
|
+
* r3f's own reset-on-removal falls back to `0` otherwise, which is wrong
|
|
28
|
+
* for anything non-numeric. Effects that require e.g. scene/camera stay
|
|
29
|
+
* hand-rolled (see Outline.tsx, SelectiveBloom.tsx, ShockWave.tsx).
|
|
30
|
+
*
|
|
31
|
+
* `blendFunction`/`opacity` are pierced through to `blendMode-*` - every
|
|
32
|
+
* `Effect` has them on a nested `blendMode`, not on the effect itself, so a
|
|
33
|
+
* plain top-level prop would silently land on a stray, unread property.
|
|
34
|
+
* Applied via useLiveDefaults, not as plain JSX props: BlendMode's own
|
|
35
|
+
* constructor requires `blendFunction` (no default), so its constructor
|
|
36
|
+
* length isn't 0 either, and r3f's native reset-on-removal falls back to
|
|
37
|
+
* `changedProps[prop] = 0` - which is BlendFunction.SKIP, not a merely
|
|
38
|
+
* "wrong" blend function but one that hides the effect entirely.
|
|
39
|
+
*/
|
|
40
|
+
export function createEffectComponent<T extends EffectConstructor, P extends object>(
|
|
41
|
+
effect: T
|
|
42
|
+
): (
|
|
43
|
+
props: P & {
|
|
44
|
+
blendFunction?: BlendFunction
|
|
45
|
+
opacity?: number
|
|
46
|
+
args?: ConstructorParameters<T>
|
|
47
|
+
ref?: Ref<InstanceType<T>>
|
|
48
|
+
}
|
|
49
|
+
) => JSX.Element {
|
|
50
|
+
return function EffectComponent({ blendFunction, opacity, ref, ...props }: any) {
|
|
51
|
+
let Component = components.get(effect)
|
|
52
|
+
|
|
53
|
+
if (!Component) {
|
|
54
|
+
const key = `@react-three/postprocessing/${effect.name}-${i++}`
|
|
55
|
+
extend({ [key]: effect })
|
|
56
|
+
components.set(effect, (Component = key))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const camera = useThree((state) => state.camera)
|
|
60
|
+
const localRef = useRef<InstanceType<T>>(null)
|
|
61
|
+
const setRef = useMergeRefs(localRef, ref)
|
|
62
|
+
|
|
63
|
+
useLiveDefaults(
|
|
64
|
+
localRef,
|
|
65
|
+
{ 'blendMode-blendFunction': blendFunction, 'blendMode-opacity-value': opacity },
|
|
66
|
+
BLEND_KEYS
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return <Component ref={setRef} camera={camera} {...props} />
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/effects/ASCII.tsx
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// https://twitter.com/emilwidlund/status/1652386482420609024
|
|
3
3
|
|
|
4
4
|
import { Effect } from 'postprocessing'
|
|
5
|
-
import { Ref
|
|
6
|
-
import { CanvasTexture, Color, NearestFilter, RepeatWrapping, Texture, Uniform } from 'three'
|
|
7
|
-
import {
|
|
5
|
+
import type { Ref } from 'react'
|
|
6
|
+
import { CanvasTexture, Color, type ColorRepresentation, NearestFilter, RepeatWrapping, Texture, Uniform } from 'three'
|
|
7
|
+
import { createEffectComponent } from '../createEffectComponent'
|
|
8
8
|
|
|
9
9
|
const fragment = /* glsl */ `
|
|
10
10
|
uniform sampler2D uCharacters;
|
|
@@ -47,17 +47,21 @@ const fragment = /* glsl */ `
|
|
|
47
47
|
}
|
|
48
48
|
`
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
export type ASCIIProps = {
|
|
51
51
|
font?: string
|
|
52
52
|
characters?: string
|
|
53
53
|
fontSize?: number
|
|
54
54
|
cellSize?: number
|
|
55
|
-
color?:
|
|
55
|
+
color?: ColorRepresentation
|
|
56
56
|
invert?: boolean
|
|
57
57
|
ref?: Ref<ASCIIEffect>
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
class ASCIIEffect extends Effect {
|
|
61
|
+
private _font: string
|
|
62
|
+
private _characters: string
|
|
63
|
+
private _fontSize: number
|
|
64
|
+
|
|
61
65
|
constructor({
|
|
62
66
|
font = 'arial',
|
|
63
67
|
characters = ` .:,'-^=*+?!|0#X%WM@`,
|
|
@@ -65,7 +69,7 @@ class ASCIIEffect extends Effect {
|
|
|
65
69
|
cellSize = 16,
|
|
66
70
|
color = '#ffffff',
|
|
67
71
|
invert = false,
|
|
68
|
-
}: Omit<
|
|
72
|
+
}: Omit<ASCIIProps, 'ref'> = {}) {
|
|
69
73
|
const uniforms = new Map<string, Uniform>([
|
|
70
74
|
['uCharacters', new Uniform(new Texture())],
|
|
71
75
|
['uCellSize', new Uniform(cellSize)],
|
|
@@ -76,11 +80,71 @@ class ASCIIEffect extends Effect {
|
|
|
76
80
|
|
|
77
81
|
super('ASCIIEffect', fragment, { uniforms })
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
this._font = font
|
|
84
|
+
this._characters = characters
|
|
85
|
+
this._fontSize = fontSize
|
|
86
|
+
this.updateCharactersTexture()
|
|
87
|
+
}
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
get cellSize(): number {
|
|
90
|
+
return this.uniforms.get('uCellSize')!.value
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
set cellSize(value: number) {
|
|
94
|
+
this.uniforms.get('uCellSize')!.value = value
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get invert(): boolean {
|
|
98
|
+
return this.uniforms.get('uInvert')!.value
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
set invert(value: boolean) {
|
|
102
|
+
this.uniforms.get('uInvert')!.value = value
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get color(): Color {
|
|
106
|
+
return this.uniforms.get('uColor')!.value
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
set color(value: ColorRepresentation) {
|
|
110
|
+
this.uniforms.get('uColor')!.value.set(value)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get font(): string {
|
|
114
|
+
return this._font
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
set font(value: string) {
|
|
118
|
+
this._font = value
|
|
119
|
+
this.updateCharactersTexture()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get characters(): string {
|
|
123
|
+
return this._characters
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
set characters(value: string) {
|
|
127
|
+
this._characters = value
|
|
128
|
+
this.uniforms.get('uCharactersCount')!.value = value.length
|
|
129
|
+
this.updateCharactersTexture()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get fontSize(): number {
|
|
133
|
+
return this._fontSize
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
set fontSize(value: number) {
|
|
137
|
+
this._fontSize = value
|
|
138
|
+
this.updateCharactersTexture()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Regenerates the character atlas texture - characters/font/fontSize have
|
|
142
|
+
// no cheaper live update path, unlike the plain-uniform props above.
|
|
143
|
+
private updateCharactersTexture(): void {
|
|
144
|
+
const uniform = this.uniforms.get('uCharacters')!
|
|
145
|
+
const previous = uniform.value as Texture
|
|
146
|
+
uniform.value = this.createCharactersTexture(this._characters, this._font, this._fontSize)
|
|
147
|
+
previous.dispose()
|
|
84
148
|
}
|
|
85
149
|
|
|
86
150
|
/** Draws the characters on a Canvas and returns a texture */
|
|
@@ -116,21 +180,4 @@ class ASCIIEffect extends Effect {
|
|
|
116
180
|
}
|
|
117
181
|
}
|
|
118
182
|
|
|
119
|
-
export
|
|
120
|
-
font = 'arial',
|
|
121
|
-
characters = ` .:,'-^=*+?!|0#X%WM@`,
|
|
122
|
-
fontSize = 54,
|
|
123
|
-
cellSize = 16,
|
|
124
|
-
color = '#ffffff',
|
|
125
|
-
invert = false,
|
|
126
|
-
ref,
|
|
127
|
-
}: IASCIIEffectProps) {
|
|
128
|
-
const effect = useMemo(
|
|
129
|
-
() => new ASCIIEffect({ characters, font, fontSize, cellSize, color, invert }),
|
|
130
|
-
[characters, fontSize, cellSize, color, invert, font]
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
useDispose(effect)
|
|
134
|
-
|
|
135
|
-
return <primitive ref={ref} object={effect} />
|
|
136
|
-
}
|
|
183
|
+
export const ASCII = /* @__PURE__ */ createEffectComponent<typeof ASCIIEffect, ASCIIProps>(ASCIIEffect)
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { createPortal, useFrame, useThree, type Vector3 as R3FVector3 } from '@react-three/fiber'
|
|
2
2
|
import { easing } from 'maath'
|
|
3
|
-
import {
|
|
3
|
+
import { DepthOfFieldEffect } from 'postprocessing'
|
|
4
4
|
import {
|
|
5
5
|
Ref,
|
|
6
6
|
useCallback,
|
|
7
|
-
useContext,
|
|
8
|
-
useEffect,
|
|
9
7
|
useImperativeHandle,
|
|
10
8
|
useMemo,
|
|
11
9
|
useRef,
|
|
@@ -15,27 +13,10 @@ import {
|
|
|
15
13
|
} from 'react'
|
|
16
14
|
import { Mesh, Vector3 } from 'three'
|
|
17
15
|
|
|
18
|
-
import {
|
|
16
|
+
import { DepthPicking, useDepthPicking, type DepthPickingApi } from '../passes/DepthPicking'
|
|
19
17
|
import { DepthOfField } from './DepthOfField'
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
// including these two, since they're added via composer.addPass below.
|
|
23
|
-
// When Autofocus unmounts alongside its ancestor EffectComposer (e.g. a
|
|
24
|
-
// full tree unmount), both the composer's own teardown AND this
|
|
25
|
-
// component's cleanup effect would dispose the same instances. Wrapping
|
|
26
|
-
// dispose here makes it safe no matter which caller gets there first.
|
|
27
|
-
function makeDisposeIdempotent<T extends { dispose: () => void }>(instance: T): T {
|
|
28
|
-
let disposed = false
|
|
29
|
-
const dispose = instance.dispose.bind(instance)
|
|
30
|
-
instance.dispose = () => {
|
|
31
|
-
if (disposed) return
|
|
32
|
-
disposed = true
|
|
33
|
-
dispose()
|
|
34
|
-
}
|
|
35
|
-
return instance
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type AutofocusProps = ComponentProps<typeof DepthOfField> & {
|
|
19
|
+
export type AutofocusProps = Omit<ComponentProps<typeof DepthOfField>, 'ref'> & {
|
|
39
20
|
target?: R3FVector3
|
|
40
21
|
/** should the target follow the pointer */
|
|
41
22
|
mouse?: boolean
|
|
@@ -64,50 +45,22 @@ export function Autofocus({
|
|
|
64
45
|
...props
|
|
65
46
|
}: AutofocusProps) {
|
|
66
47
|
const dofRef = useRef<DepthOfFieldEffect>(null)
|
|
67
|
-
const
|
|
68
|
-
const
|
|
48
|
+
const pickRef = useRef<DepthPickingApi>(null)
|
|
49
|
+
const getHit = useDepthPicking(pickRef)
|
|
50
|
+
const hitpointMarkerRef = useRef<Mesh>(null)
|
|
51
|
+
const dofTargetMarkerRef = useRef<Mesh>(null)
|
|
69
52
|
|
|
70
53
|
const scene = useThree(({ scene }) => scene)
|
|
71
54
|
const pointer = useThree(({ pointer }) => pointer)
|
|
72
|
-
const { composer, camera } = useContext(EffectComposerContext)
|
|
73
|
-
|
|
74
|
-
// see: https://codesandbox.io/s/depthpickingpass-x130hg
|
|
75
|
-
const [depthPickingPass] = useState(() => makeDisposeIdempotent(new DepthPickingPass()))
|
|
76
|
-
const [copyPass] = useState(() => makeDisposeIdempotent(new CopyPass()))
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
composer.addPass(depthPickingPass)
|
|
79
|
-
composer.addPass(copyPass)
|
|
80
|
-
return () => {
|
|
81
|
-
composer.removePass(depthPickingPass)
|
|
82
|
-
composer.removePass(copyPass)
|
|
83
|
-
}
|
|
84
|
-
}, [composer, depthPickingPass, copyPass])
|
|
85
|
-
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
return () => {
|
|
88
|
-
depthPickingPass.dispose()
|
|
89
|
-
copyPass.dispose()
|
|
90
|
-
}
|
|
91
|
-
}, [depthPickingPass, copyPass])
|
|
92
55
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
ndc.x = x
|
|
99
|
-
ndc.y = y
|
|
100
|
-
ndc.z = await depthPickingPass.readDepth(ndc)
|
|
101
|
-
ndc.z = ndc.z * 2.0 - 1.0
|
|
102
|
-
const hit = 1 - ndc.z > 0.0000001 // it is missed if ndc.z is close to 1
|
|
103
|
-
return hit ? ndc.unproject(camera) : false
|
|
104
|
-
},
|
|
105
|
-
[ndc, depthPickingPass, camera]
|
|
106
|
-
)
|
|
56
|
+
// A stable non-null value, purely to enable DepthOfField's own autoFocus
|
|
57
|
+
// mode (`target != null`) - the actual per-frame value is applied
|
|
58
|
+
// imperatively to dofRef.current.target below.
|
|
59
|
+
const [autoFocusMarker] = useState(() => new Vector3())
|
|
60
|
+
const [hitpoint] = useState(() => new Vector3())
|
|
107
61
|
|
|
108
62
|
const update = useCallback(
|
|
109
63
|
async (delta: number, updateTarget = true) => {
|
|
110
|
-
// Update hitpoint
|
|
111
64
|
if (target) {
|
|
112
65
|
hitpoint.set(...(target as unknown as [number, number, number]))
|
|
113
66
|
} else {
|
|
@@ -116,7 +69,6 @@ export function Autofocus({
|
|
|
116
69
|
if (hit) hitpoint.copy(hit)
|
|
117
70
|
}
|
|
118
71
|
|
|
119
|
-
// Update target
|
|
120
72
|
if (updateTarget && dofRef.current?.target) {
|
|
121
73
|
if (smoothTime > 0 && delta > 0) {
|
|
122
74
|
easing.damp3(dofRef.current.target, hitpoint, smoothTime, delta)
|
|
@@ -125,42 +77,37 @@ export function Autofocus({
|
|
|
125
77
|
}
|
|
126
78
|
}
|
|
127
79
|
},
|
|
128
|
-
[target, hitpoint, followMouse, getHit, smoothTime
|
|
80
|
+
[target, hitpoint, followMouse, pointer, getHit, smoothTime]
|
|
129
81
|
)
|
|
130
82
|
|
|
131
|
-
useFrame(
|
|
83
|
+
useFrame((_, delta) => {
|
|
132
84
|
if (!manual) {
|
|
133
85
|
update(delta)
|
|
134
86
|
}
|
|
135
|
-
if (
|
|
136
|
-
|
|
87
|
+
if (hitpointMarkerRef.current) {
|
|
88
|
+
hitpointMarkerRef.current.position.copy(hitpoint)
|
|
137
89
|
}
|
|
138
|
-
if (
|
|
139
|
-
|
|
90
|
+
if (dofTargetMarkerRef.current && dofRef.current?.target) {
|
|
91
|
+
dofTargetMarkerRef.current.position.copy(dofRef.current.target)
|
|
140
92
|
}
|
|
141
93
|
})
|
|
142
94
|
|
|
143
95
|
// Ref API
|
|
144
|
-
const api = useMemo<AutofocusApi>(
|
|
145
|
-
() => ({
|
|
146
|
-
dofRef,
|
|
147
|
-
hitpoint,
|
|
148
|
-
update,
|
|
149
|
-
}),
|
|
150
|
-
[hitpoint, update]
|
|
151
|
-
)
|
|
96
|
+
const api = useMemo<AutofocusApi>(() => ({ dofRef, hitpoint, update }), [hitpoint, update])
|
|
152
97
|
useImperativeHandle(ref, () => api, [api])
|
|
153
98
|
|
|
154
99
|
return (
|
|
155
100
|
<>
|
|
101
|
+
<DepthPicking ref={pickRef} />
|
|
102
|
+
|
|
156
103
|
{debug
|
|
157
104
|
? createPortal(
|
|
158
105
|
<>
|
|
159
|
-
<mesh ref={
|
|
106
|
+
<mesh ref={hitpointMarkerRef}>
|
|
160
107
|
<sphereGeometry args={[debug, 16, 16]} />
|
|
161
108
|
<meshBasicMaterial color="#00ff00" opacity={1} transparent depthWrite={false} />
|
|
162
109
|
</mesh>
|
|
163
|
-
<mesh ref={
|
|
110
|
+
<mesh ref={dofTargetMarkerRef}>
|
|
164
111
|
<sphereGeometry args={[debug / 2, 16, 16]} />
|
|
165
112
|
<meshBasicMaterial color="#00ff00" opacity={0.5} transparent depthWrite={false} />
|
|
166
113
|
</mesh>
|
|
@@ -169,7 +116,7 @@ export function Autofocus({
|
|
|
169
116
|
)
|
|
170
117
|
: null}
|
|
171
118
|
|
|
172
|
-
<DepthOfField ref={dofRef} {...props} target={
|
|
119
|
+
<DepthOfField ref={dofRef} {...props} target={autoFocusMarker} />
|
|
173
120
|
</>
|
|
174
121
|
)
|
|
175
122
|
}
|
package/src/effects/Bloom.tsx
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { BlendFunction, BloomEffect } 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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
type BloomOptions = EffectOptions<typeof BloomEffect>
|
|
7
|
+
|
|
8
|
+
const BloomImpl = /* @__PURE__ */ createEffectComponent<typeof BloomEffect, BloomOptions>(BloomEffect)
|
|
9
|
+
|
|
10
|
+
export type BloomProps = BloomOptions & { opacity?: number; ref?: Ref<BloomEffect> }
|
|
11
|
+
|
|
12
|
+
// luminanceThreshold/luminanceSmoothing/mipmapBlur/radius/levels/resolution*
|
|
13
|
+
// have no live setter in postprocessing - routed through args so they still
|
|
14
|
+
// work as plain props, just via reconstruction instead of mutation.
|
|
15
|
+
export function Bloom({
|
|
16
|
+
blendFunction = BlendFunction.ADD,
|
|
17
|
+
luminanceThreshold,
|
|
18
|
+
luminanceSmoothing,
|
|
19
|
+
mipmapBlur,
|
|
20
|
+
radius,
|
|
21
|
+
levels,
|
|
22
|
+
resolutionScale,
|
|
23
|
+
resolutionX,
|
|
24
|
+
resolutionY,
|
|
25
|
+
...liveProps
|
|
26
|
+
}: BloomProps) {
|
|
27
|
+
const args = useMemo<[BloomOptions]>(
|
|
28
|
+
() => [
|
|
29
|
+
{ luminanceThreshold, luminanceSmoothing, mipmapBlur, radius, levels, resolutionScale, resolutionX, resolutionY },
|
|
30
|
+
],
|
|
31
|
+
[luminanceThreshold, luminanceSmoothing, mipmapBlur, radius, levels, resolutionScale, resolutionX, resolutionY]
|
|
32
|
+
)
|
|
33
|
+
return <BloomImpl blendFunction={blendFunction} args={args} {...liveProps} />
|
|
34
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { BrightnessContrastEffect } from 'postprocessing'
|
|
2
|
-
import {
|
|
2
|
+
import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
|
|
3
3
|
|
|
4
|
-
export const BrightnessContrast = /* @__PURE__ */
|
|
4
|
+
export const BrightnessContrast = /* @__PURE__ */ createEffectComponent<
|
|
5
|
+
typeof BrightnessContrastEffect,
|
|
6
|
+
EffectOptions<typeof BrightnessContrastEffect>
|
|
7
|
+
>(BrightnessContrastEffect)
|
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
import type { ReactThreeFiber } from '@react-three/fiber'
|
|
2
2
|
import { ChromaticAberrationEffect } from 'postprocessing'
|
|
3
3
|
import type { Ref } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
import { useDispose, useVector2 } from '../util'
|
|
4
|
+
import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
|
|
6
5
|
|
|
6
|
+
// radialModulation/modulationOffset are typed as required by postprocessing's
|
|
7
|
+
// own .d.ts, but its JSDoc confirms both are optional with defaults - an
|
|
8
|
+
// upstream declaration bug, not a real constraint.
|
|
7
9
|
export type ChromaticAberrationProps = Omit<
|
|
8
|
-
|
|
9
|
-
'offset'
|
|
10
|
+
EffectOptions<typeof ChromaticAberrationEffect>,
|
|
11
|
+
'offset' | 'radialModulation' | 'modulationOffset'
|
|
10
12
|
> & {
|
|
11
|
-
ref?: Ref<ChromaticAberrationEffect>
|
|
12
13
|
offset?: ReactThreeFiber.Vector2
|
|
14
|
+
radialModulation?: boolean
|
|
15
|
+
modulationOffset?: number
|
|
16
|
+
ref?: Ref<ChromaticAberrationEffect>
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
() =>
|
|
20
|
-
new ChromaticAberrationEffect({
|
|
21
|
-
...props,
|
|
22
|
-
offset,
|
|
23
|
-
} as ConstructorParameters<typeof ChromaticAberrationEffect>[0]),
|
|
24
|
-
[offset, props]
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
useDispose(effect)
|
|
28
|
-
|
|
29
|
-
return <primitive object={effect} ref={ref} />
|
|
30
|
-
}
|
|
19
|
+
export const ChromaticAberration = /* @__PURE__ */ createEffectComponent<
|
|
20
|
+
typeof ChromaticAberrationEffect,
|
|
21
|
+
ChromaticAberrationProps
|
|
22
|
+
>(ChromaticAberrationEffect)
|
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { useMemo } from 'react'
|
|
4
|
-
import { useDispose } from '../util'
|
|
1
|
+
import { ColorAverageEffect } from 'postprocessing'
|
|
2
|
+
import { createEffectComponent } from '../createEffectComponent'
|
|
5
3
|
|
|
6
|
-
export
|
|
7
|
-
blendFunction?: BlendFunction
|
|
8
|
-
ref?: Ref<ColorAverageEffect>
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function ColorAverage({ blendFunction = BlendFunction.NORMAL, ref }: ColorAverageProps) {
|
|
12
|
-
const effect = useMemo(() => new ColorAverageEffect(blendFunction), [blendFunction])
|
|
13
|
-
|
|
14
|
-
useDispose(effect)
|
|
15
|
-
|
|
16
|
-
return <primitive object={effect} ref={ref} />
|
|
17
|
-
}
|
|
4
|
+
export const ColorAverage = /* @__PURE__ */ createEffectComponent<typeof ColorAverageEffect, object>(ColorAverageEffect)
|
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import { ColorDepthEffect } from 'postprocessing'
|
|
2
|
-
import {
|
|
2
|
+
import type { Ref } from 'react'
|
|
3
|
+
import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
const ColorDepthImpl = /* @__PURE__ */ createEffectComponent<
|
|
6
|
+
typeof ColorDepthEffect,
|
|
7
|
+
Omit<EffectOptions<typeof ColorDepthEffect>, 'bits'> & { bitDepth?: number }
|
|
8
|
+
>(ColorDepthEffect)
|
|
9
|
+
|
|
10
|
+
export type ColorDepthProps = EffectOptions<typeof ColorDepthEffect> & {
|
|
11
|
+
opacity?: number
|
|
12
|
+
ref?: Ref<ColorDepthEffect>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// bits (the constructor's option name) has no live setter of its own in
|
|
16
|
+
// postprocessing - only the differently-named bitDepth does (bits is a
|
|
17
|
+
// plain, dead field on the instance). Renamed here so it still works as a
|
|
18
|
+
// plain prop after the initial mount.
|
|
19
|
+
export function ColorDepth({ bits, ...props }: ColorDepthProps) {
|
|
20
|
+
// Only set bitDepth when bits is actually provided - r3f's reset-on-
|
|
21
|
+
// removal only fires when a key is absent from the new props, not when
|
|
22
|
+
// it's present but undefined.
|
|
23
|
+
if (bits !== undefined) (props as Record<string, unknown>).bitDepth = bits
|
|
24
|
+
return <ColorDepthImpl {...props} />
|
|
25
|
+
}
|
package/src/effects/Depth.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { DepthEffect } from 'postprocessing'
|
|
2
|
-
import {
|
|
2
|
+
import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
|
|
3
3
|
|
|
4
|
-
export const Depth = /* @__PURE__ */
|
|
4
|
+
export const Depth = /* @__PURE__ */ createEffectComponent<typeof DepthEffect, EffectOptions<typeof DepthEffect>>(
|
|
5
|
+
DepthEffect
|
|
6
|
+
)
|