Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AgX tonemapper #7236

Merged
merged 6 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEW_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).

- engine: Added parameter for configuring JobSystem thread count
- engine: In Java, introduce Engine.Builder
- engine: New tone mapper: `AgXTonemapper`.
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 {
romainguy marked this conversation as resolved.
Show resolved Hide resolved

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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: our existing color grading APIs let you apply a CDL.

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);
bejado marked this conversation as resolved.
Show resolved Hide resolved

// 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
Loading