| 1 | //! Write with/without rim comparison PNGs for acceptance artifacts. |
| 2 | use paperdoll_renderer::compositor::{ |
| 3 | apply_rim, compose, relight, LayerMaps, Light, RimParams, DEFAULT_DEPTH_EDGE_SCALE, |
| 4 | DEFAULT_RIM_POWER, |
| 5 | }; |
| 6 | use paperdoll_renderer::render_job::ImageBuf; |
| 7 | use std::env; |
| 8 | use std::path::PathBuf; |
| 9 | |
| 10 | fn encode_normal(n: [f32; 3]) -> [u8; 4] { |
| 11 | let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt().max(1e-10); |
| 12 | let n = [n[0] / len, n[1] / len, n[2] / len]; |
| 13 | [ |
| 14 | ((n[0] * 0.5 + 0.5) * 255.0).round() as u8, |
| 15 | ((n[1] * 0.5 + 0.5) * 255.0).round() as u8, |
| 16 | ((n[2] * 0.5 + 0.5) * 255.0).round() as u8, |
| 17 | 255, |
| 18 | ] |
| 19 | } |
| 20 | |
| 21 | fn sphere_layer(size: u32) -> LayerMaps { |
| 22 | let w = size as usize; |
| 23 | let mut diffuse = vec![0u8; w * w * 4]; |
| 24 | let mut normal = vec![0u8; w * w * 4]; |
| 25 | let mut specular = vec![0u8; w * w * 4]; |
| 26 | let mut depth = vec![0u16; w * w * 2]; |
| 27 | let cx = (size as f32 - 1.0) * 0.5; |
| 28 | let cy = cx; |
| 29 | let radius = cx * 0.92; |
| 30 | for y in 0..w { |
| 31 | for x in 0..w { |
| 32 | let i = y * w + x; |
| 33 | let dx = x as f32 - cx; |
| 34 | let dy = y as f32 - cy; |
| 35 | let r2 = dx * dx + dy * dy; |
| 36 | if r2 > radius * radius { |
| 37 | continue; |
| 38 | } |
| 39 | let nz = (1.0 - (r2 / (radius * radius))).sqrt(); |
| 40 | let nx = dx / radius; |
| 41 | let ny = -dy / radius; |
| 42 | let n = encode_normal([nx, ny, nz]); |
| 43 | let o = i * 4; |
| 44 | // Warm albedo |
| 45 | diffuse[o] = 160; |
| 46 | diffuse[o + 1] = 90; |
| 47 | diffuse[o + 2] = 70; |
| 48 | diffuse[o + 3] = 255; |
| 49 | normal[o..o + 4].copy_from_slice(&n); |
| 50 | specular[o] = 40; |
| 51 | specular[o + 1] = 40; |
| 52 | specular[o + 2] = 40; |
| 53 | specular[o + 3] = 255; |
| 54 | // Nearer at center (smaller gray) |
| 55 | let gray = (5000.0 + (1.0 - nz) * 25000.0) as u16; |
| 56 | depth[i * 2] = gray; |
| 57 | depth[i * 2 + 1] = 65535; |
| 58 | } |
| 59 | } |
| 60 | LayerMaps { |
| 61 | label: "sphere".into(), |
| 62 | width: size, |
| 63 | height: size, |
| 64 | diffuse, |
| 65 | normal, |
| 66 | specular, |
| 67 | depth, |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | fn side_by_side(a: &ImageBuf, b: &ImageBuf) -> ImageBuf { |
| 72 | assert_eq!(a.width, b.width); |
| 73 | assert_eq!(a.height, b.height); |
| 74 | let w = a.width as usize; |
| 75 | let h = a.height as usize; |
| 76 | let out_w = w * 2 + 4; |
| 77 | let mut rgba = vec![30u8; out_w * h * 4]; |
| 78 | for y in 0..h { |
| 79 | for x in 0..w { |
| 80 | let si = (y * w + x) * 4; |
| 81 | let dl = (y * out_w + x) * 4; |
| 82 | let dr = (y * out_w + (x + w + 4)) * 4; |
| 83 | rgba[dl..dl + 4].copy_from_slice(&a.rgba[si..si + 4]); |
| 84 | rgba[dr..dr + 4].copy_from_slice(&b.rgba[si..si + 4]); |
| 85 | } |
| 86 | } |
| 87 | ImageBuf { |
| 88 | width: out_w as u32, |
| 89 | height: a.height, |
| 90 | rgba, |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | fn main() -> Result<(), String> { |
| 95 | let out = PathBuf::from( |
| 96 | env::args() |
| 97 | .nth(1) |
| 98 | .unwrap_or_else(|| "/workspace/paperdoll-rim-artifacts".into()), |
| 99 | ); |
| 100 | std::fs::create_dir_all(&out).map_err(|e| e.to_string())?; |
| 101 | let layer = sphere_layer(96); |
| 102 | let composed = compose(&[layer]).map_err(|e| e.to_string())?; |
| 103 | let light = Light::default(); |
| 104 | let lit = relight(&composed, &light).map_err(|e| e.to_string())?; |
| 105 | let rimmed = apply_rim( |
| 106 | &composed, |
| 107 | &lit, |
| 108 | &RimParams { |
| 109 | intensity: 0.35, |
| 110 | power: DEFAULT_RIM_POWER, |
| 111 | depth_edge_weight: 0.25, |
| 112 | depth_edge_scale: DEFAULT_DEPTH_EDGE_SCALE, |
| 113 | }, |
| 114 | ) |
| 115 | .map_err(|e| e.to_string())?; |
| 116 | let off = apply_rim(&composed, &lit, &RimParams::off()).map_err(|e| e.to_string())?; |
| 117 | assert_eq!(off.rgba, lit.rgba); |
| 118 | let compare = side_by_side(&lit, &rimmed); |
| 119 | for (name, img) in [ |
| 120 | ("rim_off.png", &lit), |
| 121 | ("rim_on.png", &rimmed), |
| 122 | ("rim_compare_off_vs_on.png", &compare), |
| 123 | ] { |
| 124 | let path = out.join(name); |
| 125 | let file = std::fs::File::create(&path).map_err(|e| e.to_string())?; |
| 126 | let mut encoder = png::Encoder::new(file, img.width, img.height); |
| 127 | encoder.set_color(png::ColorType::Rgba); |
| 128 | encoder.set_depth(png::BitDepth::Eight); |
| 129 | let mut writer = encoder.write_header().map_err(|e| e.to_string())?; |
| 130 | writer |
| 131 | .write_image_data(&img.rgba) |
| 132 | .map_err(|e| e.to_string())?; |
| 133 | writer.finish().map_err(|e| e.to_string())?; |
| 134 | } |
| 135 | // Bound check note |
| 136 | println!("wrote comparison PNGs under {}", out.display()); |
| 137 | Ok(()) |
| 138 | } |