Skip to content

Color

The color/ registry collects small pure WGSL functions for color-space conversions, channel adjustments, and tone mapping. Each entry below is a single function you can pull into a composition by slug; the function itself never owns a vertex or fragment stage, so you decide how the result gets shown on screen.

A typical use is to chain helpers in your fragment stage — convert into a working space (sRGB or linear), reshape with grading or saturation, then tonemap before you write to the swap chain:

let main = r#"
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let lin = srgb_to_linear(input.rgb);
let graded = saturation(contrast(lin, 1.1), 1.2);
return vec4<f32>(linear_to_srgb(tonemap_aces(graded)), 1.0);
}
"#;
let shader = Shader::new(&[
"color/srgb_to_linear",
"color/linear_to_srgb",
"color/contrast",
"color/saturation",
"color/tonemap_aces",
main,
])?;

Each preview below renders the function on a 256×256 frame: most show an input gradient on top and the transformed output below, separated by a 2-pixel gutter. Tone-mapping previews use a synthetic 0..6× HDR ramp.

Linear contrast around mid-gray (0.5). c: color, k: factor (1 = identity).

fn contrast(c: vec3<f32>, k: f32) -> vec3<f32>

color/contrast preview

H, S, L in [0, 1] to linear-space RGB in [0, 1].

fn hsl_to_rgb(hsl: vec3<f32>) -> vec3<f32>

color/hsl_to_rgb preview

H, S, V in [0, 1] to RGB in [0, 1].

fn hsv_to_rgb(hsv: vec3<f32>) -> vec3<f32>

color/hsv_to_rgb preview

Rotate the hue of a linear-RGB color by radians via a YIQ-style rotation matrix. Cheap and stays in RGB — handy when you just need a wash without a round-trip through HSV.

fn hue_shift(c: vec3<f32>, radians: f32) -> vec3<f32>

color/hue_shift preview

Approximate blackbody color temperature in Kelvin to linear RGB (1000..40000 K). Tanner Helland’s piecewise approximation.

fn kelvin_to_rgb(k: f32) -> vec3<f32>

color/kelvin_to_rgb preview

DaVinci-style color grade. lift and gain scale shadows and highlights; gamma shapes the midtones. Typical ranges: lift [-0.5, 0.5], gamma [0.1, 3.0], gain [0.5, 2.0].

fn lift_gamma_gain(c: vec3<f32>, lift: vec3<f32>, gamma: vec3<f32>, gain: vec3<f32>) -> vec3<f32>

color/lift_gamma_gain preview

Linear-light sRGB to Björn Ottosson’s OkLab — a perceptually uniform space. Ideal for interpolation, lightness adjustments, and chroma work.

fn linear_srgb_to_oklab(c: vec3<f32>) -> vec3<f32>

color/linear_srgb_to_oklab preview

The inverse sRGB EOTF. Encode a linear-light value before writing it to a non-linear (sRGB) output surface.

fn linear_to_srgb(c: vec3<f32>) -> vec3<f32>

color/linear_to_srgb preview

Rec. 709 relative luminance of a linear-RGB color. The standard scalar weight set for “perceived brightness” in HD video.

fn luminance(c: vec3<f32>) -> f32

color/luminance preview

Inverse of linear_srgb_to_oklab. Converts OkLab (L, a, b) back to linear-light sRGB. Out-of-gamut inputs are returned unclamped.

fn oklab_to_linear_srgb(c: vec3<f32>) -> vec3<f32>

color/oklab_to_linear_srgb preview

OkLCh — polar OkLab with (L, C, h) and h in radians — to linear sRGB. Lets you treat chroma and hue as independent axes when grading.

fn oklch_to_linear_srgb(lch: vec3<f32>) -> vec3<f32>

color/oklch_to_linear_srgb preview

RGB in [0, 1] to (H, S, L) in [0, 1].

fn rgb_to_hsl(c: vec3<f32>) -> vec3<f32>

color/rgb_to_hsl preview

RGB in [0, 1] to (H, S, V) in [0, 1]. Sam Hocevar’s branchless form.

fn rgb_to_hsv(c: vec3<f32>) -> vec3<f32>

color/rgb_to_hsv preview

Adjust saturation by mixing toward Rec.709 luminance. s < 1 desaturates, s > 1 amplifies, s == 0 produces grayscale.

fn saturation(c: vec3<f32>, s: f32) -> vec3<f32>

color/saturation preview

The IEC 61966-2-1 sRGB EOTF, applied component-wise. Decode a non-linear sRGB value back to linear-light before doing math on it.

fn srgb_to_linear(c: vec3<f32>) -> vec3<f32>

color/srgb_to_linear preview

Krzysztof Narkowicz’s fitted ACES curve. Expects linear HDR input. Good default for cinematic output.

fn tonemap_aces(c: vec3<f32>) -> vec3<f32>

color/tonemap_aces preview

The Jim Hejl / Richard Burgess-Dawson filmic curve. Outputs gamma-encoded values directly — no separate sRGB encode step needed.

fn tonemap_filmic(c: vec3<f32>) -> vec3<f32>

color/tonemap_filmic preview

The classic Reinhard operator: c / (1 + c). Cheap and monotonic, but washes highlights. Useful baseline.

fn tonemap_reinhard(c: vec3<f32>) -> vec3<f32>

color/tonemap_reinhard preview

John Hable’s Uncharted 2 operator with an explicit white-point scale. More film-like roll-off than Reinhard at higher cost.

fn tonemap_uncharted2(c: vec3<f32>) -> vec3<f32>

color/tonemap_uncharted2 preview