@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.
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 +4 -3
  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
@@ -1,37 +1,32 @@
1
- import { ReactThreeFiber, useThree } from '@react-three/fiber'
1
+ import type { ReactThreeFiber } from '@react-three/fiber'
2
2
  import { GlitchEffect, GlitchMode } from 'postprocessing'
3
- import { Ref, useLayoutEffect, useMemo } from 'react'
4
- import { useDispose, useVector2 } from '../util'
3
+ import type { Ref } from 'react'
4
+ import { useMemo } from 'react'
5
+ import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
5
6
 
6
- export type GlitchProps = ConstructorParameters<typeof GlitchEffect>[0] &
7
- Partial<{
8
- mode: GlitchMode
9
- active: boolean
10
- delay: ReactThreeFiber.Vector2
11
- duration: ReactThreeFiber.Vector2
12
- chromaticAberrationOffset: ReactThreeFiber.Vector2
13
- strength: ReactThreeFiber.Vector2
14
- ref?: Ref<GlitchEffect>
15
- }>
16
-
17
- export function Glitch({ active = true, ref, ...props }: GlitchProps) {
18
- const invalidate = useThree((state) => state.invalidate)
19
- const delay = useVector2(props, 'delay')
20
- const duration = useVector2(props, 'duration')
21
- const strength = useVector2(props, 'strength')
22
- const chromaticAberrationOffset = useVector2(props, 'chromaticAberrationOffset')
23
-
24
- const effect = useMemo(
25
- () => new GlitchEffect({ ...props, delay, duration, strength, chromaticAberrationOffset }),
26
- [delay, duration, props, strength, chromaticAberrationOffset]
27
- )
7
+ type GlitchOptions = Omit<
8
+ EffectOptions<typeof GlitchEffect>,
9
+ 'delay' | 'duration' | 'strength' | 'chromaticAberrationOffset'
10
+ > & {
11
+ delay?: ReactThreeFiber.Vector2
12
+ duration?: ReactThreeFiber.Vector2
13
+ strength?: ReactThreeFiber.Vector2
14
+ chromaticAberrationOffset?: ReactThreeFiber.Vector2
15
+ mode?: GlitchMode
16
+ }
28
17
 
29
- useLayoutEffect(() => {
30
- effect.mode = active ? props.mode || GlitchMode.SPORADIC : GlitchMode.DISABLED
31
- invalidate()
32
- }, [active, effect, invalidate, props.mode])
18
+ const GlitchImpl = /* @__PURE__ */ createEffectComponent<typeof GlitchEffect, GlitchOptions>(GlitchEffect)
33
19
 
34
- useDispose(effect)
20
+ export type GlitchProps = GlitchOptions & {
21
+ active?: boolean
22
+ opacity?: number
23
+ ref?: Ref<GlitchEffect>
24
+ }
35
25
 
36
- return <primitive ref={ref} object={effect} />
26
+ // dtSize only seeds the auto-generated perturbation map at construction time
27
+ // (skipped entirely once a perturbationMap is provided) - routed through
28
+ // args so it still works as a plain prop.
29
+ export function Glitch({ active = true, mode = GlitchMode.SPORADIC, dtSize, ...props }: GlitchProps) {
30
+ const args = useMemo<[EffectOptions<typeof GlitchEffect>]>(() => [{ dtSize }], [dtSize])
31
+ return <GlitchImpl args={args} mode={active ? mode : GlitchMode.DISABLED} {...props} />
37
32
  }
@@ -1,18 +1,102 @@
1
+ import { useThree } from '@react-three/fiber'
1
2
  import { GodRaysEffect } from 'postprocessing'
2
- import { Ref, RefObject, useContext, useLayoutEffect, useMemo } from 'react'
3
+ import { Ref, RefObject, use, useEffect, useLayoutEffect, useMemo } from 'react'
3
4
  import { Mesh, Points } from 'three'
4
5
  import { EffectComposerContext } from '../EffectComposer'
5
- import { resolveRef, useDispose } from '../util'
6
+ import { applyPierced, readPierced, resolveRef, useDispose, useLiveDefaults } from '../util'
6
7
 
7
8
  type GodRaysProps = ConstructorParameters<typeof GodRaysEffect>[2] & {
8
9
  sun: Mesh | Points | RefObject<Mesh | Points>
9
10
  ref?: Ref<GodRaysEffect>
10
11
  }
11
12
 
12
- export function GodRays({ ref, ...props }: GodRaysProps) {
13
- const { camera } = useContext(EffectComposerContext)
14
- const effect = useMemo(() => new GodRaysEffect(camera, resolveRef(props.sun), props), [camera, props])
15
- useLayoutEffect(() => void (effect.lightSource = resolveRef(props.sun)), [effect, props.sun])
13
+ // GodRaysMaterial (godRaysMaterial) is where density/decay/weight/exposure
14
+ // actually live - clampMax maps to its differently-named maxIntensity.
15
+ // resolutionScale/resolutionX/resolutionY have no setter at all in
16
+ // postprocessing - construction-only. camera+sun being required constructor
17
+ // args also rule out createEffectComponent (needs `new Effect()` to work
18
+ // with zero args).
19
+ const LIVE_KEYS = [
20
+ 'blendMode-blendFunction',
21
+ 'godRaysMaterial-density',
22
+ 'godRaysMaterial-decay',
23
+ 'godRaysMaterial-weight',
24
+ 'godRaysMaterial-exposure',
25
+ 'clampMax',
26
+ 'blur',
27
+ 'kernelSize',
28
+ 'samples',
29
+ 'width',
30
+ 'height',
31
+ ]
32
+
33
+ function get(effect: GodRaysEffect, key: string): unknown {
34
+ return key === 'clampMax' ? effect.godRaysMaterial.maxIntensity : readPierced(effect, key)
35
+ }
36
+
37
+ function set(effect: GodRaysEffect, key: string, value: unknown): void {
38
+ if (key === 'clampMax') effect.godRaysMaterial.maxIntensity = value as number
39
+ else applyPierced(effect, key, value)
40
+ }
41
+
42
+ export function GodRays({
43
+ sun,
44
+ blendFunction,
45
+ density,
46
+ decay,
47
+ weight,
48
+ exposure,
49
+ clampMax,
50
+ blur,
51
+ kernelSize,
52
+ samples,
53
+ width,
54
+ height,
55
+ resolutionScale,
56
+ resolutionX,
57
+ resolutionY,
58
+ ref,
59
+ }: GodRaysProps) {
60
+ const { camera, autoClear } = use(EffectComposerContext)
61
+ const invalidate = useThree((state) => state.invalidate)
62
+
63
+ const effect = useMemo(
64
+ () => new GodRaysEffect(camera, resolveRef(sun), { resolutionScale, resolutionX, resolutionY }),
65
+ [camera, resolutionScale, resolutionX, resolutionY]
66
+ )
67
+
68
+ useEffect(() => {
69
+ if (autoClear !== false) {
70
+ console.warn(
71
+ 'GodRays renders an internal extra pass that needs <EffectComposer autoClear={false}> - without it, occlusion by other objects will look wrong.'
72
+ )
73
+ }
74
+ }, [autoClear])
75
+
76
+ useLayoutEffect(() => {
77
+ effect.lightSource = resolveRef(sun)
78
+ invalidate()
79
+ }, [effect, sun, invalidate])
80
+
81
+ useLiveDefaults(
82
+ effect,
83
+ {
84
+ 'blendMode-blendFunction': blendFunction,
85
+ 'godRaysMaterial-density': density,
86
+ 'godRaysMaterial-decay': decay,
87
+ 'godRaysMaterial-weight': weight,
88
+ 'godRaysMaterial-exposure': exposure,
89
+ clampMax,
90
+ blur,
91
+ kernelSize,
92
+ samples,
93
+ width,
94
+ height,
95
+ },
96
+ LIVE_KEYS,
97
+ get,
98
+ set
99
+ )
16
100
 
17
101
  useDispose(effect)
18
102
 
@@ -1,28 +1,27 @@
1
1
  import { useThree } from '@react-three/fiber'
2
2
  import { GridEffect } from 'postprocessing'
3
- import { Ref, useLayoutEffect, useMemo } from 'react'
4
- import { useDispose } from '../util'
3
+ import { type Ref, useImperativeHandle, useLayoutEffect, useRef } from 'react'
4
+ import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
5
5
 
6
- type GridProps = ConstructorParameters<typeof GridEffect>[0] &
7
- Partial<{
8
- size: {
9
- width: number
10
- height: number
11
- }
12
- ref: Ref<GridEffect>
13
- }>
6
+ const GridImpl = /* @__PURE__ */ createEffectComponent<typeof GridEffect, EffectOptions<typeof GridEffect>>(GridEffect)
7
+
8
+ export type GridProps = EffectOptions<typeof GridEffect> & {
9
+ size?: { width: number; height: number }
10
+ opacity?: number
11
+ ref?: Ref<GridEffect>
12
+ }
14
13
 
15
14
  export function Grid({ size, ref, ...props }: GridProps) {
16
15
  const invalidate = useThree((state) => state.invalidate)
17
-
18
- const effect = useMemo(() => new GridEffect(props), [props])
16
+ const localRef = useRef<GridEffect>(null)
17
+ useImperativeHandle(ref, () => localRef.current!, [])
19
18
 
20
19
  useLayoutEffect(() => {
21
- if (size) effect.setSize(size.width, size.height)
22
- invalidate()
23
- }, [effect, size, invalidate])
24
-
25
- useDispose(effect)
20
+ if (size) {
21
+ localRef.current?.setSize(size.width, size.height)
22
+ invalidate()
23
+ }
24
+ }, [size, invalidate])
26
25
 
27
- return <primitive ref={ref} object={effect} />
26
+ return <GridImpl ref={localRef} {...props} />
28
27
  }
@@ -1,4 +1,7 @@
1
1
  import { HueSaturationEffect } from 'postprocessing'
2
- import { wrapEffect } from '../wrapEffect'
2
+ import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
3
3
 
4
- export const HueSaturation = /* @__PURE__ */ wrapEffect(HueSaturationEffect)
4
+ export const HueSaturation = /* @__PURE__ */ createEffectComponent<
5
+ typeof HueSaturationEffect,
6
+ EffectOptions<typeof HueSaturationEffect>
7
+ >(HueSaturationEffect)
@@ -1,8 +1,7 @@
1
- import { useThree } from '@react-three/fiber'
2
1
  import { BlendFunction, LUT3DEffect } from 'postprocessing'
3
- import { Ref, useLayoutEffect, useMemo } from 'react'
2
+ import { Ref, useMemo } from 'react'
4
3
  import type { Texture } from 'three'
5
- import { useDispose } from '../util'
4
+ import { useDispose, useLiveDefaults } from '../util'
6
5
 
7
6
  export type LUTProps = {
8
7
  lut: Texture
@@ -11,16 +10,15 @@ export type LUTProps = {
11
10
  ref?: Ref<LUT3DEffect>
12
11
  }
13
12
 
14
- export function LUT({ lut, tetrahedralInterpolation, ref, ...props }: LUTProps) {
15
- const effect = useMemo(() => new LUT3DEffect(lut, props), [lut, props])
16
- const invalidate = useThree((state) => state.invalidate)
13
+ const LIVE_KEYS = ['blendMode-blendFunction', 'lut', 'tetrahedralInterpolation']
17
14
 
18
- useLayoutEffect(() => {
19
- if (tetrahedralInterpolation) effect.tetrahedralInterpolation = tetrahedralInterpolation
20
- if (lut) effect.lut = lut
21
- invalidate()
22
- }, [effect, invalidate, lut, tetrahedralInterpolation])
15
+ // lut is LUT3DEffect's required constructor arg (no default) - only used
16
+ // for the initial instance, later changes go through its own live setter
17
+ // (via useLiveDefaults below) instead of reconstructing.
18
+ export function LUT({ lut, blendFunction, tetrahedralInterpolation, ref }: LUTProps) {
19
+ const effect = useMemo(() => new LUT3DEffect(lut), [])
23
20
 
21
+ useLiveDefaults(effect, { 'blendMode-blendFunction': blendFunction, lut, tetrahedralInterpolation }, LIVE_KEYS)
24
22
  useDispose(effect)
25
23
 
26
24
  return <primitive ref={ref} object={effect} />
@@ -1,14 +1,15 @@
1
1
  // Created by Anderson Mancini 2023
2
2
  // From https://github.com/ektogamat/R3F-Ultimate-Lens-Flare
3
3
 
4
- import { useFrame, useThree } from '@react-three/fiber'
4
+ import { useFrame, useThree, type ReactThreeFiber } from '@react-three/fiber'
5
5
  import { easing } from 'maath'
6
6
  import { BlendFunction, Effect } from 'postprocessing'
7
- import { useContext, useEffect, useRef, useState } from 'react'
7
+ import { useContext, useEffect, useRef, useState, type Ref } from 'react'
8
8
  import { Color, Mesh, Texture, Uniform, Vector2, Vector3 } from 'three'
9
9
 
10
+ import { createEffectComponent } from '../createEffectComponent'
10
11
  import { EffectComposerContext } from '../EffectComposer'
11
- import { wrapEffect } from '../wrapEffect'
12
+ import { useMergeRefs, useVector3 } from '../util'
12
13
 
13
14
  const LensFlareShader = {
14
15
  fragmentShader: /* glsl */ `
@@ -441,26 +442,26 @@ type LensFlareEffectOptions = {
441
442
 
442
443
  export class LensFlareEffect extends Effect {
443
444
  constructor({
444
- blendFunction,
445
- enabled,
446
- glareSize,
447
- lensPosition,
448
- screenRes,
449
- starPoints,
450
- flareSize,
451
- flareSpeed,
452
- flareShape,
453
- animated,
454
- anamorphic,
455
- colorGain,
456
- lensDirtTexture,
457
- haloScale,
458
- secondaryGhosts,
459
- aditionalStreaks,
460
- ghostScale,
461
- opacity,
462
- starBurst,
463
- }: LensFlareEffectOptions) {
445
+ blendFunction = BlendFunction.NORMAL,
446
+ enabled = true,
447
+ glareSize = 0.2,
448
+ lensPosition = new Vector3(-25, 6, -60),
449
+ screenRes = new Vector2(0, 0),
450
+ starPoints = 6,
451
+ flareSize = 0.01,
452
+ flareSpeed = 0.01,
453
+ flareShape = 0.01,
454
+ animated = true,
455
+ anamorphic = false,
456
+ colorGain = new Color(20, 20, 20),
457
+ lensDirtTexture = null,
458
+ haloScale = 0.5,
459
+ secondaryGhosts = true,
460
+ aditionalStreaks = true,
461
+ ghostScale = 0.0,
462
+ opacity = 1.0,
463
+ starBurst = false,
464
+ }: Partial<LensFlareEffectOptions> = {}) {
464
465
  super('LensFlareEffect', LensFlareShader.fragmentShader, {
465
466
  blendFunction,
466
467
  uniforms: new Map<string, Uniform>([
@@ -493,40 +494,191 @@ export class LensFlareEffect extends Effect {
493
494
  time.value += deltaTime
494
495
  }
495
496
  }
497
+
498
+ private u<T>(name: string): T {
499
+ return this.uniforms.get(name)!.value
500
+ }
501
+
502
+ private setU(name: string, value: unknown): void {
503
+ this.uniforms.get(name)!.value = value
504
+ }
505
+
506
+ get enabled(): boolean {
507
+ return this.u('enabled')
508
+ }
509
+ set enabled(value: boolean) {
510
+ this.setU('enabled', value)
511
+ }
512
+
513
+ get glareSize(): number {
514
+ return this.u('glareSize')
515
+ }
516
+ set glareSize(value: number) {
517
+ this.setU('glareSize', value)
518
+ }
519
+
520
+ get lensPosition(): Vector3 {
521
+ return this.u('lensPosition')
522
+ }
523
+ set lensPosition(value: Vector3) {
524
+ this.setU('lensPosition', value)
525
+ }
526
+
527
+ get screenRes(): Vector2 {
528
+ return this.u('screenRes')
529
+ }
530
+ set screenRes(value: Vector2) {
531
+ this.setU('screenRes', value)
532
+ }
533
+
534
+ get starPoints(): number {
535
+ return this.u('starPoints')
536
+ }
537
+ set starPoints(value: number) {
538
+ this.setU('starPoints', value)
539
+ }
540
+
541
+ get flareSize(): number {
542
+ return this.u('flareSize')
543
+ }
544
+ set flareSize(value: number) {
545
+ this.setU('flareSize', value)
546
+ }
547
+
548
+ get flareSpeed(): number {
549
+ return this.u('flareSpeed')
550
+ }
551
+ set flareSpeed(value: number) {
552
+ this.setU('flareSpeed', value)
553
+ }
554
+
555
+ get flareShape(): number {
556
+ return this.u('flareShape')
557
+ }
558
+ set flareShape(value: number) {
559
+ this.setU('flareShape', value)
560
+ }
561
+
562
+ get animated(): boolean {
563
+ return this.u('animated')
564
+ }
565
+ set animated(value: boolean) {
566
+ this.setU('animated', value)
567
+ }
568
+
569
+ get anamorphic(): boolean {
570
+ return this.u('anamorphic')
571
+ }
572
+ set anamorphic(value: boolean) {
573
+ this.setU('anamorphic', value)
574
+ }
575
+
576
+ get colorGain(): Color {
577
+ return this.u('colorGain')
578
+ }
579
+ set colorGain(value: Color) {
580
+ this.setU('colorGain', value)
581
+ }
582
+
583
+ get lensDirtTexture(): Texture | null {
584
+ return this.u('lensDirtTexture')
585
+ }
586
+ set lensDirtTexture(value: Texture | null) {
587
+ this.setU('lensDirtTexture', value)
588
+ }
589
+
590
+ get haloScale(): number {
591
+ return this.u('haloScale')
592
+ }
593
+ set haloScale(value: number) {
594
+ this.setU('haloScale', value)
595
+ }
596
+
597
+ get secondaryGhosts(): boolean {
598
+ return this.u('secondaryGhosts')
599
+ }
600
+ set secondaryGhosts(value: boolean) {
601
+ this.setU('secondaryGhosts', value)
602
+ }
603
+
604
+ get aditionalStreaks(): boolean {
605
+ return this.u('aditionalStreaks')
606
+ }
607
+ set aditionalStreaks(value: boolean) {
608
+ this.setU('aditionalStreaks', value)
609
+ }
610
+
611
+ get ghostScale(): number {
612
+ return this.u('ghostScale')
613
+ }
614
+ set ghostScale(value: number) {
615
+ this.setU('ghostScale', value)
616
+ }
617
+
618
+ get starBurst(): boolean {
619
+ return this.u('starBurst')
620
+ }
621
+ set starBurst(value: boolean) {
622
+ this.setU('starBurst', value)
623
+ }
624
+
625
+ get opacity(): number {
626
+ return this.u('opacity')
627
+ }
628
+ set opacity(value: number) {
629
+ this.setU('opacity', value)
630
+ }
496
631
  }
497
632
 
498
- type LensFlareProps = {
633
+ type LensFlareProps = Omit<Partial<LensFlareEffectOptions>, 'lensPosition'> & {
499
634
  /** Position of the effect */
500
- lensPosition?: Vector3
635
+ lensPosition?: ReactThreeFiber.Vector3
501
636
  /** The time that it takes to fade the occlusion */
502
637
  smoothTime?: number
503
- } & Partial<LensFlareEffectOptions>
504
-
505
- const LensFlareWrapped = /* @__PURE__ */ wrapEffect(LensFlareEffect)
506
-
507
- export const LensFlare = ({
508
- smoothTime = 0.07,
509
- //
510
- blendFunction = BlendFunction.NORMAL,
511
- enabled = true,
512
- glareSize = 0.2,
513
- lensPosition = new Vector3(-25, 6, -60),
514
- screenRes = new Vector2(0, 0),
515
- starPoints = 6,
516
- flareSize = 0.01,
517
- flareSpeed = 0.01,
518
- flareShape = 0.01,
519
- animated = true,
520
- anamorphic = false,
521
- colorGain = new Color(20, 20, 20),
522
- lensDirtTexture = null,
523
- haloScale = 0.5,
524
- secondaryGhosts = true,
525
- aditionalStreaks = true,
526
- ghostScale = 0.0,
527
- opacity = 1.0,
528
- starBurst = false,
529
- }: LensFlareProps) => {
638
+ ref?: Ref<LensFlareEffect>
639
+ }
640
+
641
+ const LensFlareWrapped = /* @__PURE__ */ createEffectComponent<
642
+ typeof LensFlareEffect,
643
+ Partial<LensFlareEffectOptions> & { ref?: Ref<LensFlareEffect> }
644
+ >(LensFlareEffect)
645
+
646
+ // Stable identity so a parent re-render (e.g. a Leva control) doesn't hand
647
+ // r3f a brand-new Vector2 every time - r3f would then `.copy()` it onto the
648
+ // uniform in place, stomping the viewport-driven value the effect below
649
+ // maintains and leaving screenRes at (0, 0) until the next resize. Never
650
+ // mutated (only ever copied *from*), so it's safe to share across instances.
651
+ const DEFAULT_SCREEN_RES = /* @__PURE__ */ new Vector2(0, 0)
652
+
653
+ // Same reasoning as DEFAULT_SCREEN_RES above - shared, read-only default.
654
+ const DEFAULT_LENS_POSITION = /* @__PURE__ */ new Vector3(-25, 6, -60)
655
+
656
+ export const LensFlare = (props: LensFlareProps) => {
657
+ const {
658
+ ref: forwardedRef,
659
+ smoothTime = 0.07,
660
+ blendFunction = BlendFunction.NORMAL,
661
+ enabled = true,
662
+ glareSize = 0.2,
663
+ screenRes,
664
+ starPoints = 6,
665
+ flareSize = 0.01,
666
+ flareSpeed = 0.01,
667
+ flareShape = 0.01,
668
+ animated = true,
669
+ anamorphic = false,
670
+ colorGain = new Color(20, 20, 20),
671
+ lensDirtTexture = null,
672
+ haloScale = 0.5,
673
+ secondaryGhosts = true,
674
+ aditionalStreaks = true,
675
+ ghostScale = 0.0,
676
+ opacity = 1.0,
677
+ starBurst = false,
678
+ } = props
679
+
680
+ const lensPosition = useVector3({ lensPosition: props.lensPosition ?? DEFAULT_LENS_POSITION }, 'lensPosition')
681
+
530
682
  const viewport = useThree(({ viewport }) => viewport)
531
683
  const raycaster = useThree(({ raycaster }) => raycaster)
532
684
  const { scene, camera } = useContext(EffectComposerContext)
@@ -534,6 +686,7 @@ export const LensFlare = ({
534
686
  const [projectedPosition] = useState(() => new Vector3())
535
687
 
536
688
  const ref = useRef<LensFlareEffect>(null)
689
+ const setRef = useMergeRefs(ref, forwardedRef)
537
690
 
538
691
  useFrame((_, delta) => {
539
692
  if (!ref?.current) return
@@ -541,7 +694,7 @@ export const LensFlare = ({
541
694
  const uOpacity = ref.current.uniforms.get('opacity')
542
695
  if (!uLensPosition || !uOpacity) return
543
696
 
544
- let target = 1
697
+ let target = 0
545
698
 
546
699
  projectedPosition.copy(lensPosition).project(camera)
547
700
  if (projectedPosition.z > 1) return
@@ -555,6 +708,7 @@ export const LensFlare = ({
555
708
  const intersects = raycaster.intersectObjects(scene.children, true)
556
709
  const { object } = intersects[0] || {}
557
710
  if (object) {
711
+ target = 1
558
712
  if (object.userData?.lensflare === 'no-occlusion') {
559
713
  target = 0
560
714
  } else if (object instanceof Mesh) {
@@ -586,12 +740,12 @@ export const LensFlare = ({
586
740
 
587
741
  return (
588
742
  <LensFlareWrapped
589
- ref={ref}
743
+ ref={setRef}
590
744
  blendFunction={blendFunction}
591
745
  enabled={enabled}
592
746
  glareSize={glareSize}
593
747
  lensPosition={lensPosition}
594
- screenRes={screenRes}
748
+ screenRes={screenRes ?? DEFAULT_SCREEN_RES}
595
749
  starPoints={starPoints}
596
750
  flareSize={flareSize}
597
751
  flareSpeed={flareSpeed}
@@ -1,4 +1,16 @@
1
1
  import { BlendFunction, NoiseEffect } from 'postprocessing'
2
- import { wrapEffect } from '../wrapEffect'
2
+ import type { Ref } from 'react'
3
+ import { createEffectComponent, type EffectOptions } from '../createEffectComponent'
3
4
 
4
- export const Noise = /* @__PURE__ */ wrapEffect(NoiseEffect, { blendFunction: BlendFunction.COLOR_DODGE })
5
+ const NoiseImpl = /* @__PURE__ */ createEffectComponent<typeof NoiseEffect, EffectOptions<typeof NoiseEffect>>(
6
+ NoiseEffect
7
+ )
8
+
9
+ export type NoiseProps = EffectOptions<typeof NoiseEffect> & {
10
+ opacity?: number
11
+ ref?: Ref<NoiseEffect>
12
+ }
13
+
14
+ export function Noise({ blendFunction = BlendFunction.COLOR_DODGE, ...props }: NoiseProps) {
15
+ return <NoiseImpl blendFunction={blendFunction} {...props} />
16
+ }