mirror of
https://github.com/cemu-project/cemu_graphic_packs.git
synced 2024-12-25 01:11:49 +01:00
4c849d9d51
CRT filter, all credit to Timothy Lottes - PD Just for fun to see if it could be adapted, I'm not planning on fixing issues
283 lines
8.1 KiB
Plaintext
283 lines
8.1 KiB
Plaintext
#version 420
|
|
#extension GL_ARB_texture_gather : enable
|
|
#extension GL_ARB_separate_shader_objects : enable
|
|
// shader d2a97b2fb99411a5
|
|
// CRT filter - Just for fun, probably won't be fixing any issues..
|
|
|
|
|
|
layout(binding = 0) uniform sampler2D textureUnitPS0;// Tex0 addr 0xf4481800 res 768x384x1 dim 1 tm: 4 format 001a compSel: 0 1 2 3 mipView: 0x0 (num 0x1) sliceView: 0x0 (num 0x1) Sampler0 ClampX/Y/Z: 2 2 2 border: 1
|
|
layout(location = 0) in vec4 passParameterSem0;
|
|
layout(location = 0) out vec4 passPixelColor0;
|
|
uniform vec2 uf_fragCoordScale;
|
|
//
|
|
// PUBLIC DOMAIN CRT STYLED SCAN-LINE SHADER
|
|
//
|
|
// by Timothy Lottes
|
|
//
|
|
// This is more along the style of a really good CGA arcade monitor.
|
|
// With RGB inputs instead of NTSC.
|
|
// The shadow mask example has the mask rotated 90 degrees for less chromatic aberration.
|
|
//
|
|
// Left it unoptimized to show the theory behind the algorithm.
|
|
//
|
|
// It is an example what I personally would want as a display option for pixel art games.
|
|
// Please take and use, change, or whatever.
|
|
//
|
|
//old contrasty, or just copy paste clarity
|
|
const float gamma = 0.95; // 1.0 is neutral
|
|
const float exposure = 1.2; // 1.0 is neutral, first lessen to avoid truncation prob around .25 for radeon.
|
|
const float vibrance = 0.175; // 0.0 is neutral
|
|
const float crushContrast = 0.01; // 0.0 is neutral. loss of shadow detail
|
|
//const float postExposure = 1.16; // 1.0 is neutral, then slightly raise exposure back up.
|
|
|
|
vec3 contrasty(vec3 colour) {
|
|
vec3 fColour = (colour.xyz);
|
|
|
|
fColour = clamp(exposure * fColour, 0.0, 1.0);
|
|
fColour = pow(fColour, vec3(1.0 / gamma));
|
|
float luminance = fColour.r*0.299 + fColour.g*0.587 + fColour.b*0.114;
|
|
float mn = min(min(fColour.r, fColour.g), fColour.b);
|
|
float mx = max(max(fColour.r, fColour.g), fColour.b);
|
|
float sat = (1.0 - (mx - mn)) * (1.0 - mx) * luminance * 5.0;
|
|
vec3 lightness = vec3((mn + mx) / 2.0);
|
|
// vibrance
|
|
fColour = mix(fColour, mix(fColour, lightness, -vibrance), sat);
|
|
fColour = max(vec3(0.0), fColour - vec3(crushContrast));
|
|
return fColour;
|
|
}
|
|
#define RGBA(r, g, b, a) vec4(float(r)/255.0, float(g)/255.0, float(b)/255.0, float(a)/255.0)
|
|
|
|
const vec3 kBackgroundColor = RGBA(0x00, 0x60, 0xb8, 0xff).rgb; // medium-blue sky
|
|
//const vec3 kBackgroundColor = RGBA(0xff, 0x00, 0xff, 0xff).rgb; // test magenta
|
|
//const vec3 kBackgroundColor = RGBA(0x50, 0x50, 0x50, 0xff).rgb;
|
|
// Emulated input resolution.
|
|
#if 1
|
|
// Fix resolution to set amount.
|
|
//768x384x1 // Note: 256x224 is the most common resolution of the SNES, and that of Super Mario World.
|
|
vec2 res = vec2(
|
|
textureSize(textureUnitPS0, 0).x / 1.0,
|
|
textureSize(textureUnitPS0, 0).y / 2.0
|
|
);
|
|
#else
|
|
// Optimize for resize.
|
|
vec2 res = textureSize(textureUnitPS0, 0) / 6.0;
|
|
#endif
|
|
|
|
// Hardness of scanline.
|
|
// -8.0 = soft
|
|
// -16.0 = medium
|
|
float sHardScan = -8.0;
|
|
|
|
// Hardness of pixels in scanline.
|
|
// -2.0 = soft
|
|
// -4.0 = hard
|
|
const float kHardPix = -2.0;
|
|
|
|
// Display warp.
|
|
// 0.0 = none
|
|
// 1.0 / 8.0 = extreme
|
|
const vec2 kWarp = vec2(1.0 / 64.0, 1.0 / 48.0);
|
|
//const vec2 kWarp = vec2(0);
|
|
|
|
// Amount of shadow mask.
|
|
float kMaskDark = 2.0;
|
|
float kMaskLight = 0.5;
|
|
|
|
//------------------------------------------------------------------------
|
|
/*
|
|
// sRGB to Linear.
|
|
// Assuing using sRGB typed textures this should not be needed.
|
|
float toLinear1(float c) {
|
|
return (c <= 0.04045) ?
|
|
(c / 12.92) :
|
|
pow((c + 0.055) / 1.055, 2.4);
|
|
}
|
|
vec3 toLinear(vec3 c) {
|
|
return vec3(toLinear1(c.r), toLinear1(c.g), toLinear1(c.b));
|
|
}
|
|
|
|
// Linear to sRGB.
|
|
// Assuing using sRGB typed textures this should not be needed.
|
|
float toSrgb1(float c) {
|
|
return(c < 0.0031308 ?
|
|
(c * 12.92) :
|
|
(1.055 * pow(c, 0.41666) - 0.055));
|
|
}
|
|
vec3 toSrgb(vec3 c) {
|
|
return vec3(toSrgb1(c.r), toSrgb1(c.g), toSrgb1(c.b));
|
|
}
|
|
*/
|
|
// Nearest emulated sample given floating point position and texel offset.
|
|
// Also zero's off screen.
|
|
vec4 fetch(vec2 pos, vec2 off)
|
|
{
|
|
pos = floor(pos * res + off) / res;
|
|
if (max(abs(pos.x - 0.5), abs(pos.y - 0.5)) > 0.5)
|
|
return vec4(vec3(0.0), 0.0);
|
|
|
|
//vec4 sampledColor = texture(textureUnitPS0, pos.xy, -16.0);
|
|
vec4 sampledColor = texture(textureUnitPS0, pos.xy,0);
|
|
sampledColor = vec4(
|
|
(sampledColor.rgb * sampledColor.a) +
|
|
(kBackgroundColor * (1.0 - sampledColor.a)),
|
|
1.0
|
|
);
|
|
|
|
return vec4(
|
|
//toLinear(sampledColor.rgb),
|
|
//sampledColor.a
|
|
sampledColor.rgba
|
|
);
|
|
}
|
|
|
|
// Distance in emulated pixels to nearest texel.
|
|
vec2 dist(vec2 pos) {
|
|
pos = pos * res;
|
|
return -((pos - floor(pos)) - vec2(0.5));
|
|
}
|
|
|
|
// 1D Gaussian.
|
|
float gaus(float pos, float scale) {
|
|
return exp2(scale * pos * pos);
|
|
}
|
|
|
|
// 3-tap Gaussian filter along horz line.
|
|
vec3 horz3(vec2 pos, float off)
|
|
{
|
|
vec3 b = fetch(pos, vec2(-1.0, off)).rgb;
|
|
vec3 c = fetch(pos, vec2(0.0, off)).rgb;
|
|
vec3 d = fetch(pos, vec2(+1.0, off)).rgb;
|
|
float dst = dist(pos).x;
|
|
// Convert distance to weight.
|
|
float scale = kHardPix;
|
|
float wb = gaus(dst - 1.0, scale);
|
|
float wc = gaus(dst + 0.0, scale);
|
|
float wd = gaus(dst + 1.0, scale);
|
|
// Return filtered sample.
|
|
return (b * wb + c * wc + d * wd) / (wb + wc + wd);
|
|
}
|
|
|
|
// 5-tap Gaussian filter along horz line.
|
|
vec3 horz5(vec2 pos, float off)
|
|
{
|
|
vec3 a = fetch(pos, vec2(-2.0, off)).rgb;
|
|
vec3 b = fetch(pos, vec2(-1.0, off)).rgb;
|
|
vec3 c = fetch(pos, vec2(0.0, off)).rgb;
|
|
vec3 d = fetch(pos, vec2(+1.0, off)).rgb;
|
|
vec3 e = fetch(pos, vec2(+2.0, off)).rgb;
|
|
float dst = dist(pos).x;
|
|
// Convert distance to weight.
|
|
float scale = kHardPix;
|
|
float wa = gaus(dst - 2.0, scale);
|
|
float wb = gaus(dst - 1.0, scale);
|
|
float wc = gaus(dst + 0.0, scale);
|
|
float wd = gaus(dst + 1.0, scale);
|
|
float we = gaus(dst + 2.0, scale);
|
|
// Return filtered sample.
|
|
return (a * wa + b * wb + c * wc + d * wd + e * we) / (wa + wb + wc + wd + we);
|
|
}
|
|
|
|
// Return scanline weight.
|
|
float scan(vec2 pos, float off) {
|
|
float dst = dist(pos).y;
|
|
return gaus(dst + off, sHardScan);
|
|
}
|
|
|
|
// Allow nearest three lines to effect pixel.
|
|
vec3 tri(vec2 pos)
|
|
{
|
|
vec3 a = horz3(pos, -1.0);
|
|
vec3 b = horz5(pos, 0.0);
|
|
vec3 c = horz3(pos, +1.0);
|
|
float wa = scan(pos, -1.0);
|
|
float wb = scan(pos, 0.0);
|
|
float wc = scan(pos, +1.0);
|
|
return a * wa + b * wb + c * wc;
|
|
}
|
|
|
|
// Distortion of scanlines, and end of screen alpha.
|
|
vec2 warp(vec2 pos)
|
|
{
|
|
pos = pos * 2.0 - 1.0;
|
|
pos *= vec2(
|
|
1.0 + (pos.y * pos.y) * kWarp.x,
|
|
1.0 + (pos.x * pos.x) * kWarp.y
|
|
);
|
|
return pos * 0.5 + 0.5;
|
|
}
|
|
|
|
// Shadow mask.
|
|
vec3 mask(vec2 pos)
|
|
{
|
|
pos.x += pos.y * 3.0;
|
|
vec3 mask = vec3(kMaskDark, kMaskDark, kMaskDark);
|
|
pos.x = fract(pos.x / 6.0);
|
|
if (pos.x < 0.333)
|
|
mask.r = kMaskLight;
|
|
else if (pos.x < 0.666)
|
|
mask.g = kMaskLight;
|
|
else
|
|
mask.b = kMaskLight;
|
|
return mask;
|
|
}
|
|
|
|
// Draw dividing bars.
|
|
float bar(float pos, float bar) {
|
|
pos -= bar;
|
|
return (pos * pos < 4.0) ? 0.0 : 1.0;
|
|
}
|
|
|
|
float rand(vec2 co) {
|
|
return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
|
}
|
|
int clampFI32(int v)
|
|
{
|
|
if( v == 0x7FFFFFFF )
|
|
return floatBitsToInt(1.0);
|
|
else if( v == 0xFFFFFFFF )
|
|
return floatBitsToInt(0.0);
|
|
return floatBitsToInt(clamp(intBitsToFloat(v), 0.0, 1.0));
|
|
}
|
|
float mul_nonIEEE(float a, float b){ if( a == 0.0 || b == 0.0 ) return 0.0; return a*b; }
|
|
void main()
|
|
{
|
|
vec4 R0f = vec4(0.0);
|
|
float backupReg0f, backupReg1f, backupReg2f, backupReg3f, backupReg4f;
|
|
vec4 PV0f = vec4(0.0), PV1f = vec4(0.0);
|
|
float PS0f = 0.0, PS1f = 0.0;
|
|
vec4 tempf = vec4(0.0);
|
|
float tempResultf;
|
|
int tempResulti;
|
|
ivec4 ARi = ivec4(0);
|
|
bool predResult = true;
|
|
vec3 cubeMapSTM;
|
|
int cubeMapFaceId;
|
|
R0f = passParameterSem0;
|
|
//R0f.xyzw = (texture(textureUnitPS0, R0f.xy).xyzw);
|
|
// export
|
|
//passPixelColor0 = vec4(R0f.x, R0f.y, R0f.z, R0f.w);
|
|
|
|
vec2 pos = warp(gl_FragCoord.xy / textureSize(textureUnitPS0, 0));//iResolution.xy);
|
|
vec4 unmodifiedColor = fetch(pos, vec2(0));
|
|
|
|
// Unmodified.
|
|
if (passPixelColor0.x > textureSize(textureUnitPS0, 0).x * 0.333)
|
|
{
|
|
passPixelColor0.rgb = contrasty(unmodifiedColor.rgb);
|
|
}
|
|
else
|
|
{
|
|
if (passPixelColor0.x > textureSize(textureUnitPS0, 0).x * 0.666) {
|
|
sHardScan = -12.0;
|
|
kMaskDark = kMaskLight = 1.0;
|
|
}
|
|
passPixelColor0.rgb = tri(pos) * mask(gl_FragCoord.xy);
|
|
}
|
|
//passPixelColor0.rgb *= bar(gl_FragCoord.x, textureSize(textureUnitPS0, 0).x * 0.333) * bar(gl_FragCoord.x, textureSize(textureUnitPS0, 0).x * 0.666);
|
|
//passPixelColor0 = vec4(
|
|
//toSrgb(passPixelColor0.rgb),
|
|
//1.0
|
|
//);
|
|
passPixelColor0 = vec4(passPixelColor0.rgb, 1.0);
|
|
}
|