From c0fe2d1c09df70da4a84330074ce9b86d95dc554 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 4 May 2021 17:21:01 -0700 Subject: [PATCH] new screen space lens flare effect - this PR also improves bloom slightly Note: for now this is not available on WebGL due to complications in the bloom pass. --- RELEASE_NOTES.md | 2 + .../filament-android/src/main/cpp/View.cpp | 16 +- .../com/google/android/filament/View.java | 54 +++++- filament/CMakeLists.txt | 2 +- filament/include/filament/View.h | 10 ++ filament/src/PostProcessManager.cpp | 105 +++++++++++- filament/src/PostProcessManager.h | 1 + .../src/materials/bloom/bloomDownsample.mat | 8 +- .../materials/colorGrading/colorGrading.mat | 146 +++++++++++----- filament/src/materials/flare/flare.mat | 162 ++++++++++++++++++ libs/viewer/src/Settings.cpp | 18 ++ libs/viewer/src/SimpleViewer.cpp | 1 + shaders/src/bloom.fs | 17 -- web/filament-js/jsbindings.cpp | 11 +- 14 files changed, 473 insertions(+), 80 deletions(-) create mode 100644 filament/src/materials/flare/flare.mat delete mode 100644 shaders/src/bloom.fs diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4c956e54ef..b2c5361474 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,6 +5,8 @@ A new header is inserted each time a *tag* is created. ## Next release (main branch) +- engine: new screen-space lens flare + ## v1.9.24 ## v1.9.23 diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index bdad7b0fae..e61ab4578b 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -253,7 +253,10 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_View_nSetBloomOptions(JNIEnv*, jclass, jlong nativeView, jlong nativeTexture, jfloat dirtStrength, jfloat strength, jint resolution, jfloat anamorphism, jint levels, - jint blendMode, jboolean threshold, jboolean enabled, jfloat highlight) { + jint blendMode, jboolean threshold, jboolean enabled, jfloat highlight, + jboolean lensFlare, jboolean starburst, jfloat chromaticAberration, jint ghostCount, + jfloat ghostSpacing, jfloat ghostThreshold, jfloat haloThickness, jfloat haloRadius, + jfloat haloThreshold) { View* view = (View*) nativeView; Texture* dirt = (Texture*) nativeTexture; View::BloomOptions options = { @@ -266,7 +269,16 @@ Java_com_google_android_filament_View_nSetBloomOptions(JNIEnv*, jclass, .blendMode = (View::BloomOptions::BlendMode)blendMode, .threshold = (bool)threshold, .enabled = (bool)enabled, - .highlight = highlight + .highlight = highlight, + .lensFlare = (bool)lensFlare, + .starburst = (bool)starburst, + .chromaticAberration = chromaticAberration, + .ghostCount = (uint8_t)ghostCount, + .ghostSpacing = ghostSpacing, + .ghostThreshold = ghostThreshold, + .haloThickness = haloThickness, + .haloRadius = haloRadius, + .haloThreshold = haloThreshold }; view->setBloomOptions(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 8affc43ca9..6ff68fb3e4 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 @@ -363,6 +363,52 @@ public class View { * minimum value is 10.0. */ public float highlight = 1000.0f; + + + /** + * enable screen-space lens flare + */ + public boolean lensFlare = false; + + /** + * enable starburst effect on lens flare + */ + public boolean starburst = true; + + /** + * amount of chromatic aberration + */ + public float chromaticAberration = 0.005f; + + /** + * number of flare "ghosts" + */ + public int ghostCount = 4; + + /** + * spacing of the ghost in screen units [0, 1[ + */ + public float ghostSpacing = 0.6f; + + /** + * hdr threshold for the ghosts + */ + public float ghostThreshold = 10.0f; + + /** + * thickness of halo in vertical screen units, 0 to disable + */ + public float haloThickness = 0.1f; + + /** + * radius of halo in vertical screen units [0, 0.5] + */ + public float haloRadius = 0.4f; + + /** + * hdr threshold for the halo + */ + public float haloThreshold = 10.0f; } /** @@ -1357,7 +1403,10 @@ public class View { nSetBloomOptions(getNativeObject(), options.dirt != null ? options.dirt.getNativeObject() : 0, options.dirtStrength, options.strength, options.resolution, options.anamorphism, options.levels, options.blendingMode.ordinal(), - options.threshold, options.enabled, options.highlight); + options.threshold, options.enabled, options.highlight, + options.lensFlare, options.starburst, options.chromaticAberration, + options.ghostCount, options.ghostSpacing, options.ghostThreshold, + options.haloThickness, options.haloRadius, options.haloThreshold); } /** @@ -1501,7 +1550,8 @@ public class View { 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, int lowPassFilter, int upsampling, boolean enabled, float minHorizonAngleRad); private static native void nSetSSCTOptions(long nativeView, float ssctLightConeRad, float ssctStartTraceDistance, float ssctContactDistanceMax, float ssctIntensity, float v, float v1, float v2, float ssctDepthBias, float ssctDepthSlopeBias, int ssctSampleCount, int ssctRayCount, boolean ssctEnabled); - 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, float highlight); + 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, float highlight, + boolean lensFlare, boolean starburst, float chromaticAberration, int ghostCount, float ghostSpacing, float ghostThreshold, float haloThickness, float haloRadius, float haloThreshold); 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); private static native void nSetDepthOfFieldOptions(long nativeView, float focusDistance, float cocScale, float maxApertureDiameter, boolean enabled, int filter, diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 908888ed78..09c77ebb74 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -170,6 +170,7 @@ set(MATERIAL_SRCS src/materials/dof/dofDilate.mat src/materials/dof/dofMipmap.mat src/materials/dof/dofMedian.mat + src/materials/flare/flare.mat src/materials/blitLow.mat src/materials/blitMedium.mat src/materials/blitHigh.mat @@ -255,7 +256,6 @@ add_custom_command( OUTPUT "${MATERIAL_DIR}/colorGrading.filamat" DEPENDS ../shaders/src/dithering.fs DEPENDS ../shaders/src/vignette.fs - DEPENDS ../shaders/src/bloom.fs APPEND ) diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index 59bee02ceb..5ac9a1fb05 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -159,6 +159,16 @@ public: bool threshold = true; //!< whether to threshold the source bool enabled = false; //!< enable or disable bloom float highlight = 1000.0f; //!< limit highlights to this value before bloom [10, +inf] + + bool lensFlare = false; //!< enable screen-space lens flare + bool starburst = true; //!< enable starburst effect on lens flare + float chromaticAberration = 0.005f; //!< amount of chromatic aberration + uint8_t ghostCount = 4; //!< number of flare "ghosts" + float ghostSpacing = 0.6f; //!< spacing of the ghost in screen units [0, 1[ + float ghostThreshold = 10.0f; //!< hdr threshold for the ghosts + float haloThickness = 0.1f; //!< thickness of halo in vertical screen units, 0 to disable + float haloRadius = 0.4f; //!< radius of halo in vertical screen units [0, 0.5] + float haloThreshold = 10.0f; //!< hdr threshold for the halo }; /** diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index aee1eab577..92bd4d2c6a 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -209,6 +209,7 @@ static const MaterialInfo sMaterialList[] = { { "separableGaussianBlur", MATERIAL(SEPARABLEGAUSSIANBLUR) }, { "bloomDownsample", MATERIAL(BLOOMDOWNSAMPLE) }, { "bloomUpsample", MATERIAL(BLOOMUPSAMPLE) }, + { "flare", MATERIAL(FLARE) }, { "blitLow", MATERIAL(BLITLOW) }, { "blitMedium", MATERIAL(BLITMEDIUM) }, { "blitHigh", MATERIAL(BLITHIGH) }, @@ -254,15 +255,25 @@ void PostProcessManager::init() noexcept { mDummyZeroTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT); + mStarburstTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1, + TextureFormat::R8, 1, 256, 1, 1, TextureUsage::DEFAULT); + PixelBufferDescriptor dataOne(driver.allocate(4), 4, PixelDataFormat::RGBA, PixelDataType::UBYTE); PixelBufferDescriptor dataOneArray(driver.allocate(4), 4, PixelDataFormat::RGBA, PixelDataType::UBYTE); PixelBufferDescriptor dataZero(driver.allocate(4), 4, PixelDataFormat::RGBA, PixelDataType::UBYTE); + PixelBufferDescriptor dataStarburst(driver.allocate(256), 256, PixelDataFormat::R, PixelDataType::UBYTE); *static_cast(dataOne.buffer) = 0xFFFFFFFF; *static_cast(dataOneArray.buffer) = 0xFFFFFFFF; *static_cast(dataZero.buffer) = 0; + std::generate_n((uint8_t*)dataStarburst.buffer, 256, + [&dist = mUniformDistribution, &gen = mEngine.getRandomEngine()]() { + float r = 0.5 + 0.5 * dist(gen); + return uint8_t(r * 255.0f); + }); driver.update2DImage(mDummyOneTexture, 0, 0, 0, 1, 1, std::move(dataOne)); driver.update3DImage(mDummyOneTextureArray, 0, 0, 0, 0, 1, 1, 1, std::move(dataOneArray)); driver.update2DImage(mDummyZeroTexture, 0, 0, 0, 1, 1, std::move(dataZero)); + driver.update2DImage(mStarburstTexture, 0, 0, 0, 256, 1, std::move(dataStarburst)); } void PostProcessManager::terminate(DriverApi& driver) noexcept { @@ -270,6 +281,7 @@ void PostProcessManager::terminate(DriverApi& driver) noexcept { driver.destroyTexture(mDummyOneTexture); driver.destroyTexture(mDummyOneTextureArray); driver.destroyTexture(mDummyZeroTexture); + driver.destroyTexture(mStarburstTexture); auto first = mMaterialRegistry.begin(); auto last = mMaterialRegistry.end(); while (first != last) { @@ -1343,6 +1355,7 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, FrameGraphId PostProcessManager::bloom(FrameGraph& fg, FrameGraphId input, TextureFormat outFormat, View::BloomOptions& bloomOptions, float2 scale) noexcept { + FrameGraphId bloom = bloomPass(fg, input, outFormat, bloomOptions, scale); @@ -1467,10 +1480,56 @@ FrameGraphId PostProcessManager::bloomPass(FrameGraph& fg, driver.setMinMaxLevels(hwOut, i, i); // safe because we're using LINEAR_MIPMAP_NEAREST } + driver.setMinMaxLevels(hwOut, 0, bloomOptions.levels - 1); }); input = bloomDownsamplePass->out; + // flare pass + auto& flarePass = fg.addPass("Flare", + [&](FrameGraph::Builder& builder, auto& data) { + data.in = builder.sample(input); + data.out = builder.createTexture("Flare Texture", { + .width = width / 2, + .height = height / 2, + .format = outFormat + }); + data.out = builder.declareRenderPass(data.out); + }, + [=](FrameGraphResources const& resources, + auto const& data, DriverApi& driver) { + auto in = resources.getTexture(data.in); + auto out = resources.getRenderPassInfo(0); + const float aspectRatio = float(width) / height; + + auto const& material = getPostProcessMaterial("flare"); + FMaterialInstance* mi = material.getMaterialInstance(); + + mi->setParameter("color", in, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST + }); + + mi->setParameter("level", 1.0f); // adjust with resolution + mi->setParameter("aspectRatio", + float2{ aspectRatio, 1.0f / aspectRatio }); + mi->setParameter("threshold", + float2{ bloomOptions.ghostThreshold, bloomOptions.haloThreshold }); + mi->setParameter("chromaticAberration", + bloomOptions.chromaticAberration); + mi->setParameter("ghostCount", (float)bloomOptions.ghostCount); + mi->setParameter("ghostSpacing", bloomOptions.ghostSpacing); + mi->setParameter("haloRadius", bloomOptions.haloRadius); + mi->setParameter("haloThickness", bloomOptions.haloThickness); + + commitAndRender(out, material, driver); + }); + + auto flare = gaussianBlurPass(fg, flarePass->out, 0, + {}, 0, false, 9); + + fg.getBlackboard().put("flare", flare); + // upsample phase auto& bloomUpsamplePass = fg.addPass("Bloom Upsample", [&](FrameGraph::Builder& builder, auto& data) { @@ -1521,6 +1580,7 @@ FrameGraphId PostProcessManager::bloomPass(FrameGraph& fg, driver.setMinMaxLevels(hwIn, 0, bloomOptions.levels - 1); }); + return bloomUpsamplePass->out; } else { // !isWebGL @@ -1756,11 +1816,13 @@ FrameGraphId PostProcessManager::colorGrading(FrameGraph& fg, Blackboard& blackboard = fg.getBlackboard(); FrameGraphId bloomDirt; - FrameGraphId bloomBlur = blackboard.get("bloom"); + FrameGraphId starburst; + FrameGraphId bloom = blackboard.get("bloom"); + FrameGraphId flare = blackboard.get("flare"); - float bloom = 0.0f; + float bloomStrength = 0.0f; if (bloomOptions.enabled) { - bloom = clamp(bloomOptions.strength, 0.0f, 1.0f); + bloomStrength = clamp(bloomOptions.strength, 0.0f, 1.0f); if (bloomOptions.dirt) { FTexture* fdirt = upcast(bloomOptions.dirt); FrameGraphTexture frameGraphTexture{ .handle = fdirt->getHwHandle() }; @@ -1770,13 +1832,22 @@ FrameGraphId PostProcessManager::colorGrading(FrameGraph& fg, .format = fdirt->getFormat() }, FrameGraphTexture::Usage::SAMPLEABLE, frameGraphTexture); } + + if (bloomOptions.lensFlare && bloomOptions.starburst) { + starburst = fg.import("starburst", { + .width = 256, .height = 1, .format = TextureFormat::R8 + }, FrameGraphTexture::Usage::SAMPLEABLE, + FrameGraphTexture{ .handle = mStarburstTexture }); + } } struct PostProcessColorGrading { FrameGraphId input; FrameGraphId output; FrameGraphId bloom; + FrameGraphId flare; FrameGraphId dirt; + FrameGraphId starburst; }; auto& ppColorGrading = fg.addPass("colorGrading", @@ -1790,12 +1861,16 @@ FrameGraphId PostProcessManager::colorGrading(FrameGraph& fg, }); data.output = builder.declareRenderPass(data.output); - if (bloomBlur) { - data.bloom = builder.sample(bloomBlur); + if (bloom) { + data.bloom = builder.sample(bloom); } if (bloomDirt) { data.dirt = builder.sample(bloomDirt); } + if (bloomOptions.lensFlare && flare) { + data.flare = builder.sample(flare); + data.starburst = builder.sample(starburst); + } }, [=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { Handle colorTexture = resources.getTexture(data.input); @@ -1803,9 +1878,15 @@ FrameGraphId PostProcessManager::colorGrading(FrameGraph& fg, Handle bloomTexture = data.bloom ? resources.getTexture(data.bloom) : getZeroTexture(); + Handle flareTexture = + data.flare ? resources.getTexture(data.flare) : getZeroTexture(); + Handle dirtTexture = data.dirt ? resources.getTexture(data.dirt) : getOneTexture(); + Handle starburstTexture = + data.starburst ? resources.getTexture(data.starburst) : getOneTexture(); + auto const& out = resources.getRenderPassInfo(); auto const& material = getPostProcessMaterial("colorGrading"); @@ -1819,17 +1900,27 @@ FrameGraphId PostProcessManager::colorGrading(FrameGraph& fg, .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR /* always read base level in shader */ }); + mi->setParameter("flareBuffer", flareTexture, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); mi->setParameter("dirtBuffer", dirtTexture, { .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR }); + mi->setParameter("starburstBuffer", starburstTexture, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR, + .wrapS = SamplerWrapMode::REPEAT, + .wrapT = SamplerWrapMode::REPEAT + }); // Bloom params float4 bloomParameters{ - bloom / float(bloomOptions.levels), + bloomStrength / float(bloomOptions.levels), 1.0f, (bloomOptions.enabled && bloomOptions.dirt) ? bloomOptions.dirtStrength : 0.0f, - 0.0f + bloomOptions.lensFlare ? bloomStrength : 0.0f }; if (bloomOptions.blendMode == View::BloomOptions::BlendMode::INTERPOLATE) { bloomParameters.y = 1.0f - bloomParameters.x; diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index ae492c2f8e..33946d0150 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -218,6 +218,7 @@ private: backend::Handle mDummyOneTexture; backend::Handle mDummyOneTextureArray; backend::Handle mDummyZeroTexture; + backend::Handle mStarburstTexture; size_t mSeparableGaussianBlurKernelStorageSize = 0; diff --git a/filament/src/materials/bloom/bloomDownsample.mat b/filament/src/materials/bloom/bloomDownsample.mat index 6a72f4ccd0..31f0f704df 100644 --- a/filament/src/materials/bloom/bloomDownsample.mat +++ b/filament/src/materials/bloom/bloomDownsample.mat @@ -43,9 +43,11 @@ fragment { void dummy(){} void threshold(inout vec3 c) { - float l = max3(c); - highp float f = l; - c *= step(1.0, l) / (1.0 + f * materialParams.invHighlight); + // threshold everything below 1.0 + c = max(vec3(0.0), c - 1.0); + // crush everything above 1 + highp float f = max3(c); + c *= 1.0 / (1.0 + f * materialParams.invHighlight); } vec3 box4x4(vec3 s0, vec3 s1, vec3 s2, vec3 s3) { diff --git a/filament/src/materials/colorGrading/colorGrading.mat b/filament/src/materials/colorGrading/colorGrading.mat index 252fe647bb..0a5ff00a78 100644 --- a/filament/src/materials/colorGrading/colorGrading.mat +++ b/filament/src/materials/colorGrading/colorGrading.mat @@ -16,11 +16,21 @@ material { name : bloomBuffer, precision: medium }, + { + type : sampler2d, + name : flareBuffer, + precision: medium + }, { type : sampler2d, name : dirtBuffer, precision: medium }, + { + type : sampler2d, + name : starburstBuffer, + precision: medium + }, { type : int, name : dithering @@ -65,66 +75,108 @@ fragment { #include "../../../../shaders/src/dithering.fs" #include "../../../../shaders/src/vignette.fs" -#include "../../../../shaders/src/bloom.fs" - vec3 colorGrade(mediump sampler3D lut, const vec3 x) { - // Alexa LogC EI 1000 - const float a = 5.555556; - const float b = 0.047996; - const float c = 0.244161 / log2(10.0); - const float d = 0.386036; - vec3 logc = c * log2(a * x + b) + d; - return textureLod(lut, logc, 0.0).rgb; +void dummy(){ } + +float starburst(const vec2 uv) { + // get an offset that continuously moves with the camera + vec3 forward = getViewFromWorldMatrix()[2].xyz; + float offset = forward.x + forward.y + forward.z; + + vec2 center = uv - vec2(0.5001); + float d = length(center); + float radial = acosFastPositive(center.x / d); + float mask = + textureLod(materialParams_starburstBuffer, fract(vec2(radial + offset * 1.0, 0.0)), 0.0).r * + textureLod(materialParams_starburstBuffer, fract(vec2(radial - offset * 0.5, 0.0)), 0.0).r ; + + return saturate(mask + (1.0 - smoothstep(0.0, 0.3, d))); +} + +vec3 bloom(const vec3 color) { + highp vec2 uv = variable_vertex.xy; + + vec3 result = vec3(0.0); + + if (materialParams.bloom.x > 0.0) { + vec3 bloom = textureLod(materialParams_bloomBuffer, uv, 0.0).rgb; + result += bloom * materialParams.bloom.x; } - vec3 resolveFragment(const ivec2 uv) { - return texelFetch(materialParams_colorBuffer, uv, 0).rgb; + if (materialParams.bloom.w > 0.0) { + float starburstMask = starburst(uv); + vec3 flare = textureLod(materialParams_flareBuffer, uv, 0.0).rgb; + result += flare * (materialParams.bloom.w * starburstMask); } - vec4 resolveAlphaFragment(const ivec2 uv) { - return texelFetch(materialParams_colorBuffer, uv, 0); + if (materialParams.bloom.z > 0.0) { + float dirtIntensity = materialParams.bloom.z; + vec3 dirt = textureLod(materialParams_dirtBuffer, uv, 0.0).rgb; + result *= dirt * dirtIntensity; } - vec4 resolve() { + result += color * materialParams.bloom.y; + return result; +} + +vec3 colorGrade(mediump sampler3D lut, const vec3 x) { + // Alexa LogC EI 1000 + const float a = 5.555556; + const float b = 0.047996; + const float c = 0.244161 / log2(10.0); + const float d = 0.386036; + vec3 logc = c * log2(a * x + b) + d; + return textureLod(lut, logc, 0.0).rgb; +} + +vec3 resolveFragment(const ivec2 uv) { + return texelFetch(materialParams_colorBuffer, uv, 0).rgb; +} + +vec4 resolveAlphaFragment(const ivec2 uv) { + return texelFetch(materialParams_colorBuffer, uv, 0); +} + +vec4 resolve() { #if POST_PROCESS_OPAQUE - vec4 color = vec4(resolveFragment(ivec2(getUV())), 1.0); - if (materialParams.bloom.x > 0.0) { - color.rgb = bloom(color.rgb); - } - if (materialParams.vignette.x < MEDIUMP_FLT_MAX) { - highp vec2 uv = getUV() * frameUniforms.resolution.zw; - color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor); - } - color.rgb = colorGrade(materialParams_lut, color.rgb); - if (materialParams.fxaa > 0) { - color.a = luminance(color.rgb); - } -#else - vec4 color = resolveAlphaFragment(ivec2(getUV())); - color.rgb /= color.a + FLT_EPS; - if (materialParams.bloom.x > 0.0) { - color.rgb = bloom(color.rgb); - } - if (materialParams.vignette.x < MEDIUMP_FLT_MAX) { - highp vec2 uv = getUV() * frameUniforms.resolution.zw; - color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor); - } - color.rgb = colorGrade(materialParams_lut, color.rgb); - color.rgb *= color.a + FLT_EPS; -#endif - return color; + vec4 color = vec4(resolveFragment(ivec2(getUV())), 1.0); + if (materialParams.bloom.x > 0.0) { + color.rgb = bloom(color.rgb); } + if (materialParams.vignette.x < MEDIUMP_FLT_MAX) { + highp vec2 uv = getUV() * frameUniforms.resolution.zw; + color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor); + } + color.rgb = colorGrade(materialParams_lut, color.rgb); + if (materialParams.fxaa > 0) { + color.a = luminance(color.rgb); + } +#else + vec4 color = resolveAlphaFragment(ivec2(getUV())); + color.rgb /= color.a + FLT_EPS; + if (materialParams.bloom.x > 0.0) { + color.rgb = bloom(color.rgb); + } + if (materialParams.vignette.x < MEDIUMP_FLT_MAX) { + highp vec2 uv = getUV() * frameUniforms.resolution.zw; + color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor); + } + color.rgb = colorGrade(materialParams_lut, color.rgb); + color.rgb *= color.a + FLT_EPS; +#endif + return color; +} - void postProcess(inout PostProcessInputs postProcess) { - postProcess.color = resolve(); - if (materialParams.dithering > 0) { - vec4 dithered = dither(postProcess.color, materialParams.temporalNoise); +void postProcess(inout PostProcessInputs postProcess) { + postProcess.color = resolve(); + if (materialParams.dithering > 0) { + vec4 dithered = dither(postProcess.color, materialParams.temporalNoise); #if POST_PROCESS_OPAQUE - postProcess.color.rgb = dithered.rgb; + postProcess.color.rgb = dithered.rgb; #else - postProcess.color = dithered; + postProcess.color = dithered; #endif - } } +} } diff --git a/filament/src/materials/flare/flare.mat b/filament/src/materials/flare/flare.mat new file mode 100644 index 0000000000..68ababa581 --- /dev/null +++ b/filament/src/materials/flare/flare.mat @@ -0,0 +1,162 @@ +material { + name : flare, + parameters : [ + { + type : sampler2d, + name : color, + precision: medium + }, + { + type : float, + name : level, + precision: medium + }, + { + type : float2, + name : aspectRatio, + precision: medium + }, + { + type : float2, + name : threshold, + precision: medium + }, + { + type : float, + name : chromaticAberration, + precision: medium + }, + { + type : float, + name : ghostCount, + precision: medium + }, + { + type : float, + name : ghostSpacing, + precision: medium + }, + { + type : float, + name : haloRadius, + precision: medium + }, + { + type : float, + name : haloThickness, + precision: medium + } + ], + variables : [ + vertex + ], + outputs : [ + { + name : color, + target : color, + type : float4 + } + ], + domain : postprocess, + depthWrite : false, + depthCulling : false +} + + +vertex { + +void dummy(){ } + +void postProcessVertex(inout PostProcessVertexInputs postProcess) { + postProcess.vertex.xy = postProcess.normalizedUV; +#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) + // On metal/vulkan postProcess.normalizedUV has its origin at the left-top, but we need a + // uniform coordinate space. So, we flip the y coordinate so we have bottom-left UV + // coordinates across all backends. + postProcess.vertex.y = 1.0 - postProcess.vertex.y; +#endif +} + +} + + +fragment { + +void dummy(){ } + +float smoothstep01(const float t) { + return t * t * (3.0 - 2.0 * t); +} + +float ring(float x, const float c, const float r) { + return 1.0 - smoothstep01(min(abs(x - c) / r, 1.0)); +} + +vec3 sampleColor(const vec2 uv) { + // because we work on reduced resolution, we assume mediump uv is enough. + float level = materialParams.level; + float chromaticAberration = materialParams.chromaticAberration; + // make sure to avoid divide-by-zero in normalize + vec2 offset = normalize(0.5001 - uv) * chromaticAberration; + return vec3( + textureLod(materialParams_color, uv + offset, level).r, + textureLod(materialParams_color, uv, level).g, + textureLod(materialParams_color, uv - offset, level).b + ); +} + +vec3 ghosts(const vec2 vertex_uv) { + // because we work on reduced resolution, we assume mediump uv is enough. + float ghostSpacing = materialParams.ghostSpacing; + float ghostCount = materialParams.ghostCount; + float threshold = materialParams.threshold.x; + vec2 uv = 1.0 - vertex_uv; + vec2 v = (0.5 - uv) * ghostSpacing; + vec3 color = vec3(0.0); + for (float i = 0.0; i < ghostCount; i += 1.0) { + vec2 suv = fract(uv + v * i); + vec3 s = sampleColor(suv); + s = clamp(s - threshold, 0.0, 1.0); + color += s; + } + return color; +} + +vec3 halo(const vec2 uv) { + float haloRadius = materialParams.haloRadius; + float haloThickness = materialParams.haloThickness; + float threshold = materialParams.threshold.y; + float2 aspectRatio = materialParams.aspectRatio; + + // make sure to avoid divide-by-zero in normalize + vec2 v = vec2(0.5001) - uv; + // handle aspect ratio + v.x *= aspectRatio.x; + v = normalize(v); + v.x *= aspectRatio.y; + + vec3 s = sampleColor(uv + v * haloRadius); + s = clamp(s - threshold, 0.0, 1.0); + + vec2 uvWeight = (uv - vec2(0.5, 0.0)) * vec2(aspectRatio.x, 1.0) + vec2(0.5, 0.0); + float d = distance(uvWeight, vec2(0.5)); + float weight = ring(d, haloRadius, haloThickness); + return s * weight; +} + +/* + * Screen space lens flare, greatly inspired from John Chapman's 2017 blog post + * https://john-chapman.github.io/2017/11/05/pseudo-lens-flare.html + */ +void postProcess(inout PostProcessInputs postProcess) { + // because we work on reduced resolution, we assume mediump uv is enough. + vec2 uv = variable_vertex.xy; + vec3 color = vec3(0.0); + if (materialParams.ghostCount > 0.0) { + color += ghosts(uv); + } + if (materialParams.haloThickness > 0.0) { + color += halo(uv); + } + postProcess.color = vec4(color, 1.0); +} diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index 6f3eff1154..e3b5707487 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -416,6 +416,24 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, BloomOpt i = parse(tokens, i + 1, jsonChunk, &out->enabled); } else if (compare(tok, jsonChunk, "highlight") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->highlight); + } else if (compare(tok, jsonChunk, "lensFlare") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->lensFlare); + } else if (compare(tok, jsonChunk, "starburst") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->starburst); + } else if (compare(tok, jsonChunk, "chromaticAberration") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->chromaticAberration); + } else if (compare(tok, jsonChunk, "ghostCount") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->ghostCount); + } else if (compare(tok, jsonChunk, "ghostSpacing") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->ghostSpacing); + } else if (compare(tok, jsonChunk, "ghostThreshold") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->ghostThreshold); + } else if (compare(tok, jsonChunk, "haloThickness") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->haloThickness); + } else if (compare(tok, jsonChunk, "haloRadius") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->haloRadius); + } else if (compare(tok, jsonChunk, "haloThreshold") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->haloThreshold); } else { slog.w << "Invalid bloom options key: '" << STR(tok, jsonChunk) << "'" << io::endl; i = parse(tokens, i + 1); diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index 692fa6f7b5..ec780138af 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -622,6 +622,7 @@ void SimpleViewer::updateUserInterface() { ImGui::Checkbox("SSAO", &mSettings.view.ssao.enabled); ImGui::Checkbox("Bloom", &mSettings.view.bloom.enabled); + ImGui::Checkbox("Flare", &mSettings.view.bloom.lensFlare); if (ImGui::CollapsingHeader("SSAO Options")) { auto& ssao = mSettings.view.ssao; diff --git a/shaders/src/bloom.fs b/shaders/src/bloom.fs deleted file mode 100644 index 22a49fa4af..0000000000 --- a/shaders/src/bloom.fs +++ /dev/null @@ -1,17 +0,0 @@ -//------------------------------------------------------------------------------ -// Bloom -//------------------------------------------------------------------------------ - -vec3 bloom(vec3 color) { - highp vec2 uv = variable_vertex.xy; - vec3 blurred = textureLod(materialParams_bloomBuffer, uv, 0.0).rgb; - color = blurred * materialParams.bloom.x + color * materialParams.bloom.y; - - if (materialParams.bloom.z > 0.0) { - float dirtIntensity = materialParams.bloom.z; - vec3 dirt = textureLod(materialParams_dirtBuffer, uv, 0.0).rgb; - color += blurred * dirt * dirtIntensity; - } - - return color; -} diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index e7694c7f16..3da3f3f4f7 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -388,7 +388,16 @@ value_object("View$BloomOptions") .field("threshold", &filament::View::BloomOptions::threshold) .field("enabled", &filament::View::BloomOptions::enabled) .field("blendMode", &filament::View::BloomOptions::blendMode) - .field("highlight", &filament::View::BloomOptions::highlight); + .field("highlight", &filament::View::BloomOptions::highlight) + .field("lensFlare", &filament::View::BloomOptions::lensFlare) + .field("starburst", &filament::View::BloomOptions::starburst) + .field("chromaticAberration", &filament::View::BloomOptions::chromaticAberration) + .field("ghostCount", &filament::View::BloomOptions::ghostCount) + .field("ghostSpacing", &filament::View::BloomOptions::ghostSpacing) + .field("ghostThreshold", &filament::View::BloomOptions::ghostThreshold) + .field("haloThickness", &filament::View::BloomOptions::haloThickness) + .field("haloRadius", &filament::View::BloomOptions::haloRadius) + .field("haloThreshold", &filament::View::BloomOptions::haloThreshold); // TODO: add support for dirt texture in BloomOptions. // Note that simply including the field in the above list causes binding errors for nullptr.