First version of adaptive subsampling.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Add debugging images, and move rendering to output module.
This commit is contained in:
@@ -11,6 +11,7 @@ pub mod kdtree;
|
||||
pub mod material;
|
||||
pub mod moving_sphere;
|
||||
pub mod noise;
|
||||
pub mod output;
|
||||
pub mod ray;
|
||||
pub mod rect;
|
||||
pub mod renderer;
|
||||
|
||||
139
rtiow/src/output.rs
Normal file
139
rtiow/src/output.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use chrono::Local;
|
||||
use image::RgbImage;
|
||||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
// Main RGB image output from rendering the scene.
|
||||
pub const MAIN_IMAGE: &str = "@final";
|
||||
// Debug image for adaptive pixels subsampling.
|
||||
// Red indicates recursion hit maximum depth splitting the pixel.
|
||||
// Green indicates no subdivision necessary
|
||||
pub const ADAPTIVE_DEPTH: &str = "adaptive_depth";
|
||||
|
||||
lazy_static! {
|
||||
static ref DEBUGGER: Arc<Mutex<Debugger>> = Arc::new(Mutex::new(Debugger::new()));
|
||||
}
|
||||
|
||||
struct Debugger {
|
||||
images: HashMap<String, RgbImage>,
|
||||
}
|
||||
|
||||
impl Debugger {
|
||||
fn new() -> Debugger {
|
||||
Debugger {
|
||||
images: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_image(name: String, dimensions: (u32, u32)) {
|
||||
let mut debugger = DEBUGGER.lock().unwrap();
|
||||
debugger
|
||||
.images
|
||||
.insert(name, RgbImage::new(dimensions.0, dimensions.1));
|
||||
}
|
||||
|
||||
pub fn set_pixel(name: &str, x: usize, y: usize, pixel: Vec3) {
|
||||
let mut debugger = DEBUGGER.lock().unwrap();
|
||||
let img = debugger
|
||||
.images
|
||||
.get_mut(name)
|
||||
.expect(&format!("couldn't find image named '{}'", name));
|
||||
let y_inv = img.height() - y as u32 - 1;
|
||||
img.put_pixel(
|
||||
x as u32,
|
||||
y_inv as u32,
|
||||
image::Rgb([
|
||||
(pixel[0] * 255.).min(255.) as u8,
|
||||
(pixel[1] * 255.).min(255.) as u8,
|
||||
(pixel[2] * 255.).min(255.) as u8,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn write_images<P: AsRef<Path>>(output_dir: P) -> std::io::Result<()> {
|
||||
let output_dir: &Path = output_dir.as_ref();
|
||||
let debugger = DEBUGGER.lock().unwrap();
|
||||
let now = Local::now();
|
||||
/*
|
||||
let style = r#"
|
||||
<style>
|
||||
.frame {
|
||||
display: inline-block;
|
||||
}
|
||||
figure {
|
||||
margin: 0.25em;
|
||||
}
|
||||
figcaption {
|
||||
text-align: center;
|
||||
}
|
||||
img {
|
||||
max-width: 97%;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
border: 1px solid #999;
|
||||
padding: 5px;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
"#;
|
||||
writeln!(f, "{}", style)?;
|
||||
writeln!(
|
||||
f,
|
||||
"<h1>Run @ {} ({})</h1>",
|
||||
now.to_rfc2822(),
|
||||
now.timestamp()
|
||||
)?;
|
||||
*/
|
||||
// Write out images in consistent order.
|
||||
let mut names = debugger.images.keys().collect::<Vec<_>>();
|
||||
names.sort();
|
||||
let mut ratio = 1.;
|
||||
let mut size = (0, 0);
|
||||
for name in &names {
|
||||
let img = debugger.images.get(*name).unwrap();
|
||||
if *name == MAIN_IMAGE {
|
||||
let (w, h) = img.dimensions();
|
||||
ratio = w as f32 / h as f32;
|
||||
size = img.dimensions();
|
||||
}
|
||||
let filename = format!("{}.png", name);
|
||||
/*
|
||||
writeln!(
|
||||
f,
|
||||
r#"<div class="frame"><figure><a href="{0}?t={2}"><img src="{0}?t={2}"></a><figcaption>{1} ({2})</figcaption></figure></div>"#,
|
||||
filename, name, now.timestamp())?;
|
||||
*/
|
||||
let path = output_dir.join(filename);
|
||||
info!("Saving {}", path.to_string_lossy());
|
||||
img.save(path)?;
|
||||
}
|
||||
let f = File::create(output_dir.join("data.json"))?;
|
||||
#[derive(Serialize)]
|
||||
struct Data {
|
||||
timestamp: i64,
|
||||
ratio: f32,
|
||||
size: (u32, u32),
|
||||
names: Vec<String>,
|
||||
images: Vec<String>,
|
||||
}
|
||||
serde_json::ser::to_writer(
|
||||
f,
|
||||
&Data {
|
||||
timestamp: now.timestamp(),
|
||||
ratio,
|
||||
size,
|
||||
names: names.iter().map(|s| s.to_string()).collect(),
|
||||
images: names.iter().map(|s| format!("{}.png", s)).collect(),
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::fmt;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::str;
|
||||
@@ -13,8 +14,6 @@ use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
use image;
|
||||
use image::RgbImage;
|
||||
#[cfg(feature = "prom")]
|
||||
use lazy_static::lazy_static;
|
||||
use num_cpus;
|
||||
@@ -25,6 +24,7 @@ use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::human;
|
||||
use crate::material::Lambertian;
|
||||
use crate::output;
|
||||
use crate::ray::Ray;
|
||||
use crate::scenes;
|
||||
use crate::sphere::Sphere;
|
||||
@@ -150,9 +150,6 @@ pub struct Opt {
|
||||
/// Sub-samples per pixel
|
||||
#[structopt(short = "s", long = "subsample", default_value = "8")]
|
||||
pub subsamples: usize,
|
||||
/// Use adaptive subsampling
|
||||
#[structopt(long = "adaptive")]
|
||||
pub adaptive_subsampling: bool,
|
||||
/// Select scene to render, one of: "bench", "book", "tutorial", "bvh", "test", "cornell_box",
|
||||
/// "cornell_smoke", "perlin_debug", "final"
|
||||
#[structopt(long = "model", default_value = "book")]
|
||||
@@ -188,7 +185,7 @@ pub struct Scene {
|
||||
pub camera: Camera,
|
||||
pub subsamples: usize,
|
||||
/// overrides subsamples setting.
|
||||
pub adaptive_subsampling: bool,
|
||||
pub adaptive_subsampling: Option<f32>,
|
||||
pub num_threads: Option<usize>,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
@@ -223,7 +220,7 @@ impl Default for Scene {
|
||||
)),
|
||||
camera,
|
||||
subsamples: 0,
|
||||
adaptive_subsampling: false,
|
||||
adaptive_subsampling: None,
|
||||
num_threads: None,
|
||||
width: 0,
|
||||
height: 0,
|
||||
@@ -278,7 +275,103 @@ fn color(
|
||||
Vec3::new(0., 0., 0.)
|
||||
}
|
||||
|
||||
fn trace_pixel(x: usize, y: usize, scene: &Scene) -> Vec3 {
|
||||
const MAX_ADAPTIVE_DEPTH: usize = 10;
|
||||
fn trace_pixel_adaptive(
|
||||
depth: usize,
|
||||
threshold: f32,
|
||||
x: usize,
|
||||
y: usize,
|
||||
x_range: Range<f32>,
|
||||
y_range: Range<f32>,
|
||||
scene: &Scene,
|
||||
) -> Vec3 {
|
||||
let w = scene.width as f32;
|
||||
let h = scene.height as f32;
|
||||
let x_mid = x_range.start + ((x_range.end - x_range.start) / 2.);
|
||||
let y_mid = y_range.start + ((y_range.end - y_range.start) / 2.);
|
||||
let mc = ((x_mid + x as f32) / w, (y_mid + y as f32) / h);
|
||||
let center = color(
|
||||
scene.camera.get_ray(mc.0, mc.1),
|
||||
scene.world.as_ref(),
|
||||
0,
|
||||
scene.global_illumination,
|
||||
&scene.env_map,
|
||||
);
|
||||
if depth == 0 {
|
||||
output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [1., 0., 0.].into());
|
||||
return center;
|
||||
}
|
||||
// t = top
|
||||
// m = middle
|
||||
// b = bottom
|
||||
// l = left
|
||||
// c = center
|
||||
// r = right
|
||||
let tl = (
|
||||
(x_range.start + x as f32) / w,
|
||||
(y_range.start + y as f32) / h,
|
||||
);
|
||||
let tr = ((x_range.end + x as f32) / w, (y_range.start + y as f32) / h);
|
||||
let bl = ((x_range.start + x as f32) / w, (y_range.end + y as f32) / h);
|
||||
let br = ((x_range.end + x as f32) / w, (y_range.end + y as f32) / h);
|
||||
|
||||
let corners = [tl, tr, mc, bl, br]
|
||||
.iter()
|
||||
.map(|(u, v)| {
|
||||
color(
|
||||
scene.camera.get_ray(*u, *v),
|
||||
scene.world.as_ref(),
|
||||
0,
|
||||
scene.global_illumination,
|
||||
&scene.env_map,
|
||||
)
|
||||
})
|
||||
.fold([0., 0., 0.].into(), |a: Vec3, b: Vec3| a + b)
|
||||
/ 5.;
|
||||
if (corners - center).length() > threshold {
|
||||
let pixel = (trace_pixel_adaptive(
|
||||
depth - 1,
|
||||
threshold,
|
||||
x,
|
||||
y,
|
||||
x_range.start..x_mid,
|
||||
y_range.start..y_mid,
|
||||
scene,
|
||||
) + trace_pixel_adaptive(
|
||||
depth - 1,
|
||||
threshold,
|
||||
x,
|
||||
y,
|
||||
x_mid..x_range.end,
|
||||
y_range.start..y_mid,
|
||||
scene,
|
||||
) + trace_pixel_adaptive(
|
||||
depth - 1,
|
||||
threshold,
|
||||
x,
|
||||
y,
|
||||
x_range.start..x_mid,
|
||||
y_mid..y_range.end,
|
||||
scene,
|
||||
) + trace_pixel_adaptive(
|
||||
depth - 1,
|
||||
threshold,
|
||||
x,
|
||||
y,
|
||||
x_mid..x_range.end,
|
||||
y_mid..y_range.end,
|
||||
scene,
|
||||
)) / 4.;
|
||||
pixel
|
||||
} else {
|
||||
if depth == MAX_ADAPTIVE_DEPTH {
|
||||
output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [0., 1., 0.].into());
|
||||
}
|
||||
corners
|
||||
}
|
||||
}
|
||||
|
||||
fn trace_pixel_random(x: usize, y: usize, scene: &Scene) -> Vec3 {
|
||||
let mut rng = rand::thread_rng();
|
||||
let u = (rng.gen_range::<f32>(0., 1.) + x as f32) / scene.width as f32;
|
||||
let v = (rng.gen_range::<f32>(0., 1.) + y as f32) / scene.height as f32;
|
||||
@@ -306,17 +399,25 @@ static PIXEL_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
fn render_pixel(scene: &Scene, x: usize, y: usize) -> Vec3 {
|
||||
let mut pixel: Vec3 = Default::default();
|
||||
let pixel = if scene.adaptive_subsampling {
|
||||
Default::default()
|
||||
let pixel = if let Some(threshold) = scene.adaptive_subsampling {
|
||||
trace_pixel_adaptive(
|
||||
MAX_ADAPTIVE_DEPTH,
|
||||
threshold,
|
||||
x,
|
||||
y,
|
||||
0.0..1.0,
|
||||
0.0..1.0,
|
||||
scene,
|
||||
)
|
||||
} else {
|
||||
for _ in 0..scene.subsamples {
|
||||
pixel = pixel + trace_pixel(x, y, scene);
|
||||
pixel = pixel + trace_pixel_random(x, y, scene);
|
||||
}
|
||||
pixel / scene.subsamples as f32
|
||||
};
|
||||
PIXEL_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
// Gamma correct, use gamma 2 correction, which is 1/gamma where gamma=2 which is 1/2 or
|
||||
// sqrt.
|
||||
PIXEL_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
Vec3::new(pixel[0].sqrt(), pixel[1].sqrt(), pixel[2].sqrt())
|
||||
}
|
||||
|
||||
@@ -361,7 +462,7 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
|
||||
let scene = Arc::new(scene);
|
||||
let pixel_req_rx = Arc::new(Mutex::new(pixel_req_rx));
|
||||
info!("Creating {} render threads", num_threads);
|
||||
info!("Adaptive subsampling: {}", scene.adaptive_subsampling);
|
||||
info!("Adaptive subsampling: {:?}", scene.adaptive_subsampling);
|
||||
for i in 0..num_threads {
|
||||
let s = sync::Arc::clone(&scene);
|
||||
let pixel_req_rx = pixel_req_rx.clone();
|
||||
@@ -394,6 +495,18 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
|
||||
drop(pixel_req_tx);
|
||||
});
|
||||
|
||||
info!("Rendering with {} subsamples", scene.subsamples);
|
||||
output::register_image(
|
||||
output::MAIN_IMAGE.to_string(),
|
||||
(scene.width as u32, scene.height as u32),
|
||||
);
|
||||
if scene.adaptive_subsampling.is_some() {
|
||||
output::register_image(
|
||||
output::ADAPTIVE_DEPTH.to_string(),
|
||||
(scene.width as u32, scene.height as u32),
|
||||
);
|
||||
}
|
||||
|
||||
let pixel_total = scene.width * scene.height;
|
||||
thread::spawn(move || {
|
||||
let mut last_time = time::Instant::now();
|
||||
@@ -423,40 +536,17 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
|
||||
}
|
||||
});
|
||||
|
||||
info!("Rendering with {} subsamples", scene.subsamples);
|
||||
let mut img = RgbImage::new(scene.width as u32, scene.height as u32);
|
||||
for resp in pixel_resp_rx {
|
||||
match resp {
|
||||
Response::Pixel { x, y, pixel } => {
|
||||
let y_inv = scene.height - y - 1;
|
||||
img.put_pixel(
|
||||
x as u32,
|
||||
y_inv as u32,
|
||||
image::Rgb([
|
||||
(pixel[0] * 255.).min(255.) as u8,
|
||||
(pixel[1] * 255.).min(255.) as u8,
|
||||
(pixel[2] * 255.).min(255.) as u8,
|
||||
]),
|
||||
);
|
||||
output::set_pixel(output::MAIN_IMAGE, x, y, pixel);
|
||||
}
|
||||
Response::Line { y, pixels } => {
|
||||
for (x, pixel) in pixels.iter().enumerate() {
|
||||
let y_inv = scene.height - y - 1;
|
||||
img.put_pixel(
|
||||
x as u32,
|
||||
y_inv as u32,
|
||||
image::Rgb([
|
||||
(pixel[0] * 255.).min(255.) as u8,
|
||||
(pixel[1] * 255.).min(255.) as u8,
|
||||
(pixel[2] * 255.).min(255.) as u8,
|
||||
]),
|
||||
);
|
||||
output::set_pixel(output::MAIN_IMAGE, x, y, *pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let path = output_dir.join("final.png");
|
||||
// Write the contents of this image to the Writer in PNG format.
|
||||
trace!(target: "renderer", "Saving {}", path.display());
|
||||
img.save(path)
|
||||
output::write_images(output_dir)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
adaptive_subsampling: opt.adaptive_subsampling,
|
||||
adaptive_subsampling: Some(0.5),
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::camera::Camera;
|
||||
use crate::hitable::Hit;
|
||||
use crate::hitable_list::HitableList;
|
||||
use crate::kdtree::KDTree;
|
||||
use crate::material::Dielectric;
|
||||
use crate::material::Lambertian;
|
||||
use crate::material::Metal;
|
||||
use crate::moving_sphere::MovingSphere;
|
||||
@@ -12,7 +13,7 @@ use crate::texture::ConstantTexture;
|
||||
use crate::vec3::Vec3;
|
||||
|
||||
pub fn new(opt: &Opt) -> Scene {
|
||||
let lookfrom = Vec3::new(3., 3., 2.);
|
||||
let lookfrom = Vec3::new(3., 2., 2.);
|
||||
let lookat = Vec3::new(0., 0., -1.);
|
||||
let dist_to_focus = (lookfrom - lookat).length();
|
||||
let aperture = 0.1;
|
||||
@@ -37,6 +38,7 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
|
||||
let objects: Vec<Box<dyn Hit>> = vec![
|
||||
//let world: Box<Hit> = Box::new(HitableList::new(vec![
|
||||
Box::new(Sphere::new([1., 0., 0.], 0.5, Dielectric::new(1.5))),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(0., 0., -1.),
|
||||
0.5,
|
||||
@@ -70,6 +72,8 @@ pub fn new(opt: &Opt) -> Scene {
|
||||
camera,
|
||||
world,
|
||||
subsamples: opt.subsamples,
|
||||
// adaptive_subsampling: None,
|
||||
adaptive_subsampling: Some(0.5),
|
||||
num_threads: opt.num_threads,
|
||||
width: opt.width,
|
||||
height: opt.height,
|
||||
|
||||
Reference in New Issue
Block a user