Initial material visualizer
This commit is contained in:
parent
7c160731e7
commit
7e88ebbfc1
6 changed files with 584 additions and 7 deletions
11
ray-tracing-material-visualizer/Cargo.toml
Normal file
11
ray-tracing-material-visualizer/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "ray-tracing-material-validator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
plotters = "0.3.7"
|
||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||
ray-tracing-core = { path = "../ray-tracing-core" }
|
||||
ray-tracing-material = { path = "../ray-tracing-material" }
|
||||
rayon = "1.10.0"
|
||||
13
ray-tracing-material-visualizer/src/bin/plot.rs
Normal file
13
ray-tracing-material-visualizer/src/bin/plot.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
use ray_tracing_core::color::Color;
|
||||
use ray_tracing_material::{diffuse::DiffuseMaterial, mirror::Mirror};
|
||||
use ray_tracing_material_validator::generate_chart;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let m = Mirror::new(Color::new(1.0, 1.0, 1.0));
|
||||
generate_chart("mirror.png", &m, 2)?;
|
||||
|
||||
let m = DiffuseMaterial::new(Color::new(1.0, 1.0, 1.0));
|
||||
generate_chart("diffuse.png", &m, 100)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
220
ray-tracing-material-visualizer/src/lib.rs
Normal file
220
ray-tracing-material-visualizer/src/lib.rs
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
use std::ops::RangeBounds;
|
||||
|
||||
use plotters::{coord::Shift, prelude::*, style::RelativeSize};
|
||||
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||
use ray_tracing_core::prelude::*;
|
||||
use rayon::prelude::*;
|
||||
|
||||
pub fn generate_chart<M: Material<SmallRng>>(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
m: &M,
|
||||
samples_per_pixel: usize,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let root = BitMapBackend::new(&path, (800, 800)).into_drawing_area();
|
||||
|
||||
plot_material_sample(&root, m, samples_per_pixel)?;
|
||||
|
||||
root.present()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn plot_material_sample<DB: DrawingBackend, M: Material<SmallRng>>(
|
||||
root: &DrawingArea<DB, Shift>,
|
||||
m: &M,
|
||||
samples_per_pixel: usize,
|
||||
) -> Result<(), Box<dyn std::error::Error>>
|
||||
where
|
||||
DB::ErrorType: 'static,
|
||||
{
|
||||
root.fill(&WHITE)?;
|
||||
|
||||
let (upper, lower) = root.split_horizontally(RelativeSize::Width(0.5));
|
||||
|
||||
let mut upper_chart = ChartBuilder::on(&upper)
|
||||
.margin(5)
|
||||
.x_label_area_size(30)
|
||||
.top_x_label_area_size(30)
|
||||
.y_label_area_size(30)
|
||||
.right_y_label_area_size(30)
|
||||
.caption("Upper Hemisphere", ("sans-serif", 20))
|
||||
.build_cartesian_2d(-1.0..1.0, -1.0..1.0)?;
|
||||
|
||||
upper_chart
|
||||
.configure_mesh()
|
||||
.disable_x_mesh()
|
||||
.disable_y_mesh()
|
||||
.draw()?;
|
||||
|
||||
let upper_area = upper_chart.plotting_area();
|
||||
|
||||
let mut lower_chart = ChartBuilder::on(&lower)
|
||||
.margin(5)
|
||||
.x_label_area_size(30)
|
||||
.top_x_label_area_size(30)
|
||||
.y_label_area_size(30)
|
||||
.right_y_label_area_size(30)
|
||||
.caption("Lower Hemisphere", ("sans-serif", 20))
|
||||
.build_cartesian_2d(-1.0..1.0, -1.0..1.0)?;
|
||||
|
||||
lower_chart
|
||||
.configure_mesh()
|
||||
.disable_x_mesh()
|
||||
.disable_y_mesh()
|
||||
.draw()?;
|
||||
|
||||
let lower_area = lower_chart.plotting_area();
|
||||
|
||||
let (xrange, yrange) = upper_area.get_pixel_range();
|
||||
let width = (xrange.end - xrange.start) as usize - 1;
|
||||
let height = (yrange.end - yrange.start) as usize - 1;
|
||||
|
||||
let histogram = create_historgram(
|
||||
m,
|
||||
width,
|
||||
height,
|
||||
samples_per_pixel * width * height,
|
||||
Dir3::new(1.0, 1.0, 0.0).normalize(),
|
||||
0,
|
||||
);
|
||||
|
||||
let map = ViridisRGB;
|
||||
|
||||
let max = histogram.max();
|
||||
|
||||
for (dir, f) in histogram.iter() {
|
||||
let color = map.get_color_normalized(f, 0.0, max);
|
||||
if dir.y() < 0.0 {
|
||||
lower_area.draw_pixel((dir.x() as f64, dir.z() as f64), &color)?;
|
||||
} else {
|
||||
upper_area.draw_pixel((dir.x() as f64, dir.z() as f64), &color)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Histogram {
|
||||
data: Vec<f32>,
|
||||
inserted: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
max: f32,
|
||||
}
|
||||
|
||||
impl Histogram {
|
||||
fn new(width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
data: vec![0.0; 2 * width * height],
|
||||
inserted: 0,
|
||||
width,
|
||||
height,
|
||||
max: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn discretize(&self, p: Dir3) -> usize {
|
||||
(((p.x() + 1.0) * 0.5 * self.width as f32).floor() as usize)
|
||||
+ (((p.z() + 1.0) * 0.5 * self.height as f32).floor() as usize) * self.width
|
||||
+ if p.y() < 0.0 {
|
||||
2 * self.width * self.height
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&mut self, pos: Dir3, value: f32) {
|
||||
let index = self.discretize(pos);
|
||||
self.data[index] += value;
|
||||
self.max = f32::max(self.max, self.data[index]);
|
||||
self.inserted += 1;
|
||||
}
|
||||
|
||||
fn max(&self) -> f32 {
|
||||
self.max / (self.inserted as f32)
|
||||
}
|
||||
|
||||
fn iter(&self) -> impl Iterator<Item = (Dir3, f32)> + use<'_> {
|
||||
let m = 1f32 / self.inserted as f32;
|
||||
let width = self.width;
|
||||
let height = self.height;
|
||||
self.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(move |(i, &f)| {
|
||||
if i < width * height {
|
||||
let x = i % width;
|
||||
let z = i / width;
|
||||
|
||||
let x = 2.0 * (x as f32 + 0.5) / (width as f32) - 1.0;
|
||||
let z = 2.0 * (z as f32 + 0.5) / (height as f32) - 1.0;
|
||||
let y = f32::sqrt(1.0 - x * x - z * z);
|
||||
|
||||
(Dir3::new(x, y, z), f * m)
|
||||
} else {
|
||||
let x = i % width;
|
||||
let z = (i - width * height) / width;
|
||||
|
||||
let x = 2.0 * (x as f32 + 0.5) / (width as f32) - 1.0;
|
||||
let z = 2.0 * (z as f32 + 0.5) / (height as f32) - 1.0;
|
||||
let y = -f32::sqrt(1.0 - x * x - z * z);
|
||||
|
||||
(Dir3::new(x, y, z), f * m)
|
||||
}
|
||||
})
|
||||
.filter(|(dir, _)| dir.x() * dir.x() + dir.z() * dir.z() < 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParallelExtend<(Dir3, f32)> for Histogram {
|
||||
fn par_extend<I>(&mut self, par_iter: I)
|
||||
where
|
||||
I: IntoParallelIterator<Item = (Dir3, f32)>,
|
||||
{
|
||||
let (tx, rx) = std::sync::mpsc::sync_channel(100);
|
||||
|
||||
std::thread::scope(move |s| {
|
||||
s.spawn(move || {
|
||||
while let Ok((i, f)) = rx.recv() {
|
||||
self.insert(i, f)
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
let tx = tx;
|
||||
par_iter.into_par_iter().for_each(|t| {
|
||||
let _ = tx.send(t);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn create_historgram<M: Material<SmallRng>>(
|
||||
m: &M,
|
||||
width: usize,
|
||||
height: usize,
|
||||
executions: usize,
|
||||
w_in: Dir3,
|
||||
channel: usize,
|
||||
) -> Histogram {
|
||||
let mut h = Histogram::new(width, height);
|
||||
h.par_extend(
|
||||
(0..executions)
|
||||
.into_par_iter()
|
||||
.map_init(SmallRng::from_entropy, |rng, _| {
|
||||
let sample = m.sample(w_in, rng);
|
||||
|
||||
let f = match channel {
|
||||
0 => sample.color().r(),
|
||||
1 => sample.color().g(),
|
||||
2 => sample.color().b(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
(sample.w_out(), f)
|
||||
}),
|
||||
);
|
||||
|
||||
h
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue