1#![allow(missing_docs)] use crate::{
24 core::{
25 algebra::{Vector2, Vector3},
26 array_as_u8_slice,
27 },
28 graphics::{
29 error::FrameworkError,
30 gpu_texture::{CubeMapFace, GpuTexture, GpuTextureDescriptor, GpuTextureKind, PixelKind},
31 server::GraphicsServer,
32 },
33};
34use bytemuck::{Pod, Zeroable};
35use half::f16;
36use std::{fs::File, io::Write, path::Path};
37
38pub struct CubeMapFaceDescriptor {
39 pub face: CubeMapFace,
40 pub look: Vector3<f32>,
41 pub up: Vector3<f32>,
42}
43
44impl CubeMapFaceDescriptor {
45 pub fn cube_faces() -> [Self; 6] {
46 [
47 CubeMapFaceDescriptor {
48 face: CubeMapFace::PositiveX,
49 look: Vector3::new(1.0, 0.0, 0.0),
50 up: Vector3::new(0.0, -1.0, 0.0),
51 },
52 CubeMapFaceDescriptor {
53 face: CubeMapFace::NegativeX,
54 look: Vector3::new(-1.0, 0.0, 0.0),
55 up: Vector3::new(0.0, -1.0, 0.0),
56 },
57 CubeMapFaceDescriptor {
58 face: CubeMapFace::PositiveY,
59 look: Vector3::new(0.0, 1.0, 0.0),
60 up: Vector3::new(0.0, 0.0, 1.0),
61 },
62 CubeMapFaceDescriptor {
63 face: CubeMapFace::NegativeY,
64 look: Vector3::new(0.0, -1.0, 0.0),
65 up: Vector3::new(0.0, 0.0, -1.0),
66 },
67 CubeMapFaceDescriptor {
68 face: CubeMapFace::PositiveZ,
69 look: Vector3::new(0.0, 0.0, 1.0),
70 up: Vector3::new(0.0, -1.0, 0.0),
71 },
72 CubeMapFaceDescriptor {
73 face: CubeMapFace::NegativeZ,
74 look: Vector3::new(0.0, 0.0, -1.0),
75 up: Vector3::new(0.0, -1.0, 0.0),
76 },
77 ]
78 }
79}
80
81fn radical_inverse_vd_c(mut bits: u32) -> f32 {
82 bits = bits.rotate_right(16);
83 bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1);
84 bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2);
85 bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4);
86 bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8);
87 bits as f32 * 2.328_306_4e-10
88}
89
90fn hammersley(i: usize, n: usize) -> Vector2<f32> {
91 Vector2::new(i as f32 / n as f32, radical_inverse_vd_c(i as u32))
92}
93
94fn importance_sample_ggx(x_i: Vector2<f32>, roughness: f32, n: Vector3<f32>) -> Vector3<f32> {
95 let a = roughness * roughness;
96
97 let phi = 2.0 * std::f32::consts::PI * x_i.x;
98 let cos_theta = ((1.0 - x_i.y) / (1.0 + (a * a - 1.0) * x_i.y)).sqrt();
99 let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
100
101 let h = Vector3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta);
103
104 let up = if n.z.abs() < 0.999 {
106 Vector3::new(0.0, 0.0, 1.0)
107 } else {
108 Vector3::new(1.0, 0.0, 0.0)
109 };
110 let tangent = up.cross(&n).normalize();
111 let bitangent = n.cross(&tangent);
112
113 (tangent * h.x + bitangent * h.y + n * h.z).normalize()
114}
115
116fn geometry_schlick_ggx(n_dot_v: f32, roughness: f32) -> f32 {
117 let a = roughness;
118 let k = (a * a) / 2.0;
119
120 let nom = n_dot_v;
121 let denom = n_dot_v * (1.0 - k) + k;
122
123 nom / denom
124}
125
126fn geometry_smith(roughness: f32, n_dot_v: f32, n_dot_l: f32) -> f32 {
127 let ggx2 = geometry_schlick_ggx(n_dot_v, roughness);
128 let ggx1 = geometry_schlick_ggx(n_dot_l, roughness);
129
130 ggx1 * ggx2
131}
132
133fn integrate_brdf(n_dot_v: f32, roughness: f32, samples: usize) -> Vector2<f32> {
134 let v = Vector3::new((1.0 - n_dot_v * n_dot_v).sqrt(), 0.0, n_dot_v);
135
136 let mut a = 0.0;
137 let mut b = 0.0;
138
139 let n = Vector3::new(0.0, 0.0, 1.0);
140
141 for i in 0..samples {
142 let x_i = hammersley(i, samples);
143 let h = importance_sample_ggx(x_i, roughness, n);
144 let l = (2.0 * v.dot(&h) * h - v).normalize();
145
146 let n_dot_l = l.z.max(0.0);
147 let n_dot_h = h.z.max(0.0);
148 let v_dot_h = v.dot(&h).max(0.0);
149 let n_dot_v = n.dot(&v).max(0.0);
150
151 if n_dot_l > 0.0 {
152 let g = geometry_smith(roughness, n_dot_v, n_dot_l);
153
154 let g_vis = (g * v_dot_h) / (n_dot_h * n_dot_v);
155 let fc = (1.0 - v_dot_h).powf(5.0);
156
157 a += (1.0 - fc) * g_vis;
158 b += fc * g_vis;
159 }
160 }
161
162 Vector2::new(a / samples as f32, b / samples as f32)
163}
164
165#[derive(Default, Copy, Clone, Pod, Zeroable)]
166#[repr(C)]
167pub struct Pixel {
168 pub x: f16,
169 pub y: f16,
170}
171
172pub fn make_brdf_lut_image(size: usize, sample_count: usize) -> Vec<Pixel> {
173 let mut pixels = vec![Pixel::default(); size * size];
174
175 for y in 0..size {
176 for x in 0..size {
177 let n_dot_v = (y as f32 + 0.5) * (1.0 / size as f32);
178 let roughness = (x as f32 + 0.5) * (1.0 / size as f32);
179 let pair = integrate_brdf(n_dot_v, roughness, sample_count);
180 let pixel = &mut pixels[y * size + x];
181 pixel.x = f16::from_f32(pair.x);
182 pixel.y = f16::from_f32(pair.y);
183 }
184 }
185
186 pixels
187}
188
189pub fn generate_brdf_lut_texture(
190 server: &dyn GraphicsServer,
191 size: usize,
192 sample_count: usize,
193) -> Result<GpuTexture, FrameworkError> {
194 let pixels = make_brdf_lut_image(size, sample_count);
195 make_brdf_lut(server, size, array_as_u8_slice(&pixels))
196}
197
198pub fn make_brdf_lut(
199 server: &dyn GraphicsServer,
200 size: usize,
201 pixels: &[u8],
202) -> Result<GpuTexture, FrameworkError> {
203 server.create_texture(GpuTextureDescriptor {
204 name: "BrdfLut",
205 kind: GpuTextureKind::Rectangle {
206 width: size,
207 height: size,
208 },
209 pixel_kind: PixelKind::RG16F,
210 mip_count: 1,
211 data: Some(pixels),
212 ..Default::default()
213 })
214}
215
216pub fn write_brdf_lut(path: &Path, size: usize, sample_count: usize) -> std::io::Result<()> {
217 let pixels = make_brdf_lut_image(size, sample_count);
218 let mut file = File::create(path)?;
219 file.write_all(array_as_u8_slice(&pixels))?;
220 Ok(())
221}
222
223#[cfg(test)]
224mod test {
225 use crate::renderer::utils::write_brdf_lut;
226 use std::path::Path;
227
228 #[test]
230 fn test_write_brdf_lut() {
231 write_brdf_lut(
232 Path::new("src/renderer/brdf_256x256_256samples.bin"),
233 256,
234 256,
235 )
236 .unwrap();
237 }
238}