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.HelpersDefining 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.
1means 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.
See also SkyMaterial and SceneEnvironment.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.