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:
parent
78f4a9189d
commit
b1537ca232
|
@ -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));
|
||||
}
|
|
@ -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));
|
||||
}
|
Loading…
Reference in New Issue