threlte logo
@threlte/xr

<VRButton>

<VRButton /> is an HTML <button /> that can be used to init a VR session. It will also display info about browser support.

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

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

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  // The following components started as copies from https://fun-bit.vercel.app/
  import BirchTrees from './assets/birch.svelte'
  import Trees from './assets/tree.svelte'
  import Bushes from './assets/bush.svelte'
  import Rocks from './assets/rock.svelte'

  const numberOfObjects = 50
  const distinctObjects = 4
  const commonRatio = 0.5

  function calculateExponentialSumValues(
    total: number,
    numberOfValues: number,
    commonRatio: number
  ): number[] {
    let result = []
    let remainingTotal = total

    for (let i = 0; i < numberOfValues - 1; i++) {
      let term = Math.ceil(remainingTotal * (1 - commonRatio))
      result.push(term)
      remainingTotal -= term
    }

    // The last term to ensure the sum is exactly equal to the total
    result.push(remainingTotal)

    return result
  }

  const data = $derived.by(() => {
    const exponentialSumValues = calculateExponentialSumValues(
      numberOfObjects,
      distinctObjects,
      commonRatio
    )
    const totalBushes = exponentialSumValues[0] ?? 0
    const totalTrees = exponentialSumValues[1] ?? 0
    const totalBirchTrees = exponentialSumValues[2] ?? 0
    const totalRocks = exponentialSumValues[3] ?? 0

    const randomBushes = []
    const randomTrees = []
    const randomBirchTrees = []
    const randomRocks = []

    for (let i = 0; i < totalBushes; i++) {
      randomBushes.push([Math.random(), Math.random(), Math.random(), Math.random()])
      if (i < totalTrees) {
        randomTrees.push([Math.random(), Math.random(), Math.random(), Math.random()])
      }
      if (i < totalBirchTrees) {
        randomBirchTrees.push([Math.random(), Math.random(), Math.random(), Math.random()])
      }
      if (i < totalRocks) {
        randomRocks.push([Math.random(), Math.random(), Math.random(), Math.random()])
      }
    }

    return {
      randomBushes,
      randomTrees,
      randomBirchTrees,
      randomRocks
    }
  })
</script>

<Bushes transformData={data.randomBushes as [number, number, number, number][]} />
<BirchTrees transformData={data.randomBirchTrees as [number, number, number, number][]} />
<Trees transformData={data.randomTrees as [number, number, number, number][]} />
<Rocks transformData={data.randomRocks as [number, number, number, number][]} />
<script lang="ts">
  import { T } from '@threlte/core'
  import { OrbitControls } from '@threlte/extras'
  import { XR } from '@threlte/xr'
  import Random from './Random.svelte'
</script>

<XR />

<T.PerspectiveCamera
  makeDefault
  position={[20, 20, 20]}
>
  <OrbitControls maxPolarAngle={1.56} />
</T.PerspectiveCamera>

<T.DirectionalLight
  position={[3, 10, 7]}
  castShadow
  shadow.camera.top={10}
  shadow.camera.left={-10}
  shadow.camera.right={10}
  shadow.camera.bottom={-10}
/>
<T.AmbientLight />

<T.Mesh
  rotation.x={-Math.PI / 2}
  receiveShadow
>
  <T.PlaneGeometry args={[20, 20, 1, 1]} />
  <T.MeshStandardMaterial color="green" />
</T.Mesh>

<Random />
<script lang="ts">
  import { Mesh, MeshStandardMaterial, RepeatWrapping, DoubleSide } from 'three'
  import { T } from '@threlte/core'
  import { useGltf, useTexture, InstancedMesh, Instance } from '@threlte/extras'

  interface Props {
    transformData?: [number, number, number, number][]
  }

  let { transformData = [] }: Props = $props()

  type GLTFResult = {
    nodes: {
      Cube004: Mesh
      Cube004_1: Mesh
    }
    materials: {
      BirchTree_Bark: MeshStandardMaterial
      BirchTree_Leaves: MeshStandardMaterial
    }
  }

  const assets = Promise.all([
    useGltf<GLTFResult>('https://fun-bit.vercel.app/Ultimate-Stylized-Nature/BirchTree_1.gltf'),
    useTexture('https://fun-bit.vercel.app/Ultimate-Stylized-Nature/Textures/BirchTree_Bark.png'),
    useTexture('https://fun-bit.vercel.app/Ultimate-Stylized-Nature/Textures/BirchTree_Leaves.png'),
    useTexture(
      'https://fun-bit.vercel.app/Ultimate-Stylized-Nature/Textures/BirchTree_Bark_Normal.png'
    )
  ])
</script>

{#await assets then [$gltf, $texture1, $texture2, $normalMap1]}
  <InstancedMesh castShadow>
    <T is={$gltf.nodes.Cube004.geometry} />
    <T.MeshStandardMaterial
      map={$texture1}
      map.wrapS={RepeatWrapping}
      map.wrapT={RepeatWrapping}
      normalMap={$normalMap1}
      normalMap.wrapS={RepeatWrapping}
      normalMap.wrapT={RepeatWrapping}
    />
    {#each transformData as randomValues}
      {@const x = randomValues[0] * 20 - 10}
      {@const z = randomValues[1] * 20 - 10}
      {@const rot = randomValues[2] * Math.PI * 2}
      {@const scale = randomValues[3] * 2 + 1}
      <Instance
        position.x={x}
        position.z={z}
        rotation.y={rot}
        {scale}
      />
    {/each}
  </InstancedMesh>
  <InstancedMesh castShadow>
    <T is={$gltf.nodes.Cube004_1.geometry} />
    <T.MeshStandardMaterial
      map={$texture2}
      side={DoubleSide}
      alphaTest={0.5}
    />
    {#each transformData as randomValues}
      {@const x = randomValues[0] * 20 - 10}
      {@const z = randomValues[1] * 20 - 10}
      {@const rot = randomValues[2] * Math.PI * 2}
      {@const scale = randomValues[3] * 2 + 1}
      <Instance
        position.x={x}
        position.z={z}
        rotation.y={rot}
        {scale}
      />
    {/each}
  </InstancedMesh>
{/await}
<script lang="ts">
  import * as THREE from 'three'
  import { T } from '@threlte/core'
  import { useGltf, useTexture, InstancedMesh, Instance } from '@threlte/extras'

  interface Props {
    transformData?: [number, number, number, number][]
  }

  let { transformData = [] }: Props = $props()

  type GLTFResult = {
    nodes: {
      Bush: THREE.Mesh
    }
    materials: {
      Bush_Leaves: THREE.MeshStandardMaterial
    }
  }

  const gltf = useGltf<GLTFResult>('https://fun-bit.vercel.app/Ultimate-Stylized-Nature/Bush.gltf')
  const texture1 = useTexture(
    'https://fun-bit.vercel.app/Ultimate-Stylized-Nature/Textures/Bush_Leaves.png'
  )

  const assets = Promise.all([gltf, texture1])
</script>

{#await assets then [$gltf, $texture1]}
  <InstancedMesh
    castShadow
    receiveShadow
  >
    <T is={$gltf.nodes.Bush.geometry} />
    <T.MeshStandardMaterial
      map={$texture1}
      alphaTest={0.2}
    />
    {#each transformData as randomValues}
      {@const x = randomValues[0] * 20 - 10}
      {@const z = randomValues[1] * 20 - 10}
      {@const rot = randomValues[2] * Math.PI * 2}
      {@const scale = randomValues[3] * 2 + 0.5}
      <T.Group
        position.x={x}
        position.z={z}
        rotation.y={rot}
        {scale}
      >
        <Instance rotation={[1.96, -0.48, -0.85]} />
      </T.Group>
    {/each}
  </InstancedMesh>
{/await}
<script lang="ts">
  import * as THREE from 'three'
  import { T } from '@threlte/core'
  import { useGltf, InstancedMesh, Instance } from '@threlte/extras'

  interface Props {
    transformData?: [number, number, number, number][]
  }

  let { transformData = [] }: Props = $props()

  type GLTFResult = {
    nodes: {
      Rock_2: THREE.Mesh
    }
    materials: {
      Rock: THREE.MeshStandardMaterial
    }
  }

  const gltf = useGltf<GLTFResult>(
    'https://fun-bit.vercel.app/Ultimate-Stylized-Nature/Rock_2.gltf'
  )
</script>

{#if $gltf}
  <InstancedMesh
    castShadow
    receiveShadow
  >
    <T is={$gltf.nodes.Rock_2.geometry} />
    <T.MeshStandardMaterial color="grey" />
    {#each transformData as randomValues}
      {@const x = randomValues[0] * 20 - 10}
      {@const z = randomValues[1] * 20 - 10}
      {@const rot = randomValues[2] * Math.PI * 2}
      {@const scale = randomValues[3] + 0.5}
      <Instance
        position.x={x}
        position.z={z}
        rotation.y={rot}
        {scale}
      />
    {/each}
  </InstancedMesh>
{/if}
<script lang="ts">
  import * as THREE from 'three'
  import { T } from '@threlte/core'
  import { useGltf, useTexture, InstancedMesh, Instance } from '@threlte/extras'

  interface Props {
    transformData?: [number, number, number, number][]
  }

  let { transformData = [] }: Props = $props()

  type GLTFResult = {
    nodes: {
      Cylinder001: THREE.Mesh
      Cylinder001_1: THREE.Mesh
    }
    materials: {
      NormalTree_Bark: THREE.MeshStandardMaterial
      NormalTree_Leaves: THREE.MeshStandardMaterial
    }
  }

  const assets = Promise.all([
    useGltf<GLTFResult>('https://fun-bit.vercel.app/Ultimate-Stylized-Nature/NormalTree_1.gltf'),
    useTexture('https://fun-bit.vercel.app/Ultimate-Stylized-Nature/Textures/NormalTree_Bark.png'),
    useTexture(
      'https://fun-bit.vercel.app/Ultimate-Stylized-Nature/Textures/NormalTree_Leaves.png'
    ),
    useTexture(
      'https://fun-bit.vercel.app/Ultimate-Stylized-Nature/Textures/NormalTree_Bark_Normal.png'
    )
  ])
</script>

{#await assets then [$gltf, $texture1, $texture2, $normalMap1]}
  <InstancedMesh castShadow>
    <T is={$gltf.nodes.Cylinder001.geometry} />
    <T.MeshStandardMaterial
      map={$texture1}
      map.wrapS={THREE.RepeatWrapping}
      map.wrapT={THREE.RepeatWrapping}
      normalMap={$normalMap1}
      normalMap.wrapS={THREE.RepeatWrapping}
      normalMap.wrapT={THREE.RepeatWrapping}
    />
    {#each transformData as randomValues}
      {@const x = randomValues[0] * 20 - 10}
      {@const z = randomValues[1] * 20 - 10}
      {@const rot = randomValues[2] * Math.PI * 2}
      {@const scale = randomValues[3] * 2 + 1}
      <Instance
        position.x={x}
        position.z={z}
        rotation.y={rot}
        {scale}
      />
    {/each}
  </InstancedMesh>
  <InstancedMesh castShadow>
    <T is={$gltf.nodes.Cylinder001_1.geometry} />
    <T.MeshStandardMaterial
      map={$texture2}
      side={THREE.DoubleSide}
      alphaTest={0.5}
    />
    {#each transformData as randomValues}
      {@const x = randomValues[0] * 20 - 10}
      {@const z = randomValues[1] * 20 - 10}
      {@const rot = randomValues[2] * Math.PI * 2}
      {@const scale = randomValues[3] * 2 + 1}
      <Instance
        position.x={x}
        position.z={z}
        rotation.y={rot}
        {scale}
      />
    {/each}
  </InstancedMesh>
{/await}

Component Signature

Events

name
payload
description

click
{ state: 'unsupported' | 'insecure' | 'blocked' | 'supported'; nativeEvent: MouseEvent }
Fired when a user clicks the VR button.

error
Error
Fired when an enter / exit session error occurs.