├── shaders └── hdr-toys │ ├── utils │ ├── clip_black.glsl │ ├── clip_white.glsl │ ├── clip_both.glsl │ ├── invert.glsl │ ├── exposure.glsl │ ├── range.glsl │ ├── range_inv.glsl │ ├── edge_detection.glsl │ ├── transform.glsl │ └── lut.glsl │ ├── transfer-function │ ├── bt709.glsl │ ├── bt709_inv.glsl │ ├── bt1886_inv.glsl │ ├── srgb_inv.glsl │ ├── bt1886.glsl │ ├── pq_inv.glsl │ ├── srgb.glsl │ ├── pq.glsl │ ├── hlg_inv.glsl │ └── hlg.glsl │ ├── gamut-mapping │ ├── jedypod.glsl │ ├── false.glsl │ ├── bottosson.glsl │ └── clip.glsl │ └── tone-mapping │ ├── bt2446a.glsl │ ├── linear.glsl │ ├── reinhard.glsl │ ├── bt2446c.glsl │ ├── false.glsl │ ├── st2094-10.glsl │ ├── bt2390.glsl │ └── astra.glsl ├── scripts └── hdr-toys.lua ├── LICENSE ├── hdr-toys.conf └── README.md /shaders/hdr-toys/utils/clip_black.glsl: -------------------------------------------------------------------------------- 1 | //!HOOK OUTPUT 2 | //!BIND HOOKED 3 | //!DESC clip code value (black) 4 | 5 | vec4 hook() { 6 | vec4 color = HOOKED_tex(HOOKED_pos); 7 | 8 | color.rgb = max(color.rgb, 0.0); 9 | 10 | return color; 11 | } 12 | -------------------------------------------------------------------------------- /shaders/hdr-toys/utils/clip_white.glsl: -------------------------------------------------------------------------------- 1 | //!HOOK OUTPUT 2 | //!BIND HOOKED 3 | //!DESC clip code value (white) 4 | 5 | vec4 hook() { 6 | vec4 color = HOOKED_tex(HOOKED_pos); 7 | 8 | color.rgb = min(color.rgb, 1.0); 9 | 10 | return color; 11 | } 12 | -------------------------------------------------------------------------------- /shaders/hdr-toys/utils/clip_both.glsl: -------------------------------------------------------------------------------- 1 | //!HOOK OUTPUT 2 | //!BIND HOOKED 3 | //!DESC clip code value (black, white) 4 | 5 | vec4 hook() { 6 | vec4 color = HOOKED_tex(HOOKED_pos); 7 | 8 | color.rgb = clamp(color.rgb, 0.0, 1.0); 9 | 10 | return color; 11 | } 12 | -------------------------------------------------------------------------------- /shaders/hdr-toys/utils/invert.glsl: -------------------------------------------------------------------------------- 1 | // invert the signal 2 | 3 | //!HOOK OUTPUT 4 | //!BIND HOOKED 5 | //!DESC signal invert 6 | 7 | float invert(float x, float w) { 8 | return -x + w; 9 | } 10 | 11 | vec3 invert(vec3 x, float w) { 12 | return -x + w; 13 | } 14 | 15 | vec4 hook() { 16 | vec4 color = HOOKED_tex(HOOKED_pos); 17 | 18 | color.rgb = invert(color.rgb, 1.0); 19 | 20 | return color; 21 | } 22 | -------------------------------------------------------------------------------- /scripts/hdr-toys.lua: -------------------------------------------------------------------------------- 1 | local options = require("mp.options") 2 | 3 | local o = { 4 | temporal_stable_time = 1 / 3, 5 | } 6 | options.read_options(o, _, function() end) 7 | 8 | mp.observe_property("container-fps", "native", function (property, value) 9 | if not value then return end 10 | value = value * o.temporal_stable_time 11 | value = math.floor(value + 0.5) 12 | mp.command("no-osd change-list glsl-shader-opts append temporal_stable_frames=" .. value) 13 | end) 14 | -------------------------------------------------------------------------------- /shaders/hdr-toys/utils/exposure.glsl: -------------------------------------------------------------------------------- 1 | // https://en.wikipedia.org/wiki/Exposure_value 2 | 3 | //!PARAM exposure_value 4 | //!TYPE float 5 | //!MINIMUM -64 6 | //!MAXIMUM 64 7 | 0.0 8 | 9 | //!HOOK OUTPUT 10 | //!BIND HOOKED 11 | //!WHEN exposure_value 12 | //!DESC exposure 13 | 14 | float exposure(float x, float ev) { 15 | return x * exp2(ev); 16 | } 17 | 18 | vec3 exposure(vec3 x, float ev) { 19 | return x * exp2(ev); 20 | } 21 | 22 | vec4 hook() { 23 | vec4 color = HOOKED_tex(HOOKED_pos); 24 | 25 | color.rgb = exposure(color.rgb, exposure_value); 26 | 27 | return color; 28 | } 29 | -------------------------------------------------------------------------------- /shaders/hdr-toys/utils/range.glsl: -------------------------------------------------------------------------------- 1 | // from "full" to "limited" signal range 2 | 3 | //!PARAM black 4 | //!TYPE float 5 | 0.0625 6 | 7 | //!PARAM white 8 | //!TYPE float 9 | 0.91796875 10 | 11 | //!PARAM depth 12 | //!TYPE float 13 | 10.0 14 | 15 | //!HOOK OUTPUT 16 | //!BIND HOOKED 17 | //!DESC signal range scaling 18 | 19 | float range(float x, float w, float b) { 20 | return x * (w - b) + b; 21 | } 22 | 23 | vec3 range(vec3 x, float w, float b) { 24 | return x * (w - b) + b; 25 | } 26 | 27 | vec4 hook() { 28 | vec4 color = HOOKED_tex(HOOKED_pos); 29 | 30 | float l = exp2(depth); 31 | float d = l - 1.0; 32 | float b = l * black / d; 33 | float w = l * white / d; 34 | 35 | color.rgb = range(color.rgb, w, b); 36 | 37 | return color; 38 | } 39 | -------------------------------------------------------------------------------- /shaders/hdr-toys/transfer-function/bt709.glsl: -------------------------------------------------------------------------------- 1 | // https://www.itu.int/rec/R-REC-BT.601 2 | // https://www.itu.int/rec/R-REC-BT.709 3 | // https://www.itu.int/rec/R-REC-BT.2020 4 | 5 | //!HOOK OUTPUT 6 | //!BIND HOOKED 7 | //!DESC transfer function (bt.709) 8 | 9 | const float beta = 0.018053968510807; 10 | const float alpha = 1.0 + 5.5 * beta; 11 | 12 | float bt709_oetf(float L) { 13 | return L < beta ? 4.5 * L : alpha * pow(L, 0.45) - (alpha - 1.0); 14 | } 15 | 16 | vec3 bt709_oetf(vec3 color) { 17 | return vec3( 18 | bt709_oetf(color.r), 19 | bt709_oetf(color.g), 20 | bt709_oetf(color.b) 21 | ); 22 | } 23 | 24 | vec4 hook() { 25 | vec4 color = HOOKED_tex(HOOKED_pos); 26 | 27 | color.rgb = bt709_oetf(color.rgb); 28 | 29 | return color; 30 | } 31 | -------------------------------------------------------------------------------- /shaders/hdr-toys/utils/range_inv.glsl: -------------------------------------------------------------------------------- 1 | // from "limited" to "full" signal range 2 | 3 | //!PARAM black 4 | //!TYPE float 5 | 0.0625 6 | 7 | //!PARAM white 8 | //!TYPE float 9 | 0.91796875 10 | 11 | //!PARAM depth 12 | //!TYPE float 13 | 10.0 14 | 15 | //!HOOK OUTPUT 16 | //!BIND HOOKED 17 | //!DESC signal range scaling (inverse) 18 | 19 | float range_inv(float x, float w, float b) { 20 | return (x - b) / (w - b); 21 | } 22 | 23 | vec3 range_inv(vec3 x, float w, float b) { 24 | return (x - b) / (w - b); 25 | } 26 | 27 | vec4 hook() { 28 | vec4 color = HOOKED_tex(HOOKED_pos); 29 | 30 | float l = exp2(depth); 31 | float d = l - 1.0; 32 | float b = l * black / d; 33 | float w = l * white / d; 34 | 35 | color.rgb = range_inv(color.rgb, w, b); 36 | 37 | return color; 38 | } 39 | -------------------------------------------------------------------------------- /shaders/hdr-toys/transfer-function/bt709_inv.glsl: -------------------------------------------------------------------------------- 1 | // https://www.itu.int/rec/R-REC-BT.601 2 | // https://www.itu.int/rec/R-REC-BT.709 3 | // https://www.itu.int/rec/R-REC-BT.2020 4 | 5 | //!HOOK OUTPUT 6 | //!BIND HOOKED 7 | //!DESC transfer function (bt.709, inverse) 8 | 9 | const float beta = 0.018053968510807; 10 | const float alpha = 1.0 + 5.5 * beta; 11 | 12 | float bt709_oetf_inv(float V) { 13 | return V < 4.5 * beta ? V / 4.5 : pow((V + (alpha - 1.0)) / alpha, 1.0 / 0.45); 14 | } 15 | 16 | vec3 bt709_oetf_inv(vec3 color) { 17 | return vec3( 18 | bt709_oetf_inv(color.r), 19 | bt709_oetf_inv(color.g), 20 | bt709_oetf_inv(color.b) 21 | ); 22 | } 23 | 24 | vec4 hook() { 25 | vec4 color = HOOKED_tex(HOOKED_pos); 26 | 27 | color.rgb = bt709_oetf_inv(color.rgb); 28 | 29 | return color; 30 | } 31 | -------------------------------------------------------------------------------- /shaders/hdr-toys/transfer-function/bt1886_inv.glsl: -------------------------------------------------------------------------------- 1 | // https://www.itu.int/rec/R-REC-BT.1886 2 | 3 | //!PARAM contrast_ratio 4 | //!TYPE float 5 | //!MINIMUM 10.0 6 | //!MAXIMUM 100000000.0 7 | 1000.0 8 | 9 | //!HOOK OUTPUT 10 | //!BIND HOOKED 11 | //!DESC transfer function (bt.1886, inverse) 12 | 13 | float bt1886_eotf(float V, float gamma, float Lw, float Lb) { 14 | float a = pow(pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma), gamma); 15 | float b = pow(Lb, 1.0 / gamma) / (pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma)); 16 | float L = a * pow(max(V + b, 0.0), gamma); 17 | return L; 18 | } 19 | 20 | vec3 bt1886_eotf(vec3 color, float gamma, float Lw, float Lb) { 21 | return vec3( 22 | bt1886_eotf(color.r, gamma, Lw, Lb), 23 | bt1886_eotf(color.g, gamma, Lw, Lb), 24 | bt1886_eotf(color.b, gamma, Lw, Lb) 25 | ); 26 | } 27 | 28 | vec4 hook() { 29 | vec4 color = HOOKED_tex(HOOKED_pos); 30 | 31 | color.rgb = bt1886_eotf(color.rgb, 2.4, 1.0, 1.0 / contrast_ratio); 32 | 33 | return color; 34 | } 35 | -------------------------------------------------------------------------------- /shaders/hdr-toys/transfer-function/srgb_inv.glsl: -------------------------------------------------------------------------------- 1 | // https://github.com/ampas/aces-core/blob/dev/lib/Lib.Academy.DisplayEncoding.ctl 2 | // moncurve with gamma of 2.4 and offset of 0.055 matches the EOTF found in IEC 61966-2-1:1999 (sRGB) 3 | 4 | //!HOOK OUTPUT 5 | //!BIND HOOKED 6 | //!DESC transfer function (srgb, inverse) 7 | 8 | const float gamma = 2.4; 9 | const float offset = 0.055; 10 | 11 | float monitor_curve_eotf(float x) { 12 | const float fs = ((gamma - 1.0) / offset) * pow(offset * gamma / ((gamma - 1.0) * (1.0 + offset)), gamma); 13 | const float xb = offset / (gamma - 1.0); 14 | return x >= xb ? pow((x + offset) / (1.0 + offset), gamma) : x * fs; 15 | } 16 | 17 | vec3 monitor_curve_eotf(vec3 color) { 18 | return vec3( 19 | monitor_curve_eotf(color.r), 20 | monitor_curve_eotf(color.g), 21 | monitor_curve_eotf(color.b) 22 | ); 23 | } 24 | 25 | vec4 hook() { 26 | vec4 color = HOOKED_tex(HOOKED_pos); 27 | 28 | color.rgb = monitor_curve_eotf(color.rgb); 29 | 30 | return color; 31 | } 32 | -------------------------------------------------------------------------------- /shaders/hdr-toys/transfer-function/bt1886.glsl: -------------------------------------------------------------------------------- 1 | // https://www.itu.int/rec/R-REC-BT.1886 2 | 3 | //!PARAM contrast_ratio 4 | //!TYPE float 5 | //!MINIMUM 10.0 6 | //!MAXIMUM 100000000.0 7 | 1000.0 8 | 9 | //!HOOK OUTPUT 10 | //!BIND HOOKED 11 | //!DESC transfer function (bt.1886) 12 | 13 | float bt1886_eotf_inv(float L, float gamma, float Lw, float Lb) { 14 | float a = pow(pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma), gamma); 15 | float b = pow(Lb, 1.0 / gamma) / (pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma)); 16 | float V = pow(max(L / a, 0.0), 1.0 / gamma) - b; 17 | return V; 18 | } 19 | 20 | vec3 bt1886_eotf_inv(vec3 color, float gamma, float Lw, float Lb) { 21 | return vec3( 22 | bt1886_eotf_inv(color.r, gamma, Lw, Lb), 23 | bt1886_eotf_inv(color.g, gamma, Lw, Lb), 24 | bt1886_eotf_inv(color.b, gamma, Lw, Lb) 25 | ); 26 | } 27 | 28 | vec4 hook() { 29 | vec4 color = HOOKED_tex(HOOKED_pos); 30 | 31 | color.rgb = bt1886_eotf_inv(color.rgb, 2.4, 1.0, 1.0 / contrast_ratio); 32 | 33 | return color; 34 | } 35 | -------------------------------------------------------------------------------- /shaders/hdr-toys/transfer-function/pq_inv.glsl: -------------------------------------------------------------------------------- 1 | // https://ieeexplore.ieee.org/document/7291452 2 | // https://www.itu.int/rec/R-REC-BT.2100 3 | 4 | //!PARAM reference_white 5 | //!TYPE float 6 | //!MINIMUM 0.0 7 | //!MAXIMUM 1000.0 8 | 203.0 9 | 10 | //!HOOK OUTPUT 11 | //!BIND HOOKED 12 | //!DESC transfer function (pq, inverse) 13 | 14 | const float m1 = 2610.0 / 4096.0 / 4.0; 15 | const float m2 = 2523.0 / 4096.0 * 128.0; 16 | const float c1 = 3424.0 / 4096.0; 17 | const float c2 = 2413.0 / 4096.0 * 32.0; 18 | const float c3 = 2392.0 / 4096.0 * 32.0; 19 | const float pw = 10000.0; 20 | 21 | float pq_eotf(float x) { 22 | float t = pow(x, 1.0 / m2); 23 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 24 | } 25 | 26 | vec3 pq_eotf(vec3 color) { 27 | return vec3( 28 | pq_eotf(color.r), 29 | pq_eotf(color.g), 30 | pq_eotf(color.b) 31 | ); 32 | } 33 | 34 | vec4 hook() { 35 | vec4 color = HOOKED_tex(HOOKED_pos); 36 | 37 | color.rgb = pq_eotf(color.rgb) / reference_white; 38 | 39 | return color; 40 | } 41 | -------------------------------------------------------------------------------- /shaders/hdr-toys/transfer-function/srgb.glsl: -------------------------------------------------------------------------------- 1 | // https://github.com/ampas/aces-core/blob/dev/lib/Lib.Academy.DisplayEncoding.ctl 2 | // moncurve with gamma of 2.4 and offset of 0.055 matches the EOTF found in IEC 61966-2-1:1999 (sRGB) 3 | 4 | //!HOOK OUTPUT 5 | //!BIND HOOKED 6 | //!DESC transfer function (srgb) 7 | 8 | const float gamma = 2.4; 9 | const float offset = 0.055; 10 | 11 | float monitor_curve_eotf_inv(float y) { 12 | const float yb = pow(offset * gamma / ((gamma - 1.0) * (1.0 + offset)), gamma); 13 | const float rs = pow((gamma - 1.0) / offset, gamma - 1.0) * pow((1.0 + offset) / gamma, gamma); 14 | return y >= yb ? (1.0 + offset) * pow(y, 1.0 / gamma) - offset : y * rs; 15 | } 16 | 17 | vec3 monitor_curve_eotf_inv(vec3 color) { 18 | return vec3( 19 | monitor_curve_eotf_inv(color.r), 20 | monitor_curve_eotf_inv(color.g), 21 | monitor_curve_eotf_inv(color.b) 22 | ); 23 | } 24 | 25 | vec4 hook() { 26 | vec4 color = HOOKED_tex(HOOKED_pos); 27 | 28 | color.rgb = monitor_curve_eotf_inv(color.rgb); 29 | 30 | return color; 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 natural-harmonia-gropius 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /shaders/hdr-toys/transfer-function/pq.glsl: -------------------------------------------------------------------------------- 1 | // https://ieeexplore.ieee.org/document/7291452 2 | // https://www.itu.int/rec/R-REC-BT.2100 3 | 4 | // https://www.itu.int/pub/R-REP-BT.2390 5 | // pq ootf: 100.0 * bt1886_eotf(bt709_oetf(59.5208 * x), 2.4, 1.0, 0.0) 6 | 7 | //!PARAM reference_white 8 | //!TYPE float 9 | //!MINIMUM 0.0 10 | //!MAXIMUM 1000.0 11 | 203.0 12 | 13 | //!HOOK OUTPUT 14 | //!BIND HOOKED 15 | //!DESC transfer function (pq) 16 | 17 | const float m1 = 2610.0 / 4096.0 / 4.0; 18 | const float m2 = 2523.0 / 4096.0 * 128.0; 19 | const float c1 = 3424.0 / 4096.0; 20 | const float c2 = 2413.0 / 4096.0 * 32.0; 21 | const float c3 = 2392.0 / 4096.0 * 32.0; 22 | const float pw = 10000.0; 23 | 24 | float pq_eotf_inv(float x) { 25 | float t = pow(x / pw, m1); 26 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 27 | } 28 | 29 | vec3 pq_eotf_inv(vec3 color) { 30 | return vec3( 31 | pq_eotf_inv(color.r), 32 | pq_eotf_inv(color.g), 33 | pq_eotf_inv(color.b) 34 | ); 35 | } 36 | 37 | vec4 hook() { 38 | vec4 color = HOOKED_tex(HOOKED_pos); 39 | 40 | color.rgb = pq_eotf_inv(color.rgb * reference_white); 41 | 42 | return color; 43 | } 44 | -------------------------------------------------------------------------------- /shaders/hdr-toys/transfer-function/hlg_inv.glsl: -------------------------------------------------------------------------------- 1 | // https://www.arib.or.jp/kikaku/kikaku_hoso/std-b67.html 2 | // https://www.bbc.co.uk/rd/projects/high-dynamic-range 3 | // https://www.itu.int/rec/R-REC-BT.2100 4 | 5 | //!PARAM reference_white 6 | //!TYPE float 7 | //!MINIMUM 0.0 8 | //!MAXIMUM 1000.0 9 | 203.0 10 | 11 | //!HOOK OUTPUT 12 | //!BIND HOOKED 13 | //!DESC transfer function (hlg, inverse) 14 | 15 | const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); 16 | 17 | const float Lw = 1000.0; 18 | const float Lb = 0.0; 19 | const float Lamb = 5.0; 20 | 21 | const float gamma = 1.2 + 0.42 * log(Lw / 1000.0) / log(10.0) - 0.076 * log(Lamb / 5.0) / log(10.0); 22 | const float alpha = Lw; 23 | const float beta = sqrt(3.0 * pow((Lb / Lw), 1.0 / gamma)); 24 | 25 | const float a = 0.17883277; 26 | const float b = 1.0 - 4.0 * a; 27 | const float c = 0.5 - a * log(4.0 * a); 28 | 29 | float hlg_oetf_inv(float x) { 30 | return x <= 1.0 / 2.0 ? pow(x, 2.0) / 3.0 : (exp((x - c) / a) + b) / 12.0; 31 | } 32 | 33 | vec3 hlg_oetf_inv(vec3 color) { 34 | return vec3( 35 | hlg_oetf_inv(color.r), 36 | hlg_oetf_inv(color.g), 37 | hlg_oetf_inv(color.b) 38 | ); 39 | } 40 | 41 | vec3 hlg_ootf(vec3 color) { 42 | float Y = dot(color, y_coef); 43 | return alpha * pow(Y, gamma - 1.0) * color; 44 | } 45 | 46 | vec3 hlg_eotf(vec3 color) { 47 | return hlg_ootf(hlg_oetf_inv(max((1.0 - beta) * color + beta, 0.0))); 48 | } 49 | 50 | vec4 hook() { 51 | vec4 color = HOOKED_tex(HOOKED_pos); 52 | 53 | color.rgb = hlg_eotf(color.rgb) / reference_white; 54 | 55 | return color; 56 | } 57 | -------------------------------------------------------------------------------- /shaders/hdr-toys/transfer-function/hlg.glsl: -------------------------------------------------------------------------------- 1 | // https://www.arib.or.jp/kikaku/kikaku_hoso/std-b67.html 2 | // https://www.bbc.co.uk/rd/projects/high-dynamic-range 3 | // https://www.itu.int/rec/R-REC-BT.2100 4 | 5 | // https://www.itu.int/pub/R-REP-BT.2390 6 | // extended gamma model for Lw is outside 400-2000 cd/m²: 7 | // 1.2 * pow(1.111, log2(Lw / 1000.0)) * pow(0.98, log2(Lamb / 5.0)) 8 | 9 | //!PARAM reference_white 10 | //!TYPE float 11 | //!MINIMUM 0.0 12 | //!MAXIMUM 1000.0 13 | 203.0 14 | 15 | //!HOOK OUTPUT 16 | //!BIND HOOKED 17 | //!DESC transfer function (hlg) 18 | 19 | const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); 20 | 21 | const float Lw = 1000.0; 22 | const float Lb = 0.0; 23 | const float Lamb = 5.0; 24 | 25 | const float gamma = 1.2 + 0.42 * log(Lw / 1000.0) / log(10.0) - 0.076 * log(Lamb / 5.0) / log(10.0); 26 | const float alpha = Lw; 27 | const float beta = sqrt(3.0 * pow((Lb / Lw), 1.0 / gamma)); 28 | 29 | const float a = 0.17883277; 30 | const float b = 1.0 - 4.0 * a; 31 | const float c = 0.5 - a * log(4.0 * a); 32 | 33 | float hlg_oetf(float x) { 34 | return x <= 1.0 / 12.0 ? sqrt(3.0 * x) : a * log(12.0 * x - b) + c; 35 | } 36 | 37 | vec3 hlg_oetf(vec3 color) { 38 | return vec3( 39 | hlg_oetf(color.r), 40 | hlg_oetf(color.g), 41 | hlg_oetf(color.b) 42 | ); 43 | } 44 | 45 | vec3 hlg_ootf_inv(vec3 color) { 46 | float Y = dot(color, y_coef); 47 | return Y == 0.0 ? vec3(0.0) : pow(Y / alpha, (1.0 - gamma) / gamma) * (color / alpha); 48 | } 49 | 50 | vec3 hlg_eotf_inv(vec3 color) { 51 | return (hlg_oetf(hlg_ootf_inv(color)) - beta) / (1.0 - beta); 52 | } 53 | 54 | vec4 hook() { 55 | vec4 color = HOOKED_tex(HOOKED_pos); 56 | 57 | color.rgb = hlg_eotf_inv(color.rgb * reference_white); 58 | 59 | return color; 60 | } 61 | -------------------------------------------------------------------------------- /hdr-toys.conf: -------------------------------------------------------------------------------- 1 | target-colorspace-hint=no 2 | 3 | tone-mapping=clip 4 | gamut-mapping-mode=clip 5 | 6 | [bt.2100-pq] 7 | profile-cond=get("video-params/primaries") == "bt.2020" and get("video-params/gamma") == "pq" 8 | profile-restore=copy 9 | target-prim=bt.2020 10 | target-trc=pq 11 | glsl-shader=~~/shaders/hdr-toys/utils/clip_both.glsl 12 | glsl-shader=~~/shaders/hdr-toys/transfer-function/pq_inv.glsl 13 | glsl-shader=~~/shaders/hdr-toys/tone-mapping/astra.glsl 14 | glsl-shader=~~/shaders/hdr-toys/gamut-mapping/jedypod.glsl 15 | glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886.glsl 16 | glsl-shader-opts-append=auto_exposure_limit_postive=1.02 17 | 18 | [bt.2100-hlg] 19 | profile-cond=get("video-params/primaries") == "bt.2020" and get("video-params/gamma") == "hlg" 20 | profile-restore=copy 21 | target-prim=bt.2020 22 | target-trc=hlg 23 | glsl-shader=~~/shaders/hdr-toys/utils/clip_both.glsl 24 | glsl-shader=~~/shaders/hdr-toys/transfer-function/hlg_inv.glsl 25 | glsl-shader=~~/shaders/hdr-toys/tone-mapping/astra.glsl 26 | glsl-shader=~~/shaders/hdr-toys/gamut-mapping/jedypod.glsl 27 | glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886.glsl 28 | 29 | [bt.2020] 30 | profile-cond=get("video-params/primaries") == "bt.2020" and get("video-params/gamma") == "bt.1886" 31 | profile-restore=copy 32 | target-prim=bt.2020 33 | target-trc=bt.1886 34 | glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886_inv.glsl 35 | glsl-shader=~~/shaders/hdr-toys/gamut-mapping/jedypod.glsl 36 | glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886.glsl 37 | 38 | [linear] 39 | profile-cond=get("file-format") == "exr_pipe" or get("file-format") == "hdr_pipe" or get("file-format") == "tiff_pipe" 40 | profile-restore=copy 41 | vf=format:gamma=linear 42 | scale=bilinear 43 | target-prim=bt.2020 44 | target-trc=linear 45 | glsl-shader=~~/shaders/hdr-toys/utils/clip_black.glsl 46 | glsl-shader=~~/shaders/hdr-toys/tone-mapping/astra.glsl 47 | glsl-shader=~~/shaders/hdr-toys/gamut-mapping/jedypod.glsl 48 | glsl-shader=~~/shaders/hdr-toys/transfer-function/bt1886.glsl 49 | -------------------------------------------------------------------------------- /shaders/hdr-toys/gamut-mapping/jedypod.glsl: -------------------------------------------------------------------------------- 1 | // https://github.com/jedypod/gamut-compress 2 | // https://github.com/ampas/aces-dev/blob/dev/transforms/ctl/lmt/LMT.Academy.ReferenceGamutCompress.ctl 3 | 4 | //!PARAM cyan_limit 5 | //!TYPE float 6 | //!MINIMUM 1.01 7 | //!MAXIMUM 2.0 8 | 1.595 9 | 10 | //!PARAM magenta_limit 11 | //!TYPE float 12 | //!MINIMUM 1.01 13 | //!MAXIMUM 2.0 14 | 1.089 15 | 16 | //!PARAM yellow_limit 17 | //!TYPE float 18 | //!MINIMUM 1.01 19 | //!MAXIMUM 2.0 20 | 1.117 21 | 22 | //!PARAM cyan_threshold 23 | //!TYPE float 24 | //!MINIMUM 0.0 25 | //!MAXIMUM 1.0 26 | 0.990 27 | 28 | //!PARAM magenta_threshold 29 | //!TYPE float 30 | //!MINIMUM 0.0 31 | //!MAXIMUM 1.0 32 | 0.940 33 | 34 | //!PARAM yellow_threshold 35 | //!TYPE float 36 | //!MINIMUM 0.0 37 | //!MAXIMUM 1.0 38 | 0.977 39 | 40 | //!HOOK OUTPUT 41 | //!BIND HOOKED 42 | //!DESC gamut mapping (jedypod) 43 | 44 | // Parabolic compression function 45 | // https://www.desmos.com/calculator/khowxlu6xh 46 | float parabolic(float x, float t0, float x0, float y0) { 47 | float s = (y0 - t0) / sqrt(x0 - y0); 48 | float ox = t0 - s * s / 4.0; 49 | float oy = t0 - s * sqrt(s * s / 4.0); 50 | return (x < t0 ? x : s * sqrt(x - ox) + oy); 51 | } 52 | 53 | vec3 gamut_compress(vec3 rgb) { 54 | // Achromatic axis 55 | float ac = max(max(rgb.r, rgb.g), rgb.b); 56 | 57 | // Inverse RGB Ratios: distance from achromatic axis 58 | vec3 d = ac == 0.0 ? vec3(0.0) : (ac - rgb) / abs(ac); 59 | 60 | // Compressed distance 61 | vec3 cd = vec3( 62 | parabolic(d.x, cyan_threshold, cyan_limit, 1.0), 63 | parabolic(d.y, magenta_threshold, magenta_limit, 1.0), 64 | parabolic(d.z, yellow_threshold, yellow_limit, 1.0) 65 | ); 66 | 67 | // Inverse RGB Ratios to RGB 68 | vec3 crgb = ac - cd * abs(ac); 69 | 70 | return crgb; 71 | } 72 | 73 | vec3 BT2020_to_BT709(vec3 color) { 74 | return color * mat3( 75 | 1.660491002108434, -0.5876411387885491, -0.072849863319885, 76 | -0.1245504745215906, 1.1328998971259596, -0.0083494226043695, 77 | -0.0181507633549052, -0.1005788980080074, 1.118729661362913 78 | ); 79 | } 80 | 81 | vec4 hook() { 82 | vec4 color = HOOKED_tex(HOOKED_pos); 83 | 84 | color.rgb = BT2020_to_BT709(color.rgb); 85 | color.rgb = gamut_compress(color.rgb); 86 | 87 | return color; 88 | } 89 | -------------------------------------------------------------------------------- /shaders/hdr-toys/utils/edge_detection.glsl: -------------------------------------------------------------------------------- 1 | // https://anirban-karchaudhuri.medium.com/edge-detection-methods-comparison-9e4b75a9bf87 2 | // I suppose the results obtained by processing under PQ(Y) are consistent with human perception. 3 | 4 | //!PARAM edge_detection 5 | //!TYPE ENUM int 6 | laplacian 7 | sobel 8 | prewitt 9 | 10 | //!PARAM printmaking 11 | //!TYPE ENUM int 12 | relief 13 | intaglio 14 | 15 | //!HOOK OUTPUT 16 | //!BIND HOOKED 17 | //!DESC edge detection 18 | 19 | const mat3 prewitt_x = mat3( 20 | 1.0, 0.0, -1.0, 21 | 1.0, 0.0, -1.0, 22 | 1.0, 0.0, -1.0 23 | ); 24 | 25 | const mat3 prewitt_y = mat3( 26 | 1.0, 1.0, 1.0, 27 | 0.0, 0.0, 0.0, 28 | -1.0, -1.0, -1.0 29 | ); 30 | 31 | const mat3 sobel_x = mat3( 32 | 1.0, 0.0, -1.0, 33 | 2.0, 0.0, -2.0, 34 | 1.0, 0.0, -1.0 35 | ); 36 | 37 | const mat3 sobel_y = mat3( 38 | 1.0, 2.0, 1.0, 39 | 0.0, 0.0, 0.0, 40 | -1.0, -2.0, -1.0 41 | ); 42 | 43 | const mat3 laplacian_p = mat3( 44 | 0.0, 1.0, 0.0, 45 | 1.0, -4.0, 1.0, 46 | 0.0, 1.0, 0.0 47 | ); 48 | 49 | const mat3 laplacian_n = mat3( 50 | 0.0, -1.0, 0.0, 51 | -1.0, 4.0, -1.0, 52 | 0.0, -1.0, 0.0 53 | ); 54 | 55 | const float base = 0.0; 56 | const uvec2 k_size = uvec2(3, 3); 57 | const vec2 k_size_h = vec2(k_size / 2); 58 | 59 | vec3 conv(mat3 k) { 60 | vec3 x = vec3(base); 61 | for (uint i = 0; i < k_size.x; i++) 62 | for (uint j = 0; j < k_size.y; j++) 63 | x += HOOKED_texOff(vec2(j, i) - k_size_h).rgb * k[i][j]; 64 | return x; 65 | } 66 | 67 | vec3 make_prewitt() { 68 | vec3 x = conv(prewitt_x); 69 | vec3 y = conv(prewitt_y); 70 | vec3 g = abs(sqrt(x * x + y * y)); 71 | return g; 72 | } 73 | 74 | vec3 make_sobel() { 75 | vec3 x = conv(sobel_x); 76 | vec3 y = conv(sobel_y); 77 | vec3 g = abs(sqrt(x * x + y * y)); 78 | return g; 79 | } 80 | 81 | vec3 make_laplacian() { 82 | vec3 x = conv(laplacian_p); 83 | return x; 84 | } 85 | 86 | vec4 hook() { 87 | vec4 color = vec4(vec3(0.5), 1.0); 88 | 89 | float s = (printmaking == relief) ? -1.0 : 1.0; 90 | 91 | if (edge_detection == laplacian) { 92 | color.rgb += s * make_laplacian(); 93 | } else if (edge_detection == sobel) { 94 | color.rgb += s * make_sobel(); 95 | } else if (edge_detection == prewitt) { 96 | color.rgb += s * make_prewitt(); 97 | } 98 | 99 | return color; 100 | } 101 | -------------------------------------------------------------------------------- /shaders/hdr-toys/utils/transform.glsl: -------------------------------------------------------------------------------- 1 | // https://developer.mozilla.org/en-US/docs/Web/CSS/transform 2 | 3 | //!PARAM rotate 4 | //!TYPE float 5 | 0.0 6 | 7 | //!PARAM translate_x 8 | //!TYPE float 9 | 0.0 10 | 11 | //!PARAM translate_y 12 | //!TYPE float 13 | 0.0 14 | 15 | //!PARAM scale_x 16 | //!TYPE float 17 | 1.0 18 | 19 | //!PARAM scale_y 20 | //!TYPE float 21 | 1.0 22 | 23 | //!PARAM skew_x 24 | //!TYPE float 25 | 0.0 26 | 27 | //!PARAM skew_y 28 | //!TYPE float 29 | 0.0 30 | 31 | //!PARAM background 32 | //!TYPE ENUM int 33 | black 34 | border 35 | repeat 36 | mirror 37 | 38 | //!HOOK OUTPUT 39 | //!BIND HOOKED 40 | //!DESC transform 41 | 42 | mat3 make_rotate(float deg) { 43 | float rad = radians(deg); 44 | float c = cos(rad); 45 | float s = sin(rad); 46 | return mat3( 47 | c, -s, 0.0, 48 | s, c, 0.0, 49 | 0.0, 0.0, 1.0 50 | ); 51 | } 52 | 53 | mat3 make_translate(vec2 offset) { 54 | return mat3( 55 | 1.0, 0.0, 0.0, 56 | 0.0, 1.0, 0.0, 57 | -offset.x, -offset.y, 1.0 58 | ); 59 | } 60 | 61 | mat3 make_scale(vec2 scale) { 62 | return mat3( 63 | 1.0 / scale.x, 0.0, 0.0, 64 | 0.0, 1.0 / scale.y, 0.0, 65 | 0.0, 0.0, 1.0 66 | ); 67 | } 68 | 69 | mat3 make_skew(vec2 deg) { 70 | vec2 rad = radians(deg); 71 | return mat3( 72 | 1.0, -tan(rad.y), 0.0, 73 | -tan(rad.x), 1.0, 0.0, 74 | 0.0, 0.0, 1.0 75 | ); 76 | } 77 | 78 | vec2 apply_transform(vec2 coord, mat3 matrix) { 79 | vec3 homogeneous = vec3(coord, 1.0); 80 | vec3 transformed = matrix * homogeneous; 81 | return transformed.xy / transformed.z; 82 | } 83 | 84 | vec4 hook() { 85 | vec2 pos = HOOKED_pos; 86 | vec2 size = HOOKED_size; 87 | vec2 center = vec2(0.5, 0.5); 88 | 89 | pos = apply_transform( 90 | pos, 91 | make_translate(-center) * 92 | make_scale(size) * 93 | make_rotate(rotate) * 94 | make_scale(1.0 / size) * 95 | make_translate(vec2(translate_x, translate_y)) * 96 | make_scale(vec2(scale_x, scale_y)) * 97 | make_skew(vec2(skew_x, skew_y)) * 98 | make_translate(center) 99 | ); 100 | 101 | bool out_of_bounds = pos.x < 0.0 || pos.x > 1.0 || pos.y < 0.0 || pos.y > 1.0; 102 | if (out_of_bounds) { 103 | if (background == black) { 104 | return vec4(vec3(0.0), 1.0); 105 | } else if (background == border) { 106 | pos = clamp(pos, 0.0, 1.0); 107 | } else if (background == repeat) { 108 | pos = mod(pos, 1.0); 109 | } else if (background == mirror) { 110 | pos = 1.0 - abs(fract(pos * 0.5) * 2.0 - 1.0); 111 | } 112 | } 113 | 114 | return HOOKED_tex(pos); 115 | } 116 | -------------------------------------------------------------------------------- /shaders/hdr-toys/utils/lut.glsl: -------------------------------------------------------------------------------- 1 | // https://lut-to-texture.pages.dev/ 2 | // You can convert .cube format 3D LUTs to the desired texture format using the link above, 3 | // then paste it to the last line. 4 | 5 | //!HOOK OUTPUT 6 | //!BIND HOOKED 7 | //!BIND LUT 8 | //!DESC LUT 9 | 10 | vec3 tetrahedral(sampler3D lut, vec3 color) { 11 | float lut_size = float(textureSize(lut, 0).x); 12 | vec3 coord = color * (lut_size - 1.0); 13 | 14 | vec3 b = floor(coord); 15 | vec3 f = fract(coord); 16 | 17 | float texel_size = 1.0 / lut_size; 18 | vec3 base_coord = (b + 0.5) * texel_size; 19 | 20 | vec3 c000 = base_coord; 21 | vec3 c100 = base_coord + vec3(texel_size, 0.0, 0.0); 22 | vec3 c010 = base_coord + vec3(0.0, texel_size, 0.0); 23 | vec3 c110 = base_coord + vec3(texel_size, texel_size, 0.0); 24 | vec3 c001 = base_coord + vec3(0.0, 0.0, texel_size); 25 | vec3 c101 = base_coord + vec3(texel_size, 0.0, texel_size); 26 | vec3 c011 = base_coord + vec3(0.0, texel_size, texel_size); 27 | vec3 c111 = base_coord + vec3(texel_size, texel_size, texel_size); 28 | 29 | vec3 v000 = texture(lut, c000).rgb; 30 | vec3 v100 = texture(lut, c100).rgb; 31 | vec3 v010 = texture(lut, c010).rgb; 32 | vec3 v110 = texture(lut, c110).rgb; 33 | vec3 v001 = texture(lut, c001).rgb; 34 | vec3 v101 = texture(lut, c101).rgb; 35 | vec3 v011 = texture(lut, c011).rgb; 36 | vec3 v111 = texture(lut, c111).rgb; 37 | 38 | vec3 result; 39 | if (f.x >= f.y && f.y >= f.z) { 40 | // Tetrahedron 1: x >= y >= z 41 | result = (1.0 - f.x) * v000 + 42 | (f.x - f.y) * v100 + 43 | (f.y - f.z) * v110 + 44 | f.z * v111; 45 | } else if (f.x >= f.z && f.z >= f.y) { 46 | // Tetrahedron 2: x >= z >= y 47 | result = (1.0 - f.x) * v000 + 48 | (f.x - f.z) * v100 + 49 | (f.z - f.y) * v101 + 50 | f.y * v111; 51 | } else if (f.y >= f.x && f.x >= f.z) { 52 | // Tetrahedron 3: y >= x >= z 53 | result = (1.0 - f.y) * v000 + 54 | (f.y - f.x) * v010 + 55 | (f.x - f.z) * v110 + 56 | f.z * v111; 57 | } else if (f.y >= f.z && f.z >= f.x) { 58 | // Tetrahedron 4: y >= z >= x 59 | result = (1.0 - f.y) * v000 + 60 | (f.y - f.z) * v010 + 61 | (f.z - f.x) * v011 + 62 | f.x * v111; 63 | } else if (f.z >= f.x && f.x >= f.y) { 64 | // Tetrahedron 5: z >= x >= y 65 | result = (1.0 - f.z) * v000 + 66 | (f.z - f.x) * v001 + 67 | (f.x - f.y) * v101 + 68 | f.y * v111; 69 | } else { 70 | // Tetrahedron 6: z >= y >= x 71 | result = (1.0 - f.z) * v000 + 72 | (f.z - f.y) * v001 + 73 | (f.y - f.x) * v011 + 74 | f.x * v111; 75 | } 76 | 77 | return result; 78 | } 79 | 80 | vec4 hook() { 81 | vec4 color = HOOKED_tex(HOOKED_pos); 82 | color.rgb = tetrahedral(LUT, color.rgb); 83 | return color; 84 | } 85 | 86 | //!TEXTURE LUT 87 | //!SIZE 65 65 65 88 | //!FORMAT rgba16hf 89 | //!FILTER NEAREST 90 | -------------------------------------------------------------------------------- /shaders/hdr-toys/gamut-mapping/false.glsl: -------------------------------------------------------------------------------- 1 | // Visualizes the out of gamut colors using false color. 2 | 3 | //!HOOK OUTPUT 4 | //!BIND HOOKED 5 | //!DESC gamut mapping (false color) 6 | 7 | float cbrt(float x) { 8 | return sign(x) * pow(abs(x), 1.0 / 3.0); 9 | } 10 | 11 | vec3 cbrt(vec3 color) { 12 | return vec3( 13 | cbrt(color.x), 14 | cbrt(color.y), 15 | cbrt(color.z) 16 | ); 17 | } 18 | 19 | vec3 RGB_to_XYZ(vec3 RGB) { 20 | return RGB * mat3( 21 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 22 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 23 | 0.000000000000000, 0.028072693049087428, 1.060985057710791 24 | ); 25 | } 26 | 27 | vec3 XYZ_to_RGB(vec3 XYZ) { 28 | return XYZ * mat3( 29 | 1.716651187971268, -0.355670783776392, -0.253366281373660, 30 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 31 | 0.017639857445311, -0.042770613257809, 0.942103121235474 32 | ); 33 | } 34 | 35 | vec3 XYZ_to_LMS(vec3 XYZ) { 36 | return XYZ * mat3( 37 | 0.8190224379967030, 0.3619062600528904, -0.1288737815209879, 38 | 0.0329836539323885, 0.9292868615863434, 0.0361446663506424, 39 | 0.0481771893596242, 0.2642395317527308, 0.6335478284694309 40 | ); 41 | } 42 | 43 | vec3 LMS_to_XYZ(vec3 LMS) { 44 | return LMS * mat3( 45 | 1.2268798758459243, -0.5578149944602171, 0.2813910456659647, 46 | -0.0405757452148008, 1.1122868032803170, -0.0717110580655164, 47 | -0.0763729366746601, -0.4214933324022432, 1.5869240198367816 48 | ); 49 | } 50 | 51 | vec3 LMS_to_Lab(vec3 LMS) { 52 | return LMS * mat3( 53 | 0.2104542683093140, 0.7936177747023054, -0.0040720430116193, 54 | 1.9779985324311684, -2.4285922420485799, 0.4505937096174110, 55 | 0.0259040424655478, 0.7827717124575296, -0.8086757549230774 56 | ); 57 | } 58 | 59 | vec3 Lab_to_LMS(vec3 Lab) { 60 | return Lab * mat3( 61 | 1.0000000000000000, 0.3963377773761749, 0.2158037573099136, 62 | 1.0000000000000000, -0.1055613458156586, -0.0638541728258133, 63 | 1.0000000000000000, -0.0894841775298119, -1.2914855480194092 64 | ); 65 | } 66 | 67 | vec3 RGB_to_Lab(vec3 color) { 68 | color = RGB_to_XYZ(color); 69 | color = XYZ_to_LMS(color); 70 | color = cbrt(color); 71 | color = LMS_to_Lab(color); 72 | return color; 73 | } 74 | 75 | vec3 Lab_to_RGB(vec3 color) { 76 | color = Lab_to_LMS(color); 77 | color = pow(color, vec3(3.0)); 78 | color = LMS_to_XYZ(color); 79 | color = XYZ_to_RGB(color); 80 | return color; 81 | } 82 | 83 | vec3 BT2020_to_BT709(vec3 color) { 84 | return color * mat3( 85 | 1.660491002108434, -0.5876411387885491, -0.072849863319885, 86 | -0.1245504745215906, 1.1328998971259596, -0.0083494226043695, 87 | -0.0181507633549052, -0.1005788980080074, 1.118729661362913 88 | ); 89 | } 90 | 91 | vec4 hook() { 92 | vec4 color = HOOKED_tex(HOOKED_pos); 93 | 94 | vec3 color_dst = BT2020_to_BT709(color.rgb); 95 | vec3 color_dst_cliped = clamp(color_dst, 0.0, 1.0); 96 | bool is_in_gamut = color_dst == color_dst_cliped; 97 | 98 | color.rgb = RGB_to_Lab(color.rgb); 99 | color.rgb = is_in_gamut ? vec3(color.x, vec2(0.0)) : vec3(0.5, color.yz); 100 | color.rgb = Lab_to_RGB(color.rgb); 101 | color.rgb = BT2020_to_BT709(color.rgb); 102 | 103 | return color; 104 | } 105 | -------------------------------------------------------------------------------- /shaders/hdr-toys/tone-mapping/bt2446a.glsl: -------------------------------------------------------------------------------- 1 | // ITU-R BT.2446 Conversion Method A 2 | // https://www.itu.int/pub/R-REP-BT.2446 3 | 4 | //!PARAM max_luma 5 | //!TYPE float 6 | 0.0 7 | 8 | //!PARAM max_cll 9 | //!TYPE float 10 | 0.0 11 | 12 | //!PARAM reference_white 13 | //!TYPE float 14 | //!MINIMUM 0.0 15 | //!MAXIMUM 1000.0 16 | 203.0 17 | 18 | //!HOOK OUTPUT 19 | //!BIND HOOKED 20 | //!DESC tone mapping (bt.2446a) 21 | 22 | const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); 23 | 24 | const float a = y_coef.r; 25 | const float b = y_coef.g; 26 | const float c = y_coef.b; 27 | const float d = 2.0 * (1.0 - c); 28 | const float e = 2.0 * (1.0 - a); 29 | 30 | vec3 RGB_to_YCbCr(vec3 RGB) { 31 | return RGB * mat3( 32 | a, b, c, 33 | -a / d, -b / d, 0.5, 34 | 0.5, -b / e, -c / e 35 | ); 36 | } 37 | 38 | vec3 YCbCr_to_RGB(vec3 YCbCr) { 39 | return YCbCr * mat3( 40 | 1.0, 0.0, e, 41 | 1.0, -c / b * d, -a / b * e, 42 | 1.0, d, 0.0 43 | ); 44 | } 45 | 46 | float get_max_l() { 47 | if (max_cll > 0.0) 48 | return max_cll; 49 | 50 | if (max_luma > 0.0) 51 | return max_luma; 52 | 53 | return 1000.0; 54 | } 55 | 56 | float f(float Y) { 57 | Y = pow(Y, 1.0 / 2.4); 58 | 59 | float pHDR = 1.0 + 32.0 * pow(get_max_l() / 10000.0, 1.0 / 2.4); 60 | float pSDR = 1.0 + 32.0 * pow(reference_white / 10000.0, 1.0 / 2.4); 61 | 62 | float Yp = log(1.0 + (pHDR - 1.0) * Y) / log(pHDR); 63 | 64 | float Yc; 65 | if (Yp <= 0.7399) Yc = Yp * 1.0770; 66 | else if (Yp < 0.9909) Yc = Yp * (-1.1510 * Yp + 2.7811) - 0.6302; 67 | else Yc = Yp * 0.5000 + 0.5000; 68 | 69 | float Ysdr = (pow(pSDR, Yc) - 1.0) / (pSDR - 1.0); 70 | 71 | Y = pow(Ysdr, 2.4); 72 | 73 | return Y; 74 | } 75 | 76 | float curve(float Y) { 77 | return f(Y); 78 | } 79 | 80 | vec3 tone_mapping(vec3 YCbCr) { 81 | YCbCr /= get_max_l() / reference_white; 82 | 83 | float Y = YCbCr.r; 84 | float Cb = YCbCr.g; 85 | float Cr = YCbCr.b; 86 | 87 | float Ysdr = curve(Y); 88 | 89 | float Yr = Ysdr / max(1.1 * Y, 1e-6); 90 | Cb *= Yr; 91 | Cr *= Yr; 92 | Y = Ysdr - max(0.1 * Cr, 0.0); 93 | 94 | return vec3(Y, Cb, Cr); 95 | } 96 | 97 | vec4 hook() { 98 | vec4 color = HOOKED_tex(HOOKED_pos); 99 | 100 | color.rgb = RGB_to_YCbCr(color.rgb); 101 | color.rgb = tone_mapping(color.rgb); 102 | color.rgb = YCbCr_to_RGB(color.rgb); 103 | 104 | return color; 105 | } 106 | 107 | //!HOOK OUTPUT 108 | //!BIND HOOKED 109 | //!DESC black point compensation 110 | 111 | // https://www.color.org/WP40-Black_Point_Compensation_2010-07-27.pdf 112 | 113 | vec3 RGB_to_XYZ(vec3 RGB) { 114 | return RGB * mat3( 115 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 116 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 117 | 0.000000000000000, 0.028072693049087428, 1.060985057710791 118 | ); 119 | } 120 | 121 | vec3 XYZ_to_RGB(vec3 XYZ) { 122 | return XYZ * mat3( 123 | 1.716651187971268, -0.355670783776392, -0.253366281373660, 124 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 125 | 0.017639857445311, -0.042770613257809, 0.942103121235474 126 | ); 127 | } 128 | 129 | vec3 black_point_compensation(vec3 XYZ, float s, float d) { 130 | float r = (1.0 - d) / (1.0 - s); 131 | return r * XYZ + (1.0 - r) * RGB_to_XYZ(vec3(1.0)); 132 | } 133 | 134 | vec4 hook() { 135 | vec4 color = HOOKED_tex(HOOKED_pos); 136 | 137 | color.rgb = RGB_to_XYZ(color.rgb); 138 | color.rgb = black_point_compensation(color.rgb, 0.0, 0.001); 139 | color.rgb = XYZ_to_RGB(color.rgb); 140 | 141 | return color; 142 | } 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HDR Toys 2 | 3 | A set of color conversion shaders for mpv-player (gpu-next). 4 | 5 | For more detailed information, please visit the [wiki](https://github.com/natural-harmonia-gropius/hdr-toys/wiki). 6 | 7 | | | | | 8 | | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | 9 | | image | image | image | 10 | 11 | ## Getting Started 12 | 13 | 1. Download [hdr-toys.zip](https://github.com/natural-harmonia-gropius/hdr-toys/archive/refs/heads/master.zip), extract it. 14 | 2. Copy the `shaders`, `scripts`, and `hdr-toys.conf` files to your [mpv config folder](https://mpv.io/manual/master/#configuration-files). 15 | 3. Add `include=~~/hdr-toys.conf` to your `mpv.conf`. 16 | 17 | ## FAQ 18 | 19 | - **Shader not working or looks incorrect.** 20 | 21 | This set of shaders is specifically designed for use with [**vo=gpu-next**](https://mpv.io/manual/master/#video-output-drivers-gpu-next). Make sure **NOT** to set `target-peak`, `icc-profile`, or similar options in `mpv.conf`. 22 | 23 | For a complete configuration example, check out [natural-harmonia-gropius/mpv-config](https://github.com/natural-harmonia-gropius/mpv-config). 24 | 25 | If you've confirmed these settings and the problem persists, please submit an issue. 26 | 27 | You may notice black areas in files with linear light input (such as OpenEXR), which is due to the limitations of 16-bit floating-point values. 28 | 29 | - **UI/OSD looks washed out.** 30 | 31 | To ensure the video input meets the standards, I use a little trick by setting `target-prim` and `target-trc` to match the input values. As a side effect, the OSD appears washed out, I currently have no solution. 32 | 33 | - **I'm using an HDR/WCG display.** 34 | 35 | Use PQ as the transfer function instead of BT.1886, and set the reference white to match your display's peak brightness. (This may behave unexpectedly for various reasons. Be cautious—it could produce extreme output.) 36 | 37 | ```ini 38 | target-colorspace-hint=yes 39 | ... 40 | glsl-shader=~~/shaders/hdr-toys/transfer-function/pq.glsl 41 | glsl-shader-opts-append=reference_white=1000 42 | glsl-shader-opts-append=contrast_ratio=1000000 43 | ``` 44 | 45 | Replace all `gamut-mapping/*` lines in `hdr-toys.conf` with `gamut-mapping/clip.glsl`. Then modify the `#define to *` in `clip.glsl` to match your display's gamut. 46 | 47 | ```ini 48 | glsl-shader=~~/shaders/hdr-toys/gamut-mapping/clip.glsl 49 | ``` 50 | 51 | ```glsl 52 | #define from BT2020 53 | #define to P3D65 54 | ``` 55 | 56 | When to is equal to from (e.g., `#define to BT2020`), you can comment out or remove these gamut-mapping lines in the conf. 57 | 58 | - **I want the image to look more filmic.** 59 | 60 | Add the following parameters to the conf file. 61 | 62 | ```ini 63 | glsl-shader-opts-append=auto_exposure_anchor=0.5 64 | glsl-shader-opts-append=contrast_bias=0.15 65 | glsl-shader-opts-append=chroma_correction_scaling=1.33 66 | glsl-shader-opts-append=chroma_correction_power=2.4 67 | ``` 68 | 69 | - **What does hdr-toys.lua do?** 70 | 71 | This provides a way to indirectly pass the necessary information using the [glsl-shader-opts](https://mpv.io/manual/master/#options-glsl-shader-opts). 72 | 73 | - the number of frames for 1/3 second, for reduce flickering. 74 | 75 | - **I don't use mpv, can I use these shaders?** 76 | 77 | These shaders use [mpv .hook syntax](https://libplacebo.org/custom-shaders/), which requires `libplacebo` for execution. ffmpeg and VLC should be able to use. In theory, porting to other shader like languages is very feasible. 78 | -------------------------------------------------------------------------------- /shaders/hdr-toys/tone-mapping/linear.glsl: -------------------------------------------------------------------------------- 1 | // Linear applies a simple scaling to the I component 2 | 3 | //!PARAM min_luma 4 | //!TYPE float 5 | 0.0 6 | 7 | //!PARAM max_luma 8 | //!TYPE float 9 | 0.0 10 | 11 | //!PARAM max_cll 12 | //!TYPE float 13 | 0.0 14 | 15 | //!PARAM scene_max_r 16 | //!TYPE float 17 | 0.0 18 | 19 | //!PARAM scene_max_g 20 | //!TYPE float 21 | 0.0 22 | 23 | //!PARAM scene_max_b 24 | //!TYPE float 25 | 0.0 26 | 27 | //!PARAM max_pq_y 28 | //!TYPE float 29 | 0.0 30 | 31 | //!PARAM reference_white 32 | //!TYPE float 33 | //!MINIMUM 0.0 34 | //!MAXIMUM 1000.0 35 | 203.0 36 | 37 | //!PARAM chroma_correction_scaling 38 | //!TYPE float 39 | //!MINIMUM 0.0 40 | //!MAXIMUM 1.0 41 | 1.0 42 | 43 | //!HOOK OUTPUT 44 | //!BIND HOOKED 45 | //!DESC tone mapping (linear) 46 | 47 | const float m1 = 2610.0 / 4096.0 / 4.0; 48 | const float m2 = 2523.0 / 4096.0 * 128.0; 49 | const float c1 = 3424.0 / 4096.0; 50 | const float c2 = 2413.0 / 4096.0 * 32.0; 51 | const float c3 = 2392.0 / 4096.0 * 32.0; 52 | const float pw = 10000.0; 53 | 54 | float pq_eotf_inv(float x) { 55 | float t = pow(x / pw, m1); 56 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 57 | } 58 | 59 | vec3 pq_eotf_inv(vec3 color) { 60 | return vec3( 61 | pq_eotf_inv(color.r), 62 | pq_eotf_inv(color.g), 63 | pq_eotf_inv(color.b) 64 | ); 65 | } 66 | 67 | float pq_eotf(float x) { 68 | float t = pow(x, 1.0 / m2); 69 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 70 | } 71 | 72 | vec3 pq_eotf(vec3 color) { 73 | return vec3( 74 | pq_eotf(color.r), 75 | pq_eotf(color.g), 76 | pq_eotf(color.b) 77 | ); 78 | } 79 | 80 | vec3 RGB_to_XYZ(vec3 RGB) { 81 | return RGB * mat3( 82 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 83 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 84 | 0.0 , 0.028072693049087428, 1.060985057710791 85 | ); 86 | } 87 | 88 | vec3 XYZ_to_RGB(vec3 XYZ) { 89 | return XYZ * mat3( 90 | 1.716651187971268, -0.355670783776392, -0.25336628137366, 91 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 92 | 0.017639857445311, -0.042770613257809, 0.942103121235474 93 | ); 94 | } 95 | 96 | vec3 XYZ_to_LMS(vec3 XYZ) { 97 | return XYZ * mat3( 98 | 0.3592832590121217, 0.6976051147779502, -0.0358915932320290, 99 | -0.1920808463704993, 1.1004767970374321, 0.0753748658519118, 100 | 0.0070797844607479, 0.0748396662186362, 0.8433265453898765 101 | ); 102 | } 103 | 104 | vec3 LMS_to_XYZ(vec3 LMS) { 105 | return LMS * mat3( 106 | 2.0701522183894223, -1.3263473389671563, 0.2066510476294053, 107 | 0.3647385209748072, 0.6805660249472273, -0.0453045459220347, 108 | -0.0497472075358123, -0.0492609666966131, 1.1880659249923042 109 | ); 110 | } 111 | 112 | vec3 LMS_to_ICtCp(vec3 LMS) { 113 | return LMS * mat3( 114 | 2048.0 / 4096.0, 2048.0 / 4096.0, 0.0 / 4096.0, 115 | 6610.0 / 4096.0, -13613.0 / 4096.0, 7003.0 / 4096.0, 116 | 17933.0 / 4096.0, -17390.0 / 4096.0, -543.0 / 4096.0 117 | ); 118 | } 119 | 120 | vec3 ICtCp_to_LMS(vec3 ICtCp) { 121 | return ICtCp * mat3( 122 | 1.0, 0.0086090370379328, 0.1110296250030260, 123 | 1.0, -0.0086090370379328, -0.1110296250030260, 124 | 1.0, 0.5600313357106791, -0.3206271749873189 125 | ); 126 | } 127 | 128 | vec3 RGB_to_ICtCp(vec3 color) { 129 | color *= reference_white; 130 | color = RGB_to_XYZ(color); 131 | color = XYZ_to_LMS(color); 132 | color = pq_eotf_inv(color); 133 | color = LMS_to_ICtCp(color); 134 | return color; 135 | } 136 | 137 | vec3 ICtCp_to_RGB(vec3 color) { 138 | color = ICtCp_to_LMS(color); 139 | color = pq_eotf(color); 140 | color = LMS_to_XYZ(color); 141 | color = XYZ_to_RGB(color); 142 | color /= reference_white; 143 | return color; 144 | } 145 | 146 | float get_max_i() { 147 | if (max_pq_y > 0.0) 148 | return max_pq_y; 149 | 150 | if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { 151 | vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); 152 | return pq_eotf_inv(RGB_to_XYZ(scene_max_rgb).y); 153 | } 154 | 155 | if (max_cll > 0.0) 156 | return pq_eotf_inv(max_cll); 157 | 158 | if (max_luma > 0.0) 159 | return pq_eotf_inv(max_luma); 160 | 161 | return pq_eotf_inv(1000.0); 162 | } 163 | 164 | float get_min_i() { 165 | if (min_luma > 0.0) 166 | return pq_eotf_inv(min_luma); 167 | 168 | return pq_eotf_inv(0.001); 169 | } 170 | 171 | float f(float x, float a, float b, float c, float d) { 172 | return (x - a) * (d - c) / (b - a) + c; 173 | } 174 | 175 | float curve(float x) { 176 | float ow = pq_eotf_inv(reference_white); 177 | float ob = pq_eotf_inv(reference_white / 1000.0); 178 | float iw = max(get_max_i(), ow); 179 | float ib = min(get_min_i(), ob); 180 | return f(x, ib, iw, ob, ow); 181 | } 182 | 183 | vec2 chroma_correction(vec2 ab, float i1, float i2) { 184 | float r1 = i1 / max(i2, 1e-6); 185 | float r2 = i2 / max(i1, 1e-6); 186 | return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); 187 | } 188 | 189 | vec3 tone_mapping(vec3 iab) { 190 | float i2 = curve(iab.x); 191 | vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); 192 | return vec3(i2, ab2); 193 | } 194 | 195 | vec4 hook() { 196 | vec4 color = HOOKED_tex(HOOKED_pos); 197 | 198 | color.rgb = RGB_to_ICtCp(color.rgb); 199 | color.rgb = tone_mapping(color.rgb); 200 | color.rgb = ICtCp_to_RGB(color.rgb); 201 | 202 | return color; 203 | } 204 | -------------------------------------------------------------------------------- /shaders/hdr-toys/tone-mapping/reinhard.glsl: -------------------------------------------------------------------------------- 1 | // Photographic tone reproduction for digital images 2 | // https://doi.org/10.1145/566654.566575 3 | 4 | //!PARAM max_luma 5 | //!TYPE float 6 | 0.0 7 | 8 | //!PARAM max_cll 9 | //!TYPE float 10 | 0.0 11 | 12 | //!PARAM scene_max_r 13 | //!TYPE float 14 | 0.0 15 | 16 | //!PARAM scene_max_g 17 | //!TYPE float 18 | 0.0 19 | 20 | //!PARAM scene_max_b 21 | //!TYPE float 22 | 0.0 23 | 24 | //!PARAM max_pq_y 25 | //!TYPE float 26 | 0.0 27 | 28 | //!PARAM reference_white 29 | //!TYPE float 30 | //!MINIMUM 0.0 31 | //!MAXIMUM 1000.0 32 | 203.0 33 | 34 | //!PARAM chroma_correction_scaling 35 | //!TYPE float 36 | //!MINIMUM 0.0 37 | //!MAXIMUM 1.0 38 | 1.0 39 | 40 | //!HOOK OUTPUT 41 | //!BIND HOOKED 42 | //!DESC tone mapping (reinhard) 43 | 44 | const float m1 = 2610.0 / 4096.0 / 4.0; 45 | const float m2 = 2523.0 / 4096.0 * 128.0; 46 | const float c1 = 3424.0 / 4096.0; 47 | const float c2 = 2413.0 / 4096.0 * 32.0; 48 | const float c3 = 2392.0 / 4096.0 * 32.0; 49 | const float pw = 10000.0; 50 | 51 | float pq_eotf_inv(float x) { 52 | float t = pow(x / pw, m1); 53 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 54 | } 55 | 56 | vec3 pq_eotf_inv(vec3 color) { 57 | return vec3( 58 | pq_eotf_inv(color.r), 59 | pq_eotf_inv(color.g), 60 | pq_eotf_inv(color.b) 61 | ); 62 | } 63 | 64 | float pq_eotf(float x) { 65 | float t = pow(x, 1.0 / m2); 66 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 67 | } 68 | 69 | vec3 pq_eotf(vec3 color) { 70 | return vec3( 71 | pq_eotf(color.r), 72 | pq_eotf(color.g), 73 | pq_eotf(color.b) 74 | ); 75 | } 76 | 77 | vec3 RGB_to_XYZ(vec3 RGB) { 78 | return RGB * mat3( 79 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 80 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 81 | 0.0 , 0.028072693049087428, 1.060985057710791 82 | ); 83 | } 84 | 85 | vec3 XYZ_to_RGB(vec3 XYZ) { 86 | return XYZ * mat3( 87 | 1.716651187971268, -0.355670783776392, -0.25336628137366, 88 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 89 | 0.017639857445311, -0.042770613257809, 0.942103121235474 90 | ); 91 | } 92 | 93 | vec3 XYZ_to_LMS(vec3 XYZ) { 94 | return XYZ * mat3( 95 | 0.3592832590121217, 0.6976051147779502, -0.0358915932320290, 96 | -0.1920808463704993, 1.1004767970374321, 0.0753748658519118, 97 | 0.0070797844607479, 0.0748396662186362, 0.8433265453898765 98 | ); 99 | } 100 | 101 | vec3 LMS_to_XYZ(vec3 LMS) { 102 | return LMS * mat3( 103 | 2.0701522183894223, -1.3263473389671563, 0.2066510476294053, 104 | 0.3647385209748072, 0.6805660249472273, -0.0453045459220347, 105 | -0.0497472075358123, -0.0492609666966131, 1.1880659249923042 106 | ); 107 | } 108 | 109 | vec3 LMS_to_ICtCp(vec3 LMS) { 110 | return LMS * mat3( 111 | 2048.0 / 4096.0, 2048.0 / 4096.0, 0.0 / 4096.0, 112 | 6610.0 / 4096.0, -13613.0 / 4096.0, 7003.0 / 4096.0, 113 | 17933.0 / 4096.0, -17390.0 / 4096.0, -543.0 / 4096.0 114 | ); 115 | } 116 | 117 | vec3 ICtCp_to_LMS(vec3 ICtCp) { 118 | return ICtCp * mat3( 119 | 1.0, 0.0086090370379328, 0.1110296250030260, 120 | 1.0, -0.0086090370379328, -0.1110296250030260, 121 | 1.0, 0.5600313357106791, -0.3206271749873189 122 | ); 123 | } 124 | 125 | vec3 RGB_to_ICtCp(vec3 color) { 126 | color *= reference_white; 127 | color = RGB_to_XYZ(color); 128 | color = XYZ_to_LMS(color); 129 | color = pq_eotf_inv(color); 130 | color = LMS_to_ICtCp(color); 131 | return color; 132 | } 133 | 134 | vec3 ICtCp_to_RGB(vec3 color) { 135 | color = ICtCp_to_LMS(color); 136 | color = pq_eotf(color); 137 | color = LMS_to_XYZ(color); 138 | color = XYZ_to_RGB(color); 139 | color /= reference_white; 140 | return color; 141 | } 142 | 143 | float get_max_l() { 144 | if (max_pq_y > 0.0) 145 | return pq_eotf(max_pq_y); 146 | 147 | if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { 148 | vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); 149 | return RGB_to_XYZ(scene_max_rgb).y; 150 | } 151 | 152 | if (max_cll > 0.0) 153 | return max_cll; 154 | 155 | if (max_luma > 0.0) 156 | return max_luma; 157 | 158 | return 1000.0; 159 | } 160 | 161 | float f(float x, float w) { 162 | float simple = x / (1.0 + x); 163 | float extended = simple * (1.0 + x / (w * w)); 164 | return extended; 165 | } 166 | 167 | float curve(float x) { 168 | float w = get_max_l(); 169 | return f(x, w); 170 | } 171 | 172 | vec2 chroma_correction(vec2 ab, float i1, float i2) { 173 | float r1 = i1 / max(i2, 1e-6); 174 | float r2 = i2 / max(i1, 1e-6); 175 | return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); 176 | } 177 | 178 | vec3 tone_mapping(vec3 iab) { 179 | float i2 = pq_eotf_inv(curve(pq_eotf(iab.x) / reference_white) * reference_white); 180 | vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); 181 | return vec3(i2, ab2); 182 | } 183 | 184 | vec4 hook() { 185 | vec4 color = HOOKED_tex(HOOKED_pos); 186 | 187 | color.rgb = RGB_to_ICtCp(color.rgb); 188 | color.rgb = tone_mapping(color.rgb); 189 | color.rgb = ICtCp_to_RGB(color.rgb); 190 | 191 | return color; 192 | } 193 | 194 | //!HOOK OUTPUT 195 | //!BIND HOOKED 196 | //!DESC black point compensation 197 | 198 | // https://www.color.org/WP40-Black_Point_Compensation_2010-07-27.pdf 199 | 200 | vec3 RGB_to_XYZ(vec3 RGB) { 201 | return RGB * mat3( 202 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 203 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 204 | 0.000000000000000, 0.028072693049087428, 1.060985057710791 205 | ); 206 | } 207 | 208 | vec3 XYZ_to_RGB(vec3 XYZ) { 209 | return XYZ * mat3( 210 | 1.716651187971268, -0.355670783776392, -0.253366281373660, 211 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 212 | 0.017639857445311, -0.042770613257809, 0.942103121235474 213 | ); 214 | } 215 | 216 | vec3 black_point_compensation(vec3 XYZ, float s, float d) { 217 | float r = (1.0 - d) / (1.0 - s); 218 | return r * XYZ + (1.0 - r) * RGB_to_XYZ(vec3(1.0)); 219 | } 220 | 221 | vec4 hook() { 222 | vec4 color = HOOKED_tex(HOOKED_pos); 223 | 224 | color.rgb = RGB_to_XYZ(color.rgb); 225 | color.rgb = black_point_compensation(color.rgb, 0.0, 0.001); 226 | color.rgb = XYZ_to_RGB(color.rgb); 227 | 228 | return color; 229 | } 230 | -------------------------------------------------------------------------------- /shaders/hdr-toys/tone-mapping/bt2446c.glsl: -------------------------------------------------------------------------------- 1 | // ITU-R BT.2446 Conversion Method C 2 | // https://www.itu.int/pub/R-REP-BT.2446 3 | 4 | //!PARAM reference_white 5 | //!TYPE float 6 | //!MINIMUM 0.0 7 | //!MAXIMUM 1000.0 8 | 203.0 9 | 10 | //!PARAM alpha 11 | //!TYPE float 12 | //!MINIMUM 0.00 13 | //!MAXIMUM 0.33 14 | 0.04 15 | 16 | //!PARAM sigma 17 | //!TYPE float 18 | //!MINIMUM 0.0 19 | //!MAXIMUM 1.0 20 | 0.33 21 | 22 | //!HOOK OUTPUT 23 | //!BIND HOOKED 24 | //!WHEN alpha 25 | //!DESC tone mapping (bt.2446c, crosstalk) 26 | 27 | // The crosstalk matrix is applied such that saturations of linear signals are reduced to achromatic to 28 | // avoid hue changes caused by clipping of compressed highlight parts. 29 | 30 | vec3 crosstalk(vec3 x, float a) { 31 | float b = 1.0 - 2.0 * a; 32 | mat3 transform = mat3( 33 | b, a, a, 34 | a, b, a, 35 | a, a, b 36 | ); 37 | return x * transform; 38 | } 39 | 40 | vec4 hook() { 41 | vec4 color = HOOKED_tex(HOOKED_pos); 42 | 43 | color.rgb = crosstalk(color.rgb, alpha); 44 | 45 | return color; 46 | } 47 | 48 | //!HOOK OUTPUT 49 | //!BIND HOOKED 50 | //!WHEN sigma 51 | //!DESC tone mapping (bt.2446c, chroma correction) 52 | 53 | // Optional processing of chroma correction above HDR Reference White 54 | 55 | // In SDR production, highlight parts are sometimes intentionally expressed as white. The processing 56 | // described in this section is optionally used to shift chroma above HDR Reference White to achromatic 57 | // when the converted SDR content requires a degree of consistency for SDR production content. This 58 | // processing is applied as needed before the tone-mapping processing. 59 | 60 | vec3 RGB_to_XYZ(vec3 RGB) { 61 | return RGB * mat3( 62 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 63 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 64 | 0.000000000000000, 0.028072693049087428, 1.060985057710791 65 | ); 66 | } 67 | 68 | vec3 XYZ_to_RGB(vec3 XYZ) { 69 | return XYZ * mat3( 70 | 1.716651187971268, -0.355670783776392, -0.253366281373660, 71 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 72 | 0.017639857445311, -0.042770613257809, 0.942103121235474 73 | ); 74 | } 75 | 76 | float cbrt(float x) { 77 | return sign(x) * pow(abs(x), 1.0 / 3.0); 78 | } 79 | 80 | const float delta = 6.0 / 29.0; 81 | const float deltac = delta * 2.0 / 3.0; 82 | 83 | float f(float x) { 84 | return x > pow(delta, 3.0) ? 85 | cbrt(x) : 86 | deltac + x / (3.0 * pow(delta, 2.0)); 87 | } 88 | 89 | vec3 f(vec3 x) { 90 | return vec3(f(x.x), f(x.y), f(x.z)); 91 | } 92 | 93 | float f_inv(float x) { 94 | return x > delta ? 95 | pow(x, 3.0) : 96 | (x - deltac) * (3.0 * pow(delta, 2.0)); 97 | } 98 | 99 | vec3 f_inv(vec3 x) { 100 | return vec3(f_inv(x.x), f_inv(x.y), f_inv(x.z)); 101 | } 102 | 103 | const vec3 XYZn = vec3(0.95047, 1.00000, 1.08883); 104 | 105 | vec3 XYZ_to_Lab(vec3 XYZ) { 106 | XYZ = f(XYZ / XYZn); 107 | 108 | float X = XYZ.x; 109 | float Y = XYZ.y; 110 | float Z = XYZ.z; 111 | 112 | float L = 116.0 * Y - 16.0; 113 | float a = 500.0 * (X - Y); 114 | float b = 200.0 * (Y - Z); 115 | 116 | return vec3(L, a, b); 117 | } 118 | 119 | vec3 Lab_to_XYZ(vec3 Lab) { 120 | float L = Lab.x; 121 | float a = Lab.y; 122 | float b = Lab.z; 123 | 124 | float Y = (L + 16.0) / 116.0; 125 | float X = Y + a / 500.0; 126 | float Z = Y - b / 200.0; 127 | 128 | vec3 XYZ = f_inv(vec3(X, Y, Z)) * XYZn; 129 | 130 | return XYZ; 131 | } 132 | 133 | vec3 RGB_to_Lab(vec3 color) { 134 | color = RGB_to_XYZ(color); 135 | color = XYZ_to_Lab(color); 136 | return color; 137 | } 138 | 139 | vec3 Lab_to_RGB(vec3 color) { 140 | color = Lab_to_XYZ(color); 141 | color = XYZ_to_RGB(color); 142 | return color; 143 | } 144 | 145 | const float epsilon = 1e-6; 146 | 147 | vec3 Lab_to_LCh(vec3 Lab) { 148 | float L = Lab.x; 149 | float a = Lab.y; 150 | float b = Lab.z; 151 | 152 | float C = length(vec2(a, b)); 153 | float h = (abs(a) < epsilon && abs(b) < epsilon) ? 0.0 : atan(b, a); 154 | 155 | return vec3(L, C, h); 156 | } 157 | 158 | vec3 LCh_to_Lab(vec3 LCh) { 159 | float L = LCh.x; 160 | float C = LCh.y; 161 | float h = LCh.z; 162 | 163 | C = max(C, 0.0); 164 | float a = C * cos(h); 165 | float b = C * sin(h); 166 | 167 | return vec3(L, a, b); 168 | } 169 | 170 | float chroma_correction(float L, float Lref, float Lmax, float sigma) { 171 | return L <= Lref ? 1.0 : max(1.0 - sigma * (L - Lref) / (Lmax - Lref), 0.0); 172 | } 173 | 174 | vec4 hook() { 175 | vec4 color = HOOKED_tex(HOOKED_pos); 176 | 177 | const float Lref = RGB_to_Lab(vec3(1.0)).x; 178 | const float Lmax = RGB_to_Lab(vec3(1000.0 / reference_white)).x; 179 | 180 | color.rgb = RGB_to_Lab(color.rgb); 181 | color.rgb = Lab_to_LCh(color.rgb); 182 | color.y *= chroma_correction(color.x, Lref, Lmax, sigma); 183 | color.rgb = LCh_to_Lab(color.rgb); 184 | color.rgb = Lab_to_RGB(color.rgb); 185 | 186 | return color; 187 | } 188 | 189 | //!HOOK OUTPUT 190 | //!BIND HOOKED 191 | //!DESC tone mapping (bt.2446c) 192 | 193 | vec3 RGB_to_XYZ(vec3 RGB) { 194 | return RGB * mat3( 195 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 196 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 197 | 0.000000000000000, 0.028072693049087428, 1.060985057710791 198 | ); 199 | } 200 | 201 | vec3 XYZ_to_RGB(vec3 XYZ) { 202 | return XYZ * mat3( 203 | 1.716651187971268, -0.355670783776392, -0.253366281373660, 204 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 205 | 0.017639857445311, -0.042770613257809, 0.942103121235474 206 | ); 207 | } 208 | 209 | vec3 XYZ_to_xyY(vec3 XYZ) { 210 | float X = XYZ.x; 211 | float Y = XYZ.y; 212 | float Z = XYZ.z; 213 | 214 | float divisor = X + Y + Z; 215 | if (divisor == 0.0) divisor = 1e-6; 216 | 217 | float x = X / divisor; 218 | float y = Y / divisor; 219 | 220 | return vec3(x, y, Y); 221 | } 222 | 223 | vec3 xyY_to_XYZ(vec3 xyY) { 224 | float x = xyY.x; 225 | float y = xyY.y; 226 | float Y = xyY.z; 227 | 228 | float multiplier = Y / max(y, 1e-6); 229 | 230 | float z = 1.0 - x - y; 231 | float X = x * multiplier; 232 | float Z = z * multiplier; 233 | 234 | return vec3(X, Y, Z); 235 | } 236 | 237 | const float ip = 0.58535; // linear length 238 | const float k1 = 0.83802; // linear strength 239 | const float k3 = 0.74204; // shoulder strength 240 | 241 | float f(float Y, float k1, float k3, float ip) { 242 | ip /= k1; 243 | float k2 = (k1 * ip) * (1.0 - k3); 244 | float k4 = (k1 * ip) - (k2 * log(1.0 - k3)); 245 | return Y < ip ? Y * k1 : log((Y / ip) - k3) * k2 + k4; 246 | } 247 | 248 | float curve(float x) { 249 | return f(x, k1, k3, ip); 250 | } 251 | 252 | vec4 hook() { 253 | vec4 color = HOOKED_tex(HOOKED_pos); 254 | 255 | color.rgb = RGB_to_XYZ(color.rgb); 256 | color.rgb = XYZ_to_xyY(color.rgb); 257 | color.z = curve(color.z); 258 | color.rgb = xyY_to_XYZ(color.rgb); 259 | color.rgb = XYZ_to_RGB(color.rgb); 260 | 261 | return color; 262 | } 263 | 264 | //!HOOK OUTPUT 265 | //!BIND HOOKED 266 | //!WHEN alpha 267 | //!DESC tone mapping (bt.2446c, inverse crosstalk) 268 | 269 | // The inverse crosstalk matrix is applied to ensure that the original hues of input HDR images are 270 | // recovered. 271 | 272 | vec3 crosstalk_inv(vec3 x, float a) { 273 | float b = 1.0 - a; 274 | float c = 1.0 / (1.0 - 3.0 * a); 275 | mat3 transform = mat3( 276 | b, -a, -a, 277 | -a, b, -a, 278 | -a, -a, b 279 | ); 280 | return x * transform * c; 281 | } 282 | 283 | vec4 hook() { 284 | vec4 color = HOOKED_tex(HOOKED_pos); 285 | 286 | color.rgb = crosstalk_inv(color.rgb, alpha); 287 | 288 | return color; 289 | } 290 | 291 | //!HOOK OUTPUT 292 | //!BIND HOOKED 293 | //!DESC tone mapping (bt.2446c, signal scaling) 294 | 295 | // Handling 109% range (super-whites) and black point compensation 296 | 297 | float f(float x, float a, float b, float c, float d) { 298 | return (x - a) * (d - c) / (b - a) + c; 299 | } 300 | 301 | vec3 f(vec3 x, float a, float b, float c, float d) { 302 | return vec3( 303 | f(x.x, a, b, c, d), 304 | f(x.y, a, b, c, d), 305 | f(x.z, a, b, c, d) 306 | ); 307 | } 308 | 309 | vec4 hook() { 310 | vec4 color = HOOKED_tex(HOOKED_pos); 311 | 312 | color.rgb = f(color.rgb, 0.0, 1019.0 / 940.0, 0.001, 1.0); 313 | 314 | return color; 315 | } 316 | -------------------------------------------------------------------------------- /shaders/hdr-toys/gamut-mapping/bottosson.glsl: -------------------------------------------------------------------------------- 1 | // https://bottosson.github.io/posts/gamutclipping/ 2 | // https://www.shadertoy.com/view/7sXcWn 3 | 4 | //!PARAM softness_scale 5 | //!TYPE float 6 | //!MINIMUM 0.0 7 | //!MAXIMUM 1.0 8 | 0.3 9 | 10 | //!HOOK OUTPUT 11 | //!BIND HOOKED 12 | //!DESC gamut mapping (bottosson, soft) 13 | 14 | float cbrt(float x) { 15 | return sign(x) * pow(abs(x), 1.0 / 3.0); 16 | } 17 | 18 | vec3 cbrt(vec3 color) { 19 | return vec3( 20 | cbrt(color.x), 21 | cbrt(color.y), 22 | cbrt(color.z) 23 | ); 24 | } 25 | 26 | vec3 RGB_to_XYZ(vec3 RGB) { 27 | return RGB * mat3( 28 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 29 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 30 | 0.000000000000000, 0.028072693049087428, 1.060985057710791 31 | ); 32 | } 33 | 34 | vec3 XYZ_to_RGB(vec3 XYZ) { 35 | return XYZ * mat3( 36 | 1.716651187971268, -0.355670783776392, -0.253366281373660, 37 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 38 | 0.017639857445311, -0.042770613257809, 0.942103121235474 39 | ); 40 | } 41 | 42 | vec3 XYZ_to_LMS(vec3 XYZ) { 43 | return XYZ * mat3( 44 | 0.8190224379967030, 0.3619062600528904, -0.1288737815209879, 45 | 0.0329836539323885, 0.9292868615863434, 0.0361446663506424, 46 | 0.0481771893596242, 0.2642395317527308, 0.6335478284694309 47 | ); 48 | } 49 | 50 | vec3 LMS_to_XYZ(vec3 LMS) { 51 | return LMS * mat3( 52 | 1.2268798758459243, -0.5578149944602171, 0.2813910456659647, 53 | -0.0405757452148008, 1.1122868032803170, -0.0717110580655164, 54 | -0.0763729366746601, -0.4214933324022432, 1.5869240198367816 55 | ); 56 | } 57 | 58 | vec3 LMS_to_Lab(vec3 LMS) { 59 | return LMS * mat3( 60 | 0.2104542683093140, 0.7936177747023054, -0.0040720430116193, 61 | 1.9779985324311684, -2.4285922420485799, 0.4505937096174110, 62 | 0.0259040424655478, 0.7827717124575296, -0.8086757549230774 63 | ); 64 | } 65 | 66 | vec3 Lab_to_LMS(vec3 Lab) { 67 | return Lab * mat3( 68 | 1.0000000000000000, 0.3963377773761749, 0.2158037573099136, 69 | 1.0000000000000000, -0.1055613458156586, -0.0638541728258133, 70 | 1.0000000000000000, -0.0894841775298119, -1.2914855480194092 71 | ); 72 | } 73 | 74 | vec3 RGB_to_Lab(vec3 color) { 75 | color = RGB_to_XYZ(color); 76 | color = XYZ_to_LMS(color); 77 | color = cbrt(color); 78 | color = LMS_to_Lab(color); 79 | return color; 80 | } 81 | 82 | vec3 Lab_to_RGB(vec3 color) { 83 | color = Lab_to_LMS(color); 84 | color = pow(color, vec3(3.0)); 85 | color = LMS_to_XYZ(color); 86 | color = XYZ_to_RGB(color); 87 | return color; 88 | } 89 | 90 | const float epsilon = 1e-6; 91 | 92 | vec3 Lab_to_LCh(vec3 Lab) { 93 | float L = Lab.x; 94 | float a = Lab.y; 95 | float b = Lab.z; 96 | 97 | float C = length(vec2(a, b)); 98 | float h = (abs(a) < epsilon && abs(b) < epsilon) ? 0.0 : atan(b, a); 99 | 100 | return vec3(L, C, h); 101 | } 102 | 103 | vec3 LCh_to_Lab(vec3 LCh) { 104 | float L = LCh.x; 105 | float C = LCh.y; 106 | float h = LCh.z; 107 | 108 | C = max(C, 0.0); 109 | float a = C * cos(h); 110 | float b = C * sin(h); 111 | 112 | return vec3(L, a, b); 113 | } 114 | 115 | 116 | 117 | 118 | 119 | vec3 output_RGB_to_XYZ(vec3 RGB) { 120 | mat3 M = mat3( 121 | 0.41239079926595934, 0.357584339383878, 0.1804807884018343, 122 | 0.21263900587151027, 0.715168678767756, 0.07219231536073371, 123 | 0.01933081871559182, 0.11919477979462598, 0.9505321522496607); 124 | return RGB * M; 125 | } 126 | 127 | vec3 output_XYZ_to_RGB(vec3 XYZ) { 128 | mat3 M = mat3( 129 | 3.2409699419045226, -1.537383177570094, -0.4986107602930034, 130 | -0.9692436362808796, 1.8759675015077202, 0.04155505740717559, 131 | 0.05563007969699366, -0.20397695888897652, 1.0569715142428786); 132 | return XYZ * M; 133 | } 134 | 135 | float findCenter(vec3 x) { 136 | float a = 1.9779985324311684 * x.x - 2.4285922420485799 * x.y + 0.4505937096174110 * x.z; 137 | float b = 0.0259040424655478 * x.x + 0.7827717124575296 * x.y - 0.8086757549230774 * x.z; 138 | float C = sqrt(a*a+b*b); 139 | 140 | // Matrix derived for max(l,m,s) to be as close to macadam limit as possible 141 | // this makes it some kind of g0-like estimate 142 | mat3 M = mat3( 143 | 2.26923008, -1.43594808, 0.166718, 144 | -0.98545265, 2.12616699, -0.14071434, 145 | -0.02985871, -0.25753239, 1.2873911); 146 | x = x*M; 147 | 148 | float x_min = min(x.r,min(x.g,x.b)); 149 | float x_max = max(x.r,max(x.g,x.b)); 150 | 151 | float c = 0.5*(x_max+x_min); 152 | float s = (x_max-x_min); 153 | 154 | // math trickery to create values close to c and s, but without producing hard edges 155 | vec3 y = (x-c)/s; 156 | float c_smooth = c + dot(y*y*y, vec3(1.0/3.0))*s; 157 | float s_smooth = sqrt(dot(x-c,x-c)/2.0); 158 | 159 | return c_smooth; 160 | } 161 | 162 | vec2 findCenterAndPurity(vec3 x) { 163 | // Matrix derived for (c_smooth+s_smooth) to be an approximation of the macadam limit 164 | // this makes it some kind of g0-like estimate 165 | mat3 M = mat3( 166 | 2.26775149, -1.43293879, 0.1651873, 167 | -0.98535505, 2.1260072, -0.14065215, 168 | -0.02501605, -0.26349465, 1.2885107); 169 | 170 | x = x*M; 171 | 172 | float x_min = min(x.r,min(x.g,x.b)); 173 | float x_max = max(x.r,max(x.g,x.b)); 174 | 175 | float c = 0.5*(x_max+x_min); 176 | float s = (x_max-x_min); 177 | 178 | // math trickery to create values close to c and s, but without producing hard edges 179 | vec3 y = (x-c)/s; 180 | float c_smooth = c + dot(y*y*y, vec3(1.0/3.0))*s; 181 | float s_smooth = sqrt(dot(x-c,x-c)/2.0); 182 | return vec2(c_smooth, s_smooth); 183 | } 184 | 185 | 186 | vec3 toLms(vec3 c) { 187 | vec3 lms_ = XYZ_to_LMS(output_RGB_to_XYZ(c)); 188 | return sign(lms_)*pow(abs(lms_), vec3(1.0/3.0)); 189 | } 190 | 191 | float calculateC(vec3 lms) { 192 | // Most of this could be precomputed 193 | // Creating a transform that maps R,G,B in the target gamut to have same distance from grey axis 194 | 195 | vec3 lmsR = toLms(vec3(1.0,0.0,0.0)); 196 | vec3 lmsG = toLms(vec3(0.0,1.0,0.0)); 197 | vec3 lmsB = toLms(vec3(0.0,0.0,1.0)); 198 | 199 | vec3 uDir = (lmsR - lmsG)/sqrt(2.0); 200 | vec3 vDir = (lmsR + lmsG - 2.0*lmsB)/sqrt(6.0); 201 | 202 | mat3 to_uv = inverse(mat3( 203 | 1.0, uDir.x, vDir.x, 204 | 1.0, uDir.y, vDir.y, 205 | 1.0, uDir.z, vDir.z 206 | )); 207 | 208 | vec3 _uv = lms * to_uv; 209 | 210 | return sqrt(_uv.y*_uv.y + _uv.z*_uv.z); 211 | } 212 | 213 | vec3 calculateLCh(vec3 c) { 214 | vec3 lms = toLms(c); 215 | 216 | float maxLms = findCenter(lms); 217 | 218 | float a = 1.9779985324311684 * lms.x - 2.4285922420485799 * lms.y + 0.4505937096174110 * lms.z; 219 | float b = 0.0259040424655478 * lms.x + 0.7827717124575296 * lms.y - 0.8086757549230774 * lms.z; 220 | 221 | float C = sqrt(a*a+b*b); 222 | 223 | return vec3(maxLms, C, atan(-b, -a)); 224 | } 225 | 226 | vec2 expandShape(vec3 rgb, vec2 ST) { 227 | vec3 LCh = calculateLCh(rgb); 228 | vec2 STnew = vec2(LCh.x/LCh.y, (1.0-LCh.x)/LCh.y); 229 | STnew = (STnew + 3.0*STnew*STnew*LCh.y); 230 | 231 | return vec2(min(ST.x, STnew.x), min(ST.y, STnew.y)); 232 | } 233 | 234 | float expandScale(vec3 rgb, vec2 ST, float scale) { 235 | vec3 LCh = calculateLCh(rgb); 236 | float Cnew = (1.0/((ST.x/LCh.x) + (ST.y/(1.0-LCh.x)))); 237 | 238 | return max(LCh.y/Cnew, scale); 239 | } 240 | 241 | vec2 approximateShape() { 242 | float m = -softness_scale*0.2; 243 | float s = 1.0 + (softness_scale*0.2+softness_scale*0.8); 244 | 245 | vec2 ST = vec2(1000.0,1000.0); 246 | ST = expandShape(m+s*vec3(1.0,0.0,0.0), ST); 247 | ST = expandShape(m+s*vec3(1.0,1.0,0.0), ST); 248 | ST = expandShape(m+s*vec3(0.0,1.0,0.0), ST); 249 | ST = expandShape(m+s*vec3(0.0,1.0,1.0), ST); 250 | ST = expandShape(m+s*vec3(0.0,0.0,1.0), ST); 251 | ST = expandShape(m+s*vec3(1.0,0.0,1.0), ST); 252 | 253 | float scale = 0.0; 254 | scale = expandScale(m+s*vec3(1.0,0.0,0.0), ST, scale); 255 | scale = expandScale(m+s*vec3(1.0,1.0,0.0), ST, scale); 256 | scale = expandScale(m+s*vec3(0.0,1.0,0.0), ST, scale); 257 | scale = expandScale(m+s*vec3(0.0,1.0,1.0), ST, scale); 258 | scale = expandScale(m+s*vec3(0.0,0.0,1.0), ST, scale); 259 | scale = expandScale(m+s*vec3(1.0,0.0,1.0), ST, scale); 260 | 261 | return ST/scale; 262 | } 263 | 264 | vec3 compute(float L, float hue, float sat) { 265 | vec3 c = vec3(L, cos(hue), sin(hue)); 266 | 267 | float l_ = + 0.3963377773761749 * c.y + 0.2158037573099136 * c.z; 268 | float m_ = - 0.1055613458156586 * c.y - 0.0638541728258133 * c.z; 269 | float s_ = - 0.0894841775298119 * c.y - 1.2914855480194092 * c.z; 270 | 271 | vec3 lms = vec3(l_,m_,s_); 272 | 273 | vec2 MC = findCenterAndPurity(lms); 274 | 275 | lms -= MC.x; 276 | 277 | lms *= sat; 278 | 279 | lms += c.x; 280 | 281 | lms = lms*lms*lms; 282 | 283 | vec3 rgb = output_XYZ_to_RGB(LMS_to_XYZ(lms)); 284 | 285 | return rgb; 286 | } 287 | 288 | vec3 softSaturate(vec3 x, vec3 a) { 289 | a = clamp(a, 0.0,softness_scale); 290 | a = 1.0+a; 291 | x = min(x, a); 292 | vec3 b = (a-1.0)*sqrt(a/(2.0-a)); 293 | return 1.0 - (sqrt((x-a)*(x-a) + b*b) - b)/(sqrt(a*a+b*b)-b); 294 | } 295 | 296 | vec3 softClipColor(vec3 color) { 297 | // soft clip of rgb values to avoid artifacts of hard clipping 298 | // causes hues distortions, but is a smooth mapping 299 | 300 | float maxRGB = max(max(color.r, color.g), color.b); 301 | float minRGB = min(min(color.r, color.g), color.b); 302 | 303 | float grey = 0.2; 304 | 305 | vec3 x = color-grey; 306 | 307 | vec3 xsgn = sign(x); 308 | vec3 xscale = 0.5 + xsgn*(0.5-grey); 309 | x /= xscale; 310 | 311 | float softness_0 = maxRGB/(1.0+softness_scale)*softness_scale; 312 | float softness_1 = (1.0-minRGB)/(1.0+softness_scale)*softness_scale; 313 | 314 | vec3 softness = vec3(0.5)*(softness_0+softness_1 + xsgn*(softness_1 - softness_0)); 315 | 316 | return grey + xscale*xsgn*softSaturate(abs(x), softness); 317 | } 318 | 319 | 320 | 321 | 322 | 323 | vec4 hook() { 324 | vec4 color = HOOKED_tex(HOOKED_pos); 325 | 326 | vec3 lch = Lab_to_LCh(RGB_to_Lab(color.rgb)); 327 | 328 | float L = lch.x; 329 | float C = lch.y; 330 | float h = lch.z; 331 | 332 | // if (L >= 1.0) { 333 | // return vec4(vec3(1.0, 1.0, 1.0), color.a); 334 | // } 335 | 336 | // if (L <= 0.0) { 337 | // return vec4(vec3(0.0, 0.0, 0.0), color.a); 338 | // } 339 | 340 | if (C <= 1e-6) { 341 | return color; 342 | } 343 | 344 | vec2 ST = approximateShape(); 345 | float C_smooth = (1.0 / ((ST.x / L) + (ST.y / max(1.0 - L, 1e-6)))); 346 | color.rgb = compute(L, h, C / sqrt(C * C / C_smooth / C_smooth + 1.0)); 347 | color.rgb = softClipColor(color.rgb); 348 | 349 | return color; 350 | } 351 | -------------------------------------------------------------------------------- /shaders/hdr-toys/tone-mapping/false.glsl: -------------------------------------------------------------------------------- 1 | // Visualizes the image using false color 2 | 3 | // You can preview the colors in Visual Studio Code by the following plugin 4 | // https://marketplace.visualstudio.com/items?itemName=naumovs.color-highlight 5 | 6 | //!PARAM mode 7 | //!TYPE ENUM int 8 | luminance 9 | exposure 10 | 11 | //!PARAM reference_white 12 | //!TYPE float 13 | //!MINIMUM 0.0 14 | //!MAXIMUM 1000.0 15 | 203.0 16 | 17 | //!HOOK OUTPUT 18 | //!BIND HOOKED 19 | //!WHEN mode luminance = 20 | //!DESC tone mapping (false color, luminance) 21 | 22 | // oklch(0.99500 0.00 000.0) >10000 nits 23 | // oklch(0.94659 0.11 005.0) 10000 nits 24 | // oklch(0.83878 0.33 025.0) 4000 nits 25 | // oklch(0.73097 0.33 090.0) 2000 nits 26 | // oklch(0.52324 0.33 130.0) 1000 nits 27 | // oklch(0.33922 0.24 245.0) brighter than SDR 28 | // oklch(0.56925 0.00 000.0) SDR 29 | // oklch(0.20104 0.16 350.0) darker than SDR 30 | // oklch(0.13040 0.08 350.0) 0nits 31 | 32 | float cbrt(float x) { 33 | return sign(x) * pow(abs(x), 1.0 / 3.0); 34 | } 35 | 36 | vec3 cbrt(vec3 color) { 37 | return vec3( 38 | cbrt(color.x), 39 | cbrt(color.y), 40 | cbrt(color.z) 41 | ); 42 | } 43 | 44 | vec3 RGB_to_XYZ(vec3 RGB) { 45 | return RGB * mat3( 46 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 47 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 48 | 0.000000000000000, 0.028072693049087428, 1.060985057710791 49 | ); 50 | } 51 | 52 | vec3 XYZ_to_RGB(vec3 XYZ) { 53 | return XYZ * mat3( 54 | 1.716651187971268, -0.355670783776392, -0.253366281373660, 55 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 56 | 0.017639857445311, -0.042770613257809, 0.942103121235474 57 | ); 58 | } 59 | 60 | vec3 XYZ_to_LMS(vec3 XYZ) { 61 | return XYZ * mat3( 62 | 0.8190224379967030, 0.3619062600528904, -0.1288737815209879, 63 | 0.0329836539323885, 0.9292868615863434, 0.0361446663506424, 64 | 0.0481771893596242, 0.2642395317527308, 0.6335478284694309 65 | ); 66 | } 67 | 68 | vec3 LMS_to_XYZ(vec3 LMS) { 69 | return LMS * mat3( 70 | 1.2268798758459243, -0.5578149944602171, 0.2813910456659647, 71 | -0.0405757452148008, 1.1122868032803170, -0.0717110580655164, 72 | -0.0763729366746601, -0.4214933324022432, 1.5869240198367816 73 | ); 74 | } 75 | 76 | vec3 LMS_to_Lab(vec3 LMS) { 77 | return LMS * mat3( 78 | 0.2104542683093140, 0.7936177747023054, -0.0040720430116193, 79 | 1.9779985324311684, -2.4285922420485799, 0.4505937096174110, 80 | 0.0259040424655478, 0.7827717124575296, -0.8086757549230774 81 | ); 82 | } 83 | 84 | vec3 Lab_to_LMS(vec3 Lab) { 85 | return Lab * mat3( 86 | 1.0000000000000000, 0.3963377773761749, 0.2158037573099136, 87 | 1.0000000000000000, -0.1055613458156586, -0.0638541728258133, 88 | 1.0000000000000000, -0.0894841775298119, -1.2914855480194092 89 | ); 90 | } 91 | 92 | vec3 RGB_to_Lab(vec3 color) { 93 | color = RGB_to_XYZ(color); 94 | color = XYZ_to_LMS(color); 95 | color = cbrt(color); 96 | color = LMS_to_Lab(color); 97 | return color; 98 | } 99 | 100 | vec3 Lab_to_RGB(vec3 color) { 101 | color = Lab_to_LMS(color); 102 | color = pow(color, vec3(3.0)); 103 | color = LMS_to_XYZ(color); 104 | color = XYZ_to_RGB(color); 105 | return color; 106 | } 107 | 108 | const float epsilon = 1e-6; 109 | 110 | vec3 Lab_to_LCh(vec3 Lab) { 111 | float L = Lab.x; 112 | float a = Lab.y; 113 | float b = Lab.z; 114 | 115 | float C = length(vec2(a, b)); 116 | float h = (abs(a) < epsilon && abs(b) < epsilon) ? 0.0 : atan(b, a); 117 | 118 | return vec3(L, C, h); 119 | } 120 | 121 | vec3 LCh_to_Lab(vec3 LCh) { 122 | float L = LCh.x; 123 | float C = LCh.y; 124 | float h = LCh.z; 125 | 126 | C = max(C, 0.0); 127 | float a = C * cos(h); 128 | float b = C * sin(h); 129 | 130 | return vec3(L, a, b); 131 | } 132 | 133 | float l(float x, float a, float b) { 134 | float y = (x - a) / (b - a); 135 | return clamp(y , 0.0, 1.0); 136 | } 137 | 138 | vec4 hook() { 139 | vec4 color = HOOKED_tex(HOOKED_pos); 140 | 141 | float y = RGB_to_XYZ(color.rgb).y * reference_white; 142 | 143 | float l5 = 10000.0; 144 | float l4 = 4000.0; 145 | float l3 = 2000.0; 146 | float l2 = 1000.0; 147 | float l1 = reference_white; 148 | float l0 = reference_white / 1000.0; 149 | float lb = 0.0; 150 | 151 | vec3 cw = vec3(0.99500, 0.00, radians(000.0)); 152 | vec3 c5 = vec3(0.94659, 0.11, radians(005.0)); 153 | vec3 c4 = vec3(0.83878, 0.33, radians(025.0)); 154 | vec3 c3 = vec3(0.73097, 0.33, radians(090.0)); 155 | vec3 c2 = vec3(0.52324, 0.33, radians(130.0)); 156 | vec3 c1 = vec3(0.33922, 0.24, radians(245.0)); 157 | vec3 c0 = vec3(0.20104, 0.16, radians(350.0)); 158 | vec3 cb = vec3(0.13040, 0.08, radians(350.0)); 159 | 160 | if (y > l5) color.rgb = Lab_to_RGB(LCh_to_Lab(cw)); 161 | else if (y > l4) color.rgb = Lab_to_RGB(LCh_to_Lab(mix(c4, c5, l(y, l4, l5)))); 162 | else if (y > l3) color.rgb = Lab_to_RGB(LCh_to_Lab(mix(c3, c4, l(y, l3, l4)))); 163 | else if (y > l2) color.rgb = Lab_to_RGB(LCh_to_Lab(mix(c2, c3, l(y, l2, l3)))); 164 | else if (y > l1) color.rgb = Lab_to_RGB(LCh_to_Lab(mix(c1, c2, l(y, l1, l2)))); 165 | else if (y > l0) color.rgb = vec3(l(y, l0, l1)); 166 | else color.rgb = Lab_to_RGB(LCh_to_Lab(mix(cb, c0, l(y, lb, l0)))); 167 | 168 | return color; 169 | } 170 | 171 | //!HOOK OUTPUT 172 | //!BIND HOOKED 173 | //!WHEN mode exposure = 174 | //!DESC tone mapping (false color, exposure) 175 | 176 | // Inspired by the Ansel Adams' Zone System 177 | // https://en.wikipedia.org/wiki/Zone_System#Zones_as_tone_and_texture 178 | // Expanded exposure stops based on the de facto devices' dynamic range 179 | 180 | // oklch(0.99500 0.00 000.0) overexposure 181 | // oklch(0.94659 0.11 005.0) +7 stops 182 | // oklch(0.89269 0.22 015.0) +6 stops 183 | // oklch(0.83878 0.33 025.0) +5 stops 184 | // oklch(0.78487 0.11 060.0) +4 stops 185 | // oklch(0.73097 0.33 090.0) +3 stops 186 | // oklch(0.67706 0.22 105.0) +2 stops 187 | // oklch(0.62315 0.11 120.0) +1 stop 188 | // oklch(0.56925 0.00 000.0) middle gray 189 | // oklch(0.52324 0.33 130.0) -1 stop 190 | // oklch(0.47724 0.22 145.0) -2 stops 191 | // oklch(0.43123 0.11 160.0) -3 stops 192 | // oklch(0.38523 0.32 220.0) -4 stops 193 | // oklch(0.33922 0.24 245.0) -5 stops 194 | // oklch(0.29322 0.24 290.0) -6 stops 195 | // oklch(0.24721 0.16 320.0) -7 stops 196 | // oklch(0.20104 0.08 350.0) -8 stops 197 | // oklch(0.13040 0.00 000.0) underexposure 198 | 199 | float cbrt(float x) { 200 | return sign(x) * pow(abs(x), 1.0 / 3.0); 201 | } 202 | 203 | vec3 cbrt(vec3 color) { 204 | return vec3( 205 | cbrt(color.x), 206 | cbrt(color.y), 207 | cbrt(color.z) 208 | ); 209 | } 210 | 211 | vec3 RGB_to_XYZ(vec3 RGB) { 212 | return RGB * mat3( 213 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 214 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 215 | 0.000000000000000, 0.028072693049087428, 1.060985057710791 216 | ); 217 | } 218 | 219 | vec3 XYZ_to_RGB(vec3 XYZ) { 220 | return XYZ * mat3( 221 | 1.716651187971268, -0.355670783776392, -0.253366281373660, 222 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 223 | 0.017639857445311, -0.042770613257809, 0.942103121235474 224 | ); 225 | } 226 | 227 | vec3 XYZ_to_LMS(vec3 XYZ) { 228 | return XYZ * mat3( 229 | 0.8190224379967030, 0.3619062600528904, -0.1288737815209879, 230 | 0.0329836539323885, 0.9292868615863434, 0.0361446663506424, 231 | 0.0481771893596242, 0.2642395317527308, 0.6335478284694309 232 | ); 233 | } 234 | 235 | vec3 LMS_to_XYZ(vec3 LMS) { 236 | return LMS * mat3( 237 | 1.2268798758459243, -0.5578149944602171, 0.2813910456659647, 238 | -0.0405757452148008, 1.1122868032803170, -0.0717110580655164, 239 | -0.0763729366746601, -0.4214933324022432, 1.5869240198367816 240 | ); 241 | } 242 | 243 | vec3 LMS_to_Lab(vec3 LMS) { 244 | return LMS * mat3( 245 | 0.2104542683093140, 0.7936177747023054, -0.0040720430116193, 246 | 1.9779985324311684, -2.4285922420485799, 0.4505937096174110, 247 | 0.0259040424655478, 0.7827717124575296, -0.8086757549230774 248 | ); 249 | } 250 | 251 | vec3 Lab_to_LMS(vec3 Lab) { 252 | return Lab * mat3( 253 | 1.0000000000000000, 0.3963377773761749, 0.2158037573099136, 254 | 1.0000000000000000, -0.1055613458156586, -0.0638541728258133, 255 | 1.0000000000000000, -0.0894841775298119, -1.2914855480194092 256 | ); 257 | } 258 | 259 | vec3 RGB_to_Lab(vec3 color) { 260 | color = RGB_to_XYZ(color); 261 | color = XYZ_to_LMS(color); 262 | color = cbrt(color); 263 | color = LMS_to_Lab(color); 264 | return color; 265 | } 266 | 267 | vec3 Lab_to_RGB(vec3 color) { 268 | color = Lab_to_LMS(color); 269 | color = pow(color, vec3(3.0)); 270 | color = LMS_to_XYZ(color); 271 | color = XYZ_to_RGB(color); 272 | return color; 273 | } 274 | 275 | const float epsilon = 1e-6; 276 | 277 | vec3 Lab_to_LCh(vec3 Lab) { 278 | float L = Lab.x; 279 | float a = Lab.y; 280 | float b = Lab.z; 281 | 282 | float C = length(vec2(a, b)); 283 | float h = (abs(a) < epsilon && abs(b) < epsilon) ? 0.0 : atan(b, a); 284 | 285 | return vec3(L, C, h); 286 | } 287 | 288 | vec3 LCh_to_Lab(vec3 LCh) { 289 | float L = LCh.x; 290 | float C = LCh.y; 291 | float h = LCh.z; 292 | 293 | C = max(C, 0.0); 294 | float a = C * cos(h); 295 | float b = C * sin(h); 296 | 297 | return vec3(L, a, b); 298 | } 299 | 300 | vec4 hook() { 301 | vec4 color = HOOKED_tex(HOOKED_pos); 302 | 303 | float stops = log2(max(RGB_to_XYZ(color.rgb).y, 1e-6) / 0.18); 304 | 305 | if (stops >= 7.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.99500, 0.00, radians(000.0)))); 306 | else if (stops >= 6.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.94659, 0.11, radians(005.0)))); 307 | else if (stops >= 5.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.89269, 0.22, radians(015.0)))); 308 | else if (stops >= 4.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.83878, 0.33, radians(025.0)))); 309 | else if (stops >= 3.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.78487, 0.11, radians(060.0)))); 310 | else if (stops >= 2.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.73097, 0.33, radians(090.0)))); 311 | else if (stops >= 1.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.67706, 0.22, radians(105.0)))); 312 | else if (stops >= 0.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.62315, 0.11, radians(120.0)))); 313 | else if (stops >= -0.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.56925, 0.00, radians(000.0)))); 314 | else if (stops >= -1.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.52324, 0.33, radians(130.0)))); 315 | else if (stops >= -2.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.47724, 0.22, radians(145.0)))); 316 | else if (stops >= -3.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.43123, 0.11, radians(160.0)))); 317 | else if (stops >= -4.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.38523, 0.32, radians(220.0)))); 318 | else if (stops >= -5.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.33922, 0.24, radians(245.0)))); 319 | else if (stops >= -6.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.29322, 0.24, radians(290.0)))); 320 | else if (stops >= -7.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.24721, 0.16, radians(320.0)))); 321 | else if (stops >= -8.5) color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.20104, 0.08, radians(350.0)))); 322 | else color.rgb = Lab_to_RGB(LCh_to_Lab(vec3(0.13040, 0.00, radians(000.0)))); 323 | 324 | return color; 325 | } 326 | -------------------------------------------------------------------------------- /shaders/hdr-toys/tone-mapping/st2094-10.glsl: -------------------------------------------------------------------------------- 1 | // ST 2094-10:2021 - SMPTE Standard - Dynamic Metadata for Color Volume Transform - Application #1 2 | // https://ieeexplore.ieee.org/document/9405553 3 | 4 | //!PARAM min_luma 5 | //!TYPE float 6 | 0.0 7 | 8 | //!PARAM max_luma 9 | //!TYPE float 10 | 0.0 11 | 12 | //!PARAM max_cll 13 | //!TYPE float 14 | 0.0 15 | 16 | //!PARAM max_fall 17 | //!TYPE float 18 | 0.0 19 | 20 | //!PARAM scene_max_r 21 | //!TYPE float 22 | 0.0 23 | 24 | //!PARAM scene_max_g 25 | //!TYPE float 26 | 0.0 27 | 28 | //!PARAM scene_max_b 29 | //!TYPE float 30 | 0.0 31 | 32 | //!PARAM scene_avg 33 | //!TYPE float 34 | 0.0 35 | 36 | //!PARAM max_pq_y 37 | //!TYPE float 38 | 0.0 39 | 40 | //!PARAM avg_pq_y 41 | //!TYPE float 42 | 0.0 43 | 44 | //!PARAM reference_white 45 | //!TYPE float 46 | //!MINIMUM 0.0 47 | //!MAXIMUM 1000.0 48 | 203.0 49 | 50 | //!PARAM chroma_correction_scaling 51 | //!TYPE float 52 | //!MINIMUM 0.0 53 | //!MAXIMUM 1.0 54 | 0.5 55 | 56 | //!BUFFER METERED 57 | //!VAR float metered_avg_i 58 | //!STORAGE 59 | 60 | //!HOOK OUTPUT 61 | //!BIND HOOKED 62 | //!SAVE AVG 63 | //!COMPONENTS 1 64 | //!WIDTH 1024 65 | //!HEIGHT 1024 66 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 67 | //!DESC tone mapping (st2094-10, average, 1024) 68 | 69 | const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); 70 | 71 | const float m1 = 2610.0 / 4096.0 / 4.0; 72 | const float m2 = 2523.0 / 4096.0 * 128.0; 73 | const float c1 = 3424.0 / 4096.0; 74 | const float c2 = 2413.0 / 4096.0 * 32.0; 75 | const float c3 = 2392.0 / 4096.0 * 32.0; 76 | const float pw = 10000.0; 77 | 78 | float pq_eotf_inv(float x) { 79 | float t = pow(x / pw, m1); 80 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 81 | } 82 | 83 | vec4 hook() { 84 | vec4 color = HOOKED_tex(HOOKED_pos); 85 | float l = dot(color.rgb, y_coef); 86 | float l_abs = l * reference_white; 87 | float i = pq_eotf_inv(l); 88 | return vec4(i, vec3(0.0)); 89 | } 90 | 91 | //!HOOK OUTPUT 92 | //!BIND AVG 93 | //!SAVE AVG 94 | //!WIDTH AVG.w 2 / 95 | //!HEIGHT AVG.h 2 / 96 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 97 | //!DESC tone mapping (st2094-10, average, 512) 98 | vec4 hook() { return AVG_tex(AVG_pos); } 99 | 100 | //!HOOK OUTPUT 101 | //!BIND AVG 102 | //!SAVE AVG 103 | //!WIDTH AVG.w 2 / 104 | //!HEIGHT AVG.h 2 / 105 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 106 | //!DESC tone mapping (st2094-10, average, 256) 107 | vec4 hook() { return AVG_tex(AVG_pos); } 108 | 109 | //!HOOK OUTPUT 110 | //!BIND AVG 111 | //!SAVE AVG 112 | //!WIDTH AVG.w 2 / 113 | //!HEIGHT AVG.h 2 / 114 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 115 | //!DESC tone mapping (st2094-10, average, 128) 116 | vec4 hook() { return AVG_tex(AVG_pos); } 117 | 118 | //!HOOK OUTPUT 119 | //!BIND AVG 120 | //!SAVE AVG 121 | //!WIDTH AVG.w 2 / 122 | //!HEIGHT AVG.h 2 / 123 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 124 | //!DESC tone mapping (st2094-10, average, 64) 125 | vec4 hook() { return AVG_tex(AVG_pos); } 126 | 127 | //!HOOK OUTPUT 128 | //!BIND AVG 129 | //!SAVE AVG 130 | //!WIDTH AVG.w 2 / 131 | //!HEIGHT AVG.h 2 / 132 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 133 | //!DESC tone mapping (st2094-10, average, 32) 134 | vec4 hook() { return AVG_tex(AVG_pos); } 135 | 136 | //!HOOK OUTPUT 137 | //!BIND AVG 138 | //!SAVE AVG 139 | //!WIDTH AVG.w 2 / 140 | //!HEIGHT AVG.h 2 / 141 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 142 | //!DESC tone mapping (st2094-10, average, 16) 143 | vec4 hook() { return AVG_tex(AVG_pos); } 144 | 145 | //!HOOK OUTPUT 146 | //!BIND AVG 147 | //!SAVE AVG 148 | //!WIDTH AVG.w 2 / 149 | //!HEIGHT AVG.h 2 / 150 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 151 | //!DESC tone mapping (st2094-10, average, 8) 152 | vec4 hook() { return AVG_tex(AVG_pos); } 153 | 154 | //!HOOK OUTPUT 155 | //!BIND AVG 156 | //!SAVE AVG 157 | //!WIDTH AVG.w 2 / 158 | //!HEIGHT AVG.h 2 / 159 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 160 | //!DESC tone mapping (st2094-10, average, 4) 161 | vec4 hook() { return AVG_tex(AVG_pos); } 162 | 163 | //!HOOK OUTPUT 164 | //!BIND AVG 165 | //!SAVE AVG 166 | //!WIDTH AVG.w 2 / 167 | //!HEIGHT AVG.h 2 / 168 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 169 | //!DESC tone mapping (st2094-10, average, 2) 170 | vec4 hook() { return AVG_tex(AVG_pos); } 171 | 172 | //!HOOK OUTPUT 173 | //!BIND AVG 174 | //!BIND METERED 175 | //!SAVE AVG 176 | //!WIDTH 1 177 | //!HEIGHT 1 178 | //!COMPUTE 1 1 179 | //!WHEN avg_pq_y 0 = scene_avg 0 = * 180 | //!DESC tone mapping (st2094-10, average, 1) 181 | 182 | void hook() { 183 | metered_avg_i = AVG_tex(AVG_pos).x; 184 | } 185 | 186 | //!HOOK OUTPUT 187 | //!BIND HOOKED 188 | //!BIND METERED 189 | //!DESC tone mapping (st2094-10) 190 | 191 | const float m1 = 2610.0 / 4096.0 / 4.0; 192 | const float m2 = 2523.0 / 4096.0 * 128.0; 193 | const float c1 = 3424.0 / 4096.0; 194 | const float c2 = 2413.0 / 4096.0 * 32.0; 195 | const float c3 = 2392.0 / 4096.0 * 32.0; 196 | const float pw = 10000.0; 197 | 198 | float pq_eotf_inv(float x) { 199 | float t = pow(x / pw, m1); 200 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 201 | } 202 | 203 | vec3 pq_eotf_inv(vec3 color) { 204 | return vec3( 205 | pq_eotf_inv(color.r), 206 | pq_eotf_inv(color.g), 207 | pq_eotf_inv(color.b) 208 | ); 209 | } 210 | 211 | float pq_eotf(float x) { 212 | float t = pow(x, 1.0 / m2); 213 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 214 | } 215 | 216 | vec3 pq_eotf(vec3 color) { 217 | return vec3( 218 | pq_eotf(color.r), 219 | pq_eotf(color.g), 220 | pq_eotf(color.b) 221 | ); 222 | } 223 | 224 | vec3 RGB_to_XYZ(vec3 RGB) { 225 | return RGB * mat3( 226 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 227 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 228 | 0.0 , 0.028072693049087428, 1.060985057710791 229 | ); 230 | } 231 | 232 | vec3 XYZ_to_RGB(vec3 XYZ) { 233 | return XYZ * mat3( 234 | 1.716651187971268, -0.355670783776392, -0.25336628137366, 235 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 236 | 0.017639857445311, -0.042770613257809, 0.942103121235474 237 | ); 238 | } 239 | 240 | vec3 XYZ_to_LMS(vec3 XYZ) { 241 | return XYZ * mat3( 242 | 0.3592832590121217, 0.6976051147779502, -0.0358915932320290, 243 | -0.1920808463704993, 1.1004767970374321, 0.0753748658519118, 244 | 0.0070797844607479, 0.0748396662186362, 0.8433265453898765 245 | ); 246 | } 247 | 248 | vec3 LMS_to_XYZ(vec3 LMS) { 249 | return LMS * mat3( 250 | 2.0701522183894223, -1.3263473389671563, 0.2066510476294053, 251 | 0.3647385209748072, 0.6805660249472273, -0.0453045459220347, 252 | -0.0497472075358123, -0.0492609666966131, 1.1880659249923042 253 | ); 254 | } 255 | 256 | vec3 LMS_to_ICtCp(vec3 LMS) { 257 | return LMS * mat3( 258 | 2048.0 / 4096.0, 2048.0 / 4096.0, 0.0 / 4096.0, 259 | 6610.0 / 4096.0, -13613.0 / 4096.0, 7003.0 / 4096.0, 260 | 17933.0 / 4096.0, -17390.0 / 4096.0, -543.0 / 4096.0 261 | ); 262 | } 263 | 264 | vec3 ICtCp_to_LMS(vec3 ICtCp) { 265 | return ICtCp * mat3( 266 | 1.0, 0.0086090370379328, 0.1110296250030260, 267 | 1.0, -0.0086090370379328, -0.1110296250030260, 268 | 1.0, 0.5600313357106791, -0.3206271749873189 269 | ); 270 | } 271 | 272 | vec3 RGB_to_ICtCp(vec3 color) { 273 | color *= reference_white; 274 | color = RGB_to_XYZ(color); 275 | color = XYZ_to_LMS(color); 276 | color = pq_eotf_inv(color); 277 | color = LMS_to_ICtCp(color); 278 | return color; 279 | } 280 | 281 | vec3 ICtCp_to_RGB(vec3 color) { 282 | color = ICtCp_to_LMS(color); 283 | color = pq_eotf(color); 284 | color = LMS_to_XYZ(color); 285 | color = XYZ_to_RGB(color); 286 | color /= reference_white; 287 | return color; 288 | } 289 | 290 | float get_max_l() { 291 | if (max_pq_y > 0.0) 292 | return pq_eotf(max_pq_y); 293 | 294 | if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { 295 | vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); 296 | return RGB_to_XYZ(scene_max_rgb).y; 297 | } 298 | 299 | if (max_cll > 0.0) 300 | return max_cll; 301 | 302 | if (max_luma > 0.0) 303 | return max_luma; 304 | 305 | return 1000.0; 306 | } 307 | 308 | float get_min_l() { 309 | if (min_luma > 0.0) 310 | return min_luma; 311 | 312 | return 0.001; 313 | } 314 | 315 | float get_avg_l() { 316 | if (avg_pq_y > 0.0) 317 | return pq_eotf(avg_pq_y); 318 | 319 | if (scene_avg > 0.0) 320 | return scene_avg; 321 | 322 | if (metered_avg_i > 0.0) 323 | return pq_eotf(clamp(metered_avg_i, 0.1, 0.5)); 324 | 325 | if (max_fall > 0.0) 326 | return max_fall; 327 | 328 | return pq_eotf(0.3); 329 | } 330 | 331 | // n: contrast [0.5, 1.5] 332 | // o: offset [-0.5, 0.5] 333 | // g: gain [0.5, 1.5] 334 | // p: gamma [0.5, 1.5] 335 | float f( 336 | float x, 337 | float iw, float ib, float ow, float ob, float adapt, 338 | float n, float o, float g, float p 339 | ) { 340 | float x1 = ib; 341 | float y1 = ob; 342 | 343 | float x3 = iw; 344 | float y3 = ow; 345 | 346 | float x2 = adapt; 347 | float y2 = sqrt(x2 * sqrt(y3 * y1)); 348 | 349 | // the specification imposes no restrictions on x2 and y2, 350 | // but the default values consistently produce underexposed results, 351 | // and extreme values may cause abnormal display artifacts. 352 | float geo_mean = sqrt(y1 * y3); 353 | float dynamic_range = log2(x3 / x1); 354 | float lift_factor = mix(3.0, 6.0, clamp((dynamic_range - 8.0) / 8.0, 0.0, 1.0)); 355 | x2 = clamp(x2, x1 + 1e-6, x3 - 1e-6); 356 | y2 = clamp(y2, geo_mean * lift_factor * 0.85, geo_mean * lift_factor * 1.3); 357 | 358 | float a = x3 * y3 * (x1 - x2) + x2 * y2 * (x3 - x1) + x1 * y1 * (x2 - x3); 359 | 360 | mat3 cmat = mat3( 361 | x2 * x3 * (y2 - y3), x1 * x3 * (y3 - y1), x1 * x2 * (y1 - y2), 362 | x3 * y3 - x2 * y2 , x1 * y1 - x3 * y3 , x2 * y2 - x1 * y1 , 363 | x3 - x2 , x1 - x3 , x2 - x1 364 | ); 365 | 366 | vec3 coeffs = vec3(y1, y2, y3) * cmat / a; 367 | 368 | float c1 = coeffs.r; 369 | float c2 = coeffs.g; 370 | float c3 = coeffs.b; 371 | 372 | x = clamp(x, x1, x3); 373 | x = pow(x, n); 374 | x = (c1 + c2 * x) / (1.0 + c3 * x); 375 | x = pow(min(max(0.0, ((x / y3) * g) + o), 1.0), p) * y3; 376 | x = clamp(x, y1, y3); 377 | 378 | return x; 379 | } 380 | 381 | float f(float x, float iw, float ib, float ow, float ob, float adapt) { 382 | return f(x, iw, ib, ow, ob, adapt, 1.0, 0.0, 1.0, 1.0); 383 | } 384 | 385 | float curve(float x) { 386 | float ow = 1.0; 387 | float ob = 0.001; 388 | float iw = max(get_max_l() / reference_white, ow + 1e-3); 389 | float ib = min(get_min_l() / reference_white, ob - 1e-3); 390 | float avg = get_avg_l() / reference_white; 391 | return f(x, iw, ib, ow, ob, avg); 392 | } 393 | 394 | vec2 chroma_correction(vec2 ab, float i1, float i2) { 395 | float r1 = i1 / max(i2, 1e-6); 396 | float r2 = i2 / max(i1, 1e-6); 397 | return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); 398 | } 399 | 400 | vec3 tone_mapping(vec3 iab) { 401 | float i2 = pq_eotf_inv(curve(pq_eotf(iab.x) / reference_white) * reference_white); 402 | vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); 403 | return vec3(i2, ab2); 404 | } 405 | 406 | // c: chroma compensation weight [-0.5, 0.5] 407 | // s: saturation gain [-0.5, 0.5] 408 | vec3 gamut_adjustment(vec3 f, float c, float s) { 409 | float y = RGB_to_XYZ(f).y; 410 | return f * pow((1.0 + c) * f / y, vec3(s)); 411 | } 412 | 413 | vec3 gamut_adjustment(vec3 f) { 414 | return gamut_adjustment(f, 0.0, 0.0); 415 | } 416 | 417 | // t: tone detail factor [0, 1]; 418 | vec3 detail_managenment(vec3 p, float t) { 419 | // TODO: do what? 420 | vec3 q = p; 421 | return p * (1.0 - t) + q * t; 422 | } 423 | 424 | vec3 detail_managenment(vec3 p) { 425 | return detail_managenment(p, 0.0); 426 | } 427 | 428 | vec4 hook() { 429 | vec4 color = HOOKED_tex(HOOKED_pos); 430 | 431 | color.rgb = RGB_to_ICtCp(color.rgb); 432 | color.rgb = tone_mapping(color.rgb); 433 | color.rgb = ICtCp_to_RGB(color.rgb); 434 | color.rgb = gamut_adjustment(color.rgb); 435 | color.rgb = detail_managenment(color.rgb); 436 | 437 | return color; 438 | } 439 | -------------------------------------------------------------------------------- /shaders/hdr-toys/gamut-mapping/clip.glsl: -------------------------------------------------------------------------------- 1 | // RGB to RGB conversion, includes chromatic adaptation transform 2 | // All coordinates are based on the CIE 1931 2° chromaticity diagram 3 | 4 | //!HOOK OUTPUT 5 | //!BIND HOOKED 6 | //!DESC gamut mapping (clip) 7 | 8 | // You can use custom chromaticity here. 9 | // Example: BT.709 with a D93 white point: Chromaticity(BT709.r, BT709.g, BT709.b, D93) 10 | // You can also define custom coordinates: Chromaticity(vec2(0.7347, 0.2653), BT709.g, BT709.b, D65) 11 | 12 | #define from BT2020 13 | #define to BT709 14 | 15 | // White points of standard illuminants 16 | // https://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants 17 | 18 | const vec2 A = vec2(0.44757, 0.40745); 19 | const vec2 B = vec2(0.34842, 0.35161); 20 | const vec2 C = vec2(0.31006, 0.31616); 21 | const vec2 D50 = vec2(0.34567, 0.35850); 22 | const vec2 D55 = vec2(0.33242, 0.34743); 23 | const vec2 D65 = vec2(0.31271, 0.32902); 24 | const vec2 D75 = vec2(0.29902, 0.31485); 25 | const vec2 D93 = vec2(0.28315, 0.29711); 26 | const vec2 E = vec2(1.0/3.0, 1.0/3.0); 27 | const vec2 F2 = vec2(0.37208, 0.37529); 28 | const vec2 F7 = vec2(0.31292, 0.32933); 29 | const vec2 F11 = vec2(0.38052, 0.37713); 30 | const vec2 DCI = vec2(0.31400, 0.35100); 31 | // It is also known as D60 32 | const vec2 ACES = vec2(0.32168, 0.33767); 33 | // Colour Matching Between OLED and CRT 34 | // https://www.sony.jp/products/catalog/FUN_WhitePaper_OLED_ColorMatching_V1_00.pdf 35 | const vec2 BRAVIA = vec2(0.3067, 0.318); 36 | 37 | // https://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D 38 | vec2 CIE_D(float T) { 39 | // Compensate for the loss caused by the accuracy difference between the old and new standards 40 | // c2 = 1.4387768775039337 41 | // https://en.wikipedia.org/wiki/Planckian_locus#Planckian_locus_in_the_XYZ_color_space 42 | T = (T * 1.4388) / 1.438; 43 | 44 | // This formula is applicable to temperatures ranging from 4000K to 25000K 45 | T = clamp(T, 4000.0, 25000.0); 46 | 47 | float t1 = 1000.0 / T; 48 | float t2 = t1 * t1; 49 | float t3 = t1 * t2; 50 | 51 | float x = 52 | T <= 7000.0 53 | ? 0.244063 + 0.09911 * t1 + 2.9678 * t2 - 4.607 * t3 54 | : 0.23704 + 0.24748 * t1 + 1.9018 * t2 - 2.0064 * t3; 55 | 56 | // Daylight locus 57 | float y = -0.275 + 2.87 * x - 3.0 * x * x; 58 | 59 | return vec2(x, y); 60 | } 61 | 62 | // https://en.wikipedia.org/wiki/Planckian_locus#Approximation 63 | vec2 Kang(float T) { 64 | // This formula is applicable to temperatures ranging from 1667K to 25000K 65 | T = clamp(T, 1667.0, 25000.0); 66 | 67 | float t1 = 1000.0 / T; 68 | float t2 = t1 * t1; 69 | float t3 = t1 * t2; 70 | 71 | float x = 72 | T <= 4000.0 73 | ? -0.2661239 * t3 - 0.234358 * t2 + 0.8776956 * t1 + 0.17991 74 | : -3.0258469 * t3 + 2.1070379 * t2 + 0.2226347 * t1 + 0.24039; 75 | 76 | float x2 = x * x; 77 | float x3 = x2 * x; 78 | 79 | float y = 80 | T <= 2222.0 81 | ? -1.1063814 * x3 - 1.3481102 * x2 - 2.18555832 * x - 0.20219683 82 | : T <= 4000.0 83 | ? -0.9549476 * x3 - 1.37418593 * x2 - 2.09137015 * x - 0.16748867 84 | : 3.081758 * x3 - 5.8733867 * x2 + 3.75112997 * x - 0.37001483; 85 | 86 | return vec2(x, y); 87 | } 88 | 89 | // Chromaticities 90 | // https://www.itu.int/rec/T-REC-H.273 91 | // https://github.com/colour-science/colour/tree/develop/colour/models/rgb/datasets 92 | 93 | struct Chromaticity { 94 | vec2 r, g, b, w; 95 | }; 96 | 97 | // ITU-R BT.2020, ITU-R BT.2100 98 | const Chromaticity BT2020 = Chromaticity( 99 | vec2(0.708, 0.292), 100 | vec2(0.170, 0.797), 101 | vec2(0.131, 0.046), 102 | D65 103 | ); 104 | 105 | // ITU-R BT.709, IEC 61966-2-1 (sRGB) 106 | const Chromaticity BT709 = Chromaticity( 107 | vec2(0.64, 0.33), 108 | vec2(0.30, 0.60), 109 | vec2(0.15, 0.06), 110 | D65 111 | ); 112 | 113 | // ITU-R BT.601 (525 lines), SMPTE ST 240 114 | const Chromaticity BT601_525 = Chromaticity( 115 | vec2(0.630, 0.340), 116 | vec2(0.310, 0.595), 117 | vec2(0.155, 0.070), 118 | D65 119 | ); 120 | 121 | // ITU-R BT.601 (625 lines), BT.470 (B/G), EBU 3213-E 122 | const Chromaticity BT601_625 = Chromaticity( 123 | vec2(0.64, 0.33), 124 | vec2(0.29, 0.60), 125 | vec2(0.15, 0.06), 126 | D65 127 | ); 128 | 129 | // ITU-R BT.470 (M) 130 | const Chromaticity BT470m = Chromaticity( 131 | vec2(0.67, 0.33), 132 | vec2(0.21, 0.71), 133 | vec2(0.14, 0.08), 134 | C 135 | ); 136 | 137 | // P3-DCI (Theater) 138 | const Chromaticity P3DCI = Chromaticity( 139 | vec2(0.680, 0.320), 140 | vec2(0.265, 0.690), 141 | vec2(0.150, 0.060), 142 | DCI 143 | ); 144 | 145 | // P3-D65 (Display) 146 | const Chromaticity P3D65 = Chromaticity( 147 | P3DCI.r, 148 | P3DCI.g, 149 | P3DCI.b, 150 | D65 151 | ); 152 | 153 | // P3-D60 (ACES Cinema) 154 | const Chromaticity P3D60 = Chromaticity( 155 | P3DCI.r, 156 | P3DCI.g, 157 | P3DCI.b, 158 | ACES 159 | ); 160 | 161 | // ITU-T H.273 (Generic film) 162 | const Chromaticity H273_8 = Chromaticity( 163 | vec2(0.681, 0.319), 164 | vec2(0.243, 0.692), 165 | vec2(0.145, 0.049), 166 | C 167 | ); 168 | 169 | // ITU-T H.273 (No corresponding industry specification identified) 170 | const Chromaticity H273_22 = Chromaticity( 171 | vec2(0.630, 0.340), 172 | vec2(0.295, 0.605), 173 | vec2(0.155, 0.077), 174 | D65 175 | ); 176 | 177 | // CIE RGB (CIE 1931 color space) 178 | const Chromaticity CIERGB = Chromaticity( 179 | vec2(0.73474284, 0.26525716), 180 | vec2(0.27377903, 0.7174777), 181 | vec2(0.16655563, 0.00891073), 182 | E 183 | ); 184 | 185 | // CIE XYZ (CIE 1931 color space) 186 | const Chromaticity XYZ = Chromaticity( 187 | vec2(1.0, 0.0), 188 | vec2(0.0, 1.0), 189 | vec2(0.0, 0.0), 190 | E 191 | ); 192 | 193 | // CIE XYZ (CIE 1931 color space, D65 whitepoint) 194 | const Chromaticity XYZD65 = Chromaticity( 195 | XYZ.r, 196 | XYZ.g, 197 | XYZ.b, 198 | D65 199 | ); 200 | 201 | // CIE XYZ (CIE 1931 color space, D50 whitepoint) 202 | const Chromaticity XYZD50 = Chromaticity( 203 | XYZ.r, 204 | XYZ.g, 205 | XYZ.b, 206 | D50 207 | ); 208 | 209 | // Grayscale, Monochrome 210 | const Chromaticity GRAY = Chromaticity( 211 | vec2(0.0, 1.0), 212 | vec2(0.0, 1.0), 213 | vec2(0.0, 1.0), 214 | E 215 | ); 216 | 217 | // Adobe RGB (1998) 218 | const Chromaticity AdobeRGB = Chromaticity( 219 | vec2(0.64, 0.33), 220 | vec2(0.21, 0.71), 221 | vec2(0.15, 0.06), 222 | D65 223 | ); 224 | 225 | // Adobe Wide Gamut RGB 226 | const Chromaticity AdobeWideGamutRGB = Chromaticity( 227 | vec2(0.7347, 0.2653), 228 | vec2(0.1152, 0.8264), 229 | vec2(0.1566, 0.0177), 230 | D50 231 | ); 232 | 233 | // ROMM (ProPhoto RGB) 234 | const Chromaticity ROMM = Chromaticity( 235 | vec2(0.734699, 0.265301), 236 | vec2(0.159597, 0.840403), 237 | vec2(0.036598, 0.000105), 238 | D50 239 | ); 240 | 241 | // AP0 (ACES 2065-1) 242 | const Chromaticity AP0 = Chromaticity( 243 | vec2(0.7347, 0.2653), 244 | vec2(0.0000, 1.0000), 245 | vec2(0.0001, -0.0770), 246 | ACES 247 | ); 248 | 249 | // AP1 (ACEScg, cc, cct, proxy) 250 | const Chromaticity AP1 = Chromaticity( 251 | vec2(0.713, 0.293), 252 | vec2(0.165, 0.830), 253 | vec2(0.128, 0.044), 254 | ACES 255 | ); 256 | 257 | // ARRI Wide Gamut 3 258 | const Chromaticity AWG3 = Chromaticity( 259 | vec2(0.684, 0.313), 260 | vec2(0.221, 0.848), 261 | vec2(0.0861, -0.102), 262 | D65 263 | ); 264 | 265 | // ARRI Wide Gamut 4 266 | const Chromaticity AWG4 = Chromaticity( 267 | vec2(0.7347, 0.2653), 268 | vec2(0.1424, 0.8576), 269 | vec2(0.0991, -0.0308), 270 | D65 271 | ); 272 | 273 | // RED Wide Gamut RGB 274 | const Chromaticity RWG = Chromaticity( 275 | vec2(0.780308, 0.304253), 276 | vec2(0.121595, 1.493994), 277 | vec2(0.095612, -0.084589), 278 | D65 279 | ); 280 | 281 | // DaVinci Wide Gamut 282 | const Chromaticity DWG = Chromaticity( 283 | vec2(0.8000, 0.3130), 284 | vec2(0.1682, 0.9877), 285 | vec2(0.0790, -0.1155), 286 | D65 287 | ); 288 | 289 | // FilmLight E-Gamut 290 | const Chromaticity EGAMUT = Chromaticity( 291 | vec2(0.8000, 0.3177), 292 | vec2(0.1800, 0.9000), 293 | vec2(0.0650, -0.0805), 294 | D65 295 | ); 296 | 297 | // FilmLight E-Gamut 2 298 | const Chromaticity EGAMUT2 = Chromaticity( 299 | vec2(0.8300, 0.3100), 300 | vec2(0.1500, 0.9500), 301 | vec2(0.0650, -0.0805), 302 | D65 303 | ); 304 | 305 | // FUJIFILM F-Gamut C 306 | const Chromaticity FGAMUTC = Chromaticity( 307 | vec2(0.7347, 0.2653), 308 | vec2(0.0263, 0.9737), 309 | vec2(0.1173, -0.0224), 310 | D65 311 | ); 312 | 313 | // Sony S-Gamut3/S-Gamut 314 | const Chromaticity SGAMUT = Chromaticity( 315 | vec2(0.73, 0.280), 316 | vec2(0.14, 0.855), 317 | vec2(0.10, -0.050), 318 | D65 319 | ); 320 | 321 | // Sony S-Gamut.Cine 322 | const Chromaticity SGAMUTCINE = Chromaticity( 323 | vec2(0.766, 0.275), 324 | vec2(0.225, 0.800), 325 | vec2(0.089, -0.087), 326 | D65 327 | ); 328 | 329 | // Canon Cinema Gamut 330 | const Chromaticity CINEMA_GAMUT = Chromaticity( 331 | vec2(0.74, 0.27), 332 | vec2(0.17, 1.14), 333 | vec2(0.08, -0.10), 334 | D65 335 | ); 336 | 337 | // Panasonic V-Gamut 338 | const Chromaticity VGAMUT = Chromaticity( 339 | vec2(0.730, 0.28), 340 | vec2(0.165, 0.84), 341 | vec2(0.100, -0.03), 342 | D65 343 | ); 344 | 345 | // DJI D-Gamut 346 | const Chromaticity DGAMUT = Chromaticity( 347 | vec2(0.71, 0.31), 348 | vec2(0.21, 0.88), 349 | vec2(0.09, -0.08), 350 | D65 351 | ); 352 | 353 | // Apple Wide Gamut 354 | const Chromaticity AppleWideGamut = Chromaticity( 355 | vec2(0.725, 0.301), 356 | vec2(0.221, 0.814), 357 | vec2(0.068, -0.076), 358 | D65 359 | ); 360 | 361 | // Chromatic adaptation transform 362 | // https://en.wikipedia.org/wiki/LMS_color_space 363 | 364 | // It is also known as von Kries 365 | const mat3 HPE = mat3( 366 | 0.40024, 0.70760, -0.08081, 367 | -0.22630, 1.16532, 0.04570, 368 | 0.00000, 0.00000, 0.91822 369 | ); 370 | 371 | const mat3 Bradford = mat3( 372 | 0.8951, 0.2664, -0.1614, 373 | -0.7502, 1.7135, 0.0367, 374 | 0.0389, -0.0685, 1.0296 375 | ); 376 | 377 | const mat3 CAT97 = mat3( 378 | 0.8562, 0.3372, -0.1934, 379 | -0.8360, 1.8327, 0.0033, 380 | 0.0357, -0.0469, 1.0112 381 | ); 382 | 383 | const mat3 CAT02 = mat3( 384 | 0.7328, 0.4296, -0.1624, 385 | -0.7036, 1.6975, 0.0061, 386 | 0.0030, 0.0136, 0.9834 387 | ); 388 | 389 | const mat3 CAT16 = mat3( 390 | 0.401288, 0.650173, -0.051461, 391 | -0.250268, 1.204414, 0.045854, 392 | -0.002079, 0.048952, 0.953127 393 | ); 394 | 395 | // Other constants 396 | 397 | const mat3 Identity3 = mat3( 398 | 1.0, 0.0, 0.0, 399 | 0.0, 1.0, 0.0, 400 | 0.0, 0.0, 1.0 401 | ); 402 | 403 | const mat3 SingularY3 = mat3( 404 | 0.0, 1.0, 0.0, 405 | 0.0, 1.0, 0.0, 406 | 0.0, 1.0, 0.0 407 | ); 408 | 409 | // http://www.brucelindbloom.com/Eqn_xyY_to_XYZ.html 410 | vec3 xyY_to_XYZ(vec3 xyY) { 411 | float x = xyY.x; 412 | float y = xyY.y; 413 | float Y = xyY.z; 414 | 415 | float multiplier = Y / max(y, 1e-6); 416 | 417 | float z = 1.0 - x - y; 418 | float X = x * multiplier; 419 | float Z = z * multiplier; 420 | 421 | return vec3(X, Y, Z); 422 | } 423 | 424 | // http://www.brucelindbloom.com/Eqn_XYZ_to_xyY.html 425 | vec3 XYZ_to_xyY(vec3 XYZ) { 426 | float X = XYZ.x; 427 | float Y = XYZ.y; 428 | float Z = XYZ.z; 429 | 430 | float divisor = X + Y + Z; 431 | if (divisor == 0.0) divisor = 1e-6; 432 | 433 | float x = X / divisor; 434 | float y = Y / divisor; 435 | 436 | return vec3(x, y, Y); 437 | } 438 | 439 | // http://www.brucelindbloom.com/Eqn_RGB_XYZ_Matrix.html 440 | mat3 RGB_to_XYZ(Chromaticity C) { 441 | if (C == GRAY) 442 | return Identity3; 443 | 444 | vec3 r = xyY_to_XYZ(vec3(C.r, 1.0)); 445 | vec3 g = xyY_to_XYZ(vec3(C.g, 1.0)); 446 | vec3 b = xyY_to_XYZ(vec3(C.b, 1.0)); 447 | vec3 w = xyY_to_XYZ(vec3(C.w, 1.0)); 448 | 449 | mat3 xyz = transpose(mat3(r, g, b)); 450 | 451 | vec3 scale = w * inverse(xyz); 452 | mat3 scale_diag = mat3( 453 | scale.x, 0.0, 0.0, 454 | 0.0, scale.y, 0.0, 455 | 0.0, 0.0, scale.z 456 | ); 457 | 458 | return scale_diag * xyz; 459 | } 460 | 461 | mat3 XYZ_to_RGB(Chromaticity C) { 462 | if (C == GRAY) 463 | return SingularY3; 464 | 465 | return inverse(RGB_to_XYZ(C)); 466 | } 467 | 468 | // http://www.brucelindbloom.com/Eqn_ChromAdapt.html 469 | mat3 adaptation(vec2 w1, vec2 w2, mat3 cat) { 470 | vec3 src_xyz = xyY_to_XYZ(vec3(w1, 1.0)); 471 | vec3 dst_xyz = xyY_to_XYZ(vec3(w2, 1.0)); 472 | 473 | vec3 src_lms = src_xyz * cat; 474 | vec3 dst_lms = dst_xyz * cat; 475 | 476 | vec3 scale = dst_lms / src_lms; 477 | mat3 scale_diag = mat3( 478 | scale.x, 0.0, 0.0, 479 | 0.0, scale.y, 0.0, 480 | 0.0, 0.0, scale.z 481 | ); 482 | 483 | return cat * scale_diag * inverse(cat); 484 | } 485 | 486 | // CAM16 uses CAT16 as cat and equal-energy illuminant (E) as wt. 487 | // https://www.researchgate.net/publication/318152296_Comprehensive_color_solutions_CAM16_CAT16_and_CAM16-UCS 488 | // Android uses Bradford as cat and D50 as wt. 489 | // https://android.googlesource.com/platform/frameworks/base/+/master/graphics/java/android/graphics/ColorSpace.java 490 | mat3 adaptation_two_step(vec2 w1, vec2 w2, vec2 wt, mat3 cat) { 491 | return adaptation(w1, wt, cat) * adaptation(wt, w2, cat); 492 | } 493 | 494 | mat3 adaptation_two_step(vec2 w1, vec2 w2) { 495 | return adaptation_two_step(w1, w2, E, CAT16); 496 | } 497 | 498 | mat3 RGB_to_RGB(Chromaticity c1, Chromaticity c2) { 499 | mat3 m = Identity3; 500 | if (c1 != c2) { 501 | m *= RGB_to_XYZ(c1); 502 | if (c1.w != c2.w) { 503 | m *= adaptation_two_step(c1.w, c2.w); 504 | } 505 | m *= XYZ_to_RGB(c2); 506 | } 507 | return m; 508 | } 509 | 510 | vec3 RGB_to_RGB(vec3 c, Chromaticity c1, Chromaticity c2) { 511 | return c * RGB_to_RGB(c1, c2); 512 | } 513 | 514 | vec4 hook() { 515 | vec4 color = HOOKED_tex(HOOKED_pos); 516 | 517 | color.rgb = RGB_to_RGB(color.rgb, from, to); 518 | 519 | return color; 520 | } 521 | -------------------------------------------------------------------------------- /shaders/hdr-toys/tone-mapping/bt2390.glsl: -------------------------------------------------------------------------------- 1 | // ITU-R BT.2390 EETF 2 | // https://www.itu.int/pub/R-REP-BT.2390 3 | // https://www.itu.int/pub/R-REP-BT.2408 4 | 5 | //!PARAM min_luma 6 | //!TYPE float 7 | 0.0 8 | 9 | //!PARAM max_luma 10 | //!TYPE float 11 | 0.0 12 | 13 | //!PARAM max_cll 14 | //!TYPE float 15 | 0.0 16 | 17 | //!PARAM scene_max_r 18 | //!TYPE float 19 | 0.0 20 | 21 | //!PARAM scene_max_g 22 | //!TYPE float 23 | 0.0 24 | 25 | //!PARAM scene_max_b 26 | //!TYPE float 27 | 0.0 28 | 29 | //!PARAM max_pq_y 30 | //!TYPE float 31 | 0.0 32 | 33 | //!PARAM reference_white 34 | //!TYPE float 35 | //!MINIMUM 0.0 36 | //!MAXIMUM 1000.0 37 | 203.0 38 | 39 | //!PARAM chroma_correction_scaling 40 | //!TYPE float 41 | //!MINIMUM 0.0 42 | //!MAXIMUM 1.0 43 | 1.0 44 | 45 | //!PARAM representation 46 | //!TYPE ENUM int 47 | ictcp 48 | ycbcr 49 | yrgb 50 | prergb 51 | maxrgb 52 | 53 | //!HOOK OUTPUT 54 | //!BIND HOOKED 55 | //!WHEN representation ictcp = 56 | //!DESC tone mapping (bt.2390, ICtCp) 57 | 58 | const float m1 = 2610.0 / 4096.0 / 4.0; 59 | const float m2 = 2523.0 / 4096.0 * 128.0; 60 | const float c1 = 3424.0 / 4096.0; 61 | const float c2 = 2413.0 / 4096.0 * 32.0; 62 | const float c3 = 2392.0 / 4096.0 * 32.0; 63 | const float pw = 10000.0; 64 | 65 | float pq_eotf_inv(float x) { 66 | float t = pow(x / pw, m1); 67 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 68 | } 69 | 70 | vec3 pq_eotf_inv(vec3 color) { 71 | return vec3( 72 | pq_eotf_inv(color.r), 73 | pq_eotf_inv(color.g), 74 | pq_eotf_inv(color.b) 75 | ); 76 | } 77 | 78 | float pq_eotf(float x) { 79 | float t = pow(x, 1.0 / m2); 80 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 81 | } 82 | 83 | vec3 pq_eotf(vec3 color) { 84 | return vec3( 85 | pq_eotf(color.r), 86 | pq_eotf(color.g), 87 | pq_eotf(color.b) 88 | ); 89 | } 90 | 91 | vec3 RGB_to_XYZ(vec3 RGB) { 92 | return RGB * mat3( 93 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 94 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 95 | 0.0 , 0.028072693049087428, 1.060985057710791 96 | ); 97 | } 98 | 99 | vec3 XYZ_to_RGB(vec3 XYZ) { 100 | return XYZ * mat3( 101 | 1.716651187971268, -0.355670783776392, -0.25336628137366, 102 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 103 | 0.017639857445311, -0.042770613257809, 0.942103121235474 104 | ); 105 | } 106 | 107 | vec3 XYZ_to_LMS(vec3 XYZ) { 108 | return XYZ * mat3( 109 | 0.3592832590121217, 0.6976051147779502, -0.0358915932320290, 110 | -0.1920808463704993, 1.1004767970374321, 0.0753748658519118, 111 | 0.0070797844607479, 0.0748396662186362, 0.8433265453898765 112 | ); 113 | } 114 | 115 | vec3 LMS_to_XYZ(vec3 LMS) { 116 | return LMS * mat3( 117 | 2.0701522183894223, -1.3263473389671563, 0.2066510476294053, 118 | 0.3647385209748072, 0.6805660249472273, -0.0453045459220347, 119 | -0.0497472075358123, -0.0492609666966131, 1.1880659249923042 120 | ); 121 | } 122 | 123 | vec3 LMS_to_ICtCp(vec3 LMS) { 124 | return LMS * mat3( 125 | 2048.0 / 4096.0, 2048.0 / 4096.0, 0.0 / 4096.0, 126 | 6610.0 / 4096.0, -13613.0 / 4096.0, 7003.0 / 4096.0, 127 | 17933.0 / 4096.0, -17390.0 / 4096.0, -543.0 / 4096.0 128 | ); 129 | } 130 | 131 | vec3 ICtCp_to_LMS(vec3 ICtCp) { 132 | return ICtCp * mat3( 133 | 1.0, 0.0086090370379328, 0.1110296250030260, 134 | 1.0, -0.0086090370379328, -0.1110296250030260, 135 | 1.0, 0.5600313357106791, -0.3206271749873189 136 | ); 137 | } 138 | 139 | vec3 RGB_to_ICtCp(vec3 color) { 140 | color *= reference_white; 141 | color = RGB_to_XYZ(color); 142 | color = XYZ_to_LMS(color); 143 | color = pq_eotf_inv(color); 144 | color = LMS_to_ICtCp(color); 145 | return color; 146 | } 147 | 148 | vec3 ICtCp_to_RGB(vec3 color) { 149 | color = ICtCp_to_LMS(color); 150 | color = pq_eotf(color); 151 | color = LMS_to_XYZ(color); 152 | color = XYZ_to_RGB(color); 153 | color /= reference_white; 154 | return color; 155 | } 156 | 157 | float get_max_i() { 158 | if (max_pq_y > 0.0) 159 | return max_pq_y; 160 | 161 | if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { 162 | vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); 163 | return pq_eotf_inv(RGB_to_XYZ(scene_max_rgb).y); 164 | } 165 | 166 | if (max_cll > 0.0) 167 | return pq_eotf_inv(max_cll); 168 | 169 | if (max_luma > 0.0) 170 | return pq_eotf_inv(max_luma); 171 | 172 | return pq_eotf_inv(1000.0); 173 | } 174 | 175 | float get_min_i() { 176 | if (min_luma > 0.0) 177 | return pq_eotf_inv(min_luma); 178 | 179 | return pq_eotf_inv(0.001); 180 | } 181 | 182 | float f(float x, float iw, float ib, float ow, float ob) { 183 | float minLum = (ob - ib) / (iw - ib); 184 | float maxLum = (ow - ib) / (iw - ib); 185 | 186 | float KS = 1.5 * maxLum - 0.5; 187 | float b = minLum; 188 | 189 | // E1 190 | x = (x - ib) / (iw - ib); 191 | 192 | // E2 193 | if (KS <= x) { 194 | float TB = (x - KS) / (1.0 - KS); 195 | float TB2 = TB * TB; 196 | float TB3 = TB * TB2; 197 | 198 | float PB = (2.0 * TB3 - 3.0 * TB2 + 1.0) * KS + 199 | (TB3 - 2.0 * TB2 + TB) * (1.0 - KS) + 200 | (-2.0 * TB3 + 3.0 * TB2) * maxLum; 201 | 202 | x = PB; 203 | } 204 | 205 | // E3 206 | if (0.0 <= x) { 207 | x = x + b * pow((1.0 - x), 4.0); 208 | } 209 | 210 | // E4 211 | x = x * (iw - ib) + ib; 212 | 213 | return clamp(x, ob, ow); 214 | } 215 | 216 | float curve(float x) { 217 | float ow = pq_eotf_inv(reference_white); 218 | float ob = pq_eotf_inv(reference_white / 1000.0); 219 | float iw = max(get_max_i(), ow + 1e-3); 220 | float ib = min(get_min_i(), ob - 1e-3); 221 | return f(x, iw, ib, ow, ob); 222 | } 223 | 224 | vec2 chroma_correction(vec2 ab, float i1, float i2) { 225 | float r1 = i1 / max(i2, 1e-6); 226 | float r2 = i2 / max(i1, 1e-6); 227 | return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); 228 | } 229 | 230 | vec3 tone_mapping(vec3 iab) { 231 | float i2 = curve(iab.x); 232 | vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); 233 | return vec3(i2, ab2); 234 | } 235 | 236 | vec4 hook() { 237 | vec4 color = HOOKED_tex(HOOKED_pos); 238 | 239 | color.rgb = RGB_to_ICtCp(color.rgb); 240 | color.rgb = tone_mapping(color.rgb); 241 | color.rgb = ICtCp_to_RGB(color.rgb); 242 | 243 | return color; 244 | } 245 | 246 | //!HOOK OUTPUT 247 | //!BIND HOOKED 248 | //!WHEN representation ycbcr = 249 | //!DESC tone mapping (bt.2390, Y'Cb'Cr') 250 | 251 | const float m1 = 2610.0 / 4096.0 / 4.0; 252 | const float m2 = 2523.0 / 4096.0 * 128.0; 253 | const float c1 = 3424.0 / 4096.0; 254 | const float c2 = 2413.0 / 4096.0 * 32.0; 255 | const float c3 = 2392.0 / 4096.0 * 32.0; 256 | const float pw = 10000.0; 257 | 258 | float pq_eotf_inv(float x) { 259 | float t = pow(x / pw, m1); 260 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 261 | } 262 | 263 | vec3 pq_eotf_inv(vec3 color) { 264 | return vec3( 265 | pq_eotf_inv(color.r), 266 | pq_eotf_inv(color.g), 267 | pq_eotf_inv(color.b) 268 | ); 269 | } 270 | 271 | float pq_eotf(float x) { 272 | float t = pow(x, 1.0 / m2); 273 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 274 | } 275 | 276 | vec3 pq_eotf(vec3 color) { 277 | return vec3( 278 | pq_eotf(color.r), 279 | pq_eotf(color.g), 280 | pq_eotf(color.b) 281 | ); 282 | } 283 | 284 | const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); 285 | 286 | const float a = y_coef.r; 287 | const float b = y_coef.g; 288 | const float c = y_coef.b; 289 | const float d = 2.0 * (1.0 - c); 290 | const float e = 2.0 * (1.0 - a); 291 | 292 | vec3 RGB_to_YCbCr(vec3 RGB) { 293 | return RGB * mat3( 294 | a, b, c, 295 | -a / d, -b / d, 0.5, 296 | 0.5, -b / e, -c / e 297 | ); 298 | } 299 | 300 | vec3 YCbCr_to_RGB(vec3 YCbCr) { 301 | return YCbCr * mat3( 302 | 1.0, 0.0, e, 303 | 1.0, -c / b * d, -a / b * e, 304 | 1.0, d, 0.0 305 | ); 306 | } 307 | 308 | float get_max_i() { 309 | if (max_pq_y > 0.0) 310 | return max_pq_y; 311 | 312 | if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { 313 | vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); 314 | return pq_eotf_inv(dot(scene_max_rgb, y_coef)); 315 | } 316 | 317 | if (max_cll > 0.0) 318 | return pq_eotf_inv(max_cll); 319 | 320 | if (max_luma > 0.0) 321 | return pq_eotf_inv(max_luma); 322 | 323 | return pq_eotf_inv(1000.0); 324 | } 325 | 326 | float get_min_i() { 327 | if (min_luma > 0.0) 328 | return pq_eotf_inv(min_luma); 329 | 330 | return pq_eotf_inv(0.001); 331 | } 332 | 333 | float f(float x, float iw, float ib, float ow, float ob) { 334 | float minLum = (ob - ib) / (iw - ib); 335 | float maxLum = (ow - ib) / (iw - ib); 336 | 337 | float KS = 1.5 * maxLum - 0.5; 338 | float b = minLum; 339 | 340 | // E1 341 | x = (x - ib) / (iw - ib); 342 | 343 | // E2 344 | if (KS <= x) { 345 | float TB = (x - KS) / (1.0 - KS); 346 | float TB2 = TB * TB; 347 | float TB3 = TB * TB2; 348 | 349 | float PB = (2.0 * TB3 - 3.0 * TB2 + 1.0) * KS + 350 | (TB3 - 2.0 * TB2 + TB) * (1.0 - KS) + 351 | (-2.0 * TB3 + 3.0 * TB2) * maxLum; 352 | 353 | x = PB; 354 | } 355 | 356 | // E3 357 | if (0.0 <= x) { 358 | x = x + b * pow((1.0 - x), 4.0); 359 | } 360 | 361 | // E4 362 | x = x * (iw - ib) + ib; 363 | 364 | return clamp(x, ob, ow); 365 | } 366 | 367 | float curve(float x) { 368 | float ow = pq_eotf_inv(reference_white); 369 | float ob = pq_eotf_inv(reference_white / 1000.0); 370 | float iw = max(get_max_i(), ow + 1e-3); 371 | float ib = min(get_min_i(), ob - 1e-3); 372 | return f(x, iw, ib, ow, ob); 373 | } 374 | 375 | vec2 chroma_correction(vec2 ab, float i1, float i2) { 376 | float r1 = i1 / max(i2, 1e-6); 377 | float r2 = i2 / max(i1, 1e-6); 378 | return ab * mix(1.0, min(r1, r2), chroma_correction_scaling); 379 | } 380 | 381 | vec3 tone_mapping(vec3 iab) { 382 | float i2 = curve(iab.x); 383 | vec2 ab2 = chroma_correction(iab.yz, iab.x, i2); 384 | return vec3(i2, ab2); 385 | } 386 | 387 | vec4 hook() { 388 | vec4 color = HOOKED_tex(HOOKED_pos); 389 | 390 | color.rgb = pq_eotf_inv(color.rgb * reference_white); 391 | color.rgb = RGB_to_YCbCr(color.rgb); 392 | color.rgb = tone_mapping(color.rgb); 393 | color.rgb = YCbCr_to_RGB(color.rgb); 394 | color.rgb = pq_eotf(color.rgb) / reference_white; 395 | 396 | return color; 397 | } 398 | 399 | //!HOOK OUTPUT 400 | //!BIND HOOKED 401 | //!WHEN representation yrgb = 402 | //!DESC tone mapping (bt.2390, YRGB) 403 | 404 | const float m1 = 2610.0 / 4096.0 / 4.0; 405 | const float m2 = 2523.0 / 4096.0 * 128.0; 406 | const float c1 = 3424.0 / 4096.0; 407 | const float c2 = 2413.0 / 4096.0 * 32.0; 408 | const float c3 = 2392.0 / 4096.0 * 32.0; 409 | const float pw = 10000.0; 410 | 411 | float pq_eotf_inv(float x) { 412 | float t = pow(x / pw, m1); 413 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 414 | } 415 | 416 | vec3 pq_eotf_inv(vec3 color) { 417 | return vec3( 418 | pq_eotf_inv(color.r), 419 | pq_eotf_inv(color.g), 420 | pq_eotf_inv(color.b) 421 | ); 422 | } 423 | 424 | float pq_eotf(float x) { 425 | float t = pow(x, 1.0 / m2); 426 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 427 | } 428 | 429 | vec3 pq_eotf(vec3 color) { 430 | return vec3( 431 | pq_eotf(color.r), 432 | pq_eotf(color.g), 433 | pq_eotf(color.b) 434 | ); 435 | } 436 | 437 | const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); 438 | 439 | float get_max_i() { 440 | if (max_pq_y > 0.0) 441 | return max_pq_y; 442 | 443 | if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { 444 | vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); 445 | return pq_eotf_inv(dot(scene_max_rgb, y_coef)); 446 | } 447 | 448 | if (max_cll > 0.0) 449 | return pq_eotf_inv(max_cll); 450 | 451 | if (max_luma > 0.0) 452 | return pq_eotf_inv(max_luma); 453 | 454 | return pq_eotf_inv(1000.0); 455 | } 456 | 457 | float get_min_i() { 458 | if (min_luma > 0.0) 459 | return pq_eotf_inv(min_luma); 460 | 461 | return pq_eotf_inv(0.001); 462 | } 463 | 464 | float f(float x, float iw, float ib, float ow, float ob) { 465 | float minLum = (ob - ib) / (iw - ib); 466 | float maxLum = (ow - ib) / (iw - ib); 467 | 468 | float KS = 1.5 * maxLum - 0.5; 469 | float b = minLum; 470 | 471 | // E1 472 | x = (x - ib) / (iw - ib); 473 | 474 | // E2 475 | if (KS <= x) { 476 | float TB = (x - KS) / (1.0 - KS); 477 | float TB2 = TB * TB; 478 | float TB3 = TB * TB2; 479 | 480 | float PB = (2.0 * TB3 - 3.0 * TB2 + 1.0) * KS + 481 | (TB3 - 2.0 * TB2 + TB) * (1.0 - KS) + 482 | (-2.0 * TB3 + 3.0 * TB2) * maxLum; 483 | 484 | x = PB; 485 | } 486 | 487 | // E3 488 | if (0.0 <= x) { 489 | x = x + b * pow((1.0 - x), 4.0); 490 | } 491 | 492 | // E4 493 | x = x * (iw - ib) + ib; 494 | 495 | return clamp(x, ob, ow); 496 | } 497 | 498 | float curve(float x) { 499 | float ow = pq_eotf_inv(reference_white); 500 | float ob = pq_eotf_inv(reference_white / 1000.0); 501 | float iw = max(get_max_i(), ow + 1e-3); 502 | float ib = min(get_min_i(), ob - 1e-3); 503 | return f(x, iw, ib, ow, ob); 504 | } 505 | 506 | vec3 tone_mapping(vec3 rgb) { 507 | float y1 = dot(rgb, y_coef) * reference_white; 508 | float y2 = pq_eotf(curve(pq_eotf_inv(y1))); 509 | return (y2 / max(y1, 1e-6)) * rgb; 510 | } 511 | 512 | vec4 hook() { 513 | vec4 color = HOOKED_tex(HOOKED_pos); 514 | 515 | color.rgb = tone_mapping(color.rgb); 516 | 517 | return color; 518 | } 519 | 520 | //!HOOK OUTPUT 521 | //!BIND HOOKED 522 | //!WHEN representation prergb = 523 | //!DESC tone mapping (bt.2390, R'G'B') 524 | 525 | const float m1 = 2610.0 / 4096.0 / 4.0; 526 | const float m2 = 2523.0 / 4096.0 * 128.0; 527 | const float c1 = 3424.0 / 4096.0; 528 | const float c2 = 2413.0 / 4096.0 * 32.0; 529 | const float c3 = 2392.0 / 4096.0 * 32.0; 530 | const float pw = 10000.0; 531 | 532 | float pq_eotf_inv(float x) { 533 | float t = pow(x / pw, m1); 534 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 535 | } 536 | 537 | vec3 pq_eotf_inv(vec3 color) { 538 | return vec3( 539 | pq_eotf_inv(color.r), 540 | pq_eotf_inv(color.g), 541 | pq_eotf_inv(color.b) 542 | ); 543 | } 544 | 545 | float pq_eotf(float x) { 546 | float t = pow(x, 1.0 / m2); 547 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 548 | } 549 | 550 | vec3 pq_eotf(vec3 color) { 551 | return vec3( 552 | pq_eotf(color.r), 553 | pq_eotf(color.g), 554 | pq_eotf(color.b) 555 | ); 556 | } 557 | 558 | const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); 559 | 560 | float get_max_i() { 561 | if (max_pq_y > 0.0) 562 | return max_pq_y; 563 | 564 | if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { 565 | vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); 566 | return pq_eotf_inv(dot(scene_max_rgb, y_coef)); 567 | } 568 | 569 | if (max_cll > 0.0) 570 | return pq_eotf_inv(max_cll); 571 | 572 | if (max_luma > 0.0) 573 | return pq_eotf_inv(max_luma); 574 | 575 | return pq_eotf_inv(1000.0); 576 | } 577 | 578 | float get_min_i() { 579 | if (min_luma > 0.0) 580 | return pq_eotf_inv(min_luma); 581 | 582 | return pq_eotf_inv(0.001); 583 | } 584 | 585 | float f(float x, float iw, float ib, float ow, float ob) { 586 | float minLum = (ob - ib) / (iw - ib); 587 | float maxLum = (ow - ib) / (iw - ib); 588 | 589 | float KS = 1.5 * maxLum - 0.5; 590 | float b = minLum; 591 | 592 | // E1 593 | x = (x - ib) / (iw - ib); 594 | 595 | // E2 596 | if (KS <= x) { 597 | float TB = (x - KS) / (1.0 - KS); 598 | float TB2 = TB * TB; 599 | float TB3 = TB * TB2; 600 | 601 | float PB = (2.0 * TB3 - 3.0 * TB2 + 1.0) * KS + 602 | (TB3 - 2.0 * TB2 + TB) * (1.0 - KS) + 603 | (-2.0 * TB3 + 3.0 * TB2) * maxLum; 604 | 605 | x = PB; 606 | } 607 | 608 | // E3 609 | if (0.0 <= x) { 610 | x = x + b * pow((1.0 - x), 4.0); 611 | } 612 | 613 | // E4 614 | x = x * (iw - ib) + ib; 615 | 616 | return clamp(x, ob, ow); 617 | } 618 | 619 | float curve(float x) { 620 | float ow = pq_eotf_inv(reference_white); 621 | float ob = pq_eotf_inv(reference_white / 1000.0); 622 | float iw = max(get_max_i(), ow + 1e-3); 623 | float ib = min(get_min_i(), ob - 1e-3); 624 | return f(x, iw, ib, ow, ob); 625 | } 626 | 627 | vec3 tone_mapping(vec3 rgb) { 628 | return vec3( 629 | curve(rgb.r), 630 | curve(rgb.g), 631 | curve(rgb.b) 632 | ); 633 | } 634 | 635 | vec4 hook() { 636 | vec4 color = HOOKED_tex(HOOKED_pos); 637 | 638 | color.rgb = pq_eotf_inv(color.rgb * reference_white); 639 | color.rgb = tone_mapping(color.rgb); 640 | color.rgb = pq_eotf(color.rgb) / reference_white; 641 | 642 | return color; 643 | } 644 | 645 | //!HOOK OUTPUT 646 | //!BIND HOOKED 647 | //!WHEN representation maxrgb = 648 | //!DESC tone mapping (bt.2390, maxRGB) 649 | 650 | const float m1 = 2610.0 / 4096.0 / 4.0; 651 | const float m2 = 2523.0 / 4096.0 * 128.0; 652 | const float c1 = 3424.0 / 4096.0; 653 | const float c2 = 2413.0 / 4096.0 * 32.0; 654 | const float c3 = 2392.0 / 4096.0 * 32.0; 655 | const float pw = 10000.0; 656 | 657 | float pq_eotf_inv(float x) { 658 | float t = pow(x / pw, m1); 659 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 660 | } 661 | 662 | vec3 pq_eotf_inv(vec3 color) { 663 | return vec3( 664 | pq_eotf_inv(color.r), 665 | pq_eotf_inv(color.g), 666 | pq_eotf_inv(color.b) 667 | ); 668 | } 669 | 670 | float pq_eotf(float x) { 671 | float t = pow(x, 1.0 / m2); 672 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 673 | } 674 | 675 | vec3 pq_eotf(vec3 color) { 676 | return vec3( 677 | pq_eotf(color.r), 678 | pq_eotf(color.g), 679 | pq_eotf(color.b) 680 | ); 681 | } 682 | 683 | const vec3 y_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); 684 | 685 | float get_max_i() { 686 | if (max_pq_y > 0.0) 687 | return max_pq_y; 688 | 689 | if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) { 690 | vec3 scene_max_rgb = vec3(scene_max_r, scene_max_g, scene_max_b); 691 | return pq_eotf_inv(dot(scene_max_rgb, y_coef)); 692 | } 693 | 694 | if (max_cll > 0.0) 695 | return pq_eotf_inv(max_cll); 696 | 697 | if (max_luma > 0.0) 698 | return pq_eotf_inv(max_luma); 699 | 700 | return pq_eotf_inv(1000.0); 701 | } 702 | 703 | float get_min_i() { 704 | if (min_luma > 0.0) 705 | return pq_eotf_inv(min_luma); 706 | 707 | return pq_eotf_inv(0.001); 708 | } 709 | 710 | float f(float x, float iw, float ib, float ow, float ob) { 711 | float minLum = (ob - ib) / (iw - ib); 712 | float maxLum = (ow - ib) / (iw - ib); 713 | 714 | float KS = 1.5 * maxLum - 0.5; 715 | float b = minLum; 716 | 717 | // E1 718 | x = (x - ib) / (iw - ib); 719 | 720 | // E2 721 | if (KS <= x) { 722 | float TB = (x - KS) / (1.0 - KS); 723 | float TB2 = TB * TB; 724 | float TB3 = TB * TB2; 725 | 726 | float PB = (2.0 * TB3 - 3.0 * TB2 + 1.0) * KS + 727 | (TB3 - 2.0 * TB2 + TB) * (1.0 - KS) + 728 | (-2.0 * TB3 + 3.0 * TB2) * maxLum; 729 | 730 | x = PB; 731 | } 732 | 733 | // E3 734 | if (0.0 <= x) { 735 | x = x + b * pow((1.0 - x), 4.0); 736 | } 737 | 738 | // E4 739 | x = x * (iw - ib) + ib; 740 | 741 | return clamp(x, ob, ow); 742 | } 743 | 744 | float curve(float x) { 745 | float ow = pq_eotf_inv(reference_white); 746 | float ob = pq_eotf_inv(reference_white / 1000.0); 747 | float iw = max(get_max_i(), ow + 1e-3); 748 | float ib = min(get_min_i(), ob - 1e-3); 749 | return f(x, iw, ib, ow, ob); 750 | } 751 | 752 | vec3 tone_mapping(vec3 rgb) { 753 | float m1 = max(max(rgb.r, rgb.g), rgb.b) * reference_white; 754 | float m2 = pq_eotf(curve(pq_eotf_inv(m1))); 755 | return (m2 / max(m1, 1e-6)) * rgb; 756 | } 757 | 758 | vec4 hook() { 759 | vec4 color = HOOKED_tex(HOOKED_pos); 760 | 761 | color.rgb = tone_mapping(color.rgb); 762 | 763 | return color; 764 | } 765 | -------------------------------------------------------------------------------- /shaders/hdr-toys/tone-mapping/astra.glsl: -------------------------------------------------------------------------------- 1 | // Astra, a tone mapping operator designed to preserve the creator's intent 2 | 3 | // working space: https://doi.org/10.1364/OE.25.015131 4 | // hk effect: https://doi.org/10.1364/OE.534073 5 | // chroma correction: https://www.itu.int/pub/R-REP-BT.2408 6 | // dynamic metadata: https://github.com/mpv-player/mpv/pull/15239 7 | // fast gaussian blur: https://www.rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ 8 | // toe segment of curve: https://technorgb.blogspot.com/2018/02/hyperbola-tone-mapping.html 9 | // shoulder segment of curve: http://filmicworlds.com/blog/filmic-tonemapping-with-piecewise-power-curves/ 10 | 11 | //!PARAM min_luma 12 | //!TYPE float 13 | 0.0 14 | 15 | //!PARAM max_luma 16 | //!TYPE float 17 | 0.0 18 | 19 | //!PARAM max_cll 20 | //!TYPE float 21 | 0.0 22 | 23 | //!PARAM max_fall 24 | //!TYPE float 25 | 0.0 26 | 27 | //!PARAM scene_max_r 28 | //!TYPE float 29 | 0.0 30 | 31 | //!PARAM scene_max_g 32 | //!TYPE float 33 | 0.0 34 | 35 | //!PARAM scene_max_b 36 | //!TYPE float 37 | 0.0 38 | 39 | //!PARAM scene_avg 40 | //!TYPE float 41 | 0.0 42 | 43 | //!PARAM max_pq_y 44 | //!TYPE float 45 | 0.0 46 | 47 | //!PARAM avg_pq_y 48 | //!TYPE float 49 | 0.0 50 | 51 | //!PARAM reference_white 52 | //!TYPE float 53 | //!MINIMUM 0.0 54 | //!MAXIMUM 1000.0 55 | 203.0 56 | 57 | //!PARAM contrast_ratio 58 | //!TYPE float 59 | //!MINIMUM 10.0 60 | //!MAXIMUM 100000000.0 61 | 1000.0 62 | 63 | //!PARAM auto_exposure_anchor 64 | //!TYPE float 65 | //!MINIMUM 0.0 66 | //!MAXIMUM 1.0 67 | 0.6 68 | 69 | //!PARAM auto_exposure_limit_negtive 70 | //!TYPE float 71 | //!MINIMUM 0.0 72 | //!MAXIMUM 5.0 73 | 2.3 74 | 75 | //!PARAM auto_exposure_limit_postive 76 | //!TYPE float 77 | //!MINIMUM 0.0 78 | //!MAXIMUM 5.0 79 | 0.0 80 | 81 | //!PARAM shadow_weight 82 | //!TYPE float 83 | //!MINIMUM 0.0 84 | //!MAXIMUM 1.0 85 | 0.46 86 | 87 | //!PARAM highlight_weight 88 | //!TYPE float 89 | //!MINIMUM 0.0 90 | //!MAXIMUM 1.0 91 | 0.04 92 | 93 | //!PARAM contrast_bias 94 | //!TYPE float 95 | //!MINIMUM -1.0 96 | //!MAXIMUM 1.0 97 | 0.0 98 | 99 | //!PARAM hk_effect_compensate_scaling 100 | //!TYPE float 101 | //!MINIMUM 0.0 102 | //!MAXIMUM 1.0 103 | 1.0 104 | 105 | //!PARAM chroma_correction_scaling 106 | //!TYPE float 107 | //!MINIMUM 0.0 108 | //!MAXIMUM 5.0 109 | 1.0 110 | 111 | //!PARAM chroma_correction_power 112 | //!TYPE float 113 | //!MINIMUM 0.0 114 | //!MAXIMUM 5.0 115 | 1.0 116 | 117 | //!PARAM spatial_stable_iterations 118 | //!TYPE uint 119 | //!MINIMUM 0 120 | //!MAXIMUM 8 121 | 2 122 | 123 | //!PARAM temporal_stable_frames 124 | //!TYPE uint 125 | //!MINIMUM 0 126 | //!MAXIMUM 120 127 | 8 128 | 129 | //!PARAM enable_metering 130 | //!TYPE uint 131 | //!MINIMUM 0 132 | //!MAXIMUM 2 133 | 2 134 | 135 | //!PARAM preview_metering 136 | //!TYPE uint 137 | //!MINIMUM 0 138 | //!MAXIMUM 1 139 | 0 140 | 141 | //!BUFFER METERED 142 | //!VAR uint metered_max_i 143 | //!VAR uint metered_min_i 144 | //!VAR uint metered_avg_i 145 | //!STORAGE 146 | 147 | //!BUFFER METERED_TEMPORAL 148 | //!VAR uint metered_max_i_t[128] 149 | //!VAR uint metered_min_i_t[128] 150 | //!VAR uint metered_avg_i_t[128] 151 | //!STORAGE 152 | 153 | //!BUFFER METADATA 154 | //!VAR float max_i 155 | //!VAR float min_i 156 | //!VAR float avg_i 157 | //!VAR float ev 158 | //!STORAGE 159 | 160 | //!HOOK OUTPUT 161 | //!BIND HOOKED 162 | //!SAVE METERING 163 | //!COMPONENTS 1 164 | //!WHEN enable_metering 0 > max_pq_y 0 = * scene_max_r 0 = * scene_max_g 0 = * scene_max_b 0 = * 165 | //!DESC metering (intensity map) 166 | 167 | const float m1 = 2610.0 / 4096.0 / 4.0; 168 | const float m2 = 2523.0 / 4096.0 * 128.0; 169 | const float c1 = 3424.0 / 4096.0; 170 | const float c2 = 2413.0 / 4096.0 * 32.0; 171 | const float c3 = 2392.0 / 4096.0 * 32.0; 172 | const float pw = 10000.0; 173 | 174 | float pq_eotf_inv(float x) { 175 | float t = pow(x / pw, m1); 176 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 177 | } 178 | 179 | float RGB_to_Y(vec3 rgb) { 180 | const vec3 luma_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); 181 | return dot(rgb, luma_coef); 182 | } 183 | 184 | vec4 hook() { 185 | vec4 color = HOOKED_tex(HOOKED_pos); 186 | return vec4(pq_eotf_inv(RGB_to_Y(color.rgb) * reference_white)); 187 | } 188 | 189 | //!HOOK OUTPUT 190 | //!BIND METERING 191 | //!SAVE METERING 192 | //!WIDTH 512 193 | //!HEIGHT 288 194 | //!DESC metering (spatial stabilization, downscaling) 195 | 196 | vec4 hook() { 197 | return METERING_tex(METERING_pos); 198 | } 199 | 200 | //!HOOK OUTPUT 201 | //!BIND METERING 202 | //!SAVE METERING 203 | //!WHEN spatial_stable_iterations 0 > 204 | //!DESC metering (spatial stabilization, blur, horizonal) 205 | 206 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 207 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 208 | const vec2 direction = vec2(1.0, 0.0); 209 | 210 | vec4 hook(){ 211 | uint i = 0; 212 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 213 | for (i = 1; i < 4; i++) { 214 | c += METERING_texOff( direction * offset[i]) * weight[i]; 215 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 216 | } 217 | return c; 218 | } 219 | 220 | //!HOOK OUTPUT 221 | //!BIND METERING 222 | //!SAVE METERING 223 | //!WHEN spatial_stable_iterations 0 > 224 | //!DESC metering (spatial stabilization, blur, vertical) 225 | 226 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 227 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 228 | const vec2 direction = vec2(0.0, 1.0); 229 | 230 | vec4 hook(){ 231 | uint i = 0; 232 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 233 | for (i = 1; i < 4; i++) { 234 | c += METERING_texOff( direction * offset[i]) * weight[i]; 235 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 236 | } 237 | return c; 238 | } 239 | 240 | //!HOOK OUTPUT 241 | //!BIND METERING 242 | //!SAVE METERING 243 | //!WHEN spatial_stable_iterations 1 > 244 | //!DESC metering (spatial stabilization, blur, horizonal) 245 | 246 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 247 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 248 | const vec2 direction = vec2(1.0, 0.0); 249 | 250 | vec4 hook(){ 251 | uint i = 0; 252 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 253 | for (i = 1; i < 4; i++) { 254 | c += METERING_texOff( direction * offset[i]) * weight[i]; 255 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 256 | } 257 | return c; 258 | } 259 | 260 | //!HOOK OUTPUT 261 | //!BIND METERING 262 | //!SAVE METERING 263 | //!WHEN spatial_stable_iterations 1 > 264 | //!DESC metering (spatial stabilization, blur, vertical) 265 | 266 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 267 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 268 | const vec2 direction = vec2(0.0, 1.0); 269 | 270 | vec4 hook(){ 271 | uint i = 0; 272 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 273 | for (i = 1; i < 4; i++) { 274 | c += METERING_texOff( direction * offset[i]) * weight[i]; 275 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 276 | } 277 | return c; 278 | } 279 | 280 | //!HOOK OUTPUT 281 | //!BIND METERING 282 | //!SAVE METERING 283 | //!WHEN spatial_stable_iterations 2 > 284 | //!DESC metering (spatial stabilization, blur, horizonal) 285 | 286 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 287 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 288 | const vec2 direction = vec2(1.0, 0.0); 289 | 290 | vec4 hook(){ 291 | uint i = 0; 292 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 293 | for (i = 1; i < 4; i++) { 294 | c += METERING_texOff( direction * offset[i]) * weight[i]; 295 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 296 | } 297 | return c; 298 | } 299 | 300 | //!HOOK OUTPUT 301 | //!BIND METERING 302 | //!SAVE METERING 303 | //!WHEN spatial_stable_iterations 2 > 304 | //!DESC metering (spatial stabilization, blur, vertical) 305 | 306 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 307 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 308 | const vec2 direction = vec2(0.0, 1.0); 309 | 310 | vec4 hook(){ 311 | uint i = 0; 312 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 313 | for (i = 1; i < 4; i++) { 314 | c += METERING_texOff( direction * offset[i]) * weight[i]; 315 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 316 | } 317 | return c; 318 | } 319 | 320 | //!HOOK OUTPUT 321 | //!BIND METERING 322 | //!SAVE METERING 323 | //!WHEN spatial_stable_iterations 3 > 324 | //!DESC metering (spatial stabilization, blur, horizonal) 325 | 326 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 327 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 328 | const vec2 direction = vec2(1.0, 0.0); 329 | 330 | vec4 hook(){ 331 | uint i = 0; 332 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 333 | for (i = 1; i < 4; i++) { 334 | c += METERING_texOff( direction * offset[i]) * weight[i]; 335 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 336 | } 337 | return c; 338 | } 339 | 340 | //!HOOK OUTPUT 341 | //!BIND METERING 342 | //!SAVE METERING 343 | //!WHEN spatial_stable_iterations 3 > 344 | //!DESC metering (spatial stabilization, blur, vertical) 345 | 346 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 347 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 348 | const vec2 direction = vec2(0.0, 1.0); 349 | 350 | vec4 hook(){ 351 | uint i = 0; 352 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 353 | for (i = 1; i < 4; i++) { 354 | c += METERING_texOff( direction * offset[i]) * weight[i]; 355 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 356 | } 357 | return c; 358 | } 359 | 360 | //!HOOK OUTPUT 361 | //!BIND METERING 362 | //!SAVE METERING 363 | //!WHEN spatial_stable_iterations 4 > 364 | //!DESC metering (spatial stabilization, blur, horizonal) 365 | 366 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 367 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 368 | const vec2 direction = vec2(1.0, 0.0); 369 | 370 | vec4 hook(){ 371 | uint i = 0; 372 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 373 | for (i = 1; i < 4; i++) { 374 | c += METERING_texOff( direction * offset[i]) * weight[i]; 375 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 376 | } 377 | return c; 378 | } 379 | 380 | //!HOOK OUTPUT 381 | //!BIND METERING 382 | //!SAVE METERING 383 | //!WHEN spatial_stable_iterations 4 > 384 | //!DESC metering (spatial stabilization, blur, vertical) 385 | 386 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 387 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 388 | const vec2 direction = vec2(0.0, 1.0); 389 | 390 | vec4 hook(){ 391 | uint i = 0; 392 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 393 | for (i = 1; i < 4; i++) { 394 | c += METERING_texOff( direction * offset[i]) * weight[i]; 395 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 396 | } 397 | return c; 398 | } 399 | 400 | //!HOOK OUTPUT 401 | //!BIND METERING 402 | //!SAVE METERING 403 | //!WHEN spatial_stable_iterations 5 > 404 | //!DESC metering (spatial stabilization, blur, horizonal) 405 | 406 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 407 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 408 | const vec2 direction = vec2(1.0, 0.0); 409 | 410 | vec4 hook(){ 411 | uint i = 0; 412 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 413 | for (i = 1; i < 4; i++) { 414 | c += METERING_texOff( direction * offset[i]) * weight[i]; 415 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 416 | } 417 | return c; 418 | } 419 | 420 | //!HOOK OUTPUT 421 | //!BIND METERING 422 | //!SAVE METERING 423 | //!WHEN spatial_stable_iterations 5 > 424 | //!DESC metering (spatial stabilization, blur, vertical) 425 | 426 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 427 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 428 | const vec2 direction = vec2(0.0, 1.0); 429 | 430 | vec4 hook(){ 431 | uint i = 0; 432 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 433 | for (i = 1; i < 4; i++) { 434 | c += METERING_texOff( direction * offset[i]) * weight[i]; 435 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 436 | } 437 | return c; 438 | } 439 | 440 | //!HOOK OUTPUT 441 | //!BIND METERING 442 | //!SAVE METERING 443 | //!WHEN spatial_stable_iterations 6 > 444 | //!DESC metering (spatial stabilization, blur, horizonal) 445 | 446 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 447 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 448 | const vec2 direction = vec2(1.0, 0.0); 449 | 450 | vec4 hook(){ 451 | uint i = 0; 452 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 453 | for (i = 1; i < 4; i++) { 454 | c += METERING_texOff( direction * offset[i]) * weight[i]; 455 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 456 | } 457 | return c; 458 | } 459 | 460 | //!HOOK OUTPUT 461 | //!BIND METERING 462 | //!SAVE METERING 463 | //!WHEN spatial_stable_iterations 6 > 464 | //!DESC metering (spatial stabilization, blur, vertical) 465 | 466 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 467 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 468 | const vec2 direction = vec2(0.0, 1.0); 469 | 470 | vec4 hook(){ 471 | uint i = 0; 472 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 473 | for (i = 1; i < 4; i++) { 474 | c += METERING_texOff( direction * offset[i]) * weight[i]; 475 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 476 | } 477 | return c; 478 | } 479 | 480 | //!HOOK OUTPUT 481 | //!BIND METERING 482 | //!SAVE METERING 483 | //!WHEN spatial_stable_iterations 7 > 484 | //!DESC metering (spatial stabilization, blur, horizonal) 485 | 486 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 487 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 488 | const vec2 direction = vec2(1.0, 0.0); 489 | 490 | vec4 hook(){ 491 | uint i = 0; 492 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 493 | for (i = 1; i < 4; i++) { 494 | c += METERING_texOff( direction * offset[i]) * weight[i]; 495 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 496 | } 497 | return c; 498 | } 499 | 500 | //!HOOK OUTPUT 501 | //!BIND METERING 502 | //!SAVE METERING 503 | //!WHEN spatial_stable_iterations 7 > 504 | //!DESC metering (spatial stabilization, blur, vertical) 505 | 506 | const vec4 offset = vec4(0.0, 1.411764705882353, 3.2941176470588234, 5.176470588235294); 507 | const vec4 weight = vec4(0.1964825501511404, 0.2969069646728344, 0.09447039785044732, 0.010381362401148057); 508 | const vec2 direction = vec2(0.0, 1.0); 509 | 510 | vec4 hook(){ 511 | uint i = 0; 512 | vec4 c = METERING_texOff(offset[i]) * weight[i]; 513 | for (i = 1; i < 4; i++) { 514 | c += METERING_texOff( direction * offset[i]) * weight[i]; 515 | c += METERING_texOff(-direction * offset[i]) * weight[i]; 516 | } 517 | return c; 518 | } 519 | 520 | //!HOOK OUTPUT 521 | //!BIND METERING 522 | //!BIND METERED 523 | //!SAVE EMPTY 524 | //!COMPUTE 32 32 525 | //!DESC metering (max, min) 526 | 527 | shared uint local_max; 528 | shared uint local_min; 529 | 530 | void hook() { 531 | if (gl_GlobalInvocationID.x == 0 && gl_GlobalInvocationID.y == 0) { 532 | metered_max_i = 0; 533 | metered_min_i = 4095; 534 | } 535 | 536 | if (gl_LocalInvocationIndex == 0) { 537 | local_max = 0; 538 | local_min = 4095; 539 | } 540 | 541 | memoryBarrierShared(); 542 | barrier(); 543 | 544 | float value = METERING_tex(METERING_pos).x; 545 | uint rounded = uint(value * 4095.0 + 0.5); 546 | atomicMax(local_max, rounded); 547 | atomicMin(local_min, rounded); 548 | 549 | memoryBarrierShared(); 550 | barrier(); 551 | 552 | if (gl_LocalInvocationIndex == 0) { 553 | atomicMax(metered_max_i, local_max); 554 | atomicMin(metered_min_i, local_min); 555 | } 556 | } 557 | 558 | //!HOOK OUTPUT 559 | //!BIND METERING 560 | //!SAVE AVG 561 | //!COMPONENTS 1 562 | //!WIDTH 256 563 | //!HEIGHT 256 564 | //!WHEN auto_exposure_anchor 0 > enable_metering 1 > * avg_pq_y 0 = * scene_avg 0 = * 565 | //!DESC metering (avg, 256, center-weighted) 566 | 567 | vec2 map_coords(vec2 uv, float strength) { 568 | if (strength < 0.001) { 569 | return uv; 570 | } 571 | 572 | vec2 centered_uv = uv - vec2(0.5); 573 | float radius = length(centered_uv); 574 | 575 | if (radius == 0.0) { 576 | return vec2(0.5); 577 | } 578 | 579 | float distorted_radius = tan(radius * strength) / strength; 580 | vec2 distorted_centered_uv = normalize(centered_uv ) * distorted_radius; 581 | 582 | distorted_centered_uv = distorted_centered_uv / max(strength, 1.0); 583 | 584 | vec2 distorted_uv = distorted_centered_uv + vec2(0.5); 585 | 586 | vec2 kaleidoscope_uv = 1.0 - abs(fract(distorted_uv * 0.5) * 2.0 - 1.0); 587 | 588 | return kaleidoscope_uv; 589 | } 590 | 591 | vec2 map_coords(vec2 uv) { 592 | return map_coords(uv, 2.0); 593 | } 594 | 595 | vec4 hook() { 596 | return METERING_tex(map_coords(METERING_pos)); 597 | } 598 | 599 | //!HOOK OUTPUT 600 | //!BIND AVG 601 | //!SAVE AVG 602 | //!WIDTH AVG.w 2 / 603 | //!HEIGHT AVG.h 2 / 604 | //!DESC metering (avg, 128) 605 | vec4 hook() { return AVG_tex(AVG_pos); } 606 | 607 | //!HOOK OUTPUT 608 | //!BIND AVG 609 | //!SAVE AVG 610 | //!WIDTH AVG.w 2 / 611 | //!HEIGHT AVG.h 2 / 612 | //!DESC metering (avg, 64) 613 | vec4 hook() { return AVG_tex(AVG_pos); } 614 | 615 | //!HOOK OUTPUT 616 | //!BIND AVG 617 | //!SAVE AVG 618 | //!WIDTH AVG.w 2 / 619 | //!HEIGHT AVG.h 2 / 620 | //!DESC metering (avg, 32) 621 | vec4 hook() { return AVG_tex(AVG_pos); } 622 | 623 | //!HOOK OUTPUT 624 | //!BIND AVG 625 | //!SAVE AVG 626 | //!WIDTH AVG.w 2 / 627 | //!HEIGHT AVG.h 2 / 628 | //!DESC metering (avg, 16) 629 | vec4 hook() { return AVG_tex(AVG_pos); } 630 | 631 | //!HOOK OUTPUT 632 | //!BIND AVG 633 | //!SAVE AVG 634 | //!WIDTH AVG.w 2 / 635 | //!HEIGHT AVG.h 2 / 636 | //!DESC metering (avg, 8) 637 | vec4 hook() { return AVG_tex(AVG_pos); } 638 | 639 | //!HOOK OUTPUT 640 | //!BIND AVG 641 | //!SAVE AVG 642 | //!WIDTH AVG.w 2 / 643 | //!HEIGHT AVG.h 2 / 644 | //!DESC metering (avg, 4) 645 | vec4 hook() { return AVG_tex(AVG_pos); } 646 | 647 | //!HOOK OUTPUT 648 | //!BIND AVG 649 | //!SAVE AVG 650 | //!WIDTH AVG.w 2 / 651 | //!HEIGHT AVG.h 2 / 652 | //!DESC metering (avg, 2) 653 | vec4 hook() { return AVG_tex(AVG_pos); } 654 | 655 | //!HOOK OUTPUT 656 | //!BIND AVG 657 | //!BIND METERED 658 | //!SAVE AVG 659 | //!WIDTH 1 660 | //!HEIGHT 1 661 | //!COMPUTE 1 1 662 | //!DESC metering (avg) 663 | 664 | void hook() { 665 | metered_avg_i = uint(AVG_tex(AVG_pos).x * 4095.0 + 0.5); 666 | } 667 | 668 | //!HOOK OUTPUT 669 | //!BIND METERING 670 | //!BIND METERED 671 | //!BIND METERED_TEMPORAL 672 | //!SAVE EMPTY 673 | //!WIDTH 1 674 | //!HEIGHT 1 675 | //!COMPUTE 1 1 676 | //!WHEN temporal_stable_frames 677 | //!DESC metering (temporal stabilization) 678 | 679 | // ============================================================================ 680 | // TEMPORAL STABILIZATION - Configuration Parameters 681 | // ============================================================================ 682 | // These parameters control the temporal smoothing behavior to reduce flicker 683 | // while maintaining responsiveness to actual scene changes. 684 | 685 | // Exponential decay factor for weighted moving average 686 | // Range: 0.7-0.95. Lower = more smoothing but slower response 687 | // Default: 0.85 balances smoothness and responsiveness 688 | const float TEMPORAL_DECAY = 0.85; 689 | 690 | // EMA (Exponential Moving Average) smoothing factor 691 | // Range: 0.1-0.3. Lower = smoother but less responsive 692 | // Default: 0.2 provides good stability without excessive lag 693 | const float TEMPORAL_EMA_ALPHA = 0.2; 694 | 695 | // Blend factor for gradual scene transition 696 | // Range: 0.3-0.7. Lower = smoother transitions during scene cuts 697 | // Default: 0.5 provides balanced transition speed 698 | const float TEMPORAL_SCENE_BLEND = 0.5; 699 | 700 | // Scene change blend factor (applied when cut is detected) 701 | // Range: 0.2-0.5. Lower = smoother but may blur real scene changes 702 | // Default: 0.3 maintains some smoothness during cuts 703 | const float TEMPORAL_CUT_BLEND = 0.3; 704 | 705 | // Base tolerance for scene change detection (in ΔE units) 706 | // Range: 20.0-50.0. Higher = fewer false detections but may miss real cuts 707 | // Default: 36.0 provides good balance for most content 708 | const float TEMPORAL_BASE_TOLERANCE = 36.0; 709 | 710 | // Adaptive tolerance scaling based on brightness 711 | // Range: 0.3-0.7. Higher = more tolerance for bright scenes 712 | // Default: 0.5 adapts well to various brightness levels 713 | const float TEMPORAL_ADAPTIVE_SCALE = 0.5; 714 | 715 | // Black scene threshold (below this is considered pure black) 716 | // Range: 8.0-32.0 (in 12-bit range). Higher = more aggressive black detection 717 | // Default: 16.0 catches most black frames without false positives 718 | const float TEMPORAL_BLACK_THRESHOLD = 16.0; 719 | 720 | // Metric weights for scene change detection 721 | // These weights determine the relative importance of each metric 722 | // Total should sum to 1.0 for balanced detection 723 | const float TEMPORAL_WEIGHT_AVG = 0.50; // Average is most reliable 724 | const float TEMPORAL_WEIGHT_MAX = 0.35; // Maximum is important for highlights 725 | const float TEMPORAL_WEIGHT_MIN = 0.15; // Minimum is least reliable (noise) 726 | 727 | // Delta scale for converting normalized differences to perceptual units 728 | // This converts [0,1] differences to ΔE-like perceptual differences 729 | const float TEMPORAL_DELTA_SCALE = 720.0; 730 | 731 | // Metric type identifiers for array access 732 | const int METRIC_MAX = 0; 733 | const int METRIC_MIN = 1; 734 | const int METRIC_AVG = 2; 735 | 736 | // ============================================================================ 737 | // TEMPORAL STABILIZATION - Core Functions 738 | // ============================================================================ 739 | 740 | /** 741 | * Prepends current frame values to temporal history arrays 742 | * Maintains a sliding window of the last N frames for all three metrics 743 | */ 744 | void temporal_prepend() { 745 | // Shift all historical values one position forward 746 | for (uint i = temporal_stable_frames - 1; i > 0; i--) { 747 | metered_max_i_t[i] = metered_max_i_t[i - 1]; 748 | metered_min_i_t[i] = metered_min_i_t[i - 1]; 749 | metered_avg_i_t[i] = metered_avg_i_t[i - 1]; 750 | } 751 | 752 | // Insert current frame values at position 0 753 | metered_max_i_t[0] = metered_max_i; 754 | metered_min_i_t[0] = metered_min_i; 755 | metered_avg_i_t[0] = metered_avg_i; 756 | } 757 | 758 | /** 759 | * Calculates weighted moving average with exponential decay 760 | * Recent frames have higher weight than older frames 761 | * 762 | * @param type Metric type: METRIC_MAX, METRIC_MIN, or METRIC_AVG 763 | * @return Weighted average value 764 | */ 765 | float temporal_weighted_mean(int type) { 766 | float sum_weighted = 0.0; 767 | float sum_weights = 0.0; 768 | 769 | for (uint i = 0; i < temporal_stable_frames; i++) { 770 | // Select appropriate buffer based on metric type 771 | float current; 772 | if (type == METRIC_MAX) { 773 | current = float(metered_max_i_t[i]); 774 | } else if (type == METRIC_MIN) { 775 | current = float(metered_min_i_t[i]); 776 | } else { // METRIC_AVG 777 | current = float(metered_avg_i_t[i]); 778 | } 779 | 780 | // Calculate exponential decay weight: w(i) = decay^i 781 | // Recent frames (i=0) have weight=1.0, older frames decay exponentially 782 | float weight = pow(TEMPORAL_DECAY, float(i)); 783 | sum_weighted += current * weight; 784 | sum_weights += weight; 785 | } 786 | 787 | // Return normalized weighted average 788 | return sum_weighted / max(sum_weights, 1e-6); 789 | } 790 | 791 | /** 792 | * Applies Exponential Moving Average (EMA) smoothing 793 | * Provides additional stability on top of weighted average 794 | * 795 | * @param new_value New computed value 796 | * @param prev_value Previous frame's value 797 | * @return Smoothed value: prev + alpha * (new - prev) 798 | */ 799 | float apply_ema_smoothing(float new_value, float prev_value) { 800 | return prev_value + TEMPORAL_EMA_ALPHA * (new_value - prev_value); 801 | } 802 | 803 | /** 804 | * Gradually transitions temporal buffers during scene changes 805 | * Blends old values towards new values to avoid sudden jumps 806 | */ 807 | void temporal_fill_gradual() { 808 | for (uint i = 0; i < temporal_stable_frames; i++) { 809 | // Blend each buffer entry towards current value 810 | float old_max = float(metered_max_i_t[i]); 811 | float new_max = float(metered_max_i); 812 | metered_max_i_t[i] = uint(mix(old_max, new_max, TEMPORAL_SCENE_BLEND) + 0.5); 813 | 814 | float old_min = float(metered_min_i_t[i]); 815 | float new_min = float(metered_min_i); 816 | metered_min_i_t[i] = uint(mix(old_min, new_min, TEMPORAL_SCENE_BLEND) + 0.5); 817 | 818 | float old_avg = float(metered_avg_i_t[i]); 819 | float new_avg = float(metered_avg_i); 820 | metered_avg_i_t[i] = uint(mix(old_avg, new_avg, TEMPORAL_SCENE_BLEND) + 0.5); 821 | } 822 | } 823 | 824 | /** 825 | * Performs linear regression prediction for scene change detection 826 | * Uses least squares method to predict next frame value 827 | * 828 | * @param type Metric type: METRIC_MAX, METRIC_MIN, or METRIC_AVG 829 | * @return Predicted value for next frame 830 | */ 831 | float temporal_predict(int type) { 832 | float sum_x = 0.0; 833 | float sum_y = 0.0; 834 | float sum_x2 = 0.0; 835 | float sum_xy = 0.0; 836 | 837 | float n = float(temporal_stable_frames); 838 | float xp = n + 1.0; // Predict position n+1 839 | 840 | // Accumulate sums for least squares regression 841 | for (int i = 0; i < int(temporal_stable_frames); i++) { 842 | float x = float(i + 1); 843 | float y; 844 | 845 | // Select appropriate buffer based on metric type 846 | if (type == METRIC_MAX) { 847 | y = float(metered_max_i_t[i]); 848 | } else if (type == METRIC_MIN) { 849 | y = float(metered_min_i_t[i]); 850 | } else { // METRIC_AVG 851 | y = float(metered_avg_i_t[i]); 852 | } 853 | 854 | sum_x += x; 855 | sum_y += y; 856 | sum_x2 += x * x; 857 | sum_xy += x * y; 858 | } 859 | 860 | // Calculate linear regression coefficients 861 | // y = a*x + b 862 | float denominator = n * sum_x2 - sum_x * sum_x; 863 | float a = (n * sum_xy - sum_x * sum_y) / denominator; 864 | float b = (sum_y - a * sum_x) / n; 865 | 866 | // Return prediction for next frame 867 | return a * xp + b; 868 | } 869 | 870 | /** 871 | * Detects scene changes using multi-metric prediction error analysis 872 | * Combines max, min, and avg metrics with adaptive thresholding 873 | * 874 | * @param max_smoothed Smoothed maximum value 875 | * @param min_smoothed Smoothed minimum value 876 | * @param avg_smoothed Smoothed average value 877 | * @param max_pred Predicted maximum value 878 | * @param min_pred Predicted minimum value 879 | * @param avg_pred Predicted average value 880 | * @return true if scene change is detected 881 | */ 882 | bool is_scene_changed(float max_smoothed, float min_smoothed, float avg_smoothed, 883 | float max_pred, float min_pred, float avg_pred) { 884 | // Detect pure black scenes (always considered a scene change) 885 | if (metered_max_i < TEMPORAL_BLACK_THRESHOLD) { 886 | return true; 887 | } 888 | 889 | // Calculate adaptive tolerance based on current brightness 890 | // Brighter scenes get higher tolerance to reduce false positives 891 | float brightness_factor = float(metered_max_i) / 4095.0; 892 | float adaptive_tolerance = TEMPORAL_BASE_TOLERANCE * 893 | (1.0 + brightness_factor * TEMPORAL_ADAPTIVE_SCALE); 894 | 895 | // Calculate prediction errors in perceptual units (ΔE-like) 896 | // Normalize to [0,1] then scale to perceptual differences 897 | float max_delta = TEMPORAL_DELTA_SCALE * 898 | abs(max_smoothed / 4095.0 - max_pred / 4095.0); 899 | float min_delta = TEMPORAL_DELTA_SCALE * 900 | abs(min_smoothed / 4095.0 - min_pred / 4095.0); 901 | float avg_delta = TEMPORAL_DELTA_SCALE * 902 | abs(avg_smoothed / 4095.0 - avg_pred / 4095.0); 903 | 904 | // Combine errors using weighted average 905 | // Average is most reliable, max is important, min is least reliable 906 | float weighted_delta = avg_delta * TEMPORAL_WEIGHT_AVG + 907 | max_delta * TEMPORAL_WEIGHT_MAX + 908 | min_delta * TEMPORAL_WEIGHT_MIN; 909 | 910 | // Scene change detected if weighted error exceeds adaptive threshold 911 | return weighted_delta > adaptive_tolerance; 912 | } 913 | 914 | /** 915 | * Main temporal stabilization hook 916 | * Processes max, min, and avg metrics with multi-stage smoothing 917 | * and intelligent scene change detection 918 | */ 919 | void hook() { 920 | // Cache previous frame values for EMA smoothing 921 | float prev_max = float(metered_max_i); 922 | float prev_min = float(metered_min_i); 923 | float prev_avg = float(metered_avg_i); 924 | 925 | // Update temporal history with current frame 926 | temporal_prepend(); 927 | 928 | // Stage 1: Weighted moving average (exponential decay) 929 | // Gives more weight to recent frames 930 | float max_weighted = temporal_weighted_mean(METRIC_MAX); 931 | float min_weighted = temporal_weighted_mean(METRIC_MIN); 932 | float avg_weighted = temporal_weighted_mean(METRIC_AVG); 933 | 934 | // Stage 2: Exponential moving average smoothing 935 | // Provides additional stability and reduces noise 936 | float max_smoothed = apply_ema_smoothing(max_weighted, prev_max); 937 | float min_smoothed = apply_ema_smoothing(min_weighted, prev_min); 938 | float avg_smoothed = apply_ema_smoothing(avg_weighted, prev_avg); 939 | 940 | // Generate predictions for scene change detection 941 | float max_pred = temporal_predict(METRIC_MAX); 942 | float min_pred = temporal_predict(METRIC_MIN); 943 | float avg_pred = temporal_predict(METRIC_AVG); 944 | 945 | // Detect and handle scene changes 946 | if (is_scene_changed(max_smoothed, min_smoothed, avg_smoothed, 947 | max_pred, min_pred, avg_pred)) { 948 | // Gradually transition buffer values to new scene 949 | temporal_fill_gradual(); 950 | 951 | // Apply reduced smoothing for scene cuts to maintain some stability 952 | // while still responding to the new scene quickly 953 | max_smoothed = mix(prev_max, float(metered_max_i), TEMPORAL_CUT_BLEND); 954 | min_smoothed = mix(prev_min, float(metered_min_i), TEMPORAL_CUT_BLEND); 955 | avg_smoothed = mix(prev_avg, float(metered_avg_i), TEMPORAL_CUT_BLEND); 956 | } 957 | 958 | // Write back smoothed values with clamping to valid range [0, 4095] 959 | metered_max_i = uint(clamp(max_smoothed, 0.0, 4095.0) + 0.5); 960 | metered_min_i = uint(clamp(min_smoothed, 0.0, 4095.0) + 0.5); 961 | metered_avg_i = uint(clamp(avg_smoothed, 0.0, 4095.0) + 0.5); 962 | } 963 | 964 | //!HOOK OUTPUT 965 | //!BIND HOOKED 966 | //!BIND METERING 967 | //!BIND METERED 968 | //!WHEN preview_metering 969 | //!DESC metering (preview) 970 | 971 | bool almost_equal(float a, float b, float epsilon) { 972 | return abs(a - b) < epsilon; 973 | } 974 | 975 | vec4 hook() { 976 | float value = METERING_tex(METERING_pos).x; 977 | vec3 color = vec3(value); 978 | 979 | float max_i = float(metered_max_i) / 4095.0; 980 | float min_i = float(metered_min_i) / 4095.0; 981 | 982 | float d_max_i = 720 * abs(value - max_i); 983 | float d_min_i = 720 * abs(value - min_i); 984 | 985 | if (d_max_i < 4.0) 986 | color = vec3(1.0, 0.0, 0.0); 987 | if (d_min_i < 4.0) 988 | color = vec3(0.0, 0.0, 1.0); 989 | 990 | if (almost_equal(1.0 - METERING_pos.y, max_i, 1e-3)) 991 | color = vec3(1.0, 0.0, 0.0); 992 | if (almost_equal(1.0 - METERING_pos.y, min_i, 1e-3)) 993 | color = vec3(0.0, 0.0, 1.0); 994 | 995 | if (enable_metering > 1) { 996 | float avg_i = float(metered_avg_i) / 4095.0; 997 | float d_avg_i = 720 * abs(value - avg_i); 998 | 999 | if (d_avg_i < 4.0) 1000 | color = vec3(0.0, 1.0, 0.0); 1001 | 1002 | if (almost_equal(1.0 - METERING_pos.y, avg_i, 1e-3)) 1003 | color = vec3(0.0, 1.0, 0.0); 1004 | } 1005 | 1006 | return vec4(color, 1.0); 1007 | } 1008 | 1009 | //!HOOK OUTPUT 1010 | //!BIND METERED 1011 | //!BIND METADATA 1012 | //!SAVE EMPTY 1013 | //!WIDTH 1 1014 | //!HEIGHT 1 1015 | //!COMPUTE 1 1 1016 | //!WHEN preview_metering 0 = 1017 | //!DESC tone mapping (metadata) 1018 | 1019 | const float m1 = 2610.0 / 4096.0 / 4.0; 1020 | const float m2 = 2523.0 / 4096.0 * 128.0; 1021 | const float c1 = 3424.0 / 4096.0; 1022 | const float c2 = 2413.0 / 4096.0 * 32.0; 1023 | const float c3 = 2392.0 / 4096.0 * 32.0; 1024 | const float pw = 10000.0; 1025 | 1026 | float pq_eotf_inv(float x) { 1027 | float t = pow(x / pw, m1); 1028 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 1029 | } 1030 | 1031 | float pq_eotf(float x) { 1032 | float t = pow(x, 1.0 / m2); 1033 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 1034 | } 1035 | 1036 | const float m2_z = 1.7 * m2; 1037 | 1038 | float iz_eotf_inv(float x) { 1039 | float t = pow(x / pw, m1); 1040 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2_z); 1041 | } 1042 | 1043 | float iz_eotf(float x) { 1044 | float t = pow(x, 1.0 / m2_z); 1045 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 1046 | } 1047 | 1048 | const float d = -0.56; 1049 | const float d0 = 1.6295499532821566e-11; 1050 | 1051 | float I_to_J(float I) { 1052 | return ((1.0 + d) * I) / (1.0 + (d * I)) - d0; 1053 | } 1054 | 1055 | float J_to_I(float J) { 1056 | return (J + d0) / (1.0 + d - d * (J + d0)); 1057 | } 1058 | 1059 | float RGB_to_Y(vec3 rgb) { 1060 | const vec3 luma_coef = vec3(0.2627002120112671, 0.6779980715188708, 0.05930171646986196); 1061 | return dot(rgb, luma_coef); 1062 | } 1063 | 1064 | float get_max_i() { 1065 | if (max_pq_y > 0.0) 1066 | return max_pq_y; 1067 | 1068 | if (scene_max_r > 0.0 || scene_max_g > 0.0 || scene_max_b > 0.0) 1069 | return pq_eotf_inv(RGB_to_Y(vec3(scene_max_r, scene_max_g, scene_max_b))); 1070 | 1071 | if (enable_metering > 0) 1072 | return float(metered_max_i) / 4095.0; 1073 | 1074 | if (max_cll > 0.0) 1075 | return pq_eotf_inv(max_cll); 1076 | 1077 | if (max_luma > 0.0) 1078 | return pq_eotf_inv(max_luma); 1079 | 1080 | return pq_eotf_inv(1000.0); 1081 | } 1082 | 1083 | float get_min_i() { 1084 | if (enable_metering > 0) 1085 | return float(metered_min_i) / 4095.0; 1086 | 1087 | if (min_luma > 0.0) 1088 | return pq_eotf_inv(min_luma); 1089 | 1090 | return pq_eotf_inv(0.0); 1091 | } 1092 | 1093 | float get_avg_i() { 1094 | if (avg_pq_y > 0.0) 1095 | return avg_pq_y; 1096 | 1097 | if (scene_avg > 0.0) 1098 | return pq_eotf_inv(scene_avg); 1099 | 1100 | if (enable_metering > 1) 1101 | return float(metered_avg_i) / 4095.0; 1102 | 1103 | // not useful 1104 | // if (max_fall > 0.0) 1105 | // return pq_eotf_inv(max_fall); 1106 | 1107 | return 0.0; 1108 | } 1109 | 1110 | float get_ev(float avg_i) { 1111 | float reference_iz = iz_eotf_inv(reference_white); 1112 | float reference_j = I_to_J(reference_iz); 1113 | float anchor_j = auto_exposure_anchor * reference_j; 1114 | float anchor_iz = J_to_I(anchor_j); 1115 | float anchor = iz_eotf(anchor_iz); 1116 | 1117 | float average = pq_eotf(avg_i); 1118 | 1119 | float ev = log2(anchor / average); 1120 | return clamp(ev, -auto_exposure_limit_negtive, auto_exposure_limit_postive); 1121 | } 1122 | 1123 | void hook() { 1124 | max_i = get_max_i(); 1125 | min_i = get_min_i(); 1126 | avg_i = get_avg_i(); 1127 | 1128 | if (avg_i > 0.0 && auto_exposure_anchor > 0.0) { 1129 | ev = get_ev(avg_i); 1130 | } else { 1131 | ev = 0.0; 1132 | } 1133 | 1134 | if (ev != 0.0) { 1135 | float ev_scale = exp2(ev); 1136 | max_i = pq_eotf_inv(pq_eotf(max_i) * ev_scale); 1137 | min_i = pq_eotf_inv(pq_eotf(min_i) * ev_scale); 1138 | } 1139 | } 1140 | 1141 | //!HOOK OUTPUT 1142 | //!BIND HOOKED 1143 | //!BIND METADATA 1144 | //!WHEN preview_metering 0 = auto_exposure_anchor 0 > * enable_metering 1 > avg_pq_y 0 > + scene_avg 0 > + * 1145 | //!DESC tone mapping (auto exposure) 1146 | 1147 | vec3 exposure(vec3 x, float ev) { 1148 | return x * exp2(ev); 1149 | } 1150 | 1151 | vec4 hook() { 1152 | vec4 color = HOOKED_tex(HOOKED_pos); 1153 | 1154 | color.rgb = exposure(color.rgb, ev); 1155 | 1156 | return color; 1157 | } 1158 | 1159 | //!HOOK OUTPUT 1160 | //!BIND HOOKED 1161 | //!BIND METADATA 1162 | //!WHEN preview_metering 0 = 1163 | //!DESC tone mapping (astra) 1164 | 1165 | const float m1 = 2610.0 / 4096.0 / 4.0; 1166 | const float m2 = 2523.0 / 4096.0 * 128.0; 1167 | const float c1 = 3424.0 / 4096.0; 1168 | const float c2 = 2413.0 / 4096.0 * 32.0; 1169 | const float c3 = 2392.0 / 4096.0 * 32.0; 1170 | const float pw = 10000.0; 1171 | 1172 | float pq_eotf_inv(float x) { 1173 | float t = pow(x / pw, m1); 1174 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2); 1175 | } 1176 | 1177 | vec3 pq_eotf_inv(vec3 x) { 1178 | vec3 t = pow(x / pw, vec3(m1)); 1179 | return pow((c1 + c2 * t) / (1.0 + c3 * t), vec3(m2)); 1180 | } 1181 | 1182 | float pq_eotf(float x) { 1183 | float t = pow(x, 1.0 / m2); 1184 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 1185 | } 1186 | 1187 | vec3 pq_eotf(vec3 x) { 1188 | vec3 t = pow(x, vec3(1.0 / m2)); 1189 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), vec3(1.0 / m1)) * pw; 1190 | } 1191 | 1192 | // Jzazbz added a factor to m2, which differs from the original PQ equation. 1193 | const float m2_z = 1.7 * m2; 1194 | 1195 | float iz_eotf_inv(float x) { 1196 | float t = pow(x / pw, m1); 1197 | return pow((c1 + c2 * t) / (1.0 + c3 * t), m2_z); 1198 | } 1199 | 1200 | vec3 iz_eotf_inv(vec3 x) { 1201 | vec3 t = pow(x / pw, vec3(m1)); 1202 | return pow((c1 + c2 * t) / (1.0 + c3 * t), vec3(m2_z)); 1203 | } 1204 | 1205 | float iz_eotf(float x) { 1206 | float t = pow(x, 1.0 / m2_z); 1207 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), 1.0 / m1) * pw; 1208 | } 1209 | 1210 | vec3 iz_eotf(vec3 x) { 1211 | vec3 t = pow(x, vec3(1.0 / m2_z)); 1212 | return pow(max(t - c1, 0.0) / (c2 - c3 * t), vec3(1.0 / m1)) * pw; 1213 | } 1214 | 1215 | vec3 RGB_to_XYZ(vec3 RGB) { 1216 | const mat3 M = mat3( 1217 | 0.6369580483012914, 0.14461690358620832, 0.1688809751641721, 1218 | 0.2627002120112671, 0.6779980715188708, 0.05930171646986196, 1219 | 0.0 , 0.028072693049087428, 1.060985057710791 1220 | ); 1221 | return RGB * M; 1222 | } 1223 | 1224 | vec3 XYZ_to_RGB(vec3 XYZ) { 1225 | const mat3 M = mat3( 1226 | 1.716651187971268, -0.355670783776392, -0.25336628137366, 1227 | -0.666684351832489, 1.616481236634939, 0.0157685458139111, 1228 | 0.017639857445311, -0.042770613257809, 0.942103121235474 1229 | ); 1230 | return XYZ * M; 1231 | } 1232 | 1233 | const float b = 1.15; 1234 | const float g = 0.66; 1235 | 1236 | vec3 XYZ_to_XYZm(vec3 XYZ) { 1237 | float Xm = (b * XYZ.x) - ((b - 1.0) * XYZ.z); 1238 | float Ym = (g * XYZ.y) - ((g - 1.0) * XYZ.x); 1239 | return vec3(Xm, Ym, XYZ.z); 1240 | } 1241 | 1242 | vec3 XYZm_to_XYZ(vec3 XYZm) { 1243 | float Xa = (XYZm.x + ((b - 1.0) * XYZm.z)) / b; 1244 | float Ya = (XYZm.y + ((g - 1.0) * Xa)) / g; 1245 | return vec3(Xa, Ya, XYZm.z); 1246 | } 1247 | 1248 | vec3 XYZ_to_LMS(vec3 XYZ) { 1249 | const mat3 M =mat3( 1250 | 0.41478972, 0.579999, 0.0146480, 1251 | -0.2015100, 1.120649, 0.0531008, 1252 | -0.0166008, 0.264800, 0.6684799 1253 | ); 1254 | return XYZ * M; 1255 | } 1256 | 1257 | vec3 LMS_to_XYZ(vec3 LMS) { 1258 | const mat3 M = mat3( 1259 | 1.9242264357876067, -1.0047923125953657, 0.037651404030618, 1260 | 0.35031676209499907, 0.7264811939316552, -0.06538442294808501, 1261 | -0.09098281098284752, -0.3127282905230739, 1.5227665613052603 1262 | ); 1263 | return LMS * M; 1264 | } 1265 | 1266 | vec3 LMS_to_Iab(vec3 LMS) { 1267 | const mat3 M = mat3( 1268 | 0.0, 0.5, 0.5, 1269 | 3.524000, -4.066708, 0.542708, 1270 | 0.199076, 1.096799, -1.295875 1271 | ); 1272 | return LMS * M; 1273 | } 1274 | 1275 | vec3 Iab_to_LMS(vec3 Iab) { 1276 | const mat3 M = mat3( 1277 | 1.0, 0.13860504327153927, 0.05804731615611883, 1278 | 1.0, -0.1386050432715393, -0.058047316156118904, 1279 | 1.0, -0.09601924202631895, -0.81189189605603900 1280 | ); 1281 | return Iab * M; 1282 | } 1283 | 1284 | // https://doi.org/10.2352/ISSN.2169-2629.2017.25.264 1285 | // Optimized matrices for Jzazbz about LMS to I conversion. 1286 | // https://doi.org/10.1364/OE.413659 1287 | // ZCAM defines Iz = G' - ε, where ε = 3.7035226210190005e-11. 1288 | // However, it appears we do not need it. 1289 | vec3 LMS_to_Iab_optimized(vec3 LMS) { 1290 | const mat3 M = mat3( 1291 | 0.0, 1.0, 0.0, 1292 | 3.524000, -4.066708, 0.542708, 1293 | 0.199076, 1.096799, -1.295875 1294 | ); 1295 | return LMS * M; 1296 | } 1297 | 1298 | vec3 Iab_to_LMS_optimized(vec3 Iab) { 1299 | const mat3 M = mat3( 1300 | 1.0, 0.2772100865430786, 0.1160946323122377, 1301 | 1.0, 0.0, 0.0, 1302 | 1.0, 0.0425858012452203, -0.75384457989992 1303 | ); 1304 | return Iab * M; 1305 | } 1306 | 1307 | const float d = -0.56; 1308 | const float d0 = 1.6295499532821566e-11; 1309 | 1310 | float I_to_J(float I) { 1311 | return ((1.0 + d) * I) / (1.0 + (d * I)) - d0; 1312 | } 1313 | 1314 | float J_to_I(float J) { 1315 | return (J + d0) / (1.0 + d - d * (J + d0)); 1316 | } 1317 | 1318 | // CIELUV: -0.01585, -0.03017, -0.04556, -0.02667, -0.00295, 0.14592, 0.05084, -0.01900, -0.00764 1319 | float hke_fh_nayatani( 1320 | float h, float k1, 1321 | float k2, float k3, float k4, float k5, 1322 | float k6, float k7, float k8, float k9 1323 | ) { 1324 | float q = k1 + 1325 | k2 * cos(h) + k3 * cos(2.0 * h) + k4 * cos(3.0 * h) + k5 * cos(4.0 * h) + 1326 | k6 * sin(h) + k7 * sin(2.0 * h) + k8 * sin(3.0 * h) + k9 * sin(4.0 * h); 1327 | // flipped 1328 | return -q; 1329 | } 1330 | 1331 | // CIECAM02: -0.218, 0.167, -0.500, 0.032, 0.887 1332 | // CAM16: -0.160, 0.132, -0.405, 0.080, 0.792 1333 | float hke_fh_hellwig(float h, float a1, float a2, float a3, float a4, float a5) { 1334 | return a1 * cos(h) + a2 * cos(2.0 * h) + a3 * sin(h) + a4 * sin(2.0 * h) + a5; 1335 | } 1336 | 1337 | // CIELAB: 0.1644, 0.0603, 0.1307, 0.0060 1338 | float hke_fh_high(float h, float k1, float k2, float k3, float k4) { 1339 | h = mod(mod(degrees(h), 360.0) + 360.0, 360.0); 1340 | float by = k1 * abs(sin(radians((h - 90.0)/ 2.0))) + k2; 1341 | float r = h <= 90.0 || h >= 270.0 ? k3 * abs(cos(radians(h))) + k4 : 0.0; 1342 | return by + r; 1343 | } 1344 | 1345 | // CIECAM16: 1.5940, 45.0, 2.6518 1346 | // CIELAB: 0.1644, 45.0, 0.1024 1347 | float hke_fh_liao(float h, float k3, float k4, float k5) { 1348 | h = mod(mod(degrees(h), 360.0) + 360.0, 360.0); 1349 | return k3 * abs(log(((h + k4) / (90.0 + k4)))) + k5; 1350 | } 1351 | 1352 | float hke_fh(float h) { 1353 | float result = hke_fh_liao(h, 0.1351, 45.0, 0.1439); 1354 | return result * hk_effect_compensate_scaling; 1355 | } 1356 | 1357 | float J_to_Jhk(vec3 JCh) { 1358 | float J = JCh.x; 1359 | float C = JCh.y; 1360 | float h = JCh.z; 1361 | return J + C * hke_fh(h); 1362 | } 1363 | 1364 | float Jhk_to_J(vec3 JCh) { 1365 | float J = JCh.x; 1366 | float C = JCh.y; 1367 | float h = JCh.z; 1368 | return J - C * hke_fh(h); 1369 | } 1370 | 1371 | // https://www.itu.int/rec/R-REC-BT.2124 1372 | // ΔE_ITP_JND = 1 / 720 1373 | // 0.0001 of Cz is much smaller than it 1374 | const float epsilon = 0.0001; 1375 | 1376 | vec3 Lab_to_LCh(vec3 Lab) { 1377 | float L = Lab.x; 1378 | float a = Lab.y; 1379 | float b = Lab.z; 1380 | 1381 | float C = length(vec2(a, b)); 1382 | float h = C < epsilon ? 0.0 : atan(b, a); 1383 | 1384 | return vec3(L, C, h); 1385 | } 1386 | 1387 | vec3 LCh_to_Lab(vec3 LCh) { 1388 | float L = LCh.x; 1389 | float C = LCh.y; 1390 | float h = LCh.z; 1391 | 1392 | C = max(C, 0.0); 1393 | float a = C * cos(h); 1394 | float b = C * sin(h); 1395 | 1396 | return vec3(L, a, b); 1397 | } 1398 | 1399 | vec3 RGB_to_Jab(vec3 color) { 1400 | color *= reference_white; 1401 | color = RGB_to_XYZ(color); 1402 | color = XYZ_to_XYZm(color); 1403 | color = XYZ_to_LMS(color); 1404 | color = iz_eotf_inv(color); 1405 | color = LMS_to_Iab_optimized(color); 1406 | color.x = I_to_J(color.x); 1407 | color.x = J_to_Jhk(Lab_to_LCh(color)); 1408 | return color; 1409 | } 1410 | 1411 | vec3 Jab_to_RGB(vec3 color) { 1412 | color.x = Jhk_to_J(Lab_to_LCh(color)); 1413 | color.x = J_to_I(color.x); 1414 | color = Iab_to_LMS_optimized(color); 1415 | color = iz_eotf(color); 1416 | color = LMS_to_XYZ(color); 1417 | color = XYZm_to_XYZ(color); 1418 | color = XYZ_to_RGB(color); 1419 | color /= reference_white; 1420 | return color; 1421 | } 1422 | 1423 | float f_slope(float x0, float y0, float x1, float y1) { 1424 | float num = (y1 - y0); 1425 | float den = (x1 - x0); 1426 | return abs(den) < 1e-6 ? 1.0 : num / den; 1427 | } 1428 | 1429 | float f_intercept(float slope, float x0, float y0) { 1430 | return y0 - slope * x0; 1431 | } 1432 | 1433 | float f_linear(float x, float slope, float intercept) { 1434 | return slope * x + intercept; 1435 | } 1436 | 1437 | float f_contrast(float c) { 1438 | float k = 0.5; 1439 | float a = 3.0; 1440 | return k * (1.0 - exp(-a * c)); 1441 | } 1442 | 1443 | // Modified to make x0 and y0 controllable. 1444 | float f_toe_suzuki(float x, float slope, float x0, float y0, float x1, float y1) { 1445 | float dx = x1 - x0; 1446 | float dy = y1 - y0; 1447 | float dx2 = dx * dx; 1448 | float dy2 = dy * dy; 1449 | float den = dy - slope * dx; 1450 | 1451 | float a = slope * dx2 * dy2 / (den * den); 1452 | float b = slope * dx2 / den; 1453 | float c = dy2 / den; 1454 | 1455 | return -(a / (x - x0 + b)) + c + y0; 1456 | } 1457 | 1458 | float f_shoulder_suzuki(float x, float slope, float x0, float y0, float x1, float y1) { 1459 | float d = slope * (x0 - x1) - y0 + y1; 1460 | float a = (slope * (x0 - x1) * (x0 - x1) * (y0 - y1) * (y0 - y1)) / (d * d); 1461 | float b = (slope * x0 * (x1 - x0) + x1 * (y0 - y1)) / d; 1462 | float c = (y1 * (slope * (x0 - x1) + y0) - y0 * y0) / d; 1463 | return -(a / (x + b)) + c; 1464 | } 1465 | 1466 | float f_toe_hable(float x, float slope, float x0, float y0, float x1, float y1) { 1467 | float dx = x1 - x0; 1468 | float dy = y1 - y0; 1469 | 1470 | float b = slope * dx / dy; 1471 | float a = log(dy) - b * log(dx); 1472 | float s = 1.0; 1473 | 1474 | return exp(a + b * log(max((x - x0) * s, 1e-6))) * s + y0; 1475 | } 1476 | 1477 | // Simplified, no overshoot. 1478 | float f_shoulder_hable(float x, float slope, float x0, float y0, float x1, float y1) { 1479 | float dx = x1 - x0; 1480 | float dy = y1 - y0; 1481 | 1482 | float b = slope * dx / dy; 1483 | float a = log(dy) - b * log(dx); 1484 | float s = -1.0; 1485 | 1486 | return exp(a + b * log(max((x - x1) * s, 1e-6))) * s + y1; 1487 | } 1488 | 1489 | float f( 1490 | float x, float iw, float ib, float ow, float ob, 1491 | float sw, float hw, float cb 1492 | ) { 1493 | float midgray = 0.5 * ow; 1494 | float shadow = mix(midgray, ob, sw); 1495 | float highlight = mix(midgray, ow, hw); 1496 | float contrast = f_contrast(cb); 1497 | 1498 | float x0 = ib; 1499 | float y0 = ob; 1500 | float x1 = mix(shadow, midgray, contrast); 1501 | float y1 = shadow; 1502 | float x2 = mix(highlight, midgray, contrast); 1503 | float y2 = highlight; 1504 | float x3 = iw; 1505 | float y3 = ow; 1506 | 1507 | float slope = f_slope(x1, y1, x2, y2); 1508 | float intercept = f_intercept(slope, x1, y1); 1509 | 1510 | if (x >= x1 && x <= x2) { 1511 | return f_linear(x, slope, intercept); 1512 | } 1513 | 1514 | if (x < x1) { 1515 | float slope_toe = f_slope(x0, y0, x1, y1); 1516 | if (slope_toe >= slope) { 1517 | return f_linear(x, slope, intercept); 1518 | } 1519 | 1520 | return f_toe_suzuki(x, slope, x0, y0, x1, y1); 1521 | } 1522 | 1523 | if (x > x2) { 1524 | float slope_shoulder = f_slope(x2, y2, x3, y3); 1525 | if (slope_shoulder >= slope) { 1526 | return f_linear(x, slope, intercept); 1527 | } 1528 | 1529 | return f_shoulder_hable(x, slope, x2, y2, x3, y3); 1530 | } 1531 | 1532 | return x; 1533 | } 1534 | 1535 | float f(float x, float iw, float ib, float ow, float ob) { 1536 | return f( 1537 | x, iw, ib, ow, ob, 1538 | shadow_weight, highlight_weight, contrast_bias 1539 | ); 1540 | } 1541 | 1542 | float curve(float x) { 1543 | float ow = I_to_J(iz_eotf_inv(reference_white)); 1544 | float ob = I_to_J(iz_eotf_inv(reference_white / contrast_ratio)); 1545 | float iw = I_to_J(iz_eotf_inv(pq_eotf(max_i))); 1546 | float ib = I_to_J(iz_eotf_inv(pq_eotf(min_i))); 1547 | 1548 | iw = max(iw, ow); 1549 | ib = min(ib, ob); 1550 | 1551 | float y = f(x, iw, ib, ow, ob); 1552 | 1553 | return clamp(y, ob, ow); 1554 | } 1555 | 1556 | // this is a correction in generic vividness and depth. 1557 | // V = sqrt(J^2 + C^2) 1558 | // D = sqrt((J_max - J)^2 + C^2) 1559 | // more specific definitions of V and D for Jzazbz, 1560 | // see the following links: 1561 | // https://doi.org/10.2352/ISSN.2169-2629.2018.26.96 1562 | // https://doi.org/10.2352/issn.2169-2629.2019.27.43 1563 | vec2 chroma_correction(vec2 ab, float l1, float l2) { 1564 | float r_min = min(l1, l2) / max(max(l1, l2), 1e-6); 1565 | float r_scaled = mix(1.0, r_min, chroma_correction_scaling); 1566 | float r_safe = max(r_scaled, 0.0); 1567 | float r_powered = pow(r_safe, chroma_correction_power); 1568 | return ab * r_powered; 1569 | } 1570 | 1571 | vec3 tone_mapping(vec3 lab) { 1572 | float l2 = curve(lab.x); 1573 | vec2 ab2 = chroma_correction(lab.yz, lab.x, l2); 1574 | return vec3(l2, ab2); 1575 | } 1576 | 1577 | vec4 hook() { 1578 | vec4 color = HOOKED_tex(HOOKED_pos); 1579 | 1580 | color.rgb = RGB_to_Jab(color.rgb); 1581 | color.rgb = tone_mapping(color.rgb); 1582 | color.rgb = Jab_to_RGB(color.rgb); 1583 | 1584 | return color; 1585 | } 1586 | --------------------------------------------------------------------------------