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 5d37d0ae804..f1338796eb1 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 @@ -1418,6 +1418,17 @@ public enum BlendMode { * limit highlights to this value before bloom [10, +inf] */ public float highlight = 1000.0f; + /** + * Bloom quality level. + * LOW (default): use a more optimized down-sampling filter, however there can be artifacts + * with dynamic resolution, this can be alleviated by using the homogenous mode. + * MEDIUM: Good balance between quality and performance. + * HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts. + * This mode can be significantly slower on mobile, especially at high resolution. + * This mode greatly improves the anamorphic bloom. + */ + @NonNull + public QualityLevel quality = QualityLevel.LOW; /** * enable screen-space lens flare */ diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index ea2543eadc5..e417eb06dc8 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -230,6 +230,8 @@ set(MATERIAL_SRCS src/materials/flare/flare.mat src/materials/blitLow.mat src/materials/bloom/bloomDownsample.mat + src/materials/bloom/bloomDownsample2x.mat + src/materials/bloom/bloomDownsample9.mat src/materials/bloom/bloomUpsample.mat src/materials/ssao/bilateralBlur.mat src/materials/ssao/bilateralBlurBentNormals.mat diff --git a/filament/include/filament/Options.h b/filament/include/filament/Options.h index f62f2e1631a..d5fac6b3e1b 100644 --- a/filament/include/filament/Options.h +++ b/filament/include/filament/Options.h @@ -141,6 +141,17 @@ struct BloomOptions { bool enabled = false; //!< enable or disable bloom float highlight = 1000.0f; //!< limit highlights to this value before bloom [10, +inf] + /** + * Bloom quality level. + * LOW (default): use a more optimized down-sampling filter, however there can be artifacts + * with dynamic resolution, this can be alleviated by using the homogenous mode. + * MEDIUM: Good balance between quality and performance. + * HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts. + * This mode can be significantly slower on mobile, especially at high resolution. + * This mode greatly improves the anamorphic bloom. + */ + QualityLevel quality = QualityLevel::LOW; + 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 diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index a7880f414b6..4bfb70e2eaf 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -212,6 +212,8 @@ static const PostProcessManager::MaterialInfo sMaterialList[] = { { "bilateralBlurBentNormals", MATERIAL(BILATERALBLURBENTNORMALS) }, { "blitLow", MATERIAL(BLITLOW) }, { "bloomDownsample", MATERIAL(BLOOMDOWNSAMPLE) }, + { "bloomDownsample2x", MATERIAL(BLOOMDOWNSAMPLE2X) }, + { "bloomDownsample9", MATERIAL(BLOOMDOWNSAMPLE9) }, { "bloomUpsample", MATERIAL(BLOOMUPSAMPLE) }, { "colorGrading", MATERIAL(COLORGRADING) }, { "colorGradingAsSubpass", MATERIAL(COLORGRADINGASSUBPASS) }, @@ -450,7 +452,6 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph for (size_t level = 0; level < levelCount - 1; level++) { auto out = resources.getRenderPassInfo(level); driver.setMinMaxLevels(in, level, level); - mi->setParameter("level", uint32_t(level)); commitAndRender(out, material, driver); } driver.setMinMaxLevels(in, 0, levelCount - 1); @@ -1596,8 +1597,7 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, auto const& out = resources.getRenderPassInfo(data.rp[level]); driver.setMinMaxLevels(inOutColor, level, level); driver.setMinMaxLevels(inOutCoc, level, level); - mi->setParameter("mip", uint32_t(level)); - mi->setParameter("weightScale", 0.5f / float(1u<setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres? mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h }); mi->commit(driver); render(out, pipeline, driver); @@ -1870,15 +1870,53 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, return bloomPass(fg, input, outFormat, inoutBloomOptions, scale); } +FrameGraphId PostProcessManager::downscalePass(FrameGraph& fg, + FrameGraphId input, + FrameGraphTexture::Descriptor const& outDesc, + bool threshold, float highlight, bool fireflies) noexcept { + struct DownsampleData { + FrameGraphId input; + FrameGraphId output; + }; + auto& downsamplePass = fg.addPass("Downsample", + [&](FrameGraph::Builder& builder, auto& data) { + data.input = builder.sample(input); + data.output = builder.createTexture("Downsample-output", outDesc); + builder.declareRenderPass(data.output); + }, + [=](FrameGraphResources const& resources, + auto const& data, DriverApi& driver) { + auto const& material = getPostProcessMaterial("bloomDownsample2x"); + auto* mi = material.getMaterialInstance(mEngine); + mi->setParameter("source", resources.getTexture(data.input), { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); + mi->setParameter("level", 0); + mi->setParameter("threshold", threshold ? 1.0f : 0.0f); + mi->setParameter("fireflies", fireflies ? 1.0f : 0.0f); + mi->setParameter("invHighlight", std::isinf(highlight) ? 0.0f : 1.0f / highlight); + commitAndRender(resources.getRenderPassInfo(), material, driver); + }); + return downsamplePass->output; +} + PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg, FrameGraphId input, TextureFormat outFormat, BloomOptions& inoutBloomOptions, float2 scale) noexcept { - // Figure out a good size for the bloom buffer. - auto const& desc = fg.getDescriptor(input); + + // Figure out a good size for the bloom buffer. We must use a fixed bloom buffer size so + // that the size/strength of the bloom doesn't vary much with the resolution, otherwise + // dynamic resolution would affect the bloom effect too much. + auto desc = fg.getDescriptor(input); // width and height after dynamic resolution upscaling const float aspect = (float(desc.width) * scale.y) / (float(desc.height) * scale.x); + // FIXME: don't allow inoutBloomOptions.resolution to be larger than input's resolution + // (avoid upscale) but how does this affect dynamic resolution + // FIXME: check what happens on WebGL and intel's processors + // compute the desired bloom buffer size float bloomHeight = float(inoutBloomOptions.resolution); float bloomWidth = bloomHeight * aspect; @@ -1893,38 +1931,73 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg bloomHeight *= inoutBloomOptions.anamorphism; } - // convert back to integer width/height - const uint32_t width = std::max(1u, uint32_t(std::floor(bloomWidth))); - const uint32_t height = std::max(1u, uint32_t(std::floor(bloomHeight))); - // we might need to adjust the max # of levels const uint32_t major = uint32_t(std::max(bloomWidth, bloomHeight)); const uint8_t maxLevels = FTexture::maxLevelCount(major); inoutBloomOptions.levels = std::min(inoutBloomOptions.levels, maxLevels); inoutBloomOptions.levels = std::min(inoutBloomOptions.levels, kMaxBloomLevels); - if (2 * width < desc.width || 2 * height < desc.height) { - // if we're scaling down by more than 2x, prescale the image with a blit to improve - // performance. This is important on mobile/tilers. - input = opaqueBlit(fg, input, { 0, 0, desc.width, desc.height }, { - .width = std::max(1u, desc.width / 2), - .height = std::max(1u, desc.height / 2), - .format = outFormat - }); + if (inoutBloomOptions.quality == QualityLevel::LOW) { + // In low quality mode, we adjust the bloom buffer size so that both dimensions + // have enough exact mip levels. This can slightly affect the aspect ratio causing + // some artifacts: + // - add some anamorphism (experimentally not visible) + // - visible bloom size changes with dynamic resolution in non-homogenous mode + // This allows us to use the 9 sample downsampling filter (instead of 13) + // for at least 4 levels. + uint32_t width = std::max(1u, uint32_t(std::floor(bloomWidth))); + uint32_t height = std::max(1u, uint32_t(std::floor(bloomHeight))); + width &= ~((1 << 4) - 1); // at least 4 levels + height &= ~((1 << 4) - 1); + bloomWidth = float(width); + bloomHeight = float(height); + } + + bool threshold = inoutBloomOptions.threshold; + + while (2 * bloomWidth < float(desc.width) || 2 * bloomHeight < float(desc.height)) { + if (inoutBloomOptions.quality == QualityLevel::LOW || + inoutBloomOptions.quality == QualityLevel::MEDIUM) { + input = downscalePass(fg, input, { + .width = (desc.width = std::max(1u, desc.width / 2)), + .height = (desc.height = std::max(1u, desc.height / 2)), + .format = outFormat + }, + threshold, inoutBloomOptions.highlight, threshold); + threshold = false; // we do the thresholding only once during down sampling + } else if (inoutBloomOptions.quality == QualityLevel::HIGH || + inoutBloomOptions.quality == QualityLevel::ULTRA) { + // In high quality mode, we increase the size of the bloom buffer such that the + // first scaling is less than 2x, and we increase the number of levels accordingly. + if (bloomWidth * 2.0f > 2048.0f || bloomHeight * 2.0f > 2048.0f) { + // but we can't scale above the h/w guaranteed minspec + break; + } + bloomWidth *= 2.0f; + bloomHeight *= 2.0f; + inoutBloomOptions.levels++; + } } + // convert back to integer width/height + uint32_t const width = std::max(1u, uint32_t(std::floor(bloomWidth))); + uint32_t const height = std::max(1u, uint32_t(std::floor(bloomHeight))); + + input = downscalePass(fg, input, + { .width = width, .height = height, .format = outFormat }, + threshold, inoutBloomOptions.highlight, threshold); + struct BloomPassData { - FrameGraphId in; FrameGraphId out; - FrameGraphId stage; uint32_t outRT[kMaxBloomLevels]; - uint32_t stageRT[kMaxBloomLevels]; }; - // downsample phase + // Creating a mip-chain poses a "feedback" loop problem on some GPU. We will disable + // Bloom on these. + // See: https://github.com/google/filament/issues/2338 + auto& bloomDownsamplePass = fg.addPass("Bloom Downsample", [&](FrameGraph::Builder& builder, auto& data) { - data.in = builder.sample(input); data.out = builder.createTexture("Bloom Out Texture", { .width = width, .height = height, @@ -1933,165 +2006,107 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg }); data.out = builder.sample(data.out); - data.stage = builder.createTexture("Bloom Stage Texture", { - .width = width, - .height = height, - .levels = inoutBloomOptions.levels, - .format = outFormat - }); - data.stage = builder.sample(data.stage); - for (size_t i = 0; i < inoutBloomOptions.levels; i++) { auto out = builder.createSubresource(data.out, "Bloom Out Texture mip", { .level = uint8_t(i) }); - auto stage = builder.createSubresource(data.stage, - "Bloom Stage Texture mip", { .level = uint8_t(i) }); + if (i == 0) { + // this causes the last blit above to render into this mip + fg.forwardResource(out, input); + } builder.declareRenderPass(out, &data.outRT[i]); - builder.declareRenderPass(stage, &data.stageRT[i]); } }, [=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { - auto hwIn = resources.getTexture(data.in); + // TODO: if downsampling is not exactly a multiple of two, use the 13 samples + // filter. This is generally the accepted solution, however, the 13 samples + // filter is not correct either when we don't sample at integer coordinates, + // but it seems ot create less artifacts. + // A better solution might be to use the filter described in + // Castaño, 2013, "Shadow Mapping Summary Part 1", which is 5x5 filter with + // 9 samples, but works at all coordinates. + auto hwOut = resources.getTexture(data.out); - auto hwStage = resources.getTexture(data.stage); - auto const& material = getPostProcessMaterial("bloomDownsample"); - auto const* ma = material.getMaterial(mEngine); + auto const& material9 = getPostProcessMaterial("bloomDownsample9"); + auto const& material13 = getPostProcessMaterial("bloomDownsample"); - FMaterialInstance* mis[] = { - ma->createInstance("bloomDownsample-ping"), - ma->createInstance("bloomDownsample-pong"), - ma->createInstance("bloomDownsample-first"), - }; + auto* mi9 = material9.getMaterialInstance(mEngine); + auto* mi13 = material13.getMaterialInstance(mEngine); - mis[0]->setParameter("source", hwOut, { + mi9->setParameter("source", hwOut, { .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST - }); + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST }); - mis[1]->setParameter("source", hwStage, { + mi13->setParameter("source", hwOut, { .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST - }); - mis[2]->setParameter("source", hwIn, { - .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST - }); + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST }); - for (auto* mi : mis) { - mi->setParameter("level", 0.0f); - mi->setParameter("threshold", inoutBloomOptions.threshold ? 1.0f : 0.0f); - mi->setParameter("invHighlight", std::isinf(inoutBloomOptions.highlight) - ? 0.0f : 1.0f / inoutBloomOptions.highlight); - mi->commit(driver); - } - - const PipelineState pipeline(material.getPipelineState(mEngine)); + mi9->commit(driver); + mi13->commit(driver); - { // first iteration - auto hwDstRT = resources.getRenderPassInfo(data.outRT[0]); - hwDstRT.params.flags.discardStart = TargetBufferFlags::COLOR; - hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE; - mis[2]->use(driver); - render(hwDstRT, pipeline, driver); - } + // PipelineState for both materials should be the same + const PipelineState pipeline(material9.getPipelineState(mEngine)); for (size_t i = 1; i < inoutBloomOptions.levels; i++) { - const size_t parity = 1u - (i & 0x1u); - auto hwDstRT = resources.getRenderPassInfo(parity ? data.outRT[i] : data.stageRT[i]); + auto hwDstRT = resources.getRenderPassInfo(data.outRT[i]); hwDstRT.params.flags.discardStart = TargetBufferFlags::COLOR; hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE; - mis[parity]->setParameter("level", float(i - 1)); - mis[parity]->commit(driver); - mis[parity]->use(driver); - render(hwDstRT, pipeline, driver); - } - for (auto& mi : mis) { - mEngine.destroy(mi); + // if downsampling is a multiple of 2 in each dimension we can use the + // 9 samples filter. + auto vp = resources.getRenderPassInfo(data.outRT[i-1]).params.viewport; + auto* const mi = (vp.width & 1 || vp.height & 1) ? mi13 : mi9; + mi->use(driver); + driver.setMinMaxLevels(hwOut, i - 1, i - 1); // this offsets baseLevel to i-1 + render(hwDstRT, pipeline, driver); } + driver.setMinMaxLevels(hwOut, 0, inoutBloomOptions.levels - 1); }); - FrameGraphId output = bloomDownsamplePass->out; - FrameGraphId stage = bloomDownsamplePass->stage; + // output of bloom downsample pass becomes input of next (flare) pass + input = bloomDownsamplePass->out; // flare pass - auto flare = flarePass(fg, bloomDownsamplePass->out, width, height, outFormat, inoutBloomOptions); + auto flare = flarePass(fg, input, width, height, outFormat, inoutBloomOptions); - // upsample phase auto& bloomUpsamplePass = fg.addPass("Bloom Upsample", [&](FrameGraph::Builder& builder, auto& data) { - data.out = builder.sample(output); - data.stage = builder.sample(stage); + data.out = builder.sample(input); for (size_t i = 0; i < inoutBloomOptions.levels; i++) { auto out = builder.createSubresource(data.out, "Bloom Out Texture mip", { .level = uint8_t(i) }); - auto staging = builder.createSubresource(data.stage, - "Bloom Stage Texture mip", { .level = uint8_t(i) }); builder.declareRenderPass(out, &data.outRT[i]); - builder.declareRenderPass(staging, &data.stageRT[i]); } }, [=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { - auto hwOut = resources.getTexture(data.out); - auto hwStage = resources.getTexture(data.stage); auto const& outDesc = resources.getDescriptor(data.out); auto const& material = getPostProcessMaterial("bloomUpsample"); - auto const* ma = material.getMaterial(mEngine); - - FMaterialInstance* mis[] = { - ma->createInstance("bloomUpsample-ping"), - ma->createInstance("bloomUpsample-pong"), - }; - - mis[0]->setParameter("source", hwOut, { + auto* mi = material.getMaterialInstance(mEngine); + mi->setParameter("source", hwOut, { .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST - }); - - mis[1]->setParameter("source", hwStage, { - .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST - }); + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST}); + mi->use(driver); PipelineState pipeline(material.getPipelineState(mEngine)); pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE; for (size_t j = inoutBloomOptions.levels, i = j - 1; i >= 1; i--, j++) { - const size_t parity = 1u - (j % 2u); - - auto hwDstRT = resources.getRenderPassInfo( - parity ? data.outRT[i - 1] : data.stageRT[i - 1]); + auto hwDstRT = resources.getRenderPassInfo(data.outRT[i - 1]); hwDstRT.params.flags.discardStart = TargetBufferFlags::NONE; // b/c we'll blend hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE; - auto w = FTexture::valueForLevel(i - 1, outDesc.width); auto h = FTexture::valueForLevel(i - 1, outDesc.height); - mis[parity]->setParameter("resolution", float4{ w, h, 1.0f / w, 1.0f / h }); - mis[parity]->setParameter("level", float(i)); - mis[parity]->commit(driver); - mis[parity]->use(driver); + mi->setParameter("resolution", float4{ w, h, 1.0f / w, 1.0f / h }); + mi->commit(driver); + driver.setMinMaxLevels(hwOut, i, i); // this offsets baseLevel to i render(hwDstRT, pipeline, driver); } - - for (auto& mi : mis) { - mEngine.destroy(mi); - } - - // Every other level is missing from the out texture, so we need to do - // blits to complete the chain. - const SamplerMagFilter filter = SamplerMagFilter::NEAREST; - for (size_t i = 1; i < inoutBloomOptions.levels; i += 2) { - auto in = resources.getRenderPassInfo(data.stageRT[i]); - auto out = resources.getRenderPassInfo(data.outRT[i]); - driver.blit(TargetBufferFlags::COLOR, out.target, out.params.viewport, - in.target, in.params.viewport, filter); - } + driver.setMinMaxLevels(hwOut, 0, inoutBloomOptions.levels - 1); }); return { bloomUpsamplePass->out, flare }; @@ -3036,7 +3051,6 @@ FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST }); - mi->setParameter("level", uint32_t(level)); mi->setParameter("layer", uint32_t(layer)); mi->setParameter("uvscale", 1.0f / float(dim)); mi->commit(driver); diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index a379da283fe..ca65c70c74e 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -292,6 +292,11 @@ class PostProcessManager { FrameGraphId input, backend::TextureFormat outFormat, BloomOptions& inoutBloomOptions, math::float2 scale) noexcept; + FrameGraphId downscalePass(FrameGraph& fg, + FrameGraphId input, + FrameGraphTexture::Descriptor const& outDesc, + bool threshold, float highlight, bool fireflies) noexcept; + void commitAndRender(FrameGraphResources::RenderPassInfo const& out, PostProcessMaterial const& material, uint8_t variant, backend::DriverApi& driver) const noexcept; diff --git a/filament/src/details/View.cpp b/filament/src/details/View.cpp index fc3c42e5681..6e1d6252aaa 100644 --- a/filament/src/details/View.cpp +++ b/filament/src/details/View.cpp @@ -1080,8 +1080,9 @@ void FView::setSoftShadowOptions(SoftShadowOptions options) noexcept { void FView::setBloomOptions(BloomOptions options) noexcept { options.dirtStrength = math::saturate(options.dirtStrength); - options.levels = math::clamp(options.levels, uint8_t(1), uint8_t(11)); - options.resolution = math::clamp(options.resolution, 1u << options.levels, 2048u); + options.resolution = math::clamp(options.resolution, 2u, 2048u); + options.levels = math::clamp(options.levels, uint8_t(1), + FTexture::maxLevelCount(options.resolution)); options.anamorphism = math::clamp(options.anamorphism, 1.0f/32.0f, 32.0f); options.highlight = std::max(10.0f, options.highlight); mBloomOptions = options; diff --git a/filament/src/materials/bloom/bloomDownsample.mat b/filament/src/materials/bloom/bloomDownsample.mat index a05a0ca37f8..6553f767a85 100644 --- a/filament/src/materials/bloom/bloomDownsample.mat +++ b/filament/src/materials/bloom/bloomDownsample.mat @@ -5,18 +5,6 @@ material { type : sampler2d, name : source, precision: medium - }, - { - type : float, - name : level - }, - { - type : float, - name : threshold - }, - { - type : float, - name : invHighlight } ], variables : [ @@ -37,85 +25,43 @@ fragment { void dummy(){} - void threshold(inout vec3 c) { - // 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) { return (s0 + s1 + s2 + s3) * 0.25; } - vec3 box4x4Reinhard(vec3 s0, vec3 s1, vec3 s2, vec3 s3) { - float w0 = 1.0 / (1.0 + max3(s0)); - float w1 = 1.0 / (1.0 + max3(s1)); - float w2 = 1.0 / (1.0 + max3(s2)); - float w3 = 1.0 / (1.0 + max3(s3)); - return (s0 * w0 + s1 * w1 + s2 * w2 + s3 * w3) * (1.0 / (w0 + w1 + w2 + w3)); - } - void postProcess(inout PostProcessInputs postProcess) { - float lod = materialParams.level; highp vec2 uv = variable_vertex.xy; // see SIGGRAPH 2014: Advances in Real-Time Rendering // "Next Generation Post-Processing in Call of Duty Advanced Warfare" // Jorge Jimenez - vec3 c = textureLod(materialParams_source, uv, lod).rgb; + vec3 c = textureLod(materialParams_source, uv, 0.0).rgb; // The offsets below are in "source" texture space - vec3 lt = textureLodOffset(materialParams_source, uv, lod, ivec2(-1, -1)).rgb; - vec3 rt = textureLodOffset(materialParams_source, uv, lod, ivec2( 1, -1)).rgb; - vec3 rb = textureLodOffset(materialParams_source, uv, lod, ivec2( 1, 1)).rgb; - vec3 lb = textureLodOffset(materialParams_source, uv, lod, ivec2(-1, 1)).rgb; + vec3 lt = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-1, -1)).rgb; + vec3 rt = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 1, -1)).rgb; + vec3 rb = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 1, 1)).rgb; + vec3 lb = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-1, 1)).rgb; - vec3 lt2 = textureLodOffset(materialParams_source, uv, lod, ivec2(-2, -2)).rgb; - vec3 rt2 = textureLodOffset(materialParams_source, uv, lod, ivec2( 2, -2)).rgb; - vec3 rb2 = textureLodOffset(materialParams_source, uv, lod, ivec2( 2, 2)).rgb; - vec3 lb2 = textureLodOffset(materialParams_source, uv, lod, ivec2(-2, 2)).rgb; + vec3 lt2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-2, -2)).rgb; + vec3 rt2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 2, -2)).rgb; + vec3 rb2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 2, 2)).rgb; + vec3 lb2 = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-2, 2)).rgb; - vec3 l = textureLodOffset(materialParams_source, uv, lod, ivec2(-2, 0)).rgb; - vec3 t = textureLodOffset(materialParams_source, uv, lod, ivec2( 0, -2)).rgb; - vec3 r = textureLodOffset(materialParams_source, uv, lod, ivec2( 2, 0)).rgb; - vec3 b = textureLodOffset(materialParams_source, uv, lod, ivec2( 0, 2)).rgb; + vec3 l = textureLodOffset(materialParams_source, uv, 0.0, ivec2(-2, 0)).rgb; + vec3 t = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 0, -2)).rgb; + vec3 r = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 2, 0)).rgb; + vec3 b = textureLodOffset(materialParams_source, uv, 0.0, ivec2( 0, 2)).rgb; // five h4x4 boxes vec3 c0, c1; - if (materialParams.level <= 0.5) { - if (materialParams.threshold > 0.0) { - // Threshold the first level blur - threshold(c); - threshold(lt); - threshold(rt); - threshold(rb); - threshold(lb); - threshold(lt2); - threshold(rt2); - threshold(rb2); - threshold(lb2); - threshold(l); - threshold(t); - threshold(r); - threshold(b); - } - // Also apply fireflies (flickering) filtering - c0 = box4x4Reinhard(lt, rt, rb, lb); - c1 = box4x4Reinhard(c, l, t, lt2); - c1 += box4x4Reinhard(c, r, t, rt2); - c1 += box4x4Reinhard(c, r, b, rb2); - c1 += box4x4Reinhard(c, l, b, lb2); - } else { - // common case - c0 = box4x4(lt, rt, rb, lb); - c1 = box4x4(c, l, t, lt2); - c1 += box4x4(c, r, t, rt2); - c1 += box4x4(c, r, b, rb2); - c1 += box4x4(c, l, b, lb2); - } + // common case + c0 = box4x4(lt, rt, rb, lb); + c1 = box4x4(c, l, t, lt2); + c1 += box4x4(c, r, t, rt2); + c1 += box4x4(c, r, b, rb2); + c1 += box4x4(c, l, b, lb2); // weighted average of the five boxes postProcess.color.rgb = c0 * 0.5 + c1 * 0.125; diff --git a/filament/src/materials/bloom/bloomDownsample2x.mat b/filament/src/materials/bloom/bloomDownsample2x.mat new file mode 100644 index 00000000000..3af1b28ba4f --- /dev/null +++ b/filament/src/materials/bloom/bloomDownsample2x.mat @@ -0,0 +1,98 @@ +material { + name : bloomDownsample2x, + parameters : [ + { + type : sampler2d, + name : source, + precision: medium + }, + { + type : float, + name : level + }, + { + type : float, + name : threshold + }, + { + type : float, + name : fireflies + }, + { + type : float, + name : invHighlight + } + ], + variables : [ + vertex + ], + domain : postprocess, + depthWrite : false, + depthCulling : false +} + +vertex { + void postProcessVertex(inout PostProcessVertexInputs postProcess) { + postProcess.vertex.xy = uvToRenderTargetUV(postProcess.normalizedUV); + } +} + +fragment { + + void dummy(){} + + void threshold(inout vec3 c) { + // 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); + } + + void postProcess(inout PostProcessInputs postProcess) { + float lod = materialParams.level; + + highp vec2 size = vec2(textureSize(materialParams_source, int(lod))); + highp vec2 texelSize = vec2(1.0) / size; + + // Castaño, 2013, "Shadow Mapping Summary Part 1" + // 3x3 gaussian filter with 4 linear samples + vec2 offset = vec2(0.5); + highp vec2 uv = (variable_vertex.xy * size) + offset; + highp vec2 base = (floor(uv) - offset) * texelSize; + highp vec2 st = fract(uv); + vec2 uw = vec2(3.0 - 2.0 * st.x, 1.0 + 2.0 * st.x); + vec2 vw = vec2(3.0 - 2.0 * st.y, 1.0 + 2.0 * st.y); + highp vec2 u = vec2((2.0 - st.x) / uw.x - 1.0, st.x / uw.y + 1.0) * texelSize.x; + highp vec2 v = vec2((2.0 - st.y) / vw.x - 1.0, st.y / vw.y + 1.0) * texelSize.y; + vec3 c0 = textureLod(materialParams_source, base + vec2(u.x, v.x), lod).rgb; + vec3 c1 = textureLod(materialParams_source, base + vec2(u.y, v.x), lod).rgb; + vec3 c2 = textureLod(materialParams_source, base + vec2(u.x, v.y), lod).rgb; + vec3 c3 = textureLod(materialParams_source, base + vec2(u.y, v.y), lod).rgb; + + float w0 = uw.x * vw.x * (1.0 / 16.0); + float w1 = uw.y * vw.x * (1.0 / 16.0); + float w2 = uw.x * vw.y * (1.0 / 16.0); + float w3 = uw.y * vw.y * (1.0 / 16.0); + + if (materialParams.fireflies > 0.0) { + w0 /= (1.0 + max3(c0)); + w1 /= (1.0 + max3(c1)); + w2 /= (1.0 + max3(c2)); + w3 /= (1.0 + max3(c3)); + float w = 1.0 / (w0 + w1 + w2 + w3); + w0 *= w; + w1 *= w; + w2 *= w; + w3 *= w; + } + + vec3 c = c0 * w0 + c1 * w1 + c2 * w2 + c3 * w3; + + if (materialParams.threshold > 0.0) { + threshold(c); + } + + postProcess.color.rgb = c; + } +} diff --git a/filament/src/materials/bloom/bloomDownsample9.mat b/filament/src/materials/bloom/bloomDownsample9.mat new file mode 100644 index 00000000000..7a5fa47b3ec --- /dev/null +++ b/filament/src/materials/bloom/bloomDownsample9.mat @@ -0,0 +1,65 @@ +material { + name : bloomDownsample9, + parameters : [ + { + type : sampler2d, + name : source, + precision: medium + } + ], + variables : [ + vertex + ], + domain : postprocess, + depthWrite : false, + depthCulling : false +} + +vertex { + void postProcessVertex(inout PostProcessVertexInputs postProcess) { + postProcess.vertex.xy = uvToRenderTargetUV(postProcess.normalizedUV); + } +} + +fragment { + + void dummy(){} + + // see https://www.shadertoy.com/view/cslczj + // 6x6 downsampling kernel implemented via 9 bilinear samples + + void postProcess(inout PostProcessInputs postProcess) { + highp vec2 uv = variable_vertex.xy; + highp vec2 size = vec2(1.0) / vec2(textureSize(materialParams_source, 0)); + + float o = 1.5 + 0.261629; + float wa = 7.46602 / 32.0; + float wb = 1.0 - wa * 2.0; + float wab = wa * wb; + float waa = wa * wa; + float wbb = wb * wb; + + size *= o; + + vec3 c = textureLod(materialParams_source, uv + vec2(0.0) , 0.0).rgb; + vec3 l = textureLod(materialParams_source, uv + vec2(-size.x, 0.0), 0.0).rgb; + vec3 r = textureLod(materialParams_source, uv + vec2( size.x, 0.0), 0.0).rgb; + vec3 b = textureLod(materialParams_source, uv + vec2( 0.0,-size.y), 0.0).rgb; + vec3 t = textureLod(materialParams_source, uv + vec2( 0.0, size.y), 0.0).rgb; + vec3 lb = textureLod(materialParams_source, uv + vec2(-size.x,-size.y), 0.0).rgb; + vec3 rb = textureLod(materialParams_source, uv + vec2( size.x,-size.y), 0.0).rgb; + vec3 lt = textureLod(materialParams_source, uv + vec2(-size.x, size.y), 0.0).rgb; + vec3 rt = textureLod(materialParams_source, uv + vec2( size.x, size.y), 0.0).rgb; + + postProcess.color.rgb = + (c * wbb + + (l * wab + + (r * wab + + (b * wab + + (t * wab + + (lb * waa + + (rb * waa + + (lt * waa + + (rt * waa))))))))); + } +} diff --git a/filament/src/materials/bloom/bloomUpsample.mat b/filament/src/materials/bloom/bloomUpsample.mat index b67dc160c8e..9b11a60524a 100644 --- a/filament/src/materials/bloom/bloomUpsample.mat +++ b/filament/src/materials/bloom/bloomUpsample.mat @@ -10,10 +10,6 @@ material { type : float4, name : resolution, precision: high - }, - { - type : float, - name : level } ], variables : [ @@ -34,16 +30,15 @@ fragment { void dummy(){} void postProcess(inout PostProcessInputs postProcess) { - float lod = materialParams.level; highp vec2 uv = variable_vertex.xy; #if FILAMENT_QUALITY < FILAMENT_QUALITY_HIGH highp vec4 d = vec4(materialParams.resolution.zw, -materialParams.resolution.zw) * 0.5; vec3 c; - c = textureLod(materialParams_source, uv + d.zw, lod).rgb; - c += textureLod(materialParams_source, uv + d.xw, lod).rgb; - c += textureLod(materialParams_source, uv + d.xy, lod).rgb; - c += textureLod(materialParams_source, uv + d.zy, lod).rgb; + c = textureLod(materialParams_source, uv + d.zw, 0.0).rgb; + c += textureLod(materialParams_source, uv + d.xw, 0.0).rgb; + c += textureLod(materialParams_source, uv + d.xy, 0.0).rgb; + c += textureLod(materialParams_source, uv + d.zy, 0.0).rgb; postProcess.color.rgb = c * 0.25; #else // see SIGGRAPH 2014: Advances in Real-Time Rendering @@ -52,15 +47,15 @@ fragment { const float radius = 1.0; highp vec4 d = vec4(materialParams.resolution.zw, -materialParams.resolution.zw) * radius; vec3 c0, c1; - c0 = textureLod(materialParams_source, uv + d.zw, lod).rgb; - c0 += textureLod(materialParams_source, uv + d.xw, lod).rgb; - c0 += textureLod(materialParams_source, uv + d.xy, lod).rgb; - c0 += textureLod(materialParams_source, uv + d.zy, lod).rgb; - c0 += 4.0 * textureLod(materialParams_source, uv, lod).rgb; - c1 = textureLod(materialParams_source, uv + vec2(d.z, 0.0), lod).rgb; - c1 += textureLod(materialParams_source, uv + vec2(0.0, d.w), lod).rgb; - c1 += textureLod(materialParams_source, uv + vec2(d.x, 0.0), lod).rgb; - c1 += textureLod(materialParams_source, uv + vec2( 0.0, d.y), lod).rgb; + c0 = textureLod(materialParams_source, uv + d.zw, 0.0).rgb; + c0 += textureLod(materialParams_source, uv + d.xw, 0.0).rgb; + c0 += textureLod(materialParams_source, uv + d.xy, 0.0).rgb; + c0 += textureLod(materialParams_source, uv + d.zy, 0.0).rgb; + c0 += 4.0 * textureLod(materialParams_source, uv, 0.0).rgb; + c1 = textureLod(materialParams_source, uv + vec2(d.z, 0.0), 0.0).rgb; + c1 += textureLod(materialParams_source, uv + vec2(0.0, d.w), 0.0).rgb; + c1 += textureLod(materialParams_source, uv + vec2(d.x, 0.0), 0.0).rgb; + c1 += textureLod(materialParams_source, uv + vec2( 0.0, d.y), 0.0).rgb; postProcess.color.rgb = (c0 + 2.0 * c1) * (1.0 / 16.0); #endif } diff --git a/filament/src/materials/dof/dofMipmap.mat b/filament/src/materials/dof/dofMipmap.mat index b22492f463b..9c285309b66 100644 --- a/filament/src/materials/dof/dofMipmap.mat +++ b/filament/src/materials/dof/dofMipmap.mat @@ -11,10 +11,6 @@ material { name : coc, precision: medium }, - { - type : int, - name : mip - }, { type : float, name : weightScale @@ -65,19 +61,18 @@ void postProcess(inout PostProcessInputs postProcess) { // the bilateral weights need to be scaled by to match the lower resolution float weightScale = materialParams.weightScale; - float mip = float(materialParams.mip); - vec4 s01 = textureLodOffset(materialParams_color, uv, mip, ivec2(0, 1)); - vec4 s11 = textureLodOffset(materialParams_color, uv, mip, ivec2(1, 1)); - vec4 s10 = textureLodOffset(materialParams_color, uv, mip, ivec2(1, 0)); - vec4 s00 = textureLodOffset(materialParams_color, uv, mip, ivec2(0, 0)); + vec4 s01 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(0, 1)); + vec4 s11 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(1, 1)); + vec4 s10 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(1, 0)); + vec4 s00 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(0, 0)); // fetch the 4 corresponding CoC (textureGather with LOD doesn't exist) vec4 c; - c[0] = textureLodOffset(materialParams_coc, uv, mip, ivec2(0, 1)).r; - c[1] = textureLodOffset(materialParams_coc, uv, mip, ivec2(1, 1)).r; - c[2] = textureLodOffset(materialParams_coc, uv, mip, ivec2(1, 0)).r; - c[3] = textureLodOffset(materialParams_coc, uv, mip, ivec2(0, 0)).r; + c[0] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(0, 1)).r; + c[1] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(1, 1)).r; + c[2] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(1, 0)).r; + c[3] = textureLodOffset(materialParams_coc, uv, 0.0, ivec2(0, 0)).r; float outCoc = downsampleCoC(c); vec4 w = downsampleCocWeights(c, outCoc, weightScale); diff --git a/filament/src/materials/ssao/mipmapDepth.mat b/filament/src/materials/ssao/mipmapDepth.mat index 74b3835e290..1fb36ab5fe3 100644 --- a/filament/src/materials/ssao/mipmapDepth.mat +++ b/filament/src/materials/ssao/mipmapDepth.mat @@ -5,10 +5,6 @@ material { type : sampler2d, name : depth, precision: high - }, - { - type : int, - name : level } ], variables : [ @@ -30,9 +26,8 @@ fragment { // We use a rotated grid sub-sample as it's cheap and gives good results // See Scalable Ambient Obscurance by McGuire and al. void postProcess(inout PostProcessInputs postProcess) { - int level = materialParams.level; ivec2 icoord = ivec2(gl_FragCoord.xy); postProcess.depth = texelFetch(materialParams_depth, - 2 * icoord + ivec2(icoord.y & 1, icoord.x & 1), level).r; + 2 * icoord + ivec2(icoord.y & 1, icoord.x & 1), 0).r; } } diff --git a/filament/src/materials/vsmMipmap.mat b/filament/src/materials/vsmMipmap.mat index 70e65b8f344..5047447bf28 100644 --- a/filament/src/materials/vsmMipmap.mat +++ b/filament/src/materials/vsmMipmap.mat @@ -10,10 +10,6 @@ material { type : int, name : layer }, - { - type : int, - name : level - }, { type : float, name : uvscale @@ -36,7 +32,6 @@ fragment { void postProcess(inout PostProcessInputs postProcess) { highp vec2 uv = gl_FragCoord.xy * materialParams.uvscale; postProcess.color = textureLod(materialParams_color, - vec3(uv, materialParams.layer), - float(materialParams.level)); + vec3(uv, materialParams.layer), 0.0); } } diff --git a/libs/viewer/src/Settings_generated.cpp b/libs/viewer/src/Settings_generated.cpp index f9cbbdff6fc..1017b7aa618 100644 --- a/libs/viewer/src/Settings_generated.cpp +++ b/libs/viewer/src/Settings_generated.cpp @@ -249,6 +249,8 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, BloomOptions* o 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, "quality") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->quality); } else if (compare(tok, jsonChunk, "lensFlare") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->lensFlare); } else if (compare(tok, jsonChunk, "starburst") == 0) { @@ -291,6 +293,7 @@ std::ostream& operator<<(std::ostream& out, const BloomOptions& in) { << "\"threshold\": " << to_string(in.threshold) << ",\n" << "\"enabled\": " << to_string(in.enabled) << ",\n" << "\"highlight\": " << (in.highlight) << ",\n" + << "\"quality\": " << (in.quality) << ",\n" << "\"lensFlare\": " << to_string(in.lensFlare) << ",\n" << "\"starburst\": " << to_string(in.starburst) << ",\n" << "\"chromaticAberration\": " << (in.chromaticAberration) << ",\n" diff --git a/libs/viewer/src/ViewerGui.cpp b/libs/viewer/src/ViewerGui.cpp index 6befd6e264f..70fc2a4ac48 100644 --- a/libs/viewer/src/ViewerGui.cpp +++ b/libs/viewer/src/ViewerGui.cpp @@ -777,6 +777,10 @@ void ViewerGui::updateUserInterface() { ImGui::SliderInt("Levels", &levels, 3, 11); mSettings.view.bloom.levels = levels; + int quality = (int) mSettings.view.bloom.quality; + ImGui::SliderInt("Bloom Quality", &quality, 0, 3); + mSettings.view.bloom.quality = (View::QualityLevel) quality; + ImGui::Checkbox("Lens Flare", &mSettings.view.bloom.lensFlare); } diff --git a/web/filament-js/extensions_generated.js b/web/filament-js/extensions_generated.js index ae77408a84b..fb26545133e 100644 --- a/web/filament-js/extensions_generated.js +++ b/web/filament-js/extensions_generated.js @@ -26,6 +26,7 @@ Filament.loadGeneratedExtensions = function() { threshold: true, enabled: false, highlight: 1000.0, + quality: Filament.View$QualityLevel.LOW, lensFlare: false, starburst: true, chromaticAberration: 0.005, diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts index 0cd992a629d..f952c0dcc0a 100644 --- a/web/filament-js/filament.d.ts +++ b/web/filament-js/filament.d.ts @@ -1253,6 +1253,16 @@ export interface View$BloomOptions { * limit highlights to this value before bloom [10, +inf] */ highlight?: number; + /** + * Bloom quality level. + * LOW (default): use a more optimized down-sampling filter, however there can be artifacts + * with dynamic resolution, this can be alleviated by using the homogenous mode. + * MEDIUM: Good balance between quality and performance. + * HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts. + * This mode can be significantly slower on mobile, especially at high resolution. + * This mode greatly improves the anamorphic bloom. + */ + quality?: View$QualityLevel; /** * enable screen-space lens flare */ diff --git a/web/filament-js/jsbindings_generated.cpp b/web/filament-js/jsbindings_generated.cpp index f5598830e21..4ef1dd6327a 100644 --- a/web/filament-js/jsbindings_generated.cpp +++ b/web/filament-js/jsbindings_generated.cpp @@ -30,6 +30,7 @@ value_object("View$BloomOptions") .field("threshold", &View::BloomOptions::threshold) .field("enabled", &View::BloomOptions::enabled) .field("highlight", &View::BloomOptions::highlight) + .field("quality", &View::BloomOptions::quality) .field("lensFlare", &View::BloomOptions::lensFlare) .field("starburst", &View::BloomOptions::starburst) .field("chromaticAberration", &View::BloomOptions::chromaticAberration)