Skip to content

Commit

Permalink
feat: rendercubemap
Browse files Browse the repository at this point in the history
  • Loading branch information
drcmda committed Oct 8, 2023
1 parent 5a15ac8 commit 6a88b88
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 0 deletions.
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ The `native` route of the library **does not** export `Html` or `Loader`. The de
<li><a href="#hud">Hud</a></li>
<li><a href="#view">View</a></li>
<li><a href="#rendertexture">RenderTexture</a></li>
<li><a href="#rendercubetexture">RenderCubeTexture</a></li>
<li><a href="#mask">Mask</a></li>
<li><a href="#meshportalmaterial">MeshPortalMaterial</a></li>
</ul>
Expand Down Expand Up @@ -3583,6 +3584,63 @@ type Props = JSX.IntrinsicElements['texture'] & {
<mesh />
```

#### RenderCubeTexture

<p>
<a href="https://codesandbox.io/s/7qytdw"><img width="20%" src="https://codesandbox.io/api/v1/sandboxes/7qytdw/screenshot.png" alt="Demo"/></a>
</p>

This component allows you to render a live scene into a cubetexture which you can then apply to a material, for instance as an environment map (via the envMap property). The contents of it run inside a portal and are separate from the rest of the canvas, therefore you can have events in there, environment maps, etc.

```tsx
export type RenderCubeTextureProps = Omit<JSX.IntrinsicElements['texture'], 'rotation'> & {
/** Optional stencil buffer, defaults to false */
stencilBuffer?: boolean
/** Optional depth buffer, defaults to true */
depthBuffer?: boolean
/** Optional generate mipmaps, defaults to false */
generateMipmaps?: boolean
/** Optional render priority, defaults to 0 */
renderPriority?: number
/** Optional event priority, defaults to 0 */
eventPriority?: number
/** Optional frame count, defaults to Infinity. If you set it to 1, it would only render a single frame, etc */
frames?: number
/** Optional event compute, defaults to undefined */
compute?: (event: any, state: any, previous: any) => false | undefined
/** Flip cubemap, see https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLCubeRenderTarget.js */
flip?: boolean
/** Cubemap resolution (for each of the 6 takes), null === full screen resolution, default: 896 */
resolution?: number
/** Children will be rendered into a portal */
children: React.ReactNode
near?: number
far?: number
position?: ReactThreeFiber.Vector3
rotation?: ReactThreeFiber.Euler
scale?: ReactThreeFiber.Vector3
quaternion?: ReactThreeFiber.Quaternion
matrix?: ReactThreeFiber.Matrix4
matrixAutoUpdate?: boolean
}

export type RenderCubeTextureApi = {
scene: THREE.Scene
fbo: THREE.WebGLCubeRenderTarget
camera: THREE.CubeCamera
}
```

```jsx
const api = useRef<RenderCubeTextureApi>(null!)
// ...
<mesh ref={api}>
<sphereGeometry args={[1, 64, 64]} />
<meshBasicMaterial>
<RenderCubeTexture attach="envMap" flip>
<mesh />
```

#### Mask

<p>
Expand Down
149 changes: 149 additions & 0 deletions src/core/RenderCubeTexture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import * as THREE from 'three'
import * as React from 'react'
import { ReactThreeFiber, createPortal, useFrame, useThree } from '@react-three/fiber'
import { ForwardRefComponent } from '../helpers/ts-utils'

export type RenderCubeTextureProps = Omit<JSX.IntrinsicElements['texture'], 'rotation'> & {
/** Optional stencil buffer, defaults to false */
stencilBuffer?: boolean
/** Optional depth buffer, defaults to true */
depthBuffer?: boolean
/** Optional generate mipmaps, defaults to false */
generateMipmaps?: boolean
/** Optional render priority, defaults to 0 */
renderPriority?: number
/** Optional event priority, defaults to 0 */
eventPriority?: number
/** Optional frame count, defaults to Infinity. If you set it to 1, it would only render a single frame, etc */
frames?: number
/** Optional event compute, defaults to undefined */
compute?: (event: any, state: any, previous: any) => false | undefined
/** Flip cubemap, see https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLCubeRenderTarget.js */
flip?: boolean
/** Cubemap resolution (for each of the 6 takes), null === full screen resolution, default: 896 */
resolution?: number
/** Children will be rendered into a portal */
children: React.ReactNode
near?: number
far?: number
position?: ReactThreeFiber.Vector3
rotation?: ReactThreeFiber.Euler
scale?: ReactThreeFiber.Vector3
quaternion?: ReactThreeFiber.Quaternion
matrix?: ReactThreeFiber.Matrix4
matrixAutoUpdate?: boolean
}

export type RenderCubeTextureApi = {
scene: THREE.Scene
fbo: THREE.WebGLCubeRenderTarget
camera: THREE.CubeCamera
}

export const RenderCubeTexture: ForwardRefComponent<RenderCubeTextureProps, RenderCubeTextureApi> = React.forwardRef(
(
{
children,
compute,
renderPriority = -1,
eventPriority = 0,
frames = Infinity,
stencilBuffer = false,
depthBuffer = true,
generateMipmaps = false,
resolution = 896,
near = 0.1,
far = 1000,
flip = false,
position,
rotation,
scale,
quaternion,
matrix,
matrixAutoUpdate,
...props
},
forwardRef
) => {
const { size, viewport } = useThree()

const camera = React.useRef<THREE.CubeCamera>(null!)
const fbo = React.useMemo(() => {
const fbo = new THREE.WebGLCubeRenderTarget(
Math.max((resolution || size.width) * viewport.dpr, (resolution || size.height) * viewport.dpr),
{
stencilBuffer,
depthBuffer,
generateMipmaps,
}
)
fbo.texture.isRenderTargetTexture = !flip
fbo.texture.flipY = true
fbo.texture.type = THREE.HalfFloatType
return fbo
}, [resolution, flip])

React.useEffect(() => {
return () => fbo.dispose()
}, [fbo])

const [vScene] = React.useState(() => new THREE.Scene())
const uvCompute = React.useCallback((event, state, previous) => {
// https://github.com/pmndrs/react-three-fiber/pull/782
// Events trigger outside of canvas when moved, use offsetX/Y by default and allow overrides
state.pointer.set((event.offsetX / state.size.width) * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1)
state.raycaster.setFromCamera(state.pointer, state.camera)
}, [])

React.useImperativeHandle(forwardRef, () => ({ scene: vScene, fbo, camera: camera.current }), [fbo])

return (
<>
{createPortal(
<Container renderPriority={renderPriority} frames={frames} camera={camera}>
{children}
{/* Without an element that receives pointer events state.pointer will always be 0/0 */}
<group onPointerOver={() => null} />
</Container>,
vScene,
{ events: { compute: compute || uvCompute, priority: eventPriority } }
)}
<primitive object={fbo.texture} {...props} />
<cubeCamera
ref={camera}
args={[near, far, fbo]}
position={position}
rotation={rotation}
scale={scale}
quaternion={quaternion}
matrix={matrix}
matrixAutoUpdate={matrixAutoUpdate}
/>
</>
)
}
)

// The container component has to be separate, it can not be inlined because "useFrame(state" when run inside createPortal will return
// the portals own state which includes user-land overrides (custom cameras etc), but if it is executed in <RenderTexture>'s render function
// it would return the default state.
function Container({
frames,
renderPriority,
children,
camera,
}: {
frames: number
renderPriority: number
children: React.ReactNode
camera: React.MutableRefObject<THREE.CubeCamera>
}) {
let count = 0
useFrame((state) => {
if (frames === Infinity || count < frames) {
camera.current.update(state.gl, state.scene)
count++
}
}, renderPriority)
return <>{children}</>
}
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export * from './PerformanceMonitor'

// Portals
export * from './RenderTexture'
export * from './RenderCubeTexture'
export * from './Mask'
export * from './Hud'
export * from './MeshPortalMaterial'

1 comment on commit 6a88b88

@vercel
Copy link

@vercel vercel bot commented on 6a88b88 Oct 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.