anvilkit_render/renderer/
ibl.rs1use std::f32::consts::PI;
8
9pub fn generate_brdf_lut(size: u32) -> Vec<u8> {
32 let sample_count = 1024u32;
33 let mut data = Vec::with_capacity((size * size * 4) as usize);
34
35 for y in 0..size {
36 for x in 0..size {
37 let n_dot_v = ((x as f32) + 0.5) / size as f32;
38 let roughness = ((y as f32) + 0.5) / size as f32;
39 let n_dot_v = n_dot_v.max(0.001);
40
41 let (scale, bias) = integrate_brdf(n_dot_v, roughness, sample_count);
42
43 data.push((scale.clamp(0.0, 1.0) * 255.0) as u8);
44 data.push((bias.clamp(0.0, 1.0) * 255.0) as u8);
45 data.push(0);
46 data.push(255);
47 }
48 }
49
50 data
51}
52
53fn integrate_brdf(n_dot_v: f32, roughness: f32, sample_count: u32) -> (f32, f32) {
55 let v = glam::Vec3::new((1.0 - n_dot_v * n_dot_v).sqrt(), 0.0, n_dot_v);
56 let n = glam::Vec3::Z;
57
58 let mut a = 0.0f32;
59 let mut b = 0.0f32;
60
61 for i in 0..sample_count {
62 let xi = hammersley(i, sample_count);
63 let h = importance_sample_ggx(xi, n, roughness);
64 let l = (2.0 * v.dot(h) * h - v).normalize();
65
66 let n_dot_l = l.z.max(0.0);
67 let n_dot_h = h.z.max(0.0);
68 let v_dot_h = v.dot(h).max(0.0);
69
70 if n_dot_l > 0.0 {
71 let g = geometry_smith_ibl(n_dot_v, n_dot_l, roughness);
72 let g_vis = (g * v_dot_h) / (n_dot_h * n_dot_v).max(0.0001);
73 let fc = (1.0 - v_dot_h).powf(5.0);
74
75 a += (1.0 - fc) * g_vis;
76 b += fc * g_vis;
77 }
78 }
79
80 let inv = 1.0 / sample_count as f32;
81 (a * inv, b * inv)
82}
83
84fn hammersley(i: u32, n: u32) -> glam::Vec2 {
86 glam::Vec2::new(i as f32 / n as f32, radical_inverse_vdc(i))
87}
88
89fn radical_inverse_vdc(mut bits: u32) -> f32 {
91 bits = (bits << 16) | (bits >> 16);
92 bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1);
93 bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2);
94 bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4);
95 bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8);
96 bits as f32 * 2.3283064365386963e-10 }
98
99fn importance_sample_ggx(xi: glam::Vec2, n: glam::Vec3, roughness: f32) -> glam::Vec3 {
101 let a = roughness * roughness;
102
103 let phi = 2.0 * PI * xi.x;
104 let cos_theta = ((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y)).sqrt();
105 let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
106
107 let h = glam::Vec3::new(phi.cos() * sin_theta, phi.sin() * sin_theta, cos_theta);
109
110 let up = if n.z.abs() < 0.999 {
112 glam::Vec3::Z
113 } else {
114 glam::Vec3::X
115 };
116 let tangent = up.cross(n).normalize();
117 let bitangent = n.cross(tangent);
118
119 (tangent * h.x + bitangent * h.y + n * h.z).normalize()
120}
121
122fn geometry_smith_ibl(n_dot_v: f32, n_dot_l: f32, roughness: f32) -> f32 {
124 let a = roughness;
125 let k = (a * a) / 2.0;
126
127 let ggx_v = n_dot_v / (n_dot_v * (1.0 - k) + k);
128 let ggx_l = n_dot_l / (n_dot_l * (1.0 - k) + k);
129
130 ggx_v * ggx_l
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_generate_brdf_lut_size() {
139 let data = generate_brdf_lut(32);
140 assert_eq!(data.len(), 32 * 32 * 4);
141 }
142
143 #[test]
144 fn test_brdf_lut_values_in_range() {
145 let data = generate_brdf_lut(16);
146 for chunk in data.chunks(4) {
147 assert_eq!(chunk[2], 0);
149 assert_eq!(chunk[3], 255);
150 }
151 }
152
153 #[test]
154 fn test_hammersley_sequence() {
155 let h0 = hammersley(0, 16);
156 assert_eq!(h0.x, 0.0);
157
158 let h8 = hammersley(8, 16);
159 assert!((h8.x - 0.5).abs() < 0.001);
160 }
161
162 #[test]
163 fn test_radical_inverse() {
164 assert!((radical_inverse_vdc(0) - 0.0).abs() < 0.001);
165 assert!((radical_inverse_vdc(1) - 0.5).abs() < 0.001);
166 }
167
168 #[test]
169 fn test_brdf_lut_smooth_surface() {
170 let (scale, bias) = integrate_brdf(0.9, 0.05, 512);
172 assert!(scale > 0.5, "scale={}", scale);
173 assert!(bias < 0.2, "bias={}", bias);
174 }
175}