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.
This commit is contained in:
Pixelflinger
2020-05-19 19:38:02 -07:00
committed by Mathias Agopian
parent 0f6a06a2e1
commit 62a591bc73
9 changed files with 218 additions and 18 deletions

View File

@@ -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

View File

@@ -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 <private/filament/SibGenerator.h>
@@ -145,6 +146,8 @@ void PostProcessManager::init() noexcept {
*static_cast<uint32_t *>(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<FrameGraphTexture> 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<HwRenderPrimitive> 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);

View File

@@ -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<backend::HwTexture> mDummyZeroTexture;
size_t mSeparableGaussianBlurKernelStorageSize = 0;
// eventually this will be a separate object
Tonemapper* mTonemapper = nullptr;
};
} // namespace filament

111
filament/src/Tonemapper.cpp Normal file
View File

@@ -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 <filament/Color.h>
#include <utils/JobSystem.h>
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<float, std::milli> 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

View File

@@ -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 <backend/DriverEnums.h>
#include <backend/Handle.h>
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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}