diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7d783f0bc1..10600a1b2b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,7 @@ A new header is inserted each time a *tag* is created. ## Next release - Add missing JavaScript API for View::setVisibleLayers(). +- SSAO now has an optional high(er) quality upsampler. ## v1.7.0 diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index 5fc3e87844..c885f93a53 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -192,7 +192,7 @@ Java_com_google_android_filament_View_nGetAmbientOcclusion(JNIEnv*, jclass, jlon extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_View_nSetAmbientOcclusionOptions(JNIEnv*, jclass, jlong nativeView, jfloat radius, jfloat bias, jfloat power, jfloat resolution, jfloat intensity, - jint quality) { + jint quality, jint upsampling) { View* view = (View*) nativeView; View::AmbientOcclusionOptions options = { .radius = radius, @@ -200,7 +200,8 @@ Java_com_google_android_filament_View_nSetAmbientOcclusionOptions(JNIEnv*, jclas .bias = bias, .resolution = resolution, .intensity = intensity, - .quality = (View::QualityLevel)quality + .quality = (View::QualityLevel)quality, + .upsampling = (View::QualityLevel)upsampling }; view->setAmbientOcclusionOptions(options); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java index c50e8aced0..1de1d7fa78 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/View.java +++ b/android/filament-android/src/main/java/com/google/android/filament/View.java @@ -171,6 +171,14 @@ public class View { */ @NonNull public QualityLevel quality = QualityLevel.LOW; + + /** + * The upsampling setting controls the quality of the ambient occlusion buffer upsampling. + * The default is QualityLevel.LOW and uses bilinear filtering, a value of + * QualityLevel.HIGH or more enables a better bilateral filter. + */ + @NonNull + public QualityLevel upsampling = QualityLevel.LOW; } /** @@ -924,7 +932,7 @@ public class View { public void setAmbientOcclusionOptions(@NonNull AmbientOcclusionOptions options) { mAmbientOcclusionOptions = options; nSetAmbientOcclusionOptions(getNativeObject(), options.radius, options.bias, options.power, - options.resolution, options.intensity, options.quality.ordinal()); + options.resolution, options.intensity, options.quality.ordinal(), options.upsampling.ordinal()); } /** @@ -1059,7 +1067,7 @@ public class View { private static native boolean nIsFrontFaceWindingInverted(long nativeView); private static native void nSetAmbientOcclusion(long nativeView, int ordinal); private static native int nGetAmbientOcclusion(long nativeView); - private static native void nSetAmbientOcclusionOptions(long nativeView, float radius, float bias, float power, float resolution, float intensity, int quality); + private static native void nSetAmbientOcclusionOptions(long nativeView, float radius, float bias, float power, float resolution, float intensity, int quality, int upsampling); private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, float anamorphism, int levels, int blendMode, boolean threshold, boolean enabled); private static native void nSetFogOptions(long nativeView, float distance, float maximumOpacity, float height, float heightFalloff, float v, float v1, float v2, float density, float inScatteringStart, float inScatteringSize, boolean fogColorFromIbl, boolean enabled); private static native void nSetBlendMode(long nativeView, int blendMode); diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index ddc658e89f..a131704e42 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -204,6 +204,7 @@ public: float resolution = 0.5f;//!< How each dimension of the AO buffer is scaled. Must be either 0.5 or 1.0. float intensity = 1.0f; //!< Strength of the Ambient Occlusion effect. QualityLevel quality = QualityLevel::LOW; //!< affects # of samples used for AO. + QualityLevel upsampling = QualityLevel::LOW; //!< affects AO buffer upsampling quality. }; /** diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 8d42071bc8..3b14b04dcc 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -770,8 +770,15 @@ FrameGraphId PostProcessManager::screenSpaceAmbientOclusion( /* * Final separable bilateral blur pass */ - ssao = bilateralBlurPass(fg, ssao, { 1, 0 }, cameraInfo.zf, TextureFormat::RGB8); - ssao = bilateralBlurPass(fg, ssao, { 0, 1 }, cameraInfo.zf, TextureFormat::R8); + + const bool highQualitySampling = + options.upsampling >= View::QualityLevel::HIGH && options.resolution < 1.0f; + + ssao = bilateralBlurPass(fg, ssao, { 1, 0 }, cameraInfo.zf, + TextureFormat::RGB8); + + ssao = bilateralBlurPass(fg, ssao, { 0, 1 }, cameraInfo.zf, + highQualitySampling ? TextureFormat::RGB8 : TextureFormat::R8); fg.getBlackboard().put("ssao", ssao); return ssao; diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 8e2f0364e9..e4fd6df982 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -632,9 +632,18 @@ void FView::prepareViewport(const filament::Viewport &viewport) const noexcept { } void FView::prepareSSAO(Handle ssao) const noexcept { + // High quality sampling is enabled only if AO itself is enabled and upsampling quality is at + // least set to high and of course only if upsampling is needed. + const bool highQualitySampling = mAmbientOcclusionOptions.upsampling >= QualityLevel::HIGH + && mAmbientOcclusionOptions.resolution < 1.0f; + + // LINEAR filtering is only needed when AO is enabled and low-quality upsampling is used. mPerViewSb.setSampler(PerViewSib::SSAO, ssao, { - .filterMag = SamplerMagFilter::LINEAR + .filterMag = mAmbientOcclusion != AmbientOcclusion::NONE && !highQualitySampling ? + SamplerMagFilter::LINEAR : SamplerMagFilter::NEAREST }); + mPerViewUb.setUniform(offsetof(PerViewUib, aoSamplingQuality), + mAmbientOcclusion != AmbientOcclusion::NONE && highQualitySampling ? 1.0f : 0.0f); } void FView::prepareSSR(backend::Handle ssr, float refractionLodOffset) const noexcept { diff --git a/libs/filabridge/include/private/filament/UibGenerator.h b/libs/filabridge/include/private/filament/UibGenerator.h index 63345cf597..af8290759c 100644 --- a/libs/filabridge/include/private/filament/UibGenerator.h +++ b/libs/filabridge/include/private/filament/UibGenerator.h @@ -117,8 +117,13 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) // bit 8-11: cascade has visible shadows uint32_t cascades; + float aoSamplingQuality; // 0: bilinear, !0: bilateral + float aoReserved1; + float aoReserved2; + float aoReserved3; + // bring PerViewUib to 2 KiB - filament::math::float4 padding2[63]; + filament::math::float4 padding2[62]; }; // 2 KiB == 128 float4s diff --git a/libs/filabridge/src/UibGenerator.cpp b/libs/filabridge/src/UibGenerator.cpp index 2852b9e421..20e37ce0dd 100644 --- a/libs/filabridge/src/UibGenerator.cpp +++ b/libs/filabridge/src/UibGenerator.cpp @@ -99,8 +99,14 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { // CSM information .add("cascades", 1, UniformInterfaceBlock::Type::UINT) - // bring PerViewUib to 2 KiB - .add("padding2", 63, UniformInterfaceBlock::Type::FLOAT4) + // SSAO sampling parameters + .add("aoSamplingQuality", 1, UniformInterfaceBlock::Type::FLOAT) + .add("aoReserved1", 1, UniformInterfaceBlock::Type::FLOAT) + .add("aoReserved2", 1, UniformInterfaceBlock::Type::FLOAT) + .add("aoReserved3", 1, UniformInterfaceBlock::Type::FLOAT) + + // bring PerViewUib to 2 KiB + .add("padding2", 62, UniformInterfaceBlock::Type::FLOAT4) .build(); return uib; } diff --git a/libs/gltfio/include/gltfio/SimpleViewer.h b/libs/gltfio/include/gltfio/SimpleViewer.h index 455e6da546..28a6c72058 100644 --- a/libs/gltfio/include/gltfio/SimpleViewer.h +++ b/libs/gltfio/include/gltfio/SimpleViewer.h @@ -263,6 +263,7 @@ SimpleViewer::SimpleViewer(filament::Engine* engine, filament::Scene* scene, fil if (mEnableSunlight) { mScene->addEntity(mSunlight); } + view->setAmbientOcclusionOptions({ .upsampling = View::QualityLevel::HIGH }); } SimpleViewer::~SimpleViewer() { @@ -303,9 +304,9 @@ void SimpleViewer::populateScene(FilamentAsset* asset, bool scale) { void SimpleViewer::removeAsset() { if (mAsset) { - const auto begin = mAsset->getEntities(); - const auto end = begin + mAsset->getEntityCount(); - for (auto entity = begin; entity != end; ++entity) { + const auto *const begin = mAsset->getEntities(); + const auto *const end = begin + mAsset->getEntityCount(); + for (const auto *entity = begin; entity != end; ++entity) { mScene->remove(*entity); } } diff --git a/shaders/src/ambient_occlusion.fs b/shaders/src/ambient_occlusion.fs index 1be584df6d..b2724c2a99 100644 --- a/shaders/src/ambient_occlusion.fs +++ b/shaders/src/ambient_occlusion.fs @@ -11,11 +11,61 @@ // Ambient occlusion helpers //------------------------------------------------------------------------------ +float unpack(vec2 depth) { + // this is equivalent to (x8 * 256 + y8) / 65535, which gives a value between 0 and 1 + return (depth.x * (256.0 / 257.0) + depth.y * (1.0 / 257.0)); +} + float evaluateSSAO() { - // Note: Here we are essentially upscalling the SSAO buffer using a bilinear filter, - // but it would be better to apply a bilateral filter. It's a bit too expensive on mobile - // though. - return textureLod(light_ssao, uvToRenderTargetUV(getNormalizedViewportCoord().xy), 0.0).r; + highp vec2 uv = uvToRenderTargetUV(getNormalizedViewportCoord().xy); + + // Upscale the SSAO buffer in real-time, in high quality mode we use a custom bilinear + // filter. This adds about 2.0ms @ 250MHz on Pixel 4. + + if (frameUniforms.aoSamplingQuality > 0.0) { + highp vec2 size = vec2(textureSize(light_ssao, 0)); + + // Read four AO samples and their depths values +#if defined(TRGET_MOBILE) + // on mobile we can't use textureGather() because we're limited to ES3.0, + // so we emulate it with texelFetch(), on Pixel 4 this doesn't seem to have any + // significant impact on performance. + ivec2 i = ivec2(uv * size - 0.5); + vec3 s01 = texelFetch(light_ssao, i + ivec2(0, 1), 0).rgb; + vec3 s11 = texelFetch(light_ssao, i + ivec2(1, 1), 0).rgb; + vec3 s10 = texelFetch(light_ssao, i + ivec2(1, 0), 0).rgb; + vec3 s00 = texelFetch(light_ssao, i, 0).rgb; + vec4 ao = vec4(s01.r, s11.r, s10.r, s00.r); + vec4 dg = vec4(s01.g, s11.g, s10.g, s00.g); + vec4 db = vec4(s01.b, s11.b, s10.b, s00.b); +#else + vec4 ao = textureGather(light_ssao, uv, 0); // 01, 11, 10, 00 + vec4 dg = textureGather(light_ssao, uv, 1); // 01, 11, 10, 00 + vec4 db = textureGather(light_ssao, uv, 2); // 01, 11, 10, 00 +#endif + // bilinear weights + vec2 f = fract(uv * size - 0.5); + vec4 b; + b.x = (1.0 - f.x) * f.y; + b.y = f.x * f.y; + b.z = f.x * (1.0 - f.y); + b.w = (1.0 - f.x) * (1.0 - f.y); + + // bilateral weights + vec4 depths; + depths.x = unpack(vec2(dg.x, db.x)); + depths.y = unpack(vec2(dg.y, db.y)); + depths.z = unpack(vec2(dg.z, db.z)); + depths.w = unpack(vec2(dg.w, db.w)); + depths *= -frameUniforms.cameraFar; + float d = (getViewFromWorldMatrix() * vec4(getWorldPosition(), 1.0)).z; + const float oneOverEdgeDistance = 1.0 / 0.0625;// TODO: don't hardcode this + vec4 w = (vec4(d) - depths) * oneOverEdgeDistance; + w = max(vec4(MEDIUMP_FLT_MIN), 1.0 - w * w) * b; + return dot(ao, w) * (1.0 / (w.x + w.y + w.z + w.w)); + } else { + return textureLod(light_ssao, uv, 0.0).r; + } } float SpecularAO_Lagarde(float NoV, float visibility, float roughness) {