Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: allow ortho and pers cams to film into fbos
- Loading branch information
Showing
3 changed files
with
174 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,80 @@ | ||
import * as THREE from 'three' | ||
import * as React from 'react' | ||
import { OrthographicCamera as OrthographicCameraImpl } from 'three' | ||
import { useThree } from '@react-three/fiber' | ||
import { useThree, useFrame } from '@react-three/fiber' | ||
import mergeRefs from 'react-merge-refs' | ||
import { useFBO } from './useFBO' | ||
|
||
type Props = JSX.IntrinsicElements['orthographicCamera'] & { | ||
const isFunction = (node: any): node is Function => typeof node === 'function' | ||
|
||
type Props = Omit<JSX.IntrinsicElements['orthographicCamera'], 'children'> & { | ||
/** Registers the camera as the system default, fiber will start rendering with it */ | ||
makeDefault?: boolean | ||
/** Making it manual will stop responsiveness and you have to calculate aspect ratio yourself. */ | ||
manual?: boolean | ||
children?: React.ReactNode | ||
/** The contents will either follow the camera, or be hidden when filming if you pass a function */ | ||
children?: React.ReactNode | ((texture: THREE.Texture) => React.ReactNode) | ||
/** Number of frames to render, Infinity */ | ||
frames?: number | ||
/** Resolution of the FBO, 256 */ | ||
resolution?: number | ||
} | ||
|
||
export const OrthographicCamera = React.forwardRef(({ makeDefault, ...props }: Props, ref) => { | ||
const set = useThree(({ set }) => set) | ||
const camera = useThree(({ camera }) => camera) | ||
const size = useThree(({ size }) => size) | ||
const cameraRef = React.useRef<OrthographicCameraImpl>(null!) | ||
export const OrthographicCamera = React.forwardRef( | ||
({ resolution = 256, frames = Infinity, children, makeDefault, ...props }: Props, ref) => { | ||
const set = useThree(({ set }) => set) | ||
const camera = useThree(({ camera }) => camera) | ||
const size = useThree(({ size }) => size) | ||
const cameraRef = React.useRef<OrthographicCameraImpl>(null!) | ||
const groupRef = React.useRef<THREE.Group>(null!) | ||
const fbo = useFBO(resolution) | ||
|
||
React.useLayoutEffect(() => { | ||
if (!props.manual) { | ||
cameraRef.current.updateProjectionMatrix() | ||
} | ||
}, [size, props]) | ||
|
||
React.useLayoutEffect(() => { | ||
if (!props.manual) { | ||
React.useLayoutEffect(() => { | ||
cameraRef.current.updateProjectionMatrix() | ||
} | ||
}, [size, props]) | ||
|
||
React.useLayoutEffect(() => { | ||
cameraRef.current.updateProjectionMatrix() | ||
}) | ||
|
||
React.useLayoutEffect(() => { | ||
if (makeDefault) { | ||
const oldCam = camera | ||
set(() => ({ camera: cameraRef.current! })) | ||
return () => set(() => ({ camera: oldCam })) | ||
} | ||
// The camera should not be part of the dependency list because this components camera is a stable reference | ||
// that must exchange the default, and clean up after itself on unmount. | ||
}, [cameraRef, makeDefault, set]) | ||
|
||
return ( | ||
<orthographicCamera | ||
left={size.width / -2} | ||
right={size.width / 2} | ||
top={size.height / 2} | ||
bottom={size.height / -2} | ||
ref={mergeRefs([cameraRef, ref])} | ||
{...props} | ||
/> | ||
) | ||
}) | ||
}) | ||
|
||
React.useLayoutEffect(() => { | ||
if (makeDefault) { | ||
const oldCam = camera | ||
set(() => ({ camera: cameraRef.current! })) | ||
return () => set(() => ({ camera: oldCam })) | ||
} | ||
// The camera should not be part of the dependency list because this components camera is a stable reference | ||
// that must exchange the default, and clean up after itself on unmount. | ||
}, [cameraRef, makeDefault, set]) | ||
|
||
let count = 0 | ||
const functional = isFunction(children) | ||
useFrame((state) => { | ||
if (functional && (frames === Infinity || count < frames)) { | ||
groupRef.current.visible = false | ||
state.gl.setRenderTarget(fbo) | ||
state.gl.render(state.scene, cameraRef.current) | ||
state.gl.setRenderTarget(null) | ||
groupRef.current.visible = true | ||
} | ||
}) | ||
|
||
return ( | ||
<> | ||
<orthographicCamera | ||
left={size.width / -2} | ||
right={size.width / 2} | ||
top={size.height / 2} | ||
bottom={size.height / -2} | ||
ref={mergeRefs([cameraRef, ref])} | ||
{...props} | ||
> | ||
{!functional && children} | ||
</orthographicCamera> | ||
<group ref={groupRef}>{functional && children(fbo.texture)}</group> | ||
</> | ||
) | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,73 @@ | ||
import * as THREE from 'three' | ||
import * as React from 'react' | ||
import { PerspectiveCamera as PerspectiveCameraImpl } from 'three' | ||
import { useThree } from '@react-three/fiber' | ||
import { useFrame, useThree } from '@react-three/fiber' | ||
import mergeRefs from 'react-merge-refs' | ||
import { useFBO } from './useFBO' | ||
|
||
type Props = JSX.IntrinsicElements['perspectiveCamera'] & { | ||
const isFunction = (node: any): node is Function => typeof node === 'function' | ||
|
||
type Props = Omit<JSX.IntrinsicElements['perspectiveCamera'], 'children'> & { | ||
/** Registers the camera as the system default, fiber will start rendering with it */ | ||
makeDefault?: boolean | ||
/** Making it manual will stop responsiveness and you have to calculate aspect ratio yourself. */ | ||
manual?: boolean | ||
children?: React.ReactNode | ||
/** The contents will either follow the camera, or be hidden when filming if you pass a function */ | ||
children?: React.ReactNode | ((texture: THREE.Texture) => React.ReactNode) | ||
/** Number of frames to render, Infinity */ | ||
frames?: number | ||
/** Resolution of the FBO, 256 */ | ||
resolution?: number | ||
} | ||
|
||
export const PerspectiveCamera = React.forwardRef(({ makeDefault, ...props }: Props, ref) => { | ||
const set = useThree(({ set }) => set) | ||
const camera = useThree(({ camera }) => camera) | ||
const size = useThree(({ size }) => size) | ||
const cameraRef = React.useRef<PerspectiveCameraImpl>(null!) | ||
|
||
React.useLayoutEffect(() => { | ||
if (!props.manual) { | ||
cameraRef.current.aspect = size.width / size.height | ||
} | ||
}, [size, props]) | ||
|
||
React.useLayoutEffect(() => { | ||
cameraRef.current.updateProjectionMatrix() | ||
}) | ||
|
||
React.useLayoutEffect(() => { | ||
if (makeDefault) { | ||
const oldCam = camera | ||
set(() => ({ camera: cameraRef.current! })) | ||
return () => set(() => ({ camera: oldCam })) | ||
} | ||
// The camera should not be part of the dependency list because this components camera is a stable reference | ||
// that must exchange the default, and clean up after itself on unmount. | ||
}, [cameraRef, makeDefault, set]) | ||
|
||
return <perspectiveCamera ref={mergeRefs([cameraRef, ref])} {...props} /> | ||
}) | ||
export const PerspectiveCamera = React.forwardRef( | ||
({ resolution = 256, frames = Infinity, makeDefault, children, ...props }: Props, ref) => { | ||
const set = useThree(({ set }) => set) | ||
const camera = useThree(({ camera }) => camera) | ||
const size = useThree(({ size }) => size) | ||
const cameraRef = React.useRef<PerspectiveCameraImpl>(null!) | ||
const groupRef = React.useRef<THREE.Group>(null!) | ||
const fbo = useFBO(resolution) | ||
|
||
React.useLayoutEffect(() => { | ||
if (!props.manual) { | ||
cameraRef.current.aspect = size.width / size.height | ||
} | ||
}, [size, props]) | ||
|
||
React.useLayoutEffect(() => { | ||
cameraRef.current.updateProjectionMatrix() | ||
}) | ||
|
||
let count = 0 | ||
const functional = isFunction(children) | ||
useFrame((state) => { | ||
if (functional && (frames === Infinity || count < frames)) { | ||
groupRef.current.visible = false | ||
state.gl.setRenderTarget(fbo) | ||
state.gl.render(state.scene, cameraRef.current) | ||
state.gl.setRenderTarget(null) | ||
groupRef.current.visible = true | ||
} | ||
}) | ||
|
||
React.useLayoutEffect(() => { | ||
if (makeDefault) { | ||
const oldCam = camera | ||
set(() => ({ camera: cameraRef.current! })) | ||
return () => set(() => ({ camera: oldCam })) | ||
} | ||
// The camera should not be part of the dependency list because this components camera is a stable reference | ||
// that must exchange the default, and clean up after itself on unmount. | ||
}, [cameraRef, makeDefault, set]) | ||
|
||
return ( | ||
<> | ||
<perspectiveCamera ref={mergeRefs([cameraRef, ref])} {...props}> | ||
{!functional && children} | ||
</perspectiveCamera> | ||
<group ref={groupRef}>{functional && children(fbo.texture)}</group> | ||
</> | ||
) | ||
} | ||
) |
b46d9a1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
drei – ./
drei.vercel.app
drei.pmnd.rs
drei.react-spring.io
drei-pmndrs.vercel.app
drei-git-master-pmndrs.vercel.app