Add CRT shaders

I've ported a couple of shaders from RetroArch - particularly the
crt-pi and crt-lottes-fast ones. Unlike other shaders such as
crt-royale, these shaders are single-pass, allowing them to work
within Dolphin's framework. These look pretty good when paired with
native resolution and SSAA.
This commit is contained in:
Clownacy 2023-06-29 21:00:08 +01:00 committed by GitHub
parent 78f4a9189d
commit b1537ca232
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 974 additions and 0 deletions

View File

@ -0,0 +1,663 @@
//_____________________________/\_______________________________
//==============================================================
//
//
// [CRTS] PUBLIC DOMAIN CRT-STYLED SCALAR - 20180120b
//
// by Timothy Lottes
// https://www.shadertoy.com/view/MtSfRK
// adapted for RetroArch by hunterk
// adapted for Dolphin by Clownacy
//
//
//==============================================================
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
//_____________________________/\_______________________________
//==============================================================
//
// WHAT'S NEW
//
//--------------------------------------------------------------
// Evolution of prior shadertoy example
//--------------------------------------------------------------
// This one is semi-optimized
// - Less texture fetches
// - Didn't get to instruction level optimization
// - Could likely use texture fetch to generate phosphor mask
//--------------------------------------------------------------
// Added options to disable unused features
//--------------------------------------------------------------
// Added in exposure matching
// - Given scan-line effect and mask always darkens image
// - Uses generalized tonemapper to boost mid-level
// - Note this can compress highlights
// - And won't get back peak brightness
// - But best option if one doesn't want as much darkening
//--------------------------------------------------------------
// Includes option saturation and contrast controls
//--------------------------------------------------------------
// Added in subtractive aperture grille
// - This is a bit brighter than prior
//--------------------------------------------------------------
// Make sure input to this filter is already low-resolution
// - This is not designed to work on titles doing the following
// - Rendering to hi-res with nearest sampling
//--------------------------------------------------------------
// Added a fast and more pixely option for 2 tap/pixel
//--------------------------------------------------------------
// Improved the vignette when WARP is enabled
//--------------------------------------------------------------
// Didn't test HLSL or CPU options
// - Will incorportate patches if they are broken
// - But out of time to try them myself
//==============================================================
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
//_____________________________/\_______________________________
//==============================================================
//
// LICENSE = UNLICENSE (aka PUBLIC DOMAIN)
//
//--------------------------------------------------------------
// This is free and unencumbered software released into the
// public domain.
//--------------------------------------------------------------
// Anyone is free to copy, modify, publish, use, compile, sell,
// or distribute this software, either in source code form or as
// a compiled binary, for any purpose, commercial or
// non-commercial, and by any means.
//--------------------------------------------------------------
// In jurisdictions that recognize copyright laws, the author or
// authors of this software dedicate any and all copyright
// interest in the software to the public domain. We make this
// dedication for the benefit of the public at large and to the
// detriment of our heirs and successors. We intend this
// dedication to be an overt act of relinquishment in perpetuity
// of all present and future rights to this software under
// copyright law.
//--------------------------------------------------------------
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
// KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
// OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//--------------------------------------------------------------
// For more information, please refer to
// <http://unlicense.org/>
//==============================================================
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
/*
[configuration]
[OptionRangeFloat]
GUIName = Mask Type
OptionName = MASK
MaxValue = 3.0
MinValue = 0.0
DefaultValue = 1.0
StepAmount = 1.0
[OptionRangeFloat]
GUIName = Mask Intensity
OptionName = MASK_INTENSITY
MaxValue = 1.0
MinValue = 0.0
DefaultValue = 0.5
StepAmount = 0.05
[OptionRangeFloat]
GUIName = Scanline Intensity
OptionName = SCANLINE_THINNESS
MaxValue = 1.0
MinValue = 0.0
DefaultValue = 0.5
StepAmount = 0.1
[OptionRangeFloat]
GUIName = Sharpness
OptionName = SCAN_BLUR
MaxValue = 3.0
MinValue = 1.0
DefaultValue = 2.5
StepAmount = 0.1
[OptionRangeFloat]
GUIName = Curvature
OptionName = CURVATURE
MaxValue = 0.25
MinValue = 0.0
DefaultValue = 0.02
StepAmount = 0.01
[OptionRangeFloat]
GUIName = Trinitron-style Curve
OptionName = TRINITRON_CURVE
MaxValue = 1.0
MinValue = 0.0
DefaultValue = 0.0
StepAmount = 1.0
[OptionRangeFloat]
GUIName = Corner Round
OptionName = CORNER
MaxValue = 11.0
MinValue = 0.0
DefaultValue = 3.0
StepAmount = 1.0
[OptionRangeFloat]
GUIName = CRT Gamma
OptionName = CRT_GAMMA
MaxValue = 51.0
MinValue = 0.0
DefaultValue = 2.4
StepAmount = 0.1
[/configuration]
*/
//_____________________________/\_______________________________
//==============================================================
//
// GAMMA FUNCTIONS
//
//--------------------------------------------------------------
//--------------------------------------------------------------
// Since shadertoy doesn't have sRGB textures
// And we need linear input into shader
// Don't do this in your code
float FromSrgb1(float c){
return (c<=0.04045)?c*(1.0/12.92):
pow(abs(c)*(1.0/1.055)+(0.055/1.055),GetOption(CRT_GAMMA));}
//--------------------------------------------------------------
vec3 FromSrgb(vec3 c){return vec3(
FromSrgb1(c.r),FromSrgb1(c.g),FromSrgb1(c.b));}
// Convert from linear to sRGB
// Since shader toy output is not linear
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));}
//--------------------------------------------------------------
//_____________________________/\_______________________________
//==============================================================
//
// DEFINES
//
//--------------------------------------------------------------
// CRTS_CPU - CPU code
// CRTS_GPU - GPU code
//--------------------------------------------------------------
// CRTS_GLSL - GLSL
// CRTS_HLSL - HLSL (not tested yet)
//--------------------------------------------------------------
// CRTS_DEBUG - Define to see on/off split screen
//--------------------------------------------------------------
// CRTS_WARP - Apply screen warp
//--------------------------------------------------------------
// CRTS_2_TAP - Faster very pixely 2-tap filter (off is 8)
//--------------------------------------------------------------
// CRTS_MASK_GRILLE - Aperture grille (aka Trinitron)
// CRTS_MASK_GRILLE_LITE - Brighter (subtractive channels)
// CRTS_MASK_NONE - No mask
// CRTS_MASK_SHADOW - Horizontally stretched shadow mask
//--------------------------------------------------------------
// CRTS_TONE - Normalize mid-level and process color
// CRTS_CONTRAST - Process color - enable contrast control
// CRTS_SATURATION - Process color - enable saturation control
//--------------------------------------------------------------
#define CRTS_STATIC
#define CrtsPow
#define CRTS_RESTRICT
//==============================================================
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
//==============================================================
// SETUP FOR CRTS
//--------------------------------------------------------------
//==============================================================
//#define CRTS_DEBUG 1
#define CRTS_GPU 1
#define CRTS_GLSL 1
//--------------------------------------------------------------
//#define CRTS_2_TAP 1
//--------------------------------------------------------------
#define CRTS_TONE 1
#define CRTS_CONTRAST 0
#define CRTS_SATURATION 0
//--------------------------------------------------------------
#define CRTS_WARP 1
//--------------------------------------------------------------
// Try different masks -> moved to runtime parameters
//#define CRTS_MASK_GRILLE 1
//#define CRTS_MASK_GRILLE_LITE 1
//#define CRTS_MASK_NONE 1
//#define CRTS_MASK_SHADOW 1
//--------------------------------------------------------------
// Scanline thinness
// 0.50 = fused scanlines
// 0.70 = recommended default
// 1.00 = thinner scanlines (too thin)
#define INPUT_THIN (0.5 + (0.5 * GetOption(SCANLINE_THINNESS)))
//--------------------------------------------------------------
// Horizonal scan blur
// -3.0 = pixely
// -2.5 = default
// -2.0 = smooth
// -1.0 = too blurry
#define INPUT_BLUR (-1.0 * GetOption(SCAN_BLUR))
//--------------------------------------------------------------
// Shadow mask effect, ranges from,
// 0.25 = large amount of mask (not recommended, too dark)
// 0.50 = recommended default
// 1.00 = no shadow mask
#define INPUT_MASK (1.0 - GetOption(MASK_INTENSITY))
//--------------------------------------------------------------
//#define INPUT_X SourceSize.x
//#define INPUT_Y SourceSize.y
//--------------------------------------------------------------
// Setup the function which returns input image color
vec3 CrtsFetch(vec2 uv){
// For shadertoy, scale to get native texels in the image
// uv*=vec2(INPUT_X,INPUT_Y)/SourceSize.xy;
// Move towards intersting parts
// uv+=vec2(0.5,0.5);
// Non-shadertoy case would not have the color conversion
return FromSrgb(SampleLocation(uv.xy).rgb);}
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
//_____________________________/\_______________________________
//==============================================================
//
// GPU CODE
//
//==============================================================
#ifdef CRTS_GPU
//_____________________________/\_______________________________
//==============================================================
// PORTABILITY
//==============================================================
#ifdef CRTS_GLSL
#define CrtsF1 float
#define CrtsF2 vec2
#define CrtsF3 vec3
#define CrtsF4 vec4
#define CrtsFractF1 fract
#define CrtsRcpF1(x) (1.0/(x))
#define CrtsSatF1(x) clamp((x),0.0,1.0)
//--------------------------------------------------------------
CrtsF1 CrtsMax3F1(CrtsF1 a,CrtsF1 b,CrtsF1 c){
return max(a,max(b,c));}
#endif
//==============================================================
#ifdef CRTS_HLSL
#define CrtsF1 float
#define CrtsF2 float2
#define CrtsF3 float3
#define CrtsF4 float4
#define CrtsFractF1 frac
#define CrtsRcpF1(x) (1.0/(x))
#define CrtsSatF1(x) saturate(x)
//--------------------------------------------------------------
CrtsF1 CrtsMax3F1(CrtsF1 a,CrtsF1 b,CrtsF1 c){
return max(a,max(b,c));}
#endif
//_____________________________/\_______________________________
//==============================================================
// TONAL CONTROL CONSTANT GENERATION
//--------------------------------------------------------------
// This is in here for rapid prototyping
// Please use the CPU code and pass in as constants
//==============================================================
CrtsF4 CrtsTone(
CrtsF1 contrast,
CrtsF1 saturation,
CrtsF1 thin,
CrtsF1 mask){
//--------------------------------------------------------------
if(GetOption(MASK) == 0.0) mask=1.0;
//--------------------------------------------------------------
if(GetOption(MASK) == 1.0){
// Normal R mask is {1.0,mask,mask}
// LITE R mask is {mask,1.0,1.0}
mask=0.5+mask*0.5;
}
//--------------------------------------------------------------
CrtsF4 ret;
CrtsF1 midOut=0.18/((1.5-thin)*(0.5*mask+0.5));
CrtsF1 pMidIn=pow(0.18,contrast);
ret.x=contrast;
ret.y=((-pMidIn)+midOut)/((1.0-pMidIn)*midOut);
ret.z=((-pMidIn)*midOut+pMidIn)/(midOut*(-pMidIn)+midOut);
ret.w=contrast+saturation;
return ret;}
//_____________________________/\_______________________________
//==============================================================
// MASK
//--------------------------------------------------------------
// Letting LCD/OLED pixel elements function like CRT phosphors
// So "phosphor" resolution scales with display resolution
//--------------------------------------------------------------
// Not applying any warp to the mask (want high frequency)
// Real aperture grille has a mask which gets wider on ends
// Not attempting to be "real" but instead look the best
//--------------------------------------------------------------
// Shadow mask is stretched horizontally
// RRGGBB
// GBBRRG
// RRGGBB
// This tends to look better on LCDs than vertical
// Also 2 pixel width is required to get triad centered
//--------------------------------------------------------------
// The LITE version of the Aperture Grille is brighter
// Uses {dark,1.0,1.0} for R channel
// Non LITE version uses {1.0,dark,dark}
//--------------------------------------------------------------
// 'pos' - This is 'fragCoord.xy'
// Pixel {0,0} should be {0.5,0.5}
// Pixel {1,1} should be {1.5,1.5}
//--------------------------------------------------------------
// 'dark' - Exposure of of masked channel
// 0.0=fully off, 1.0=no effect
//==============================================================
CrtsF3 CrtsMask(CrtsF2 pos,CrtsF1 dark){
if(GetOption(MASK) == 2.0){
CrtsF3 m=CrtsF3(dark,dark,dark);
CrtsF1 x=CrtsFractF1(pos.x*(1.0/3.0));
if(x<(1.0/3.0))m.r=1.0;
else if(x<(2.0/3.0))m.g=1.0;
else m.b=1.0;
return m;
}
//--------------------------------------------------------------
else if(GetOption(MASK) == 1.0){
CrtsF3 m=CrtsF3(1.0,1.0,1.0);
CrtsF1 x=CrtsFractF1(pos.x*(1.0/3.0));
if(x<(1.0/3.0))m.r=dark;
else if(x<(2.0/3.0))m.g=dark;
else m.b=dark;
return m;
}
//--------------------------------------------------------------
else if(GetOption(MASK) == 3.0){
pos.x+=pos.y*2.9999;
CrtsF3 m=CrtsF3(dark,dark,dark);
CrtsF1 x=CrtsFractF1(pos.x*(1.0/6.0));
if(x<(1.0/3.0))m.r=1.0;
else if(x<(2.0/3.0))m.g=1.0;
else m.b=1.0;
return m;
}
//--------------------------------------------------------------
else{
return CrtsF3(1.0,1.0,1.0);
}
}
//_____________________________/\_______________________________
//==============================================================
// FILTER ENTRY
//--------------------------------------------------------------
// Input must be linear
// Output color is linear
//--------------------------------------------------------------
// Must have fetch function setup: CrtsF3 CrtsFetch(CrtsF2 uv)
// - The 'uv' range is {0.0 to 1.0} for input texture
// - Output of this must be linear color
//--------------------------------------------------------------
// SCANLINE MATH & AUTO-EXPOSURE NOTES
// ===================================
// Each output line has contribution from at most 2 scanlines
// Scanlines are shaped by a windowed cosine function
// This shape blends together well with only 2 lines of overlap
//--------------------------------------------------------------
// Base scanline intensity is as follows
// which leaves output intensity range from {0 to 1.0}
// --------
// thin := range {thick 0.5 to thin 1.0}
// off := range {0.0 to <1.0},
// sub-pixel offset between two scanlines
// --------
// a0=cos(min(0.5, off *thin)*2pi)*0.5+0.5;
// a1=cos(min(0.5,(1.0-off)*thin)*2pi)*0.5+0.5;
//--------------------------------------------------------------
// This leads to a image darkening factor of roughly:
// {(1.5-thin)/1.0}
// This is further reduced by the mask:
// {1.0/2.0+mask*1.0/2.0}
// Reciprocal of combined effect is used for auto-exposure
// to scale up the mid-level in the tonemapper
//==============================================================
CrtsF3 CrtsFilter(
//--------------------------------------------------------------
// SV_POSITION, fragCoord.xy
CrtsF2 ipos,
//--------------------------------------------------------------
// inputSize / outputSize (in pixels)
CrtsF2 inputSizeDivOutputSize,
//--------------------------------------------------------------
// 0.5 * inputSize (in pixels)
CrtsF2 halfInputSize,
//--------------------------------------------------------------
// 1.0 / inputSize (in pixels)
CrtsF2 rcpInputSize,
//--------------------------------------------------------------
// 1.0 / outputSize (in pixels)
CrtsF2 rcpOutputSize,
//--------------------------------------------------------------
// 2.0 / outputSize (in pixels)
CrtsF2 twoDivOutputSize,
//--------------------------------------------------------------
// inputSize.y
CrtsF1 inputHeight,
//--------------------------------------------------------------
// Warp scanlines but not phosphor mask
// 0.0 = no warp
// 1.0/64.0 = light warping
// 1.0/32.0 = more warping
// Want x and y warping to be different (based on aspect)
CrtsF2 warp,
//--------------------------------------------------------------
// Scanline thinness
// 0.50 = fused scanlines
// 0.70 = recommended default
// 1.00 = thinner scanlines (too thin)
// Shared with CrtsTone() function
CrtsF1 thin,
//--------------------------------------------------------------
// Horizonal scan blur
// -3.0 = pixely
// -2.5 = default
// -2.0 = smooth
// -1.0 = too blurry
CrtsF1 blur,
//--------------------------------------------------------------
// Shadow mask effect, ranges from,
// 0.25 = large amount of mask (not recommended, too dark)
// 0.50 = recommended default
// 1.00 = no shadow mask
// Shared with CrtsTone() function
CrtsF1 mask,
//--------------------------------------------------------------
// Tonal curve parameters generated by CrtsTone()
CrtsF4 tone
//--------------------------------------------------------------
){
//--------------------------------------------------------------
#ifdef CRTS_DEBUG
CrtsF2 uv=ipos*rcpOutputSize;
// Show second half processed, and first half un-processed
if(uv.x<0.5){
// Force nearest to get squares
uv*=1.0/rcpInputSize;
uv=floor(uv)+CrtsF2(0.5,0.5);
uv*=rcpInputSize;
CrtsF3 color=CrtsFetch(uv);
return color;}
#endif
//--------------------------------------------------------------
// Optional apply warp
CrtsF2 pos;
#ifdef CRTS_WARP
// Convert to {-1 to 1} range
pos=ipos*twoDivOutputSize-CrtsF2(1.0,1.0);
// Distort pushes image outside {-1 to 1} range
pos*=CrtsF2(
1.0+(pos.y*pos.y)*warp.x,
1.0+(pos.x*pos.x)*warp.y);
// TODO: Vignette needs optimization
CrtsF1 vin=(1.0-(
(1.0-CrtsSatF1(pos.x*pos.x))*(1.0-CrtsSatF1(pos.y*pos.y)))) * (0.998 + (0.001 * GetOption(CORNER)));
vin=CrtsSatF1((-vin)*inputHeight+inputHeight);
// Leave in {0 to inputSize}
pos=pos*halfInputSize+halfInputSize;
#else
pos=ipos*inputSizeDivOutputSize;
#endif
//--------------------------------------------------------------
// Snap to center of first scanline
CrtsF1 y0=floor(pos.y-0.5)+0.5;
#ifdef CRTS_2_TAP
// Using Inigo's "Improved Texture Interpolation"
// http://iquilezles.org/www/articles/texture/texture.htm
pos.x+=0.5;
CrtsF1 xi=floor(pos.x);
CrtsF1 xf=pos.x-xi;
xf=xf*xf*xf*(xf*(xf*6.0-15.0)+10.0);
CrtsF1 x0=xi+xf-0.5;
CrtsF2 p=CrtsF2(x0*rcpInputSize.x,y0*rcpInputSize.y);
// Coordinate adjusted bilinear fetch from 2 nearest scanlines
CrtsF3 colA=CrtsFetch(p);
p.y+=rcpInputSize.y;
CrtsF3 colB=CrtsFetch(p);
#else
// Snap to center of one of four pixels
CrtsF1 x0=floor(pos.x-1.5)+0.5;
// Inital UV position
CrtsF2 p=CrtsF2(x0*rcpInputSize.x,y0*rcpInputSize.y);
// Fetch 4 nearest texels from 2 nearest scanlines
CrtsF3 colA0=CrtsFetch(p);
p.x+=rcpInputSize.x;
CrtsF3 colA1=CrtsFetch(p);
p.x+=rcpInputSize.x;
CrtsF3 colA2=CrtsFetch(p);
p.x+=rcpInputSize.x;
CrtsF3 colA3=CrtsFetch(p);
p.y+=rcpInputSize.y;
CrtsF3 colB3=CrtsFetch(p);
p.x-=rcpInputSize.x;
CrtsF3 colB2=CrtsFetch(p);
p.x-=rcpInputSize.x;
CrtsF3 colB1=CrtsFetch(p);
p.x-=rcpInputSize.x;
CrtsF3 colB0=CrtsFetch(p);
#endif
//--------------------------------------------------------------
// Vertical filter
// Scanline intensity is using sine wave
// Easy filter window and integral used later in exposure
CrtsF1 off=pos.y-y0;
CrtsF1 pi2=6.28318530717958;
CrtsF1 hlf=0.5;
CrtsF1 scanA=cos(min(0.5, off *thin )*pi2)*hlf+hlf;
CrtsF1 scanB=cos(min(0.5,(-off)*thin+thin)*pi2)*hlf+hlf;
//--------------------------------------------------------------
#ifdef CRTS_2_TAP
#ifdef CRTS_WARP
// Get rid of wrong pixels on edge
scanA*=vin;
scanB*=vin;
#endif
// Apply vertical filter
CrtsF3 color=(colA*scanA)+(colB*scanB);
#else
// Horizontal kernel is simple gaussian filter
CrtsF1 off0=pos.x-x0;
CrtsF1 off1=off0-1.0;
CrtsF1 off2=off0-2.0;
CrtsF1 off3=off0-3.0;
CrtsF1 pix0=exp2(blur*off0*off0);
CrtsF1 pix1=exp2(blur*off1*off1);
CrtsF1 pix2=exp2(blur*off2*off2);
CrtsF1 pix3=exp2(blur*off3*off3);
CrtsF1 pixT=CrtsRcpF1(pix0+pix1+pix2+pix3);
#ifdef CRTS_WARP
// Get rid of wrong pixels on edge
pixT*=vin;
#endif
scanA*=pixT;
scanB*=pixT;
// Apply horizontal and vertical filters
CrtsF3 color=
(colA0*pix0+colA1*pix1+colA2*pix2+colA3*pix3)*scanA +
(colB0*pix0+colB1*pix1+colB2*pix2+colB3*pix3)*scanB;
#endif
//--------------------------------------------------------------
// Apply phosphor mask
color*=CrtsMask(ipos,mask);
//--------------------------------------------------------------
// Optional color processing
#ifdef CRTS_TONE
// Tonal control, start by protecting from /0
CrtsF1 peak=max(1.0/(256.0*65536.0),
CrtsMax3F1(color.r,color.g,color.b));
// Compute the ratios of {R,G,B}
CrtsF3 ratio=color*CrtsRcpF1(peak);
// Apply tonal curve to peak value
#ifdef CRTS_CONTRAST
peak=pow(peak,tone.x);
#endif
peak=peak*CrtsRcpF1(peak*tone.y+tone.z);
// Apply saturation
#ifdef CRTS_SATURATION
ratio=pow(ratio,CrtsF3(tone.w,tone.w,tone.w));
#endif
// Reconstruct color
return ratio*peak;
#else
return color;
#endif
//--------------------------------------------------------------
}
#endif
void main()
{
vec4 OutputSize = vec4(GetWindowResolution(), GetInvWindowResolution());
vec4 SourceSize = vec4(GetResolution(), GetInvResolution());
vec2 warp_factor;
warp_factor.x = GetOption(CURVATURE);
warp_factor.y = (3.0 / 4.0) * warp_factor.x; // assume 4:3 aspect
warp_factor.x *= (1.0 - GetOption(TRINITRON_CURVE));
vec3 color = CrtsFilter(GetCoordinates().xy * OutputSize.xy,
SourceSize.xy * OutputSize.zw,
SourceSize.xy * vec2(0.5,0.5),
SourceSize.zw,
OutputSize.zw,
2.0 * OutputSize.zw,
SourceSize.y,
warp_factor,
INPUT_THIN,
INPUT_BLUR,
INPUT_MASK,
CrtsTone(1.0,0.0,INPUT_THIN,INPUT_MASK));
// Shadertoy outputs non-linear color
color = ToSrgb(color);
SetOutput(vec4(color, 1.0));
}

View File

@ -0,0 +1,311 @@
/*
[configuration]
[OptionRangeFloat]
GUIName = Bloom factor
OptionName = BLOOM_FACTOR
MaxValue = 5.0
MinValue = 0.0
DefaultValue = 1.5
StepAmount = 0.01
[OptionBool]
GUIName = Sharper
OptionName = SHARPER
DefaultValue = false
[OptionBool]
GUIName = Curvature
OptionName = CURVATURE
DefaultValue = true
[OptionRangeFloat]
GUIName = Horizontal
OptionName = CURVATURE_X
DependentOption = CURVATURE
MaxValue = 1.0
MinValue = 0.0
DefaultValue = 0.10
StepAmount = 0.01
[OptionRangeFloat]
GUIName = Vertical
OptionName = CURVATURE_Y
DependentOption = CURVATURE
MaxValue = 1.0
MinValue = 0.0
DefaultValue = 0.15
StepAmount = 0.01
[OptionBool]
GUIName = Scanlines
OptionName = SCANLINES
DefaultValue = true
[OptionRangeFloat]
GUIName = Weight
OptionName = SCANLINE_WEIGHT
DependentOption = SCANLINES
MaxValue = 15.0
MinValue = 0.0
DefaultValue = 6.0
StepAmount = 0.1
[OptionRangeFloat]
GUIName = Gap brightness
OptionName = SCANLINE_GAP_BRIGHTNESS
DependentOption = SCANLINES
MaxValue = 1.0
MinValue = 0.0
DefaultValue = 0.12
StepAmount = 0.01
[OptionBool]
GUIName = Multisample
OptionName = MULTISAMPLE
DependentOption = SCANLINES
DefaultValue = true
[OptionBool]
GUIName = Gamma
OptionName = GAMMA
DefaultValue = true
[OptionBool]
GUIName = Fake Gamma
OptionName = FAKE_GAMMA
DependentOption = GAMMA
DefaultValue = false
[OptionRangeFloat]
GUIName = Input
OptionName = INPUT_GAMMA
DependentOption = GAMMA
MaxValue = 5.0
MinValue = 0.0
DefaultValue = 2.4
StepAmount = 0.01
[OptionRangeFloat]
GUIName = Output
OptionName = OUTPUT_GAMMA
DependentOption = GAMMA
MaxValue = 5.0
MinValue = 0.0
DefaultValue = 2.2
StepAmount = 0.01
[OptionBool]
GUIName = Shadow mask
OptionName = SHADOW_MASK
DefaultValue = true
[OptionBool]
GUIName = Trinitron(ish)
OptionName = MASK_TRINITRON
DependentOption = SHADOW_MASK
DefaultValue = true
[OptionRangeFloat]
GUIName = Brightness
OptionName = MASK_BRIGHTNESS
DependentOption = SHADOW_MASK
MaxValue = 1.0
MinValue = 0.0
DefaultValue = 0.70
StepAmount = 0.01
[/configuration]
*/
/*
crt-pi - A Raspberry Pi friendly CRT shader.
Copyright (C) 2015-2016 davej
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
Notes:
This shader is designed to work well on Raspberry Pi GPUs (i.e. 1080P @ 60Hz on
a game with a 4:3 aspect ratio).
It pushes the Pi's GPU hard and enabling some features will slow it down so that
it is no longer able to match 1080P @ 60Hz.
You will need to overclock your Pi to the fastest setting in raspi-config to get
the best results from this shader: 'Pi2' for Pi2 and 'Turbo' for original Pi and
Pi Zero.
Note: Pi2s are slower at running the shader than other Pis, this seems to be
down to Pi2s lower maximum memory speed.
Pi2s don't quite manage 1080P @ 60Hz - they drop about 1 in 1000 frames.
You probably won't notice this, but if you do, try enabling FAKE_GAMMA.
SCANLINES enables scanlines.
You'll almost certainly want to use it with MULTISAMPLE to reduce moire effects.
SCANLINE_WEIGHT defines how wide scanlines are (it is an inverse value so a
higher number = thinner lines).
SCANLINE_GAP_BRIGHTNESS defines how dark the gaps between the scan lines are.
Darker gaps between scan lines make moire effects more likely.
GAMMA enables gamma correction using the values in INPUT_GAMMA and OUTPUT_GAMMA.
FAKE_GAMMA causes it to ignore the values in INPUT_GAMMA and OUTPUT_GAMMA and
approximate gamma correction in a way which is faster than true gamma whilst
still looking better than having none.
You must have GAMMA defined to enable FAKE_GAMMA.
CURVATURE distorts the screen by CURVATURE_X and CURVATURE_Y.
Curvature slows things down a lot.
By default the shader uses linear blending horizontally. If you find this too
blury, enable SHARPER.
BLOOM_FACTOR controls the increase in width for bright scanlines.
MASK_TYPE defines what, if any, shadow mask to use. MASK_BRIGHTNESS defines how
much the mask type darkens the screen.
[MASK_TYPE has been replaced with SHADOW_MASK and MASK_TRINITRON]
*/
vec2 CURVATURE_DISTORTION = vec2(GetOption(CURVATURE_X), GetOption(CURVATURE_Y));
// Barrel distortion shrinks the display area a bit, this will allow us to counteract that.
vec2 barrelScale = 1.0 - (0.23 * CURVATURE_DISTORTION);
vec2 Distort(vec2 coord)
{
// coord *= screenScale; // not necessary in slang
coord -= vec2(0.5);
float rsq = coord.x * coord.x + coord.y * coord.y;
coord += coord * (CURVATURE_DISTORTION * rsq);
coord *= barrelScale;
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5)
coord = vec2(-1.0); // If out of bounds, return an invalid value.
else
{
coord += vec2(0.5);
// coord /= screenScale; // not necessary in slang
}
return coord;
}
float CalcScanLineWeight(float dist)
{
return max(1.0-dist*dist*GetOption(SCANLINE_WEIGHT), GetOption(SCANLINE_GAP_BRIGHTNESS));
}
float filterWidth;
float CalcScanLine(float dy)
{
float scanLineWeight = CalcScanLineWeight(dy);
if (OptionEnabled(MULTISAMPLE))
{
scanLineWeight += CalcScanLineWeight(dy - filterWidth);
scanLineWeight += CalcScanLineWeight(dy + filterWidth);
scanLineWeight *= 0.3333333;
}
return scanLineWeight;
}
void main()
{
vec2 vTexCoord = GetCoordinates();
vec4 OutputSize = vec4(GetWindowResolution(), GetInvWindowResolution());
vec4 SourceSize = vec4(GetResolution(), GetInvResolution());
filterWidth = (SourceSize.y * OutputSize.w) * 0.333333333;
vec2 texcoord = vTexCoord;
if (OptionEnabled(CURVATURE))
{
texcoord = Distort(texcoord);
if (texcoord.x < 0.0)
{
SetOutput(vec4(0.0));
return;
}
}
vec2 texcoordInPixels = texcoord * SourceSize.xy;
vec2 tc;
float scanLineWeight;
if (OptionEnabled(SHARPER))
{
vec2 tempCoord = floor(texcoordInPixels) + 0.5;
vec2 coord = tempCoord * SourceSize.zw;
vec2 deltas = texcoordInPixels - tempCoord;
scanLineWeight = CalcScanLine(deltas.y);
vec2 signs = sign(deltas);
deltas = abs(deltas) * 2.0;
deltas.x = deltas.x * deltas.x;
deltas.y = deltas.y * deltas.y * deltas.y;
deltas *= 0.5 * SourceSize.zw * signs;
tc = coord + deltas;
}
else
{
float tempCoord = floor(texcoordInPixels.y) + 0.5;
float coord = tempCoord * SourceSize.w;
float deltas = texcoordInPixels.y - tempCoord;
scanLineWeight = CalcScanLine(deltas);
float signs = sign(deltas);
deltas = abs(deltas) * 2.0;
deltas = deltas * deltas * deltas;
deltas *= 0.5 * SourceSize.w * signs;
tc = vec2(texcoord.x, coord + deltas);
}
vec3 colour = SampleLocation(tc).rgb;
if (OptionEnabled(SCANLINES))
{
if (OptionEnabled(GAMMA))
{
if (OptionEnabled(FAKE_GAMMA))
colour = colour * colour;
else
colour = pow(colour, vec3(GetOption(INPUT_GAMMA)));
}
/* Apply scanlines */
scanLineWeight *= GetOption(BLOOM_FACTOR);
colour *= scanLineWeight;
if (OptionEnabled(GAMMA))
{
if (OptionEnabled(FAKE_GAMMA))
colour = sqrt(colour);
else
colour = pow(colour, vec3(1.0/GetOption(OUTPUT_GAMMA)));
}
}
if (OptionEnabled(SHADOW_MASK))
{
if (OptionEnabled(MASK_TRINITRON))
{
/* Trinitron(ish) */
float whichMask = fract((vTexCoord.x * OutputSize.x) * 0.3333333);
vec3 mask = vec3(GetOption(MASK_BRIGHTNESS));
if (whichMask < 0.3333333) mask.r = 1.0;
else if (whichMask < 0.6666666) mask.g = 1.0;
else mask.b = 1.0;
colour *= mask;
}
else
{
/* Green/magenta */
float whichMask = fract((vTexCoord.x * OutputSize.x) * 0.5);
vec3 mask = vec3(1.0);
if (whichMask < 0.5) mask.rb = vec2(GetOption(MASK_BRIGHTNESS));
else mask.g = GetOption(MASK_BRIGHTNESS);
colour *= mask;
}
}
SetOutput(vec4(colour, 1.0));
}