Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: spectral rendering #187

Merged
merged 47 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4e933f7
chore: add triangular prism stl
Walther Sep 17, 2023
56fa6e8
chore: add a couple of Color helpers
Walther Sep 23, 2023
e12ff12
feature: add spectral rendering helper methods
Walther Sep 23, 2023
bbe64b1
feature: spectral rendering and dispersive materials
Walther Sep 23, 2023
9eeb85c
feat: add a ConeLight material
Walther Oct 3, 2023
5fe3d03
improvement: better color handling with XYZ and sRGB
Walther Oct 3, 2023
68604c4
improvement: optimized release builds for faster runtime
Walther Oct 3, 2023
c8f5180
fix: rotate_wavelength function
Walther Oct 3, 2023
2d14b05
chore: impl Sum for Color
Walther Oct 3, 2023
8432cd2
chore: public consts for wavelength stuff
Walther Oct 8, 2023
2a4528d
improvement: add more color helpers and tests
Walther Oct 9, 2023
3b03bfb
feat: add D65 standard illuminant helpers
Walther Oct 9, 2023
dbdf0ae
chore: rename color.to_rgb_u8 to .to_bytes()
Walther Oct 9, 2023
3f73203
chore: use color.is_finite() helper in sample()
Walther Oct 9, 2023
3af2a27
chore: cornell.json, use glass material for priority object too
Walther Oct 10, 2023
59b1c7f
feat: add spectral upsampling from XYZ utilities
Walther Oct 11, 2023
b811483
chore: add chromaticity() helper for XYZ
Walther Oct 11, 2023
0488870
chore: add clippy allow conversion in the sample() function
Walther Oct 11, 2023
22e785f
feat: spectral rendering, hopefully more correct now!
Walther Oct 11, 2023
9d5700e
improvement: temporarily skip XYZ_Normalized in conversions
Walther Oct 11, 2023
73c4b1c
chore: wavelength probabilities
Walther Oct 13, 2023
1c24d83
improvement: use `palette` library, remove own impl of xyz and rgb
Walther Oct 13, 2023
371b452
fix: brightness issue in rendering
Walther Oct 13, 2023
e895918
chore: simplify pixelbuffer handling
Walther Oct 13, 2023
1699e60
chore: add a color checker scene
Walther Oct 13, 2023
5f8fe4c
feat: add cie spectral luminous efficiency helper
Walther Oct 13, 2023
2fdaa47
chore: fix rustdoc lint warns
Walther Oct 13, 2023
a1c8330
improvement: use LinSrgb for emit()
Walther Oct 15, 2023
b2c18d0
improvement: use Srgb and LinSrgb for textures and materials
Walther Oct 15, 2023
a417abe
improvement: use Srgb and LinSrgb for gltf material, remove color.rs
Walther Oct 16, 2023
8d63030
chore: doc fixes
Walther Oct 16, 2023
1b4a872
fix: default SolidColor back to 18% grey
Walther Oct 16, 2023
357f89b
chore: adjust colorchecker scene brightness
Walther Oct 16, 2023
3b97fdf
fix: wavelength_into_xyz whitepoint adaptation
Walther Oct 16, 2023
4b10f1d
chore: remove clippy unused_self annotations
Walther Oct 16, 2023
9f1c64b
chore: delete FlipFace object
Walther Oct 16, 2023
5f6e189
fix: finally achieve full color correctness <3
Walther Oct 16, 2023
00ff0aa
chore: fix docstring
Walther Oct 16, 2023
ecaa487
chore: remove a bunch of unnecessary scenes
Walther Oct 16, 2023
6231979
chore: extract adjust functions for emitted and attenuation
Walther Oct 17, 2023
45a6867
fix: random direction generation on unit sphere, not inside it
Walther Oct 17, 2023
ef54e86
chore: bump up the brightness on scifi helmet
Walther Oct 17, 2023
bc818b3
chore: refactor wavelength
Walther Oct 18, 2023
8fee239
chore: remove unnecessary utilities
Walther Oct 18, 2023
ca6ab15
chore: improve dispersive and cornell scenes
Walther Oct 18, 2023
941a005
chore: minor comment improvements
Walther Oct 18, 2023
e14734b
chore: remove custom gamma, real sRGB functions used
Walther Oct 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[workspace]
resolver = "2"

members = [
"clovers",
"clovers-cli",
]
members = ["clovers", "clovers-cli"]

[profile.release]
codegen-units = 1
lto = "fat"
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@ This repository has some example model files for demonstrating triangle-based ob
- Stanford Bunny model `bunny.stl` CC Attribution 3.0 Unported [Wikipedia](https://commons.wikimedia.org/wiki/File:Stanford_Bunny.stl)
- Stanford Dragon model `dragon.stl` (stl converted version) CC Attribution [Thingiverse](https://www.thingiverse.com/thing:27666)
- Rubber Duck model `duck.stl` CC0 1.0 Universal Public Domain [Thingiverse](https://www.thingiverse.com/thing:139894)
- Triangular Prism model `prism.stl` Public Domain [Wikipedia](https://commons.wikimedia.org/wiki/File:Triangular_prism.stl)

## Useful references

Making this renderer would not have been possible without the availability of an abundance of great literature. Here are listed some of the sources that have been useful:

- [Ray Tracing in One Weekend](https://raytracing.github.io/books/RayTracingInOneWeekend.html)
- [Ray Tracing: The Next Week](https://raytracing.github.io/books/RayTracingTheNextWeek.html)
- [Ray Tracing: The Rest of Your Life](https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html)
- [Physically Meaningful Rendering using Tristimulus Colours](https://doi.org/10.1111/cgf.12676)
- [Hero Wavelength Spectral Sampling](https://doi.org/10.1111/cgf.12419)
- [How to interpret the sRGB color space](https://color.org/chardata/rgb/sRGB.pdf)
19 changes: 15 additions & 4 deletions clovers-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,27 @@ path = "src/main.rs"

[dependencies]
# Internal
clovers = { path = "../clovers", features = ["serde-derive", "stl", "traces", "gl_tf"], default-features = false }
clovers = { path = "../clovers", features = [
"serde-derive",
"stl",
"traces",
"gl_tf",
], default-features = false }

# External
clap = { version = "4.4.1", features = ["std", "derive"] }
human_format = "1.0.3"
humantime = "2.1.0"
image = { version = "0.24.7", features = ["png"], default-features = false }
image = { version = "0.24.7", features = ["png"], default-features = false }
img-parts = "0.3.0"
indicatif = { version = "0.17.6", features = ["rayon"], default-features = false }
rand = { version = "0.8.5", features = ["small_rng", "getrandom"], default-features = false }
indicatif = { version = "0.17.6", features = [
"rayon",
], default-features = false }
palette = { version = "0.7.3", features = ["serializing"] }
rand = { version = "0.8.5", features = [
"small_rng",
"getrandom",
], default-features = false }
rayon = "1.7.0"
serde = { version = "1.0.188", features = ["derive"], default-features = false }
serde_json = { version = "1.0", features = ["alloc"], default-features = false }
Expand Down
33 changes: 19 additions & 14 deletions clovers-cli/src/draw_cpu.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use crate::{color::Color, colorize::colorize, normals::normal_map, ray::Ray, scenes, Float};
use crate::{colorize::colorize, normals::normal_map, ray::Ray, scenes, Float};
use clovers::RenderOpts;
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
use palette::chromatic_adaptation::AdaptInto;
use palette::convert::IntoColorUnclamped;
use palette::white_point::E;
use palette::{IntoColor, LinSrgb, Srgb, Xyz};
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use rayon::prelude::*;
use scenes::Scene;
use std::time::Duration;

/// The main drawing function, returns a `Vec<Color>` as a pixelbuffer.
pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec<Color> {
/// The main drawing function, returns a `Vec<Srgb>` as a pixelbuffer.
pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec<Srgb<u8>> {
// Progress bar
let pixels = (opts.width * opts.height) as u64;
let bar = ProgressBar::new(pixels);
Expand All @@ -22,7 +26,7 @@ pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec<Color> {
bar.enable_steady_tick(Duration::from_millis(100));
}

let black = Color::new(0.0, 0.0, 0.0);
let black: Srgb<u8> = Srgb::new(0, 0, 0);
let mut pixelbuffer = vec![black; pixels as usize];

pixelbuffer
Expand All @@ -40,15 +44,16 @@ pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec<Color> {
let mut rng = SmallRng::from_entropy();

// Initialize a mutable base color for the pixel
let mut color: Color = Color::new(0.0, 0.0, 0.0);
let mut color: LinSrgb = LinSrgb::new(0.0, 0.0, 0.0);

if opts.normalmap {
// If we are rendering just a normalmap, make it quick and early return
let u = x / width;
let v = y / height;
let ray: Ray = scene.camera.get_ray(u, v, &mut rng);
color = normal_map(&ray, scene, &mut rng);
*pixel = color;
let color: Srgb = color.into_color();
*pixel = color.into_format();
return;
}
// Otherwise, do a regular render
Expand All @@ -60,10 +65,9 @@ pub fn draw(opts: RenderOpts, scene: &Scene) -> Vec<Color> {
}
}
color /= opts.samples as Float;

// After multisampling, perform gamma correction and store final color into the pixel
color = color.gamma_correction(opts.gamma);
*pixel = color;
// Gamma / component transfer function
let color: Srgb = color.into_color();
*pixel = color.into_format();

bar.inc(1);
});
Expand All @@ -80,13 +84,14 @@ fn sample(
height: Float,
rng: &mut SmallRng,
max_depth: u32,
) -> Option<Color> {
) -> Option<LinSrgb> {
let u = (x + rng.gen::<Float>()) / width;
let v = (y + rng.gen::<Float>()) / height;
let ray: Ray = scene.camera.get_ray(u, v, rng);
let new_color = colorize(&ray, scene, 0, max_depth, rng);
// skip NaN and Infinity
if new_color.r.is_finite() && new_color.g.is_finite() && new_color.b.is_finite() {
let new_color: Xyz<E> = colorize(&ray, scene, 0, max_depth, rng);
let new_color: Xyz = new_color.adapt_into();
let new_color: LinSrgb = new_color.into_color_unclamped();
if new_color.red.is_finite() && new_color.green.is_finite() && new_color.blue.is_finite() {
return Some(new_color);
}
None
Expand Down
6 changes: 1 addition & 5 deletions clovers-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ struct Opts {
/// Maximum evaluated bounce depth for each ray
#[clap(short = 'd', long, default_value = "100")]
max_depth: u32,
/// Gamma correction value
#[clap(short, long, default_value = "2.0")]
gamma: Float,
/// Suppress most of the text output
#[clap(short, long)]
quiet: bool,
Expand Down Expand Up @@ -96,7 +93,6 @@ fn main() -> Result<(), Box<dyn Error>> {
height: opts.height,
samples: opts.samples,
max_depth: opts.max_depth,
gamma: opts.gamma,
quiet: opts.quiet,
normalmap: opts.normalmap,
};
Expand Down Expand Up @@ -127,7 +123,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut img: RgbImage = ImageBuffer::new(width, height);
img.enumerate_pixels_mut().for_each(|(x, y, pixel)| {
let index = y * width + x;
*pixel = Rgb(pixelbuffer[index as usize].to_rgb_u8());
*pixel = Rgb(pixelbuffer[index as usize].into());
});

// Graphics assume origin at bottom left corner of the screen
Expand Down
5 changes: 4 additions & 1 deletion clovers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ traces = ["tracing"]
enum_dispatch = "0.3.12"
gltf = { version = "1.3.0", optional = true }
nalgebra = { version = "0.32.3", features = ["libm"], default-features = false }
palette = { version = "0.7.3", features = ["serializing"] }
rand = { version = "0.8.5", features = ["small_rng"], default-features = false }
rand_distr = "0.4.3"
serde = { version = "1.0.188", features = ["derive"], default-features = false, optional = true }
serde = { version = "1.0.188", features = [
"derive",
], default-features = false, optional = true }
stl_io = { version = "0.7.0", optional = true }
tracing = { version = "0.1.37", optional = true }

Expand Down
4 changes: 2 additions & 2 deletions clovers/benches/random.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let mut rng = SmallRng::from_entropy();

c.bench_function("random in unit sphere", |b| {
b.iter(|| random_in_unit_sphere(black_box(&mut rng)))
b.iter(|| random_unit_vector(black_box(&mut rng)))
});

c.bench_function("random in unit disk", |b| {
Expand All @@ -25,7 +25,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {

let normal = Vec3::new(1.0, 0.0, 0.0);
c.bench_function("random in hemisphere", |b| {
b.iter(|| random_in_hemisphere(normal, black_box(&mut rng)))
b.iter(|| random_on_hemisphere(normal, black_box(&mut rng)))
});
}

Expand Down
18 changes: 13 additions & 5 deletions clovers/src/bvhnode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
aabb::AABB,
hitable::{Empty, HitRecord, Hitable, HitableTrait},
ray::Ray,
wavelength::Wavelength,
Box, Float, Vec, Vec3,
};

Expand Down Expand Up @@ -184,13 +185,20 @@ impl<'scene> HitableTrait for BVHNode<'scene> {

/// Returns a probability density function value based on the children
#[must_use]
fn pdf_value(&self, origin: Vec3, vector: Vec3, time: f32, rng: &mut SmallRng) -> f32 {
fn pdf_value(
&self,
origin: Vec3,
vector: Vec3,
wavelength: Wavelength,
time: Float,
rng: &mut SmallRng,
) -> Float {
match (&*self.left, &*self.right) {
(_, Hitable::Empty(_)) => self.left.pdf_value(origin, vector, time, rng),
(Hitable::Empty(_), _) => self.right.pdf_value(origin, vector, time, rng),
(_, Hitable::Empty(_)) => self.left.pdf_value(origin, vector, wavelength, time, rng),
(Hitable::Empty(_), _) => self.right.pdf_value(origin, vector, wavelength, time, rng),
(_, _) => {
(self.left.pdf_value(origin, vector, time, rng)
+ self.right.pdf_value(origin, vector, time, rng))
(self.left.pdf_value(origin, vector, wavelength, time, rng)
+ self.right.pdf_value(origin, vector, wavelength, time, rng))
/ 2.0
}
}
Expand Down
14 changes: 10 additions & 4 deletions clovers/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#![allow(clippy::too_many_arguments)] // TODO: Camera::new() has a lot of arguments.

use crate::wavelength::random_wavelength;
use crate::{random::random_in_unit_disk, ray::Ray, Float, Vec3, PI};
use rand::rngs::SmallRng;
use rand::Rng;
Expand Down Expand Up @@ -106,10 +107,15 @@ impl Camera {
let offset: Vec3 = self.u * rd.x + self.v * rd.y;
// Randomized time used for motion blur
let time: Float = rng.gen_range(self.time_0..self.time_1);
Ray::new(
self.origin + offset,
self.lower_left_corner + s * self.horizontal + t * self.vertical - self.origin - offset,
// Random wavelength for spectral rendering
let wavelength = random_wavelength(rng);
Ray {
origin: self.origin + offset,
direction: self.lower_left_corner + s * self.horizontal + t * self.vertical
- self.origin
- offset,
time,
)
wavelength,
}
}
}
Loading