Last active
November 18, 2024 08:47
-
-
Save ninjadynamics/a341dc27ed2dfc91b7e8cad459d67ffb to your computer and use it in GitHub Desktop.
CRT-Mattias-Blur
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 450 // Specify the GLSL version to use | |
// CRT Emulation | |
// by Mattias | |
// https://www.shadertoy.com/view/lsB3DV | |
// Updated 2024.11.17 by Ninja Dynamics | |
// Define a push constant block to pass parameters to the shader | |
layout(push_constant) uniform Push { | |
vec4 SourceSize; // Size of the source texture | |
vec4 OriginalSize; // Original size of the content | |
vec4 OutputSize; // Size of the output (screen) | |
uint FrameCount; // Current frame count | |
float CURVATURE; // Screen curvature amount | |
float SCANSPEED; // Speed of scanline effect | |
float SCALE; // Resolution scale factor | |
float BLUR; // Strength of the blur effect | |
} params; | |
// Parameters for external control (e.g., in a shader editor) | |
#pragma parameter CURVATURE "Curvature" 0.0 0.0 1.0 0.05 | |
#pragma parameter SCANSPEED "Scanline Crawl Speed" 1.0 0.0 10.0 0.5 | |
#pragma parameter SCALE "Resolution Scale" 0.5 0.1 1.0 0.1 | |
#pragma parameter BLUR "Blur Strength" 0.5 0.0 1.0 0.05 | |
// Uniform buffer object containing the Model-View-Projection matrix | |
layout(std140, set = 0, binding = 0) uniform UBO { | |
mat4 MVP; // Model-View-Projection matrix | |
} global; | |
#pragma stage vertex | |
// Vertex shader inputs | |
layout(location = 0) in vec4 Position; // Vertex position | |
layout(location = 1) in vec2 TexCoord; // Vertex texture coordinate | |
// Vertex shader outputs to the fragment shader | |
layout(location = 0) out vec2 vTexCoord; // Pass-through texture coordinate | |
void main() { | |
gl_Position = global.MVP * Position; // Transform the vertex position | |
vTexCoord = TexCoord; // Pass the texture coordinate to the fragment shader | |
} | |
#pragma stage fragment | |
// Fragment shader inputs | |
layout(location = 0) in vec2 vTexCoord; // Interpolated texture coordinate from vertex shader | |
// Fragment shader output | |
layout(location = 0) out vec4 FragColor; // Final color output of the fragment | |
// Texture sampler for the source image | |
layout(set = 0, binding = 2) uniform sampler2D Source; | |
// Define macros for compatibility with Shadertoy variables | |
#define iChannel0 Source | |
#define iTime (float(params.FrameCount) / 60.0) | |
#define iResolution params.OutputSize.xy | |
#define fragCoord (vTexCoord.xy * params.OutputSize.xy) | |
// Function to sample the texture with gamma correction | |
vec3 sample_(sampler2D tex, vec2 tc) { | |
vec3 s = pow(texture(tex, tc).rgb, vec3(2.2)); // Apply gamma correction | |
return s; | |
} | |
// Function to perform a Gaussian blur on the texture | |
vec3 blur(sampler2D tex, vec2 tc, float offs) { | |
// Adjust blur strength based on the BLUR parameter | |
float blur_strength = 1.5 - params.BLUR; | |
// Calculate offset vectors for sampling neighboring pixels | |
vec4 xoffs = offs * vec4(-2.0, -1.0, 1.0, 2.0) / (iResolution.x * blur_strength * params.SCALE); | |
vec4 yoffs = offs * vec4(-2.0, -1.0, 1.0, 2.0) / (iResolution.y * blur_strength * params.SCALE); | |
vec3 color = vec3(0.0); // Initialize color accumulator | |
// Sample surrounding pixels with Gaussian weights | |
color += sample_(tex, tc + vec2(xoffs.x, yoffs.x)) * 0.00366; | |
color += sample_(tex, tc + vec2(xoffs.y, yoffs.x)) * 0.01465; | |
color += sample_(tex, tc + vec2( 0.0, yoffs.x)) * 0.02564; | |
color += sample_(tex, tc + vec2(xoffs.z, yoffs.x)) * 0.01465; | |
color += sample_(tex, tc + vec2(xoffs.w, yoffs.x)) * 0.00366; | |
color += sample_(tex, tc + vec2(xoffs.x, yoffs.y)) * 0.01465; | |
color += sample_(tex, tc + vec2(xoffs.y, yoffs.y)) * 0.05861; | |
color += sample_(tex, tc + vec2( 0.0, yoffs.y)) * 0.09524; | |
color += sample_(tex, tc + vec2(xoffs.z, yoffs.y)) * 0.05861; | |
color += sample_(tex, tc + vec2(xoffs.w, yoffs.y)) * 0.01465; | |
color += sample_(tex, tc + vec2(xoffs.x, 0.0)) * 0.02564; | |
color += sample_(tex, tc + vec2(xoffs.y, 0.0)) * 0.09524; | |
color += sample_(tex, tc + vec2( 0.0, 0.0)) * 0.15018; // Center pixel | |
color += sample_(tex, tc + vec2(xoffs.z, 0.0)) * 0.09524; | |
color += sample_(tex, tc + vec2(xoffs.w, 0.0)) * 0.02564; | |
color += sample_(tex, tc + vec2(xoffs.x, yoffs.z)) * 0.01465; | |
color += sample_(tex, tc + vec2(xoffs.y, yoffs.z)) * 0.05861; | |
color += sample_(tex, tc + vec2( 0.0, yoffs.z)) * 0.09524; | |
color += sample_(tex, tc + vec2(xoffs.z, yoffs.z)) * 0.05861; | |
color += sample_(tex, tc + vec2(xoffs.w, yoffs.z)) * 0.01465; | |
color += sample_(tex, tc + vec2(xoffs.x, yoffs.w)) * 0.00366; | |
color += sample_(tex, tc + vec2(xoffs.y, yoffs.w)) * 0.01465; | |
color += sample_(tex, tc + vec2( 0.0, yoffs.w)) * 0.02564; | |
color += sample_(tex, tc + vec2(xoffs.z, yoffs.w)) * 0.01465; | |
color += sample_(tex, tc + vec2(xoffs.w, yoffs.w)) * 0.00366; | |
return color; // Return the blurred color | |
} | |
// Function to generate pseudo-random numbers based on a 2D coordinate | |
float rand(vec2 co) { | |
float a = 12.9898; | |
float b = 78.233; | |
float c = 43758.5453; | |
float dt = dot(co.xy, vec2(a, b)); | |
float sn = mod(dt, 3.14); | |
return fract(sin(sn) * c); | |
} | |
// Function to apply a screen curvature effect to the UV coordinates | |
vec2 curve(vec2 uv) { | |
uv = (uv - 0.5) * 2.0; // Normalize UV coordinates to range [-1, 1] | |
uv *= 1.1; // Slightly scale up the UV coordinates | |
// Apply horizontal curvature based on the vertical position | |
uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0); | |
// Apply vertical curvature based on the horizontal position | |
uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0); | |
uv = (uv / 2.0) + 0.5; // Convert back to [0, 1] range | |
uv = uv * 0.92 + 0.04; // Add padding to UV coordinates | |
return uv; | |
} | |
void main() { | |
vec2 q = fragCoord.xy / iResolution.xy; // Normalized pixel coordinates [0, 1] | |
vec2 uv = q; // Copy of normalized coordinates | |
// Mix the original UV with the curved UV based on the CURVATURE parameter | |
uv = mix(uv, curve(uv), params.CURVATURE); | |
// Sample the original texture color at the current coordinates | |
vec3 oricol = texture(iChannel0, vec2(q.x, q.y)).xyz; | |
vec3 col; // Variable to hold the final color | |
// Calculate a small offset 'x' to create a subtle distortion effect | |
float x = sin(0.1 * iTime + uv.y * 21.0) * | |
sin(0.23 * iTime + uv.y * 29.0) * | |
sin(0.3 + 0.11 * iTime + uv.y * 31.0) * 0.0017; | |
// Offset to create a sub-pixel effect | |
float o = 2.0 * mod(fragCoord.y, 2.0) / iResolution.x; | |
x += o; | |
// Apply chromatic aberration by sampling the blurred texture at slightly offset positions for each color channel | |
col.r = 1.0 * blur(iChannel0, vec2(uv.x + 0.0009, uv.y + 0.0009), 1.2).x + 0.005; | |
col.g = 1.0 * blur(iChannel0, vec2(uv.x + 0.0000, uv.y - 0.0015), 1.2).y + 0.005; | |
col.b = 1.0 * blur(iChannel0, vec2(uv.x - 0.0015, uv.y + 0.0000), 1.2).z + 0.005; | |
// Additive blurring to enhance glow effects | |
col.r += 0.2 * blur(iChannel0, vec2(uv.x + 0.0009, uv.y + 0.0009), 2.25).x - 0.005; | |
col.g += 0.2 * blur(iChannel0, vec2(uv.x + 0.0000, uv.y - 0.0015), 1.75).y - 0.005; | |
col.b += 0.2 * blur(iChannel0, vec2(uv.x - 0.0015, uv.y + 0.0000), 1.25).z - 0.005; | |
// Simulate a ghosting effect using additional blur passes | |
float ghs = 0.05; // Ghosting strength | |
col.r += ghs * (1.0 - 0.299) * blur(iChannel0, 0.75 * vec2(0.01, -0.027) + vec2(uv.x + 0.001, uv.y + 0.001), 7.0).x; | |
col.g += ghs * (1.0 - 0.587) * blur(iChannel0, 0.75 * vec2(-0.022, -0.02) + vec2(uv.x + 0.000, uv.y - 0.002), 5.0).y; | |
col.b += ghs * (1.0 - 0.114) * blur(iChannel0, 0.75 * vec2(-0.02, -0.0) + vec2(uv.x - 0.002, uv.y + 0.000), 3.0).z; | |
// Adjust brightness and contrast | |
col = clamp(col * 0.4 + 0.6 * col * col * 1.0, 0.0, 1.0); | |
// Apply a vignette effect to darken the edges of the screen | |
float vig = (0.0 + 1.0 * 16.0 * uv.x * uv.y * (1.0 - uv.x) * (1.0 - uv.y)); | |
vig = pow(vig, 0.3); // Adjust the strength of the vignette | |
col *= vec3(vig); // Apply the vignette to the color | |
// Slight color tint adjustments to simulate CRT phosphors | |
col *= vec3(0.95, 1.05, 0.95); | |
// Enhance the color by mixing it with its square (bloom effect) | |
col = mix(col, col * col, 0.3) * 3.8; | |
// Create a scanline effect | |
float scans = clamp( | |
0.35 + 0.15 * sin(3.5 * (iTime * params.SCANSPEED) + uv.y * (iResolution.y * params.SCALE) * 1.5), | |
0.0, | |
1.0 | |
); | |
float s = pow(scans, 0.9); // Adjust scanline brightness | |
col = col * vec3(s); // Apply the scanline effect | |
// Add a subtle flickering effect | |
col *= 1.0 + 0.0015 * sin(300.0 * iTime); | |
// Simulate horizontal lines (interlacing) | |
col *= 1.0 - 0.15 * vec3(clamp((mod(fragCoord.x + o, 2.0) - 1.0) * 2.0, 0.0, 1.0)); | |
// Add noise to simulate signal interference | |
col *= vec3(1.0) - 0.25 * vec3( | |
rand(uv + 0.0001 * iTime), | |
rand(uv + 0.0001 * iTime + 0.3), | |
rand(uv + 0.0001 * iTime + 0.5) | |
); | |
// Apply gamma correction to the final color | |
col = pow(col, vec3(0.45)); | |
// Black out areas outside the UV range [0, 1] to simulate screen edges | |
if (uv.x < 0.0 || uv.x > 1.0) col *= 0.0; | |
if (uv.y < 0.0 || uv.y > 1.0) col *= 0.0; | |
// Compute a value 'comp' for potential use (currently unused) | |
float comp = smoothstep(0.1, 0.9, sin(iTime)); | |
FragColor = vec4(col, 1.0); // Output the final color with full opacity | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment