Skip to content

Commit b5492d0

Browse files
add white balance adjustments in display
1 parent 76324cc commit b5492d0

File tree

4 files changed

+78
-19
lines changed

4 files changed

+78
-19
lines changed

src/films/display.cpp

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,16 @@ class DisplayInstance final : public Film::Instance {
117117
luisa::unique_ptr<Film::Instance> _base;
118118
luisa::unique_ptr<ImGuiWindow> _window;
119119
Image<float> _framebuffer;
120-
Shader2D<int, bool, float3> _blit;
120+
Shader2D<int, bool, float3, float3> _blit;
121121
Shader2D<Image<float>> _clear;
122122
Clock _clock;
123123
Stream *_stream{};
124124
ImTextureID _background{};
125+
mutable float3 _exposure{};
125126
mutable double _last_frame_time{};
127+
mutable float2 _white_balance{};
128+
mutable float _brightness{};
126129
mutable int _tone_mapping{};
127-
mutable float3 _exposure{};
128130
mutable int _background_fit{};
129131
mutable bool _link_rgb_exposure{true};
130132

@@ -180,9 +182,6 @@ class DisplayInstance final : public Film::Instance {
180182
// All values used to derive this implementation are sourced from Troy’s initial AgX implementation/OCIO config file available here:
181183
// https://github.com/sobotka/AgX
182184

183-
// 0: Default, 1: Golden, 2: Punchy
184-
#define AGX_LOOK 0
185-
186185
// Mean error^2: 1.85907662e-06
187186
static Callable agxDefaultContrastApprox = [](Float3 x) noexcept {
188187
auto x2 = x * x;
@@ -263,6 +262,11 @@ class DisplayInstance final : public Film::Instance {
263262
color * 12.92f,
264263
1.055f * pow(color, 1.f / 2.4f) - .055f);
265264
}
265+
[[nodiscard]] static auto _apply_white_balance(Expr<float3> rgb, Expr<float> brightness, Expr<float> temperature, Expr<float> tint) noexcept {
266+
auto lab = cie_xyz_to_lab(linear_srgb_to_cie_xyz(rgb));
267+
auto v = make_float3(clamp(lab.x + brightness, 0.f, 100.f), lab.y + tint, lab.z + temperature);
268+
return cie_xyz_to_linear_srgb(lab_to_cie_xyz(v));
269+
}
266270

267271
public:
268272
DisplayInstance(const Pipeline &pipeline, const Display *film,
@@ -296,9 +300,13 @@ class DisplayInstance final : public Film::Instance {
296300
.back_buffers = d->back_buffers()});
297301
_framebuffer = device.create_image<float>(_window->swapchain().backend_storage(), size);
298302
_background = reinterpret_cast<ImTextureID>(_window->register_texture(_framebuffer, TextureSampler::linear_point_zero()));
299-
_blit = device.compile<2>([base = _base.get(), &framebuffer = _framebuffer](Int tonemapping, Bool ldr, Float3 scale) noexcept {
303+
_blit = device.compile<2>([base = _base.get(), &framebuffer = _framebuffer](Int tonemapping, Bool ldr, Float3 scale, Float3 white_balance) noexcept {
300304
auto p = dispatch_id().xy();
301305
auto color = base->read(p).average * scale;
306+
$if (any(white_balance != 0.f)) {
307+
color = _apply_white_balance(color, white_balance.z, white_balance.x, white_balance.y);
308+
};
309+
color = max(color, 0.f);
302310
$switch (tonemapping) {
303311
$case (static_cast<int>(Display::ToneMapping::NONE)) {};
304312
$case (static_cast<int>(Display::ToneMapping::UNCHARTED2)) { color = _tone_mapping_uncharted2(color); };
@@ -363,7 +371,7 @@ class DisplayInstance final : public Film::Instance {
363371
auto scale = _link_rgb_exposure ? make_float3(luisa::exp2(_exposure.x)) : luisa::exp2(_exposure);
364372
auto is_ldr = _window->framebuffer().storage() != PixelStorage::FLOAT4;
365373
auto size = _framebuffer.size();
366-
*_stream << _blit(_tone_mapping, is_ldr, scale).dispatch(size);
374+
*_stream << _blit(_tone_mapping, is_ldr, scale, make_float3(_white_balance, _brightness)).dispatch(size);
367375
auto viewport = ImGui::GetMainViewport();
368376
auto bg_size = _compute_background_size(viewport, _background_fit);
369377
auto p_min = make_float2(viewport->Pos.x, viewport->Pos.y) +
@@ -373,16 +381,30 @@ class DisplayInstance final : public Film::Instance {
373381
ImVec2{p_min.x + bg_size.x, p_min.y + bg_size.y});
374382
ImGui::Begin("Console", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
375383
{
376-
ImGui::Text("Display FPS: %.2f", ImGui::GetIO().Framerate);
384+
ImGui::Text("Render: %ux%u", node()->resolution().x, node()->resolution().y);
385+
ImGui::Text("Display: %ux%u (%.2ffps)", size.x, size.y, ImGui::GetIO().Framerate);
386+
// Exposure
387+
if (ImGui::Button("Reset##Exposure")) { _exposure = make_float3(); }
388+
ImGui::SameLine();
377389
if (_link_rgb_exposure) {
378390
ImGui::SliderFloat("Exposure", &_exposure.x, -10.f, 10.f);
379391
} else {
380392
ImGui::SliderFloat3("Exposure", &_exposure.x, -10.f, 10.f);
381393
}
382394
ImGui::SameLine();
383395
ImGui::Checkbox("Link", &_link_rgb_exposure);
396+
// Brightness
397+
if (ImGui::Button("Reset##Brightness")) { _brightness = 0.f; }
398+
ImGui::SameLine();
399+
ImGui::SliderFloat("Brightness", &_brightness, -100.f, 100.f);
400+
// Temperature
401+
if (ImGui::Button("Reset##Temperature")) { _white_balance.x = 0.f; }
402+
ImGui::SameLine();
403+
ImGui::SliderFloat("Temperature", &_white_balance.x, -100.f, 100.f);
404+
// Tint
405+
if (ImGui::Button("Reset##Tint")) { _white_balance.y = 0.f; }
384406
ImGui::SameLine();
385-
if (ImGui::Button("Reset")) { _exposure = make_float3(); }
407+
ImGui::SliderFloat("Tint", &_white_balance.y, -100.f, 100.f);
386408
constexpr const char *const tone_mapping_names[] = {"None", "Uncharted2", "ACES", "AgX", "AgX (Golden)", "AgX (Punchy)"};
387409
ImGui::Combo("Tone Mapping", &_tone_mapping, tone_mapping_names, std::size(tone_mapping_names));
388410
constexpr const char *const fit_names[] = {"Fill", "Fit", "Stretch"};

src/spectra/hero.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,14 @@ class RGB2SpectrumTable {
8484
};
8585
return c;
8686
};
87-
return make_float4(decode(Expr{array}, base_index, rgb), srgb_to_cie_y(rgb));
87+
return make_float4(decode(Expr{array}, base_index, rgb), linear_srgb_to_cie_y(rgb));
8888
}
8989

9090
[[nodiscard]] float4 decode_albedo(float3 rgb_in) const noexcept {
9191
auto rgb = clamp(rgb_in, 0.0f, 1.0f);
9292
if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) {
9393
auto s = (rgb[0] - 0.5f) / std::sqrt(rgb[0] * (1.0f - rgb[0]));
94-
return make_float4(0.0f, 0.0f, s, srgb_to_cie_y(rgb));
94+
return make_float4(0.0f, 0.0f, s, linear_srgb_to_cie_y(rgb));
9595
}
9696
// Find maximum component and compute remapped component values
9797
auto maxc = (rgb[0] > rgb[1]) ?
@@ -124,7 +124,7 @@ class RGB2SpectrumTable {
124124
lerp(co(0, 1, 1), co(1, 1, 1), dx), dy),
125125
dz);
126126
}
127-
return make_float4(c, srgb_to_cie_y(rgb));
127+
return make_float4(c, linear_srgb_to_cie_y(rgb));
128128
}
129129

130130
[[nodiscard]] Float4 decode_unbounded(

src/spectra/srgb.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,31 +36,31 @@ struct SRGBSpectrumInstance final : public Spectrum::Instance {
3636
SampledSpectrum s{node()->dimension()};
3737
auto sv = saturate(v.xyz());
3838
for (auto i = 0u; i < 3u; i++) { s[i] = sv[i]; }
39-
return {.value = s, .strength = srgb_to_cie_y(sv)};
39+
return {.value = s, .strength = linear_srgb_to_cie_y(sv)};
4040
}
4141
[[nodiscard]] Spectrum::Decode decode_unbounded(
4242
const SampledWavelengths &swl, Expr<float4> v) const noexcept override {
4343
SampledSpectrum s{node()->dimension()};
4444
auto sv = v.xyz();
4545
for (auto i = 0u; i < 3u; i++) { s[i] = sv[i]; }
46-
return {.value = s, .strength = srgb_to_cie_y(sv)};
46+
return {.value = s, .strength = linear_srgb_to_cie_y(sv)};
4747
}
4848
[[nodiscard]] Spectrum::Decode decode_illuminant(
4949
const SampledWavelengths &swl, Expr<float4> v) const noexcept override {
5050
auto sv = max(v.xyz(), 0.f);
5151
SampledSpectrum s{node()->dimension()};
5252
for (auto i = 0u; i < 3u; i++) { s[i] = sv[i]; }
53-
return {.value = s, .strength = srgb_to_cie_y(sv)};
53+
return {.value = s, .strength = linear_srgb_to_cie_y(sv)};
5454
}
5555
[[nodiscard]] Float cie_y(
5656
const SampledWavelengths &swl,
5757
const SampledSpectrum &sp) const noexcept override {
58-
return srgb_to_cie_y(srgb(swl, sp));
58+
return linear_srgb_to_cie_y(srgb(swl, sp));
5959
}
6060
[[nodiscard]] Float3 cie_xyz(
6161
const SampledWavelengths &swl,
6262
const SampledSpectrum &sp) const noexcept override {
63-
return srgb_to_cie_xyz(srgb(swl, sp));
63+
return linear_srgb_to_cie_xyz(srgb(swl, sp));
6464
}
6565
[[nodiscard]] Float3 srgb(
6666
const SampledWavelengths &swl,

src/util/colorspace.h

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,55 @@ template<typename T>
1919
}
2020

2121
template<typename T>
22-
[[nodiscard]] inline auto srgb_to_cie_y(T &&rgb) noexcept {
22+
[[nodiscard]] inline auto linear_srgb_to_cie_y(T &&rgb) noexcept {
2323
constexpr auto m = make_float3(0.212671f, 0.715160f, 0.072169f);
2424
return dot(m, std::forward<T>(rgb));
2525
}
2626

2727
template<typename T>
28-
[[nodiscard]] inline auto srgb_to_cie_xyz(T &&rgb) noexcept {
28+
[[nodiscard]] inline auto linear_srgb_to_cie_xyz(T &&rgb) noexcept {
2929
constexpr auto m = make_float3x3(
3030
0.412453f, 0.212671f, 0.019334f,
3131
0.357580f, 0.715160f, 0.119193f,
3232
0.180423f, 0.072169f, 0.950227f);
3333
return m * std::forward<T>(rgb);
3434
}
3535

36+
template<typename T>
37+
[[nodiscard]] inline auto cie_xyz_to_lab(T &&xyz) noexcept {
38+
static constexpr auto delta = 6.f / 29.f;
39+
auto f = [](const auto &x) noexcept {
40+
constexpr auto d3 = delta * delta * delta;
41+
constexpr auto one_over_3d2 = 1.f / (3.f * delta * delta);
42+
if constexpr (compute::is_dsl_v<T>) {
43+
return compute::ite(x > d3, compute::pow(x, 1.f / 3.f), one_over_3d2 * x + (4.f / 29.f));
44+
} else {
45+
return x > d3 ? std::pow(x, 1.f / 3.f) : one_over_3d2 * x + 4.f / 29.f;
46+
}
47+
};
48+
auto f_y_yn = f(xyz.y);
49+
auto l = 116.f * f_y_yn - 16.f;
50+
auto a = 500.f * (f(xyz.x / .950489f) - f_y_yn);
51+
auto b = 200.f * (f_y_yn - f(xyz.z / 1.08884f));
52+
return make_float3(l, a, b);
53+
}
54+
55+
template<typename T>
56+
[[nodiscard]] inline auto lab_to_cie_xyz(T &&lab) noexcept {
57+
static constexpr auto delta = 6.f / 29.f;
58+
auto f = [](const auto &x) noexcept {
59+
constexpr auto three_dd = 3.f * delta * delta;
60+
if constexpr (compute::is_dsl_v<T>) {
61+
return compute::ite(x > delta, x * x * x, three_dd * x - (three_dd * 4.f / 29.f));
62+
} else {
63+
return x > delta ? x * x * x : three_dd * x - three_dd * 4.f / 29.f;
64+
}
65+
};
66+
auto v = (lab.x + 16.f) / 116.f;
67+
auto x = .950489f * f(v + lab.y / 500.f);
68+
auto y = f(v);
69+
auto z = 1.08884f * f(v - lab.z / 200.f);
70+
return make_float3(x, y, z);
71+
}
72+
3673
}// namespace luisa::render

0 commit comments

Comments
 (0)