summaryrefslogtreecommitdiff
path: root/src/shaders/math/color.wgsl
blob: f5f06f88c5f322dce3c95e3054b756c51e9a459e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 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);
}