diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 54edfad3ec..8e82cf9a9c 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -209,7 +209,7 @@ AmbientOcclusionEffectConfig::AmbientOcclusionEffectConfig() : radius{ 0.5f }, perspectiveScale{ 1.0f }, obscuranceLevel{ 0.25f }, - falloffAngle{ 0.7f }, + falloffAngle{ 0.4f }, edgeSharpness{ 1.0f }, blurDeviation{ 2.5f }, numSpiralTurns{ 7.0f }, @@ -219,15 +219,16 @@ AmbientOcclusionEffectConfig::AmbientOcclusionEffectConfig() : ditheringEnabled{ true }, borderingEnabled{ true }, fetchMipsEnabled{ true }, - horizonBased{ true } { + horizonBased{ true }, + jitterEnabled{ true }{ } AmbientOcclusionEffect::AOParameters::AOParameters() { - _resolutionInfo = { -1.0f, 1.0f, 1.0f, 0.0f }; - _radiusInfo = { 0.5f, 0.5f * 0.5f, 1.0f / (0.25f * 0.25f * 0.25f), 1.0f }; - _ditheringInfo = { 0.0f, 0.0f, 0.01f, 1.0f }; - _sampleInfo = { 11.0f, 1.0f / 11.0f, 7.0f, 1.0f }; - _falloffInfo = { 0.5f, 2.0f, 0.866f, 1.1547f }; + _resolutionInfo = glm::vec4{ 0.0f }; + _radiusInfo = glm::vec4{ 0.0f }; + _ditheringInfo = glm::vec4{ 0.0f }; + _sampleInfo = glm::vec4{ 0.0f }; + _falloffInfo = glm::vec4{ 0.0f }; } AmbientOcclusionEffect::BlurParameters::BlurParameters() { @@ -242,6 +243,8 @@ void AmbientOcclusionEffect::configure(const Config& config) { bool shouldUpdateBlurs = false; + _isJitterEnabled = config.jitterEnabled; + const double RADIUS_POWER = 6.0; const auto& radius = config.radius; if (radius != _aoParametersBuffer->getRadius() || config.horizonBased != _aoParametersBuffer->isHorizonBased()) { @@ -321,6 +324,7 @@ void AmbientOcclusionEffect::configure(const Config& config) { } _randomSamples[i] = r * 2.0f * M_PI / config.numSamples; } + updateJitterSamples(); } if (config.fetchMipsEnabled != _aoParametersBuffer->isFetchMipsEnabled()) { @@ -472,6 +476,14 @@ int AmbientOcclusionEffect::getDepthResolutionLevel() const { return std::min(1, _aoParametersBuffer->getResolutionLevel()); } +void AmbientOcclusionEffect::updateJitterSamples() { + const int SSAO_RANDOM_SAMPLE_COUNT = int(_randomSamples.size() / (SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT)); + for (int splitId = 0; splitId < SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT; splitId++) { + auto& sample = _aoFrameParametersBuffer[splitId].edit(); + sample._angleInfo.x = _randomSamples[splitId + SSAO_RANDOM_SAMPLE_COUNT * _frameId]; + } +} + void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); @@ -534,12 +546,11 @@ void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderConte auto occlusionDepthSize = glm::ivec2(occlusionDepthTexture->getDimensions()); // Update sample rotation - const int SSAO_RANDOM_SAMPLE_COUNT = int(_randomSamples.size() / (SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT)); - for (int splitId=0 ; splitId < SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT ; splitId++) { - auto& sample = _aoFrameParametersBuffer[splitId].edit(); - sample._angleInfo.x = _randomSamples[splitId + SSAO_RANDOM_SAMPLE_COUNT * _frameId]; + if (_isJitterEnabled) { + const int SSAO_RANDOM_SAMPLE_COUNT = int(_randomSamples.size() / (SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT)); + updateJitterSamples(); + _frameId = (_frameId + 1) % SSAO_RANDOM_SAMPLE_COUNT; } - //_frameId = (_frameId + 1) % SSAO_RANDOM_SAMPLE_COUNT; gpu::doInBatch("AmbientOcclusionEffect::run", args->_context, [=](gpu::Batch& batch) { PROFILE_RANGE_BATCH(batch, "SSAO"); diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h index 8d5eef42a5..3fb58e3437 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.h +++ b/libraries/render-utils/src/AmbientOcclusionEffect.h @@ -83,6 +83,7 @@ class AmbientOcclusionEffectConfig : public render::GPUJobConfig::Persistent { Q_PROPERTY(bool ditheringEnabled MEMBER ditheringEnabled NOTIFY dirty) Q_PROPERTY(bool borderingEnabled MEMBER borderingEnabled NOTIFY dirty) Q_PROPERTY(bool fetchMipsEnabled MEMBER fetchMipsEnabled NOTIFY dirty) + Q_PROPERTY(bool jitterEnabled MEMBER jitterEnabled NOTIFY dirty) Q_PROPERTY(float radius MEMBER radius WRITE setRadius) Q_PROPERTY(float obscuranceLevel MEMBER obscuranceLevel WRITE setObscuranceLevel) Q_PROPERTY(float falloffAngle MEMBER falloffAngle WRITE setFalloffAngle) @@ -123,6 +124,7 @@ public: bool ditheringEnabled; // randomize the distribution of taps per pixel, should always be true bool borderingEnabled; // avoid evaluating information from non existing pixels out of the frame, should always be true bool fetchMipsEnabled; // fetch taps in sub mips to otpimize cache, should always be true + bool jitterEnabled; // Add small jittering to AO samples at each frame signals: void dirty(); @@ -181,6 +183,7 @@ private: void updateBlurParameters(); void updateFramebufferSizes(); + void updateJitterSamples(); int getDepthResolutionLevel() const; @@ -204,6 +207,7 @@ private: AmbientOcclusionFramebufferPointer _framebuffer; std::array _randomSamples; int _frameId{ 0 }; + bool _isJitterEnabled{ true }; gpu::RangeTimerPointer _gpuTimer; diff --git a/libraries/render-utils/src/ssao.slh b/libraries/render-utils/src/ssao.slh index a289654486..93d0fb95e7 100644 --- a/libraries/render-utils/src/ssao.slh +++ b/libraries/render-utils/src/ssao.slh @@ -390,12 +390,14 @@ float evalVisibilitySSAO(in vec3 centerPosition, in vec3 centerNormal, in vec3 t return f * f * f * max((vn - getFalloffCosAngle()) / (epsilon + vv), 0.0); } -#define HBAO_USE_COS_ANGLE 0 +#define HBAO_USE_COS_ANGLE 1 + +float computeWeightForHorizon(float horizonLimit, float distanceSquared) { + return max(0.0, 1.0 - distanceSquared / getRadius2()); +} float computeWeightedHorizon(float horizonLimit, float distanceSquared) { - float radiusFalloff = distanceSquared / getRadius2(); - - radiusFalloff = max(0.0, 1.0 - radiusFalloff); + float radiusFalloff = computeWeightForHorizon(horizonLimit, distanceSquared); #if !HBAO_USE_COS_ANGLE horizonLimit = getFalloffSinAngle() - horizonLimit; @@ -417,44 +419,46 @@ float computeWeightedHorizon(float horizonLimit, float distanceSquared) { vec3 tapPositionES = evalEyePositionFromZeye(side.x, tapMipZ.y, fragUVPos); vec3 deltaVec = tapPositionES - fragPositionES; float distanceSquared = dot(deltaVec, deltaVec); - float distance = sqrt(distanceSquared); float deltaDotNormal = dot(deltaVec, fragFrameES.normal); #if HBAO_USE_COS_ANGLE float tapHorizonLimit = deltaDotNormal; #else float tapHorizonLimit = dot(deltaVec, fragFrameES.tangent); #endif - float epsilon = 0.0001; - tapHorizonLimit /= (distance + epsilon); + tapHorizonLimit *= inversesqrt(distanceSquared); - if (distanceSquared < getRadius2() && deltaDotNormal>0.0 && + if (distanceSquared < getRadius2() && deltaDotNormal>0.0) { #if HBAO_USE_COS_ANGLE - tapHorizonLimit > horizonLimit -#else - tapHorizonLimit < horizonLimit -#endif - ) { - tapHorizonLimit = computeWeightedHorizon(tapHorizonLimit, distanceSquared); -#if HBAO_USE_COS_ANGLE - horizonLimit = max(horizonLimit, tapHorizonLimit); + float weight = computeWeightForHorizon(tapHorizonLimit, distanceSquared); + if (tapHorizonLimit > horizonLimit) { + occlusion += weight * (tapHorizonLimit - horizonLimit); + horizonLimit = tapHorizonLimit; + } else if (dot(deltaVec, fragFrameES.tangent) < 0.0) { + // This is a hack to try to handle the case where the occlusion angle is + // greater than 90° + occlusion = mix(occlusion, (occlusion+1.0) * 0.5, weight); + } #else - horizonLimit = min(horizonLimit, tapHorizonLimit); + if (tapHorizonLimit < horizonLimit) { + tapHorizonLimit = computeWeightedHorizon(tapHorizonLimit, distanceSquared); + horizonLimit = min(horizonLimit, tapHorizonLimit); + } #endif } <@endfunc@> #define HBAO_HORIZON_SEARCH_CONSTANT_STEP 0 -float computeHorizon(ivec4 side, vec2 fragUVPos, vec3 fragPositionES, TBNFrame fragFrameES, vec2 searchVec, float searchRadius, int stepCount) { - vec2 absSearchVec = abs(searchVec); +float computeOcclusion(ivec4 side, vec2 fragUVPos, vec3 fragPositionES, TBNFrame fragFrameES, vec2 searchDir, float searchRadius, int stepCount) { #if HBAO_USE_COS_ANGLE float horizonLimit = getFalloffCosAngle(); + float occlusion = 0.0; #else float horizonLimit = getFalloffSinAngle(); #endif if (stepCount>0) { - vec2 deltaTapUV = searchVec / float(stepCount); + vec2 deltaTapUV = searchDir / float(stepCount); float deltaRadius = searchRadius / float(stepCount); #if HBAO_HORIZON_SEARCH_CONSTANT_STEP @@ -487,31 +491,32 @@ float computeHorizon(ivec4 side, vec2 fragUVPos, vec3 fragPositionES, TBNFrame f #endif } - return horizonLimit; +#if HBAO_USE_COS_ANGLE + occlusion = min(occlusion * getFalloffCosAngleScale(), 1.0); +#else + occlusion = horizonLimit > 0.0 ? horizonLimit * getFalloffSinAngleScale() : horizonLimit; +#endif + + return occlusion; } float evalVisibilityHBAO(ivec4 side, vec2 fragUVPos, vec2 invSideImageSize, vec2 deltaTap, float diskPixelRadius, vec3 fragPositionES, TBNFrame fragFrameES) { vec2 pixelSearchVec = deltaTap * diskPixelRadius; - vec2 searchVec = pixelSearchVec * invSideImageSize; + vec2 searchDir = pixelSearchVec * invSideImageSize; float obscuranceH1 = 0.0; float obscuranceH2 = 0.0; - -#if !HBAO_USE_COS_ANGLE - fragFrameES.tangent = normalize(fragFrameES.tangent * deltaTap.x + fragFrameES.binormal * deltaTap.y); -#endif - pixelSearchVec = abs(pixelSearchVec); int stepCount = int(ceil(max(pixelSearchVec.x, pixelSearchVec.y))); + fragFrameES.tangent = normalize(fragFrameES.tangent * deltaTap.x + fragFrameES.binormal * deltaTap.y); + // Forward search for h1 - obscuranceH1 = computeHorizon(side, fragUVPos, fragPositionES, fragFrameES, searchVec, diskPixelRadius, stepCount); + obscuranceH1 = computeOcclusion(side, fragUVPos, fragPositionES, fragFrameES, searchDir, diskPixelRadius, stepCount); // Backward search for h2 -#if !HBAO_USE_COS_ANGLE fragFrameES.tangent = -fragFrameES.tangent; -#endif - obscuranceH2 = computeHorizon(side, fragUVPos, fragPositionES, fragFrameES, -searchVec, diskPixelRadius, stepCount); + obscuranceH2 = computeOcclusion(side, fragUVPos, fragPositionES, fragFrameES, -searchDir, diskPixelRadius, stepCount); return obscuranceH1 + obscuranceH2; } diff --git a/libraries/render-utils/src/ssao_makeOcclusion.slf b/libraries/render-utils/src/ssao_makeOcclusion.slf index 76357ffbb9..f0c406f446 100644 --- a/libraries/render-utils/src/ssao_makeOcclusion.slf +++ b/libraries/render-utils/src/ssao_makeOcclusion.slf @@ -60,9 +60,7 @@ void main(void) { fragFrameES.tangent = vec3(0.0); fragFrameES.binormal = vec3(0.0); fragFrameES.normal = fragNormalES; -#if !HBAO_USE_COS_ANGLE buildTangentBinormal(side, fragUVPos, fragPositionES, fragNormalES, deltaDepthUV, fragFrameES.tangent, fragFrameES.binormal); -#endif // Let's make noise float randomPatternRotationAngle = getAngleDithering(fragPixelPos); @@ -79,12 +77,10 @@ void main(void) { } obscuranceSum *= invNumSamples; #if HBAO_USE_COS_ANGLE - obscuranceSum *= 0.5 / PI; - obscuranceSum = 1.0 - obscuranceSum * obscuranceSum * getObscuranceScaling(); + obscuranceSum *= 0.5; + obscuranceSum = 1.0 - obscuranceSum * getObscuranceScaling(); #else obscuranceSum *= 0.5; - obscuranceSum += 1.0 - getFalloffSinAngle(); - obscuranceSum = mix(1.0, obscuranceSum, getObscuranceScaling()); #endif } else { diff --git a/scripts/developer/utilities/render/ambientOcclusionPass.qml b/scripts/developer/utilities/render/ambientOcclusionPass.qml index 2ea3a6c416..91e385edb8 100644 --- a/scripts/developer/utilities/render/ambientOcclusionPass.qml +++ b/scripts/developer/utilities/render/ambientOcclusionPass.qml @@ -59,6 +59,7 @@ Rectangle { Repeater { model: [ "horizonBased:horizonBased", + "jitterEnabled:jitterEnabled", "ditheringEnabled:ditheringEnabled", "fetchMipsEnabled:fetchMipsEnabled", "borderingEnabled:borderingEnabled"