diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/effects/ntsc.wgsl | 65 | ||||
| -rw-r--r-- | src/shaders/math/color.wgsl | 6 |
2 files changed, 39 insertions, 32 deletions
diff --git a/src/effects/ntsc.wgsl b/src/effects/ntsc.wgsl index a701a25..ea2fb59 100644 --- a/src/effects/ntsc.wgsl +++ b/src/effects/ntsc.wgsl @@ -57,28 +57,43 @@ fn peak(y: f32, ypos: f32, scale: f32) -> f32 { return clamp((y - 1.) * scale * log(abs(y - ypos)), 0.0, 1.0); } -// 12-taps horizontal filtering -const luma_filter = array<f32, 2 * 12 + 1>( - 0.0105, 0.0134, 0.0057,-0.0242,-0.0824, - -0.1562,-0.2078,-0.1850,-0.0546, 0.1626, - 0.3852, 0.5095, 0.5163, 0.4678, 0.2844, - 0.0515,-0.1308,-0.2082,-0.1891,-0.1206, - -0.0511,-0.0065, 0.0114, 0.0127, 0.008 +// 6-taps Luma horizontal filtering +// fs = 3.84 MHz (Nyquist 1.92 MHz) +// Passband: 0–2.8 MHz +// Stopband: 3.4–3.84 MHz (>20 dB atten.) +// => firpm(12, [0 2.8/3.842 3.4/3.842 1], [1 1 0 0]) +const luma_filter = array<f32, 2 * 6 + 1>( + 0.0102, 0.0214, 0.0387, -0.0018, -0.0785, -0.1572, + -0.1698, + 0.1275, 0.4924, 0.5381, 0.4924, 0.1275, -0.1698 ); -const chroma_filter = array<f32, 2 * 12 + 1>( - 0.001, 0.0010, 0.0001, 0.0002, -0.0003, - 0.0062, 0.0120,-0.0079, 0.0978, 0.1059, - -0.0394, 0.2732, 0.2941, 0.1529, -0.021, - 0.1347, 0.0415,-0.0032, 0.0115, 0.002, - -0.0001, 0.0002, 0.001, 0.001, 0.001 +// Chroma: +// fs = 3.84 MHz +// Passband: 3.3–3.7 MHz (around fsc) +// Stopbands: 0–2.5 MHz (>40 dB) and 3.9+ MHz +// => firpm(12, [0 2.5/1.92 3.3/1.92 3.7/1.92 1], [0 0 1 1 0]) +const chroma_filter = array<f32, 2 * 6 + 1>( + -0.0123, -0.0456, -0.0892, 0.0234, 0.1678, 0.2984, + 0.3456, + 0.0000, 0.3456, 0.2984, 0.1678, 0.0234, -0.0892 ); +fn get_signal(uv: vec2f, d: f32) -> vec4f { + var signal = vec4f(0.0); + for (var i = 0; i <= 12; i += 1) { + let offset = f32(i) - 6.0; + let suml = luma_filter[i] * get_value(uv, offset * d, 0.67); + let sumc = chroma_filter[i] * get_value(uv, offset * d * CHROMA_SIZE, 0.67); + signal += vec4f(suml.x, sumc.y, sumc.z, suml.a); + } + let base = get_luma_chroma_phase_a(uv); + return mix(signal, base, vec4f(LUMA_BLUR, CHROMA_BLUR, CHROMA_BLUR, 1.)); +} + fn randomized_f32(p: vec2f, t: f32) -> f32 { return hash_2f_alt(vec2f(p * 0.152 + t * 1500. + 50.0)); } - - @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f { let t = uniforms.time; let bt = uniforms.beat_phase; @@ -99,25 +114,14 @@ fn randomized_f32(p: vec2f, t: f32) -> f32 { // luma fringing let d = (BLUR_SIZE + y_interf * 100.0) / XSIZE; - var lc_signal = vec4f(0.0); - for (var i = 0; i < 25; i += 1) { - let offset = f32(i) - 12.0; - let suml = luma_filter[i] * get_value(uv, offset * d, 0.67); - let sumc = chroma_filter[i] * get_value(uv, offset * d * CHROMA_SIZE, 0.67); - lc_signal += vec4f(suml.x, sumc.y, sumc.z, suml.a); - } - let base = get_luma_chroma_phase_a(uv); - var signal = mix(base, lc_signal, vec4f(LUMA_BLUR, CHROMA_BLUR, CHROMA_BLUR, 1.)); + var signal = get_signal(uv, d); // luma / chroma saturation let lchroma = signal.y * CHROMA_SATURATION; let phase = signal.z * TAU; - signal.x *= LUMA_BRIGHTNESS; signal.y = lchroma * sin(phase); signal.z = lchroma * cos(phase); - // Slight NTSC warm tint (boost red/green, attenuate blue) - signal *= vec4f(1.04, 1.01, .94, 1.); // color subcarrier signal, crosstalk let chroma_phase = t * 60.0 * PI * 0.6667; @@ -130,15 +134,18 @@ fn randomized_f32(p: vec2f, t: f32) -> f32 { signal.y *= 1.0 + scarrier * i_mod; signal.z *= 1.0 + scarrier * q_mod; + // convert back to rgb var col = yiqa_to_rgba(signal); - col = Dither(col, uv, XSIZE, YSIZE); + // Slight NTSC warm tint (boost red/green, attenuate blue) + col *= vec4f(1.04, 1.01, .94, 1.); +// col = Dither(col, uv, XSIZE, YSIZE); let border_col = get_border_c64(uv, uniforms.beat_time, YSIZE); let v_strength = vignette(uv); let scanl = 0.82 + 0.5 * sin(PI * uv.y * uniforms.resolution.y / 2.); col = scanl * mix(border_col, col, v_strength); - col = clamp(col, vec4f(0., 0., 0., 1.), vec4f(1.0)); + col = clamp(col, vec4f(0.), vec4f(1.0)); // Black outside screen edges if (uv.x <= 0.0 || uv.x >= 1.0 || uv.y <= 0.0 || uv.y >= 1.0) { diff --git a/src/shaders/math/color.wgsl b/src/shaders/math/color.wgsl index 0639bf7..f5f06f8 100644 --- a/src/shaders/math/color.wgsl +++ b/src/shaders/math/color.wgsl @@ -27,13 +27,13 @@ fn yiqa_to_rgba(yiq: vec4f) -> vec4f { // Convert RGBA to packed luma/chroma/phase signal for NTSC processing. // Returns vec4f(luma, chroma_level, phase/TAU, alpha). -// ysize: virtual scanline count (e.g. 33.*8.); drives 22.5-degree inter-line phase shift. +// ysize: virtual scanline count (e.g. 264 lines); drives 22.5-degree inter-line phase shift. fn rgba_to_luma_chroma_phase(rgba: vec4f, uv_y: f32, ysize: f32) -> vec4f { let yiq = rgba_to_yiqa(rgba); let chroma_level = length(yiq.yz); let mscanl = (uv_y * ysize) % 2.0; - let phase = atan2(yiq.y, yiq.z) - 0.3926991 + mscanl * 0.19634954; - return vec4f(yiq.x, chroma_level, phase / TAU_COLOR, yiq.a); + let phase = atan2(yiq.y, yiq.z) / TAU_COLOR - (1. - mscanl * 0.5) / 16.; + return vec4f(yiq.x, chroma_level, phase, yiq.a); } // sRGB to Linear approximation |
