threlte logo
@threlte/extras

useFBO

useFBO (Framebuffer Object) is a port of drei’s useFBO that creates a THREE.WebGLRenderTarget.

Framebuffer objects are useful when you want to render a scene to something other than the canvas. In ThreeJS, you do this by rendering the scene to a RenderTarget. The render target has a texture property that can be used for various things such as postprocessing effects or applying the texture to something in the scene.

<script lang="ts">
  import { Canvas } from '@threlte/core'
  import Scene from './Scene.svelte'
</script>

<div>
  <Canvas>
    <Scene />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import { T, useTask, useThrelte } from '@threlte/core'
  import { OrbitControls, Sky, useFBO, useGltf } from '@threlte/extras'
  import {
    CameraHelper,
    Group,
    Mesh,
    MeshStandardMaterial,
    PerspectiveCamera,
    PlaneGeometry
  } from 'three'

  const gltf = useGltf('/models/Duck.glb')

  const { renderer, scene } = useThrelte()
  const fbo = useFBO({
    size: {
      width: 1024,
      height: 2048
    }
  })

  const group = new Group()

  const cameraMeshHeight = 1
  const cameraRotationRadius = 5

  // create the plane and other camera in the script tag so that we don't have to bind to the ref and worry about `undefined` cases
  const rotatingCamera = new PerspectiveCamera(40, 1, 2, 8)
  const helper = new CameraHelper(rotatingCamera)

  const planeMesh = new Mesh(new PlaneGeometry(), new MeshStandardMaterial({ map: fbo.texture }))
  planeMesh.position.setZ(-1 * (cameraMeshHeight + cameraRotationRadius))
  planeMesh.scale.setScalar(10)

  let time = 0
  useTask((delta) => {
    time += delta * 0.2
    const c = Math.cos(time)
    const s = Math.sin(time)
    rotatingCamera.position.set(cameraRotationRadius * c, 2 * c * s, cameraRotationRadius * s)
    rotatingCamera.lookAt(group.position)

    planeMesh.visible = false
    helper.visible = false
    const lastRenderTarget = renderer.getRenderTarget()

    renderer.setRenderTarget(fbo)
    renderer.render(scene, rotatingCamera)

    helper.visible = true
    planeMesh.visible = true
    renderer.setRenderTarget(lastRenderTarget)
  })
</script>

<T.PerspectiveCamera
  position.x={5}
  position.y={5}
  position.z={10}
  makeDefault
>
  <OrbitControls />
</T.PerspectiveCamera>

<T.AmbientLight />

<Sky />

<T
  is={rotatingCamera}
  manual
/>
<T
  is={helper}
  attach={scene}
/>

<T is={planeMesh} />

<T is={group}>
  <T.Group position.y={-1}>
    {#await gltf then { scene }}
      <T is={scene} />
    {/await}
  </T.Group>
</T>

Options

useFBO’s options object extends Three.RenderTargetOptions. The additional properties are listed below.

size

size is used to set the width and height of the render target.

If size is not provided, the target’s size is set to the size of the canvas and it will follow any updates to the size.

const { size } = useThrelte()

// use `size`'s width and height
const target = useFBO()

assert(target.width === size.current.width)
assert(target.height === size.current.height)

If size is provided, the width and height of the texture are taken from size.

const size = { width: 512, height: 512 }

const target = useFBO({ size })
assert(target.width === size.width)
assert(target.height === size.height)

width and height both default to 1 if they are not found on the size object.

const size = { width: 512 }

const target = useFBO({ size })
assert(target.width === size.width)
assert(target.height === 1)
const size = { height: 512 }

const target = useFBO({ size })
assert(target.width === 1)
assert(target.height === size.height)
const size = {}

const target = useFBO({ size })
assert(target.width === 1)
assert(target.height === 1)

depth

depth is used to assign a DepthTexture to the depthTexture property of the render target. It can either be a boolean, “size” object, or DepthTexture instance. By default, depth is set to false and a depth texture is not created. When depth is used, the scene’s depth is rendered to the depthTexture of the render target.

If depth is set to true, a depth texture is created and sized to match the size of the render target, after an appropriate size has been given to the render target.

const { size } = useThrelte()

const target = useFBO({ depth: true })

assert(target.depthTexture.image.width === size.current.width)
assert(target.depthTexture.image.height === size.current.height)

If depth is a depth texture instance, it is assigned to the depthTexture property of the returned render target.

const depthTexture = new DepthTexture(512, 512)

const target = useFBO({ depth: depthTexture })

assert(target.depthTexture === depthTexture)

If depth is set to a “size” object, the depth texture size is set the width and height of the object.

const depth = { width: 512, height: 512 }

const target = useFBO({ depth })

assert(target.depthTexture.image.width === depth.width)
assert(target.depthTexture.image.height === depth.height)

width and height default to 1 if they are not found on the depth object.

const depth = { width: 512 }

const target = useFBO({ depth })

assert(target.depthTexture.image.width === depth.width)
assert(target.depthTexture.image.height === 1)
const depth = { height: 512 }

const target = useFBO({ depth })

assert(target.depthTexture.image.width === 1)
assert(target.depthTexture.image.height === depth.height)
const depth = {}

const target = useFBO({ depth })

assert(target.depthTexture.image.width === 1)
assert(target.depthTexture.image.height === 1)