// Common color space and tone mapping functions. // Includes: sRGB, ACES, HSV, YIQ (NTSC) conversions. // Use rgba_to_luma_chroma_phase() to encode YIQ into a temp buffer for NTSC-style effects. const TAU_COLOR = 6.28318530718; // RGB <-> YIQ (NTSC) color space fn rgba_to_yiqa(rgba: vec4f) -> vec4f { const m = mat4x4f( 0.299, 0.596, 0.211, 0., 0.587, -0.274, -0.523, 0., 0.114, -0.322, 0.312, 0., 0., 0., 0., 1., ); return m * rgba; } fn yiqa_to_rgba(yiq: vec4f) -> vec4f { const m = mat4x4f( 1.000, 1.000, 1.000, 0., 0.956, -0.272, -1.106, 0., 0.621, -0.647, 1.703, 0., 0., 0., 0., 1., ); return m * yiq; } // 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. 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) / TAU_COLOR - (1. - mscanl * 0.5) / 16.; return vec4f(yiq.x, chroma_level, phase, yiq.a); } // sRGB to Linear approximation // Note: Assumes input is in sRGB color space. fn sRGB(t: vec3f) -> vec3f { return mix(1.055 * pow(t, vec3f(1.0/2.4)) - 0.055, 12.92 * t, step(t, vec3f(0.0031308))); } // ACES Filmic Tone Mapping (Approximate) // A common tone mapping algorithm used in games and film. fn aces_approx(v_in: vec3f) -> vec3f { var v = max(v_in, vec3f(0.0)); v *= 0.6; let a = 2.51; let b = 0.03; let c = 2.43; let d = 0.59; let e = 0.14; return clamp((v * (a * v + b)) / (v * (c * v + d) + e), vec3f(0.0), vec3f(1.0)); } // HSV to RGB conversion const hsv2rgb_K = vec4f(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); fn hsv2rgb(c: vec3f) -> vec3f { let p = abs(fract(c.xxx + hsv2rgb_K.xyz) * 6.0 - hsv2rgb_K.www); return c.z * mix(hsv2rgb_K.xxx, clamp(p - hsv2rgb_K.xxx, vec3f(0.0), vec3f(1.0)), c.y); }