diff --git a/.storybook/public/porsche.glb b/.storybook/public/porsche.glb new file mode 100644 index 00000000..f89bc5ba Binary files /dev/null and b/.storybook/public/porsche.glb differ diff --git a/.storybook/stories/Autofocus.stories.tsx b/.storybook/stories/Autofocus.stories.tsx index fc54fbed..f2530202 100644 --- a/.storybook/stories/Autofocus.stories.tsx +++ b/.storybook/stories/Autofocus.stories.tsx @@ -8,7 +8,7 @@ import { EffectComposer, Autofocus } from '../../src' // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta = { - title: 'Effect/Autofocus', + title: 'Effects/Autofocus', component: Autofocus, decorators: [ (Story) => ( diff --git a/.storybook/stories/HBAO.stories.tsx b/.storybook/stories/HBAO.stories.tsx new file mode 100644 index 00000000..254e0511 --- /dev/null +++ b/.storybook/stories/HBAO.stories.tsx @@ -0,0 +1,56 @@ +import * as React from 'react' +import * as THREE from 'three' +import type { Meta, StoryObj } from '@storybook/react' +import { EffectComposer, HBAO as HBAOImpl } from '../../src' +import { Canvas } from '@react-three/fiber' +import { Environment, useGLTF, OrbitControls } from '@react-three/drei' + +const meta = { + title: 'Effects/HBAO', + component: HBAOImpl, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta +export default meta + +function Model() { + const gltf = useGLTF('/porsche.glb') + return +} + +type Story = StoryObj + +export const HBAO: Story = { + render: (args) => ( + + + + + + + + + + ), + args: { + // AO + resolutionScale: 1, + spp: 8, + distance: 2, + distancePower: 1, + power: 2, + bias: 40, + thickness: 0.075, + color: new THREE.Color('black'), + useNormalPass: false, + // Poisson + iterations: 1, + radius: 8, + rings: 5.625, + lumaPhi: 10, + depthPhi: 2, + normalPhi: 3.25, + samples: 16, + }, +} diff --git a/.storybook/stories/SSGI.stories.tsx b/.storybook/stories/SSGI.stories.tsx new file mode 100644 index 00000000..489ec58e --- /dev/null +++ b/.storybook/stories/SSGI.stories.tsx @@ -0,0 +1,59 @@ +import * as React from 'react' +import type { Meta, StoryObj } from '@storybook/react' +import { EffectComposer, SSGI as SSGIImpl, TRAA, MotionBlur } from '../../src' +import { Canvas } from '@react-three/fiber' +import { Environment, useGLTF, OrbitControls } from '@react-three/drei' + +const meta = { + title: 'Effects/SSGI', + component: SSGIImpl, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta +export default meta + +function Model() { + const gltf = useGLTF('/porsche.glb') + return +} + +type Story = StoryObj + +export const SSGI: Story = { + render: (args) => ( + + + + + + + + + + + + ), + args: { + distance: 10, + thickness: 10, + autoThickness: false, + maxRoughness: 1, + blend: 0.9, + denoiseIterations: 1, + denoiseKernel: 2, + denoiseDiffuse: 10, + denoiseSpecular: 10, + depthPhi: 2, + normalPhi: 50, + roughnessPhi: 1, + envBlur: 0.5, + importanceSampling: true, + directLightMultiplier: 1, + steps: 20, + refineSteps: 5, + spp: 1, + resolutionScale: 1, + missedRays: false, + }, +} diff --git a/package.json b/package.json index c47316a2..f25649d4 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "maath": "^0.6.0", "n8ao": "^1.6.6", "postprocessing": "^6.32.1", + "realism-effects": "^1.1.2", "screen-space-reflections": "^2.5.0", "three-stdlib": "^2.23.4" }, diff --git a/src/index.tsx b/src/index.tsx index 2e65653e..14df2ed6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,7 +1,3 @@ -export * from './Selection' -export * from './EffectComposer' -export * from './util' - export * from './effects/Autofocus' export * from './effects/Bloom' export * from './effects/BrightnessContrast' @@ -33,6 +29,14 @@ export * from './effects/TiltShift' export * from './effects/TiltShift2' export * from './effects/ASCII' -// These are not effect passes -export * from './effects/SSR' -export * from './effects/N8AO' +export * from './passes/HBAO' +export * from './passes/MotionBlur' +export * from './passes/N8AO' +export * from './passes/SSGI' +export * from './passes/SSR' +export * from './passes/TRAA' +export * from './passes/useVelocityBuffer' + +export * from './Selection' +export * from './EffectComposer' +export * from './util' diff --git a/src/passes/HBAO.tsx b/src/passes/HBAO.tsx new file mode 100644 index 00000000..6a8e74c9 --- /dev/null +++ b/src/passes/HBAO.tsx @@ -0,0 +1,121 @@ +import { forwardRef, useContext, useEffect, useMemo } from 'react' +import * as THREE from 'three' +import { EffectComposer, EffectPass } from 'postprocessing' +// @ts-ignore +import { VelocityDepthNormalPass, HBAOEffect } from 'realism-effects' +import { EffectComposerContext } from '../EffectComposer' +import { useVelocityBuffer } from './useVelocityBuffer' + +export interface HBAOOptions { + // AO options + resolutionScale: number + spp: number + distance: number + distancePower: number + power: number + bias: number + thickness: number + color: THREE.Color + useNormalPass: boolean + velocityDepthNormalPass: VelocityDepthNormalPass | null + normalTexture: THREE.Texture | null + // Poisson blur options + iterations: number + radius: number + rings: number + lumaPhi: number + depthPhi: number + normalPhi: number + samples: number + // normalTexture: THREE.Texture | null +} + +export class HBAOPass extends EffectPass { + constructor(composer: EffectComposer, camera: THREE.Camera, scene: THREE.Scene, options?: Partial) { + super(camera, new HBAOEffect(composer, camera, scene, options)) + } +} + +export interface HBAOProps extends Omit, 'velocityDepthNormalPass'>, Partial {} + +export const HBAO = forwardRef(function HBAO( + { + // AO + resolutionScale = 1, + spp = 8, + distance = 2, + distancePower = 1, + power = 2, + bias = 40, + thickness = 0.075, + color = new THREE.Color('black'), + useNormalPass = false, + normalTexture = null, + // Poisson + iterations = 1, + radius = 8, + rings = 5.625, + lumaPhi = 10, + depthPhi = 2, + normalPhi = 3.25, + samples = 16, + ...props + }, + ref +) { + const velocityDepthNormalPass = useVelocityBuffer() + const { composer, camera, scene } = useContext(EffectComposerContext) + const effect = useMemo( + () => + new HBAOPass(composer, camera, scene, { + resolutionScale, + spp, + distance, + distancePower, + power, + bias, + thickness, + color, + useNormalPass, + velocityDepthNormalPass, + normalTexture, + iterations, + radius, + rings, + lumaPhi, + depthPhi, + normalPhi, + samples, + }), + [ + composer, + camera, + scene, + resolutionScale, + spp, + distance, + distancePower, + power, + bias, + thickness, + color, + useNormalPass, + velocityDepthNormalPass, + normalTexture, + iterations, + radius, + rings, + lumaPhi, + depthPhi, + normalPhi, + samples, + ] + ) + useEffect(() => { + return () => { + effect.dispose() + } + }, [effect]) + + return +}) diff --git a/src/passes/MotionBlur.tsx b/src/passes/MotionBlur.tsx new file mode 100644 index 00000000..82baef1a --- /dev/null +++ b/src/passes/MotionBlur.tsx @@ -0,0 +1,27 @@ +import { forwardRef, useContext, useEffect, useMemo } from 'react' +import { VelocityBuffer, useVelocityBuffer } from './useVelocityBuffer' +import { EffectPass } from 'postprocessing' +// @ts-ignore +import { MotionBlurEffect } from 'realism-effects' +import { EffectComposerContext } from '../EffectComposer' + +export class MotionBlurPass extends EffectPass { + constructor(camera: THREE.Camera, velocityBuffer: VelocityBuffer) { + super(camera, new MotionBlurEffect(velocityBuffer)) + } +} + +export interface MotionBlurProps extends Partial {} + +export const MotionBlur = forwardRef(function MotionBlur(props, ref) { + const velocityBuffer = useVelocityBuffer() + const { camera } = useContext(EffectComposerContext) + const effect = useMemo(() => new MotionBlurPass(camera, velocityBuffer), [camera, velocityBuffer]) + useEffect(() => { + return () => { + effect.dispose() + } + }, [effect]) + + return +}) diff --git a/src/effects/N8AO.tsx b/src/passes/N8AO.tsx similarity index 100% rename from src/effects/N8AO.tsx rename to src/passes/N8AO.tsx diff --git a/src/passes/SSGI.tsx b/src/passes/SSGI.tsx new file mode 100644 index 00000000..89a10cbd --- /dev/null +++ b/src/passes/SSGI.tsx @@ -0,0 +1,129 @@ +import * as THREE from 'three' +import { useMemo, useEffect, forwardRef, useContext } from 'react' +import { EffectPass } from 'postprocessing' +// @ts-ignore +import { SSGIEffect } from 'realism-effects' +import { EffectComposerContext } from '../EffectComposer' +import { VelocityBuffer, useVelocityBuffer } from './useVelocityBuffer' + +export interface SSGIOptions { + distance: number + thickness: number + autoThickness: boolean + maxRoughness: number + blend: number + denoiseIterations: number + denoiseKernel: number + denoiseDiffuse: number + denoiseSpecular: number + depthPhi: number + normalPhi: number + roughnessPhi: number + envBlur: number + importanceSampling: boolean + directLightMultiplier: number + steps: number + refineSteps: number + spp: number + resolutionScale: number + missedRays: boolean +} + +export class SSGIPass extends EffectPass { + constructor( + scene: THREE.Scene, + camera: THREE.Camera, + velocityBuffer: VelocityBuffer, + options?: Partial + ) { + super(camera, new SSGIEffect(scene, camera, velocityBuffer, options)) + } +} + +export interface SSGIProps extends Partial, Partial {} + +export const SSGI = forwardRef(function SSGI( + { + distance = 10, + thickness = 10, + autoThickness = false, + maxRoughness = 1, + blend = 0.9, + denoiseIterations = 1, + denoiseKernel = 2, + denoiseDiffuse = 10, + denoiseSpecular = 10, + depthPhi = 2, + normalPhi = 50, + roughnessPhi = 1, + envBlur = 0.5, + importanceSampling = true, + directLightMultiplier = 1, + steps = 20, + refineSteps = 5, + spp = 1, + resolutionScale = 1, + missedRays = false, + ...props + }, + ref +) { + const velocityBuffer = useVelocityBuffer() + const { scene, camera } = useContext(EffectComposerContext) + const effect = useMemo( + () => + new SSGIPass(scene, camera, velocityBuffer, { + distance, + thickness, + autoThickness, + maxRoughness, + blend, + denoiseIterations, + denoiseKernel, + denoiseDiffuse, + denoiseSpecular, + depthPhi, + normalPhi, + roughnessPhi, + envBlur, + importanceSampling, + directLightMultiplier, + steps, + refineSteps, + spp, + resolutionScale, + missedRays, + }), + [ + scene, + camera, + velocityBuffer, + distance, + thickness, + autoThickness, + maxRoughness, + blend, + denoiseIterations, + denoiseKernel, + denoiseDiffuse, + denoiseSpecular, + depthPhi, + normalPhi, + roughnessPhi, + envBlur, + importanceSampling, + directLightMultiplier, + steps, + refineSteps, + spp, + resolutionScale, + missedRays, + ] + ) + + useEffect(() => { + return () => effect.dispose() + }, [effect]) + + return +}) diff --git a/src/effects/SSR.tsx b/src/passes/SSR.tsx similarity index 100% rename from src/effects/SSR.tsx rename to src/passes/SSR.tsx diff --git a/src/passes/TRAA.tsx b/src/passes/TRAA.tsx new file mode 100644 index 00000000..344af1e7 --- /dev/null +++ b/src/passes/TRAA.tsx @@ -0,0 +1,28 @@ +import { forwardRef, useContext, useEffect, useMemo } from 'react' +import * as THREE from 'three' +import { EffectPass } from 'postprocessing' +// @ts-ignore +import { TRAAEffect } from 'realism-effects' +import { VelocityBuffer, useVelocityBuffer } from './useVelocityBuffer' +import { EffectComposerContext } from '../EffectComposer' + +export class TRAAPass extends EffectPass { + constructor(scene: THREE.Scene, camera: THREE.Camera, velocityBuffer: VelocityBuffer) { + super(camera, new TRAAEffect(scene, camera, velocityBuffer)) + } +} + +export interface TRAAProps extends Partial {} + +export const TRAA = forwardRef(function TRAA(props, ref) { + const { scene, camera } = useContext(EffectComposerContext) + const velocityBuffer = useVelocityBuffer() + const effect = useMemo(() => new TRAAPass(scene, camera, velocityBuffer), [scene, camera, velocityBuffer]) + useEffect(() => { + return () => { + effect.dispose() + } + }, [effect]) + + return +}) diff --git a/src/passes/useVelocityBuffer.ts b/src/passes/useVelocityBuffer.ts new file mode 100644 index 00000000..901c1897 --- /dev/null +++ b/src/passes/useVelocityBuffer.ts @@ -0,0 +1,36 @@ +import * as THREE from 'three' +import { useThree } from '@react-three/fiber' +// @ts-ignore +import { VelocityDepthNormalPass } from 'realism-effects' +import { useMemo, useLayoutEffect, useContext } from 'react' +import { EffectComposerContext } from '../EffectComposer' + +export type VelocityBuffer = VelocityDepthNormalPass + +const _buffers = new WeakMap() + +export function useVelocityBuffer(): VelocityBuffer { + const gl = useThree((state) => state.gl) + const { composer, camera, scene } = useContext(EffectComposerContext) + + const buffer = useMemo(() => { + let _buffer = _buffers.get(gl) + if (!_buffer) { + _buffer = new VelocityDepthNormalPass(scene, camera) + _buffers.set(gl, _buffer) + } + return _buffer + }, [gl, scene, camera]) + + useLayoutEffect(() => { + let needsUpdate = !composer.passes.includes(buffer) + if (needsUpdate) composer.addPass(buffer) + + return () => { + if (needsUpdate) composer.removePass(buffer) + buffer.dispose() + } + }, [composer, buffer]) + + return buffer +} diff --git a/yarn.lock b/yarn.lock index 62e7f831..bd817b63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8854,6 +8854,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +realism-effects@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/realism-effects/-/realism-effects-1.1.2.tgz#14b844b45267b1afe661ad9b31251a63b0429fdd" + integrity sha512-dHI++mxZNYJ/k8UO+TUE/PHiNiZcUSkrNlSEwVT48c4d4PHjSz9sjdkH2QlzbmOaI4wYJEmcc9VzI6BZNgpx1A== + recast@^0.21.0: version "0.21.5" resolved "https://registry.yarnpkg.com/recast/-/recast-0.21.5.tgz#e8cd22bb51bcd6130e54f87955d33a2b2e57b495"