1#version 450 2 3layout(push_constant) uniform Push 4{ 5 float hardScan; 6 float hardPix; 7 float warpX; 8 float warpY; 9 float maskDark; 10 float maskLight; 11 float scaleInLinearGamma; 12 float shadowMask; 13 float brightBoost; 14 float hardBloomScan; 15 float hardBloomPix; 16 float bloomAmount; 17 float shape; 18} param; 19 20#pragma parameter hardScan "hardScan" -8.0 -20.0 0.0 1.0 21#pragma parameter hardPix "hardPix" -3.0 -20.0 0.0 1.0 22#pragma parameter warpX "warpX" 0.031 0.0 0.125 0.01 23#pragma parameter warpY "warpY" 0.041 0.0 0.125 0.01 24#pragma parameter maskDark "maskDark" 0.5 0.0 2.0 0.1 25#pragma parameter maskLight "maskLight" 1.5 0.0 2.0 0.1 26#pragma parameter scaleInLinearGamma "scaleInLinearGamma" 1.0 0.0 1.0 1.0 27#pragma parameter shadowMask "shadowMask" 3.0 0.0 4.0 1.0 28#pragma parameter brightBoost "brightness boost" 1.0 0.0 2.0 0.05 29#pragma parameter hardBloomPix "bloom-x soft" -1.5 -2.0 -0.5 0.1 30#pragma parameter hardBloomScan "bloom-y soft" -2.0 -4.0 -1.0 0.1 31#pragma parameter bloomAmount "bloom amount" 0.4 0.0 1.0 0.05 32#pragma parameter shape "filter kernel shape" 2.0 0.0 10.0 0.05 33 34layout(std140, set = 0, binding = 0) uniform UBO 35{ 36 mat4 MVP; 37 vec4 OutputSize; 38 vec4 OriginalSize; 39 vec4 SourceSize; 40} global; 41 42#pragma stage vertex 43layout(location = 0) in vec4 Position; 44layout(location = 1) in vec2 TexCoord; 45layout(location = 0) out vec2 vTexCoord; 46 47void main() 48{ 49 gl_Position = global.MVP * Position; 50 vTexCoord = TexCoord; 51} 52 53// PUBLIC DOMAIN CRT STYLED SCAN-LINE SHADER 54// 55// by Timothy Lottes 56// 57// This is more along the style of a really good CGA arcade monitor. 58// With RGB inputs instead of NTSC. 59// The shadow mask example has the mask rotated 90 degrees for less chromatic aberration. 60// 61// Left it unoptimized to show the theory behind the algorithm. 62// 63// It is an example what I personally would want as a display option for pixel art games. 64// Please take and use, change, or whatever. 65 66#pragma stage fragment 67layout(location = 0) in vec2 vTexCoord; 68layout(location = 1) in vec2 FragCoord; 69layout(location = 0) out vec4 FragColor; 70layout(set = 0, binding = 2) uniform sampler2D Source; 71layout(set = 0, binding = 3) uniform sampler2D ORIG_LINEARIZED; 72 73//Uncomment to reduce instructions with simpler linearization 74//(fixes HD3000 Sandy Bridge IGP) 75#define SIMPLE_LINEAR_GAMMA 76#define DO_BLOOM 1 77 78// ------------- // 79 80// sRGB to Linear. 81// Assuming using sRGB typed textures this should not be needed. 82#ifdef SIMPLE_LINEAR_GAMMA 83float ToLinear1(float c) 84{ 85 return c; 86} 87vec3 ToLinear(vec3 c) 88{ 89 return c; 90} 91vec3 ToSrgb(vec3 c) 92{ 93 return pow(c, vec3(1.0 / 2.2)); 94} 95#else 96float ToLinear1(float c) 97{ 98 if (param.scaleInLinearGamma == 0) 99 return c; 100 101 return(c<=0.04045) ? c/12.92 : pow((c + 0.055)/1.055, 2.4); 102} 103 104vec3 ToLinear(vec3 c) 105{ 106 if (param.scaleInLinearGamma==0) 107 return c; 108 109 return vec3(ToLinear1(c.r), ToLinear1(c.g), ToLinear1(c.b)); 110} 111 112// Linear to sRGB. 113// Assuming using sRGB typed textures this should not be needed. 114float ToSrgb1(float c) 115{ 116 if (param.scaleInLinearGamma == 0) 117 return c; 118 119 return(c<0.0031308 ? c*12.92 : 1.055*pow(c, 0.41666) - 0.055); 120} 121 122vec3 ToSrgb(vec3 c) 123{ 124 if (param.scaleInLinearGamma == 0) 125 return c; 126 127 return vec3(ToSrgb1(c.r), ToSrgb1(c.g), ToSrgb1(c.b)); 128} 129#endif 130 131 132 133// Nearest emulated sample given floating point position and texel offset. 134// Also zero's off screen. 135vec3 Fetch(vec2 pos,vec2 off){ 136 pos=(floor(pos*global.SourceSize.xy+off)+vec2(0.5,0.5))/global.SourceSize.xy; 137#ifdef SIMPLE_LINEAR_GAMMA 138 return ToLinear(param.brightBoost * (texture(ORIG_LINEARIZED,pos.xy).rgb)); 139#else 140 return ToLinear(param.brightBoost * texture(ORIG_LINEARIZED,pos.xy).rgb); 141#endif 142} 143 144// Distance in emulated pixels to nearest texel. 145vec2 Dist(vec2 pos) 146{ 147 pos = pos*global.SourceSize.xy; 148 149 return -((pos - floor(pos)) - vec2(0.5)); 150} 151 152// 1D Gaussian. 153float Gaus(float pos, float scale) 154{ 155 return exp2(scale*pow(abs(pos), param.shape)); 156} 157 158// 3-tap Gaussian filter along horz line. 159vec3 Horz3(vec2 pos, float off) 160{ 161 vec3 b = Fetch(pos, vec2(-1.0, off)); 162 vec3 c = Fetch(pos, vec2( 0.0, off)); 163 vec3 d = Fetch(pos, vec2( 1.0, off)); 164 float dst = Dist(pos).x; 165 166 // Convert distance to weight. 167 float scale = param.hardPix; 168 float wb = Gaus(dst-1.0,scale); 169 float wc = Gaus(dst+0.0,scale); 170 float wd = Gaus(dst+1.0,scale); 171 172 // Return filtered sample. 173 return (b*wb+c*wc+d*wd)/(wb+wc+wd); 174} 175 176// 5-tap Gaussian filter along horz line. 177vec3 Horz5(vec2 pos,float off){ 178 vec3 a = Fetch(pos,vec2(-2.0, off)); 179 vec3 b = Fetch(pos,vec2(-1.0, off)); 180 vec3 c = Fetch(pos,vec2( 0.0, off)); 181 vec3 d = Fetch(pos,vec2( 1.0, off)); 182 vec3 e = Fetch(pos,vec2( 2.0, off)); 183 184 float dst = Dist(pos).x; 185 // Convert distance to weight. 186 float scale = param.hardPix; 187 float wa = Gaus(dst - 2.0, scale); 188 float wb = Gaus(dst - 1.0, scale); 189 float wc = Gaus(dst + 0.0, scale); 190 float wd = Gaus(dst + 1.0, scale); 191 float we = Gaus(dst + 2.0, scale); 192 193 // Return filtered sample. 194 return (a*wa+b*wb+c*wc+d*wd+e*we)/(wa+wb+wc+wd+we); 195} 196 197// 7-tap Gaussian filter along horz line. 198vec3 Horz7(vec2 pos,float off) 199{ 200 vec3 a = Fetch(pos, vec2(-3.0, off)); 201 vec3 b = Fetch(pos, vec2(-2.0, off)); 202 vec3 c = Fetch(pos, vec2(-1.0, off)); 203 vec3 d = Fetch(pos, vec2( 0.0, off)); 204 vec3 e = Fetch(pos, vec2( 1.0, off)); 205 vec3 f = Fetch(pos, vec2( 2.0, off)); 206 vec3 g = Fetch(pos, vec2( 3.0, off)); 207 208 float dst = Dist(pos).x; 209 // Convert distance to weight. 210 float scale = param.hardBloomPix; 211 float wa = Gaus(dst - 3.0, scale); 212 float wb = Gaus(dst - 2.0, scale); 213 float wc = Gaus(dst - 1.0, scale); 214 float wd = Gaus(dst + 0.0, scale); 215 float we = Gaus(dst + 1.0, scale); 216 float wf = Gaus(dst + 2.0, scale); 217 float wg = Gaus(dst + 3.0, scale); 218 219 // Return filtered sample. 220 return (a*wa+b*wb+c*wc+d*wd+e*we+f*wf+g*wg)/(wa+wb+wc+wd+we+wf+wg); 221} 222 223// Return scanline weight. 224float Scan(vec2 pos, float off) 225{ 226 float dst = Dist(pos).y; 227 228 return Gaus(dst + off, param.hardScan); 229} 230 231// Return scanline weight for bloom. 232float BloomScan(vec2 pos, float off) 233{ 234 float dst = Dist(pos).y; 235 236 return Gaus(dst + off, param.hardBloomScan); 237} 238 239// Allow nearest three lines to effect pixel. 240vec3 Tri(vec2 pos) 241{ 242 vec3 a = Horz3(pos,-1.0); 243 vec3 b = Horz5(pos, 0.0); 244 vec3 c = Horz3(pos, 1.0); 245 246 float wa = Scan(pos,-1.0); 247 float wb = Scan(pos, 0.0); 248 float wc = Scan(pos, 1.0); 249 250 return a*wa + b*wb + c*wc; 251} 252 253// Small bloom. 254vec3 Bloom(vec2 pos) 255{ 256 vec3 a = Horz5(pos,-2.0); 257 vec3 b = Horz7(pos,-1.0); 258 vec3 c = Horz7(pos, 0.0); 259 vec3 d = Horz7(pos, 1.0); 260 vec3 e = Horz5(pos, 2.0); 261 262 float wa = BloomScan(pos,-2.0); 263 float wb = BloomScan(pos,-1.0); 264 float wc = BloomScan(pos, 0.0); 265 float wd = BloomScan(pos, 1.0); 266 float we = BloomScan(pos, 2.0); 267 268 return a*wa+b*wb+c*wc+d*wd+e*we; 269} 270 271// Distortion of scanlines, and end of screen alpha. 272vec2 Warp(vec2 pos) 273{ 274 pos = pos*2.0-1.0; 275 pos *= vec2(1.0 + (pos.y*pos.y)*param.warpX, 1.0 + (pos.x*pos.x)*param.warpY); 276 277 return pos*0.5 + 0.5; 278} 279 280// Shadow mask. 281vec3 Mask(vec2 pos) 282{ 283 vec3 mask = vec3(param.maskDark, param.maskDark, param.maskDark); 284 285 // Very compressed TV style shadow mask. 286 if (param.shadowMask == 1.0) 287 { 288 float line = param.maskLight; 289 float odd = 0.0; 290 291 if (fract(pos.x*0.166666666) < 0.5) odd = 1.0; 292 if (fract((pos.y + odd) * 0.5) < 0.5) line = param.maskDark; 293 294 pos.x = fract(pos.x*0.333333333); 295 296 if (pos.x < 0.333) mask.r = param.maskLight; 297 else if (pos.x < 0.666) mask.g = param.maskLight; 298 else mask.b = param.maskLight; 299 mask*=line; 300 } 301 302 // Aperture-grille. 303 else if (param.shadowMask == 2.0) 304 { 305 pos.x = fract(pos.x*0.333333333); 306 307 if (pos.x < 0.333) mask.r = param.maskLight; 308 else if (pos.x < 0.666) mask.g = param.maskLight; 309 else mask.b = param.maskLight; 310 } 311 312 // Stretched VGA style shadow mask (same as prior shaders). 313 else if (param.shadowMask == 3.0) 314 { 315 pos.x += pos.y*3.0; 316 pos.x = fract(pos.x*0.166666666); 317 318 if (pos.x < 0.333) mask.r = param.maskLight; 319 else if (pos.x < 0.666) mask.g = param.maskLight; 320 else mask.b = param.maskLight; 321 } 322 323 // VGA style shadow mask. 324 else if (param.shadowMask == 4.0) 325 { 326 pos.xy = floor(pos.xy*vec2(1.0, 0.5)); 327 pos.x += pos.y*3.0; 328 pos.x = fract(pos.x*0.166666666); 329 330 if (pos.x < 0.333) mask.r = param.maskLight; 331 else if (pos.x < 0.666) mask.g = param.maskLight; 332 else mask.b = param.maskLight; 333 } 334 335 return mask; 336} 337 338void main() 339{ 340 vec2 pos = vTexCoord; 341 342 FragColor = vec4(Bloom(pos)*param.bloomAmount, 1.0); 343} 344