1use super::{
4 ColorImage, DistancePixel, GeometryBuffer, GeometryPixel, Image, ImageSize,
5 ThreadPool,
6};
7use nalgebra::{
8 Const, Matrix3, MatrixXx2, MatrixXx3, OMatrix, RowVector2, RowVector3,
9 Vector3, Vector4,
10};
11use rand::prelude::*;
12
13pub fn denoise_normals(
18 image: &GeometryBuffer,
19 threads: Option<&ThreadPool>,
20) -> GeometryBuffer {
21 let radius = 2;
22 let mut out = GeometryBuffer::new(image.size());
23 out.apply_effect(
24 |x, y| {
25 let depth = image[(y, x)].depth;
26 let normal = if depth > 0.0 {
27 denoise_pixel(image, x, y, radius)
28 } else {
29 [0.0; 3]
30 };
31 GeometryPixel { depth, normal }
32 },
33 threads,
34 );
35 out
36}
37
38pub fn apply_shading(
43 image: &GeometryBuffer,
44 ssao: bool,
45 threads: Option<&ThreadPool>,
46) -> ColorImage {
47 let ssao = if ssao {
48 let ssao = compute_ssao(image, threads);
49 Some(blur_ssao(&ssao, threads))
50 } else {
51 None
52 };
53
54 let size = image.size();
55 let mut out = ColorImage::new(ImageSize::new(size.width(), size.height()));
56 out.apply_effect(
57 |x, y| {
58 if image[(y, x)].depth > 0.0 {
59 shade_pixel(image, ssao.as_ref(), x, y)
60 } else {
61 [0u8; 3]
62 }
63 },
64 threads,
65 );
66 out
67}
68
69pub fn compute_ssao(
74 image: &GeometryBuffer,
75 threads: Option<&ThreadPool>,
76) -> Image<f32> {
77 let ssao_kernel = ssao_kernel(64);
78 let ssao_noise = ssao_noise(16 * 16);
79
80 let size = image.size();
81 let mut out =
82 Image::<f32>::new(ImageSize::new(size.width(), size.height()));
83 out.apply_effect(
84 |x, y| {
85 if image[(y, x)].depth > 0.0 {
86 compute_pixel_ssao(image, x, y, &ssao_kernel, &ssao_noise)
87 } else {
88 f32::NAN
89 }
90 },
91 threads,
92 );
93
94 out
95}
96
97pub fn blur_ssao(
99 ssao: &Image<f32>,
100 threads: Option<&ThreadPool>,
101) -> Image<f32> {
102 let mut out = Image::<f32>::new(ssao.size());
103 let blur_radius = 2;
104 out.apply_effect(
105 |x, y| {
106 if ssao[(y, x)].is_nan() {
107 f32::NAN
108 } else {
109 compute_pixel_blur(ssao, x, y, blur_radius)
110 }
111 },
112 threads,
113 );
114 out
115}
116
117fn shade_pixel(
119 image: &GeometryBuffer,
120 ssao: Option<&Image<f32>>,
121 x: usize,
122 y: usize,
123) -> [u8; 3] {
124 let [nx, ny, nz] = image[(y, x)].normal;
125 let n = Vector3::new(nx, ny, nz).normalize();
126
127 let p = Vector3::new(
129 2.0 * (x as f32 / image.width() as f32 - 0.5),
130 2.0 * (y as f32 / image.height() as f32 - 0.5),
131 2.0 * (image[(y, x)].depth / image.depth() as f32 - 0.5),
132 );
133
134 let lights = [
135 Vector4::new(5.0, -5.0, 10.0, 0.5),
136 Vector4::new(-5.0, 0.0, 10.0, 0.15),
137 Vector4::new(0.0, -5.0, 10.0, 0.15),
138 ];
139 let mut accum = 0.2; for light in lights {
141 let light_dir = (light.xyz() - p).normalize();
142 accum += light_dir.dot(&n).max(0.0) * light.w;
143 }
144 if let Some(ssao) = ssao {
145 accum *= ssao[(y, x)] * 0.6 + 0.4;
146 }
147
148 accum = accum.clamp(0.0, 1.0);
149
150 let c = (accum * 255.0) as u8;
151 [c, c, c]
152}
153
154fn compute_pixel_ssao(
158 image: &GeometryBuffer,
159 x: usize,
160 y: usize,
161 kernel: &OMatrix<f32, nalgebra::Dyn, Const<3>>,
162 noise: &OMatrix<f32, nalgebra::Dyn, Const<2>>,
163) -> f32 {
164 let pos = (y, x);
165 let GeometryPixel {
166 normal: [nx, ny, nz],
167 depth: d,
168 } = image[pos];
169
170 if d == 0.0 {
171 return f32::NAN;
172 }
173
174 let p = Vector3::new(
176 (((x as f32) / image.width() as f32) - 0.5) * 2.0,
177 (((y as f32) / image.height() as f32) - 0.5) * 2.0,
178 ((d / image.depth() as f32) - 0.5) * 2.0,
179 );
180
181 let n = Vector3::new(nx, ny, nz).normalize();
183
184 let idx = pos.0 + pos.1 * image.width();
186 let rvec = noise.row((idx * 19) % noise.nrows()).transpose();
187 let rvec = Vector3::new(rvec.x, rvec.y, 0.0);
188
189 let tangent = (rvec - n * rvec.dot(&n)).normalize();
191 let bitangent = n.cross(&tangent);
192 let tbn = Matrix3::from_columns(&[tangent, bitangent, n]);
193
194 const RADIUS: f32 = 0.1;
195 let mut occlusion = 0.0;
196 for i in 0..kernel.nrows() {
197 let sample_pos = tbn * kernel.row(i).transpose() * RADIUS + p;
199
200 let px = ((sample_pos.x / 2.0) + 0.5) * image.width() as f32;
203 let py = ((sample_pos.y / 2.0) + 0.5) * image.height() as f32;
204
205 let actual_h = if px < image.width() as f32
207 && py < image.height() as f32
208 && px > 0.0
209 && py > 0.0
210 {
211 image[(py as usize, px as usize)].depth
212 } else {
213 0.0
214 };
215
216 let actual_z = ((actual_h / image.depth() as f32) - 0.5) * 2.0;
217
218 let dz = sample_pos.z - actual_z;
219 if dz < RADIUS {
220 occlusion += (sample_pos.z <= actual_z) as usize as f32;
221 } else if dz < RADIUS * 2.0 && sample_pos.z <= actual_z {
222 occlusion += ((RADIUS - (dz - RADIUS)) / RADIUS).powi(2);
223 }
224 }
225 1.0 - (occlusion / kernel.nrows() as f32)
226}
227
228fn denoise_pixel(
230 image: &GeometryBuffer,
231 x: usize,
232 y: usize,
233 denoise_radius: isize,
234) -> [f32; 3] {
235 let n = image[(y, x)].normal;
236 if n[2] > 0.0 {
237 return n;
238 }
239 let x = x as isize;
240 let y = y as isize;
241 [
242 (0, 0),
243 (-denoise_radius, 0),
244 (0, -denoise_radius),
245 (-denoise_radius, -denoise_radius),
246 ]
247 .into_iter()
248 .flat_map(|(xmin, ymin)| {
249 let mut sum = Vector3::zeros();
250 let mut count = 0;
251 for i in 0..=denoise_radius {
252 for j in 0..=denoise_radius {
253 let tx = x + xmin + i;
254 let ty = y + ymin + j;
255 if tx >= 0
256 && ty >= 0
257 && (tx as usize) < image.width()
258 && (ty as usize) < image.height()
259 {
260 let pos = (ty as usize, tx as usize);
261 if image[pos].depth != 0.0 {
262 let n = image[pos].normal;
263 sum += Vector3::new(n[0], n[1], n[2]);
264 count += 1;
265 }
266 }
267 }
268 }
269 if count == 0 {
270 return None;
271 }
272 let mean = sum / count as f32;
273 let mut score = 0.0;
274 for i in 0..=denoise_radius {
275 for j in 0..=denoise_radius {
276 let tx = x + xmin + i;
277 let ty = y + ymin + j;
278 if tx >= 0
279 && ty >= 0
280 && (tx as usize) < image.width()
281 && (ty as usize) < image.height()
282 {
283 let pos = (ty as usize, tx as usize);
284 if image[pos].depth != 0.0 {
285 let n = image[pos].normal;
286 score += Vector3::new(n[0], n[1], n[2]).dot(&mean);
287 }
288 }
289 }
290 }
291 Some((score, mean.into()))
292 })
293 .max_by_key(|(score, _mean)| ordered_float::OrderedFloat(*score))
294 .unwrap_or((0.0, n))
295 .1
296}
297
298fn compute_pixel_blur(
300 ssao: &Image<f32>,
301 x: usize,
302 y: usize,
303 blur_radius: isize,
304) -> f32 {
305 let x = x as isize;
306 let y = y as isize;
307 [
308 (0, 0),
309 (-blur_radius, 0),
310 (0, -blur_radius),
311 (-blur_radius, -blur_radius),
312 ]
313 .into_iter()
314 .flat_map(|(xmin, ymin)| {
315 let mut sum = 0.0;
316 let mut count = 0;
317 for i in 0..=blur_radius {
318 for j in 0..=blur_radius {
319 let tx = x + xmin + i;
320 let ty = y + ymin + j;
321 if tx >= 0
322 && ty >= 0
323 && (tx as usize) < ssao.width()
324 && (ty as usize) < ssao.height()
325 {
326 let s = ssao[(ty as usize, tx as usize)];
327 if !s.is_nan() {
328 sum += s;
329 count += 1;
330 }
331 }
332 }
333 }
334 if count == 0 {
335 return None;
336 }
337 let mean = sum / count as f32;
338 let mut stdev = 0.0;
339 for i in 0..=blur_radius {
340 for j in 0..=blur_radius {
341 let tx = x + xmin + i;
342 let ty = y + ymin + j;
343 if tx >= 0
344 && ty >= 0
345 && (tx as usize) < ssao.width()
346 && (ty as usize) < ssao.height()
347 {
348 let s = ssao[(ty as usize, tx as usize)];
349 if !s.is_nan() {
350 stdev += (mean - s).powi(2);
351 }
352 }
353 }
354 }
355 Some((stdev / count as f32, mean))
356 })
357 .min_by_key(|(stdev, _mean)| ordered_float::OrderedFloat(*stdev))
358 .unwrap_or_else(|| (0.0, ssao[(y as usize, x as usize)]))
359 .1
360}
361
362pub fn ssao_kernel(n: usize) -> OMatrix<f32, nalgebra::Dyn, Const<3>> {
369 use rand::prelude::*;
371
372 let mut kernel = MatrixXx3::<f32>::zeros(n);
373 let mut rng = rand::rng();
374 let xy_range = rand::distr::Uniform::new_inclusive(-1.0, 1.0).unwrap();
375 let z_range = rand::distr::Uniform::new_inclusive(0.0, 1.0).unwrap();
376
377 for i in 0..n {
378 let row = RowVector3::<f32>::new(
379 rng.sample(xy_range),
380 rng.sample(xy_range),
381 rng.sample(z_range),
382 );
383 let scale =
385 ((i as f32) / (kernel.nrows() as f32 - 1.0)).powi(2) * 0.9 + 0.1;
386 kernel.set_row(i, &(row * scale / row.norm()));
387 }
388 kernel
389}
390
391pub fn ssao_noise(n: usize) -> OMatrix<f32, nalgebra::Dyn, Const<2>> {
396 let mut noise = MatrixXx2::<f32>::zeros(n);
397 let mut rng = rand::rng();
398 let xy_range = rand::distr::Uniform::new_inclusive(-1.0, 1.0).unwrap();
399 for i in 0..n {
400 let row =
401 RowVector2::<f32>::new(rng.sample(xy_range), rng.sample(xy_range));
402 noise.set_row(i, &(row / row.norm()));
403 }
404 noise
405}
406
407pub fn to_rgba_bitmap(
412 image: Image<DistancePixel>,
413 transparent: bool,
414 threads: Option<&ThreadPool>,
415) -> Image<[u8; 4]> {
416 let mut out = Image::new(image.size());
417 out.apply_effect(
418 |x, y| {
419 let p = image[(y, x)];
420 if p.inside() {
421 [255u8; 4]
422 } else if transparent {
423 [0u8; 4]
424 } else {
425 [0, 0, 0, 255]
426 }
427 },
428 threads,
429 );
430 out
431}
432
433pub fn to_debug_bitmap(
435 image: Image<DistancePixel>,
436 threads: Option<&ThreadPool>,
437) -> Image<[u8; 4]> {
438 let mut out = Image::new(image.size());
439 out.apply_effect(
440 |x, y| match image[(y, x)].distance() {
441 Ok(v) => {
442 if v < 0.0 {
443 [255u8; 4]
444 } else {
445 [0, 0, 0, 255]
446 }
447 }
448 Err(f) => match (f.depth, f.inside) {
450 (0, true) => [255, 0, 0, 255],
451 (0, false) => [50, 0, 0, 255],
452 (1, true) => [0, 255, 0, 255],
453 (1, false) => [0, 50, 0, 255],
454 (2, true) => [0, 0, 255, 255],
455 (2, false) => [0, 0, 50, 255],
456 (_, true) => [255, 255, 0, 255],
457 (_, false) => [50, 50, 0, 255],
458 },
459 },
460 threads,
461 );
462 out
463}
464
465pub fn to_rgba_distance(
473 image: Image<DistancePixel>,
474 threads: Option<&ThreadPool>,
475) -> Image<[u8; 4]> {
476 let mut out = Image::new(image.size());
477 out.apply_effect(
478 |x, y| match image[(y, x)].distance() {
479 Ok(f) if f.is_nan() => [255, 0, 0, 255],
480 Ok(f) => {
481 let r = 1.0 - 0.1f32.copysign(f);
482 let g = 1.0 - 0.4f32.copysign(f);
483 let b = 1.0 - 0.7f32.copysign(f);
484
485 let dim = 1.0 - (-4.0 * f.abs()).exp(); let bands = 0.8 + 0.2 * (140.0 * f).cos(); let smoothstep = |edge0: f32, edge1: f32, x: f32| {
489 let t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
490 t * t * (3.0 - 2.0 * t)
491 };
492 let mix = |x: f32, y: f32, a: f32| x * (1.0 - a) + y * a;
493
494 let run = |v: f32| {
495 let mut v = v * dim * bands;
496 v = mix(v, 1.0, 1.0 - smoothstep(0.0, 0.015, f.abs()));
497 v = mix(v, 1.0, 1.0 - smoothstep(0.0, 0.005, f.abs()));
498 (v.clamp(0.0, 1.0) * 255.0) as u8
499 };
500
501 [run(r), run(g), run(b), 255]
502 }
503 Err(f) => {
505 if f.inside {
506 [184, 235, 255, 255]
507 } else {
508 [217, 144, 72, 255]
509 }
510 }
511 },
512 threads,
513 );
514 out
515}