threlte logo
@threlte/extras

<Sky>

webgl <Sky> works with WebGL only.

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)

  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 presetSpring = Spring.of(
    () => ({
      azimuth,
      elevation,
      exposure,
      mieCoefficient,
      mieDirectionalG,
      rayleigh,
      turbidity
    }),
    {
      damping: 0.95,
      precision: 0.0001,
      stiffness: 0.05
    }
  )

  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)
</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
description

azimuth
number
no
180
The sun's horizontal direction, in degrees.

cubeMapSize
number
no
128
The width and height in pixels of each face of the environment cube map.

elevation
number
no
2
The sun's height above the horizon, in degrees.

mieCoefficient
number
no
0.005
The amount of Mie scattering, which creates the glow around the sun.

mieDirectionalG
number
no
0.7
How concentrated the glow around the sun is, from 0 to 1.

rayleigh
number
no
3
The amount of Rayleigh scattering, which tints the sky blue and the sunset red.

scale
number
no
1000
The size of the sky box along every axis. It needs to enclose the scene and fit within the camera's far plane.

setEnvironment
boolean
no
true
Renders the sky into a cube map and sets it as scene.environment, so it lights the scene.

turbidity
number
no
10
How hazy the atmosphere is. Higher values look more overcast.

webGLRenderTargetOptions
RenderTargetOptions
no
{}
Options for the environment cube map's WebGLCubeRenderTarget.