Skip to content

Commit 948fde7

Browse files
committed
Add CRT filter, barrel distortion, and bezel vignette to Bioluminescent Waves visualizer
1 parent 0e01ed1 commit 948fde7

2 files changed

Lines changed: 61 additions & 6 deletions

File tree

src/engine.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,18 +1145,31 @@ impl<'a> VulkanEngine<'a> {
11451145
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(r#"
11461146
struct VertexOutput {
11471147
@builtin(position) clip_position: vec4<f32>,
1148+
@location(0) ndc: vec2<f32>,
11481149
}
11491150
@vertex
11501151
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput {
11511152
var out: VertexOutput;
11521153
let x = f32((in_vertex_index << 1u) & 2u) * 2.0 - 1.0;
11531154
let y = f32(in_vertex_index & 2u) * 2.0 - 1.0;
1154-
out.clip_position = vec4<f32>(x, y, 1.0, 1.0);
1155+
1156+
let ndc = vec2<f32>(x, y);
1157+
let r2 = dot(ndc, ndc);
1158+
let distorted_ndc = ndc * (1.0 + r2 * 0.055);
1159+
1160+
out.clip_position = vec4<f32>(distorted_ndc, 1.0, 1.0);
1161+
out.ndc = distorted_ndc;
11551162
return out;
11561163
}
11571164
@fragment
1158-
fn fs_main() -> @location(0) vec4<f32> {
1159-
return vec4<f32>(0.001, 0.003, 0.008, 1.0);
1165+
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
1166+
if (abs(in.ndc.x) > 1.0 || abs(in.ndc.y) > 1.0) {
1167+
discard;
1168+
}
1169+
let border_dist = min(1.0 - abs(in.ndc.x), 1.0 - abs(in.ndc.y));
1170+
let bezel_mask = smoothstep(0.0, 0.03, border_dist);
1171+
1172+
return vec4<f32>(vec3<f32>(0.001, 0.003, 0.008) * bezel_mask, 1.0);
11601173
}
11611174
"#)),
11621175
});

src/shaders/vis_bioluminescence.wgsl

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ struct VertexOutput3D {
2727
@location(0) uv: vec2<f32>,
2828
@location(1) energy: f32,
2929
@location(2) depth: f32,
30+
@location(3) @interpolate(linear) ndc: vec2<f32>,
3031
}
3132

3233
@vertex
@@ -41,6 +42,7 @@ fn vs_main_3d(in: VertexInput, @builtin(instance_index) inst_idx: u32) -> Vertex
4142
out.uv = vec2<f32>(0.0);
4243
out.energy = 0.0;
4344
out.depth = 0.0;
45+
out.ndc = vec2<f32>(0.0);
4446
return out;
4547
}
4648

@@ -58,20 +60,44 @@ fn vs_main_3d(in: VertexInput, @builtin(instance_index) inst_idx: u32) -> Vertex
5860
out.uv = vec2<f32>(0.0);
5961
out.energy = 0.0;
6062
out.depth = 0.0;
63+
out.ndc = vec2<f32>(0.0);
6164
return out;
6265
}
6366

64-
out.clip_position = camera.proj_matrix * view_pos;
67+
var clip_pos = camera.proj_matrix * view_pos;
68+
69+
// Apply barrel distortion in clip space (NDC)
70+
let ndc = clamp(clip_pos.xy / clip_pos.w, vec2<f32>(-2.0), vec2<f32>(2.0));
71+
let r2 = min(2.0, dot(ndc, ndc));
72+
let distorted_ndc = ndc * (1.0 + r2 * 0.055);
73+
clip_pos = vec4<f32>(distorted_ndc * clip_pos.w, clip_pos.z, clip_pos.w);
74+
75+
out.clip_position = clip_pos;
6576
out.uv = in.tex_coords;
6677
out.energy = p.vel.w;
6778
out.depth = depth;
79+
out.ndc = distorted_ndc;
6880

6981
return out;
7082
}
7183

84+
fn hash21(p: vec2<f32>) -> f32 {
85+
var p3 = fract(vec3<f32>(p.xyx) * 0.1031);
86+
p3 = p3 + dot(p3, p3.yzx + 33.33);
87+
return fract((p3.x + p3.y) * p3.z);
88+
}
89+
7290
@fragment
7391
fn fs_main(in: VertexOutput3D) -> @location(0) vec4<f32> {
74-
// Circle clip on each face using UV coordinate distance from center
92+
// 1. Bezel boundary check
93+
if (abs(in.ndc.x) > 1.0 || abs(in.ndc.y) > 1.0) {
94+
discard;
95+
}
96+
97+
let border_dist = min(1.0 - abs(in.ndc.x), 1.0 - abs(in.ndc.y));
98+
let bezel_mask = smoothstep(0.0, 0.03, border_dist);
99+
100+
// 2. Circle clip on each face using UV coordinate distance from center
75101
let dist = length(in.uv - vec2<f32>(0.5, 0.5));
76102
if (dist > 0.5) {
77103
discard;
@@ -96,7 +122,23 @@ fn fs_main(in: VertexOutput3D) -> @location(0) vec4<f32> {
96122

97123
// Add distance fog to match the deep ocean depth feel
98124
let fog_factor = smoothstep(5.0, 35.0, in.depth);
99-
let final_color = mix(color * intensity, vec3<f32>(0.001, 0.003, 0.008), fog_factor);
125+
var final_color = mix(color * intensity, vec3<f32>(0.001, 0.003, 0.008), fog_factor);
126+
127+
// Apply bezel vignette mask
128+
final_color = final_color * bezel_mask;
129+
130+
// CRT Filter: Scanlines
131+
let scanline = 0.86 + 0.14 * cos(in.clip_position.y * 3.14159);
132+
final_color = final_color * scanline;
133+
134+
// CRT Filter: Flicker
135+
let flicker = 0.98 + 0.02 * sin(audio.time * 115.0);
136+
final_color = final_color * flicker;
137+
138+
// CRT Filter: Analog static noise
139+
let noise_val = hash21(in.clip_position.xy + fract(audio.smooth_time) * 149.0);
140+
let static_noise = noise_val * 0.022 * bezel_mask;
141+
final_color = final_color + vec3<f32>(static_noise);
100142

101143
return vec4<f32>(final_color, 1.0);
102144
}

0 commit comments

Comments
 (0)