├── 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 | |
|
|
|
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 |
--------------------------------------------------------------------------------