Skip to content

Commit 6a88b88

Browse files
committedOct 8, 2023
feat: rendercubemap
1 parent 5a15ac8 commit 6a88b88

File tree

3 files changed

+208
-0
lines changed

3 files changed

+208
-0
lines changed
 

‎README.md

+58
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ The `native` route of the library **does not** export `Html` or `Loader`. The de
159159
<li><a href="#hud">Hud</a></li>
160160
<li><a href="#view">View</a></li>
161161
<li><a href="#rendertexture">RenderTexture</a></li>
162+
<li><a href="#rendercubetexture">RenderCubeTexture</a></li>
162163
<li><a href="#mask">Mask</a></li>
163164
<li><a href="#meshportalmaterial">MeshPortalMaterial</a></li>
164165
</ul>
@@ -3583,6 +3584,63 @@ type Props = JSX.IntrinsicElements['texture'] & {
35833584
<mesh />
35843585
```
35853586

3587+
#### RenderCubeTexture
3588+
3589+
<p>
3590+
<a href="https://codesandbox.io/s/7qytdw"><img width="20%" src="https://codesandbox.io/api/v1/sandboxes/7qytdw/screenshot.png" alt="Demo"/></a>
3591+
</p>
3592+
3593+
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.
3594+
3595+
```tsx
3596+
export type RenderCubeTextureProps = Omit<JSX.IntrinsicElements['texture'], 'rotation'> & {
3597+
/** Optional stencil buffer, defaults to false */
3598+
stencilBuffer?: boolean
3599+
/** Optional depth buffer, defaults to true */
3600+
depthBuffer?: boolean
3601+
/** Optional generate mipmaps, defaults to false */
3602+
generateMipmaps?: boolean
3603+
/** Optional render priority, defaults to 0 */
3604+
renderPriority?: number
3605+
/** Optional event priority, defaults to 0 */
3606+
eventPriority?: number
3607+
/** Optional frame count, defaults to Infinity. If you set it to 1, it would only render a single frame, etc */
3608+
frames?: number
3609+
/** Optional event compute, defaults to undefined */
3610+
compute?: (event: any, state: any, previous: any) => false | undefined
3611+
/** Flip cubemap, see https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLCubeRenderTarget.js */
3612+
flip?: boolean
3613+
/** Cubemap resolution (for each of the 6 takes), null === full screen resolution, default: 896 */
3614+
resolution?: number
3615+
/** Children will be rendered into a portal */
3616+
children: React.ReactNode
3617+
near?: number
3618+
far?: number
3619+
position?: ReactThreeFiber.Vector3
3620+
rotation?: ReactThreeFiber.Euler
3621+
scale?: ReactThreeFiber.Vector3
3622+
quaternion?: ReactThreeFiber.Quaternion
3623+
matrix?: ReactThreeFiber.Matrix4
3624+
matrixAutoUpdate?: boolean
3625+
}
3626+
3627+
export type RenderCubeTextureApi = {
3628+
scene: THREE.Scene
3629+
fbo: THREE.WebGLCubeRenderTarget
3630+
camera: THREE.CubeCamera
3631+
}
3632+
```
3633+
3634+
```jsx
3635+
const api = useRef<RenderCubeTextureApi>(null!)
3636+
// ...
3637+
<mesh ref={api}>
3638+
<sphereGeometry args={[1, 64, 64]} />
3639+
<meshBasicMaterial>
3640+
<RenderCubeTexture attach="envMap" flip>
3641+
<mesh />
3642+
```
3643+
35863644
#### Mask
35873645

35883646
<p>

‎src/core/RenderCubeTexture.tsx

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import * as THREE from 'three'
2+
import * as React from 'react'
3+
import { ReactThreeFiber, createPortal, useFrame, useThree } from '@react-three/fiber'
4+
import { ForwardRefComponent } from '../helpers/ts-utils'
5+
6+
export type RenderCubeTextureProps = Omit<JSX.IntrinsicElements['texture'], 'rotation'> & {
7+
/** Optional stencil buffer, defaults to false */
8+
stencilBuffer?: boolean
9+
/** Optional depth buffer, defaults to true */
10+
depthBuffer?: boolean
11+
/** Optional generate mipmaps, defaults to false */
12+
generateMipmaps?: boolean
13+
/** Optional render priority, defaults to 0 */
14+
renderPriority?: number
15+
/** Optional event priority, defaults to 0 */
16+
eventPriority?: number
17+
/** Optional frame count, defaults to Infinity. If you set it to 1, it would only render a single frame, etc */
18+
frames?: number
19+
/** Optional event compute, defaults to undefined */
20+
compute?: (event: any, state: any, previous: any) => false | undefined
21+
/** Flip cubemap, see https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLCubeRenderTarget.js */
22+
flip?: boolean
23+
/** Cubemap resolution (for each of the 6 takes), null === full screen resolution, default: 896 */
24+
resolution?: number
25+
/** Children will be rendered into a portal */
26+
children: React.ReactNode
27+
near?: number
28+
far?: number
29+
position?: ReactThreeFiber.Vector3
30+
rotation?: ReactThreeFiber.Euler
31+
scale?: ReactThreeFiber.Vector3
32+
quaternion?: ReactThreeFiber.Quaternion
33+
matrix?: ReactThreeFiber.Matrix4
34+
matrixAutoUpdate?: boolean
35+
}
36+
37+
export type RenderCubeTextureApi = {
38+
scene: THREE.Scene
39+
fbo: THREE.WebGLCubeRenderTarget
40+
camera: THREE.CubeCamera
41+
}
42+
43+
export const RenderCubeTexture: ForwardRefComponent<RenderCubeTextureProps, RenderCubeTextureApi> = React.forwardRef(
44+
(
45+
{
46+
children,
47+
compute,
48+
renderPriority = -1,
49+
eventPriority = 0,
50+
frames = Infinity,
51+
stencilBuffer = false,
52+
depthBuffer = true,
53+
generateMipmaps = false,
54+
resolution = 896,
55+
near = 0.1,
56+
far = 1000,
57+
flip = false,
58+
position,
59+
rotation,
60+
scale,
61+
quaternion,
62+
matrix,
63+
matrixAutoUpdate,
64+
...props
65+
},
66+
forwardRef
67+
) => {
68+
const { size, viewport } = useThree()
69+
70+
const camera = React.useRef<THREE.CubeCamera>(null!)
71+
const fbo = React.useMemo(() => {
72+
const fbo = new THREE.WebGLCubeRenderTarget(
73+
Math.max((resolution || size.width) * viewport.dpr, (resolution || size.height) * viewport.dpr),
74+
{
75+
stencilBuffer,
76+
depthBuffer,
77+
generateMipmaps,
78+
}
79+
)
80+
fbo.texture.isRenderTargetTexture = !flip
81+
fbo.texture.flipY = true
82+
fbo.texture.type = THREE.HalfFloatType
83+
return fbo
84+
}, [resolution, flip])
85+
86+
React.useEffect(() => {
87+
return () => fbo.dispose()
88+
}, [fbo])
89+
90+
const [vScene] = React.useState(() => new THREE.Scene())
91+
const uvCompute = React.useCallback((event, state, previous) => {
92+
// https://github.com/pmndrs/react-three-fiber/pull/782
93+
// Events trigger outside of canvas when moved, use offsetX/Y by default and allow overrides
94+
state.pointer.set((event.offsetX / state.size.width) * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1)
95+
state.raycaster.setFromCamera(state.pointer, state.camera)
96+
}, [])
97+
98+
React.useImperativeHandle(forwardRef, () => ({ scene: vScene, fbo, camera: camera.current }), [fbo])
99+
100+
return (
101+
<>
102+
{createPortal(
103+
<Container renderPriority={renderPriority} frames={frames} camera={camera}>
104+
{children}
105+
{/* Without an element that receives pointer events state.pointer will always be 0/0 */}
106+
<group onPointerOver={() => null} />
107+
</Container>,
108+
vScene,
109+
{ events: { compute: compute || uvCompute, priority: eventPriority } }
110+
)}
111+
<primitive object={fbo.texture} {...props} />
112+
<cubeCamera
113+
ref={camera}
114+
args={[near, far, fbo]}
115+
position={position}
116+
rotation={rotation}
117+
scale={scale}
118+
quaternion={quaternion}
119+
matrix={matrix}
120+
matrixAutoUpdate={matrixAutoUpdate}
121+
/>
122+
</>
123+
)
124+
}
125+
)
126+
127+
// The container component has to be separate, it can not be inlined because "useFrame(state" when run inside createPortal will return
128+
// the portals own state which includes user-land overrides (custom cameras etc), but if it is executed in <RenderTexture>'s render function
129+
// it would return the default state.
130+
function Container({
131+
frames,
132+
renderPriority,
133+
children,
134+
camera,
135+
}: {
136+
frames: number
137+
renderPriority: number
138+
children: React.ReactNode
139+
camera: React.MutableRefObject<THREE.CubeCamera>
140+
}) {
141+
let count = 0
142+
useFrame((state) => {
143+
if (frames === Infinity || count < frames) {
144+
camera.current.update(state.gl, state.scene)
145+
count++
146+
}
147+
}, renderPriority)
148+
return <>{children}</>
149+
}

‎src/core/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export * from './PerformanceMonitor'
138138

139139
// Portals
140140
export * from './RenderTexture'
141+
export * from './RenderCubeTexture'
141142
export * from './Mask'
142143
export * from './Hud'
143144
export * from './MeshPortalMaterial'

1 commit comments

Comments
 (1)

vercel[bot] commented on Oct 8, 2023

@vercel[bot]
Please sign in to comment.