Compare commits

..

7 Commits

17 changed files with 347 additions and 21 deletions

57
rtiow/Cargo.lock generated
View File

@ -267,7 +267,7 @@ dependencies = [
"num-traits",
"serde",
"serde_derive",
"toml",
"toml 0.4.10",
]
[[package]]
@ -1892,6 +1892,15 @@ dependencies = [
"version_check 0.1.5",
]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "num-derive"
version = "0.2.5"
@ -2632,6 +2641,7 @@ dependencies = [
"structopt 0.2.18",
"strum",
"strum_macros",
"thiserror",
"vec3",
]
@ -2784,6 +2794,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.5.5"
@ -3409,6 +3428,40 @@ dependencies = [
"serde",
]
[[package]]
name = "toml"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5"
dependencies = [
"indexmap",
"nom8",
"serde",
"serde_spanned",
"toml_datetime",
]
[[package]]
name = "tower-service"
version = "0.1.0"
@ -3422,11 +3475,13 @@ dependencies = [
name = "tracer"
version = "0.1.0"
dependencies = [
"anyhow",
"log 0.4.17",
"renderer",
"stderrlog",
"structopt 0.2.18",
"strum",
"toml 0.7.2",
]
[[package]]

View File

@ -10,3 +10,5 @@ members = [
[profile.release]
debug = true
[profile.dev]
opt-level = 3

View File

@ -29,6 +29,7 @@ vec3 = {path = "../vec3"}
stl = {path = "../../../stl"}
strum = { version = "0.24.1", features = ["derive"] }
strum_macros = "0.24.3"
thiserror = "1.0.38"
#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

@ -0,0 +1,192 @@
use crate::{
bvh_triangles::BVHTriangles,
camera::Camera,
cuboid::Cuboid,
hitable::Hit,
hitable_list::HitableList,
material::{Lambertian, Material, Metal},
renderer::Scene,
sphere::Sphere,
texture::{EnvMap, Texture},
};
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)),
};
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 },
}
#[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,7 +23,7 @@ use crate::{
camera::Camera,
hitable::Hit,
human,
material::Lambertian,
material::{Lambertian, Material},
output,
ray::Ray,
scenes,
@ -99,6 +100,9 @@ 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>,
@ -126,11 +130,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,

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

@ -7,8 +7,10 @@ edition = "2021"
# 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,49 @@
[scene]
width = 768
height = 768
#subsamples = 1000
[camera]
lookfrom = [0.0, 30.0, -100.0]
lookat = [0.0, 0.0, 0.0]
fov = 45
aperture = 0.0
focus_dist = 10.0
time_min = 0.0
time_max = 1.0
[[materials]]
name = "green"
type = "metal"
albedo = [0, 1, 0]
fuzzy = 1.5
[[materials]]
name = "blue"
type = "metal"
albedo = [0, 0, 1]
fuzzy = 1.5
[[materials]]
name = "red"
type = "metal"
albedo = [1, 0, 0]
fuzzy = 1.5
[[hitables]]
type = "cuboid"
min = [-10.0, -10.0, -10.0]
max = [10.0, 10.0, 10.0]
material_name = "green"
[[hitables]]
type = "sphere"
center = [30.0, 0.0, 0.0]
radius = 10
material_name = "blue"
[[hitables]]
type = "stl"
path = "/net/nasx.h.xinu.tv/x/3dprint/stl/stanford_dragon.stl"
scale = 200
material_name = "red"
[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");
@ -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?)
}