├── .gitignore ├── .cargo └── config ├── balls.png ├── renders ├── rt-car.png ├── rt-car2.png ├── rt-house.png ├── rt-balls.png ├── rt-bathroom.png ├── rt-bedroom.png ├── rt-classroom.png ├── rt-dragon.png ├── rt-ganesha.png ├── rt-kitchen.png ├── rt-spaceship.png ├── rt-staircase.png ├── rt-teapot.png ├── rt-veach-mis.png ├── README.md ├── rt-cornell-box.png ├── rt-living-room-2.png ├── rt-living-room.png └── material_tests │ ├── rt-material-testball-glass.png │ ├── rt-material-testball-matte.png │ ├── rt-material-testball-metal.png │ ├── rt-material-testball-mirror.png │ ├── rt-material-testball-wood.png │ ├── rt-material-testball-plastic.png │ ├── rt-material-testball-roughglass.png │ ├── rt-material-testball-roughmetal.png │ ├── rt-material-testball-substrate.png │ ├── rt-material-testball-rough-plastic.png │ └── README.md ├── Cargo.toml ├── rustracer-cli ├── Cargo.toml └── src │ ├── argparse.rs │ └── main.rs ├── .gitattributes ├── .github └── workflows │ └── rust.yml ├── rustracer-core ├── src │ ├── filter │ │ ├── mod.rs │ │ ├── boxfilter.rs │ │ ├── triangle.rs │ │ ├── gaussian.rs │ │ └── mitchell.rs │ ├── sampler │ │ ├── sobol.rs │ │ ├── mod.rs │ │ ├── lowdiscrepancy.rs │ │ └── zerotwosequence.rs │ ├── integrator │ │ ├── normal.rs │ │ ├── ao.rs │ │ ├── whitted.rs │ │ └── directlighting.rs │ ├── floatfile.rs │ ├── texture │ │ ├── constant.rs │ │ ├── scale.rs │ │ ├── mix.rs │ │ ├── uv.rs │ │ ├── fbm.rs │ │ ├── mod.rs │ │ ├── checkerboard.rs │ │ └── imagemap.rs │ ├── bsdf │ │ ├── lambertian.rs │ │ ├── oren_nayar.rs │ │ └── bxdf.rs │ ├── material │ │ ├── fourier.rs │ │ ├── mirror.rs │ │ ├── matte.rs │ │ ├── mixmat.rs │ │ ├── substrate.rs │ │ ├── plastic.rs │ │ ├── mod.rs │ │ ├── translucent.rs │ │ ├── glass.rs │ │ ├── uber.rs │ │ └── metal.rs │ ├── fileutil.rs │ ├── rng.rs │ ├── sampling │ │ ├── distribution2d.rs │ │ ├── mod.rs │ │ └── distribution1d.rs │ ├── scene.rs │ ├── light │ │ ├── point.rs │ │ ├── mod.rs │ │ ├── distant.rs │ │ └── diffuse.rs │ ├── shapes │ │ ├── mod.rs │ │ └── disk.rs │ ├── pbrt │ │ └── mod.rs │ ├── blockedarray.rs │ ├── primitive.rs │ ├── ray.rs │ ├── geometry │ │ ├── matrix.rs │ │ ├── mod.rs │ │ └── normal.rs │ ├── skydome.rs │ ├── noise.rs │ ├── renderer.rs │ ├── stats │ │ └── macros.rs │ └── efloat.rs ├── Cargo.toml └── tests │ ├── shapes.rs │ └── efloat.rs ├── LICENSE-MIT └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-Ctarget-cpu=native"] 3 | -------------------------------------------------------------------------------- /balls.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a537b851b52d20075c59ff402e84aa67dd2b87c8decdb3174ad02661896d8935 3 | size 350036 4 | -------------------------------------------------------------------------------- /renders/rt-car.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9b18df6f1d608adc652f5249c9cea01c736548dbeddeaf47dd7a3a9ae8d9be1a 3 | size 883616 4 | -------------------------------------------------------------------------------- /renders/rt-car2.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1e8d524bd109e0902e6442e46ec4bde47e24168c6292acbe390142ee55e0991a 3 | size 773355 4 | -------------------------------------------------------------------------------- /renders/rt-house.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:64e14e83084c835e13894ad3ffd224e19f50ba33e4975a4807a5485f9ef7f480 3 | size 834872 4 | -------------------------------------------------------------------------------- /renders/rt-balls.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ae1c582b6df147475621b1dd372f7306e940ae367801edd9fcfeea078be3a67b 3 | size 1047173 4 | -------------------------------------------------------------------------------- /renders/rt-bathroom.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:89524fefea024d585bae80d55a65707bd4bb4748fcbb2cf969b3c0955400b0bc 3 | size 1758172 4 | -------------------------------------------------------------------------------- /renders/rt-bedroom.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:67d8c9f23f3d1a3b7b3112371bf6f05fbefda9de75bfe90ead9463febf41919e 3 | size 1436535 4 | -------------------------------------------------------------------------------- /renders/rt-classroom.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d51ca7e506d192635bf61686f707e6705323b9a27e3d1a221ee54f0ac60bf318 3 | size 1655643 4 | -------------------------------------------------------------------------------- /renders/rt-dragon.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6ef3dc406660af8f4a76bf33d791945fd4829f02e7d0e721bcafb74c12d57f3f 3 | size 218587 4 | -------------------------------------------------------------------------------- /renders/rt-ganesha.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a3a24809a8862b4bf8b607b5f3e079cb74531ad79552894c51da24f89751a9a1 3 | size 2043599 4 | -------------------------------------------------------------------------------- /renders/rt-kitchen.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:34a0175d7b716673dcef0b2bbf59b85a2a1244030febafe704372c345f0c5535 3 | size 1294769 4 | -------------------------------------------------------------------------------- /renders/rt-spaceship.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0702420d4d136f2a3b75033bdf4c18c57dd939aec8f57c785eac06e7716588d4 3 | size 689263 4 | -------------------------------------------------------------------------------- /renders/rt-staircase.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:348f492b29d0c51a6a3d682e426868012707b7209584c0c1667a88932ecd3ef1 3 | size 1500866 4 | -------------------------------------------------------------------------------- /renders/rt-teapot.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e6b92f3164bdb4c9f399913e3ce4e23564bcfa2f214e30272f9e878d5d7549c0 3 | size 1484132 4 | -------------------------------------------------------------------------------- /renders/rt-veach-mis.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9089188bd0c28e7c4cc96d08a58554d0be0e044e56bf8f4703ca50c3336db232 3 | size 962616 4 | -------------------------------------------------------------------------------- /renders/README.md: -------------------------------------------------------------------------------- 1 | This directory contains renders from scenes found on Benedikt Bitterli's excellent [Rendering Resources page](https://benedikt-bitterli.me/resources/). 2 | -------------------------------------------------------------------------------- /renders/rt-cornell-box.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:bc33107dc8c2b62b66f8586e7718b1f331ba9d6273f66034d10020f835dd305b 3 | size 1465890 4 | -------------------------------------------------------------------------------- /renders/rt-living-room-2.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7f1343169e2b1e32c0a24d933bcce1ba3eb83c17c85edb0d9f1b33dc35b57b59 3 | size 1449268 4 | -------------------------------------------------------------------------------- /renders/rt-living-room.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:65635977a5da9f20e77d0c973a43ea015960963d3aac560635a398d5d4daa1a0 3 | size 1799927 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["rustracer-core", "rustracer-cli"] 3 | 4 | [profile.dev] 5 | opt-level = 1 6 | 7 | [profile.release] 8 | opt-level = 3 9 | debug = false 10 | -------------------------------------------------------------------------------- /renders/material_tests/rt-material-testball-glass.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:17d60238b7229f1d198f53e6fd09e2d7dbb2c301a905e9b6bdcf500092077a2d 3 | size 1507517 4 | -------------------------------------------------------------------------------- /renders/material_tests/rt-material-testball-matte.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3ec335f01334c2092fd1f602b506272e8c5828b3702615409157a25ea359ec56 3 | size 1017032 4 | -------------------------------------------------------------------------------- /renders/material_tests/rt-material-testball-metal.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9cc3d1aeeb3b4844bf09165681e8b6ea4262b4fab90d406f87a308f728e592fa 3 | size 1218430 4 | -------------------------------------------------------------------------------- /renders/material_tests/rt-material-testball-mirror.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:dc3a944e05ca0f225db608b85ab851b3189227ce79ea8cd4831ac1b151c08a40 3 | size 1420562 4 | -------------------------------------------------------------------------------- /renders/material_tests/rt-material-testball-wood.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1947ccaa7cd01af00c1c82b09bd6db5287497fd6e33ef187c5be51c31bb6abf9 3 | size 1147991 4 | -------------------------------------------------------------------------------- /renders/material_tests/rt-material-testball-plastic.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:85c98c35d05cda008e7b029c1f2690ea553fbacca9e4df1d94bf5433ce0fe74a 3 | size 1006503 4 | -------------------------------------------------------------------------------- /renders/material_tests/rt-material-testball-roughglass.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:812b993a942e93d8ba4da1a0d74721d5fa78b36583f0abb36b78b4662dace6a9 3 | size 1244869 4 | -------------------------------------------------------------------------------- /renders/material_tests/rt-material-testball-roughmetal.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:fe3da10006c732872a477c21da862778177bf10e23136a359ccf95d8c54b0d0f 3 | size 1183442 4 | -------------------------------------------------------------------------------- /renders/material_tests/rt-material-testball-substrate.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c89d49e4db9c8de6297778b6866a8420331c9b1cffc95dfeddd8f0c87f77cd4e 3 | size 1143893 4 | -------------------------------------------------------------------------------- /renders/material_tests/rt-material-testball-rough-plastic.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4f69bf6d5db9bd67efc20e6485ede095ddbf301da870930d75d7428e24e907d4 3 | size 959374 4 | -------------------------------------------------------------------------------- /rustracer-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustracer" 3 | version = "0.1.0" 4 | authors = ["Antoine Büsch "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | rustracer-core = { path = "../rustracer-core/"} 9 | anyhow = "1" 10 | clap = "3.2" 11 | indicatif = "0.16" 12 | log = "0.4" 13 | flexi_logger = "0.22" 14 | num_cpus = "1" 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | models/* filter=lfs diff=lfs merge=lfs -text 2 | renders/material_tests filter=lfs diff=lfs merge=lfs -text 3 | renders/rt-car2.png filter=lfs diff=lfs merge=lfs -text 4 | renders/rt-dragon.png filter=lfs diff=lfs merge=lfs -text 5 | *.png filter=lfs diff=lfs merge=lfs -text 6 | *.tga filter=lfs diff=lfs merge=lfs -text 7 | *.exr filter=lfs diff=lfs merge=lfs -text 8 | *.hdr filter=lfs diff=lfs merge=lfs -text 9 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /renders/material_tests/README.md: -------------------------------------------------------------------------------- 1 | # Material tests 2 | 3 | ![Matte](rt-material-testball-matte.png) 4 | ![Glass](rt-material-testball-glass.png) 5 | ![Metal](rt-material-testball-metal.png) 6 | ![Mirror](rt-material-testball-mirror.png) 7 | ![Plastic](rt-material-testball-plastic.png) 8 | ![Rough glass](rt-material-testball-roughglass.png) 9 | ![Rough metal](rt-material-testball-roughmetal.png) 10 | ![Rough plastic](rt-material-testball-rough-plastic.png) 11 | ![Substrate](rt-material-testball-substrate.png) 12 | ![Wood texture](rt-material-testball-wood.png) 13 | -------------------------------------------------------------------------------- /rustracer-core/src/filter/mod.rs: -------------------------------------------------------------------------------- 1 | mod boxfilter; 2 | mod gaussian; 3 | mod mitchell; 4 | mod triangle; 5 | 6 | pub use self::boxfilter::BoxFilter; 7 | pub use self::gaussian::GaussianFilter; 8 | pub use self::mitchell::MitchellNetravali; 9 | pub use self::triangle::TriangleFilter; 10 | 11 | pub trait Filter: Send + Sync { 12 | fn evaluate(&self, x: f32, y: f32) -> f32; 13 | /// Return the x_width and y_width of the filter. The width is the distance from the origin to 14 | /// the cutoff point. The support or extent of the filter (in one direction), is the total 15 | /// domain where the filter is non-zero. Extent = 2*width. 16 | fn width(&self) -> (f32, f32); 17 | fn inv_width(&self) -> (f32, f32); 18 | } 19 | -------------------------------------------------------------------------------- /rustracer-core/src/sampler/sobol.rs: -------------------------------------------------------------------------------- 1 | use Point2i; 2 | use bounds::Bounds2i; 3 | use super::lowdiscrepancy::sobol_interval_to_index; 4 | 5 | pub struct SobolSampler { 6 | dimension: usize, 7 | interval_sample_index: u64, 8 | array_start_dim: usize, 9 | array_end_dim: usize, 10 | sample_bounds: Bounds2i, 11 | resolution: u32, 12 | log2_resolution: u32, 13 | } 14 | 15 | impl SobolSampler { 16 | fn get_index_for_sample(&self, sample_num: u64) -> u64 { 17 | sobol_interval_to_index( 18 | self.log2_resolution, 19 | sample_num, 20 | &Point2i::new(self.current_pixel - sample_bounds.p_min), 21 | ) 22 | } 23 | fn sample_dimension(&self, index: u64, dim: usize) -> f32 { 24 | 0.0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rustracer-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustracer-core" 3 | version = "0.1.0" 4 | authors = ["Antoine Büsch "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | anyhow = "1" 9 | approx = "0.5" 10 | atomic = "0.5" 11 | bitflags = "1" 12 | byteorder = "1.4" 13 | chrono = "0.4" 14 | crossbeam = "0.8" 15 | exr = "1.3" 16 | image = "0.24" 17 | indicatif = "0.16" 18 | itertools = "0.10" 19 | lazy_static = "1.4" 20 | light_arena = "1.0.1" 21 | log = "0.4" 22 | ndarray = { version = "0.15", features = ["rayon"] } 23 | num = "0.4" 24 | num_cpus = "1" 25 | parking_lot = "0.12" 26 | ply-rs = "0.1" 27 | rayon = "1" 28 | state = { version = "0.5", features = ["tls"]} 29 | thread-id = "4" 30 | nom = "7.0" 31 | 32 | [dev-dependencies] 33 | rand = "0.8" 34 | quickcheck = "1" 35 | -------------------------------------------------------------------------------- /rustracer-core/src/sampler/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::camera::CameraSample; 2 | use crate::{Point2f, Point2i}; 3 | 4 | pub mod lowdiscrepancy; 5 | pub mod zerotwosequence; 6 | 7 | pub trait Sampler: Send + Sync { 8 | fn start_pixel(&mut self, p: Point2i); 9 | fn get_1d(&mut self) -> f32; 10 | fn get_2d(&mut self) -> Point2f; 11 | fn get_camera_sample(&mut self, p_raster: Point2i) -> CameraSample; 12 | fn request_1d_array(&mut self, n: usize); 13 | fn request_2d_array(&mut self, n: usize); 14 | fn round_count(&self, count: usize) -> usize; 15 | fn get_1d_array(&mut self, n: usize) -> Option<&[f32]>; 16 | fn get_2d_array(&mut self, n: usize) -> Option<&[Point2f]>; 17 | fn start_next_sample(&mut self) -> bool; 18 | fn reseed(&mut self, seed: u64); 19 | fn spp(&self) -> usize; 20 | fn box_clone(&self) -> Box; 21 | fn current_sample_number(&self) -> usize; 22 | } 23 | -------------------------------------------------------------------------------- /rustracer-core/src/filter/boxfilter.rs: -------------------------------------------------------------------------------- 1 | use crate::filter::Filter; 2 | use crate::paramset::ParamSet; 3 | 4 | pub struct BoxFilter { 5 | radius: (f32, f32), 6 | inv_radius: (f32, f32), 7 | } 8 | 9 | impl BoxFilter { 10 | pub fn new(xwidth: f32, ywidth: f32) -> BoxFilter { 11 | BoxFilter { 12 | radius: (xwidth, ywidth), 13 | inv_radius: (1.0 / xwidth, 1.0 / ywidth), 14 | } 15 | } 16 | 17 | pub fn create(ps: &ParamSet) -> Box { 18 | let xw = ps.find_one_float("xwidth", 0.5); 19 | let yw = ps.find_one_float("ywidth", 0.5); 20 | 21 | Box::new(Self::new(xw, yw)) 22 | } 23 | } 24 | 25 | impl Filter for BoxFilter { 26 | fn evaluate(&self, _x: f32, _y: f32) -> f32 { 27 | 1.0 28 | } 29 | 30 | fn width(&self) -> (f32, f32) { 31 | self.radius 32 | } 33 | 34 | fn inv_width(&self) -> (f32, f32) { 35 | self.inv_radius 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rustracer-core/src/integrator/normal.rs: -------------------------------------------------------------------------------- 1 | use light_arena::Allocator; 2 | 3 | use crate::bounds::Bounds2i; 4 | use crate::integrator::SamplerIntegrator; 5 | use crate::ray::Ray; 6 | use crate::sampler::Sampler; 7 | use crate::scene::Scene; 8 | use crate::spectrum::Spectrum; 9 | 10 | #[derive(Default)] 11 | pub struct Normal { 12 | pixel_bounds: Bounds2i, 13 | } 14 | 15 | impl SamplerIntegrator for Normal { 16 | fn pixel_bounds(&self) -> &Bounds2i { 17 | &self.pixel_bounds 18 | } 19 | 20 | fn li( 21 | &self, 22 | scene: &Scene, 23 | ray: &mut Ray, 24 | _sampler: &mut dyn Sampler, 25 | _arena: &Allocator<'_>, 26 | _depth: u32, 27 | ) -> Spectrum { 28 | if let Some(intersection) = scene.intersect(ray) { 29 | let n = intersection.hit.n; 30 | Spectrum::grey(ray.d.dotn(&n).abs()) 31 | } else { 32 | Spectrum::black() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rustracer-core/src/floatfile.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io::{BufRead, BufReader}; 3 | use std::path::Path; 4 | 5 | use anyhow::Result; 6 | use log::warn; 7 | 8 | pub fn read_float_file>(filename: P) -> Result> { 9 | let mut values = Vec::new(); 10 | 11 | let file = fs::File::open(filename.as_ref())?; 12 | let reader = BufReader::new(file); 13 | 14 | for (line_num, line) in reader.lines().enumerate() { 15 | let line = line?; 16 | if line.starts_with('#') { 17 | continue; 18 | } 19 | for token in line.split_whitespace() { 20 | match token.parse::() { 21 | Ok(float) => values.push(float), 22 | Err(_) => { 23 | warn!( 24 | "Unexpected text found at line {} of float file \"{}\"", 25 | line_num, 26 | filename.as_ref().to_string_lossy() 27 | ); 28 | continue; 29 | } 30 | } 31 | } 32 | } 33 | 34 | Ok(values) 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2017 Jake Taylor 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /rustracer-core/src/texture/constant.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::interaction::SurfaceInteraction; 4 | use crate::paramset::TextureParams; 5 | use crate::spectrum::Spectrum; 6 | use crate::texture::Texture; 7 | use crate::Transform; 8 | 9 | #[derive(Debug)] 10 | pub struct ConstantTexture { 11 | value: T, 12 | } 13 | 14 | impl ConstantTexture { 15 | pub fn new(value: T) -> ConstantTexture { 16 | ConstantTexture { value } 17 | } 18 | } 19 | 20 | impl ConstantTexture { 21 | pub fn create_float(_tex2world: &Transform, tp: &TextureParams<'_>) -> ConstantTexture { 22 | ConstantTexture::new(tp.find_float("value", 1.0)) 23 | } 24 | } 25 | 26 | impl ConstantTexture { 27 | pub fn create_spectrum( 28 | _tex2world: &Transform, 29 | tp: &TextureParams<'_>, 30 | ) -> ConstantTexture { 31 | ConstantTexture::new(tp.find_spectrum("value", Spectrum::white())) 32 | } 33 | } 34 | 35 | impl Texture for ConstantTexture { 36 | fn evaluate(&self, _si: &SurfaceInteraction<'_, '_>) -> T { 37 | self.value 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rustracer-core/src/filter/triangle.rs: -------------------------------------------------------------------------------- 1 | use crate::filter::Filter; 2 | use crate::paramset::ParamSet; 3 | use crate::Vector2f; 4 | 5 | #[derive(Debug)] 6 | pub struct TriangleFilter { 7 | radius: Vector2f, 8 | inv_radius: Vector2f, 9 | } 10 | 11 | impl TriangleFilter { 12 | pub fn new(radius_x: f32, radius_y: f32) -> TriangleFilter { 13 | TriangleFilter { 14 | radius: Vector2f::new(radius_x, radius_y), 15 | inv_radius: Vector2f::new(1.0 / radius_x, 1.0 / radius_y), 16 | } 17 | } 18 | 19 | pub fn create(ps: &ParamSet) -> Box { 20 | let xw = ps.find_one_float("xwidth", 2.0); 21 | let yw = ps.find_one_float("ywidth", 2.0); 22 | 23 | Box::new(TriangleFilter::new(xw, yw)) 24 | } 25 | } 26 | 27 | impl Filter for TriangleFilter { 28 | fn evaluate(&self, x: f32, y: f32) -> f32 { 29 | f32::max(0.0, self.radius.x - f32::abs(x)) * f32::max(0.0, self.radius.y - f32::abs(y)) 30 | } 31 | 32 | fn width(&self) -> (f32, f32) { 33 | (self.radius.x, self.radius.y) 34 | } 35 | 36 | fn inv_width(&self) -> (f32, f32) { 37 | (self.inv_radius.x, self.inv_radius.y) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rustracer-cli/src/argparse.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg, ArgMatches}; 2 | 3 | pub fn parse_args() -> ArgMatches { 4 | App::new("rustracer") 5 | .version("0.1") 6 | .author("Antoine Büsch") 7 | .about("Toy raytracer in Rust based on PBRTv3") 8 | .arg( 9 | Arg::with_name("output") 10 | .long("output") 11 | .short('o') 12 | .help("Output file name") 13 | .default_value("image.png"), 14 | ) 15 | .arg( 16 | Arg::with_name("nthreads") 17 | .long("nthreads") 18 | .short('t') 19 | .help("Number of worker threads") 20 | .takes_value(true), 21 | ) 22 | .arg( 23 | Arg::with_name("verbose") 24 | .short('v') 25 | .help("log debug information"), 26 | ) 27 | .arg( 28 | Arg::with_name("display") 29 | .short('p') 30 | .help("Display image as it is rendered"), 31 | ) 32 | .arg( 33 | Arg::with_name("INPUT") 34 | .required(true) 35 | .help("PBRT scene file to render"), 36 | ) 37 | .get_matches() 38 | } 39 | -------------------------------------------------------------------------------- /rustracer-core/src/bsdf/lambertian.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts; 2 | 3 | use crate::bsdf::{BxDF, BxDFType}; 4 | use crate::spectrum::Spectrum; 5 | use crate::Vector3f; 6 | 7 | #[derive(Copy, Clone, Debug)] 8 | pub struct LambertianReflection { 9 | r: Spectrum, 10 | } 11 | 12 | impl LambertianReflection { 13 | pub fn new(r: Spectrum) -> LambertianReflection { 14 | LambertianReflection { r } 15 | } 16 | } 17 | 18 | impl BxDF for LambertianReflection { 19 | fn f(&self, _wo: &Vector3f, _wi: &Vector3f) -> Spectrum { 20 | self.r * consts::FRAC_1_PI 21 | } 22 | 23 | fn get_type(&self) -> BxDFType { 24 | BxDFType::BSDF_DIFFUSE | BxDFType::BSDF_REFLECTION 25 | } 26 | } 27 | 28 | #[derive(Copy, Clone, Debug)] 29 | pub struct LambertianTransmission { 30 | t: Spectrum, 31 | } 32 | 33 | impl LambertianTransmission { 34 | pub fn new(t: Spectrum) -> LambertianTransmission { 35 | LambertianTransmission { t } 36 | } 37 | } 38 | 39 | impl BxDF for LambertianTransmission { 40 | fn f(&self, _wo: &Vector3f, _wi: &Vector3f) -> Spectrum { 41 | self.t * consts::FRAC_1_PI 42 | } 43 | 44 | fn get_type(&self) -> BxDFType { 45 | BxDFType::BSDF_DIFFUSE | BxDFType::BSDF_TRANSMISSION 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rustracer-core/src/texture/scale.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::ops::Mul; 3 | use std::sync::Arc; 4 | 5 | use crate::interaction::SurfaceInteraction; 6 | use crate::paramset::TextureParams; 7 | use crate::spectrum::Spectrum; 8 | use crate::texture::Texture; 9 | 10 | #[derive(Debug)] 11 | pub struct ScaleTexture { 12 | tex1: Arc>, 13 | tex2: Arc>, 14 | } 15 | 16 | impl Texture for ScaleTexture 17 | where 18 | T: Debug, 19 | T: Send, 20 | T: Sync, 21 | T: Mul, 22 | { 23 | fn evaluate(&self, si: &SurfaceInteraction<'_, '_>) -> T { 24 | self.tex1.evaluate(si) * self.tex2.evaluate(si) 25 | } 26 | } 27 | 28 | impl ScaleTexture { 29 | pub fn create(tp: &TextureParams<'_>) -> ScaleTexture { 30 | let tex1 = tp.get_spectrum_texture("tex1", &Spectrum::white()); 31 | let tex2 = tp.get_spectrum_texture("tex2", &Spectrum::white()); 32 | 33 | ScaleTexture { tex1, tex2 } 34 | } 35 | } 36 | 37 | impl ScaleTexture { 38 | pub fn create(tp: &TextureParams<'_>) -> ScaleTexture { 39 | let tex1 = tp.get_float_texture("tex1", 1.0); 40 | let tex2 = tp.get_float_texture("tex2", 1.0); 41 | 42 | ScaleTexture { tex1, tex2 } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rustracer-core/src/filter/gaussian.rs: -------------------------------------------------------------------------------- 1 | use super::Filter; 2 | use crate::paramset::ParamSet; 3 | use crate::Vector2f; 4 | 5 | pub struct GaussianFilter { 6 | radius: (f32, f32), 7 | inv_radius: (f32, f32), 8 | alpha: f32, 9 | expx: f32, 10 | expy: f32, 11 | } 12 | 13 | impl GaussianFilter { 14 | pub fn new(radius: Vector2f, alpha: f32) -> GaussianFilter { 15 | GaussianFilter { 16 | radius: (radius.x, radius.y), 17 | inv_radius: (1.0 / radius.x, 1.0 / radius.y), 18 | alpha, 19 | expx: (-alpha * radius.x * radius.x).exp(), 20 | expy: (-alpha * radius.y * radius.y).exp(), 21 | } 22 | } 23 | 24 | fn gaussian(&self, d: f32, expv: f32) -> f32 { 25 | ((-self.alpha * d * d).exp() - expv).max(0f32) 26 | } 27 | 28 | pub fn create(ps: &ParamSet) -> Box { 29 | let xw = ps.find_one_float("xwidth", 2.0); 30 | let yw = ps.find_one_float("ywidth", 2.0); 31 | let alpha = ps.find_one_float("alpha", 2.0); 32 | Box::new(GaussianFilter::new(Vector2f::new(xw, yw), alpha)) 33 | } 34 | } 35 | 36 | impl Filter for GaussianFilter { 37 | fn evaluate(&self, x: f32, y: f32) -> f32 { 38 | self.gaussian(x, self.expx) * self.gaussian(y, self.expy) 39 | } 40 | 41 | fn width(&self) -> (f32, f32) { 42 | self.radius 43 | } 44 | 45 | fn inv_width(&self) -> (f32, f32) { 46 | self.inv_radius 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /rustracer-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | 3 | mod argparse; 4 | 5 | use anyhow::Result; 6 | use clap::ArgMatches; 7 | 8 | use flexi_logger::FileSpec; 9 | use rustracer_core::{init_stats, pbrt, PbrtOptions}; 10 | 11 | fn main() { 12 | println!("Rustracer 0.1 [Detected {} cores]", num_cpus::get()); 13 | println!("Copyright (c)2016-2018 Antoine Büsch."); 14 | println!("Based on the original PBRTv3 code by Matt Pharr, Greg Humphreys, and Wenzel Jacob."); 15 | let matches = argparse::parse_args(); 16 | 17 | flexi_logger::Logger::try_with_str("rustracer=info,rustracer_core=info") 18 | .unwrap() 19 | .log_to_file(FileSpec::default().suppress_timestamp().directory("/tmp")) 20 | .format(flexi_logger::opt_format) 21 | .start() 22 | .unwrap_or_else(|e| panic!("Failed to initialize logger: {}", e)); 23 | 24 | if let Err(ref e) = run(&matches) { 25 | println!("Application error: {}", e); 26 | ::std::process::exit(1); 27 | } 28 | } 29 | 30 | fn run(matches: &ArgMatches) -> Result<()> { 31 | init_stats(); 32 | let nthreads = matches 33 | .value_of("nthreads") 34 | .and_then(|v| v.parse::().ok()) 35 | .unwrap_or(0); 36 | let opts = PbrtOptions { 37 | num_threads: nthreads as u8, 38 | ..PbrtOptions::default() 39 | }; 40 | let filename = matches.value_of("INPUT").unwrap(); 41 | pbrt::parse_scene(opts, filename)?; 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /rustracer-core/src/material/fourier.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | 5 | use crate::bsdf::{Bsdf, BxDFHolder, FourierBSDF, FourierBSDFTable}; 6 | use crate::interaction::SurfaceInteraction; 7 | use crate::material::{Material, TransportMode}; 8 | use crate::paramset::TextureParams; 9 | use crate::texture::TextureFloat; 10 | 11 | #[derive(Debug)] 12 | pub struct FourierMaterial { 13 | bsdf_table: Box, 14 | bump_map: Option>, 15 | } 16 | 17 | impl FourierMaterial { 18 | pub fn create(mp: &TextureParams<'_>) -> Arc { 19 | let bump_map = mp.get_float_texture_or_none("bumpmap"); 20 | let filename = mp.find_filename("bsdffile", ""); 21 | let bsdf_table = Box::new(FourierBSDFTable::read(filename).unwrap()); // TODO error 22 | Arc::new(FourierMaterial { 23 | bsdf_table, 24 | bump_map, 25 | }) 26 | } 27 | } 28 | 29 | impl Material for FourierMaterial { 30 | fn compute_scattering_functions<'a, 'b>( 31 | &self, 32 | si: &mut SurfaceInteraction<'a, 'b>, 33 | mode: TransportMode, 34 | _allow_multiple_lobes: bool, 35 | arena: &'b Allocator<'_>, 36 | ) { 37 | let mut bxdfs = BxDFHolder::new(arena); 38 | 39 | if let Some(ref bump) = self.bump_map { 40 | super::bump(bump, si); 41 | } 42 | bxdfs.add(arena.alloc(FourierBSDF::new(&self.bsdf_table, mode))); 43 | let bsdf = Bsdf::new(si, 1.0, bxdfs.into_slice()); 44 | si.bsdf = Some(Arc::new(bsdf)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rustracer-core/src/texture/mix.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::ops::{Add, Mul}; 3 | use std::sync::Arc; 4 | 5 | use crate::interaction::SurfaceInteraction; 6 | use crate::paramset::TextureParams; 7 | use crate::spectrum::Spectrum; 8 | use crate::texture::{Texture, TextureFloat}; 9 | use crate::Transform; 10 | 11 | #[derive(Debug)] 12 | pub struct MixTexture { 13 | tex1: Arc>, 14 | tex2: Arc>, 15 | amount: Arc, 16 | } 17 | 18 | impl Texture for MixTexture 19 | where 20 | T: Debug, 21 | T: Mul, 22 | T: Add, 23 | { 24 | fn evaluate(&self, si: &SurfaceInteraction<'_, '_>) -> T { 25 | let t1 = self.tex1.evaluate(si); 26 | let t2 = self.tex2.evaluate(si); 27 | let amt = self.amount.evaluate(si); 28 | 29 | t1 * (1.0 - amt) + t2 * amt 30 | } 31 | } 32 | 33 | impl MixTexture { 34 | pub fn create_float(_tex2world: &Transform, tp: &TextureParams<'_>) -> MixTexture { 35 | MixTexture { 36 | tex1: tp.get_float_texture("tex1", 0.0), 37 | tex2: tp.get_float_texture("tex2", 1.0), 38 | amount: tp.get_float_texture("amount", 0.5), 39 | } 40 | } 41 | } 42 | 43 | impl MixTexture { 44 | pub fn create_spectrum(_tex2world: &Transform, tp: &TextureParams<'_>) -> MixTexture { 45 | MixTexture { 46 | tex1: tp.get_spectrum_texture("tex1", &Spectrum::black()), 47 | tex2: tp.get_spectrum_texture("tex2", &Spectrum::white()), 48 | amount: tp.get_float_texture("amount", 0.5), 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /rustracer-core/src/material/mirror.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | use log::info; 5 | 6 | use crate::bsdf::{no_op, Bsdf, BxDFHolder, SpecularReflection}; 7 | use crate::interaction::SurfaceInteraction; 8 | use crate::material::{Material, TransportMode}; 9 | use crate::paramset::TextureParams; 10 | use crate::spectrum::Spectrum; 11 | use crate::texture::{TextureFloat, TextureSpectrum}; 12 | 13 | #[derive(Debug)] 14 | pub struct MirrorMaterial { 15 | kr: Arc, 16 | bump_map: Option>, 17 | } 18 | 19 | impl MirrorMaterial { 20 | pub fn create(mp: &TextureParams<'_>) -> Arc { 21 | info!("Creating Mirror material"); 22 | let Kr = mp.get_spectrum_texture("Kr", &Spectrum::grey(0.9)); 23 | let bump_map = mp.get_float_texture_or_none("bumpmap"); 24 | 25 | Arc::new(MirrorMaterial { kr: Kr, bump_map }) 26 | } 27 | } 28 | 29 | impl Material for MirrorMaterial { 30 | fn compute_scattering_functions<'a, 'b>( 31 | &self, 32 | si: &mut SurfaceInteraction<'a, 'b>, 33 | _mode: TransportMode, 34 | _allow_multiple_lobes: bool, 35 | arena: &'b Allocator<'_>, 36 | ) { 37 | if let Some(ref bump) = self.bump_map { 38 | super::bump(bump, si); 39 | } 40 | let mut bxdfs = BxDFHolder::new(arena); 41 | let R = self.kr.evaluate(si).clamp(); 42 | if !R.is_black() { 43 | let fresnel = arena.alloc(no_op()); 44 | bxdfs.add(arena.alloc(SpecularReflection::new(R, fresnel))); 45 | } 46 | 47 | si.bsdf = Some(Arc::new(Bsdf::new(si, 1.0, bxdfs.into_slice()))); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rustracer-core/src/integrator/ao.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use light_arena::Allocator; 4 | 5 | use crate::bounds::Bounds2i; 6 | use crate::integrator::SamplerIntegrator; 7 | use crate::ray::Ray; 8 | use crate::sampler::Sampler; 9 | use crate::sampling::uniform_sample_sphere; 10 | use crate::scene::Scene; 11 | use crate::spectrum::Spectrum; 12 | 13 | pub struct AmbientOcclusion { 14 | pixel_bounds: Bounds2i, 15 | n_samples: usize, 16 | } 17 | 18 | impl AmbientOcclusion { 19 | pub fn new(n_samples: usize) -> AmbientOcclusion { 20 | AmbientOcclusion { 21 | n_samples, 22 | pixel_bounds: Bounds2i::new(), 23 | } 24 | } 25 | } 26 | 27 | impl SamplerIntegrator for AmbientOcclusion { 28 | fn pixel_bounds(&self) -> &Bounds2i { 29 | &self.pixel_bounds 30 | } 31 | 32 | fn li( 33 | &self, 34 | scene: &Scene, 35 | ray: &mut Ray, 36 | sampler: &mut dyn Sampler, 37 | _arena: &Allocator<'_>, 38 | _depth: u32, 39 | ) -> Spectrum { 40 | let mut n_clear: usize = 0; 41 | 42 | if let Some(intersection) = scene.intersect(ray) { 43 | let n = intersection.hit.n; 44 | for _ in 0..self.n_samples { 45 | let s = sampler.get_2d(); 46 | let mut w = uniform_sample_sphere(s); 47 | if w.dotn(&n) < 0.0 { 48 | w = -w; 49 | } 50 | let ao_ray = intersection.spawn_ray(&w); 51 | if !scene.intersect_p(&ao_ray) { 52 | n_clear += 1; 53 | } 54 | } 55 | } 56 | 57 | Spectrum::grey((n_clear as f32) / (self.n_samples as f32)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /rustracer-core/src/fileutil.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use lazy_static::lazy_static; 4 | use log::debug; 5 | use parking_lot::Mutex; 6 | 7 | lazy_static! { 8 | static ref SEARCH_DIR: Mutex> = Mutex::new(None); 9 | } 10 | 11 | pub fn set_search_directory>(d: P) { 12 | let d = d.as_ref(); 13 | let mut dir = SEARCH_DIR.lock(); 14 | dir.get_or_insert(PathBuf::from(d)); 15 | debug!("Set search directory to {}", d.display()); 16 | } 17 | 18 | pub fn directory_containing>(path: P) -> PathBuf { 19 | let path = path.as_ref(); 20 | 21 | path.canonicalize() 22 | .unwrap() 23 | .parent() 24 | .unwrap_or_else(|| { 25 | panic!( 26 | "Failed to get the parent directory of the input file {}", 27 | path.display() 28 | ) 29 | }) 30 | .to_owned() 31 | } 32 | 33 | pub fn resolve_filename(filename: &str) -> String { 34 | debug!("Resolving filename {}", filename); 35 | let search_directory = SEARCH_DIR.lock(); 36 | if search_directory.is_none() || filename.is_empty() || Path::new(filename).is_absolute() { 37 | filename.to_owned() 38 | } else { 39 | let mut buf = (*search_directory).clone().unwrap(); //PathBuf::from(*search_directory); 40 | buf.push(filename); 41 | buf.as_path() 42 | .canonicalize() 43 | .ok() 44 | .and_then(|p| p.to_str().map(|s| s.to_owned())) 45 | .unwrap_or_else(|| filename.to_owned()) 46 | } 47 | } 48 | 49 | pub fn has_extension>(filename: P, extension: &str) -> bool { 50 | filename 51 | .as_ref() 52 | .extension() 53 | .map(|e| e == extension) 54 | .unwrap_or(false) 55 | } 56 | -------------------------------------------------------------------------------- /rustracer-core/src/rng.rs: -------------------------------------------------------------------------------- 1 | use std::num::Wrapping; 2 | 3 | use crate::ONE_MINUS_EPSILON; 4 | 5 | const PCG32_DEFAULT_STATE: Wrapping = Wrapping(0x853c49e6748fea9b); 6 | const PCG32_DEFAULT_STREAM: Wrapping = Wrapping(0xda3e39cb94b95bdb); 7 | const PCG32_MULT: Wrapping = Wrapping(0x5851f42d4c957f2d); 8 | 9 | #[derive(Copy, Clone)] 10 | pub struct RNG { 11 | state: Wrapping, 12 | inc: Wrapping, 13 | } 14 | 15 | impl RNG { 16 | pub fn new() -> RNG { 17 | RNG { 18 | state: PCG32_DEFAULT_STATE, 19 | inc: PCG32_DEFAULT_STREAM, 20 | } 21 | } 22 | 23 | pub fn uniform_u32(&mut self) -> u32 { 24 | let oldstate = self.state; 25 | self.state = oldstate * PCG32_MULT + self.inc; 26 | let xorshifted = Wrapping((((oldstate >> 18) ^ oldstate) >> 27).0 as u32); 27 | let rot = (oldstate >> 59).0 as u32; 28 | 29 | (xorshifted.0 >> rot) | (xorshifted.0 << ((!Wrapping(rot) + Wrapping(1)).0 & 31)) 30 | } 31 | 32 | pub fn uniform_u32_bounded(&mut self, b: u32) -> u32 { 33 | let threshold = (!b + 1) & b; 34 | loop { 35 | let r = self.uniform_u32(); 36 | if r >= threshold { 37 | return r % b; 38 | } 39 | } 40 | } 41 | 42 | pub fn uniform_f32(&mut self) -> f32 { 43 | (self.uniform_u32() as f32 * 2.3283064365386963e-10).min(ONE_MINUS_EPSILON) 44 | } 45 | 46 | pub fn set_sequence(&mut self, seed: u64) { 47 | self.state = Wrapping(0); 48 | self.inc = Wrapping((seed << 1) | 1); 49 | let _ = self.uniform_u32(); 50 | self.state += PCG32_DEFAULT_STATE; 51 | let _ = self.uniform_u32(); 52 | } 53 | } 54 | 55 | impl Default for RNG { 56 | fn default() -> RNG { 57 | RNG::new() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /rustracer-core/src/sampling/distribution2d.rs: -------------------------------------------------------------------------------- 1 | use crate::sampling::Distribution1D; 2 | use crate::{clamp, Point2f}; 3 | 4 | #[derive(Debug)] 5 | pub struct Distribution2D { 6 | p_conditional_v: Vec, 7 | p_marginal: Distribution1D, 8 | } 9 | 10 | impl Distribution2D { 11 | pub fn new(func: &[f32], nu: usize, nv: usize) -> Distribution2D { 12 | let mut p_conditional_v = Vec::with_capacity(nv); 13 | for v in 0..nv { 14 | // compute conditional sampling distribution for v_tilde 15 | p_conditional_v.push(Distribution1D::new(&func[(v * nu)..((v + 1) * nu)])); 16 | } 17 | // compute marginal sampling distribution p[v_tilde] 18 | let mut marginal_func = Vec::with_capacity(nv); 19 | for p_cond in &p_conditional_v { 20 | marginal_func.push(p_cond.func_int) 21 | } 22 | 23 | Distribution2D { 24 | p_conditional_v, 25 | p_marginal: Distribution1D::new(&marginal_func[..]), 26 | } 27 | } 28 | 29 | pub fn sample_continuous(&self, u: Point2f) -> (Point2f, f32) { 30 | let (d_1, pdf_1, v) = self.p_marginal.sample_continuous(u[1]); 31 | let (d_0, pdf_0, _) = self.p_conditional_v[v].sample_continuous(u[0]); 32 | 33 | (Point2f::new(d_0, d_1), pdf_0 * pdf_1) 34 | } 35 | 36 | pub fn pdf(&self, p: Point2f) -> f32 { 37 | let iu = clamp( 38 | (p[0] * self.p_conditional_v[0].count() as f32) as usize, 39 | 0, 40 | self.p_conditional_v[0].count() - 1, 41 | ); 42 | let iv = clamp( 43 | (p[1] * self.p_marginal.count() as f32) as usize, 44 | 0, 45 | self.p_marginal.count() - 1, 46 | ); 47 | 48 | self.p_conditional_v[iv].func[iu] / self.p_marginal.func_int 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rustracer-core/src/scene.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::bounds::Bounds3f; 4 | use crate::interaction::SurfaceInteraction; 5 | use crate::light::{Light, LightFlags}; 6 | use crate::primitive::Primitive; 7 | use crate::ray::Ray; 8 | 9 | stat_counter!( 10 | "Intersections/Regular ray intersection tests", 11 | n_intersection_tests 12 | ); 13 | stat_counter!( 14 | "Intersections/Shadow ray intersection tests", 15 | n_shadow_tests 16 | ); 17 | pub fn init_stats() { 18 | n_intersection_tests::init(); 19 | n_shadow_tests::init(); 20 | } 21 | 22 | pub struct Scene { 23 | pub lights: Vec>, 24 | pub infinite_lights: Vec>, 25 | aggregate: Arc, 26 | } 27 | 28 | impl Scene { 29 | pub fn new(aggregate: Arc, lights: Vec>) -> Scene { 30 | let mut scene = Scene { 31 | lights: Vec::new(), 32 | infinite_lights: Vec::new(), 33 | aggregate, 34 | }; 35 | 36 | let mut infinite_lights = Vec::new(); 37 | 38 | for l in &lights { 39 | l.preprocess(&scene); 40 | if l.flags().contains(LightFlags::INFINITE) { 41 | infinite_lights.push(Arc::clone(l)); 42 | } 43 | } 44 | 45 | scene.lights = lights; 46 | scene.infinite_lights = infinite_lights; 47 | 48 | scene 49 | } 50 | 51 | pub fn intersect(&self, ray: &mut Ray) -> Option> { 52 | n_intersection_tests::inc(); 53 | self.aggregate.intersect(ray) 54 | } 55 | 56 | pub fn intersect_p(&self, ray: &Ray) -> bool { 57 | n_shadow_tests::inc(); 58 | self.aggregate.intersect_p(ray) 59 | } 60 | 61 | pub fn world_bounds(&self) -> Bounds3f { 62 | self.aggregate.world_bounds() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rustracer-core/src/texture/uv.rs: -------------------------------------------------------------------------------- 1 | use log::error; 2 | 3 | use crate::interaction::SurfaceInteraction; 4 | use crate::paramset::TextureParams; 5 | use crate::spectrum::Spectrum; 6 | use crate::texture::{Texture, TextureMapping2D, UVMapping2D}; 7 | use crate::Transform; 8 | 9 | #[derive(Debug)] 10 | pub struct UVTexture { 11 | mapping: Box, 12 | } 13 | 14 | impl UVTexture { 15 | pub fn new() -> UVTexture { 16 | UVTexture { 17 | mapping: Box::new(UVMapping2D::new(1.0, 1.0, 0.0, 0.0)), 18 | } 19 | } 20 | 21 | pub fn create_spectrum(_tex2world: &Transform, tp: &TextureParams<'_>) -> UVTexture { 22 | let typ = tp.find_string("mapping", "uv"); 23 | let mapping = if typ == "uv" { 24 | let su = tp.find_float("uscale", 1.0); 25 | let sv = tp.find_float("vscale", 1.0); 26 | let du = tp.find_float("udelta", 0.0); 27 | let dv = tp.find_float("vdelta", 0.0); 28 | Box::new(UVMapping2D::new(su, sv, du, dv)) 29 | } else if typ == "spherical" { 30 | unimplemented!() 31 | } else if typ == "cylindrical" { 32 | unimplemented!() 33 | } else if typ == "planar" { 34 | unimplemented!() 35 | } else { 36 | error!("2D texture mapping \"{}\" unknown.", typ); 37 | Box::new(UVMapping2D::new(1.0, 1.0, 0.0, 0.0)) 38 | }; 39 | 40 | UVTexture { mapping } 41 | } 42 | } 43 | 44 | impl Default for UVTexture { 45 | fn default() -> Self { 46 | Self::new() 47 | } 48 | } 49 | 50 | impl Texture for UVTexture { 51 | fn evaluate(&self, si: &SurfaceInteraction<'_, '_>) -> Spectrum { 52 | let (st, _dstdx, _dstdy) = self.mapping.map(si); 53 | Spectrum::rgb(st[0] - st[0].floor(), st[1] - st[1].floor(), 0.0) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /rustracer-core/src/bsdf/oren_nayar.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts; 2 | 3 | use crate::bsdf::bxdf::BxDF; 4 | use crate::bsdf::BxDFType; 5 | use crate::geometry::{abs_cos_theta, cos_phi, sin_phi, sin_theta}; 6 | use crate::spectrum::Spectrum; 7 | use crate::Vector3f; 8 | 9 | #[derive(Copy, Clone, Debug)] 10 | pub struct OrenNayar { 11 | r: Spectrum, 12 | a: f32, 13 | b: f32, 14 | } 15 | 16 | impl OrenNayar { 17 | pub fn new(r: Spectrum, sigma: f32) -> OrenNayar { 18 | let sigma_rad = sigma.to_radians(); 19 | let sigma2 = sigma_rad * sigma_rad; 20 | 21 | OrenNayar { 22 | r, 23 | a: 1.0 - (sigma2 / (2.0 * (sigma2 + 0.33))), 24 | b: 0.45 * sigma2 / (sigma2 + 0.09), 25 | } 26 | } 27 | } 28 | 29 | impl BxDF for OrenNayar { 30 | fn f(&self, wo: &Vector3f, wi: &Vector3f) -> Spectrum { 31 | let sin_theta_i = sin_theta(wi); 32 | let sin_theta_o = sin_theta(wo); 33 | 34 | // compute cosine term of the Oren-Nayar model 35 | let max_cos = if sin_theta_i > 1e-4 && sin_theta_o > 1e-4 { 36 | let sin_phi_i = sin_phi(wi); 37 | let cos_phi_i = cos_phi(wi); 38 | let sin_phi_o = sin_phi(wo); 39 | let cos_phi_o = cos_phi(wo); 40 | let d_cos = sin_phi_i * sin_phi_o + cos_phi_i * cos_phi_o; 41 | d_cos.max(0.0) 42 | } else { 43 | 0.0 44 | }; 45 | // compute sine and tangent terms of Oren-Nayar model 46 | let (sin_alpha, tan_beta) = if abs_cos_theta(wi) > abs_cos_theta(wo) { 47 | (sin_theta_o, sin_theta_i / abs_cos_theta(wi)) 48 | } else { 49 | (sin_theta_i, sin_theta_o / abs_cos_theta(wo)) 50 | }; 51 | 52 | self.r * consts::FRAC_1_PI * (self.a + self.b * max_cos * sin_alpha * tan_beta) 53 | } 54 | 55 | fn get_type(&self) -> BxDFType { 56 | BxDFType::BSDF_REFLECTION | BxDFType::BSDF_DIFFUSE 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /rustracer-core/src/texture/fbm.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::interaction::SurfaceInteraction; 4 | use crate::noise; 5 | use crate::paramset::TextureParams; 6 | use crate::spectrum::Spectrum; 7 | use crate::texture::{IdentityMapping3D, Texture, TextureMapping3D}; 8 | use crate::Transform; 9 | 10 | #[derive(Debug)] 11 | pub struct FbmTexture { 12 | mapping: Box, 13 | roughness: f32, 14 | octaves: u32, 15 | _phantom: PhantomData, 16 | } 17 | 18 | impl FbmTexture { 19 | fn evaluate_as_float(&self, si: &SurfaceInteraction<'_, '_>) -> f32 { 20 | let (p, dpdx, dpdy) = self.mapping.map(si); 21 | noise::fbm(&p, &dpdx, &dpdy, self.roughness, self.octaves) 22 | } 23 | } 24 | 25 | impl FbmTexture { 26 | pub fn create_float(tex2world: &Transform, tp: &TextureParams<'_>) -> FbmTexture { 27 | FbmTexture { 28 | mapping: Box::new(IdentityMapping3D::new(tex2world.clone())), 29 | roughness: tp.find_float("omega", 0.5), 30 | octaves: tp.find_int("octaves", 8) as u32, 31 | _phantom: PhantomData, 32 | } 33 | } 34 | } 35 | 36 | impl FbmTexture { 37 | pub fn create_spectrum(tex2world: &Transform, tp: &TextureParams<'_>) -> FbmTexture { 38 | FbmTexture { 39 | mapping: Box::new(IdentityMapping3D::new(tex2world.clone())), 40 | roughness: tp.find_float("omega", 0.5), 41 | octaves: tp.find_int("octaves", 8) as u32, 42 | _phantom: PhantomData, 43 | } 44 | } 45 | } 46 | 47 | impl Texture for FbmTexture { 48 | fn evaluate(&self, si: &SurfaceInteraction<'_, '_>) -> f32 { 49 | self.evaluate_as_float(si) 50 | } 51 | } 52 | 53 | impl Texture for FbmTexture { 54 | fn evaluate(&self, si: &SurfaceInteraction<'_, '_>) -> Spectrum { 55 | Spectrum::from(self.evaluate_as_float(si)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rustracer-core/tests/shapes.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::StdRng, Rng, SeedableRng}; 2 | use std::f32; 3 | 4 | use rustracer_core::ray::Ray; 5 | use rustracer_core::sampling; 6 | use rustracer_core::shapes::{Shape, Sphere}; 7 | use rustracer_core::{Point2f, Point3f, Transform}; 8 | 9 | fn pexp(rng: &mut T, exp: f32) -> f32 { 10 | // let range = Range::new(-exp, exp); 11 | let logu: f32 = rng.gen_range(-exp..=exp); // range.ind_sample(rng); 12 | let base = 10.0_f32; 13 | base.powf(logu) 14 | } 15 | 16 | #[test] 17 | fn full_sphere_reintersect() { 18 | for i in 0..1000 { 19 | let mut rng = StdRng::seed_from_u64(i as u64); 20 | let radius = pexp(&mut rng, 4.0); 21 | let sphere = Sphere::new(Transform::default(), radius, -radius, radius, 360.0, false); 22 | test_reintersection_convex(&sphere, &mut rng); 23 | } 24 | } 25 | 26 | fn test_reintersection_convex(shape: &T, rng: &mut StdRng) { 27 | // Ray origin 28 | let o = Point3f::new(pexp(rng, 8.0), pexp(rng, 8.0), pexp(rng, 8.0)); 29 | 30 | // Destination 31 | let bounds = shape.world_bounds(); 32 | let t = Point3f::new(rng.gen(), rng.gen(), rng.gen()); 33 | let p = bounds.lerp(&t); 34 | let mut ray = Ray::new(o, p - o); 35 | if rng.gen::() < 0.5 { 36 | ray.d = ray.d.normalize(); 37 | } 38 | 39 | // We usually, but not always, get an intersection 40 | if let Some((isect, _t_hit)) = shape.intersect(&ray) { 41 | // Now trace a bunch of rays leaving the intersection point 42 | for _ in 0..1000 { 43 | // Random direction leaving the intersection point 44 | let u = Point2f::new(rng.gen(), rng.gen()); 45 | let mut w = sampling::uniform_sample_sphere(u); 46 | if w.dotn(&isect.hit.n) < 0.0 { 47 | w = -w; 48 | } 49 | let ray_out = isect.spawn_ray(&w); 50 | assert!(!shape.intersect_p(&ray_out)); 51 | assert!(shape.intersect(&ray_out).is_none()); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /rustracer-core/src/material/matte.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | use log::info; 5 | 6 | use crate::bsdf::{Bsdf, BxDFHolder, LambertianReflection, OrenNayar}; 7 | use crate::clamp; 8 | use crate::interaction::SurfaceInteraction; 9 | use crate::material::{Material, TransportMode}; 10 | use crate::paramset::TextureParams; 11 | use crate::spectrum::Spectrum; 12 | use crate::texture::{TextureFloat, TextureSpectrum}; 13 | 14 | #[derive(Debug)] 15 | pub struct MatteMaterial { 16 | kd: Arc, 17 | sigma: Arc, 18 | bump_map: Option>, 19 | } 20 | 21 | impl MatteMaterial { 22 | pub fn create(mp: &TextureParams<'_>) -> Arc { 23 | info!("Creating Matte material"); 24 | let kd = mp.get_spectrum_texture("Kd", &Spectrum::grey(0.5)); 25 | let sigma = mp.get_float_texture("sigma", 0.0); 26 | let bump_map = mp.get_float_texture_or_none("bumpmap"); 27 | 28 | Arc::new(MatteMaterial { 29 | kd, 30 | sigma, 31 | bump_map, 32 | }) 33 | } 34 | } 35 | 36 | impl Material for MatteMaterial { 37 | fn compute_scattering_functions<'a, 'b>( 38 | &self, 39 | si: &mut SurfaceInteraction<'a, 'b>, 40 | _mode: TransportMode, 41 | _allow_multiple_lobes: bool, 42 | arena: &'b Allocator<'_>, 43 | ) { 44 | let mut bxdfs = BxDFHolder::new(arena); 45 | 46 | if let Some(ref bump_map) = self.bump_map { 47 | super::bump(bump_map, si); 48 | } 49 | 50 | let r = self.kd.evaluate(si).clamp(); 51 | let sigma = clamp(self.sigma.evaluate(si), 0.0, 1.0); 52 | if !r.is_black() { 53 | if sigma == 0.0 { 54 | bxdfs.add(arena.alloc(LambertianReflection::new(r))); 55 | } else { 56 | bxdfs.add(arena.alloc(OrenNayar::new(r, sigma))); 57 | } 58 | } 59 | 60 | let bsdf = Bsdf::new(si, 1.0, bxdfs.into_slice()); 61 | si.bsdf = Some(Arc::new(bsdf)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /rustracer-core/src/light/point.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | use std::sync::Arc; 3 | 4 | use num::Zero; 5 | 6 | use crate::interaction::Interaction; 7 | use crate::light::{Light, LightFlags, VisibilityTester}; 8 | use crate::paramset::ParamSet; 9 | use crate::spectrum::Spectrum; 10 | use crate::{Point2f, Point3f, Transform, Vector3f}; 11 | 12 | #[derive(Debug)] 13 | pub struct PointLight { 14 | id: u32, 15 | pos: Point3f, 16 | emission_colour: Spectrum, 17 | } 18 | 19 | impl PointLight { 20 | pub fn new(p: Point3f, ec: Spectrum) -> PointLight { 21 | PointLight { 22 | id: super::get_next_id(), 23 | pos: p, 24 | emission_colour: ec, 25 | } 26 | } 27 | 28 | pub fn create(l2w: &Transform, params: &ParamSet) -> Arc { 29 | let I = params.find_one_spectrum("I", Spectrum::white()); 30 | let scale = params.find_one_spectrum("scale", Spectrum::white()); 31 | let p = params.find_one_point3f("from", Point3f::zero()); 32 | 33 | let t = &Transform::translate(&Vector3f::new(p.x, p.y, p.z)) * l2w; 34 | Arc::new(PointLight::new(&t * &Point3f::zero(), I * scale)) 35 | } 36 | } 37 | 38 | impl Light for PointLight { 39 | fn id(&self) -> u32 { 40 | self.id 41 | } 42 | 43 | fn sample_li( 44 | &self, 45 | isect: &Interaction, 46 | _u: Point2f, 47 | ) -> (Spectrum, Vector3f, f32, VisibilityTester) { 48 | let wi = self.pos - isect.p; 49 | let r2 = wi.length_squared(); 50 | let l_i = self.emission_colour / (4.0 * PI * r2); 51 | let vt = VisibilityTester::new(*isect, Interaction::from_point(&self.pos)); 52 | 53 | (l_i, wi.normalize(), 1.0, vt) 54 | } 55 | 56 | fn pdf_li(&self, _si: &Interaction, _wi: &Vector3f) -> f32 { 57 | 0.0 58 | } 59 | 60 | fn n_samples(&self) -> u32 { 61 | 1 62 | } 63 | 64 | fn flags(&self) -> LightFlags { 65 | LightFlags::DELTA_POSITION 66 | } 67 | 68 | fn power(&self) -> Spectrum { 69 | 4.0 * PI * self.emission_colour 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rustracer-core/src/shapes/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::bounds::Bounds3f; 4 | use crate::geometry; 5 | use crate::interaction::{Interaction, SurfaceInteraction}; 6 | use crate::ray::Ray; 7 | use crate::{Point2f, Vector3f}; 8 | 9 | mod cylinder; 10 | mod disk; 11 | mod mesh; 12 | pub mod plymesh; 13 | mod sphere; 14 | 15 | pub use self::cylinder::Cylinder; 16 | pub use self::disk::Disk; 17 | pub use self::mesh::{Triangle, TriangleMesh}; 18 | pub use self::sphere::Sphere; 19 | 20 | pub fn init_stats() { 21 | mesh::init_stats(); 22 | } 23 | 24 | pub trait Shape: Debug + Send + Sync { 25 | fn intersect(&self, ray: &Ray) -> Option<(SurfaceInteraction<'_, '_>, f32)>; 26 | 27 | fn intersect_p(&self, ray: &Ray) -> bool { 28 | self.intersect(ray).is_some() 29 | } 30 | 31 | fn area(&self) -> f32; 32 | 33 | fn object_bounds(&self) -> Bounds3f; 34 | 35 | fn world_bounds(&self) -> Bounds3f; 36 | 37 | fn sample(&self, u: Point2f) -> (Interaction, f32); 38 | 39 | fn sample_si(&self, si: &Interaction, u: Point2f) -> (Interaction, f32) { 40 | let (intr, mut pdf) = self.sample(u); 41 | let mut wi = intr.p - si.p; 42 | if wi.length_squared() == 0.0 { 43 | pdf = 0.0; 44 | } else { 45 | wi = wi.normalize(); 46 | pdf *= geometry::distance_squared(&si.p, &intr.p) / (intr.n.dot(&(-wi)).abs()); 47 | if pdf.is_infinite() { 48 | pdf = 0.0; 49 | } 50 | } 51 | 52 | (intr, pdf) 53 | } 54 | 55 | fn pdf(&self, _si: &Interaction) -> f32 { 56 | 1.0 / self.area() 57 | } 58 | 59 | fn pdf_wi(&self, si: &Interaction, wi: &Vector3f) -> f32 { 60 | let ray = si.spawn_ray(wi); 61 | 62 | if let Some((isect_light, _t_hit)) = self.intersect(&ray) { 63 | geometry::distance_squared(&si.p, &isect_light.hit.p) 64 | / (isect_light.hit.n.dot(&(-(*wi))).abs() * self.area()) 65 | } else { 66 | 0.0 67 | } 68 | } 69 | 70 | fn reverse_orientation(&self) -> bool; 71 | 72 | fn transform_swaps_handedness(&self) -> bool; 73 | } 74 | -------------------------------------------------------------------------------- /rustracer-core/src/sampling/mod.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts; 2 | 3 | use crate::{Point2f, Vector2f, Vector3f}; 4 | 5 | mod distribution1d; 6 | mod distribution2d; 7 | 8 | pub use self::distribution1d::Distribution1D; 9 | pub use self::distribution2d::Distribution2D; 10 | 11 | const FRAC_PI_4: f32 = consts::FRAC_PI_2 / 2.0; 12 | 13 | // Inline functions 14 | pub fn uniform_sample_sphere(u: Point2f) -> Vector3f { 15 | let z = 1.0 - 2.0 * u.x; 16 | let r = (1.0 - z * z).max(0.0).sqrt(); 17 | let phi = 2.0 * consts::PI * u.y; 18 | 19 | Vector3f::new(r * phi.cos(), r * phi.sin(), z) 20 | } 21 | 22 | pub fn cosine_sample_hemisphere(u: Point2f) -> Vector3f { 23 | let d = concentric_sample_disk(u); 24 | let z = (1.0 - d.x * d.x - d.y * d.y).max(0.0).sqrt(); 25 | Vector3f::new(d.x, d.y, z) 26 | } 27 | 28 | pub fn concentric_sample_disk(u: Point2f) -> Point2f { 29 | // Map uniform random numbers to `[-1, 1]^2` 30 | let u_offset = 2.0 * u - Vector2f::new(1.0, 1.0); 31 | 32 | // Handle degeneracy at the origin 33 | if u_offset.x == 0.0 && u_offset.y == 0.0 { 34 | return Point2f::new(0.0, 0.0); 35 | } 36 | 37 | // Apply concentric mapping to point 38 | let (r, theta) = if u_offset.x.abs() > u_offset.y.abs() { 39 | (u_offset.x, FRAC_PI_4 * (u_offset.y / u_offset.x)) 40 | } else { 41 | ( 42 | u_offset.y, 43 | consts::FRAC_PI_2 - FRAC_PI_4 * (u_offset.x / u_offset.y), 44 | ) 45 | }; 46 | r * Point2f::new(theta.cos(), theta.sin()) 47 | } 48 | 49 | pub fn uniform_sample_triangle(u: Point2f) -> Point2f { 50 | let su0 = u[0].sqrt(); 51 | Point2f::new(1.0 - su0, u[1] * su0) 52 | } 53 | 54 | pub fn uniform_cone_pdf(cos_theta_max: f32) -> f32 { 55 | 1.0 / (2.0 * consts::PI * (1.0 - cos_theta_max)) 56 | } 57 | 58 | #[inline] 59 | pub fn power_heuristic(nf: u32, f_pdf: f32, ng: u32, g_pdf: f32) -> f32 { 60 | let f = nf as f32 * f_pdf; 61 | let g = ng as f32 * g_pdf; 62 | (f * f) / (f * f + g * g) 63 | } 64 | 65 | // pub fn zero_two_sequence(n: u32, scramble: (u32, u32)) -> (f32, f32) { 66 | // (van_der_corput(n, scramble.0), sobol(n, scramble.1)) 67 | // } 68 | -------------------------------------------------------------------------------- /rustracer-core/src/filter/mitchell.rs: -------------------------------------------------------------------------------- 1 | use crate::filter::Filter; 2 | use crate::paramset::ParamSet; 3 | 4 | pub struct MitchellNetravali { 5 | width: f32, 6 | height: f32, 7 | inv_width: f32, 8 | inv_height: f32, 9 | b: f32, 10 | c: f32, 11 | } 12 | 13 | impl MitchellNetravali { 14 | pub fn new(w: f32, h: f32, b: f32, c: f32) -> MitchellNetravali { 15 | MitchellNetravali { 16 | width: w, 17 | height: h, 18 | inv_width: 1.0 / w, 19 | inv_height: 1.0 / h, 20 | b, 21 | c, 22 | } 23 | } 24 | 25 | fn mitchell_1d(&self, x: f32) -> f32 { 26 | let fx = x.abs() * 2.0; 27 | if fx < 1.0 { 28 | ((12.0 - 9.0 * self.b - 6.0 * self.c) * fx * fx * fx 29 | + (-18.0 + 12.0 * self.b + 6.0 * self.c) * fx * fx 30 | + (6.0 - 2.0 * self.b)) 31 | * (1.0 / 6.0) 32 | } else if fx < 2.0 { 33 | ((-self.b - 6.0 * self.c) * fx * fx * fx 34 | + (6.0 * self.b + 30.0 * self.c) * fx * fx 35 | + (-12.0 * self.b - 48.0 * self.c) * fx 36 | + (8.0 * self.b + 24.0 * self.c)) 37 | * (1.0 / 6.0) 38 | } else { 39 | 0.0 40 | } 41 | } 42 | 43 | pub fn create(ps: &ParamSet) -> Box { 44 | let xw = ps.find_one_float("xwidth", 2.0); 45 | let yw = ps.find_one_float("ywidth", 2.0); 46 | let B = ps.find_one_float("B", 1.0 / 3.0); 47 | let C = ps.find_one_float("C", 1.0 / 3.0); 48 | 49 | Box::new(Self::new(xw, yw, B, C)) 50 | } 51 | } 52 | 53 | impl Filter for MitchellNetravali { 54 | fn evaluate(&self, x: f32, y: f32) -> f32 { 55 | self.mitchell_1d(x * self.inv_width) * self.mitchell_1d(y * self.inv_height) 56 | } 57 | 58 | fn width(&self) -> (f32, f32) { 59 | (self.width, self.height) 60 | } 61 | 62 | fn inv_width(&self) -> (f32, f32) { 63 | (self.inv_width, self.inv_height) 64 | } 65 | } 66 | 67 | impl Default for MitchellNetravali { 68 | fn default() -> Self { 69 | MitchellNetravali::new(2.0, 2.0, 1.0 / 3.0, 1.0 / 3.0) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rustracer-core/src/material/mixmat.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | 5 | use crate::bsdf::{BxDFHolder, ScaledBxDF}; 6 | use crate::interaction::SurfaceInteraction; 7 | use crate::material::{Material, TransportMode}; 8 | use crate::paramset::TextureParams; 9 | use crate::spectrum::Spectrum; 10 | use crate::texture::TextureSpectrum; 11 | 12 | #[derive(Debug)] 13 | pub struct MixMaterial { 14 | mat1: Arc, 15 | mat2: Arc, 16 | scale: Arc, 17 | } 18 | 19 | impl MixMaterial { 20 | pub fn create( 21 | mp: &TextureParams<'_>, 22 | m1: Arc, 23 | m2: Arc, 24 | ) -> Arc { 25 | Arc::new(MixMaterial { 26 | mat1: m1, 27 | mat2: m2, 28 | scale: mp.get_spectrum_texture("amount", &Spectrum::from(0.5)), 29 | }) 30 | } 31 | } 32 | 33 | impl Material for MixMaterial { 34 | fn compute_scattering_functions<'a, 'b>( 35 | &self, 36 | si: &mut SurfaceInteraction<'a, 'b>, 37 | mode: TransportMode, 38 | allow_multiple_lobes: bool, 39 | arena: &'b Allocator<'_>, 40 | ) { 41 | let s1 = self.scale.evaluate(si).clamp(); 42 | let s2 = (Spectrum::white() - s1).clamp(); 43 | let mut si2 = si.clone(); 44 | self.mat1 45 | .compute_scattering_functions(si, mode, allow_multiple_lobes, arena); 46 | self.mat2 47 | .compute_scattering_functions(&mut si2, mode, allow_multiple_lobes, arena); 48 | let n1 = si.bsdf.as_ref().unwrap().bxdfs.len(); 49 | let n2 = si2.bsdf.as_ref().unwrap().bxdfs.len(); 50 | 51 | let mut bxdfs = BxDFHolder::new(arena); 52 | for i in 0..n1 { 53 | bxdfs.add(arena.alloc(ScaledBxDF::new(si.bsdf.as_ref().unwrap().bxdfs[i], s1))); 54 | } 55 | for i in 0..n2 { 56 | bxdfs.add(arena.alloc(ScaledBxDF::new(si2.bsdf.as_ref().unwrap().bxdfs[i], s2))); 57 | } 58 | 59 | si.bsdf.as_mut().map(|b| { 60 | (Arc::get_mut(b)) 61 | .as_mut() 62 | .map(|b| b.bxdfs = bxdfs.into_slice()) 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rustracer-core/src/bsdf/bxdf.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts; 2 | use std::fmt::Debug; 3 | 4 | use super::BxDFType; 5 | use crate::geometry::{abs_cos_theta, same_hemisphere}; 6 | use crate::sampling::cosine_sample_hemisphere; 7 | use crate::spectrum::Spectrum; 8 | use crate::{Point2f, Vector3f}; 9 | 10 | pub trait BxDF: Debug { 11 | /// Evaluate the BxDF for the given incoming and outgoing directions. 12 | fn f(&self, wo: &Vector3f, wi: &Vector3f) -> Spectrum; 13 | 14 | /// Sample the BxDF for the given outgoing direction, using the given pair of uniform samples. 15 | /// 16 | /// The default implementation uses importance sampling by using a cosine-weighted 17 | /// distribution. 18 | fn sample_f(&self, wo: &Vector3f, u: Point2f) -> (Spectrum, Vector3f, f32, BxDFType) { 19 | let mut wi = cosine_sample_hemisphere(u); 20 | if wo.z < 0.0 { 21 | wi.z *= -1.0; 22 | } 23 | let pdf = self.pdf(wo, &wi); 24 | (self.f(wo, &wi), wi, pdf, BxDFType::empty()) 25 | } 26 | // TODO implement rho functions 27 | // fn rho(&self, wo: &Vector3f, n_samples: u32) -> (Point2f, Spectrum); 28 | // fn rho_hh(&self, n_samples: u32) -> (Point2f, Point2f, Spectrum); 29 | fn matches(&self, flags: BxDFType) -> bool { 30 | self.get_type() & flags == self.get_type() 31 | } 32 | 33 | fn get_type(&self) -> BxDFType; 34 | 35 | /// Evaluate the PDF for the given outgoing and incoming directions. 36 | /// 37 | /// Note: this method needs to be consistent with ```BxDF::sample_f()```. 38 | fn pdf(&self, wo: &Vector3f, wi: &Vector3f) -> f32 { 39 | if same_hemisphere(wo, wi) { 40 | abs_cos_theta(wi) * consts::FRAC_1_PI 41 | } else { 42 | 0.0 43 | } 44 | } 45 | } 46 | 47 | #[derive(Debug, Clone, Copy)] 48 | pub struct ScaledBxDF<'a> { 49 | bxdf: &'a dyn BxDF, 50 | scale: Spectrum, 51 | } 52 | 53 | impl<'a> ScaledBxDF<'a> { 54 | pub fn new(bxdf: &'a dyn BxDF, scale: Spectrum) -> ScaledBxDF<'a> { 55 | ScaledBxDF { bxdf, scale } 56 | } 57 | } 58 | 59 | impl<'a> BxDF for ScaledBxDF<'a> { 60 | fn f(&self, wo: &Vector3f, wi: &Vector3f) -> Spectrum { 61 | self.bxdf.f(wo, wi) * self.scale 62 | } 63 | fn sample_f(&self, wo: &Vector3f, sample: Point2f) -> (Spectrum, Vector3f, f32, BxDFType) { 64 | let (spectrum, wi, pdf, bxdftype) = self.bxdf.sample_f(wo, sample); 65 | (spectrum * self.scale, wi, pdf, bxdftype) 66 | } 67 | 68 | fn get_type(&self) -> BxDFType { 69 | self.bxdf.get_type() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rustracer-core/src/light/mod.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use std::fmt::Debug; 3 | 4 | use bitflags::bitflags; 5 | use lazy_static::lazy_static; 6 | use parking_lot::Mutex; 7 | 8 | use crate::interaction::Interaction; 9 | use crate::ray::Ray; 10 | use crate::scene::Scene; 11 | use crate::spectrum::Spectrum; 12 | use crate::{Point2f, Vector3f}; 13 | 14 | mod diffuse; 15 | mod distant; 16 | mod infinite; 17 | mod point; 18 | 19 | pub use self::diffuse::DiffuseAreaLight; 20 | pub use self::distant::DistantLight; 21 | pub use self::infinite::InfiniteAreaLight; 22 | pub use self::point::PointLight; 23 | 24 | bitflags! { 25 | pub struct LightFlags: u32 { 26 | const DELTA_POSITION = 0b_0000_0001; 27 | const DELTA_DIRECTION = 0b_0000_0010; 28 | const AREA = 0b_0000_0100; 29 | const INFINITE = 0b_0000_1000; 30 | } 31 | } 32 | 33 | lazy_static! { 34 | static ref COUNTER: Mutex = Mutex::new(0); 35 | } 36 | 37 | #[inline] 38 | pub fn is_delta_light(flags: LightFlags) -> bool { 39 | flags.contains(LightFlags::DELTA_POSITION) || flags.contains(LightFlags::DELTA_DIRECTION) 40 | } 41 | 42 | pub struct VisibilityTester { 43 | pub p0: Interaction, 44 | pub p1: Interaction, 45 | } 46 | 47 | impl VisibilityTester { 48 | pub fn new(p0: Interaction, p1: Interaction) -> VisibilityTester { 49 | VisibilityTester { p0, p1 } 50 | } 51 | 52 | pub fn unoccluded(&self, scene: &Scene) -> bool { 53 | let r = self.p0.spawn_ray_to_interaction(&self.p1); 54 | !scene.intersect_p(&r) 55 | } 56 | } 57 | 58 | pub fn get_next_id() -> u32 { 59 | let mut counter = COUNTER.lock(); 60 | let id = *counter; 61 | *counter += 1; 62 | 63 | id 64 | } 65 | 66 | pub trait Light: Debug + Send + Sync { 67 | fn id(&self) -> u32; 68 | /// Sample the light source 69 | /// Return a tuple of: 70 | /// * emitted light in the sampled direction 71 | /// * the sampled direction wi 72 | /// * the pdf for that direction 73 | /// * A VisibilityTester 74 | fn sample_li( 75 | &self, 76 | isect: &Interaction, 77 | u: Point2f, 78 | ) -> (Spectrum, Vector3f, f32, VisibilityTester); 79 | 80 | fn pdf_li(&self, si: &Interaction, wi: &Vector3f) -> f32; 81 | 82 | fn preprocess(&self, _scene: &Scene) {} 83 | 84 | fn n_samples(&self) -> u32; 85 | 86 | fn flags(&self) -> LightFlags; 87 | 88 | fn power(&self) -> Spectrum; 89 | 90 | fn le(&self, _ray: &Ray) -> Spectrum { 91 | Spectrum::black() 92 | } 93 | } 94 | 95 | pub trait AreaLight: Light { 96 | fn l(&self, si: &Interaction, w: &Vector3f) -> Spectrum; 97 | } 98 | -------------------------------------------------------------------------------- /rustracer-core/src/material/substrate.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | 5 | use crate::bsdf::{Bsdf, BxDFHolder, FresnelBlend, TrowbridgeReitzDistribution}; 6 | use crate::interaction::SurfaceInteraction; 7 | use crate::material::{Material, TransportMode}; 8 | use crate::paramset::TextureParams; 9 | use crate::spectrum::Spectrum; 10 | use crate::texture::{TextureFloat, TextureSpectrum}; 11 | 12 | #[derive(Debug)] 13 | pub struct SubstrateMaterial { 14 | kd: Arc, 15 | ks: Arc, 16 | nu: Arc, 17 | nv: Arc, 18 | bump_map: Option>, 19 | remap_roughness: bool, 20 | } 21 | 22 | impl SubstrateMaterial { 23 | pub fn create(mp: &TextureParams<'_>) -> Arc { 24 | let kd = mp.get_spectrum_texture("Kd", &Spectrum::grey(0.5)); 25 | let ks = mp.get_spectrum_texture("Ks", &Spectrum::grey(0.5)); 26 | let urough = mp.get_float_texture("uroughness", 0.1); 27 | let vrough = mp.get_float_texture("vroughness", 0.1); 28 | let bump_map = mp.get_float_texture_or_none("bumpmap"); 29 | let remap_roughness = mp.find_bool("remaproughness", true); 30 | 31 | Arc::new(SubstrateMaterial { 32 | kd, 33 | ks, 34 | nu: urough, 35 | nv: vrough, 36 | bump_map, 37 | remap_roughness, 38 | }) 39 | } 40 | } 41 | 42 | impl Material for SubstrateMaterial { 43 | fn compute_scattering_functions<'a, 'b>( 44 | &self, 45 | si: &mut SurfaceInteraction<'a, 'b>, 46 | _mode: TransportMode, 47 | _allow_multiple_lobes: bool, 48 | arena: &'b Allocator<'_>, 49 | ) { 50 | if let Some(ref bump) = self.bump_map { 51 | super::bump(bump, si); 52 | } 53 | let mut bxdfs = BxDFHolder::new(arena); 54 | 55 | let d = self.kd.evaluate(si).clamp(); 56 | let s = self.ks.evaluate(si).clamp(); 57 | let mut roughu = self.nu.evaluate(si); 58 | let mut roughv = self.nv.evaluate(si); 59 | 60 | if !d.is_black() || !s.is_black() { 61 | if self.remap_roughness { 62 | roughu = TrowbridgeReitzDistribution::roughness_to_alpha(roughu); 63 | roughv = TrowbridgeReitzDistribution::roughness_to_alpha(roughv); 64 | } 65 | let distrib = arena.alloc(TrowbridgeReitzDistribution::new(roughu, roughv)); 66 | bxdfs.add(arena.alloc(FresnelBlend::new(s, d, distrib))); 67 | } 68 | 69 | let bsdf = Bsdf::new(si, 1.0, bxdfs.into_slice()); 70 | si.bsdf = Some(Arc::new(bsdf)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /rustracer-core/src/material/plastic.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | use log::info; 5 | 6 | use crate::bsdf::{ 7 | dielectric, Bsdf, BxDFHolder, LambertianReflection, MicrofacetReflection, 8 | TrowbridgeReitzDistribution, 9 | }; 10 | use crate::interaction::SurfaceInteraction; 11 | use crate::material::{Material, TransportMode}; 12 | use crate::paramset::TextureParams; 13 | use crate::spectrum::Spectrum; 14 | use crate::texture::{TextureFloat, TextureSpectrum}; 15 | 16 | #[derive(Debug)] 17 | pub struct Plastic { 18 | kd: Arc, 19 | ks: Arc, 20 | roughness: Arc, 21 | bump_map: Option>, 22 | remap_roughness: bool, 23 | } 24 | 25 | impl Plastic { 26 | pub fn create(mp: &TextureParams<'_>) -> Arc { 27 | info!("Creating Plastic material"); 28 | let Kd = mp.get_spectrum_texture("Kd", &Spectrum::grey(0.25)); 29 | let Ks = mp.get_spectrum_texture("Ks", &Spectrum::grey(0.25)); 30 | let roughness = mp.get_float_texture("roughness", 0.1); 31 | let bump_map = mp.get_float_texture_or_none("bumpmap"); 32 | let remap_roughness = mp.find_bool("remaproughness", true); 33 | 34 | Arc::new(Plastic { 35 | kd: Kd, 36 | ks: Ks, 37 | roughness, 38 | bump_map, 39 | remap_roughness, 40 | }) 41 | } 42 | } 43 | 44 | impl Material for Plastic { 45 | fn compute_scattering_functions<'a, 'b>( 46 | &self, 47 | si: &mut SurfaceInteraction<'a, 'b>, 48 | _mode: TransportMode, 49 | _allow_multiple_lobes: bool, 50 | arena: &'b Allocator<'_>, 51 | ) { 52 | if let Some(ref bump) = self.bump_map { 53 | super::bump(bump, si); 54 | } 55 | let kd = self.kd.evaluate(si); 56 | let ks = self.ks.evaluate(si); 57 | 58 | let mut bxdfs = BxDFHolder::new(arena); 59 | if !kd.is_black() { 60 | bxdfs.add(arena.alloc(LambertianReflection::new(kd))); 61 | } 62 | if !ks.is_black() { 63 | let fresnel = arena.alloc(dielectric(1.5, 1.0)); 64 | let mut roughness = self.roughness.evaluate(si); 65 | if self.remap_roughness { 66 | roughness = TrowbridgeReitzDistribution::roughness_to_alpha(roughness); 67 | } 68 | let distrib = arena.alloc(TrowbridgeReitzDistribution::new(roughness, roughness)); 69 | bxdfs.add(arena.alloc(MicrofacetReflection::new(ks, distrib, fresnel))); 70 | } 71 | 72 | let bsdf: Bsdf<'b> = Bsdf::new(si, 1.0, bxdfs.into_slice()); 73 | si.bsdf = Some(Arc::new(bsdf)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rustracer-core/src/light/distant.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | use std::sync::Arc; 3 | 4 | use num::Zero; 5 | use parking_lot::RwLock; 6 | 7 | use crate::interaction::Interaction; 8 | use crate::light::{Light, LightFlags, VisibilityTester}; 9 | use crate::paramset::ParamSet; 10 | use crate::scene::Scene; 11 | use crate::spectrum::Spectrum; 12 | use crate::{Point2f, Point3f, Transform, Vector3f}; 13 | 14 | #[derive(Debug)] 15 | pub struct DistantLight { 16 | id: u32, 17 | dir: Vector3f, 18 | emission_colour: Spectrum, 19 | w_center: RwLock, 20 | w_radius: RwLock, 21 | } 22 | 23 | impl DistantLight { 24 | pub fn new(dir: Vector3f, ec: Spectrum) -> DistantLight { 25 | DistantLight { 26 | id: super::get_next_id(), 27 | dir: dir.normalize(), 28 | emission_colour: ec, 29 | w_center: RwLock::new(Point3f::new(0.0, 0.0, 0.0)), 30 | w_radius: RwLock::new(0.0), 31 | } 32 | } 33 | 34 | pub fn create(l2w: &Transform, params: &ParamSet) -> Arc { 35 | let L = params.find_one_spectrum("L", Spectrum::white()); 36 | let scale = params.find_one_spectrum("scale", Spectrum::white()); 37 | let from = params.find_one_point3f("from", Point3f::zero()); 38 | let to = params.find_one_point3f("to", Point3f::new(0.0, 0.0, 1.0)); 39 | let dir = from - to; 40 | Arc::new(DistantLight::new(l2w * &dir, L * scale)) 41 | } 42 | } 43 | 44 | impl Light for DistantLight { 45 | fn id(&self) -> u32 { 46 | self.id 47 | } 48 | 49 | fn preprocess(&self, scene: &Scene) { 50 | let (w_center, w_radius) = scene.world_bounds().bounding_sphere(); 51 | let mut wc = self.w_center.write(); 52 | *wc = w_center; 53 | let mut wr = self.w_radius.write(); 54 | *wr = w_radius; 55 | } 56 | 57 | fn sample_li( 58 | &self, 59 | isect: &Interaction, 60 | _u: Point2f, 61 | ) -> (Spectrum, Vector3f, f32, VisibilityTester) { 62 | let wr = self.w_radius.read(); 63 | let p_outside = isect.p + self.dir * (2.0 * *wr); 64 | ( 65 | self.emission_colour, 66 | self.dir, 67 | 1.0, 68 | VisibilityTester::new(*isect, Interaction::from_point(&p_outside)), 69 | ) 70 | } 71 | 72 | fn pdf_li(&self, _si: &Interaction, _wi: &Vector3f) -> f32 { 73 | 0.0 74 | } 75 | 76 | fn n_samples(&self) -> u32 { 77 | 1 78 | } 79 | 80 | fn flags(&self) -> LightFlags { 81 | LightFlags::DELTA_DIRECTION 82 | } 83 | 84 | fn power(&self) -> Spectrum { 85 | let wr = self.w_radius.read(); 86 | self.emission_colour * PI * *wr * *wr 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /rustracer-core/src/pbrt/mod.rs: -------------------------------------------------------------------------------- 1 | mod lexer; 2 | mod parser; 3 | 4 | use std::fs::File; 5 | use std::io::prelude::*; 6 | use std::path::Path; 7 | 8 | use anyhow::*; 9 | 10 | use crate::api::{Api, RealApi}; 11 | use crate::fileutil; 12 | use crate::pbrt::lexer::Tokens; 13 | use crate::PbrtOptions; 14 | 15 | pub fn parse_scene>(opts: PbrtOptions, filename: P) -> Result<()> { 16 | let filename = filename.as_ref(); 17 | let tokens = tokenize_file(filename)?; 18 | fileutil::set_search_directory(fileutil::directory_containing(filename)); 19 | let api = RealApi::with_options(opts); 20 | api.init()?; 21 | parser::parse(Tokens::new(&tokens[..]), &api) 22 | .map_err(|e| format_err!("Failed to parse scene file: {:?}", e))?; 23 | 24 | Ok(()) 25 | } 26 | 27 | pub fn tokenize_file>(filename: P) -> Result> { 28 | let resolved_filename = fileutil::resolve_filename(filename.as_ref().to_str().unwrap()); 29 | let mut file = File::open(&resolved_filename).context("Failed to open scene file")?; 30 | let mut file_content = String::new(); 31 | file.read_to_string(&mut file_content) 32 | .context("Failed to read content of scene file")?; 33 | 34 | // TODO handle errors 35 | let (_rest, tokens) = lexer::tokenize(&file_content[..]) 36 | .map_err(|e| format_err!("Failed to tokenize scene file: {:?}", e))?; 37 | // strip comments 38 | let filtered_tokens = tokens 39 | .into_iter() 40 | .filter(|x| *x != lexer::Token::COMMENT) 41 | .collect::>(); 42 | 43 | Ok(filtered_tokens) 44 | } 45 | 46 | #[ignore] 47 | #[test] 48 | fn test_parse_scene() { 49 | let scene = r##" 50 | LookAt 0 0 5 0 0 0 0 1 0 51 | Camera "perspective" "float fov" [50] 52 | Sampler "02sequence" 53 | 54 | Film "image" "integer xresolution" [800] "integer yresolution" [600] 55 | "string filename" "test-whitted.tga" 56 | 57 | Integrator "whitted" 58 | #Integrator "directlighting" 59 | 60 | WorldBegin 61 | LightSource "distant" "point from" [0 1 5] "point to" [0 0 0] 62 | 63 | #Material "matte" "rgb Kd" [1.0 0.0 0.0] "float sigma" [20] 64 | AttributeBegin 65 | #Material "matte" "rgb Kd" [1.0 0.0 0.0] 66 | Material "plastic" "rgb Kd" [1.0 0.0 0.0] "rgb Ks" [1.0 1.0 1.0] 67 | 68 | Shape "sphere" 69 | AttributeEnd 70 | 71 | AttributeBegin 72 | Rotate -90 1 0 0 73 | Material "matte" "rgb Kd" [1.0 1.0 1.0] 74 | Shape "disk" "float radius" [20] "float height" [-1] 75 | AttributeEnd 76 | 77 | AttributeBegin 78 | AreaLightSource "diffuse" "rgb L" [2.0 2.0 2.0] 79 | Rotate 90 1 0 0 80 | Shape "disk" "float height" [-2] "float radius" [0.5] 81 | AttributeEnd 82 | WorldEnd 83 | "##; 84 | 85 | parse_scene(PbrtOptions::default(), scene).unwrap(); 86 | } 87 | -------------------------------------------------------------------------------- /rustracer-core/src/light/diffuse.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | use std::sync::Arc; 3 | 4 | use crate::interaction::Interaction; 5 | use crate::light::{AreaLight, Light, LightFlags, VisibilityTester}; 6 | use crate::paramset::ParamSet; 7 | use crate::shapes::Shape; 8 | use crate::spectrum::Spectrum; 9 | use crate::{Point2f, Transform, Vector3f}; 10 | 11 | #[derive(Debug)] 12 | pub struct DiffuseAreaLight { 13 | id: u32, 14 | l_emit: Spectrum, 15 | shape: Arc, 16 | n_samples: u32, 17 | two_sided: bool, 18 | area: f32, 19 | } 20 | 21 | impl DiffuseAreaLight { 22 | pub fn new( 23 | l_emit: Spectrum, 24 | shape: Arc, 25 | n_samples: u32, 26 | two_sided: bool, 27 | ) -> DiffuseAreaLight { 28 | let area = shape.area(); 29 | DiffuseAreaLight { 30 | id: super::get_next_id(), 31 | l_emit, 32 | shape, 33 | n_samples, 34 | two_sided, 35 | area, 36 | } 37 | } 38 | 39 | pub fn create( 40 | _light2world: &Transform, 41 | ps: &ParamSet, 42 | shape: Arc, 43 | ) -> Arc { 44 | let L = ps.find_one_spectrum("L", Spectrum::white()); 45 | let sc = ps.find_one_spectrum("scale", Spectrum::white()); 46 | let nsamples = ps.find_one_int("nsamples", 1); 47 | let nsamples = ps.find_one_int("samples", nsamples); 48 | let two_sided = ps.find_one_bool("twosided", false); 49 | 50 | Arc::new(Self::new(L * sc, shape, nsamples as u32, two_sided)) 51 | } 52 | } 53 | 54 | impl Light for DiffuseAreaLight { 55 | fn id(&self) -> u32 { 56 | self.id 57 | } 58 | 59 | fn sample_li( 60 | &self, 61 | si: &Interaction, 62 | u: Point2f, 63 | ) -> (Spectrum, Vector3f, f32, VisibilityTester) { 64 | let (p_shape, pdf) = self.shape.sample_si(si, u); 65 | assert!(!p_shape.p.x.is_nan() && !p_shape.p.y.is_nan() && !p_shape.p.z.is_nan()); 66 | let wi = (p_shape.p - si.p).normalize(); 67 | let vis = VisibilityTester::new(*si, p_shape); 68 | 69 | (self.l(&p_shape, &(-wi)), wi, pdf, vis) 70 | } 71 | 72 | fn pdf_li(&self, si: &Interaction, wi: &Vector3f) -> f32 { 73 | self.shape.pdf_wi(si, wi) 74 | } 75 | 76 | fn n_samples(&self) -> u32 { 77 | self.n_samples 78 | } 79 | 80 | fn flags(&self) -> LightFlags { 81 | LightFlags::AREA 82 | } 83 | 84 | fn power(&self) -> Spectrum { 85 | let factor = if self.two_sided { 2.0 } else { 1.0 }; 86 | factor * self.l_emit * PI * self.area 87 | } 88 | } 89 | 90 | impl AreaLight for DiffuseAreaLight { 91 | fn l(&self, si: &Interaction, w: &Vector3f) -> Spectrum { 92 | if self.two_sided || si.n.dot(w) > 0.0 { 93 | self.l_emit 94 | } else { 95 | Spectrum::black() 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /rustracer-core/src/blockedarray.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::ops::{Index, IndexMut}; 3 | 4 | use num::{zero, Zero}; 5 | 6 | /// The logarithm (in base-2) of the block size. This ensures that the block size is a power of 2. 7 | /// In this case, 3 means a block size of 8. 8 | const LOG_BLOCK_SIZE: usize = 3; 9 | const BLOCK_SIZE: usize = 1 << LOG_BLOCK_SIZE; 10 | 11 | #[derive(Debug)] 12 | pub struct BlockedArray { 13 | u_res: usize, 14 | v_res: usize, 15 | u_blocks: usize, 16 | log_block_size: usize, 17 | block_size: usize, 18 | data: Vec, 19 | } 20 | 21 | impl BlockedArray 22 | where 23 | T: Copy + Zero + Debug, 24 | { 25 | pub fn new(u_res: usize, v_res: usize) -> BlockedArray { 26 | let data = vec![zero(); round_up(u_res) * round_up(v_res)]; 27 | 28 | BlockedArray { 29 | u_res, 30 | v_res, 31 | u_blocks: round_up(u_res) >> LOG_BLOCK_SIZE, 32 | log_block_size: LOG_BLOCK_SIZE, 33 | block_size: BLOCK_SIZE, 34 | data, 35 | } 36 | } 37 | 38 | pub fn new_from(u_res: usize, v_res: usize, d: &[T]) -> BlockedArray { 39 | let mut ba = Self::new(u_res, v_res); 40 | 41 | for v in 0..v_res { 42 | for u in 0..u_res { 43 | ba[(u, v)] = d[v * u_res + u] 44 | } 45 | } 46 | 47 | ba 48 | } 49 | 50 | pub fn u_size(&self) -> usize { 51 | self.u_res 52 | } 53 | 54 | pub fn v_size(&self) -> usize { 55 | self.v_res 56 | } 57 | 58 | pub fn block_size(&self) -> usize { 59 | self.block_size 60 | } 61 | 62 | pub fn block(&self, a: usize) -> usize { 63 | a >> self.log_block_size 64 | } 65 | 66 | pub fn offset(&self, a: usize) -> usize { 67 | a & (self.block_size() - 1) 68 | } 69 | } 70 | 71 | fn round_up(x: usize) -> usize { 72 | (x + BLOCK_SIZE - 1) & !(BLOCK_SIZE - 1) 73 | } 74 | 75 | impl Index<(usize, usize)> for BlockedArray 76 | where 77 | T: Copy, 78 | T: Zero, 79 | T: Debug, 80 | { 81 | type Output = T; 82 | 83 | fn index(&self, (u, v): (usize, usize)) -> &T { 84 | let bu = self.block(u); 85 | let bv = self.block(v); 86 | let ou = self.offset(u); 87 | let ov = self.offset(v); 88 | let offset = self.block_size() * self.block_size() * (self.u_blocks * bv + bu) 89 | + self.block_size() * ov 90 | + ou; 91 | &self.data[offset] 92 | } 93 | } 94 | 95 | impl IndexMut<(usize, usize)> for BlockedArray 96 | where 97 | T: Copy, 98 | T: Zero, 99 | T: Debug, 100 | { 101 | fn index_mut(&mut self, (u, v): (usize, usize)) -> &mut T { 102 | let bu = self.block(u); 103 | let bv = self.block(v); 104 | let ou = self.offset(u); 105 | let ov = self.offset(v); 106 | let offset = self.block_size() * self.block_size() * (self.u_blocks * bv + bu) 107 | + self.block_size() * ov 108 | + ou; 109 | &mut self.data[offset] 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /rustracer-core/src/material/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::sync::Arc; 3 | 4 | use light_arena::Allocator; 5 | 6 | use crate::interaction::SurfaceInteraction; 7 | use crate::texture::Texture; 8 | use crate::{Normal3f, Vector2f, Vector3f}; 9 | 10 | mod disney; 11 | mod fourier; 12 | mod glass; 13 | mod matte; 14 | mod metal; 15 | mod mirror; 16 | mod mixmat; 17 | mod plastic; 18 | mod substrate; 19 | mod translucent; 20 | mod uber; 21 | 22 | pub use self::disney::DisneyMaterial; 23 | pub use self::fourier::FourierMaterial; 24 | pub use self::glass::GlassMaterial; 25 | pub use self::matte::MatteMaterial; 26 | pub use self::metal::Metal; 27 | pub use self::mirror::MirrorMaterial; 28 | pub use self::mixmat::MixMaterial; 29 | pub use self::plastic::Plastic; 30 | pub use self::substrate::SubstrateMaterial; 31 | pub use self::translucent::TranslucentMaterial; 32 | pub use self::uber::UberMaterial; 33 | 34 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 35 | pub enum TransportMode { 36 | RADIANCE, 37 | IMPORTANCE, 38 | } 39 | 40 | pub trait Material: Debug + Send + Sync { 41 | fn compute_scattering_functions<'a, 'b>( 42 | &self, 43 | isect: &mut SurfaceInteraction<'a, 'b>, 44 | mode: TransportMode, 45 | allow_multiple_lobes: bool, 46 | arena: &'b Allocator<'_>, 47 | ); 48 | } 49 | 50 | pub fn bump(d: &Arc>, si: &mut SurfaceInteraction<'_, '_>) { 51 | // Compute offset positions and evaluate displacement texture 52 | let mut si_eval = si.clone(); 53 | 54 | // Shift si_eval du in the u direction 55 | let mut du = 0.5 * (si.dudx.abs() + si.dudy.abs()); 56 | // The most common reason for du to be zero is for ray that start from 57 | // light sources, where no differentials are available. In this case, 58 | // we try to choose a small enough du so that we still get a decently 59 | // accurate bump value. 60 | if du == 0.0 { 61 | du = 0.0005; 62 | } 63 | si_eval.hit.p = si.hit.p + du * si.shading.dpdu; 64 | si_eval.uv = si.uv + Vector2f::new(du, 0.0); 65 | si_eval.hit.n = 66 | (Normal3f::from(si.shading.dpdu.cross(&si.shading.dpdv)) + du * si.dndu).normalize(); 67 | let u_displace = d.evaluate(&si_eval); 68 | 69 | // Shift si_eval dv in the v direction 70 | let mut dv = 0.5 * (si.dvdx.abs() + si.dvdy.abs()); 71 | if dv == 0.0 { 72 | dv = 0.0005; 73 | } 74 | si_eval.hit.p = si.hit.p + dv * si.shading.dpdv; 75 | si_eval.uv = si.uv + Vector2f::new(0.0, dv); 76 | si_eval.hit.n = 77 | (Normal3f::from(si.shading.dpdu.cross(&si.shading.dpdv)) + dv * si.dndv).normalize(); 78 | let v_displace = d.evaluate(&si_eval); 79 | 80 | let displace = d.evaluate(si); 81 | 82 | // Compute bump-mapped differential geometry 83 | let dpdu = si.shading.dpdu 84 | + (u_displace - displace) / du * Vector3f::from(si.shading.n) 85 | + displace * Vector3f::from(si.shading.dndu); 86 | let dpdv = si.shading.dpdv 87 | + (v_displace - displace) / dv * Vector3f::from(si.shading.n) 88 | + displace * Vector3f::from(si.shading.dndv); 89 | let dndu = si.shading.dndu; 90 | let dndv = si.shading.dndv; 91 | si.set_shading_geometry(&dpdu, &dpdv, &dndu, &dndv, false); 92 | } 93 | -------------------------------------------------------------------------------- /rustracer-core/src/sampling/distribution1d.rs: -------------------------------------------------------------------------------- 1 | use crate::find_interval; 2 | 3 | #[derive(Debug)] 4 | pub struct Distribution1D { 5 | pub func: Vec, 6 | cdf: Vec, 7 | pub func_int: f32, 8 | } 9 | 10 | impl Distribution1D { 11 | pub fn new(f: &[f32]) -> Distribution1D { 12 | let n = f.len(); 13 | let func = Vec::from(f); 14 | let mut cdf = vec![0.0; n + 1]; 15 | // compute integral of step function at xi 16 | cdf[0] = 0.0; 17 | for i in 1..(n + 1) { 18 | cdf[i] = cdf[i - 1] + func[i - 1] / n as f32; 19 | } 20 | // transform step function integral into CDF 21 | let func_int = cdf[n]; 22 | if func_int == 0.0 { 23 | cdf.iter_mut() 24 | .enumerate() 25 | .skip(1) 26 | .for_each(|(i, v)| *v = i as f32 / n as f32); 27 | // for i in 1..(n + 1) { 28 | // cdf[i] = i as f32 / n as f32; 29 | // } 30 | } else { 31 | cdf.iter_mut().skip(1).for_each(|v| *v /= func_int); 32 | // for i in 1..(n + 1) { 33 | // cdf[i] /= func_int; 34 | // } 35 | } 36 | 37 | Distribution1D { 38 | func, 39 | cdf, 40 | func_int, 41 | } 42 | } 43 | 44 | pub fn count(&self) -> usize { 45 | self.func.len() 46 | } 47 | 48 | pub fn sample_continuous(&self, u: f32) -> (f32, f32, usize) { 49 | // Find surrounding CDF segments and offset 50 | let offset = find_interval(self.cdf.len(), |i| self.cdf[i] <= u); 51 | // compute offset along CDF segment 52 | let mut du = u - self.cdf[offset]; 53 | if self.cdf[offset + 1] - self.cdf[offset] > 0.0 { 54 | assert!(self.cdf[offset + 1] > self.cdf[offset]); 55 | du /= self.cdf[offset + 1] - self.cdf[offset]; 56 | } 57 | // compute PDF for sampled offset 58 | let pdf = if self.func_int > 0.0 { 59 | self.func[offset] / self.func_int 60 | } else { 61 | 0.0 62 | }; 63 | 64 | // return x ∈ [0,1) corresponding to sample 65 | let x = (offset as f32 + du) / self.count() as f32; 66 | 67 | (x, pdf, offset) 68 | } 69 | 70 | pub fn sample_discrete(&self, u: f32) -> (usize, f32) { 71 | let offset = find_interval(self.cdf.len(), |i| self.cdf[i] <= u); 72 | let pdf = if self.func_int > 0.0 { 73 | self.func[offset] / (self.func_int * self.count() as f32) 74 | } else { 75 | 0.0 76 | }; 77 | 78 | (offset, pdf) 79 | } 80 | 81 | // TODO pdf_discrete 82 | } 83 | 84 | #[test] 85 | fn test_discrete() { 86 | let func = [0.0, 1.0, 0.0, 3.0]; 87 | let distrib = Distribution1D::new(&func[..]); 88 | 89 | assert_eq!(4, distrib.count()); 90 | 91 | assert_eq!((1, 0.25), distrib.sample_discrete(0.0)); 92 | assert_eq!((1, 0.25), distrib.sample_discrete(0.125)); 93 | assert_eq!((1, 0.25), distrib.sample_discrete(0.24999)); 94 | assert_eq!((3, 0.75), distrib.sample_discrete(0.250001)); 95 | assert_eq!((3, 0.75), distrib.sample_discrete(0.625)); 96 | assert_eq!((3, 0.75), distrib.sample_discrete(crate::ONE_MINUS_EPSILON)); 97 | assert_eq!((3, 0.75), distrib.sample_discrete(1.0)); 98 | } 99 | -------------------------------------------------------------------------------- /rustracer-core/src/texture/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::interaction::SurfaceInteraction; 4 | use crate::spectrum::Spectrum; 5 | use crate::{Point2f, Point3f, Transform, Vector2f, Vector3f}; 6 | 7 | mod checkerboard; 8 | mod constant; 9 | mod fbm; 10 | mod imagemap; 11 | mod mix; 12 | mod scale; 13 | mod uv; 14 | 15 | pub use self::checkerboard::CheckerboardTexture; 16 | pub use self::constant::ConstantTexture; 17 | pub use self::fbm::FbmTexture; 18 | pub use self::imagemap::ImageTexture; 19 | pub use self::mix::MixTexture; 20 | pub use self::scale::ScaleTexture; 21 | pub use self::uv::UVTexture; 22 | 23 | pub trait Texture: Debug + Send + Sync { 24 | fn evaluate(&self, si: &SurfaceInteraction<'_, '_>) -> T; 25 | } 26 | 27 | // Some convenient aliases 28 | pub type TextureSpectrum = dyn Texture; 29 | pub type TextureFloat = dyn Texture; 30 | 31 | // Texture mappings 32 | 33 | pub trait TextureMapping2D: Debug + Send + Sync { 34 | fn map(&self, si: &SurfaceInteraction<'_, '_>) -> (Point2f, Vector2f, Vector2f); 35 | } 36 | 37 | #[derive(Debug)] 38 | pub struct UVMapping2D { 39 | su: f32, 40 | sv: f32, 41 | du: f32, 42 | dv: f32, 43 | } 44 | 45 | impl UVMapping2D { 46 | pub fn new(su: f32, sv: f32, du: f32, dv: f32) -> UVMapping2D { 47 | UVMapping2D { su, sv, du, dv } 48 | } 49 | } 50 | 51 | impl TextureMapping2D for UVMapping2D { 52 | fn map(&self, si: &SurfaceInteraction<'_, '_>) -> (Point2f, Vector2f, Vector2f) { 53 | ( 54 | Point2f::new(self.su * si.uv.x + self.du, self.sv * si.uv.y + self.dv), 55 | // dstdx 56 | Vector2f::new(self.su * si.dudx, self.sv * si.dvdx), 57 | // dstdy 58 | Vector2f::new(self.su * si.dudy, self.sv * si.dvdy), 59 | ) 60 | } 61 | } 62 | 63 | #[derive(Debug)] 64 | struct PlanarMapping2D { 65 | vs: Vector3f, 66 | vt: Vector3f, 67 | ds: f32, 68 | dt: f32, 69 | } 70 | 71 | impl PlanarMapping2D { 72 | pub fn new(vs: Vector3f, vt: Vector3f, ds: f32, dt: f32) -> PlanarMapping2D { 73 | PlanarMapping2D { vs, vt, ds, dt } 74 | } 75 | } 76 | 77 | impl TextureMapping2D for PlanarMapping2D { 78 | fn map(&self, si: &SurfaceInteraction<'_, '_>) -> (Point2f, Vector2f, Vector2f) { 79 | let vec = Vector3f::from(si.hit.p); 80 | ( 81 | Point2f::new(self.ds + vec.dot(&self.vs), self.dt + vec.dot(&self.vt)), 82 | Vector2f::new(si.dpdx.dot(&self.vs), si.dpdx.dot(&self.vt)), 83 | Vector2f::new(si.dpdy.dot(&self.vs), si.dpdy.dot(&self.vt)), 84 | ) 85 | } 86 | } 87 | 88 | pub trait TextureMapping3D: Debug + Send + Sync { 89 | fn map(&self, si: &SurfaceInteraction<'_, '_>) -> (Point3f, Vector3f, Vector3f); 90 | } 91 | 92 | #[derive(Debug, Default)] 93 | pub struct IdentityMapping3D { 94 | world_to_texture: Transform, 95 | } 96 | 97 | impl IdentityMapping3D { 98 | pub fn new(tex2world: Transform) -> IdentityMapping3D { 99 | IdentityMapping3D { 100 | world_to_texture: tex2world, 101 | } 102 | } 103 | } 104 | 105 | impl TextureMapping3D for IdentityMapping3D { 106 | fn map(&self, si: &SurfaceInteraction<'_, '_>) -> (Point3f, Vector3f, Vector3f) { 107 | let dpdx = &self.world_to_texture * &si.dpdx; 108 | let dpdy = &self.world_to_texture * &si.dpdy; 109 | let p = &self.world_to_texture * &si.hit.p; 110 | 111 | (p, dpdx, dpdy) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /rustracer-core/src/integrator/whitted.rs: -------------------------------------------------------------------------------- 1 | use light_arena::Allocator; 2 | 3 | use crate::bounds::Bounds2i; 4 | use crate::bsdf; 5 | use crate::integrator::SamplerIntegrator; 6 | use crate::material::TransportMode; 7 | use crate::paramset::ParamSet; 8 | use crate::ray::Ray; 9 | use crate::sampler::Sampler; 10 | use crate::scene::Scene; 11 | use crate::spectrum::Spectrum; 12 | 13 | /// Simple integrator using the original Whitted recursive algorithm. Only handles direct illumination. See 14 | /// ```DirectLightingIntegrator``` for a slighly better integrator that uses better light sampling. 15 | pub struct Whitted { 16 | pixel_bounds: Bounds2i, 17 | /// Maximum number of times a ray can bounce before being terminated. 18 | pub max_ray_depth: u8, 19 | } 20 | 21 | impl Whitted { 22 | pub fn new(n: u8) -> Whitted { 23 | Whitted { 24 | max_ray_depth: n, 25 | pixel_bounds: Bounds2i::new(), 26 | } 27 | } 28 | 29 | pub fn create(ps: &ParamSet) -> Box { 30 | let max_depth = ps.find_one_int("maxdepth", 5); 31 | // TODO pixel_bounds 32 | Box::new(Self::new(max_depth as u8)) 33 | } 34 | } 35 | 36 | impl SamplerIntegrator for Whitted { 37 | fn pixel_bounds(&self) -> &Bounds2i { 38 | &self.pixel_bounds 39 | } 40 | 41 | fn li( 42 | &self, 43 | scene: &Scene, 44 | ray: &mut Ray, 45 | sampler: &mut dyn Sampler, 46 | arena: &Allocator<'_>, 47 | depth: u32, 48 | ) -> Spectrum { 49 | let mut colour = Spectrum::black(); 50 | 51 | match scene.intersect(ray) { 52 | Some(mut isect) => { 53 | let n = isect.shading.n; 54 | let wo = isect.hit.wo; 55 | 56 | // Compute scattering functions for surface interaction 57 | isect.compute_scattering_functions(ray, TransportMode::RADIANCE, false, arena); 58 | 59 | // Yuck, there's got to be a better way to do this FIXME 60 | if isect.bsdf.is_none() { 61 | let mut r = isect.spawn_ray(&ray.d); 62 | return self.li(scene, &mut r, sampler, arena, depth); 63 | } 64 | let bsdf = isect.bsdf.clone().unwrap(); 65 | 66 | // Compute emitted light if ray hit an area light source 67 | colour += isect.le(&wo); 68 | 69 | // Add contribution of each light source 70 | for light in &scene.lights { 71 | let (li, wi, pdf, visibility_tester) = 72 | light.sample_li(&isect.hit, sampler.get_2d()); 73 | if li.is_black() || pdf == 0.0 { 74 | continue; 75 | } 76 | 77 | let f = bsdf.f(&wo, &wi, bsdf::BxDFType::all()); 78 | if !f.is_black() && visibility_tester.unoccluded(scene) { 79 | colour += f * li * wi.dotn(&n).abs() / pdf; 80 | } 81 | } 82 | 83 | if depth + 1 < u32::from(self.max_ray_depth) { 84 | colour += 85 | self.specular_reflection(ray, &isect, scene, &bsdf, sampler, arena, depth); 86 | colour += self 87 | .specular_transmission(ray, &isect, scene, &bsdf, sampler, arena, depth); 88 | } 89 | } 90 | None => { 91 | colour = scene 92 | .lights 93 | .iter() 94 | .fold(Spectrum::black(), |c, l| c + l.le(ray)); 95 | } 96 | } 97 | 98 | colour 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /rustracer-core/src/primitive.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::sync::Arc; 3 | 4 | use light_arena::Allocator; 5 | 6 | use crate::bounds::Bounds3f; 7 | use crate::interaction::SurfaceInteraction; 8 | use crate::light::AreaLight; 9 | use crate::material::{Material, TransportMode}; 10 | use crate::ray::Ray; 11 | use crate::shapes::Shape; 12 | use crate::Transform; 13 | 14 | pub trait Primitive: Debug + Send + Sync { 15 | fn world_bounds(&self) -> Bounds3f; 16 | 17 | fn intersect(&self, ray: &mut Ray) -> Option>; 18 | 19 | fn intersect_p(&self, ray: &Ray) -> bool; 20 | 21 | fn area_light(&self) -> Option>; 22 | 23 | fn material(&self) -> Option>; 24 | fn compute_scattering_functions<'a, 'b>( 25 | &self, 26 | isect: &mut SurfaceInteraction<'a, 'b>, 27 | mode: TransportMode, 28 | allow_multiple_lobes: bool, 29 | arena: &'b Allocator<'_>, 30 | ); 31 | } 32 | 33 | #[derive(Debug)] 34 | pub struct GeometricPrimitive { 35 | pub shape: Arc, 36 | pub area_light: Option>, 37 | pub material: Option>, 38 | } 39 | 40 | impl Primitive for GeometricPrimitive { 41 | fn world_bounds(&self) -> Bounds3f { 42 | self.shape.world_bounds() 43 | } 44 | 45 | fn intersect(&self, ray: &mut Ray) -> Option> { 46 | self.shape.intersect(ray).map(|(mut isect, t_hit)| { 47 | isect.primitive = Some(self); 48 | ray.t_max = t_hit; 49 | isect 50 | }) 51 | } 52 | 53 | fn intersect_p(&self, ray: &Ray) -> bool { 54 | self.shape.intersect_p(ray) 55 | } 56 | 57 | fn area_light(&self) -> Option> { 58 | self.area_light.clone() 59 | } 60 | 61 | fn material(&self) -> Option> { 62 | self.material.clone() 63 | } 64 | 65 | fn compute_scattering_functions<'a, 'b>( 66 | &self, 67 | isect: &mut SurfaceInteraction<'a, 'b>, 68 | mode: TransportMode, 69 | allow_multiple_lobes: bool, 70 | arena: &'b Allocator<'_>, 71 | ) { 72 | if let Some(ref material) = self.material() { 73 | material.compute_scattering_functions(isect, mode, allow_multiple_lobes, arena); 74 | } 75 | } 76 | } 77 | 78 | #[derive(Debug)] 79 | pub struct TransformedPrimitive { 80 | pub primitive: Arc, 81 | pub primitive_to_world: Transform, 82 | } 83 | 84 | impl Primitive for TransformedPrimitive { 85 | fn world_bounds(&self) -> Bounds3f { 86 | &self.primitive_to_world * &self.primitive.world_bounds() 87 | } 88 | 89 | fn intersect(&self, ray: &mut Ray) -> Option> { 90 | let mut r = self.primitive_to_world.inverse() * *ray; 91 | self.primitive.intersect(&mut r).map(|isect| { 92 | ray.t_max = r.t_max; 93 | isect.transform(&self.primitive_to_world) 94 | }) 95 | } 96 | 97 | fn intersect_p(&self, ray: &Ray) -> bool { 98 | let r = self.primitive_to_world.inverse() * *ray; 99 | self.primitive.intersect_p(&r) 100 | } 101 | 102 | fn area_light(&self) -> Option> { 103 | None 104 | } 105 | 106 | fn material(&self) -> Option> { 107 | None 108 | } 109 | fn compute_scattering_functions<'a, 'b>( 110 | &self, 111 | _isect: &mut SurfaceInteraction<'a, 'b>, 112 | _mode: TransportMode, 113 | _allow_multiple_lobes: bool, 114 | _arena: &'b Allocator<'_>, 115 | ) { 116 | panic!("TransformedPrimitive::compute_scattering_functions() should not be called!"); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /rustracer-core/src/material/translucent.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | 5 | use crate::bsdf::{ 6 | dielectric, Bsdf, BxDFHolder, LambertianReflection, LambertianTransmission, 7 | MicrofacetReflection, MicrofacetTransmission, TrowbridgeReitzDistribution, 8 | }; 9 | use crate::interaction::SurfaceInteraction; 10 | use crate::material::{Material, TransportMode}; 11 | use crate::paramset::TextureParams; 12 | use crate::spectrum::Spectrum; 13 | use crate::texture::{TextureFloat, TextureSpectrum}; 14 | 15 | #[derive(Debug)] 16 | pub struct TranslucentMaterial { 17 | kd: Arc, 18 | ks: Arc, 19 | roughness: Arc, 20 | reflect: Arc, 21 | transmit: Arc, 22 | bumpmap: Option>, 23 | remap_roughness: bool, 24 | } 25 | 26 | impl TranslucentMaterial { 27 | pub fn create(mp: &TextureParams<'_>) -> Arc { 28 | let kd = mp.get_spectrum_texture("Kd", &Spectrum::from(0.25)); 29 | let ks = mp.get_spectrum_texture("Ks", &Spectrum::from(0.25)); 30 | let reflect = mp.get_spectrum_texture("reflect", &Spectrum::from(0.5)); 31 | let transmit = mp.get_spectrum_texture("transmit", &Spectrum::from(0.5)); 32 | let roughness = mp.get_float_texture("roughness", 0.1); 33 | let bumpmap = mp.get_float_texture_or_none("bumpmap"); 34 | let remap_roughness = mp.find_bool("remaproughness", true); 35 | 36 | Arc::new(TranslucentMaterial { 37 | kd, 38 | ks, 39 | roughness, 40 | reflect, 41 | transmit, 42 | bumpmap, 43 | remap_roughness, 44 | }) 45 | } 46 | } 47 | 48 | impl Material for TranslucentMaterial { 49 | fn compute_scattering_functions<'a, 'b>( 50 | &self, 51 | si: &mut SurfaceInteraction<'a, 'b>, 52 | mode: TransportMode, 53 | _allow_multiple_lobes: bool, 54 | arena: &'b Allocator<'_>, 55 | ) { 56 | let mut bxdfs = BxDFHolder::new(arena); 57 | let eta = 1.5; 58 | 59 | if let Some(ref bump_map) = self.bumpmap { 60 | super::bump(bump_map, si); 61 | } 62 | 63 | let r = self.reflect.evaluate(si).clamp(); 64 | let t = self.transmit.evaluate(si).clamp(); 65 | 66 | if !r.is_black() || !t.is_black() { 67 | let kd = self.kd.evaluate(si).clamp(); 68 | if !kd.is_black() { 69 | if !r.is_black() { 70 | bxdfs.add(arena.alloc(LambertianReflection::new(r * kd))); 71 | } 72 | if !t.is_black() { 73 | bxdfs.add(arena.alloc(LambertianTransmission::new(t * kd))); 74 | } 75 | } 76 | let ks = self.ks.evaluate(si).clamp(); 77 | if !ks.is_black() && (!r.is_black() || !t.is_black()) { 78 | let mut rough = self.roughness.evaluate(si); 79 | if self.remap_roughness { 80 | rough = TrowbridgeReitzDistribution::roughness_to_alpha(rough); 81 | } 82 | let distrib = arena.alloc(TrowbridgeReitzDistribution::new(rough, rough)); 83 | if !r.is_black() { 84 | let fresnel = arena.alloc(dielectric(1.0, eta)); 85 | bxdfs.add(arena.alloc(MicrofacetReflection::new(r * ks, distrib, fresnel))); 86 | } 87 | if !t.is_black() { 88 | bxdfs.add(arena.alloc(MicrofacetTransmission::new( 89 | t * ks, 90 | distrib, 91 | 1.0, 92 | eta, 93 | mode, 94 | ))); 95 | } 96 | } 97 | } 98 | 99 | let bsdf: Bsdf<'b> = Bsdf::new(si, eta, bxdfs.into_slice()); 100 | si.bsdf = Some(Arc::new(bsdf)); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /rustracer-core/src/ray.rs: -------------------------------------------------------------------------------- 1 | use std::f32::INFINITY; 2 | use std::fmt; 3 | use std::ops::Mul; 4 | 5 | use num::zero; 6 | 7 | use crate::{Point3f, Transform, Vector3f}; 8 | 9 | #[derive(Debug, Copy, Clone)] 10 | pub struct Ray { 11 | pub o: Point3f, 12 | pub d: Vector3f, 13 | pub t_max: f32, 14 | pub differential: Option, 15 | } 16 | 17 | impl Ray { 18 | pub fn new(o: Point3f, d: Vector3f) -> Ray { 19 | assert!(!o.x.is_nan() && !o.y.is_nan() && !o.z.is_nan()); 20 | assert!(!d.x.is_nan() && !d.y.is_nan() && !d.z.is_nan()); 21 | assert_ne!(d.length_squared(), 0.0); 22 | Ray { 23 | o, 24 | d, 25 | t_max: INFINITY, 26 | differential: None, 27 | } 28 | } 29 | 30 | pub fn segment(o: Point3f, d: Vector3f, tmax: f32) -> Ray { 31 | assert!(!o.x.is_nan() && !o.y.is_nan() && !o.z.is_nan()); 32 | assert!(!d.x.is_nan() && !d.y.is_nan() && !d.z.is_nan()); 33 | assert_ne!(d.length_squared(), 0.0); 34 | Ray { 35 | o, 36 | d, 37 | t_max: tmax, 38 | differential: None, 39 | } 40 | } 41 | 42 | pub fn at(&self, t: f32) -> Point3f { 43 | self.o + t * self.d 44 | } 45 | 46 | pub fn transform(&self, transform: &Transform) -> (Ray, Vector3f, Vector3f) { 47 | let (mut o, o_error) = transform.transform_point(&self.o); 48 | let (d, d_error) = transform.transform_vector(&self.d); 49 | let t_max = self.t_max; 50 | let length_squared = d.length_squared(); 51 | 52 | if length_squared > 0.0 { 53 | let dt = d.abs().dot(&o_error) / length_squared; 54 | o += d * dt; 55 | } 56 | 57 | let diff = self.differential.map(|d| RayDifferential { 58 | rx_origin: transform * &d.rx_origin, 59 | ry_origin: transform * &d.ry_origin, 60 | rx_direction: transform * &d.rx_direction, 61 | ry_direction: transform * &d.ry_direction, 62 | }); 63 | 64 | let r = Ray { 65 | o, 66 | d, 67 | t_max, 68 | differential: diff, 69 | }; 70 | (r, o_error, d_error) 71 | } 72 | 73 | pub fn scale_differentials(&mut self, s: f32) { 74 | if let Some(d) = self.differential.iter_mut().next() { 75 | d.rx_origin = self.o + (d.rx_origin - self.o) * s; 76 | d.ry_origin = self.o + (d.ry_origin - self.o) * s; 77 | d.rx_direction = self.d + (d.rx_direction - self.d) * s; 78 | d.ry_direction = self.d + (d.ry_direction - self.d) * s; 79 | } 80 | } 81 | } 82 | 83 | impl Mul for Transform { 84 | type Output = Ray; 85 | 86 | fn mul(self, rhs: Ray) -> Ray { 87 | let mut new_ray = rhs; 88 | new_ray.o = &self * &rhs.o; 89 | new_ray.d = &self * &rhs.d; 90 | 91 | new_ray 92 | } 93 | } 94 | 95 | impl fmt::Display for Ray { 96 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 97 | write!(f, "[o={}, d={}, t_max={}]", self.o, self.d, self.t_max) 98 | } 99 | } 100 | 101 | #[derive(Copy, Clone, Debug)] 102 | pub struct RayDifferential { 103 | pub rx_origin: Point3f, 104 | pub ry_origin: Point3f, 105 | pub rx_direction: Vector3f, 106 | pub ry_direction: Vector3f, 107 | } 108 | 109 | impl Default for RayDifferential { 110 | fn default() -> Self { 111 | RayDifferential { 112 | rx_origin: zero(), 113 | ry_origin: zero(), 114 | rx_direction: zero(), 115 | ry_direction: zero(), 116 | } 117 | } 118 | } 119 | 120 | #[test] 121 | fn test_translation() { 122 | let r = Ray::new(Point3f::new(1.0, 0.0, 0.0), Vector3f::new(0.0, 1.0, 0.0)); 123 | let t = Transform::translate(&Vector3f::new(1.0, 1.0, 1.0)); 124 | let s = t * r; 125 | 126 | assert_eq!(s.o, Point3f::new(2.0, 1.0, 1.0)); 127 | assert_eq!(s.d, r.d); 128 | } 129 | -------------------------------------------------------------------------------- /rustracer-core/src/material/glass.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | use log::info; 5 | 6 | use crate::bsdf::{ 7 | dielectric, Bsdf, BxDF, BxDFHolder, FresnelSpecular, MicrofacetReflection, 8 | MicrofacetTransmission, SpecularReflection, SpecularTransmission, TrowbridgeReitzDistribution, 9 | }; 10 | use crate::interaction::SurfaceInteraction; 11 | use crate::material::{Material, TransportMode}; 12 | use crate::paramset::TextureParams; 13 | use crate::spectrum::Spectrum; 14 | use crate::texture::{TextureFloat, TextureSpectrum}; 15 | 16 | #[derive(Debug)] 17 | pub struct GlassMaterial { 18 | kr: Arc, 19 | kt: Arc, 20 | u_roughness: Arc, 21 | v_roughness: Arc, 22 | index: Arc, 23 | bump_map: Option>, 24 | remap_roughness: bool, 25 | } 26 | 27 | impl GlassMaterial { 28 | pub fn create(mp: &TextureParams<'_>) -> Arc { 29 | info!("Creating Glass material"); 30 | let Kr = mp.get_spectrum_texture("Kr", &Spectrum::white()); 31 | let Kt = mp.get_spectrum_texture("Kt", &Spectrum::white()); 32 | let eta = mp 33 | .get_float_texture_or_none("eta") 34 | .unwrap_or_else(|| mp.get_float_texture("index", 1.5)); 35 | let rough_u = mp.get_float_texture("uroughness", 0.0); 36 | let rough_v = mp.get_float_texture("vroughness", 0.0); 37 | let bump_map = mp.get_float_texture_or_none("bumpmap"); 38 | let remap_roughness = mp.find_bool("remaproughness", true); 39 | 40 | Arc::new(GlassMaterial { 41 | kr: Kr, 42 | kt: Kt, 43 | u_roughness: rough_u, 44 | v_roughness: rough_v, 45 | index: eta, 46 | bump_map, 47 | remap_roughness, 48 | }) 49 | } 50 | } 51 | 52 | impl Material for GlassMaterial { 53 | fn compute_scattering_functions<'a, 'b>( 54 | &self, 55 | si: &mut SurfaceInteraction<'a, 'b>, 56 | mode: TransportMode, 57 | allow_multiple_lobes: bool, 58 | arena: &'b Allocator<'_>, 59 | ) { 60 | if let Some(ref bump) = self.bump_map { 61 | super::bump(bump, si); 62 | } 63 | let eta = self.index.evaluate(si); 64 | let mut u_rough = self.u_roughness.evaluate(si); 65 | let mut v_rough = self.v_roughness.evaluate(si); 66 | let r = self.kr.evaluate(si); 67 | let t = self.kt.evaluate(si); 68 | 69 | let mut bxdfs = BxDFHolder::new(arena); 70 | 71 | if !r.is_black() || !t.is_black() { 72 | let is_specular = u_rough == 0.0 && v_rough == 0.0; 73 | if is_specular && allow_multiple_lobes { 74 | bxdfs.add(arena.alloc(FresnelSpecular::new(r, t, 1.0, eta, mode))); 75 | } else { 76 | if self.remap_roughness { 77 | u_rough = TrowbridgeReitzDistribution::roughness_to_alpha(u_rough); 78 | v_rough = TrowbridgeReitzDistribution::roughness_to_alpha(v_rough); 79 | } 80 | if !r.is_black() { 81 | let fresnel = arena.alloc(dielectric(1.0, eta)); 82 | let bxdf: &'b dyn BxDF = if is_specular { 83 | arena.alloc(SpecularReflection::new(r, fresnel)) 84 | } else { 85 | let distrib = 86 | arena.alloc(TrowbridgeReitzDistribution::new(u_rough, v_rough)); 87 | arena.alloc(MicrofacetReflection::new(r, distrib, fresnel)) 88 | }; 89 | bxdfs.add(bxdf); 90 | } 91 | if !t.is_black() { 92 | let bxdf: &'b dyn BxDF = if is_specular { 93 | arena.alloc(SpecularTransmission::new(t, 1.0, eta, mode)) 94 | } else { 95 | let distrib = 96 | arena.alloc(TrowbridgeReitzDistribution::new(u_rough, v_rough)); 97 | arena.alloc(MicrofacetTransmission::new(r, distrib, 1.0, eta, mode)) 98 | }; 99 | bxdfs.add(bxdf); 100 | } 101 | } 102 | } 103 | 104 | let bsdf = Bsdf::new(si, eta, bxdfs.into_slice()); 105 | si.bsdf = Some(Arc::new(bsdf)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /rustracer-core/src/material/uber.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | 5 | use crate::bsdf::{ 6 | dielectric, Bsdf, BxDFHolder, LambertianReflection, MicrofacetReflection, SpecularReflection, 7 | SpecularTransmission, TrowbridgeReitzDistribution, 8 | }; 9 | use crate::interaction::SurfaceInteraction; 10 | use crate::material::{Material, TransportMode}; 11 | use crate::paramset::TextureParams; 12 | use crate::spectrum::Spectrum; 13 | use crate::texture::{TextureFloat, TextureSpectrum}; 14 | 15 | #[derive(Debug)] 16 | pub struct UberMaterial { 17 | kd: Arc, 18 | ks: Arc, 19 | kr: Arc, 20 | kt: Arc, 21 | opacity: Arc, 22 | roughness: Arc, 23 | roughnessu: Option>, 24 | roughnessv: Option>, 25 | eta: Arc, 26 | bumpmap: Option>, 27 | remap_roughness: bool, 28 | } 29 | 30 | impl UberMaterial { 31 | pub fn create(mp: &TextureParams<'_>) -> Arc { 32 | let kd = mp.get_spectrum_texture("Kd", &Spectrum::from(0.25)); 33 | let ks = mp.get_spectrum_texture("Ks", &Spectrum::from(0.25)); 34 | let kr = mp.get_spectrum_texture("Kr", &Spectrum::from(0.0)); 35 | let kt = mp.get_spectrum_texture("Kt", &Spectrum::from(0.0)); 36 | let roughness = mp.get_float_texture("roughness", 0.1); 37 | let uroughness = mp.get_float_texture_or_none("uroughness"); 38 | let vroughness = mp.get_float_texture_or_none("vroughness"); 39 | let eta = mp 40 | .get_float_texture_or_none("eta") 41 | .unwrap_or_else(|| mp.get_float_texture("index", 1.5)); 42 | let opacity = mp.get_spectrum_texture("opacity", &Spectrum::from(1.0)); 43 | let bumpmap = mp.get_float_texture_or_none("bumpmap"); 44 | let remap_roughness = mp.find_bool("remaproughness", true); 45 | 46 | Arc::new(UberMaterial { 47 | kd, 48 | ks, 49 | kr, 50 | kt, 51 | opacity, 52 | roughness, 53 | roughnessu: uroughness, 54 | roughnessv: vroughness, 55 | eta, 56 | bumpmap, 57 | remap_roughness, 58 | }) 59 | } 60 | } 61 | 62 | impl Material for UberMaterial { 63 | fn compute_scattering_functions<'a, 'b>( 64 | &self, 65 | si: &mut SurfaceInteraction<'a, 'b>, 66 | mode: TransportMode, 67 | _allow_multiple_lobes: bool, 68 | arena: &'b Allocator<'_>, 69 | ) { 70 | let mut bxdfs = BxDFHolder::new(arena); 71 | 72 | if let Some(ref bump_map) = self.bumpmap { 73 | super::bump(bump_map, si); 74 | } 75 | 76 | let e = self.eta.evaluate(si); 77 | let op = self.opacity.evaluate(si).clamp(); 78 | let t = (Spectrum::white() - op).clamp(); 79 | 80 | let mut eta = e; 81 | if !t.is_black() { 82 | eta = 1.0; 83 | bxdfs.add(arena.alloc(SpecularTransmission::new(t, 1.0, 1.0, mode))); 84 | } 85 | 86 | let kd = op * self.kd.evaluate(si).clamp(); 87 | if !kd.is_black() { 88 | bxdfs.add(arena.alloc(LambertianReflection::new(kd))); 89 | } 90 | 91 | let ks = op * self.ks.evaluate(si).clamp(); 92 | if !ks.is_black() { 93 | let fresnel = arena.alloc(dielectric(1.0, e)); 94 | let mut roughu = self 95 | .roughnessu 96 | .as_ref() 97 | .unwrap_or(&self.roughness) 98 | .evaluate(si); 99 | let mut roughv = self 100 | .roughnessv 101 | .as_ref() 102 | .unwrap_or(&self.roughness) 103 | .evaluate(si); 104 | if self.remap_roughness { 105 | roughu = TrowbridgeReitzDistribution::roughness_to_alpha(roughu); 106 | roughv = TrowbridgeReitzDistribution::roughness_to_alpha(roughv); 107 | } 108 | let distrib = arena.alloc(TrowbridgeReitzDistribution::new(roughu, roughv)); 109 | bxdfs.add(arena.alloc(MicrofacetReflection::new(ks, distrib, fresnel))); 110 | } 111 | 112 | let kr = op * self.kr.evaluate(si).clamp(); 113 | if !kr.is_black() { 114 | let fresnel = arena.alloc(dielectric(1.0, e)); 115 | bxdfs.add(arena.alloc(SpecularReflection::new(kr, fresnel))); 116 | } 117 | 118 | let kt = op * self.kt.evaluate(si).clamp(); 119 | if !kt.is_black() { 120 | bxdfs.add(arena.alloc(SpecularTransmission::new(kt, 1.0, e, mode))); 121 | } 122 | 123 | let bsdf: Bsdf<'b> = Bsdf::new(si, eta, bxdfs.into_slice()); 124 | si.bsdf = Some(Arc::new(bsdf)); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rustracer 2 | 3 | Rustracer is a toy 4 | [raytracer](https://en.wikipedia.org/wiki/Ray_tracing_(graphics)) written in 5 | [Rust](http://rust-lang.org). 6 | 7 | [![Rust](https://github.com/abusch/rustracer/actions/workflows/rust.yml/badge.svg)](https://github.com/abusch/rustracer/actions/workflows/rust.yml) 8 | [![dependency status](https://deps.rs/repo/github/abusch/rustracer/status.svg)](https://deps.rs/repo/github/abusch/rustracer) 9 | 10 | > *Update (27/05/2020):* This project is not really developped anymore as I ran 11 | > out of steam and spare time. I'm doing a little bit of maintainance 12 | > occasionally, such as updating dependencies, to keep it from bitrotting but I 13 | > suggest you look at [rs_pbrt](https://github.com/wahn/rs_pbrt) for a more 14 | > full-featured port of `pbrt`. 15 | 16 | ## History 17 | 18 | It started as a little playground to play with raytracing concepts that I found 19 | on [Scratchapixel](http://www.scratchapixel.com). I then found out about 20 | [tray_rust](http://github.com/TwinkleBear/tray_rust) and began refactoring my 21 | code to follow its design, but then eventually bought a copy of 22 | [Physically-Based Ray Tracing](http://www.pbrt.org) and decided to follow the 23 | [C++ implementation](https://github.com/mmp/pbrt-v3) as closely as possible to 24 | make it easier to follow along with the book. Consider this a port of PBRTv3 to 25 | Rust. 26 | 27 | **Note**: I am very much a beginner in Rust as well as in raytracing and this 28 | project is a way for me to learn about both. If the code is ugly or broken, it 29 | is very much my fault :) This is not helped by the fact that the C++ 30 | implementation is _very_ object-oriented, and doesn't always translate cleanly 31 | or idiomatically to Rust... I haven't really paid attention to performance yet, 32 | or number of allocations, so both are probably pretty bad :) That being said, 33 | feedback is more than welcome! 34 | 35 | ## Related projects 36 | * [tray_rust](http://github.com/TwinkleBear/tray_rust): if you want to see a 37 | good physically-based raytracer in Rust written by someone who knows what 38 | they're talking about, you should check this project instead of mine :) It's 39 | got some nice features, like distributed rendering, and the code is 40 | beautifully written and documented. 41 | 42 | * [rs_pbrt](https://github.com/wahn/rs_pbrt): another port of PBRT to Rust. 43 | 44 | ## Examples 45 | 46 | More examples can be found in the [renders/](renders/) directory. 47 | 48 | ![balls](balls.png) 49 | 50 | ## How to build 51 | 52 | To run the CLI, run `cargo run --release -p rustracer -- scene_file.pbrt`. 53 | 54 | ## Currently supported 55 | * Integrators: 56 | * Whitted 57 | * Direct Lighting (with "all" or "one" sampling strategies) 58 | * Path Tracing (with "uniform" or "spatial" sampling strategies) 59 | * Normal (for debuging) 60 | * Ambient Occlusion 61 | * Shapes: 62 | * Basic shapes: disc, sphere, cylinder 63 | * triangle meshes 64 | * Perspective camera with thin-lens depth-of-field 65 | * Lights: 66 | * Point lights 67 | * distant lights 68 | * diffuse area lights 69 | * infinite area light 70 | * BxDFs: 71 | * Lambertian 72 | * perfect specular reflection / transmission 73 | * microfacet (Oren-Nayar and Torrance-Sparrow models, Beckmann and 74 | Trowbridge-Heitz distributions, Smith masking-shadowing function) 75 | * Materials: 76 | * Matte 77 | * Plastic 78 | * Metal / RoughMetal 79 | * Glass / RoughGlass 80 | * Substrate (thin-coated) 81 | * Translucent 82 | * Uber 83 | * Disney (without SSS) 84 | * Textures (imagemaps, UV, CheckerBoard) 85 | * imagemap with mipmapping (with trilinear and EWA filtering) 86 | * UV 87 | * CheckerBoard 88 | * Fbm 89 | * BVH acceleration structure (MIDDLE and SAH splitting strategy) 90 | * multi-threaded rendering 91 | * Support for PBRT scene file format (still incomplete) 92 | * Read textures in PNG, TGA, PFM, HDR or EXR (thanks to [openexr-rs](https://github.com/cessen/openexr-rs)) 93 | * Write images in PNG or EXR 94 | * Support for PLY meshes (thanks to [ply-rs](https://github.com/Fluci/ply-rs)) 95 | * Bump mapping 96 | * Memory arena (thanks to [light_arena](https://github.com/Twinklebear/light_arena)) 97 | 98 | ## TODO 99 | * More CLI parameters (e.g. output file name, thread numbers) 100 | * More camera types (orthographic, realistic, environment) 101 | * More shapes (cone, paraboloid, hyperboloid, NURBs...) 102 | * More samplers (Halton, Sobol...) 103 | * More BxDFs and materials 104 | * Implement subsurface scattering 105 | * Volumetric rendering 106 | * More integrators: bidirectional path tracing, MTL, SPPM 107 | * Animations 108 | * more light types 109 | * k-d trees? 110 | * Spectral rendering? 111 | * some SIMD optimisation? 112 | * ... 113 | 114 | ## License 115 | 116 | Licensed under either of 117 | 118 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 119 | http://www.apache.org/licenses/LICENSE-2.0) 120 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 121 | 122 | at your option. 123 | 124 | -------------------------------------------------------------------------------- /rustracer-core/src/geometry/matrix.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::needless_range_loop)] 2 | use std::ops::Mul; 3 | 4 | use log::error; 5 | 6 | #[derive(Copy, Clone, Debug, PartialEq)] 7 | pub struct Matrix4x4 { 8 | pub m: [[f32; 4]; 4], 9 | } 10 | 11 | impl Matrix4x4 { 12 | pub fn new() -> Matrix4x4 { 13 | Matrix4x4 { 14 | m: [ 15 | [1.0, 0.0, 0.0, 0.0], 16 | [0.0, 1.0, 0.0, 0.0], 17 | [0.0, 0.0, 1.0, 0.0], 18 | [0.0, 0.0, 0.0, 1.0], 19 | ], 20 | } 21 | } 22 | 23 | pub fn from_elements( 24 | t00: f32, 25 | t01: f32, 26 | t02: f32, 27 | t03: f32, 28 | t10: f32, 29 | t11: f32, 30 | t12: f32, 31 | t13: f32, 32 | t20: f32, 33 | t21: f32, 34 | t22: f32, 35 | t23: f32, 36 | t30: f32, 37 | t31: f32, 38 | t32: f32, 39 | t33: f32, 40 | ) -> Matrix4x4 { 41 | Matrix4x4 { 42 | m: [ 43 | [t00, t01, t02, t03], 44 | [t10, t11, t12, t13], 45 | [t20, t21, t22, t23], 46 | [t30, t31, t32, t33], 47 | ], 48 | } 49 | } 50 | 51 | pub fn transpose(&self) -> Matrix4x4 { 52 | Matrix4x4::from_elements( 53 | self.m[0][0], 54 | self.m[1][0], 55 | self.m[2][0], 56 | self.m[3][0], 57 | self.m[0][1], 58 | self.m[1][1], 59 | self.m[2][1], 60 | self.m[3][1], 61 | self.m[0][2], 62 | self.m[1][2], 63 | self.m[2][2], 64 | self.m[3][2], 65 | self.m[0][3], 66 | self.m[1][3], 67 | self.m[2][3], 68 | self.m[3][3], 69 | ) 70 | } 71 | 72 | pub fn inverse(&self) -> Matrix4x4 { 73 | let mut indxc = [0usize; 4]; 74 | let mut indxr = [0usize; 4]; 75 | let mut ipiv = [0usize; 4]; 76 | let mut minv = self.m; 77 | 78 | for i in 0..4 { 79 | let mut irow = 0; 80 | let mut icol = 0; 81 | let mut big = 0.0; 82 | 83 | // Choose pivot 84 | for j in 0..4 { 85 | if ipiv[j] != 1 { 86 | for k in 0..4 { 87 | if ipiv[k] == 0 { 88 | if f32::abs(minv[j][k]) >= big { 89 | big = f32::abs(minv[j][k]); 90 | irow = j; 91 | icol = k; 92 | } 93 | } else if ipiv[k] > 1 { 94 | error!("Singular matrix in Matrix4x4::inverse()"); 95 | } 96 | } 97 | } 98 | } 99 | ipiv[icol] += 1; 100 | // Swap rows `irow` and `icol` for pivot 101 | if irow != icol { 102 | for k in 0..4 { 103 | let tmp = minv[irow][k]; 104 | minv[irow][k] = minv[icol][k]; 105 | minv[icol][k] = tmp; 106 | // This doesn't work because I can't borrow minv mutably twice :( 107 | // ::std::mem::swap(&mut minv[irow][k], &mut minv[icol][k]); 108 | } 109 | } 110 | indxr[i] = irow; 111 | indxc[i] = icol; 112 | if minv[icol][icol] == 0.0 { 113 | error!("Singular matrix in Matrix4x4::inverse()"); 114 | } 115 | 116 | // Set `m[icol][icol]` to one by rscaling row `icol` appropriately 117 | let pivinv = 1.0 / minv[icol][icol]; 118 | minv[icol][icol] = 1.0; 119 | for j in 0..4 { 120 | minv[icol][j] *= pivinv; 121 | } 122 | 123 | // Substract this row from others to zero out their columns 124 | for j in 0..4 { 125 | if j != icol { 126 | let save = minv[j][icol]; 127 | minv[j][icol] = 0.0; 128 | for k in 0..4 { 129 | minv[j][k] -= minv[icol][k] * save; 130 | } 131 | } 132 | } 133 | } 134 | 135 | // Swap columns to reflect permutation 136 | for j in (0..4).rev() { 137 | if indxr[j] != indxc[j] { 138 | for k in 0..4 { 139 | minv[k].swap(indxr[j], indxc[j]); 140 | } 141 | } 142 | } 143 | 144 | Matrix4x4 { m: minv } 145 | } 146 | } 147 | 148 | impl Default for Matrix4x4 { 149 | fn default() -> Self { 150 | Matrix4x4::new() 151 | } 152 | } 153 | 154 | impl<'a, 'b> Mul<&'b Matrix4x4> for &'a Matrix4x4 { 155 | type Output = Matrix4x4; 156 | 157 | fn mul(self, m2: &'b Matrix4x4) -> Matrix4x4 { 158 | let mut r = Matrix4x4::new(); 159 | for i in 0..4 { 160 | for j in 0..4 { 161 | r.m[i][j] = self.m[i][0] * m2.m[0][j] 162 | + self.m[i][1] * m2.m[1][j] 163 | + self.m[i][2] * m2.m[2][j] 164 | + self.m[i][3] * m2.m[3][j]; 165 | } 166 | } 167 | r 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /rustracer-core/tests/efloat.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::StdRng, Rng, SeedableRng}; 2 | 3 | use rustracer_core::efloat::EFloat; 4 | use rustracer_core::{next_float_down, next_float_up}; 5 | 6 | const NUM_ITER: usize = 10_000; 7 | 8 | /// Return an exponentially distributed floating-point value 9 | fn get_float(rng: &mut T, min_exp: f32, max_exp: f32) -> EFloat { 10 | let logu: f32 = rng.gen_range(min_exp..max_exp); 11 | let val = (10.0_f32).powf(logu); 12 | 13 | let err: f32 = match rng.gen_range(0..4) { 14 | 0 => 0.0, 15 | 1 => { 16 | let ulp_err: u32 = rng.gen_range(0..1024); 17 | let offset: f32 = f32::from_bits(val.to_bits() + ulp_err); 18 | (offset - val).abs() 19 | } 20 | 2 => { 21 | let ulp_err: u32 = rng.gen_range(0..1024 * 1024); 22 | let offset: f32 = f32::from_bits(val.to_bits() + ulp_err); 23 | (offset - val).abs() 24 | } 25 | 3 => (4.0 * rng.gen::()) * val.abs(), 26 | _ => panic!("should not happen"), 27 | }; 28 | let sign = if rng.gen::() < 0.5 { -1.0 } else { 1.0 }; 29 | EFloat::new(sign * val, err) 30 | } 31 | 32 | fn get_precise(ef: &EFloat, rng: &mut T) -> f64 { 33 | match rng.gen_range(0..3) { 34 | 0 => f64::from(ef.lower_bound()), 35 | 1 => f64::from(ef.upper_bound()), 36 | 2 => { 37 | let t: f64 = rng.gen(); 38 | let p: f64 = (1.0 - t) * f64::from(ef.lower_bound()) + t * f64::from(ef.upper_bound()); 39 | if p > f64::from(ef.upper_bound()) { 40 | f64::from(ef.upper_bound()) 41 | } else if p < f64::from(ef.lower_bound()) { 42 | f64::from(ef.lower_bound()) 43 | } else { 44 | p 45 | } 46 | } 47 | _ => panic!("should not happen"), 48 | } 49 | } 50 | 51 | #[test] 52 | fn test_efloat_abs() { 53 | for trial in 0..NUM_ITER { 54 | let mut rng = StdRng::seed_from_u64(trial as u64); 55 | let ef = get_float(&mut rng, -6.0, 6.0); 56 | let precise = get_precise(&ef, &mut rng); 57 | 58 | let result = ef.abs(); 59 | let precise_result = precise.abs(); 60 | 61 | assert!(precise_result >= f64::from(result.lower_bound())); 62 | assert!(precise_result <= f64::from(result.upper_bound())); 63 | } 64 | } 65 | 66 | #[test] 67 | fn test_efloat_sqrt() { 68 | for trial in 0..NUM_ITER { 69 | let mut rng = StdRng::seed_from_u64(trial as u64); 70 | let ef = get_float(&mut rng, -6.0, 6.0); 71 | let precise = get_precise(&ef, &mut rng); 72 | 73 | let result = ef.abs(); 74 | let precise_result = precise.abs(); 75 | 76 | assert!(precise_result >= f64::from(result.lower_bound())); 77 | assert!(precise_result <= f64::from(result.upper_bound())); 78 | } 79 | } 80 | 81 | #[test] 82 | fn test_efloat_add() { 83 | for trial in 0..NUM_ITER { 84 | let mut rng = StdRng::seed_from_u64(trial as u64); 85 | let a = get_float(&mut rng, -6.0, 6.0); 86 | let b = get_float(&mut rng, -6.0, 6.0); 87 | let ap = get_precise(&a, &mut rng); 88 | let bp = get_precise(&b, &mut rng); 89 | 90 | let result = a + b; 91 | let precise_result = ap + bp; 92 | 93 | assert!(precise_result >= f64::from(result.lower_bound())); 94 | assert!(precise_result <= f64::from(result.upper_bound())); 95 | } 96 | } 97 | 98 | #[test] 99 | fn test_efloat_sub() { 100 | for trial in 0..NUM_ITER { 101 | let mut rng = StdRng::seed_from_u64(trial as u64); 102 | let a = get_float(&mut rng, -6.0, 6.0); 103 | let b = get_float(&mut rng, -6.0, 6.0); 104 | let ap = get_precise(&a, &mut rng); 105 | let bp = get_precise(&b, &mut rng); 106 | 107 | let result = a - b; 108 | let precise_result = ap - bp; 109 | 110 | assert!(precise_result >= f64::from(result.lower_bound())); 111 | assert!(precise_result <= f64::from(result.upper_bound())); 112 | } 113 | } 114 | 115 | #[test] 116 | fn test_efloat_mul() { 117 | for trial in 0..NUM_ITER { 118 | let mut rng = StdRng::seed_from_u64(trial as u64); 119 | let a = get_float(&mut rng, -6.0, 6.0); 120 | let b = get_float(&mut rng, -6.0, 6.0); 121 | let ap = get_precise(&a, &mut rng); 122 | let bp = get_precise(&b, &mut rng); 123 | 124 | let result = a * b; 125 | let precise_result = ap * bp; 126 | 127 | assert!(precise_result >= f64::from(result.lower_bound())); 128 | assert!(precise_result <= f64::from(result.upper_bound())); 129 | } 130 | } 131 | 132 | #[test] 133 | fn test_efloat_div() { 134 | for trial in 0..NUM_ITER { 135 | let mut rng = StdRng::seed_from_u64(trial as u64); 136 | let a = get_float(&mut rng, -6.0, 6.0); 137 | let b = get_float(&mut rng, -6.0, 6.0); 138 | let ap = get_precise(&a, &mut rng); 139 | let bp = get_precise(&b, &mut rng); 140 | 141 | let result = a / b; 142 | let precise_result = ap / bp; 143 | 144 | assert!(precise_result >= f64::from(result.lower_bound())); 145 | assert!(precise_result <= f64::from(result.upper_bound())); 146 | } 147 | } 148 | 149 | #[test] 150 | fn test_ieee754_next() { 151 | let neg_zero = -0.0f32; 152 | assert!(!next_float_down(neg_zero).is_nan()); 153 | assert!(!next_float_up(neg_zero).is_nan()); 154 | } 155 | -------------------------------------------------------------------------------- /rustracer-core/src/skydome.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | use Vector; 4 | use Transform; 5 | use ray::Ray; 6 | use geometry::Sphere; 7 | use spectrum::Spectrum; 8 | use na::{Norm, Dot, zero, Inverse}; 9 | 10 | const BETA_R: Spectrum = Spectrum { 11 | r: 5.5e-6, 12 | g: 13e-6, 13 | b: 22.4e-6, 14 | }; 15 | const BETA_M: Spectrum = Spectrum { 16 | r: 21e-6, 17 | g: 21e-6, 18 | b: 21e-6, 19 | }; 20 | 21 | pub struct Atmosphere { 22 | hr: f32, 23 | hm: f32, 24 | radius_earth: f32, 25 | sun_direction: Vector, 26 | sun_intensity: f32, 27 | g: f32, 28 | atmosphere: Sphere, 29 | transform_inv: Transform, 30 | } 31 | 32 | impl Atmosphere { 33 | pub fn earth(sd: Vector) -> Atmosphere { 34 | Atmosphere { 35 | hr: 7994.0, 36 | hm: 1200.0, 37 | radius_earth: 6360e3, 38 | sun_direction: sd, 39 | sun_intensity: 20.0, 40 | g: 0.76, 41 | atmosphere: Sphere::new(6420e3), 42 | transform_inv: Transform::new(Vector::new(0.0, -6360e3, 0.0), zero(), 1.0) 43 | .inverse() 44 | .unwrap(), 45 | } 46 | } 47 | 48 | pub fn compute_incident_light(&self, ray: &mut Ray) -> Spectrum { 49 | let mut r = self.transform_inv * *ray; 50 | match self.atmosphere.intersect_sphere(&r) { 51 | None => Spectrum::black(), 52 | Some((t0, t1)) => { 53 | if t1 < 0.0 { 54 | return Spectrum::black(); 55 | } 56 | if t0 > r.t_min && t0 > 0.0 { 57 | r.t_min = t0; 58 | } 59 | if t1 < r.t_max { 60 | r.t_max = t1; 61 | } 62 | 63 | let num_samples = 16u8; 64 | let num_samples_light = 8u8; 65 | 66 | let segment_length = (r.t_max - r.t_min) / num_samples as f32; 67 | let mut t_current = r.t_min; 68 | let mut sum_r = Spectrum::black(); 69 | let mut sum_m = Spectrum::black(); 70 | let mut optical_depth_r = 0.0; 71 | let mut optical_depth_m = 0.0; 72 | let mu = r.d.normalize().dot(&self.sun_direction); 73 | let phase_r = 3.0 / (16.0 * PI) * (1.0 + mu * mu); 74 | let phase_m = 3.0 / (8.0 * PI) * ((1.0 - self.g * self.g) * (1.0 + mu * mu)) / 75 | ((2.0 + self.g * self.g) * 76 | (1.0 + self.g * self.g - 2.0 * self.g * mu).powf(1.5)); 77 | 78 | for _ in 0..num_samples { 79 | let sample_position = r.at(t_current + 0.5 * segment_length); 80 | let height = sample_position.to_vector().norm() - self.radius_earth; 81 | // compute optical depth for light 82 | let h_r = (-height / self.hr).exp() * segment_length; 83 | let h_m = (-height / self.hm).exp() * segment_length; 84 | optical_depth_r += h_r; 85 | optical_depth_m += h_m; 86 | // light optical depth 87 | let light_ray = Ray::new(sample_position, self.sun_direction); 88 | let res = self.atmosphere.intersect_sphere(&light_ray); 89 | if res.is_none() { 90 | break; 91 | } 92 | 93 | let (_, tl1) = res.unwrap(); 94 | let segment_length_light = tl1 / num_samples_light as f32; 95 | let mut t_current_light = 0.0; 96 | let mut optical_depth_light_r = 0.0; 97 | let mut optical_depth_light_m = 0.0; 98 | let mut exit_early = false; 99 | for _ in 0..num_samples_light { 100 | let sample_position_light = 101 | light_ray.at(t_current_light + 0.5 * segment_length_light); 102 | let height_light = sample_position_light.to_vector().norm() - 103 | self.radius_earth; 104 | if height_light < 0.0 { 105 | exit_early = true; 106 | break; 107 | } 108 | optical_depth_light_r += (-height_light / self.hr).exp() * 109 | segment_length_light; 110 | optical_depth_light_m += (-height_light / self.hm).exp() * 111 | segment_length_light; 112 | t_current_light += segment_length_light; 113 | } 114 | if !exit_early { 115 | let tau = BETA_R * (optical_depth_r + optical_depth_light_r) + 116 | BETA_M * 1.1 * (optical_depth_m + optical_depth_light_m); 117 | let attenuation = 118 | Spectrum::rgb((-tau.r).exp(), (-tau.g).exp(), (-tau.b).exp()); 119 | sum_r += attenuation * h_r; 120 | sum_m += attenuation * h_m; 121 | } 122 | t_current += segment_length; 123 | } 124 | 125 | let c = (sum_r * phase_r * BETA_R + sum_m * phase_m * BETA_M) * self.sun_intensity; 126 | assert!(!c.has_nan()); 127 | c 128 | } 129 | } 130 | 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /rustracer-core/src/material/metal.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | 5 | use crate::bsdf::{conductor, Bsdf, BxDFHolder, MicrofacetReflection, TrowbridgeReitzDistribution}; 6 | use crate::interaction::SurfaceInteraction; 7 | use crate::material::{self, Material, TransportMode}; 8 | use crate::paramset::TextureParams; 9 | use crate::spectrum::Spectrum; 10 | use crate::texture::{TextureFloat, TextureSpectrum}; 11 | 12 | #[derive(Debug)] 13 | pub struct Metal { 14 | eta: Arc, 15 | k: Arc, 16 | rough: Arc, 17 | bump: Option>, 18 | urough: Option>, 19 | vrough: Option>, 20 | remap_roughness: bool, 21 | } 22 | 23 | impl Metal { 24 | pub fn create(mp: &TextureParams<'_>) -> Arc { 25 | let copper_eta = 26 | Spectrum::from_sampled(&COPPER_WAVELENGTHS[..], &COPPER_N[..], COPPER_SAMPLES); 27 | let eta = mp.get_spectrum_texture("eta", &copper_eta); 28 | let copper_k = 29 | Spectrum::from_sampled(&COPPER_WAVELENGTHS[..], &COPPER_K[..], COPPER_SAMPLES); 30 | let k = mp.get_spectrum_texture("k", &copper_k); 31 | let rough = mp.get_float_texture("roughness", 0.01); 32 | let urough = mp.get_float_texture_or_none("uroughness"); 33 | let vrough = mp.get_float_texture_or_none("vroughness"); 34 | let bump = mp.get_float_texture_or_none("bumpmap"); 35 | let remap_roughness = mp.find_bool("remaproughness", true); 36 | 37 | Arc::new(Metal { 38 | eta, 39 | k, 40 | rough, 41 | bump, 42 | urough, 43 | vrough, 44 | remap_roughness, 45 | }) 46 | } 47 | } 48 | 49 | impl Material for Metal { 50 | fn compute_scattering_functions<'a, 'b>( 51 | &self, 52 | si: &mut SurfaceInteraction<'a, 'b>, 53 | _mode: TransportMode, 54 | _allow_multiple_lobes: bool, 55 | arena: &'b Allocator<'_>, 56 | ) { 57 | if let Some(ref bump) = self.bump { 58 | material::bump(bump, si); 59 | } 60 | let mut bxdfs = BxDFHolder::new(arena); 61 | let mut urough = self.urough.as_ref().unwrap_or(&self.rough).evaluate(si); 62 | let mut vrough = self.vrough.as_ref().unwrap_or(&self.rough).evaluate(si); 63 | if self.remap_roughness { 64 | urough = TrowbridgeReitzDistribution::roughness_to_alpha(urough); 65 | vrough = TrowbridgeReitzDistribution::roughness_to_alpha(vrough); 66 | } 67 | let fresnel = arena.alloc(conductor( 68 | Spectrum::white(), 69 | self.eta.evaluate(si), 70 | self.k.evaluate(si), 71 | )); 72 | let distrib = arena.alloc(TrowbridgeReitzDistribution::new(urough, vrough)); 73 | bxdfs.add(arena.alloc(MicrofacetReflection::new( 74 | Spectrum::white(), 75 | distrib, 76 | fresnel, 77 | ))); 78 | 79 | let bsdf = Bsdf::new(si, 1.0, bxdfs.into_slice()); 80 | si.bsdf = Some(Arc::new(bsdf)); 81 | } 82 | } 83 | 84 | const COPPER_SAMPLES: usize = 56; 85 | const COPPER_WAVELENGTHS: [f32; COPPER_SAMPLES] = [ 86 | 298.7570554, 87 | 302.4004341, 88 | 306.1337728, 89 | 309.960445, 90 | 313.8839949, 91 | 317.9081487, 92 | 322.036826, 93 | 326.2741526, 94 | 330.6244747, 95 | 335.092373, 96 | 339.6826795, 97 | 344.4004944, 98 | 349.2512056, 99 | 354.2405086, 100 | 359.374429, 101 | 364.6593471, 102 | 370.1020239, 103 | 375.7096303, 104 | 381.4897785, 105 | 387.4505563, 106 | 393.6005651, 107 | 399.9489613, 108 | 406.5055016, 109 | 413.2805933, 110 | 420.2853492, 111 | 427.5316483, 112 | 435.0322035, 113 | 442.8006357, 114 | 450.8515564, 115 | 459.2006593, 116 | 467.8648226, 117 | 476.8622231, 118 | 486.2124627, 119 | 495.936712, 120 | 506.0578694, 121 | 516.6007417, 122 | 527.5922468, 123 | 539.0616435, 124 | 551.0407911, 125 | 563.5644455, 126 | 576.6705953, 127 | 590.4008476, 128 | 604.8008683, 129 | 619.92089, 130 | 635.8162974, 131 | 652.5483053, 132 | 670.1847459, 133 | 688.8009889, 134 | 708.4810171, 135 | 729.3186941, 136 | 751.4192606, 137 | 774.9011125, 138 | 799.8979226, 139 | 826.5611867, 140 | 855.0632966, 141 | 885.6012714, 142 | ]; 143 | 144 | const COPPER_N: [f32; COPPER_SAMPLES] = [ 145 | 1.400313, 1.38, 1.358438, 1.34, 1.329063, 1.325, 1.3325, 1.34, 1.334375, 1.325, 1.317812, 1.31, 146 | 1.300313, 1.29, 1.281563, 1.27, 1.249062, 1.225, 1.2, 1.18, 1.174375, 1.175, 1.1775, 1.18, 147 | 1.178125, 1.175, 1.172812, 1.17, 1.165312, 1.16, 1.155312, 1.15, 1.142812, 1.135, 1.131562, 148 | 1.12, 1.092437, 1.04, 0.950375, 0.826, 0.645875, 0.468, 0.35125, 0.272, 0.230813, 0.214, 149 | 0.20925, 0.213, 0.21625, 0.223, 0.2365, 0.25, 0.254188, 0.26, 0.28, 0.3, 150 | ]; 151 | 152 | const COPPER_K: [f32; COPPER_SAMPLES] = [ 153 | 1.662125, 1.687, 1.703313, 1.72, 1.744563, 1.77, 1.791625, 1.81, 1.822125, 1.834, 1.85175, 154 | 1.872, 1.89425, 1.916, 1.931688, 1.95, 1.972438, 2.015, 2.121562, 2.21, 2.177188, 2.13, 155 | 2.160063, 2.21, 2.249938, 2.289, 2.326, 2.362, 2.397625, 2.433, 2.469187, 2.504, 2.535875, 156 | 2.564, 2.589625, 2.605, 2.595562, 2.583, 2.5765, 2.599, 2.678062, 2.809, 3.01075, 3.24, 157 | 3.458187, 3.67, 3.863125, 4.05, 4.239563, 4.43, 4.619563, 4.817, 5.034125, 5.26, 5.485625, 158 | 5.717, 159 | ]; 160 | -------------------------------------------------------------------------------- /rustracer-core/src/integrator/directlighting.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use light_arena::Allocator; 4 | use log::{info, warn}; 5 | 6 | use crate::bounds::Bounds2i; 7 | use crate::integrator::{uniform_sample_all_light, uniform_sample_one_light, SamplerIntegrator}; 8 | use crate::material::TransportMode; 9 | use crate::paramset::ParamSet; 10 | use crate::ray::Ray; 11 | use crate::sampler::Sampler; 12 | use crate::scene::Scene; 13 | use crate::spectrum::Spectrum; 14 | 15 | /// Strategy to use for sampling lights 16 | #[derive(PartialEq, Eq)] 17 | pub enum LightStrategy { 18 | /// For each pixel sample, sample every light in the scene 19 | UniformSampleAll, 20 | /// For each pixel sample, only sample one light from the scene, chosen at random 21 | UniformSampleOne, 22 | } 23 | 24 | /// Integrator that only takes into account direct lighting i.e no global illumination. It is very 25 | /// similar to the Whitted integrator but has a better light sampling strategy. 26 | pub struct DirectLightingIntegrator { 27 | pixel_bounds: Bounds2i, 28 | /// The strategy to use to sample lights 29 | light_strategy: LightStrategy, 30 | /// Maximum number of times a ray can bounce before terminating 31 | max_depth: u8, 32 | // 33 | n_light_samples: Vec, 34 | } 35 | 36 | impl DirectLightingIntegrator { 37 | pub fn new(n: u8, strategy: LightStrategy) -> DirectLightingIntegrator { 38 | DirectLightingIntegrator { 39 | pixel_bounds: Bounds2i::new(), 40 | max_depth: n, 41 | light_strategy: strategy, 42 | n_light_samples: Vec::new(), 43 | } 44 | } 45 | 46 | pub fn create(ps: &ParamSet) -> Box { 47 | let max_depth = ps.find_one_int("maxdepth", 5); 48 | let st = ps.find_one_string("strategy", "all".into()); 49 | let strategy = if st == "one" { 50 | LightStrategy::UniformSampleOne 51 | } else if st == "all" { 52 | LightStrategy::UniformSampleAll 53 | } else { 54 | warn!( 55 | "Strategy \"{}\" for directlighting unknown. Using \"all\".", 56 | st 57 | ); 58 | LightStrategy::UniformSampleAll 59 | }; 60 | // TODO pixel_bounds 61 | Box::new(Self::new(max_depth as u8, strategy)) 62 | } 63 | } 64 | 65 | impl SamplerIntegrator for DirectLightingIntegrator { 66 | fn pixel_bounds(&self) -> &Bounds2i { 67 | &self.pixel_bounds 68 | } 69 | 70 | fn preprocess(&mut self, scene: Arc, sampler: &mut dyn Sampler) { 71 | info!("Preprocessing DirectLighting integrator"); 72 | if self.light_strategy == LightStrategy::UniformSampleAll { 73 | // Compute number of samples to use for each light 74 | for light in &scene.lights { 75 | self.n_light_samples 76 | .push(sampler.round_count(light.n_samples() as usize)); 77 | } 78 | info!("n sample sizes: {:?}", self.n_light_samples); 79 | 80 | for _i in 0..self.max_depth { 81 | for j in 0..scene.lights.len() { 82 | sampler.request_2d_array(self.n_light_samples[j]); 83 | sampler.request_2d_array(self.n_light_samples[j]); 84 | } 85 | } 86 | } 87 | } 88 | 89 | fn li( 90 | &self, 91 | scene: &Scene, 92 | ray: &mut Ray, 93 | sampler: &mut dyn Sampler, 94 | arena: &Allocator<'_>, 95 | depth: u32, 96 | ) -> Spectrum { 97 | let mut colour = Spectrum::black(); 98 | 99 | match scene.intersect(ray) { 100 | Some(mut isect) => { 101 | let wo = isect.hit.wo; 102 | 103 | // Compute scattering functions for surface interaction 104 | isect.compute_scattering_functions(ray, TransportMode::RADIANCE, false, arena); 105 | 106 | if isect.bsdf.is_none() { 107 | let mut r = isect.spawn_ray(&ray.d); 108 | return self.li(scene, &mut r, sampler, arena, depth); 109 | } 110 | let bsdf = isect.bsdf.clone().unwrap(); 111 | 112 | // Compute emitted light if ray hit an area light source 113 | colour += isect.le(&wo); 114 | if !scene.lights.is_empty() { 115 | // Compute direct lighting for DirectLightingIntegrator 116 | colour += match self.light_strategy { 117 | LightStrategy::UniformSampleAll => { 118 | uniform_sample_all_light(&isect, scene, sampler, &self.n_light_samples) 119 | } 120 | LightStrategy::UniformSampleOne => { 121 | uniform_sample_one_light(&isect, scene, sampler, None) 122 | } 123 | } 124 | } 125 | 126 | if depth + 1 < u32::from(self.max_depth) { 127 | colour += 128 | self.specular_reflection(ray, &isect, scene, &bsdf, sampler, arena, depth); 129 | colour += self 130 | .specular_transmission(ray, &isect, scene, &bsdf, sampler, arena, depth); 131 | } 132 | } 133 | None => { 134 | // If we didn't intersect anything, add the backgound radiance from every light 135 | colour = scene 136 | .lights 137 | .iter() 138 | .fold(Spectrum::black(), |c, l| c + l.le(ray)); 139 | } 140 | } 141 | 142 | colour 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /rustracer-core/src/noise.rs: -------------------------------------------------------------------------------- 1 | use crate::{clamp, lerp, Point3f, Vector3f}; 2 | 3 | /// Perlin noise 4 | pub fn noise3(p: &Point3f) -> f32 { 5 | noise(p.x, p.y, p.z) 6 | } 7 | 8 | pub fn noise(x: f32, y: f32, z: f32) -> f32 { 9 | // Compute noise cell coordinates and offsets 10 | let mut ix = x.floor() as i32; 11 | let mut iy = y.floor() as i32; 12 | let mut iz = z.floor() as i32; 13 | let dx = x - ix as f32; 14 | let dy = y - iy as f32; 15 | let dz = z - iz as f32; 16 | 17 | // Compute gradient weights 18 | ix &= NOISE_PERM_SIZE as i32 - 1; 19 | iy &= NOISE_PERM_SIZE as i32 - 1; 20 | iz &= NOISE_PERM_SIZE as i32 - 1; 21 | let w000 = grad(ix, iy, iz, dx, dy, dz); 22 | let w100 = grad(ix + 1, iy, iz, dx - 1.0, dy, dz); 23 | let w010 = grad(ix, iy + 1, iz, dx, dy - 1.0, dz); 24 | let w110 = grad(ix + 1, iy + 1, iz, dx - 1.0, dy - 1.0, dz); 25 | let w001 = grad(ix, iy, iz + 1, dx, dy, dz - 1.0); 26 | let w101 = grad(ix + 1, iy, iz + 1, dx - 1.0, dy, dz - 1.0); 27 | let w011 = grad(ix, iy + 1, iz + 1, dx, dy - 1.0, dz - 1.0); 28 | let w111 = grad(ix + 1, iy + 1, iz + 1, dx - 1.0, dy - 1.0, dz - 1.0); 29 | 30 | // Compute trilinear interpolation of weights 31 | let wx = noise_weight(dx); 32 | let wy = noise_weight(dy); 33 | let wz = noise_weight(dz); 34 | let x00 = lerp(wx, w000, w100); 35 | let x10 = lerp(wx, w010, w110); 36 | let x01 = lerp(wx, w001, w101); 37 | let x11 = lerp(wx, w011, w111); 38 | let y0 = lerp(wy, x00, x10); 39 | let y1 = lerp(wy, x01, x11); 40 | 41 | lerp(wz, y0, y1) 42 | } 43 | 44 | /// Fractional Brownian Motion 45 | pub fn fbm(p: &Point3f, dpdx: &Vector3f, dpdy: &Vector3f, omega: f32, max_octaves: u32) -> f32 { 46 | // Compute number of octaves for antialiased FBm 47 | let len2 = dpdx.length_squared().max(dpdy.length_squared()); 48 | let n = clamp(-1.0 - 0.5 * len2.log2(), 0.0, max_octaves as f32); 49 | let n_int = n.floor() as u32; 50 | 51 | // TODO replace with fold()? 52 | // Compute sum of octaves of noise for FBm 53 | let mut sum = 0.0; 54 | let mut lambda = 1.0; 55 | let mut o = 1.0; 56 | for _ in 0..n_int { 57 | sum += o * noise3(&(lambda * *p)); 58 | lambda *= 1.99; 59 | o *= omega; 60 | } 61 | let n_partial = n - n_int as f32; 62 | sum += o * smooth_step(0.3, 0.7, n_partial) * noise3(&(lambda * *p)); 63 | 64 | sum 65 | } 66 | 67 | #[inline] 68 | fn grad(x: i32, y: i32, z: i32, dx: f32, dy: f32, dz: f32) -> f32 { 69 | let mut h = NOISE_PERM[NOISE_PERM[NOISE_PERM[x as usize] + y as usize] + z as usize]; 70 | h &= 15; 71 | let u = if h < 8 || h == 12 || h == 13 { dx } else { dy }; 72 | let v = if h < 4 || h == 12 || h == 13 { dy } else { dz }; 73 | 74 | (if h & 1 != 0 { -u } else { u }) + (if h & 2 != 0 { -v } else { v }) 75 | } 76 | 77 | #[inline] 78 | fn noise_weight(t: f32) -> f32 { 79 | let t3 = t * t * t; 80 | let t4 = t3 * t; 81 | 6.0 * t4 * t - 15.0 * t4 + 10.0 * t3 82 | } 83 | 84 | #[inline] 85 | fn smooth_step(min: f32, max: f32, value: f32) -> f32 { 86 | let v = clamp((value - min) / (max - min), 0.0, 1.0); 87 | 88 | v * v * (-2.0 * v + 3.0) 89 | } 90 | 91 | const NOISE_PERM_SIZE: usize = 256; 92 | const NOISE_PERM: [usize; 2 * NOISE_PERM_SIZE] = [ 93 | 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 94 | 142, // Remainder of the noise permutation table 95 | 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 96 | 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 97 | 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 98 | 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 99 | 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 100 | 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 101 | 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 102 | 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 103 | 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 104 | 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 105 | 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 106 | 128, 195, 78, 66, 215, 61, 156, 180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 107 | 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 108 | 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 109 | 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 110 | 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 111 | 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 112 | 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 113 | 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 114 | 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 115 | 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 116 | 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 117 | 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 118 | 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 119 | ]; 120 | -------------------------------------------------------------------------------- /rustracer-core/src/texture/checkerboard.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::ops::{Add, Mul}; 3 | use std::sync::Arc; 4 | 5 | use log::{error, warn}; 6 | 7 | use crate::interaction::SurfaceInteraction; 8 | use crate::paramset::TextureParams; 9 | use crate::spectrum::Spectrum; 10 | use crate::texture::{PlanarMapping2D, Texture, TextureMapping2D, UVMapping2D}; 11 | use crate::{Transform, Vector3f}; 12 | 13 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 14 | pub enum AAMethod { 15 | None, 16 | ClosedForm, 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct CheckerboardTexture { 21 | tex1: Arc>, 22 | tex2: Arc>, 23 | mapping: Box, 24 | aa_method: AAMethod, 25 | } 26 | 27 | impl CheckerboardTexture { 28 | pub fn new( 29 | tex1: Arc>, 30 | tex2: Arc>, 31 | mapping: Box, 32 | aa_method: AAMethod, 33 | ) -> CheckerboardTexture { 34 | CheckerboardTexture { 35 | tex1, 36 | tex2, 37 | mapping, 38 | aa_method, 39 | } 40 | } 41 | } 42 | 43 | impl CheckerboardTexture { 44 | pub fn create_spectrum( 45 | _tex2world: &Transform, 46 | tp: &TextureParams<'_>, 47 | ) -> CheckerboardTexture { 48 | let dim = tp.find_int("dimension", 2); 49 | if dim != 2 && dim != 3 { 50 | panic!("{} dimensional checkerboard texture not supported", dim); 51 | } 52 | let tex1 = tp.get_spectrum_texture("tex1", &Spectrum::white()); 53 | let tex2 = tp.get_spectrum_texture("tex2", &Spectrum::black()); 54 | if dim == 2 { 55 | // Initialize 2D texture mapping `map` from `tp` 56 | let typ = tp.find_string("mapping", "uv"); 57 | let map: Box = if typ == "uv" { 58 | let su = tp.find_float("uscale", 1.0); 59 | let sv = tp.find_float("vscale", 1.0); 60 | let du = tp.find_float("udelta", 0.0); 61 | let dv = tp.find_float("vdelta", 0.0); 62 | Box::new(UVMapping2D::new(su, sv, du, dv)) 63 | } else if typ == "spherical" { 64 | unimplemented!() 65 | } else if typ == "cylindrical" { 66 | unimplemented!() 67 | } else if typ == "planar" { 68 | let vs = tp.find_vector3f("v1", Vector3f::new(1.0, 0.0, 0.0)); 69 | let vt = tp.find_vector3f("v2", Vector3f::new(0.0, 1.0, 0.0)); 70 | let ds = tp.find_float("udelta", 0.0); 71 | let dt = tp.find_float("vdelta", 0.0); 72 | Box::new(PlanarMapping2D::new(vs, vt, ds, dt)) 73 | } else { 74 | error!("2D texture mapping {} unknown", typ); 75 | Box::new(UVMapping2D::new(1.0, 1.0, 0.0, 0.0)) 76 | }; 77 | 78 | // Compute `aaMethod` for `CheckerboardTexture` 79 | let aa = tp.find_string("aamode", "closedform"); 80 | let aa_method = if aa == "none" { 81 | AAMethod::None 82 | } else if aa == "closedform" { 83 | AAMethod::ClosedForm 84 | } else { 85 | warn!("Unknown aamethod \"{}\" found for CheckerboardTexture. Using closedform instead", 86 | aa); 87 | AAMethod::ClosedForm 88 | }; 89 | CheckerboardTexture::new(tex1, tex2, map, aa_method) 90 | } else { 91 | unimplemented!() 92 | } 93 | } 94 | } 95 | 96 | impl Texture for CheckerboardTexture 97 | where 98 | T: Debug, 99 | T: Mul, 100 | T: Add, 101 | { 102 | fn evaluate(&self, si: &SurfaceInteraction<'_, '_>) -> T { 103 | let (st, dstdx, dstdy) = self.mapping.map(si); 104 | match self.aa_method { 105 | AAMethod::None => { 106 | if (st.x.floor() as u32 + st.y.floor() as u32) % 2 == 0 { 107 | self.tex1.evaluate(si) 108 | } else { 109 | self.tex2.evaluate(si) 110 | } 111 | } 112 | AAMethod::ClosedForm => { 113 | // Compute closed-form box-filtered _Checkerboard2DTexture_ value 114 | 115 | // Evaluate single check if filter is entirely inside one of them 116 | let ds = f32::max(f32::abs(dstdx[0]), f32::abs(dstdy[0])); 117 | let dt = f32::max(f32::abs(dstdx[1]), f32::abs(dstdy[1])); 118 | let s0 = st[0] - ds; 119 | let s1 = st[0] + ds; 120 | let t0 = st[1] - dt; 121 | let t1 = st[1] + dt; 122 | if f32::floor(s0) == f32::floor(s1) && f32::floor(t0) == f32::floor(t1) { 123 | // Point sample _Checkerboard2DTexture_ 124 | if (f32::floor(st[0]) as i32 + f32::floor(st[1]) as i32) % 2 == 0 { 125 | return self.tex1.evaluate(si); 126 | } else { 127 | return self.tex2.evaluate(si); 128 | } 129 | } 130 | 131 | // Apply box filter to checkerboard region 132 | fn bump_int(x: f32) -> f32 { 133 | f32::floor(x / 2.0) + 2.0 * f32::max(x / 2.0 - f32::floor(x / 2.0) - 0.5, 0.0) 134 | } 135 | let sint = (bump_int(s1) - bump_int(s0)) / (2.0 * ds); 136 | let tint = (bump_int(t1) - bump_int(t0)) / (2.0 * dt); 137 | let mut area2 = sint + tint - 2.0 * sint * tint; 138 | if ds > 1.0 || dt > 1.0 { 139 | area2 = 0.5 140 | }; 141 | self.tex1.evaluate(si) * (1.0 - area2) + self.tex2.evaluate(si) * area2 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /rustracer-core/src/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | use crate::{clamp, next_float_down, next_float_up, Normal3f, Point3f, Vector3f}; 4 | 5 | mod matrix; 6 | pub use self::matrix::*; 7 | mod vector; 8 | pub use self::vector::*; 9 | mod normal; 10 | pub use self::normal::*; 11 | mod point; 12 | pub use self::point::*; 13 | 14 | // Common geometric functions 15 | #[inline] 16 | pub fn cos_theta(w: &Vector3f) -> f32 { 17 | w.z 18 | } 19 | 20 | #[inline] 21 | pub fn cos2_theta(w: &Vector3f) -> f32 { 22 | w.z * w.z 23 | } 24 | 25 | #[inline] 26 | pub fn abs_cos_theta(w: &Vector3f) -> f32 { 27 | w.z.abs() 28 | } 29 | 30 | #[inline] 31 | pub fn sin2_theta(w: &Vector3f) -> f32 { 32 | (1.0 - cos2_theta(w)).max(0.0) 33 | } 34 | 35 | #[inline] 36 | pub fn sin_theta(w: &Vector3f) -> f32 { 37 | sin2_theta(w).sqrt() 38 | } 39 | 40 | #[inline] 41 | pub fn tan_theta(w: &Vector3f) -> f32 { 42 | sin_theta(w) / cos_theta(w) 43 | } 44 | 45 | #[inline] 46 | pub fn tan2_theta(w: &Vector3f) -> f32 { 47 | sin2_theta(w) / cos2_theta(w) 48 | } 49 | 50 | #[inline] 51 | pub fn cos_phi(w: &Vector3f) -> f32 { 52 | let sin_theta = sin_theta(w); 53 | if sin_theta == 0.0 { 54 | 1.0 55 | } else { 56 | clamp(w.x / sin_theta, -1.0, 1.0) 57 | } 58 | } 59 | 60 | #[inline] 61 | pub fn sin_phi(w: &Vector3f) -> f32 { 62 | let sin_theta = sin_theta(w); 63 | if sin_theta == 0.0 { 64 | 0.0 65 | } else { 66 | clamp(w.y / sin_theta, -1.0, 1.0) 67 | } 68 | } 69 | 70 | #[inline] 71 | pub fn cos2_phi(w: &Vector3f) -> f32 { 72 | cos_phi(w) * cos_phi(w) 73 | } 74 | 75 | #[inline] 76 | pub fn sin2_phi(w: &Vector3f) -> f32 { 77 | sin_phi(w) * sin_phi(w) 78 | } 79 | 80 | #[inline] 81 | #[allow(dead_code)] 82 | pub fn cos_d_phi(wa: &Vector3f, wb: &Vector3f) -> f32 { 83 | clamp( 84 | (wa.x * wb.x + wa.y * wa.y) 85 | / ((wa.x * wa.x + wa.y * wa.y) * (wb.x * wb.x + wb.y * wb.y)).sqrt(), 86 | -1.0, 87 | 1.0, 88 | ) 89 | } 90 | 91 | #[inline] 92 | pub fn same_hemisphere(w: &Vector3f, wp: &Vector3f) -> bool { 93 | w.z * wp.z > 0.0 94 | } 95 | 96 | #[inline] 97 | pub fn spherical_theta(v: &Vector3f) -> f32 { 98 | clamp(v.z, -1.0, 1.0).acos() 99 | } 100 | 101 | #[inline] 102 | pub fn spherical_phi(v: &Vector3f) -> f32 { 103 | let p = v.y.atan2(v.x); 104 | if p < 0.0 { 105 | p + 2.0 * PI 106 | } else { 107 | p 108 | } 109 | } 110 | 111 | #[inline] 112 | pub fn spherical_direction(sin_theta: f32, cos_theta: f32, phi: f32) -> Vector3f { 113 | Vector3f::new(sin_theta * phi.cos(), sin_theta * phi.sin(), cos_theta) 114 | } 115 | 116 | #[inline] 117 | pub fn spherical_direction_vec( 118 | sin_theta: f32, 119 | cos_theta: f32, 120 | phi: f32, 121 | x: &Vector3f, 122 | y: &Vector3f, 123 | z: &Vector3f, 124 | ) -> Vector3f { 125 | sin_theta * phi.cos() * *x + sin_theta * phi.sin() * *y + cos_theta * *z 126 | } 127 | 128 | #[inline] 129 | pub fn face_forward(v1: &Vector3f, v2: &Vector3f) -> Vector3f { 130 | if v1.dot(v2) < 0.0 { 131 | -(*v1) 132 | } else { 133 | *v1 134 | } 135 | } 136 | 137 | #[inline] 138 | pub fn face_forward_n(v1: &Normal3f, v2: &Normal3f) -> Normal3f { 139 | if v1.dotn(v2) < 0.0 { 140 | -(*v1) 141 | } else { 142 | *v1 143 | } 144 | } 145 | 146 | /// Polynomial approximation of the inverse Gauss error function 147 | #[inline] 148 | pub fn erf_inv(x: f32) -> f32 { 149 | let x = clamp(x, -0.99999, 0.99999); 150 | let mut w = -((1.0 - x) * (1.0 + x)).ln(); 151 | let mut p; 152 | if w < 5.0 { 153 | w -= 2.5; 154 | p = 2.81022636e-08; 155 | p = 3.43273939e-07 + p * w; 156 | p = -3.5233877e-06 + p * w; 157 | p = -4.39150654e-06 + p * w; 158 | p = 0.00021858087 + p * w; 159 | p = -0.00125372503 + p * w; 160 | p = -0.00417768164 + p * w; 161 | p = 0.246640727 + p * w; 162 | p = 1.50140941 + p * w; 163 | } else { 164 | w = w.sqrt() - 3.0; 165 | p = -0.000200214257; 166 | p = 0.000100950558 + p * w; 167 | p = 0.00134934322 + p * w; 168 | p = -0.00367342844 + p * w; 169 | p = 0.00573950773 + p * w; 170 | p = -0.0076224613 + p * w; 171 | p = 0.00943887047 + p * w; 172 | p = 1.00167406 + p * w; 173 | p = 2.83297682 + p * w; 174 | } 175 | 176 | p * x 177 | } 178 | 179 | /// Polynomial approximation of the Gauss error function. 180 | /// 181 | /// See [this link](https://en.wikipedia.org/wiki/Error_function) 182 | pub fn erf(x: f32) -> f32 { 183 | // constants 184 | let a1: f32 = 0.254829592; 185 | let a2: f32 = -0.284496736; 186 | let a3: f32 = 1.421413741; 187 | let a4: f32 = -1.453152027; 188 | let a5: f32 = 1.061405429; 189 | let p: f32 = 0.3275911; 190 | 191 | // Save the sign of x 192 | let sign = if x < 0.0 { -1.0 } else { 1.0 }; 193 | let x = x.abs(); 194 | 195 | // A&S formula 7.1.26 196 | let t = 1.0 / (1.0 + p * x); 197 | let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp(); 198 | 199 | sign * y 200 | } 201 | 202 | #[inline] 203 | pub fn offset_ray_origin(p: &Point3f, p_error: &Vector3f, n: &Normal3f, w: &Vector3f) -> Point3f { 204 | let d = n.abs().dot(p_error); 205 | let mut offset = d * Vector3f::from(*n); 206 | if w.dotn(n) < 0.0 { 207 | offset = -offset; 208 | } 209 | let mut po = *p + offset; 210 | // Round offset point `po` away from `p` 211 | for i in 0..3 { 212 | if offset[i] > 0.0 { 213 | po[i] = next_float_up(po[i]); 214 | } else if offset[i] < 0.0 { 215 | po[i] = next_float_down(po[i]); 216 | } 217 | } 218 | 219 | po 220 | } 221 | 222 | pub fn distance_squared(p1: &Point3f, p2: &Point3f) -> f32 { 223 | (*p2 - *p1).length_squared() 224 | } 225 | 226 | pub fn distance(p1: &Point3f, p2: &Point3f) -> f32 { 227 | (*p2 - *p1).length() 228 | } 229 | -------------------------------------------------------------------------------- /rustracer-core/src/geometry/normal.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | use std::fmt::{Display, Error, Formatter}; 3 | use std::ops::{ 4 | Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, 5 | }; 6 | 7 | use num::{Num, Zero}; 8 | 9 | use crate::geometry::Vector3; 10 | 11 | #[derive(Copy, Clone, Debug, PartialEq)] 12 | pub struct Normal3 { 13 | pub x: T, 14 | pub y: T, 15 | pub z: T, 16 | } 17 | 18 | impl Normal3 19 | where 20 | T: Num + Copy, 21 | { 22 | pub fn new(x: T, y: T, z: T) -> Normal3 { 23 | Normal3 { x, y, z } 24 | } 25 | 26 | pub fn dot(&self, v: &Vector3) -> T { 27 | self.x * v.x + self.y * v.y + self.z * v.z 28 | } 29 | 30 | pub fn dotn(&self, v: &Normal3) -> T { 31 | self.x * v.x + self.y * v.y + self.z * v.z 32 | } 33 | } 34 | 35 | impl Normal3 { 36 | pub fn has_nan(&self) -> bool { 37 | self.x.is_nan() || self.y.is_nan() || self.z.is_nan() 38 | } 39 | 40 | pub fn length_squared(&self) -> f32 { 41 | self.x * self.x + self.y * self.y + self.z * self.z 42 | } 43 | 44 | pub fn length(&self) -> f32 { 45 | f32::sqrt(self.length_squared()) 46 | } 47 | 48 | pub fn normalize(&self) -> Normal3 { 49 | *self / self.length() 50 | } 51 | 52 | pub fn abs(&self) -> Normal3 { 53 | Normal3::new(self.x.abs(), self.y.abs(), self.z.abs()) 54 | } 55 | } 56 | 57 | // Operators 58 | impl Add> for Normal3 59 | where 60 | T: Add + Copy, 61 | { 62 | type Output = Normal3; 63 | 64 | fn add(self, rhs: Normal3) -> Normal3 { 65 | Normal3 { 66 | x: self.x + rhs.x, 67 | y: self.y + rhs.y, 68 | z: self.z + rhs.z, 69 | } 70 | } 71 | } 72 | 73 | impl AddAssign> for Normal3 74 | where 75 | T: AddAssign + Copy, 76 | { 77 | fn add_assign(&mut self, other: Normal3) { 78 | self.x += other.x; 79 | self.y += other.y; 80 | self.z += other.z; 81 | } 82 | } 83 | 84 | impl Sub> for Normal3 85 | where 86 | T: Sub + Copy, 87 | { 88 | type Output = Normal3; 89 | 90 | fn sub(self, rhs: Normal3) -> Normal3 { 91 | Normal3 { 92 | x: self.x - rhs.x, 93 | y: self.y - rhs.y, 94 | z: self.z - rhs.z, 95 | } 96 | } 97 | } 98 | 99 | impl SubAssign> for Normal3 100 | where 101 | T: SubAssign + Copy, 102 | { 103 | fn sub_assign(&mut self, other: Normal3) { 104 | self.x -= other.x; 105 | self.y -= other.y; 106 | self.z -= other.z; 107 | } 108 | } 109 | 110 | impl Div for Normal3 111 | where 112 | T: Div + Copy, 113 | { 114 | type Output = Normal3; 115 | 116 | fn div(self, v: T) -> Normal3 { 117 | Normal3 { 118 | x: self.x / v, 119 | y: self.y / v, 120 | z: self.z / v, 121 | } 122 | } 123 | } 124 | 125 | impl DivAssign for Normal3 126 | where 127 | T: DivAssign + Copy, 128 | { 129 | fn div_assign(&mut self, v: T) { 130 | self.x /= v; 131 | self.y /= v; 132 | self.z /= v; 133 | } 134 | } 135 | 136 | impl Mul for Normal3 137 | where 138 | T: Mul + Copy, 139 | { 140 | type Output = Normal3; 141 | 142 | fn mul(self, v: T) -> Normal3 { 143 | Normal3 { 144 | x: self.x * v, 145 | y: self.y * v, 146 | z: self.z * v, 147 | } 148 | } 149 | } 150 | 151 | impl Mul> for f32 { 152 | type Output = Normal3; 153 | 154 | fn mul(self, v: Normal3) -> Normal3 { 155 | Normal3 { 156 | x: self * v.x, 157 | y: self * v.y, 158 | z: self * v.z, 159 | } 160 | } 161 | } 162 | 163 | impl MulAssign for Normal3 164 | where 165 | T: MulAssign + Copy, 166 | { 167 | fn mul_assign(&mut self, v: T) { 168 | self.x *= v; 169 | self.y *= v; 170 | self.z *= v; 171 | } 172 | } 173 | 174 | impl Neg for Normal3 175 | where 176 | T: Neg, 177 | { 178 | type Output = Normal3; 179 | 180 | fn neg(self) -> Normal3 { 181 | Normal3 { 182 | x: -self.x, 183 | y: -self.y, 184 | z: -self.z, 185 | } 186 | } 187 | } 188 | 189 | impl Index for Normal3 { 190 | type Output = T; 191 | 192 | fn index(&self, i: usize) -> &T { 193 | match i { 194 | 0 => &self.x, 195 | 1 => &self.y, 196 | 2 => &self.z, 197 | _ => panic!("Invalid index into normal"), 198 | } 199 | } 200 | } 201 | 202 | impl IndexMut for Normal3 { 203 | fn index_mut(&mut self, i: usize) -> &mut T { 204 | match i { 205 | 0 => &mut self.x, 206 | 1 => &mut self.y, 207 | 2 => &mut self.z, 208 | _ => panic!("Invalid index into normal"), 209 | } 210 | } 211 | } 212 | 213 | impl Default for Normal3 214 | where 215 | T: Default, 216 | { 217 | fn default() -> Self { 218 | Normal3 { 219 | x: T::default(), 220 | y: T::default(), 221 | z: T::default(), 222 | } 223 | } 224 | } 225 | 226 | impl Zero for Normal3 227 | where 228 | T: Num + Copy, 229 | { 230 | fn zero() -> Normal3 { 231 | Normal3::new(T::zero(), T::zero(), T::zero()) 232 | } 233 | 234 | fn is_zero(&self) -> bool { 235 | self.x.is_zero() && self.y.is_zero() && self.z.is_zero() 236 | } 237 | } 238 | 239 | impl From> for Normal3 240 | where 241 | T: Num + Copy, 242 | { 243 | fn from(v: Vector3) -> Normal3 { 244 | Normal3::new(v.x, v.y, v.z) 245 | } 246 | } 247 | 248 | impl Display for Normal3 249 | where 250 | T: Display, 251 | { 252 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 253 | write!(f, "[{}, {}, {}]", self.x, self.y, self.z) 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /rustracer-core/src/shapes/disk.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts; 2 | use std::sync::Arc; 3 | 4 | use crate::bounds::Bounds3f; 5 | use crate::interaction::{Interaction, SurfaceInteraction}; 6 | use crate::paramset::ParamSet; 7 | use crate::ray::Ray; 8 | use crate::sampling::concentric_sample_disk; 9 | use crate::shapes::Shape; 10 | use crate::{clamp, Normal3f, Point2f, Point3f, Transform, Vector3f}; 11 | 12 | #[derive(Debug)] 13 | pub struct Disk { 14 | height: f32, 15 | radius: f32, 16 | inner_radius: f32, 17 | phi_max: f32, 18 | object_to_world: Transform, 19 | world_to_object: Transform, 20 | reverse_orientation: bool, 21 | transform_swaps_handedness: bool, 22 | } 23 | 24 | impl Disk { 25 | pub fn new( 26 | height: f32, 27 | radius: f32, 28 | inner_radius: f32, 29 | phi_max: f32, 30 | object_to_world: Transform, 31 | reverse_orientation: bool, 32 | ) -> Disk { 33 | assert!(radius > 0.0 && inner_radius >= 0.0 && phi_max > 0.0); 34 | let transform_swaps_handedness = object_to_world.swaps_handedness(); 35 | Disk { 36 | height, 37 | radius, 38 | inner_radius, 39 | phi_max: clamp(phi_max, 0.0, 360.0).to_radians(), 40 | world_to_object: object_to_world.inverse(), 41 | object_to_world, 42 | reverse_orientation, 43 | transform_swaps_handedness, 44 | } 45 | } 46 | 47 | pub fn create(o2w: &Transform, reverse_orientation: bool, params: &ParamSet) -> Arc { 48 | let height = params.find_one_float("height", 0.0); 49 | let radius = params.find_one_float("radius", 1.0); 50 | let inner_radius = params.find_one_float("innerradius", 0.0); 51 | let phimax = params.find_one_float("phimax", 360.0); 52 | 53 | Arc::new(Disk::new( 54 | height, 55 | radius, 56 | inner_radius, 57 | phimax, 58 | o2w.clone(), 59 | reverse_orientation, 60 | )) 61 | } 62 | } 63 | 64 | impl Shape for Disk { 65 | fn intersect(&self, r: &Ray) -> Option<(SurfaceInteraction<'_, '_>, f32)> { 66 | // Transform ray to object space 67 | let (ray, _o_err, _d_err) = r.transform(&self.world_to_object); 68 | // Compute plane intersection for disk 69 | if ray.d.z == 0.0 { 70 | // Reject disk intersection for rays parallel to the disk plane 71 | return None; 72 | } 73 | let t_shape_hit = (self.height - ray.o.z) / ray.d.z; 74 | if t_shape_hit <= 0.0 || t_shape_hit > ray.t_max { 75 | return None; 76 | } 77 | // See if hit point is inside radii and phi_max 78 | let mut p_hit = ray.at(t_shape_hit); 79 | let dist2 = p_hit.x * p_hit.x + p_hit.y * p_hit.y; 80 | if dist2 > self.radius * self.radius || dist2 < self.inner_radius * self.inner_radius { 81 | return None; 82 | } 83 | let mut phi = p_hit.y.atan2(p_hit.x); 84 | if phi < 0.0 { 85 | phi += 2.0 * consts::PI; 86 | } 87 | if phi > self.phi_max { 88 | return None; 89 | } 90 | // Find parametric representation of disk hit 91 | let u = phi / self.phi_max; 92 | let r_hit = dist2.sqrt(); 93 | let one_minus_v = (r_hit - self.inner_radius) / (self.radius - self.inner_radius); 94 | let v = 1.0 - one_minus_v; 95 | let dpdu = Vector3f::new(-self.phi_max * p_hit.y, self.phi_max * p_hit.x, 0.0); 96 | let dpdv = Vector3f::new(p_hit.x, p_hit.y, 0.0) * (self.radius - self.inner_radius) / r_hit; 97 | let dndu = Normal3f::new(0.0, 0.0, 0.0); 98 | let dndv = Normal3f::new(0.0, 0.0, 0.0); 99 | 100 | // Refine disk intersection point 101 | p_hit.z = self.height; 102 | 103 | // Compute error bounds for intersection point 104 | let p_err = Vector3f::new(0.0, 0.0, 0.0); 105 | // Initialize SurfaceInteraction from parametric information 106 | let isect = SurfaceInteraction::new( 107 | p_hit, 108 | p_err, 109 | Point2f::new(u, v), 110 | -ray.d, 111 | dpdu, 112 | dpdv, 113 | dndu, 114 | dndv, 115 | self, 116 | ); 117 | // Update t_hit for quadric intersection 118 | 119 | Some((isect.transform(&self.object_to_world), t_shape_hit)) 120 | } 121 | 122 | fn object_bounds(&self) -> Bounds3f { 123 | Bounds3f::from_points( 124 | &Point3f::new(-self.radius, -self.radius, self.height), 125 | &Point3f::new(self.radius, self.radius, self.height), 126 | ) 127 | } 128 | 129 | fn world_bounds(&self) -> Bounds3f { 130 | let ob = self.object_bounds(); 131 | let p1 = &self.object_to_world * &ob.p_min; 132 | let p2 = &self.object_to_world * &ob.p_max; 133 | let p_min = Point3f::new(p1.x.min(p2.x), p1.y.min(p2.y), p1.z.min(p2.z)); 134 | let p_max = Point3f::new(p1.x.max(p2.x), p1.y.max(p2.y), p1.z.max(p2.z)); 135 | Bounds3f::from_points(&p_min, &p_max) 136 | } 137 | 138 | fn sample(&self, u: Point2f) -> (Interaction, f32) { 139 | let pd = concentric_sample_disk(u); 140 | let p_obj = Point3f::new(pd.x * self.radius, pd.y * self.radius, self.height); 141 | let mut it = Interaction::empty(); 142 | it.n = (&self.object_to_world * &Normal3f::new(0.0, 0.0, 1.0)).normalize(); 143 | if self.reverse_orientation { 144 | it.n = -it.n; 145 | } 146 | let (p, p_err) = self 147 | .object_to_world 148 | .transform_point_with_error(&p_obj, &Vector3f::new(0.0, 0.0, 0.0)); 149 | it.p = p; 150 | it.p_error = p_err; 151 | let pdf = 1.0 / self.area(); 152 | 153 | (it, pdf) 154 | } 155 | 156 | fn area(&self) -> f32 { 157 | self.phi_max * 0.5 * (self.radius * self.radius - self.inner_radius * self.inner_radius) 158 | } 159 | 160 | fn reverse_orientation(&self) -> bool { 161 | self.reverse_orientation 162 | } 163 | 164 | fn transform_swaps_handedness(&self) -> bool { 165 | self.transform_swaps_handedness 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /rustracer-core/src/sampler/lowdiscrepancy.rs: -------------------------------------------------------------------------------- 1 | use crate::rng::RNG; 2 | use crate::{Point2f, Point2i, ONE_MINUS_EPSILON}; 3 | 4 | pub fn van_der_corput( 5 | n_samples_per_pixel_sample: u32, 6 | n_pixel_samples: u32, 7 | samples: &mut [f32], 8 | rng: &mut RNG, 9 | ) { 10 | let scramble: u32 = rng.uniform_u32(); 11 | let total_samples = n_samples_per_pixel_sample * n_pixel_samples; 12 | gray_code_sample(&CVAN_DER_CORPUT, total_samples, scramble, samples); 13 | // Randomly shuffle 1D points 14 | for i in 0..n_pixel_samples { 15 | shuffle( 16 | &mut samples[(i as usize * n_samples_per_pixel_sample as usize)..], 17 | n_samples_per_pixel_sample, 18 | 1, 19 | rng, 20 | ); 21 | } 22 | shuffle(samples, n_pixel_samples, n_samples_per_pixel_sample, rng); 23 | } 24 | 25 | pub fn sobol_2d( 26 | n_samples_per_pixel_sample: u32, 27 | n_pixel_samples: u32, 28 | samples: &mut [Point2f], 29 | rng: &mut RNG, 30 | ) { 31 | let scramble = Point2i::new(rng.uniform_u32() as i32, rng.uniform_u32() as i32); 32 | 33 | gray_code_sample_2d( 34 | &CSOBOL[0], 35 | &CSOBOL[1], 36 | n_samples_per_pixel_sample * n_pixel_samples, 37 | scramble, 38 | samples, 39 | ); 40 | // Randomly shuffle 2D points 41 | for i in 0..n_pixel_samples { 42 | shuffle( 43 | &mut samples[(i as usize * n_samples_per_pixel_sample as usize)..], 44 | n_samples_per_pixel_sample, 45 | 1, 46 | rng, 47 | ); 48 | } 49 | shuffle(samples, n_pixel_samples, n_samples_per_pixel_sample, rng); 50 | } 51 | 52 | pub fn radical_inverse(base: u32, a: u64) -> f32 { 53 | match base { 54 | 0 => reverse_bits_64(a) as f32 * 5.4210108624275222e-20, 55 | 1 => radical_inverse_specialized(3, a), 56 | 2 => radical_inverse_specialized(5, a), 57 | 3 => radical_inverse_specialized(7, a), 58 | 4 => radical_inverse_specialized(11, a), 59 | 5 => radical_inverse_specialized(13, a), 60 | _ => unimplemented!(), 61 | } 62 | } 63 | 64 | fn reverse_bits_32(n: u32) -> u32 { 65 | let mut n = n; 66 | n = (n << 16) | (n >> 16); 67 | n = ((n & 0x00ff00ff) << 8) | ((n & 0xff00ff00) >> 8); 68 | n = ((n & 0x0f0f0f0f) << 4) | ((n & 0xf0f0f0f0) >> 4); 69 | n = ((n & 0x33333333) << 2) | ((n & 0xcccccccc) >> 2); 70 | n = ((n & 0x55555555) << 1) | ((n & 0xaaaaaaaa) >> 1); 71 | n 72 | } 73 | 74 | fn reverse_bits_64(n: u64) -> u64 { 75 | let n0 = reverse_bits_32(n as u32); 76 | let n1 = reverse_bits_32((n >> 32) as u32); 77 | (u64::from(n0) << 32) | u64::from(n1) 78 | } 79 | 80 | fn radical_inverse_specialized(base: u32, a: u64) -> f32 { 81 | let mut a = a; 82 | let inv_base: f32 = 1.0 / base as f32; 83 | let mut reversed_digits: u64 = 0; 84 | let mut inv_base_n = 1.0; 85 | while a != 0 { 86 | let next = a / u64::from(base); 87 | let digit = a - next * u64::from(base); 88 | reversed_digits = reversed_digits * u64::from(base) + digit; 89 | inv_base_n *= inv_base; 90 | a = next; 91 | } 92 | assert!(reversed_digits as f32 * inv_base_n < 1.00001); 93 | f32::min(reversed_digits as f32 * inv_base_n, ONE_MINUS_EPSILON) 94 | } 95 | 96 | fn gray_code_sample(c: &[u32], n: u32, scramble: u32, p: &mut [f32]) { 97 | let mut v = scramble; 98 | for i in 0..n { 99 | p[i as usize] = (v as f32 * 2.3283064365386963e-10f32).min(ONE_MINUS_EPSILON); 100 | v ^= c[(i + 1).trailing_zeros() as usize]; 101 | } 102 | } 103 | 104 | fn gray_code_sample_2d(c0: &[u32], c1: &[u32], n: u32, scramble: Point2i, p: &mut [Point2f]) { 105 | let mut v = [scramble.x as u32, scramble.y as u32]; 106 | for i in 0..n { 107 | p[i as usize].x = (v[0] as f32 * 2.3283064365386963e-10f32).min(ONE_MINUS_EPSILON); 108 | p[i as usize].y = (v[1] as f32 * 2.3283064365386963e-10f32).min(ONE_MINUS_EPSILON); 109 | v[0] ^= c0[(i + 1).trailing_zeros() as usize]; 110 | v[1] ^= c1[(i + 1).trailing_zeros() as usize]; 111 | } 112 | } 113 | 114 | fn shuffle(samp: &mut [T], count: u32, n_dimensions: u32, rng: &mut RNG) { 115 | for i in 0..count { 116 | let other: u32 = i + rng.uniform_u32_bounded(count - i); 117 | for j in 0..n_dimensions { 118 | samp.swap( 119 | (n_dimensions * i + j) as usize, 120 | (n_dimensions * other + j) as usize, 121 | ); 122 | } 123 | } 124 | } 125 | 126 | const CVAN_DER_CORPUT: [u32; 32] = [ 127 | 0b10000000000000000000000000000000, 128 | 0b1000000000000000000000000000000, 129 | 0b100000000000000000000000000000, 130 | 0b10000000000000000000000000000, 131 | 0b1000000000000000000000000000, 132 | 0b100000000000000000000000000, 133 | 0b10000000000000000000000000, 134 | 0b1000000000000000000000000, 135 | 0b100000000000000000000000, 136 | 0b10000000000000000000000, 137 | 0b1000000000000000000000, 138 | 0b100000000000000000000, 139 | 0b10000000000000000000, 140 | 0b1000000000000000000, 141 | 0b100000000000000000, 142 | 0b10000000000000000, 143 | 0b1000000000000000, 144 | 0b100000000000000, 145 | 0b10000000000000, 146 | 0b1000000000000, 147 | 0b100000000000, 148 | 0b10000000000, 149 | 0b1000000000, 150 | 0b100000000, 151 | 0b10000000, 152 | 0b1000000, 153 | 0b100000, 154 | 0b10000, 155 | 0b1000, 156 | 0b100, 157 | 0b10, 158 | 0b1, 159 | ]; 160 | /// Generator matrices for Sobol 2D 161 | const CSOBOL: [[u32; 32]; 2] = [ 162 | [ 163 | 0x80000000, 0x40000000, 0x20000000, 0x10000000, 0x8000000, 0x4000000, 0x2000000, 0x1000000, 164 | 0x800000, 0x400000, 0x200000, 0x100000, 0x80000, 0x40000, 0x20000, 0x10000, 0x8000, 0x4000, 165 | 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1, 166 | ], 167 | [ 168 | 0x80000000, 0xc0000000, 0xa0000000, 0xf0000000, 0x88000000, 0xcc000000, 0xaa000000, 169 | 0xff000000, 0x80800000, 0xc0c00000, 0xa0a00000, 0xf0f00000, 0x88880000, 0xcccc0000, 170 | 0xaaaa0000, 0xffff0000, 0x80008000, 0xc000c000, 0xa000a000, 0xf000f000, 0x88008800, 171 | 0xcc00cc00, 0xaa00aa00, 0xff00ff00, 0x80808080, 0xc0c0c0c0, 0xa0a0a0a0, 0xf0f0f0f0, 172 | 0x88888888, 0xcccccccc, 0xaaaaaaaa, 0xffffffff, 173 | ], 174 | ]; 175 | -------------------------------------------------------------------------------- /rustracer-core/src/renderer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | use light_arena::MemoryArena; 5 | use log::{error, info}; 6 | use parking_lot::Mutex; 7 | 8 | use crate::bounds::Bounds2i; 9 | use crate::camera::Camera; 10 | use crate::integrator::SamplerIntegrator; 11 | use crate::sampler::Sampler; 12 | use crate::scene::Scene; 13 | use crate::spectrum::Spectrum; 14 | use crate::stats; 15 | use crate::Point2i; 16 | 17 | stat_counter!("Integrator/Camera rays traced", n_camera_ray); 18 | pub fn init_stats() { 19 | n_camera_ray::init(); 20 | } 21 | 22 | pub fn render( 23 | scene: &Arc, 24 | integrator: &mut dyn SamplerIntegrator, 25 | camera: &dyn Camera, 26 | num_threads: usize, 27 | sampler: &mut dyn Sampler, 28 | block_size: i32, 29 | ) -> Result<()> { 30 | integrator.preprocess(Arc::clone(scene), sampler); 31 | let sample_bounds = camera.get_film().get_sample_bounds(); 32 | let sample_extent = sample_bounds.diagonal(); 33 | let pixel_bounds = integrator.pixel_bounds(); 34 | info!( 35 | "Rendering with sample_bounds = {}, pixel_bounds = {}", 36 | sample_bounds, pixel_bounds 37 | ); 38 | let n_tiles = Point2i::new( 39 | (sample_extent.x + block_size - 1) / block_size, 40 | (sample_extent.y + block_size - 1) / block_size, 41 | ); 42 | 43 | let num_blocks = n_tiles.x * n_tiles.y; 44 | info!("Rendering scene using {} threads", num_threads); 45 | let image_bounds = 46 | Bounds2i::from_points(&Point2i::new(0, 0), &Point2i::new(n_tiles.x, n_tiles.y)); 47 | let tiles_iter = Arc::new(Mutex::new(image_bounds.into_iter())); 48 | let pb = indicatif::ProgressBar::new(num_blocks as _); 49 | pb.set_style( 50 | indicatif::ProgressStyle::default_bar() 51 | .progress_chars("=>-") 52 | .template("[{elapsed_precise}] [{wide_bar}] {percent}% [{pos}/{len}] {eta}"), 53 | ); 54 | pb.tick(); 55 | 56 | crossbeam::scope(|scope| { 57 | // We only want to use references to these in the thread, not move the structs themselves... 58 | let integrator = &integrator; 59 | let camera = &camera; 60 | let pb = &pb; 61 | 62 | // Spawn worker threads 63 | for _ in 0..num_threads { 64 | let mut sampler = sampler.box_clone(); 65 | let tiles_iter = Arc::clone(&tiles_iter); 66 | scope.spawn(move |_| { 67 | loop { 68 | let maybe_tile = { 69 | let mut iter = tiles_iter.lock(); 70 | iter.next() 71 | }; 72 | let tile = if let Some(t) = maybe_tile { 73 | t 74 | } else { 75 | break; 76 | }; 77 | // Render section of image corresponding to `tile` 78 | 79 | // Allocate MemoryArena for tile 80 | let mut arena = MemoryArena::new(1); 81 | 82 | // Get sampler instance for tile 83 | let seed = tile.y * n_tiles.x + tile.x; 84 | sampler.reseed(seed as u64); 85 | 86 | // Compute sample bounds for tile 87 | let x0 = sample_bounds.p_min.x + tile.x * block_size; 88 | let x1 = i32::min(x0 + block_size, sample_bounds.p_max.x); 89 | let y0 = sample_bounds.p_min.y + tile.y * block_size; 90 | let y1 = i32::min(y0 + block_size, sample_bounds.p_max.y); 91 | let tile_bounds = 92 | Bounds2i::from_points(&Point2i::new(x0, y0), &Point2i::new(x1, y1)); 93 | info!("Starting image tile {}", tile_bounds); 94 | 95 | let mut film_tile = camera.get_film().get_film_tile(&tile_bounds); 96 | for p in &tile_bounds { 97 | sampler.start_pixel(p); 98 | 99 | // Do this check after the start_pixel() call; this keeps 100 | // the usage of RNG values from (most) Samplers that use 101 | // RNGs consistent, which improves reproducability / 102 | // debugging 103 | if !pixel_bounds.inside_exclusive(&p) { 104 | continue; 105 | } 106 | 107 | loop { 108 | let alloc = arena.allocator(); 109 | let s = sampler.get_camera_sample(p); 110 | let mut ray = camera.generate_ray_differential(&s); 111 | ray.scale_differentials(1.0 / (sampler.spp() as f32).sqrt()); 112 | n_camera_ray::inc(); 113 | let mut sample_colour = 114 | integrator.li(scene, &mut ray, sampler.as_mut(), &alloc, 0); 115 | if sample_colour.has_nan() { 116 | error!("Not-a-number radiance value returned for pixel {}, sample {}. Setting to black.", p, sampler.current_sample_number()); 117 | sample_colour = Spectrum::black(); 118 | } 119 | if sample_colour.y() < -1e-5 { 120 | error!("Negative luminance value, {}, returned for pixel {}, sample {}. Setting to black.", sample_colour.y(), p, sampler.current_sample_number()); 121 | sample_colour = Spectrum::black(); 122 | } 123 | if sample_colour.y().is_infinite() { 124 | error!("Infinite luminance value returned for pixel {}, sample {}. Setting to black.", p, sampler.current_sample_number()); 125 | sample_colour = Spectrum::black(); 126 | } 127 | film_tile.add_sample(s.p_film, sample_colour); 128 | if !sampler.start_next_sample() { 129 | break; 130 | } 131 | } 132 | } 133 | camera.get_film().merge_film_tile(&film_tile); 134 | pb.inc(1); 135 | } 136 | stats::report_stats(); 137 | }); 138 | } 139 | }).unwrap(); 140 | pb.finish(); 141 | 142 | camera.get_film().write_image() 143 | } 144 | -------------------------------------------------------------------------------- /rustracer-core/src/stats/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! stat_counter( 3 | ($d:expr, $x:ident) => ( 4 | mod $x { 5 | use std::cell::Cell; 6 | use lazy_static::lazy_static; 7 | use state::LocalStorage; 8 | use $crate::stats::StatAccumulator; 9 | 10 | lazy_static! { 11 | static ref VALUE: LocalStorage> = LocalStorage::new(); 12 | } 13 | 14 | pub fn init() { 15 | VALUE.set(|| Cell::new(0)); 16 | let mutex = $crate::stats::STAT_REPORTERS.get(); 17 | let mut vec = mutex.lock(); 18 | vec.push(Box::new(report)); 19 | } 20 | 21 | #[allow(dead_code)] 22 | #[inline(always)] 23 | pub fn inc() { 24 | let v = VALUE.get(); 25 | v.set(v.get() + 1); 26 | } 27 | 28 | pub fn report(acc: &mut StatAccumulator) { 29 | acc.report_counter($d, VALUE.get().get()); 30 | } 31 | } 32 | ); 33 | ); 34 | 35 | #[macro_export] 36 | macro_rules! stat_memory_counter( 37 | ($d:expr, $x:ident) => ( 38 | mod $x { 39 | use std::cell::Cell; 40 | use lazy_static::lazy_static; 41 | use state::LocalStorage; 42 | use $crate::stats::StatAccumulator; 43 | 44 | lazy_static! { 45 | static ref VALUE: LocalStorage> = LocalStorage::new(); 46 | } 47 | 48 | pub fn init() { 49 | VALUE.set(|| Cell::new(0)); 50 | let mutex = $crate::stats::STAT_REPORTERS.get(); 51 | let mut vec = mutex.lock(); 52 | vec.push(Box::new(report)); 53 | } 54 | 55 | #[allow(dead_code)] 56 | #[inline(always)] 57 | pub fn add(a: u64) { 58 | let v = VALUE.get(); 59 | v.set(v.get() + a); 60 | } 61 | 62 | pub fn report(acc: &mut StatAccumulator) { 63 | acc.report_memory_counter($d, VALUE.get().get()); 64 | } 65 | } 66 | ); 67 | ); 68 | 69 | #[macro_export] 70 | macro_rules! stat_int_distribution( 71 | ($d:expr, $x:ident) => ( 72 | mod $x { 73 | use std::cell::Cell; 74 | use std::u64; 75 | use lazy_static::lazy_static; 76 | use state::LocalStorage; 77 | use $crate::stats::StatAccumulator; 78 | 79 | lazy_static! { 80 | static ref SUM: LocalStorage> = LocalStorage::new(); 81 | static ref COUNT: LocalStorage> = LocalStorage::new(); 82 | static ref MIN: LocalStorage> = LocalStorage::new(); 83 | static ref MAX: LocalStorage> = LocalStorage::new(); 84 | } 85 | 86 | pub fn init() { 87 | SUM.set(|| Cell::new(0)); 88 | COUNT.set(|| Cell::new(0)); 89 | MIN.set(|| Cell::new(u64::MAX)); 90 | MAX.set(|| Cell::new(u64::MIN)); 91 | let mutex = $crate::stats::STAT_REPORTERS.get(); 92 | let mut vec = mutex.lock(); 93 | vec.push(Box::new(report)); 94 | } 95 | 96 | #[allow(dead_code)] 97 | #[inline(always)] 98 | pub fn report_value(v: u64) { 99 | let s = SUM.get(); 100 | s.set(s.get() + v); 101 | let c = COUNT.get(); 102 | c.set(c.get() + 1); 103 | let min = MIN.get(); 104 | min.set(u64::min(min.get(), v)); 105 | let max = MAX.get(); 106 | max.set(u64::max(max.get(), v)); 107 | } 108 | 109 | pub fn report(acc: &mut StatAccumulator) { 110 | acc.report_int_distribution( 111 | $d, 112 | SUM.get().get(), 113 | COUNT.get().get(), 114 | MIN.get().get(), 115 | MAX.get().get()); 116 | } 117 | } 118 | ); 119 | ); 120 | 121 | #[macro_export] 122 | macro_rules! stat_percent( 123 | ($d:expr, $x:ident) => ( 124 | mod $x { 125 | use std::cell::Cell; 126 | use lazy_static::lazy_static; 127 | use state::LocalStorage; 128 | use $crate::stats::StatAccumulator; 129 | 130 | lazy_static! { 131 | static ref NUM: LocalStorage> = LocalStorage::new(); 132 | static ref DENOM: LocalStorage> = LocalStorage::new(); 133 | } 134 | 135 | pub fn init() { 136 | NUM.set(|| Cell::new(0)); 137 | DENOM.set(|| Cell::new(0)); 138 | let mutex = $crate::stats::STAT_REPORTERS.get(); 139 | let mut vec = mutex.lock(); 140 | vec.push(Box::new(report)); 141 | } 142 | 143 | #[allow(dead_code)] 144 | #[inline(always)] 145 | pub fn inc() { 146 | let v = NUM.get(); 147 | v.set(v.get() + 1); 148 | } 149 | 150 | #[allow(dead_code)] 151 | #[inline(always)] 152 | pub fn inc_total() { 153 | let v = DENOM.get(); 154 | v.set(v.get() + 1); 155 | } 156 | 157 | pub fn report(acc: &mut StatAccumulator) { 158 | acc.report_percentage($d, NUM.get().get(), DENOM.get().get()); 159 | } 160 | } 161 | ); 162 | ); 163 | 164 | #[macro_export] 165 | macro_rules! stat_ratio( 166 | ($d:expr, $x:ident) => ( 167 | mod $x { 168 | use std::cell::Cell; 169 | use lazy_static::lazy_static; 170 | use state::LocalStorage; 171 | use $crate::stats::StatAccumulator; 172 | 173 | lazy_static! { 174 | static ref NUM: LocalStorage> = LocalStorage::new(); 175 | static ref DENOM: LocalStorage> = LocalStorage::new(); 176 | } 177 | 178 | pub fn init() { 179 | NUM.set(|| Cell::new(0)); 180 | DENOM.set(|| Cell::new(0)); 181 | let mutex = $crate::stats::STAT_REPORTERS.get(); 182 | let mut vec = mutex.lock(); 183 | vec.push(Box::new(report)); 184 | } 185 | 186 | #[allow(dead_code)] 187 | #[inline(always)] 188 | pub fn inc() { 189 | let v = NUM.get(); 190 | v.set(v.get() + 1); 191 | } 192 | 193 | #[allow(dead_code)] 194 | #[inline(always)] 195 | pub fn add(a: u64) { 196 | let v = NUM.get(); 197 | v.set(v.get() + a); 198 | } 199 | 200 | #[allow(dead_code)] 201 | #[inline(always)] 202 | pub fn inc_total() { 203 | let v = DENOM.get(); 204 | v.set(v.get() + 1); 205 | } 206 | 207 | pub fn report(acc: &mut StatAccumulator) { 208 | acc.report_ratio($d, NUM.get().get(), DENOM.get().get()); 209 | } 210 | } 211 | ); 212 | ); 213 | -------------------------------------------------------------------------------- /rustracer-core/src/sampler/zerotwosequence.rs: -------------------------------------------------------------------------------- 1 | use log::info; 2 | use num::Zero; 3 | 4 | use crate::camera::CameraSample; 5 | use crate::paramset::ParamSet; 6 | use crate::rng::RNG; 7 | use crate::sampler::lowdiscrepancy::{sobol_2d, van_der_corput}; 8 | use crate::sampler::Sampler; 9 | use crate::{Point2f, Point2i}; 10 | 11 | #[derive(Clone)] 12 | pub struct ZeroTwoSequence { 13 | spp: usize, 14 | current_pixel: Point2i, 15 | current_pixel_sample_index: usize, 16 | sample_1d_array_sizes: Vec, 17 | sample_2d_array_sizes: Vec, 18 | sample_array_1d: Vec>, 19 | sample_array_2d: Vec>, 20 | array_1d_offset: usize, 21 | array_2d_offset: usize, 22 | // Pixel sampler data 23 | samples_1d: Vec>, 24 | samples_2d: Vec>, 25 | current_1d_dimension: usize, 26 | current_2d_dimension: usize, 27 | rng: RNG, 28 | } 29 | 30 | impl ZeroTwoSequence { 31 | pub fn new(spp: usize, n_sampled_dimensions: usize) -> ZeroTwoSequence { 32 | let spp = spp.next_power_of_two(); 33 | let mut samples1d = Vec::with_capacity(n_sampled_dimensions); 34 | let mut samples2d = Vec::with_capacity(n_sampled_dimensions); 35 | for _ in 0..n_sampled_dimensions { 36 | samples1d.push(vec![0.0; spp]); 37 | samples2d.push(vec![Point2f::new(0.0, 0.0); spp]); 38 | } 39 | 40 | ZeroTwoSequence { 41 | spp, 42 | current_pixel: Point2i::new(0, 0), 43 | current_pixel_sample_index: 0, 44 | sample_1d_array_sizes: Vec::new(), 45 | sample_2d_array_sizes: Vec::new(), 46 | sample_array_1d: Vec::new(), 47 | sample_array_2d: Vec::new(), 48 | array_1d_offset: 0, 49 | array_2d_offset: 0, 50 | samples_1d: samples1d, 51 | samples_2d: samples2d, 52 | current_1d_dimension: 0, 53 | current_2d_dimension: 0, 54 | rng: RNG::new(), 55 | } 56 | } 57 | 58 | pub fn create(ps: &ParamSet) -> Box { 59 | let nsamples = ps.find_one_int("pixelsamples", 16); 60 | let sd = ps.find_one_int("dimensions", 4); 61 | // TODO quickrender 62 | Box::new(Self::new(nsamples as usize, sd as usize)) 63 | } 64 | } 65 | 66 | impl Sampler for ZeroTwoSequence { 67 | fn start_pixel(&mut self, p: Point2i) { 68 | // Generate 1D and 2D pixel sample components using (0, 2)-sequence 69 | for i in 0..self.samples_1d.len() { 70 | van_der_corput( 71 | 1, 72 | self.spp as u32, 73 | &mut self.samples_1d[i][..], 74 | &mut self.rng, 75 | ); 76 | } 77 | for i in 0..self.samples_2d.len() { 78 | sobol_2d( 79 | 1, 80 | self.spp as u32, 81 | &mut self.samples_2d[i][..], 82 | &mut self.rng, 83 | ); 84 | } 85 | 86 | // generate 1d and 2d array samples 87 | for i in 0..self.sample_1d_array_sizes.len() { 88 | van_der_corput( 89 | self.sample_1d_array_sizes[i] as u32, 90 | self.spp as u32, 91 | &mut self.sample_array_1d[i][..], 92 | &mut self.rng, 93 | ); 94 | } 95 | for i in 0..self.sample_2d_array_sizes.len() { 96 | sobol_2d( 97 | self.sample_2d_array_sizes[i] as u32, 98 | self.spp as u32, 99 | &mut self.sample_array_2d[i][..], 100 | &mut self.rng, 101 | ); 102 | } 103 | 104 | self.current_pixel = p; 105 | self.current_pixel_sample_index = 0; 106 | self.array_1d_offset = 0; 107 | self.array_2d_offset = 0; 108 | } 109 | 110 | fn start_next_sample(&mut self) -> bool { 111 | self.array_1d_offset = 0; 112 | self.array_2d_offset = 0; 113 | self.current_1d_dimension = 0; 114 | self.current_2d_dimension = 0; 115 | self.current_pixel_sample_index += 1; 116 | self.current_pixel_sample_index < self.spp 117 | } 118 | 119 | fn request_1d_array(&mut self, n: usize) { 120 | self.sample_1d_array_sizes.push(n); 121 | let mut vec = Vec::new(); 122 | vec.resize(n * self.spp, 0.0); 123 | self.sample_array_1d.push(vec); 124 | } 125 | 126 | fn request_2d_array(&mut self, n: usize) { 127 | info!("Requesting 2d array of {} samples", n); 128 | self.sample_2d_array_sizes.push(n); 129 | let mut vec = Vec::new(); 130 | vec.resize(n * self.spp, Point2f::zero()); 131 | self.sample_array_2d.push(vec); 132 | } 133 | 134 | fn get_1d_array(&mut self, n: usize) -> Option<&[f32]> { 135 | if self.array_1d_offset == self.sample_array_1d.len() { 136 | return None; 137 | } 138 | assert_eq!(self.sample_1d_array_sizes[self.array_1d_offset], n); 139 | assert!(self.current_pixel_sample_index < self.spp); 140 | let res = 141 | &self.sample_array_1d[self.array_1d_offset][(self.current_pixel_sample_index * n)..]; 142 | self.array_1d_offset += 1; 143 | Some(res) 144 | } 145 | 146 | fn get_2d_array(&mut self, n: usize) -> Option<&[Point2f]> { 147 | if self.array_2d_offset == self.sample_array_2d.len() { 148 | return None; 149 | } 150 | assert_eq!(self.sample_2d_array_sizes[self.array_2d_offset], n); 151 | assert!(self.current_pixel_sample_index < self.spp); 152 | let res = 153 | &self.sample_array_2d[self.array_2d_offset][(self.current_pixel_sample_index * n)..]; 154 | self.array_2d_offset += 1; 155 | Some(res) 156 | } 157 | 158 | fn get_1d(&mut self) -> f32 { 159 | if self.current_1d_dimension < self.samples_1d.len() { 160 | let res = self.samples_1d[self.current_1d_dimension][self.current_pixel_sample_index]; 161 | self.current_1d_dimension += 1; 162 | res 163 | } else { 164 | self.rng.uniform_f32() 165 | } 166 | } 167 | 168 | fn get_2d(&mut self) -> Point2f { 169 | if self.current_2d_dimension < self.samples_2d.len() { 170 | let res = self.samples_2d[self.current_2d_dimension][self.current_pixel_sample_index]; 171 | self.current_2d_dimension += 1; 172 | res 173 | } else { 174 | let x = self.rng.uniform_f32(); 175 | let y = self.rng.uniform_f32(); 176 | // For some reason, the C++ version evaluates its second argument first... 177 | // Replicating the same behaviour helps with debuggability. 178 | Point2f::new(y, x) 179 | } 180 | } 181 | 182 | fn get_camera_sample(&mut self, p_raster: Point2i) -> CameraSample { 183 | let p_film = Point2f::from(p_raster) + self.get_2d(); 184 | let time = self.get_1d(); 185 | let p_lens = self.get_2d(); 186 | 187 | CameraSample { 188 | p_film, 189 | p_lens, 190 | time, 191 | } 192 | } 193 | 194 | fn round_count(&self, count: usize) -> usize { 195 | count.next_power_of_two() 196 | } 197 | 198 | fn reseed(&mut self, seed: u64) { 199 | self.rng.set_sequence(seed); 200 | } 201 | 202 | fn spp(&self) -> usize { 203 | self.spp 204 | } 205 | 206 | fn box_clone(&self) -> Box { 207 | Box::new(self.clone()) 208 | } 209 | 210 | fn current_sample_number(&self) -> usize { 211 | self.current_pixel_sample_index 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /rustracer-core/src/efloat.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use std::mem; 3 | use std::ops::{Add, Div, Mul, Sub}; 4 | 5 | use super::MACHINE_EPSILON; 6 | use crate::{next_float_down, next_float_up}; 7 | 8 | #[derive(Debug, Clone, Copy)] 9 | pub struct EFloat { 10 | v: f32, 11 | low: f32, 12 | high: f32, 13 | } 14 | 15 | impl EFloat { 16 | pub fn new(v: f32, err: f32) -> EFloat { 17 | let (low, high) = if err == 0.0 { 18 | (v, v) 19 | } else { 20 | (next_float_down(v - err), next_float_up(v + err)) 21 | }; 22 | 23 | let r = EFloat { v, low, high }; 24 | r.check(); 25 | r 26 | } 27 | 28 | pub fn lower_bound(&self) -> f32 { 29 | self.low 30 | } 31 | 32 | pub fn upper_bound(&self) -> f32 { 33 | self.high 34 | } 35 | 36 | pub fn absolute_error(&self) -> f32 { 37 | self.high - self.low 38 | } 39 | 40 | pub fn sqrt(&self) -> EFloat { 41 | let r = EFloat { 42 | v: self.v.sqrt(), 43 | low: next_float_down(self.lower_bound().sqrt()), 44 | high: next_float_up(self.upper_bound().sqrt()), 45 | }; 46 | r.check(); 47 | r 48 | } 49 | 50 | pub fn abs(&self) -> EFloat { 51 | let r = if self.low >= 0.0 { 52 | // The entire interval is greater than 0: nothing to do! 53 | *self 54 | } else if self.high <= 0.0 { 55 | // The entire interval is less than zero: just inverse everything 56 | EFloat { 57 | v: -self.v, 58 | low: -self.high, 59 | high: -self.low, 60 | } 61 | } else { 62 | // The interval straddles zero 63 | EFloat { 64 | v: self.v.abs(), 65 | low: 0.0, 66 | high: (-self.low).max(self.high), 67 | } 68 | }; 69 | r.check(); 70 | r 71 | } 72 | 73 | #[inline] 74 | pub fn check(&self) { 75 | assert!(!self.v.is_nan()); 76 | assert!(!self.low.is_nan()); 77 | assert!(!self.high.is_nan()); 78 | if !self.low.is_infinite() 79 | && !self.low.is_nan() 80 | && !self.high.is_infinite() 81 | && !self.high.is_nan() 82 | { 83 | assert!(self.low <= self.high); 84 | assert!(self.low <= self.v); 85 | assert!(self.v <= self.high); 86 | } 87 | } 88 | } 89 | 90 | impl Default for EFloat { 91 | fn default() -> Self { 92 | EFloat::new(0.0, 0.0) 93 | } 94 | } 95 | 96 | // Quadratic solver 97 | pub fn solve_quadratic(a: &EFloat, b: &EFloat, c: &EFloat) -> Option<(EFloat, EFloat)> { 98 | let discrim: f64 = f64::from(b.v) * f64::from(b.v) - 4f64 * f64::from(a.v) * f64::from(c.v); 99 | if discrim < 0.0 { 100 | return None; 101 | } 102 | 103 | let root_discrim = discrim.sqrt(); 104 | let float_root_discrim = 105 | EFloat::new(root_discrim as f32, MACHINE_EPSILON * root_discrim as f32); 106 | 107 | let q = if b.v < 0.0 { 108 | -0.5 * (*b - float_root_discrim) 109 | } else { 110 | -0.5 * (*b + float_root_discrim) 111 | }; 112 | let mut t0 = q / *a; 113 | let mut t1 = *c / q; 114 | if t0.v > t1.v { 115 | mem::swap(&mut t0, &mut t1); 116 | } 117 | 118 | Some((t0, t1)) 119 | } 120 | 121 | impl PartialEq for EFloat { 122 | fn eq(&self, other: &Self) -> bool { 123 | self.v == other.v 124 | } 125 | } 126 | 127 | // Operator overloads 128 | 129 | impl Add for EFloat { 130 | type Output = EFloat; 131 | 132 | fn add(self, f: EFloat) -> EFloat { 133 | let r = EFloat { 134 | v: self.v + f.v, 135 | low: next_float_down(self.lower_bound() + f.lower_bound()), 136 | high: next_float_up(self.upper_bound() + f.upper_bound()), 137 | }; 138 | r.check(); 139 | r 140 | } 141 | } 142 | 143 | impl Sub for EFloat { 144 | type Output = EFloat; 145 | 146 | fn sub(self, f: EFloat) -> EFloat { 147 | let r = EFloat { 148 | v: self.v - f.v, 149 | low: next_float_down(self.lower_bound() - f.upper_bound()), 150 | high: next_float_up(self.upper_bound() - f.lower_bound()), 151 | }; 152 | r.check(); 153 | r 154 | } 155 | } 156 | 157 | impl Mul for EFloat { 158 | type Output = EFloat; 159 | 160 | fn mul(self, f: EFloat) -> EFloat { 161 | let prod: [f32; 4] = [ 162 | self.lower_bound() * f.lower_bound(), 163 | self.upper_bound() * f.lower_bound(), 164 | self.lower_bound() * f.upper_bound(), 165 | self.upper_bound() * f.upper_bound(), 166 | ]; 167 | 168 | let r = EFloat { 169 | v: self.v * f.v, 170 | low: next_float_down(f32::min( 171 | f32::min(prod[0], prod[1]), 172 | f32::min(prod[2], prod[3]), 173 | )), 174 | high: next_float_up(f32::max( 175 | f32::max(prod[0], prod[1]), 176 | f32::max(prod[2], prod[3]), 177 | )), 178 | }; 179 | r.check(); 180 | r 181 | } 182 | } 183 | 184 | impl Div for EFloat { 185 | type Output = EFloat; 186 | 187 | fn div(self, f: EFloat) -> EFloat { 188 | let (low, high) = if f.lower_bound() < 0.0 && f.upper_bound() > 0.0 { 189 | (f32::NEG_INFINITY, f32::INFINITY) 190 | } else { 191 | let div: [f32; 4] = [ 192 | self.lower_bound() / f.lower_bound(), 193 | self.upper_bound() / f.lower_bound(), 194 | self.lower_bound() / f.upper_bound(), 195 | self.upper_bound() / f.upper_bound(), 196 | ]; 197 | ( 198 | next_float_down(f32::min(f32::min(div[0], div[1]), f32::min(div[2], div[3]))), 199 | next_float_up(f32::max(f32::max(div[0], div[1]), f32::max(div[2], div[3]))), 200 | ) 201 | }; 202 | let r = EFloat { 203 | v: self.v / f.v, 204 | low, 205 | high, 206 | }; 207 | r.check(); 208 | r 209 | } 210 | } 211 | 212 | impl From for EFloat { 213 | fn from(v: f32) -> EFloat { 214 | EFloat::new(v, 0.0) 215 | } 216 | } 217 | 218 | impl From for f32 { 219 | fn from(v: EFloat) -> f32 { 220 | v.v 221 | } 222 | } 223 | 224 | impl Add for EFloat { 225 | type Output = EFloat; 226 | fn add(self, f: f32) -> EFloat { 227 | self + EFloat::from(f) 228 | } 229 | } 230 | 231 | impl Sub for EFloat { 232 | type Output = EFloat; 233 | fn sub(self, f: f32) -> EFloat { 234 | self - EFloat::from(f) 235 | } 236 | } 237 | 238 | impl Mul for EFloat { 239 | type Output = EFloat; 240 | fn mul(self, f: f32) -> EFloat { 241 | self * EFloat::from(f) 242 | } 243 | } 244 | 245 | impl Div for EFloat { 246 | type Output = EFloat; 247 | fn div(self, f: f32) -> EFloat { 248 | self / EFloat::from(f) 249 | } 250 | } 251 | 252 | impl Add for f32 { 253 | type Output = EFloat; 254 | 255 | fn add(self, f: EFloat) -> EFloat { 256 | EFloat::from(self) + f 257 | } 258 | } 259 | 260 | impl Sub for f32 { 261 | type Output = EFloat; 262 | 263 | fn sub(self, f: EFloat) -> EFloat { 264 | EFloat::from(self) - f 265 | } 266 | } 267 | 268 | impl Mul for f32 { 269 | type Output = EFloat; 270 | 271 | fn mul(self, f: EFloat) -> EFloat { 272 | EFloat::from(self) * f 273 | } 274 | } 275 | 276 | impl Div for f32 { 277 | type Output = EFloat; 278 | 279 | fn div(self, f: EFloat) -> EFloat { 280 | EFloat::from(self) / f 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /rustracer-core/src/texture/imagemap.rs: -------------------------------------------------------------------------------- 1 | use num::Zero; 2 | use std::fmt::Debug; 3 | use std::ops::{AddAssign, Div, Mul}; 4 | use std::path::Path; 5 | use std::sync::Arc; 6 | 7 | use log::{debug, info, warn}; 8 | 9 | use crate::bounds::Bounds2i; 10 | use crate::fileutil; 11 | use crate::imageio::read_image; 12 | use crate::interaction::SurfaceInteraction; 13 | use crate::mipmap::{MIPMap, WrapMode}; 14 | use crate::paramset::TextureParams; 15 | use crate::spectrum::Spectrum; 16 | use crate::texture::{Texture, TextureMapping2D, UVMapping2D}; 17 | use crate::transform::Transform; 18 | use crate::{Clampable, Point2i}; 19 | 20 | #[derive(Debug)] 21 | pub struct ImageTexture { 22 | mapping: Box, 23 | mipmap: Arc>, 24 | } 25 | 26 | impl ImageTexture 27 | where 28 | T: Zero, 29 | T: Clone, 30 | T: Copy, 31 | T: Clampable, 32 | T: Debug, 33 | T: AddAssign, 34 | T: Mul, 35 | T: Div, 36 | T: Sized, 37 | T: Send + Sync, 38 | { 39 | pub fn new T>( 40 | path: &Path, 41 | wrap_mode: WrapMode, 42 | trilerp: bool, 43 | max_aniso: f32, 44 | scale: f32, 45 | gamma: bool, 46 | map: Box, 47 | convert: F, 48 | ) -> ImageTexture { 49 | debug!("Loading texture {}", path.display()); 50 | let (res, texels) = match read_image(path) { 51 | Ok((mut pixels, res)) => { 52 | // Flip image in y; texture coordinate space has (0,0) at the lower 53 | // left corner. 54 | for y in 0..res.y / 2 { 55 | for x in 0..res.x { 56 | let o1 = (y * res.x + x) as usize; 57 | let o2 = ((res.y - 1 - y) * res.x + x) as usize; 58 | pixels.swap(o1, o2); 59 | } 60 | } 61 | 62 | (res, pixels) 63 | } 64 | Err(e) => { 65 | warn!( 66 | "Could not open texture file. Using grey texture instead: {}", 67 | e 68 | ); 69 | (Point2i::new(1, 1), vec![Spectrum::grey(0.18)]) 70 | } 71 | }; 72 | 73 | let converted_texels: Vec = texels 74 | .iter() 75 | .map(|p| { 76 | let s = if gamma { 77 | scale * p.inverse_gamma_correct() 78 | } else { 79 | scale * *p 80 | }; 81 | convert(&s) 82 | }) 83 | .collect(); 84 | 85 | let mipmap = Arc::new(MIPMap::new( 86 | res, 87 | &converted_texels[..], 88 | trilerp, 89 | max_aniso, 90 | wrap_mode, 91 | )); 92 | ImageTexture { 93 | mapping: map, 94 | mipmap, 95 | } 96 | } 97 | } 98 | 99 | impl ImageTexture { 100 | pub fn create(_tex2world: &Transform, tp: &TextureParams<'_>) -> ImageTexture { 101 | let typ = tp.find_string("mapping", "uv"); 102 | let map = if typ == "uv" { 103 | let su = tp.find_float("uscale", 1.0); 104 | let sv = tp.find_float("vscale", 1.0); 105 | let du = tp.find_float("udelta", 0.0); 106 | let dv = tp.find_float("vdelta", 0.0); 107 | 108 | UVMapping2D::new(su, sv, du, dv) 109 | } else { 110 | unimplemented!() 111 | }; 112 | let max_aniso = tp.find_float("maxanisotropy", 8.0); 113 | let trilerp = tp.find_bool("trilinear", false); 114 | let wrap = tp.find_string("wrap", "repeat"); 115 | let wrap_mode = if wrap == "black" { 116 | WrapMode::Black 117 | } else if wrap == "clamp" { 118 | WrapMode::Clamp 119 | } else { 120 | WrapMode::Repeat 121 | }; 122 | let scale = tp.find_float("scale", 1.0); 123 | let filename = tp.find_filename("filename", ""); 124 | let gamma = tp.find_bool( 125 | "gamma", 126 | fileutil::has_extension(&filename, "tga") || fileutil::has_extension(&filename, "png"), 127 | ); 128 | 129 | Self::new( 130 | Path::new(&filename), 131 | wrap_mode, 132 | trilerp, 133 | max_aniso, 134 | scale, 135 | gamma, 136 | Box::new(map), 137 | convert_to_spectrum, 138 | ) 139 | } 140 | 141 | pub fn dump_mipmap(&self) { 142 | info!("Dumping MIPMap levels for debugging..."); 143 | self.mipmap 144 | .pyramid 145 | .iter() 146 | .enumerate() 147 | .for_each(|(i, level)| { 148 | let mut buf = Vec::new(); 149 | for y in 0..level.v_size() { 150 | for x in 0..level.u_size() { 151 | let p = level[(x, y)]; 152 | buf.push(p[0]); 153 | buf.push(p[1]); 154 | buf.push(p[2]); 155 | } 156 | } 157 | crate::imageio::write_image( 158 | format!("mipmap_level_{}.png", i), 159 | &buf[..], 160 | &Bounds2i::from_elements(0, 0, level.u_size() as i32, level.v_size() as i32), 161 | Point2i::new(level.u_size() as i32, level.v_size() as i32), 162 | ) 163 | .unwrap(); 164 | }); 165 | } 166 | } 167 | 168 | impl ImageTexture { 169 | pub fn create(_tex2world: &Transform, tp: &TextureParams<'_>) -> ImageTexture { 170 | let typ = tp.find_string("mapping", "uv"); 171 | let map = if typ == "uv" { 172 | let su = tp.find_float("uscale", 1.0); 173 | let sv = tp.find_float("vscale", 1.0); 174 | let du = tp.find_float("udelta", 0.0); 175 | let dv = tp.find_float("vdelta", 0.0); 176 | 177 | UVMapping2D::new(su, sv, du, dv) 178 | } else { 179 | unimplemented!() 180 | }; 181 | let max_aniso = tp.find_float("maxanisotropy", 8.0); 182 | let trilerp = tp.find_bool("trilinear", false); 183 | let wrap = tp.find_string("wrap", "repeat"); 184 | let wrap_mode = if wrap == "black" { 185 | WrapMode::Black 186 | } else if wrap == "clamp" { 187 | WrapMode::Clamp 188 | } else { 189 | WrapMode::Repeat 190 | }; 191 | let scale = tp.find_float("scale", 1.0); 192 | let filename = tp.find_filename("filename", ""); 193 | let gamma = tp.find_bool( 194 | "gamma", 195 | fileutil::has_extension(&filename, "tga") || fileutil::has_extension(&filename, "png"), 196 | ); 197 | 198 | Self::new( 199 | Path::new(&filename), 200 | wrap_mode, 201 | trilerp, 202 | max_aniso, 203 | scale, 204 | gamma, 205 | Box::new(map), 206 | convert_to_float, 207 | ) 208 | } 209 | } 210 | fn convert_to_spectrum(from: &Spectrum) -> Spectrum { 211 | *from 212 | } 213 | 214 | fn convert_to_float(from: &Spectrum) -> f32 { 215 | from.y() 216 | } 217 | 218 | impl Texture for ImageTexture 219 | where 220 | T: Zero, 221 | T: Clone, 222 | T: Copy, 223 | T: Send, 224 | T: Sync, 225 | T: Clampable, 226 | T: Debug, 227 | T: AddAssign, 228 | T: Mul, 229 | T: Div, 230 | T: Sized, 231 | { 232 | fn evaluate(&self, si: &SurfaceInteraction<'_, '_>) -> T { 233 | let (st, dstdx, dstdy) = self.mapping.map(si); 234 | self.mipmap.lookup_diff(st, dstdx, dstdy) 235 | } 236 | } 237 | --------------------------------------------------------------------------------