136 lines
3.8 KiB
GLSL
136 lines
3.8 KiB
GLSL
// #type vertex
|
|
#version 460 core
|
|
|
|
layout (location = 0) in vec2 aPos;
|
|
layout (location = 1) in vec2 aUV;
|
|
|
|
layout (location = 0) out vec2 oUV;
|
|
|
|
void main()
|
|
{
|
|
oUV = aUV;
|
|
gl_Position = vec4(aPos, 0.0, 1.0);
|
|
}
|
|
|
|
// #type fragment
|
|
#version 460 core
|
|
|
|
precision highp float;
|
|
|
|
// ConsoleColor vec3 palette (normalized)
|
|
const vec3 ConsoleColorVec3[16] = vec3[](
|
|
vec3(0.0, 0.0, 0.0), // Black
|
|
vec3(0.0, 0.0, 0.5), // DarkBlue
|
|
vec3(0.0, 0.5, 0.0), // DarkGreen
|
|
vec3(0.0, 0.5, 0.5), // DarkCyan
|
|
vec3(0.5, 0.0, 0.0), // DarkRed
|
|
vec3(0.5, 0.0, 0.5), // DarkMagenta
|
|
vec3(0.5, 0.5, 0.0), // DarkYellow
|
|
vec3(0.5, 0.5, 0.5), // Gray
|
|
vec3(0.25, 0.25, 0.25), // DarkGray
|
|
vec3(0.0, 0.0, 1.0), // Blue
|
|
vec3(0.0, 1.0, 0.0), // Green
|
|
vec3(0.0, 1.0, 1.0), // Cyan
|
|
vec3(1.0, 0.0, 0.0), // Red
|
|
vec3(1.0, 0.0, 1.0), // Magenta
|
|
vec3(1.0, 1.0, 0.0), // Yellow
|
|
vec3(1.0, 1.0, 1.0) // White
|
|
);
|
|
|
|
vec3 srgbToLinear(vec3 color) {
|
|
return mix(color / 12.92, pow((color + 0.055) / 1.055, vec3(2.4)), step(0.04045, color));
|
|
}
|
|
|
|
vec3 rgbToXyz(vec3 color) {
|
|
const mat3 rgbToXyzMatrix = mat3(
|
|
0.4124564, 0.3575761, 0.1804375,
|
|
0.2126729, 0.7151522, 0.0721750,
|
|
0.0193339, 0.1191920, 0.9503041
|
|
);
|
|
return rgbToXyzMatrix * color;
|
|
}
|
|
|
|
vec3 xyzToLab(vec3 xyz) {
|
|
const vec3 whitePoint = vec3(0.95047, 1.0, 1.08883); // D65 white point
|
|
xyz = xyz / whitePoint;
|
|
|
|
vec3 f = mix(pow(xyz, vec3(1.0 / 3.0)), 7.787 * xyz + 16.0 / 116.0, step(0.008856, xyz));
|
|
return vec3(
|
|
(116.0 * f.y) - 16.0,
|
|
500.0 * (f.x - f.y),
|
|
200.0 * (f.y - f.z)
|
|
);
|
|
}
|
|
|
|
vec3 rgb2hsv(vec3 c)
|
|
{
|
|
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
|
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
|
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
|
|
|
|
float d = q.x - min(q.w, q.y);
|
|
float e = 1.0e-10;
|
|
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
|
}
|
|
|
|
float perceptualColorDistance(vec3 color1, vec3 color2) {
|
|
vec3 lab1 = xyzToLab(rgbToXyz(srgbToLinear(color1)));
|
|
vec3 lab2 = xyzToLab(rgbToXyz(srgbToLinear(color2)));
|
|
vec3 delta = lab1 - lab2;
|
|
|
|
return dot(delta, delta);
|
|
}
|
|
|
|
int findMostPerceptuallyAccurateColor(vec3 color) {
|
|
int bestMatchIndex = 0;
|
|
float minDistance = perceptualColorDistance(color, ConsoleColorVec3[0]);
|
|
|
|
for (int i = 1; i < 16; i++) {
|
|
float currentDistance = perceptualColorDistance(color, ConsoleColorVec3[i]);
|
|
if (currentDistance < minDistance) {
|
|
minDistance = currentDistance;
|
|
bestMatchIndex = i;
|
|
}
|
|
}
|
|
|
|
return bestMatchIndex;
|
|
}
|
|
|
|
// Enhanced luminosity calculation considering human perception
|
|
float calculatePerceptualLuminance(vec3 color) {
|
|
// BT.709 luminance coefficients with slight adjustment
|
|
return pow(
|
|
0.2126 * pow(color.r, 2.2) +
|
|
0.7152 * pow(color.g, 2.2) +
|
|
0.0722 * pow(color.b, 2.2),
|
|
1.0 / 2.2
|
|
);
|
|
}
|
|
|
|
// Dithering function to reduce color banding
|
|
float interleavedGradientNoise(vec2 pixel) {
|
|
return fract(52.982919 * fract(0.06711056 * pixel.x + 0.00583715 * pixel.y));
|
|
}
|
|
|
|
uniform sampler2D uInputTexture;
|
|
|
|
layout (location = 0) in vec2 iUV;
|
|
layout (location = 0) out vec4 FragColor;
|
|
|
|
void main() {
|
|
vec3 pixelColor = texture(uInputTexture, iUV).rgb;
|
|
|
|
// Find most perceptually accurate console color
|
|
int colorIndex = findMostPerceptuallyAccurateColor(pixelColor);
|
|
|
|
// Calculate perceptual luminance with gamma correction
|
|
float luminance = calculatePerceptualLuminance(pixelColor);
|
|
|
|
// Output with high precision color mapping
|
|
FragColor = vec4(
|
|
luminance, // Red: Perceptual luminance
|
|
float(colorIndex) / 15.0, // Green: Normalized color index
|
|
0.0, // Blue: Unused
|
|
1.0 // Alpha
|
|
);
|
|
} |