Skip to content

Commit

Permalink
refactor: prepare for multiple BVH construction algorithm support
Browse files Browse the repository at this point in the history
  • Loading branch information
Walther committed Jul 2, 2024
1 parent 9f5a4c4 commit 8250498
Show file tree
Hide file tree
Showing 20 changed files with 186 additions and 147 deletions.
3 changes: 2 additions & 1 deletion clovers-cli/src/draw_cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub fn draw(
max_depth: _,
mode,
sampler,
bvh: _,
} = *render_options;
let bar = progress_bar(height, quiet);

Expand All @@ -59,7 +60,7 @@ pub fn draw(
for index in 0..width {
let index = index + row_index * width;
let pixel = match mode {
RenderMode::Default => {
RenderMode::PathTracing => {
render_pixel(scene, render_options, index, &mut rng, &mut *sampler)
}
RenderMode::NormalMap => {
Expand Down
8 changes: 6 additions & 2 deletions clovers-cli/src/json_scene.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use clovers::scenes::{self, Scene, SceneFile};
use clovers::bvh::BvhAlgorithm;
use clovers::scenes::Scene;
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::path::Path;

use tracing::info;

use crate::scenefile::SceneFile;

pub fn initialize<'scene>(
path: &Path,
bvh_algorithm: BvhAlgorithm,
width: u32,
height: u32,
) -> Result<Scene<'scene>, Box<dyn Error>> {
Expand All @@ -17,7 +21,7 @@ pub fn initialize<'scene>(
info!("Parsing the scene file");
let scene_file: SceneFile = serde_json::from_str(&contents)?;
info!("Initializing the scene");
let scene: Scene = scenes::initialize(scene_file, width, height);
let scene: Scene = SceneFile::initialize(scene_file, bvh_algorithm, width, height);
info!("Count of nodes in the BVH tree: {}", scene.bvh_root.count());
Ok(scene)
}
1 change: 1 addition & 0 deletions clovers-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod json_scene;
pub mod normals;
pub mod render;
pub mod sampler;
pub mod scenefile;

// TODO: move this into a better place - but keep rustc happy with the imports
/// Global options
Expand Down
2 changes: 2 additions & 0 deletions clovers-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ mod sampler;
#[doc(hidden)]
mod validate;
use validate::{validate, ValidateParams};
#[doc(hidden)]
pub mod scenefile;

/// clovers 🍀 path tracing renderer
#[derive(Parser)]
Expand Down
29 changes: 24 additions & 5 deletions clovers-cli/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct RenderOptions {
/// Input filename / location
#[arg()]
pub input: String,
/// Output filename / location. [default: ./renders/unix_timestamp.png]
/// Output filename / location. Defaults to ./renders/unix_timestamp.png
#[arg(short, long)]
pub output: Option<String>,
/// Width of the image in pixels.
Expand All @@ -36,21 +36,34 @@ pub struct RenderOptions {
#[arg(short = 'd', long, default_value = "64")]
pub max_depth: u32,
/// Rendering mode.
#[arg(short = 'm', long, default_value = "default")]
#[arg(short = 'm', long, default_value = "path-tracing")]
pub mode: RenderMode,
/// Sampler to use for rendering.
#[arg(long, default_value = "random")]
pub sampler: Sampler,
/// BVH construction algorithm.
#[arg(long, default_value = "longest-axis")]
pub bvh: BvhAlgorithm,
}

#[derive(Copy, Clone, Debug, PartialEq, ValueEnum)]
pub enum RenderMode {
Default,
/// Full path tracing, the default
PathTracing,
/// Surface normals of the first hit
NormalMap,
/// Debug view for BVH ray hit count
BvhTestCount,
/// Debug view for primitive object ray hit count
PrimitiveTestCount,
}

#[derive(Copy, Clone, Debug, PartialEq, ValueEnum)]
pub enum BvhAlgorithm {
/// Split based on the longest axis of the current AABB
LongestAxis,
}

// CLI usage somehow not detected
#[allow(dead_code)]
pub(crate) fn render(
Expand All @@ -67,6 +80,7 @@ pub(crate) fn render(
max_depth,
mode,
sampler,
bvh,
} = render_options;

if debug {
Expand Down Expand Up @@ -101,13 +115,18 @@ pub(crate) fn render(
panic!("the blue sampler only supports the following sample-per-pixel counts: [1, 2, 4, 8, 16, 32, 64, 128, 256]");
}

// TODO: improve ergonomics?
let bvh_algorithm: clovers::bvh::BvhAlgorithm = match bvh {
BvhAlgorithm::LongestAxis => clovers::bvh::BvhAlgorithm::LongestAxis,
};

let threads = std::thread::available_parallelism()?;

info!("Reading the scene file");
let path = Path::new(&input);
let scene = match path.extension() {
Some(ext) => match &ext.to_str() {
Some("json") => initialize(path, width, height),
Some("json") => initialize(path, bvh_algorithm, width, height),
_ => panic!("Unknown file type"),
},
None => panic!("Unknown file type"),
Expand Down Expand Up @@ -148,7 +167,7 @@ pub(crate) fn render(
"Comment\0Rendered with the clovers path tracing engine. Scene file {input} rendered using the {mode:?} rendering mode at {width}x{height} resolution"
);
let details = match mode {
RenderMode::Default => {
RenderMode::PathTracing => {
format!(", {samples} samples per pixel, {max_depth} max ray bounce depth.")
}
_ => ".".to_owned(),
Expand Down
4 changes: 2 additions & 2 deletions clovers-cli/src/sampler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ pub struct Randomness {
/// Enum of the supported samplers.
#[derive(Copy, Clone, Debug, PartialEq, ValueEnum)]
pub enum Sampler {
/// Blue noise based sampler, see [BlueSampler](blue::BlueSampler)
/// Blue noise based sampler
Blue,
/// Random number generator based sampler, see [RandomSampler](random::RandomSampler)
/// Random number generator based sampler
Random,
}

Expand Down
105 changes: 105 additions & 0 deletions clovers-cli/src/scenefile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use std::time::Instant;

use std::boxed::Box;

use clovers::{
bvh::{BVHNode, BvhAlgorithm},
camera::{Camera, CameraInit},
hitable::Hitable,
materials::SharedMaterial,
objects::{object_to_hitable, Object},
scenes::Scene,
Float, Vec,
};

use palette::Srgb;
use tracing::info;

// TODO: better naming
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
/// A serialized representation of a [Scene].
pub struct SceneFile {
time_0: Float,
time_1: Float,
background_color: Srgb,
camera: CameraInit,
objects: Vec<Object>,
#[serde(default)]
materials: Vec<SharedMaterial>,
}

impl SceneFile {
/// Initializes a new [Scene] instance by parsing the contents of a [`SceneFile`] structure and then using those details to construct the [Scene].
#[must_use]
pub fn initialize<'scene>(
scene_file: SceneFile,
bvh_algorithm: BvhAlgorithm,
width: u32,
height: u32,
) -> Scene<'scene> {
let time_0 = scene_file.time_0;
let time_1 = scene_file.time_1;
let background_color = scene_file.background_color;

#[allow(clippy::cast_precision_loss)]
let camera = Camera::new(
scene_file.camera.look_from,
scene_file.camera.look_at,
scene_file.camera.up,
scene_file.camera.vertical_fov,
width as Float / height as Float,
scene_file.camera.aperture,
scene_file.camera.focus_distance,
time_0,
time_1,
);
let mut materials = scene_file.materials;
materials.push(SharedMaterial::default());
let materials = Box::leak(Box::new(materials));

info!("Creating a flattened list from the objects");
let mut hitables: Vec<Hitable> = Vec::new();
let mut priority_hitables: Vec<Hitable> = Vec::new();

// TODO: this isn't the greatest ergonomics, but it gets the job done for now
for object in scene_file.objects {
if match &object {
Object::Boxy(i) => i.priority,
Object::ConstantMedium(i) => i.priority,
Object::MovingSphere(i) => i.priority,
Object::ObjectList(i) => i.priority,
Object::Quad(i) => i.priority,
Object::RotateY(i) => i.priority,
Object::Sphere(i) => i.priority,
Object::STL(i) => i.priority,
Object::GLTF(i) => i.priority,
Object::Translate(i) => i.priority,
Object::Triangle(i) => i.priority,
} {
let hitable = object_to_hitable(object, materials);
hitables.push(hitable.clone());
priority_hitables.push(hitable);
} else {
let hitable = object_to_hitable(object, materials);
hitables.push(hitable.clone());
}
}
info!("All objects parsed");

info!("BVH tree build starting");
let start = Instant::now();
let bvh_root = BVHNode::from_list(bvh_algorithm, hitables);
let end = Instant::now();
let duration = (end - start).as_millis();
info!("BVH tree build done in {duration} ms");

let mis_bvh_root = Hitable::BVHNode(BVHNode::from_list(bvh_algorithm, priority_hitables));

Scene {
camera,
bvh_root,
mis_bvh_root,
background_color,
}
}
}
3 changes: 2 additions & 1 deletion clovers-cli/src/validate.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use clap::Args;
use clovers::bvh::BvhAlgorithm;
use std::{error::Error, path::Path};

use crate::json_scene;
Expand All @@ -18,7 +19,7 @@ pub(crate) fn validate(params: ValidateParams) -> Result<(), Box<dyn Error>> {
let path = Path::new(&input);
let scene = match path.extension() {
Some(ext) => match &ext.to_str() {
Some("json") => json_scene::initialize(path, 1, 1),
Some("json") => json_scene::initialize(path, BvhAlgorithm::LongestAxis, 1, 1),
_ => panic!("Unknown file type"),
},
None => panic!("Unknown file type"),
Expand Down
14 changes: 11 additions & 3 deletions clovers/src/bvh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,20 @@ pub struct BVHNode<'scene> {
pub bounding_box: AABB,
}

/// The choice of algorithms used for constructing the Bounding Volume Hierarchy tree
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum BvhAlgorithm {
/// Splitting method based on the longest axis of the current `AABB`
LongestAxis,
}

impl<'scene> BVHNode<'scene> {
/// Create a new `BVHNode` tree from a given list of [Object](crate::objects::Object)s
#[must_use]
pub fn from_list(hitables: Vec<Hitable>) -> BVHNode {
// TODO: more alternative build algorithms
build::longest_axis_midpoint(hitables)
pub fn from_list(bvh_algorithm: BvhAlgorithm, hitables: Vec<Hitable>) -> BVHNode {
match bvh_algorithm {
BvhAlgorithm::LongestAxis => build::longest_axis_midpoint(hitables),
}
}

#[must_use]
Expand Down
14 changes: 11 additions & 3 deletions clovers/src/bvh/build/longest_axis_midpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use core::cmp::Ordering;

use crate::{
aabb::AABB,
bvh::BVHNode,
bvh::{BVHNode, BvhAlgorithm},
hitable::{Empty, Hitable, HitableTrait},
};

pub fn build(mut hitables: Vec<Hitable>) -> BVHNode {
let bvh_algorithm = BvhAlgorithm::LongestAxis;

// Initialize two child nodes
let left: Box<Hitable>;
let right: Box<Hitable>;
Expand Down Expand Up @@ -77,8 +79,14 @@ pub fn build(mut hitables: Vec<Hitable>) -> BVHNode {
// Split the vector; divide and conquer
let mid = object_span / 2;
let hitables_right = hitables.split_off(mid);
left = Box::new(Hitable::BVHNode(BVHNode::from_list(hitables)));
right = Box::new(Hitable::BVHNode(BVHNode::from_list(hitables_right)));
left = Box::new(Hitable::BVHNode(BVHNode::from_list(
bvh_algorithm,
hitables,
)));
right = Box::new(Hitable::BVHNode(BVHNode::from_list(
bvh_algorithm,
hitables_right,
)));
}

let box_left = left.bounding_box();
Expand Down
5 changes: 3 additions & 2 deletions clovers/src/objects.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Various literal objects and meta-object utilities for creating content in [Scenes](crate::scenes::Scene).

use crate::{
bvh::BVHNode,
bvh::{BVHNode, BvhAlgorithm},
hitable::Hitable,
materials::{Material, MaterialInit, SharedMaterial},
Box,
Expand Down Expand Up @@ -108,7 +108,8 @@ pub fn object_to_hitable(obj: Object, materials: &[SharedMaterial]) -> Hitable<'
.iter()
.map(|object| -> Hitable { object_to_hitable(object.clone(), materials) })
.collect();
let bvh = BVHNode::from_list(objects);
// TODO: get rid of this
let bvh: BVHNode = BVHNode::from_list(BvhAlgorithm::LongestAxis, objects);
Hitable::BVHNode(bvh)
}
Object::Quad(x) => {
Expand Down
2 changes: 1 addition & 1 deletion clovers/src/objects/boxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
};
use rand::rngs::SmallRng;

/// `BoxyInit` structure describes the necessary data for constructing a [Boxy]. Used with [serde] when importing [`SceneFile`](crate::scenes::SceneFile)s.
/// `BoxyInit` structure describes the necessary data for constructing a [Boxy].
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))]
pub struct BoxyInit {
Expand Down
2 changes: 1 addition & 1 deletion clovers/src/objects/constant_medium.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use super::Object;

#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))]
/// `ConstantMediumInit` structure describes the necessary data for constructing a [`ConstantMedium`]. Used with [serde] when importing [`SceneFiles`](crate::scenes::SceneFile).
/// `ConstantMediumInit` structure describes the necessary data for constructing a [`ConstantMedium`].
pub struct ConstantMediumInit {
/// Used for multiple importance sampling
#[cfg_attr(feature = "serde-derive", serde(default))]
Expand Down
8 changes: 5 additions & 3 deletions clovers/src/objects/gltf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tracing::debug;

use crate::{
aabb::AABB,
bvh::BVHNode,
bvh::{BVHNode, BvhAlgorithm},
hitable::{get_orientation, Hitable, HitableTrait},
interval::Interval,
materials::gltf::GLTFMaterial,
Expand Down Expand Up @@ -69,7 +69,8 @@ impl<'scene> GLTF<'scene> {
/// Create a new STL object with the given initialization parameters.
pub fn new(gltf_init: GLTFInit) -> Self {
let triangles: Vec<Hitable> = gltf_init.into();
let bvhnode = BVHNode::from_list(triangles);
// TODO: probably move or remove this?
let bvhnode = BVHNode::from_list(BvhAlgorithm::LongestAxis, triangles);
// TODO: remove unwrap
let aabb = bvhnode.bounding_box().unwrap().clone();

Expand Down Expand Up @@ -211,7 +212,8 @@ fn parse_mesh<'scene>(
}
}

let bvh: BVHNode = BVHNode::from_list(trianglelist);
// TODO: get rid of this
let bvh: BVHNode = BVHNode::from_list(BvhAlgorithm::LongestAxis, trianglelist);
objects.push(Hitable::BVHNode(bvh));
}
_ => unimplemented!(),
Expand Down
Loading

0 comments on commit 8250498

Please sign in to comment.