more work on the ui theme and changing the hud
This commit is contained in:
340
Shaders/crt_harrison.gdshader
Normal file
340
Shaders/crt_harrison.gdshader
Normal file
@@ -0,0 +1,340 @@
|
||||
// CRT Shader by Harrison Allen
|
||||
// V4
|
||||
|
||||
shader_type canvas_item;
|
||||
|
||||
/**
|
||||
The input texture that will have the CRT effect applied to it.
|
||||
Scanline count will be determined by this texture's height.
|
||||
You'll need to use a texture that's roughly screen height / 4.5
|
||||
(for instance 240 on a 1080 monitor or 480 on a 4k monitor)
|
||||
Else The scanlines won't properly resolve and you're get moiré patters.
|
||||
*/
|
||||
uniform sampler2D tex: filter_linear;
|
||||
|
||||
|
||||
/**
|
||||
Set the type of mask this CRT will have.
|
||||
Dots: emulates a typical PC CRT monitor.
|
||||
Grille: emulates an aperture grille. Good with higher curve values.
|
||||
Wide Grille: more suitable for 4k monitors.
|
||||
Soft Grille: very close to wide grille, but a little softer.
|
||||
Slot mask: this is the pattern found on most TVs, but it can clash with the
|
||||
scanlines unless the input texture resolution is halved.
|
||||
*/
|
||||
uniform int mask_type : hint_enum(
|
||||
"Dots:1",
|
||||
"Aperture Grille:2",
|
||||
"Wide Grille:3",
|
||||
"Wide Soft Grille:4",
|
||||
"Slot Mask:5",
|
||||
"Null:0") = 1;
|
||||
|
||||
uniform float curve : hint_range(0.0, 0.5) = 0.0;
|
||||
|
||||
/**
|
||||
Controls how sharp the image is. Low values are fun with dithering, but a
|
||||
value of 0.5 will destroy high frequency details and render small text
|
||||
illegible. Use with care.
|
||||
*/
|
||||
uniform float sharpness : hint_range(0.5, 1.0) = 0.6666666666666666666666666667;
|
||||
|
||||
/**
|
||||
Use to offset color channels from each other. I've personally observed this
|
||||
effect in real CRTs. It can go in either direction, and some CRTs are better
|
||||
aligned than others.
|
||||
I'd suggest offsetting this at least a little bit from the default value.
|
||||
Note that because of the typical RGB subpixel layout on on LCD, a very small
|
||||
positive value will actually align colors better (it depends on
|
||||
screen size), and -0.5 is just slightly more misaligned than 0.5, which is
|
||||
important if you want to be as misaligned as possible.
|
||||
*/
|
||||
uniform float color_offset : hint_range(-0.5, 0.5) = 0.0;
|
||||
|
||||
/**
|
||||
Reduce to preserve phosphor mask details in highlights at the cost of
|
||||
overall brightness. This should usually be kept at or near 1
|
||||
*/
|
||||
uniform float mask_brightness : hint_range(0, 1) = 1.0;
|
||||
|
||||
/**
|
||||
Reduce to preserve scanline details in highlights at the cost of
|
||||
overall brightness. This should usually be kept at or near 1
|
||||
*/
|
||||
uniform float scanline_brightness : hint_range(0.5, 1.0) = 1.0;
|
||||
|
||||
/**
|
||||
Raising this value can help reduce Moiré patterns.
|
||||
A value of 1 will eliminate scanlines entirely.
|
||||
*/
|
||||
uniform float min_scanline_thickness : hint_range(0.25, 1.0) = 0.5;
|
||||
|
||||
/**
|
||||
This should be the input texture's height divided by width.
|
||||
Only important if curve is used.
|
||||
For 16:9, this should be 0.5625.
|
||||
*/
|
||||
uniform float aspect : hint_range(0.5, 1.0) = 0.75;
|
||||
|
||||
/**
|
||||
This controls slight horizontal shaking. This is set to 0 (off) by default.
|
||||
*/
|
||||
uniform float wobble_strength : hint_range(0.0, 1.0) = 0.0;
|
||||
|
||||
varying flat float wobble;
|
||||
|
||||
void vertex()
|
||||
{
|
||||
wobble = cos(TIME * TAU * 15.0) * wobble_strength / 8192.0;
|
||||
}
|
||||
|
||||
vec2 warp(vec2 uv, float _aspect, float _curve)
|
||||
{
|
||||
// Centralize coordinates
|
||||
uv -= 0.5;
|
||||
|
||||
uv.x /= _aspect;
|
||||
|
||||
// Squared distance from the middle
|
||||
float warping = dot(uv, uv) * _curve;
|
||||
|
||||
// Compensate for shrinking
|
||||
warping -= _curve * 0.25;
|
||||
|
||||
// Warp the coordinates
|
||||
uv /= 1.0 - warping;
|
||||
|
||||
uv.x *= _aspect;
|
||||
|
||||
// Decentralize the coordinates
|
||||
uv += 0.5;
|
||||
|
||||
return uv;
|
||||
}
|
||||
|
||||
vec3 linear_to_srgb(vec3 col)
|
||||
{
|
||||
return mix(
|
||||
(pow(col, vec3(1.0 / 2.4)) * 1.055) - 0.055,
|
||||
col * 12.92,
|
||||
lessThan(col, vec3(0.0031318))
|
||||
);
|
||||
}
|
||||
|
||||
vec3 srgb_to_linear(vec3 col)
|
||||
{
|
||||
return mix(
|
||||
pow((col + 0.055) / 1.055, vec3(2.4)),
|
||||
col / 12.92,
|
||||
lessThan(col, vec3(0.04045))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Get scanlines from coordinates (returns in linear color)
|
||||
vec3 scanlines(vec2 uv)
|
||||
{
|
||||
// Set coordinates to match texture dimensions
|
||||
uv *= vec2(textureSize(tex, 0));
|
||||
|
||||
// Vertical coordinate scanline samples
|
||||
int y = int(uv.y + 0.5) - 1;
|
||||
|
||||
float x = floor(uv.x);
|
||||
|
||||
// Horizontal coordinates for the texture samples
|
||||
float ax = x - 2.0;
|
||||
float bx = x - 1.0;
|
||||
float cx = x;
|
||||
float dx = x + 1.0;
|
||||
float ex = x + 2.0;
|
||||
|
||||
// Sample the texture at various points
|
||||
vec3 upper_a = texelFetch(tex, ivec2(int(ax), y), 0).rgb;
|
||||
vec3 upper_b = texelFetch(tex, ivec2(int(bx), y), 0).rgb;
|
||||
vec3 upper_c = texelFetch(tex, ivec2(int(cx), y), 0).rgb;
|
||||
vec3 upper_d = texelFetch(tex, ivec2(int(dx), y), 0).rgb;
|
||||
vec3 upper_e = texelFetch(tex, ivec2(int(ex), y), 0).rgb;
|
||||
|
||||
// Adjust the vertical coordinate for the lower scanline
|
||||
y += 1;
|
||||
|
||||
// Sample the texture at various points
|
||||
vec3 lower_a = texelFetch(tex, ivec2(int(ax), y), 0).rgb;
|
||||
vec3 lower_b = texelFetch(tex, ivec2(int(bx), y), 0).rgb;
|
||||
vec3 lower_c = texelFetch(tex, ivec2(int(cx), y), 0).rgb;
|
||||
vec3 lower_d = texelFetch(tex, ivec2(int(dx), y), 0).rgb;
|
||||
vec3 lower_e = texelFetch(tex, ivec2(int(ex), y), 0).rgb;
|
||||
|
||||
// Convert every sample to linear color
|
||||
upper_a = srgb_to_linear(upper_a);
|
||||
upper_b = srgb_to_linear(upper_b);
|
||||
upper_c = srgb_to_linear(upper_c);
|
||||
upper_d = srgb_to_linear(upper_d);
|
||||
upper_e = srgb_to_linear(upper_e);
|
||||
|
||||
lower_a = srgb_to_linear(lower_a);
|
||||
lower_b = srgb_to_linear(lower_b);
|
||||
lower_c = srgb_to_linear(lower_c);
|
||||
lower_d = srgb_to_linear(lower_d);
|
||||
lower_e = srgb_to_linear(lower_e);
|
||||
|
||||
// The x coordinates of electron beam offsets
|
||||
vec3 beam = vec3(uv.x - 0.5);
|
||||
beam.r -= color_offset;
|
||||
beam.b += color_offset;
|
||||
|
||||
// Calculate weights
|
||||
vec3 weight_a = smoothstep(1, 0, (beam - ax) * sharpness);
|
||||
vec3 weight_b = smoothstep(1, 0, (beam - bx) * sharpness);
|
||||
vec3 weight_c = smoothstep(1, 0, abs(beam - cx) * sharpness);
|
||||
vec3 weight_d = smoothstep(1, 0, (dx - beam) * sharpness);
|
||||
vec3 weight_e = smoothstep(1, 0, (ex - beam) * sharpness);
|
||||
|
||||
// This can be a fun place to raise each weight to some power
|
||||
|
||||
// Mix samples into the upper scanline color
|
||||
vec3 upper_col = vec3(
|
||||
upper_a * weight_a +
|
||||
upper_b * weight_b +
|
||||
upper_c * weight_c +
|
||||
upper_d * weight_d +
|
||||
upper_e * weight_e
|
||||
);
|
||||
|
||||
// Mix samples into the lower scanline color
|
||||
vec3 lower_col = vec3(
|
||||
lower_a * weight_a +
|
||||
lower_b * weight_b +
|
||||
lower_c * weight_c +
|
||||
lower_d * weight_d +
|
||||
lower_e * weight_e
|
||||
);
|
||||
|
||||
vec3 weight_scaler = vec3(1.0) / (weight_a + weight_b + weight_c + weight_d + weight_e);
|
||||
|
||||
// Normalize weight
|
||||
upper_col *= weight_scaler;
|
||||
lower_col *= weight_scaler;
|
||||
|
||||
// Apply scanline brightness
|
||||
upper_col *= scanline_brightness;
|
||||
lower_col *= scanline_brightness;
|
||||
|
||||
|
||||
// Scanline size (and roughly the apperent brightness of this line)
|
||||
vec3 upper_thickness = mix(vec3(min_scanline_thickness), vec3(1.0), upper_col);
|
||||
vec3 lower_thickness = mix(vec3(min_scanline_thickness), vec3(1.0), lower_col);
|
||||
|
||||
// Vertical sawtooth wave used to generate scanlines
|
||||
// Almost the same as fract(uv.y + 0.5), but prevents a rare visual bug
|
||||
float sawtooth = (uv.y + 0.5) - float(y);
|
||||
|
||||
vec3 upper_line = vec3(sawtooth) / upper_thickness;
|
||||
upper_line = smoothstep(1.0, 0.0, upper_line);
|
||||
|
||||
vec3 lower_line = vec3(1.0 - sawtooth) / lower_thickness;
|
||||
lower_line = smoothstep(1.0, 0.0, lower_line);
|
||||
|
||||
// Correct line brightness below min_scanline_thickness
|
||||
upper_line *= upper_col / upper_thickness;
|
||||
lower_line *= lower_col / lower_thickness;
|
||||
|
||||
// Combine the upper and lower scanlines
|
||||
return upper_line + lower_line;
|
||||
}
|
||||
|
||||
vec4 generate_mask(vec2 fragcoord)
|
||||
{
|
||||
switch (mask_type)
|
||||
{
|
||||
case 1: // Dots
|
||||
const vec3 pattern[] = {vec3(1,0,0), vec3(0,1,0), vec3(0,0,1), vec3(0,0,0)};
|
||||
|
||||
ivec2 icoords = ivec2(fragcoord);
|
||||
|
||||
return vec4(pattern[(icoords.y * 2 + icoords.x) % 4], 0.25);
|
||||
|
||||
case 2: // Grille
|
||||
const vec3 pattern[] = {vec3(0,1,0), vec3(1,0,1)};
|
||||
|
||||
return vec4(pattern[int(fragcoord.x) % 2], 0.5);
|
||||
|
||||
case 3: // Wide grille
|
||||
const vec3 pattern[] = {
|
||||
vec3(1,0,0), vec3(0,1,0), vec3(0,0,1), vec3(0,0,0)};
|
||||
|
||||
return vec4(pattern[int(fragcoord.x) % 4], 0.25);
|
||||
|
||||
case 4: // Grille wide soft
|
||||
const vec3 pattern[] = {
|
||||
vec3(1.0,0.125,0.0),
|
||||
vec3(0.125,1.0,0.125),
|
||||
vec3(0.0,0.125,1.0),
|
||||
vec3(0.125,0.0,0.125)};
|
||||
|
||||
return vec4(pattern[int(fragcoord.x) % 4], 0.3125);
|
||||
|
||||
case 5: // Slotmask
|
||||
const vec3 pattern[] = {
|
||||
vec3(1,0,1), vec3(0,1,0), vec3(1,0,1), vec3(0,1,0),
|
||||
vec3(0,0,1), vec3(0,1,0), vec3(1,0,0), vec3(0,0,0),
|
||||
vec3(1,0,1), vec3(0,1,0), vec3(1,0,1), vec3(0,1,0),
|
||||
vec3(1,0,0), vec3(0,0,0), vec3(0,0,1), vec3(0,1,0)
|
||||
};
|
||||
|
||||
ivec2 icoords = ivec2(fragcoord) % 4;
|
||||
|
||||
return vec4(pattern[icoords.y * 4 + icoords.x], 0.375);
|
||||
|
||||
default:
|
||||
return vec4(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// Add phosphor mask/grill
|
||||
vec3 mask(vec3 linear_color, vec2 fragcoord)
|
||||
{
|
||||
// Get the pattern for the mask. Mask.w equals avg. brightness of the mask
|
||||
vec4 mask = generate_mask(fragcoord);
|
||||
|
||||
// Dim the color if brightness is reduced to preserve mask details
|
||||
linear_color *= mix(mask.w, 1.0, mask_brightness);
|
||||
|
||||
// How bright the color needs to be to maintain 100% brightness while masked
|
||||
vec3 target_color = linear_color / mask.w;
|
||||
|
||||
// Target color limited to the 0 to 1 range.
|
||||
vec3 primary_col = clamp(target_color, 0.0, 1.0);
|
||||
|
||||
// This calculates how bright the secondary subpixels will need to be
|
||||
vec3 highlights = target_color - primary_col;
|
||||
highlights /= 1.0 / mask.w - 1.0;
|
||||
|
||||
primary_col *= mask.rgb;
|
||||
|
||||
// Add the secondary subpixels
|
||||
primary_col += highlights * (1.0 - mask.rgb);
|
||||
|
||||
return primary_col;
|
||||
}
|
||||
|
||||
void fragment()
|
||||
{
|
||||
// Warp UV coordinates
|
||||
vec2 warped_coords = warp(UV, aspect, curve);
|
||||
|
||||
// Add wobble
|
||||
warped_coords.x += wobble;
|
||||
|
||||
// Sample the scanlines
|
||||
vec3 col = scanlines(warped_coords);
|
||||
|
||||
// Apply phosphor mask
|
||||
col = mask(col, FRAGCOORD.xy);
|
||||
|
||||
// Convert back to srgb
|
||||
col = linear_to_srgb(col);
|
||||
|
||||
COLOR.rgb = col;
|
||||
}
|
||||
1
Shaders/crt_harrison.gdshader.uid
Normal file
1
Shaders/crt_harrison.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c5onkcewwqwoh
|
||||
Reference in New Issue
Block a user