renderling/
tonemapping.rs1use crabslab::{Slab, SlabItem};
8use glam::{mat3, Mat3, Vec2, Vec3, Vec4, Vec4Swizzles};
9use spirv_std::{image::Image2d, spirv, Sampler};
10
11#[cfg(not(target_arch = "spirv"))]
12mod cpu;
13#[cfg(not(target_arch = "spirv"))]
14pub use cpu::*;
15
16const GAMMA: f32 = 2.2;
17const INV_GAMMA: f32 = 1.0 / GAMMA;
18
19const ACESINPUT_MAT: Mat3 = mat3(
21 Vec3::new(0.59719, 0.07600, 0.02840),
22 Vec3::new(0.35458, 0.90834, 0.13383),
23 Vec3::new(0.04823, 0.01566, 0.83777),
24);
25
26const ACESOUTPUT_MAT: Mat3 = mat3(
28 Vec3::new(1.60475, -0.10208, -0.00327),
29 Vec3::new(-0.53108, 1.10813, -0.07276),
30 Vec3::new(-0.07367, -0.00605, 1.07602),
31);
32
33pub fn linear_to_srgb(color: Vec3) -> Vec3 {
36 color.powf(INV_GAMMA)
37}
38
39pub fn srgb_to_linear(srgb_in: Vec3) -> Vec3 {
42 srgb_in.powf(GAMMA)
43}
44
45pub fn srgba_to_linear(srgb_in: Vec4) -> Vec4 {
48 srgb_to_linear(srgb_in.xyz()).extend(srgb_in.w)
49}
50
51pub fn tone_map_aces_narkowicz(color: Vec3) -> Vec3 {
54 const A: f32 = 2.51;
55 const B: f32 = 0.03;
56 const C: f32 = 2.43;
57 const D: f32 = 0.59;
58 const E: f32 = 0.14;
59 let c = (color * (A * color + B)) / (color * (C * color + D) + E);
60 c.clamp(Vec3::ZERO, Vec3::ONE)
61}
62
63fn rrt_and_odtfit(color: Vec3) -> Vec3 {
66 let a: Vec3 = color * (color + 0.0245786) - 0.000090537;
67 let b: Vec3 = color * (0.983729 * color + 0.432951) + 0.238081;
68 a / b
69}
70
71pub fn tone_map_aces_hill(mut color: Vec3) -> Vec3 {
72 color = ACESINPUT_MAT * color;
73 color = rrt_and_odtfit(color);
75 color = ACESOUTPUT_MAT * color;
76 color = color.clamp(Vec3::ZERO, Vec3::ONE);
78
79 color
80}
81
82pub fn tone_map_reinhard(color: Vec3) -> Vec3 {
83 color / (color + Vec3::ONE)
84}
85
86#[repr(transparent)]
87#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
88#[derive(Clone, Copy, Default, PartialEq, Eq, SlabItem)]
89pub struct Tonemap(u32);
90
91impl Tonemap {
92 pub const NONE: Self = Tonemap(0);
93 pub const ACES_NARKOWICZ: Self = Tonemap(1);
94 pub const ACES_HILL: Self = Tonemap(2);
95 pub const ACES_HILL_EXPOSURE_BOOST: Self = Tonemap(3);
96 pub const REINHARD: Self = Tonemap(4);
97}
98
99#[repr(C)]
100#[derive(Clone, Copy, PartialEq, SlabItem)]
101pub struct TonemapConstants {
102 pub tonemap: Tonemap,
103 pub exposure: f32,
104}
105
106impl Default for TonemapConstants {
107 fn default() -> Self {
108 Self {
109 tonemap: Tonemap::NONE,
110 exposure: 1.0,
111 }
112 }
113}
114
115pub fn tonemap(mut color: Vec4, slab: &[u32]) -> Vec4 {
116 let constants = slab.read::<TonemapConstants>(0u32.into());
117 color *= constants.exposure;
118
119 match constants.tonemap {
120 Tonemap::ACES_NARKOWICZ => tone_map_aces_narkowicz(color.xyz()).extend(color.w),
121 Tonemap::ACES_HILL => tone_map_aces_hill(color.xyz()).extend(color.w),
122 Tonemap::ACES_HILL_EXPOSURE_BOOST => {
123 tone_map_aces_hill(color.xyz() / 0.6).extend(color.w)
127 }
128 Tonemap::REINHARD => {
129 tone_map_reinhard(color.xyz()).extend(color.w)
131 }
132 _ => color,
133 }
134}
135
136const QUAD_2D_POINTS: [(Vec2, Vec2); 6] = {
137 let tl = (Vec2::new(-1.0, 1.0), Vec2::new(0.0, 0.0));
138 let tr = (Vec2::new(1.0, 1.0), Vec2::new(1.0, 0.0));
139 let bl = (Vec2::new(-1.0, -1.0), Vec2::new(0.0, 1.0));
140 let br = (Vec2::new(1.0, -1.0), Vec2::new(1.0, 1.0));
141 [tl, bl, br, tl, br, tr]
142};
143
144#[spirv(vertex)]
145pub fn tonemapping_vertex(
146 #[spirv(vertex_index)] vertex_id: u32,
147 out_uv: &mut glam::Vec2,
148 #[spirv(position)] gl_pos: &mut glam::Vec4,
149) {
150 let (pos, uv) = QUAD_2D_POINTS[vertex_id as usize];
151 *out_uv = uv;
152 *gl_pos = pos.extend(0.0).extend(1.0);
153}
154
155#[spirv(fragment)]
156pub fn tonemapping_fragment(
157 #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32],
158 #[spirv(descriptor_set = 0, binding = 1)] texture: &Image2d,
159 #[spirv(descriptor_set = 0, binding = 2)] sampler: &Sampler,
160 in_uv: glam::Vec2,
161 output: &mut glam::Vec4,
162) {
163 let color: Vec4 = texture.sample(*sampler, in_uv);
164 let color = tonemap(color, slab);
165 *output = color;
166}