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