From bccf85a7a43149c7c07ebe298543c37390f1e3b5 Mon Sep 17 00:00:00 2001
From: Johan Klokkhammer Helsing <johan.helsing@qt.io>
Date: Thu, 6 Jun 2019 13:16:24 +0200
Subject: [PATCH] Add SkyBox background mode

Implemented by sampling the light probe texture onto a quad that fills the
entire screen, which uses an equirectangular projection.

In the future we might want to consider using separate textures, and/or
different projections. There are some minor issues with texture gradients and
equirectangular projections. These have been worked around by setting the
gradients to zero, which is not optimal, but works OK in practice. If we switch
to using texture cubes instead of equirectangular projections, these issues can
be avoided.
---
 src/quick3d/qdemonsceneenvironment.h          |  3 +-
 .../graphobjects/qdemonrenderlayer.h          |  4 +-
 .../rendererimpl/qdemonrendererimpl.h         |  2 +
 .../qdemonrendererimpllayerrenderdata.cpp     | 81 +++++++++++++------
 .../qdemonrendererimpllayerrenderdata.h       |  1 +
 .../qdemonrendererimplshaders.cpp             | 70 ++++++++++++++++
 .../rendererimpl/qdemonrendererimplshaders.h  | 19 +++++
 7 files changed, 154 insertions(+), 26 deletions(-)

diff --git a/src/quick3d/qdemonsceneenvironment.h b/src/quick3d/qdemonsceneenvironment.h
index 104c2aa5..e98110b5 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 978e0cfd..7f170f9e 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 781c24b8..ead798cd 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 e5ef4810..aac33cfc 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 04e88827..ab79cd13 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 ae78706e..810ab64b 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 c5ac1b89..93bdd5e9 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;
-- 
GitLab