Skip to content

Instantly share code, notes, and snippets.

@ninjadynamics
Last active November 18, 2024 08:47
Show Gist options
  • Save ninjadynamics/a341dc27ed2dfc91b7e8cad459d67ffb to your computer and use it in GitHub Desktop.
Save ninjadynamics/a341dc27ed2dfc91b7e8cad459d67ffb to your computer and use it in GitHub Desktop.
CRT-Mattias-Blur
#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