From 4e8f323e47bab49484a0c5045d4f77fd1fa9354f Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 18 Feb 2020 18:07:38 -0800 Subject: [PATCH] Reduced AO processing time by 1/3 This is achieved by computing a small 2x2 box blur in the AO pass taking advantage of quad shading, which allows to halve the size of the bilateral blur kernel. Reducing the size of the blur kernel has a side effect to kick Adreno gpu into direct mode, which apparently is much faster here. Overall we go from 3.4ms to 2.4ms on Pixel4. The quality is impacted, but not severely. This probably assumes GPU that have working derivatives. --- .../filament-android/src/main/cpp/View.cpp | 2 +- filament/src/PostProcessManager.cpp | 13 +++++----- filament/src/materials/bilateralBlur.mat | 4 ++-- filament/src/materials/sao.mat | 24 ++++++++++++------- filament/tools/ssaogen.cpp | 5 ++-- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index aca5c18407..cfab8fabe6 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -239,8 +239,8 @@ Java_com_google_android_filament_View_nSetAmbientOcclusionOptions(JNIEnv*, jclas View* view = (View*) nativeView; View::AmbientOcclusionOptions options = { .radius = radius, - .bias = bias, .power = power, + .bias = bias, .resolution = resolution, .intensity = intensity }; diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 24bd28044b..719d39ec54 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -545,14 +545,13 @@ FrameGraphId PostProcessManager::ssao(FrameGraph& fg, RenderP FrameGraphId ssao = SSAOPass.getData().ssao; /* - * Final separable blur pass + * Final separable bilateral blur pass + * + * Since we apply a 2x2 box filter to the output of the AO -- effectively downsampling it + * (except at edges), we can widen the blur by skipping every other sample. */ - - // horizontal separable blur pass - ssao = bilateralBlurPass(fg, ssao, depth, { 1, 0 }); - - // vertical separable blur pass - ssao = bilateralBlurPass(fg, ssao, depth, { 0, 1 }); + ssao = bilateralBlurPass(fg, ssao, depth, { 2, 0 }); // horizontal pass + ssao = bilateralBlurPass(fg, ssao, depth, { 0, 2 }); // vertical pass return ssao; } diff --git a/filament/src/materials/bilateralBlur.mat b/filament/src/materials/bilateralBlur.mat index ed54aad640..4873421dc0 100644 --- a/filament/src/materials/bilateralBlur.mat +++ b/filament/src/materials/bilateralBlur.mat @@ -36,9 +36,9 @@ vertex { } fragment { - const int kGaussianCount = 8; + const int kGaussianCount = 4; const float kGaussianSamples[] = float[]( - 0.149603, 0.139446, 0.112927, 0.079454, 0.048569, 0.025795, 0.011902, 0.004772 + 0.199471, 0.176033, 0.120985, 0.064759 // stdev = 2.0 ); highp float linearizeDepth(highp float depth) { diff --git a/filament/src/materials/sao.mat b/filament/src/materials/sao.mat index a8a59ea344..95f42d8594 100644 --- a/filament/src/materials/sao.mat +++ b/filament/src/materials/sao.mat @@ -110,22 +110,20 @@ fragment { // compute normals using derivatives, which essentially results in half-resolution normals // this creates arifacts around geometry edges. - // Moreover, this seems slower (measured on Pixel4), which I find curious. - highp vec3 computeViewSpaceNormalNotNormalized(const highp vec3 position) { - highp vec3 dpdx = dFdx(position); - highp vec3 dpdy = dFdy(position); - return cross(dpdx, dpdy); + // Moreover, this seems a lot slower (measured on Pixel4), which I find curious. + highp vec3 computeViewSpaceNormal(const highp vec3 position) { + return normalize(cross(dFdx(position), dFdy(position))); } // compute normals directly from the depth texture, resulting in full resolution normals - highp vec3 computeViewSpaceNormalNotNormalized(const highp vec3 position, const vec2 uv) { + highp vec3 computeViewSpaceNormal(const highp vec3 position, const vec2 uv) { vec2 uvdx = uv + vec2(materialParams.resolution.z, 0.0); vec2 uvdy = uv + vec2(0.0, materialParams.resolution.w); highp vec3 px = computeViewSpacePositionFromDepth(uvdx, sampleDepthLinear(uvdx, 0.0)); highp vec3 py = computeViewSpacePositionFromDepth(uvdy, sampleDepthLinear(uvdy, 0.0)); highp vec3 dpdx = px - position; highp vec3 dpdy = py - position; - return cross(dpdx, dpdy); + return normalize(cross(dpdx, dpdy)); } // Ambient Occlusion, largely inspired from: @@ -167,9 +165,8 @@ fragment { highp float depth = sampleDepthLinear(uv, 0.0); highp vec3 origin = computeViewSpacePositionFromDepth(uv, depth); - highp vec3 normalNotNormalized = computeViewSpaceNormalNotNormalized(origin, uv); - vec3 normal = normalize(normalNotNormalized); + vec3 normal = computeViewSpaceNormal(origin, uv); float noise = random(gl_FragCoord.xy); // Choose the screen-space sample radius @@ -184,6 +181,15 @@ fragment { float ao = max(0.0, 1.0 - occlusion * (materialParams.intensity * (1.0 / kSpiralSampleCount))); ao = pow(ao, materialParams.power); + // Apply a 2x2 bilateral box filter, taking advantage of quad shading. If we were not + // applying the bilateral filter, this would be equivalent to downsampling the AO buffer + // using a 2x2 box. Thanks to the bilateral filter, we keep some sharp edges. + // mod2() below will return either 0.5 or 1.5 + // This assumes full derivatives are supported. + const float kEdgeDistance = 0.1; // this shouldn't be hardcoded + ivec2 coords = ivec2(gl_FragCoord.xy); + ao += (1.0 - step(kEdgeDistance, abs(dFdx(origin.z)))) * dFdx(ao) * (0.5 - float(coords.x & 1)); + ao += (1.0 - step(kEdgeDistance, abs(dFdy(origin.z)))) * dFdy(ao) * (0.5 - float(coords.y & 1)); postProcess.color.r = ao; } } diff --git a/filament/tools/ssaogen.cpp b/filament/tools/ssaogen.cpp index 74b822abdc..6cb9ff34b1 100644 --- a/filament/tools/ssaogen.cpp +++ b/filament/tools/ssaogen.cpp @@ -27,7 +27,7 @@ int main(int argc, char** argv) { std::default_random_engine generator; float weightSum = 0; - size_t gaussianWidth = 15; // must be odd + size_t gaussianWidth = 7; // must be odd const size_t gaussianSampleCount = (gaussianWidth + 1) / 2; std::cout << "const int kGaussianCount = " << gaussianSampleCount << ";" << std::endl; std::cout << "const int kRadius = kGaussianCount - 1;" << std::endl; @@ -46,7 +46,8 @@ int main(int argc, char** argv) { // Cut-off frequency definition: // fc = 1.1774 / (2pi * q) (half power frequency or 0.707 amplitude) - float q = (gaussianWidth + 1.0f) / 6.0f; // ~2.667 for 15 taps + //float q = (gaussianWidth + 1.0f) / 6.0f; // ~1.333 for 7 taps + float q = 2.0; // this works better, eventhough the filter is no longer really gaussian float g = float((1.0 / (std::sqrt(2.0 * F_PI) * q)) * std::exp(-(x * x) / (2.0 * q * q))); weightSum += g * (i == 0 ? 1.0f : 2.0f);