@torridgristle Could you post the new curvature shader code? I’d try to implement it myself, but I only followed/understood half/fourth of what you said.
I’ve been wanting to check it out, and I’ve been watching both GLSL and slang repos.
@torridgristle Could you post the new curvature shader code? I’d try to implement it myself, but I only followed/understood half/fourth of what you said.
I’ve been wanting to check it out, and I’ve been watching both GLSL and slang repos.
I think this is pretty much nailing it.
Reference shot, Sony PVM (Can anyone confirm this is a PVM and not a regular Trinitron? Kinda looks like a regular Trinitron to me):
My shader settings (photos taken with my phone camera):
Here are the settings. I’m also using black frame insertion and have my backlight cranked up, so brightness and gamma will look off otherwise (hence the photos taken with my phone camera). TVL is around 400-500, slightly higher than the reference shot (which makes me think the reference shot is actually a regular Trinitron TV and not a PVM)
alias0 = ""
BRIGHTNESS = "1.750000"
filter_linear0 = "false"
float_framebuffer0 = "false"
GAMMA_INPUT = "2.700000"
GAMMA_OUTPUT = "2.400000"
GLOW_DIFFUSION = "0.050000"
GLOW_HALATION = "0.100000"
GLOW_HEIGHT = "0.500000"
GLOW_WIDTH = "0.500000"
MASK_COLORS = "2.000000"
MASK_SIZE = "1.000000"
MASK_STRENGTH = "0.150000"
mipmap_input0 = "false"
parameters = "SHARPNESS_IMAGE;SHARPNESS_EDGES;GLOW_WIDTH;GLOW_HEIGHT;GLOW_HALATION;GLOW_DIFFUSION;MASK_COLORS;MASK_STRENGTH;MASK_SIZE;SCANLINE_SIZE_MIN;SCANLINE_SIZE_MAX;SCANLINE_SHAPE;SCANLINE_OFFSET;GAMMA_INPUT;GAMMA_OUTPUT;BRIGHTNESS"
SCANLINE_OFFSET = "0.000000"
SCANLINE_SHAPE = "1.000000"
SCANLINE_SIZE_MAX = "1.500000"
SCANLINE_SIZE_MIN = "0.500000"
shader0 = "C:\Program Files\RetroArch\shaders\shaders_glsl\crt\shaders\crt-aperture.glsl"
shaders = "1"
SHARPNESS_EDGES = "2.000000"
SHARPNESS_IMAGE = "1.000000"
srgb_framebuffer0 = "false"
wrap_mode0 = "clamp_to_border"
EDIT: Nothing to see here, lol.
Nice, screenshots.
Some experiments with “slot mask” and minimal diffusion bloom.
1 https://pasteboard.co/Ij8nHld.png
2 https://pasteboard.co/Ij8oePX.png
That new curvature is looking great, not the biggest fan of EWA though (it makes makes me feel as I’ve rubbed Vaseline on my screen), lol. Is it possible to get version of this in Lottes, Hyllian, Apeture, or something (doesn’t necessarily need to be part of the repo)? I’d just really like to see in nearest neighbor.
Regardless, thanks for all your hard work guys!
I put it into ewa because I try not to change people’s code too much without their permission/support. The curvature block from ewa can drop right into lottes, though, as I purposely used the same function names, etc.
That’s understandable, thank you for the reply.
I’ll try to port it over later, if I can’t get it to work I may message you with whatever I have done to try and trouble-shoot. If that’s alright with you?
EDIT: Figured it out, you’re definitely right about it just dropping in. The only change I had to do besides just dropping the code in, was move vec2 Distortion into the Warp code (this was for GLSL though, probably could’ve left it be for Slang).
After all these years, CRT-Geom still looks fantastic. In fact, only CRT-Aperture is better in terms of ease of use and objective picture quality, IMO. I prefer a slightly sharper image than what CRT-Geom provides on the middle sharpness setting, but I don’t like how sharp it is on the sharpest setting, either. Still, CRT-Geom is a good alternative to CRT-Aperture if you prefer a slightly softer/lower-res look.
Disclaimer: brightness and gamma are different in person because I’m using black frame insertion and maxing out my backlight. A backlight/brightness setting of 50% on most LED-lit LCDs should give an approximate idea of what I’m seeing on my display.
CRT-Geom with my preferred settings:
CRT-Aperture with my preferred settings:
Settings, CRT-Geom:
alias0 = ""
cornersize = "0.001000"
cornersmooth = "1000.000000"
CRTgamma = "2.500000"
CURVATURE = "0.000000"
d = "1.600000"
DOTMASK = "0.300000"
filter_linear0 = "false"
float_framebuffer0 = "false"
interlace_detect = "1.000000"
lum = "0.650000"
mipmap_input0 = "false"
monitorgamma = "2.200000"
overscan_x = "100.000000"
overscan_y = "100.000000"
parameters = "CRTgamma;monitorgamma;d;CURVATURE;R;cornersize;cornersmooth;x_tilt;y_tilt;overscan_x;overscan_y;DOTMASK;SHARPER;scanline_weight;lum;interlace_detect"
R = "6.599996"
scanline_weight = "0.300000"
shader0 = "C:\Program Files\RetroArch\shaders\shaders_glsl\crt\shaders\crt-geom.glsl"
shaders = "1"
SHARPER = "2.000000"
srgb_framebuffer0 = "false"
wrap_mode0 = "clamp_to_border"
x_tilt = "0.000000"
y_tilt = "0.000000"
Settings, CRT-Aperture:
See my previous post.
Any programmers in this topic that could take a look at this issue?
Thought I would bring it up here since the main thing that bothers me about it is that CRT shaders end up looking pretty bad when you use them on rotated vertical games in FB Neo. This comparison shows it well: http://screenshotcomparison.com/comparison/701
With the mouse off the image it shows MAME using crt-guest-dr-venom with the shader’s TATE mode on. Mousing over the image shows FB Neo with TATE off. The first one looks good because the rotation is done using the MAME core’s software rotation. That doesn’t also rotate shaders, but if the shader has a rotation option, it works pretty well. FB Neo uses RetroArch’s rotation API. It rotates shaders, but for some reason it looks bad. Something with the scaling or alignment maybe?
Hello, hope someone can help me.
Crt-royale is said to be 4k friendly, however there is noticeable difference between 1080 and 4k resolutions and not in 4k favor. With 4k everything looks more blocky with less color bleed, overall shader intensity appears to be reduced. At 1080 image looks infinitely better and I can achieve similar results with 4k display if I force 1080 res in retroarch settings. So perhaps there are some settings in shader itself I need to change to make 4k comparable?
This pic from this thread on retrogameboards caught my atention.
Can you guys elaborate on why it looks so green or suggest a preset that comes closer to what colors on the CRT look like? I just booted it on retroarch to confirm it looks like the capture (it looks even greener on blastem core).
This is the “ntsc-320px-gauss-scanline.slangp” shader preset (in the ntsc directory). I does really look like that old garbage TV my cousin had many years ago to play N64.
(I’m using a 1440p display, so it might look wrong when viewing on 1080p screens.)
First post here, forgive me if my nomenclature isn’t entirely accurate.
I’ve noticed that CRT shader presets that integrate resolve.slang cut off some of the top portion of the screen as seen below. I noticed it a few days ago, and used the process of elimination to discover it was resolve.slang that was causing it. I simply loaded up crtglow_gauss_ntsc_3phase and disabled the 8 passes one by one to find the culprit.
Would anyone happen to know if this cutting off of a small portion of the top of the screen is by design? Is it intended, or have I done something wrong on my end? I played around with some or the parameters that are exposed, and none had an effect.
It’s not a gamebreaker, and I’ve played on enough CRTs back in the day to know that the curvature and bezel crop things off anyway, but I’m just wondering if this cropping of the top when using CRT presets that include resolve.slang is intended or not.Also, I didn’t want to make a separate topic for this as I’ve been lurking this thread for a few days and have noticed one of the developers who is credited in resolve.slang is active in this thread, plus a few others whose names I recognize. So I figured this may be an appropriate topic to ask in.
That has to do with the moire-mitigation code, which only matters when there’s curvature applied, so you can try replacing that pass with this modified version that won’t bother with it when there’s no curvature:
#version 450
// Original bits by Themaister
// Moire mitigation bits by Timothy Lottes, added by hunterk
layout(push_constant) uniform Push
{
float BLOOM_STRENGTH;
float OUTPUT_GAMMA;
float CURVATURE;
float moire_mitigation;
float warpX;
float warpY;
float shadowMask;
float maskDark;
float maskLight;
} params;
#pragma parameter BLOOM_STRENGTH "Glow Strength" 0.45 0.0 0.8 0.05
#pragma parameter OUTPUT_GAMMA "Monitor Gamma" 2.2 1.8 2.6 0.02
#pragma parameter CURVATURE "Curvature" 0.0 0.0 1.0 1.0
#pragma parameter moire_mitigation "Moire:Noise Tradeoff" 4.0 1.0 10.0 1.0
#pragma parameter warpX "Curvature X-Axis" 0.031 0.0 0.125 0.01
#pragma parameter warpY "Curvature Y-Axis" 0.041 0.0 0.125 0.01
#pragma parameter shadowMask "Mask Effect" 0.0 0.0 4.0 1.0
#pragma parameter maskDark "maskDark" 0.5 0.0 2.0 0.1
#pragma parameter maskLight "maskLight" 1.5 0.0 2.0 0.1
#define iTime mod(float(global.FrameCount) / 60.0, 600.0)
#define fragCoord (vTexCoord.xy * global.OutputSize.xy)
layout(std140, set = 0, binding = 0) uniform UBO
{
mat4 MVP;
vec4 OutputSize;
vec4 OriginalSize;
vec4 SourceSize;
vec4 CRTPassSize;
uint FrameCount;
} global;
#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 = 1) uniform sampler2D Source;
layout(set = 0, binding = 2) uniform sampler2D CRTPass;
// For debugging
#define BLOOM_ONLY 0
#define CRT_PASS CRTPass
// Convert from linear to sRGB.
//float Srgb(float c){return(c<0.0031308?c*12.92:1.055*pow(c,0.41666)-0.055);}
vec4 Srgb(vec4 c){return pow(c, vec4(1.0 / 2.2));}
// Convert from sRGB to linear.
//float Linear(float c){return(c<=0.04045)?c/12.92:pow((c+0.055)/1.055,2.4);}
float Linear(float c){return pow(c, 2.2);}
//
// Semi-Poor Quality Temporal Noise
//
// Base.
// Ripped ad modified from: https://www.shadertoy.com/view/4djSRW
float Noise(vec2 p,float x){p+=x;
vec3 p3=fract(vec3(p.xyx)*10.1031);
p3+=dot(p3,p3.yzx+19.19);
return (fract((p3.x+p3.y)*p3.z)*2.0-1.0) / pow(2.0, 11.0 - params.moire_mitigation);}
// Step 1 in generation of the dither source texture.
float Noise1(vec2 uv,float n){
float a=1.0,b=2.0,c=-12.0,t=1.0;
return (1.0/max(a*4.0+b*4.0,-c))*(
Noise(uv+vec2(-1.0,-1.0)*t,n)*a+
Noise(uv+vec2( 0.0,-1.0)*t,n)*b+
Noise(uv+vec2( 1.0,-1.0)*t,n)*a+
Noise(uv+vec2(-1.0, 0.0)*t,n)*b+
Noise(uv+vec2( 0.0, 0.0)*t,n)*c+
Noise(uv+vec2( 1.0, 0.0)*t,n)*b+
Noise(uv+vec2(-1.0, 1.0)*t,n)*a+
Noise(uv+vec2( 0.0, 1.0)*t,n)*b+
Noise(uv+vec2( 1.0, 1.0)*t,n)*a+
0.0);}
// Step 2 in generation of the dither source texture.
float Noise2(vec2 uv,float n){
float a=1.0,b=2.0,c=-2.0,t=1.0;
return (1.0/(a*4.0+b*4.0))*(
Noise1(uv+vec2(-1.0,-1.0)*t,n)*a+
Noise1(uv+vec2( 0.0,-1.0)*t,n)*b+
Noise1(uv+vec2( 1.0,-1.0)*t,n)*a+
Noise1(uv+vec2(-1.0, 0.0)*t,n)*b+
Noise1(uv+vec2( 0.0, 0.0)*t,n)*c+
Noise1(uv+vec2( 1.0, 0.0)*t,n)*b+
Noise1(uv+vec2(-1.0, 1.0)*t,n)*a+
Noise1(uv+vec2( 0.0, 1.0)*t,n)*b+
Noise1(uv+vec2( 1.0, 1.0)*t,n)*a+
0.0);}
// Compute temporal dither from integer pixel position uv.
float Noise3(vec2 uv){return Noise2(uv,fract(iTime));}
// Energy preserving dither, for {int pixel pos,color,amount}.
vec2 Noise4(vec2 uv,vec2 c,float a){
// Grain value {-1 to 1}.
vec2 g=vec2(Noise3(uv)*2.0);
// Step size for black in non-linear space.
float rcpStep=1.0/(256.0-1.0);
// Estimate amount negative which still quantizes to zero.
vec2 black=vec2(0.5*Linear(rcpStep));
// Estimate amount above 1.0 which still quantizes to 1.0.
vec2 white=vec2(2.0-Linear(1.0-rcpStep));
// Add grain.
return vec2(clamp(c+g*min(c+black,min(white-c,a)),0.0,1.0));}
//
// Pattern
//
// 4xMSAA pattern for quad given integer coordinates.
//
// . x . . | < pixel
// . . . x |
// x . . .
// . . x .
//
// 01
// 23
//
vec2 Quad4(vec2 pp){
int q=(int(pp.x)&1)+((int(pp.y)&1)<<1);
if(q==0)return pp+vec2( 0.25,-0.25);
if(q==1)return pp+vec2( 0.25, 0.25);
if(q==2)return pp+vec2(-0.25,-0.25);
return pp+vec2(-0.25, 0.25);}
// Rotate {0.0,r} by a {-1.0 to 1.0}.
vec2 Rot(float r,float a){return vec2(r*cos(a*3.14159),r*sin(a*3.14159));}
//
// POOR QUALITY JITTERED
//
// Jittered position.
vec2 Jit(vec2 pp){
// Start with better baseline pattern.
pp=Quad4(pp);
// Very poor quality (clumping) move in disc around pixel.
float n=Noise(pp,fract(iTime));
float m=Noise(pp,fract(iTime*0.333))*0.5+0.5;
m = sqrt(m) / 4.0;
return pp+Rot(0.707*0.5*m,n);}
//
// POOR QUALITY JITTERED 4x
//
// Gaussian filtered jittered tap.
void JitGaus4(inout vec2 sumC,inout vec2 sumW,vec2 pp,vec2 mm){
vec2 jj=Jit(pp);
vec2 c=jj;
vec2 vv=mm-jj;
float w=exp2(-1.0*dot(vv,vv));
sumC+=c*vec2(w); sumW+=vec2(w);}
// Many tap gaussian from poor quality jittered 4/sample per pixel
//
// . x x x .
// x x x x x
// x x x x x
// x x x x x
// . x x x .
//
vec2 ResolveJitGaus4(vec2 pp){
vec2 ppp=(pp);
vec2 sumC=vec2(0.0);
vec2 sumW=vec2(0.0);
JitGaus4(sumC,sumW,ppp+vec2(-1.0,-2.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 0.0,-2.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 1.0,-2.0),pp);
JitGaus4(sumC,sumW,ppp+vec2(-2.0,-1.0),pp);
JitGaus4(sumC,sumW,ppp+vec2(-1.0,-1.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 0.0,-1.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 1.0,-1.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 2.0,-1.0),pp);
JitGaus4(sumC,sumW,ppp+vec2(-2.0, 0.0),pp);
JitGaus4(sumC,sumW,ppp+vec2(-1.0, 0.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 0.0, 0.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 1.0, 0.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 2.0, 0.0),pp);
JitGaus4(sumC,sumW,ppp+vec2(-2.0, 1.0),pp);
JitGaus4(sumC,sumW,ppp+vec2(-1.0, 1.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 0.0, 1.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 1.0, 1.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 2.0, 1.0),pp);
JitGaus4(sumC,sumW,ppp+vec2(-1.0, 2.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 0.0, 2.0),pp);
JitGaus4(sumC,sumW,ppp+vec2( 1.0, 2.0),pp);
return sumC/sumW;}
vec2 moire_resolve(vec2 coord){
vec2 pp = coord;
vec2 cc = vec2(0.0, 0.0);
cc = ResolveJitGaus4(pp);
cc = Noise4(pp, cc, 1.0 / 32.0);
cc = (params.CURVATURE < 0.5) ? pp : cc + vec2(0.0105, 0.015);
return cc;
}
// 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)*params.warpX, 1.0 + (pos.x*pos.x)*params.warpY);
return pos*0.5 + 0.5;
}
// Shadow mask.
vec3 Mask(vec2 pos)
{
vec3 mask = vec3(params.maskDark, params.maskDark, params.maskDark);
// Very compressed TV style shadow mask.
if (params.shadowMask == 1.0)
{
float line = params.maskLight;
float odd = 0.0;
if (fract(pos.x*0.166666666) < 0.5) odd = 1.0;
if (fract((pos.y + odd) * 0.5) < 0.5) line = params.maskDark;
pos.x = fract(pos.x*0.333333333);
if (pos.x < 0.333) mask.r = params.maskLight;
else if (pos.x < 0.666) mask.g = params.maskLight;
else mask.b = params.maskLight;
mask*=line;
}
// Aperture-grille.
else if (params.shadowMask == 2.0)
{
pos.x = fract(pos.x*0.333333333);
if (pos.x < 0.333) mask.r = params.maskLight;
else if (pos.x < 0.666) mask.g = params.maskLight;
else mask.b = params.maskLight;
}
// Stretched VGA style shadow mask (same as prior shaders).
else if (params.shadowMask == 3.0)
{
pos.x += pos.y*3.0;
pos.x = fract(pos.x*0.166666666);
if (pos.x < 0.333) mask.r = params.maskLight;
else if (pos.x < 0.666) mask.g = params.maskLight;
else mask.b = params.maskLight;
}
// VGA style shadow mask.
else if (params.shadowMask == 4.0)
{
pos.xy = floor(pos.xy*vec2(1.0, 0.5));
pos.x += pos.y*3.0;
pos.x = fract(pos.x*0.166666666);
if (pos.x < 0.333) mask.r = params.maskLight;
else if (pos.x < 0.666) mask.g = params.maskLight;
else mask.b = params.maskLight;
}
return mask;
}
void main()
{
vec2 pp = moire_resolve(vTexCoord.xy);
pp = (params.CURVATURE > 0.5) ? Warp(pp) : pp;
#if BLOOM_ONLY
vec3 source = BLOOM_STRENGTH * texture(Source, pp).rgb;
#else
vec3 source = 1.15 * texture(CRT_PASS, pp).rgb;
vec3 bloom = texture(Source, pp).rgb;
source += params.BLOOM_STRENGTH * bloom;
#endif
FragColor = vec4(pow(clamp(source, 0.0, 1.0), vec3(1.0 / params.OUTPUT_GAMMA)), 1.0);
if (params.shadowMask > 0.0)
FragColor.rgb = pow(pow(FragColor.rgb, vec3(2.2)) * Mask(vTexCoord.xy * global.OutputSize.xy * 1.000001), vec3(1.0 / 2.2));
if (params.CURVATURE < 0.5) return;
/* TODO/FIXME - hacky clamp fix */
if ( pp.x > 0.0106 && pp.x < 0.9999 && pp.y > 0.016 && pp.y < 0.9999)
FragColor.rgb = FragColor.rgb;
else
FragColor.rgb = vec3(0.0);
}
That shader preset may be my favorite one in the entire RetroArch shader package. It may not be intended to provide the sharpest picture, but it looks exactly like the cheap TV I had in my bedroom as a child.
I kid you not, when I first stumbled across that NTSC gaussian scanline shader, I said “Wow” out loud. Not because of image clarity or anything, but rather because of its perfect representation of my childhood video game experience. Beauty is in the eye of the beholder, and recreating the blurry garbage of 1995 on a modern LCD is completely stunning to me.
Works perfectly.
Thank you so much hunterk, your helpful forum presence and your contributions to RetroArch’s shaders are immensely appreciated.
Do you happen to know what the “320px” means? Obviously it says something about 320 pixels, but what does that mean? I see shader presets with 240 vs 320 variants.
I’ve always wondered this as well. How the 320 v 240 affects this shader pack.
NTSC emulation is closely tied to the timing of the signal, which equates to horizontal resolution in digital terms. While there are many resolutions that consoles can output, most either do 256 or 320 px wide, or some permutation thereof (640 or 512, etc.). The presets correspond to that.