Spaces:
Sleeping
Sleeping
File size: 4,402 Bytes
1829efb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | import { useEffect, useMemo } from 'react'
import { EffectComposer } from '@react-three/postprocessing'
import { BlendFunction, BloomEffect, ChromaticAberrationEffect, KernelSize, ToneMappingEffect, ToneMappingMode } from 'postprocessing'
import { useThree, useFrame } from '@react-three/fiber'
import * as THREE from 'three'
import { MotionBlurEffect } from './MotionBlurEffect'
import { useDebugStore } from '../../store/debug'
const _prevQuat = new THREE.Quaternion()
const _prevForward = new THREE.Vector3()
const _currentForward = new THREE.Vector3()
const _deltaForward = new THREE.Vector3()
const _cameraRight = new THREE.Vector3()
const _cameraUp = new THREE.Vector3()
function OptionalEffect({ enabled, object }: { enabled: boolean; object: object }) {
return enabled ? <primitive object={object} /> : null
}
// Why we instantiate effects directly instead of using <Bloom>/<ChromaticAberration>:
// @react-three/postprocessing's wrapEffect uses `useMemo(..., [JSON.stringify(a)])`
// where `a` is the rest-spread of props. In React 19, `ref` is a regular prop, so
// once the ref populates with the effect instance, JSON.stringify recurses through
// the BloomEffect's render targets/textures and hits circular parent/children refs.
// Mounting effects via <primitive> bypasses that path entirely.
export function PostProcessing() {
const { camera } = useThree()
const bloomEnabled = useDebugStore((s) => s.bloomEnabled)
const chromaticEnabled = useDebugStore((s) => s.chromaticEnabled)
const motionBlurEnabled = useDebugStore((s) => s.motionBlurEnabled)
const bloomEffect = useMemo(() => {
const initial = useDebugStore.getState()
return new BloomEffect({
intensity: initial.bloomIntensity,
luminanceThreshold: initial.bloomThreshold,
luminanceSmoothing: 0.9,
blendFunction: BlendFunction.ADD,
kernelSize: KernelSize.MEDIUM,
mipmapBlur: false,
resolutionScale: 0.35,
})
}, [])
const chromaEffect = useMemo(() => {
const initial = useDebugStore.getState()
return new ChromaticAberrationEffect({
offset: new THREE.Vector2(initial.chromaticOffset, initial.chromaticOffset),
radialModulation: false,
modulationOffset: 0,
blendFunction: BlendFunction.NORMAL,
})
}, [])
const blurEffect = useMemo(() => new MotionBlurEffect(), [])
const toneMappingEffect = useMemo(() => new ToneMappingEffect({
mode: ToneMappingMode.LINEAR,
blendFunction: BlendFunction.SRC,
}), [])
useEffect(() => {
_prevQuat.copy(camera.quaternion)
camera.getWorldDirection(_prevForward)
}, [camera])
useEffect(() => {
return () => {
bloomEffect.dispose()
chromaEffect.dispose()
blurEffect.dispose()
toneMappingEffect.dispose()
}
}, [bloomEffect, chromaEffect, blurEffect, toneMappingEffect])
useFrame(() => {
const s = useDebugStore.getState()
const intensityUniform = bloomEffect.uniforms.get('intensity')
if (intensityUniform) intensityUniform.value = s.bloomIntensity
bloomEffect.luminanceMaterial.threshold = s.bloomThreshold
chromaEffect.offset.set(s.chromaticOffset, s.chromaticOffset)
camera.getWorldDirection(_currentForward)
_deltaForward.copy(_currentForward).sub(_prevForward)
_cameraRight.set(1, 0, 0).applyQuaternion(camera.quaternion)
_cameraUp.set(0, 1, 0).applyQuaternion(camera.quaternion)
const x = _deltaForward.dot(_cameraRight)
const y = _deltaForward.dot(_cameraUp)
const angle = _deltaForward.length()
const strength = Math.min(angle * s.motionBlurStrength * 8, 1)
blurEffect.setVelocity(x * 0.5, y * 0.5, strength)
_prevQuat.copy(camera.quaternion)
_prevForward.copy(_currentForward)
})
const hasEffects = bloomEnabled || chromaticEnabled || motionBlurEnabled
if (!hasEffects) return null
const effectKey = [
bloomEnabled ? 'bloom' : '',
chromaticEnabled ? 'chroma' : '',
motionBlurEnabled ? 'blur' : '',
].join('|')
return (
<EffectComposer key={effectKey} multisampling={0}>
<OptionalEffect key="blur" enabled={motionBlurEnabled} object={blurEffect} />
<OptionalEffect key="bloom" enabled={bloomEnabled} object={bloomEffect} />
<OptionalEffect key="chroma" enabled={chromaticEnabled} object={chromaEffect} />
<primitive key="tone-mapping" object={toneMappingEffect} />
</EffectComposer>
)
}
|