Compare commits

...

13 Commits

18 changed files with 1102 additions and 619 deletions

998
rtiow/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
[workspace]
resolver = "2"
members = [
"noise_explorer",
@@ -10,3 +11,5 @@ members = [
[profile.release]
debug = true
[profile.dev]
opt-level = 3

View File

@@ -23,12 +23,14 @@ num_cpus = "1.15.0"
rand = "0.8.5"
serde = "1.0.152"
serde_derive = "1.0.152"
serde_json = "1.0.91"
serde_json = "1.0.93"
structopt = "0.2.18"
vec3 = {path = "../vec3"}
stl = {path = "../../../stl"}
strum = { version = "0.24.1", features = ["derive"] }
strum_macros = "0.24.3"
thiserror = "1.0.38"
tev_client = "0.5.2"
#stl = {git = "https://git-private.z.xinu.tv/wathiede/stl"}
[dev-dependencies]

View File

@@ -120,7 +120,7 @@ impl<M> BVHTriangles<M>
where
M: Material,
{
pub fn new(stl: &STL, material: M) -> BVHTriangles<M> {
pub fn new(stl: &STL, material: M, scale_factor: f32) -> BVHTriangles<M> {
let now = std::time::Instant::now();
assert_eq!(std::mem::size_of::<BVHNode>(), 32);
@@ -129,9 +129,9 @@ where
.triangles
.iter()
.map(|t| {
let v0 = t.verts[0];
let v1 = t.verts[1];
let v2 = t.verts[2];
let v0 = t.verts[0] * scale_factor;
let v1 = t.verts[1] * scale_factor;
let v2 = t.verts[2] * scale_factor;
let centroid = (v0 + v1 + v2) * div3;
Triangle {
centroid,

View File

@@ -18,6 +18,7 @@ fn random_in_unit_disk() -> Vec3 {
}
}
#[derive(Debug)]
pub struct Camera {
origin: Vec3,
lower_left_corner: Vec3,

View File

@@ -3,8 +3,6 @@ use crate::{
hitable::{Hit, HitRecord},
material::Lambertian,
ray::Ray,
texture::{self, ConstantTexture},
vec3::Vec3,
};
#[derive(Debug)]

View File

@@ -16,6 +16,7 @@ pub mod material;
pub mod moving_sphere;
pub mod noise;
pub mod output;
pub mod parser;
pub mod ray;
pub mod rect;
pub mod renderer;

View File

@@ -2,6 +2,7 @@ use std::{
collections::HashMap,
fs::File,
io::BufWriter,
net::TcpStream,
path::Path,
sync::{Arc, Mutex},
time,
@@ -9,9 +10,9 @@ use std::{
use chrono::Local;
use image;
use lazy_static::lazy_static;
use log::info;
use serde_derive::Serialize;
use tev_client::{PacketCreateImage, PacketUpdateImage, TevClient};
use crate::{renderer::Scene, vec3::Vec3};
@@ -24,10 +25,6 @@ pub const ADAPTIVE_DEPTH: &str = "adaptive_depth";
// Grey scale showing rays cast per pixel.
pub const RAYS_PER_PIXEL: &str = "rays_per_pixel";
lazy_static! {
static ref DEBUGGER: Arc<Mutex<Debugger>> = Arc::new(Mutex::new(Debugger::new()));
}
#[derive(Serialize)]
struct ImageMetadata {
name: String,
@@ -74,43 +71,176 @@ impl Image {
}
}
struct Debugger {
images: HashMap<String, (ImageType, Image)>,
pub struct OutputManager {
images: Arc<Mutex<HashMap<String, (ImageType, Image)>>>,
tev_client: Option<Arc<Mutex<TevClient>>>,
}
impl Debugger {
fn new() -> Debugger {
Debugger {
images: HashMap::new(),
}
impl OutputManager {
pub fn new(tev_addr: &Option<String>) -> std::io::Result<OutputManager> {
let tev_client = if let Some(addr) = tev_addr {
Some(Arc::new(Mutex::new(TevClient::wrap(TcpStream::connect(
addr,
)?))))
} else {
None
};
Ok(OutputManager {
images: Arc::new(Mutex::new(HashMap::new())),
tev_client,
})
}
}
pub fn register_image(name: String, dimensions: (usize, usize), it: ImageType) {
let mut debugger = DEBUGGER.lock().unwrap();
debugger
.images
.insert(name, (it, Image::new(dimensions.0, dimensions.1)));
}
pub fn register_image(&self, name: String, dimensions: (usize, usize), it: ImageType) {
let mut images = self.images.lock().unwrap();
images.insert(name.clone(), (it, Image::new(dimensions.0, dimensions.1)));
self.tev_client.clone().map(|c| {
c.lock().unwrap().send(PacketCreateImage {
image_name: &name,
grab_focus: false,
width: dimensions.0 as u32,
height: dimensions.1 as u32,
channel_names: &["R", "G", "B"],
})
});
}
pub fn set_pixel(name: &str, x: usize, y: usize, pixel: Vec3) {
let mut debugger = DEBUGGER.lock().unwrap();
let (_it, img) = debugger
.images
.get_mut(name)
.unwrap_or_else(|| panic!("couldn't find image named '{}'", name));
let y_inv = img.h - y - 1;
img.put_pixel(x, y_inv, pixel);
}
pub fn set_pixel(&self, name: &str, x: usize, y: usize, pixel: Vec3) {
let mut images = self.images.lock().unwrap();
let (_it, img) = images
.get_mut(name)
.unwrap_or_else(|| panic!("couldn't find image named '{}'", name));
let y_inv = img.h - y - 1;
img.put_pixel(x, y_inv, pixel);
self.tev_client.clone().map(|c| {
c.lock().unwrap().send(PacketUpdateImage {
image_name: &name,
grab_focus: false,
channel_names: &["R", "G", "B"],
channel_offsets: &[0, 1, 2],
channel_strides: &[0, 0, 0],
x: x as u32,
y: y_inv as u32,
width: 1,
height: 1,
data: &[pixel.x, pixel.y, pixel.z],
})
});
}
pub fn set_pixel_grey(name: &str, x: usize, y: usize, grey: f32) {
let mut debugger = DEBUGGER.lock().unwrap();
let (_it, img) = debugger
.images
.get_mut(name)
.unwrap_or_else(|| panic!("couldn't find image named '{}'", name));
let y_inv = img.h - y - 1;
img.put_pixel(x, y_inv, [grey, grey, grey].into());
pub fn set_pixel_grey(&self, name: &str, x: usize, y: usize, grey: f32) {
let mut images = self.images.lock().unwrap();
let (_it, img) = images
.get_mut(name)
.unwrap_or_else(|| panic!("couldn't find image named '{}'", name));
let y_inv = img.h - y - 1;
img.put_pixel(x, y_inv, [grey, grey, grey].into());
}
pub fn write_images<P: AsRef<Path>>(
&self,
scene: &Scene,
render_time: time::Duration,
output_dir: P,
) -> std::io::Result<()> {
let output_dir: &Path = output_dir.as_ref();
let now = Local::now();
let images = self.images.lock().unwrap();
// Write out images in consistent order.
let mut names = images.keys().collect::<Vec<_>>();
names.sort();
let mut image_metadata = Vec::new();
for name in &names {
let (it, img) = images.get(*name).unwrap();
let image = format!("{}.png", name);
let binary = format!("{}.json", name);
let ratio = img.w as f32 / img.h as f32;
let size = (img.w, img.h);
let image_path = output_dir.join(&image);
let binary_path = output_dir.join(&binary);
image_metadata.push(ImageMetadata {
name: name.to_string(),
image,
binary,
ratio,
size,
format: *it,
});
info!("Saving {}", image_path.to_string_lossy());
match it {
ImageType::RGB01 => {
let mut out_img = image::RgbImage::new(img.w as u32, img.h as u32);
out_img
.enumerate_pixels_mut()
.enumerate()
.for_each(|(i, (_x, _y, p))| {
let pixel = img.pix[i];
*p = image::Rgb([
(pixel[0] * 255.).min(255.) as u8,
(pixel[1] * 255.).min(255.) as u8,
(pixel[2] * 255.).min(255.) as u8,
])
});
out_img.save(image_path)?;
}
ImageType::Grey01 => {
let mut out_img = image::GrayImage::new(img.w as u32, img.h as u32);
out_img
.enumerate_pixels_mut()
.enumerate()
.for_each(|(i, (_x, _y, p))| {
let pixel = img.pix[i];
*p = image::Luma([(pixel[0] * 255.).min(255.) as u8])
});
out_img.save(image_path)?;
}
ImageType::GreyNormalized => {
let mut out_img = image::GrayImage::new(img.w as u32, img.h as u32);
let max_val = img.pix.iter().map(|v| v.x).fold(0., f32::max);
out_img
.enumerate_pixels_mut()
.enumerate()
.for_each(|(i, (_x, _y, p))| {
let pixel = img.pix[i];
*p = image::Luma([(pixel[0] / max_val * 255.).min(255.) as u8])
});
out_img.save(image_path)?;
}
};
info!("Saving {}", binary_path.to_string_lossy());
let f = File::create(output_dir.join(binary_path))?;
let f = BufWriter::new(f);
match it {
ImageType::RGB01 => {
serde_json::ser::to_writer(
f,
&img.pix
.iter()
.map(|v| [v.x, v.y, v.z])
.collect::<Vec<[f32; 3]>>(),
)?;
}
ImageType::Grey01 | ImageType::GreyNormalized => {
serde_json::ser::to_writer(
f,
&img.pix.iter().map(|v| v.x).collect::<Vec<f32>>(),
)?;
}
};
}
let f = File::create(output_dir.join("data.json"))?;
let f = BufWriter::new(f);
serde_json::ser::to_writer(
f,
&Data {
timestamp: now.timestamp(),
render_time_seconds: render_time.as_secs_f32(),
scene,
image_metadata,
},
)?;
Ok(())
}
}
trait ImageSaver {
@@ -118,105 +248,3 @@ trait ImageSaver {
where
Q: AsRef<Path> + Sized;
}
pub fn write_images<P: AsRef<Path>>(
scene: &Scene,
render_time: time::Duration,
output_dir: P,
) -> std::io::Result<()> {
let output_dir: &Path = output_dir.as_ref();
let debugger = DEBUGGER.lock().unwrap();
let now = Local::now();
// Write out images in consistent order.
let mut names = debugger.images.keys().collect::<Vec<_>>();
names.sort();
let mut image_metadata = Vec::new();
for name in &names {
let (it, img) = debugger.images.get(*name).unwrap();
let image = format!("{}.png", name);
let binary = format!("{}.json", name);
let ratio = img.w as f32 / img.h as f32;
let size = (img.w, img.h);
let image_path = output_dir.join(&image);
let binary_path = output_dir.join(&binary);
image_metadata.push(ImageMetadata {
name: name.to_string(),
image,
binary,
ratio,
size,
format: *it,
});
info!("Saving {}", image_path.to_string_lossy());
match it {
ImageType::RGB01 => {
let mut out_img = image::RgbImage::new(img.w as u32, img.h as u32);
out_img
.enumerate_pixels_mut()
.enumerate()
.for_each(|(i, (_x, _y, p))| {
let pixel = img.pix[i];
*p = image::Rgb([
(pixel[0] * 255.).min(255.) as u8,
(pixel[1] * 255.).min(255.) as u8,
(pixel[2] * 255.).min(255.) as u8,
])
});
out_img.save(image_path)?;
}
ImageType::Grey01 => {
let mut out_img = image::GrayImage::new(img.w as u32, img.h as u32);
out_img
.enumerate_pixels_mut()
.enumerate()
.for_each(|(i, (_x, _y, p))| {
let pixel = img.pix[i];
*p = image::Luma([(pixel[0] * 255.).min(255.) as u8])
});
out_img.save(image_path)?;
}
ImageType::GreyNormalized => {
let mut out_img = image::GrayImage::new(img.w as u32, img.h as u32);
let max_val = img.pix.iter().map(|v| v.x).fold(0., f32::max);
out_img
.enumerate_pixels_mut()
.enumerate()
.for_each(|(i, (_x, _y, p))| {
let pixel = img.pix[i];
*p = image::Luma([(pixel[0] / max_val * 255.).min(255.) as u8])
});
out_img.save(image_path)?;
}
};
info!("Saving {}", binary_path.to_string_lossy());
let f = File::create(output_dir.join(binary_path))?;
let f = BufWriter::new(f);
match it {
ImageType::RGB01 => {
serde_json::ser::to_writer(
f,
&img.pix
.iter()
.map(|v| [v.x, v.y, v.z])
.collect::<Vec<[f32; 3]>>(),
)?;
}
ImageType::Grey01 | ImageType::GreyNormalized => {
serde_json::ser::to_writer(f, &img.pix.iter().map(|v| v.x).collect::<Vec<f32>>())?;
}
};
}
let f = File::create(output_dir.join("data.json"))?;
let f = BufWriter::new(f);
serde_json::ser::to_writer(
f,
&Data {
timestamp: now.timestamp(),
render_time_seconds: render_time.as_secs_f32(),
scene,
image_metadata,
},
)?;
Ok(())
}

View File

@@ -0,0 +1,206 @@
use crate::{
bvh_triangles::BVHTriangles,
camera::Camera,
cuboid::Cuboid,
hitable::Hit,
hitable_list::HitableList,
material::{Dielectric, DiffuseLight, Isotropic, Lambertian, Material, Metal},
renderer::Scene,
sphere::Sphere,
texture::{EnvMap, Texture},
};
use chrono::IsoWeek;
use serde::Deserialize;
use std::{collections::HashMap, fs::File, io::BufReader, path::PathBuf, sync::Arc};
use stl::STL;
use thiserror::Error;
use vec3::Vec3;
#[derive(Debug, Deserialize)]
pub struct Config {
scene: SceneConfig,
camera: CameraConfig,
materials: Vec<MaterialConfig>,
hitables: Vec<HitableConfig>,
envmap: Option<EnvMapConfig>,
}
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("failed to load image")]
ImageError(#[from] image::ImageError),
#[error("failed to parser STL")]
STLError(#[from] stl::ParseError),
#[error("I/O error")]
IOError(#[from] std::io::Error),
#[error("duplication material named '{0}'")]
DuplicateMaterial(String),
#[error("unkown material named '{0}'")]
UnknownMaterial(String),
}
impl TryFrom<Config> for Scene {
type Error = ConfigError;
fn try_from(c: Config) -> Result<Scene, Self::Error> {
let mut materials = HashMap::new();
for mc in c.materials {
let v: Arc<dyn Material> = match mc.material {
Materials::Metal { albedo, fuzzy } => Arc::new(Metal::new(albedo, fuzzy)),
Materials::Dielectric { ref_idx } => Arc::new(Dielectric::new(ref_idx)),
Materials::DiffuseLight { texture } => Arc::new(DiffuseLight::new(texture)),
Materials::Isotropic { texture } => Arc::new(Isotropic::new(texture)),
Materials::Lambertian { texture } => Arc::new(Lambertian::new(texture)),
};
if materials.insert(mc.name.clone(), v).is_some() {
return Err(ConfigError::DuplicateMaterial(mc.name));
}
}
let hitables: Result<Vec<Box<dyn Hit>>, Self::Error> = c
.hitables
.into_iter()
.map(|hc| -> Result<Box<dyn Hit>, Self::Error> {
match hc.hitable {
Hitables::Sphere { center, radius } => Ok(Box::new(Sphere::new(
center,
radius,
Arc::clone(
materials
.get(&hc.material_name)
.ok_or(ConfigError::UnknownMaterial(hc.material_name))?,
),
))),
Hitables::Cuboid { min, max } => Ok(Box::new(Cuboid::new(
min.into(),
max.into(),
Arc::clone(
materials
.get(&hc.material_name)
.ok_or(ConfigError::UnknownMaterial(hc.material_name))?,
),
))),
Hitables::STL { path, scale } => {
let r = BufReader::new(File::open(path)?);
let stl = STL::parse(r, false)?;
Ok(Box::new(BVHTriangles::new(
&stl,
Arc::clone(
materials
.get(&hc.material_name)
.ok_or(ConfigError::UnknownMaterial(hc.material_name))?,
),
scale.unwrap_or(1.),
)))
}
}
})
.collect();
let hitables = hitables?;
let world: Box<dyn Hit> = Box::new(HitableList::new(hitables));
let mut env_map: Option<EnvMap> = None;
if let Some(em) = c.envmap {
let im = image::open(em.path)?.into_rgb();
env_map = Some(EnvMap::new(im));
};
let camera = make_camera(&c.camera, c.scene.width, c.scene.height);
let scene = Scene {
world,
camera,
env_map,
subsamples: c.scene.subsamples.unwrap_or(8),
adaptive_subsampling: c.scene.adaptive_subsampling,
num_threads: c.scene.num_threads,
width: c.scene.width,
height: c.scene.height,
global_illumination: c.scene.global_illumination.unwrap_or(true),
};
Ok(scene)
}
}
fn make_camera(cfg: &CameraConfig, width: usize, height: usize) -> Camera {
Camera::new(
cfg.lookfrom.into(),
cfg.lookat.into(),
Vec3::new(0., 1., 0.),
cfg.fov,
width as f32 / height as f32,
cfg.aperture,
cfg.focus_dist,
cfg.time_min,
cfg.time_max,
)
}
#[derive(Debug, Deserialize)]
struct SceneConfig {
subsamples: Option<usize>,
adaptive_subsampling: Option<f32>,
num_threads: Option<usize>,
width: usize,
height: usize,
global_illumination: Option<bool>,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
struct HitableConfig {
material_name: String,
#[serde(flatten)]
hitable: Hitables,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
enum Hitables {
#[serde(rename = "sphere")]
Sphere { center: [f32; 3], radius: f32 },
#[serde(rename = "cuboid")]
Cuboid { min: [f32; 3], max: [f32; 3] },
#[serde(rename = "stl")]
STL { path: PathBuf, scale: Option<f32> },
}
#[derive(Debug, Deserialize)]
struct MaterialConfig {
name: String,
#[serde(flatten)]
material: Materials,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
enum Materials {
#[serde(rename = "metal")]
Metal { albedo: [f32; 3], fuzzy: f32 },
#[serde(rename = "dielectric")]
Dielectric { ref_idx: f32 },
// TODO(wathiede): these all take Textures, for now, only support RGB
#[serde(rename = "diffuse_light")]
DiffuseLight { texture: [f32; 3] },
#[serde(rename = "isotropic")]
Isotropic { texture: [f32; 3] },
#[serde(rename = "lambertian")]
Lambertian { texture: [f32; 3] },
}
#[derive(Debug, Deserialize)]
pub struct CameraConfig {
lookfrom: [f32; 3],
lookat: [f32; 3],
fov: f32,
aperture: f32,
focus_dist: f32,
time_min: f32,
time_max: f32,
}
#[derive(Debug, Deserialize)]
struct EnvMapConfig {
path: PathBuf,
}

View File

@@ -1,4 +1,5 @@
use std::{
collections::HashMap,
fmt,
ops::{AddAssign, Range},
path::{Path, PathBuf},
@@ -22,8 +23,9 @@ use crate::{
camera::Camera,
hitable::Hit,
human,
material::Lambertian,
material::{Lambertian, Material},
output,
output::OutputManager,
ray::Ray,
scenes,
sphere::Sphere,
@@ -99,15 +101,26 @@ pub struct Opt {
/// Select scene to render.
#[structopt(long = "model")]
pub model: Option<Model>,
/// Toml config describing scene.
#[structopt(long = "config")]
pub config: Option<PathBuf>,
/// Path to store pprof profile data, i.e. /tmp/cpuprofile.pprof
#[structopt(long = "pprof", parse(from_os_str))]
pub pprof: Option<PathBuf>,
/// Use acceleration data structure, may be BVH or kd-tree depending on scene.
#[structopt(long = "use_accel")]
pub use_accel: bool,
/// Host:port of running tev instance.
#[structopt(long = "tev_addr")]
pub tev_addr: Option<String>,
/// Output directory
#[structopt(parse(from_os_str), default_value = "/tmp/tracer")]
#[structopt(
short = "o",
long = "output",
parse(from_os_str),
default_value = "/tmp/tracer"
)]
pub output: PathBuf,
}
@@ -126,11 +139,13 @@ pub fn opt_hash(opt: &Opt) -> String {
}
// TODO(wathiede): implement the skips and then the renderer could use json as an input file type.
#[derive(Serialize)]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Scene {
#[serde(skip)]
pub world: Box<dyn Hit>,
//#[serde(skip)]
//pub materials: HashMap<String, Box<dyn Material>>,
#[serde(skip)]
pub camera: Camera,
pub subsamples: usize,
@@ -235,6 +250,7 @@ fn trace_pixel_adaptive(
x_range: Range<f32>,
y_range: Range<f32>,
scene: &Scene,
output: &OutputManager,
) -> (Vec3, usize) {
let w = scene.width as f32;
let h = scene.height as f32;
@@ -249,7 +265,7 @@ fn trace_pixel_adaptive(
&scene.env_map,
);
if depth == 0 {
output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [1., 0., 0.].into());
output.set_pixel(output::ADAPTIVE_DEPTH, x, y, [1., 0., 0.].into());
return (center, rays);
}
// t = top
@@ -291,6 +307,7 @@ fn trace_pixel_adaptive(
x_range.start..x_mid,
y_range.start..y_mid,
scene,
output,
);
let tr = trace_pixel_adaptive(
depth - 1,
@@ -300,6 +317,7 @@ fn trace_pixel_adaptive(
x_mid..x_range.end,
y_range.start..y_mid,
scene,
output,
);
let bl = trace_pixel_adaptive(
depth - 1,
@@ -309,6 +327,7 @@ fn trace_pixel_adaptive(
x_range.start..x_mid,
y_mid..y_range.end,
scene,
output,
);
let br = trace_pixel_adaptive(
depth - 1,
@@ -318,13 +337,14 @@ fn trace_pixel_adaptive(
x_mid..x_range.end,
y_mid..y_range.end,
scene,
output,
);
let pixel = (tl.0 + tr.0 + bl.0 + br.0) / 4.;
let rays = tl.1 + tr.1 + bl.1 + br.1;
(pixel, rays)
} else {
if depth == MAX_ADAPTIVE_DEPTH {
output::set_pixel(output::ADAPTIVE_DEPTH, x, y, [0., 1., 0.].into());
output.set_pixel(output::ADAPTIVE_DEPTH, x, y, [0., 1., 0.].into());
}
(corners, rays)
}
@@ -407,7 +427,7 @@ enum Response {
},
}
fn render_pixel(scene: &Scene, x: usize, y: usize) -> (Vec3, usize) {
fn render_pixel(scene: &Scene, x: usize, y: usize, output: &OutputManager) -> (Vec3, usize) {
let (pixel, rays) = if let Some(threshold) = scene.adaptive_subsampling {
trace_pixel_adaptive(
MAX_ADAPTIVE_DEPTH,
@@ -417,6 +437,7 @@ fn render_pixel(scene: &Scene, x: usize, y: usize) -> (Vec3, usize) {
0.0..1.0,
0.0..1.0,
scene,
output,
)
} else {
let (pixel, rays) = (0..scene.subsamples)
@@ -425,7 +446,7 @@ fn render_pixel(scene: &Scene, x: usize, y: usize) -> (Vec3, usize) {
([0., 0., 0.].into(), 0),
|(p1, r1): (Vec3, usize), (p2, r2): (Vec3, usize)| ((p1 + p2), (r1 + r2)),
);
output::set_pixel_grey(output::RAYS_PER_PIXEL, x, y, rays as f32);
output.set_pixel_grey(output::RAYS_PER_PIXEL, x, y, rays as f32);
(pixel / scene.subsamples as f32, rays)
};
// Gamma correct, use gamma 2 correction, which is 1/gamma where gamma=2 which is 1/2 or
@@ -441,6 +462,7 @@ fn render_worker(
scene: &Scene,
input_chan: Arc<Mutex<Receiver<Request>>>,
output_chan: &SyncSender<Response>,
output: &OutputManager,
) {
loop {
let job = { input_chan.lock().unwrap().recv() };
@@ -455,7 +477,7 @@ fn render_worker(
let batch = false;
if batch {
let (pixels, rays): (Vec<Vec3>, Vec<usize>) = (0..width)
.map(|x| render_pixel(scene, x, y))
.map(|x| render_pixel(scene, x, y, output))
.collect::<Vec<(_, _)>>()
.into_iter()
.unzip();
@@ -472,7 +494,7 @@ fn render_worker(
.expect("failed to send pixel response");
} else {
(0..width).for_each(|x| {
let (pixel, rays) = render_pixel(scene, x, y);
let (pixel, rays) = render_pixel(scene, x, y, output);
output_chan
.send(Response::Pixel {
x,
@@ -486,7 +508,7 @@ fn render_worker(
}
Request::Pixel { x, y } => {
trace!("tid {} x {} y {}", tid, x, y);
let (pixel, rays) = render_pixel(scene, x, y);
let (pixel, rays) = render_pixel(scene, x, y, output);
output_chan
.send(Response::Pixel {
x,
@@ -501,7 +523,17 @@ fn render_worker(
}
}
pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::io::Error> {
/*
lazy_static! {
static ref DEBUGGER: Arc<Mutex<OutputManager>> = Arc::new(Mutex::new(OutputManager::new()));
}
*/
pub fn render(
scene: Scene,
output_dir: &Path,
tev_addr: &Option<String>,
) -> std::result::Result<(), std::io::Error> {
// Default to half the cores to disable hyperthreading.
let num_threads = scene.num_threads.unwrap_or_else(|| num_cpus::get() / 2);
let (pixel_req_tx, pixel_req_rx) = sync_channel(2 * num_threads);
@@ -517,20 +549,23 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
} else {
core_ids
};
let output = output::OutputManager::new(tev_addr)?;
let output = Arc::new(output);
info!("Creating {} render threads", core_ids.len());
output::register_image(
output.register_image(
output::MAIN_IMAGE.to_string(),
(scene.width, scene.height),
output::ImageType::RGB01,
);
if scene.adaptive_subsampling.is_some() {
output::register_image(
output.register_image(
output::ADAPTIVE_DEPTH.to_string(),
(scene.width, scene.height),
output::ImageType::RGB01,
);
}
output::register_image(
output.register_image(
output::RAYS_PER_PIXEL.to_string(),
(scene.width, scene.height),
output::ImageType::GreyNormalized,
@@ -544,9 +579,10 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
let s = sync::Arc::clone(&scene);
let pixel_req_rx = pixel_req_rx.clone();
let pixel_resp_tx = pixel_resp_tx.clone();
let output = sync::Arc::clone(&output);
thread::spawn(move || {
core_affinity::set_for_current(id);
render_worker(i, &s, pixel_req_rx, &pixel_resp_tx);
render_worker(i, &s, pixel_req_rx, &pixel_resp_tx, &output);
})
})
.collect::<Vec<_>>();
@@ -585,12 +621,12 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
match resp {
Response::Pixel { x, y, pixel, rs } => {
current_stat += rs;
output::set_pixel(output::MAIN_IMAGE, x, y, pixel);
output.set_pixel(output::MAIN_IMAGE, x, y, pixel);
}
Response::Line { y, pixels, rs } => {
current_stat += rs;
for (x, pixel) in pixels.iter().enumerate() {
output::set_pixel(output::MAIN_IMAGE, x, y, *pixel);
output.set_pixel(output::MAIN_IMAGE, x, y, *pixel);
}
}
}
@@ -628,5 +664,5 @@ pub fn render(scene: Scene, output_dir: &Path) -> std::result::Result<(), std::i
)
);
output::write_images(&scene, time_diff, output_dir)
output.write_images(&scene, time_diff, output_dir)
}

View File

@@ -2,7 +2,6 @@ use crate::{
aabb::AABB,
hitable::{Hit, HitRecord},
ray::Ray,
vec3::Vec3,
};
#[derive(Debug)]

View File

@@ -52,6 +52,7 @@ pub fn new(opt: &Opt) -> Scene {
height: opt.height,
global_illumination: true,
env_map: Some(EnvMap::new(skybox)),
..Default::default()
}
}

View File

@@ -1,7 +1,6 @@
use std::{
f32::consts::PI,
io::{BufReader, Cursor},
iter::Inspect,
};
use stl::STL;
@@ -13,7 +12,7 @@ use crate::{
hitable::Hit,
hitable_list::HitableList,
kdtree::KDTree,
material::{Dielectric, Lambertian, Metal},
material::{Lambertian, Metal},
renderer::{Opt, Scene},
rotate::RotateY,
scale::Scale,
@@ -53,7 +52,6 @@ pub fn new(opt: &Opt) -> Scene {
let stl_cube = STL::parse(
BufReader::new(Cursor::new(include_bytes!("../../stls/dragon.stl"))),
//BufReader::new(Cursor::new(include_bytes!("../../stls/cube.stl"))),
false,
)
.expect("failed to parse cube");
@@ -89,7 +87,7 @@ pub fn new(opt: &Opt) -> Scene {
// STL Mesh
Box::new(crate::debug_hit::DebugHit::new(RotateY::new(
Translate::new(
Scale::new(BVHTriangles::new(&stl_cube, dragon_material), 250.),
BVHTriangles::new(&stl_cube, dragon_material, 250.),
[0., -10., 0.],
),
180.,

View File

@@ -112,5 +112,6 @@ pub fn new(opt: &Opt) -> Scene {
height: opt.height,
global_illumination: true,
env_map: Some(EnvMap::new(skybox)),
..Default::default()
}
}

View File

@@ -91,7 +91,7 @@ pub fn new(opt: &Opt) -> Scene {
)),
// STL Mesh
Box::new(Translate::new(
BVHTriangles::new(&stl_cube, glass),
BVHTriangles::new(&stl_cube, glass, 1.),
[0., 10., 0.],
)),
//Box::new(BVHTriangles::new(&stl_cube, box_material.clone())),

View File

@@ -3,12 +3,15 @@ name = "tracer"
version = "0.1.0"
authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2021"
default-run = "tracer"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.69"
log = "0.4.17"
renderer = { path = "../renderer" }
stderrlog = "0.4.3"
structopt = "0.2.18"
strum = "0.24.1"
toml = "0.7.2"

View File

@@ -0,0 +1,74 @@
[scene]
width = 768
height = 512
subsamples = 100
[camera]
lookfrom = [0.0, 50.0, 100.0]
lookat = [0.0, 10.0, 0.0]
fov = 45
aperture = 0.0
focus_dist = 10.0
time_min = 0.0
time_max = 1.0
[[materials]]
name = "light1"
type = "isotropic"
texture = [20, 10, 10]
[[materials]]
name = "yellow"
type = "isotropic"
texture = [1, 1, 0]
[[materials]]
name = "magenta"
type = "lambertian"
texture = [1, 0, 1]
[[materials]]
name = "green"
type = "diffuse_light"
texture = [0, 1, 0]
[[materials]]
name = "metal"
type = "metal"
albedo = [1, 1, 1]
fuzzy = 0
[[materials]]
name = "glass"
type = "dielectric"
ref_idx = 1.5
[[hitables]]
type = "sphere"
center = [-30.0, 0.0, 0.0]
radius = 10
material_name = "yellow"
[[hitables]]
type = "sphere"
center = [30.0, 0.0, 0.0]
radius = 10
material_name = "green"
[[hitables]]
type = "sphere"
center = [0.0, -10.0, 0.0]
radius = 10
material_name = "metal"
[[hitables]]
type = "sphere"
center = [0.0, 0.0, -30.0]
radius = 10
material_name = "magenta"
[[hitables]]
type = "stl"
path = "/net/nasx.h.xinu.tv/x/3dprint/stl/stanford_dragon.stl"
scale = 200
material_name = "glass"
#[[hitables]]
#type = "sphere"
#center = [0.0, 50.0, -100.0]
#radius = 10
#material_name = "light1"
[envmap]
path = "/home/wathiede/src/xinu.tv/raytracers/rtiow/renderer/images/52681723945_e1d94d3df9_6k.jpg"

View File

@@ -1,12 +1,16 @@
#![warn(unused_extern_crates)]
use std::{fs, time::Instant};
use anyhow::Result;
#[cfg(feature = "profile")]
use cpuprofiler::PROFILER;
use log::info;
use structopt::StructOpt;
use renderer::renderer::{render, Model, Opt};
use renderer::{
parser::Config,
renderer::{render, Model, Opt},
};
use strum::VariantNames;
#[cfg(not(feature = "profile"))]
@@ -35,7 +39,7 @@ impl MockProfiler {
#[cfg(not(feature = "profile"))]
static PROFILER: MockProfiler = MockProfiler {};
fn main() -> Result<(), std::io::Error> {
fn main() -> Result<()> {
let start_time = Instant::now();
stderrlog::new()
.verbosity(3)
@@ -43,12 +47,28 @@ fn main() -> Result<(), std::io::Error> {
.init()
.unwrap();
let opt = Opt::from_args();
if opt.model.is_none() {
eprintln!("--model should be one of {:?}", Model::VARIANTS);
if opt.model.is_none() && opt.config.is_none() {
eprintln!(
"--config <path> or --model should be one of {:?}",
Model::VARIANTS
);
return Ok(());
}
if opt.model.is_some() && opt.config.is_some() {
eprintln!("only specify one of --config or --model");
return Ok(());
}
info!("{:#?}", opt);
let scene = opt.model.as_ref().unwrap().scene(&opt);
let scene = match (&opt.model, &opt.config) {
(Some(model), None) => model.scene(&opt),
(None, Some(config)) => {
let s = std::fs::read_to_string(config)?;
let cfg: Config = toml::from_str(&s)?;
println!("{:#?}", cfg);
cfg.try_into()?
}
_ => unreachable!(),
};
fs::create_dir_all(&opt.output)?;
if opt.pprof.is_some() && !cfg!(feature = "profile") {
panic!("profiling disabled at compile time, but -pprof specified");
@@ -60,7 +80,7 @@ fn main() -> Result<(), std::io::Error> {
.start(pprof_path.to_str().unwrap().as_bytes())
.unwrap();
}
let res = render(scene, &opt.output);
let res = render(scene, &opt.output, &opt.tev_addr);
if let Some(pprof_path) = &opt.pprof {
info!("Saving pprof to {}", pprof_path.to_string_lossy());
PROFILER.lock().unwrap().stop().unwrap();
@@ -68,5 +88,5 @@ fn main() -> Result<(), std::io::Error> {
let time_diff = Instant::now() - start_time;
info!("Total runtime {} seconds", time_diff.as_secs_f32());
res
Ok(res?)
}