Skip to content

Commit

Permalink
Add AgX tonemapper (google#7236)
Browse files Browse the repository at this point in the history
  • Loading branch information
bejado authored and plepers committed Dec 9, 2023
1 parent 7233628 commit 19dc2a0
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 9 deletions.
3 changes: 2 additions & 1 deletion NEW_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).

- engine: Added parameter for configuring JobSystem thread count
- engine: In Java, introduce Engine.Builder
- matinfo: Add support for viewing ESSL 1.0 shaders
- engine: New tone mapper: `AgXTonemapper`.
- matinfo: Add support for viewing ESSL 1.0 shaders
5 changes: 5 additions & 0 deletions android/filament-android/src/main/cpp/ToneMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ Java_com_google_android_filament_ToneMapper_nCreateFilmicToneMapper(JNIEnv*, jcl
return (jlong) new FilmicToneMapper();
}

extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_ToneMapper_nCreateAgxToneMapper(JNIEnv*, jclass, jint look) {
return (jlong) new AgxToneMapper(AgxToneMapper::AgxLook(look));
}

extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_ToneMapper_nCreateGenericToneMapper(JNIEnv*, jclass,
jfloat contrast, jfloat midGrayIn, jfloat midGrayOut, jfloat hdrMax) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,45 @@ public Filmic() {
}
}

/**
* AgX tone mapping operator.
*/
public static class Agx extends ToneMapper {

public enum AgxLook {
/**
* Base contrast with no look applied
*/
NONE,

/**
* A punchy and more chroma laden look for sRGB displays
*/
PUNCHY,

/**
* A golden tinted, slightly washed look for BT.1886 displays
*/
GOLDEN
}

/**
* Builds a new AgX tone mapper with no look applied.
*/
public Agx() {
this(AgxLook.NONE);
}

/**
* Builds a new AgX tone mapper.
*
* @param look: an optional creative adjustment to contrast and saturation
*/
public Agx(AgxLook look) {
super(nCreateAgxToneMapper(look.ordinal()));
}
}

/**
* Generic tone mapping operator that gives control over the tone mapping
* curve. This operator can be used to control the aesthetics of the final
Expand Down Expand Up @@ -194,6 +233,7 @@ public void setHdrMax(float hdrMax) {
private static native long nCreateACESToneMapper();
private static native long nCreateACESLegacyToneMapper();
private static native long nCreateFilmicToneMapper();
private static native long nCreateAgxToneMapper(int look);
private static native long nCreateGenericToneMapper(
float contrast, float midGrayIn, float midGrayOut, float hdrMax);

Expand Down
24 changes: 24 additions & 0 deletions filament/include/filament/ToneMapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,30 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper {
math::float3 operator()(math::float3 x) const noexcept override;
};

/**
* AgX tone mapping operator.
*/
struct UTILS_PUBLIC AgxToneMapper final : public ToneMapper {

enum class AgxLook : uint8_t {
NONE = 0, //!< Base contrast with no look applied
PUNCHY, //!< A punchy and more chroma laden look for sRGB displays
GOLDEN //!< A golden tinted, slightly washed look for BT.1886 displays
};

/**
* Builds a new AgX tone mapper.
*
* @param look an optional creative adjustment to contrast and saturation
*/
explicit AgxToneMapper(AgxLook look = AgxLook::NONE) noexcept;
~AgxToneMapper() noexcept final;

math::float3 operator()(math::float3 x) const noexcept override;

AgxLook look;
};

/**
* Generic tone mapping operator that gives control over the tone mapping
* curve. This operator can be used to control the aesthetics of the final
Expand Down
100 changes: 100 additions & 0 deletions filament/src/ToneMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,106 @@ float3 FilmicToneMapper::operator()(math::float3 x) const noexcept {
return (x * (a * x + b)) / (x * (c * x + d) + e);
}

//------------------------------------------------------------------------------
// AgX tone mapper
//------------------------------------------------------------------------------

AgxToneMapper::AgxToneMapper(AgxToneMapper::AgxLook look) noexcept : look(look) {}
AgxToneMapper::~AgxToneMapper() noexcept = default;

// These matrices taken from Blender's implementation of AgX, which works with Rec.2020 primaries.
// https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBaseRec2020.py
constexpr mat3f AgXInsetMatrix {
0.856627153315983, 0.137318972929847, 0.11189821299995,
0.0951212405381588, 0.761241990602591, 0.0767994186031903,
0.0482516061458583, 0.101439036467562, 0.811302368396859
};
constexpr mat3f AgXOutsetMatrixInv {
0.899796955911611, 0.11142098895748, 0.11142098895748,
0.0871996192028351, 0.875575586156966, 0.0871996192028349,
0.013003424885555, 0.0130034248855548, 0.801379391839686
};
constexpr mat3f AgXOutsetMatrix { inverse(AgXOutsetMatrixInv) };

// LOG2_MIN = -10.0
// LOG2_MAX = +6.5
// MIDDLE_GRAY = 0.18
const float AgxMinEv = -12.47393f; // log2(pow(2, LOG2_MIN) * MIDDLE_GRAY)
const float AgxMaxEv = 4.026069f; // log2(pow(2, LOG2_MAX) * MIDDLE_GRAY)

// Adapted from https://iolite-engine.com/blog_posts/minimal_agx_implementation
float3 agxDefaultContrastApprox(float3 x) {
float3 x2 = x * x;
float3 x4 = x2 * x2;
float3 x6 = x4 * x2;
return - 17.86 * x6 * x
+ 78.01 * x6
- 126.7 * x4 * x
+ 92.06 * x4
- 28.72 * x2 * x
+ 4.361 * x2
- 0.1718 * x
+ 0.002857;
}

// Adapted from https://iolite-engine.com/blog_posts/minimal_agx_implementation
float3 agxLook(float3 val, AgxToneMapper::AgxLook look) {
if (look == AgxToneMapper::AgxLook::NONE) {
return val;
}

const float3 lw = float3(0.2126, 0.7152, 0.0722);
float luma = dot(val, lw);

// Default
float3 offset = float3(0.0);
float3 slope = float3(1.0);
float3 power = float3(1.0);
float sat = 1.0;

if (look == AgxToneMapper::AgxLook::GOLDEN) {
slope = float3(1.0, 0.9, 0.5);
power = float3(0.8);
sat = 1.3;
}
if (look == AgxToneMapper::AgxLook::PUNCHY) {
slope = float3(1.0);
power = float3(1.35, 1.35, 1.35);
sat = 1.4;
}

// ASC CDL
val = pow(val * slope + offset, power);
return luma + sat * (val - luma);
}

float3 AgxToneMapper::operator()(float3 v) const noexcept {
// Ensure no negative values
v = max(float3(0.0), v);

v = AgXInsetMatrix * v;

// Log2 encoding
v = max(v, 1E-10); // avoid 0 or negative numbers for log2
v = log2(v);
v = (v - AgxMinEv) / (AgxMaxEv - AgxMinEv);

v = clamp(v, 0, 1);

// Apply sigmoid
v = agxDefaultContrastApprox(v);

// Apply AgX look
v = agxLook(v, look);

v = AgXOutsetMatrix * v;

// Linearize
v = pow(max(float3(0.0), v), 2.2);

return v;
}

//------------------------------------------------------------------------------
// Display range tone mapper
//------------------------------------------------------------------------------
Expand Down
17 changes: 12 additions & 5 deletions libs/viewer/include/viewer/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ enum class ToneMapping : uint8_t {
ACES_LEGACY = 1,
ACES = 2,
FILMIC = 3,
GENERIC = 4,
DISPLAY_RANGE = 5,
AGX = 4,
GENERIC = 5,
DISPLAY_RANGE = 6,
};

using AmbientOcclusionOptions = filament::View::AmbientOcclusionOptions;
Expand Down Expand Up @@ -114,8 +115,14 @@ struct GenericToneMapperSettings {
float midGrayIn = 0.18f;
float midGrayOut = 0.215f;
float hdrMax = 10.0f;
bool operator!=(const GenericToneMapperSettings &rhs) const { return !(rhs == *this); }
bool operator==(const GenericToneMapperSettings &rhs) const;
bool operator!=(const GenericToneMapperSettings& rhs) const { return !(rhs == *this); }
bool operator==(const GenericToneMapperSettings& rhs) const;
};

struct AgxToneMapperSettings {
AgxToneMapper::AgxLook look = AgxToneMapper::AgxLook::NONE;
bool operator!=(const AgxToneMapperSettings& rhs) const { return !(rhs == *this); }
bool operator==(const AgxToneMapperSettings& rhs) const;
};

struct ColorGradingSettings {
Expand All @@ -127,7 +134,7 @@ struct ColorGradingSettings {
filament::ColorGrading::QualityLevel quality = filament::ColorGrading::QualityLevel::MEDIUM;
ToneMapping toneMapping = ToneMapping::ACES_LEGACY;
bool padding0{};
bool padding1{};
AgxToneMapperSettings agxToneMapper;
color::ColorSpace colorspace = Rec709-sRGB-D65;
GenericToneMapperSettings genericToneMapper;
math::float4 shadows{1.0f, 1.0f, 1.0f, 0.0f};
Expand Down
61 changes: 59 additions & 2 deletions libs/viewer/src/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ToneMapp
else if (0 == compare(tokens[i], jsonChunk, "ACES_LEGACY")) { *out = ToneMapping::ACES_LEGACY; }
else if (0 == compare(tokens[i], jsonChunk, "ACES")) { *out = ToneMapping::ACES; }
else if (0 == compare(tokens[i], jsonChunk, "FILMIC")) { *out = ToneMapping::FILMIC; }
else if (0 == compare(tokens[i], jsonChunk, "AGX")) { *out = ToneMapping::AGX; }
else if (0 == compare(tokens[i], jsonChunk, "GENERIC")) { *out = ToneMapping::GENERIC; }
else if (0 == compare(tokens[i], jsonChunk, "DISPLAY_RANGE")) { *out = ToneMapping::DISPLAY_RANGE; }
else {
Expand Down Expand Up @@ -130,6 +131,36 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, GenericT
return i;
}

static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, AgxToneMapper::AgxLook* out) {
if (0 == compare(tokens[i], jsonChunk, "NONE")) { *out = AgxToneMapper::AgxLook::NONE; }
else if (0 == compare(tokens[i], jsonChunk, "PUNCHY")) { *out = AgxToneMapper::AgxLook::PUNCHY; }
else if (0 == compare(tokens[i], jsonChunk, "GOLDEN")) { *out = AgxToneMapper::AgxLook::GOLDEN; }
else {
slog.w << "Invalid AgxLook: '" << STR(tokens[i], jsonChunk) << "'" << io::endl;
}
return i + 1;
}

static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, AgxToneMapperSettings* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int size = tokens[i++].size;
for (int j = 0; j < size; ++j) {
const jsmntok_t tok = tokens[i];
CHECK_KEY(tok);
if (compare(tok, jsonChunk, "look") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->look);
} else {
slog.w << "Invalid AgX tone mapper key: '" << STR(tok, jsonChunk) << "'" << io::endl;
i = parse(tokens, i + 1);
}
if (i < 0) {
slog.e << "Invalid AgX tone mapper value: '" << STR(tok, jsonChunk) << "'" << io::endl;
return i;
}
}
return i;
}

static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ColorGradingSettings* out) {
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int size = tokens[i++].size;
Expand All @@ -146,6 +177,8 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ColorGra
i = parse(tokens, i + 1, jsonChunk, &out->toneMapping);
} else if (compare(tok, jsonChunk, "genericToneMapper") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->genericToneMapper);
} else if (compare(tok, jsonChunk, "agxToneMapper") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->agxToneMapper);
} else if (compare(tok, jsonChunk, "luminanceScaling") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->luminanceScaling);
} else if (compare(tok, jsonChunk, "gamutMapping") == 0) {
Expand Down Expand Up @@ -619,6 +652,7 @@ constexpr ToneMapper* createToneMapper(const ColorGradingSettings& settings) noe
case ToneMapping::ACES_LEGACY: return new ACESLegacyToneMapper;
case ToneMapping::ACES: return new ACESToneMapper;
case ToneMapping::FILMIC: return new FilmicToneMapper;
case ToneMapping::AGX: return new AgxToneMapper(settings.agxToneMapper.look);
case ToneMapping::GENERIC: return new GenericToneMapper(
settings.genericToneMapper.contrast,
settings.genericToneMapper.midGrayIn,
Expand Down Expand Up @@ -673,6 +707,7 @@ static std::ostream& operator<<(std::ostream& out, ToneMapping in) {
case ToneMapping::ACES_LEGACY: return out << "\"ACES_LEGACY\"";
case ToneMapping::ACES: return out << "\"ACES\"";
case ToneMapping::FILMIC: return out << "\"FILMIC\"";
case ToneMapping::AGX: return out << "\"AGX\"";
case ToneMapping::GENERIC: return out << "\"GENERIC\"";
case ToneMapping::DISPLAY_RANGE: return out << "\"DISPLAY_RANGE\"";
}
Expand All @@ -688,13 +723,29 @@ static std::ostream& operator<<(std::ostream& out, const GenericToneMapperSettin
<< "}";
}

static std::ostream& operator<<(std::ostream& out, AgxToneMapper::AgxLook in) {
switch (in) {
case AgxToneMapper::AgxLook::NONE: return out << "\"NONE\"";
case AgxToneMapper::AgxLook::PUNCHY: return out << "\"PUNCHY\"";
case AgxToneMapper::AgxLook::GOLDEN: return out << "\"GOLDEN\"";
}
return out << "\"INVALID\"";
}

static std::ostream& operator<<(std::ostream& out, const AgxToneMapperSettings& in) {
return out << "{\n"
<< "\"look\": " << (in.look) << ",\n"
<< "}";
}

static std::ostream& operator<<(std::ostream& out, const ColorGradingSettings& in) {
return out << "{\n"
<< "\"enabled\": " << to_string(in.enabled) << ",\n"
<< "\"colorspace\": " << to_string(in.colorspace) << ",\n"
<< "\"quality\": " << (in.quality) << ",\n"
<< "\"toneMapping\": " << (in.toneMapping) << ",\n"
<< "\"genericToneMapper\": " << (in.genericToneMapper) << ",\n"
<< "\"agxToneMapper\": " << (in.agxToneMapper) << ",\n"
<< "\"luminanceScaling\": " << to_string(in.luminanceScaling) << ",\n"
<< "\"gamutMapping\": " << to_string(in.gamutMapping) << ",\n"
<< "\"exposure\": " << (in.exposure) << ",\n"
Expand Down Expand Up @@ -854,15 +905,20 @@ static std::ostream& operator<<(std::ostream& out, const Settings& in) {
<< "}";
}

bool GenericToneMapperSettings::operator==(const GenericToneMapperSettings &rhs) const {
bool GenericToneMapperSettings::operator==(const GenericToneMapperSettings& rhs) const {
static_assert(sizeof(GenericToneMapperSettings) == 16, "Please update Settings.cpp");
return contrast == rhs.contrast &&
midGrayIn == rhs.midGrayIn &&
midGrayOut == rhs.midGrayOut &&
hdrMax == rhs.hdrMax;
}

bool ColorGradingSettings::operator==(const ColorGradingSettings &rhs) const {
bool AgxToneMapperSettings::operator==(const AgxToneMapperSettings& rhs) const {
static_assert(sizeof(AgxToneMapperSettings) == 1, "Please update Settings.cpp");
return look == rhs.look;
}

bool ColorGradingSettings::operator==(const ColorGradingSettings& rhs) const {
// If you had to fix the following codeline, then you likely also need to update the
// implementation of operator==.
static_assert(sizeof(ColorGradingSettings) == 312, "Please update Settings.cpp");
Expand All @@ -871,6 +927,7 @@ bool ColorGradingSettings::operator==(const ColorGradingSettings &rhs) const {
quality == rhs.quality &&
toneMapping == rhs.toneMapping &&
genericToneMapper == rhs.genericToneMapper &&
agxToneMapper == rhs.agxToneMapper &&
luminanceScaling == rhs.luminanceScaling &&
gamutMapping == rhs.gamutMapping &&
exposure == rhs.exposure &&
Expand Down
Loading

0 comments on commit 19dc2a0

Please sign in to comment.