First version of adaptive subsampling.
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:
Bill Thiede 2019-10-16 20:21:16 -07:00
parent f0f90a6b80
commit 5d9e180817
7 changed files with 286 additions and 50 deletions

19
rtiow/Cargo.lock generated
View File

@ -96,7 +96,7 @@ dependencies = [
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_urlencoded 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -428,7 +428,7 @@ dependencies = [
"rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"tinytemplate 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1566,7 +1566,7 @@ dependencies = [
"mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_urlencoded 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-executor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1614,6 +1614,7 @@ dependencies = [
"rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
"stderrlog 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1641,7 +1642,7 @@ dependencies = [
[[package]]
name = "ryu"
version = "0.2.7"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -1732,11 +1733,11 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.38"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1926,7 +1927,7 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2593,7 +2594,7 @@ dependencies = [
"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619"
"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
"checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9"
"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
"checksum schannel 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "0e1a231dc10abf6749cfa5d7767f25888d484201accbd919b66ab5413c502d56"
@ -2606,7 +2607,7 @@ dependencies = [
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)" = "9f301d728f2b94c9a7691c90f07b0b4e8a4517181d9461be94c04bddeb4bd850"
"checksum serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)" = "beed18e6f5175aef3ba670e57c60ef3b1b74d250d962a26604bff4c80e970dd4"
"checksum serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "27dce848e7467aa0e2fcaf0a413641499c0b745452aaca1194d24dedde9e13c9"
"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
"checksum serde_urlencoded 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d48f9f99cd749a2de71d29da5f948de7f2764cc5a9d7f3c97e3514d4ee6eabf2"
"checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
"checksum signal-hook 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1f272d1b7586bec132ed427f532dd418d8beca1ca7f2caf7df35569b1415a4b4"

View File

@ -20,6 +20,7 @@ num_cpus = "1.8.0"
rand = "0.5.5"
serde = "1.0.79"
serde_derive = "1.0.79"
serde_json = "1.0.41"
stderrlog = "0.4.1"
structopt = "0.2.10"

View File

@ -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
View 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(())
}

View File

@ -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)
}

View File

@ -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,

View File

@ -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,