threlte logo

WebGPURenderer

This example shows how to run the experimental Three.js WebGPURenderer renderer with Threlte’s <Canvas>.

<script lang="ts">
  import { Canvas, extend } from '@threlte/core'
  import Scene from './Scene.svelte'
  import * as THREE from 'three/webgpu'

  extend(THREE)
</script>

<div>
  <Canvas
    createRenderer={(canvas) => {
      return new THREE.WebGPURenderer({
        canvas,
        antialias: true,
        forceWebGL: false
      })
    }}
  >
    <Scene />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import { T, useTask, useThrelte, watch } from '@threlte/core'
  import { OrbitControls } from '@threlte/extras'
  import Stats from 'three/addons/libs/stats.module.js'
  import * as THREE from 'three/webgpu'

  const { scene, size, renderer, invalidate } = useThrelte()

  scene.background = new THREE.Color(0xc1c1c1)

  let geometries: THREE.BufferGeometry[] = [
    new THREE.ConeGeometry(1.0, 2.0, 3, 1),
    new THREE.BoxGeometry(2.0, 2.0, 2.0),
    new THREE.PlaneGeometry(2.0, 2, 1, 1),
    new THREE.CapsuleGeometry(),
    new THREE.CircleGeometry(1.0, 3),
    new THREE.CylinderGeometry(1.0, 1.0, 2.0, 3, 1),
    new THREE.DodecahedronGeometry(1.0, 0),
    new THREE.IcosahedronGeometry(1.0, 0),
    new THREE.OctahedronGeometry(1.0, 0),
    new THREE.PolyhedronGeometry([0, 0, 0], [0, 0, 0], 1, 0),
    new THREE.RingGeometry(1.0, 1.5, 3),
    new THREE.SphereGeometry(1.0, 3, 2),
    new THREE.TetrahedronGeometry(1.0, 0),
    new THREE.TorusGeometry(1.0, 0.5, 3, 3),
    new THREE.TorusKnotGeometry(1.0, 0.5, 20, 3, 1, 1)
  ]

  const group = new THREE.Group()
  group.static = true

  const position = new THREE.Vector3()
  const rotation = new THREE.Euler()
  const quaternion = new THREE.Quaternion()
  const scale = new THREE.Vector3()
  const count = 3000

  function randomizeMatrix(matrix: THREE.Matrix4) {
    position.x = Math.random() * 80 - 40
    position.y = Math.random() * 80 - 40
    position.z = Math.random() * 80 - 40

    rotation.x = Math.random() * 2 * Math.PI
    rotation.y = Math.random() * 2 * Math.PI
    rotation.z = Math.random() * 2 * Math.PI

    quaternion.setFromEuler(rotation)

    const factorScale = 1
    scale.x = scale.y = scale.z = 0.35 * factorScale + Math.random() * 0.5 * factorScale

    return matrix.compose(position, quaternion, scale)
  }

  const randomizeRotationSpeed = (rotation: THREE.Euler) => {
    rotation.x = Math.random() * 0.05
    rotation.y = Math.random() * 0.05
    rotation.z = Math.random() * 0.05
    return rotation
  }

  for (let i = 0; i < count; i++) {
    const material = new THREE.MeshToonNodeMaterial({
      color: new THREE.Color(Math.random() * 0xffffff),
      side: THREE.DoubleSide
    })

    const child = new THREE.Mesh(geometries[i % geometries.length], material)
    randomizeMatrix(child.matrix)
    child.matrix.decompose(child.position, child.quaternion, child.scale)
    child.userData.rotationSpeed = randomizeRotationSpeed(new THREE.Euler())
    child.frustumCulled = false
    group.add(child)
  }

  watch(size, () => {
    group.needsUpdate = true
  })

  const stats = new Stats()
  renderer.domElement.parentNode?.appendChild(stats.dom)

  stats.begin()

  useTask(() => {
    stats.end()

    for (const child of group.children) {
      if (!child) return

      const { rotationSpeed } = child.userData

      child.rotation.set(
        child.rotation.x + rotationSpeed.x,
        child.rotation.y + rotationSpeed.y,
        child.rotation.z + rotationSpeed.z
      )
    }

    stats.begin()
  })
</script>

<T is={group} />

<T.PerspectiveCamera
  position.z={50}
  makeDefault
>
  <OrbitControls
    autoRotate
    enableZoom={false}
    autoRotateSpeed={1}
    onchange={invalidate}
  />
</T.PerspectiveCamera>

<T.DirectionalLight intensity={3.4} />

Adapted from this Three.js example.

In a browser that supports WebGPU, such as Chrome, this example will default to the new WebGPURenderer. In other browsers, it will fallback to a WebGL Renderer.

WebGPU support in Three.js is still in an early stage and the implementation may change from Three.js version to version. This guide is based on Three.js r169.

Using the Three.js WebGPU build

Since Three.js r167, Three.js provides a dedicated WebGPU build, available as three/webgpu. Whenever you’re targeting WebGPU, you must always import the WebGPU build instead of the default three build:

import * as THREE from 'three'
import * as THREE from 'three/webgpu'

You also need to extend the Threlte catalogue with the WebGPU build. This enables the <T> component to pick up the WebGPU build as well.

App.svelte
<script>
  import { extend } from '@threlte/core'
  import Scene from './Scene.svelte'
  import * as THREE from 'three/webgpu'

  extend(THREE)
</script>

<Canvas
  createRenderer={(canvas) => {
    return new THREE.WebGPURenderer({
      canvas,
      antialias: true,
      forceWebGL: false
    })
  }}
>
  <Scene />
</Canvas>

Vite

The WebGPURenderer uses top-level async to determine WebGPU compatibility. Vite will often throw an error when it detects top level await.

To circumvent this issue, the following can be added to your Vite config.

// vite.config.js
optimizeDeps: {
  esbuildOptions: {
    target: 'esnext'
  }
},
build: {
  target: 'esnext'
}

Alternatively, vite-plugin-top-level-await can be used, although less success has been reported with this method.