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);