From 62a591bc7353e7215a20bcdda7ea29b2b105289d Mon Sep 17 00:00:00 2001 From: Pixelflinger Date: Tue, 19 May 2020 19:38:02 -0700 Subject: [PATCH] Add a new tone mapping option using a LUT This is now the default, and the tone mapper used is now the same on desktop and mobile. In the future this will allow us to implement many features such as color grading at no cpu cost. --- filament/CMakeLists.txt | 2 + filament/src/PostProcessManager.cpp | 12 ++ filament/src/PostProcessManager.h | 6 +- filament/src/Tonemapper.cpp | 111 ++++++++++++++++++ filament/src/details/Tonemapper.h | 48 ++++++++ filament/src/materials/tonemapping.mat | 11 +- .../src/materials/tonemappingWithSubpass.mat | 13 +- shaders/src/conversion_functions.fs | 2 +- shaders/src/tone_mapping.fs | 31 +++-- 9 files changed, 218 insertions(+), 18 deletions(-) create mode 100644 filament/src/Tonemapper.cpp create mode 100644 filament/src/details/Tonemapper.h diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 523a1b1b5b..7c1c870b3d 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -84,6 +84,7 @@ set(SRCS src/SwapChain.cpp src/Stream.cpp src/Texture.cpp + src/Tonemapper.cpp src/UniformBuffer.cpp src/View.cpp src/Viewport.cpp @@ -129,6 +130,7 @@ set(PRIVATE_HDRS src/details/Stream.h src/details/SwapChain.h src/details/Texture.h + src/details/Tonemapper.h src/details/VertexBuffer.h src/details/View.h src/FilamentAPI-impl.h diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 3b14b04dcc..3f1c57e497 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -27,6 +27,7 @@ #include "details/Material.h" #include "details/MaterialInstance.h" #include "details/Texture.h" +#include "details/Tonemapper.h" #include "generated/resources/materials.h" #include @@ -145,6 +146,8 @@ void PostProcessManager::init() noexcept { *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)); + + mTonemapper = new Tonemapper(mEngine); } void PostProcessManager::terminate(DriverApi& driver) noexcept { @@ -165,6 +168,7 @@ void PostProcessManager::terminate(DriverApi& driver) noexcept { mFxaa.terminate(engine); mDoFBlur.terminate(engine); mDoF.terminate(engine); + mTonemapper->terminate(engine); } // ------------------------------------------------------------------------------------------------ @@ -235,6 +239,10 @@ FrameGraphId PostProcessManager::toneMapping(FrameGraph& fg, data.dirt.isValid() ? resources.getTexture(data.dirt) : getOneTexture(); FMaterialInstance* mi = mTonemapping.getMaterialInstance(); + mi->setParameter("lut", mTonemapper->getHwHandle(), { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); mi->setParameter("colorBuffer", colorTexture, { /* shader uses texelFetch */ }); mi->setParameter("bloomBuffer", bloomTexture, { .filterMag = SamplerMagFilter::LINEAR, @@ -280,6 +288,10 @@ void PostProcessManager::toneMappingSubpass(DriverApi& driver, Handle const& fullScreenRenderPrimitive = engine.getFullScreenRenderPrimitive(); FMaterialInstance* mi = mTonemappingWithSubpass.getMaterialInstance(); + mi->setParameter("lut", mTonemapper->getHwHandle(), { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); mi->setParameter("dithering", dithering); mi->setParameter("fxaa", fxaa); mi->commit(driver); diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index 732d3af785..51b5bc021f 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -28,11 +28,12 @@ namespace filament { +class FEngine; class FMaterial; class FMaterialInstance; -class FEngine; class FView; class RenderPass; +class Tonemapper; struct CameraInfo; class PostProcessManager { @@ -148,6 +149,9 @@ private: backend::Handle mDummyZeroTexture; size_t mSeparableGaussianBlurKernelStorageSize = 0; + + // eventually this will be a separate object + Tonemapper* mTonemapper = nullptr; }; } // namespace filament diff --git a/filament/src/Tonemapper.cpp b/filament/src/Tonemapper.cpp new file mode 100644 index 0000000000..a365538afa --- /dev/null +++ b/filament/src/Tonemapper.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "details/Tonemapper.h" + +#include "details/Engine.h" + +#include + +#include + +namespace filament { + +using namespace utils; +using namespace math; +using namespace backend; + +static constexpr size_t LUT_DIMENSION = 32u; + + +Tonemapper::Tonemapper(FEngine& engine) { + DriverApi& driver = engine.getDriverApi(); + + // create the texture, we use RGBA16F because RGB16F is not supported everywhere + mLutHandle = driver.createTexture(SamplerType::SAMPLER_3D, 1, + TextureFormat::RGBA16F, 0, + LUT_DIMENSION, LUT_DIMENSION, LUT_DIMENSION, TextureUsage::DEFAULT); + + // it's okay to allocate the data in the command stream because it's only 256 KiB + constexpr size_t lutSize = LUT_DIMENSION * LUT_DIMENSION * LUT_DIMENSION; + constexpr size_t elementSize = sizeof(half4); + void* data = malloc(lutSize * elementSize); + PixelBufferDescriptor lutData(data, + lutSize * elementSize, PixelDataFormat::RGBA, PixelDataType::HALF, + [](void* buffer, size_t, void*) { + free(buffer); + } + ); + + auto now = std::chrono::steady_clock::now(); + half4* p = (half4*)data; + for (size_t b = 0; b < LUT_DIMENSION; b++) { + for (size_t g = 0; g < LUT_DIMENSION; g++) { + for (size_t r = 0; r < LUT_DIMENSION; r++) { + float3 v = float3{ r, g, b } * (1.0 / (LUT_DIMENSION - 1u)); + v.x = lutToLinear(v.x); + v.y = lutToLinear(v.y); + v.z = lutToLinear(v.z); + v = tonemap_ACES(v); + v = Color::toSRGB(v); + *p++ = half4{ v, 0 }; + } + } + } + std::chrono::duration duration = std::chrono::steady_clock::now() - now; + slog.d << "LUT generation time: " << duration.count() << " ms" << io::endl; + + driver.update3DImage(mLutHandle, 0, + 0, 0, 0, + LUT_DIMENSION, LUT_DIMENSION, LUT_DIMENSION, std::move(lutData)); +} + +Tonemapper::~Tonemapper() noexcept = default; + +void Tonemapper::terminate(FEngine& engine) { + DriverApi& driver = engine.getDriverApi(); + driver.destroyTexture(mLutHandle); +} + +float3 Tonemapper::tonemap_ACES(float3 x) noexcept { + // Narkowicz 2015, "ACES Filmic Tone Mapping Curve" + const float a = 2.51f; + const float b = 0.03f; + const float c = 2.43f; + const float d = 0.59f; + const float e = 0.14f; + return (x * (a * x + b)) / (x * (c * x + d) + e); +} + +float Tonemapper::lutToLinear(float x) noexcept { + // Alexa LogC EI 1000 curve + const float ia = 1.0f / 5.555556f; + const float b = 0.047996f; + const float ic = 1.0f / 0.244161f; + const float d = 0.386036f; + return (std::pow(10.0f, (x - d) * ic) - b) * ia; +} + +float Tonemapper::linearToLut(float x) noexcept { + // Alexa LogC EI 1000 curve + const float a = 5.555556f; + const float b = 0.047996f; + const float c = 0.244161f; + const float d = 0.386036f; + return c * std::log10(a * x + b) + d; +} + +} //namespace filament diff --git a/filament/src/details/Tonemapper.h b/filament/src/details/Tonemapper.h new file mode 100644 index 0000000000..64ad279a17 --- /dev/null +++ b/filament/src/details/Tonemapper.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_DETAILS_TONEMAPPER_H +#define TNT_FILAMENT_DETAILS_TONEMAPPER_H + +#include +#include + +namespace filament { + +class FEngine; + +class Tonemapper { +public: + Tonemapper(FEngine& engine); + + ~Tonemapper() noexcept; + + // frees driver resources, object becomes invalid + void terminate(FEngine& engine); + + backend::TextureHandle getHwHandle() const noexcept { return mLutHandle; } + +private: + static math::float3 tonemap_ACES(math::float3 x) noexcept; + static float lutToLinear(float x) noexcept; + static float linearToLut(float x) noexcept; + + backend::TextureHandle mLutHandle; +}; + +} // namespace filament + +#endif //TNT_FILAMENT_DETAILS_TONEMAPPER_H diff --git a/filament/src/materials/tonemapping.mat b/filament/src/materials/tonemapping.mat index ebc5f299b6..026ee087fb 100644 --- a/filament/src/materials/tonemapping.mat +++ b/filament/src/materials/tonemapping.mat @@ -4,7 +4,12 @@ material { { type : sampler2d, name : colorBuffer, - precision: high + precision: medium + }, + { + type : sampler3d, + name : lut, + precision: medium }, { type : sampler2d, @@ -64,7 +69,7 @@ fragment { if (materialParams.bloom.x > 0.0) { color.rgb = bloom(color.rgb); } - color.rgb = tonemap(color.rgb); + color.rgb = tonemap(materialParams_lut, color.rgb); color.rgb = OECF(color.rgb); if (materialParams.fxaa > 0) { color.a = luminance(color.rgb); @@ -75,7 +80,7 @@ fragment { if (materialParams.bloom.x > 0.0) { color.rgb = bloom(color.rgb); } - color.rgb = tonemap(color.rgb); + color.rgb = tonemap(materialParams_lut, color.rgb); color.rgb = OECF(color.rgb); color.rgb *= color.a + FLT_EPS; #endif diff --git a/filament/src/materials/tonemappingWithSubpass.mat b/filament/src/materials/tonemappingWithSubpass.mat index f9f2868160..42fc90bc97 100644 --- a/filament/src/materials/tonemappingWithSubpass.mat +++ b/filament/src/materials/tonemappingWithSubpass.mat @@ -1,6 +1,11 @@ material { name : tonemap, parameters : [ + { + type : sampler3d, + name : lut, + precision: medium + }, { type : int, name : dithering @@ -8,10 +13,6 @@ material { { type : int, name : fxaa - }, - { - type : float4, - name : bloom } ], variables : [ @@ -50,7 +51,7 @@ fragment { vec4 resolve() { #if POST_PROCESS_OPAQUE vec4 color = vec4(resolveFragment(ivec2(getUV())), 1.0); - color.rgb = tonemap(color.rgb); + color.rgb = tonemap(materialParams_lut, color.rgb); color.rgb = OECF(color.rgb); if (materialParams.fxaa > 0) { color.a = luminance(color.rgb); @@ -58,7 +59,7 @@ fragment { #else vec4 color = resolveAlphaFragment(ivec2(getUV())); color.rgb /= color.a + FLT_EPS; - color.rgb = tonemap(color.rgb); + color.rgb = tonemap(materialParams_lut, color.rgb); color.rgb = OECF(color.rgb); color.rgb *= color.a + FLT_EPS; #endif diff --git a/shaders/src/conversion_functions.fs b/shaders/src/conversion_functions.fs index 4ab1034361..6ddbddc427 100644 --- a/shaders/src/conversion_functions.fs +++ b/shaders/src/conversion_functions.fs @@ -7,7 +7,7 @@ #define CONVERSION_FUNCTION_sRGB 1 #define CONVERSION_FUNCTION_sRGB_FAST 2 -#if TONE_MAPPING_OPERATOR <= TONE_MAPPING_FILMIC_ALU +#if TONE_MAPPING_OPERATOR < TONE_MAPPING_LINEAR #define CONVERSION_FUNCTION CONVERSION_FUNCTION_LINEAR #else #ifdef TARGET_MOBILE diff --git a/shaders/src/tone_mapping.fs b/shaders/src/tone_mapping.fs index e6ba29344c..1c6498e888 100644 --- a/shaders/src/tone_mapping.fs +++ b/shaders/src/tone_mapping.fs @@ -3,22 +3,27 @@ //------------------------------------------------------------------------------ // Operators for LDR output + +// Operators with built-in sRGB #define TONE_MAPPING_UNREAL 0 #define TONE_MAPPING_FILMIC_ALU 1 -#define TONE_MAPPING_LINEAR 2 // Operators with built-in sRGB go above -#define TONE_MAPPING_REINHARD 3 -#define TONE_MAPPING_ACES 4 +#define TONE_MAPPING_LUT 2 + +// Operators without built-in sRGB +#define TONE_MAPPING_LINEAR 3 +#define TONE_MAPPING_REINHARD 4 +#define TONE_MAPPING_ACES 5 // Operators for HDR output -#define TONE_MAPPING_ACES_REC2020_1K 5 +#define TONE_MAPPING_ACES_REC2020_1K 6 // Debug operators #define TONE_MAPPING_DISPLAY_RANGE 9 #ifdef TARGET_MOBILE - #define TONE_MAPPING_OPERATOR TONE_MAPPING_UNREAL + #define TONE_MAPPING_OPERATOR TONE_MAPPING_LUT #else - #define TONE_MAPPING_OPERATOR TONE_MAPPING_ACES + #define TONE_MAPPING_OPERATOR TONE_MAPPING_LUT #endif //------------------------------------------------------------------------------ @@ -59,6 +64,16 @@ vec3 Tonemap_ACES(const vec3 x) { return (x * (a * x + b)) / (x * (c * x + d) + e); } +vec3 Tonemap_LUT(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; +} + //------------------------------------------------------------------------------ // Tone-mapping operators for HDR output //------------------------------------------------------------------------------ @@ -145,7 +160,7 @@ vec3 Tonemap_DisplayRange(const vec3 x) { * pre-exposed. Our HDR to LDR tone mapping operators are designed to tone-map * the range [0..~8] to [0..1]. */ -vec3 tonemap(const vec3 x) { +vec3 tonemap(mediump sampler3D lut, const vec3 x) { #if TONE_MAPPING_OPERATOR == TONE_MAPPING_UNREAL return Tonemap_Unreal(x); #elif TONE_MAPPING_OPERATOR == TONE_MAPPING_FILMIC_ALU @@ -160,6 +175,8 @@ vec3 tonemap(const vec3 x) { return Tonemap_ACES_Rec2020_1k(x); #elif TONE_MAPPING_OPERATOR == TONE_MAPPING_DISPLAY_RANGE return Tonemap_DisplayRange(x); +#elif TONE_MAPPING_OPERATOR == TONE_MAPPING_LUT + return Tonemap_LUT(lut, x); #endif }