mirror of
https://github.com/libretro/slang-shaders.git
synced 2024-11-23 00:10:03 +00:00
Patchy NTSC - Bunch of last minute improvements (#629)
* Patchy NTSC - Fix hue offset on 2C02E * Patchy NTSC - Fully update patchy-color.slang Updated Patchy Color to have all of Patchy NTSC's features. * Add slangp file for Patchy Color Now, Patchy Color is officially usable, instead of being hidden and unfinished. * Patchy NTSC - Add NES real hardware video capture colors * Patchy NTSC - Fix bizzare incorrect smearing levels Seriously, why did I leave it like that? * Patchy NTSC - Don't apply Gen/MD palette on test patterns * Patchy NTSC - Add chromatic adaptation for some D93 LUTs * blah blah * Patchy NTSC - Set default LUT to Trinitron * Patchy NTSC - Update presets based on fixed smearing behavior Returned the hue rotations and saturations to defaults, and increased the threshold for smearing. The defaults don't show the smear out-of-the-box anymore; you will only see it if you turn up your contrast and/or saturation. * Patchy NTSC - Use video-captured NES color by default
This commit is contained in:
parent
4cf85d3147
commit
b9e012f25c
68
misc/patchy-color.slangp
Normal file
68
misc/patchy-color.slangp
Normal file
@ -0,0 +1,68 @@
|
||||
shaders = "3"
|
||||
feedback_pass = "0"
|
||||
shader0 = "../ntsc/shaders/patchy-ntsc/patchy-color.slang"
|
||||
filter_linear0 = "false"
|
||||
wrap_mode0 = "clamp_to_border"
|
||||
mipmap_input0 = "false"
|
||||
alias0 = ""
|
||||
float_framebuffer0 = "true"
|
||||
srgb_framebuffer0 = "false"
|
||||
scale_type_x0 = "source"
|
||||
scale_x0 = "1.000000"
|
||||
scale_type_y0 = "source"
|
||||
scale_y0 = "1.000000"
|
||||
shader1 = "../ntsc/shaders/patchy-ntsc/trilinearLUT-switchable.slang"
|
||||
filter_linear1 = "false"
|
||||
wrap_mode1 = "clamp_to_border"
|
||||
mipmap_input1 = "false"
|
||||
alias1 = ""
|
||||
float_framebuffer1 = "true"
|
||||
srgb_framebuffer1 = "false"
|
||||
scale_type_x1 = "source"
|
||||
scale_x1 = "1.000000"
|
||||
scale_type_y1 = "source"
|
||||
scale_y1 = "1.000000"
|
||||
shader2 = "../ntsc/shaders/patchy-ntsc/linear-to-srgb.slang"
|
||||
filter_linear2 = "false"
|
||||
wrap_mode2 = "clamp_to_border"
|
||||
mipmap_input2 = "false"
|
||||
alias2 = ""
|
||||
float_framebuffer2 = "true"
|
||||
srgb_framebuffer2 = "false"
|
||||
scale_type_x2 = "source"
|
||||
scale_x2 = "1.000000"
|
||||
scale_type_y2 = "source"
|
||||
scale_y2 = "1.000000"
|
||||
|
||||
textures = "PhosphorSamplerLUT1;PhosphorSamplerLUT2;PhosphorSamplerLUT3;PhosphorSamplerLUT4;PhosphorSamplerLUT5;PhosphorSamplerLUT6"
|
||||
|
||||
PhosphorSamplerLUT1 = "../ntsc/shaders/patchy-ntsc/P22_80s_D65.png"
|
||||
PhosphorSamplerLUT1_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT1_mipmap = "false"
|
||||
PhosphorSamplerLUT1_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT2 = "../ntsc/shaders/patchy-ntsc/P22_90s_D65.png"
|
||||
PhosphorSamplerLUT2_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT2_mipmap = "false"
|
||||
PhosphorSamplerLUT2_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT3 = "../ntsc/shaders/patchy-ntsc/P22_J_D65.png"
|
||||
PhosphorSamplerLUT3_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT3_mipmap = "false"
|
||||
PhosphorSamplerLUT3_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT4 = "../ntsc/shaders/patchy-ntsc/TrinitronP22_D65.png"
|
||||
PhosphorSamplerLUT4_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT4_mipmap = "false"
|
||||
PhosphorSamplerLUT4_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT5 = "../ntsc/shaders/patchy-ntsc/P22_J_D93.png"
|
||||
PhosphorSamplerLUT5_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT5_mipmap = "false"
|
||||
PhosphorSamplerLUT5_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT6 = "../ntsc/shaders/patchy-ntsc/TrinitronP22_D93.png"
|
||||
PhosphorSamplerLUT6_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT6_mipmap = "false"
|
||||
PhosphorSamplerLUT6_linear = "false"
|
||||
|
@ -85,14 +85,14 @@ scale_x6 = "1.000000"
|
||||
scale_type_y6 = "source"
|
||||
scale_y6 = "1.000000"
|
||||
pn_knob_contrast = "0.725000"
|
||||
pn_knob_saturation = "1.150000"
|
||||
pn_knob_tint = "-5.000000"
|
||||
pn_rgb_smear_enable = "1.000000"
|
||||
pn_width_uncropped = "256.000000"
|
||||
pn_height_uncropped = "240.000000"
|
||||
pn_nes_enable = "1.000000"
|
||||
pn_nes_real_capture = "1.0"
|
||||
pn_rgb_smear_rate = "1.15"
|
||||
|
||||
textures = "PhosphorSamplerLUT1;PhosphorSamplerLUT2;PhosphorSamplerLUT3;PhosphorSamplerLUT4"
|
||||
textures = "PhosphorSamplerLUT1;PhosphorSamplerLUT2;PhosphorSamplerLUT3;PhosphorSamplerLUT4;PhosphorSamplerLUT5;PhosphorSamplerLUT6"
|
||||
|
||||
PhosphorSamplerLUT1 = "../ntsc/shaders/patchy-ntsc/P22_80s_D65.png"
|
||||
PhosphorSamplerLUT1_wrap_mode = "clamp_to_border"
|
||||
@ -114,3 +114,13 @@ PhosphorSamplerLUT4_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT4_mipmap = "false"
|
||||
PhosphorSamplerLUT4_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT5 = "../ntsc/shaders/patchy-ntsc/P22_J_D93.png"
|
||||
PhosphorSamplerLUT5_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT5_mipmap = "false"
|
||||
PhosphorSamplerLUT5_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT6 = "../ntsc/shaders/patchy-ntsc/TrinitronP22_D93.png"
|
||||
PhosphorSamplerLUT6_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT6_mipmap = "false"
|
||||
PhosphorSamplerLUT6_linear = "false"
|
||||
|
||||
|
@ -84,8 +84,6 @@ scale_type_x6 = "source"
|
||||
scale_x6 = "1.000000"
|
||||
scale_type_y6 = "source"
|
||||
scale_y6 = "1.000000"
|
||||
pn_knob_saturation = "1.150000"
|
||||
pn_knob_tint = "-5.000000"
|
||||
pn_rgb_smear_enable = "1.000000"
|
||||
pn_genesis_jailbar_enable = "1.000000"
|
||||
pn_genesis_jailbar_offset = "0.312500"
|
||||
@ -93,25 +91,37 @@ pn_scanline_dur = "47.699997"
|
||||
pn_color_init_offset = "0.700000"
|
||||
pn_modulator_luma_filter_type = "2.000000"
|
||||
pn_modulator_luma_res = "220.000000"
|
||||
textures = "PhosphorSamplerLUT1;PhosphorSamplerLUT2;PhosphorSamplerLUT3;PhosphorSamplerLUT4"
|
||||
pn_rgb_smear_rate = "1.15"
|
||||
|
||||
PhosphorSamplerLUT1 = "shaders/patchy-ntsc/P22_80s_D65.png"
|
||||
textures = "PhosphorSamplerLUT1;PhosphorSamplerLUT2;PhosphorSamplerLUT3;PhosphorSamplerLUT4;PhosphorSamplerLUT5;PhosphorSamplerLUT6"
|
||||
|
||||
PhosphorSamplerLUT1 = "../ntsc/shaders/patchy-ntsc/P22_80s_D65.png"
|
||||
PhosphorSamplerLUT1_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT1_mipmap = "false"
|
||||
PhosphorSamplerLUT1_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT2 = "shaders/patchy-ntsc/P22_90s_D65.png"
|
||||
PhosphorSamplerLUT2 = "../ntsc/shaders/patchy-ntsc/P22_90s_D65.png"
|
||||
PhosphorSamplerLUT2_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT2_mipmap = "false"
|
||||
PhosphorSamplerLUT2_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT3 = "shaders/patchy-ntsc/P22_J_D65.png"
|
||||
PhosphorSamplerLUT3 = "../ntsc/shaders/patchy-ntsc/P22_J_D65.png"
|
||||
PhosphorSamplerLUT3_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT3_mipmap = "false"
|
||||
PhosphorSamplerLUT3_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT4 = "shaders/patchy-ntsc/TrinitronP22_D65.png"
|
||||
PhosphorSamplerLUT4 = "../ntsc/shaders/patchy-ntsc/TrinitronP22_D65.png"
|
||||
PhosphorSamplerLUT4_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT4_mipmap = "false"
|
||||
PhosphorSamplerLUT4_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT5 = "../ntsc/shaders/patchy-ntsc/P22_J_D93.png"
|
||||
PhosphorSamplerLUT5_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT5_mipmap = "false"
|
||||
PhosphorSamplerLUT5_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT6 = "../ntsc/shaders/patchy-ntsc/TrinitronP22_D93.png"
|
||||
PhosphorSamplerLUT6_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT6_mipmap = "false"
|
||||
PhosphorSamplerLUT6_linear = "false"
|
||||
|
||||
|
@ -84,8 +84,6 @@ scale_type_x6 = "source"
|
||||
scale_x6 = "1.000000"
|
||||
scale_type_y6 = "source"
|
||||
scale_y6 = "1.000000"
|
||||
pn_knob_saturation = "1.150000"
|
||||
pn_knob_tint = "-5.000000"
|
||||
pn_rgb_smear_enable = "1.000000"
|
||||
pn_genesis_palette = "1.0"
|
||||
pn_genesis_jailbar_enable = "1.000000"
|
||||
@ -94,25 +92,37 @@ pn_scanline_dur = "47.699997"
|
||||
pn_color_init_offset = "0.700000"
|
||||
pn_modulator_luma_filter_type = "2.000000"
|
||||
pn_modulator_luma_res = "220.000000"
|
||||
textures = "PhosphorSamplerLUT1;PhosphorSamplerLUT2;PhosphorSamplerLUT3;PhosphorSamplerLUT4"
|
||||
pn_rgb_smear_rate = "1.15"
|
||||
|
||||
PhosphorSamplerLUT1 = "shaders/patchy-ntsc/P22_80s_D65.png"
|
||||
textures = "PhosphorSamplerLUT1;PhosphorSamplerLUT2;PhosphorSamplerLUT3;PhosphorSamplerLUT4;PhosphorSamplerLUT5;PhosphorSamplerLUT6"
|
||||
|
||||
PhosphorSamplerLUT1 = "../ntsc/shaders/patchy-ntsc/P22_80s_D65.png"
|
||||
PhosphorSamplerLUT1_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT1_mipmap = "false"
|
||||
PhosphorSamplerLUT1_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT2 = "shaders/patchy-ntsc/P22_90s_D65.png"
|
||||
PhosphorSamplerLUT2 = "../ntsc/shaders/patchy-ntsc/P22_90s_D65.png"
|
||||
PhosphorSamplerLUT2_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT2_mipmap = "false"
|
||||
PhosphorSamplerLUT2_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT3 = "shaders/patchy-ntsc/P22_J_D65.png"
|
||||
PhosphorSamplerLUT3 = "../ntsc/shaders/patchy-ntsc/P22_J_D65.png"
|
||||
PhosphorSamplerLUT3_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT3_mipmap = "false"
|
||||
PhosphorSamplerLUT3_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT4 = "shaders/patchy-ntsc/TrinitronP22_D65.png"
|
||||
PhosphorSamplerLUT4 = "../ntsc/shaders/patchy-ntsc/TrinitronP22_D65.png"
|
||||
PhosphorSamplerLUT4_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT4_mipmap = "false"
|
||||
PhosphorSamplerLUT4_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT5 = "../ntsc/shaders/patchy-ntsc/P22_J_D93.png"
|
||||
PhosphorSamplerLUT5_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT5_mipmap = "false"
|
||||
PhosphorSamplerLUT5_linear = "false"
|
||||
|
||||
PhosphorSamplerLUT6 = "../ntsc/shaders/patchy-ntsc/TrinitronP22_D93.png"
|
||||
PhosphorSamplerLUT6_wrap_mode = "clamp_to_border"
|
||||
PhosphorSamplerLUT6_mipmap = "false"
|
||||
PhosphorSamplerLUT6_linear = "false"
|
||||
|
||||
|
BIN
ntsc/shaders/patchy-ntsc/P22_J_D93.png
Normal file
BIN
ntsc/shaders/patchy-ntsc/P22_J_D93.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
BIN
ntsc/shaders/patchy-ntsc/TrinitronP22_D93.png
Normal file
BIN
ntsc/shaders/patchy-ntsc/TrinitronP22_D93.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
@ -53,7 +53,9 @@ layout(std140, set = 0, binding = 0) uniform UBO
|
||||
float pc_color;
|
||||
float pc_tint;
|
||||
float pc_unused6;
|
||||
float pc_gamma;
|
||||
float pc_gamma_type;
|
||||
float pc_g_CRT_l;
|
||||
float pc_power_gamma;
|
||||
} global;
|
||||
|
||||
#pragma parameter pc_unused1 "===== Patchy Color Settings =====" 0.0 0.0 0 1
|
||||
@ -65,10 +67,10 @@ layout(std140, set = 0, binding = 0) uniform UBO
|
||||
|
||||
#pragma parameter pc_unused3 "== Console settings (Does work with test patterns) ==" 0 0 0 1
|
||||
#pragma parameter pc_console "Console: Other | Gen/MD | NES/FC (raw palette)" 0 0 2 1
|
||||
#pragma parameter pc_nes_lut "NES: Use real capture (2C02G) | Use formulas" 0 0 1 1
|
||||
#pragma parameter pc_nes_lut "NES: Use real capture (probably 2C02G) | Use formulas" 0 0 1 1
|
||||
#pragma parameter pc_nes_version "NES PPU formula: 2C02G (NTSC) | 2C02E (NTSC) | PAL" 0 0 2 1
|
||||
//#pragma parameter pc_nes_alt_voltages "(Reccomend 0 (off)) NES: Use outdated voltages" 0 0 1 1
|
||||
#pragma parameter pc_genesis_needfix "MD emu: BlastEm (accurate) | GenPlusGX (Needs correcting)" 0 0 1 1
|
||||
#pragma parameter pc_genesis_needfix "MD emu: BlastEm (Leave as-is) | GenPlusGX (Apply correction)" 0 0 1 1
|
||||
|
||||
#pragma parameter pc_unused4 "=== Composite video demodulation controls ===" 0 0 0 1
|
||||
#pragma parameter pc_enableComposite "Enable composite demodulator simulation" 1.0 0.0 1.0 1.0
|
||||
@ -85,7 +87,9 @@ layout(std140, set = 0, binding = 0) uniform UBO
|
||||
#pragma parameter pc_tint "Tint (Hue Rotation) (Rec. exactly 0.0)" 0 -45 45 0.5
|
||||
|
||||
#pragma parameter pc_unused6 "=== Gamma and phosphors ===" 0 0 0 1
|
||||
#pragma parameter pc_gamma "Gamma" 2.5 2.3 2.6 0.01
|
||||
#pragma parameter pc_gamma_type "Gamma: BT.1886 + Grade black lift fix | BT.1886 | Power | sRGB" 1 0 3 1
|
||||
#pragma parameter pc_g_CRT_l "Black lift fix approximate gamma" 2.50 2.30 2.60 0.01
|
||||
#pragma parameter pc_power_gamma "Power gamma" 2.4 2.2 3.0 0.01
|
||||
|
||||
#define pi 3.14159265358
|
||||
|
||||
@ -233,22 +237,40 @@ vec3 createNesColorYuv(vec3 c) {
|
||||
float skew;
|
||||
if(global.pc_nes_version < 0.5) {
|
||||
// 2C02G
|
||||
skew = level * -5.0 / 180.0 * pi; // -5 degrees
|
||||
skew = (3 - level) * -5.0 / 180.0 * pi; // -5 degrees
|
||||
} else if(global.pc_nes_version < 1.5) {
|
||||
// 2C02E
|
||||
skew = level * -2.5 / 180.0 * pi; // -2.5 degrees
|
||||
skew = (3 - level) * -2.5 / 180.0 * pi; // -2.5 degrees
|
||||
} else {
|
||||
// In PAL, the alternating phase causes this color error to be cancelled out.
|
||||
skew = 0.0;
|
||||
}
|
||||
|
||||
// Also from lidnariq's post:
|
||||
float burstLow = (0.148 - black) / (white - black);
|
||||
float burstHigh = (0.524 - black) / (white - black);
|
||||
float burstMid = (burstLow + burstHigh) / 2;
|
||||
vec2 burstUV = vec2(0);
|
||||
|
||||
for(int i = 0; i < 12; i++) {
|
||||
|
||||
resultYiq.y += (signal[i] - resultYiq.x) * sin((i + 3) * pi / 6 - skew);
|
||||
resultYiq.z += (signal[i] - resultYiq.x) * cos((i + 3) * pi / 6 - skew);
|
||||
resultYiq.y += (signal[i] - resultYiq.x) * sin((i + 3) * pi / 6 + skew);
|
||||
resultYiq.z += (signal[i] - resultYiq.x) * cos((i + 3) * pi / 6 + skew);
|
||||
if(i >= 5 && i <= 10) {
|
||||
burstUV.x += (burstHigh - burstMid) * sin(i * pi / 6);
|
||||
burstUV.y += (burstHigh - burstMid) * cos(i * pi / 6);
|
||||
} else {
|
||||
burstUV.x += (burstLow - burstMid) * sin(i * pi / 6);
|
||||
burstUV.y += (burstLow - burstMid) * cos(i * pi / 6);
|
||||
}
|
||||
}
|
||||
|
||||
resultYiq.yz *= 1.0 / 12.0;
|
||||
burstUV *= 1.0 / 12.0;
|
||||
|
||||
// 1.1 comes from the NES's white (color 0x20 or 0x30) being at roughly 110 IRE.
|
||||
// 0.2 comes from the standard NTSC colorburst amplitude.
|
||||
// The result is still quite different from my real hardware capture.
|
||||
resultYiq.yz *= (0.2 / 1.1) / sqrt(pow(burstUV.x, 2) + pow(burstUV.y, 2));
|
||||
|
||||
return resultYiq;
|
||||
}
|
||||
@ -573,31 +595,36 @@ vec3 lookupNesColor(vec3 nesFormat) {
|
||||
}
|
||||
|
||||
vec3 retreiveNesColorYuv(vec3 nesFormat) {
|
||||
mat3 yuvMat;
|
||||
//if(global.pc_nes_lut_newyuv < 0.5) {
|
||||
// // https://en.wikipedia.org/wiki/Y%E2%80%B2UV as of 27 Jul 2024
|
||||
// // Standard matrix for BT.470
|
||||
// yuvMat = mat3x3(
|
||||
// 0.299, -0.14713, 0.615,
|
||||
// 0.587, -0.28886, -0.51499,
|
||||
// 0.114, 0.436, -0.10001
|
||||
// );
|
||||
//} else {
|
||||
// https://en.wikipedia.org/wiki/Y%E2%80%B2UV as of 27 Jul 2024
|
||||
// Standard matrix for BT.709
|
||||
yuvMat = mat3(0.2126, -0.09991, 0.615,
|
||||
0.7152, -0.33609, -0.55861,
|
||||
0.0722, 0.436, -0.05639);
|
||||
//}
|
||||
vec3 colYuv;
|
||||
if(global.pc_nes_lut < 0.5) {
|
||||
// Lookup table (a.k.a. color palette) is enabled.
|
||||
mat3 yuvMat;
|
||||
//if(global.pc_nes_lut_newyuv < 0.5) {
|
||||
// // https://en.wikipedia.org/wiki/Y%E2%80%B2UV as of 27 Jul 2024
|
||||
// // Standard matrix for BT.470
|
||||
// yuvMat = mat3x3(
|
||||
// 0.299, -0.14713, 0.615,
|
||||
// 0.587, -0.28886, -0.51499,
|
||||
// 0.114, 0.436, -0.10001
|
||||
// );
|
||||
//} else {
|
||||
// https://en.wikipedia.org/wiki/Y%E2%80%B2UV as of 27 Jul 2024
|
||||
// Standard matrix for BT.709
|
||||
yuvMat = mat3(0.2126, -0.09991, 0.615,
|
||||
0.7152, -0.33609, -0.55861,
|
||||
0.0722, 0.436, -0.05639);
|
||||
//}
|
||||
return yuvMat * lookupNesColor(nesFormat);
|
||||
colYuv = yuvMat * lookupNesColor(nesFormat);
|
||||
} else {
|
||||
return mat3x3(
|
||||
1, 0, 0, // Compared to my real capture, the computed colors are off by half an NES PPU cycle.
|
||||
0, cos(-15.0 * pi / 180.0), -sin(-15.0 * pi / 180.0),
|
||||
0, sin(-15.0 * pi / 180.0), cos(-15.0 * pi / 180.0)
|
||||
) * createNesColorYuv(nesFormat);
|
||||
colYuv = createNesColorYuv(nesFormat);
|
||||
}
|
||||
if(global.pc_enableComposite < 0.5) {
|
||||
return mat3( // BT.470 / Rec. 601 YUV to RGB matrix
|
||||
1.0000000, 1.0, 1.0,
|
||||
-0.000000029378826483, -0.396552562713623050, 2.031872510910034000,
|
||||
1.1383928060531616, -0.5800843834877014, 0.0000000000000000) * colYuv;
|
||||
} else {
|
||||
return colYuv;
|
||||
}
|
||||
}
|
||||
|
||||
@ -866,11 +893,36 @@ float nesMaxRgb() {
|
||||
return max(maxRgb, 1.0); // 1.0 is the maximum pure white on the NES.
|
||||
}
|
||||
|
||||
float EOTF_1886a_default(float color, float brightness, float contrast) {
|
||||
// From Rec. ITU-R BT.1886
|
||||
float Vc = 0.35, // Vc, a1, and a2 are directly from the paper
|
||||
a1 = 2.6,
|
||||
a2 = 3.0,
|
||||
Lw = contrast;
|
||||
|
||||
float V = color,
|
||||
b = brightness, // The paper recommends about either 0 or 0.1.
|
||||
k = Lw / pow(1.0 + b, a1);
|
||||
|
||||
if(V < Vc) {
|
||||
return k * pow(Vc + b, a1 - a2) * pow(V + b, a2);
|
||||
} else {
|
||||
return k * pow(V + b, a1);
|
||||
}
|
||||
}
|
||||
|
||||
vec3 EOTF_1886a_default_f3(vec3 color, float brightness, float contrast) {
|
||||
color.r = EOTF_1886a_default(color.r, brightness, contrast);
|
||||
color.g = EOTF_1886a_default(color.g, brightness, contrast);
|
||||
color.b = EOTF_1886a_default(color.b, brightness, contrast);
|
||||
return color;
|
||||
}
|
||||
|
||||
// CRT EOTF Function
|
||||
// Taken from Grade
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
#define CRT_l -(100000.*log((72981.-500000./(3.*max(2.3,global.pc_gamma)))/9058.))/945461.
|
||||
#define CRT_l -(100000.*log((72981.-500000./(3.*max(2.3,global.pc_g_CRT_l)))/9058.))/945461.
|
||||
|
||||
float EOTF_1886a(float color, float bl, float brightness, float contrast) {
|
||||
|
||||
@ -1020,6 +1072,9 @@ vec3 testPattern(vec2 pos) {
|
||||
}
|
||||
}
|
||||
|
||||
// For sRGB_to_linear
|
||||
#include "../../../include/colorspace-tools.h"
|
||||
|
||||
#pragma stage vertex
|
||||
layout(location = 0) in vec4 Position;
|
||||
layout(location = 1) in vec2 TexCoord;
|
||||
@ -1048,7 +1103,7 @@ void main()
|
||||
|
||||
if(global.pc_console > 0.5) {
|
||||
if(global.pc_console < 1.5) { // Sega Genesis / MegaDrive
|
||||
if(global.pc_genesis_needfix > 0.5) // Genesis gamma correction--BlastEm already performs this fix for you
|
||||
if(global.pc_genesis_needfix > 0.5 && global.pc_testpattern < 0.5) // Genesis gamma correction--BlastEm already performs this fix for you
|
||||
changedColor = fixGenColor(changedColor);
|
||||
}
|
||||
else if(global.pc_console < 2.5) { // NES PPU
|
||||
@ -1066,7 +1121,7 @@ void main()
|
||||
if(global.pc_console < 1.5)
|
||||
changedColor = toYuv * changedColor;
|
||||
|
||||
float tint = global.pc_tint * pi / 180.0;
|
||||
float tint = -global.pc_tint * pi / 180.0;
|
||||
changedColor.gb *= mat2(
|
||||
cos(tint), -sin(tint),
|
||||
sin(tint), cos(tint)
|
||||
@ -1074,18 +1129,20 @@ void main()
|
||||
changedColor.gb *= global.pc_color;
|
||||
|
||||
changedColor = toRgb * changedColor;
|
||||
// changedColor *= global.pc_contrast;
|
||||
// changedColor.r += global.pc_brightness / (1 + global.pc_brightness) * global.pc_contrast;
|
||||
} else {
|
||||
// When not simulating Composite video, simulate RGB instead.
|
||||
// Only the Contrast and Brightness knobs work in RGB.
|
||||
// changedColor = (changedColor + global.pc_brightness) / (1 + global.pc_brightness) * global.pc_contrast;
|
||||
// changedColor *= global.pc_contrast;
|
||||
// The setting for 1.0 contrast's behavior doesn't matter in RGB because the white ramp and RGB space are of equal size.
|
||||
}
|
||||
|
||||
changedColor = EOTF_1886a_f3(changedColor, CRT_l, global.pc_brightness, global.pc_contrast);
|
||||
if(global.pc_gamma_type < 0.5) {
|
||||
changedColor = EOTF_1886a_f3(changedColor, CRT_l, global.pc_brightness, global.pc_contrast);
|
||||
} else if(global.pc_gamma_type < 1.5) {
|
||||
changedColor = EOTF_1886a_default_f3(changedColor, global.pc_brightness, global.pc_contrast);
|
||||
} else if(global.pc_gamma_type < 2.5) {
|
||||
changedColor = pow((changedColor + global.pc_brightness) / (1 + global.pc_brightness), vec3(global.pc_power_gamma)) * global.pc_contrast;
|
||||
} else {
|
||||
float contr = global.pc_contrast <= 1.001 ? linear_to_sRGB(vec3(global.pc_contrast), 2.4).r : 1.055 * pow(global.pc_contrast, 1.0 / 2.4) - 0.055; // 2.4 instead of 2.2 is not a mistake.
|
||||
changedColor = (changedColor + global.pc_brightness) / (1 + global.pc_brightness) * contr;
|
||||
changedColor = sRGB_to_linear(changedColor, 2.4); // Beware that this function clamps between 0 and 1.
|
||||
}
|
||||
|
||||
FragColor = vec4(changedColor, originalColor.a);
|
||||
FragColor = vec4(clamp(changedColor, 0, 1), originalColor.a);
|
||||
}
|
||||
|
||||
|
@ -681,6 +681,370 @@ vec2 uvDemodPickable(float type, float chroma, float phase, float tint) {
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// NES real hardware video capture lookup table //
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
|
||||
// The current video capture is of low quality. I am going to redo it soon.
|
||||
// The problem is that it is captured at only 25% saturation (and 25% contrast), which is extremely low.
|
||||
// The video capture can be done with a much higher saturation. The goal is to undersaturate to avoid clamping over/under 0.0 or 1.0.
|
||||
|
||||
vec3 nesRealCaptureLookup(vec3 yBmyRmy, float saturation) {
|
||||
mat3x2 yuvAxisPts = yuvAxisPointsSynced();
|
||||
|
||||
float xr, yr, xg, yg, xb, yb;
|
||||
xr = yuvAxisPts[0][1] * cos(yuvAxisPts[0][0]);
|
||||
yr = yuvAxisPts[0][1] * sin(yuvAxisPts[0][0]);
|
||||
xg = yuvAxisPts[1][1] * cos(yuvAxisPts[1][0]);
|
||||
yg = yuvAxisPts[1][1] * sin(yuvAxisPts[1][0]);
|
||||
xb = yuvAxisPts[2][1] * cos(yuvAxisPts[2][0]);
|
||||
yb = yuvAxisPts[2][1] * sin(yuvAxisPts[2][0]);
|
||||
|
||||
// YUV to Y B-Y R-Y matrix
|
||||
mat3 toYBmyRmy = mat3(1, 0, 0,
|
||||
0, xb, xr,
|
||||
0, yb, yr);
|
||||
|
||||
// YUV to RGB matrix
|
||||
mat3 toRgb = mat3(1, 1, 1,
|
||||
xr, xg, xb,
|
||||
yr, yg, yb);
|
||||
|
||||
vec3 YIQ = mat3(1, 0, 0, // 30 instead of 33 is not a mistake. The hue offsets below were based on GTU-famicom's output, which has its YIQ off by exactly 3 degrees.
|
||||
0, -sin(30.0 * pi / 180.0), cos(30.0 * pi / 180.0),
|
||||
0, cos(30.0 * pi / 180.0), sin(30.0 * pi / 180.0)) *
|
||||
inverse(toYBmyRmy) * yBmyRmy;
|
||||
|
||||
// Grays. I only approximated the three grays in the 00-column and put pure black for 1d. I'll probably get the remaining ones from the 0d column sometime later.
|
||||
float unsaturatedLevels[4] = {0.0, (0.616 - 0.312) / (1.100 - 0.312), (0.840 - 0.312) / (1.100 - 0.312), 1.0}; // Calculated from DAC.slang
|
||||
// The two equivalent whites 20 and 30 are only one white in this.
|
||||
float whiteRed[] = {0, 0.26171875, 0.41796875, 0.62109375, };
|
||||
float whiteGreen[] = {0, 0.19921875, 0.3984375, 0.5, };
|
||||
float whiteBlue[] = {0, 0.3203125, 0.5390625, 0.7421875, };
|
||||
|
||||
// Colors that aren't gray, i.e. x1 thru xC for x = 0, 1, 2, 3
|
||||
float levelSats[4] = {sqrt((16 + 9) / 255.0), sqrt((3 * 16) / 255.0), sqrt((3 * 16 + 2) / 255.0), sqrt(9.0 / 255.0)}; // Distance of cartesian point (I, Q) from origin (0, 0). Screenshotted from shader test output.
|
||||
float saturatedLevels[4] = {((0.228 + 0.616) / 2. - 0.312) / (1.100 - 0.312), ((0.312 + 0.840) / 2. - 0.312) / (1.100 - 0.312), ((0.552 + 1.100) / 2. - 0.312) / (1.100 - 0.312), ((0.880 + 1.100) / 2. - 0.312) / (1.100 - 0.312)}; // Calculated from DAC.slang
|
||||
float levelHues[12] = {0.2890625, 0.3672419921875, 0.4453125, 0.53125, 0.609375, 0.69140625, 0.76953125, 0.890625, 0.96875, 0.0546875, 0.1328125, 0.20703125}; // Screenshotted from shader test output
|
||||
float level0Red[] = {0.06640625, 0.11328125, 0.16015625, 0.30078125, 0.3203125, 0.33203125, 0.26171875, 0.12890625, 0.0, 0.0, 0.0, 0.05078125, };
|
||||
float level0Green[] = {0.0546875, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.046875, 0.08203125, 0.109375, 0.08984375, 0.04296875, };
|
||||
float level0Blue[] = {0.45703125, 0.5, 0.44140625, 0.328125, 0.0703125, 0.0, 0.0, 0.0, 0.0, 0.0, 0.12109375, 0.328125, };
|
||||
float level1Red[] = {0.1640625, 0.16796875, 0.3359375, 0.4453125, 0.5078125, 0.5, 0.4609375, 0.37109375, 0.125, 0.0, 0.0, 0.08203125, };
|
||||
float level1Green[] = {0.09375, 0.08203125, 0.0, 0.0, 0.08203125, 0.08984375, 0.14453125, 0.2265625, 0.265625, 0.2890625, 0.2734375, 0.27734375, };
|
||||
float level1Blue[] = {0.69921875, 0.76953125, 0.7265625, 0.5859375, 0.328125, 0.0, 0.0, 0.0, 0.0, 0.0, 0.22265625, 0.51171875, };
|
||||
float level2Red[] = {0.1328125, 0.265625, 0.3984375, 0.5234375, 0.56640625, 0.625, 0.55859375, 0.52734375, 0.359375, 0.21875, 0.0, 0.125, };
|
||||
float level2Green[] = {0.41015625, 0.26953125, 0.1953125, 0.203125, 0.19921875, 0.28125, 0.328125, 0.3828125, 0.37890625, 0.44140625, 0.4296875, 0.47265625, };
|
||||
float level2Blue[] = {0.80859375, 0.73046875, 0.6953125, 0.70703125, 0.58203125, 0.3359375, 0.07421875, 0.0, 0.0, 0.1484375, 0.3984375, 0.63671875, };
|
||||
float level3Red[] = {0.46875, 0.50390625, 0.5390625, 0.59765625, 0.6328125, 0.57421875, 0.609375, 0.58203125, 0.51953125, 0.4375, 0.41796875, 0.390625, };
|
||||
float level3Green[] = {0.51953125, 0.48046875, 0.48046875, 0.4765625, 0.4765625, 0.4375, 0.49609375, 0.48828125, 0.48828125, 0.4765625, 0.4765625, 0.4765625, };
|
||||
float level3Blue[] = {0.796875, 0.78125, 0.76953125, 0.8046875, 0.7265625, 0.56640625, 0.5390625, 0.4765625, 0.48046875, 0.52734375, 0.56640625, 0.6015625, };
|
||||
|
||||
// The saturation amounts were screenshotted from GTU-famicom, which does not properly bandpass the chroma signal.
|
||||
// That results in GTU-famicom's chroma being oversaturated, due to the NES's chroma signal being a square wave instead of a sinusoid.
|
||||
// Those incorrect saturation amounts can be fixed by just calculating them properly.
|
||||
float lowLevels[4] = {(0.228 - 0.312) / (1.100 - 0.312), 0.0, (0.522 - 0.312) / (1.1 - 0.312), (0.880 - 0.312) / (1.1 - 0.312)};
|
||||
float highLevels[4] = {(0.616 - 0.312) / (1.1 - 0.312), (0.84 - 0.312) / (1.1 - 0.312), 1.0, 1.0};
|
||||
for(int i = 0; i < 4; i++) {
|
||||
float amplitude = (highLevels[i] - lowLevels[i]) / 2;
|
||||
levelSats[i] = 0.0;
|
||||
for(int j = 0; j < 12; j++) {
|
||||
levelSats[i] += 2 * amplitude * pow(sin(j / 12.0 * pi / 2.0), 2.0) / 12.0; // Use a sine wave instead of a square wave, to roughly simulate bandpassing. Result is a slight underestimate.
|
||||
}
|
||||
}
|
||||
|
||||
// Screw all that. Replace that entire eyeballed palette with a direct video capture of the NES's composite video.
|
||||
// I originally wrote this code months ago, so pardon the mess. I've carefully looked through this
|
||||
// horrible mess of code and made sure it works. The only problem is that emphasis bits aren't implemented.
|
||||
// I do not know how I would implement emphasis bits into this lookup, but the result is already close enough for me.
|
||||
|
||||
float pal[192] = {
|
||||
0.478321, 0.478585, 0.478334,
|
||||
0.366470, 0.403287, 0.500952,
|
||||
0.400869, 0.390277, 0.509457,
|
||||
0.429738, 0.383858, 0.496003,
|
||||
0.461984, 0.383553, 0.462478,
|
||||
0.464060, 0.381734, 0.399996,
|
||||
0.456407, 0.392188, 0.335543,
|
||||
0.429468, 0.403545, 0.299086,
|
||||
0.404554, 0.416319, 0.288624,
|
||||
0.360529, 0.423274, 0.301706,
|
||||
0.335662, 0.426387, 0.352844,
|
||||
0.330750, 0.423530, 0.401436,
|
||||
0.341014, 0.418906, 0.454200,
|
||||
0.364416, 0.364810, 0.364445,
|
||||
0.368611, 0.369176, 0.368467,
|
||||
0.368611, 0.369176, 0.368467,
|
||||
0.558871, 0.559355, 0.558862,
|
||||
0.397998, 0.459932, 0.570550,
|
||||
0.445642, 0.440000, 0.594072,
|
||||
0.486899, 0.428728, 0.585057,
|
||||
0.527970, 0.429614, 0.550925,
|
||||
0.537326, 0.425318, 0.467786,
|
||||
0.530366, 0.436656, 0.377826,
|
||||
0.502618, 0.451730, 0.328883,
|
||||
0.470762, 0.468125, 0.308494,
|
||||
0.410478, 0.479321, 0.318534,
|
||||
0.372913, 0.485220, 0.374387,
|
||||
0.361928, 0.482245, 0.437718,
|
||||
0.366940, 0.480164, 0.507869,
|
||||
0.368611, 0.369176, 0.368467,
|
||||
0.368611, 0.369176, 0.368467,
|
||||
0.368611, 0.369176, 0.368467,
|
||||
0.648117, 0.648838, 0.648073,
|
||||
0.479182, 0.551627, 0.647106,
|
||||
0.520994, 0.532234, 0.681346,
|
||||
0.562577, 0.519448, 0.680616,
|
||||
0.602906, 0.518809, 0.653828,
|
||||
0.624149, 0.510424, 0.573186,
|
||||
0.621879, 0.523408, 0.486425,
|
||||
0.598359, 0.535614, 0.428962,
|
||||
0.570986, 0.553840, 0.400425,
|
||||
0.512108, 0.564480, 0.401383,
|
||||
0.471310, 0.572906, 0.444626,
|
||||
0.450232, 0.571801, 0.509053,
|
||||
0.455226, 0.571961, 0.579762,
|
||||
0.454777, 0.455136, 0.454750,
|
||||
0.368611, 0.369176, 0.368467,
|
||||
0.368611, 0.369176, 0.368467,
|
||||
0.648117, 0.648838, 0.648073,
|
||||
0.581149, 0.612569, 0.642884,
|
||||
0.592490, 0.605032, 0.665907,
|
||||
0.610979, 0.600103, 0.666567,
|
||||
0.630066, 0.598694, 0.649834,
|
||||
0.636469, 0.596019, 0.623470,
|
||||
0.641093, 0.600434, 0.590489,
|
||||
0.627745, 0.604366, 0.564505,
|
||||
0.619065, 0.613878, 0.551681,
|
||||
0.592630, 0.617272, 0.548023,
|
||||
0.580770, 0.619989, 0.564797,
|
||||
0.571081, 0.620166, 0.590528,
|
||||
0.568069, 0.622819, 0.618941,
|
||||
0.568471, 0.569152, 0.568223,
|
||||
0.368611, 0.369176, 0.368467,
|
||||
0.368611, 0.369176, 0.368467
|
||||
};
|
||||
|
||||
#define OVERWRITE_COLOR(pal_offset, array_offset, arrays) \
|
||||
arrays##Red[array_offset] = (pal[(pal_offset) * 3] - 0.368467) / (0.648117 - 0.368467); \
|
||||
arrays##Green[array_offset] = (pal[(pal_offset) * 3 + 1] - 0.368467) / (0.648117 - 0.368467); \
|
||||
arrays##Blue[array_offset] = (pal[(pal_offset) * 3 + 2] - 0.368467) / (0.648117 - 0.368467);
|
||||
|
||||
#define OVERWRITE_ROW(rowNumber) \
|
||||
for(int i = 1; i <= 12; i++) { \
|
||||
OVERWRITE_COLOR(i + (rowNumber) * 16, (i-1) % 12, level##rowNumber); \
|
||||
}
|
||||
|
||||
#define OVERWRITE_PALETTE \
|
||||
OVERWRITE_ROW(0); \
|
||||
OVERWRITE_ROW(1); \
|
||||
OVERWRITE_ROW(2); \
|
||||
OVERWRITE_ROW(3); \
|
||||
OVERWRITE_COLOR(30, 0, white); \
|
||||
OVERWRITE_COLOR(0, 1, white); \
|
||||
OVERWRITE_COLOR(16, 2, white); \
|
||||
OVERWRITE_COLOR(48, 3, white);
|
||||
|
||||
OVERWRITE_PALETTE;
|
||||
|
||||
int unsatLevelI = -1; // Integer part. Can be 0, 1, or 2. If the level is 3 or higher, it is represented as 2 with a fractional part greater than 1.
|
||||
float unsatLevelF; // Fractional part. If I=0, -1<F<1. If I=1, 0<F<1. If I=2, 0<F<2.
|
||||
do {
|
||||
if(unsatLevelI == -1)
|
||||
unsatLevelI = 0;
|
||||
else
|
||||
unsatLevelI++; // This if-statement is stupid, but my stupid computer requires me to do this...
|
||||
float unsatYRemaining = YIQ.r - unsaturatedLevels[unsatLevelI];
|
||||
unsatLevelF = unsatYRemaining / (unsaturatedLevels[unsatLevelI + 1] - unsaturatedLevels[unsatLevelI]);
|
||||
} while(unsatLevelI < 3 && unsatLevelF > 1.0);
|
||||
|
||||
// Note that the color does not cap at 0 and 1; it can be negative or greater than 1 too.
|
||||
vec3 unsaturatedColor = vec3(whiteRed[unsatLevelI], whiteGreen[unsatLevelI], whiteBlue[unsatLevelI]) * (1 - unsatLevelF) +
|
||||
unsatLevelF * vec3(whiteRed[unsatLevelI + 1], whiteGreen[unsatLevelI + 1], whiteBlue[unsatLevelI + 1]);
|
||||
|
||||
int satLevelI = -1; // Integer part. Can be 0, 1, or 2. If the level is 3 or higher, it is represented as 2 with a fractional part greater than 1.
|
||||
float satLevelF; // Fractional part. If I=0, -1<F<1. If I=1, 0<F<1. If I=2, 0<F<2.
|
||||
do {
|
||||
if(satLevelI < 0)
|
||||
satLevelI = 0;
|
||||
else
|
||||
satLevelI++;
|
||||
float satYRemaining = YIQ.r - saturatedLevels[satLevelI];
|
||||
satLevelF = satYRemaining / (saturatedLevels[satLevelI + 1] - saturatedLevels[satLevelI]);
|
||||
} while(satLevelI < 2 && satLevelF > 1.0);
|
||||
|
||||
float hue = atan(YIQ.g, YIQ.b) / 2 / pi + 0.5;
|
||||
int floorI = -1; // Hue integer that's less than hue.
|
||||
int ceilI = 0; // Hue integer that's greater than hue.
|
||||
float partI; // Fractional part of hue.
|
||||
|
||||
// Performed a linear regression on screenshotted test output.
|
||||
// As long as the preset's settings are all nearest-neighbor at 8x horizontal resolution,
|
||||
// this is the same for all setups.
|
||||
float hueFitted = (hue - 0.0418747) / 0.0826453 + 12.0 - 3.0;
|
||||
floorI = int(floor(hueFitted) + 0.5);
|
||||
ceilI = floorI + 1;
|
||||
partI = hueFitted - floorI;
|
||||
while(floorI >= 12)
|
||||
floorI -= 12;
|
||||
while(ceilI >= 12)
|
||||
ceilI -= 12;
|
||||
|
||||
// Old code using an array of screenshotted values.
|
||||
// do {
|
||||
// floorI++;
|
||||
// ceilI = (floorI + 1) % 12;
|
||||
// float floorH = levelHues[floorI];
|
||||
// float ceilH = levelHues[ceilI];
|
||||
// if(floorH < ceilH) {
|
||||
// partI = (hue - floorH) / (ceilH - floorH);
|
||||
// } else if(hue <= ceilH) {
|
||||
// partI = (hue + 1 - floorH) / (ceilH + 1 - floorH);
|
||||
// } else if(hue >= floorH) {
|
||||
// partI = (hue - floorH) / (ceilH + 1 - floorH);
|
||||
// } else { // hue >= ceilH && hue <= floorH
|
||||
// continue;
|
||||
// }
|
||||
// } while(floorI < 12 && (partI >= 1.0 || partI < 0.0));
|
||||
|
||||
vec3 floorLow, ceilLow, floorHigh, ceilHigh;
|
||||
switch(satLevelI) {
|
||||
case 0:
|
||||
floorLow = vec3(level0Red[floorI], level0Green[floorI], level0Blue[floorI]);
|
||||
ceilLow = vec3(level0Red[ceilI], level0Green[ceilI], level0Blue[ceilI]);
|
||||
floorHigh = vec3(level1Red[floorI], level1Green[floorI], level1Blue[floorI]);
|
||||
ceilHigh = vec3(level1Red[ceilI], level1Green[ceilI], level1Blue[ceilI]);
|
||||
|
||||
// vec3 floorDiff = floorHigh - floorLow;
|
||||
// vec3 ceilDiff = ceilHigh - ceilLow;
|
||||
// while(satLevelF < 0) {
|
||||
// satLevelF++;
|
||||
// floorLow -= floorDiff;
|
||||
// floorHigh -= floorDiff;
|
||||
// ceilLow -= ceilDiff;
|
||||
// ceilHigh -= ceilDiff;
|
||||
// }
|
||||
break;
|
||||
case 1:
|
||||
floorLow = vec3(level1Red[floorI], level1Green[floorI], level1Blue[floorI]);
|
||||
ceilLow = vec3(level1Red[ceilI], level1Green[ceilI], level1Blue[ceilI]);
|
||||
floorHigh = vec3(level2Red[floorI], level2Green[floorI], level2Blue[floorI]);
|
||||
ceilHigh = vec3(level2Red[ceilI], level2Green[ceilI], level2Blue[ceilI]);
|
||||
break;
|
||||
case 2:
|
||||
floorLow = vec3(level2Red[floorI], level2Green[floorI], level2Blue[floorI]);
|
||||
ceilLow = vec3(level2Red[ceilI], level2Green[ceilI], level2Blue[ceilI]);
|
||||
floorHigh = vec3(level3Red[floorI], level3Green[floorI], level3Blue[floorI]);
|
||||
ceilHigh = vec3(level3Red[ceilI], level3Green[ceilI], level3Blue[ceilI]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// I've since learned that each of these calculations can easily be replaced with mix().
|
||||
vec3 saturatedLow = floorLow + partI * (ceilLow - floorLow);
|
||||
vec3 saturatedHigh = floorHigh + partI * (ceilHigh - floorHigh);
|
||||
|
||||
vec3 saturatedColor;
|
||||
float expectedSaturationRaw;
|
||||
|
||||
if(satLevelI == 0 && satLevelF < 0.0) {
|
||||
// For some palettes, it might be better to use the normal "else" clause instead of this special case.
|
||||
expectedSaturationRaw = levelSats[0];
|
||||
saturatedColor = saturatedLow + (YIQ.r - saturatedLevels[0]) / (unsaturatedLevels[1] - unsaturatedLevels[0]) * (vec3(whiteRed[1] - whiteRed[0], whiteGreen[1] - whiteGreen[0], whiteBlue[1] - whiteBlue[0]));
|
||||
} else if(satLevelI == 2 && satLevelF > 1.0) {
|
||||
expectedSaturationRaw = levelSats[3];
|
||||
saturatedColor = saturatedHigh + (YIQ.r - saturatedLevels[3]) / (unsaturatedLevels[3] - unsaturatedLevels[2]) * (vec3(whiteRed[3] - whiteRed[2], whiteGreen[3] - whiteGreen[2], whiteBlue[3] - whiteBlue[2]));
|
||||
} else {
|
||||
saturatedColor = saturatedLow + satLevelF * (saturatedHigh - saturatedLow); // saturatedLow * (1 - satLevelF) + saturatedHigh * satLevelF;
|
||||
expectedSaturationRaw = levelSats[satLevelI] + satLevelF * (levelSats[satLevelI + 1] - levelSats[satLevelI]);
|
||||
}
|
||||
|
||||
float saturationRaw = sqrt(YIQ.g * YIQ.g + YIQ.b * YIQ.b);
|
||||
float saturationNorm = saturationRaw / expectedSaturationRaw;
|
||||
//RGB = vec3(saturationNorm);
|
||||
vec3 RGB = unsaturatedColor + saturationNorm * (saturatedColor - unsaturatedColor);
|
||||
|
||||
return toRgb *
|
||||
mat3(1, 0, 0,
|
||||
0, saturation, 0,
|
||||
0, 0, saturation) *
|
||||
mat3(0.2126, -0.09991, 0.615, // Rec. 709 YUV matrix.
|
||||
0.7152, -0.33609, -0.55861, // My video capture used Rec. 709.
|
||||
0.0722, 0.436, -0.05639) * RGB;
|
||||
}
|
||||
|
||||
//////////////////
|
||||
// Inverse EOTF //
|
||||
//////////////////
|
||||
|
||||
// CRT EOTF function taken from Grade; modified to perform its steps backward.
|
||||
#define CRT_l -(100000.*log((72981.-500000./(3.*max(2.3,global.pn_g_CRT_l)))/9058.))/945461.
|
||||
float EOTF_1886a_inverse_max(float bl, float brightness, float contrast) {
|
||||
|
||||
// Defaults:
|
||||
// Black Level = 0.1
|
||||
// Brightness = 0
|
||||
// Contrast = 100
|
||||
|
||||
const float wl = 100.0;
|
||||
float b = pow(bl, 1./2.4);
|
||||
float a = pow(wl, 1./2.4)-b;
|
||||
// b = (brightness-50.) / 250. + b/a; // -0.20 to +0.20
|
||||
b = (brightness * 100.) / 250. + b/a; // -0.20 to +0.20
|
||||
//a = contrast!=50. ? pow(2.,(contrast-50.)/50.) : 1.; // 0.50 to +2.00
|
||||
a = contrast;
|
||||
|
||||
const float Vc = 0.35; // Offset
|
||||
float Lw = wl/100. * a; // White level
|
||||
float Lb = min( b * a,Vc); // Black level
|
||||
const float a1 = 2.6; // Shoulder gamma
|
||||
const float a2 = 3.0; // Knee gamma
|
||||
float k = Lw /pow(1. + Lb, a1);
|
||||
float sl = k * pow(Vc + Lb, a1-a2); // Slope for knee gamma
|
||||
|
||||
// What input value results in 1.0?
|
||||
float bc = 0.00446395*pow(bl,1.23486);
|
||||
float color = 1.0;
|
||||
color = pow(color, 1.0 / (1.0-0.00843283*pow(bl,1.22744)));
|
||||
color = color / (1.0 / (1.0 - bc)) + bc;
|
||||
|
||||
color = pow(color / k, 1.0 / a1) - Lb;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
float EOTF_1886a_default_inverse_max(float brightness, float contrast) {
|
||||
// From Rec. ITU-R BT.1886
|
||||
float Vc = 0.35, // Vc, a1, and a2 are directly from the paper
|
||||
a1 = 2.6,
|
||||
a2 = 3.0,
|
||||
Lw = contrast;
|
||||
|
||||
float V = 1.0,
|
||||
b = brightness, // The paper recommends about either 0 or 0.1.
|
||||
k = Lw / pow(1.0 + b, a1);
|
||||
|
||||
// What value of V gives a result of 1.0?
|
||||
// 1.0 = k * pow(V + b, a1)
|
||||
// pow(1.0 / k, 1.0 / a1) - b = V
|
||||
|
||||
return pow(1.0 / k, 1.0 / a1) - b;
|
||||
}
|
||||
|
||||
float gammaInverseMaxPickable() {
|
||||
if(global.pn_gamma_type < 0.5) {
|
||||
return 1.0 / EOTF_1886a_inverse_max(CRT_l, global.pn_knob_brightness, global.pn_knob_contrast);
|
||||
} else if(global.pn_gamma_type < 1.5) {
|
||||
return 1.0 / EOTF_1886a_default_inverse_max(global.pn_knob_brightness, global.pn_knob_contrast);
|
||||
} else if(global.pn_gamma_type < 2.5) {
|
||||
return pow(global.pn_knob_contrast, 1.0 / global.pn_power_gamma);
|
||||
} else {
|
||||
float contr = 1.055 * pow(global.pn_knob_contrast, 1.0 / 2.4) - 0.055; // 2.4 instead of 2.2 is not a mistake.
|
||||
// rgb = (rgb + global.pn_knob_brightness) / (1 + global.pn_knob_brightness) * contr;
|
||||
// rgb * (1 + global.pn_knob_brightness) / contr - global.pn_knob_brightness = rgb
|
||||
return 1.0 / ((1 + global.pn_knob_brightness) / contr - global.pn_knob_brightness);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __patchy_ntsc_inc_filters_inc__
|
||||
|
||||
|
@ -34,6 +34,7 @@ layout(std140, set = 0, binding = 0) uniform UBO
|
||||
pn_color_screen_offset,
|
||||
pn_nes_enable,
|
||||
pn_nes_phase_mod,
|
||||
pn_nes_real_capture,
|
||||
pn_nes_hue_skew,
|
||||
pn_genesis_palette,
|
||||
pn_genesis_jailbar_enable,
|
||||
@ -122,7 +123,8 @@ layout(std140, set = 0, binding = 0) uniform UBO
|
||||
#pragma parameter pn_comment_nes_palette "== Note: Use Mesen (not FCEUmm) and change palette to Raw ==" 0 0 0 1
|
||||
#pragma parameter pn_nes_enable "Enable NES Raw Palette Mode" 0 0 1 1
|
||||
#pragma parameter pn_nes_phase_mod "NES Battletoads / Battletoads Double Dragon Phase Cycle" 0 0 1 1
|
||||
#pragma parameter pn_nes_hue_skew "NES colors emulated PPU version: 2C02G | 2C02E" 0 0 1 1
|
||||
#pragma parameter pn_nes_real_capture "NES color fix: Hue-skew | Real video capture LUT (unfinished)" 0 0 1 1
|
||||
#pragma parameter pn_nes_hue_skew "NES hue-skew PPU version: 2C02G | 2C02E" 0 0 1 1
|
||||
|
||||
#pragma parameter pn_comment_genesis_jailbar "=== Genesis Settings ===" 0 0 0 1
|
||||
#pragma parameter pn_genesis_palette "Genesis Plus GX color fix (not for BlastEm or other consoles)" 0 0 1 1
|
||||
|
@ -282,7 +282,7 @@ void main()
|
||||
|
||||
} else {
|
||||
|
||||
if(global.pn_genesis_palette > 0.5) {
|
||||
if(global.pn_genesis_palette > 0.5 && global.pn_test_pattern < 0.5) {
|
||||
colorIn.rgb = fixGenColor(colorIn.rgb);
|
||||
}
|
||||
|
||||
|
@ -72,20 +72,19 @@ void main()
|
||||
|
||||
if(global.pn_nes_enable < 0.5) {
|
||||
luma *= global.pn_color_amplitude; // Correction by the color carrier amplitude
|
||||
} else {
|
||||
}
|
||||
|
||||
float tint = global.pn_knob_tint * pi / 180.0;
|
||||
|
||||
if(global.pn_nes_enable > 0.5 && global.pn_nes_real_capture < 0.5) {
|
||||
// From lidnariq's measurements at http://forums.nesdev.org/viewtopic.php?p=159266#p159266
|
||||
// 30 = colorburst max
|
||||
// -23 = colorburst min
|
||||
// 110 = white
|
||||
// 0 = black
|
||||
float nesAmp = (30.0 - -23.0) / 2.0 / (110.0 - 0.0);
|
||||
float stdAmp = 0.2; // Standard colorburst amplitude in NTSC
|
||||
float stdAmp = 0.2 * 100.0 / 110.0; // Standard colorburst amplitude in NTSC
|
||||
chroma *= stdAmp / nesAmp;
|
||||
}
|
||||
|
||||
float tint = global.pn_knob_tint * pi / 180.0;
|
||||
|
||||
if(global.pn_nes_enable > 0.5) {
|
||||
float saturatedLevels[4] = {((0.228 + 0.616) / 2. - 0.312) / (1.100 - 0.312), ((0.312 + 0.840) / 2. - 0.312) / (1.100 - 0.312), ((0.552 + 1.100) / 2. - 0.312) / (1.100 - 0.312), ((0.880 + 1.100) / 2. - 0.312) / (1.100 - 0.312)}; // Calculated from DAC.slang from GTU-famicom
|
||||
|
||||
int satLevelI = -1; // Integer part. Can be 0, 1, or 2. If the level is 3 or higher, it is represented as 2 with a fractional part greater than 1.
|
||||
@ -105,16 +104,13 @@ void main()
|
||||
float skew;
|
||||
if(global.pn_nes_hue_skew < 0.5) {
|
||||
// 2C02G
|
||||
skew = (satLevelI + satLevelF) * -5.0 / 180.0 * pi; // -5 degrees
|
||||
skew = -5.0 / 180.0 * pi; // -5 degrees
|
||||
} else {
|
||||
// 2C02E
|
||||
skew = (satLevelI + satLevelF) * -2.5 / 180.0 * pi; // -2.5 degrees
|
||||
skew = -2.5 / 180.0 * pi; // -2.5 degrees
|
||||
}
|
||||
|
||||
// 15 degree rotation was a very close match against real hardware video capture.
|
||||
// It makes sense because it's half of an NES PPU cycle.
|
||||
// The change is done here instead of in the video signal for better precision, due to the video signal sample resolution being the same as the NES PPU cycle rate.
|
||||
tint += -skew - 15.0 / 180.0 * pi;
|
||||
tint += (3.0 - (satLevelI + satLevelF)) * skew;
|
||||
}
|
||||
|
||||
vec2 bmyRmy = uvDemodPickable(global.pn_demodulator_chroma_filter_type, chroma, phase, tint);
|
||||
|
@ -52,9 +52,16 @@ void main()
|
||||
// A slight lowpass on B-Y and R-Y after demodulating
|
||||
vec2 rmybmy = lowpassGTU(240).gb;
|
||||
|
||||
vec3 rgb = YBmyRmyToRGBMatrix() * vec3(y, rmybmy * global.pn_knob_saturation);
|
||||
rgb *= global.pn_knob_contrast;
|
||||
// rgb += global.pn_knob_brightness; // Brightness will be handled later.
|
||||
vec3 rgb;
|
||||
if(global.pn_nes_enable > 0.5 && global.pn_nes_real_capture > 0.5) {
|
||||
rgb = nesRealCaptureLookup(vec3(y, rmybmy), global.pn_knob_saturation);
|
||||
} else {
|
||||
rgb = YBmyRmyToRGBMatrix() * vec3(y, rmybmy * global.pn_knob_saturation);
|
||||
}
|
||||
|
||||
rgb = (rgb + global.pn_knob_brightness) / (1 + global.pn_knob_brightness);
|
||||
rgb *= gammaInverseMaxPickable();
|
||||
|
||||
if(global.pn_rgb_smear_enable > 0.5)
|
||||
rgb = min(vec3(global.pn_rgb_smear_clamp), rgb);
|
||||
|
||||
|
@ -146,20 +146,20 @@ void main()
|
||||
rgb = min(col, 1);
|
||||
}
|
||||
|
||||
rgb /= global.pn_knob_contrast;
|
||||
rgb /= gammaInverseMaxPickable();
|
||||
rgb = rgb * (1.0 + global.pn_knob_brightness) - global.pn_knob_brightness;
|
||||
|
||||
if(global.pn_gamma_type < 0.5) {
|
||||
FragColor = vec4(EOTF_1886a_f3(rgb, CRT_l, global.pn_knob_brightness, global.pn_knob_contrast), 1.0);
|
||||
} else if(global.pn_gamma_type < 1.5) {
|
||||
FragColor = vec4(clamp(EOTF_1886a_default_f3(rgb, global.pn_knob_brightness, global.pn_knob_contrast), 0, 1), 1.0);
|
||||
} else if(global.pn_gamma_type < 2.5) {
|
||||
rgb = max(rgb * (1.0 - global.pn_knob_brightness) + global.pn_knob_brightness, 0);
|
||||
rgb = max((rgb + global.pn_knob_brightness) / (1.0 + global.pn_knob_brightness), 0);
|
||||
rgb *= pow(global.pn_knob_contrast, 1.0 / global.pn_power_gamma);
|
||||
FragColor = vec4(pow(min(rgb, vec3(1)), vec3(global.pn_power_gamma)), 1.0);
|
||||
} else {
|
||||
// The different values 2.2 and 2.4 are not a mistake.
|
||||
rgb = max(rgb * (1.0 - global.pn_knob_brightness) + global.pn_knob_brightness, 0);
|
||||
rgb *= global.pn_knob_contrast <= 1.0 ? linear_to_sRGB(vec3(global.pn_knob_contrast), 2.4).r : pow(global.pn_knob_contrast, 1.0 / 2.2);
|
||||
float contr = 1.055 * pow(global.pn_knob_contrast, 1.0 / 2.4) - 0.055; // 2.4 instead of 2.2 is not a mistake.
|
||||
rgb = (rgb + global.pn_knob_brightness) / (1 + global.pn_knob_brightness) * contr;
|
||||
FragColor = vec4(sRGB_to_linear(rgb, 2.4), 1.0);
|
||||
}
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ layout(std140, set = 0, binding = 0) uniform UBO
|
||||
mat4 MVP;
|
||||
} global;
|
||||
|
||||
#pragma parameter lut_toggle "Gamut: sRGB, P22-80s, P22-90s, P22-J, Trinitron P22" 0 0 4 1
|
||||
#pragma parameter lut_index "Reference White: SMPTE-C (D65) | NTSC-U (C) | NTSC-J (D93)" 1 0 2 1
|
||||
//#pragma parameter lut_chroma_adapt "(For Trinitron Only) Chromatic Adaptation to D65" 0 0 1 1
|
||||
#pragma parameter lut_toggle "Gamut: sRGB, P22-80s, P22-90s, P22-J, Trinitron P22" 4 0 4 1
|
||||
#pragma parameter lut_index "White point: SMPTE-C (D65) | NTSC-U (C) | NTSC-J (9300K+27MPCD)" 1 0 2 1
|
||||
#pragma parameter lut_chroma_adapt "(For P22-J and Trinitron w/ NTSC-J) Chromatic Adaptation to D65" 0 0 1 1
|
||||
|
||||
#pragma stage vertex
|
||||
layout(location = 0) in vec4 Position;
|
||||
@ -63,7 +63,8 @@ layout(set = 0, binding = 3) uniform sampler2D PhosphorSamplerLUT1;
|
||||
layout(set = 0, binding = 4) uniform sampler2D PhosphorSamplerLUT2;
|
||||
layout(set = 0, binding = 5) uniform sampler2D PhosphorSamplerLUT3;
|
||||
layout(set = 0, binding = 6) uniform sampler2D PhosphorSamplerLUT4;
|
||||
//layout(set = 0, binding = 7) uniform sampler2D PhosphorSamplerLUT5;
|
||||
layout(set = 0, binding = 7) uniform sampler2D PhosphorSamplerLUT5;
|
||||
layout(set = 0, binding = 8) uniform sampler2D PhosphorSamplerLUT6;
|
||||
|
||||
#define saturate(c) clamp(c, 0.0, 1.0)
|
||||
|
||||
@ -72,9 +73,11 @@ void main()
|
||||
vec2 LUT_Size = textureSize(PhosphorSamplerLUT1, 0); // All are the same size... right?
|
||||
vec3 imgColor = texture(Source, vTexCoord.xy).rgb;
|
||||
|
||||
// Chromatic adaptation is available for P22-J and Trinitron P22 when using white D93.
|
||||
bool doChromaAdapt = params.lut_toggle > 2.5 && params.lut_index > 1.5 && params.lut_chroma_adapt > 0.5;
|
||||
|
||||
// Force disable chromatic adaptation if a chroma-adapted LUT is missing.
|
||||
// The current version lacks any chroma-adapted LUTs whatsoever, so the check is commented out.
|
||||
// if(params.lut_chroma_adapt < 0.5 || params.lut_toggle < 2.5) {
|
||||
if(!doChromaAdapt) {
|
||||
// Change the white balance ourselves, and use the D65-to-D65 LUT.
|
||||
|
||||
float Wx, Wy;
|
||||
@ -131,7 +134,7 @@ void main()
|
||||
vec3 normConsts = cNormConsts / d65NormConsts;
|
||||
normConsts /= max(normConsts.r, max(normConsts.g, normConsts.b));
|
||||
imgColor *= normConsts; // Scale R, G, and B to get the white point that we want.
|
||||
// }
|
||||
}
|
||||
|
||||
if(params.lut_toggle < 0.5) {
|
||||
FragColor = vec4(imgColor, 1.0);
|
||||
@ -172,15 +175,23 @@ void main()
|
||||
RcGcBf = (texture(SamplerLUT, vec2(floors.b + ceils.r, ceils.g))).xyz; \
|
||||
RcGcBc = (texture(SamplerLUT, vec2(ceils.b + ceils.r, ceils.g))).xyz;
|
||||
|
||||
if(params.lut_toggle < 1.5) {
|
||||
eightSamples(PhosphorSamplerLUT1);
|
||||
} else if(params.lut_toggle < 2.5) {
|
||||
eightSamples(PhosphorSamplerLUT2);
|
||||
} else if (params.lut_toggle < 3.5) {
|
||||
eightSamples(PhosphorSamplerLUT3);
|
||||
} else {
|
||||
eightSamples(PhosphorSamplerLUT4);
|
||||
}
|
||||
if(doChromaAdapt) {
|
||||
if(params.lut_toggle < 3.5) {
|
||||
eightSamples(PhosphorSamplerLUT5);
|
||||
} else {
|
||||
eightSamples(PhosphorSamplerLUT6);
|
||||
}
|
||||
} else {
|
||||
if(params.lut_toggle < 1.5) {
|
||||
eightSamples(PhosphorSamplerLUT1);
|
||||
} else if(params.lut_toggle < 2.5) {
|
||||
eightSamples(PhosphorSamplerLUT2);
|
||||
} else if (params.lut_toggle < 3.5) {
|
||||
eightSamples(PhosphorSamplerLUT3);
|
||||
} else {
|
||||
eightSamples(PhosphorSamplerLUT4);
|
||||
}
|
||||
}
|
||||
|
||||
#undef eightSamples
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user