Skip to content

Commit

Permalink
Merge pull request #208 from Walther/2024-06-bvh-improvements
Browse files Browse the repository at this point in the history
Surface Area Heuristic for Bounding Value Hierarchy structures
  • Loading branch information
Walther authored Jul 28, 2024
2 parents 91c30de + eed638c commit 34b838f
Show file tree
Hide file tree
Showing 57 changed files with 1,755 additions and 1,100 deletions.
12 changes: 4 additions & 8 deletions clovers-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,20 @@ clovers = { path = "../clovers", features = [

# External
blue-noise-sampler = "0.1.0"
clap = { version = "4.5.6", features = ["std", "derive"] }
clap = { version = "4.5.10", features = ["std", "derive"] }
human_format = "1.1.0"
humantime = "2.1.0"
image = { version = "0.25.1", features = ["png"], default-features = false }
image = { version = "0.25.2", features = ["png"], default-features = false }
img-parts = "0.3.0"
indicatif = { version = "0.17.8", features = [
"rayon",
], default-features = false }
nalgebra = { version = "0.32.5" }
nalgebra = { version = "0.33.0" }
palette = { version = "0.7.6", features = ["serializing"] }
paste = { version = "1.0.15" }
rand = { version = "0.8.5", features = ["small_rng"], default-features = false }
rayon = "1.10.0"
serde = { version = "1.0.203", features = ["derive"], default-features = false }
serde = { version = "1.0.204", features = ["derive"], default-features = false }
serde_json = { version = "1.0", features = ["alloc"], default-features = false }
time = { version = "0.3.36", default-features = false }
tracing = "0.1.40"
Expand All @@ -45,7 +45,3 @@ tracing-subscriber = { version = "0.3.18", features = ["time"] }
[dev-dependencies]
divan = "0.1.14"
proptest = "1"

[[bench]]
name = "draw_cpu"
harness = false
38 changes: 0 additions & 38 deletions clovers-cli/benches/draw_cpu.rs

This file was deleted.

8 changes: 3 additions & 5 deletions clovers-cli/src/colorize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub fn colorize(
// Send the ray to the scene, and see if it hits anything.
// distance_min is set to an epsilon to avoid "shadow acne" that can happen when set to zero
let Some(hit_record) = scene
.hitables
.bvh_root
.hit(ray, EPSILON_SHADOW_ACNE, Float::MAX, rng)
else {
// If the ray hits nothing, early return the background color.
Expand Down Expand Up @@ -88,10 +88,8 @@ pub fn colorize(
// Multiple Importance Sampling:

// Create a new PDF object from the priority hitables of the scene, given the current hit_record position
let light_ptr = PDF::HitablePDF(HitablePDF::new(
&scene.priority_hitables,
hit_record.position,
));
let light_ptr =
PDF::HitablePDF(HitablePDF::new(&scene.mis_bvh_root, hit_record.position));

// Create a mixture PDF from the above + the PDF from the scatter_record
let mixture_pdf = MixturePDF::new(light_ptr, scatter_record.pdf_ptr);
Expand Down
61 changes: 61 additions & 0 deletions clovers-cli/src/debug_visualizations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! Alternative rendering methods for debug visualization purposes.

use clovers::{ray::Ray, scenes::Scene, Float, EPSILON_SHADOW_ACNE};
use palette::LinSrgb;
use rand::rngs::SmallRng;

/// Visualizes the BVH traversal count - how many BVH nodes needed to be tested for intersection?
#[must_use]
pub fn bvh_testcount(ray: &Ray, scene: &Scene, rng: &mut SmallRng) -> LinSrgb {
let mut depth = 0;
scene
.bvh_root
.testcount(&mut depth, ray, EPSILON_SHADOW_ACNE, Float::MAX, rng);

bvh_testcount_to_color(depth)
}

#[must_use]
pub fn bvh_testcount_to_color(depth: usize) -> LinSrgb {
match depth {
// under 256, grayscale
0..=255 => {
let depth = depth as Float / 255.0;
LinSrgb::new(depth, depth, depth)
}
// more than 256, yellow
256..=511 => LinSrgb::new(1.0, 1.0, 0.0),
// more than 512, orange
512..=1023 => LinSrgb::new(1.0, 0.5, 0.0),
// more than 1024, red
1024.. => LinSrgb::new(1.0, 0.0, 0.0),
}
}

/// Visualizes the primitive traversal count - how many primitives needed to be tested for intersection?
#[must_use]
pub fn primitive_testcount(ray: &Ray, scene: &Scene, rng: &mut SmallRng) -> LinSrgb {
let mut depth = 0;
scene
.bvh_root
.primitive_testcount(&mut depth, ray, EPSILON_SHADOW_ACNE, Float::MAX, rng);

primitive_testcount_to_color(depth)
}

#[must_use]
pub fn primitive_testcount_to_color(depth: usize) -> LinSrgb {
match depth {
// under 256, grayscale
0..=255 => {
let depth = depth as Float / 255.0;
LinSrgb::new(depth, depth, depth)
}
// more than 256, yellow
256..=511 => LinSrgb::new(1.0, 1.0, 0.0),
// more than 512, orange
512..=1023 => LinSrgb::new(1.0, 0.5, 0.0),
// more than 1024, red
1024.. => LinSrgb::new(1.0, 0.0, 0.0),
}
}
119 changes: 102 additions & 17 deletions clovers-cli/src/draw_cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use clovers::wavelength::random_wavelength;
use clovers::Vec2;
use clovers::{ray::Ray, scenes::Scene, Float, RenderOpts};
use clovers::{ray::Ray, scenes::Scene, Float};
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
use palette::chromatic_adaptation::AdaptInto;
use palette::convert::IntoColorUnclamped;
Expand All @@ -13,23 +13,44 @@ use rand::{Rng, SeedableRng};
use rayon::prelude::*;

use crate::colorize::colorize;
use crate::debug_visualizations::{bvh_testcount, primitive_testcount};
use crate::normals::normal_map;
use crate::render::{RenderMode, RenderOptions};
use crate::sampler::blue::BlueSampler;
use crate::sampler::random::RandomSampler;
use crate::sampler::{Randomness, Sampler, SamplerTrait};
use crate::GlobalOptions;

/// The main drawing function, returns a `Vec<Srgb>` as a pixelbuffer.
pub fn draw(opts: RenderOpts, scene: &Scene, sampler: Sampler) -> Vec<Srgb<u8>> {
let width = opts.width as usize;
let height = opts.height as usize;
let bar = progress_bar(&opts);
pub fn draw(
global_options: &GlobalOptions,
render_options: &RenderOptions,
scene: &Scene,
_sampler: Sampler,
) -> Vec<Srgb<u8>> {
let GlobalOptions { debug: _, quiet } = *global_options;
let RenderOptions {
input: _,
output: _,
width,
height,
samples,
max_depth: _,
mode,
sampler,
bvh: _,
} = *render_options;
let bar = progress_bar(height, quiet);

let height = height as usize;
let width = width as usize;

let pixelbuffer: Vec<Srgb<u8>> = (0..height)
.into_par_iter()
.map(|row_index| {
let mut sampler_rng = SmallRng::from_entropy();
let mut sampler: Box<dyn SamplerTrait> = match sampler {
Sampler::Blue => Box::new(BlueSampler::new(&opts)),
Sampler::Blue => Box::new(BlueSampler::new(samples)),
Sampler::Random => Box::new(RandomSampler::new(&mut sampler_rng)),
};

Expand All @@ -38,11 +59,29 @@ pub fn draw(opts: RenderOpts, scene: &Scene, sampler: Sampler) -> Vec<Srgb<u8>>

for index in 0..width {
let index = index + row_index * width;
if opts.normalmap {
row.push(render_pixel_normalmap(scene, &opts, index, &mut rng));
} else {
row.push(render_pixel(scene, &opts, index, &mut rng, &mut *sampler));
}
let pixel = match mode {
RenderMode::PathTracing => {
render_pixel(scene, render_options, index, &mut rng, &mut *sampler)
}
RenderMode::NormalMap => {
render_pixel_normalmap(scene, render_options, index, &mut rng)
}
RenderMode::BvhTestCount => render_pixel_bvhtestcount(
scene,
render_options,
index,
&mut rng,
&mut *sampler,
),
RenderMode::PrimitiveTestCount => render_pixel_primitivetestcount(
scene,
render_options,
index,
&mut rng,
&mut *sampler,
),
};
row.push(pixel);
}
bar.inc(1);
row
Expand All @@ -56,7 +95,7 @@ pub fn draw(opts: RenderOpts, scene: &Scene, sampler: Sampler) -> Vec<Srgb<u8>>
// Render a single pixel, including possible multisampling
fn render_pixel(
scene: &Scene,
opts: &RenderOpts,
opts: &RenderOptions,
index: usize,
rng: &mut SmallRng,
sampler: &mut dyn SamplerTrait,
Expand Down Expand Up @@ -95,7 +134,7 @@ fn render_pixel(
// Render a single pixel in normalmap mode
fn render_pixel_normalmap(
scene: &Scene,
opts: &RenderOpts,
opts: &RenderOptions,
index: usize,
rng: &mut SmallRng,
) -> Srgb<u8> {
Expand All @@ -115,17 +154,63 @@ fn render_pixel_normalmap(
color
}

fn index_to_params(opts: &RenderOpts, index: usize) -> (Float, Float, Float, Float) {
// Render a single pixel in bvh test count visualization mode
fn render_pixel_bvhtestcount(
scene: &Scene,
render_options: &RenderOptions,
index: usize,
rng: &mut SmallRng,
_sampler: &mut dyn SamplerTrait,
) -> Srgb<u8> {
let (x, y, width, height) = index_to_params(render_options, index);
let pixel_location = Vec2::new(x / width, y / height);
let lens_offset = Vec2::new(0.0, 0.0);
let wavelength = random_wavelength(rng);
let time = rng.gen();
let ray: Ray = scene
.camera
.get_ray(pixel_location, lens_offset, time, wavelength);

let color: LinSrgb = { bvh_testcount(&ray, scene, rng) };
let color: Srgb = color.into_color_unclamped();
let color: Srgb<u8> = color.into_format();
color
}

// Render a single pixel in primitive test count visualization mode
fn render_pixel_primitivetestcount(
scene: &Scene,
render_options: &RenderOptions,
index: usize,
rng: &mut SmallRng,
_sampler: &mut dyn SamplerTrait,
) -> Srgb<u8> {
let (x, y, width, height) = index_to_params(render_options, index);
let pixel_location = Vec2::new(x / width, y / height);
let lens_offset = Vec2::new(0.0, 0.0);
let wavelength = random_wavelength(rng);
let time = rng.gen();
let ray: Ray = scene
.camera
.get_ray(pixel_location, lens_offset, time, wavelength);

let color: LinSrgb = { primitive_testcount(&ray, scene, rng) };
let color: Srgb = color.into_color_unclamped();
let color: Srgb<u8> = color.into_format();
color
}

fn index_to_params(opts: &RenderOptions, index: usize) -> (Float, Float, Float, Float) {
let x = (index % (opts.width as usize)) as Float;
let y = (index / (opts.width as usize)) as Float;
let width = opts.width as Float;
let height = opts.height as Float;
(x, y, width, height)
}

fn progress_bar(opts: &RenderOpts) -> ProgressBar {
let bar = ProgressBar::new(opts.height as u64);
if opts.quiet {
fn progress_bar(height: u32, quiet: bool) -> ProgressBar {
let bar = ProgressBar::new(height as u64);
if quiet {
bar.set_draw_target(ProgressDrawTarget::hidden())
} else {
bar.set_style(ProgressStyle::default_bar().template(
Expand Down
12 changes: 8 additions & 4 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;

pub(crate) fn initialize<'scene>(
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(crate) 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);
info!("Count of nodes in the BVH tree: {}", scene.hitables.count());
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)
}
18 changes: 18 additions & 0 deletions clovers-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
//! Runtime functions of the `clovers` renderer.

use clap::Args;

pub mod colorize;
pub mod debug_visualizations;
pub mod draw_cpu;
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
#[derive(Args, Debug)]
pub struct GlobalOptions {
/// Enable some debug logging
#[clap(long)]
pub debug: bool,
/// Suppress most of the text output
#[clap(short, long)]
pub quiet: bool,
}
Loading

0 comments on commit 34b838f

Please sign in to comment.