From 6b0b17ff633f72bb16171da4f34ad9f672ecefec Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 26 Jan 2018 17:57:20 +0100 Subject: [PATCH 01/12] Working on better adaptive bias algorithm for shadow --- libraries/render-utils/src/LightStage.cpp | 8 ++--- libraries/render-utils/src/LightStage.h | 2 +- .../render-utils/src/RenderShadowTask.cpp | 10 ++++-- libraries/render-utils/src/RenderShadowTask.h | 16 ++++++++- libraries/render-utils/src/Shadow.slh | 17 +++++---- .../src/directional_ambient_light_shadow.slf | 4 ++- .../src/directional_skybox_light_shadow.slf | 4 ++- scripts/developer/utilities/render/shadow.qml | 35 +++++++++++++++++++ 8 files changed, 77 insertions(+), 19 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index f146cd6e0a..eac9dd7657 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -220,7 +220,7 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, } void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, - float nearDepth, float farDepth) { + float nearDepth, float farDepth, float baseBias) { assert(nearDepth < farDepth); assert(cascadeIndex < _cascades.size()); @@ -270,11 +270,7 @@ void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, co // Update the buffer auto& schema = _schemaBuffer.edit(); schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); - // Adapt shadow bias to shadow resolution with a totally empirical formula - const auto maxShadowFrustumDim = std::max(fabsf(min.x - max.x), fabsf(min.y - max.y)); - const auto REFERENCE_TEXEL_DENSITY = 7.5f; - const auto cascadeTexelDensity = MAP_SIZE / maxShadowFrustumDim; - schema.cascades[cascadeIndex].bias = MAX_BIAS * std::min(1.0f, REFERENCE_TEXEL_DENSITY / cascadeTexelDensity); + schema.cascades[cascadeIndex].bias = baseBias; } void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) { diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index d1a8680706..c2c2df8c89 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -80,7 +80,7 @@ public: void setKeylightFrustum(const ViewFrustum& viewFrustum, float nearDepth = 1.0f, float farDepth = 1000.0f); void setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, - float nearDepth = 1.0f, float farDepth = 1000.0f); + float nearDepth = 1.0f, float farDepth = 1000.0f, float baseBias = 0.005f); void setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum); const UniformBufferView& getBuffer() const { return _schemaBuffer; } diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index d83dfd73a5..f41860b6de 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -216,7 +216,9 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende task.addJob("ShadowSetup"); for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { - const auto setupOutput = task.addJob("ShadowCascadeSetup", i); + char jobName[64]; + sprintf(jobName, "ShadowCascadeSetup%d", i); + const auto setupOutput = task.addJob(jobName, i); const auto shadowFilter = setupOutput.getN(1); // CPU jobs: @@ -253,6 +255,10 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { } } +void RenderShadowCascadeSetup::configure(const Config& configuration) { + _baseBias = configuration.bias * configuration.bias * 0.01f; +} + void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { auto lightStage = renderContext->_scene->getStage(); assert(lightStage); @@ -266,7 +272,7 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon if (globalShadow && _cascadeIndexgetCascadeCount()) { output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); - globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); + globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, _baseBias); // Set the keylight render args args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index d8d4c624e7..42c279c02a 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -62,17 +62,31 @@ public: }; +class RenderShadowCascadeSetupConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float bias MEMBER bias NOTIFY dirty) +public: + + float bias{ 0.5f }; + +signals: + void dirty(); +}; + class RenderShadowCascadeSetup { public: using Outputs = render::VaryingSet3; - using JobModel = render::Job::ModelO; + using Config = RenderShadowCascadeSetupConfig; + using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {} + void configure(const Config& configuration); void run(const render::RenderContextPointer& renderContext, Outputs& output); private: unsigned int _cascadeIndex; + float _baseBias{ 0.1f }; }; class RenderShadowCascadeTeardown { diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index a12dd0f4a4..50ae8864f4 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -83,27 +83,30 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve return shadowAttenuation; } -float evalShadowCascadeAttenuation(int cascadeIndex, vec3 viewNormal, ShadowSampleOffsets offsets, vec4 shadowTexcoord) { +float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) { if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { // If a point is not in the map, do not attenuate return 1.0; } - // Multiply bias if we are at a grazing angle with light - float tangentFactor = abs(dot(getShadowDirInViewSpace(), viewNormal)); - float bias = getShadowBias(cascadeIndex) * (5.0-4.0*tangentFactor); + // Add fixed bias + bias += getShadowBias(cascadeIndex); return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); } -float evalShadowAttenuation(vec4 worldPosition, float viewDepth, vec3 viewNormal) { +float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) { ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition); vec4 cascadeShadowCoords[2]; ivec2 cascadeIndices; float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); + // Adjust bias if we are at a grazing angle with light + float ndotl = dot(worldLightDir, worldNormal); + float bias = 0.5*(1.0/(ndotl*ndotl)-1.0); + bias *= getShadowScale(); vec2 cascadeAttenuations = vec2(1.0, 1.0); - cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, viewNormal, offsets, cascadeShadowCoords[0]); + cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias); if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { - cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, viewNormal, offsets, cascadeShadowCoords[1]); + cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias); } float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix); // Falloff to max distance diff --git a/libraries/render-utils/src/directional_ambient_light_shadow.slf b/libraries/render-utils/src/directional_ambient_light_shadow.slf index f7ea8c5966..eead392fd8 100644 --- a/libraries/render-utils/src/directional_ambient_light_shadow.slf +++ b/libraries/render-utils/src/directional_ambient_light_shadow.slf @@ -28,7 +28,9 @@ void main(void) { vec4 viewPos = vec4(frag.position.xyz, 1.0); vec4 worldPos = getViewInverse() * viewPos; - float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal); + Light shadowLight = getKeyLight(); + vec3 worldLightDirection = getLightDirection(shadowLight); + float shadowAttenuation = evalShadowAttenuation(worldLightDirection, worldPos, -viewPos.z, frag.normal); if (frag.mode == FRAG_MODE_UNLIT) { discard; diff --git a/libraries/render-utils/src/directional_skybox_light_shadow.slf b/libraries/render-utils/src/directional_skybox_light_shadow.slf index 37c9ae7fba..2a2f8f65a3 100644 --- a/libraries/render-utils/src/directional_skybox_light_shadow.slf +++ b/libraries/render-utils/src/directional_skybox_light_shadow.slf @@ -28,7 +28,9 @@ void main(void) { vec4 viewPos = vec4(frag.position.xyz, 1.0); vec4 worldPos = getViewInverse() * viewPos; - float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal); + Light shadowLight = getKeyLight(); + vec3 worldLightDirection = getLightDirection(shadowLight); + float shadowAttenuation = evalShadowAttenuation(worldLightDirection, worldPos, -viewPos.z, frag.normal); // Light mapped or not ? if (frag.mode == FRAG_MODE_UNLIT) { diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index a8fcf1aec7..32405a5260 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -10,6 +10,9 @@ // import QtQuick 2.5 import QtQuick.Controls 1.4 +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls +import "configSlider" Column { id: root @@ -68,4 +71,36 @@ Column { font.italic: true } } + ConfigSlider { + label: qsTr("Cascade 0 bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") + property: "bias" + max: 1.0 + min: 0.01 + } + ConfigSlider { + label: qsTr("Cascade 1 bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") + property: "bias" + max: 1.0 + min: 0.01 + } + ConfigSlider { + label: qsTr("Cascade 2 bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") + property: "bias" + max: 1.0 + min: 0.01 + } + ConfigSlider { + label: qsTr("Cascade 3 bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") + property: "bias" + max: 1.0 + min: 0.01 + } } From 0324f41565c77acdda2d326560aeba2a2f260498 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 29 Jan 2018 17:23:35 +0100 Subject: [PATCH 02/12] Trying to improve adaptive shadow bias --- libraries/render-utils/src/LightStage.cpp | 35 ++++++++++++++----- libraries/render-utils/src/LightStage.h | 3 ++ .../render-utils/src/RenderShadowTask.cpp | 4 ++- libraries/render-utils/src/RenderShadowTask.h | 2 +- libraries/render-utils/src/Shadow.slh | 15 +++++--- libraries/render-utils/src/ShadowCore.slh | 14 ++++++-- libraries/render-utils/src/Shadows_shared.slh | 8 ++--- 7 files changed, 58 insertions(+), 23 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index eac9dd7657..e06d24f1b1 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -23,8 +23,6 @@ const glm::mat4 LightStage::Shadow::_biasMatrix{ 0.5, 0.5, 0.5, 1.0 }; const int LightStage::Shadow::MAP_SIZE = 1024; -static const auto MAX_BIAS = 0.006f; - const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; LightStage::LightStage() { @@ -63,7 +61,7 @@ LightStage::LightStage() { LightStage::Shadow::Schema::Schema() { ShadowTransform defaultTransform; - defaultTransform.bias = MAX_BIAS; + defaultTransform.fixedBias = 0.005f; std::fill(cascades, cascades + SHADOW_CASCADE_MAX_COUNT, defaultTransform); invMapSize = 1.0f / MAP_SIZE; cascadeCount = 1; @@ -72,6 +70,18 @@ LightStage::Shadow::Schema::Schema() { maxDistance = 20.0f; } +void LightStage::Shadow::Schema::updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum) { + auto& cascade = cascades[cascadeIndex]; + cascade.frustumPosition = shadowFrustum.getPosition(); + // The adaptative bias is computing how much depth offset we have to add to + // push back a coarsely sampled surface perpendicularly to the shadow direction, + // to not have shadow acnee. The final computation is done in the shader, based on the + // surface normal. + auto maxWorldFrustumSize = glm::max(shadowFrustum.getWidth(), shadowFrustum.getHeight()); + cascade.adaptiveBiasUnitScale = maxWorldFrustumSize * 0.5f * invMapSize; + cascade.adaptiveBiasTransformScale = shadowFrustum.getNearClip() * shadowFrustum.getFarClip() / (shadowFrustum.getFarClip() - shadowFrustum.getNearClip()); +} + LightStage::Shadow::Cascade::Cascade() : _frustum{ std::make_shared() }, _minDistance{ 0.0f }, @@ -210,13 +220,15 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, // Position the keylight frustum auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection; + // Update the buffer + auto& schema = _schemaBuffer.edit(); + auto cascadeIndex = 0; for (auto& cascade : _cascades) { cascade._frustum->setOrientation(orientation); cascade._frustum->setPosition(position); + schema.cascades[cascadeIndex].frustumPosition = position; + cascadeIndex++; } - // Update the buffer - auto& schema = _schemaBuffer.edit(); - schema.lightDirInViewSpace = glm::inverse(viewFrustum.getView()) * glm::vec4(lightDirection, 0.f); } void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, @@ -269,8 +281,10 @@ void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, co // Update the buffer auto& schema = _schemaBuffer.edit(); - schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); - schema.cascades[cascadeIndex].bias = baseBias; + auto& schemaCascade = schema.cascades[cascadeIndex]; + schemaCascade.reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); + schemaCascade.fixedBias = baseBias; + schema.updateCascade(cascadeIndex, *cascade._frustum); } void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) { @@ -281,7 +295,10 @@ void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const View *cascade._frustum = shadowFrustum; // Update the buffer - _schemaBuffer.edit().cascades[cascadeIndex].reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix(); + auto& schema = _schemaBuffer.edit(); + auto& schemaCascade = schema.cascades[cascadeIndex]; + schemaCascade.reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix(); + schema.updateCascade(cascadeIndex, shadowFrustum); } LightStage::Index LightStage::findLight(const LightPointer& light) const { diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index c2c2df8c89..922ec5eb4a 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -110,6 +110,8 @@ public: Schema(); + void updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum); + }; UniformBufferView _schemaBuffer = nullptr; }; @@ -213,6 +215,7 @@ protected: Index _sunOffLightId; Index _defaultLightId; + }; using LightStagePointer = std::shared_ptr; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index f41860b6de..2172dda3e3 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -256,7 +256,9 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { } void RenderShadowCascadeSetup::configure(const Config& configuration) { - _baseBias = configuration.bias * configuration.bias * 0.01f; + // I'm not very proud of this empirical adjustment + auto cascadeBias = configuration.bias * powf(1.1f, _cascadeIndex); + _baseBias = cascadeBias * cascadeBias * 0.01f; } void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 42c279c02a..c4f0c65bfc 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -67,7 +67,7 @@ class RenderShadowCascadeSetupConfig : public render::Job::Config { Q_PROPERTY(float bias MEMBER bias NOTIFY dirty) public: - float bias{ 0.5f }; + float bias{ 0.25f }; signals: void dirty(); diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 50ae8864f4..c11f5fa6a7 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -83,11 +83,17 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve return shadowAttenuation; } -float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) { +float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias, + vec3 worldPosition, vec3 worldLightDir) { if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { // If a point is not in the map, do not attenuate return 1.0; } + // After this, bias is in view units + bias *= getShadowAdaptiveBiasUnitScale(cascadeIndex); + // Transform to texcoord space (between 0 and 1) + float shadowDepth = abs(dot(worldPosition-getShadowFrustumPosition(cascadeIndex), worldLightDir)); + bias = transformShadowAdaptiveBias(cascadeIndex, shadowDepth, bias); // Add fixed bias bias += getShadowBias(cascadeIndex); return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); @@ -101,12 +107,11 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe // Adjust bias if we are at a grazing angle with light float ndotl = dot(worldLightDir, worldNormal); - float bias = 0.5*(1.0/(ndotl*ndotl)-1.0); - bias *= getShadowScale(); + float bias = 1.0/(ndotl*ndotl)-1.0; vec2 cascadeAttenuations = vec2(1.0, 1.0); - cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias); + cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias, worldPosition.xyz, worldLightDir); if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { - cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias); + cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias, worldPosition.xyz, worldLightDir); } float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix); // Falloff to max distance diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index 9b3b086598..2d48e16ef4 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -38,11 +38,19 @@ float getShadowScale() { } float getShadowBias(int cascadeIndex) { - return shadow.cascades[cascadeIndex].bias; + return shadow.cascades[cascadeIndex].fixedBias; } -vec3 getShadowDirInViewSpace() { - return shadow.lightDirInViewSpace; +vec3 getShadowFrustumPosition(int cascadeIndex) { + return shadow.cascades[cascadeIndex].frustumPosition; +} + +float getShadowAdaptiveBiasUnitScale(int cascadeIndex) { + return shadow.cascades[cascadeIndex].adaptiveBiasUnitScale; +} + +float transformShadowAdaptiveBias(int cascadeIndex, float shadowDepth, float depthBias) { + return shadow.cascades[cascadeIndex].adaptiveBiasTransformScale * depthBias / (shadowDepth*shadowDepth); } // Compute the texture coordinates from world coordinates diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index abb226421c..def6b1b4d4 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -11,16 +11,16 @@ struct ShadowTransform { MAT4 reprojection; - - float bias; + VEC3 frustumPosition; + float fixedBias; + float adaptiveBiasUnitScale; + float adaptiveBiasTransformScale; float _padding1; float _padding2; - float _padding3; }; struct ShadowParameters { ShadowTransform cascades[SHADOW_CASCADE_MAX_COUNT]; - VEC3 lightDirInViewSpace; int cascadeCount; float invMapSize; float invCascadeBlendWidth; From f344e44d26bf8d307a00593e34481bd25954bebf Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 31 Jan 2018 10:19:17 +0100 Subject: [PATCH 03/12] Switched to a simpler manual fixed/slope based shadow bias system. Automatic stuff fail most of the time --- libraries/render-utils/src/LightStage.cpp | 22 +------ libraries/render-utils/src/LightStage.h | 4 +- .../render-utils/src/RenderShadowTask.cpp | 8 +-- libraries/render-utils/src/RenderShadowTask.h | 9 ++- libraries/render-utils/src/Shadow.slh | 18 ++--- libraries/render-utils/src/ShadowCore.slh | 13 +--- libraries/render-utils/src/Shadows_shared.slh | 4 +- .../developer/utilities/render/debugShadow.js | 4 +- scripts/developer/utilities/render/shadow.qml | 65 +++++++++++++++---- 9 files changed, 78 insertions(+), 69 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index e06d24f1b1..259d0dd665 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -70,18 +70,6 @@ LightStage::Shadow::Schema::Schema() { maxDistance = 20.0f; } -void LightStage::Shadow::Schema::updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum) { - auto& cascade = cascades[cascadeIndex]; - cascade.frustumPosition = shadowFrustum.getPosition(); - // The adaptative bias is computing how much depth offset we have to add to - // push back a coarsely sampled surface perpendicularly to the shadow direction, - // to not have shadow acnee. The final computation is done in the shader, based on the - // surface normal. - auto maxWorldFrustumSize = glm::max(shadowFrustum.getWidth(), shadowFrustum.getHeight()); - cascade.adaptiveBiasUnitScale = maxWorldFrustumSize * 0.5f * invMapSize; - cascade.adaptiveBiasTransformScale = shadowFrustum.getNearClip() * shadowFrustum.getFarClip() / (shadowFrustum.getFarClip() - shadowFrustum.getNearClip()); -} - LightStage::Shadow::Cascade::Cascade() : _frustum{ std::make_shared() }, _minDistance{ 0.0f }, @@ -222,17 +210,14 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection; // Update the buffer auto& schema = _schemaBuffer.edit(); - auto cascadeIndex = 0; for (auto& cascade : _cascades) { cascade._frustum->setOrientation(orientation); cascade._frustum->setPosition(position); - schema.cascades[cascadeIndex].frustumPosition = position; - cascadeIndex++; } } void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, - float nearDepth, float farDepth, float baseBias) { + float nearDepth, float farDepth, float fixedBias, float slopeBias) { assert(nearDepth < farDepth); assert(cascadeIndex < _cascades.size()); @@ -283,8 +268,8 @@ void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, co auto& schema = _schemaBuffer.edit(); auto& schemaCascade = schema.cascades[cascadeIndex]; schemaCascade.reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); - schemaCascade.fixedBias = baseBias; - schema.updateCascade(cascadeIndex, *cascade._frustum); + schemaCascade.fixedBias = fixedBias; + schemaCascade.slopeBias = slopeBias; } void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) { @@ -298,7 +283,6 @@ void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const View auto& schema = _schemaBuffer.edit(); auto& schemaCascade = schema.cascades[cascadeIndex]; schemaCascade.reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix(); - schema.updateCascade(cascadeIndex, shadowFrustum); } LightStage::Index LightStage::findLight(const LightPointer& light) const { diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 922ec5eb4a..9812426fa6 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -80,7 +80,7 @@ public: void setKeylightFrustum(const ViewFrustum& viewFrustum, float nearDepth = 1.0f, float farDepth = 1000.0f); void setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, - float nearDepth = 1.0f, float farDepth = 1000.0f, float baseBias = 0.005f); + float nearDepth = 1.0f, float farDepth = 1000.0f, float fixedBias = 0.005f, float slopeBias = 0.005f); void setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum); const UniformBufferView& getBuffer() const { return _schemaBuffer; } @@ -110,8 +110,6 @@ public: Schema(); - void updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum); - }; UniformBufferView _schemaBuffer = nullptr; }; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 2172dda3e3..2b5ab09c06 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -256,9 +256,8 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { } void RenderShadowCascadeSetup::configure(const Config& configuration) { - // I'm not very proud of this empirical adjustment - auto cascadeBias = configuration.bias * powf(1.1f, _cascadeIndex); - _baseBias = cascadeBias * cascadeBias * 0.01f; + _fixedBias = configuration.fixedBias * configuration.fixedBias * configuration.fixedBias * 0.004f; + _slopeBias = configuration.slopeBias * configuration.slopeBias * configuration.slopeBias * 0.01f; } void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { @@ -274,7 +273,8 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon if (globalShadow && _cascadeIndexgetCascadeCount()) { output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); - globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, _baseBias); + globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, + _fixedBias, _slopeBias); // Set the keylight render args args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index c4f0c65bfc..7e5655b375 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -64,10 +64,12 @@ public: class RenderShadowCascadeSetupConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(float bias MEMBER bias NOTIFY dirty) + Q_PROPERTY(float fixedBias MEMBER fixedBias NOTIFY dirty) + Q_PROPERTY(float slopeBias MEMBER slopeBias NOTIFY dirty) public: - float bias{ 0.25f }; + float fixedBias{ 0.15f }; + float slopeBias{ 0.55f }; signals: void dirty(); @@ -86,7 +88,8 @@ public: private: unsigned int _cascadeIndex; - float _baseBias{ 0.1f }; + float _fixedBias{ 0.1f }; + float _slopeBias{ 0.1f }; }; class RenderShadowCascadeTeardown { diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index c11f5fa6a7..6575e68090 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -83,19 +83,12 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve return shadowAttenuation; } -float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias, - vec3 worldPosition, vec3 worldLightDir) { +float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float oneMinusNdotL) { if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { // If a point is not in the map, do not attenuate return 1.0; } - // After this, bias is in view units - bias *= getShadowAdaptiveBiasUnitScale(cascadeIndex); - // Transform to texcoord space (between 0 and 1) - float shadowDepth = abs(dot(worldPosition-getShadowFrustumPosition(cascadeIndex), worldLightDir)); - bias = transformShadowAdaptiveBias(cascadeIndex, shadowDepth, bias); - // Add fixed bias - bias += getShadowBias(cascadeIndex); + float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * oneMinusNdotL; return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); } @@ -106,12 +99,11 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); // Adjust bias if we are at a grazing angle with light - float ndotl = dot(worldLightDir, worldNormal); - float bias = 1.0/(ndotl*ndotl)-1.0; + float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1); vec2 cascadeAttenuations = vec2(1.0, 1.0); - cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias, worldPosition.xyz, worldLightDir); + cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], oneMinusNdotL); if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { - cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias, worldPosition.xyz, worldLightDir); + cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], oneMinusNdotL); } float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix); // Falloff to max distance diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index 2d48e16ef4..782e2bc2b8 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -37,21 +37,14 @@ float getShadowScale() { return shadow.invMapSize; } -float getShadowBias(int cascadeIndex) { +float getShadowFixedBias(int cascadeIndex) { return shadow.cascades[cascadeIndex].fixedBias; } -vec3 getShadowFrustumPosition(int cascadeIndex) { - return shadow.cascades[cascadeIndex].frustumPosition; +float getShadowSlopeBias(int cascadeIndex) { + return shadow.cascades[cascadeIndex].slopeBias; } -float getShadowAdaptiveBiasUnitScale(int cascadeIndex) { - return shadow.cascades[cascadeIndex].adaptiveBiasUnitScale; -} - -float transformShadowAdaptiveBias(int cascadeIndex, float shadowDepth, float depthBias) { - return shadow.cascades[cascadeIndex].adaptiveBiasTransformScale * depthBias / (shadowDepth*shadowDepth); -} // Compute the texture coordinates from world coordinates vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) { diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index def6b1b4d4..0d49fc037e 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -11,10 +11,8 @@ struct ShadowTransform { MAT4 reprojection; - VEC3 frustumPosition; float fixedBias; - float adaptiveBiasUnitScale; - float adaptiveBiasTransformScale; + float slopeBias; float _padding1; float _padding2; }; diff --git a/scripts/developer/utilities/render/debugShadow.js b/scripts/developer/utilities/render/debugShadow.js index a0d2142258..1f1d00e6b4 100644 --- a/scripts/developer/utilities/render/debugShadow.js +++ b/scripts/developer/utilities/render/debugShadow.js @@ -14,7 +14,7 @@ var qml = Script.resolvePath('shadow.qml'); var window = new OverlayWindow({ title: 'Shadow Debug', source: qml, - width: 200, - height: 90 + width: 250, + height: 300 }); window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index 32405a5260..a077ab9f50 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -36,6 +36,14 @@ Column { shadow1Config.enabled = false; shadow2Config.enabled = false; shadow3Config.enabled = false; + shadow0Config.isFrozen = false; + shadow1Config.isFrozen = false; + shadow2Config.isFrozen = false; + shadow3Config.isFrozen = false; + shadow0BoundConfig.isFrozen = false; + shadow1BoundConfig.isFrozen = false; + shadow2BoundConfig.isFrozen = false; + shadow3BoundConfig.isFrozen = false; } CheckBox { @@ -72,35 +80,68 @@ Column { } } ConfigSlider { - label: qsTr("Cascade 0 bias") + label: qsTr("Cascade 0 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 } ConfigSlider { - label: qsTr("Cascade 1 bias") + label: qsTr("Cascade 1 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 } ConfigSlider { - label: qsTr("Cascade 2 bias") + label: qsTr("Cascade 2 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 } ConfigSlider { - label: qsTr("Cascade 3 bias") + label: qsTr("Cascade 3 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 + } + + ConfigSlider { + label: qsTr("Cascade 0 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") + property: "slopeBias" + max: 1.0 + min: 0.0 + } + ConfigSlider { + label: qsTr("Cascade 1 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") + property: "slopeBias" + max: 1.0 + min: 0.0 + } + ConfigSlider { + label: qsTr("Cascade 2 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") + property: "slopeBias" + max: 1.0 + min: 0.0 + } + ConfigSlider { + label: qsTr("Cascade 3 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") + property: "slopeBias" + max: 1.0 + min: 0.0 } } From 3fa2babec2ea97178ae2030982459f6b511366b7 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 31 Jan 2018 11:55:46 +0100 Subject: [PATCH 04/12] Moved cascade frustum pre-computation to single ShadowSetup job --- .../render-utils/src/RenderShadowTask.cpp | 75 ++++++++++++++++--- libraries/render-utils/src/RenderShadowTask.h | 61 ++++++++++----- scripts/developer/utilities/render/shadow.qml | 41 +++++----- 3 files changed, 128 insertions(+), 49 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 2b5ab09c06..b83911582c 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -213,7 +213,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende initZPassPipelines(*shapePlumber, state); } - task.addJob("ShadowSetup"); + const auto coarseFrustum = task.addJob("ShadowSetup"); for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { char jobName[64]; @@ -243,7 +243,31 @@ void RenderShadowTask::configure(const Config& configuration) { // Task::configure(configuration); } -void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { +RenderShadowSetup::RenderShadowSetup() : + _coarseShadowFrustum{ std::make_shared() } { + +} + +void RenderShadowSetup::configure(const Config& configuration) { + setConstantBias(0, configuration.constantBias0); + setConstantBias(1, configuration.constantBias1); + setConstantBias(2, configuration.constantBias2); + setConstantBias(3, configuration.constantBias3); + setSlopeBias(0, configuration.slopeBias0); + setSlopeBias(1, configuration.slopeBias1); + setSlopeBias(2, configuration.slopeBias2); + setSlopeBias(3, configuration.slopeBias3); +} + +void RenderShadowSetup::setConstantBias(int cascadeIndex, float value) { + _bias[cascadeIndex]._constant = value * value * value * 0.004f; +} + +void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) { + _bias[cascadeIndex]._slope = value * value * value * 0.01f; +} + +void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Output& output) { auto lightStage = renderContext->_scene->getStage(); assert(lightStage); // Cache old render args @@ -252,12 +276,46 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow) { globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); - } -} + auto firstCascadeFrustum = globalShadow->getCascade(0).getFrustum(); + unsigned int cascadeIndex; + _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); + _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); -void RenderShadowCascadeSetup::configure(const Config& configuration) { - _fixedBias = configuration.fixedBias * configuration.fixedBias * configuration.fixedBias * 0.004f; - _slopeBias = configuration.slopeBias * configuration.slopeBias * configuration.slopeBias * 0.01f; + // Adjust each cascade frustum + for (cascadeIndex = 0; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { + auto& bias = _bias[cascadeIndex]; + globalShadow->setKeylightCascadeFrustum(cascadeIndex, args->getViewFrustum(), + SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, + bias._constant, bias._slope); + } + // Now adjust coarse frustum bounds + auto left = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getRight()); + auto right = glm::dot(firstCascadeFrustum->getFarTopRight(), firstCascadeFrustum->getRight()); + auto top = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getUp()); + auto bottom = glm::dot(firstCascadeFrustum->getFarBottomRight(), firstCascadeFrustum->getUp()); + auto near = firstCascadeFrustum->getNearClip(); + auto far = firstCascadeFrustum->getFarClip(); + for (cascadeIndex = 1; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { + auto cascadeLeft = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getRight()); + auto cascadeRight = glm::dot(firstCascadeFrustum->getFarTopRight(), firstCascadeFrustum->getRight()); + auto cascadeTop = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getUp()); + auto cascadeBottom = glm::dot(firstCascadeFrustum->getFarBottomRight(), firstCascadeFrustum->getUp()); + auto cascadeNear = firstCascadeFrustum->getNearClip(); + auto cascadeFar = firstCascadeFrustum->getFarClip(); + left = glm::min(left, cascadeLeft); + right = glm::max(right, cascadeRight); + bottom = glm::min(bottom, cascadeBottom); + top = glm::max(top, cascadeTop); + near = glm::min(near, cascadeNear); + far = glm::max(far, cascadeFar); + } + _coarseShadowFrustum->setProjection(glm::ortho(left, right, bottom, top, near, far)); + _coarseShadowFrustum->calculate(); + + output = _coarseShadowFrustum; + } else { + output = nullptr; + } } void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { @@ -273,9 +331,6 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon if (globalShadow && _cascadeIndexgetCascadeCount()) { output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); - globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, - _fixedBias, _slopeBias); - // Set the keylight render args args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 7e5655b375..f488c46b8e 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -17,6 +17,8 @@ #include +#include "Shadows_shared.slh" + class ViewFrustum; class RenderShadowMap { @@ -53,43 +55,64 @@ public: void configure(const Config& configuration); }; -class RenderShadowSetup { -public: - using JobModel = render::Job::Model; - - RenderShadowSetup() {} - void run(const render::RenderContextPointer& renderContext); - -}; - -class RenderShadowCascadeSetupConfig : public render::Job::Config { +class RenderShadowSetupConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(float fixedBias MEMBER fixedBias NOTIFY dirty) - Q_PROPERTY(float slopeBias MEMBER slopeBias NOTIFY dirty) + Q_PROPERTY(float constantBias0 MEMBER constantBias0 NOTIFY dirty) + Q_PROPERTY(float constantBias1 MEMBER constantBias1 NOTIFY dirty) + Q_PROPERTY(float constantBias2 MEMBER constantBias2 NOTIFY dirty) + Q_PROPERTY(float constantBias3 MEMBER constantBias3 NOTIFY dirty) + Q_PROPERTY(float slopeBias0 MEMBER slopeBias0 NOTIFY dirty) + Q_PROPERTY(float slopeBias1 MEMBER slopeBias1 NOTIFY dirty) + Q_PROPERTY(float slopeBias2 MEMBER slopeBias2 NOTIFY dirty) + Q_PROPERTY(float slopeBias3 MEMBER slopeBias3 NOTIFY dirty) public: - float fixedBias{ 0.15f }; - float slopeBias{ 0.55f }; + float constantBias0{ 0.15f }; + float constantBias1{ 0.15f }; + float constantBias2{ 0.15f }; + float constantBias3{ 0.15f }; + float slopeBias0{ 0.55f }; + float slopeBias1{ 0.55f }; + float slopeBias2{ 0.55f }; + float slopeBias3{ 0.55f }; signals: void dirty(); }; +class RenderShadowSetup { +public: + using Output = ViewFrustumPointer; + using Config = RenderShadowSetupConfig; + using JobModel = render::Job::ModelO; + + RenderShadowSetup(); + void configure(const Config& configuration); + void run(const render::RenderContextPointer& renderContext, Output& output); + +private: + + ViewFrustumPointer _coarseShadowFrustum; + struct { + float _constant; + float _slope; + } _bias[SHADOW_CASCADE_MAX_COUNT]; + + void setConstantBias(int cascadeIndex, float value); + void setSlopeBias(int cascadeIndex, float value); +}; + class RenderShadowCascadeSetup { public: using Outputs = render::VaryingSet3; - using Config = RenderShadowCascadeSetupConfig; - using JobModel = render::Job::ModelO; + using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {} - void configure(const Config& configuration); void run(const render::RenderContextPointer& renderContext, Outputs& output); private: unsigned int _cascadeIndex; - float _fixedBias{ 0.1f }; - float _slopeBias{ 0.1f }; }; class RenderShadowCascadeTeardown { diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index a077ab9f50..3400dcd847 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -18,6 +18,7 @@ Column { id: root spacing: 8 property var viewConfig: Render.getConfig("RenderMainView.DrawViewFrustum"); + property var shadowConfig : Render.getConfig("RenderMainView.ShadowSetup"); property var shadow0Config: Render.getConfig("RenderMainView.DrawShadowFrustum0"); property var shadow1Config: Render.getConfig("RenderMainView.DrawShadowFrustum1"); property var shadow2Config: Render.getConfig("RenderMainView.DrawShadowFrustum2"); @@ -80,34 +81,34 @@ Column { } } ConfigSlider { - label: qsTr("Cascade 0 fixed bias") + label: qsTr("Cascade 0 constant bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") - property: "fixedBias" + config: shadowConfig + property: "constantBias0" max: 1.0 min: 0.0 } ConfigSlider { - label: qsTr("Cascade 1 fixed bias") + label: qsTr("Cascade 1 constant bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") - property: "fixedBias" + config: shadowConfig + property: "constantBias1" max: 1.0 min: 0.0 } ConfigSlider { - label: qsTr("Cascade 2 fixed bias") + label: qsTr("Cascade 2 constant bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") - property: "fixedBias" + config: shadowConfig + property: "constantBias2" max: 1.0 min: 0.0 } ConfigSlider { - label: qsTr("Cascade 3 fixed bias") + label: qsTr("Cascade 3 constant bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") - property: "fixedBias" + config: shadowConfig + property: "constantBias3" max: 1.0 min: 0.0 } @@ -115,32 +116,32 @@ Column { ConfigSlider { label: qsTr("Cascade 0 slope bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") - property: "slopeBias" + config: shadowConfig + property: "slopeBias0" max: 1.0 min: 0.0 } ConfigSlider { label: qsTr("Cascade 1 slope bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") - property: "slopeBias" + config: shadowConfig + property: "slopeBias1" max: 1.0 min: 0.0 } ConfigSlider { label: qsTr("Cascade 2 slope bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") - property: "slopeBias" + config: shadowConfig + property: "slopeBias2" max: 1.0 min: 0.0 } ConfigSlider { label: qsTr("Cascade 3 slope bias") integral: false - config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") - property: "slopeBias" + config: shadowConfig + property: "slopeBias3" max: 1.0 min: 0.0 } From d422545c78a999041f956e02a5d2e74203e79493 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 31 Jan 2018 17:13:06 +0100 Subject: [PATCH 05/12] Changed shadow task to do a single octree query as well as pipeline/depth sort for all cascades. Still issue with disapearing objects from shadow map with viewpoint --- .../render-utils/src/RenderShadowTask.cpp | 105 +++++---- libraries/render-utils/src/RenderShadowTask.h | 17 +- libraries/render-utils/src/RenderViewTask.cpp | 9 +- libraries/render/src/render/CullTask.cpp | 203 ++++++++++++++---- libraries/render/src/render/CullTask.h | 40 +++- .../src/render/RenderFetchCullSortTask.cpp | 6 +- 6 files changed, 273 insertions(+), 107 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index b83911582c..c9df67dad1 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -213,28 +213,34 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende initZPassPipelines(*shapePlumber, state); } - const auto coarseFrustum = task.addJob("ShadowSetup"); + const auto setupOutput = task.addJob("ShadowSetup"); + // Fetch and cull the items from the scene + static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); + const auto fetchInput = render::Varying(shadowCasterFilter); + const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); + const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); + const auto shadowItems = task.addJob("FetchShadowSelection", selectionInputs); + + // Sort + const auto sortedPipelines = task.addJob("PipelineSortShadow", shadowItems); + const auto sortedShapes = task.addJob("DepthSortShadow", sortedPipelines, true); for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { char jobName[64]; sprintf(jobName, "ShadowCascadeSetup%d", i); - const auto setupOutput = task.addJob(jobName, i); - const auto shadowFilter = setupOutput.getN(1); + const auto shadowFilter = task.addJob(jobName, i); - // CPU jobs: - // Fetch and cull the items from the scene - const auto shadowSelection = task.addJob("FetchShadowSelection", shadowFilter); - const auto cullInputs = CullSpatialSelection::Inputs(shadowSelection, shadowFilter).asVarying(); - const auto culledShadowSelection = task.addJob("CullShadowSelection", cullInputs, cullFunctor, RenderDetails::SHADOW); - - // Sort - const auto sortedPipelines = task.addJob("PipelineSortShadowSort", culledShadowSelection); - const auto sortedShapesAndBounds = task.addJob("DepthSortShadowMap", sortedPipelines, true); + // CPU jobs: finer grained culling + const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter).asVarying(); + const auto culledShadowItemsAndBounds = task.addJob("CullShadowCascade", cullInputs, cullFunctor, RenderDetails::SHADOW); // GPU jobs: Render to shadow map - task.addJob("RenderShadowMap", sortedShapesAndBounds, shapePlumber, i); - task.addJob("ShadowCascadeTeardown", setupOutput); + sprintf(jobName, "RenderShadowMap%d", i); + task.addJob(jobName, culledShadowItemsAndBounds, shapePlumber, i); + task.addJob("ShadowCascadeTeardown", shadowFilter); } + + task.addJob("ShadowTeardown", setupOutput); } void RenderShadowTask::configure(const Config& configuration) { @@ -267,16 +273,19 @@ void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) { _bias[cascadeIndex]._slope = value * value * value * 0.01f; } -void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Output& output) { +void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { auto lightStage = renderContext->_scene->getStage(); assert(lightStage); // Cache old render args RenderArgs* args = renderContext->args; + output.edit0() = args->_renderMode; + output.edit1() = args->_sizeScale; + const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow) { globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); - auto firstCascadeFrustum = globalShadow->getCascade(0).getFrustum(); + auto& firstCascadeFrustum = globalShadow->getCascade(0).getFrustum(); unsigned int cascadeIndex; _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); @@ -296,12 +305,13 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O auto near = firstCascadeFrustum->getNearClip(); auto far = firstCascadeFrustum->getFarClip(); for (cascadeIndex = 1; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { - auto cascadeLeft = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getRight()); - auto cascadeRight = glm::dot(firstCascadeFrustum->getFarTopRight(), firstCascadeFrustum->getRight()); - auto cascadeTop = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getUp()); - auto cascadeBottom = glm::dot(firstCascadeFrustum->getFarBottomRight(), firstCascadeFrustum->getUp()); - auto cascadeNear = firstCascadeFrustum->getNearClip(); - auto cascadeFar = firstCascadeFrustum->getFarClip(); + auto& cascadeFrustum = globalShadow->getCascade(cascadeIndex).getFrustum(); + auto cascadeLeft = glm::dot(cascadeFrustum->getFarTopLeft(), cascadeFrustum->getRight()); + auto cascadeRight = glm::dot(cascadeFrustum->getFarTopRight(), cascadeFrustum->getRight()); + auto cascadeTop = glm::dot(cascadeFrustum->getFarTopLeft(), cascadeFrustum->getUp()); + auto cascadeBottom = glm::dot(cascadeFrustum->getFarBottomRight(), cascadeFrustum->getUp()); + auto cascadeNear = cascadeFrustum->getNearClip(); + auto cascadeFar = cascadeFrustum->getFarClip(); left = glm::min(left, cascadeLeft); right = glm::max(right, cascadeRight); bottom = glm::min(bottom, cascadeBottom); @@ -312,9 +322,14 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O _coarseShadowFrustum->setProjection(glm::ortho(left, right, bottom, top, near, far)); _coarseShadowFrustum->calculate(); - output = _coarseShadowFrustum; - } else { - output = nullptr; + // Push frustum for further culling and selection + args->pushViewFrustum(*_coarseShadowFrustum); + + args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; + if (lightStage->getCurrentKeyLight()->getType() == graphics::Light::SUN) { + // Set to ridiculously high amount to prevent solid angle culling in octree selection + args->_sizeScale = 1e16f; + } } } @@ -324,37 +339,41 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon // Cache old render args RenderArgs* args = renderContext->args; - output.edit0() = args->_renderMode; - output.edit2() = args->_sizeScale; - const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); + output = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); // Set the keylight render args - args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); - args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; - if (lightStage->getCurrentKeyLight()->getType() == graphics::Light::SUN) { - const float shadowSizeScale = 1e16f; - // Set the size scale to a ridiculously high value to prevent small object culling which assumes - // the view frustum is a perspective projection. But this isn't the case for the sun which - // is an orthographic projection. - args->_sizeScale = shadowSizeScale; - } - + auto& cascade = globalShadow->getCascade(_cascadeIndex); + auto& cascadeFrustum = cascade.getFrustum(); + args->pushViewFrustum(*cascadeFrustum); + // Set the cull threshold to 2 shadow texels. + auto texelSize = glm::max(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x; + texelSize *= 2.0f; + // SizeScale is used in the shadow cull function defined ine RenderViewTask + args->_sizeScale = texelSize * texelSize; } else { - output.edit1() = ItemFilter::Builder::nothing(); + output = ItemFilter::Builder::nothing(); } } void RenderShadowCascadeTeardown::run(const render::RenderContextPointer& renderContext, const Input& input) { RenderArgs* args = renderContext->args; - if (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE && !input.get1().selectsNothing()) { + if (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE && !input.selectsNothing()) { + args->popViewFrustum(); + } + assert(args->hasViewFrustum()); +} + +void RenderShadowTeardown::run(const render::RenderContextPointer& renderContext, const Input& input) { + RenderArgs* args = renderContext->args; + + if (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE) { args->popViewFrustum(); } assert(args->hasViewFrustum()); // Reset the render args args->_renderMode = input.get0(); - args->_sizeScale = input.get2(); -}; + args->_sizeScale = input.get1(); +} diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index f488c46b8e..1736d07fd5 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -82,13 +82,13 @@ signals: class RenderShadowSetup { public: - using Output = ViewFrustumPointer; + using Outputs = render::VaryingSet2; using Config = RenderShadowSetupConfig; - using JobModel = render::Job::ModelO; + using JobModel = render::Job::ModelO; RenderShadowSetup(); void configure(const Config& configuration); - void run(const render::RenderContextPointer& renderContext, Output& output); + void run(const render::RenderContextPointer& renderContext, Outputs& output); private: @@ -104,7 +104,7 @@ private: class RenderShadowCascadeSetup { public: - using Outputs = render::VaryingSet3; + using Outputs = render::ItemFilter; using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {} @@ -117,9 +117,16 @@ private: class RenderShadowCascadeTeardown { public: - using Input = RenderShadowCascadeSetup::Outputs; + using Input = render::ItemFilter; using JobModel = render::Job::ModelI; void run(const render::RenderContextPointer& renderContext, const Input& input); }; +class RenderShadowTeardown { +public: + using Input = RenderShadowSetup::Outputs; + using JobModel = render::Job::ModelI; + void run(const render::RenderContextPointer& renderContext, const Input& input); +}; + #endif // hifi_RenderShadowTask_h diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index dc6c66e058..c2e43582cd 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -21,13 +21,8 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render: // but the cullFunctor passed is probably tailored for perspective projection and culls too much. task.addJob("RenderShadowTask", [](const RenderArgs* args, const AABox& bounds) { // Cull only objects that are too small relatively to shadow frustum - auto& frustum = args->getViewFrustum(); - auto frustumSize = std::max(frustum.getHeight(), frustum.getWidth()); - const auto boundsRadius = bounds.getDimensions().length(); - const auto relativeBoundRadius = boundsRadius / frustumSize; - const auto threshold = 1e-3f; - return relativeBoundRadius > threshold; - return true; + const auto boundsSquareRadius = glm::dot(bounds.getDimensions(), bounds.getDimensions()); + return boundsSquareRadius > args->_sizeScale; }); const auto items = task.addJob("FetchCullSort", cullFunctor); diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 70331cdb47..c6ff224560 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -19,6 +19,50 @@ using namespace render; +// Culling Frustum / solidAngle test helper class +struct Test { + CullFunctor _functor; + RenderArgs* _args; + RenderDetails::Item& _renderDetails; + glm::vec3 _eyePos; + float _squareTanAlpha; + + Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails) : + _functor(functor), + _args(pargs), + _renderDetails(renderDetails) { + // FIXME: Keep this code here even though we don't use it yet + /*_eyePos = _args->getViewFrustum().getPosition(); + float a = glm::degrees(Octree::getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); + auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees + angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree + auto tanAlpha = tan(angle); + _squareTanAlpha = (float)(tanAlpha * tanAlpha); + */ + } + + bool frustumTest(const AABox& bound) { + if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) { + _renderDetails._outOfView++; + return false; + } + return true; + } + + bool solidAngleTest(const AABox& bound) { + // FIXME: Keep this code here even though we don't use it yet + //auto eyeToPoint = bound.calcCenter() - _eyePos; + //auto boundSize = bound.getDimensions(); + //float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha; + //if (test < 0.0f) { + if (!_functor(_args, bound)) { + _renderDetails._tooSmall++; + return false; + } + return true; + } +}; + void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details, const ItemBounds& inItems, ItemBounds& outItems) { assert(renderContext->args); @@ -82,18 +126,20 @@ void FetchSpatialTree::configure(const Config& config) { _lodAngle = config.lodAngle; } -void FetchSpatialTree::run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection) { +void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemSpatialTree::ItemSelection& outSelection) { // start fresh outSelection.clear(); + auto& filter = inputs; + if (!filter.selectsNothing()) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; auto& scene = renderContext->_scene; - // Eventually use a frozen frustum auto queryFrustum = args->getViewFrustum(); + // Eventually use a frozen frustum if (_freezeFrustum) { if (_justFrozeFrustum) { _justFrozeFrustum = false; @@ -134,50 +180,6 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, args->pushViewFrustum(_frozenFrustum); // replace the true view frustum by the frozen one } - // Culling Frustum / solidAngle test helper class - struct Test { - CullFunctor _functor; - RenderArgs* _args; - RenderDetails::Item& _renderDetails; - glm::vec3 _eyePos; - float _squareTanAlpha; - - Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails) : - _functor(functor), - _args(pargs), - _renderDetails(renderDetails) - { - // FIXME: Keep this code here even though we don't use it yet - /*_eyePos = _args->getViewFrustum().getPosition(); - float a = glm::degrees(Octree::getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); - auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees - angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree - auto tanAlpha = tan(angle); - _squareTanAlpha = (float)(tanAlpha * tanAlpha); - */ - } - - bool frustumTest(const AABox& bound) { - if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) { - _renderDetails._outOfView++; - return false; - } - return true; - } - - bool solidAngleTest(const AABox& bound) { - // FIXME: Keep this code here even though we don't use it yet - //auto eyeToPoint = bound.calcCenter() - _eyePos; - //auto boundSize = bound.getDimensions(); - //float test = (glm::dot(boundSize, boundSize) / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha; - //if (test < 0.0f) { - if (!_functor(_args, bound)) { - _renderDetails._tooSmall++; - return false; - } - return true; - } - }; Test test(_cullFunctor, args, details); // Now we have a selection of items to render @@ -309,3 +311,112 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, std::static_pointer_cast(renderContext->jobConfig)->numItems = (int)outItems.size(); } + +void CullShapeBounds::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + RenderArgs* args = renderContext->args; + + const auto& inShapes = inputs.get0(); + const auto& filter = inputs.get1(); + auto& outShapes = outputs.edit0(); + auto& outBounds = outputs.edit1(); + + outShapes.clear(); + outBounds = AABox(); + + if (!filter.selectsNothing()) { + auto& details = args->_details.edit(_detailType); + Test test(_cullFunctor, args, details); + + for (auto& inItems : inShapes) { + auto key = inItems.first; + auto outItems = outShapes.find(key); + if (outItems == outShapes.end()) { + outItems = outShapes.insert(std::make_pair(key, ItemBounds{})).first; + outItems->second.reserve(inItems.second.size()); + } + + details._considered += (int)inItems.second.size(); + + for (auto& item : inItems.second) { + if (test.frustumTest(item.bound) && test.solidAngleTest(item.bound)) { + outItems->second.emplace_back(item); + outBounds += item.bound; + } + } + + details._rendered += (int)outItems->second.size(); + } + + for (auto& items : outShapes) { + items.second.shrink_to_fit(); + } + } +} + +void FetchSpatialSelection::run(const RenderContextPointer& renderContext, + const Inputs& inputs, ItemBounds& outItems) { + assert(renderContext->args); + RenderArgs* args = renderContext->args; + auto& scene = renderContext->_scene; + auto& inSelection = inputs.get0(); + + // Now we have a selection of items to render + outItems.clear(); + outItems.reserve(inSelection.numItems()); + + const auto filter = inputs.get1(); + if (!filter.selectsNothing()) { + // Now get the bound, and + // filter individually against the _filter + + // inside & fit items: filter only, culling is disabled + { + PerformanceTimer perfTimer("insideFitItems"); + for (auto id : inSelection.insideItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // inside & subcell items: filter only, culling is disabled + { + PerformanceTimer perfTimer("insideSmallItems"); + for (auto id : inSelection.insideSubcellItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // partial & fit items: filter only, culling is disabled + { + PerformanceTimer perfTimer("partialFitItems"); + for (auto id : inSelection.partialItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // partial & subcell items: filter only, culling is disabled + { + PerformanceTimer perfTimer("partialSmallItems"); + for (auto id : inSelection.partialSubcellItems) { + auto& item = scene->getItem(id); + if (filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + } +} diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 486c4f4cdf..a140a86aee 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -55,12 +55,13 @@ namespace render { float _lodAngle; public: using Config = FetchSpatialTreeConfig; - using JobModel = Job::ModelIO; + using Inputs = ItemFilter; + using JobModel = Job::ModelIO; FetchSpatialTree() {} void configure(const Config& config); - void run(const RenderContextPointer& renderContext, const ItemFilter& filter, ItemSpatialTree::ItemSelection& outSelection); + void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemSpatialTree::ItemSelection& outSelection); }; class CullSpatialSelectionConfig : public Job::Config { @@ -96,7 +97,8 @@ namespace render { _detailType(type) {} CullSpatialSelection(CullFunctor cullFunctor) : - _cullFunctor{ cullFunctor } {} + _cullFunctor{ cullFunctor } { + } CullFunctor _cullFunctor; RenderDetails::Type _detailType{ RenderDetails::OTHER }; @@ -105,6 +107,38 @@ namespace render { void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; + class CullShapeBounds { + public: + using Inputs = render::VaryingSet2; + using Outputs = render::VaryingSet2; + using JobModel = Job::ModelIO; + + CullShapeBounds(CullFunctor cullFunctor, RenderDetails::Type type) : + _cullFunctor{ cullFunctor }, + _detailType(type) {} + + CullShapeBounds(CullFunctor cullFunctor) : + _cullFunctor{ cullFunctor } { + } + + void run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + + private: + + CullFunctor _cullFunctor; + RenderDetails::Type _detailType{ RenderDetails::OTHER }; + + }; + + class FetchSpatialSelection { + public: + using Inputs = render::VaryingSet2; + using JobModel = Job::ModelIO; + + FetchSpatialSelection() {} + void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); + }; + } #endif // hifi_render_CullTask_h; \ No newline at end of file diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index d7294fa2bd..a1b4f079e7 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -23,9 +23,9 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin // CPU jobs: // Fetch and cull the items from the scene const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered(); - const auto spatialFilter = render::Varying(filter); - const auto spatialSelection = task.addJob("FetchSceneSelection", spatialFilter); - const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, spatialFilter).asVarying(); + const auto fetchInput = render::Varying(filter); + const auto spatialSelection = task.addJob("FetchSceneSelection", fetchInput); + const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, render::Varying(filter)).asVarying(); const auto culledSpatialSelection = task.addJob("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM); // Overlays are not culled From 3804917cf46abb3f9919e252d68ad41a0aa5b6c3 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 2 Feb 2018 09:40:57 +0100 Subject: [PATCH 06/12] Orthographic octree selection seems to be working --- libraries/gpu/src/gpu/Framebuffer.cpp | 2 +- libraries/octree/src/OctreeUtils.cpp | 8 +- libraries/octree/src/OctreeUtils.h | 3 +- .../render-utils/src/RenderShadowTask.cpp | 50 +++++++++---- libraries/render-utils/src/RenderShadowTask.h | 10 +-- libraries/render-utils/src/Shadow.slh | 5 -- libraries/render-utils/src/ShadowCore.slh | 4 +- libraries/render/src/render/CullTask.cpp | 30 +++++--- libraries/render/src/render/CullTask.h | 3 +- .../render/src/render/DrawSceneOctree.cpp | 2 +- .../src/render/RenderFetchCullSortTask.cpp | 2 +- libraries/render/src/render/SpatialTree.cpp | 74 +++++++++++++------ libraries/render/src/render/SpatialTree.h | 31 ++++---- libraries/shared/src/ViewFrustum.cpp | 4 + libraries/shared/src/ViewFrustum.h | 1 + 15 files changed, 152 insertions(+), 77 deletions(-) diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index 4fcc9ab0f9..8bb9be4a76 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -62,7 +62,7 @@ Framebuffer* Framebuffer::createShadowmap(uint16 width) { samplerDesc._wrapModeU = Sampler::WRAP_BORDER; samplerDesc._wrapModeV = Sampler::WRAP_BORDER; samplerDesc._filter = Sampler::FILTER_MIN_MAG_LINEAR; - samplerDesc._comparisonFunc = LESS_EQUAL; + samplerDesc._comparisonFunc = LESS; depthTexture->setSampler(Sampler(samplerDesc)); framebuffer->setDepthStencilBuffer(depthTexture, depthFormat); diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp index c55016d8e2..ca15324d4e 100644 --- a/libraries/octree/src/OctreeUtils.cpp +++ b/libraries/octree/src/OctreeUtils.cpp @@ -64,8 +64,14 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc return voxelSizeScale / powf(2.0f, renderLevel); } -float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { +float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { const float maxScale = (float)TREE_SCALE; float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO; return atan(maxScale / visibleDistanceAtMaxScale); } + +float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust) { + // Smallest visible element is 1cm + const float smallestSize = 0.01f; + return (smallestSize * MAX_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT) / boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale); +} diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h index c257bcd5f1..0f87bb6f68 100644 --- a/libraries/octree/src/OctreeUtils.h +++ b/libraries/octree/src/OctreeUtils.h @@ -25,7 +25,8 @@ float calculateRenderAccuracy(const glm::vec3& position, float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); -float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); +float getPerspectiveAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); +float getOrthographicAccuracySize(float octreeSizeScale, int boundaryLevelAdjust); // MIN_ELEMENT_ANGULAR_DIAMETER = angular diameter of 1x1x1m cube at 400m = sqrt(3) / 400 = 0.0043301 radians ~= 0.25 degrees const float MIN_ELEMENT_ANGULAR_DIAMETER = 0.0043301f; // radians diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index c9df67dad1..b576bf774c 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -214,9 +214,10 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende } const auto setupOutput = task.addJob("ShadowSetup"); + const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); - const auto fetchInput = render::Varying(shadowCasterFilter); + const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); const auto shadowItems = task.addJob("FetchShadowSelection", selectionInputs); @@ -281,14 +282,15 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O output.edit0() = args->_renderMode; output.edit1() = args->_sizeScale; + output.edit2() = glm::ivec2(0, 0); const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow) { globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); - auto& firstCascadeFrustum = globalShadow->getCascade(0).getFrustum(); + + auto& firstCascade = globalShadow->getCascade(0); + auto& firstCascadeFrustum = firstCascade.getFrustum(); unsigned int cascadeIndex; - _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); - _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); // Adjust each cascade frustum for (cascadeIndex = 0; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { @@ -297,19 +299,29 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, bias._constant, bias._slope); } + // Now adjust coarse frustum bounds - auto left = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getRight()); - auto right = glm::dot(firstCascadeFrustum->getFarTopRight(), firstCascadeFrustum->getRight()); - auto top = glm::dot(firstCascadeFrustum->getFarTopLeft(), firstCascadeFrustum->getUp()); - auto bottom = glm::dot(firstCascadeFrustum->getFarBottomRight(), firstCascadeFrustum->getUp()); + auto frustumPosition = firstCascadeFrustum->getPosition(); + auto farTopLeft = firstCascadeFrustum->getFarTopLeft() - frustumPosition; + auto farBottomRight = firstCascadeFrustum->getFarBottomRight() - frustumPosition; + + auto left = glm::dot(farTopLeft, firstCascadeFrustum->getRight()); + auto right = glm::dot(farBottomRight, firstCascadeFrustum->getRight()); + auto top = glm::dot(farTopLeft, firstCascadeFrustum->getUp()); + auto bottom = glm::dot(farBottomRight, firstCascadeFrustum->getUp()); auto near = firstCascadeFrustum->getNearClip(); auto far = firstCascadeFrustum->getFarClip(); + for (cascadeIndex = 1; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { auto& cascadeFrustum = globalShadow->getCascade(cascadeIndex).getFrustum(); - auto cascadeLeft = glm::dot(cascadeFrustum->getFarTopLeft(), cascadeFrustum->getRight()); - auto cascadeRight = glm::dot(cascadeFrustum->getFarTopRight(), cascadeFrustum->getRight()); - auto cascadeTop = glm::dot(cascadeFrustum->getFarTopLeft(), cascadeFrustum->getUp()); - auto cascadeBottom = glm::dot(cascadeFrustum->getFarBottomRight(), cascadeFrustum->getUp()); + + farTopLeft = cascadeFrustum->getFarTopLeft() - frustumPosition; + farBottomRight = cascadeFrustum->getFarBottomRight() - frustumPosition; + + auto cascadeLeft = glm::dot(farTopLeft, cascadeFrustum->getRight()); + auto cascadeRight = glm::dot(farBottomRight, cascadeFrustum->getRight()); + auto cascadeTop = glm::dot(farTopLeft, cascadeFrustum->getUp()); + auto cascadeBottom = glm::dot(farBottomRight, cascadeFrustum->getUp()); auto cascadeNear = cascadeFrustum->getNearClip(); auto cascadeFar = cascadeFrustum->getFarClip(); left = glm::min(left, cascadeLeft); @@ -319,6 +331,9 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O near = glm::min(near, cascadeNear); far = glm::max(far, cascadeFar); } + + _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); + _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); _coarseShadowFrustum->setProjection(glm::ortho(left, right, bottom, top, near, far)); _coarseShadowFrustum->calculate(); @@ -326,10 +341,13 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O args->pushViewFrustum(*_coarseShadowFrustum); args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; - if (lightStage->getCurrentKeyLight()->getType() == graphics::Light::SUN) { - // Set to ridiculously high amount to prevent solid angle culling in octree selection - args->_sizeScale = 1e16f; - } + + // We want for the octree query enough resolution to catch the details in the lowest cascade. So compute + // the desired resolution for the first cascade frustum and extrapolate it to the coarse frustum. + glm::ivec2 queryResolution = firstCascade.framebuffer->getSize(); + queryResolution.x = int(queryResolution.x * _coarseShadowFrustum->getWidth() / firstCascadeFrustum->getWidth()); + queryResolution.y = int(queryResolution.y * _coarseShadowFrustum->getHeight() / firstCascadeFrustum->getHeight()); + output.edit2() = queryResolution; } } diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 1736d07fd5..ce4c3047d8 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -71,10 +71,10 @@ public: float constantBias1{ 0.15f }; float constantBias2{ 0.15f }; float constantBias3{ 0.15f }; - float slopeBias0{ 0.55f }; - float slopeBias1{ 0.55f }; - float slopeBias2{ 0.55f }; - float slopeBias3{ 0.55f }; + float slopeBias0{ 0.6f }; + float slopeBias1{ 0.6f }; + float slopeBias2{ 0.6f }; + float slopeBias3{ 0.6f }; signals: void dirty(); @@ -82,7 +82,7 @@ signals: class RenderShadowSetup { public: - using Outputs = render::VaryingSet2; + using Outputs = render::VaryingSet3; using Config = RenderShadowSetupConfig; using JobModel = render::Job::ModelO; diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 6575e68090..e87519b5f4 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -79,15 +79,10 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[2]) + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[3]) ); - return shadowAttenuation; } float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float oneMinusNdotL) { - if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { - // If a point is not in the map, do not attenuate - return 1.0; - } float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * oneMinusNdotL; return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); } diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index 782e2bc2b8..e49e8d638f 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -53,8 +53,8 @@ vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) { } bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) { - bvec2 greaterThanZero = greaterThanEqual(cascadeTexCoords.xy, vec2(0)); - bvec2 lessThanOne = lessThanEqual(cascadeTexCoords.xy, vec2(1)); + bvec2 greaterThanZero = greaterThan(cascadeTexCoords.xy, vec2(0)); + bvec2 lessThanOne = lessThan(cascadeTexCoords.xy, vec2(1)); return all(greaterThanZero) && all(lessThanOne); } diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index c6ff224560..b3efc4f1a8 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -14,8 +14,8 @@ #include #include -#include #include +#include using namespace render; @@ -33,7 +33,7 @@ struct Test { _renderDetails(renderDetails) { // FIXME: Keep this code here even though we don't use it yet /*_eyePos = _args->getViewFrustum().getPosition(); - float a = glm::degrees(Octree::getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); + float a = glm::degrees(Octree::getPerspectiveAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree auto tanAlpha = tan(angle); @@ -130,7 +130,8 @@ void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inpu // start fresh outSelection.clear(); - auto& filter = inputs; + auto& filter = inputs.get0(); + auto frustumResolution = inputs.get1(); if (!filter.selectsNothing()) { assert(renderContext->args); @@ -149,8 +150,19 @@ void FetchSpatialTree::run(const RenderContextPointer& renderContext, const Inpu } // Octree selection! - float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); - scene->getSpatialTree().selectCellItems(outSelection, filter, queryFrustum, angle); + float threshold = 0.0f; + if (queryFrustum.isPerspective()) { + threshold = getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust); + if (frustumResolution.y > 0) { + threshold = glm::max(queryFrustum.getFieldOfView() / frustumResolution.y, threshold); + } + } else { + threshold = getOrthographicAccuracySize(args->_sizeScale, args->_boundaryLevelAdjust); + glm::vec2 frustumSize = glm::vec2(queryFrustum.getWidth(), queryFrustum.getHeight()); + const auto pixelResolution = frustumResolution.x > 0 ? frustumResolution : glm::ivec2(2048, 2048); + threshold = glm::max(threshold, glm::min(frustumSize.x / pixelResolution.x, frustumSize.y / pixelResolution.y)); + } + scene->getSpatialTree().selectCellItems(outSelection, filter, queryFrustum, threshold); } } @@ -371,7 +383,7 @@ void FetchSpatialSelection::run(const RenderContextPointer& renderContext, // Now get the bound, and // filter individually against the _filter - // inside & fit items: filter only, culling is disabled + // inside & fit items: filter only { PerformanceTimer perfTimer("insideFitItems"); for (auto id : inSelection.insideItems) { @@ -383,7 +395,7 @@ void FetchSpatialSelection::run(const RenderContextPointer& renderContext, } } - // inside & subcell items: filter only, culling is disabled + // inside & subcell items: filter only { PerformanceTimer perfTimer("insideSmallItems"); for (auto id : inSelection.insideSubcellItems) { @@ -395,7 +407,7 @@ void FetchSpatialSelection::run(const RenderContextPointer& renderContext, } } - // partial & fit items: filter only, culling is disabled + // partial & fit items: filter only { PerformanceTimer perfTimer("partialFitItems"); for (auto id : inSelection.partialItems) { @@ -407,7 +419,7 @@ void FetchSpatialSelection::run(const RenderContextPointer& renderContext, } } - // partial & subcell items: filter only, culling is disabled + // partial & subcell items: filter only { PerformanceTimer perfTimer("partialSmallItems"); for (auto id : inSelection.partialSubcellItems) { diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index a140a86aee..53d46d11b4 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -53,9 +53,10 @@ namespace render { bool _justFrozeFrustum{ false }; ViewFrustum _frozenFrustum; float _lodAngle; + public: using Config = FetchSpatialTreeConfig; - using Inputs = ItemFilter; + using Inputs = render::VaryingSet2; using JobModel = Job::ModelIO; FetchSpatialTree() {} diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 36663a454a..08d6340e43 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -148,7 +148,7 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS } // Draw the LOD Reticle { - float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); + float angle = glm::degrees(getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); Transform crosshairModel; crosshairModel.setTranslation(glm::vec3(0.0, 0.0, -1000.0)); crosshairModel.setScale(1000.0f * tanf(glm::radians(angle))); // Scaling at the actual tan of the lod angle => Multiplied by TWO diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index a1b4f079e7..23935851b3 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -23,7 +23,7 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin // CPU jobs: // Fetch and cull the items from the scene const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered(); - const auto fetchInput = render::Varying(filter); + const auto fetchInput = FetchSpatialTree::Inputs(filter, glm::ivec2(0,0)).asVarying(); const auto spatialSelection = task.addJob("FetchSceneSelection", fetchInput); const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, render::Varying(filter)).asVarying(); const auto culledSpatialSelection = task.addJob("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM); diff --git a/libraries/render/src/render/SpatialTree.cpp b/libraries/render/src/render/SpatialTree.cpp index 1bb3538521..c9f810ebc5 100644 --- a/libraries/render/src/render/SpatialTree.cpp +++ b/libraries/render/src/render/SpatialTree.cpp @@ -12,9 +12,26 @@ #include - using namespace render; +void Octree::PerspectiveSelector::setAngle(float a) { + const float MAX_LOD_ANGLE = glm::radians(45.0f); + const float MIN_LOD_ANGLE = glm::radians(1.0f / 60.0f); + + angle = std::max(MIN_LOD_ANGLE, std::min(MAX_LOD_ANGLE, a)); + auto tanAlpha = tan(angle); + squareTanAlpha = (float)(tanAlpha * tanAlpha); +} + +float Octree::PerspectiveSelector::testThreshold(const Coord3f& point, float size) const { + auto eyeToPoint = point - eyePos; + return (size * size / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha; +} + +float Octree::OrthographicSelector::testThreshold(const Coord3f& point, float size) const { + return (size * size) - squareMinSize; +} + const float Octree::INV_DEPTH_DIM[] = { 1.0f, @@ -520,10 +537,9 @@ int Octree::selectTraverse(Index cellID, CellSelection& selection, const Frustum // Test for lod auto cellLocation = cell.getlocation(); - float lod = selector.testSolidAngle(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth)); - if (lod < 0.0f) { + float test = selector.testThreshold(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth)); + if (test < 0.0f) { return 0; - break; } // Select this cell partially in frustum @@ -543,13 +559,13 @@ int Octree::selectTraverse(Index cellID, CellSelection& selection, const Frustum } -int Octree::selectBranch(Index cellID, CellSelection& selection, const FrustumSelector& selector) const { +int Octree::selectBranch(Index cellID, CellSelection& selection, const FrustumSelector& selector) const { int numSelectedsIn = (int) selection.size(); auto cell = getConcreteCell(cellID); auto cellLocation = cell.getlocation(); - float lod = selector.testSolidAngle(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth)); - if (lod < 0.0f) { + float test = selector.testThreshold(cellLocation.getCenter(), Octree::getCoordSubcellWidth(cellLocation.depth)); + if (test < 0.0f) { return 0; } @@ -580,24 +596,40 @@ int Octree::selectCellBrick(Index cellID, CellSelection& selection, bool inside) return (int) selection.size() - numSelectedsIn; } - -int ItemSpatialTree::selectCells(CellSelection& selection, const ViewFrustum& frustum, float lodAngle) const { +int ItemSpatialTree::selectCells(CellSelection& selection, const ViewFrustum& frustum, float threshold) const { auto worldPlanes = frustum.getPlanes(); - FrustumSelector selector; - for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) { - ::Plane octPlane; - octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH)); - selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient()); + if (frustum.isPerspective()) { + PerspectiveSelector selector; + for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) { + ::Plane octPlane; + octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH)); + selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient()); + } + + selector.eyePos = evalCoordf(frustum.getPosition(), ROOT_DEPTH); + selector.setAngle(threshold); + + return Octree::select(selection, selector); + } else { + OrthographicSelector selector; + for (int i = 0; i < ViewFrustum::NUM_PLANES; i++) { + ::Plane octPlane; + octPlane.setNormalAndPoint(worldPlanes[i].getNormal(), evalCoordf(worldPlanes[i].getPoint(), ROOT_DEPTH)); + selector.frustum[i] = Coord4f(octPlane.getNormal(), octPlane.getDCoefficient()); + } + + // Divide the threshold (which is in world distance units) by the dimension of the octree + // as all further computations will be done in normalized octree units + threshold *= getInvCellWidth(ROOT_DEPTH); + selector.setSize(threshold); + + return Octree::select(selection, selector); } - - selector.eyePos = evalCoordf(frustum.getPosition(), ROOT_DEPTH); - selector.setAngle(glm::radians(lodAngle)); - - return Octree::select(selection, selector); } -int ItemSpatialTree::selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, float lodAngle) const { - selectCells(selection.cellSelection, frustum, lodAngle); +int ItemSpatialTree::selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, + float threshold) const { + selectCells(selection.cellSelection, frustum, threshold); // Just grab the items in every selected bricks for (auto brickId : selection.cellSelection.insideBricks) { diff --git a/libraries/render/src/render/SpatialTree.h b/libraries/render/src/render/SpatialTree.h index a89b9847e6..b06053344d 100644 --- a/libraries/render/src/render/SpatialTree.h +++ b/libraries/render/src/render/SpatialTree.h @@ -312,23 +312,27 @@ namespace render { class FrustumSelector { public: Coord4f frustum[6]; + + virtual ~FrustumSelector() {} + virtual float testThreshold(const Coord3f& point, float size) const = 0; + }; + + class PerspectiveSelector : public FrustumSelector { + public: Coord3f eyePos; float angle; float squareTanAlpha; - const float MAX_LOD_ANGLE = glm::radians(45.0f); - const float MIN_LOD_ANGLE = glm::radians(1.0f / 60.0f); + void setAngle(float a); + float testThreshold(const Coord3f& point, float size) const override; + }; - void setAngle(float a) { - angle = std::max(MIN_LOD_ANGLE, std::min(MAX_LOD_ANGLE, a)); - auto tanAlpha = tan(angle); - squareTanAlpha = (float)(tanAlpha * tanAlpha); - } + class OrthographicSelector : public FrustumSelector { + public: + float squareMinSize; - float testSolidAngle(const Coord3f& point, float size) const { - auto eyeToPoint = point - eyePos; - return (size * size / glm::dot(eyeToPoint, eyeToPoint)) - squareTanAlpha; - } + void setSize(float a) { squareMinSize = a * a; } + float testThreshold(const Coord3f& point, float size) const override; }; int select(CellSelection& selection, const FrustumSelector& selector) const; @@ -443,7 +447,7 @@ namespace render { Index resetItem(Index oldCell, const ItemKey& oldKey, const AABox& bound, const ItemID& item, ItemKey& newKey); // Selection and traverse - int selectCells(CellSelection& selection, const ViewFrustum& frustum, float lodAngle) const; + int selectCells(CellSelection& selection, const ViewFrustum& frustum, float threshold) const; class ItemSelection { public: @@ -469,7 +473,8 @@ namespace render { } }; - int selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, float lodAngle) const; + int selectCellItems(ItemSelection& selection, const ItemFilter& filter, const ViewFrustum& frustum, + float threshold) const; }; } diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 5b016d4e91..0f98e8020c 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -847,3 +847,7 @@ void ViewFrustum::tesselateSides(const glm::vec3 points[8], Triangle triangles[8 triangle.v2 = points[vertexIndices[2]]; } } + +bool ViewFrustum::isPerspective() const { + return _projection[3][2] != 0.0f && _projection[2][3] != 0.0f && _projection[3][3] == 0.0f; +} diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index b55fe8b327..859b5c49c5 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -49,6 +49,7 @@ public: // setters for lens attributes void setProjection(const glm::mat4 & projection); void setFocalLength(float focalLength) { _focalLength = focalLength; } + bool isPerspective() const; // getters for lens attributes const glm::mat4& getProjection() const { return _projection; } From 1f4671ba175547f00ccce78b30627bba9de321b6 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 2 Feb 2018 11:09:28 +0100 Subject: [PATCH 07/12] Cleaned up orthographic shadow culling functor --- interface/src/SecondaryCamera.cpp | 2 +- .../render-utils/src/RenderShadowTask.cpp | 25 ++++++++++--------- libraries/render-utils/src/RenderShadowTask.h | 20 ++++++++++++--- libraries/render-utils/src/RenderViewTask.cpp | 8 +----- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 5db34c9441..c4199f15b2 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -19,7 +19,7 @@ using RenderArgsPointer = std::shared_ptr; void MainRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) { - task.addJob("RenderShadowTask", cullFunctor); + task.addJob("RenderShadowTask"); const auto items = task.addJob("FetchCullSort", cullFunctor); assert(items.canCast()); if (!isDeferred) { diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index b576bf774c..da7f6d97fa 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -200,8 +200,10 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con }); } -void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, CullFunctor cullFunctor) { - cullFunctor = cullFunctor ? cullFunctor : [](const RenderArgs*, const AABox&) { return true; }; +void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { + ::CullFunctor cullFunctor = [this](const RenderArgs* args, const AABox& bounds) { + return _cullFunctor(args, bounds); + }; // Prepare the ShapePipeline ShapePlumberPointer shapePlumber = std::make_shared(); @@ -229,7 +231,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { char jobName[64]; sprintf(jobName, "ShadowCascadeSetup%d", i); - const auto shadowFilter = task.addJob(jobName, i); + const auto shadowFilter = task.addJob(jobName, i, _cullFunctor); // CPU jobs: finer grained culling const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter).asVarying(); @@ -281,8 +283,7 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O RenderArgs* args = renderContext->args; output.edit0() = args->_renderMode; - output.edit1() = args->_sizeScale; - output.edit2() = glm::ivec2(0, 0); + output.edit1() = glm::ivec2(0, 0); const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow) { @@ -347,7 +348,7 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O glm::ivec2 queryResolution = firstCascade.framebuffer->getSize(); queryResolution.x = int(queryResolution.x * _coarseShadowFrustum->getWidth() / firstCascadeFrustum->getWidth()); queryResolution.y = int(queryResolution.y * _coarseShadowFrustum->getHeight() / firstCascadeFrustum->getHeight()); - output.edit2() = queryResolution; + output.edit1() = queryResolution; } } @@ -365,11 +366,12 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon auto& cascade = globalShadow->getCascade(_cascadeIndex); auto& cascadeFrustum = cascade.getFrustum(); args->pushViewFrustum(*cascadeFrustum); - // Set the cull threshold to 2 shadow texels. - auto texelSize = glm::max(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x; - texelSize *= 2.0f; - // SizeScale is used in the shadow cull function defined ine RenderViewTask - args->_sizeScale = texelSize * texelSize; + auto texelSize = glm::min(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x; + // Set the cull threshold to 16 shadow texels. + const auto minTexelCount = 16.0f; + // TODO : maybe adapt that with LOD management system? + texelSize *= minTexelCount; + _cullFunctor._minSquareSize = texelSize * texelSize; } else { output = ItemFilter::Builder::nothing(); } @@ -393,5 +395,4 @@ void RenderShadowTeardown::run(const render::RenderContextPointer& renderContext assert(args->hasViewFrustum()); // Reset the render args args->_renderMode = input.get0(); - args->_sizeScale = input.get1(); } diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index ce4c3047d8..15651354f1 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -50,9 +50,22 @@ public: using JobModel = render::Task::Model; RenderShadowTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor shouldRender); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); void configure(const Config& configuration); + + struct CullFunctor { + float _minSquareSize{ 0.0f }; + + bool operator()(const RenderArgs* args, const AABox& bounds) const { + // Cull only objects that are too small relatively to shadow frustum + const auto boundsSquareRadius = glm::dot(bounds.getDimensions(), bounds.getDimensions()); + return boundsSquareRadius > _minSquareSize; + } + }; + + CullFunctor _cullFunctor; + }; class RenderShadowSetupConfig : public render::Job::Config { @@ -82,7 +95,7 @@ signals: class RenderShadowSetup { public: - using Outputs = render::VaryingSet3; + using Outputs = render::VaryingSet2; using Config = RenderShadowSetupConfig; using JobModel = render::Job::ModelO; @@ -107,12 +120,13 @@ public: using Outputs = render::ItemFilter; using JobModel = render::Job::ModelO; - RenderShadowCascadeSetup(unsigned int cascadeIndex) : _cascadeIndex{ cascadeIndex } {} + RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor) : _cascadeIndex{ cascadeIndex }, _cullFunctor{ cullFunctor } {} void run(const render::RenderContextPointer& renderContext, Outputs& output); private: unsigned int _cascadeIndex; + RenderShadowTask::CullFunctor& _cullFunctor; }; class RenderShadowCascadeTeardown { diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index c2e43582cd..68585ac437 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -17,13 +17,7 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred) { // auto items = input.get(); - // Shadows use an orthographic projection because they are linked to sunlights - // but the cullFunctor passed is probably tailored for perspective projection and culls too much. - task.addJob("RenderShadowTask", [](const RenderArgs* args, const AABox& bounds) { - // Cull only objects that are too small relatively to shadow frustum - const auto boundsSquareRadius = glm::dot(bounds.getDimensions(), bounds.getDimensions()); - return boundsSquareRadius > args->_sizeScale; - }); + task.addJob("RenderShadowTask"); const auto items = task.addJob("FetchCullSort", cullFunctor); assert(items.canCast()); From 234cb1e3e612a1fe4b19ac516840b771a020f0da Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 2 Feb 2018 12:15:43 +0100 Subject: [PATCH 08/12] Added anti frustum test to remove lower cascade objects from higher shadow cascades --- .../render-utils/src/RenderShadowTask.cpp | 24 ++++++++++--- libraries/render-utils/src/RenderShadowTask.h | 2 +- libraries/render/src/render/CullTask.cpp | 36 ++++++++++++++----- libraries/render/src/render/CullTask.h | 2 +- libraries/shared/src/ViewFrustum.cpp | 12 +++++++ libraries/shared/src/ViewFrustum.h | 1 + 6 files changed, 63 insertions(+), 14 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index da7f6d97fa..829c0fbcf2 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -228,13 +228,26 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto sortedPipelines = task.addJob("PipelineSortShadow", shadowItems); const auto sortedShapes = task.addJob("DepthSortShadow", sortedPipelines, true); + render::Varying cascadeFrustums[SHADOW_CASCADE_MAX_COUNT] = { + ViewFrustumPointer(), + ViewFrustumPointer(), + ViewFrustumPointer(), + ViewFrustumPointer() + }; + for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { char jobName[64]; sprintf(jobName, "ShadowCascadeSetup%d", i); - const auto shadowFilter = task.addJob(jobName, i, _cullFunctor); + const auto cascadeSetupOutput = task.addJob(jobName, i, _cullFunctor); + const auto shadowFilter = cascadeSetupOutput.getN(0); + auto antiFrustum = render::Varying(ViewFrustumPointer()); + cascadeFrustums[i] = cascadeSetupOutput.getN(1); + if (i > 1) { + antiFrustum = cascadeFrustums[i - 2]; + } // CPU jobs: finer grained culling - const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter).asVarying(); + const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter, antiFrustum).asVarying(); const auto culledShadowItemsAndBounds = task.addJob("CullShadowCascade", cullInputs, cullFunctor, RenderDetails::SHADOW); // GPU jobs: Render to shadow map @@ -360,7 +373,7 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - output = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); + output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); // Set the keylight render args auto& cascade = globalShadow->getCascade(_cascadeIndex); @@ -372,8 +385,11 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon // TODO : maybe adapt that with LOD management system? texelSize *= minTexelCount; _cullFunctor._minSquareSize = texelSize * texelSize; + + output.edit1() = cascadeFrustum; } else { - output = ItemFilter::Builder::nothing(); + output.edit0() = ItemFilter::Builder::nothing(); + output.edit1() = ViewFrustumPointer(); } } diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 15651354f1..975f755a48 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -117,7 +117,7 @@ private: class RenderShadowCascadeSetup { public: - using Outputs = render::ItemFilter; + using Outputs = render::VaryingSet2; using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor) : _cascadeIndex{ cascadeIndex }, _cullFunctor{ cullFunctor } {} diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index b3efc4f1a8..633465dba3 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -24,13 +24,15 @@ struct Test { CullFunctor _functor; RenderArgs* _args; RenderDetails::Item& _renderDetails; + ViewFrustumPointer _antiFrustum; glm::vec3 _eyePos; float _squareTanAlpha; - Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails) : + Test(CullFunctor& functor, RenderArgs* pargs, RenderDetails::Item& renderDetails, ViewFrustumPointer antiFrustum = nullptr) : _functor(functor), _args(pargs), - _renderDetails(renderDetails) { + _renderDetails(renderDetails), + _antiFrustum(antiFrustum) { // FIXME: Keep this code here even though we don't use it yet /*_eyePos = _args->getViewFrustum().getPosition(); float a = glm::degrees(Octree::getPerspectiveAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); @@ -49,6 +51,15 @@ struct Test { return true; } + bool antiFrustumTest(const AABox& bound) { + assert(_antiFrustum); + if (_antiFrustum->boxInsideFrustum(bound)) { + _renderDetails._outOfView++; + return false; + } + return true; + } + bool solidAngleTest(const AABox& bound) { // FIXME: Keep this code here even though we don't use it yet //auto eyeToPoint = bound.calcCenter() - _eyePos; @@ -331,6 +342,7 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input const auto& inShapes = inputs.get0(); const auto& filter = inputs.get1(); + const auto& antiFrustum = inputs.get2(); auto& outShapes = outputs.edit0(); auto& outBounds = outputs.edit1(); @@ -339,7 +351,7 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input if (!filter.selectsNothing()) { auto& details = args->_details.edit(_detailType); - Test test(_cullFunctor, args, details); + Test test(_cullFunctor, args, details, antiFrustum); for (auto& inItems : inShapes) { auto key = inItems.first; @@ -351,13 +363,21 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input details._considered += (int)inItems.second.size(); - for (auto& item : inItems.second) { - if (test.frustumTest(item.bound) && test.solidAngleTest(item.bound)) { - outItems->second.emplace_back(item); - outBounds += item.bound; + if (antiFrustum == nullptr) { + for (auto& item : inItems.second) { + if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) { + outItems->second.emplace_back(item); + outBounds += item.bound; + } + } + } else { + for (auto& item : inItems.second) { + if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) { + outItems->second.emplace_back(item); + outBounds += item.bound; + } } } - details._rendered += (int)outItems->second.size(); } diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 53d46d11b4..a9695d6281 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -110,7 +110,7 @@ namespace render { class CullShapeBounds { public: - using Inputs = render::VaryingSet2; + using Inputs = render::VaryingSet3; using Outputs = render::VaryingSet2; using JobModel = Job::ModelIO; diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 0f98e8020c..2a2eebc0a7 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -280,6 +280,18 @@ bool ViewFrustum::boxIntersectsFrustum(const AABox& box) const { return true; } +bool ViewFrustum::boxInsideFrustum(const AABox& box) const { + // only check against frustum + for (int i = 0; i < NUM_FRUSTUM_PLANES; i++) { + const glm::vec3& normal = _planes[i].getNormal(); + // check distance to nearest box point + if (_planes[i].distance(box.getNearestVertex(normal)) < 0.0f) { + return false; + } + } + return true; +} + bool ViewFrustum::sphereIntersectsKeyhole(const glm::vec3& center, float radius) const { // check positive touch against central sphere if (glm::length(center - _position) <= (radius + _centerSphereRadius)) { diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index 859b5c49c5..981aabe70c 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -104,6 +104,7 @@ public: bool sphereIntersectsFrustum(const glm::vec3& center, float radius) const; bool cubeIntersectsFrustum(const AACube& box) const; bool boxIntersectsFrustum(const AABox& box) const; + bool boxInsideFrustum(const AABox& box) const; bool sphereIntersectsKeyhole(const glm::vec3& center, float radius) const; bool cubeIntersectsKeyhole(const AACube& cube) const; From e9747e9d85336baa56c12f664aa0dcda54ee2e6b Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 2 Feb 2018 15:06:22 +0100 Subject: [PATCH 09/12] Small optimisations in shadow shader --- libraries/render-utils/src/RenderShadowTask.cpp | 4 ++-- libraries/render-utils/src/RenderShadowTask.h | 8 ++++---- libraries/render-utils/src/Shadow.slh | 2 +- libraries/render-utils/src/ShadowCore.slh | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 829c0fbcf2..f3797edc11 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -380,8 +380,8 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon auto& cascadeFrustum = cascade.getFrustum(); args->pushViewFrustum(*cascadeFrustum); auto texelSize = glm::min(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x; - // Set the cull threshold to 16 shadow texels. - const auto minTexelCount = 16.0f; + // Set the cull threshold to 24 shadow texels. This is totally arbitrary + const auto minTexelCount = 24.0f; // TODO : maybe adapt that with LOD management system? texelSize *= minTexelCount; _cullFunctor._minSquareSize = texelSize * texelSize; diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 975f755a48..87d78ffe51 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -82,12 +82,12 @@ public: float constantBias0{ 0.15f }; float constantBias1{ 0.15f }; - float constantBias2{ 0.15f }; - float constantBias3{ 0.15f }; + float constantBias2{ 0.175f }; + float constantBias3{ 0.2f }; float slopeBias0{ 0.6f }; float slopeBias1{ 0.6f }; - float slopeBias2{ 0.6f }; - float slopeBias3{ 0.6f }; + float slopeBias2{ 0.7f }; + float slopeBias3{ 0.82f }; signals: void dirty(); diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index e87519b5f4..abb04a4498 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -89,7 +89,7 @@ float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) { ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition); - vec4 cascadeShadowCoords[2]; + vec4 cascadeShadowCoords[2] = { vec4(0), vec4(0) }; ivec2 cascadeIndices; float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index e49e8d638f..a787c54ca0 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -82,10 +82,10 @@ float evalShadowCascadeWeight(vec4 cascadeTexCoords) { float determineShadowCascadesOnPixel(vec4 worldPosition, float viewDepth, out vec4 cascadeShadowCoords[2], out ivec2 cascadeIndices) { cascadeIndices.x = getFirstShadowCascadeOnPixel(0, worldPosition, cascadeShadowCoords[0]); cascadeIndices.y = cascadeIndices.x+1; - if (cascadeIndices.x < (getShadowCascadeCount()-1)) { + float firstCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[0]); + if (firstCascadeWeight<1.0 && cascadeIndices.x < (getShadowCascadeCount()-1)) { cascadeIndices.y = getFirstShadowCascadeOnPixel(cascadeIndices.y, worldPosition, cascadeShadowCoords[1]); - float firstCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[0]); float secondCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[1]); // Returns the mix amount between first and second cascade. return ((1.0-firstCascadeWeight) * secondCascadeWeight) / (firstCascadeWeight + secondCascadeWeight); From 8a011036ef4f9be5b5412936a3f0cfe6a0de208b Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 2 Feb 2018 18:07:35 +0100 Subject: [PATCH 10/12] Removed warnings on mac and ubuntu --- libraries/render-utils/src/LightStage.cpp | 2 -- libraries/render/src/render/CullTask.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index 259d0dd665..854ff71e20 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -208,8 +208,6 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, // Position the keylight frustum auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection; - // Update the buffer - auto& schema = _schemaBuffer.edit(); for (auto& cascade : _cascades) { cascade._frustum->setOrientation(orientation); cascade._frustum->setPosition(position); diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 633465dba3..c745220ab8 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -390,7 +390,6 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input void FetchSpatialSelection::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems) { assert(renderContext->args); - RenderArgs* args = renderContext->args; auto& scene = renderContext->_scene; auto& inSelection = inputs.get0(); From 445ffbd82f27590c6035af1407be3b3d7ce3539a Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 5 Feb 2018 16:35:55 +0100 Subject: [PATCH 11/12] Added tag bits to shadow octree query --- libraries/render-utils/src/RenderShadowTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index e6bbc06510..33887675ab 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -218,7 +218,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto setupOutput = task.addJob("ShadowSetup"); const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene - static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); + static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); From 179aca2bf99f24340c60ef98a52cdc832f3e8091 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Tue, 6 Feb 2018 11:47:39 +0100 Subject: [PATCH 12/12] Shadow task now uses LOD culling from main view task --- interface/src/SecondaryCamera.cpp | 2 +- .../render-utils/src/RenderShadowTask.cpp | 18 ++++++++++--- libraries/render-utils/src/RenderShadowTask.h | 5 ++-- libraries/render-utils/src/RenderViewTask.cpp | 4 ++- libraries/render/src/render/CullTask.cpp | 26 +++++++++++++++++++ libraries/render/src/render/CullTask.h | 13 ++++++++++ 6 files changed, 60 insertions(+), 8 deletions(-) diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index ed16cc4321..45f1756eb7 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -19,7 +19,7 @@ using RenderArgsPointer = std::shared_ptr; void MainRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) { - task.addJob("RenderShadowTask", render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); + task.addJob("RenderShadowTask", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); const auto items = task.addJob("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); assert(items.canCast()); if (!isDeferred) { diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 33887675ab..e8963c2e4e 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -200,8 +200,8 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con }); } -void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t tagBits, uint8_t tagMask) { - ::CullFunctor cullFunctor = [this](const RenderArgs* args, const AABox& bounds) { +void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cameraCullFunctor, uint8_t tagBits, uint8_t tagMask) { + ::CullFunctor shadowCullFunctor = [this](const RenderArgs* args, const AABox& bounds) { return _cullFunctor(args, bounds); }; @@ -224,8 +224,14 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); const auto shadowItems = task.addJob("FetchShadowSelection", selectionInputs); + // Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not + // frustum culling or this will make shadow casters out of the camera frustum disappear. + const auto cameraFrustum = setupOutput.getN(2); + const auto applyFunctorInputs = ApplyCullFunctorOnItemBounds::Inputs(shadowItems, cameraFrustum).asVarying(); + const auto culledShadowItems = task.addJob("ShadowCullCamera", applyFunctorInputs, cameraCullFunctor); + // Sort - const auto sortedPipelines = task.addJob("PipelineSortShadow", shadowItems); + const auto sortedPipelines = task.addJob("PipelineSortShadow", culledShadowItems); const auto sortedShapes = task.addJob("DepthSortShadow", sortedPipelines, true); render::Varying cascadeFrustums[SHADOW_CASCADE_MAX_COUNT] = { @@ -248,7 +254,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende // CPU jobs: finer grained culling const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter, antiFrustum).asVarying(); - const auto culledShadowItemsAndBounds = task.addJob("CullShadowCascade", cullInputs, cullFunctor, RenderDetails::SHADOW); + const auto culledShadowItemsAndBounds = task.addJob("CullShadowCascade", cullInputs, shadowCullFunctor, RenderDetails::SHADOW); // GPU jobs: Render to shadow map sprintf(jobName, "RenderShadowMap%d", i); @@ -266,6 +272,7 @@ void RenderShadowTask::configure(const Config& configuration) { } RenderShadowSetup::RenderShadowSetup() : + _cameraFrustum{ std::make_shared() }, _coarseShadowFrustum{ std::make_shared() } { } @@ -297,6 +304,9 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O output.edit0() = args->_renderMode; output.edit1() = glm::ivec2(0, 0); + // Save main camera frustum + *_cameraFrustum = args->getViewFrustum(); + output.edit2() = _cameraFrustum; const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow) { diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 4ae43a7b6c..7f127a558c 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -50,7 +50,7 @@ public: using JobModel = render::Task::Model; RenderShadowTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cameraCullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00); void configure(const Config& configuration); @@ -95,7 +95,7 @@ signals: class RenderShadowSetup { public: - using Outputs = render::VaryingSet2; + using Outputs = render::VaryingSet3; using Config = RenderShadowSetupConfig; using JobModel = render::Job::ModelO; @@ -105,6 +105,7 @@ public: private: + ViewFrustumPointer _cameraFrustum; ViewFrustumPointer _coarseShadowFrustum; struct { float _constant; diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 93844b3a39..122fc16e61 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -17,7 +17,9 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred, uint8_t tagBits, uint8_t tagMask) { // auto items = input.get(); - task.addJob("RenderShadowTask", tagBits, tagMask); + // Warning : the cull functor passed to the shadow pass should only be testing for LOD culling. If frustum culling + // is performed, then casters not in the view frustum will be removed, which is not what we wish. + task.addJob("RenderShadowTask", cullFunctor, tagBits, tagMask); const auto items = task.addJob("FetchCullSort", cullFunctor, tagBits, tagMask); assert(items.canCast()); diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 3062966241..4dec3030ef 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -389,6 +389,32 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input } } +void ApplyCullFunctorOnItemBounds::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + RenderArgs* args = renderContext->args; + auto& inItems = inputs.get0(); + auto& outItems = outputs; + auto inputFrustum = inputs.get1(); + + if (inputFrustum != nullptr) { + args->pushViewFrustum(*inputFrustum); + } + + outItems.clear(); + outItems.reserve(inItems.size()); + + for (auto& item : inItems) { + if (_cullFunctor(args, item.bound)) { + outItems.emplace_back(item); + } + } + + if (inputFrustum != nullptr) { + args->popViewFrustum(); + } +} + void FetchSpatialSelection::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems) { assert(renderContext->args); diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index bb6a7e84f1..3c5a30de89 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -140,6 +140,19 @@ namespace render { void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; + class ApplyCullFunctorOnItemBounds { + public: + using Inputs = render::VaryingSet2; + using Outputs = ItemBounds; + using JobModel = Job::ModelIO; + + ApplyCullFunctorOnItemBounds(render::CullFunctor cullFunctor) : _cullFunctor(cullFunctor) {} + void run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + + private: + + render::CullFunctor _cullFunctor; + }; } #endif // hifi_render_CullTask_h; \ No newline at end of file