diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index 68019b31d5..aca5c18407 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -249,10 +249,14 @@ Java_com_google_android_filament_View_nSetAmbientOcclusionOptions(JNIEnv*, jclas extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_View_nSetBloomOptions(JNIEnv*, jclass, - jlong nativeView, jfloat strength, jint resolution, jfloat anamorphism, jint levels, + jlong nativeView, jlong nativeTexture, + jfloat dirtStrength, jfloat strength, jint resolution, jfloat anamorphism, jint levels, jint blendMode, jboolean threshold, jboolean enabled) { View* view = (View*) nativeView; + Texture* dirt = (Texture*) nativeTexture; View::BloomOptions options = { + .dirt = dirt, + .dirtStrength = dirtStrength, .strength = strength, .resolution = (uint32_t)resolution, .anamorphism = anamorphism, 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 735903235b..394eebe32a 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 @@ -175,6 +175,9 @@ public class View { * anamorphism: Bloom's aspect ratio (x/y), for artistic purposes. * threshold: When enabled, a threshold at 1.0 is applied on the source image, this is * useful for artistic reasons. + * dirt: A dirt/scratch/smudges texture (that can be RGB), which gets added to the + * bloom effect. Smudges are visible where bloom occurs. + * dirtStrength: Strength of the dirt texture. * * @see setBloomOptions */ @@ -185,6 +188,17 @@ public class View { INTERPOLATE } + /** + * User provided dirt texture + */ + @Nullable + public Texture dirt = null; + + /** + * strength of the dirt texture + */ + public float dirtStrength = 0.2f; + /** * Strength of the bloom effect, between 0.0 and 1.0 */ @@ -802,7 +816,8 @@ public class View { */ public void setBloomOptions(@NonNull BloomOptions options) { mBloomOptions = options; - nSetBloomOptions(getNativeObject(), options.strength, options.resolution, + nSetBloomOptions(getNativeObject(), options.dirt.getNativeObject(), + options.dirtStrength, options.strength, options.resolution, options.anamorphism, options.levels, options.blendingMode.ordinal(), options.threshold, options.enabled); } @@ -862,5 +877,5 @@ public class View { 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); - private static native void nSetBloomOptions(long nativeView, float strength, int resolution, float anamorphism, int levels, int blendMode, boolean threshold, boolean enabled); + 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); } diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index 63d03ec35c..7b7416ee09 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -34,6 +34,7 @@ class Camera; class MaterialInstance; class RenderTarget; class Scene; +class Texture; class Viewport; /** @@ -140,19 +141,24 @@ public: * anamorphism: Bloom's aspect ratio (x/y), for artistic purposes. * threshold: When enabled, a threshold at 1.0 is applied on the source image, this is * useful for artistic reasons. + * dirt: A dirt/scratch/smudges texture (that can be RGB), which gets added to the + * bloom effect. Smudges are visible where bloom occurs. + * dirtStrength: Strength of the dirt texture. */ struct BloomOptions { enum class BlendMode : uint8_t { ADD, //!< Bloom is modulated by the strength parameter and added to the scene INTERPOLATE //!< Bloom is interpolated with the scene using the strength parameter }; - float strength = 0.10f; //!< Between 0.0 and 1.0 - uint32_t resolution = 360; //!< Resolution of minor axis (2^levels to 4096) - float anamorphism = 1.0f; //!< Bloom x/y aspect-ratio (1/32 to 32) - uint8_t levels = 6; //!< number of blur levels (3 to 12) - BlendMode blendMode = BlendMode::ADD; //!< How the bloom effect is applied - bool threshold = true; //!< Whether to threshold the source - bool enabled = false; //!< enable or disable bloom + Texture* dirt = nullptr; //!< User provided dirt texture + float dirtStrength = 0.2f; //!< strength of the dirt texture + float strength = 0.10f; //!< Between 0.0 and 1.0 + uint32_t resolution = 360; //!< Resolution of minor axis (2^levels to 4096) + float anamorphism = 1.0f; //!< Bloom x/y aspect-ratio (1/32 to 32) + uint8_t levels = 6; //!< number of blur levels (3 to 12) + BlendMode blendMode = BlendMode::ADD; //!< How the bloom effect is applied + bool threshold = true; //!< Whether to threshold the source + bool enabled = false; //!< enable or disable bloom }; /** diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index c296b3b6d6..c88c4c00df 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -133,8 +133,11 @@ void PostProcessManager::init() noexcept { mSeparableGaussianBlurKernelStorageSize = mSeparableGaussianBlur.getMaterial()->reflect("kernel")->size; DriverApi& driver = mEngine.getDriverApi(); - mNoSSAOTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1, - TextureFormat::R8, 0, 1, 1, 1, TextureUsage::DEFAULT); + mDummyOneTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1, + TextureFormat::RGBA8, 0, 1, 1, 1, TextureUsage::DEFAULT); + + mDummyZeroTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1, + TextureFormat::RGBA8, 0, 1, 1, 1, TextureUsage::DEFAULT); mNoiseTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGB16F, 0, 16, 16, 1, TextureUsage::DEFAULT); @@ -164,17 +167,19 @@ void PostProcessManager::init() noexcept { } driver.update2DImage(mNoiseTexture, 0, 0, 0, 16, 16, std::move(noiseData)); - - PixelBufferDescriptor data(driver.allocate(1), 1, PixelDataFormat::R, PixelDataType::UBYTE); - auto p = static_cast(data.buffer); - *p = 0xFFu; - driver.update2DImage(mNoSSAOTexture, 0, 0, 0, 1, 1, std::move(data)); + PixelBufferDescriptor dataOne(driver.allocate(4), 4, PixelDataFormat::RGBA, PixelDataType::UBYTE); + PixelBufferDescriptor dataZero(driver.allocate(4), 4, PixelDataFormat::RGBA, PixelDataType::UBYTE); + *static_cast(dataOne.buffer) = 0xFFFFFFFF; + *static_cast(dataZero.buffer) = 0; + driver.update2DImage(mDummyOneTexture, 0, 0, 0, 1, 1, std::move(dataOne)); + driver.update2DImage(mDummyZeroTexture, 0, 0, 0, 1, 1, std::move(dataZero)); } void PostProcessManager::terminate(DriverApi& driver) noexcept { - driver.destroyTexture(mNoSSAOTexture); - driver.destroyTexture(mNoiseTexture); FEngine& engine = mEngine; + driver.destroyTexture(mDummyOneTexture); + driver.destroyTexture(mDummyZeroTexture); + driver.destroyTexture(mNoiseTexture); mSSAO.terminate(engine); mMipmapDepth.terminate(engine); mBilateralBlur.terminate(engine); @@ -200,15 +205,26 @@ FrameGraphId PostProcessManager::toneMapping(FrameGraph& fg, FrameGraphId input; FrameGraphId output; FrameGraphId bloom; + FrameGraphId dirt; FrameGraphRenderTargetHandle rt; }; FrameGraphId bloomBlur; + FrameGraphId bloomDirt; float bloom = 0.0f; if (bloomOptions.enabled) { bloom = clamp(bloomOptions.strength, 0.0f, 1.0f); bloomBlur = bloomPass(fg, input, TextureFormat::R11F_G11F_B10F, bloomOptions, scale); + if (bloomOptions.dirt) { + FTexture* fdirt = upcast(bloomOptions.dirt); + FrameGraphTexture frameGraphTexture { .texture = fdirt->getHwHandle() }; + bloomDirt = fg.import("dirt", { + .width = (uint32_t)fdirt->getWidth(0u), + .height = (uint32_t)fdirt->getHeight(0u), + .format = fdirt->getFormat() + }, frameGraphTexture); + } } auto& ppToneMapping = fg.addPass("tonemapping", @@ -222,33 +238,49 @@ FrameGraphId PostProcessManager::toneMapping(FrameGraph& fg, }); data.rt = builder.createRenderTarget(data.output); - if (!bloomBlur.isValid()) { - // we need a dummy texture - bloomBlur = builder.createTexture("dummy", {}); + if (bloomBlur.isValid()) { + data.bloom = builder.sample(bloomBlur); + } + if (bloomDirt.isValid()) { + data.dirt = builder.sample(bloomDirt); } - data.bloom = builder.sample(bloomBlur); }, [=](FrameGraphPassResources const& resources, PostProcessToneMapping const& data, DriverApi& driver) { - auto const& colorTexture = resources.getTexture(data.input); - auto const& bloomTexture = resources.getTexture(data.bloom); - FMaterialInstance* pInstance = mTonemapping.getMaterialInstance(); - pInstance->setParameter("colorBuffer", colorTexture, { /* shader uses texelFetch */ }); - pInstance->setParameter("bloomBuffer", bloomTexture, { + Handle colorTexture = resources.getTexture(data.input); + + Handle bloomTexture = + data.bloom.isValid() ? resources.getTexture(data.bloom) : getZeroTexture(); + + Handle dirtTexture = + data.dirt.isValid() ? resources.getTexture(data.dirt) : getOneTexture(); + + FMaterialInstance* mi = mTonemapping.getMaterialInstance(); + mi->setParameter("colorBuffer", colorTexture, { /* shader uses texelFetch */ }); + mi->setParameter("bloomBuffer", bloomTexture, { .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR /* always read base level in shader */ }); + mi->setParameter("dirtBuffer", dirtTexture, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); - float2 bloomParameter{ bloom / float(bloomOptions.levels), 1.0f }; + float4 bloomParameter{ + bloom / float(bloomOptions.levels), + 1.0f, + (bloomOptions.enabled && bloomOptions.dirt) ? bloomOptions.dirtStrength : 0.0f, + 0.0f + }; if (bloomOptions.blendMode == View::BloomOptions::BlendMode::INTERPOLATE) { bloomParameter.y = 1.0f - bloomParameter.x; } - pInstance->setParameter("dithering", dithering); - pInstance->setParameter("bloom", bloomParameter); - pInstance->setParameter("fxaa", fxaa); - pInstance->commit(driver); + mi->setParameter("dithering", dithering); + mi->setParameter("bloom", bloomParameter); + mi->setParameter("fxaa", fxaa); + mi->commit(driver); const uint8_t variant = uint8_t(translucent ? PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE); @@ -256,12 +288,12 @@ FrameGraphId PostProcessManager::toneMapping(FrameGraph& fg, PipelineState pipeline{ .program = mTonemapping.getMaterial()->getProgram(variant), .rasterState = mTonemapping.getMaterial()->getRasterState(), - .scissor = pInstance->getScissor() + .scissor = mi->getScissor() }; auto const& target = resources.getRenderTarget(data.rt); driver.beginRenderPass(target.target, target.params); - pInstance->use(driver); + mi->use(driver); driver.draw(pipeline, fullScreenRenderPrimitive); driver.endRenderPass(); }); diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index 43401c8514..934e188d49 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -77,9 +77,8 @@ public: FrameGraphId output, uint8_t dstLevel, bool reinhard, size_t kernelWidth, float sigma = 6.0f) noexcept; - backend::Handle getNoSSAOTexture() const { - return mNoSSAOTexture; - } + backend::Handle getOneTexture() const { return mDummyOneTexture; } + backend::Handle getZeroTexture() const { return mDummyZeroTexture; } private: details::FEngine& mEngine; @@ -134,7 +133,8 @@ private: PostProcessMaterial mTonemapping; PostProcessMaterial mFxaa; - backend::Handle mNoSSAOTexture; + backend::Handle mDummyOneTexture; + backend::Handle mDummyZeroTexture; backend::Handle mNoiseTexture; size_t mSeparableGaussianBlurKernelStorageSize = 0; diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index 9b5e9e3195..bb7a21b4c6 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -344,7 +344,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { [&ppm, &js, &view, jobFroxelize] (FrameGraphPassResources const& resources, auto const& data, DriverApi& driver) { view.prepareSSAO(data.ssao.isValid() ? resources.getTexture(data.ssao) - : ppm.getNoSSAOTexture()); + : ppm.getOneTexture()); view.commitUniforms(driver); if (jobFroxelize) { auto sync = jobFroxelize; diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 45cfcc5e1f..70c722401f 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -245,6 +245,7 @@ public: } void setBloomOptions(BloomOptions options) noexcept { + options.dirtStrength = math::saturate(options.dirtStrength); options.levels = math::clamp(options.levels, uint8_t(3), uint8_t(12)); mBloomOptions = options; } diff --git a/filament/src/materials/tonemapping.mat b/filament/src/materials/tonemapping.mat index 9b5762f477..e2e4c26f78 100644 --- a/filament/src/materials/tonemapping.mat +++ b/filament/src/materials/tonemapping.mat @@ -11,6 +11,11 @@ material { name : bloomBuffer, precision: medium }, + { + type : sampler2d, + name : dirtBuffer, + precision: medium + }, { type : int, name : dithering @@ -20,7 +25,7 @@ material { name : fxaa }, { - type : float2, + type : float4, name : bloom } ], diff --git a/libs/gltfio/include/gltfio/SimpleViewer.h b/libs/gltfio/include/gltfio/SimpleViewer.h index 34aea18dd0..30637352a0 100644 --- a/libs/gltfio/include/gltfio/SimpleViewer.h +++ b/libs/gltfio/include/gltfio/SimpleViewer.h @@ -154,6 +154,14 @@ public: */ void enableSSAO(bool b) { mEnableSsao = b; } + /** + * Enables Bloom. + * Defaults to true. + */ + void enableBloom(bool bloom) { + mBloomOptions.enabled = bloom; + } + /** * Adjusts the intensity of the IBL. * See also filament::IndirectLight::setIntensity(). diff --git a/samples/app/Config.h b/samples/app/Config.h index 5145573179..a5b9bc268f 100644 --- a/samples/app/Config.h +++ b/samples/app/Config.h @@ -24,6 +24,7 @@ struct Config { std::string title; std::string iblDirectory; + std::string dirt; float scale = 1.0f; bool splitView = false; filament::Engine::Backend backend = filament::Engine::Backend::OPENGL; diff --git a/samples/app/FilamentApp.cpp b/samples/app/FilamentApp.cpp index 57ec754ca2..dffe873e63 100644 --- a/samples/app/FilamentApp.cpp +++ b/samples/app/FilamentApp.cpp @@ -46,6 +46,8 @@ #include "Cube.h" #include "NativeWindowHelper.h" +#include + #include "generated/resources/resources.h" using namespace filament; @@ -137,6 +139,7 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback, window->mDepthView->getView()->setClearColor({0, 0, 0, 1}); } + loadDirt(config); loadIBL(config); if (mIBL != nullptr) { mIBL->getSkybox()->setLayerMask(0x7, 0x4); @@ -452,6 +455,37 @@ void FilamentApp::loadIBL(const Config& config) { } } +void FilamentApp::loadDirt(const Config& config) { + if (!config.dirt.empty()) { + Path dirtPath(config.dirt); + + if (!dirtPath.exists()) { + std::cerr << "The specified dirt file does not exist: " << dirtPath << std::endl; + return; + } + + if (!dirtPath.isFile()) { + std::cerr << "The specified dirt path is not a file: " << dirtPath << std::endl; + return; + } + + int w, h, n; + + unsigned char* data = stbi_load(dirtPath.getAbsolutePath().c_str(), &w, &h, &n, 3); + assert(n == 3); + + mDirt = Texture::Builder() + .width(w) + .height(h) + .format(Texture::InternalFormat::RGB8) + .build(*mEngine); + + mDirt->setImage(*mEngine, 0, { data, size_t(w * h * 3), + Texture::Format::RGB, Texture::Type::UBYTE, + (Texture::PixelBufferDescriptor::Callback)&stbi_image_free }); + } +} + void FilamentApp::initSDL() { ASSERT_POSTCONDITION(SDL_Init(SDL_INIT_EVENTS) == 0, "SDL_Init Failure"); } diff --git a/samples/app/FilamentApp.h b/samples/app/FilamentApp.h index 283d65f4b7..9e142e8bff 100644 --- a/samples/app/FilamentApp.h +++ b/samples/app/FilamentApp.h @@ -76,6 +76,7 @@ public: filament::Material const* getDefaultMaterial() const noexcept { return mDefaultMaterial; } filament::Material const* getTransparentMaterial() const noexcept { return mTransparentMaterial; } IBL* getIBL() const noexcept { return mIBL.get(); } + filament::Texture* getDirtTexture() const noexcept { return mDirt; } filament::View* getGuiView() const noexcept; void close() { mClosed = true; } @@ -199,10 +200,12 @@ private: void initSDL(); void loadIBL(const Config& config); + void loadDirt(const Config& config); filament::Engine* mEngine = nullptr; filament::Scene* mScene = nullptr; std::unique_ptr mIBL; + filament::Texture* mDirt = nullptr; bool mClosed = false; uint64_t mTime = 0; diff --git a/samples/material_sandbox.cpp b/samples/material_sandbox.cpp index c68cd2e4ab..1affb5f967 100644 --- a/samples/material_sandbox.cpp +++ b/samples/material_sandbox.cpp @@ -88,6 +88,8 @@ static void printUsage(char* name) { " Enable shadow plane\n\n" " --single\n" " Only apply the edited material to the first renderable in the scene\n\n" + " --dirt\n" + " Specify a dirt texture\n\n" ); const std::string from("SAMPLE_MATERIAL"); for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) { @@ -97,7 +99,7 @@ static void printUsage(char* name) { } static int handleCommandLineArgments(int argc, char* argv[], Config* config) { - static constexpr const char* OPTSTR = "ha:vps:i:"; + static constexpr const char* OPTSTR = "ha:vps:i:d:"; static const struct option OPTIONS[] = { { "help", no_argument, nullptr, 'h' }, { "api", required_argument, nullptr, 'a' }, @@ -106,6 +108,7 @@ static int handleCommandLineArgments(int argc, char* argv[], Config* config) { { "scale", required_argument, nullptr, 's' }, { "shadow-plane", no_argument, nullptr, 'p' }, { "single", no_argument, nullptr, 'n' }, + { "dirt", required_argument, nullptr, 'd' }, { nullptr, 0, nullptr, 0 } // termination of the option list }; int opt; @@ -149,6 +152,9 @@ static int handleCommandLineArgments(int argc, char* argv[], Config* config) { case 'n': g_singleMode = true; break; + case 'd': + config->dirt = arg; + break; } } @@ -289,6 +295,8 @@ static void setup(Engine* engine, View*, Scene* scene) { params.lightIntensity = c.w * pIndirectLight->getIntensity(); params.lightColor = c.rgb; } + + g_params.bloomOptions.dirt = FilamentApp::get().getDirtTexture(); } static void gui(filament::Engine* engine, filament::View*) { @@ -401,6 +409,7 @@ static void gui(filament::Engine* engine, filament::View*) { ImGui::Checkbox("bloom", ¶ms.bloomOptions.enabled); if (params.bloomOptions.enabled) { ImGui::SliderFloat("strength", ¶ms.bloomOptions.strength, 0.0f, 1.0f); + ImGui::SliderFloat("dirt", ¶ms.bloomOptions.dirtStrength, 0.0f, 1.0f); } ImGui::Checkbox("dithering", ¶ms.dithering); ImGui::Unindent(); @@ -520,6 +529,5 @@ int main(int argc, char* argv[]) { g_config.title = "Material Sandbox"; FilamentApp& filamentApp = FilamentApp::get(); filamentApp.run(g_config, setup, cleanup, gui, preRender); - return 0; } diff --git a/shaders/src/bloom.fs b/shaders/src/bloom.fs index e44367a5df..22a49fa4af 100644 --- a/shaders/src/bloom.fs +++ b/shaders/src/bloom.fs @@ -2,8 +2,16 @@ // Bloom //------------------------------------------------------------------------------ -vec3 bloom(const vec3 color) { +vec3 bloom(vec3 color) { highp vec2 uv = variable_vertex.xy; vec3 blurred = textureLod(materialParams_bloomBuffer, uv, 0.0).rgb; - return blurred * materialParams.bloom.x + color * materialParams.bloom.y; + 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; }