threlte logo
@threlte/extras

<Sky>

This component adds a Three.js Sky object to the scene, renders that on-demand to a cubemap which is assigned to the default scene as the environment map.

<script lang="ts">
  import Scene from './Scene.svelte'
  import type { Preset } from './presets'
  import { Canvas } from '@threlte/core'
  import { Pane, Slider, Checkbox, Button, Folder } from 'svelte-tweakpane-ui'
  import { Sky } from '@threlte/extras'
  import { Spring } from 'svelte/motion'
  import { presets } from './presets'

  const entries = Object.entries(presets)

  const presetSpring = new Spring(presets.sunset, {
    damping: 0.95,
    precision: 0.0001,
    stiffness: 0.05
  })

  let setEnvironment = $state(true)

  let azimuth = $state(0)
  let elevation = $state(0)
  let exposure = $state(0)
  let mieCoefficient = $state(0)
  let mieDirectionalG = $state(0)
  let rayleigh = $state(0)
  let turbidity = $state(0)

  const applyPreset = (preset: Preset) => {
    azimuth = preset.azimuth
    elevation = preset.elevation
    exposure = preset.exposure
    mieCoefficient = preset.mieCoefficient
    mieDirectionalG = preset.mieDirectionalG
    rayleigh = preset.rayleigh
    turbidity = preset.turbidity
  }
  applyPreset(presets.sunset)

  $effect(() => {
    presetSpring.set({
      azimuth,
      elevation,
      exposure,
      mieCoefficient,
      mieDirectionalG,
      rayleigh,
      turbidity
    })
  })
</script>

<Pane
  title="Sky"
  position="fixed"
>
  <Checkbox
    bind:value={setEnvironment}
    label="Set Environment"
  />
  <Slider
    label="Turbidity"
    bind:value={turbidity}
    min={0}
    max={20}
  />
  <Slider
    label="Rayleigh"
    bind:value={rayleigh}
    min={0}
    max={4}
  />
  <Slider
    label="Azimuth"
    bind:value={azimuth}
    min={-180}
    max={180}
  />
  <Slider
    label="Elevation"
    bind:value={elevation}
    min={-5}
    max={90}
  />
  <Slider
    label="Mie Coefficient"
    bind:value={mieCoefficient}
    min={0}
    max={0.1}
  />
  <Slider
    label="Mie Directional G"
    bind:value={mieDirectionalG}
    min={0}
    max={1}
  />
  <Slider
    label="Exposure"
    bind:value={exposure}
    min={0}
    max={2}
  />
  <Folder title="Presets">
    {#each entries as [title, preset]}
      <Button
        {title}
        on:click={() => {
          applyPreset(preset)
        }}
      />
    {/each}
  </Folder>
</Pane>

<Canvas>
  <Sky
    {setEnvironment}
    {...presetSpring.current}
  />
  <Scene exposure={presetSpring.current.exposure} />
</Canvas>
<script lang="ts">
  import { DEG2RAD } from 'three/src/math/MathUtils.js'
  import { Grid, OrbitControls } from '@threlte/extras'
  import { SphereGeometry } from 'three'
  import { T, useThrelte } from '@threlte/core'

  let {
    exposure = 1
  }: {
    exposure?: number
  } = $props()

  const { renderer, invalidate } = useThrelte()

  $effect(() => {
    renderer.toneMappingExposure = exposure
    invalidate()
  })

  const sphereGeo = new SphereGeometry(2.5, 32, 32)
</script>

<T.PerspectiveCamera
  position={[0, 7, 18]}
  fov={60}
  near={1}
  far={20000}
  makeDefault
>
  <OrbitControls
    maxPolarAngle={85 * DEG2RAD}
    enableDamping
    target={[0, 2.5, 0]}
  />
</T.PerspectiveCamera>

<T.Mesh
  castShadow
  position.x={3}
  position.y={2.5}
>
  <T is={sphereGeo} />
  <T.MeshStandardMaterial
    roughness={0.1}
    metalness={1}
  />
</T.Mesh>

<T.Mesh
  castShadow
  position.x={-3}
  position.y={2.5}
>
  <T is={sphereGeo} />
  <T.MeshStandardMaterial />
</T.Mesh>

<Grid
  cellColor="white"
  sectionColor="white"
/>
export type Preset = {
  azimuth: number
  elevation: number
  exposure: number
  mieCoefficient: number
  mieDirectionalG: number
  rayleigh: number
  turbidity: number
}

export const presets: Record<'afternoon' | 'noon' | 'sunset' | 'night', Preset> = {
  afternoon: {
    azimuth: 180,
    elevation: 30,
    exposure: 0.65,
    mieCoefficient: 0.002,
    mieDirectionalG: 0.86,
    rayleigh: 0.3,
    turbidity: 4.78
  },
  night: {
    azimuth: 180,
    elevation: -5,
    exposure: 0.26,
    mieCoefficient: 0.038,
    mieDirectionalG: 0,
    rayleigh: 0.57,
    turbidity: 20
  },
  noon: {
    azimuth: 180,
    elevation: 85,
    exposure: 1,
    mieCoefficient: 0.013,
    mieDirectionalG: 0.7,
    rayleigh: 0.17,
    turbidity: 0.65
  },
  sunset: {
    azimuth: 180,
    elevation: 0.5,
    exposure: 0.37,
    mieCoefficient: 0.005,
    mieDirectionalG: 0.7,
    rayleigh: 3,
    turbidity: 10
  }
}

Usage

<script lang="ts">
  import { T, Canvas } from '@threlte/core'
  import { Sky } from '@threlte/extras'
</script>

<Canvas>
  <Sky elevation={0.5} />

  <T.PerspectiveCamera
    makeDefault
    position={[0, 3, 18]}
    fov={60}
    oncreate={(ref) => {
      ref.lookAt(0, 0, 0)
    }}
  />
</Canvas>

Environment

By default, this component will render the sky to the scene environment. This can be disabled by setting the setEnvironment prop to false.

<Sky setEnvironment={false} />

Performance

The <Sky> component will only re-render the cubemap when the properties change.

Component Signature

Props

name
type
required
default

azimuth
number
no
180

cubeMapSize
number
no
128

elevation
number
no
2

mieCoefficient
number
no
0.005

mieDirectionalG
number
no
0.7

rayleigh
number
no
3

scale
number
no
1000

setEnvironment
boolean
no
true

turbidity
number
no
10

webGLRenderTargetOptions
number
no
{}