diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 97132a0fc7..9bdabbfb04 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -228,6 +228,11 @@ void AmbientOcclusionEffect::configure(const Config& config) { current.y = 1.0f / config.numSamples; } + if (config.fetchMipsEnabled != _parametersBuffer->isFetchMipsEnabled()) { + auto& current = _parametersBuffer->sampleInfo; + current.w = (float)config.fetchMipsEnabled; + } + if (!_framebuffer) { _framebuffer = std::make_shared(); } @@ -510,6 +515,11 @@ void DebugAmbientOcclusion::run(const render::SceneContextPointer& sceneContext, const auto linearDepthFramebuffer = inputs.get2(); const auto ambientOcclusionUniforms = inputs.get3(); + // Skip if AO is not started yet + if (!ambientOcclusionUniforms._buffer) { + return; + } + auto linearDepthTexture = linearDepthFramebuffer->getLinearDepthTexture(); auto normalTexture = deferredFramebuffer->getDeferredNormalTexture(); auto sourceViewport = args->_viewport; diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h index 5a0e7da587..7c2c465ba3 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.h +++ b/libraries/render-utils/src/AmbientOcclusionEffect.h @@ -62,8 +62,8 @@ class AmbientOcclusionEffectConfig : public render::Job::Config::Persistent { Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty) 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(float radius MEMBER radius WRITE setRadius) - Q_PROPERTY(float perspectiveScale MEMBER perspectiveScale WRITE setPerspectiveScale) Q_PROPERTY(float obscuranceLevel MEMBER obscuranceLevel WRITE setObscuranceLevel) Q_PROPERTY(float falloffBias MEMBER falloffBias WRITE setFalloffBias) Q_PROPERTY(float edgeSharpness MEMBER edgeSharpness WRITE setEdgeSharpness) @@ -80,7 +80,6 @@ public: const int MAX_BLUR_RADIUS = 6; void setRadius(float newRadius) { radius = std::max(0.01f, newRadius); emit dirty(); } - void setPerspectiveScale(float scale) { perspectiveScale = scale; emit dirty(); } void setObscuranceLevel(float level) { obscuranceLevel = std::max(0.01f, level); emit dirty(); } void setFalloffBias(float bias) { falloffBias = std::max(0.0f, std::min(bias, 0.2f)); emit dirty(); } void setEdgeSharpness(float sharpness) { edgeSharpness = std::max(0.0f, (float)sharpness); emit dirty(); } @@ -101,8 +100,9 @@ public: int numSamples{ 11 }; int resolutionLevel{ 0 }; int blurRadius{ 3 }; // 0 means no blurring - bool ditheringEnabled{ true }; // randomize the distribution of rays per pixel, should always be true + bool ditheringEnabled{ true }; // randomize the distribution of taps per pixel, should always be true bool borderingEnabled{ true }; // avoid evaluating information from non existing pixels out of the frame, should always be true + bool fetchMipsEnabled{ true }; // fetch taps in sub mips to otpimize cache, should always be true double gpuTime{ 0.0 }; signals: @@ -163,8 +163,11 @@ public: float getFalloffBias() const { return (float)ditheringInfo.z; } float getEdgeSharpness() const { return (float)blurInfo.x; } float getBlurDeviation() const { return blurInfo.z; } + float getNumSpiralTurns() const { return sampleInfo.z; } int getNumSamples() const { return (int)sampleInfo.x; } + bool isFetchMipsEnabled() const { return sampleInfo.w; } + int getBlurRadius() const { return (int)blurInfo.y; } bool isDitheringEnabled() const { return ditheringInfo.x; } bool isBorderingEnabled() const { return ditheringInfo.w; } diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index 6dc1cb2f9b..683fe69ff0 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -71,7 +71,7 @@ void LinearDepthFramebuffer::allocate() { // For Linear Depth: _linearDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); - // _linearDepthTexture->autoGenerateMips(1); + _linearDepthTexture->autoGenerateMips(1); _linearDepthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _linearDepthFramebuffer->setRenderBuffer(0, _linearDepthTexture); _linearDepthFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat()); diff --git a/libraries/render-utils/src/ssao.slh b/libraries/render-utils/src/ssao.slh index 3a3203e2e3..7efcd861c7 100644 --- a/libraries/render-utils/src/ssao.slh +++ b/libraries/render-utils/src/ssao.slh @@ -93,6 +93,10 @@ float getNumSpiralTurns() { return params._sampleInfo.z; } +int doFetchMips() { + return int(params._sampleInfo.w); +} + float getBlurEdgeSharpness() { return params._blurInfo.x; } @@ -128,6 +132,19 @@ float getBlurCoef(int c) { <@func declareSamplingDisk()@> +float getAngleDitheringWorldPos(in vec3 pixelWorldPos) { + vec3 worldPosFract = fract(pixelWorldPos * 0.2); + + ivec3 pixelPos = ivec3(worldPosFract * 256); + + return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering(); +} + +float getAngleDithering(in ivec2 pixelPos) { + // Hash function used in the AlchemyAO paper + return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering(); +} + float evalDiskRadius(float Zeye, vec2 imageSize) { // Choose the screen-space sample radius // proportional to the projected area of the sphere @@ -179,20 +196,28 @@ vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius, tapPos.y -= (imageSize.y - tapPos.y); redoTap = true; } - +/* + if ((tapPos.x < 0.5)) { + tapPos.x = 0.5; + redoTap = true; + } else if ((tapPos.x > imageSize.x - 0.5)) { + tapPos.x = imageSize.x - 0.5; + redoTap = true; + } + + if ((tapPos.y < 0.5)) { + tapPos.y = 0.5; + redoTap = true; + } else if ((tapPos.y > imageSize.y - 0.5)) { + tapPos.y = imageSize.y - 0.5; + redoTap = true; + } +*/ + if (redoTap) { tap.xy = tapPos - pixelPos; tap.z = length(tap.xy); } -/* - if ((tapPos.x < 0.0) || (tapPos.x >= imageSize.x)) { - // tapPos.x = pixelPos.x - tapVec.x; - tap.x = -tap.x; - } - if ((tapPos.y < 0.0) || (tapPos.y >= imageSize.y)) { - // tapPos.y = pixelPos.y - tapVec.y; - tap.y = -tap.y; - }*/ return tap; } @@ -202,8 +227,6 @@ vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius, <@func declareFetchDepthPyramidMap()@> -const int LOG_MAX_OFFSET = 3; -const int MAX_MIP_LEVEL = 5; // the depth pyramid texture uniform sampler2D pyramidMap; @@ -212,23 +235,34 @@ float getZEye(ivec2 pixel) { return -texelFetch(pyramidMap, pixel, getResolutionLevel()).x; } -vec3 getOffsetPosition(ivec3 side, ivec2 ssC, vec3 tap, vec2 imageSize) { - // Derivation: +const int LOG_MAX_OFFSET = 3; +const int MAX_MIP_LEVEL = 5; +int evalMipFromRadius(float radius) { // mipLevel = floor(log(ssR / MAX_OFFSET)); - int mipLevel = clamp(findMSB(int(tap.z)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL); + return doFetchMips() * clamp(findMSB(int(radius)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL); +} + +vec3 getOffsetPosition(ivec3 side, ivec2 ssC, vec3 tap, vec2 imageSize) { + int mipLevel = evalMipFromRadius(tap.z); ivec2 ssP = ivec2(tap.xy) + ssC; ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y); - vec3 P; - // We need to divide by 2^mipLevel to read the appropriately scaled coordinate from a MIP-map. // Manually clamp to the texture size because texelFetch bypasses the texture unit - ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), textureSize(pyramidMap, getResolutionLevel() + mipLevel) - ivec2(1)); - P.z = -texelFetch(pyramidMap, mipP, getResolutionLevel() + mipLevel).r; + // ivec2 mipSize = textureSize(pyramidMap, mipLevel); + // ivec2 mipSize = max(ivec2(imageSize) >> mipLevel, ivec2(1)); + + // ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), mipSize - ivec2(1)); + + vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize; + // vec2 tapUV = (vec2(mipP) + vec2(0.5)) / vec2(mipSize); + + vec3 P; + // P.z = -texelFetch(pyramidMap, mipP, mipLevel).r; + P.z = -textureLod(pyramidMap, tapUV, float(mipLevel)).r; // Offset to pixel center - vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize; P = evalEyePositionFromZeye(side.x, P.z, tapUV); return P; } diff --git a/libraries/render-utils/src/ssao_debugOcclusion.slf b/libraries/render-utils/src/ssao_debugOcclusion.slf index 7e6a792c3b..ecb1d112b9 100644 --- a/libraries/render-utils/src/ssao_debugOcclusion.slf +++ b/libraries/render-utils/src/ssao_debugOcclusion.slf @@ -16,6 +16,9 @@ <$declarePackOcclusionDepth()$> +<@include gpu/color.slh@> +<$declareColorWheel()$> + struct DebugParams{ vec4 pixelInfo; }; @@ -32,10 +35,6 @@ out vec4 outFragColor; uniform sampler2D normalMap; -float getAngleDithering(in ivec2 pixelPos) { - // Hash function used in the AlchemyAO paper - return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering(); -} float evalAO(in vec3 C, in vec3 n_C, vec3 Q) { vec3 v = Q - C; @@ -81,18 +80,23 @@ void main(void) { } // Let's make noise - float randomPatternRotationAngle = getAngleDithering(ssC); + // float randomPatternRotationAngle = getAngleDithering(ssC); + float randomPatternRotationAngle = getAngleDitheringWorldPos(Cp); + // Accumulate the Obscurance for each samples float sum = 0.0; - float keepTapRadius = 2.0; - bool keep = (dot(fragToCursor,fragToCursor) < keepTapRadius); + float keepTapRadius = 1.0; + int keepedMip = -1; + bool keep = false; + for (int i = 0; i < getNumSamples(); ++i) { vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, cursorPixelPos, imageSize); // The occluding point in camera space vec2 fragToTap = vec2(ssC) + tap.xy - gl_FragCoord.xy; if (dot(fragToTap,fragToTap) < keepTapRadius) { keep = true; + keepedMip = evalMipFromRadius(tap.z); } vec3 Q = getOffsetPosition(side.xyz, ssC, tap, imageSize); @@ -130,8 +134,14 @@ void main(void) { // outFragColor = vec4((Cn + vec3(1.0))* 0.5, 1.0); //outFragColor = vec4(vec3(ssDiskRadius / 100.0), 1.0); - + if ((dot(fragToCursor,fragToCursor) < (4.0 * keepTapRadius * keepTapRadius) )) { + outFragColor = vec4(vec3(A), 1.0); + return; + } + if (!keep) { outFragColor = vec4(0.1); + } else { + outFragColor.rgb = colorWheel(float(keepedMip)/float(MAX_MIP_LEVEL)); } } diff --git a/libraries/render-utils/src/ssao_makeOcclusion.slf b/libraries/render-utils/src/ssao_makeOcclusion.slf index 739dc1ddcb..19a6244a07 100644 --- a/libraries/render-utils/src/ssao_makeOcclusion.slf +++ b/libraries/render-utils/src/ssao_makeOcclusion.slf @@ -21,37 +21,6 @@ out vec4 outFragColor; uniform sampler2D normalMap; -float getAngleDithering(in ivec2 pixelPos) { - // Hash function used in the AlchemyAO paper - return isDitheringEnabled() * (3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10 + getFrameDithering(); -} -/* -vec3 getOffsetPosition(ivec3 side, ivec2 ssC, vec2 unitOffset, float ssR) { - // Derivation: - // mipLevel = floor(log(ssR / MAX_OFFSET)); - int mipLevel = clamp(findMSB(int(ssR)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL); - - ivec2 ssOffset = ivec2(ssR * unitOffset); - ivec2 ssP = ssOffset + ssC; - if (bool(isBorderingEnabled())) { - ssP.x = ((ssP.x < 0 || ssP.x >= side.z) ? ssC.x - ssOffset.x : ssP.x); - ssP.y = ((ssP.y < 0 || ssP.y >= int(getWidthHeight(getResolutionLevel()).y)) ? ssC.y - ssOffset.y : ssP.y); - } - - ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y); - - vec3 P; - - // We need to divide by 2^mipLevel to read the appropriately scaled coordinate from a MIP-map. - // Manually clamp to the texture size because texelFetch bypasses the texture unit - ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), textureSize(pyramidMap, getResolutionLevel() + mipLevel) - ivec2(1)); - P.z = -texelFetch(pyramidMap, mipP, getResolutionLevel() + mipLevel).r; - - // Offset to pixel center - vec2 tapUV = (vec2(ssP) + vec2(0.5)) / float(side.z); - P = evalEyePositionFromZeye(side.x, P.z, tapUV); - return P; -}*/ float evalAO(in vec3 C, in vec3 n_C, in vec3 Q) { vec3 v = Q - C; @@ -88,7 +57,8 @@ void main(void) { float ssDiskRadius = evalDiskRadius(Cp.z, imageSize); // Let's make noise - float randomPatternRotationAngle = getAngleDithering(ssC); + // float randomPatternRotationAngle = getAngleDithering(ssC); + float randomPatternRotationAngle = getAngleDitheringWorldPos(Cp); // Accumulate the Obscurance for each samples float sum = 0.0; diff --git a/scripts/developer/utilities/render/ambientOcclusionPass.qml b/scripts/developer/utilities/render/ambientOcclusionPass.qml index 527c8d8cd0..c4d86e544e 100644 --- a/scripts/developer/utilities/render/ambientOcclusionPass.qml +++ b/scripts/developer/utilities/render/ambientOcclusionPass.qml @@ -10,6 +10,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import "configSlider" +import "../lib/plotperf" Column { spacing: 8 @@ -35,11 +36,15 @@ Column { min: 0.0 } } + } + + Column { Repeater { model: [ "resolutionLevel:resolutionLevel", "ditheringEnabled:ditheringEnabled", "borderingEnabled:borderingEnabled", + "fetchMipsEnabled:fetchMipsEnabled", ] CheckBox { text: qsTr(modelData.split(":")[0]) @@ -48,5 +53,21 @@ Column { } } } + + PlotPerf { + title: "Timing" + height: 50 + object: Render.getConfig("AmbientOcclusion") + valueUnit: "ms" + valueScale: 1 + valueNumDigits: "4" + plots: [ + { + prop: "gpuTime", + label: "gpu", + color: "#FFFFFF" + } + ] + } } } diff --git a/scripts/developer/utilities/render/debugAmbientOcclusionPass.js b/scripts/developer/utilities/render/debugAmbientOcclusionPass.js index 680db81607..60fb8bf918 100644 --- a/scripts/developer/utilities/render/debugAmbientOcclusionPass.js +++ b/scripts/developer/utilities/render/debugAmbientOcclusionPass.js @@ -13,7 +13,7 @@ var qml = Script.resolvePath('ambientOcclusionPass.qml'); var window = new OverlayWindow({ title: 'Ambient Occlusion Pass', source: qml, - width: 400, height: 170, + width: 400, height: 200, }); window.setPosition(Window.innerWidth - 420, 50 + 550 + 50); window.closed.connect(function() { Script.stop(); });