mirror of
https://github.com/italicsjenga/slang-shaders.git
synced 2024-11-23 00:01:31 +11:00
Merge pull request #58 from Monroe88/nes_palette
nes-color-decoder improvements and colorimetry shader
This commit is contained in:
commit
08a810b7d1
256
misc/colorimetry.slang
Normal file
256
misc/colorimetry.slang
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
/*
|
||||||
|
Colorimetry shader
|
||||||
|
Ported from Drag's NES Palette Generator
|
||||||
|
http://drag.wootest.net/misc/palgen.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
layout(std140, set = 0, binding = 0) uniform UBO
|
||||||
|
{
|
||||||
|
mat4 MVP;
|
||||||
|
vec4 OutputSize;
|
||||||
|
vec4 OriginalSize;
|
||||||
|
vec4 SourceSize;
|
||||||
|
uint FrameCount;
|
||||||
|
} global;
|
||||||
|
|
||||||
|
layout(push_constant) uniform Push
|
||||||
|
{
|
||||||
|
float color_mode;
|
||||||
|
float white_point_d93;
|
||||||
|
float clipping_method;
|
||||||
|
} params;
|
||||||
|
|
||||||
|
#pragma parameter color_mode "Colorimetry mode" 0.0 0.0 2.0 1.0
|
||||||
|
#pragma parameter white_point_d93 "Use D93 white point" 1.0 0.0 1.0 1.0
|
||||||
|
#pragma parameter clipping_method "Color clipping method" 0.0 0.0 2.0 1.0
|
||||||
|
|
||||||
|
#define color_mode params.color_mode
|
||||||
|
#define white_point_d93 params.white_point_d93
|
||||||
|
#define clipping_method params.clipping_method
|
||||||
|
|
||||||
|
vec3 huePreserveClipDarken(float r, float g, float b)
|
||||||
|
{
|
||||||
|
float ratio = 1.0;
|
||||||
|
if ((r > 1.0) || (g > 1.0) || (b > 1.0))
|
||||||
|
{
|
||||||
|
float max = r;
|
||||||
|
if (g > max)
|
||||||
|
max = g;
|
||||||
|
if (b > max)
|
||||||
|
max = b;
|
||||||
|
ratio = 1.0 / max;
|
||||||
|
}
|
||||||
|
|
||||||
|
r *= ratio;
|
||||||
|
g *= ratio;
|
||||||
|
b *= ratio;
|
||||||
|
|
||||||
|
r = clamp(r, 0.0, 1.0);
|
||||||
|
g = clamp(g, 0.0, 1.0);
|
||||||
|
b = clamp(b, 0.0, 1.0);
|
||||||
|
|
||||||
|
return vec3(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 huePreserveClipDesaturate(float r, float g, float b)
|
||||||
|
{
|
||||||
|
float l = (.299 * r) + (0.587 * g) + (0.114 * b);
|
||||||
|
bool ovr = false;
|
||||||
|
float ratio = 1.0;
|
||||||
|
|
||||||
|
if ((r > 1.0) || (g > 1.0) || (b > 1.0))
|
||||||
|
{
|
||||||
|
ovr = true;
|
||||||
|
float max = r;
|
||||||
|
if (g > max) max = g;
|
||||||
|
if (b > max) max = b;
|
||||||
|
ratio = 1.0 / max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ovr)
|
||||||
|
{
|
||||||
|
r -= 1.0;
|
||||||
|
g -= 1.0;
|
||||||
|
b -= 1.0;
|
||||||
|
r *= ratio;
|
||||||
|
g *= ratio;
|
||||||
|
b *= ratio;
|
||||||
|
r += 1.0;
|
||||||
|
g += 1.0;
|
||||||
|
b += 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = clamp(r, 0.0, 1.0);
|
||||||
|
g = clamp(g, 0.0, 1.0);
|
||||||
|
b = clamp(b, 0.0, 1.0);
|
||||||
|
|
||||||
|
return vec3(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 ApplyColorimetry(vec3 color)
|
||||||
|
{
|
||||||
|
//http://www.brucelindbloom.com/Eqn_RGB_XYZ_Matrix.html
|
||||||
|
//http://www.dr-lex.be/random/matrix_inv.html
|
||||||
|
// Convert the (x,y) values to X Y Z.
|
||||||
|
|
||||||
|
float R = color.r;
|
||||||
|
float G = color.g;
|
||||||
|
float B = color.b;
|
||||||
|
|
||||||
|
float Rx = 0.0;
|
||||||
|
float Ry = 0.0;
|
||||||
|
float Gx = 0.0;
|
||||||
|
float Gy = 0.0;
|
||||||
|
float Bx = 0.0;
|
||||||
|
float By = 0.0;
|
||||||
|
float Wx = 0.0;
|
||||||
|
float Wy = 0.0;
|
||||||
|
|
||||||
|
if (white_point_d93 > 0.5)
|
||||||
|
{
|
||||||
|
Wx = 0.31;
|
||||||
|
Wy = 0.316;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Wx = 0.3127;
|
||||||
|
Wy = 0.329;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color_mode < 0.5)
|
||||||
|
{
|
||||||
|
// FCC 1953
|
||||||
|
// Original FCC standard for the color of the phosphors
|
||||||
|
Rx = 0.67;
|
||||||
|
Ry = 0.33;
|
||||||
|
Gx = 0.21;
|
||||||
|
Gy = 0.71;
|
||||||
|
Bx = 0.14;
|
||||||
|
By = 0.08;
|
||||||
|
}
|
||||||
|
else if (color_mode == 1.0)
|
||||||
|
{
|
||||||
|
// SMPTE C (1987)
|
||||||
|
// A newer standard for the color of the phospors. (Not used in Japan)
|
||||||
|
Rx = 0.63;
|
||||||
|
Ry = 0.34;
|
||||||
|
Gx = 0.31;
|
||||||
|
Gy = 0.595;
|
||||||
|
Bx = 0.155;
|
||||||
|
By = 0.07;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//sRGB (PC Monitors)
|
||||||
|
//The colorimetry used in PC monitors, like the one you're (probably) looking at right now.
|
||||||
|
Rx = 0.64;
|
||||||
|
Ry = 0.33;
|
||||||
|
Gx = 0.3;
|
||||||
|
Gy = 0.6;
|
||||||
|
Bx = 0.15;
|
||||||
|
By = 0.06;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Xr = Rx / Ry;
|
||||||
|
float Xg = Gx / Gy;
|
||||||
|
float Xb = Bx / By;
|
||||||
|
float Xw = Wx / Wy;
|
||||||
|
float Yr = 1.0;
|
||||||
|
float Yg = 1.0;
|
||||||
|
float Yb = 1.0;
|
||||||
|
float Yw = 1.0;
|
||||||
|
float Zr = (1.0 - Rx - Ry) / Ry;
|
||||||
|
float Zg = (1.0 - Gx - Gy) / Gy;
|
||||||
|
float Zb = (1.0 - Bx - By) / By;
|
||||||
|
float Zw = (1.0 - Wx - Wy) / Wy;
|
||||||
|
|
||||||
|
// Get ready for a bunch of painful math. I need to invert a matrix, then multiply it by a vector.
|
||||||
|
// Determinant for inverse matrix
|
||||||
|
|
||||||
|
float sDet = (Xr*((Zb*Yg)-(Zg*Yb)))-(Yr*((Zb*Xg)-(Zg*Xb)))+(Zr*((Yb*Xg)-(Yg*Xb)));
|
||||||
|
|
||||||
|
float Sr = ((((Zb*Yg)-(Zg*Yb))/sDet)*Xw) + ((-((Zb*Xg)-(Zg*Xb))/sDet)*Yw) + ((((Yb*Xg)-(Yg*Xb))/sDet)*Zw);
|
||||||
|
float Sg = ((-((Zb*Yr)-(Zr*Yb))/sDet)*Xw) + ((((Zb*Xr)-(Zr*Xb))/sDet)*Yw) + ((-((Yb*Xr)-(Yr*Xb))/sDet)*Zw);
|
||||||
|
float Sb = ((((Zg*Yr)-(Zr*Yg))/sDet)*Xw) + ((-((Zg*Xr)-(Zr*Xg))/sDet)*Yw) + ((((Yg*Xr)-(Yr*Xg))/sDet)*Zw);
|
||||||
|
|
||||||
|
// This should be the completed RGB -> XYZ matrix.
|
||||||
|
// Multiply each of the first three members by R, then add them together to get X
|
||||||
|
float convMatrix[9] = float[] (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
convMatrix[0] = Sr*Xr;
|
||||||
|
convMatrix[1] = Sg*Xg;
|
||||||
|
convMatrix[2] = Sb*Xb;
|
||||||
|
convMatrix[3] = Sr*Yr;
|
||||||
|
convMatrix[4] = Sg*Yg;
|
||||||
|
convMatrix[5] = Sb*Yb;
|
||||||
|
convMatrix[6] = Sr*Zr;
|
||||||
|
convMatrix[7] = Sg*Zg;
|
||||||
|
convMatrix[8] = Sb*Zb;
|
||||||
|
|
||||||
|
// Convert RGB to XYZ using the matrix generated with the specified RGB and W points.
|
||||||
|
float X = (convMatrix[0] * R) + (convMatrix[1] * G) + (convMatrix[2] * B);
|
||||||
|
float Y = (convMatrix[3] * R) + (convMatrix[4] * G) + (convMatrix[5] * B);
|
||||||
|
float Z = (convMatrix[6] * R) + (convMatrix[7] * G) + (convMatrix[8] * B);
|
||||||
|
|
||||||
|
// This is the conversion matrix for CIEXYZ -> sRGB. I nicked this from:
|
||||||
|
// http://www.brucelindbloom.com/Eqn_RGB_XYZ_Matrix.html
|
||||||
|
// and I know it's right because when you use the sRGB colorimetry, this matrix produces identical results to
|
||||||
|
// just using the raw R, G, and B above.
|
||||||
|
float xyztorgb[9] = float[] (3.2404, -1.5371, -0.4985, -0.9693, 1.876, 0.0416, 0.0556, -0.204, 1.0572);
|
||||||
|
|
||||||
|
// Convert back to RGB using the XYZ->sRGB matrix.
|
||||||
|
R = (xyztorgb[0]*X) + (xyztorgb[1]*Y) + (xyztorgb[2]*Z);
|
||||||
|
G = (xyztorgb[3]*X) + (xyztorgb[4]*Y) + (xyztorgb[5]*Z);
|
||||||
|
B = (xyztorgb[6]*X) + (xyztorgb[7]*Y) + (xyztorgb[8]*Z);
|
||||||
|
|
||||||
|
vec3 corrected_rgb;
|
||||||
|
|
||||||
|
// Apply desired clipping method to out-of-gamut colors.
|
||||||
|
if (clipping_method < 0.5)
|
||||||
|
{
|
||||||
|
//If a channel is out of range (> 1.0), it's simply clamped to 1.0. This may change hue, saturation, and/or lightness.
|
||||||
|
R = clamp(R, 0.0, 1.0);
|
||||||
|
G = clamp(G, 0.0, 1.0);
|
||||||
|
B = clamp(B, 0.0, 1.0);
|
||||||
|
corrected_rgb = vec3(R, G, B);
|
||||||
|
}
|
||||||
|
else if (clipping_method == 1.0)
|
||||||
|
{
|
||||||
|
//If any channels are out of range, the color is darkened until it is completely in range.
|
||||||
|
corrected_rgb = huePreserveClipDarken(R, G, B);
|
||||||
|
}
|
||||||
|
else if (clipping_method == 2.0)
|
||||||
|
{
|
||||||
|
//If any channels are out of range, the color is desaturated towards the luminance it would've had.
|
||||||
|
corrected_rgb = huePreserveClipDesaturate(R, G, B);
|
||||||
|
}
|
||||||
|
|
||||||
|
return corrected_rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma stage vertex
|
||||||
|
layout(location = 0) in vec4 Position;
|
||||||
|
layout(location = 1) in vec2 TexCoord;
|
||||||
|
layout(location = 0) out vec2 vTexCoord;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = global.MVP * Position;
|
||||||
|
vTexCoord = TexCoord;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma stage fragment
|
||||||
|
layout(location = 0) in vec2 vTexCoord;
|
||||||
|
layout(location = 0) out vec4 FragColor;
|
||||||
|
layout(set = 0, binding = 2) uniform sampler2D Source;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 c = texture(Source, vTexCoord.xy);
|
||||||
|
vec3 rgb = vec3(c.r, c.g, c.b);
|
||||||
|
|
||||||
|
vec3 out_color = ApplyColorimetry(rgb);
|
||||||
|
FragColor = vec4(out_color, 1.0);
|
||||||
|
}
|
|
@ -1,144 +0,0 @@
|
||||||
#version 450
|
|
||||||
|
|
||||||
/*
|
|
||||||
NES NTSC Color Decoder shader
|
|
||||||
Ported from Bisqwit's C++ NES Palette Generator
|
|
||||||
https://forums.nesdev.com/viewtopic.php?p=85060#p85060
|
|
||||||
|
|
||||||
Use with Nestopia or FCEUmm libretro cores with the palette set to 'raw'.
|
|
||||||
*/
|
|
||||||
|
|
||||||
layout(std140, set = 0, binding = 0) uniform UBO
|
|
||||||
{
|
|
||||||
mat4 MVP;
|
|
||||||
vec4 OutputSize;
|
|
||||||
vec4 OriginalSize;
|
|
||||||
vec4 SourceSize;
|
|
||||||
uint FrameCount;
|
|
||||||
} global;
|
|
||||||
|
|
||||||
layout(push_constant) uniform Push
|
|
||||||
{
|
|
||||||
float nes_saturation;
|
|
||||||
float nes_hue;
|
|
||||||
float nes_contrast;
|
|
||||||
float nes_brightness;
|
|
||||||
float nes_gamma;
|
|
||||||
} params;
|
|
||||||
|
|
||||||
#pragma parameter nes_saturation "Saturation" 1.0 0.0 5.0 0.05
|
|
||||||
#pragma parameter nes_hue "Hue" 0.0 -360.0 360.0 1.0
|
|
||||||
#pragma parameter nes_contrast "Contrast" 1.0 0.0 2.0 0.05
|
|
||||||
#pragma parameter nes_brightness "Brightness" 1.0 0.0 2.0 0.05
|
|
||||||
#pragma parameter nes_gamma "Gamma" 1.8 1.0 2.5 0.05
|
|
||||||
|
|
||||||
#define saturation params.nes_saturation
|
|
||||||
#define hue params.nes_hue
|
|
||||||
#define contrast params.nes_contrast
|
|
||||||
#define brightness params.nes_brightness
|
|
||||||
#define gamma params.nes_gamma
|
|
||||||
|
|
||||||
bool wave (int p, int color)
|
|
||||||
{
|
|
||||||
return ((color + p + 8) % 12 < 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
float gammafix (float f)
|
|
||||||
{
|
|
||||||
return f < 0.0 ? 0.0 : pow(f, 2.2 / gamma);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 MakeRGBColor(int emphasis, int level, int color)
|
|
||||||
{
|
|
||||||
float y = 0.0;
|
|
||||||
float i = 0.0;
|
|
||||||
float q = 0.0;
|
|
||||||
|
|
||||||
float r = 0.0;
|
|
||||||
float g = 0.0;
|
|
||||||
float b = 0.0;
|
|
||||||
|
|
||||||
// Color 0xE and 0xF are black
|
|
||||||
level = (color < 14) ? level : 1;
|
|
||||||
|
|
||||||
// Voltage levels, relative to synch voltage
|
|
||||||
float black = 0.518;
|
|
||||||
float white = 1.962;
|
|
||||||
float attenuation = 0.746;
|
|
||||||
const float levels[8] = float[] ( 0.350 , 0.518, 0.962, 1.550,
|
|
||||||
1.094, 1.506, 1.962, 1.962);
|
|
||||||
|
|
||||||
float low = levels[level + 4 * int(color == 0)];
|
|
||||||
float high = levels[level + 4 * int(color < 13)];
|
|
||||||
|
|
||||||
// Calculate the luma and chroma by emulating the relevant circuits:
|
|
||||||
for(int p = 0; p < 12; p++) // 12 clock cycles per pixel.
|
|
||||||
{
|
|
||||||
// NES NTSC modulator (square wave between two voltage levels):
|
|
||||||
float spot = wave(p, color) ? high : low;
|
|
||||||
|
|
||||||
// De-emphasis bits attenuate a part of the signal:
|
|
||||||
if ((bool(emphasis & 1) && wave(p, 12)) ||
|
|
||||||
(bool(emphasis & 2) && wave(p, 4)) ||
|
|
||||||
(bool(emphasis & 4) && wave(p, 8)))
|
|
||||||
{
|
|
||||||
spot *= attenuation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize:
|
|
||||||
float v = (spot - black) / (white - black);
|
|
||||||
|
|
||||||
// Ideal TV NTSC demodulator:
|
|
||||||
// Apply contrast/brightness
|
|
||||||
v = (v - 0.5) * contrast + 0.5;
|
|
||||||
v *= (brightness / 12.0);
|
|
||||||
|
|
||||||
float hue_tweak = hue * 12.0 / 360.0;
|
|
||||||
|
|
||||||
y += v;
|
|
||||||
i += v * cos((3.141592653 / 6.0) * (p + hue_tweak) );
|
|
||||||
q += v * sin((3.141592653 / 6.0) * (p + hue_tweak) );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
i *= saturation;
|
|
||||||
q *= saturation;
|
|
||||||
|
|
||||||
// Convert YIQ into RGB according to a commonly used conversion matrix.
|
|
||||||
r = clamp((1.0 * gammafix(y + 0.956 * i + 0.621 * q)), 0, 1.0);
|
|
||||||
g = clamp((1.0 * gammafix(y + -0.272 * i + -0.647 * q)), 0, 1.0);
|
|
||||||
b = clamp((1.0 * gammafix(y + -1.105 * i + 1.702 * q)), 0, 1.0);
|
|
||||||
|
|
||||||
return vec3(r,g,b);
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma stage vertex
|
|
||||||
layout(location = 0) in vec4 Position;
|
|
||||||
layout(location = 1) in vec2 TexCoord;
|
|
||||||
layout(location = 0) out vec2 vTexCoord;
|
|
||||||
layout(location = 1) out float colorPhase;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
gl_Position = global.MVP * Position;
|
|
||||||
vTexCoord = TexCoord;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma stage fragment
|
|
||||||
layout(location = 0) in vec2 vTexCoord;
|
|
||||||
layout(location = 1) in float colorPhase;
|
|
||||||
layout(location = 0) out vec4 FragColor;
|
|
||||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
vec4 c = texture(Source, vTexCoord.xy);
|
|
||||||
|
|
||||||
// Extract the chroma, level, and emphasis from the normalized RGB triplet
|
|
||||||
int color = int(floor((c.r * 15.0) + 0.5));
|
|
||||||
int level = int(floor((c.g * 3.0) + 0.5));
|
|
||||||
int emphasis = int(floor((c.b * 7.0) + 0.5));
|
|
||||||
|
|
||||||
vec3 out_color = MakeRGBColor(emphasis, level, color);
|
|
||||||
FragColor = vec4(out_color, 1.0);
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
#version 450
|
|
||||||
|
|
||||||
/*
|
|
||||||
NES NTSC Color Decoder shader
|
|
||||||
Ported from Bisqwit's C++ NES Palette Generator
|
|
||||||
https://forums.nesdev.com/viewtopic.php?p=85060#p85060
|
|
||||||
|
|
||||||
Use with Nestopia or FCEUmm libretro cores with the palette set to 'raw'.
|
|
||||||
*/
|
|
||||||
|
|
||||||
layout(std140, set = 0, binding = 0) uniform UBO
|
|
||||||
{
|
|
||||||
mat4 MVP;
|
|
||||||
vec4 OutputSize;
|
|
||||||
vec4 OriginalSize;
|
|
||||||
vec4 SourceSize;
|
|
||||||
uint FrameCount;
|
|
||||||
} global;
|
|
||||||
|
|
||||||
layout(push_constant) uniform Push
|
|
||||||
{
|
|
||||||
float nes_saturation;
|
|
||||||
float nes_hue;
|
|
||||||
float nes_contrast;
|
|
||||||
float nes_brightness;
|
|
||||||
float nes_gamma;
|
|
||||||
} params;
|
|
||||||
|
|
||||||
#pragma parameter nes_saturation "Saturation" 1.0 0.0 5.0 0.05
|
|
||||||
#pragma parameter nes_hue "Hue" 0.0 -360.0 360.0 1.0
|
|
||||||
#pragma parameter nes_contrast "Contrast" 1.0 0.0 2.0 0.05
|
|
||||||
#pragma parameter nes_brightness "Brightness" 1.0 0.0 2.0 0.05
|
|
||||||
#pragma parameter nes_gamma "Gamma" 1.8 1.0 2.5 0.05
|
|
||||||
|
|
||||||
#define saturation params.nes_saturation
|
|
||||||
#define hue params.nes_hue
|
|
||||||
#define contrast params.nes_contrast
|
|
||||||
#define brightness params.nes_brightness
|
|
||||||
#define gamma params.nes_gamma
|
|
||||||
|
|
||||||
bool wave (int p, int color)
|
|
||||||
{
|
|
||||||
return ((color + p + 8) % 12 < 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
float gammafix (float f)
|
|
||||||
{
|
|
||||||
return f < 0.0 ? 0.0 : pow(f, 2.2 / gamma);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 MakeRGBColor(int emphasis, int level, int color)
|
|
||||||
{
|
|
||||||
float y = 0.0;
|
|
||||||
float i = 0.0;
|
|
||||||
float q = 0.0;
|
|
||||||
|
|
||||||
float r = 0.0;
|
|
||||||
float g = 0.0;
|
|
||||||
float b = 0.0;
|
|
||||||
|
|
||||||
// Color 0xE and 0xF are black
|
|
||||||
level = (color < 14) ? level : 1;
|
|
||||||
|
|
||||||
// Voltage levels, relative to synch voltage
|
|
||||||
float black = 0.518;
|
|
||||||
float white = 1.962;
|
|
||||||
float attenuation = 0.746;
|
|
||||||
const float levels[8] = float[] ( 0.350 , 0.518, 0.962, 1.550,
|
|
||||||
1.094, 1.506, 1.962, 1.962);
|
|
||||||
|
|
||||||
float low = levels[level + 4 * int(color == 0)];
|
|
||||||
float high = levels[level + 4 * int(color < 13)];
|
|
||||||
|
|
||||||
// Calculate the luma and chroma by emulating the relevant circuits:
|
|
||||||
for(int p = 0; p < 12; p++) // 12 clock cycles per pixel.
|
|
||||||
{
|
|
||||||
// NES NTSC modulator (square wave between two voltage levels):
|
|
||||||
float spot = wave(p, color) ? high : low;
|
|
||||||
|
|
||||||
// De-emphasis bits attenuate a part of the signal:
|
|
||||||
if ((bool(emphasis & 1) && wave(p, 12)) ||
|
|
||||||
(bool(emphasis & 2) && wave(p, 4)) ||
|
|
||||||
(bool(emphasis & 4) && wave(p, 8)))
|
|
||||||
{
|
|
||||||
spot *= attenuation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize:
|
|
||||||
float v = (spot - black) / (white - black);
|
|
||||||
|
|
||||||
// Ideal TV NTSC demodulator:
|
|
||||||
// Apply contrast/brightness
|
|
||||||
v = (v - 0.5) * contrast + 0.5;
|
|
||||||
v *= (brightness / 12.0);
|
|
||||||
|
|
||||||
float hue_tweak = hue * 12.0 / 360.0;
|
|
||||||
|
|
||||||
y += v;
|
|
||||||
i += v * cos((3.141592653 / 6.0) * (p + hue_tweak) );
|
|
||||||
q += v * sin((3.141592653 / 6.0) * (p + hue_tweak) );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
i *= saturation;
|
|
||||||
q *= saturation;
|
|
||||||
|
|
||||||
// Convert YIQ into RGB according to the Sony CXA2025AS US decoder matrix.
|
|
||||||
r = clamp((1.0 * gammafix(y + 1.630 * i + 0.317 * q)), 0, 1.0);
|
|
||||||
g = clamp((1.0 * gammafix(y + -0.378 * i + -0.466 * q)), 0, 1.0);
|
|
||||||
b = clamp((1.0 * gammafix(y + -1.089 * i + 1.677 * q)), 0, 1.0);
|
|
||||||
|
|
||||||
return vec3(r,g,b);
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma stage vertex
|
|
||||||
layout(location = 0) in vec4 Position;
|
|
||||||
layout(location = 1) in vec2 TexCoord;
|
|
||||||
layout(location = 0) out vec2 vTexCoord;
|
|
||||||
layout(location = 1) out float colorPhase;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
gl_Position = global.MVP * Position;
|
|
||||||
vTexCoord = TexCoord;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma stage fragment
|
|
||||||
layout(location = 0) in vec2 vTexCoord;
|
|
||||||
layout(location = 1) in float colorPhase;
|
|
||||||
layout(location = 0) out vec4 FragColor;
|
|
||||||
layout(set = 0, binding = 2) uniform sampler2D Source;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
vec4 c = texture(Source, vTexCoord.xy);
|
|
||||||
|
|
||||||
// Extract the chroma, level, and emphasis from the normalized RGB triplet
|
|
||||||
int color = int(floor((c.r * 15.0) + 0.5));
|
|
||||||
int level = int(floor((c.g * 3.0) + 0.5));
|
|
||||||
int emphasis = int(floor((c.b * 7.0) + 0.5));
|
|
||||||
|
|
||||||
vec3 out_color = MakeRGBColor(emphasis, level, color);
|
|
||||||
FragColor = vec4(out_color, 1.0);
|
|
||||||
}
|
|
|
@ -5,6 +5,9 @@
|
||||||
Ported from Bisqwit's C++ NES Palette Generator
|
Ported from Bisqwit's C++ NES Palette Generator
|
||||||
https://forums.nesdev.com/viewtopic.php?p=85060#p85060
|
https://forums.nesdev.com/viewtopic.php?p=85060#p85060
|
||||||
|
|
||||||
|
Hue Preserve Clip functions ported from Drag's Palette Generator
|
||||||
|
http://drag.wootest.net/misc/palgen.html
|
||||||
|
|
||||||
Use with Nestopia or FCEUmm libretro cores with the palette set to 'raw'.
|
Use with Nestopia or FCEUmm libretro cores with the palette set to 'raw'.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -24,6 +27,8 @@ layout(push_constant) uniform Push
|
||||||
float nes_contrast;
|
float nes_contrast;
|
||||||
float nes_brightness;
|
float nes_brightness;
|
||||||
float nes_gamma;
|
float nes_gamma;
|
||||||
|
float nes_sony_matrix;
|
||||||
|
float nes_clip_method;
|
||||||
} params;
|
} params;
|
||||||
|
|
||||||
#pragma parameter nes_saturation "Saturation" 1.0 0.0 5.0 0.05
|
#pragma parameter nes_saturation "Saturation" 1.0 0.0 5.0 0.05
|
||||||
|
@ -31,12 +36,19 @@ layout(push_constant) uniform Push
|
||||||
#pragma parameter nes_contrast "Contrast" 1.0 0.0 2.0 0.05
|
#pragma parameter nes_contrast "Contrast" 1.0 0.0 2.0 0.05
|
||||||
#pragma parameter nes_brightness "Brightness" 1.0 0.0 2.0 0.05
|
#pragma parameter nes_brightness "Brightness" 1.0 0.0 2.0 0.05
|
||||||
#pragma parameter nes_gamma "Gamma" 1.8 1.0 2.5 0.05
|
#pragma parameter nes_gamma "Gamma" 1.8 1.0 2.5 0.05
|
||||||
|
#pragma parameter nes_sony_matrix "Sony CXA2025AS US colors" 0.0 0.0 1.0 1.0
|
||||||
|
#pragma parameter nes_clip_method "Palette clipping method" 0.0 0.0 2.0 1.0
|
||||||
|
|
||||||
#define saturation params.nes_saturation
|
#define saturation params.nes_saturation
|
||||||
#define hue params.nes_hue
|
#define hue params.nes_hue
|
||||||
#define contrast params.nes_contrast
|
#define contrast params.nes_contrast
|
||||||
#define brightness params.nes_brightness
|
#define brightness params.nes_brightness
|
||||||
#define gamma params.nes_gamma
|
#define gamma params.nes_gamma
|
||||||
|
#define nes_sony_matrix params.nes_sony_matrix
|
||||||
|
#define nes_clip_method params.nes_clip_method
|
||||||
|
|
||||||
|
//comment the define out to use the "common" conversion matrix instead of the FCC sanctioned one
|
||||||
|
#define USE_FCC_MATRIX
|
||||||
|
|
||||||
bool wave (int p, int color)
|
bool wave (int p, int color)
|
||||||
{
|
{
|
||||||
|
@ -48,6 +60,65 @@ float gammafix (float f)
|
||||||
return f < 0.0 ? 0.0 : pow(f, 2.2 / gamma);
|
return f < 0.0 ? 0.0 : pow(f, 2.2 / gamma);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec3 huePreserveClipDarken(float r, float g, float b)
|
||||||
|
{
|
||||||
|
float ratio = 1.0;
|
||||||
|
if ((r > 1.0) || (g > 1.0) || (b > 1.0))
|
||||||
|
{
|
||||||
|
float max = r;
|
||||||
|
if (g > max)
|
||||||
|
max = g;
|
||||||
|
if (b > max)
|
||||||
|
max = b;
|
||||||
|
ratio = 1.0 / max;
|
||||||
|
}
|
||||||
|
|
||||||
|
r *= ratio;
|
||||||
|
g *= ratio;
|
||||||
|
b *= ratio;
|
||||||
|
|
||||||
|
r = clamp(r, 0.0, 1.0);
|
||||||
|
g = clamp(g, 0.0, 1.0);
|
||||||
|
b = clamp(b, 0.0, 1.0);
|
||||||
|
|
||||||
|
return vec3(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 huePreserveClipDesaturate(float r, float g, float b)
|
||||||
|
{
|
||||||
|
float l = (.299 * r) + (0.587 * g) + (0.114 * b);
|
||||||
|
bool ovr = false;
|
||||||
|
float ratio = 1.0;
|
||||||
|
|
||||||
|
if ((r > 1.0) || (g > 1.0) || (b > 1.0))
|
||||||
|
{
|
||||||
|
ovr = true;
|
||||||
|
float max = r;
|
||||||
|
if (g > max) max = g;
|
||||||
|
if (b > max) max = b;
|
||||||
|
ratio = 1.0 / max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ovr)
|
||||||
|
{
|
||||||
|
r -= 1.0;
|
||||||
|
g -= 1.0;
|
||||||
|
b -= 1.0;
|
||||||
|
r *= ratio;
|
||||||
|
g *= ratio;
|
||||||
|
b *= ratio;
|
||||||
|
r += 1.0;
|
||||||
|
g += 1.0;
|
||||||
|
b += 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = clamp(r, 0.0, 1.0);
|
||||||
|
g = clamp(g, 0.0, 1.0);
|
||||||
|
b = clamp(b, 0.0, 1.0);
|
||||||
|
|
||||||
|
return vec3(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
vec3 MakeRGBColor(int emphasis, int level, int color)
|
vec3 MakeRGBColor(int emphasis, int level, int color)
|
||||||
{
|
{
|
||||||
float y = 0.0;
|
float y = 0.0;
|
||||||
|
@ -58,6 +129,8 @@ vec3 MakeRGBColor(int emphasis, int level, int color)
|
||||||
float g = 0.0;
|
float g = 0.0;
|
||||||
float b = 0.0;
|
float b = 0.0;
|
||||||
|
|
||||||
|
float yiq2rgb[6];
|
||||||
|
|
||||||
// Color 0xE and 0xF are black
|
// Color 0xE and 0xF are black
|
||||||
level = (color < 14) ? level : 1;
|
level = (color < 14) ? level : 1;
|
||||||
|
|
||||||
|
@ -104,19 +177,71 @@ vec3 MakeRGBColor(int emphasis, int level, int color)
|
||||||
i *= saturation;
|
i *= saturation;
|
||||||
q *= saturation;
|
q *= saturation;
|
||||||
|
|
||||||
// Convert YIQ into RGB according to FCC-sanctioned conversion matrix.
|
if (nes_sony_matrix > 0.5)
|
||||||
r = clamp((1.0 * gammafix(y + 0.946882 * i + 0.623557 * q)), 0, 1.0);
|
{
|
||||||
g = clamp((1.0 * gammafix(y + -0.274788 * i + -0.635691 * q)), 0, 1.0);
|
// Sony CXA2025AS US conversion matrix
|
||||||
b = clamp((1.0 * gammafix(y + -1.108545 * i + 1.709007 * q)), 0, 1.0);
|
yiq2rgb[0] = 1.630;
|
||||||
|
yiq2rgb[1] = 0.317;
|
||||||
|
yiq2rgb[2] = -0.378;
|
||||||
|
yiq2rgb[3] = -0.466;
|
||||||
|
yiq2rgb[4] = -1.089;
|
||||||
|
yiq2rgb[5] = 1.677;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef USE_FCC_MATRIX
|
||||||
|
// FCC sanctioned conversion matrix
|
||||||
|
yiq2rgb[0] = 0.946882;
|
||||||
|
yiq2rgb[1] = 0.623557;
|
||||||
|
yiq2rgb[2] = -0.274788;
|
||||||
|
yiq2rgb[3] = -0.635691;
|
||||||
|
yiq2rgb[4] = -1.108545;
|
||||||
|
yiq2rgb[5] = 1.709007;
|
||||||
|
#else
|
||||||
|
// commonly used conversion matrix
|
||||||
|
yiq2rgb[0] = 0.956;
|
||||||
|
yiq2rgb[1] = 0.621;
|
||||||
|
yiq2rgb[2] = -0.272;
|
||||||
|
yiq2rgb[3] = -0.647;
|
||||||
|
yiq2rgb[4] = -1.105;
|
||||||
|
yiq2rgb[5] = 1.702;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
return vec3(r,g,b);
|
// Convert YIQ into RGB according to selected conversion matrix
|
||||||
|
r = gammafix(y + yiq2rgb[0] * i + yiq2rgb[1] * q);
|
||||||
|
g = gammafix(y + yiq2rgb[2] * i + yiq2rgb[3] * q);
|
||||||
|
b = gammafix(y + yiq2rgb[4] * i + yiq2rgb[5] * q);
|
||||||
|
|
||||||
|
vec3 corrected_rgb;
|
||||||
|
|
||||||
|
// Apply desired clipping method to out-of-gamut colors.
|
||||||
|
if (nes_clip_method < 0.5)
|
||||||
|
{
|
||||||
|
//If a channel is out of range (> 1.0), it's simply clamped to 1.0. This may change hue, saturation, and/or lightness.
|
||||||
|
r = clamp(r, 0.0, 1.0);
|
||||||
|
g = clamp(g, 0.0, 1.0);
|
||||||
|
b = clamp(b, 0.0, 1.0);
|
||||||
|
corrected_rgb = vec3(r, g, b);
|
||||||
|
}
|
||||||
|
else if (nes_clip_method == 1.0)
|
||||||
|
{
|
||||||
|
//If any channels are out of range, the color is darkened until it is completely in range.
|
||||||
|
corrected_rgb = huePreserveClipDarken(r, g, b);
|
||||||
|
}
|
||||||
|
else if (nes_clip_method == 2.0)
|
||||||
|
{
|
||||||
|
//If any channels are out of range, the color is desaturated towards the luminance it would've had.
|
||||||
|
corrected_rgb = huePreserveClipDesaturate(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return corrected_rgb;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma stage vertex
|
#pragma stage vertex
|
||||||
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 colorPhase;
|
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
|
@ -126,7 +251,6 @@ void main()
|
||||||
|
|
||||||
#pragma stage fragment
|
#pragma stage fragment
|
||||||
layout(location = 0) in vec2 vTexCoord;
|
layout(location = 0) in vec2 vTexCoord;
|
||||||
layout(location = 1) in float colorPhase;
|
|
||||||
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;
|
||||||
|
|
||||||
|
|
12
presets/nes-color-decoder+colorimetry+pixellate.slangp
Normal file
12
presets/nes-color-decoder+colorimetry+pixellate.slangp
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
shaders = "3"
|
||||||
|
shader0 = ../misc/nes-color-decoder.slang
|
||||||
|
shader1 = ../misc/colorimetry.slang
|
||||||
|
shader2 = ../retro/shaders/pixellate.slang
|
||||||
|
|
||||||
|
filter_linear0 = "false"
|
||||||
|
scale_type0 = "source"
|
||||||
|
scale0 = "1.000000"
|
||||||
|
|
||||||
|
filter_linear1 = "false"
|
||||||
|
scale_type1 = "source"
|
||||||
|
scale1 = "1.000000"
|
Loading…
Reference in a new issue