diff --git a/src/quick3d/qdemonsceneenvironment.h b/src/quick3d/qdemonsceneenvironment.h
index 104c2aa520d7c063dd0eb75bfc9bbe45758c9b5a..e98110b5a4643867b8fee3dcef33c60830b64662 100644
--- a/src/quick3d/qdemonsceneenvironment.h
+++ b/src/quick3d/qdemonsceneenvironment.h
@@ -59,7 +59,8 @@ public:
     enum QDemonEnvironmentBackgroundTypes {
         Transparent = 0,
         Unspecified,
-        Color
+        Color,
+        SkyBox
     };
     Q_ENUM(QDemonEnvironmentBackgroundTypes)
 
diff --git a/src/runtimerender/graphobjects/qdemonrenderlayer.h b/src/runtimerender/graphobjects/qdemonrenderlayer.h
index 978e0cfd1ce6652ea060a94362efc69ce2c49eb2..7f170f9eb73c24fe45e3933777c41c26a8b34d8c 100644
--- a/src/runtimerender/graphobjects/qdemonrenderlayer.h
+++ b/src/runtimerender/graphobjects/qdemonrenderlayer.h
@@ -77,7 +77,9 @@ struct Q_DEMONRUNTIMERENDER_EXPORT QDemonRenderLayer : public QDemonRenderNode
     enum class Background : quint8
     {
         Transparent = 0,
-        Unspecified, Color
+        Unspecified,
+        Color,
+        SkyBox
     };
 
     enum class BlendMode : quint8
diff --git a/src/runtimerender/rendererimpl/qdemonrendererimpl.h b/src/runtimerender/rendererimpl/qdemonrendererimpl.h
index 781c24b85f193e90502ea85948f83f5e945a762b..ead798cd67f8d5fbd4311e90514aa988f8351191 100644
--- a/src/runtimerender/rendererimpl/qdemonrendererimpl.h
+++ b/src/runtimerender/rendererimpl/qdemonrendererimpl.h
@@ -132,6 +132,7 @@ class Q_DEMONRUNTIMERENDER_EXPORT QDemonRendererImpl : public QDemonRendererInte
     QDemonRef<QDemonRenderableDepthPrepassShader> m_depthTessLinearPrepassShaderDisplaced;
     QDemonRef<QDemonRenderableDepthPrepassShader> m_depthTessPhongPrepassShader;
     QDemonRef<QDemonRenderableDepthPrepassShader> m_depthTessNPatchPrepassShader;
+    QDemonRef<QDemonSkyBoxShader> m_skyBoxShader;
     QDemonRef<QDemonDefaultAoPassShader> m_defaultAoPassShader;
     QDemonRef<QDemonDefaultAoPassShader> m_fakeDepthShader;
     QDemonRef<QDemonDefaultAoPassShader> m_fakeCubemapDepthShader;
@@ -295,6 +296,7 @@ public:
     QDemonRef<QDemonShaderGeneratorGeneratedShader> getShader(QDemonSubsetRenderable &inRenderable,
                                                               const TShaderFeatureSet &inFeatureSet);
 
+    QDemonRef<QDemonSkyBoxShader> getSkyBoxShader();
     QDemonRef<QDemonDefaultAoPassShader> getDefaultAoPassShader(TShaderFeatureSet inFeatureSet);
     QDemonRef<QDemonDefaultAoPassShader> getFakeDepthShader(TShaderFeatureSet inFeatureSet);
     QDemonRef<QDemonDefaultAoPassShader> getFakeCubeDepthShader(TShaderFeatureSet inFeatureSet);
diff --git a/src/runtimerender/rendererimpl/qdemonrendererimpllayerrenderdata.cpp b/src/runtimerender/rendererimpl/qdemonrendererimpllayerrenderdata.cpp
index e5ef481089973aedc8461c3ce94cfbac7f316086..aac33cfc9c7d0aa1b911b54a4e6bf36de8d437c2 100644
--- a/src/runtimerender/rendererimpl/qdemonrendererimpllayerrenderdata.cpp
+++ b/src/runtimerender/rendererimpl/qdemonrendererimpllayerrenderdata.cpp
@@ -212,6 +212,57 @@ QDemonRenderFrameBufferAttachment QDemonLayerRenderData::getFramebufferDepthAtta
     return fmt;
 }
 
+void QDemonLayerRenderData::renderClearPass()
+{
+    QDemonStackPerfTimer ___timer(renderer->demonContext()->performanceTimer(), Q_FUNC_INFO);
+    if (camera == nullptr)
+        return;
+
+    renderer->beginLayerRender(*this);
+
+    auto theContext = renderer->context();
+    if (layer.background == QDemonRenderLayer::Background::SkyBox) {
+        theContext->setDepthTestEnabled(false); // Draw to every pixel
+        theContext->setDepthWriteEnabled(false); // Depth will be cleared in a separate step
+        QDemonRef<QDemonSkyBoxShader> shader = renderer->getSkyBoxShader();
+        theContext->setActiveShader(shader->shader);
+        // Setup constants
+        shader->projection.set(camera->projection);
+        shader->viewMatrix.set(camera->globalTransform);
+        shader->skyboxTexture.set(layer.lightProbe->m_textureData.m_texture.data());
+        renderer->renderQuad();
+    }
+
+    QDemonRenderClearFlags clearFlags = 0;
+    if (!layer.flags.testFlag(QDemonRenderLayer::Flag::LayerEnableDepthPrePass)) {
+        clearFlags |= QDemonRenderClearValues::Depth;
+        clearFlags |= QDemonRenderClearValues::Stencil;
+        // Enable depth write for the clear below
+        theContext->setDepthWriteEnabled(true);
+    }
+
+    if (layer.background == QDemonRenderLayer::Background::SkyBox) {
+        theContext->clear(clearFlags);
+    } else if (layer.background == QDemonRenderLayer::Background::Color) {
+        clearFlags |= QDemonRenderClearValues::Color;
+        QDemonRenderContextScopedProperty<QVector4D> __clearColor(*theContext,
+                                                                  &QDemonRenderContext::clearColor,
+                                                                  &QDemonRenderContext::setClearColor,
+                                                                  QVector4D(layer.clearColor, 1.0f));
+        theContext->clear(clearFlags);
+    } else {
+        if (layerPrepResult->flags.requiresTransparentClear()) {
+            clearFlags |= QDemonRenderClearValues::Color;
+            QDemonRenderContextScopedProperty<QVector4D> __clearColor(*theContext,
+                                                                      &QDemonRenderContext::clearColor,
+                                                                      &QDemonRenderContext::setClearColor,
+                                                                      QVector4D(0.0, 0.0, 0.0, 0.0f));
+            theContext->clear(clearFlags);
+        }
+    }
+    renderer->endLayerRender();
+}
+
 void QDemonLayerRenderData::renderAoPass()
 {
     renderer->beginLayerDepthPassRender(*this);
@@ -1122,6 +1173,9 @@ void QDemonLayerRenderData::renderToViewport()
                                                    &layer);
             }
         } else {
+            startProfiling("Clear pass", false);
+            renderClearPass();
+            endProfiling("Clear pass");
 
             if (layer.flags.testFlag(QDemonRenderLayer::Flag::LayerEnableDepthPrePass)) {
                 startProfiling("Depth pass", false);
@@ -1630,7 +1684,9 @@ void QDemonLayerRenderData::runnableRenderToViewport(const QDemonRef<QDemonRende
     // Then we can't possible affect the resulting render target.
     bool needsToRender = layer.firstEffect != nullptr || opaqueObjects.empty() == false
             || anyCompletelyNonTransparentObjects(transparentObjects) || usesOffscreenRenderer()
-            || m_layerWidgetTexture.getTexture() || m_boundingRectColor.hasValue() || layer.background == QDemonRenderLayer::Background::Color;
+            || m_layerWidgetTexture.getTexture() || m_boundingRectColor.hasValue()
+            || layer.background == QDemonRenderLayer::Background::Color
+            || layer.background == QDemonRenderLayer::Background::SkyBox;
 
     if (needsToRender == false)
         return;
@@ -1657,29 +1713,6 @@ void QDemonLayerRenderData::runnableRenderToViewport(const QDemonRef<QDemonRende
         theContext->setViewport(layerPrepResult->viewport().toRect());
         theContext->setScissorTestEnabled(true);
         theContext->setScissorRect(layerPrepResult->scissor().toRect());
-
-        QDemonRenderClearFlags clearFlags = QDemonRenderClearValues::Color;
-        if (!layer.flags.testFlag(QDemonRenderLayer::Flag::LayerEnableDepthPrePass)) {
-                        clearFlags |= (QDemonRenderClearValues::Depth);
-                        clearFlags |= (QDemonRenderClearValues::Stencil);
-                        // enable depth write for the clear below
-                        theContext->setDepthWriteEnabled(true);
-        }
-        if (layer.background == QDemonRenderLayer::Background::Color) {
-            QDemonRenderContextScopedProperty<QVector4D> __clearColor(*theContext,
-                                                                      &QDemonRenderContext::clearColor,
-                                                                      &QDemonRenderContext::setClearColor,
-                                                                      QVector4D(layer.clearColor, 1.0f));
-            theContext->clear(clearFlags);
-        } else {
-            if (thePrepResult.flags.requiresTransparentClear()) {
-                QDemonRenderContextScopedProperty<QVector4D> __clearColor(*theContext,
-                                                                          &QDemonRenderContext::clearColor,
-                                                                          &QDemonRenderContext::setClearColor,
-                                                                          QVector4D(0.0, 0.0, 0.0, 0.0f));
-                theContext->clear(clearFlags);
-            }
-        }
         renderToViewport();
     } else {
         // First, render the layer along with whatever progressive AA is appropriate.
diff --git a/src/runtimerender/rendererimpl/qdemonrendererimpllayerrenderdata.h b/src/runtimerender/rendererimpl/qdemonrendererimpllayerrenderdata.h
index 04e88827baee45853045769eb8b6cdedf34c9647..ab79cd136fe3f200e90bec928094b49d5dd8700f 100644
--- a/src/runtimerender/rendererimpl/qdemonrendererimpllayerrenderdata.h
+++ b/src/runtimerender/rendererimpl/qdemonrendererimpllayerrenderdata.h
@@ -104,6 +104,7 @@ struct QDemonLayerRenderData : public QDemonLayerRenderPreparationData
 
     // Render this layer assuming viewport and RT are setup.  Just renders exactly this item
     // no effects.
+    void renderClearPass();
     void renderDepthPass(bool inEnableTransparentDepthWrite = false);
     void renderAoPass();
     void renderFakeDepthMapPass(QDemonRenderTexture2D *theDepthTex, QDemonRenderTextureCube *theDepthCube);
diff --git a/src/runtimerender/rendererimpl/qdemonrendererimplshaders.cpp b/src/runtimerender/rendererimpl/qdemonrendererimplshaders.cpp
index ae78706eb3424026c22f4ed31db1a682679fe0f8..810ab64b51e5c3158b8beee4f5b1a256b7a46033 100644
--- a/src/runtimerender/rendererimpl/qdemonrendererimplshaders.cpp
+++ b/src/runtimerender/rendererimpl/qdemonrendererimplshaders.cpp
@@ -1545,6 +1545,76 @@ QDemonRef<QDemonRenderableDepthPrepassShader> QDemonRendererImpl::getDepthTessNP
     return m_depthTessNPatchPrepassShader;
 }
 
+QDemonRef<QDemonSkyBoxShader> QDemonRendererImpl::getSkyBoxShader()
+{
+    if (!m_skyBoxShader) {
+        QDemonRef<QDemonShaderCache> theCache = m_demonContext->shaderCache();
+        QByteArray name = "fullscreen skybox shader";
+        QDemonRef<QDemonRenderShaderProgram> skyBoxShaderProgram = theCache->getProgram(name, TShaderFeatureSet());
+        if (!skyBoxShaderProgram) {
+            QDemonRef<QDemonShaderProgramGeneratorInterface> theGenerator(getProgramGenerator());
+            theGenerator->beginProgram();
+            QDemonShaderStageGeneratorInterface &vertexGenerator(*theGenerator->getStage(QDemonShaderGeneratorStage::Vertex));
+            QDemonShaderStageGeneratorInterface &fragmentGenerator(*theGenerator->getStage(QDemonShaderGeneratorStage::Fragment));
+
+            vertexGenerator.addIncoming("attr_pos", "vec3");
+
+            vertexGenerator.addOutgoing("eye_direction", "vec3");
+
+            vertexGenerator.addUniform("view_matrix", "mat4");
+            vertexGenerator.addUniform("projection", "mat4");
+
+            vertexGenerator.append("void main() {");
+            vertexGenerator.append("\tgl_Position = vec4(attr_pos, 1.0);");
+            vertexGenerator.append("\tmat4 inverseProjection = inverse(projection);");
+            vertexGenerator.append("\tvec3 unprojected = (inverseProjection * gl_Position).xyz;");
+            vertexGenerator.append("\teye_direction = normalize(mat3(view_matrix) * unprojected);");
+            vertexGenerator.append("}");
+
+            fragmentGenerator.addInclude("customMaterial.glsllib"); // Needed for PI, PI_TWO
+
+            fragmentGenerator.addUniform("skybox_image", "sampler2D");
+            fragmentGenerator.addUniform("output_color", "vec3");
+
+            fragmentGenerator.append("void main() {");
+
+            // Ideally, we would just reuse getProbeSampleUV like this, but that leads to issues
+            // with incorrect texture gradients because we're drawing on a quad and not a sphere.
+            // See explanation below.
+            // fragmentGenerator.addInclude("sampleProbe.glsllib");
+            // fragmentGenerator.append("\tgl_FragColor = texture2D(skybox_image, getProbeSampleUV(eye, vec4(1.0, 0.0, 0.0, 1.0), vec2(0,0)));");
+
+            // nlerp direction vector, not entirely correct, but simple/efficient
+            fragmentGenerator.append("\tvec3 eye = normalize(eye_direction);");
+
+            // Equirectangular textures project longitude and latitude to the xy plane
+            fragmentGenerator.append("\tfloat longitude = atan(eye.x, eye.z) / PI_TWO + 0.5;");
+            fragmentGenerator.append("\tfloat latitude = asin(eye.y) / PI + 0.5;");
+            fragmentGenerator.append("\tvec2 uv = vec2(longitude, latitude);");
+
+            // Because of the non-standard projection, the texture lookup for normal texture
+            // filtering is messed up. Make this somewhat better by manually setting the gradients.
+            // Right now, they're set to 0,0, but ideally, we would derive the correct gradient
+            // function mathematically.
+            // TODO: Alternatively, we could check if it's possible to disable some of the texture
+            // filtering just for the skybox part.
+            fragmentGenerator.append("\tgl_FragColor = textureGrad(skybox_image, uv, vec2(0), vec2(0));");
+            fragmentGenerator.append("}");
+
+            // No flags enabled
+            skyBoxShaderProgram = theGenerator->compileGeneratedShader(name, QDemonShaderCacheProgramFlags(), TShaderFeatureSet());
+        }
+
+        if (skyBoxShaderProgram) {
+            m_skyBoxShader = QDemonRef<QDemonSkyBoxShader>(
+                        new QDemonSkyBoxShader(skyBoxShaderProgram, context()));
+        } else {
+            m_skyBoxShader = QDemonRef<QDemonSkyBoxShader>();
+        }
+    }
+    return m_skyBoxShader;
+}
+
 QDemonRef<QDemonDefaultAoPassShader> QDemonRendererImpl::getDefaultAoPassShader(TShaderFeatureSet inFeatureSet)
 {
     if (m_defaultAoPassShader.isNull()) {
diff --git a/src/runtimerender/rendererimpl/qdemonrendererimplshaders.h b/src/runtimerender/rendererimpl/qdemonrendererimplshaders.h
index c5ac1b89c89cb184d66d640a7c7f496dfd8106b7..93bdd5e9f16991a1f31d8dc544a71d5a5a39c753 100644
--- a/src/runtimerender/rendererimpl/qdemonrendererimplshaders.h
+++ b/src/runtimerender/rendererimpl/qdemonrendererimplshaders.h
@@ -180,6 +180,25 @@ struct QDemonRenderableDepthPrepassShader
     ~QDemonRenderableDepthPrepassShader() {}
 };
 
+struct QDemonSkyBoxShader
+{
+    QAtomicInt ref;
+    QDemonRef<QDemonRenderShaderProgram> shader;
+    QDemonRenderCachedShaderProperty<QMatrix4x4> viewMatrix;
+    QDemonRenderCachedShaderProperty<QMatrix4x4> projection;
+    QDemonRenderCachedShaderProperty<QDemonRenderTexture2D *> skyboxTexture;
+
+    QDemonSkyBoxShader(QDemonRef<QDemonRenderShaderProgram> inShader, QDemonRef<QDemonRenderContext> inContext)
+        : shader(inShader)
+        , viewMatrix("view_matrix", inShader)
+        , projection("projection", inShader)
+        , skyboxTexture("skybox_image", inShader)
+    {
+        Q_UNUSED(inContext)
+    }
+    ~QDemonSkyBoxShader() = default;
+};
+
 struct QDemonDefaultAoPassShader
 {
     QAtomicInt ref;