Skip to content

Commit

Permalink
feat(Autofocus): new effect
Browse files Browse the repository at this point in the history
  • Loading branch information
abernier committed Apr 30, 2023
1 parent 4865a95 commit a0702b8
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 0 deletions.
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,54 @@ Selection can be nested and group multiple object, higher up selection take prec
</Select>
```

#### Autofocus

<p>
<a href="https://codesandbox.io/s/dfw6w4"><img width="20%" src="https://codesandbox.io/api/v1/sandboxes/dfw6w4/thumbnail.png" alt="Autofocus demo"/></a>
</p>

An auto-focus effect, that extends `<DepthOfField>`.

```tsx
export type AutofocusProps = typeof DepthOfField & {
target?: [number, number, number] // undefined
mouse?: boolean // false
debug?: number // undefined
manual?: boolean // false
smoothTime?: number // .25
}
```
```tsx
<EffectComposer>
<Autofocus />
</EffectComposer>
```
Ref-api:
```tsx
type AutofocusApi = {
dofRef: RefObject<DepthOfFieldEffect>
hitpoint: THREE.Vector3
update: (delta: number, updateTarget: boolean) => void
}
```
```tsx
<Autofocus ref={autofocusRef} />
```
Associated with `manual` prop, you can for example, animate the DOF target yourself:
```tsx
useFrame((_, delta) => {
const api = autofocusRef.current
api.update(delta, false) // update hitpoint only
easing.damp3(api.dofRef.curent.target, api.hitpoint, 0.5, delta) // custom easing
})
```
#### Selective bloom
Bloom is selective by default, you control it not on the effect pass but on the materials by lifting their colors out of 0-1 range. a `luminanceThreshold` of 1 ensures that ootb nothing will glow, only the materials you pick. For this to work `toneMapped` has to be false on the materials, because it would otherwise clamp colors between 0 and 1 again.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"typegen": "tsc --emitDeclarationOnly || true"
},
"dependencies": {
"maath": "^0.5.3",
"postprocessing": "^6.30.2",
"screen-space-reflections": "2.5.0",
"three-stdlib": "^2.21.10"
Expand Down
159 changes: 159 additions & 0 deletions src/Autofocus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import * as THREE from 'three'
import React, {
useRef,
useContext,
useState,
useEffect,
useCallback,
forwardRef,
useImperativeHandle,
RefObject,
} from 'react'
import { useThree, useFrame, createPortal } from '@react-three/fiber'
import { CopyPass, DepthPickingPass } from 'postprocessing'
import { DepthOfField, EffectComposerContext } from './index'
import { DepthOfFieldEffect } from 'postprocessing'
import { easing } from 'maath'

export type AutofocusProps = typeof DepthOfField & {
target?: [number, number, number]
mouse?: boolean
debug?: number
manual?: boolean
smoothTime?: number
}

export type AutofocusApi = {
dofRef: RefObject<DepthOfFieldEffect>
hitpoint: THREE.Vector3
update: (delta: number, updateTarget: boolean) => void
}

export const Autofocus = forwardRef<AutofocusApi, AutofocusProps>(
(
{ target = undefined, mouse: followMouse = false, debug = undefined, manual = false, smoothTime = 0, ...props },
fref
) => {
const dofRef = useRef<DepthOfFieldEffect>(null)
const hitpointRef = useRef<THREE.Mesh>(null)
const targetRef = useRef<THREE.Mesh>(null)

const { size, gl, scene } = useThree()
const { composer, camera } = useContext(EffectComposerContext)

// see: https://codesandbox.io/s/depthpickingpass-x130hg
const [depthPickingPass] = useState(new DepthPickingPass())
useEffect(() => {
const copyPass = new CopyPass()
composer.addPass(depthPickingPass)
composer.addPass(copyPass)
return () => {
composer.removePass(copyPass)
composer.removePass(depthPickingPass)
}
}, [composer, depthPickingPass])

const [hitpoint] = useState(new THREE.Vector3(0, 0, 0))

const [ndc] = useState(new THREE.Vector3(0, 0, 0))
const getHit = useCallback(
async (x: number, y: number) => {
ndc.x = x
ndc.y = y
ndc.z = await depthPickingPass.readDepth(ndc)
ndc.z = ndc.z * 2.0 - 1.0
const hit = 1 - ndc.z > 0.0000001 // it is missed if ndc.z is close to 1
return hit ? ndc.unproject(camera) : false
},
[ndc, depthPickingPass, camera]
)

const [pointer] = useState(new THREE.Vector2())
useEffect(() => {
if (!followMouse) return

async function onPointermove(e: PointerEvent) {
const clientX = e.clientX - size.left
const clientY = e.clientY - size.top
const x = (clientX / size.width) * 2.0 - 1.0
const y = -(clientY / size.height) * 2.0 + 1.0

pointer.set(x, y)
}
gl.domElement.addEventListener('pointermove', onPointermove, {
passive: true,
})

return () => void gl.domElement.removeEventListener('pointermove', onPointermove)
}, [gl.domElement, hitpoint, size, followMouse, getHit, pointer])

const update = useCallback(
async (delta: number, updateTarget = true) => {
// Update hitpoint
if (target) {
hitpoint.set(...target)
} else {
const { x, y } = followMouse ? pointer : { x: 0, y: 0 }
const hit = await getHit(x, y)
if (hit) hitpoint.copy(hit)
}

// Update target
if (updateTarget && dofRef.current?.target) {
if (smoothTime > 0 && delta > 0) {
easing.damp3(dofRef.current.target, hitpoint, smoothTime, delta)
} else {
dofRef.current.target.copy(hitpoint)
}
}
},
[target, hitpoint, followMouse, getHit, smoothTime, pointer]
)

useFrame(async (_, delta) => {
if (manual) return
update(delta)
})

useFrame(() => {
if (hitpointRef.current) {
hitpointRef.current.position.copy(hitpoint)
}
if (targetRef.current && dofRef.current?.target) {
targetRef.current.position.copy(dofRef.current.target)
}
})

// Ref API
useImperativeHandle(
fref,
() => ({
dofRef,
hitpoint,
update,
}),
[hitpoint, update]
)

return (
<>
{debug &&
createPortal(
<>
<mesh ref={hitpointRef}>
<sphereGeometry args={[debug, 16, 16]} />
<meshBasicMaterial color="#00ff00" opacity={1} transparent depthWrite={false} />
</mesh>
<mesh ref={targetRef}>
<sphereGeometry args={[debug / 2, 16, 16]} />
<meshBasicMaterial color="#00ff00" opacity={0.5} transparent depthWrite={false} />
</mesh>
</>,
scene
)}

<DepthOfField ref={dofRef} {...props} target={hitpoint} />
</>
)
}
)
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ export * from './effects/TiltShift2'
export * from './effects/SSR'

export * from './Selection'
export * from './Autofocus'
export * from './EffectComposer'
export * from './util'
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3219,6 +3219,11 @@ lru-cache@^9.0.0:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.1.tgz#c58a93de58630b688de39ad04ef02ef26f1902f1"
integrity sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==

maath@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/maath/-/maath-0.5.3.tgz#777a1f9b8463c6ffb199ea43406874a357c0cd58"
integrity sha512-ut63A4zTd9abtpi+sOHW1fPWPtAFrjK0E17eAthx1k93W/T2cWLKV5oaswyotJVDvvW1EXSdokAqhK5KOu0Qdw==

magic-string@^0.27.0:
version "0.27.0"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3"
Expand Down

0 comments on commit a0702b8

Please sign in to comment.