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

This commit is contained in:
majorpainthecactus 2022-01-29 22:53:23 +00:00
parent 0d881b43bb
commit 0af8be9016
7 changed files with 282 additions and 211 deletions

View file

@ -1,6 +1,12 @@
#reference "crt-sony-pvm-4k-hdr.slangp" #reference "crt-sony-pvm-4k-hdr.slangp"
Contrast = "3.7500000" PaperWhiteNits = "650.000000"
PaperWhiteNits = "450.000000" CRTGamma = "1.8"
ScanlineWidth = "0.950000" ScanlineMin = "0.55000"
ResolutionPattern = "2.000000" ScanlineMax = "0.90000"
Sharpness = "2.400000" ResolutionPattern = "3.000000"
HorizontalSharpness = "1.500000"
HorizontalAttack = "0.600000"
VerticalAttack = "0.650000"
RedConvergence = "-1.800000"
GreenConvergence = "-0.600000"
BlueConvergence = "0.000000"

View file

@ -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. 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" feedback_pass = "0"
shader0 = "../stock.slang" shader0 = "shaders/crt-sony-pvm-4k-hdr.slang"
filter_linear0 = false filter_linear0 = "false"
wrap_mode0 = "clamp_to_border" wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false" mipmap_input0 = "false"
alias0 = "StockPass" alias0 = ""
float_framebuffer0 = "false" float_framebuffer0 = "false"
srgb_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"

View file

@ -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 #pragma format A2B10G10R10_UNORM_PACK32
#include "inverse_tonemap.h"
#include "hdr10.h"
layout(push_constant) uniform Push layout(push_constant) uniform Push
{ {
vec4 SourceSize; vec4 SourceSize;
vec4 OriginalSize; vec4 OriginalSize;
vec4 OutputSize; vec4 OutputSize;
uint FrameCount; uint FrameCount;
float ScanlineWidth; float PaperWhiteNits;
float MaxNits;
float ExpandGamut;
float CRTGamma;
float ScanlineMin;
float ScanlineMax;
float ResolutionPattern; float ResolutionPattern;
float Sharpness; float HorizontalSharpness;
float HorizontalAttack;
float VerticalAttack;
float RedConvergence;
float GreenConvergence;
float BlueConvergence;
} params; } params;
#pragma parameter ScanlineWidth "Scanline Width" 0.95 0.0 1.0 0.01 #pragma parameter PaperWhiteNits "Paper White Luminance" 650.0 0.0 10000.0 10.0
#pragma parameter ResolutionPattern "Resolution Pattern" 2.0 0.0 8.0 1.0 #pragma parameter MaxNits "Peak Luminance" 700.0 0.0 10000.0 10.0
#pragma parameter Sharpness "Sharpness" 2.4 0.0 5.0 0.1 #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 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 = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord; layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord; 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() void main()
{ {
gl_Position = global.MVP * Position; gl_Position = global.MVP * Position;
vTexCoord = TexCoord * vec2(1.00001); // To resolve rounding issues when sampling 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 #pragma stage fragment
layout(location = 0) in vec2 vTexCoord; 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(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source; layout(set = 0, binding = 2) uniform sampler2D Source;
layout(set = 0, binding = 3) uniform sampler2D StockPass;
float ModInteger(float a, float b) float ModInteger(float a, float b)
{ {
@ -64,12 +97,11 @@ float ModInteger(float a, float b)
return floor(m + 0.5); return floor(m + 0.5);
} }
#define kPi 3.1415926536 #define kPi 3.1415926536f
#define kEuler 2.718281828459 #define kEuler 2.718281828459f
#define kMax 1.0 #define kMax 1.0f
#define kGuassianMin vec3(1.1) #define kLumaRatio 0.5f
#define kGuassianMax vec3(3.0)
#define kRed vec3(1.0, 0.0, 0.0) #define kRed vec3(1.0, 0.0, 0.0)
#define kGreen vec3(0.0, 1.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 kBlack vec3(0.0, 0.0, 0.0)
#define kWhite vec3(1.0, 1.0, 1.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() 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); 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;
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 vec3 HDRColour = mix(HDRColour0, HDRColour1, vec3(HorizInterp)); const float source_tex_coord_x = (current_source_center_x /* * PatternSize[resolution_pattern]*/) / params.SourceSize.x;
const vec3 SDRColour = mix(SDRColour0, SDRColour1, vec3(HorizInterp));
vec3 Luminance = Ramp3(vec3(Gaussian), (SDRColour * kGuassianMax) + kGuassianMin);
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 /* 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 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. Pattern 1's 960TVL at 16:9 is about right for a 800TVL at 4:3.
*/ */
uint ResolutionPattern = uint(params.ResolutionPattern); switch(resolution_pattern)
switch(ResolutionPattern)
{ {
case 0: /* 2 pattern - 1440TVL (16:9) horiz resolution (too high?) on a 4K screen */ 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)); uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[0]));
OutputColour = Luminance * HDRColour * Mask[PatternX]; scanline_colour *= mask[pattern_x];
break; break;
} }
case 1: /* 3 pattern - 960TVL (16:9) horiz resolution on a 4K screen */ 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)); uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[1]));
OutputColour = Luminance * HDRColour * Mask[PatternX]; scanline_colour *= mask[pattern_x];
break; break;
} }
case 2: /* 4 pattern - 720TVL (16:9) horiz resolution on a 4K screen */ 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)); uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[2]));
OutputColour = Luminance * HDRColour * Mask[PatternX]; scanline_colour *= mask[pattern_x];
break; 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[4] = vec3[]( kRed, kGreen, kBlue, kBlack );
const vec3 Mask[5] = vec3[]( kRed, kMagenta, kBlue, kGreen, kGreen );
uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[3]));
OutputColour = Luminance * HDRColour * Mask[PatternX]; scanline_colour *= mask[pattern_x];
break; break;
} }
case 4: /* 5 pattern - 576TVL (16:9) horiz resolution on a 4K screen */ case 4: /* 5 BRG pattern - 576TVL (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[5] = vec3[]( kRed, kYellow, kGreen, kCyan, kBlue );
uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[4]));
OutputColour = Luminance * HDRColour * Mask[PatternX]; scanline_colour *= mask[pattern_x];
break; break;
} }
case 5: /* 6 pattern - 480TVL (16:9) horiz resolution on a 4K screen */ case 5: /* 5 pattern - 576TVL (16:9) horiz resolution on a 4K screen */
{ {
const uint PatternSize = 6; const vec3 mask[5] = vec3[]( kRed, kYellow, kGreen, kCyan, kBlue );
const vec3 Mask[6] = vec3[]( kRed, kRed, kGreen, kGreen, kBlue, kBlue );
uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[5]));
OutputColour = Luminance * HDRColour * Mask[PatternX]; scanline_colour *= mask[pattern_x];
break; 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[6] = vec3[]( kRed, kRed, kGreen, kGreen, kBlue, kBlue );
const vec3 Mask[7] = vec3[]( kRed, kRed, kYellow, kGreen, kCyan, kBlue, kBlue );
uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[6]));
OutputColour = Luminance * HDRColour * Mask[PatternX]; scanline_colour *= mask[pattern_x];
break; 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[7] = vec3[]( kRed, kRed, kYellow, kGreen, kCyan, kBlue, kBlue );
const vec3 Mask[9] = vec3[]( kBlack, kRed, kRed, kBlack, kGreen, kGreen, kBlack, kBlue, kBlue );
uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[7]));
OutputColour = Luminance * HDRColour * Mask[PatternX]; scanline_colour *= mask[pattern_x];
break; 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[9] = vec3[]( kBlack, kRed, kRed, kBlack, kGreen, kGreen, kBlack, kBlue, kBlue );
const vec3 Mask[12] = vec3[]( kBlack, kRed, kRed, kRed, kBlack, kGreen, kGreen, kGreen, kBlack, kBlue, kBlue, kBlue );
uint PatternX = uint(ModInteger(floor(InPixels.x), PatternSize)); uint pattern_x = uint(ModInteger(floor(current_position.x), PatternSize[8]));
OutputColour = Luminance * HDRColour * Mask[PatternX]; 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; break;
} }
default: default:
{ {
OutputColour = vec3(0.0); scanline_colour = vec3(0.0);
break; 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);
} }

37
hdr/shaders/hdr10.h Normal file
View file

@ -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;
}

View file

@ -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 #pragma format A2B10G10R10_UNORM_PACK32
#include "hdr10.h"
layout(push_constant) uniform Push layout(push_constant) uniform Push
{ {
vec4 SourceSize; vec4 SourceSize;
@ -44,45 +46,9 @@ layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor; layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source; 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() void main()
{ {
vec4 hdr = texture(Source, vTexCoord); vec4 hdr_linear = texture(Source, vTexCoord);
FragColor = vec4(Hdr10(hdr.rgb), hdr.a); FragColor = vec4(Hdr10(hdr_linear.rgb, params.PaperWhiteNits, params.ExpandGamut), hdr_linear.a);
} }

View file

@ -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;
}

View file

@ -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 #pragma format R16G16B16A16_SFLOAT
#include "inverse_tonemap.h"
layout(push_constant) uniform Push layout(push_constant) uniform Push
{ {
vec4 SourceSize; vec4 SourceSize;
@ -19,11 +21,15 @@ layout(push_constant) uniform Push
float Contrast; float Contrast;
float PaperWhiteNits; float PaperWhiteNits;
float MaxNits; float MaxNits;
float Saturation;
float DisplayGamma;
} params; } 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 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 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 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(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source; 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() void main()
{ {
vec4 sdr = texture(Source, vTexCoord); vec4 source = texture(Source, vTexCoord);
FragColor = vec4(InverseTonemap(sdr.rgb), sdr.a);
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);
} }