1use super::{
4 ColorImage, Image, ImageSize, ThreadPool, pixel, pixel::DistancePixel,
5 voxel,
6};
7use nalgebra::{
8 Const, Matrix2xX, Matrix3, Matrix3xX, OMatrix, Vector2, Vector3, Vector4,
9};
10use rand::prelude::*;
11
12pub fn denoise_normals(
17 image: &voxel::Image,
18 threads: Option<&ThreadPool>,
19) -> voxel::Image {
20 let radius = 2;
21 let mut out = voxel::Image::new(image.size());
22 out.apply_effect(
23 |x, y| {
24 let depth = image[(y, x)].depth;
25 let normal = if depth > 0 {
26 denoise_pixel(image, x, y, radius)
27 } else {
28 [0.0; 3]
29 };
30 voxel::GeometryPixel { depth, normal }
31 },
32 threads,
33 );
34 out
35}
36
37pub fn apply_shading(
42 image: &voxel::Image,
43 ssao: bool,
44 threads: Option<&ThreadPool>,
45) -> ColorImage {
46 let ssao = if ssao {
47 let ssao = compute_ssao(image, threads);
48 Some(blur_ssao(&ssao, threads))
49 } else {
50 None
51 };
52
53 let size = image.size();
54 let mut out = ColorImage::new(ImageSize::new(size.width(), size.height()));
55 out.apply_effect(
56 |x, y| {
57 if image[(y, x)].depth > 0 {
58 shade_pixel(image, ssao.as_ref(), x, y)
59 } else {
60 [0u8; 3]
61 }
62 },
63 threads,
64 );
65 out
66}
67
68pub fn compute_ssao(
73 image: &voxel::Image,
74 threads: Option<&ThreadPool>,
75) -> Image<f32> {
76 let ssao_kernel = ssao_kernel(64);
77 let ssao_noise = ssao_noise(16 * 16);
78
79 let size = image.size();
80 let mut out =
81 Image::<f32>::new(ImageSize::new(size.width(), size.height()));
82 out.apply_effect(
83 |x, y| {
84 if image[(y, x)].depth > 0 {
85 compute_pixel_ssao(image, x, y, &ssao_kernel, &ssao_noise)
86 } else {
87 f32::NAN
88 }
89 },
90 threads,
91 );
92
93 out
94}
95
96pub fn blur_ssao(
98 ssao: &Image<f32>,
99 threads: Option<&ThreadPool>,
100) -> Image<f32> {
101 let mut out = Image::<f32>::new(ssao.size());
102 let blur_radius = 2;
103 out.apply_effect(
104 |x, y| {
105 if ssao[(y, x)].is_nan() {
106 f32::NAN
107 } else {
108 compute_pixel_blur(ssao, x, y, blur_radius)
109 }
110 },
111 threads,
112 );
113 out
114}
115
116fn shade_pixel(
118 image: &voxel::Image,
119 ssao: Option<&Image<f32>>,
120 x: usize,
121 y: usize,
122) -> [u8; 3] {
123 let [nx, ny, nz] = image[(y, x)].normal;
124 let n = Vector3::new(nx, ny, nz).normalize();
125
126 let p = Vector3::new(
128 2.0 * (x as f32 / image.width() as f32 - 0.5),
129 2.0 * (y as f32 / image.height() as f32 - 0.5),
130 2.0 * (image[(y, x)].depth as f32 / image.depth() as f32 - 0.5),
131 );
132
133 let lights = [
134 Vector4::new(5.0, -5.0, 10.0, 0.5),
135 Vector4::new(-5.0, 0.0, 10.0, 0.15),
136 Vector4::new(0.0, -5.0, 10.0, 0.15),
137 ];
138 let mut accum = 0.2; for light in lights {
140 let light_dir = (light.xyz() - p).normalize();
141 accum += light_dir.dot(&n).max(0.0) * light.w;
142 }
143 if let Some(ssao) = ssao {
144 accum *= ssao[(y, x)] * 0.6 + 0.4;
145 }
146
147 accum = accum.clamp(0.0, 1.0);
148
149 let c = (accum * 255.0) as u8;
150 [c, c, c]
151}
152
153fn pcg2d(mut x: u32, mut y: u32) -> u32 {
158 x = x.wrapping_mul(1664525).wrapping_add(1013904223);
159 y = y.wrapping_mul(1664525).wrapping_add(1013904223);
160 x = x.wrapping_add(y.wrapping_mul(1664525));
161 y = y.wrapping_add(x.wrapping_mul(1664525));
162 x ^= x >> 16;
163 y ^= y >> 16;
164 x = x.wrapping_add(y.wrapping_mul(1664525));
166 x ^= x >> 16;
167 x
168}
169
170fn compute_pixel_ssao(
174 image: &voxel::Image,
175 x: usize,
176 y: usize,
177 kernel: &OMatrix<f32, Const<3>, nalgebra::Dyn>,
178 noise: &OMatrix<f32, Const<2>, nalgebra::Dyn>,
179) -> f32 {
180 let pos = (y, x);
181 let voxel::GeometryPixel {
182 normal: [nx, ny, nz],
183 depth: d,
184 } = image[pos];
185
186 if d == 0 {
187 return f32::NAN;
188 }
189
190 let scale_min = image
192 .size
193 .width()
194 .min(image.size.height())
195 .min(image.size.depth()) as f32;
196 let (scale_x, scale_y, scale_z) = (
197 scale_min / image.size.width() as f32,
198 scale_min / image.size.height() as f32,
199 scale_min / image.size.depth() as f32,
200 );
201
202 let p = Vector3::new(
206 (((x as f32 + 0.5) / image.width() as f32) - 0.5) * 2.0,
207 (((y as f32 + 0.5) / image.height() as f32) - 0.5) * 2.0,
208 ((d as f32 / image.depth() as f32) - 0.5) * 2.0,
209 );
210
211 let n = Vector3::new(nx, ny, nz).normalize();
213
214 let rvec = noise
216 .column(pcg2d(pos.0 as u32, pos.1 as u32) as usize % noise.ncols());
217 let rvec = Vector3::new(rvec.x, rvec.y, 0.0);
218
219 let tangent = (rvec - n * rvec.dot(&n)).normalize();
221 let bitangent = n.cross(&tangent);
222 let tbn = Matrix3::from_columns(&[tangent, bitangent, n]);
223
224 const RADIUS: f32 = 0.1;
225 let mut occlusion = 0.0;
226 for i in 0..kernel.ncols() {
227 let mut offset = tbn * kernel.column(i) * RADIUS;
229 offset.x *= scale_x;
230 offset.y *= scale_y;
231 offset.z *= scale_z;
232
233 let sample_pos = offset + p;
235
236 let px = ((sample_pos.x / 2.0) + 0.5) * image.width() as f32;
238 let py = ((sample_pos.y / 2.0) + 0.5) * image.height() as f32;
239
240 let actual_h = if px < image.width() as f32
242 && py < image.height() as f32
243 && px > 0.0
244 && py > 0.0
245 {
246 image[(py as usize, px as usize)].depth
247 } else {
248 0
249 };
250
251 let actual_z = ((actual_h as f32 / image.depth() as f32) - 0.5) * 2.0;
252
253 let dz = sample_pos.z - actual_z;
254 if dz < RADIUS {
255 occlusion += (sample_pos.z <= actual_z) as usize as f32;
256 } else if dz < RADIUS * 2.0 && sample_pos.z <= actual_z {
257 occlusion += ((RADIUS - (dz - RADIUS)) / RADIUS).powi(2);
258 }
259 }
260 1.0 - (occlusion / kernel.ncols() as f32)
261}
262
263fn denoise_pixel(
265 image: &voxel::Image,
266 x: usize,
267 y: usize,
268 denoise_radius: isize,
269) -> [f32; 3] {
270 let n = image[(y, x)].normal;
271 if n[2] > 0.0 {
272 return n;
273 }
274 let x = x as isize;
275 let y = y as isize;
276 [
277 (0, 0),
278 (-denoise_radius, 0),
279 (0, -denoise_radius),
280 (-denoise_radius, -denoise_radius),
281 ]
282 .into_iter()
283 .flat_map(|(xmin, ymin)| {
284 let mut sum = Vector3::zeros();
285 let mut count = 0;
286 for i in 0..=denoise_radius {
287 for j in 0..=denoise_radius {
288 let tx = x + xmin + i;
289 let ty = y + ymin + j;
290 if tx >= 0
291 && ty >= 0
292 && (tx as usize) < image.width()
293 && (ty as usize) < image.height()
294 {
295 let pos = (ty as usize, tx as usize);
296 if image[pos].depth != 0 && image[pos].normal[2] > 0.0 {
297 let n = image[pos].normal;
298 sum += Vector3::new(n[0], n[1], n[2]);
299 count += 1;
300 }
301 }
302 }
303 }
304 if count == 0 {
305 return None;
306 }
307 let mean = sum / count as f32;
308 let mut score = 0.0;
309 for i in 0..=denoise_radius {
310 for j in 0..=denoise_radius {
311 let tx = x + xmin + i;
312 let ty = y + ymin + j;
313 if tx >= 0
314 && ty >= 0
315 && (tx as usize) < image.width()
316 && (ty as usize) < image.height()
317 {
318 let pos = (ty as usize, tx as usize);
319 if image[pos].depth != 0 {
320 let n = image[pos].normal;
321 score += Vector3::new(n[0], n[1], n[2]).dot(&mean);
322 }
323 }
324 }
325 }
326 Some((score, mean.into()))
327 })
328 .max_by_key(|(score, _mean)| ordered_float::OrderedFloat(*score))
329 .unwrap_or((0.0, n))
330 .1
331}
332
333fn compute_pixel_blur(
335 ssao: &Image<f32>,
336 x: usize,
337 y: usize,
338 blur_radius: isize,
339) -> f32 {
340 let x = x as isize;
341 let y = y as isize;
342 [
343 (0, 0),
344 (-blur_radius, 0),
345 (0, -blur_radius),
346 (-blur_radius, -blur_radius),
347 ]
348 .into_iter()
349 .flat_map(|(xmin, ymin)| {
350 let mut sum = 0.0;
351 let mut count = 0;
352 for i in 0..=blur_radius {
353 for j in 0..=blur_radius {
354 let tx = x + xmin + i;
355 let ty = y + ymin + j;
356 if tx >= 0
357 && ty >= 0
358 && (tx as usize) < ssao.width()
359 && (ty as usize) < ssao.height()
360 {
361 let s = ssao[(ty as usize, tx as usize)];
362 if !s.is_nan() {
363 sum += s;
364 count += 1;
365 }
366 }
367 }
368 }
369 if count == 0 {
370 return None;
371 }
372 let mean = sum / count as f32;
373 let mut stdev = 0.0;
374 for i in 0..=blur_radius {
375 for j in 0..=blur_radius {
376 let tx = x + xmin + i;
377 let ty = y + ymin + j;
378 if tx >= 0
379 && ty >= 0
380 && (tx as usize) < ssao.width()
381 && (ty as usize) < ssao.height()
382 {
383 let s = ssao[(ty as usize, tx as usize)];
384 if !s.is_nan() {
385 stdev += (mean - s).powi(2);
386 }
387 }
388 }
389 }
390 Some((stdev / count as f32, mean))
391 })
392 .min_by_key(|(stdev, _mean)| ordered_float::OrderedFloat(*stdev))
393 .unwrap_or_else(|| (0.0, ssao[(y as usize, x as usize)]))
394 .1
395}
396
397pub fn ssao_kernel(n: usize) -> OMatrix<f32, Const<3>, nalgebra::Dyn> {
404 use rand::prelude::*;
406
407 let mut kernel = Matrix3xX::<f32>::zeros(n);
408 let mut rng = rand::rng();
409 let xy_range = rand::distr::Uniform::new_inclusive(-1.0, 1.0).unwrap();
410 let z_range = rand::distr::Uniform::new_inclusive(0.0, 1.0).unwrap();
411
412 for i in 0..n {
413 loop {
414 let row = Vector3::<f32>::new(
415 rng.sample(xy_range),
416 rng.sample(xy_range),
417 rng.sample(z_range),
418 );
419 if row.norm() < 1.0 && row.norm() > f32::EPSILON {
421 let scale =
423 ((i as f32) / (kernel.ncols() as f32 - 1.0)).powi(2) * 0.9
424 + 0.1;
425 kernel.set_column(i, &(row * scale / row.norm()));
426 break;
427 }
428 }
429 }
430 kernel
431}
432
433pub fn ssao_noise(n: usize) -> OMatrix<f32, Const<2>, nalgebra::Dyn> {
438 let mut noise = Matrix2xX::<f32>::zeros(n);
439 let mut rng = rand::rng();
440 let xy_range = rand::distr::Uniform::new_inclusive(-1.0, 1.0).unwrap();
441 for i in 0..n {
442 loop {
443 let row =
444 Vector2::<f32>::new(rng.sample(xy_range), rng.sample(xy_range));
445 if row.norm() < 1.0 && row.norm() > f32::EPSILON {
447 noise.set_column(i, &(row / row.norm()));
448 break;
449 }
450 }
451 }
452 noise
453}
454
455pub fn to_rgba_bitmap(
460 image: pixel::Image,
461 transparent: bool,
462 threads: Option<&ThreadPool>,
463) -> Image<[u8; 4]> {
464 let mut out = Image::new(image.size());
465 out.apply_effect(
466 |x, y| {
467 let p = image[(y, x)];
468 if p.inside() {
469 [255u8; 4]
470 } else if transparent {
471 [0u8; 4]
472 } else {
473 [0, 0, 0, 255]
474 }
475 },
476 threads,
477 );
478 out
479}
480
481pub fn to_debug_bitmap(
483 image: pixel::Image,
484 threads: Option<&ThreadPool>,
485) -> Image<[u8; 4]> {
486 let mut out = Image::new(image.size());
487 out.apply_effect(
488 |x, y| match image[(y, x)].unpack() {
489 DistancePixel::Value(v) => {
490 if v < 0.0 {
491 [255u8; 4]
492 } else {
493 [0, 0, 0, 255]
494 }
495 }
496 DistancePixel::Fill { depth, inside } => match (depth, inside) {
498 (0, true) => [255, 0, 0, 255],
499 (0, false) => [50, 0, 0, 255],
500 (1, true) => [0, 255, 0, 255],
501 (1, false) => [0, 50, 0, 255],
502 (2, true) => [0, 0, 255, 255],
503 (2, false) => [0, 0, 50, 255],
504 (_, true) => [255, 255, 0, 255],
505 (_, false) => [50, 50, 0, 255],
506 },
507 },
508 threads,
509 );
510 out
511}
512
513pub fn to_rgba_distance(
521 image: pixel::Image,
522 threads: Option<&ThreadPool>,
523) -> Image<[u8; 4]> {
524 let mut out = Image::new(image.size());
525 out.apply_effect(
526 |x, y| match image[(y, x)].unpack() {
527 DistancePixel::Value(f) if f.is_nan() => [255, 0, 0, 255],
528 DistancePixel::Value(f) => {
529 let r = 1.0 - 0.1f32.copysign(f);
530 let g = 1.0 - 0.4f32.copysign(f);
531 let b = 1.0 - 0.7f32.copysign(f);
532
533 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| {
537 let t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
538 t * t * (3.0 - 2.0 * t)
539 };
540 let mix = |x: f32, y: f32, a: f32| x * (1.0 - a) + y * a;
541
542 let run = |v: f32| {
543 let mut v = v * dim * bands;
544 v = mix(v, 1.0, 1.0 - smoothstep(0.0, 0.015, f.abs()));
545 v = mix(v, 1.0, 1.0 - smoothstep(0.0, 0.005, f.abs()));
546 (v.clamp(0.0, 1.0) * 255.0) as u8
547 };
548
549 [run(r), run(g), run(b), 255]
550 }
551 DistancePixel::Fill { inside, .. } => {
553 if inside {
554 [184, 235, 255, 255]
555 } else {
556 [217, 144, 72, 255]
557 }
558 }
559 },
560 threads,
561 );
562 out
563}