diff --git a/src/films/display.cpp b/src/films/display.cpp index 27e9abb9..229063c4 100644 --- a/src/films/display.cpp +++ b/src/films/display.cpp @@ -117,14 +117,16 @@ class DisplayInstance final : public Film::Instance { luisa::unique_ptr _base; luisa::unique_ptr _window; Image _framebuffer; - Shader2D _blit; + Shader2D _blit; Shader2D> _clear; Clock _clock; Stream *_stream{}; ImTextureID _background{}; + mutable float3 _exposure{}; mutable double _last_frame_time{}; + mutable float2 _white_balance{}; + mutable float _brightness{}; mutable int _tone_mapping{}; - mutable float3 _exposure{}; mutable int _background_fit{}; mutable bool _link_rgb_exposure{true}; @@ -180,9 +182,6 @@ class DisplayInstance final : public Film::Instance { // All values used to derive this implementation are sourced from Troy’s initial AgX implementation/OCIO config file available here: // https://github.com/sobotka/AgX -// 0: Default, 1: Golden, 2: Punchy -#define AGX_LOOK 0 - // Mean error^2: 1.85907662e-06 static Callable agxDefaultContrastApprox = [](Float3 x) noexcept { auto x2 = x * x; @@ -263,6 +262,11 @@ class DisplayInstance final : public Film::Instance { color * 12.92f, 1.055f * pow(color, 1.f / 2.4f) - .055f); } + [[nodiscard]] static auto _apply_white_balance(Expr rgb, Expr brightness, Expr temperature, Expr tint) noexcept { + auto lab = cie_xyz_to_lab(linear_srgb_to_cie_xyz(rgb)); + auto v = make_float3(clamp(lab.x + brightness, 0.f, 100.f), lab.y + tint, lab.z + temperature); + return cie_xyz_to_linear_srgb(lab_to_cie_xyz(v)); + } public: DisplayInstance(const Pipeline &pipeline, const Display *film, @@ -296,9 +300,13 @@ class DisplayInstance final : public Film::Instance { .back_buffers = d->back_buffers()}); _framebuffer = device.create_image(_window->swapchain().backend_storage(), size); _background = reinterpret_cast(_window->register_texture(_framebuffer, TextureSampler::linear_point_zero())); - _blit = device.compile<2>([base = _base.get(), &framebuffer = _framebuffer](Int tonemapping, Bool ldr, Float3 scale) noexcept { + _blit = device.compile<2>([base = _base.get(), &framebuffer = _framebuffer](Int tonemapping, Bool ldr, Float3 scale, Float3 white_balance) noexcept { auto p = dispatch_id().xy(); auto color = base->read(p).average * scale; + $if (any(white_balance != 0.f)) { + color = _apply_white_balance(color, white_balance.z, white_balance.x, white_balance.y); + }; + color = max(color, 0.f); $switch (tonemapping) { $case (static_cast(Display::ToneMapping::NONE)) {}; $case (static_cast(Display::ToneMapping::UNCHARTED2)) { color = _tone_mapping_uncharted2(color); }; @@ -363,7 +371,7 @@ class DisplayInstance final : public Film::Instance { auto scale = _link_rgb_exposure ? make_float3(luisa::exp2(_exposure.x)) : luisa::exp2(_exposure); auto is_ldr = _window->framebuffer().storage() != PixelStorage::FLOAT4; auto size = _framebuffer.size(); - *_stream << _blit(_tone_mapping, is_ldr, scale).dispatch(size); + *_stream << _blit(_tone_mapping, is_ldr, scale, make_float3(_white_balance, _brightness)).dispatch(size); auto viewport = ImGui::GetMainViewport(); auto bg_size = _compute_background_size(viewport, _background_fit); auto p_min = make_float2(viewport->Pos.x, viewport->Pos.y) + @@ -373,7 +381,11 @@ class DisplayInstance final : public Film::Instance { ImVec2{p_min.x + bg_size.x, p_min.y + bg_size.y}); ImGui::Begin("Console", nullptr, ImGuiWindowFlags_AlwaysAutoResize); { - ImGui::Text("Display FPS: %.2f", ImGui::GetIO().Framerate); + ImGui::Text("Render: %ux%u", node()->resolution().x, node()->resolution().y); + ImGui::Text("Display: %ux%u (%.2ffps)", size.x, size.y, ImGui::GetIO().Framerate); + // Exposure + if (ImGui::Button("Reset##Exposure")) { _exposure = make_float3(); } + ImGui::SameLine(); if (_link_rgb_exposure) { ImGui::SliderFloat("Exposure", &_exposure.x, -10.f, 10.f); } else { @@ -381,8 +393,18 @@ class DisplayInstance final : public Film::Instance { } ImGui::SameLine(); ImGui::Checkbox("Link", &_link_rgb_exposure); + // Brightness + if (ImGui::Button("Reset##Brightness")) { _brightness = 0.f; } + ImGui::SameLine(); + ImGui::SliderFloat("Brightness", &_brightness, -100.f, 100.f); + // Temperature + if (ImGui::Button("Reset##Temperature")) { _white_balance.x = 0.f; } + ImGui::SameLine(); + ImGui::SliderFloat("Temperature", &_white_balance.x, -100.f, 100.f); + // Tint + if (ImGui::Button("Reset##Tint")) { _white_balance.y = 0.f; } ImGui::SameLine(); - if (ImGui::Button("Reset")) { _exposure = make_float3(); } + ImGui::SliderFloat("Tint", &_white_balance.y, -100.f, 100.f); constexpr const char *const tone_mapping_names[] = {"None", "Uncharted2", "ACES", "AgX", "AgX (Golden)", "AgX (Punchy)"}; ImGui::Combo("Tone Mapping", &_tone_mapping, tone_mapping_names, std::size(tone_mapping_names)); constexpr const char *const fit_names[] = {"Fill", "Fit", "Stretch"}; diff --git a/src/spectra/hero.cpp b/src/spectra/hero.cpp index ddf309ee..625decb1 100644 --- a/src/spectra/hero.cpp +++ b/src/spectra/hero.cpp @@ -84,14 +84,14 @@ class RGB2SpectrumTable { }; return c; }; - return make_float4(decode(Expr{array}, base_index, rgb), srgb_to_cie_y(rgb)); + return make_float4(decode(Expr{array}, base_index, rgb), linear_srgb_to_cie_y(rgb)); } [[nodiscard]] float4 decode_albedo(float3 rgb_in) const noexcept { auto rgb = clamp(rgb_in, 0.0f, 1.0f); if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) { auto s = (rgb[0] - 0.5f) / std::sqrt(rgb[0] * (1.0f - rgb[0])); - return make_float4(0.0f, 0.0f, s, srgb_to_cie_y(rgb)); + return make_float4(0.0f, 0.0f, s, linear_srgb_to_cie_y(rgb)); } // Find maximum component and compute remapped component values auto maxc = (rgb[0] > rgb[1]) ? @@ -124,7 +124,7 @@ class RGB2SpectrumTable { lerp(co(0, 1, 1), co(1, 1, 1), dx), dy), dz); } - return make_float4(c, srgb_to_cie_y(rgb)); + return make_float4(c, linear_srgb_to_cie_y(rgb)); } [[nodiscard]] Float4 decode_unbounded( diff --git a/src/spectra/srgb.cpp b/src/spectra/srgb.cpp index 37d6d079..266991fa 100644 --- a/src/spectra/srgb.cpp +++ b/src/spectra/srgb.cpp @@ -36,31 +36,31 @@ struct SRGBSpectrumInstance final : public Spectrum::Instance { SampledSpectrum s{node()->dimension()}; auto sv = saturate(v.xyz()); for (auto i = 0u; i < 3u; i++) { s[i] = sv[i]; } - return {.value = s, .strength = srgb_to_cie_y(sv)}; + return {.value = s, .strength = linear_srgb_to_cie_y(sv)}; } [[nodiscard]] Spectrum::Decode decode_unbounded( const SampledWavelengths &swl, Expr v) const noexcept override { SampledSpectrum s{node()->dimension()}; auto sv = v.xyz(); for (auto i = 0u; i < 3u; i++) { s[i] = sv[i]; } - return {.value = s, .strength = srgb_to_cie_y(sv)}; + return {.value = s, .strength = linear_srgb_to_cie_y(sv)}; } [[nodiscard]] Spectrum::Decode decode_illuminant( const SampledWavelengths &swl, Expr v) const noexcept override { auto sv = max(v.xyz(), 0.f); SampledSpectrum s{node()->dimension()}; for (auto i = 0u; i < 3u; i++) { s[i] = sv[i]; } - return {.value = s, .strength = srgb_to_cie_y(sv)}; + return {.value = s, .strength = linear_srgb_to_cie_y(sv)}; } [[nodiscard]] Float cie_y( const SampledWavelengths &swl, const SampledSpectrum &sp) const noexcept override { - return srgb_to_cie_y(srgb(swl, sp)); + return linear_srgb_to_cie_y(srgb(swl, sp)); } [[nodiscard]] Float3 cie_xyz( const SampledWavelengths &swl, const SampledSpectrum &sp) const noexcept override { - return srgb_to_cie_xyz(srgb(swl, sp)); + return linear_srgb_to_cie_xyz(srgb(swl, sp)); } [[nodiscard]] Float3 srgb( const SampledWavelengths &swl, diff --git a/src/util/colorspace.h b/src/util/colorspace.h index 68ae2b0b..723e4a00 100644 --- a/src/util/colorspace.h +++ b/src/util/colorspace.h @@ -19,13 +19,13 @@ template } template -[[nodiscard]] inline auto srgb_to_cie_y(T &&rgb) noexcept { +[[nodiscard]] inline auto linear_srgb_to_cie_y(T &&rgb) noexcept { constexpr auto m = make_float3(0.212671f, 0.715160f, 0.072169f); return dot(m, std::forward(rgb)); } template -[[nodiscard]] inline auto srgb_to_cie_xyz(T &&rgb) noexcept { +[[nodiscard]] inline auto linear_srgb_to_cie_xyz(T &&rgb) noexcept { constexpr auto m = make_float3x3( 0.412453f, 0.212671f, 0.019334f, 0.357580f, 0.715160f, 0.119193f, @@ -33,4 +33,41 @@ template return m * std::forward(rgb); } +template +[[nodiscard]] inline auto cie_xyz_to_lab(T &&xyz) noexcept { + static constexpr auto delta = 6.f / 29.f; + auto f = [](const auto &x) noexcept { + constexpr auto d3 = delta * delta * delta; + constexpr auto one_over_3d2 = 1.f / (3.f * delta * delta); + if constexpr (compute::is_dsl_v) { + return compute::ite(x > d3, compute::pow(x, 1.f / 3.f), one_over_3d2 * x + (4.f / 29.f)); + } else { + return x > d3 ? std::pow(x, 1.f / 3.f) : one_over_3d2 * x + 4.f / 29.f; + } + }; + auto f_y_yn = f(xyz.y); + auto l = 116.f * f_y_yn - 16.f; + auto a = 500.f * (f(xyz.x / .950489f) - f_y_yn); + auto b = 200.f * (f_y_yn - f(xyz.z / 1.08884f)); + return make_float3(l, a, b); +} + +template +[[nodiscard]] inline auto lab_to_cie_xyz(T &&lab) noexcept { + static constexpr auto delta = 6.f / 29.f; + auto f = [](const auto &x) noexcept { + constexpr auto three_dd = 3.f * delta * delta; + if constexpr (compute::is_dsl_v) { + return compute::ite(x > delta, x * x * x, three_dd * x - (three_dd * 4.f / 29.f)); + } else { + return x > delta ? x * x * x : three_dd * x - three_dd * 4.f / 29.f; + } + }; + auto v = (lab.x + 16.f) / 116.f; + auto x = .950489f * f(v + lab.y / 500.f); + auto y = f(v); + auto z = 1.08884f * f(v - lab.z / 200.f); + return make_float3(x, y, z); +} + }// namespace luisa::render