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 }