- Qt 6.12
- Qt Quick 3D
- Qt Quick 3D Examples and Tutorials
- Qt Quick 3D - Sky Material Example
- Qt Quick 3D | Commercial or GPLv3

# Qt Quick 3D - Sky Material Example

Demonstrates procedural sky rendering and image-based lighting with SkyMaterial.

This example shows how to use SkyMaterial to render a procedural sky directly from a fragment shader and use the same procedural cubemap as the light probe for image-based lighting (IBL) on the scene's PBR materials.

Two SkyMaterial subclasses are provided side by side:

- `BasicSky` — a small shader that produces a gradient sky with a sun disk, suitable as a starting point for stylized or game-style skies.
- `AdvancedSky` — a physically-based atmospheric scattering model following Hillaire's "A Scalable and Production Ready Sky and Atmosphere Rendering Technique" (2020), with configurable aerosols, ozone, and eye altitude.

#### Setting Up the Scene

The application is a standard Window with a View3D. The relevant QtQuick3D imports are:

     import QtQuick
     import QtQuick.Window
     import QtQuick.Controls
     import QtQuick.Layouts

     import QtQuick3D
     import QtQuick3D.Helpers

#### Defining the Sky Materials

Both sky variants are declared inside the View3D so they share the same skyLight Node (the sun). The active material is selected by a property that the UI swaps between the two:

     // Two SkyMaterial subclasses are instantiated up front. The toggle in
     // the side panel swaps which one is assigned to SceneEnvironment.skyMaterial.
     BasicSky {
         id: basicSky
         iblRenderFrames: view3D.iblRenderFrames
         iblSampleCount: view3D.iblSampleCount
         enableIBL: view3D.enableIBL
         radianceMapSize: view3D.radianceMapSize
         skyLight: sun
     }

     AdvancedSky {
         id: advancedSky
         iblRenderFrames: view3D.iblRenderFrames
         iblSampleCount: view3D.iblSampleCount
         enableIBL: view3D.enableIBL
         radianceMapSize: view3D.radianceMapSize
         skyLight: sun
     }

Each helper file is a SkyMaterial subclass with its own fragment shader and an exported set of QML properties; the properties are automatically forwarded as uniforms to the shader. See `BasicSky.qml` and `AdvancedSky.qml`.

#### Image-Based Lighting

Assigning the active SkyMaterial to SceneEnvironment::skyMaterial uses the procedurally rendered cubemap both as the visible background and as the source for image-based lighting on the PBR materials in the scene:

     // SkyMaterial produces both the visible sky (rendered as the background)
     // and the image-based lighting cubemap used for reflections and ambient
     // light on the PBR materials in the scene.
     environment: SceneEnvironment {
         backgroundMode: SceneEnvironment.SkyMaterial
         skyMaterial: view3D.activeSky
     }

The smoothest sphere in the demo mirrors the procedural sky almost perfectly; the rougher spheres show progressively blurred reflections, produced by SkyMaterial's GGX prefilter chain over the procedural cubemap.

#### Time-Sliced IBL Convergence

Generating the prefiltered IBL cubemap is the most expensive part of a procedural sky. SkyMaterial exposes iblRenderFrames so that the cost can be spread across several frames: the same total iblSampleCount is integrated incrementally, with the accumulator advancing each frame until convergence.

With the default value of `1` the prefilter runs in a single frame. Setting it higher amortizes the cost, which is useful when the sky changes infrequently relative to the frame rate, e.g. an animated sun-sweep that should not stall the main thread on every step.

#### Animating the Sun

A FrameAnimation drives the sun back and forth across the horizon when the "Animate sun" toggle is on. Time-slicing pairs naturally with this kind of slow drift: every sun position invalidates the accumulator, but the convergence catches up over the next few frames without any single frame paying the full prefilter cost.

     // Ping-pongs the sun back and forth across the horizon while
     // view3D.animateSun is true. With time-sliced IBL the cubemap
     // progressively re-converges as the sun moves.
     FrameAnimation {
         id: sunAnimator
         running: view3D.animateSun

         property real t: 0
         property real lastT: 0
         property int direction: 1

         onTriggered: {
             const min = view3D.sunSweepMinRot
             const max = view3D.sunSweepMaxRot
             lastT = t
             t += direction * frameTime * view3D.sunSweepSpeedDegPerSec * 0.01
             sun.eulerRotation.x = min + (Math.sin(t) * 0.5 + 0.5) * (max - min)
         }

         function updatePhaseFromSun() {
             const min = view3D.sunSweepMinRot
             const max = view3D.sunSweepMaxRot
             let norm = Math.max(0.0, Math.min(1.0, (sun.eulerRotation.x - min) / (max - min)))
             t = Math.asin(norm * 2.0 - 1.0)
             direction = lastT < t ? 1 : -1
         }
     }

#### Controls

- Sky variant — selects which SkyMaterial subclass is active.
- Animate sun — runs the sun sweep across the horizon.
- Sun elevation — manual sun position; enabled when the sweep animation is off.
- IBL Render frames — how many frames the prefilter takes to converge. `1` means single-frame (no time-slicing); higher values spread the cost.
- IBL sample count — number of GGX importance samples integrated per output texel of the prefiltered radiance map. Higher values reduce shimmering on high-frequency features (e.g. the sun disk) at the cost of more work per cycle.
- Radiance map size — resolution of the procedural environment cubemap. Higher values sharpen reflections on glossy materials but increase memory and rendering cost. Internally clamped to the nearest power-of-two.

The camera responds to standard WasdController navigation — W/A/S/D plus mouse drag to look around.

Example project @ code.qt.io

See also SkyMaterial and SceneEnvironment.