From 0af8be9016f957969fa09d3d314ce2fb4e7381e1 Mon Sep 17 00:00:00 2001 From: majorpainthecactus Date: Sat, 29 Jan 2022 22:53:23 +0000 Subject: [PATCH] Major improvement to the Sony PVM 4K HDR shader Refactored it into a single pass for simplicity and speed Rewrote a lot of the curves Defaulted to a new mask RGBX Added ability for scanlines to overlap Broke out HDR and inverse tonemapper into headers --- hdr/crt-sony-pvm-2730-4k-hdr.slangp | 16 +- hdr/crt-sony-pvm-4k-hdr.slangp | 44 +--- hdr/shaders/crt-sony-pvm-4k-hdr.slang | 276 +++++++++++++++++--------- hdr/shaders/hdr10.h | 37 ++++ hdr/shaders/hdr10.slang | 42 +--- hdr/shaders/inverse_tonemap.h | 30 +++ hdr/shaders/inverse_tonemap.slang | 48 ++--- 7 files changed, 282 insertions(+), 211 deletions(-) create mode 100644 hdr/shaders/hdr10.h create mode 100644 hdr/shaders/inverse_tonemap.h diff --git a/hdr/crt-sony-pvm-2730-4k-hdr.slangp b/hdr/crt-sony-pvm-2730-4k-hdr.slangp index 1db7de1..9dd4470 100644 --- a/hdr/crt-sony-pvm-2730-4k-hdr.slangp +++ b/hdr/crt-sony-pvm-2730-4k-hdr.slangp @@ -1,6 +1,12 @@ #reference "crt-sony-pvm-4k-hdr.slangp" -Contrast = "3.7500000" -PaperWhiteNits = "450.000000" -ScanlineWidth = "0.950000" -ResolutionPattern = "2.000000" -Sharpness = "2.400000" +PaperWhiteNits = "650.000000" +CRTGamma = "1.8" +ScanlineMin = "0.55000" +ScanlineMax = "0.90000" +ResolutionPattern = "3.000000" +HorizontalSharpness = "1.500000" +HorizontalAttack = "0.600000" +VerticalAttack = "0.650000" +RedConvergence = "-1.800000" +GreenConvergence = "-0.600000" +BlueConvergence = "0.000000" \ No newline at end of file diff --git a/hdr/crt-sony-pvm-4k-hdr.slangp b/hdr/crt-sony-pvm-4k-hdr.slangp index 5042de6..9966a04 100644 --- a/hdr/crt-sony-pvm-4k-hdr.slangp +++ b/hdr/crt-sony-pvm-4k-hdr.slangp @@ -31,49 +31,13 @@ We need 8K to really start to get round the right ballpark. We need 9600 resolut TODO: Make the horizontal scanlines more blurry in the vertical direction - we're working in HDR space at this point so its trickier than normal. */ -shaders = "4" +shaders = "1" feedback_pass = "0" -shader0 = "../stock.slang" -filter_linear0 = false +shader0 = "shaders/crt-sony-pvm-4k-hdr.slang" +filter_linear0 = "false" wrap_mode0 = "clamp_to_border" mipmap_input0 = "false" -alias0 = "StockPass" +alias0 = "" float_framebuffer0 = "false" srgb_framebuffer0 = "false" -scale_type_x0 = "source" -scale_x0 = "1.000000" -scale_type_y0 = "source" -scale_y0 = "1.000000" - -shader1 = "shaders/inverse_tonemap.slang" -filter_linear1 = "false" -wrap_mode1 = "clamp_to_border" -mipmap_input1 = "false" -alias1 = "" -float_framebuffer1 = "false" -srgb_framebuffer1 = "false" -scale_type_x1 = "source" -scale_x1 = "1.000000" -scale_type_y1 = "source" -scale_y1 = "1.000000" - -shader2 = "shaders/hdr10.slang" -filter_linear2 = "false" -wrap_mode2 = "clamp_to_border" -mipmap_input2 = "false" -alias2 = "" -float_framebuffer2 = "false" -srgb_framebuffer2 = "false" -scale_type_x2 = "source" -scale_x2 = "1.000000" -scale_type_y2 = "source" -scale_y2 = "1.000000" - -shader3 = "shaders/crt-sony-pvm-4k-hdr.slang" -filter_linear3 = "false" -wrap_mode3 = "clamp_to_border" -mipmap_input3 = "false" -alias3 = "" -float_framebuffer3 = "false" -srgb_framebuffer3 = "false" diff --git a/hdr/shaders/crt-sony-pvm-4k-hdr.slang b/hdr/shaders/crt-sony-pvm-4k-hdr.slang index 1b030b7..4fcc4aa 100644 --- a/hdr/shaders/crt-sony-pvm-4k-hdr.slang +++ b/hdr/shaders/crt-sony-pvm-4k-hdr.slang @@ -21,20 +21,43 @@ Dont use this shader directly - use the crt\crt-sony-pvm-4k-hdr.slangp to have t #pragma format A2B10G10R10_UNORM_PACK32 +#include "inverse_tonemap.h" +#include "hdr10.h" + layout(push_constant) uniform Push { vec4 SourceSize; vec4 OriginalSize; vec4 OutputSize; uint FrameCount; - float ScanlineWidth; + float PaperWhiteNits; + float MaxNits; + float ExpandGamut; + float CRTGamma; + float ScanlineMin; + float ScanlineMax; float ResolutionPattern; - float Sharpness; + float HorizontalSharpness; + float HorizontalAttack; + float VerticalAttack; + float RedConvergence; + float GreenConvergence; + float BlueConvergence; } params; -#pragma parameter ScanlineWidth "Scanline Width" 0.95 0.0 1.0 0.01 -#pragma parameter ResolutionPattern "Resolution Pattern" 2.0 0.0 8.0 1.0 -#pragma parameter Sharpness "Sharpness" 2.4 0.0 5.0 0.1 +#pragma parameter PaperWhiteNits "Paper White Luminance" 650.0 0.0 10000.0 10.0 +#pragma parameter MaxNits "Peak Luminance" 700.0 0.0 10000.0 10.0 +#pragma parameter ExpandGamut "ExpandGamut" 1.0 0.0 1.0 1.0 +#pragma parameter CRTGamma "CRTGamma" 1.8 0.0 5.0 0.05 +#pragma parameter ScanlineMin "Scanline Min" 0.55 0.0 2.0 0.01 +#pragma parameter ScanlineMax "Scanline Max" 0.90 0.0 2.0 0.01 +#pragma parameter ResolutionPattern "Resolution Pattern" 3.0 0.0 8.0 1.0 +#pragma parameter HorizontalSharpness "Horizontal Sharpness" 1.5 0.0 5.0 0.1 +#pragma parameter HorizontalAttack "Horizontal Attack" 0.60 0.0 1.0 0.05 +#pragma parameter VerticalAttack "Vertical Attack" 0.65 0.0 1.0 0.05 +#pragma parameter RedConvergence "Red Convergence" 0.0 -10.0 10.0 0.05 +#pragma parameter GreenConvergence "Green Convergence" 0.0 -10.0 10.0 0.05 +#pragma parameter BlueConvergence "Blue Convergence" 0.0 -10.0 10.0 0.05 layout(std140, set = 0, binding = 0) uniform UBO { @@ -45,18 +68,28 @@ layout(std140, set = 0, binding = 0) uniform UBO layout(location = 0) in vec4 Position; layout(location = 1) in vec2 TexCoord; layout(location = 0) out vec2 vTexCoord; +layout(location = 1) out float ScanlineSize; +layout(location = 2) out float InverseScanlineSize; +layout(location = 3) out vec3 Convergence; void main() { gl_Position = global.MVP * Position; vTexCoord = TexCoord * vec2(1.00001); // To resolve rounding issues when sampling + + ScanlineSize = params.OutputSize.y / params.SourceSize.y; + InverseScanlineSize = 1.0f / ScanlineSize; + + Convergence = vec3(params.RedConvergence, params.GreenConvergence, params.BlueConvergence); } #pragma stage fragment layout(location = 0) in vec2 vTexCoord; +layout(location = 1) in float ScanlineSize; +layout(location = 2) in float InverseScanlineSize; +layout(location = 3) in vec3 Convergence; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source; -layout(set = 0, binding = 3) uniform sampler2D StockPass; float ModInteger(float a, float b) { @@ -64,12 +97,11 @@ float ModInteger(float a, float b) return floor(m + 0.5); } -#define kPi 3.1415926536 -#define kEuler 2.718281828459 -#define kMax 1.0 +#define kPi 3.1415926536f +#define kEuler 2.718281828459f +#define kMax 1.0f -#define kGuassianMin vec3(1.1) -#define kGuassianMax vec3(3.0) +#define kLumaRatio 0.5f #define kRed vec3(1.0, 0.0, 0.0) #define kGreen vec3(0.0, 1.0, 0.0) @@ -80,50 +112,109 @@ float ModInteger(float a, float b) #define kBlack vec3(0.0, 0.0, 0.0) #define kWhite vec3(1.0, 1.0, 1.0) -float Ramp(const float gaussian, float colour) +const float PatternSize[10] = {2.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 7.0f, 9.0f, 12.0f}; + +const mat4 kCubicBezier = mat4( 1.0f, 0.0f, 0.0f, 0.0f, + -3.0f, 3.0f, 0.0f, 0.0f, + 3.0f, -6.0f, 3.0f, 0.0f, + -1.0f, 3.0f, -3.0f, 1.0f ); + +const vec4 kFallOffControlPoints = vec4(0.0f, 0.0f, 0.0f, 1.0f); +const vec4 kAttackControlPoints = vec4(0.0f, 1.0f, 1.0f, 1.0f); +//const vec4 kScanlineControlPoints = vec4(1.0f, 1.0f, 0.0f, 0.0f); + +vec4 HorizControlPoints(const bool falloff) { - return clamp(gaussian * colour, 0.0, 1.0); + return falloff ? kFallOffControlPoints + vec4(0.0f, 0.0f, params.HorizontalAttack, 0.0f) : kAttackControlPoints - vec4(0.0f, params.HorizontalAttack, 0.0f, 0.0f); } -vec3 Ramp3(const vec3 gaussian, vec3 colour) +float Bezier(const float t0, const vec4 control_points) { - return clamp(gaussian * colour, vec3(0.0), vec3(1.0)); + vec4 t = vec4(1.0, t0, t0*t0, t0*t0*t0); + return dot(t, control_points * kCubicBezier); +} + +float ToLinear1(float channel) +{ + return (channel > 0.04045f) ? pow(abs(channel) * (1.0f / 1.055f) + (0.055f / 1.055f), params.CRTGamma) : channel * (1.0f / 12.92f); +} + +vec3 ToLinear(vec3 colour) +{ + return vec3(ToLinear1(colour.r), ToLinear1(colour.g), ToLinear1(colour.b)); +} + +vec3 Ramp(const vec3 luminance, const vec3 colour) +{ + return clamp(luminance * colour, 0.0, 1.0); +} + +vec3 ScanlineColour(const float current_position, const float current_center, const float source_tex_coord_x, const float narrowed_source_pixel_offset, inout float next_prev ) +{ + const float current_source_position_y = (vTexCoord.y * params.SourceSize.y) - next_prev; + const float current_source_center_y = floor(current_source_position_y) + 0.5f; + + const float source_tex_coord_y = current_source_center_y / params.SourceSize.y; + + const vec2 source_tex_coord_0 = vec2(source_tex_coord_x, source_tex_coord_y); + const vec2 source_tex_coord_1 = vec2(source_tex_coord_x + (1.0f / params.SourceSize.x), source_tex_coord_y); + + const float scanline_position = current_source_center_y * ScanlineSize; + const vec3 scanline_delta = vec3(scanline_position) - (vec3(current_center) - Convergence); + const vec3 scanline_distance = abs(scanline_delta * InverseScanlineSize * 2.0f); + + next_prev = scanline_delta.x > 0.0f ? 1.0f : -1.0f; + + const vec3 sdr_colour_0 = ToLinear(texture(Source, source_tex_coord_0).xyz); + const vec3 sdr_colour_1 = ToLinear(texture(Source, source_tex_coord_1).xyz); + + const vec3 hdr_colour_0 = InverseTonemap(sdr_colour_0, params.MaxNits, params.PaperWhiteNits, kLumaRatio); + const vec3 hdr_colour_1 = InverseTonemap(sdr_colour_1, params.MaxNits, params.PaperWhiteNits, kLumaRatio); + + /* Horizontal interpolation between pixels */ + + const vec3 horiz_interp = vec3(Bezier(narrowed_source_pixel_offset, HorizControlPoints(sdr_colour_0.x > sdr_colour_1.x)), + Bezier(narrowed_source_pixel_offset, HorizControlPoints(sdr_colour_0.y > sdr_colour_1.y)), + Bezier(narrowed_source_pixel_offset, HorizControlPoints(sdr_colour_0.z > sdr_colour_1.z))); + + const vec3 hdr_colour = mix(hdr_colour_0, hdr_colour_1, horiz_interp); + const vec3 sdr_colour = mix(sdr_colour_0, sdr_colour_1, horiz_interp); + + //const vec3 scanline_width = mix(vec3(0.2f), vec3(0.4f), sdr_colour) * params.ScanlineWidth; + //const vec3 luminance = pow(vec3(kEuler), vec3(-0.5f) * pow(scanline_distance / scanline_width, vec3(2.0f))); /* Gaussian distribution */ + + const vec3 narrowed_scanline_distance = clamp(scanline_distance / (sdr_colour * vec3(params.ScanlineMax - params.ScanlineMin) + vec3(params.ScanlineMin)), vec3(0.0f), vec3(1.0f)); + + const vec4 scanline_control_points = vec4(1.0f, 1.0f, params.VerticalAttack, 0.0f); + + const vec3 luminance = vec3(Bezier(narrowed_scanline_distance.x, scanline_control_points), + Bezier(narrowed_scanline_distance.y, scanline_control_points), + Bezier(narrowed_scanline_distance.z, scanline_control_points)); + + return luminance * hdr_colour; } void main() { - float ScanlineSize = params.OutputSize.y / params.SourceSize.y; + uint resolution_pattern = uint(params.ResolutionPattern); - const vec2 InPixels = (vTexCoord * params.OutputSize.xy); + const vec2 current_position = vTexCoord * params.OutputSize.xy; + const float current_center = floor(current_position.y) + 0.5f; - const float ScanlinePosition = (floor(vTexCoord.y * params.SourceSize.y) * ScanlineSize) + (ScanlineSize * 0.5); - - float ScanlineDistance = ScanlinePosition - (floor(InPixels.y) + 0.5); - - ScanlineDistance /= ScanlineSize * params.ScanlineWidth; - ScanlineDistance = clamp(abs(ScanlineDistance * 2.0), 0.0, 1.0); - - const float Gaussian = pow(kEuler, -0.5 * pow(ScanlineDistance/0.3, 2.0)); /* Gaussian distribution */ - - float HorizInterp = (vTexCoord.x * params.SourceSize.x) - (floor(vTexCoord.x * params.SourceSize.x)); - HorizInterp = clamp(((HorizInterp - 0.5) * params.Sharpness) + 0.5, 0.0f, 1.0); - - const vec2 SourceTexCoord0 = vec2(vTexCoord.x, ScanlinePosition / params.OutputSize.y); - - vec3 HDRColour0 = texture(Source, SourceTexCoord0).xyz; - vec3 SDRColour0 = texture(StockPass, SourceTexCoord0).xyz; - - const vec2 SourceTexCoord1 = vec2(vTexCoord.x + (1.0 / params.SourceSize.x), ScanlinePosition / params.OutputSize.y); - - const vec3 HDRColour1 = texture(Source, SourceTexCoord1).xyz; - const vec3 SDRColour1 = texture(StockPass, SourceTexCoord1).xyz; + const float current_source_position_x = (vTexCoord.x * params.SourceSize.x) /* / PatternSize[resolution_pattern] */; + const float current_source_center_x = floor(current_source_position_x) + 0.5f; - const vec3 HDRColour = mix(HDRColour0, HDRColour1, vec3(HorizInterp)); - const vec3 SDRColour = mix(SDRColour0, SDRColour1, vec3(HorizInterp)); - - vec3 Luminance = Ramp3(vec3(Gaussian), (SDRColour * kGuassianMax) + kGuassianMin); + const float source_tex_coord_x = (current_source_center_x /* * PatternSize[resolution_pattern]*/) / params.SourceSize.x; - vec3 OutputColour; + const float source_pixel_offset = current_source_position_x - floor(current_source_position_x); + const float narrowed_source_pixel_offset = clamp(((source_pixel_offset - 0.5f) * params.HorizontalSharpness) + 0.5f, 0.0f, 1.0f); + + float next_prev = 0.0f; + + const vec3 scanline_colour0 = ScanlineColour(current_position.y, current_center, source_tex_coord_x, narrowed_source_pixel_offset, next_prev); + const vec3 scanline_colour1 = ScanlineColour(current_position.y, current_center, source_tex_coord_x, narrowed_source_pixel_offset, next_prev); + + vec3 scanline_colour = scanline_colour0 + scanline_colour1; /* Various resolution patterns - remember your LCD 4K screen will likely be 16:9 whereas the CRT TV will likely be 4:3 and so higher TVL values will be required on your 16:9 screen @@ -131,107 +222,108 @@ void main() Pattern 1's 960TVL at 16:9 is about right for a 800TVL at 4:3. */ - uint ResolutionPattern = uint(params.ResolutionPattern); - - switch(ResolutionPattern) + switch(resolution_pattern) { case 0: /* 2 pattern - 1440TVL (16:9) horiz resolution (too high?) on a 4K screen */ { - const uint PatternSize = 2; - const vec3 Mask[2] = vec3[]( kYellow, kCyan ); + const vec3 mask[2] = vec3[]( kYellow, kCyan ); - uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); - OutputColour = Luminance * HDRColour * Mask[PatternX]; + uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[0])); + scanline_colour *= mask[pattern_x]; break; } case 1: /* 3 pattern - 960TVL (16:9) horiz resolution on a 4K screen */ { - const uint PatternSize = 3; - const vec3 Mask[3] = vec3[]( kRed, kGreen, kBlue ); + const vec3 mask[3] = vec3[]( kRed, kGreen, kBlue ); - uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); - OutputColour = Luminance * HDRColour * Mask[PatternX]; + uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[1])); + scanline_colour *= mask[pattern_x]; break; } case 2: /* 4 pattern - 720TVL (16:9) horiz resolution on a 4K screen */ { - const uint PatternSize = 4; - const vec3 Mask[4] = vec3[]( kRed, kYellow, kCyan, kBlue ); + const vec3 mask[4] = vec3[]( kRed, kYellow, kCyan, kBlue ); - uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); - OutputColour = Luminance * HDRColour * Mask[PatternX]; + uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[2])); + scanline_colour *= mask[pattern_x]; break; } - case 3: /* 5 BRG pattern - 576TVL (16:9) horiz resolution on a 4K screen */ + case 3: /* 4 pattern - 720TVL (16:9) horiz resolution on a 4K screen */ { - const uint PatternSize = 5; - const vec3 Mask[5] = vec3[]( kRed, kMagenta, kBlue, kGreen, kGreen ); + const vec3 mask[4] = vec3[]( kRed, kGreen, kBlue, kBlack ); - uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); - OutputColour = Luminance * HDRColour * Mask[PatternX]; + uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[3])); + scanline_colour *= mask[pattern_x]; break; } - case 4: /* 5 pattern - 576TVL (16:9) horiz resolution on a 4K screen */ - { - const uint PatternSize = 5; - const vec3 Mask[5] = vec3[]( kRed, kYellow, kGreen, kCyan, kBlue ); + case 4: /* 5 BRG pattern - 576TVL (16:9) horiz resolution on a 4K screen */ + { + const vec3 mask[5] = vec3[]( kRed, kMagenta, kBlue, kGreen, kGreen ); - uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); - OutputColour = Luminance * HDRColour * Mask[PatternX]; + uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[4])); + scanline_colour *= mask[pattern_x]; break; } - case 5: /* 6 pattern - 480TVL (16:9) horiz resolution on a 4K screen */ - { - const uint PatternSize = 6; - const vec3 Mask[6] = vec3[]( kRed, kRed, kGreen, kGreen, kBlue, kBlue ); + case 5: /* 5 pattern - 576TVL (16:9) horiz resolution on a 4K screen */ + { + const vec3 mask[5] = vec3[]( kRed, kYellow, kGreen, kCyan, kBlue ); - uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); - OutputColour = Luminance * HDRColour * Mask[PatternX]; + uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[5])); + scanline_colour *= mask[pattern_x]; break; } - case 6: /* 7 pattern - 410TVL (16:9) horiz resolution on a 4K screen */ + case 6: /* 6 pattern - 480TVL (16:9) horiz resolution on a 4K screen */ { - const uint PatternSize = 7; - const vec3 Mask[7] = vec3[]( kRed, kRed, kYellow, kGreen, kCyan, kBlue, kBlue ); + const vec3 mask[6] = vec3[]( kRed, kRed, kGreen, kGreen, kBlue, kBlue ); - uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); - OutputColour = Luminance * HDRColour * Mask[PatternX]; + uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[6])); + scanline_colour *= mask[pattern_x]; break; } - case 7: /* 9 pattern - 640TVL (16:9) horiz resolution on a *8K* screen */ + case 7: /* 7 pattern - 410TVL (16:9) horiz resolution on a 4K screen */ { - const uint PatternSize = 9; - const vec3 Mask[9] = vec3[]( kBlack, kRed, kRed, kBlack, kGreen, kGreen, kBlack, kBlue, kBlue ); + const vec3 mask[7] = vec3[]( kRed, kRed, kYellow, kGreen, kCyan, kBlue, kBlue ); - uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); - OutputColour = Luminance * HDRColour * Mask[PatternX]; + uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[7])); + scanline_colour *= mask[pattern_x]; break; } - case 8: /* 12 pattern - 480TVL (16:9) horiz resolution on a *8K* screen */ + case 8: /* 9 pattern - 640TVL (16:9) horiz resolution on a *8K* screen */ { - const uint PatternSize = 12; - const vec3 Mask[12] = vec3[]( kBlack, kRed, kRed, kRed, kBlack, kGreen, kGreen, kGreen, kBlack, kBlue, kBlue, kBlue ); + const vec3 mask[9] = vec3[]( kBlack, kRed, kRed, kBlack, kGreen, kGreen, kBlack, kBlue, kBlue ); - uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); - OutputColour = Luminance * HDRColour * Mask[PatternX]; + uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[8])); + scanline_colour *= mask[pattern_x]; + + break; + } + case 9: /* 12 pattern - 480TVL (16:9) horiz resolution on a *8K* screen */ + { + const vec3 mask[12] = vec3[]( kBlack, kRed, kRed, kRed, kBlack, kGreen, kGreen, kGreen, kBlack, kBlue, kBlue, kBlue ); + + uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[9])); + scanline_colour *= mask[pattern_x]; break; } default: { - OutputColour = vec3(0.0); + scanline_colour = vec3(0.0); break; } } - FragColor = vec4(OutputColour, 1.0); + const vec3 hdr10 = Hdr10(scanline_colour, params.PaperWhiteNits, params.ExpandGamut); + + //FragColor = vec4(scanline_colour, 1.0); + FragColor = vec4(hdr10, 1.0); } diff --git a/hdr/shaders/hdr10.h b/hdr/shaders/hdr10.h new file mode 100644 index 0000000..5c93162 --- /dev/null +++ b/hdr/shaders/hdr10.h @@ -0,0 +1,37 @@ + +#define kMaxNitsFor2084 10000.0f + +const mat3 k709to2020 = mat3 ( + 0.6274040f, 0.3292820f, 0.0433136f, + 0.0690970f, 0.9195400f, 0.0113612f, + 0.0163916f, 0.0880132f, 0.8955950f); + +/* START Converted from (Copyright (c) Microsoft Corporation - Licensed under the MIT License.) https://github.com/microsoft/Xbox-ATG-Samples/tree/master/Kits/ATGTK/HDR */ +const mat3 kExpanded709to2020 = mat3 ( + 0.6274040f, 0.3292820f, 0.0433136f, + 0.0457456, 0.941777, 0.0124772, + -0.00121055, 0.0176041, 0.983607); + +vec3 LinearToST2084(vec3 normalizedLinearValue) +{ + vec3 ST2084 = pow((0.8359375f + 18.8515625f * pow(abs(normalizedLinearValue), vec3(0.1593017578f))) / (1.0f + 18.6875f * pow(abs(normalizedLinearValue), vec3(0.1593017578f))), vec3(78.84375f)); + return ST2084; /* Don't clamp between [0..1], so we can still perform operations on scene values higher than 10,000 nits */ +} +/* END Converted from (Copyright (c) Microsoft Corporation - Licensed under the MIT License.) https://github.com/microsoft/Xbox-ATG-Samples/tree/master/Kits/ATGTK/HDR */ + +/* Convert into HDR10 */ +vec3 Hdr10(vec3 hdr_linear, float paper_white_nits, float expand_gamut) +{ + vec3 rec2020 = hdr_linear * k709to2020; + + if(expand_gamut > 0.0f) + { + rec2020 = hdr_linear * kExpanded709to2020; + } + + vec3 linearColour = rec2020 * (paper_white_nits / kMaxNitsFor2084); + vec3 hdr10 = LinearToST2084(linearColour); + + return hdr10; +} + diff --git a/hdr/shaders/hdr10.slang b/hdr/shaders/hdr10.slang index 05a0d94..0c7f4e7 100644 --- a/hdr/shaders/hdr10.slang +++ b/hdr/shaders/hdr10.slang @@ -10,6 +10,8 @@ Originally part of the crt\crt-sony-pvm-4k-hdr.slangp but can be used for any sh #pragma format A2B10G10R10_UNORM_PACK32 +#include "hdr10.h" + layout(push_constant) uniform Push { vec4 SourceSize; @@ -44,45 +46,9 @@ layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source; -#define kMaxNitsFor2084 10000.0f - -const mat3 k709to2020 = mat3 ( - 0.6274040f, 0.3292820f, 0.0433136f, - 0.0690970f, 0.9195400f, 0.0113612f, - 0.0163916f, 0.0880132f, 0.8955950f); - -/* START Converted from (Copyright (c) Microsoft Corporation - Licensed under the MIT License.) https://github.com/microsoft/Xbox-ATG-Samples/tree/master/Kits/ATGTK/HDR */ -const mat3 kExpanded709to2020 = mat3 ( - 0.6274040f, 0.3292820f, 0.0433136f, - 0.0457456, 0.941777, 0.0124772, - -0.00121055, 0.0176041, 0.983607); - -vec3 LinearToST2084(vec3 normalizedLinearValue) -{ - vec3 ST2084 = pow((0.8359375f + 18.8515625f * pow(abs(normalizedLinearValue), vec3(0.1593017578f))) / (1.0f + 18.6875f * pow(abs(normalizedLinearValue), vec3(0.1593017578f))), vec3(78.84375f)); - return ST2084; /* Don't clamp between [0..1], so we can still perform operations on scene values higher than 10,000 nits */ -} -/* END Converted from (Copyright (c) Microsoft Corporation - Licensed under the MIT License.) https://github.com/microsoft/Xbox-ATG-Samples/tree/master/Kits/ATGTK/HDR */ - -vec3 Hdr10(vec3 hdr) -{ - /* Now convert into HDR10 */ - vec3 rec2020 = hdr * k709to2020; - - if(params.ExpandGamut > 0.0f) - { - rec2020 = hdr * kExpanded709to2020; - } - - vec3 linearColour = rec2020 * (params.PaperWhiteNits / kMaxNitsFor2084); - vec3 hdr10 = LinearToST2084(linearColour); - - return hdr10; -} - void main() { - vec4 hdr = texture(Source, vTexCoord); - FragColor = vec4(Hdr10(hdr.rgb), hdr.a); + vec4 hdr_linear = texture(Source, vTexCoord); + FragColor = vec4(Hdr10(hdr_linear.rgb, params.PaperWhiteNits, params.ExpandGamut), hdr_linear.a); } diff --git a/hdr/shaders/inverse_tonemap.h b/hdr/shaders/inverse_tonemap.h new file mode 100644 index 0000000..3f7fe6b --- /dev/null +++ b/hdr/shaders/inverse_tonemap.h @@ -0,0 +1,30 @@ + +#define kMaxNitsFor2084 10000.0f +#define kEpsilon 0.0001f + +vec3 InverseTonemap(vec3 sdr_linear, float max_nits, float paper_white_nits, float luma_ratio) +{ + float luma = dot(sdr_linear, vec3(0.2126, 0.7152, 0.0722)); /* Rec BT.709 luma coefficients - https://en.wikipedia.org/wiki/Luma_(video) */ + + /* Inverse reinhard tonemap */ + float max_value = (max_nits / paper_white_nits) + kEpsilon; + float elbow = max_value / (max_value - 1.0f); + float offset = 1.0f - ((0.5f * elbow) / (elbow - 0.5f)); + + float hdr_luma_inv_tonemap = offset + ((luma * elbow) / (elbow - luma)); + float sdr_luma_inv_tonemap = luma / ((1.0f + kEpsilon) - luma); /* Convert the srd < 0.5 to 0.0 -> 1.0 range */ + + float luma_inv_tonemap = (luma > 0.5f) ? hdr_luma_inv_tonemap : sdr_luma_inv_tonemap; + vec3 per_luma = sdr_linear / (luma + kEpsilon) * luma_inv_tonemap; + + vec3 hdr_inv_tonemap = offset + ((sdr_linear * elbow) / (elbow - sdr_linear)); + vec3 sdr_inv_tonemap = sdr_linear / ((1.0f + kEpsilon) - sdr_linear); /* Convert the srd < 0.5 to 0.0 -> 1.0 range */ + + vec3 per_channel = vec3(sdr_linear.x > 0.5f ? hdr_inv_tonemap.x : sdr_inv_tonemap.x, + sdr_linear.y > 0.5f ? hdr_inv_tonemap.y : sdr_inv_tonemap.y, + sdr_linear.z > 0.5f ? hdr_inv_tonemap.z : sdr_inv_tonemap.z); + + vec3 hdr = mix(per_luma, per_channel, vec3(luma_ratio)); + + return hdr; +} diff --git a/hdr/shaders/inverse_tonemap.slang b/hdr/shaders/inverse_tonemap.slang index e75af84..1514006 100644 --- a/hdr/shaders/inverse_tonemap.slang +++ b/hdr/shaders/inverse_tonemap.slang @@ -10,6 +10,8 @@ Originally part of the crt\crt-sony-pvm-4k-hdr.slangp but can be used for any sh #pragma format R16G16B16A16_SFLOAT +#include "inverse_tonemap.h" + layout(push_constant) uniform Push { vec4 SourceSize; @@ -19,11 +21,15 @@ layout(push_constant) uniform Push float Contrast; float PaperWhiteNits; float MaxNits; + float Saturation; + float DisplayGamma; } params; -#pragma parameter Contrast "Contrast" 3.75 0.0 10.0 0.01 +#pragma parameter Contrast "Contrast" 3.75 0.0 10.0 0.05 #pragma parameter PaperWhiteNits "Paper White Luminance" 450.0 0.0 10000.0 10.0 #pragma parameter MaxNits "Peak Luminance" 700.0 0.0 10000.0 10.0 +#pragma parameter Saturation "Saturation" 0.25 0.0 1.0 0.01 +#pragma parameter DisplayGamma "Display Gamma" 2.2 0.0 5.0 0.1 layout(std140, set = 0, binding = 0) uniform UBO { @@ -46,42 +52,12 @@ layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source; -#define kMaxNitsFor2084 10000.0f -#define kEpsilon 0.0001f -#define kLumaChannelRatio 0.25f - -vec3 InverseTonemap(vec3 sdr) -{ - sdr = pow(abs(sdr), vec3(params.Contrast / 2.2f)); /* Display Gamma - needs to be determined by calibration screen */ - - float luma = dot(sdr, vec3(0.2126, 0.7152, 0.0722)); /* Rec BT.709 luma coefficients - https://en.wikipedia.org/wiki/Luma_(video) */ - - /* Inverse reinhard tonemap */ - float maxValue = (params.MaxNits / params.PaperWhiteNits) + kEpsilon; - float elbow = maxValue / (maxValue - 1.0f); - float offset = 1.0f - ((0.5f * elbow) / (elbow - 0.5f)); - - float hdrLumaInvTonemap = offset + ((luma * elbow) / (elbow - luma)); - float sdrLumaInvTonemap = luma / ((1.0f + kEpsilon) - luma); /* Convert the srd < 0.5 to 0.0 -> 1.0 range */ - - float lumaInvTonemap = (luma > 0.5f) ? hdrLumaInvTonemap : sdrLumaInvTonemap; - vec3 perLuma = sdr / (luma + kEpsilon) * lumaInvTonemap; - - vec3 hdrInvTonemap = offset + ((sdr * elbow) / (elbow - sdr)); - vec3 sdrInvTonemap = sdr / ((1.0f + kEpsilon) - sdr); /* Convert the srd < 0.5 to 0.0 -> 1.0 range */ - - vec3 perChannel = vec3(sdr.x > 0.5f ? hdrInvTonemap.x : sdrInvTonemap.x, - sdr.y > 0.5f ? hdrInvTonemap.y : sdrInvTonemap.y, - sdr.z > 0.5f ? hdrInvTonemap.z : sdrInvTonemap.z); - - vec3 hdr = mix(perLuma, perChannel, vec3(kLumaChannelRatio)); - - return hdr; -} - void main() { - vec4 sdr = texture(Source, vTexCoord); - FragColor = vec4(InverseTonemap(sdr.rgb), sdr.a); + vec4 source = texture(Source, vTexCoord); + + vec3 sdr = pow(abs(source.rgb), vec3(params.Contrast / params.DisplayGamma)); /* Display Gamma - needs to be determined by calibration screen */ + + FragColor = vec4(InverseTonemap(sdr, params.MaxNits, params.PaperWhiteNits, params.Saturation), source.a); }