├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── pbrt_macros ├── Cargo.toml └── src │ └── lib.rs ├── rendered_scenes ├── classroom.png ├── dining-room.png ├── dragon.png ├── ecosystem.png ├── ganesha.png ├── glass.png ├── kitchen.png ├── living-room-2.png ├── spheres.png ├── staircase.png ├── view-3.png └── view-o.png ├── src ├── accelerators │ ├── bvh.rs │ ├── kdtreeaccel.rs │ └── mod.rs ├── cameras │ ├── environment.rs │ ├── mod.rs │ ├── orthographic.rs │ ├── perspective.rs │ └── realistic.rs ├── commands.lalrpop ├── core │ ├── api.rs │ ├── bssrdf.rs │ ├── camera.rs │ ├── cie.rs │ ├── efloat.rs │ ├── fileutil.rs │ ├── film.rs │ ├── filter.rs │ ├── floatfile.rs │ ├── geometry │ │ ├── bounds.rs │ │ ├── geometry.rs │ │ ├── mod.rs │ │ ├── normal.rs │ │ ├── point.rs │ │ ├── ray.rs │ │ └── vector.rs │ ├── imageio.rs │ ├── integrator.rs │ ├── interaction.rs │ ├── interpolation.rs │ ├── light.rs │ ├── lightdistrib.rs │ ├── lowdiscrepancy.rs │ ├── material.rs │ ├── medium.rs │ ├── memory.rs │ ├── microfacet.rs │ ├── mipmap.rs │ ├── mod.rs │ ├── parallel.rs │ ├── paramset.rs │ ├── pbrt.rs │ ├── primitive.rs │ ├── quaternion.rs │ ├── reflection.rs │ ├── rng.rs │ ├── sampler.rs │ ├── sampling.rs │ ├── scene.rs │ ├── shape.rs │ ├── sobolmatrices.rs │ ├── spectrum.rs │ ├── stats.rs │ ├── texture.rs │ └── transform.rs ├── filters │ ├── boxfilter.rs │ ├── gaussian.rs │ ├── mitchell.rs │ ├── mod.rs │ ├── sinc.rs │ └── triangle.rs ├── integrators │ ├── ao.rs │ ├── bdpt.rs │ ├── directlighting.rs │ ├── mlt.rs │ ├── mod.rs │ ├── path.rs │ ├── sppm.rs │ ├── volpath.rs │ └── whitted.rs ├── lib.rs ├── lights │ ├── diffuse.rs │ ├── distant.rs │ ├── goniometric.rs │ ├── infinite.rs │ ├── mod.rs │ ├── point.rs │ ├── projection.rs │ └── spot.rs ├── main.rs ├── materials │ ├── disney.rs │ ├── fourier.rs │ ├── glass.rs │ ├── hair.rs │ ├── kdsubsurface.rs │ ├── matte.rs │ ├── metal.rs │ ├── mirror.rs │ ├── mix.rs │ ├── mod.rs │ ├── plastic.rs │ ├── substrate.rs │ ├── subsurface.rs │ ├── translucent.rs │ └── uber.rs ├── media │ ├── grid.rs │ ├── homogeneous.rs │ └── mod.rs ├── pbrtparser │ ├── lexer.rs │ ├── mod.rs │ ├── pbrtparser.rs │ ├── syntax.rs │ └── tokens.rs ├── samplers │ ├── halton.rs │ ├── maxmin.rs │ ├── mod.rs │ ├── random.rs │ ├── sobol.rs │ ├── stratified.rs │ └── zerotwosequence.rs ├── scenes │ ├── caustic-glass.pbrt │ ├── geometry │ │ ├── mesh_00001.ply │ │ └── mesh_00002.ply │ ├── spheres-differentials-texfilt.pbrt │ ├── sss-dragon.pbrt │ └── textures │ │ ├── envmap.exr │ │ ├── envmap.hdr │ │ └── sky.hdr ├── shapes │ ├── cone.rs │ ├── curve.rs │ ├── cylinder.rs │ ├── disk.rs │ ├── heightfield.rs │ ├── hyperboloid.rs │ ├── loopsubdiv.rs │ ├── mod.rs │ ├── nurbs.rs │ ├── paraboloid.rs │ ├── plymesh.rs │ ├── sphere.rs │ └── triangle.rs └── textures │ ├── biler.rs │ ├── checkerboard.rs │ ├── constant.rs │ ├── dots.rs │ ├── fbm.rs │ ├── imagemap.rs │ ├── marble.rs │ ├── mix.rs │ ├── mod.rs │ ├── scaled.rs │ ├── uv.rs │ ├── windy.rs │ └── wrinkled.rs └── tests ├── animatedtransform.rs ├── bitops.rs ├── bounds.rs ├── find_interval.rs ├── fourierbsdf.rs ├── fp.rs ├── hg.rs ├── imageio.rs ├── sampling.rs └── shapes.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: build 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: Install latest rust toolchain 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: nightly 23 | default: true 24 | override: true 25 | - name: Build 26 | run: cargo build --verbose 27 | - name: Run tests 28 | run: cargo test --verbose 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pbrt-rust" 3 | version = "0.1.0" 4 | authors = ["Alex "] 5 | edition = "2018" 6 | build = "build.rs" # LALRPOP preprocessing 7 | 8 | [build-dependencies] 9 | lalrpop = "0.19.4" 10 | 11 | [dependencies] 12 | num = "0.2.0" 13 | nalgebra = "0.19.0" 14 | ndarray = { version = "0.14.0", features = ["rayon"] } 15 | enum_dispatch = "0.3.5" 16 | lalrpop-util = "0.19.4" 17 | regex = "1.4.3" 18 | rayon = "1.1" 19 | image = "0.23.12" 20 | array-init = "1.0.0" 21 | num_cpus = "1.0" 22 | atom = "0.4.0" 23 | exr = "1.0.0" 24 | parking_lot = "0.11.1" 25 | typed-arena = "2.0.1" 26 | ordered-float = "2.1.1" 27 | structopt = "0.3.21" 28 | indicatif = {version = "0.15.0", features = ["rayon"]} 29 | bumpalo = "3.4.0" 30 | bumpalo-herd = "0.1.1" 31 | lazy_static = "1.4.0" 32 | crossbeam = "0.7" 33 | hexf = "0.2.1" 34 | anyhow = "1.0" 35 | log = "0.4" 36 | fern = { version = "0.6", features = ["colored"] } 37 | ply-rs = "0.1.3" 38 | byteorder = "1.3.4" 39 | smallvec = "1.6.0" 40 | static_assertions = "1.1.0" 41 | approx = "0.4.0" 42 | float_next_after = "0.1.5" 43 | state = { version = "0.4.2", features = ["tls"] } 44 | pbrt_macros = { path = "./pbrt_macros" } 45 | 46 | [profile.release] 47 | debug = true -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Alex Meli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pbrt-rust 2 | 3 | [![Build Status](https://github.com/alexmeli100/pbrt-rust/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/alexmeli100/pbrt-rust/actions) 4 | [![Github License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/alexmeli100/pbrt-rust/blob/master/LICENSE-APACHE) 5 | [![Github License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/alexmeli100/pbrt-rust/blob/master/LICENSE-MIT) 6 | 7 | Another implementation of PBRT in Rust based on the PBRT book and the C++ version by Matt Pharr, Wenzel Jacob: 8 | 9 | http://www.pbrt.org 10 | 11 | ## Usage 12 | ```shell 13 | > cargo build --release 14 | > ./target/release/pbrt-rust --help 15 | pbrt-rust 0.1 16 | Parse a PBRT scene description file and render it 17 | 18 | USAGE: 19 | pbrt-rust.exe [FLAGS] [OPTIONS] 20 | 21 | FLAGS: 22 | -c, --cat Print a reformatted version of the input file(s) to standard output. Does not render an image 23 | -h, --help Prints help information 24 | -e, --logtostderr Print all logging messages to stderr 25 | -t, --toply Print a formatted version of input file(s) to standard output and convert all triangle meshes 26 | to PLY files. Does not render and image 27 | -V, --version Prints version information 28 | -v, --verbose set LOG verbosity 29 | 30 | OPTIONS: 31 | -w, --cropwindow Specify an image crop window 32 | -l, --logdir Specify directory that log files should be writtend to. Default: system temp 33 | directory (e.g $TMPDIR or /tmp) 34 | -n, --nthreads Use specified number of threads for rendering [default: 0] 35 | -o, --outfile Write the final image to the given filename 36 | 37 | ARGS: 38 | Path to PBRT scene description file 39 | ``` 40 | 41 | ## Example scenes 42 | These are the first few scenes rendered using pbrt-rust. More scenes will be rendered as I fix the remaining bugs in the system if there are any 43 | 44 | ## First scene rendered in pbrt-rust 45 | 46 | ![Two spheres with glass and mirror material](https://github.com/alexmeli100/pbrt-rust/blob/master/rendered_scenes/spheres.png) 47 | 48 | ## Ganesha statue 49 | 50 | ![Ganesha statue](https://github.com/alexmeli100/pbrt-rust/blob/master/rendered_scenes/ganesha.png) 51 | 52 | ## Subsurface Scattering with dragon 53 | 54 | ![Subsurface Scattering](https://github.com/alexmeli100/pbrt-rust/blob/master/rendered_scenes/dragon.png) 55 | 56 | ## Stochastic Progressive Photon Mapping 57 | 58 | ![SPPM with caustic glass](https://github.com/alexmeli100/pbrt-rust/blob/master/rendered_scenes/glass.png) 59 | 60 | ## Country Kitchen by [Jay-Artist][jay-artist] 61 | 62 | ![Kitchen rendered with pbrt-rust](https://github.com/alexmeli100/pbrt-rust/blob/master/rendered_scenes/kitchen.png) 63 | 64 | ## The Wooden Staircase by [Wig42][wig42] 65 | 66 | ![Staircase rendered with pbrt-rust](https://github.com/alexmeli100/pbrt-rust/blob/master/rendered_scenes/staircase.png) 67 | 68 | ## Japanese Classroom by [NovaZeeke][novazeeke] 69 | 70 | ![Japanese classroom rendered with pbrt-rust](https://github.com/alexmeli100/pbrt-rust/blob/master/rendered_scenes/classroom.png) 71 | 72 | ## Ecosystem 73 | 74 | ![Cover image of first edition of PBRT rendered with pbrt-rust](https://github.com/alexmeli100/pbrt-rust/blob/master/rendered_scenes/ecosystem.png) 75 | 76 | ## TODO 77 | - [ ] Render more scenes and fix any bugs 78 | - [ ] Add more unit tests 79 | - [ ] Do atleast some of the exercises in the book as most of them add new features to the system or improve performance 80 | - [ ] Benchmark pbrt-rust against the C++ version 81 | - [ ] Add a system profiler equivalent to the C++ version 82 | - [ ] SIMD optimizations 83 | - [ ] Add feature to create animations 84 | - [ ] Update parts of the system to PBRTv4 when the book releases 85 | - [ ] Implement parsing and rendering of blend files 86 | 87 | ## Other implementations 88 | You can find other implementations of PBRT in rust here 89 | * https://github.com/wahn/rs_pbrt (Very well documented) 90 | * https://github.com/abusch/rustracer 91 | 92 | ## License 93 | 94 | Licensed under either of 95 | 96 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 97 | http://www.apache.org/licenses/LICENSE-2.0) 98 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 99 | 100 | at your option. 101 | 102 | 103 | 104 | [jay-artist]: https://www.blendswap.com/user/Jay-Artist 105 | [wig42]: https://www.blendswap.com/user/Wig42 106 | [novazeeke]: https://www.blendswap.com/user/NovaZeeke 107 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate lalrpop; 2 | 3 | fn main() { 4 | lalrpop::process_root().unwrap(); 5 | } -------------------------------------------------------------------------------- /pbrt_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pbrt_macros" 3 | version = "0.1.0" 4 | authors = ["alexmeli100 "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | syn = "1.0" 14 | proc-macro2 = "1.0.24" 15 | quote = "1.0" 16 | -------------------------------------------------------------------------------- /rendered_scenes/classroom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/classroom.png -------------------------------------------------------------------------------- /rendered_scenes/dining-room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/dining-room.png -------------------------------------------------------------------------------- /rendered_scenes/dragon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/dragon.png -------------------------------------------------------------------------------- /rendered_scenes/ecosystem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/ecosystem.png -------------------------------------------------------------------------------- /rendered_scenes/ganesha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/ganesha.png -------------------------------------------------------------------------------- /rendered_scenes/glass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/glass.png -------------------------------------------------------------------------------- /rendered_scenes/kitchen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/kitchen.png -------------------------------------------------------------------------------- /rendered_scenes/living-room-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/living-room-2.png -------------------------------------------------------------------------------- /rendered_scenes/spheres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/spheres.png -------------------------------------------------------------------------------- /rendered_scenes/staircase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/staircase.png -------------------------------------------------------------------------------- /rendered_scenes/view-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/view-3.png -------------------------------------------------------------------------------- /rendered_scenes/view-o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/rendered_scenes/view-o.png -------------------------------------------------------------------------------- /src/accelerators/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bvh; 2 | pub mod kdtreeaccel; 3 | 4 | pub fn init_stats() { 5 | bvh::init_stats(); 6 | } -------------------------------------------------------------------------------- /src/cameras/environment.rs: -------------------------------------------------------------------------------- 1 | use crate::core::transform::AnimatedTransform; 2 | use crate::core::pbrt::{Float, PI, INFINITY, lerp}; 3 | use std::sync::Arc; 4 | use log::{warn, error}; 5 | use crate::core::film::Film; 6 | use crate::core::medium::Mediums; 7 | use crate::core::camera::{Camera, CameraSample, Cameras}; 8 | use crate::core::geometry::ray::Ray; 9 | use crate::core::geometry::vector::Vector3f; 10 | use crate::core::geometry::point::Point3f; 11 | use crate::core::paramset::ParamSet; 12 | use crate::core::geometry::bounds::Bounds2f; 13 | use crate::get_camera_data; 14 | 15 | pub struct EnvironmentCamera { 16 | pub camera_to_world : AnimatedTransform, 17 | pub shutter_open : Float, 18 | pub shutter_close : Float, 19 | pub film : Arc, 20 | pub medium : Option> 21 | } 22 | 23 | impl EnvironmentCamera { 24 | pub fn new( 25 | camera_to_world: AnimatedTransform, shutter_open: Float, 26 | shutter_close: Float, film: Arc, medium: Option>) -> Self { 27 | Self { 28 | camera_to_world, shutter_open, shutter_close, film, medium 29 | } 30 | } 31 | } 32 | 33 | impl Camera for EnvironmentCamera { 34 | get_camera_data!(); 35 | 36 | fn generate_ray(&self, sample: &CameraSample, r: &mut Ray) -> Float { 37 | // TODO: ProfilePhase 38 | let theta = PI * sample.pfilm.y / self.film.full_resolution.y as Float; 39 | let phi = 2.0 * PI * sample.pfilm.x / self.film.full_resolution.x as Float; 40 | let dir = Vector3f::new( 41 | theta.sin() * phi.cos(), 42 | theta.cos(), 43 | theta.sin() * phi.sin() 44 | ); 45 | *r = Ray::new(&Point3f::new(0.0, 0.0, 0.0), &dir, INFINITY, 46 | lerp(sample.time, self.shutter_open, self.shutter_close), self.medium.clone(), None); 47 | 48 | *r = self.camera_to_world.transform_ray(r); 49 | 50 | 1.0 51 | } 52 | } 53 | 54 | pub fn create_environment_camera( 55 | params: &ParamSet, cam2world: AnimatedTransform, film: Arc, 56 | medium: Option>) -> Option> { 57 | let mut shutteropen = params.find_one_float("shutteropen", 1.0); 58 | let mut shutterclose = params.find_one_float("shutterclose", 1.0); 59 | 60 | if shutterclose < shutteropen { 61 | warn!("Shutter close time [{}] < shutter open [{}]. Swapping time.", 62 | shutterclose, shutteropen); 63 | std::mem::swap(&mut shutteropen, &mut shutterclose); 64 | } 65 | 66 | let _lensradius = params.find_one_float("lensradius", 0.0); 67 | let _focaldistance = params.find_one_float("focaldistance", 1.0e30); 68 | let frame = params.find_one_float( 69 | "frameaspectratio", 70 | film.full_resolution.x as Float / film.full_resolution.y as Float); 71 | 72 | let mut screen = Bounds2f::default(); 73 | 74 | if frame > 1.0 { 75 | screen.p_min.x = -frame; 76 | screen.p_max.x = frame; 77 | screen.p_min.y = -1.0; 78 | screen.p_max.y = 1.0; 79 | } else { 80 | screen.p_min.x = -1.0; 81 | screen.p_max.x = 1.0; 82 | screen.p_min.y = -1.0 / frame; 83 | screen.p_max.y = 1.0 / frame; 84 | } 85 | 86 | let mut swi = 0; 87 | let sw = params.find_float("screenwindow", &mut swi); 88 | 89 | if let Some(ref s) = sw { 90 | if swi == 4 { 91 | screen.p_min.x = s[0]; 92 | screen.p_max.x = s[1]; 93 | screen.p_min.y = s[2]; 94 | screen.p_max.y = s[3]; 95 | } else { 96 | error!("\"screenwindow\" should have four values"); 97 | } 98 | } 99 | 100 | let cam = EnvironmentCamera::new( 101 | cam2world, shutteropen, 102 | shutterclose, film, medium); 103 | 104 | Some(Arc::new(cam.into())) 105 | } -------------------------------------------------------------------------------- /src/cameras/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod orthographic; 2 | pub mod perspective; 3 | pub mod environment; 4 | pub mod realistic; 5 | 6 | pub fn init_stats() { 7 | realistic::init_stats(); 8 | } -------------------------------------------------------------------------------- /src/core/camera.rs: -------------------------------------------------------------------------------- 1 | use enum_dispatch::enum_dispatch; 2 | use crate::core::geometry::point::Point2f; 3 | use crate::core::pbrt::Float; 4 | use crate::core::geometry::ray::Ray; 5 | use crate::core::interaction::{InteractionData}; 6 | use crate::core::geometry::vector::Vector3f; 7 | use crate::core::light::VisibilityTester; 8 | use crate::core::spectrum::Spectrum; 9 | use crate::cameras::orthographic::OrthographicCamera; 10 | use crate::cameras::perspective::PerspectiveCamera; 11 | use crate::cameras::environment::EnvironmentCamera; 12 | use crate::cameras::realistic::RealisticCamera; 13 | use bumpalo::core_alloc::sync::Arc; 14 | use crate::core::film::Film; 15 | use crate::core::medium::Mediums; 16 | use std::fmt::{Display, Result, Formatter}; 17 | 18 | #[macro_export] 19 | macro_rules! get_camera_data { 20 | () => { 21 | fn film(&self) -> Arc { 22 | self.film.clone() 23 | } 24 | 25 | fn shutter_open(&self) -> Float { 26 | self.shutter_open 27 | } 28 | 29 | fn shutter_close(&self) -> Float { 30 | self.shutter_close 31 | } 32 | 33 | fn medium(&self) -> Option> { 34 | self.medium.clone() 35 | } 36 | } 37 | } 38 | 39 | #[enum_dispatch(Cameras)] 40 | pub trait Camera { 41 | fn generate_ray(&self, sample: &CameraSample, r: &mut Ray) -> Float; 42 | 43 | fn generate_ray_differential(&self, sample: &CameraSample, rd: &mut Ray) -> Float { 44 | let wt = self.generate_ray(sample, rd); 45 | 46 | if wt == 0.0 { return 0.0; } 47 | let mut wtx = 0.0; 48 | let mut wty = 0.0; 49 | 50 | // find camera ray after shifting a fraction of a pixel in the x direction 51 | for eps in [0.05, -0.05].iter() { 52 | let mut sshift = *sample; 53 | sshift.pfilm.x += eps; 54 | let mut rx = Ray::default(); 55 | wtx = self.generate_ray(&sshift, &mut rx); 56 | 57 | if let Some(ref mut diff) = rd.diff { 58 | diff.rx_origin = rd.o + (rx.o - rd.o) / *eps; 59 | diff.rx_direction = rd.d + (rx.d - rd.d) / *eps; 60 | 61 | if wtx != 0.0 { 62 | break; 63 | } 64 | } 65 | } 66 | 67 | if wtx == 0.0 { 68 | return 0.0; 69 | } 70 | 71 | // find camera ray after shifting a fraction of a pixel in the y direction 72 | for eps in [0.05, -0.05].iter() { 73 | let mut sshift = *sample; 74 | sshift.pfilm.y += eps; 75 | let mut rx = Ray::default(); 76 | wty = self.generate_ray(&sshift, &mut rx); 77 | 78 | if let Some(ref mut diff) = rd.diff { 79 | diff.ry_origin = rd.o + (rx.o - rd.o) / *eps; 80 | diff.ry_direction = rd.d + (rx.d - rd.d) / *eps; 81 | 82 | if wtx != 0.0 { 83 | break; 84 | } 85 | } 86 | } 87 | 88 | if wty == 0.0 { 89 | return 0.0; 90 | } 91 | 92 | if let Some(ref mut diff) = rd.diff { 93 | diff.has_differentials = true; 94 | } 95 | 96 | wt 97 | } 98 | 99 | fn we(&self, _ray: &Ray, _p_raster2: Option<&mut Point2f>) -> Spectrum { 100 | panic!("Camera::we() is not implemented") 101 | } 102 | 103 | fn pdf_we(&self, _ray: &Ray, _pdf_pos: &mut Float, _pdf_dir: &mut Float) { 104 | panic!("Camera::pdf_we() is not implemented"); 105 | } 106 | 107 | fn sample_wi( 108 | &self, _r: &InteractionData, _u: &Point2f, _wi: &mut Vector3f, _pdf: &mut Float, 109 | _p_raster: &mut Point2f, _vis: &mut VisibilityTester) -> Spectrum { 110 | panic!("Camera::sample_wi() is not implemeted"); 111 | } 112 | 113 | fn film(&self) -> Arc; 114 | 115 | fn shutter_open(&self) -> Float; 116 | 117 | fn shutter_close(&self) -> Float; 118 | 119 | fn medium(&self) -> Option>; 120 | 121 | } 122 | 123 | #[derive(Default, Debug, Copy, Clone)] 124 | pub struct CameraSample { 125 | pub pfilm: Point2f, 126 | pub plens: Point2f, 127 | pub time : Float 128 | } 129 | 130 | impl Display for CameraSample { 131 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 132 | write!(f, 133 | "[ pFilm: {}, pLens: {}, time {}]", 134 | self.pfilm, self.plens, self.time 135 | ) 136 | } 137 | } 138 | 139 | #[enum_dispatch] 140 | pub enum Cameras { 141 | OrthographicCamera, 142 | PerspectiveCamera, 143 | RealisticCamera, 144 | EnvironmentCamera 145 | } -------------------------------------------------------------------------------- /src/core/fileutil.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use std::sync::Mutex; 3 | use std::path::{Path, PathBuf}; 4 | use log::debug; 5 | 6 | 7 | lazy_static! { 8 | static ref SEARCH_DIR: Mutex> = Mutex::new(None); 9 | } 10 | 11 | pub fn set_search_directory>(dir: P) { 12 | let mut sdir = SEARCH_DIR.lock().unwrap(); 13 | sdir.get_or_insert(PathBuf::from(dir.as_ref())); 14 | debug!("Set search directory to {}", dir.as_ref().display()); 15 | 16 | } 17 | 18 | pub fn absolute_path(filename: &str) -> String { 19 | let path = PathBuf::from(filename); 20 | 21 | path.canonicalize() 22 | .ok() 23 | .and_then(|p| p.to_str().map(|res| res.to_owned())) 24 | .unwrap_or_else(|| filename.to_owned()) 25 | } 26 | 27 | pub fn directory_containing>(path: P) -> PathBuf { 28 | let path = path.as_ref(); 29 | let res = path 30 | .canonicalize() 31 | .unwrap() 32 | .parent() 33 | .unwrap_or_else(|| { 34 | panic!("{}", 35 | format!("Failed to get parent directory of input file {}", 36 | path.display())) 37 | }) 38 | .to_owned(); 39 | res 40 | } 41 | 42 | pub fn resolve_filename(filename: &str) -> String { 43 | let sdir = SEARCH_DIR.lock().unwrap(); 44 | let p = Path::new(filename); 45 | 46 | if sdir.is_none() || filename.is_empty() ||p.is_absolute() { 47 | return filename.to_owned() 48 | } 49 | 50 | let mut pp = (*sdir).clone().unwrap(); 51 | 52 | let ps = filename.split("/"); 53 | 54 | for name in ps { 55 | pp.push(name); 56 | } 57 | 58 | pp 59 | .as_path() 60 | .canonicalize() 61 | .ok() 62 | .and_then(|p| p.to_str().map(|res| res.to_owned())) 63 | .unwrap_or_else(|| filename.to_owned()) 64 | } 65 | 66 | pub fn has_extension>(name: P, ext: &str) -> bool { 67 | name 68 | .as_ref() 69 | .extension() 70 | .map(|x| x == ext) 71 | .unwrap_or(false) 72 | } -------------------------------------------------------------------------------- /src/core/filter.rs: -------------------------------------------------------------------------------- 1 | use enum_dispatch::enum_dispatch; 2 | 3 | use crate::filters::boxfilter::BoxFilter; 4 | use crate::filters::triangle::TriangleFilter; 5 | use crate::filters::gaussian::GaussianFilter; 6 | use crate::filters::mitchell::MitchellFilter; 7 | use crate::filters::sinc::LanczosSincFilter; 8 | use crate::core::geometry::vector::Vector2f; 9 | use crate::core::pbrt::Float; 10 | use crate::core::geometry::point::Point2f; 11 | 12 | #[enum_dispatch(Filter)] 13 | pub enum Filters { 14 | BoxFilter, 15 | TriangleFilter, 16 | GaussianFilter, 17 | MitchellFilter, 18 | LanczosSincFilter 19 | } 20 | 21 | #[enum_dispatch] 22 | pub trait Filter { 23 | fn evaluate(&self, p: &Point2f) -> Float; 24 | fn radius(&self) -> Vector2f; 25 | } 26 | 27 | impl Default for Filters { 28 | fn default() -> Self { 29 | BoxFilter::new(Vector2f::default()).into() 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/core/floatfile.rs: -------------------------------------------------------------------------------- 1 | use crate::core::pbrt::Float; 2 | use anyhow::{Result, Context}; 3 | use std::path::Path; 4 | use std::fs::File; 5 | use::std::io::{BufRead, BufReader}; 6 | use log::warn; 7 | 8 | 9 | pub fn read_float_file(name: &str, values: &mut Vec) -> Result<()> { 10 | let path = Path::new(name); 11 | let res = File::open(path).with_context(|| format!("Error: failed to open file {}", name))?; 12 | let reader = BufReader::new(res); 13 | 14 | for (n, l) in reader.lines().enumerate() { 15 | let line = l?; 16 | 17 | if !line.is_empty() { 18 | if line.starts_with('#') { 19 | continue; 20 | } 21 | 22 | for token in line.split_whitespace() { 23 | match token.parse::() { 24 | Ok(val) => values.push(val), 25 | Err(_) => warn!("Unexpected text found at line {} of float file {}", n, name) 26 | } 27 | 28 | let val = token.parse::()?; 29 | values.push(val); 30 | } 31 | 32 | } 33 | } 34 | 35 | Ok(()) 36 | } -------------------------------------------------------------------------------- /src/core/geometry/geometry.rs: -------------------------------------------------------------------------------- 1 | use crate::core::geometry::point::Point3f; 2 | use crate::core::geometry::vector::{Vector3f}; 3 | use crate::core::geometry::normal::Normal3f; 4 | use crate::core::pbrt::*; 5 | 6 | pub fn offset_ray_origin(p: &Point3f, p_error: &Vector3f, n: &Normal3f, w: &Vector3f) -> Point3f { 7 | let d = n.abs().dot_vec(p_error); 8 | let mut offset: Vector3f = Vector3f::from(*n) * d; 9 | 10 | if w.dot_norm(n) < 0.0 { 11 | offset = -offset; 12 | } 13 | 14 | let mut po = *p + offset; 15 | for i in 0..3 { 16 | if offset[i] > 0.0 { 17 | po[i] = next_float_up(po[i]); 18 | } else if offset[i] < 0.0 { 19 | po[i] = next_float_down(po[i]); 20 | } 21 | } 22 | 23 | po 24 | } 25 | 26 | #[inline] 27 | pub fn spherical_direction(sin_theta: Float, cos_theta: Float, phi: Float) -> Vector3f { 28 | Vector3f::new( 29 | sin_theta * phi.cos(), 30 | sin_theta * phi.sin(), 31 | cos_theta 32 | ) 33 | } 34 | 35 | #[inline] 36 | pub fn spherical_direction_basis(sin_theta: Float, cos_theta: Float, phi: Float, x: &Vector3f, y: &Vector3f, z: &Vector3f) -> Vector3f { 37 | *x * sin_theta * phi.cos() + *y * sin_theta * phi.sin() + *z * cos_theta 38 | } 39 | #[inline] 40 | pub fn spherical_theta(v: &Vector3f) -> Float { 41 | clamp(v.z, -1.0, 1.0).acos() 42 | } 43 | 44 | #[inline] 45 | pub fn spherical_phi(v: &Vector3f) -> Float { 46 | let p = v.y.atan2(v.x); 47 | 48 | if p < 0.0 { 49 | p + 2.0 * PI 50 | } else { 51 | p 52 | } 53 | } -------------------------------------------------------------------------------- /src/core/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod vector; 2 | pub mod point; 3 | pub mod normal; 4 | pub mod geometry; 5 | pub mod ray; 6 | pub mod bounds; 7 | -------------------------------------------------------------------------------- /src/core/geometry/ray.rs: -------------------------------------------------------------------------------- 1 | use super::point::Point3f; 2 | use super::vector::Vector3f; 3 | use crate::core::pbrt::Float; 4 | use crate::core::medium::{Mediums}; 5 | use std::sync::Arc; 6 | use std::fmt::{Display, Result, Formatter}; 7 | 8 | #[derive(Clone)] 9 | pub struct Ray { 10 | pub o : Point3f, 11 | pub d : Vector3f, 12 | pub t_max : Float, 13 | pub time : Float, 14 | pub medium : Option>, 15 | pub diff : Option 16 | } 17 | 18 | impl Ray { 19 | pub fn find_point(&self, t: Float) -> Point3f { 20 | self.o + self.d * t 21 | } 22 | 23 | pub fn new(o: &Point3f, d: &Vector3f, t_max: Float, time: Float, medium: Option>, diff: Option) -> Self { 24 | Self { 25 | o: *o, 26 | d: *d, 27 | t_max, 28 | time, 29 | medium, 30 | diff 31 | } 32 | } 33 | 34 | pub fn scale_differential(&mut self, s: Float) { 35 | if let Some(ref mut d) = self.diff { 36 | d.rx_origin = self.o + (d.rx_origin - self.o) * s; 37 | d.ry_origin = self.o + (d.ry_origin - self.o) * s; 38 | d.rx_direction = self.d + (d.rx_direction - self.d) * s; 39 | d.ry_direction = self.d + (d.ry_direction - self.d) * s; 40 | } 41 | } 42 | } 43 | 44 | impl Default for Ray { 45 | fn default() -> Self { 46 | Ray { 47 | t_max: std::f32::INFINITY, 48 | medium: None, 49 | d: Vector3f::default(), 50 | o: Point3f::default(), 51 | time: 0 as Float, 52 | diff: None 53 | } 54 | } 55 | } 56 | 57 | impl Display for Ray { 58 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 59 | write!(f, 60 | "[ o={}, d={}, tMax={}, time={} ]", 61 | self.o, self.d, self.t_max, self.time) 62 | } 63 | } 64 | 65 | #[derive(Debug, Default, Copy, Clone)] 66 | pub struct RayDifferential { 67 | pub rx_origin : Point3f, 68 | pub ry_origin : Point3f, 69 | pub rx_direction : Vector3f, 70 | pub ry_direction : Vector3f, 71 | pub has_differentials : bool 72 | } 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/core/light.rs: -------------------------------------------------------------------------------- 1 | use enum_dispatch::enum_dispatch; 2 | use crate::core::interaction::{Interaction, SurfaceInteraction, InteractionData}; 3 | use crate::core::scene::Scene; 4 | use crate::core::sampler::{Sampler}; 5 | use crate::core::spectrum::Spectrum; 6 | use crate::core::primitive::Primitive; 7 | use crate::core::medium::Medium; 8 | use crate::core::geometry::ray::Ray; 9 | use crate::core::geometry::vector::Vector3f; 10 | use crate::core::pbrt::Float; 11 | use crate::core::geometry::point::Point2f; 12 | use crate::core::geometry::normal::Normal3f; 13 | use crate::lights::projection::ProjectionLight; 14 | use crate::lights::distant::DistantLight; 15 | use crate::lights::spot::SpotLight; 16 | use crate::lights::goniometric::GonioPhotometricLight; 17 | use crate::lights::point::PointLight; 18 | use crate::lights::diffuse::DiffuseAreaLight; 19 | use crate::lights::infinite::InfiniteAreaLight; 20 | 21 | #[macro_export] 22 | macro_rules! init_light_data { 23 | ($l:ident, $f:expr, $n:expr, $mi:expr, $ltw:expr) => { 24 | $l.flags = $f; 25 | $l.nsamples = std::cmp::max(1, $n); 26 | $l.medium_interface = $mi; 27 | $l.light_to_world = *$ltw; 28 | $l.world_to_light = crate::core::transform::Transform::inverse($ltw); 29 | } 30 | } 31 | 32 | #[repr(u8)] 33 | pub enum LightFlags { 34 | DeltaPosition = 1, 35 | DeltaDirection = 2, 36 | Area = 4, 37 | Infinite = 8 38 | } 39 | 40 | #[inline] 41 | pub fn is_delta_light(flags: u8) -> bool { 42 | (flags & LightFlags::DeltaPosition as u8) > 0 || 43 | (flags & LightFlags::DeltaDirection as u8) > 0 44 | } 45 | 46 | #[enum_dispatch] 47 | pub trait Light { 48 | fn power(&self, ) -> Spectrum; 49 | 50 | fn preprocess(&self, _scene: &Scene){} 51 | 52 | fn le(&self, _r: &Ray) -> Spectrum { Spectrum::new(0.0) } 53 | 54 | fn pdf_li(&self, re: &InteractionData, wi: &Vector3f) -> Float; 55 | 56 | fn sample_li( 57 | &self, re: &InteractionData, u: &Point2f, wi: &mut Vector3f, 58 | pdf: &mut Float, vis: &mut VisibilityTester) -> Spectrum; 59 | 60 | fn sample_le( 61 | &self, u1: &Point2f, u2: &Point2f, time: Float, 62 | ray: &mut Ray, nlight: &mut Normal3f, pdf_pos: &mut Float, 63 | pdf_dir: &mut Float) -> Spectrum; 64 | 65 | fn pdf_le( 66 | &self, ray: &Ray, nlight: &Normal3f, 67 | pdf_pos: &mut Float, pdf_dir: &mut Float); 68 | 69 | fn nsamples(&self) -> usize; 70 | 71 | fn flags(&self) -> u8; 72 | 73 | fn l(&self, _intr: &I, _w: &Vector3f) -> Spectrum { 74 | panic!("Not an Area Light") 75 | } 76 | } 77 | 78 | #[enum_dispatch(Light)] 79 | pub enum Lights { 80 | AreaLights(AreaLights), 81 | SpotLight(SpotLight), 82 | PointLight(PointLight), 83 | DistantLight(DistantLight), 84 | InfiniteAreaLight(InfiniteAreaLight), 85 | ProjectionLight(ProjectionLight), 86 | DiffuseAreaLight(DiffuseAreaLight), 87 | GonioPhotometricLight(GonioPhotometricLight) 88 | } 89 | 90 | #[enum_dispatch] 91 | pub trait AreaLight: Light + Clone { 92 | fn l(&self, intr: &I, w: &Vector3f) -> Spectrum; 93 | } 94 | 95 | #[enum_dispatch(AreaLight, Light)] 96 | #[derive(Clone)] 97 | pub enum AreaLights { 98 | DiffuseAreaLight 99 | } 100 | 101 | #[derive(Default)] 102 | pub struct VisibilityTester { 103 | p0: InteractionData, 104 | p1: InteractionData 105 | } 106 | 107 | impl VisibilityTester { 108 | pub fn new(p0: InteractionData, p1: InteractionData) -> Self { 109 | Self { p0, p1 } 110 | } 111 | 112 | pub fn p0(&self) -> &InteractionData { 113 | &self.p0 114 | } 115 | 116 | pub fn p1(&self) -> &InteractionData { 117 | &self.p1 118 | } 119 | 120 | pub fn unoccluded(&self, scene: &Scene) -> bool { 121 | let mut r = self.p0.spawn_rayto_interaction(&self.p1); 122 | !scene.intersect_p(&mut r) 123 | } 124 | 125 | pub fn tr(&self, scene: &Scene, sampler: &mut S) -> Spectrum { 126 | let mut ray = self.p0.spawn_rayto_interaction(&self.p1); 127 | let mut Tr = Spectrum::new(1.0); 128 | 129 | loop { 130 | let mut isect = SurfaceInteraction::default(); 131 | 132 | let hitt = scene.intersect(&mut ray, &mut isect); 133 | 134 | // Handle opaque surface along ray's path 135 | if hitt && isect.primitive.as_ref().unwrap().get_material().is_some() { 136 | return Spectrum::new(0.0); 137 | } 138 | 139 | // Update transmittance for current ray segment 140 | if let Some(ref m) = ray.medium { 141 | Tr *= m.tr(&ray, sampler); 142 | } 143 | 144 | // Generate next ray segment or return final transmittance 145 | if !hitt { break; } 146 | ray = isect.spawn_rayto_interaction(&self.p1) 147 | } 148 | 149 | Tr 150 | } 151 | } -------------------------------------------------------------------------------- /src/core/material.rs: -------------------------------------------------------------------------------- 1 | use enum_dispatch::enum_dispatch; 2 | use crate::core::interaction::SurfaceInteraction; 3 | use bumpalo_herd::Member; 4 | use crate::core::texture::{Texture, TextureFloat}; 5 | use std::sync::Arc; 6 | use std::fmt::{Display, Result, Formatter}; 7 | use crate::materials::matte::MatteMaterial; 8 | use crate::materials::plastic::PlasticMaterial; 9 | use crate::materials::fourier::FourierMaterial; 10 | use crate::materials::mix::MixMaterial; 11 | use crate::materials::glass::GlassMaterial; 12 | use crate::materials::mirror::MirrorMaterial; 13 | use crate::materials::hair::HairMaterial; 14 | use crate::materials::metal::MetalMaterial; 15 | use crate::materials::translucent::TranslucentMaterial; 16 | use crate::materials::substrate::SubstrateMaterial; 17 | use crate::materials::uber::UberMaterial; 18 | use crate::materials::kdsubsurface::KdSubsurfaceMaterial; 19 | use crate::materials::subsurface::SubsurfaceMaterial; 20 | use crate::materials::disney::DisneyMaterial; 21 | use crate::core::geometry::vector::{Vector2f, Vector3f}; 22 | use crate::core::geometry::normal::Normal3f; 23 | 24 | #[derive(Debug, Eq, PartialEq, Copy, Clone)] 25 | pub enum TransportMode { 26 | Radiance, 27 | Importance 28 | } 29 | 30 | impl Display for TransportMode { 31 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { 32 | match self { 33 | TransportMode::Importance => write!(f, "Importance"), 34 | TransportMode::Radiance => write!(f, "Radiance") 35 | } 36 | } 37 | } 38 | 39 | #[enum_dispatch(Materials)] 40 | pub trait Material { 41 | fn compute_scattering_functions<'b: 'b>( 42 | &self, si: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 43 | mat: Option>, mode: TransportMode, allow_multiple_lobes: bool); 44 | } 45 | 46 | pub fn bump(d: &Arc, si: &mut SurfaceInteraction) { 47 | // Compute offset positions and evaluete displacement texture 48 | let mut si_eval = si.clone(); 49 | 50 | // Shift si_eval du in the u direction 51 | let mut du = 0.5 * (si.dudx.get().abs() + si.dudy.get().abs()); 52 | 53 | // The most common reason for du to be zero is for ray that start from 54 | // light sources, where no differentials are available. In this case, 55 | // we try to choose a small enough du so that we still get a decently 56 | // accurate bump value. 57 | if du == 0.0 { du = 0.0005; } 58 | si_eval.p = si.p + si.shading.dpdu * du; 59 | si_eval.uv = si.uv + Vector2f::new(du, 0.0); 60 | si_eval.n = Normal3f::from(si.shading.dpdu.cross(&si.shading.dpdv)).normalize() + si.dndu * du; 61 | let udisplace = d.evaluate(&si_eval); 62 | 63 | // Shift si_eval dv in the v direction 64 | let mut dv = 0.5 * (si.dvdx.get().abs() + si.dvdy.get().abs()); 65 | if dv == 0.0 { dv = 0.0005; } 66 | 67 | si_eval.p = si.p + si.shading.dpdv * dv; 68 | si_eval.uv = si.uv + Vector2f::new(0.0, dv); 69 | si_eval.n = Normal3f::from(si.shading.dpdu.cross(&si.shading.dpdv)).normalize() + si.dndv * dv; 70 | let vdisplace = d.evaluate(&si_eval); 71 | let displace = d.evaluate(si); 72 | 73 | // Compute bump-mapped differential geometry 74 | let dpdu = 75 | si.shading.dpdu + 76 | Vector3f::from(si.shading.n) * ((udisplace - displace) / du) + 77 | Vector3f::from(si.shading.dndu) * displace; 78 | let dpdv = 79 | si.shading.dpdv + 80 | Vector3f::from(si.shading.n) * ((vdisplace - displace) / dv) + 81 | Vector3f::from(si.shading.dndv) * displace; 82 | let dndu = si.shading.dndu; 83 | let dndv = si.shading.dndv; 84 | 85 | si.set_shading_geometry(&dpdu, &dpdv, &dndu, &dndv, false); 86 | 87 | } 88 | 89 | #[enum_dispatch] 90 | pub enum Materials { 91 | MixMaterial, 92 | HairMaterial, 93 | MetalMaterial, 94 | UberMaterial, 95 | GlassMaterial, 96 | MatteMaterial, 97 | MirrorMaterial, 98 | PlasticMaterial, 99 | FourierMaterial, 100 | DisneyMaterial, 101 | SubstrateMaterial, 102 | SubsurfaceMaterial, 103 | TranslucentMaterial, 104 | KdSubsurfaceMaterial 105 | } -------------------------------------------------------------------------------- /src/core/memory.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Index, IndexMut}; 2 | 3 | pub type BlockedArray = BlockedArrayX; 4 | 5 | #[derive(Debug)] 6 | pub struct BlockedArrayX { 7 | data : Vec, 8 | ures : usize, 9 | vres : usize, 10 | ublocks : usize, 11 | } 12 | 13 | impl BlockedArrayX 14 | { 15 | pub fn new(ures: usize, vres: usize, data: Option<&[T]>) -> Self { 16 | let mut arr = Self { 17 | ures, 18 | vres, 19 | ublocks: 0, 20 | data: Vec::new() 21 | }; 22 | 23 | let rures = arr.roundup(ures); 24 | let rvres = arr.roundup(vres); 25 | 26 | arr.ublocks = rures >> LogBlockSize; 27 | 28 | arr.data = vec![T::zero(); rures * rvres]; 29 | 30 | if let Some(ref block) = data { 31 | for v in 0..vres { 32 | for u in 0.. ures { 33 | arr[(u, v)] = block[v * ures + u]; 34 | } 35 | } 36 | } 37 | 38 | arr 39 | } 40 | 41 | fn roundup(&self, x: usize) -> usize { 42 | (x + self.block_size() - 1) & !(self.block_size() - 1) 43 | } 44 | 45 | 46 | pub const fn block_size(&self) -> usize { 1 << LogBlockSize } 47 | 48 | pub fn block(&self, a: usize) -> usize { 49 | a >> LogBlockSize 50 | } 51 | 52 | pub fn offset(&self, a: usize) -> usize { 53 | a & (self.block_size() - 1) 54 | } 55 | 56 | pub fn ures(&self) -> usize { self.ures } 57 | pub fn vres(&self) -> usize { self.vres } 58 | } 59 | 60 | impl Index<(usize, usize)> for BlockedArrayX { 61 | type Output = T; 62 | 63 | fn index(&self, index: (usize, usize)) -> &Self::Output { 64 | let (u, v) = index; 65 | let (bu, bv) = (self.block(u), self.block(v)); 66 | let (ou, ov) = (self.offset(u), self.offset(v)); 67 | let mut offset = self.block_size() * self.block_size() * (self.ublocks * bv + bu); 68 | offset += self.block_size() * ov + ou; 69 | 70 | &self.data[offset] 71 | } 72 | } 73 | 74 | impl IndexMut<(usize, usize)> for BlockedArrayX { 75 | 76 | fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output { 77 | let (u, v) = index; 78 | let (bu, bv) = (self.block(u), self.block(v)); 79 | let (ou, ov) = (self.offset(u), self.offset(v)); 80 | let mut offset = self.block_size() * self.block_size() * (self.ublocks * bv + bu); 81 | offset += self.block_size() * ov + ou; 82 | 83 | &mut self.data[offset] 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod geometry; 2 | pub mod pbrt; 3 | pub mod medium; 4 | pub mod transform; 5 | pub mod quaternion; 6 | pub mod interaction; 7 | pub mod primitive; 8 | #[macro_use] 9 | pub mod reflection; 10 | pub mod bssrdf; 11 | pub mod shape; 12 | pub mod efloat; 13 | pub mod texture; 14 | pub mod material; 15 | pub mod spectrum; 16 | #[macro_use] 17 | pub mod camera; 18 | pub mod film; 19 | #[macro_use] 20 | pub mod light; 21 | pub mod sampling; 22 | #[macro_use] 23 | pub mod sampler; 24 | pub mod rng; 25 | pub mod paramset; 26 | pub mod fileutil; 27 | pub mod floatfile; 28 | pub mod api; 29 | #[macro_use] 30 | pub mod stats; 31 | mod cie; 32 | pub mod filter; 33 | pub mod integrator; 34 | pub mod scene; 35 | pub mod lowdiscrepancy; 36 | pub mod parallel; 37 | #[macro_use] 38 | pub mod microfacet; 39 | pub mod interpolation; 40 | #[macro_use] 41 | pub mod mipmap; 42 | pub mod memory; 43 | pub mod imageio; 44 | pub mod lightdistrib; 45 | pub mod sobolmatrices; -------------------------------------------------------------------------------- /src/core/parallel.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU32, Ordering}; 2 | use crate::core::pbrt::{Float, float_to_bits, bits_to_float}; 3 | 4 | pub struct AtomicFloat { 5 | bits: AtomicU32 6 | } 7 | 8 | impl AtomicFloat { 9 | pub fn new(v: Float) -> Self { 10 | Self { 11 | bits: AtomicU32::new(float_to_bits(v)) 12 | } 13 | } 14 | 15 | pub fn add(&self, v: Float) { 16 | let mut oldbits = self.bits.load(Ordering::Relaxed); 17 | 18 | loop { 19 | let newbits = float_to_bits(bits_to_float(oldbits) + v); 20 | 21 | match self.bits.compare_exchange_weak(oldbits, newbits, Ordering::SeqCst, Ordering::Relaxed) { 22 | Ok(_) => break, 23 | Err(x) => oldbits = x 24 | } 25 | } 26 | } 27 | } 28 | 29 | impl Default for AtomicFloat { 30 | fn default() -> Self { 31 | Self { 32 | bits: AtomicU32::new(0) 33 | } 34 | } 35 | } 36 | 37 | impl From for f32 { 38 | fn from(a: AtomicFloat) -> Self { 39 | f32::from_bits(a.bits.load(Ordering::Relaxed)) 40 | } 41 | } 42 | 43 | impl From for f64 { 44 | fn from(a: AtomicFloat) -> Self { 45 | f64::from_bits(a.bits.load(Ordering::Relaxed) as u64) 46 | } 47 | } 48 | 49 | impl<'a> From<&'a AtomicFloat> for f32 { 50 | fn from(a: &'a AtomicFloat) -> Self { 51 | f32::from_bits(a.bits.load(Ordering::Relaxed)) 52 | } 53 | } 54 | 55 | impl<'a> From<&'a AtomicFloat> for f64 { 56 | fn from(a: &'a AtomicFloat) -> Self { 57 | f64::from_bits(a.bits.load(Ordering::Relaxed) as u64) 58 | } 59 | } 60 | 61 | impl Clone for AtomicFloat { 62 | fn clone(&self) -> Self { 63 | let bits = self.bits.load(Ordering::SeqCst); 64 | 65 | Self { 66 | bits: AtomicU32::new(bits) 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/core/primitive.rs: -------------------------------------------------------------------------------- 1 | use enum_dispatch::enum_dispatch; 2 | use crate::core::geometry::bounds::Bounds3f; 3 | use crate::core::geometry::ray::Ray; 4 | use crate::core::interaction::{SurfaceInteraction}; 5 | use crate::core::material::Material; 6 | use crate::core::material::{Materials, TransportMode}; 7 | use std::sync::Arc; 8 | use crate::core::light::{Lights}; 9 | use bumpalo_herd::Member; 10 | use crate::core::transform::{AnimatedTransform, Transform}; 11 | use crate::core::shape::{Shape, Shapes}; 12 | use crate::core::medium::MediumInterface; 13 | use crate::accelerators::bvh::BVHAccel; 14 | use crate::accelerators::kdtreeaccel::KdTreeAccel; 15 | 16 | #[enum_dispatch] 17 | pub trait Primitive { 18 | fn world_bound(&self) -> Bounds3f; 19 | fn intersect( 20 | &self, r: &mut Ray, 21 | s: &mut SurfaceInteraction, 22 | p: Arc) -> bool; 23 | 24 | fn intersect_p(&self, r: &mut Ray) -> bool; 25 | fn get_material(&self) -> Option>; 26 | fn get_area_light(&self) -> Option>; 27 | fn compute_scattering_functions<'b: 'b>( 28 | &self, isect: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 29 | mode: TransportMode, allow_multiple_lobes: bool); 30 | } 31 | 32 | 33 | #[enum_dispatch(Primitive)] 34 | pub enum Primitives { 35 | BVHAccel, 36 | KdTreeAccel, 37 | TransformedPrimitive, 38 | GeometricPrimitive, 39 | } 40 | 41 | pub struct TransformedPrimitive { 42 | primitive : Arc, 43 | prim_to_world : AnimatedTransform 44 | 45 | } 46 | 47 | impl TransformedPrimitive { 48 | pub fn new(primitive: Arc, prim_to_world: AnimatedTransform) -> Self { 49 | Self { primitive, prim_to_world } 50 | } 51 | } 52 | 53 | impl Primitive for TransformedPrimitive { 54 | fn world_bound(&self) -> Bounds3f { 55 | self.prim_to_world.motion_bounds(&self.primitive.world_bound()) 56 | } 57 | 58 | fn intersect( 59 | &self, r: &mut Ray, 60 | isect: &mut SurfaceInteraction, 61 | _p: Arc) -> bool { 62 | // Compute ray after transformation by PrimitiveToWorld 63 | let mut inter_prim_toworld = Transform::default(); 64 | self.prim_to_world.interpolate(r.time, &mut inter_prim_toworld); 65 | let mut ray = Transform::inverse(&inter_prim_toworld).transform_ray(r); 66 | 67 | if !self.primitive.intersect(&mut ray, isect, self.primitive.clone()) { 68 | return false; 69 | } 70 | 71 | r.t_max = ray.t_max; 72 | 73 | // Transform instance's intersection data to world space 74 | if !inter_prim_toworld.is_identity() { 75 | *isect = inter_prim_toworld.transform_surface_interaction(isect); 76 | } 77 | 78 | assert!(isect.n.dot(&isect.shading.n) >= 0.0); 79 | true 80 | } 81 | 82 | fn intersect_p(&self, r: &mut Ray) -> bool { 83 | let mut inter_prim_toworld = Transform::default(); 84 | self.prim_to_world.interpolate(r.time, &mut inter_prim_toworld); 85 | let inter_worldto_prim = Transform::inverse(&inter_prim_toworld); 86 | 87 | self.primitive.intersect_p(&mut inter_worldto_prim.transform_ray(r)) 88 | } 89 | 90 | fn get_material(&self) -> Option> { 91 | None 92 | } 93 | 94 | fn get_area_light(&self) -> Option> { 95 | None 96 | } 97 | 98 | fn compute_scattering_functions<'b: 'b>( 99 | &self, _isect: &mut SurfaceInteraction<'b>, _arena: &Member<'b>, 100 | _mode: TransportMode, _allow_multiple_lobes: bool) { 101 | panic!("TransformedPrimitive::compute_scattering_functions() shouldn't be called") 102 | } 103 | } 104 | 105 | #[derive(Clone)] 106 | pub struct GeometricPrimitive { 107 | shape : Arc, 108 | material : Option>, 109 | area_light : Option>, 110 | medium_interface : MediumInterface 111 | } 112 | 113 | impl GeometricPrimitive { 114 | pub fn new( 115 | shape: Arc, material: Option>, 116 | area_light: Option>, medium_interface: MediumInterface) -> Self { 117 | Self { shape, material, area_light, medium_interface } 118 | } 119 | } 120 | 121 | impl Primitive for GeometricPrimitive { 122 | fn world_bound(&self) -> Bounds3f { 123 | self.shape.world_bound() 124 | } 125 | 126 | fn intersect( 127 | &self, r: &mut Ray, 128 | isect: &mut SurfaceInteraction, 129 | p: Arc) -> bool { 130 | let mut thit = 0.0; 131 | 132 | let s = Some(self.shape.clone()); 133 | if !self.shape.intersect(r, &mut thit, isect, true, s) { 134 | return false; 135 | } 136 | 137 | r.t_max = thit; 138 | isect.primitive = Some(p); 139 | assert!(isect.n.dot(&isect.shading.n) >= 0.0, "n: {}, shanding_n: {}", isect.n, isect.shading.n); 140 | 141 | let m = if self.medium_interface.is_medium_transition() { 142 | self.medium_interface.clone() 143 | } else { 144 | MediumInterface::new(r.medium.clone()) 145 | }; 146 | 147 | isect.medium_interface = Some(m); 148 | true 149 | } 150 | 151 | fn intersect_p(&self, r: &mut Ray) -> bool { 152 | self.shape.intersect_p(r, true) 153 | } 154 | 155 | fn get_material(&self) -> Option> { 156 | self.material.clone() 157 | } 158 | 159 | fn get_area_light(&self) -> Option> { 160 | self.area_light.clone() 161 | } 162 | 163 | fn compute_scattering_functions<'b: 'b>( 164 | &self, isect: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 165 | mode: TransportMode, allow_multiple_lobes: bool) { 166 | // TODO: ProfilePhase 167 | 168 | if let Some(m) = &self.material { 169 | m.compute_scattering_functions(isect, arena, Some(m.clone()), mode, allow_multiple_lobes); 170 | } 171 | 172 | assert!(isect.n.dot(&isect.shading.n) >= 0.0) 173 | } 174 | } 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/core/quaternion.rs: -------------------------------------------------------------------------------- 1 | use crate::core::geometry::vector::Vector3f; 2 | use crate::core::pbrt::{Float, clamp}; 3 | use super::transform::Transform; 4 | use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Neg, Div, DivAssign}; 5 | 6 | #[derive(Debug, Default, Copy, Clone)] 7 | pub struct Quaternion { 8 | pub v: Vector3f, 9 | pub w: Float 10 | } 11 | 12 | impl Quaternion { 13 | pub fn new(v: &Vector3f) -> Quaternion { 14 | Self { 15 | v: *v, 16 | w: 1.0, 17 | } 18 | } 19 | 20 | pub fn dot(&self, q1: &Self) -> Float { 21 | self.v.dot(&q1.v) + self.w * q1.w 22 | } 23 | 24 | pub fn normalize(&self) -> Self { 25 | *self / self.dot(self).sqrt() 26 | } 27 | } 28 | 29 | impl Add for Quaternion { 30 | type Output = Self; 31 | 32 | fn add(self, other: Self) -> Self::Output { 33 | Self { 34 | v: self.v + other.v, 35 | w: self.w + other.w 36 | } 37 | } 38 | } 39 | 40 | impl AddAssign for Quaternion { 41 | fn add_assign(&mut self, other: Self) { 42 | self.v += other.v; 43 | self.w += other.w; 44 | } 45 | } 46 | 47 | impl Sub for Quaternion { 48 | type Output = Self; 49 | 50 | fn sub(self, other: Self) -> Self::Output { 51 | Self { 52 | v: self.v - other.v, 53 | w: self.w - other.w 54 | } 55 | } 56 | } 57 | 58 | impl SubAssign for Quaternion { 59 | fn sub_assign(&mut self, other: Self) { 60 | self.v -= other.v; 61 | self.w -= other.w; 62 | } 63 | } 64 | 65 | impl Neg for Quaternion { 66 | type Output = Self; 67 | 68 | fn neg(self) -> Self::Output { 69 | Self { 70 | v: -self.v, 71 | w: -self.w 72 | } 73 | } 74 | } 75 | 76 | impl Mul for Quaternion { 77 | type Output = Self; 78 | 79 | fn mul(self, f: Float) -> Self::Output { 80 | Self { 81 | v: self.v * f, 82 | w: self.w * f 83 | } 84 | } 85 | } 86 | 87 | impl MulAssign for Quaternion { 88 | fn mul_assign(&mut self, f: Float) { 89 | self.v *= f; 90 | self.w *= f; 91 | } 92 | } 93 | 94 | impl Div for Quaternion { 95 | type Output = Self; 96 | 97 | fn div(self, f: Float) -> Self::Output { 98 | Self { 99 | v: self.v / f, 100 | w: self.w / f 101 | } 102 | } 103 | } 104 | 105 | impl DivAssign for Quaternion { 106 | fn div_assign(&mut self, f: Float) { 107 | self.v /= f; 108 | self.w /= f; 109 | } 110 | } 111 | 112 | impl From for Quaternion { 113 | fn from(t: Transform) -> Self { 114 | let m= t.m; 115 | let trace: Float = m.m[0][0] + m.m[1][1] + m.m[2][2]; 116 | 117 | if trace > 0.0 { 118 | 119 | let mut s: Float = (trace + 1.0).sqrt(); 120 | let w: Float = s / 2.0; 121 | s = 0.5 / s; 122 | 123 | Self { 124 | v: Vector3f::new( 125 | (m.m[2][1] - m.m[1][2]) * s, 126 | (m.m[0][2] - m.m[2][0]) * s, 127 | (m.m[1][0] - m.m[0][1]) * s), 128 | w, 129 | } 130 | } else { 131 | // compute largest of $x$, $y$, or $z$, then remaining components 132 | let nxt: [usize; 3] = [1, 2, 0]; 133 | let mut q: [Float; 3] = [0.0; 3]; 134 | let mut i = if m.m[1][1] > m.m[0][0] { 1 } else { 0 }; 135 | if m.m[2][2] > m.m[i][i] { 136 | i = 2; 137 | } 138 | let j = nxt[i]; 139 | let k = nxt[j]; 140 | let mut s: Float = ((m.m[i][i] - (m.m[j][j] + m.m[k][k])) + 1.0).sqrt(); 141 | q[i] = s * 0.5; 142 | if s != 0.0 { 143 | s = 0.5 / s; 144 | } 145 | let w: Float = (m.m[k][j] - m.m[j][k]) * s; 146 | q[j] = (m.m[j][i] + m.m[i][j]) * s; 147 | q[k] = (m.m[k][i] + m.m[i][k]) * s; 148 | 149 | Self { v: Vector3f::new(q[0], q[1], q[2]), w, } 150 | } 151 | } 152 | } 153 | 154 | pub fn slerp(q1: &Quaternion, q2: &Quaternion, t: Float) -> Quaternion { 155 | let cos_theta = q1.dot(q2); 156 | 157 | if cos_theta > 0.9995 { 158 | (*q1 * (1.0 - t) + *q2 * t).normalize() 159 | } else { 160 | let theta = clamp(cos_theta, -1.0, 1.0).acos(); 161 | let thetap = theta * t; 162 | let qperp = (*q2 - *q1 * cos_theta).normalize(); 163 | 164 | *q1 * thetap.cos() + qperp * thetap.sin() 165 | } 166 | } -------------------------------------------------------------------------------- /src/core/rng.rs: -------------------------------------------------------------------------------- 1 | use hexf::*; 2 | use crate::core::pbrt::Float; 3 | 4 | pub const FLOAT_ONE_MINUS_EPSILON : Float = hexf32!("0x1.fffffep-1"); 5 | pub const ONE_MINUS_EPSILON : Float = FLOAT_ONE_MINUS_EPSILON; 6 | pub const PCG32_DEFAULT_STATE : u64 = 0x853c_49e6_748f_ea9b; 7 | pub const PCG32_DEFAULT_STREAM : u64 = 0xda3e_39cb_94b9_5bdb; 8 | pub const PCG32_MULT : u64 = 0x5851_f42d_4c95_7f2d; 9 | 10 | #[derive(Debug, Copy, Clone)] 11 | pub struct RNG { 12 | state: u64, 13 | inc : u64 14 | } 15 | 16 | impl Default for RNG { 17 | fn default() -> Self { 18 | Self { 19 | state: PCG32_DEFAULT_STATE, 20 | inc: PCG32_DEFAULT_STREAM 21 | } 22 | } 23 | } 24 | 25 | impl RNG { 26 | pub fn new(sequence_index: u64) -> Self { 27 | let mut rng = RNG::default(); 28 | rng.set_sequence(sequence_index); 29 | 30 | rng 31 | } 32 | 33 | pub fn uniform_int32(&mut self) -> u32 { 34 | let oldstate: u64 = self.state; 35 | let (mul, _) = oldstate.overflowing_mul(PCG32_MULT); 36 | let (add, _) = mul.overflowing_add(self.inc); 37 | self.state = add; 38 | let (shr, _) = oldstate.overflowing_shr(18); 39 | let combine = shr ^ oldstate; 40 | let (shr, _) = combine.overflowing_shr(27); 41 | let xorshifted: u32 = shr as u32; 42 | let (shr, _) = oldstate.overflowing_shr(59); 43 | let rot: u32 = shr as u32; 44 | let (shr, _overflow) = xorshifted.overflowing_shr(rot); 45 | let neg = !rot; 46 | let (add, _) = neg.overflowing_add(1_u32); 47 | let (shl, _) = xorshifted.overflowing_shl(add & 31); 48 | shr | shl 49 | } 50 | 51 | pub fn uniform_int32_2(&mut self, b: u32) -> u32 { 52 | let threshold = (!b + 1) % b; 53 | 54 | loop { 55 | let r = self.uniform_int32(); 56 | if r >= threshold { 57 | return r % b; 58 | } 59 | 60 | } 61 | } 62 | 63 | pub fn uniform_float(&mut self) -> Float { 64 | ONE_MINUS_EPSILON.min(self.uniform_int32() as Float * hexf32!("0x1.0p-32")) 65 | } 66 | 67 | pub fn set_sequence(&mut self, initseq: u64) { 68 | self.state = 0; 69 | let (shl, _) = initseq.overflowing_shl(1); 70 | self.inc = shl | 1; 71 | self.uniform_int32(); 72 | let (add, _) = self.state.overflowing_add(PCG32_DEFAULT_STATE); 73 | self.state = add; 74 | self.uniform_int32(); 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/core/scene.rs: -------------------------------------------------------------------------------- 1 | use bumpalo::core_alloc::sync::Arc; 2 | use crate::core::light::{Lights, LightFlags}; 3 | use crate::core::primitive::Primitives; 4 | use crate::core::geometry::bounds::Bounds3f; 5 | use crate::core::medium::Medium; 6 | use crate::core::light::Light; 7 | use crate::core::primitive::Primitive; 8 | use crate::core::sampler::Samplers; 9 | use crate::core::geometry::ray::Ray; 10 | use crate::core::interaction::{SurfaceInteraction, Interaction}; 11 | use crate::core::spectrum::Spectrum; 12 | use crate::core::geometry::vector::Vector3f; 13 | 14 | stat_counter!("Intersections/Regular ray intersection tests", nintersection_tests); 15 | stat_counter!("Intersections/Shadow ray intersection tests", nshadow_tests); 16 | 17 | pub fn init_stats() { 18 | nintersection_tests::init(); 19 | nshadow_tests::init(); 20 | } 21 | 22 | #[derive(Clone)] 23 | pub struct Scene { 24 | pub lights : Vec>, 25 | pub infinite_lights : Vec>, 26 | pub aggregate : Arc, 27 | // world bound 28 | pub wb : Bounds3f 29 | } 30 | 31 | impl Scene { 32 | pub fn new(aggregate: Arc, lights: Vec>) -> Self { 33 | let wb = aggregate.world_bound(); 34 | let mut scene = Scene { 35 | aggregate, wb, lights, 36 | infinite_lights: Vec::new() 37 | }; 38 | 39 | 40 | let mut infinite_lights = Vec::new(); 41 | 42 | for light in scene.lights.iter() { 43 | light.preprocess(&scene); 44 | 45 | if (light.flags() & LightFlags::Infinite as u8) != 0 { 46 | infinite_lights.push(light.clone()) 47 | } 48 | } 49 | 50 | scene.infinite_lights = infinite_lights; 51 | scene 52 | } 53 | 54 | pub fn intersect(&self, r: &mut Ray, isect: &mut SurfaceInteraction) -> bool { 55 | nintersection_tests::inc(); 56 | assert_ne!(r.d, Vector3f::new(0.0, 0.0, 0.0)); 57 | 58 | self.aggregate.intersect(r, isect, self.aggregate.clone()) 59 | } 60 | 61 | pub fn intersect_p(&self, ray: &mut Ray) -> bool { 62 | nshadow_tests::inc(); 63 | assert_ne!(ray.d, Vector3f::new(0.0, 0.0, 0.0)); 64 | 65 | self.aggregate.intersect_p(ray) 66 | } 67 | 68 | pub fn intersect_tr( 69 | &self, mut ray: Ray, sampler: &mut Samplers, 70 | isect: &mut SurfaceInteraction, tr: &mut Spectrum) -> bool { 71 | *tr = Spectrum::new(1.0); 72 | 73 | loop { 74 | let hits = self.intersect(&mut ray, isect); 75 | 76 | // Accumulate beam transmittance for ray segment 77 | if let Some(ref m) = ray.medium { 78 | *tr *= m.tr(&ray, sampler); 79 | } 80 | 81 | // Initialize next ray segment or terminate transmittance computation 82 | if !hits { return false; } 83 | if isect.primitive.as_ref().unwrap().get_material().is_some() { return true; } 84 | 85 | ray = isect.spawn_ray(&ray.d); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/core/shape.rs: -------------------------------------------------------------------------------- 1 | use crate::core::geometry::bounds::Bounds3f; 2 | use crate::core::pbrt::{Float}; 3 | use crate::core::geometry::ray::Ray; 4 | use enum_dispatch::enum_dispatch; 5 | use crate::core::interaction::{SurfaceInteraction, Interaction, InteractionData}; 6 | use crate::core::geometry::point::{Point2f, Point3f}; 7 | use crate::core::geometry::vector::Vector3f; 8 | use crate::core::transform::Transform; 9 | use crate::shapes::sphere::Sphere; 10 | use crate::shapes::cylinder::Cylinder; 11 | use crate::shapes::disk::Disk; 12 | use crate::shapes::triangle::Triangle; 13 | use crate::shapes::curve::Curve; 14 | use crate::shapes::cone::Cone; 15 | use crate::shapes::paraboloid::Paraboloid; 16 | use crate::core::lowdiscrepancy::radical_inverse; 17 | use crate::shapes::hyperboloid::Hyperboloid; 18 | use std::sync::Arc; 19 | 20 | #[enum_dispatch] 21 | pub trait Shape { 22 | fn object_bound(&self) -> Bounds3f; 23 | fn world_bound(&self) -> Bounds3f { 24 | self.object_to_world().transform_bounds(&self.object_bound()) 25 | } 26 | fn intersect( 27 | &self, r: &Ray, t_hit: &mut Float, 28 | isect: &mut SurfaceInteraction, 29 | test_aphatexture: bool, s: Option>) -> bool; 30 | 31 | fn intersect_p(&self, r: &Ray, test_alpha_texture: bool) -> bool { 32 | let mut t_hit = r.t_max; 33 | let mut isect = SurfaceInteraction::default(); 34 | 35 | self.intersect(r, &mut t_hit, &mut isect, test_alpha_texture, None) 36 | } 37 | 38 | fn area(&self) -> Float; 39 | fn sample(&self, u: &Point2f, pdf: &mut Float) -> InteractionData; 40 | fn sample_interaction(&self, i: &InteractionData, u: &Point2f, pdf: &mut Float) -> InteractionData { 41 | let intr = self.sample(u, pdf); 42 | let mut wi = intr.p - i.p; 43 | 44 | 45 | if wi.length_squared() == 0.0 { 46 | *pdf = 0.0; 47 | } else { 48 | wi = wi.normalize(); 49 | // Convert from area measure, as returned by the Sample() call 50 | // above, to solid angle measure. 51 | *pdf *= i.p.distance_squared(&intr.p) / intr.n.abs_dot_vec(&(-wi)); 52 | 53 | if pdf.is_infinite() { *pdf = 0.0 } 54 | } 55 | 56 | 57 | intr 58 | } 59 | fn pdf(&self, _i: &InteractionData) -> Float { 60 | 1.0 / self.area() 61 | } 62 | 63 | fn pdf_wi(&self, re: &InteractionData, wi: &Vector3f) -> Float { 64 | // Intersect sample ray with area light geometry 65 | let ray = re.spawn_ray(wi); 66 | let mut thit = 0.0; 67 | let mut isect_light = SurfaceInteraction::default(); 68 | 69 | // Ignore any alpha textures used for trimming the shape when performing 70 | // this intersection. Hack for the "San Miguel" scene, where this is used 71 | // to make an invisible area light. 72 | if !self.intersect(&ray, &mut thit, &mut isect_light, false, None) { return 0.0 } 73 | 74 | // Convert light sample weight to solid angle measure 75 | let mut pdf = 76 | re.p.distance_squared(&isect_light.p) / 77 | (isect_light.n.dot_vec(&(-*wi)) * self.area()); 78 | 79 | if pdf.is_infinite() { pdf = 0.0; } 80 | 81 | pdf 82 | } 83 | 84 | fn solid_angle(&self, p: &Point3f, nsamples: usize) -> Float { 85 | let re = InteractionData { 86 | p: *p, 87 | time: 0.0, 88 | wo: Vector3f::new(0.0, 0.0, 1.0), 89 | medium_interface: Some(Default::default()), 90 | ..Default::default() 91 | }; 92 | 93 | let mut solid_angle = 0.0f64; 94 | 95 | for i in 0..nsamples { 96 | let u = Point2f::new(radical_inverse(0, i as u64), radical_inverse(1, i as u64)); 97 | let mut pdf = 0.0; 98 | let pshape = self.sample_interaction(&re, &u, &mut pdf); 99 | 100 | 101 | let r = Ray::new(&p, &(pshape.p - *p), 0.999, 0.0, None, None); 102 | if pdf > 0.0 && !self.intersect_p(&r, true) { 103 | solid_angle += 1.0f64 / pdf as f64; 104 | } 105 | } 106 | 107 | (solid_angle / nsamples as f64) as Float 108 | } 109 | 110 | fn reverse_orientation(&self) -> bool; 111 | fn transform_swapshandedness(&self) -> bool; 112 | 113 | fn object_to_world(&self) -> Arc; 114 | fn world_to_object(&self) -> Arc; 115 | } 116 | 117 | pub fn shape_pdfwi(shape: &S, re: &InteractionData, wi: &Vector3f) -> Float { 118 | // Intersect sample ray with area light geometry 119 | let ray = re.spawn_ray(wi); 120 | let mut thit = 0.0; 121 | let mut isect_light = SurfaceInteraction::default(); 122 | 123 | // Ignore any alpha textures used for trimming the shape when performing 124 | // this intersection. Hack for the "San Miguel" scene, where this is used 125 | // to make an invisible area light. 126 | if !shape.intersect(&ray, &mut thit, &mut isect_light, false, None) { return 0.0 } 127 | 128 | // Convert light sample weight to solid angle measure 129 | let mut pdf = 130 | re.p.distance_squared(&isect_light.p) / 131 | (isect_light.n.dot_vec(&(-*wi)) * shape.area()); 132 | 133 | if pdf.is_infinite() { pdf = 0.0; } 134 | 135 | pdf 136 | } 137 | 138 | #[enum_dispatch(Shape)] 139 | pub enum Shapes { 140 | Cone(Cone), 141 | Sphere(Sphere), 142 | Cylinder(Cylinder), 143 | Disk(Disk), 144 | Curve(Curve), 145 | Triangle(Triangle), 146 | Hyperboloid(Hyperboloid), 147 | Paraboloid(Paraboloid) 148 | } -------------------------------------------------------------------------------- /src/filters/boxfilter.rs: -------------------------------------------------------------------------------- 1 | use crate::core::filter::{Filter, Filters}; 2 | use crate::core::geometry::point::Point2f; 3 | use crate::core::pbrt::Float; 4 | use crate::core::paramset::ParamSet; 5 | use crate::core::geometry::vector::Vector2f; 6 | 7 | 8 | pub struct BoxFilter { 9 | radius: Vector2f 10 | } 11 | 12 | impl BoxFilter { 13 | pub fn new(radius: Vector2f) -> Self { 14 | return Self{ radius } 15 | } 16 | } 17 | 18 | impl Filter for BoxFilter { 19 | fn evaluate(&self, _p: &Point2f) -> Float { 20 | 1.0 21 | } 22 | 23 | fn radius(&self) -> Vector2f { 24 | self.radius 25 | } 26 | } 27 | 28 | pub fn create_box_filter(ps: &ParamSet) -> Filters { 29 | let xw = ps.find_one_float("xwidth", 0.5); 30 | let yw = ps.find_one_float("ywidth", 0.5); 31 | let b = BoxFilter::new(Vector2f::new(xw, yw)); 32 | 33 | b.into() 34 | } -------------------------------------------------------------------------------- /src/filters/gaussian.rs: -------------------------------------------------------------------------------- 1 | use crate::core::pbrt::Float; 2 | use crate::core::geometry::vector::Vector2f; 3 | use crate::core::filter::{Filter, Filters}; 4 | use crate::core::geometry::point::Point2f; 5 | use crate::core::paramset::ParamSet; 6 | 7 | pub struct GaussianFilter { 8 | alpha : Float, 9 | expx : Float, 10 | expy : Float, 11 | radius : Vector2f 12 | } 13 | 14 | impl GaussianFilter { 15 | pub fn new(radius: Vector2f, alpha: Float) -> Self { 16 | Self { 17 | radius, 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: Float, expv: Float) -> Float { 25 | (0.0 as Float).max((-self.alpha * d * d).exp() - expv) 26 | } 27 | } 28 | 29 | impl Filter for GaussianFilter { 30 | fn evaluate(&self, p: &Point2f) -> f32 { 31 | self.gaussian(p.x, self.expx) * self.gaussian(p.y, self.expy) 32 | } 33 | 34 | fn radius(&self) -> Vector2f { 35 | self.radius 36 | } 37 | } 38 | 39 | pub fn create_gaussian_filter(ps: &ParamSet) -> Filters { 40 | let xw = ps.find_one_float("xwidth", 2.0); 41 | let yw = ps.find_one_float("ywidth", 2.0); 42 | let alpha = ps.find_one_float("alpha", 2.0); 43 | 44 | GaussianFilter::new(Vector2f::new(xw, yw), alpha).into() 45 | } -------------------------------------------------------------------------------- /src/filters/mitchell.rs: -------------------------------------------------------------------------------- 1 | use crate::core::pbrt::Float; 2 | use crate::core::geometry::vector::Vector2f; 3 | use crate::core::filter::{Filter, Filters}; 4 | use crate::core::geometry::point::Point2f; 5 | use crate::core::paramset::ParamSet; 6 | 7 | pub struct MitchellFilter { 8 | B: Float, 9 | C: Float, 10 | radius: Vector2f, 11 | inv_radius: Vector2f 12 | } 13 | 14 | impl MitchellFilter { 15 | pub fn new(radius: Vector2f, B: Float, C: Float) -> Self { 16 | let inv_radius = Vector2f::new(1.0/ radius.x, 1.0 / radius.y); 17 | 18 | Self { 19 | inv_radius, 20 | radius, 21 | B, 22 | C 23 | } 24 | } 25 | 26 | pub fn mitchell_1d(&self, x: Float) -> Float { 27 | let a = (2.0 * x).abs(); 28 | 29 | if a > 1.0 { 30 | ((-self.B - 6.0 * self.C) * a * a * a + (6.0 * self.B * 30.0 * self.C) * a * a + 31 | (-12.0 * self.B - 48.0 * self.C) * a + (8.0 * self.B + 24.0 * self.C)) * 32 | (1.0 / 6.0) 33 | } else { 34 | ((12.0 - 9.0 * self.B - 6.0 * self.C) * a * a * a + 35 | (-18.0 + 12.0 * self.B + 6.0 * self.C) * x * x + (6.0 - 2.0 * self.B)) * 36 | (1.0 / 6.0) 37 | } 38 | } 39 | } 40 | 41 | impl Filter for MitchellFilter { 42 | fn evaluate(&self, p: &Point2f) -> f32 { 43 | self.mitchell_1d(p.x * self.inv_radius.x) * self.mitchell_1d(p.y * self.inv_radius.y) 44 | } 45 | 46 | fn radius(&self) -> Vector2f { 47 | self.radius 48 | } 49 | } 50 | 51 | pub fn create_mitchell_filter(ps: &ParamSet) -> Filters { 52 | let xw = ps.find_one_float("xwidth", 2.0); 53 | let yw = ps.find_one_float("ywidth", 2.0); 54 | let B = ps.find_one_float("B", 1.0 / 3.0); 55 | let C = ps.find_one_float("C", 1.0 / 3.0); 56 | 57 | MitchellFilter::new(Vector2f::new(xw, yw), B, C).into() 58 | } -------------------------------------------------------------------------------- /src/filters/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod boxfilter; 2 | pub mod triangle; 3 | pub mod gaussian; 4 | pub mod mitchell; 5 | pub mod sinc; -------------------------------------------------------------------------------- /src/filters/sinc.rs: -------------------------------------------------------------------------------- 1 | use crate::core::geometry::vector::Vector2f; 2 | use crate::core::pbrt::{Float, PI}; 3 | use crate::core::filter::{Filter, Filters}; 4 | use crate::core::geometry::point::Point2f; 5 | use crate::core::paramset::ParamSet; 6 | 7 | pub struct LanczosSincFilter { 8 | radius: Vector2f, 9 | tau: Float 10 | } 11 | 12 | impl LanczosSincFilter { 13 | pub fn new(radius: Vector2f, tau: Float) -> Self { 14 | Self { radius, tau } 15 | } 16 | 17 | fn sinc(&self, x: Float) -> Float { 18 | let y = x.abs(); 19 | 20 | if y < 1e-5 { 21 | return 1.0 22 | } 23 | 24 | (PI * y).sin() / (PI * y) 25 | } 26 | 27 | fn windowed_sinc(&self, x: Float, radius: Float) -> Float { 28 | let y = x.abs(); 29 | 30 | if y < radius { 31 | return 0.0; 32 | } 33 | 34 | let lanczos = self.sinc(y / self.tau); 35 | 36 | self.sinc(y) * lanczos 37 | } 38 | } 39 | 40 | impl Filter for LanczosSincFilter { 41 | fn evaluate(&self, p: &Point2f) -> f32 { 42 | self.windowed_sinc(p.x, self.radius.x) * self.windowed_sinc(p.y, self.radius.y) 43 | } 44 | 45 | fn radius(&self) -> Vector2f { 46 | self.radius 47 | } 48 | } 49 | 50 | pub fn create_sinc_filter(ps: &ParamSet) -> Filters { 51 | let xw = ps.find_one_float("xwidth", 4.0); 52 | let yw = ps.find_one_float("ywidth", 4.0); 53 | let tau = ps.find_one_float("tau", 3.0); 54 | 55 | LanczosSincFilter::new(Vector2f::new(xw, yw), tau).into() 56 | } -------------------------------------------------------------------------------- /src/filters/triangle.rs: -------------------------------------------------------------------------------- 1 | use crate::core::geometry::vector::Vector2f; 2 | use crate::core::filter::{Filter, Filters}; 3 | use crate::core::geometry::point::Point2f; 4 | use crate::core::pbrt::Float; 5 | use crate::core::paramset::ParamSet; 6 | 7 | pub struct TriangleFilter { 8 | radius: Vector2f 9 | } 10 | 11 | impl TriangleFilter { 12 | fn new(radius: Vector2f) -> Self { 13 | Self { radius } 14 | } 15 | } 16 | 17 | impl Filter for TriangleFilter { 18 | fn evaluate(&self, p: &Point2f) -> Float { 19 | 0.0_f32.max(self.radius.x - p.x.abs()) * 20 | 0.0_f32.max(self.radius.y - p.y.abs()) 21 | } 22 | 23 | fn radius(&self) -> Vector2f { 24 | self.radius 25 | } 26 | } 27 | 28 | pub fn create_triangle_filter(ps: &ParamSet) -> Filters { 29 | let xw = ps.find_one_float("xwidth", 2.0); 30 | let yw = ps.find_one_float("ywidth", 2.0); 31 | 32 | TriangleFilter::new(Vector2f::new(xw, yw)).into() 33 | } -------------------------------------------------------------------------------- /src/integrators/ao.rs: -------------------------------------------------------------------------------- 1 | use crate::core::camera::{Cameras, Camera}; 2 | use std::sync::Arc; 3 | use crate::core::sampler::{Samplers, Sampler}; 4 | use crate::core::geometry::bounds::Bounds2i; 5 | use log::{warn, error}; 6 | use crate::core::integrator::{SamplerIntegrator, Integrator, Integrators}; 7 | use crate::core::geometry::ray::Ray; 8 | use crate::core::scene::Scene; 9 | use bumpalo_herd::Member; 10 | use crate::core::spectrum::Spectrum; 11 | use crate::core::interaction::{SurfaceInteraction, Interaction}; 12 | use crate::core::geometry::vector::Vector3f; 13 | use crate::core::sampling::{cosine_sample_hemisphere, cosine_hemisphere_pdf, uniform_sample_sphere, uniform_sphere_pdf}; 14 | use crate::core::pbrt::{Float, Options}; 15 | use crate::core::material::TransportMode; 16 | use crate::core::paramset::ParamSet; 17 | use crate::core::geometry::point::Point2i; 18 | 19 | pub struct AOIntegrator { 20 | camera : Arc, 21 | sampler : Box, 22 | bounds : Bounds2i, 23 | cos_sample : bool, 24 | nsamples : usize 25 | } 26 | 27 | impl AOIntegrator { 28 | pub fn new( 29 | cos_sample: bool, ns: usize, camera: Arc, 30 | sampler: Box, bounds: Bounds2i) -> Self { 31 | let nsamples = sampler.round_count(ns); 32 | 33 | if ns != nsamples { 34 | warn!("Taking {} samples, not {} as specified", nsamples, ns); 35 | } 36 | 37 | Self { 38 | camera, sampler, nsamples, 39 | cos_sample, bounds 40 | } 41 | } 42 | } 43 | 44 | impl Integrator for AOIntegrator { 45 | fn render(&mut self, scene: &Scene) { 46 | SamplerIntegrator::render(self, scene) 47 | } 48 | } 49 | 50 | impl SamplerIntegrator for AOIntegrator { 51 | fn camera(&self) -> Arc { 52 | self.camera.clone() 53 | } 54 | 55 | fn sampler(&self) -> &Samplers { 56 | &self.sampler 57 | } 58 | 59 | fn bounds(&self) -> Bounds2i { 60 | self.bounds 61 | } 62 | 63 | fn preprocess(&mut self, _scene: &Scene) { 64 | self.sampler.request_2d_array(self.nsamples) 65 | } 66 | 67 | fn li( 68 | &self, r: &mut Ray, scene: &Scene, 69 | sampler: &mut Samplers, arena: &Member, 70 | _depth: usize) -> Spectrum { 71 | // TODO: ProfilePhase 72 | let mut L = Spectrum::new(0.0); 73 | let mut ray = r.clone(); 74 | 75 | // Intersect ray with scene and store intersection in isect; 76 | let mut isect = SurfaceInteraction::default(); 77 | 78 | if scene.intersect(&mut ray, &mut isect) { 79 | isect.compute_scattering_functions(&ray, arena, true, TransportMode::Radiance); 80 | 81 | // Compute coordinate frame based on true geometry, not shading geometry 82 | let n = isect.n.face_foward_vec(&(-ray.d)); 83 | let s = isect.dpdu.normalize(); 84 | let t = isect.n.cross_vec(&s); 85 | 86 | let uopt = sampler.get_2d_array(self.nsamples); 87 | 88 | if let Some(u) = uopt { 89 | for p in u.iter().take(self.nsamples) { 90 | let mut wi: Vector3f; 91 | let pdf = if self.cos_sample { 92 | wi = cosine_sample_hemisphere(&p); 93 | cosine_hemisphere_pdf(wi.z.abs()) 94 | } else { 95 | wi = uniform_sample_sphere(&p); 96 | uniform_sphere_pdf() 97 | }; 98 | 99 | // Transform wi from local frame to world space 100 | wi = Vector3f::new( 101 | s.x * wi.x + t.x * wi.y + n.x * wi.z, 102 | s.y * wi.x + t.y * wi.y + n.y * wi.z, 103 | s.z * wi.x + t.z * wi.y + n.z * wi.z); 104 | 105 | if !scene.intersect_p(&mut isect.spawn_ray(&wi)) { 106 | L += Spectrum::new(wi.dot_norm(&n) / (pdf * self.nsamples as Float)); 107 | } 108 | } 109 | } 110 | } 111 | 112 | L 113 | } 114 | } 115 | 116 | pub fn create_ao_integrator( 117 | params: &ParamSet, sampler: Box, camera: Arc, 118 | opts: &Options) -> Option { 119 | let mut np = 0; 120 | let pb = params.find_int("pixelbounds", &mut np); 121 | let mut pbounds = camera.film().get_sample_bounds(); 122 | 123 | if let Some(p) = pb { 124 | if p.len() != 4 { 125 | error!("Expected four values for \"pixelbounds\" parameter. Got {}.", np); 126 | } else { 127 | let b = Bounds2i::from_points( 128 | &Point2i::new(p[0], p[2]), 129 | &Point2i::new(p[1], p[3])); 130 | pbounds = b.intersect(&pbounds); 131 | 132 | if pbounds.area() == 0 { 133 | error!("Degenerate \"pixelbounds\" specified."); 134 | } 135 | } 136 | } 137 | 138 | let cossample = params.find_one_bool("cossample", true); 139 | let mut nsamples = params.find_one_int("nsamples", 64) as usize; 140 | 141 | if opts.quick_render { nsamples = 1; } 142 | 143 | Some(AOIntegrator::new(cossample, nsamples, camera, sampler, pbounds).into()) 144 | } -------------------------------------------------------------------------------- /src/integrators/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod directlighting; 2 | pub mod path; 3 | pub mod volpath; 4 | pub mod whitted; 5 | pub mod sppm; 6 | pub mod bdpt; 7 | pub mod mlt; 8 | pub mod ao; 9 | 10 | pub fn init_stats() { 11 | sppm::init_stats(); 12 | bdpt::init_stats(); 13 | mlt::init_stats(); 14 | path::init_stats(); 15 | volpath::init_stats(); 16 | } -------------------------------------------------------------------------------- /src/integrators/whitted.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use crate::core::camera::{Cameras, Camera}; 3 | use crate::core::sampler::Samplers; 4 | use crate::core::geometry::bounds::Bounds2i; 5 | use crate::core::integrator::{Integrator, SamplerIntegrator, Integrators}; 6 | use crate::core::scene::Scene; 7 | use crate::core::sampler::Sampler; 8 | use crate::core::light::{Light, VisibilityTester}; 9 | use crate::core::geometry::ray::Ray; 10 | use crate::core::spectrum::Spectrum; 11 | use crate::core::interaction::{SurfaceInteraction, Interaction}; 12 | use crate::core::material::TransportMode; 13 | use crate::core::geometry::vector::Vector3f; 14 | use crate::core::reflection::BxDFType; 15 | use bumpalo_herd::Member; 16 | use crate::core::paramset::ParamSet; 17 | use log::error; 18 | use crate::core::geometry::point::Point2i; 19 | 20 | pub struct WhittedIntegrator { 21 | camera : Arc, 22 | sampler : Box, 23 | bounds : Bounds2i, 24 | max_depth : usize 25 | } 26 | 27 | impl WhittedIntegrator { 28 | pub fn new( 29 | max_depth: usize, camera: Arc, 30 | sampler: Box, bounds: Bounds2i) -> Self { 31 | Self { max_depth, camera, sampler, bounds } 32 | } 33 | } 34 | 35 | impl Integrator for WhittedIntegrator { 36 | fn render(&mut self, scene: &Scene) { 37 | SamplerIntegrator::render(self, scene) 38 | } 39 | } 40 | 41 | impl SamplerIntegrator for WhittedIntegrator { 42 | fn camera(&self) -> Arc { 43 | self.camera.clone() 44 | } 45 | 46 | fn sampler(&self) -> &Samplers { 47 | &self.sampler 48 | } 49 | 50 | fn bounds(&self) -> Bounds2i { 51 | self.bounds 52 | } 53 | 54 | fn li( 55 | &self, r: &mut Ray, scene: &Scene, sampler: &mut Samplers, 56 | arena: &Member, depth: usize) -> Spectrum { 57 | let mut L = Spectrum::new(0.0); 58 | // Find closes ray intersection or return background radiance 59 | let mut isect = SurfaceInteraction::default(); 60 | if !scene.intersect(r, &mut isect) { 61 | for light in scene.lights.iter() { 62 | L += light.le(r); 63 | } 64 | 65 | return L; 66 | } 67 | 68 | // Compute emitted and reflected light at ray intersection point 69 | 70 | // Initialize common variables for Whitted integrator 71 | let n = isect.n; 72 | let wo = isect.wo; 73 | 74 | // Compute scattering functions for surface interaction 75 | isect.compute_scattering_functions(r, arena, false, TransportMode::Radiance); 76 | if isect.bsdf.is_none() { 77 | let mut ray = isect.spawn_ray(&r.d); 78 | 79 | return self.li(&mut ray, scene, sampler, arena, depth); 80 | } 81 | 82 | // Compute emitted light if ray hit an area light source 83 | L += isect.le(&wo); 84 | 85 | // Add contribution of each light source 86 | for light in scene.lights.iter() { 87 | let mut wi = Vector3f::default(); 88 | let mut pdf = 0.0; 89 | let mut vis = VisibilityTester::default(); 90 | let Li = light.sample_li(&isect.get_data(), &sampler.get_2d(), &mut wi, &mut pdf, &mut vis); 91 | if Li.is_black() || pdf == 0.0 { continue; } 92 | let flags = BxDFType::All as u8; 93 | let f = isect.bsdf.as_ref().unwrap().f(&wo, &wi, flags); 94 | 95 | if !f.is_black() && vis.unoccluded(scene) { 96 | L += f * Li * wi.abs_dot_norm(&n) / pdf; 97 | } 98 | } 99 | 100 | if depth + 1 < self.max_depth { 101 | // Trace rays for specular reflection and refraction 102 | L += self.specular_reflect(r, &isect, scene, sampler, arena, depth); 103 | L += self.specular_transmit(r, &isect, scene, sampler, arena, depth); 104 | } 105 | 106 | L 107 | } 108 | } 109 | 110 | pub fn create_whitted_integrator( 111 | params: &ParamSet, sampler: Box, 112 | camera: Arc) -> Option { 113 | let maxdepth = params.find_one_int("maxdepth", 5) as usize; 114 | let mut np = 0; 115 | let pb = params.find_int("pixelbounds", &mut np); 116 | let mut pbounds = camera.film().get_sample_bounds(); 117 | 118 | if let Some(p) = pb { 119 | if p.len() != 4 { 120 | error!("Expected four values for \"pixelbounds\" parameter. Got {}.", np); 121 | } else { 122 | let b = Bounds2i::from_points( 123 | &Point2i::new(p[0], p[2]), 124 | &Point2i::new(p[1], p[3])); 125 | pbounds = b.intersect(&pbounds); 126 | 127 | if pbounds.area() == 0 { 128 | error!("Degenerate \"pixelbounds\" specified."); 129 | } 130 | } 131 | } 132 | 133 | Some(WhittedIntegrator::new(maxdepth, camera, sampler, pbounds).into()) 134 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(iter_partition_in_place)] 2 | #![feature(slice_partition_at_index)] 3 | #![feature(const_fn)] 4 | #![allow(incomplete_features)] 5 | #![feature(const_evaluatable_checked)] 6 | #![feature(const_generics)] 7 | 8 | // lints 9 | #![allow(non_snake_case)] 10 | #![allow(non_upper_case_globals)] 11 | 12 | 13 | // clippy 14 | #![cfg_attr( 15 | feature = "cargo-clippy", 16 | allow( 17 | clippy::upper_case_acronyms, 18 | clippy::many_single_char_names, 19 | clippy::too_many_arguments, 20 | clippy::excessive_precision, 21 | clippy::float_cmp 22 | ) 23 | )] 24 | 25 | pub mod core; 26 | pub mod materials; 27 | pub mod shapes; 28 | pub mod accelerators; 29 | pub mod cameras; 30 | pub mod samplers; 31 | pub mod textures; 32 | pub mod filters; 33 | pub mod pbrtparser; 34 | pub mod media; 35 | pub mod lights; 36 | pub mod integrators; 37 | 38 | pub fn init_stats() { 39 | core::stats::init_stats(); 40 | core::api::init_stats(); 41 | core::integrator::init_stats(); 42 | core::lightdistrib::init_stats(); 43 | core::mipmap::init_stats(); 44 | core::scene::init_stats(); 45 | accelerators::init_stats(); 46 | cameras::init_stats(); 47 | shapes::init_stats(); 48 | integrators::init_stats(); 49 | media::init_stats(); 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/lights/distant.rs: -------------------------------------------------------------------------------- 1 | use crate::core::medium::MediumInterface; 2 | use crate::core::transform::Transform; 3 | use crate::core::spectrum::Spectrum; 4 | use crate::core::geometry::vector::{Vector3f, vec3_coordinate_system}; 5 | use crate::core::geometry::point::{Point3f, Point2f}; 6 | use crate::core::pbrt::{Float, PI, INFINITY}; 7 | use crate::init_light_data; 8 | use crate::core::light::{LightFlags, Light, VisibilityTester, Lights}; 9 | use crate::core::geometry::ray::Ray; 10 | use crate::core::interaction::{InteractionData}; 11 | use crate::core::scene::Scene; 12 | use crate::core::geometry::normal::{Normal3f}; 13 | use std::sync::{RwLock, Arc}; 14 | use crate::core::paramset::ParamSet; 15 | use crate::core::sampling::concentric_sample_disk; 16 | 17 | #[allow(dead_code)] 18 | #[derive(Default)] 19 | pub struct DistantLight { 20 | L : Spectrum, 21 | wlight : Vector3f, 22 | world_center : RwLock, 23 | world_radius : RwLock, 24 | // Light Data 25 | flags : u8, 26 | nsamples : usize, 27 | medium_interface : MediumInterface, 28 | light_to_world : Transform, 29 | world_to_light : Transform 30 | } 31 | 32 | impl DistantLight { 33 | pub fn new(l2w: &Transform, L: &Spectrum, w: &Vector3f) -> Self { 34 | // Initialize Light data 35 | let flags = LightFlags::DeltaDirection as u8; 36 | let mut dl = DistantLight::default(); 37 | init_light_data!(dl, flags, 1, MediumInterface::default(), l2w); 38 | 39 | dl.L = *L; 40 | dl.wlight = l2w.transform_vector(w).normalize(); 41 | 42 | dl 43 | } 44 | } 45 | 46 | impl Light for DistantLight { 47 | fn power(&self) -> Spectrum { 48 | let wr = *self.world_radius.read().unwrap(); 49 | self.L * PI * wr * wr 50 | } 51 | 52 | fn preprocess(&self, scene: &Scene) { 53 | let (p, f) = scene.wb.bounding_sphere(); 54 | let mut wc = self.world_center.write().unwrap(); 55 | let mut wr = self.world_radius.write().unwrap(); 56 | *wc = p; 57 | *wr = f; 58 | } 59 | 60 | fn pdf_li(&self, _re: &InteractionData, _wi: &Vector3f) -> Float { 61 | 0.0 62 | } 63 | 64 | fn sample_li( 65 | &self, re: &InteractionData, _u: &Point2f, wi: &mut Vector3f, 66 | pdf: &mut f32, vis: &mut VisibilityTester) -> Spectrum { 67 | // TODO: ProfilePhase 68 | *wi = self.wlight; 69 | *pdf = 1.0; 70 | let wr = *self.world_radius.read().unwrap(); 71 | let poutside = re.p + self.wlight * (2.0 * wr); 72 | 73 | let s2 = InteractionData { 74 | p : poutside, 75 | time : re.time, 76 | medium_interface: Some(self.medium_interface.clone()), 77 | ..Default::default() 78 | }; 79 | 80 | *vis = VisibilityTester::new(re.clone(), s2); 81 | 82 | self.L 83 | } 84 | 85 | fn sample_le( 86 | &self, u1: &Point2f, _u2: &Point2f, time: Float, ray: &mut Ray, 87 | nlight: &mut Normal3f, pdf_pos: &mut Float, pdf_dir: &mut Float) -> Spectrum { 88 | // TODO ProfilePhase 89 | let wc = self.world_center.read().unwrap(); 90 | let wr = self.world_radius.read().unwrap(); 91 | 92 | // Choose point on disk oriented toward infinite light direction 93 | let mut v1 = Vector3f::default(); 94 | let mut v2 = Vector3f::default(); 95 | vec3_coordinate_system(&self.wlight, &mut v1, &mut v2); 96 | let cd = concentric_sample_disk(u1); 97 | let pdisk = *wc + (v1 * cd.x + v2 * cd.y) * *wr; 98 | 99 | // Set ray origin and direction for infinite light ray 100 | *ray = Ray::new( 101 | &(pdisk + self.wlight * *wr), &(-self.wlight), INFINITY, time, None, None); 102 | *nlight = Normal3f::from(ray.d); 103 | *pdf_pos = 1.0 / (PI * *wr * *wr); 104 | *pdf_dir = 1.0; 105 | 106 | self.L 107 | } 108 | 109 | fn pdf_le(&self, _ray: &Ray, _nlight: &Normal3f, pdf_pos: &mut Float, pdf_dir: &mut Float) { 110 | // TODO ProfilePhase 111 | let wr = self.world_radius.read().unwrap(); 112 | 113 | *pdf_pos = 1.0 / (PI * *wr * *wr); 114 | *pdf_dir = 0.0; 115 | } 116 | 117 | fn nsamples(&self) -> usize { self.nsamples } 118 | 119 | fn flags(&self) -> u8 { 120 | self.flags 121 | } 122 | } 123 | 124 | pub fn create_distantlight(l2w: &Transform, params: &ParamSet) -> Option> { 125 | let L = params.find_one_spectrum("L", Spectrum::new(1.0)); 126 | let sc = params.find_one_spectrum("scale", Spectrum::new(1.0)); 127 | let from = params.find_one_point3f("from", Default::default()); 128 | let to = params.find_one_point3f("to", Point3f::new(0.0, 0.0, 1.0)); 129 | let dir = from - to; 130 | let l = DistantLight::new(l2w, &(L * sc), &dir); 131 | 132 | Some(Arc::new(l.into())) 133 | } -------------------------------------------------------------------------------- /src/lights/goniometric.rs: -------------------------------------------------------------------------------- 1 | use crate::core::medium::MediumInterface; 2 | use crate::core::transform::Transform; 3 | use crate::core::geometry::point::{Point3f, Point2f}; 4 | use crate::core::spectrum::{Spectrum, RGBSpectrum, SpectrumType}; 5 | use crate::core::mipmap::MIPMap; 6 | use crate::mipmap; 7 | use crate::init_light_data; 8 | use crate::core::light::{LightFlags, Light, VisibilityTester, Lights}; 9 | use crate::core::imageio::read_image; 10 | use crate::core::geometry::vector::Vector3f; 11 | use crate::core::geometry::geometry::{spherical_theta, spherical_phi}; 12 | use crate::core::pbrt::{INV2_PI, INV_PI, Float, PI, INFINITY}; 13 | use crate::core::geometry::ray::Ray; 14 | use crate::core::interaction::{Interaction, InteractionData}; 15 | use crate::core::geometry::normal::Normal3f; 16 | use crate::core::paramset::ParamSet; 17 | use std::sync::Arc; 18 | use crate::core::sampling::{uniform_sample_sphere, uniform_sphere_pdf}; 19 | 20 | #[allow(dead_code)] 21 | #[derive(Default)] 22 | pub struct GonioPhotometricLight { 23 | plight : Point3f, 24 | I : Spectrum, 25 | mipmap : Option>, 26 | // Light Data 27 | flags : u8, 28 | nsamples : usize, 29 | medium_interface : MediumInterface, 30 | light_to_world : Transform, 31 | world_to_light : Transform 32 | } 33 | 34 | impl GonioPhotometricLight { 35 | pub fn new( 36 | l2w: &Transform, mi: MediumInterface, 37 | I: &Spectrum, texname: &str) -> Self { 38 | let flags = LightFlags::DeltaPosition as u8; 39 | let mut cl = GonioPhotometricLight::default(); 40 | init_light_data!(cl, flags, 1, mi, l2w); 41 | 42 | match read_image(texname) { 43 | Ok((m, res)) => cl.mipmap = Some(mipmap!(&res, m)), 44 | _ => cl.mipmap = None 45 | } 46 | 47 | cl.I = *I; 48 | cl.plight = l2w.transform_point(&Point3f::new(0.0, 0.0, 0.0)); 49 | 50 | cl 51 | } 52 | 53 | fn scale(&self, w: &Vector3f) -> Spectrum { 54 | let mut wp = self.world_to_light.transform_vector(w).normalize(); 55 | std::mem::swap(&mut wp.y, &mut wp.z); 56 | let theta = spherical_theta(&wp); 57 | let phi = spherical_phi(&wp); 58 | let st = Point2f::new(phi * INV2_PI, theta * INV_PI); 59 | 60 | match self.mipmap.as_ref() { 61 | Some(m) => { 62 | let s = m.lookup(&st, 0.0); 63 | Spectrum::from_rgb_spectrum(&s, SpectrumType::Illuminant) 64 | }, 65 | _ => Spectrum::new(1.0) 66 | } 67 | } 68 | } 69 | 70 | impl Light for GonioPhotometricLight { 71 | fn power(&self) -> Spectrum { 72 | let r = match self.mipmap.as_ref() { 73 | Some(m) => m.lookup(&Point2f::new(0.5, 0.5), 0.5), 74 | _ => RGBSpectrum::new(1.0) 75 | }; 76 | let s = Spectrum::from_rgb_spectrum(&r, SpectrumType::Illuminant); 77 | 78 | self.I * s * 4.0 * PI 79 | } 80 | 81 | fn pdf_li(&self, _re: &InteractionData, _wi: &Vector3f) -> Float { 82 | 0.0 83 | } 84 | 85 | fn sample_li( 86 | &self, re: &InteractionData, _u: &Point2f, wi: &mut Vector3f, 87 | pdf: &mut Float, vis: &mut VisibilityTester) -> Spectrum { 88 | // TODO: ProfilePhase 89 | *wi = (self.plight - re.p()).normalize(); 90 | *pdf = 1.0; 91 | 92 | let s2 = InteractionData { 93 | p : self.plight, 94 | time : re.time(), 95 | medium_interface: Some(self.medium_interface.clone()), 96 | ..Default::default() 97 | }; 98 | 99 | *vis = VisibilityTester::new(re.clone(), s2); 100 | 101 | 102 | self.I * self.scale(&(-*wi)) / self.plight.distance_squared(&re.p()) 103 | } 104 | 105 | fn sample_le( 106 | &self, u1: &Point2f, _u2: &Point2f, time: Float, ray: &mut Ray, 107 | nlight: &mut Normal3f, pdf_pos: &mut Float, pdf_dir: &mut Float) -> Spectrum { 108 | // TODO: ProfilePhase 109 | *ray = Ray::new( 110 | &self.plight, &uniform_sample_sphere(u1), INFINITY, 111 | time, self.medium_interface.inside.clone(), None); 112 | *nlight = Normal3f::from(ray.d); 113 | *pdf_pos = 1.0; 114 | *pdf_dir = uniform_sphere_pdf(); 115 | 116 | self.I * self.scale(&ray.d) 117 | } 118 | 119 | fn pdf_le(&self, _ray: &Ray, _nlight: &Normal3f, pdf_pos: &mut Float, pdf_dir: &mut Float) { 120 | // TODO ProfilePhase 121 | *pdf_pos = 0.0; 122 | *pdf_dir = uniform_sphere_pdf(); 123 | } 124 | 125 | fn nsamples(&self) -> usize { self.nsamples } 126 | 127 | fn flags(&self) -> u8 { 128 | self.flags 129 | } 130 | } 131 | 132 | pub fn create_goniometriclight(l2w: &Transform, mi: MediumInterface, params: &ParamSet) -> Option> { 133 | let I = params.find_one_spectrum("I", Spectrum::new(1.0)); 134 | let sc = params.find_one_spectrum("sc", Spectrum::new(1.0)); 135 | let texname = params.find_one_filename("mapname", ""); 136 | let l = GonioPhotometricLight::new(l2w, mi, &(I * sc), &texname); 137 | 138 | Some(Arc::new(l.into())) 139 | } -------------------------------------------------------------------------------- /src/lights/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod point; 2 | pub mod spot; 3 | pub mod projection; 4 | pub mod goniometric; 5 | pub mod distant; 6 | pub mod diffuse; 7 | pub mod infinite; 8 | 9 | -------------------------------------------------------------------------------- /src/lights/point.rs: -------------------------------------------------------------------------------- 1 | use crate::core::geometry::point::{Point3f, Point2f}; 2 | use crate::core::spectrum::Spectrum; 3 | use crate::init_light_data; 4 | use crate::core::transform::Transform; 5 | use crate::core::medium::MediumInterface; 6 | use crate::core::light::{LightFlags, Light, VisibilityTester, Lights}; 7 | use crate::core::geometry::vector::Vector3f; 8 | use crate::core::interaction::{InteractionData}; 9 | use crate::core::geometry::ray::Ray; 10 | use crate::core::geometry::normal::Normal3f; 11 | use crate::core::pbrt::{PI, Float, INFINITY}; 12 | use crate::core::interaction::Interaction; 13 | use crate::core::paramset::ParamSet; 14 | use std::sync::Arc; 15 | use crate::core::sampling::{uniform_sample_sphere, uniform_sphere_pdf}; 16 | 17 | #[allow(dead_code)] 18 | #[derive(Default)] 19 | pub struct PointLight { 20 | plight : Point3f, 21 | I : Spectrum, 22 | // Light Data 23 | flags : u8, 24 | nsamples : usize, 25 | medium_interface: MediumInterface, 26 | light_to_world : Transform, 27 | world_to_light : Transform 28 | } 29 | 30 | impl PointLight { 31 | pub fn new(l2w: &Transform, mi: MediumInterface, I: &Spectrum) -> Self { 32 | let flags = LightFlags::DeltaPosition as u8; 33 | let mut pl = PointLight::default(); 34 | init_light_data!(pl, flags, 1, mi, l2w); 35 | 36 | pl.plight = l2w.transform_point(&Point3f::new(0.0, 0.0, 0.0)); 37 | pl.I = *I; 38 | 39 | pl 40 | } 41 | } 42 | 43 | impl Light for PointLight { 44 | fn power(&self) -> Spectrum { 45 | self.I * 4.0 * PI 46 | } 47 | 48 | fn pdf_li(&self, _re: &InteractionData, _wi: &Vector3f) -> Float { 49 | 0.0 50 | } 51 | 52 | fn sample_li( 53 | &self, re: &InteractionData, _u: &Point2f, wi: &mut Vector3f, 54 | pdf: &mut f32, vis: &mut VisibilityTester) -> Spectrum { 55 | // TODO: ProfilePhase 56 | *wi = (self.plight - re.p).normalize(); 57 | *pdf = 1.0; 58 | 59 | let s2 = InteractionData { 60 | p : self.plight, 61 | time : re.time(), 62 | medium_interface: Some(self.medium_interface.clone()), 63 | ..Default::default() 64 | }; 65 | 66 | *vis = VisibilityTester::new(re.clone(), s2); 67 | 68 | self.I / self.plight.distance_squared(&re.p) 69 | } 70 | 71 | fn sample_le( 72 | &self, u1: &Point2f, _u2: &Point2f, time: Float, 73 | ray: &mut Ray, nlight: &mut Normal3f, 74 | pdf_pos: &mut Float, pdf_dir: &mut Float) -> Spectrum { 75 | *ray = Ray::new( 76 | &self.plight, &uniform_sample_sphere(u1), INFINITY, 77 | time, self.medium_interface.inside.clone(), None); 78 | *nlight = Normal3f::from(ray.d); 79 | *pdf_pos = 1.0; 80 | *pdf_dir = uniform_sphere_pdf(); 81 | 82 | self.I 83 | } 84 | 85 | fn pdf_le(&self, _ray: &Ray, _nlight: &Normal3f, pdf_pos: &mut Float, pdf_dir: &mut Float) { 86 | // TODO ProfilePhase 87 | *pdf_pos = 0.0; 88 | *pdf_dir = uniform_sphere_pdf(); 89 | 90 | } 91 | 92 | fn nsamples(&self) -> usize { self.nsamples } 93 | 94 | fn flags(&self) -> u8 { 95 | self.flags 96 | } 97 | } 98 | 99 | pub fn create_pointlight(l2w: &Transform, mi: MediumInterface, params: &ParamSet) -> Option> { 100 | let I = params.find_one_spectrum("I", Spectrum::new(1.0)); 101 | let sc = params.find_one_spectrum("scale", Spectrum::new(1.0)); 102 | let P = params.find_one_point3f("from", Default::default()); 103 | let l2wn = Transform::translate(&Vector3f::new(P.x, P.y, P.x)) * *l2w; 104 | let l = PointLight::new(&l2wn, mi, &(I * sc)); 105 | 106 | Some(Arc::new(l.into())) 107 | } -------------------------------------------------------------------------------- /src/lights/spot.rs: -------------------------------------------------------------------------------- 1 | use crate::core::geometry::point::{Point3f, Point2f}; 2 | use crate::core::spectrum::Spectrum; 3 | use crate::core::medium::MediumInterface; 4 | use crate::core::transform::{Transform, Matrix4x4}; 5 | use crate::core::pbrt::{Float, radians, PI, INFINITY}; 6 | use crate::core::light::{LightFlags, Light, VisibilityTester, Lights}; 7 | use crate::init_light_data; 8 | use crate::core::geometry::vector::{Vector3f, vec3_coordinate_system}; 9 | use crate::core::interaction::{Interaction, InteractionData}; 10 | use crate::core::geometry::ray::Ray; 11 | use crate::core::geometry::normal::Normal3f; 12 | use crate::core::paramset::ParamSet; 13 | use std::sync::Arc; 14 | use crate::core::sampling::{uniform_sample_cone, uniform_cone_pdf}; 15 | use crate::core::reflection::cos_theta; 16 | 17 | #[derive(Default)] 18 | pub struct SpotLight { 19 | plight : Point3f, 20 | I : Spectrum, 21 | cos_total_width : Float, 22 | cos_falloff_start : Float, 23 | // Light Data 24 | flags : u8, 25 | nsamples : usize, 26 | medium_interface : MediumInterface, 27 | light_to_world : Transform, 28 | world_to_light : Transform 29 | } 30 | 31 | impl SpotLight { 32 | pub fn new( 33 | l2w: &Transform, mi: MediumInterface, I: &Spectrum, 34 | total_width: Float, falloff_start: Float) -> Self { 35 | let flags = LightFlags::DeltaPosition as u8; 36 | let mut sl = SpotLight::default(); 37 | init_light_data!(sl, flags, 1, mi, l2w); 38 | 39 | sl.plight = l2w.transform_point(&Point3f::new(0.0, 0.0, 0.0)); 40 | sl.I = *I; 41 | sl.cos_total_width = radians(total_width).cos(); 42 | sl.cos_falloff_start = radians(falloff_start).cos(); 43 | 44 | sl 45 | } 46 | 47 | pub fn falloff(&self, w: &Vector3f) -> Float { 48 | let wl = self.world_to_light.transform_vector(w).normalize(); 49 | let cos_theta = wl.z; 50 | 51 | if cos_theta < self.cos_total_width { return 0.0; } 52 | if cos_theta >= self.cos_falloff_start { return 1.0; } 53 | 54 | // Compute falloff inside spotlight cone 55 | let delta = 56 | (cos_theta - self.cos_total_width) / (self.cos_falloff_start - self.cos_total_width); 57 | 58 | (delta * delta) * (delta * delta) 59 | } 60 | } 61 | 62 | impl Light for SpotLight { 63 | fn power(&self) -> Spectrum { 64 | self.I * 2.0 * PI * (1.0 - 0.5 * (self.cos_falloff_start + self.cos_total_width)) 65 | } 66 | 67 | fn pdf_li(&self, _re: &InteractionData, _wi: &Vector3f) -> Float { 68 | 0.0 69 | } 70 | 71 | fn sample_li(&self, re: &InteractionData, _u: &Point2f, wi: &mut Vector3f, pdf: &mut f32, vis: &mut VisibilityTester) -> Spectrum { 72 | // TODO: ProfilePhase 73 | *wi = (self.plight - re.p()).normalize(); 74 | *pdf = 1.0; 75 | 76 | let s2 = InteractionData { 77 | p : self.plight, 78 | time : re.time, 79 | medium_interface: Some(self.medium_interface.clone()), 80 | ..Default::default() 81 | }; 82 | 83 | *vis = VisibilityTester::new(re.clone(), s2); 84 | 85 | self.I * self.falloff(&-*wi) / self.plight.distance_squared(&re.p) 86 | } 87 | 88 | fn sample_le( 89 | &self, u1: &Point2f, _u2: &Point2f, time: Float, ray: &mut Ray, 90 | nlight: &mut Normal3f, pdf_pos: &mut Float, pdf_dir: &mut Float) -> Spectrum { 91 | // TODO: ProfilePhase 92 | let w = uniform_sample_cone(u1, self.cos_total_width); 93 | *ray = Ray::new( 94 | &self.plight, &self.light_to_world.transform_vector(&w), 95 | INFINITY, time, self.medium_interface.inside.clone(), None); 96 | *nlight = Normal3f::from(ray.d); 97 | *pdf_pos = 1.0; 98 | *pdf_dir = uniform_cone_pdf(self.cos_total_width); 99 | 100 | self.I * self.falloff(&ray.d) 101 | } 102 | 103 | fn pdf_le(&self, ray: &Ray, _nlight: &Normal3f, pdf_pos: &mut Float, pdf_dir: &mut Float) { 104 | // TODO ProfilePhase 105 | *pdf_pos = 0.0; 106 | let v = self.world_to_light.transform_vector(&ray.d); 107 | *pdf_dir = if cos_theta(&v) > self.cos_total_width { 108 | uniform_cone_pdf(self.cos_total_width) 109 | } else { 110 | 0.0 111 | }; 112 | } 113 | 114 | fn nsamples(&self) -> usize { self.nsamples } 115 | 116 | fn flags(&self) -> u8 { 117 | self.flags 118 | } 119 | } 120 | 121 | pub fn create_spotlight(l2w: &Transform, mi: MediumInterface, params: &ParamSet) -> Option> { 122 | let I = params.find_one_spectrum("I", Spectrum::new(1.0)); 123 | let sc = params.find_one_spectrum("scale", Spectrum::new(1.0)); 124 | let coneangle = params.find_one_float("coneangle", 30.0); 125 | let conedelta = params.find_one_float("conedeltaangle", 5.0); 126 | // Compute spotlight world to light transformation 127 | let from = params.find_one_point3f("from", Default::default()); 128 | let to = params.find_one_point3f("to", Point3f::new(0.0, 0.0, 1.0)); 129 | let dir = (to - from).normalize(); 130 | let mut du = Vector3f::default(); 131 | let mut dv = Vector3f::default(); 132 | vec3_coordinate_system(&dir, &mut du, &mut dv); 133 | let mat = Matrix4x4::from_row_slice(&[ 134 | du.x, du.y, du.z, 0.0, 135 | dv.x, dv.y, dv.z, 0.0, 136 | dir.x, dir.y, dir.z, 0.0, 137 | 0.0, 0.0, 0.0, 1.0 138 | ]); 139 | 140 | let dirtoz = Transform::from_matrix(&mat); 141 | let v = Vector3f::new(from.x, from.y, from.z); 142 | let light2world = *l2w * Transform::translate(&v) * Transform::inverse(&dirtoz); 143 | let l = SpotLight::new(&light2world, mi, &(I * sc), coneangle, coneangle - conedelta); 144 | 145 | Some(Arc::new(l.into())) 146 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use structopt::StructOpt; 2 | use pbrt_rust::core::pbrt::{Float, Options, get_progress_bar}; 3 | use pbrt_rust::init_stats; 4 | use std::path::PathBuf; 5 | use num_cpus; 6 | use anyhow::Result; 7 | use fern::colors::{ColoredLevelConfig, Color}; 8 | use pbrt_rust::pbrtparser::pbrtparser::pbrt_parse; 9 | use fern::Output; 10 | use std::io::Write; 11 | 12 | #[derive(StructOpt, Debug)] 13 | #[structopt(name = "pbrt-rust", version = "0.1", about = "Parse a PBRT scene description file and render it")] 14 | struct Args { 15 | /// set LOG verbosity 16 | #[structopt(short, long)] 17 | verbose: bool, 18 | 19 | /// Specify directory that log files should be writtend to. 20 | /// Default: system temp directory (e.g $TMPDIR or /tmp). 21 | #[structopt(short, long)] 22 | logdir: Option, 23 | 24 | /// Print all logging messages to stderr 25 | #[structopt(short = "e", long)] 26 | logtostderr: bool, 27 | 28 | /// Print a reformatted version of the input file(s) 29 | /// to standard output. Does not render an image. 30 | #[structopt(short, long)] 31 | cat: bool, 32 | 33 | /// Print a formatted version of input file(s) to 34 | /// standard output and convert all triangle meshes to 35 | /// PLY files. Does not render and image. 36 | #[structopt(short, long)] 37 | toply: bool, 38 | 39 | /// Use specified number of threads for rendering 40 | #[structopt(short, long, default_value = "0")] 41 | nthreads: u8, 42 | 43 | /// Specify an image crop window 44 | #[structopt(short = "w", long, number_of_values = 4, value_names = &["x0", "x1", "y0", "y1"])] 45 | cropwindow: Option>, 46 | 47 | #[structopt(short, long, parse(from_os_str))] 48 | /// Write the final image to the given filename 49 | outfile: Option, 50 | 51 | #[structopt(parse(from_os_str))] 52 | /// Path to PBRT scene description file 53 | input: PathBuf 54 | } 55 | 56 | fn setup_logging(verbose: bool, logdir: PathBuf, stderr: bool) -> Result<()> { 57 | let colors = ColoredLevelConfig::new() 58 | .error(Color::Red) 59 | .warn(Color::Yellow); 60 | let clevel = colors.clone().info(Color::Green); 61 | 62 | let mut base_config = fern::Dispatch::new(); 63 | 64 | let level = if verbose { 65 | log::LevelFilter::Debug 66 | } else { 67 | log::LevelFilter::Info 68 | }; 69 | 70 | base_config = base_config.level(level); 71 | 72 | let file_config = fern::Dispatch::new() 73 | .format(|out, message, record| { 74 | out.finish(format_args!( 75 | "[{}] {}", 76 | record.level(), 77 | message 78 | )) 79 | }) 80 | .chain(fern::log_file(logdir)?); 81 | 82 | let stderr_config = fern::Dispatch::new() 83 | .format(move |out, message, record| { 84 | out.finish(format_args!( 85 | "{color_line}[{level}] {message}\x1B[0m", 86 | color_line = format_args!("\x1B[{}m", colors.get_color(&record.level()).to_fg_str()), 87 | level = clevel.color(record.level()), 88 | message = message, 89 | )); 90 | }) 91 | .level(level) 92 | .chain( 93 | Output::call(|record| { 94 | if let Some(pb) = get_progress_bar() { 95 | pb.println(record.args().to_string()); 96 | } else { 97 | writeln!(std::io::stderr(), "{}", record.args()).ok(); 98 | } 99 | }) 100 | 101 | ); 102 | 103 | base_config = base_config.chain(file_config); 104 | if stderr { base_config = base_config.chain(stderr_config); } 105 | base_config.apply()?; 106 | 107 | Ok(()) 108 | 109 | } 110 | 111 | fn main() -> Result<()> { 112 | let mut opts = Options::new(); 113 | let args: Args = Args::from_args(); 114 | 115 | let nthreads = match args.nthreads { 116 | 0 => num_cpus::get(), 117 | n => n as usize 118 | }; 119 | 120 | println!("pbrt-rust 0.1 [Detected {} cores. Using {}]", num_cpus::get(), nthreads); 121 | println!("Copyright (c) 2020-2021 Alex Meli."); 122 | println!("Based on the original PBRTv3 C++ version by Matt Pharr, Grep Humphreys, and Wenzel Jacob."); 123 | 124 | rayon::ThreadPoolBuilder::new().num_threads(nthreads).build_global().unwrap(); 125 | 126 | if let Some(w) = args.cropwindow { 127 | opts.crop_window = [[w[0], w[1]], [w[2], w[3]]]; 128 | } 129 | 130 | if let Some(f) = args.outfile { 131 | opts.image_file = f 132 | } 133 | 134 | opts.to_ply = args.toply; 135 | opts.cat = args.cat; 136 | 137 | let logdir = if let Some(dir) = args.logdir { 138 | dir 139 | } else { 140 | PathBuf::from(String::from("pbrt.log")) 141 | }; 142 | 143 | setup_logging(args.verbose, logdir, args.logtostderr)?; 144 | let filename = args.input; 145 | // Initialize statistics counter 146 | init_stats(); 147 | 148 | pbrt_parse(&filename, opts) 149 | } 150 | -------------------------------------------------------------------------------- /src/materials/fourier.rs: -------------------------------------------------------------------------------- 1 | use crate::core::reflection::{FourierBSDFTable, BSDF, FourierBSDF}; 2 | use std::sync::Arc; 3 | use std::sync::Mutex; 4 | use std::collections::HashMap; 5 | use bumpalo_herd::Member; 6 | use lazy_static::lazy_static; 7 | use crate::core::texture::{TextureFloat}; 8 | use crate::core::material::{Material, TransportMode, bump, Materials}; 9 | use crate::core::interaction::SurfaceInteraction; 10 | use crate::core::paramset::TextureParams; 11 | 12 | lazy_static! { 13 | static ref LOADED_BSDFS: Mutex>> = Mutex::new(HashMap::new()); 14 | } 15 | 16 | pub struct FourierMaterial { 17 | table : Arc, 18 | bump_map: Option> 19 | } 20 | 21 | impl FourierMaterial { 22 | pub fn new(filename: &str, bump_map: Option>) -> Self { 23 | let mut loaded = LOADED_BSDFS.lock().unwrap(); 24 | 25 | if !loaded.contains_key(filename) { 26 | let mut table = FourierBSDFTable::default(); 27 | FourierBSDFTable::read(filename, &mut table); 28 | loaded.insert(filename.to_owned(), Arc::new(table)); 29 | } 30 | 31 | Self { 32 | bump_map, 33 | table: loaded.get(filename).unwrap().clone() 34 | } 35 | } 36 | } 37 | 38 | impl Material for FourierMaterial { 39 | fn compute_scattering_functions<'b: 'b>( 40 | &self, si: &mut SurfaceInteraction<'b>, arena: & Member<'b>, 41 | _mat: Option>, mode: TransportMode, _allow_multiple_lobes: bool) { 42 | // Perform bump mapping with bumpMap if present 43 | if self.bump_map.is_some() { bump(self.bump_map.as_ref().unwrap(), si); } 44 | 45 | let mut bsdf = BSDF::new(si, 1.0); 46 | 47 | // Checking for zero channels works as a proxy for checking whether the 48 | // table was successfully read from the file 49 | if self.table.nchannels > 0 { 50 | bsdf.add(arena.alloc(FourierBSDF::new(self.table.clone(), mode).into())); 51 | } 52 | 53 | si.bsdf = Some(bsdf) 54 | } 55 | } 56 | 57 | pub fn create_fourier_material(mp: &mut TextureParams) -> Materials { 58 | let bump_map = mp.get_floattexture_ornull("bumpmap"); 59 | let filename = mp.find_filename("bsdffile", ""); 60 | 61 | FourierMaterial::new(&filename, bump_map).into() 62 | } -------------------------------------------------------------------------------- /src/materials/glass.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureSpec, TextureFloat, Texture}; 2 | use std::sync::Arc; 3 | use crate::core::material::{Material, TransportMode, bump, Materials}; 4 | use bumpalo_herd::Member; 5 | use crate::core::interaction::{SurfaceInteraction}; 6 | use crate::core::pbrt::INFINITY; 7 | use crate::core::reflection::{BSDF, BxDFs, FresnelSpecular, Fresnels, FresnelDielectric, SpecularReflection, MicrofacetReflection, SpecularTransmission, MicrofacetTransmission}; 8 | use crate::core::microfacet::{TrowbridgeReitzDistribution, MicrofacetDistributions}; 9 | use crate::core::paramset::TextureParams; 10 | use crate::core::spectrum::Spectrum; 11 | 12 | pub struct GlassMaterial { 13 | Kr : Arc, 14 | Kt : Arc, 15 | uroughness : Arc, 16 | vroughness : Arc, 17 | index : Arc, 18 | bump_map : Option>, 19 | remap_roughness : bool 20 | } 21 | 22 | impl GlassMaterial { 23 | pub fn new( 24 | Kr: Arc, Kt: Arc, uroughness: Arc, 25 | vroughness: Arc, index: Arc, 26 | bump_map : Option>, remap_roughness : bool) -> Self { 27 | Self { 28 | Kr, Kt, uroughness, vroughness, 29 | index, bump_map, remap_roughness 30 | } 31 | } 32 | } 33 | 34 | impl Material for GlassMaterial { 35 | fn compute_scattering_functions<'b: 'b>( 36 | &self, si: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 37 | _mat: Option>, mode: TransportMode, allow_multiple_lobes: bool) { 38 | // Perform bump mapping with bump_map if present 39 | if let Some(ref map) = self.bump_map { 40 | bump(map, si); 41 | } 42 | 43 | let eta = self.index.evaluate(si); 44 | let mut urough = self.uroughness.evaluate(si); 45 | let mut vrough = self.vroughness.evaluate(si); 46 | let R = self.Kr.evaluate(si).clamps(0.0, INFINITY); 47 | let T = self.Kt.evaluate(si).clamps(0.0, INFINITY); 48 | 49 | 50 | let mut bsdf = BSDF::new(si, eta); 51 | 52 | if R.is_black() && T.is_black() { return; } 53 | 54 | let is_specular = urough == 0.0 && vrough == 0.0; 55 | 56 | if is_specular && allow_multiple_lobes { 57 | let bxdf: &mut BxDFs = arena.alloc(FresnelSpecular::new(&R, &T, 1.0, eta, mode).into()); 58 | bsdf.add(bxdf) 59 | } else { 60 | if self.remap_roughness { 61 | urough = TrowbridgeReitzDistribution::roughness_to_alpha(urough); 62 | vrough = TrowbridgeReitzDistribution::roughness_to_alpha(vrough); 63 | } 64 | 65 | let distrib: &mut MicrofacetDistributions = arena.alloc(TrowbridgeReitzDistribution::new(urough, vrough, true).into()); 66 | 67 | 68 | if !R.is_black() { 69 | let fresnel: &mut Fresnels = arena.alloc(FresnelDielectric::new(1.0, eta).into()); 70 | let bxdf: &mut BxDFs = if is_specular { 71 | arena.alloc(SpecularReflection::new(&R, fresnel).into()) 72 | } else { 73 | arena.alloc(MicrofacetReflection::new(&R, distrib, fresnel).into()) 74 | }; 75 | 76 | bsdf.add(bxdf) 77 | } 78 | 79 | if !T.is_black() { 80 | let bxdf: &mut BxDFs = if is_specular { 81 | arena.alloc(SpecularTransmission::new(&T, 1.0, eta, mode).into()) 82 | } else { 83 | arena.alloc(MicrofacetTransmission::new(&T, distrib, 1.0, eta, mode).into()) 84 | }; 85 | 86 | bsdf.add(bxdf) 87 | } 88 | } 89 | 90 | si.bsdf = Some(bsdf); 91 | 92 | } 93 | } 94 | 95 | pub fn create_glass_material(mp: &mut TextureParams) -> Materials { 96 | let Kr = mp.get_spectrumtexture("Kr", Spectrum::new(1.0)); 97 | let Kt = mp.get_spectrumtexture("Kt", Spectrum::new(1.0)); 98 | let eta = match mp.get_floattexture_ornull("eta") { 99 | Some(t) => t, 100 | _ => mp.get_floattexture("index", 1.5) 101 | }; 102 | let roughu = mp.get_floattexture("uroughness", 0.0); 103 | let roughv = mp.get_floattexture("vroughness", 0.0); 104 | let bump_map = mp.get_floattexture_ornull("bumpmap"); 105 | let remap_roughness = mp.find_bool("remaproughness", true); 106 | 107 | let glass = GlassMaterial::new(Kr, Kt, roughu, roughv, eta, bump_map, remap_roughness); 108 | 109 | glass.into() 110 | } -------------------------------------------------------------------------------- /src/materials/kdsubsurface.rs: -------------------------------------------------------------------------------- 1 | use crate::core::pbrt::{Float, INFINITY}; 2 | use std::sync::Arc; 3 | use crate::core::texture::{TextureSpec, TextureFloat, Texture}; 4 | use crate::core::bssrdf::{BSSRDFTable, subsurface_from_diffuse, TabulatedBSSRDF, compute_beam_diffusion_bssrdf}; 5 | use crate::core::material::{Material, TransportMode, bump, Materials}; 6 | use bumpalo_herd::Member; 7 | use crate::core::interaction::SurfaceInteraction; 8 | use crate::core::reflection::{BSDF, BxDFs, FresnelSpecular, FresnelDielectric, Fresnels, SpecularReflection, MicrofacetReflection, SpecularTransmission, MicrofacetTransmission}; 9 | use crate::core::microfacet::{TrowbridgeReitzDistribution, MicrofacetDistributions}; 10 | use crate::core::paramset::TextureParams; 11 | use crate::core::spectrum::{Spectrum, SpectrumType}; 12 | 13 | pub struct KdSubsurfaceMaterial { 14 | scale : Float, 15 | kd : Arc, 16 | kr : Arc, 17 | kt : Arc, 18 | mfp : Arc, 19 | uroughness : Arc, 20 | vroughness : Arc, 21 | bumpmap : Option>, 22 | eta : Float, 23 | remap_roughness : bool, 24 | table : Arc 25 | } 26 | 27 | impl KdSubsurfaceMaterial { 28 | pub fn new( 29 | scale: Float, kd: Arc, kr: Arc, 30 | kt: Arc, mfp: Arc, uroughness: Arc, 31 | vroughness: Arc, bumpmap: Option>, eta: Float, 32 | g: Float, remap_roughness: bool) -> Self { 33 | let mut table = BSSRDFTable::new(100, 64); 34 | compute_beam_diffusion_bssrdf(g, eta, &mut table); 35 | 36 | Self { 37 | scale, kd, kr, kt, mfp, uroughness, 38 | vroughness, bumpmap, eta, remap_roughness, 39 | table: Arc::new(table) 40 | } 41 | } 42 | } 43 | 44 | impl Material for KdSubsurfaceMaterial { 45 | fn compute_scattering_functions<'b:'b>( 46 | &self, si: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 47 | mat: Option>, mode: TransportMode, allow_multiple_lobes: bool) { 48 | // Perform bump mapping with bumpmap if present 49 | if let Some(ref map) = self.bumpmap { 50 | bump(map, si); 51 | } 52 | 53 | let R = self.kr.evaluate(si).clamps(0.0, INFINITY); 54 | let T = self.kt.evaluate(si).clamps(0.0, INFINITY); 55 | let mut urough = self.uroughness.evaluate(si); 56 | let mut vrough = self.vroughness.evaluate(si); 57 | 58 | // Initialize bsdf for smooth or rough dielectric 59 | let mut bsdf = BSDF::new(si, self.eta); 60 | 61 | if R.is_black() && T.is_black() { return; } 62 | 63 | let is_specular = urough == 0.0 && vrough == 0.0; 64 | 65 | if is_specular && allow_multiple_lobes { 66 | let bxdf: &mut BxDFs = arena.alloc(FresnelSpecular::new(&R, &T, 1.0, self.eta, mode).into()); 67 | bsdf.add(bxdf); 68 | } else { 69 | if self.remap_roughness { 70 | urough = TrowbridgeReitzDistribution::roughness_to_alpha(urough); 71 | vrough = TrowbridgeReitzDistribution::roughness_to_alpha(vrough); 72 | } 73 | 74 | let distrib: &mut MicrofacetDistributions = arena.alloc(TrowbridgeReitzDistribution::new(urough, vrough, true).into()); 75 | 76 | if !R.is_black() { 77 | let fresnel: &mut Fresnels = arena.alloc(FresnelDielectric::new(1.0, self.eta).into()); 78 | 79 | bsdf.add( 80 | if is_specular { 81 | arena.alloc(SpecularReflection::new(&R, fresnel).into()) 82 | } else { 83 | arena.alloc(MicrofacetReflection::new(&R, distrib, fresnel).into()) 84 | } 85 | ) 86 | } 87 | 88 | if !T.is_black() { 89 | bsdf.add( 90 | if is_specular { 91 | arena.alloc(SpecularTransmission::new(&T, 1.0, self.eta, mode).into()) 92 | } else { 93 | arena.alloc(MicrofacetTransmission::new(&T, distrib, 1.0, self.eta, mode).into()) 94 | } 95 | ) 96 | } 97 | } 98 | 99 | si.bsdf = Some(bsdf); 100 | 101 | let mfree = self.mfp.evaluate(si).clamps(0.0, INFINITY) * self.scale; 102 | let kd = self.kd.evaluate(si).clamps(0.0, INFINITY).clamps(0.0, INFINITY); 103 | let (sigma_a, sigma_s) = subsurface_from_diffuse(&self.table, &kd, &mfree); 104 | let bssrdf = TabulatedBSSRDF::new(si, mat, mode, self.eta, &sigma_a, &sigma_s, self.table.clone()).into(); 105 | 106 | si.bssrdf = Some(bssrdf) 107 | } 108 | } 109 | 110 | pub fn create_kdsubsurface_material(mp: &mut TextureParams) -> Materials { 111 | let Kd = [0.5, 0.5, 0.5]; 112 | let kd = mp.get_spectrumtexture("Kd", Spectrum::from_rgb(Kd, SpectrumType::Reflectance)); 113 | let mfp = mp.get_spectrumtexture("mfp", Spectrum::new(1.0)); 114 | let kr = mp.get_spectrumtexture("Kr", Spectrum::new(1.0)); 115 | let kt = mp.get_spectrumtexture("Kt", Spectrum::new(1.0)); 116 | let roughu = mp.get_floattexture("uroughness", 0.0); 117 | let roughv = mp.get_floattexture("vroughness", 0.0); 118 | let bumpmap = mp.get_floattexture_ornull("bumpmap"); 119 | let eta = mp.find_float("eta", 1.33); 120 | let scale = mp.find_float("scale", 1.0); 121 | let g = mp.find_float("g", 0.0); 122 | let remap_roughness = mp.find_bool("remaproughness", true); 123 | 124 | KdSubsurfaceMaterial::new( 125 | scale, kd, kr, kt, mfp, roughu, 126 | roughv, bumpmap, eta, g, remap_roughness).into() 127 | } -------------------------------------------------------------------------------- /src/materials/matte.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use crate::core::texture::{Texture, TextureSpec, TextureFloat}; 3 | use crate::core::spectrum::Spectrum; 4 | use crate::core::pbrt::{clamp, INFINITY}; 5 | use crate::core::material::{Material, TransportMode, bump, Materials}; 6 | use crate::core::interaction::SurfaceInteraction; 7 | use crate::core::reflection::{BSDF, BxDFs, OrenNayar}; 8 | use crate::core::reflection::{LambertianReflection}; 9 | use crate::core::paramset::TextureParams; 10 | use bumpalo_herd::Member; 11 | 12 | pub struct MatteMaterial { 13 | kd : Arc, 14 | sigma : Arc, 15 | bump_map: Option> 16 | } 17 | 18 | impl MatteMaterial { 19 | pub fn new( 20 | kd: Arc, 21 | sigma: Arc, 22 | bump_map: Option>) -> Self { 23 | Self { kd, sigma, bump_map } 24 | } 25 | } 26 | 27 | impl Material for MatteMaterial { 28 | fn compute_scattering_functions<'b: 'b>( 29 | &self, si: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 30 | _mat: Option>, _mode: TransportMode, _allow_multiple_lobes: bool) { 31 | // Perform bump mapping with bumpMap if present 32 | if let Some(ref map) = self.bump_map { 33 | bump(map, si); 34 | } 35 | 36 | // Evaluate textures for MatteMaterial and allocate BRDF 37 | let mut bsdf = BSDF::new(si, 1.0); 38 | let r = self.kd.evaluate(si).clamps(0.0, INFINITY); 39 | let sig = clamp(self.sigma.evaluate(si), 0.0 , 90.0); 40 | 41 | if !r.is_black() { 42 | let bxdf: &mut BxDFs = if sig == 0.0 { 43 | arena.alloc(LambertianReflection::new(&r).into()) 44 | } else { 45 | arena.alloc(OrenNayar::new(&r, sig).into()) 46 | }; 47 | 48 | bsdf.add(bxdf); 49 | } 50 | 51 | si.bsdf = Some(bsdf); 52 | } 53 | } 54 | 55 | pub fn create_matte_material(mp: &mut TextureParams) -> Materials { 56 | let kd = mp.get_spectrumtexture("Kd", Spectrum::new(0.5)); 57 | let sigma = mp.get_floattexture("sigma", 0.0); 58 | let bump_map = mp.get_floattexture_ornull("bumpmap"); 59 | 60 | MatteMaterial::new(kd, sigma, bump_map).into() 61 | 62 | } -------------------------------------------------------------------------------- /src/materials/metal.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureSpec, TextureFloat, Texture}; 2 | use std::sync::Arc; 3 | use crate::core::material::{Material, TransportMode, bump, Materials}; 4 | use crate::core::interaction::SurfaceInteraction; 5 | use crate::core::reflection::{BSDF, Fresnels, FresnelConductor, BxDFs, MicrofacetReflection}; 6 | use crate::core::microfacet::{TrowbridgeReitzDistribution, MicrofacetDistributions}; 7 | use crate::core::spectrum::Spectrum; 8 | use crate::core::pbrt::Float; 9 | use crate::core::paramset::TextureParams; 10 | use lazy_static::lazy_static; 11 | use bumpalo_herd::Member; 12 | 13 | const COPPER_SAMPLES: usize = 56; 14 | 15 | const COPPER_WAVE_LENGHTS: [Float; COPPER_SAMPLES] = [ 16 | 298.7570554, 302.4004341, 306.1337728, 309.960445, 313.8839949, 17 | 317.9081487, 322.036826, 326.2741526, 330.6244747, 335.092373, 18 | 339.6826795, 344.4004944, 349.2512056, 354.2405086, 359.374429, 19 | 364.6593471, 370.1020239, 375.7096303, 381.4897785, 387.4505563, 20 | 393.6005651, 399.9489613, 406.5055016, 413.2805933, 420.2853492, 21 | 427.5316483, 435.0322035, 442.8006357, 450.8515564, 459.2006593, 22 | 467.8648226, 476.8622231, 486.2124627, 495.936712, 506.0578694, 23 | 516.6007417, 527.5922468, 539.0616435, 551.0407911, 563.5644455, 24 | 576.6705953, 590.4008476, 604.8008683, 619.92089, 635.8162974, 25 | 652.5483053, 670.1847459, 688.8009889, 708.4810171, 729.3186941, 26 | 751.4192606, 774.9011125, 799.8979226, 826.5611867, 855.0632966, 27 | 885.6012714 28 | ]; 29 | 30 | const COPPERN: [Float; COPPER_SAMPLES] = [ 31 | 1.400313, 1.38, 1.358438, 1.34, 1.329063, 1.325, 1.3325, 1.34, 32 | 1.334375, 1.325, 1.317812, 1.31, 1.300313, 1.29, 1.281563, 1.27, 33 | 1.249062, 1.225, 1.2, 1.18, 1.174375, 1.175, 1.1775, 1.18, 34 | 1.178125, 1.175, 1.172812, 1.17, 1.165312, 1.16, 1.155312, 1.15, 35 | 1.142812, 1.135, 1.131562, 1.12, 1.092437, 1.04, 0.950375, 0.826, 36 | 0.645875, 0.468, 0.35125, 0.272, 0.230813, 0.214, 0.20925, 0.213, 37 | 0.21625, 0.223, 0.2365, 0.25, 0.254188, 0.26, 0.28, 0.3 38 | ]; 39 | 40 | const COPPERK: [Float; COPPER_SAMPLES] = [ 41 | 1.662125, 1.687, 1.703313, 1.72, 1.744563, 1.77, 1.791625, 1.81, 42 | 1.822125, 1.834, 1.85175, 1.872, 1.89425, 1.916, 1.931688, 1.95, 43 | 1.972438, 2.015, 2.121562, 2.21, 2.177188, 2.13, 2.160063, 2.21, 44 | 2.249938, 2.289, 2.326, 2.362, 2.397625, 2.433, 2.469187, 2.504, 45 | 2.535875, 2.564, 2.589625, 2.605, 2.595562, 2.583, 2.5765, 2.599, 46 | 2.678062, 2.809, 3.01075, 3.24, 3.458187, 3.67, 3.863125, 4.05, 47 | 4.239563, 4.43, 4.619563, 4.817, 5.034125, 5.26, 5.485625, 5.717 48 | ]; 49 | 50 | lazy_static! { 51 | static ref COPPER_N: Spectrum = Spectrum::from_sampled(&COPPER_WAVE_LENGHTS, &COPPERN, COPPER_SAMPLES); 52 | static ref COPPER_K: Spectrum = Spectrum::from_sampled(&COPPER_WAVE_LENGHTS, &COPPERK, COPPER_SAMPLES); 53 | } 54 | 55 | pub struct MetalMaterial { 56 | eta : Arc, 57 | k : Arc, 58 | roughness : Arc, 59 | uroughness : Option>, 60 | vroughness : Option>, 61 | bump_map : Option>, 62 | remap_roughness : bool 63 | } 64 | 65 | impl MetalMaterial { 66 | pub fn new( 67 | eta: Arc, k: Arc, roughness: Arc, 68 | uroughness: Option>, vroughness: Option>, 69 | bump_map: Option>, remap_roughness: bool) -> Self { 70 | Self { 71 | eta, k, roughness, uroughness, 72 | vroughness, bump_map , remap_roughness 73 | } 74 | } 75 | } 76 | 77 | impl Material for MetalMaterial { 78 | fn compute_scattering_functions<'b: 'b>( 79 | &self, si: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 80 | _mat: Option>, _mode: TransportMode, _allow_multiple_lobes: bool) { 81 | // Perform bump mapping with bump_map if present 82 | if let Some(ref map) = self.bump_map { 83 | bump(map, si) 84 | } 85 | 86 | let mut bsdf = BSDF::new(si, 1.0); 87 | 88 | let mut urough = if let Some(ref u) = self.uroughness { 89 | u.evaluate(si) 90 | } else { 91 | self.roughness.evaluate(si) 92 | }; 93 | let mut vrough = if let Some(ref v) = self.vroughness { 94 | v.evaluate(si) 95 | } else { 96 | self.roughness.evaluate(si) 97 | }; 98 | 99 | if self.remap_roughness { 100 | urough = TrowbridgeReitzDistribution::roughness_to_alpha(urough); 101 | vrough = TrowbridgeReitzDistribution::roughness_to_alpha(vrough); 102 | } 103 | 104 | let etat = self.eta.evaluate(si); 105 | let k = self.k.evaluate(si); 106 | let fres: &mut Fresnels = arena.alloc(FresnelConductor::new(&Spectrum::new(1.0), &etat, &k).into()); 107 | 108 | let distrib: &mut MicrofacetDistributions = arena.alloc(TrowbridgeReitzDistribution::new(urough, vrough, true).into()); 109 | let bxdf: &mut BxDFs = arena.alloc(MicrofacetReflection::new(&Spectrum::new(1.0), distrib, fres).into()); 110 | bsdf.add(bxdf); 111 | si.bsdf = Some(bsdf) 112 | } 113 | } 114 | 115 | pub fn create_metal_material(mp: &mut TextureParams) -> Materials { 116 | let eta = mp.get_spectrumtexture("eta", *COPPER_N); 117 | let k = mp.get_spectrumtexture("k", *COPPER_K); 118 | let roughness = mp.get_floattexture("roughness", 0.01); 119 | let uroughness = mp.get_floattexture_ornull("uroughness"); 120 | let vroughness = mp.get_floattexture_ornull("vroughness"); 121 | let bump_map = mp.get_floattexture_ornull("bumpmap"); 122 | let remap_roughness = mp.find_bool("remaproughness", true); 123 | 124 | MetalMaterial::new(eta, k, roughness, uroughness, vroughness, bump_map, remap_roughness).into() 125 | } -------------------------------------------------------------------------------- /src/materials/mirror.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureSpec, TextureFloat, Texture}; 2 | use std::sync::Arc; 3 | use crate::core::material::{Material, TransportMode, bump, Materials}; 4 | use crate::core::interaction::SurfaceInteraction; 5 | use crate::core::reflection::{BSDF, BxDFs, SpecularReflection, Fresnels, FresnelNoOp}; 6 | use crate::core::pbrt::INFINITY; 7 | use crate::core::paramset::TextureParams; 8 | use crate::core::spectrum::Spectrum; 9 | use bumpalo_herd::Member; 10 | 11 | pub struct MirrorMaterial { 12 | Kr : Arc, 13 | bump_map : Option> 14 | } 15 | 16 | impl MirrorMaterial { 17 | pub fn new(Kr: Arc, bump_map: Option>) -> Self { 18 | Self { Kr, bump_map } 19 | } 20 | } 21 | 22 | impl Material for MirrorMaterial { 23 | fn compute_scattering_functions<'b: 'b>( 24 | &self, si: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 25 | _mat: Option>, _mode: TransportMode, _allow_multiple_lobes: bool) { 26 | // Perform bump mapping with bump_map if present 27 | if let Some(ref map) = self.bump_map { 28 | bump(map, si); 29 | } 30 | 31 | let mut bsdf = BSDF::new(si, 1.0); 32 | let R = self.Kr.evaluate(si).clamps(0.0, INFINITY); 33 | 34 | if !R.is_black() { 35 | let fresnel: &mut Fresnels = arena.alloc(FresnelNoOp().into()); 36 | let bxdf: &mut BxDFs = arena.alloc(SpecularReflection::new(&R, fresnel).into()); 37 | bsdf.add(bxdf); 38 | } 39 | 40 | si.bsdf = Some(bsdf) 41 | } 42 | } 43 | 44 | pub fn create_mirror_material(mp: &mut TextureParams) -> Materials { 45 | let Kr = mp.get_spectrumtexture("Kr", Spectrum::new(0.9)); 46 | let bump_map = mp.get_floattexture_ornull("bumpmap"); 47 | let mirror = MirrorMaterial::new(Kr, bump_map); 48 | 49 | mirror.into() 50 | } 51 | 52 | -------------------------------------------------------------------------------- /src/materials/mix.rs: -------------------------------------------------------------------------------- 1 | use crate::core::material::{Materials, Material, TransportMode}; 2 | use std::sync::Arc; 3 | use crate::core::texture::{Texture, TextureSpec}; 4 | use crate::core::spectrum::Spectrum; 5 | use crate::core::interaction::SurfaceInteraction; 6 | use crate::core::pbrt::INFINITY; 7 | use crate::core::reflection::{BxDFType, ScaledBxDF}; 8 | use crate::core::paramset::TextureParams; 9 | use bumpalo_herd::Member; 10 | 11 | pub struct MixMaterial { 12 | m1 : Arc, 13 | m2 : Arc, 14 | scale : Arc 15 | } 16 | 17 | impl MixMaterial { 18 | pub fn new( 19 | m1: Arc, m2: Arc, 20 | scale: Arc) -> Self { 21 | Self { m1, m2, scale } 22 | } 23 | } 24 | 25 | impl Material for MixMaterial { 26 | fn compute_scattering_functions<'b: 'b>( 27 | &self, si: &mut SurfaceInteraction<'b>, arena: & Member<'b>, 28 | _mat: Option>, mode: TransportMode, allow_multiple_lobes: bool) { 29 | let s1 = self.scale.evaluate(si).clamps(0.0, INFINITY); 30 | let s2 = (Spectrum::new(1.0) - s1).clamps(0.0, INFINITY); 31 | let mut si2 = SurfaceInteraction::new( 32 | &si.p, &si.p_error, &si.uv, &si.wo, &si.dpdu, &si.dpdv, 33 | &si.dndu, &si.dndv, si.time, si.shape.clone()); 34 | 35 | self.m1.compute_scattering_functions(si, arena, Some(self.m1.clone()), mode, allow_multiple_lobes); 36 | self.m2.compute_scattering_functions(&mut si2, arena, Some(self.m2.clone()), mode, allow_multiple_lobes); 37 | 38 | let b1 = si.bsdf.as_mut().unwrap(); 39 | let b2 = si2.bsdf.as_mut().unwrap(); 40 | let n1 = b1.num_components(BxDFType::All as u8); 41 | let n2 = b2.num_components(BxDFType::All as u8); 42 | 43 | for i in 0..n1 { 44 | b1.bxdfs[i] = arena.alloc(ScaledBxDF::new(b1.bxdfs[i], &s1).into()); 45 | } 46 | 47 | for i in 0..n2 { 48 | b1.add(arena.alloc(ScaledBxDF::new(b2.bxdfs[i], &s2).into())); 49 | } 50 | } 51 | } 52 | 53 | pub fn create_mix_material(mp: &mut TextureParams, m1: Arc, m2: Arc) -> Materials { 54 | let scale = mp.get_spectrumtexture("amount", Spectrum::new(0.5)); 55 | 56 | MixMaterial::new(m1, m2, scale).into() 57 | } -------------------------------------------------------------------------------- /src/materials/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod matte; 2 | pub mod plastic; 3 | pub mod mix; 4 | pub mod fourier; 5 | pub mod subsurface; 6 | pub mod kdsubsurface; 7 | pub mod glass; 8 | pub mod mirror; 9 | pub mod hair; 10 | pub mod metal; 11 | pub mod substrate; 12 | pub mod uber; 13 | pub mod translucent; 14 | pub mod disney; -------------------------------------------------------------------------------- /src/materials/plastic.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use crate::core::texture::{Texture, TextureSpec, TextureFloat}; 3 | use crate::core::spectrum::Spectrum; 4 | use crate::core::pbrt::{INFINITY}; 5 | use crate::core::material::{Material, TransportMode, bump, Materials}; 6 | use crate::core::interaction::SurfaceInteraction; 7 | use crate::core::reflection::{BSDF, Fresnels, MicrofacetReflection, BxDFs}; 8 | use crate::core::reflection::{LambertianReflection}; 9 | use crate::core::reflection::FresnelDielectric; 10 | use crate::core::microfacet::{TrowbridgeReitzDistribution, MicrofacetDistributions}; 11 | use crate::core::paramset::TextureParams; 12 | use bumpalo_herd::Member; 13 | 14 | 15 | pub struct PlasticMaterial { 16 | kd: Arc, 17 | ks: Arc, 18 | roughness: Arc, 19 | bump_map: Option>, 20 | remap_roughness: bool 21 | 22 | } 23 | 24 | impl PlasticMaterial { 25 | pub fn new( 26 | kd: Arc, ks: Arc, 27 | roughness: Arc, bump_map: Option>, 28 | remap_roughness: bool) -> Self { 29 | Self { kd, ks, roughness, bump_map, remap_roughness } 30 | } 31 | } 32 | 33 | impl Material for PlasticMaterial { 34 | fn compute_scattering_functions<'b: 'b>( 35 | &self, si: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 36 | _mat: Option>, _mode: TransportMode, _allow_multiple_lobes: bool) { 37 | // Perform bump mapping with bumpmap if present 38 | if self.bump_map.is_some() { bump(self.bump_map.as_ref().unwrap(), si); } 39 | let mut bsdf = BSDF::new(si, 1.0); 40 | 41 | // Initialize diffuse component of plastic material 42 | let kd = self.kd.evaluate(si).clamps(0.0, INFINITY); 43 | 44 | if !kd.is_black() { 45 | bsdf.add(arena.alloc(LambertianReflection::new(&kd).into())); 46 | } 47 | 48 | // Initialize specular component of plastic material 49 | let ks = self.ks.evaluate(si).clamps(0.0, INFINITY); 50 | 51 | if !ks.is_black() { 52 | let fresnel: &mut Fresnels = arena.alloc(FresnelDielectric::new(1.5, 1.0).into()); 53 | 54 | // Create microfacet distribution distrib for plastic material 55 | let mut rough = self.roughness.evaluate(si); 56 | 57 | if self.remap_roughness { 58 | rough = TrowbridgeReitzDistribution::roughness_to_alpha(rough); 59 | } 60 | 61 | let distrib: &mut MicrofacetDistributions = 62 | arena.alloc(TrowbridgeReitzDistribution::new(rough, rough, true).into()); 63 | let spec: &mut BxDFs = 64 | arena.alloc(MicrofacetReflection::new(&ks, distrib, fresnel).into()); 65 | bsdf.add(spec); 66 | } 67 | 68 | si.bsdf = Some(bsdf); 69 | } 70 | } 71 | 72 | pub fn create_plastic_material(mp: &mut TextureParams) -> Materials { 73 | let kd = mp.get_spectrumtexture("Kd", Spectrum::new(0.25)); 74 | let ks = mp.get_spectrumtexture("Ks", Spectrum::new(0.25)); 75 | let roughness = mp.get_floattexture("roughness", 0.1); 76 | let bump_map = mp.get_floattexture_ornull("bumpmap"); 77 | let remap_roughness = mp.find_bool("remaproughness", true); 78 | 79 | PlasticMaterial::new(kd, ks, roughness, bump_map, remap_roughness).into() 80 | 81 | } -------------------------------------------------------------------------------- /src/materials/substrate.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureSpec, TextureFloat, Texture}; 2 | use std::sync::Arc; 3 | use crate::core::material::{Material, TransportMode, bump, Materials}; 4 | use crate::core::interaction::SurfaceInteraction; 5 | use crate::core::reflection::{BSDF, FresnelBlend, BxDFs}; 6 | use crate::core::pbrt::INFINITY; 7 | use crate::core::microfacet::{TrowbridgeReitzDistribution, MicrofacetDistributions}; 8 | use crate::core::paramset::TextureParams; 9 | use crate::core::spectrum::Spectrum; 10 | use bumpalo_herd::Member; 11 | 12 | pub struct SubstrateMaterial { 13 | kd : Arc, 14 | ks : Arc, 15 | nu : Arc, 16 | nv : Arc, 17 | bumpmap : Option>, 18 | remap_roughness : bool 19 | } 20 | 21 | impl SubstrateMaterial { 22 | pub fn new( 23 | kd: Arc, 24 | ks: Arc, 25 | nu: Arc, 26 | nv: Arc, 27 | bumpmap: Option>, 28 | remap_roughness: bool) -> Self { 29 | Self { kd, ks, nu, nv, bumpmap, remap_roughness } 30 | } 31 | } 32 | 33 | impl Material for SubstrateMaterial { 34 | fn compute_scattering_functions<'b: 'b>( 35 | &self, si: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 36 | _mat: Option>, _mode: TransportMode, _allow_multiple_lobes: bool) { 37 | // Perform bump mapping with bumpmap if present 38 | if let Some(ref map) = self.bumpmap { 39 | bump(map, si); 40 | } 41 | 42 | let mut bsdf = BSDF::new(si, 1.0); 43 | let d = self.kd.evaluate(si).clamps(0.0, INFINITY); 44 | let s = self.ks.evaluate(si).clamps(0.0, INFINITY); 45 | let mut roughu = self.nu.evaluate(si); 46 | let mut roughv = self.nv.evaluate(si); 47 | 48 | if !d.is_black() || !s.is_black() { 49 | if self.remap_roughness { 50 | roughu = TrowbridgeReitzDistribution::roughness_to_alpha(roughu); 51 | roughv = TrowbridgeReitzDistribution::roughness_to_alpha(roughv) 52 | } 53 | 54 | let distrib: &mut MicrofacetDistributions = 55 | arena.alloc(TrowbridgeReitzDistribution::new(roughu, roughv, true).into()); 56 | let bxdf: &mut BxDFs = arena.alloc(FresnelBlend::new(&d, &s, distrib).into()); 57 | bsdf.add(bxdf); 58 | si.bsdf = Some(bsdf); 59 | } 60 | } 61 | } 62 | 63 | pub fn create_substrate_material(mp: &mut TextureParams) -> Materials { 64 | let kd = mp.get_spectrumtexture("Kd", Spectrum::new(0.5)); 65 | let ks = mp.get_spectrumtexture("Ks", Spectrum::new(0.5)); 66 | let urough = mp.get_floattexture("uroughness", 0.1); 67 | let vrough = mp.get_floattexture("vroughness", 0.1); 68 | let bumpmap = mp.get_floattexture_ornull("bumpmap"); 69 | let remap_roughness = mp.find_bool("remaproughness", true); 70 | 71 | SubstrateMaterial::new(kd, ks, urough, vrough, bumpmap, remap_roughness).into() 72 | } -------------------------------------------------------------------------------- /src/materials/translucent.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureSpec, TextureFloat, Texture}; 2 | use std::sync::Arc; 3 | use crate::core::material::{Material, Materials, TransportMode, bump}; 4 | use crate::core::interaction::SurfaceInteraction; 5 | use crate::core::paramset::TextureParams; 6 | use crate::core::spectrum::Spectrum; 7 | use crate::core::reflection::{BSDF, BxDFs, LambertianReflection, LambertianTransmission, Fresnels, FresnelDielectric, MicrofacetReflection, MicrofacetTransmission}; 8 | use crate::core::pbrt::INFINITY; 9 | use crate::core::microfacet::{TrowbridgeReitzDistribution, MicrofacetDistributions}; 10 | use bumpalo_herd::Member; 11 | 12 | pub struct TranslucentMaterial { 13 | kd : Arc, 14 | ks : Arc, 15 | roughness : Arc, 16 | reflect : Arc, 17 | transmit : Arc, 18 | bumpmap : Option>, 19 | remap_roughness : bool 20 | } 21 | 22 | impl TranslucentMaterial { 23 | pub fn new( 24 | kd: Arc, ks: Arc, roughness: Arc, 25 | reflect: Arc, transmit: Arc, 26 | bumpmap: Option>, remap_roughness: bool) -> Self { 27 | Self { kd, ks, roughness, reflect, transmit, bumpmap, remap_roughness } 28 | } 29 | } 30 | 31 | impl Material for TranslucentMaterial { 32 | fn compute_scattering_functions<'b: 'b>( 33 | &self, si: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 34 | _mat: Option>, mode: TransportMode, _allow_multiple_lobes: bool) { 35 | // Perform bump mapping with bumpmap if present 36 | if let Some(ref map) = self.bumpmap { 37 | bump(map, si); 38 | } 39 | 40 | let eta = 1.5; 41 | let mut bsdf = BSDF::new(si, eta); 42 | 43 | let r = self.reflect.evaluate(si).clamps(0.0, INFINITY); 44 | let t = self.transmit.evaluate(si).clamps(0.0, INFINITY); 45 | 46 | if r.is_black() && t.is_black() { return; } 47 | 48 | let kd = self.kd.evaluate(si).clamps(0.0, INFINITY); 49 | if !kd.is_black() { 50 | if !r.is_black() { 51 | bsdf.add(arena.alloc(LambertianReflection::new(&(r * kd)).into())) 52 | } 53 | if !t.is_black() { 54 | bsdf.add(arena.alloc(LambertianTransmission::new(&(t * kd)).into())) 55 | } 56 | } 57 | 58 | let ks = self.ks.evaluate(si).clamps(0.0, INFINITY); 59 | if !ks.is_black() && (!r.is_black() || !t.is_black()) { 60 | let mut rough = self.roughness.evaluate(si); 61 | 62 | if self.remap_roughness { 63 | rough = TrowbridgeReitzDistribution::roughness_to_alpha(rough) 64 | } 65 | 66 | let distrib: &mut MicrofacetDistributions = arena.alloc(TrowbridgeReitzDistribution::new(rough, rough, true).into()); 67 | 68 | if !r.is_black() { 69 | let fresnel: &mut Fresnels = arena.alloc(FresnelDielectric::new(1.0, eta).into()); 70 | let bxdf: &mut BxDFs = arena.alloc(MicrofacetReflection::new(&(r * ks), distrib, fresnel).into()); 71 | bsdf.add(bxdf); 72 | } 73 | 74 | if !t.is_black() { 75 | let bxdf: &mut BxDFs = arena.alloc(MicrofacetTransmission::new(&(t * ks), distrib, 1.0, eta, mode).into()); 76 | bsdf.add(bxdf); 77 | } 78 | } 79 | 80 | si.bsdf = Some(bsdf); 81 | } 82 | } 83 | 84 | pub fn create_translucent_material(mp: &mut TextureParams) -> Materials { 85 | let kd = mp.get_spectrumtexture("Kd", Spectrum::new(0.25)); 86 | let ks = mp.get_spectrumtexture("Ks", Spectrum::new(0.25)); 87 | let reflect = mp.get_spectrumtexture("reflect", Spectrum::new(0.5)); 88 | let transmit = mp.get_spectrumtexture("transmit", Spectrum::new(0.5)); 89 | let roughness = mp.get_floattexture("roughness", 0.1); 90 | let bumpmap = mp.get_floattexture_ornull("bumpmap"); 91 | let remap = mp.find_bool("remaproughness", true); 92 | 93 | TranslucentMaterial::new(kd, ks, roughness, reflect, transmit, bumpmap, remap).into() 94 | } -------------------------------------------------------------------------------- /src/materials/uber.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureSpec, TextureFloat, Texture}; 2 | use std::sync::Arc; 3 | use crate::core::material::{Material, Materials, TransportMode, bump}; 4 | use crate::core::interaction::SurfaceInteraction; 5 | use crate::core::pbrt::INFINITY; 6 | use crate::core::spectrum::Spectrum; 7 | use crate::core::reflection::{BSDF, BxDFs, SpecularTransmission, LambertianReflection, MicrofacetReflection, Fresnels, FresnelDielectric, SpecularReflection}; 8 | use crate::core::microfacet::{TrowbridgeReitzDistribution, MicrofacetDistributions}; 9 | use crate::core::paramset::TextureParams; 10 | use bumpalo_herd::Member; 11 | 12 | pub struct UberMaterial { 13 | kd : Arc, 14 | ks : Arc, 15 | kr : Arc, 16 | kt : Arc, 17 | opacity : Arc, 18 | roughness : Arc, 19 | roughnessu : Option>, 20 | roughnessv : Option>, 21 | eta : Arc, 22 | bumpmap : Option>, 23 | remap_roughness : bool, 24 | } 25 | 26 | impl UberMaterial { 27 | pub fn new( 28 | kd: Arc, ks: Arc, kr: Arc, 29 | kt: Arc, opacity: Arc, roughness: Arc, 30 | roughnessu: Option>, roughnessv: Option>, 31 | eta: Arc, bumpmap : Option>, remap_roughness: bool) -> Self { 32 | Self { 33 | kd, ks, kr, kt, opacity, roughness, roughnessu, 34 | roughnessv, eta, bumpmap, remap_roughness 35 | } 36 | } 37 | } 38 | 39 | impl Material for UberMaterial { 40 | fn compute_scattering_functions<'b: 'b>( 41 | &self, si: &mut SurfaceInteraction<'b>, arena: &Member<'b>, 42 | _mat: Option>, mode: TransportMode, _allow_multiple_lobes: bool) { 43 | // Perform bump mapping with bumpmap if present 44 | if let Some(ref map) = self.bumpmap { 45 | bump(map, si); 46 | } 47 | 48 | let e = self.eta.evaluate(si); 49 | let op = self.opacity.evaluate(si).clamps(0.0, INFINITY); 50 | let t = (-op + Spectrum::new(1.0)).clamps(0.0, INFINITY); 51 | 52 | let mut bsdf = if !t.is_black() { 53 | let mut bsdf = BSDF::new(si, 1.0); 54 | let tr: &mut BxDFs = arena.alloc(SpecularTransmission::new(&t, 1.0,1.0, mode).into()); 55 | bsdf.add(tr); 56 | 57 | bsdf 58 | } else { 59 | BSDF::new(si, e) 60 | }; 61 | 62 | let kd = op * self.kd.evaluate(si).clamps(0.0, INFINITY); 63 | if !kd.is_black() { 64 | let diff: &mut BxDFs = arena.alloc(LambertianReflection::new(&kd).into()); 65 | bsdf.add(diff); 66 | } 67 | 68 | let ks = op * self.ks.evaluate(si).clamps(0.0, INFINITY); 69 | if !ks.is_black() { 70 | let fresnel: &mut Fresnels = arena.alloc(FresnelDielectric::new(1.0, e).into()); 71 | let mut roughu = if let Some(ref ru) = self.roughnessu { 72 | ru.evaluate(si) 73 | } else { 74 | self.roughness.evaluate(si) 75 | }; 76 | let mut roughv = if let Some(ref rv) = self.roughnessv { 77 | rv.evaluate(si) 78 | } else { 79 | self.roughness.evaluate(si) 80 | }; 81 | 82 | if self.remap_roughness { 83 | roughu = TrowbridgeReitzDistribution::roughness_to_alpha(roughu); 84 | roughv = TrowbridgeReitzDistribution::roughness_to_alpha(roughv); 85 | } 86 | 87 | let distrib: &mut MicrofacetDistributions = arena.alloc(TrowbridgeReitzDistribution::new(roughu, roughv, true).into()); 88 | let spec: &mut BxDFs = arena.alloc(MicrofacetReflection::new(&ks, distrib, fresnel).into()); 89 | bsdf.add(spec) 90 | } 91 | 92 | let kr = op * self.kr.evaluate(si).clamps(0.0, INFINITY); 93 | if !kr.is_black() { 94 | let fresnel: &mut Fresnels = arena.alloc(FresnelDielectric::new(1.0, e).into()); 95 | let bxdf: &mut BxDFs = arena.alloc(SpecularReflection::new(&kr, fresnel).into()); 96 | bsdf.add(bxdf); 97 | } 98 | 99 | let kt = op * self.kt.evaluate(si).clamps(0.0, INFINITY); 100 | if !kt.is_black() { 101 | let bxdf: &mut BxDFs = arena.alloc(SpecularTransmission::new(&kt, 1.0, e, mode).into()); 102 | bsdf.add(bxdf); 103 | } 104 | 105 | si.bsdf = Some(bsdf); 106 | } 107 | } 108 | 109 | pub fn create_uber_material(mp: &mut TextureParams) -> Materials { 110 | let kd = mp.get_spectrumtexture("Kd", Spectrum::new(0.25)); 111 | let ks = mp.get_spectrumtexture("Ks", Spectrum::new(0.25)); 112 | let kr = mp.get_spectrumtexture("Kr", Spectrum::new(0.0)); 113 | let kt = mp.get_spectrumtexture("Kt", Spectrum::new(0.0)); 114 | let roughness = mp.get_floattexture("roughness", 0.1); 115 | let urough = mp.get_floattexture_ornull("uroughness"); 116 | let vrough = mp.get_floattexture_ornull("vroughness"); 117 | let eta = if let Some(e) = mp.get_floattexture_ornull("eta") { 118 | e 119 | } else { 120 | mp.get_floattexture("index", 1.5) 121 | }; 122 | let opacity = mp.get_spectrumtexture("opacity", Spectrum::new(1.0)); 123 | let bumpmap = mp.get_floattexture_ornull("bumpmap"); 124 | let remap_roughness = mp.find_bool("remaproughness", true); 125 | 126 | UberMaterial::new( 127 | kd, ks, kr, kt, opacity, roughness, urough, 128 | vrough, eta, bumpmap, remap_roughness).into() 129 | } -------------------------------------------------------------------------------- /src/media/homogeneous.rs: -------------------------------------------------------------------------------- 1 | use crate::core::spectrum::Spectrum; 2 | use crate::core::pbrt::{Float}; 3 | use crate::core::medium::{Medium, Mediums, PhaseFunctions, HenyeyGreenstein, MediumInterface}; 4 | use crate::core::sampler::{Sampler}; 5 | use crate::core::interaction::MediumInteraction; 6 | use crate::core::geometry::ray::Ray; 7 | use std::sync::Arc; 8 | 9 | #[derive(Debug, Clone, Copy)] 10 | pub struct HomogeneousMedium { 11 | g : Float, 12 | sigma_t : Spectrum, 13 | sigma_a : Spectrum, 14 | sigma_s : Spectrum 15 | } 16 | 17 | impl HomogeneousMedium { 18 | pub fn new(sigma_a: &Spectrum, sigma_s: &Spectrum, g: Float) -> Self { 19 | Self { 20 | g, 21 | sigma_a: *sigma_a, 22 | sigma_s: *sigma_s, 23 | sigma_t: *sigma_a + *sigma_s 24 | } 25 | } 26 | } 27 | 28 | impl Medium for HomogeneousMedium { 29 | fn tr(&self, ray: &Ray, _sampler: &mut S) -> Spectrum { 30 | // TODO: ProfilePhase 31 | (-self.sigma_t * (ray.t_max * ray.d.length()).min(f32::MAX)).exp() 32 | } 33 | 34 | fn sample( 35 | &self, ray: &Ray, sampler: &mut S, 36 | mi: &mut MediumInteraction) -> Spectrum { 37 | // Todo: ProfilePhase 38 | // Sample a channel and distance along the ray; 39 | let channel = std::cmp::min( 40 | (sampler.get_1d() * Spectrum::n() as Float) as usize, 41 | Spectrum::n() - 1); 42 | let dist = -((1.0 - sampler.get_1d()).ln()) / self.sigma_t[channel]; 43 | let t = (dist / ray.d.length()).min(ray.t_max); 44 | let sampled_medium = t < ray.t_max; 45 | 46 | if sampled_medium { 47 | let medium: Option> = Some(Arc::new((*self).into())); 48 | let pf: Option = Some(HenyeyGreenstein::new(self.g).into()); 49 | let minter = Some(MediumInterface::new(medium)); 50 | *mi = MediumInteraction::new( 51 | &ray.find_point(t), &(-ray.d), 52 | ray.time, minter, pf); 53 | } 54 | 55 | // Compute the transmittance and sampling for scattering from homogeneous medium 56 | let Tr = (-self.sigma_t * t.min(f32::MAX) * ray.d.length()).exp(); 57 | 58 | // Return weighting factor for scattering from homogeneous medium 59 | let density = if sampled_medium { self.sigma_t * Tr } else { Tr }; 60 | let mut pdf = 0.0; 61 | for i in 0..Spectrum::n() { pdf += density[i]; } 62 | pdf *= 1.0 / Spectrum::n() as Float; 63 | if pdf == 0.0 { 64 | assert!(Tr.is_black()); 65 | pdf = 1.0; 66 | } 67 | 68 | if sampled_medium { Tr * self.sigma_s / pdf } else { Tr / pdf } 69 | } 70 | } -------------------------------------------------------------------------------- /src/media/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod homogeneous; 2 | pub mod grid; 3 | 4 | pub fn init_stats() { 5 | grid::init_stats(); 6 | } -------------------------------------------------------------------------------- /src/pbrtparser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod syntax; 2 | pub mod lexer; 3 | pub mod tokens; 4 | pub mod pbrtparser; -------------------------------------------------------------------------------- /src/pbrtparser/syntax.rs: -------------------------------------------------------------------------------- 1 | use crate::core::paramset::ParamSet; 2 | use crate::core::pbrt::Float; 3 | 4 | #[derive(Debug)] 5 | pub enum PBRTCommands { 6 | All, 7 | StartTime, 8 | EndTime, 9 | Accelerator(StringParams), 10 | AttributeBegin, 11 | AttributeEnd, 12 | TransformBegin, 13 | TransformEnd, 14 | ObjectBegin(String), 15 | ObjectEnd, 16 | ObjectInstance(String), 17 | WorldBegin, 18 | WorldEnd, 19 | LookAt([Float; 9]), 20 | CoordSys(String), 21 | CoordTransform(String), 22 | Camera(StringParams), 23 | Film(StringParams), 24 | Include(String), 25 | Integrator(StringParams), 26 | AreaLight(StringParams), 27 | LightSource(StringParams), 28 | Material(StringParams), 29 | MakeNamedMaterial(StringParams), 30 | MakeNamedMedium(StringParams), 31 | NamedMaterial(String), 32 | MediumInterface((String, String)), 33 | Sampler(StringParams), 34 | Shape(StringParams), 35 | Filter(StringParams), 36 | ReverseOrientation, 37 | Scale([Float; 3]), 38 | Translate([Float; 3]), 39 | Rotate([Float; 4]), 40 | Texture(TextureInfo), 41 | ConcatTransform(Vec), 42 | Transform(Vec) 43 | 44 | } 45 | 46 | #[derive(Debug)] 47 | pub struct TextureInfo { 48 | pub name : String, 49 | pub ty : String, 50 | pub texname : String, 51 | pub params : ParamSet 52 | } 53 | 54 | type StringParams = (String, ParamSet); 55 | -------------------------------------------------------------------------------- /src/pbrtparser/tokens.rs: -------------------------------------------------------------------------------- 1 | use crate::core::pbrt::Float; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub enum ErrorCode { 5 | UnrecognizedToken, 6 | UnterminatedStringLiteral, 7 | ExpectedStringLiteral 8 | } 9 | 10 | #[derive(Clone, Debug, PartialEq, Eq)] 11 | pub struct Error { 12 | pub location : usize, 13 | pub code : ErrorCode 14 | } 15 | 16 | #[derive(Debug, Clone, PartialEq)] 17 | pub enum Tokens { 18 | AttributeBegin, 19 | AttributeEnd, 20 | ActiveTransform, 21 | All, 22 | EndTime, 23 | StartTime, 24 | AreaLightSource, 25 | Accelerator, 26 | ConcatTransform, 27 | CoordinateSystem, 28 | CoordSysTransform, 29 | Camera, 30 | Film, 31 | Integrator, 32 | Include, 33 | Identity, 34 | LightSource, 35 | LookAt, 36 | MakeNamedMaterial, 37 | MakeNamedMedium, 38 | Material, 39 | MediumInterface, 40 | NamedMaterial, 41 | ObjectBegin, 42 | ObjectEnd, 43 | ObjectInstance, 44 | PixelFilter, 45 | ReverseOrientation, 46 | Rotate, 47 | Shape, 48 | Sampler, 49 | Scale, 50 | TransformBegin, 51 | TransformEnd, 52 | Transform, 53 | Translate, 54 | TransformTimes, 55 | Texture, 56 | WorldBegin, 57 | WorldEnd, 58 | LeftBracket, 59 | RightBracket, 60 | Number(Float), 61 | STR(String), 62 | Comment 63 | } -------------------------------------------------------------------------------- /src/samplers/maxmin.rs: -------------------------------------------------------------------------------- 1 | use crate::core::geometry::point::{Point2i, Point2f}; 2 | use crate::core::pbrt::{Float, log2_int64, is_power_of2, round_up_pow2_64, Options}; 3 | use crate::core::rng::RNG; 4 | use log::warn; 5 | use crate::core::lowdiscrepancy::{CMAX_MIN_DIST, sample_generator_matrix, vander_corput, sobol_2d}; 6 | use crate::*; 7 | use crate::core::sampler::{Sampler, Samplers}; 8 | use crate::core::camera::CameraSample; 9 | use crate::core::sampling::shuffle; 10 | use crate::core::paramset::ParamSet; 11 | 12 | #[derive(Clone)] 13 | pub struct MaxMinDistSampler { 14 | cpixel : &'static [u32], 15 | // Sampler data 16 | current_pixel : Point2i, 17 | current_pixel_sample_index : u64, 18 | samples_1d_array_sizes : Vec, 19 | samples_2d_array_sizes : Vec, 20 | sample_array_1d : Vec>, 21 | sample_array_2d : Vec>, 22 | array_1d_offset : usize, 23 | array_2d_offset : usize, 24 | samples_per_pixel : u64, 25 | // PixelSampler data 26 | samples_1d : Vec>, 27 | samples_2d : Vec>, 28 | current_1d_dimension : usize, 29 | current_2d_dimension : usize, 30 | rng : RNG 31 | } 32 | 33 | impl MaxMinDistSampler { 34 | pub fn new(samples_per_pixel: u64, nsampled_dimensions: usize) -> Self { 35 | let mut spp = samples_per_pixel; 36 | let cindex = log2_int64(spp as i64) as u64; 37 | 38 | if cindex >= 17 { 39 | panic!( 40 | "No more than {} samples per pixel are supported with \ 41 | MaxMinDistSampler.", cindex); 42 | } 43 | 44 | if !is_power_of2(spp) { 45 | spp = round_up_pow2_64(spp as i64) as u64; 46 | warn!( 47 | "Non power-of-two sample count rounded up to {} \ 48 | for MaxMinDistSampler", spp); 49 | } 50 | 51 | let cindex = log2_int64(spp as i64); 52 | assert!(cindex >= 0 && cindex < 17); 53 | 54 | let mut maxmin = MaxMinDistSampler { 55 | cpixel: &CMAX_MIN_DIST[cindex as usize], 56 | current_pixel: Default::default(), 57 | current_pixel_sample_index: 0, 58 | samples_1d_array_sizes: vec![], 59 | samples_2d_array_sizes: vec![], 60 | sample_array_1d: vec![], 61 | sample_array_2d: vec![], 62 | array_1d_offset: 0, 63 | array_2d_offset: 0, 64 | samples_per_pixel: spp, 65 | samples_1d: vec![], 66 | samples_2d: vec![], 67 | current_1d_dimension: 0, 68 | current_2d_dimension: 0, 69 | rng: Default::default() 70 | }; 71 | 72 | pixel_sampler_new!(spp, nsampled_dimensions, maxmin); 73 | 74 | maxmin 75 | } 76 | } 77 | 78 | impl Sampler for MaxMinDistSampler { 79 | get_sampler_data!(); 80 | 81 | fn start_pixel(&mut self, p: &Point2i) { 82 | // TODO: ProfilePhase 83 | let inv_spp = 1.0 / self.samples_per_pixel as Float; 84 | 85 | for i in 0..self.samples_per_pixel as usize { 86 | let pix = Point2f::new( 87 | i as Float * inv_spp, 88 | sample_generator_matrix(&self.cpixel, i as u32, 0)); 89 | 90 | 91 | self.samples_2d[0][i] = pix 92 | } 93 | 94 | let samples = self.samples_2d[0].as_mut_slice(); 95 | shuffle(samples, self.samples_per_pixel as usize, 1, &mut self.rng); 96 | // Generate remaining samples for MaxMinDistSampler 97 | for samples in &mut self.samples_1d { 98 | vander_corput(1, self.samples_per_pixel as usize, samples, &mut self.rng); 99 | } 100 | 101 | 102 | 103 | for samples in self.samples_2d.iter_mut().skip(1) { 104 | sobol_2d(1, self.samples_per_pixel as usize, samples, &mut self.rng); 105 | } 106 | 107 | 108 | for i in 0..self.samples_1d_array_sizes.len() { 109 | let count = self.samples_1d_array_sizes[i]; 110 | let samples = self.sample_array_1d[i].as_mut_slice(); 111 | vander_corput(count, self.samples_per_pixel as usize, samples, &mut self.rng); 112 | } 113 | for i in 0..self.samples_2d_array_sizes.len() { 114 | let count = self.samples_2d_array_sizes[i]; 115 | let samples = self.sample_array_2d[i].as_mut_slice(); 116 | sobol_2d(count, self.samples_per_pixel as usize, samples, &mut self.rng); 117 | } 118 | 119 | start_pixel_default!(self, *p); 120 | } 121 | 122 | pixel_get_1d!(); 123 | 124 | pixel_get_2d!(); 125 | 126 | get_camera_sample_default!(); 127 | 128 | request_1d_array_default!(); 129 | 130 | request_2d_array_default!(); 131 | 132 | fn round_count(&self, n: usize) -> usize { 133 | round_up_pow2_64(n as i64) as usize 134 | } 135 | 136 | get_1d_array_default!(); 137 | 138 | get_2d_array_default!(); 139 | 140 | pixel_start_next_sample!(); 141 | 142 | pixel_set_sample_number!(); 143 | 144 | current_sample_number_default!(); 145 | 146 | fn clone(&self, seed: isize) -> Samplers { 147 | let mut mmds = Clone::clone(self); 148 | mmds.rng.set_sequence(seed as u64); 149 | 150 | mmds.into() 151 | } 152 | } 153 | 154 | pub fn create_maxmin_dist_sampler(params: &ParamSet, opts: &Options) -> Option> { 155 | let mut nsamp = params.find_one_int("pixelsamplers", 16); 156 | let sd = params.find_one_int("dimensions", 4); 157 | if opts.quick_render { nsamp = 1; } 158 | 159 | Some(Box::new(MaxMinDistSampler::new(nsamp as u64, sd as usize).into())) 160 | } -------------------------------------------------------------------------------- /src/samplers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod stratified; 2 | pub mod halton; 3 | pub mod sobol; 4 | pub mod zerotwosequence; 5 | pub mod maxmin; 6 | pub mod random; -------------------------------------------------------------------------------- /src/samplers/random.rs: -------------------------------------------------------------------------------- 1 | use crate::core::rng::RNG; 2 | use crate::core::geometry::point::{Point2i, Point2f}; 3 | use crate::core::pbrt::Float; 4 | use crate::*; 5 | use crate::core::sampler::{Sampler, Samplers}; 6 | use crate::core::camera::CameraSample; 7 | use crate::core::paramset::ParamSet; 8 | 9 | #[derive(Default, Clone)] 10 | pub struct RandomSampler { 11 | rng : RNG, 12 | // Sampler data 13 | current_pixel : Point2i, 14 | current_pixel_sample_index : u64, 15 | samples_1d_array_sizes : Vec, 16 | samples_2d_array_sizes : Vec, 17 | sample_array_1d : Vec>, 18 | sample_array_2d : Vec>, 19 | array_1d_offset : usize, 20 | array_2d_offset : usize, 21 | samples_per_pixel : u64, 22 | } 23 | 24 | impl RandomSampler { 25 | pub fn new(ns: u64, seed: u64) -> Self { 26 | let mut rs = RandomSampler { 27 | rng: RNG::new(seed), 28 | ..Default::default() 29 | }; 30 | 31 | sampler_new!(ns, rs); 32 | 33 | rs 34 | } 35 | } 36 | 37 | impl Sampler for RandomSampler { 38 | get_sampler_data!(); 39 | 40 | fn start_pixel(&mut self, p: &Point2i) { 41 | for i in 0..self.sample_array_1d.len() { 42 | for j in 0..self.sample_array_1d[i].len() { 43 | self.sample_array_1d[i][j] = self.rng.uniform_float(); 44 | } 45 | } 46 | 47 | for i in 0..self.sample_array_2d.len() { 48 | for j in 0..self.sample_array_2d[i].len() { 49 | let x = self.rng.uniform_float(); 50 | let y = self.rng.uniform_float(); 51 | self.sample_array_2d[i][j] = Point2f::new(x, y) 52 | } 53 | } 54 | 55 | start_pixel_default!(self, *p); 56 | } 57 | 58 | fn get_1d(&mut self) -> f32 { 59 | // TODO: ProfilePhase 60 | assert!(self.current_pixel_sample_index < self.samples_per_pixel); 61 | 62 | self.rng.uniform_float() 63 | } 64 | 65 | fn get_2d(&mut self) -> Point2f { 66 | // TODO: ProfilePhase 67 | assert!(self.current_pixel_sample_index < self.samples_per_pixel); 68 | let x = self.rng.uniform_float(); 69 | let y = self.rng.uniform_float(); 70 | 71 | Point2f::new(x, y) 72 | } 73 | 74 | get_camera_sample_default!(); 75 | 76 | request_1d_array_default!(); 77 | 78 | request_2d_array_default!(); 79 | 80 | get_1d_array_default!(); 81 | 82 | get_2d_array_default!(); 83 | 84 | fn start_next_sample(&mut self) -> bool { 85 | start_next_sample_default!(self) 86 | } 87 | 88 | fn set_sample_number(&mut self, sample_num: u64) -> bool { 89 | set_sample_number_default!(self, sample_num) 90 | } 91 | 92 | current_sample_number_default!(); 93 | 94 | fn clone(&self, seed: isize) -> Samplers { 95 | let mut rs = Clone::clone(self); 96 | rs.rng.set_sequence(seed as u64); 97 | 98 | rs.into() 99 | } 100 | } 101 | 102 | pub fn create_random_sampler(params: &ParamSet) -> Option> { 103 | let ns = params.find_one_int("pixelsamples", 4) as u64; 104 | 105 | Some(Box::new(RandomSampler::new(ns, 0).into())) 106 | } -------------------------------------------------------------------------------- /src/samplers/sobol.rs: -------------------------------------------------------------------------------- 1 | use crate::core::geometry::point::{Point2i, Point2f}; 2 | use crate::core::pbrt::{Float, is_power_of2, clamp, Options, round_up_pow2_32, log2_int}; 3 | use crate::core::geometry::bounds::Bounds2i; 4 | use log::warn; 5 | use crate::*; 6 | use crate::core::sampler::{Sampler, Samplers, GlobalSampler}; 7 | use crate::core::camera::CameraSample; 8 | use crate::core::lowdiscrepancy::{sobol_interval_to_index, sobol_sample}; 9 | use crate::core::sobolmatrices::NUM_SOBOL_DIMENSIONS; 10 | use crate::core::rng::ONE_MINUS_EPSILON; 11 | use crate::core::paramset::ParamSet; 12 | 13 | #[derive(Default, Clone)] 14 | pub struct SobolSampler { 15 | sample_bounds : Bounds2i, 16 | resolution : i32, 17 | log2_resolution : i32, 18 | // Sampler data 19 | current_pixel : Point2i, 20 | current_pixel_sample_index : u64, 21 | samples_1d_array_sizes : Vec, 22 | samples_2d_array_sizes : Vec, 23 | sample_array_1d : Vec>, 24 | sample_array_2d : Vec>, 25 | array_1d_offset : usize, 26 | array_2d_offset : usize, 27 | samples_per_pixel : u64, 28 | // GlobalSampler data 29 | dimension : usize, 30 | interval_sample_index : u64, 31 | array_end_dim : usize, 32 | } 33 | 34 | impl SobolSampler { 35 | pub fn new(samples_per_pixel: u64, sbounds: &Bounds2i) -> Self { 36 | if !is_power_of2(samples_per_pixel) { 37 | warn!( 38 | "Non power-of-two sample count rounded up to {} \ 39 | for SobolSampler.", samples_per_pixel); 40 | } 41 | 42 | let diag = sbounds.diagonal(); 43 | let resolution = round_up_pow2_32(std::cmp::max(diag.x as i32, diag.y as i32)); 44 | let log2_resolution = log2_int(resolution); 45 | if resolution > 0 { assert_eq!(1 << log2_resolution, resolution); } 46 | 47 | let mut ss = SobolSampler { 48 | resolution, 49 | log2_resolution, 50 | sample_bounds: *sbounds, 51 | ..Default::default() 52 | }; 53 | 54 | sampler_new!(samples_per_pixel, ss); 55 | 56 | ss 57 | } 58 | } 59 | 60 | impl GlobalSampler for SobolSampler { 61 | fn get_index_for_sample(&self, sample_num: u64) -> u64 { 62 | sobol_interval_to_index( 63 | self.log2_resolution as u32, 64 | sample_num, 65 | &Point2i::from(self.current_pixel - self.sample_bounds.p_min)) 66 | } 67 | 68 | fn sample_dimension(&self, index: u64, dim: usize) -> Float { 69 | if dim >= NUM_SOBOL_DIMENSIONS { 70 | panic!( 71 | "SobolSampler can only sample up to {} \ 72 | dimensions! Exiting.", NUM_SOBOL_DIMENSIONS); 73 | } 74 | 75 | let mut s = sobol_sample(index, dim, 0); 76 | 77 | // Remap sobol' dimensions used for pixel samples 78 | if dim == 0 || dim == 1 { 79 | s = s * self.resolution as Float + self.sample_bounds.p_min[dim] as Float; 80 | s = clamp(s - self.current_pixel[dim] as Float, 0.0, ONE_MINUS_EPSILON) 81 | } 82 | 83 | 84 | 85 | s 86 | } 87 | } 88 | 89 | impl Sampler for SobolSampler { 90 | get_sampler_data!(); 91 | 92 | global_start_pixel!(); 93 | 94 | global_get_1d!(); 95 | 96 | global_get_2d!(); 97 | 98 | get_camera_sample_default!(); 99 | 100 | request_1d_array_default!(); 101 | 102 | request_2d_array_default!(); 103 | 104 | get_1d_array_default!(); 105 | 106 | get_2d_array_default!(); 107 | 108 | global_start_next_sample!(); 109 | 110 | global_set_sample_number!(); 111 | 112 | current_sample_number_default!(); 113 | 114 | fn clone(&self, _seed: isize) -> Samplers { 115 | Clone::clone(self).into() 116 | } 117 | } 118 | 119 | pub fn create_sobol_sampler(params: &ParamSet, sbounds: &Bounds2i, opts: &Options) -> Option> { 120 | let mut nsamp = params.find_one_int("pixelsamples", 16); 121 | if opts.quick_render { nsamp = 1; } 122 | 123 | Some(Box::new(SobolSampler::new(nsamp as u64, sbounds).into())) 124 | } -------------------------------------------------------------------------------- /src/samplers/stratified.rs: -------------------------------------------------------------------------------- 1 | use crate::core::sampler::*; 2 | use crate::*; 3 | use crate::core::sampling::{stratified_sample_1d, stratified_sample_2d, shuffle, latin_hypercube}; 4 | use crate::core::geometry::point::{Point2i, Point2f}; 5 | use crate::core::pbrt::Float; 6 | use crate::core::rng::RNG; 7 | use crate::core::camera::CameraSample; 8 | use crate::core::paramset::ParamSet; 9 | 10 | #[derive(Debug, Default, Clone)] 11 | pub struct StratifiedSampler { 12 | xpixel_samples : usize, 13 | ypixel_samples : usize, 14 | jitter_samples : bool, 15 | // Sampler data 16 | current_pixel : Point2i, 17 | current_pixel_sample_index : u64, 18 | samples_1d_array_sizes : Vec, 19 | samples_2d_array_sizes : Vec, 20 | sample_array_1d : Vec>, 21 | sample_array_2d : Vec>, 22 | array_1d_offset : usize, 23 | array_2d_offset : usize, 24 | samples_per_pixel : u64, 25 | // PixelSampler data 26 | samples_1d : Vec>, 27 | samples_2d : Vec>, 28 | current_1d_dimension : usize, 29 | current_2d_dimension : usize, 30 | rng : RNG 31 | } 32 | 33 | impl StratifiedSampler { 34 | fn new( 35 | xpixel_samples: usize, ypixel_samples: usize, 36 | jitter_samples: bool, n_sampled_dimensions: usize) -> Self { 37 | let mut ss = Self { 38 | xpixel_samples, 39 | ypixel_samples, 40 | jitter_samples, 41 | ..Default::default() 42 | }; 43 | 44 | pixel_sampler_new!((xpixel_samples * ypixel_samples) as u64, n_sampled_dimensions, ss); 45 | 46 | ss 47 | } 48 | } 49 | 50 | impl Sampler for StratifiedSampler { 51 | get_sampler_data!(); 52 | 53 | fn start_pixel(&mut self, p: &Point2i) { 54 | // Generate single statified samples for the pixel 55 | let count = (self.xpixel_samples * self.ypixel_samples) as usize; 56 | 57 | for i in 0..self.samples_1d.len() { 58 | let samples = &mut self.samples_1d[i][0..]; 59 | 60 | stratified_sample_1d(samples, count, &mut self.rng, self.jitter_samples); 61 | shuffle(samples, count, 1, &mut self.rng); 62 | } 63 | 64 | for i in 0..self.samples_2d.len() { 65 | let samples = &mut self.samples_2d[i][0..]; 66 | stratified_sample_2d(samples, self.xpixel_samples as usize, self.ypixel_samples as usize, &mut self.rng, self.jitter_samples); 67 | shuffle(samples, count, 1, &mut self.rng); 68 | } 69 | 70 | // Generate arrays of stratified samples for the pixel 71 | for i in 0..self.samples_1d_array_sizes.len() { 72 | for j in 0..self.samples_per_pixel { 73 | let count = self.samples_1d_array_sizes[i] as usize; 74 | let samples = &mut self.sample_array_1d[i][(j as usize * count as usize)..]; 75 | stratified_sample_1d(samples, count, &mut self.rng, self.jitter_samples); 76 | shuffle(samples, count , 1, &mut self.rng) 77 | } 78 | } 79 | 80 | for i in 0..self.samples_2d_array_sizes.len() { 81 | for j in 0..self.samples_per_pixel { 82 | let count = self.samples_2d_array_sizes[i]; 83 | let samples = &mut self.sample_array_2d[i][(j as usize * count as usize)..]; 84 | latin_hypercube(samples, count as usize, 2, &mut self.rng) 85 | } 86 | } 87 | 88 | start_pixel_default!(self, *p); 89 | } 90 | 91 | pixel_get_1d!(); 92 | 93 | pixel_get_2d!(); 94 | 95 | get_camera_sample_default!(); 96 | 97 | request_1d_array_default!(); 98 | 99 | request_2d_array_default!(); 100 | 101 | get_1d_array_default!(); 102 | 103 | get_2d_array_default!(); 104 | 105 | pixel_start_next_sample!(); 106 | 107 | pixel_set_sample_number!(); 108 | 109 | current_sample_number_default!(); 110 | 111 | fn clone(&self, seed: isize) -> Samplers { 112 | let mut ss = Clone::clone(self); 113 | ss.rng.set_sequence(seed as u64); 114 | 115 | ss.into() 116 | } 117 | } 118 | 119 | pub fn create_stratified_sampler(params: &ParamSet, quick_render: bool) -> Option> { 120 | let jitter = params.find_one_bool("jitter", true); 121 | let mut xsamp = params.find_one_int("xsample", 4); 122 | let mut ysamp = params.find_one_int("ysamples", 4); 123 | let sd = params.find_one_int("dimensions", 4); 124 | 125 | if quick_render { 126 | xsamp = 1; 127 | ysamp = 1; 128 | } 129 | 130 | Some(Box::new(StratifiedSampler::new( 131 | xsamp as usize, ysamp as usize, 132 | jitter, sd as usize).into())) 133 | } -------------------------------------------------------------------------------- /src/samplers/zerotwosequence.rs: -------------------------------------------------------------------------------- 1 | use crate::core::geometry::point::{Point2i, Point2f}; 2 | use crate::core::pbrt::{Float, round_up_pow2_64, is_power_of2, Options}; 3 | use crate::core::rng::RNG; 4 | use log::warn; 5 | use crate::*; 6 | use crate::pixel_sampler_new; 7 | use crate::core::sampler::{Sampler, Samplers}; 8 | use crate::core::camera::CameraSample; 9 | use crate::core::lowdiscrepancy::{vander_corput, sobol_2d}; 10 | use crate::core::paramset::ParamSet; 11 | 12 | #[derive(Default, Clone)] 13 | pub struct ZeroTwoSequenceSampler { 14 | // Sampler data 15 | current_pixel : Point2i, 16 | current_pixel_sample_index : u64, 17 | samples_1d_array_sizes : Vec, 18 | samples_2d_array_sizes : Vec, 19 | sample_array_1d : Vec>, 20 | sample_array_2d : Vec>, 21 | array_1d_offset : usize, 22 | array_2d_offset : usize, 23 | samples_per_pixel : u64, 24 | // PixelSampler data 25 | samples_1d : Vec>, 26 | samples_2d : Vec>, 27 | current_1d_dimension : usize, 28 | current_2d_dimension : usize, 29 | rng : RNG 30 | } 31 | 32 | impl ZeroTwoSequenceSampler { 33 | pub fn new(samples_per_pixel: u64, nsampled_dimensions: usize) -> Self { 34 | let mut zts = Self::default(); 35 | let spp = round_up_pow2_64(samples_per_pixel as i64); 36 | pixel_sampler_new!(spp as u64, nsampled_dimensions, zts); 37 | 38 | if !is_power_of2(samples_per_pixel) { 39 | warn!( 40 | "Pixel samples being rounded up to power of 2 \ 41 | (from {} to {}).", samples_per_pixel, spp); 42 | } 43 | 44 | zts 45 | } 46 | } 47 | 48 | impl Sampler for ZeroTwoSequenceSampler { 49 | get_sampler_data!(); 50 | 51 | fn start_pixel(&mut self, p: &Point2i) { 52 | // TODO: ProfilePhase 53 | // Generate 1D and 2D pixel sample components using (0, 2)-sequence 54 | for samples in &mut self.samples_1d { 55 | vander_corput(1, self.samples_per_pixel as usize, samples, &mut self.rng); 56 | } 57 | for samples in &mut self.samples_2d { 58 | sobol_2d(1, self.samples_per_pixel as usize, samples, &mut self.rng); 59 | } 60 | 61 | // Generate 1D and 2D array samples using (0, 2)-sequence 62 | for i in 0..self.samples_1d_array_sizes.len() { 63 | let samples = self.sample_array_1d[i].as_mut_slice(); 64 | let nspps = self.samples_1d_array_sizes[i]; 65 | vander_corput(nspps, self.samples_per_pixel as usize, samples, &mut self.rng); 66 | } 67 | for i in 0..self.samples_2d_array_sizes.len() { 68 | let samples = self.sample_array_2d[i].as_mut_slice(); 69 | let nspps = self.samples_2d_array_sizes[i]; 70 | sobol_2d(nspps, self.samples_per_pixel as usize, samples, &mut self.rng); 71 | } 72 | 73 | start_pixel_default!(self, *p); 74 | } 75 | 76 | pixel_get_1d!(); 77 | 78 | pixel_get_2d!(); 79 | 80 | get_camera_sample_default!(); 81 | 82 | request_1d_array_default!(); 83 | 84 | request_2d_array_default!(); 85 | 86 | fn round_count(&self, n: usize) -> usize { 87 | round_up_pow2_64(n as i64) as usize 88 | } 89 | 90 | get_1d_array_default!(); 91 | 92 | get_2d_array_default!(); 93 | 94 | pixel_start_next_sample!(); 95 | 96 | pixel_set_sample_number!(); 97 | 98 | current_sample_number_default!(); 99 | 100 | fn clone(&self, seed: isize) -> Samplers { 101 | let mut lds = Clone::clone(self); 102 | lds.rng.set_sequence(seed as u64); 103 | 104 | lds.into() 105 | } 106 | } 107 | 108 | pub fn create_zerotwo_sequence_sampler(params: &ParamSet, opts: &Options) -> Option> { 109 | let mut nsamp = params.find_one_int("pixelsamples", 16); 110 | let sd = params.find_one_int("dimensions", 4); 111 | 112 | if opts.quick_render { nsamp = 1; } 113 | 114 | Some(Box::new(ZeroTwoSequenceSampler::new(nsamp as u64, sd as usize).into())) 115 | } -------------------------------------------------------------------------------- /src/scenes/caustic-glass.pbrt: -------------------------------------------------------------------------------- 1 | 2 | #LookAt -5 9 -5 -4.75 2.75 0 0 1 0 3 | LookAt -5.5 7 -5.5 -4.75 2.25 0 0 1 0 4 | 5 | Camera "perspective" "float fov" [30] 6 | 7 | Film "image" 8 | "integer xresolution" [700] "integer yresolution" [1000] 9 | "float scale" 1.5 10 | "string filename" "f16-9c.exr" 11 | 12 | Integrator "sppm" "integer numiterations" [10000] "float radius" .075 13 | # "integer imagewritefrequency" [1] 14 | #Integrator "bdpt" "integer maxdepth" 10 15 | #Integrator "mlt" "integer maxdepth" 16 16 | # "integer mutationsperpixel" [4096] 17 | # "integer mutationsperpixel" [32768] 18 | # "float largestepprobability" [0.05] 19 | 20 | WorldBegin 21 | 22 | LightSource "spot" "point from" [0 5 9] 23 | "point to" [-5 2.75 0] "blackbody I" [5500 125] 24 | 25 | AttributeBegin 26 | LightSource "infinite" "color L" [.1 .1 .1] 27 | AttributeEnd 28 | 29 | AttributeBegin 30 | Material "glass" 31 | "float index" [ 1.2500000000 ] 32 | Shape "plymesh" "string filename" "geometry/mesh_00001.ply" 33 | AttributeEnd 34 | 35 | AttributeBegin 36 | Material "uber" 37 | "float roughness" [ 0.0104080001 ] 38 | "float index" [ 1 ] 39 | "rgb Kd" [ 0.6399999857 0.6399999857 0.6399999857 ] 40 | "rgb Ks" [ 0.1000000015 0.1000000015 0.1000000015 ] 41 | "rgb Kt" [ 0 0 0 ] 42 | "rgb opacity" [ 1 1 1 ] 43 | Shape "plymesh" "string filename" "geometry/mesh_00002.ply" 44 | AttributeEnd 45 | 46 | WorldEnd 47 | -------------------------------------------------------------------------------- /src/scenes/geometry/mesh_00001.ply: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/src/scenes/geometry/mesh_00001.ply -------------------------------------------------------------------------------- /src/scenes/geometry/mesh_00002.ply: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/src/scenes/geometry/mesh_00002.ply -------------------------------------------------------------------------------- /src/scenes/spheres-differentials-texfilt.pbrt: -------------------------------------------------------------------------------- 1 | LookAt 2 2 5 0 -.4 0 0 1 0 2 | Camera "perspective" "float fov" [30 ] 3 | 4 | Film "image" "integer xresolution" [1000 ] "integer yresolution" [500 ] 5 | "string filename" "spheres-differentials-texfilt.exr" 6 | 7 | Integrator "directlighting" "integer maxdepth" [10] 8 | 9 | Sampler "lowdiscrepancy" "integer pixelsamples" [1] 10 | PixelFilter "box" 11 | 12 | WorldBegin 13 | LightSource "distant" "point from" [0 10 0 ] "point to" [0 0 0 ] 14 | "color L" [3.141593 3.141593 3.141593 ] 15 | 16 | AttributeBegin 17 | Translate .25 0 0 18 | Texture "checker" "color" "checkerboard" 19 | "string mapping" "planar" 20 | "vector v1" [ 1 0 0 ] "vector v2" [ 0 0 1] 21 | "string aamode" ["none"] 22 | Texture "lines-tex" "color" "imagemap" "string filename" "textures/lines.png" 23 | "float uscale" [100] "float vscale" [100] 24 | 25 | Material "matte" "texture Kd" "lines-tex" 26 | Shape "trianglemesh" "integer indices" [0 2 1 0 3 2 ] 27 | "point P" [-100 -1 -100 400 -1 -100 400 -1 400 -100 -1 400 ] 28 | "float st" [ 0 0 1 0 0 1 1 1] 29 | AttributeEnd 30 | 31 | Translate -1.3 0 0 32 | Material "mirror" 33 | Shape "sphere" 34 | 35 | Translate 2.6 0 0 36 | Material "glass" 37 | Shape "sphere" 38 | WorldEnd -------------------------------------------------------------------------------- /src/scenes/sss-dragon.pbrt: -------------------------------------------------------------------------------- 1 | Integrator "path" "integer maxdepth" [5] 2 | 3 | Sampler "halton" "integer pixelsamples" [8192] 4 | 5 | PixelFilter "gaussian" 6 | 7 | Film "image" "integer xresolution" [1366] "integer yresolution" [1024] 8 | "float scale" 2 9 | 10 | Scale -1 1 1 11 | LookAt 3.69558 -3.46243 3.25463 3.04072 -2.85176 2.80939 -0.317366 0.312466 0.895346 12 | Camera "perspective" "float fov" [28.8415038750464] 13 | 14 | WorldBegin 15 | AttributeBegin 16 | Rotate -120 0 0 1 17 | LightSource "infinite" "string mapname" ["textures/envmap.hdr"] 18 | AttributeEnd 19 | 20 | AttributeBegin 21 | Translate 0.2 0.3 0.78 22 | Rotate 90.0 1.0 0.0 0.0 23 | Rotate -90.0 0.0 1.0 0.0 24 | Scale 0.02 0.02 0.02 25 | Material "subsurface" 26 | "float eta" [1.5] 27 | "string name" ["Skin1"] 28 | "float scale" [20] 29 | Shape "plymesh" "string filename" ["geometry/dragon.ply"] 30 | AttributeEnd 31 | 32 | Texture "checkers" "color" "checkerboard" "color tex1" [0.4 0.4 0.4] "color tex2" [0.2 0.2 0.2] "float vscale" [16.0] "float uscale" [16.0] 33 | 34 | AttributeBegin 35 | Material "matte" "texture Kd" "checkers" 36 | Shape "plymesh" "string filename" ["geometry/meshes_0.ply"] 37 | AttributeEnd 38 | WorldEnd 39 | -------------------------------------------------------------------------------- /src/scenes/textures/envmap.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/src/scenes/textures/envmap.exr -------------------------------------------------------------------------------- /src/scenes/textures/envmap.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/src/scenes/textures/envmap.hdr -------------------------------------------------------------------------------- /src/scenes/textures/sky.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmeli100/pbrt-rust/ece163970df708a2f9ab6dbabc8cd051215bd00a/src/scenes/textures/sky.hdr -------------------------------------------------------------------------------- /src/shapes/heightfield.rs: -------------------------------------------------------------------------------- 1 | use crate::core::transform::Transform; 2 | use crate::core::paramset::ParamSet; 3 | use crate::core::shape::Shapes; 4 | use std::sync::Arc; 5 | use crate::core::geometry::point::{Point3f, Point2f}; 6 | use crate::core::pbrt::Float; 7 | use crate::shapes::triangle::create_trianglemesh; 8 | 9 | pub fn create_heightfield( 10 | o2w: Arc, w2o: Arc, 11 | reverse_orientation: bool, 12 | params: &ParamSet) -> Vec> { 13 | let nx = params.find_one_int("nu", -1); 14 | let ny = params.find_one_int("nv", -1); 15 | let mut nitems = 0; 16 | let z = params.find_float("Pz", &mut nitems); 17 | assert_eq!(nitems as isize, nx * ny); 18 | assert!(nx != -1 && ny != -1 && z.is_some()); 19 | 20 | let nverts = (nx * ny) as usize; 21 | let ntris = 2 * (nx - 1) as usize * (ny - 1) as usize; 22 | let mut indices = vec![0; 3 * ntris]; 23 | let mut P = vec![Point3f::default(); nverts]; 24 | let mut uvs = vec![Point2f::default(); nverts]; 25 | let mut pos = 0; 26 | let z = z.unwrap(); 27 | 28 | // Compute heightfield vertex positions 29 | for y in 0..ny as usize { 30 | for x in 0..nx as usize { 31 | let xval = x as Float / (nx - 1) as Float; 32 | let yval = y as Float / (ny - 1) as Float; 33 | P[pos].x = xval; 34 | uvs[pos].x = xval; 35 | P[pos].y = yval; 36 | uvs[pos].y = yval; 37 | P[pos].z = z[pos]; 38 | pos += 1; 39 | } 40 | } 41 | 42 | // Fill in heightfield vextex offset array 43 | let mut p = 0; 44 | macro_rules! vert { 45 | ($x:expr, $y:expr) => {{ 46 | $x + $y * nx as usize 47 | }} 48 | } 49 | 50 | for y in 0..(ny - 1) as usize { 51 | for x in 0..(nx - 1) as usize { 52 | indices[p] = vert!(x, y); 53 | p += 1; 54 | indices[p] = vert!(x + 1, y); 55 | p += 1; 56 | indices[p] = vert!(x + 1, y + 1); 57 | p += 1; 58 | 59 | indices[p] = vert!(x, y); 60 | p += 1; 61 | indices[p] = vert!(x + 1, y + 1); 62 | p += 1; 63 | indices[p] = vert!(x, y + 1); 64 | p += 1; 65 | } 66 | } 67 | 68 | create_trianglemesh( 69 | o2w, w2o, reverse_orientation, ntris, 70 | indices, nverts, P, Vec::new(), Vec::new(), 71 | uvs, None, None) 72 | 73 | } -------------------------------------------------------------------------------- /src/shapes/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod sphere; 2 | pub mod cylinder; 3 | pub mod disk; 4 | pub mod triangle; 5 | pub mod plymesh; 6 | pub mod nurbs; 7 | pub mod curve; 8 | pub mod loopsubdiv; 9 | pub mod heightfield; 10 | pub mod hyperboloid; 11 | pub mod paraboloid; 12 | pub mod cone; 13 | 14 | pub fn init_stats() { 15 | curve::init_stats(); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/textures/biler.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureMapping2Ds, Texture, TextureMapping2D, TextureFloat, get_mapping2d, TextureSpec}; 2 | use crate::core::interaction::SurfaceInteraction; 3 | use crate::core::geometry::vector::{Vector2f}; 4 | use static_assertions::_core::ops::{Mul, Add}; 5 | use crate::core::pbrt::Float; 6 | use crate::core::paramset::TextureParams; 7 | use crate::core::transform::Transform; 8 | use crate::core::spectrum::Spectrum; 9 | use std::sync::Arc; 10 | 11 | pub struct BilerTexture { 12 | v00 : T, 13 | v01 : T, 14 | v10 : T, 15 | v11 : T, 16 | mapping : TextureMapping2Ds, 17 | } 18 | 19 | impl BilerTexture { 20 | pub fn new(mapping: TextureMapping2Ds, v00: T, v01: T, v10: T, v11: T) -> Self { 21 | Self { v00, v01, v10, v11, mapping } 22 | } 23 | } 24 | 25 | impl Texture for BilerTexture 26 | where T: Add + Mul + Copy 27 | { 28 | fn evaluate(&self, s: &SurfaceInteraction) -> T { 29 | let mut dstdx = Vector2f::default(); 30 | let mut dstdy = Vector2f::default(); 31 | let st = self.mapping.map(s, &mut dstdx, &mut dstdy); 32 | 33 | self.v00 * (1.0 - st[1]) * (1.0 - st[0]) + self.v01 * (1.0 - st[0]) * (st[1]) + 34 | self.v10 * (1.0 - st[1]) * (st[0]) + self.v11 * (st[1]) * (st[0]) 35 | } 36 | } 37 | 38 | pub fn create_biler_float(t2w: &Transform, tp: &mut TextureParams) -> Option> { 39 | let map = get_mapping2d(t2w, tp); 40 | let v00 = tp.find_float("v00", 0.0); 41 | let v01 = tp.find_float("v01", 1.0); 42 | let v10 = tp.find_float("v10", 0.0); 43 | let v11 = tp.find_float("v11", 1.0); 44 | 45 | Some(Arc::new(BilerTexture::new(map, v00, v01, v10, v11).into())) 46 | } 47 | 48 | pub fn create_biler_spectrum(t2w: &Transform, tp: &mut TextureParams) -> Option> { 49 | let map = get_mapping2d(t2w, tp); 50 | let v00 = tp.find_spectrum("v00", Spectrum::new(0.0)); 51 | let v01 = tp.find_spectrum("v01", Spectrum::new(1.0)); 52 | let v10 = tp.find_spectrum("v10", Spectrum::new(0.0)); 53 | let v11 = tp.find_spectrum("v11", Spectrum::new(1.0)); 54 | 55 | Some(Arc::new(BilerTexture::new(map, v00, v01, v10, v11).into())) 56 | } -------------------------------------------------------------------------------- /src/textures/constant.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{Texture, TextureFloat, TextureSpec}; 2 | use crate::core::interaction::SurfaceInteraction; 3 | use crate::core::transform::Transform; 4 | use crate::core::paramset::TextureParams; 5 | use crate::core::spectrum::Spectrum; 6 | use bumpalo::core_alloc::sync::Arc; 7 | 8 | pub struct ConstantTexture { 9 | value: T 10 | } 11 | 12 | impl ConstantTexture { 13 | pub fn new(value: T) -> Self { 14 | Self { value } 15 | } 16 | } 17 | 18 | impl Texture for ConstantTexture { 19 | fn evaluate(&self, _s: &SurfaceInteraction) -> T { 20 | self.value 21 | } 22 | } 23 | 24 | pub fn create_constant_float(_t2w: &Transform, tp: &mut TextureParams) -> Option> { 25 | let value = tp.find_float("value", 1.0); 26 | 27 | Some(Arc::new(ConstantTexture::new(value).into())) 28 | } 29 | 30 | pub fn create_constant_spectrum(_t2w: &Transform, tp: &mut TextureParams) -> Option> { 31 | let value = tp.find_spectrum("value", Spectrum::new(1.0)); 32 | 33 | Some(Arc::new(ConstantTexture::new(value).into())) 34 | } -------------------------------------------------------------------------------- /src/textures/dots.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use crate::core::texture::{Textures, Texture, SpectrumT, TextureMapping2Ds, TextureMapping2D, noise, TextureFloat, get_mapping2d, TextureSpec}; 3 | use crate::core::interaction::SurfaceInteraction; 4 | use crate::core::pbrt::Float; 5 | use crate::core::geometry::vector::Vector2f; 6 | use crate::core::geometry::point::Point2f; 7 | use crate::core::paramset::TextureParams; 8 | use crate::core::transform::Transform; 9 | use crate::core::spectrum::Spectrum; 10 | 11 | pub struct DotsTexture> { 12 | mapping : TextureMapping2Ds, 13 | outside : Arc>, 14 | inside : Arc>, 15 | } 16 | 17 | impl> DotsTexture { 18 | pub fn new( 19 | mapping: TextureMapping2Ds, 20 | outside: Arc>, 21 | inside: Arc>) -> Self { 22 | Self { mapping, outside, inside } 23 | } 24 | } 25 | 26 | impl> Texture for DotsTexture 27 | { 28 | fn evaluate(&self, s: &SurfaceInteraction) -> T { 29 | let mut dstdx = Vector2f::default(); 30 | let mut dstdy = Vector2f::default(); 31 | let st = self.mapping.map(s, &mut dstdx, &mut dstdy); 32 | let (scell, tcell) = ( 33 | (st.x + 0.5).floor() as usize, 34 | (st.y + 0.5).floor() as usize); 35 | 36 | // Return insideDot result if point is inside dot 37 | if noise(scell as Float + 0.5, tcell as Float + 0.5, 0.5) > 0.0 { 38 | let radius = 0.35; 39 | let max_shift = 0.5 - radius; 40 | let scenter = 41 | scell as Float + max_shift * 42 | noise(scell as Float + 1.5, tcell as Float + 2.8, 0.5); 43 | let tcenter = 44 | tcell as Float + max_shift * 45 | noise(scell as Float + 4.5, tcell as Float + 9.8, 0.5); 46 | let dst = st - Point2f::new(scenter, tcenter); 47 | 48 | if dst.length_squared() < radius * radius { 49 | return self.inside.evaluate(s); 50 | } 51 | } 52 | 53 | self.outside.evaluate(s) 54 | } 55 | } 56 | 57 | pub fn create_dots_float(t2w: &Transform, tp: &mut TextureParams) -> Option> { 58 | let map = get_mapping2d(t2w, tp); 59 | let inside = tp.get_floattexture("inside", 1.0); 60 | let outside = tp.get_floattexture("outside", 0.0); 61 | 62 | Some(Arc::new(DotsTexture::new(map, inside, outside).into())) 63 | } 64 | 65 | pub fn create_dots_spectrum(t2w: &Transform, tp: &mut TextureParams) -> Option> { 66 | let map = get_mapping2d(t2w, tp); 67 | let inside = tp.get_spectrumtexture("inside", Spectrum::new(1.0)); 68 | let outside = tp.get_spectrumtexture("outside", Spectrum::new(0.0)); 69 | 70 | Some(Arc::new(DotsTexture::new(map, inside, outside).into())) 71 | } -------------------------------------------------------------------------------- /src/textures/fbm.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureMapping3Ds, Texture, fbm, TextureMapping3D, TextureFloat, IdentityMapping3D, TextureSpec}; 2 | use crate::core::pbrt::Float; 3 | use crate::core::interaction::SurfaceInteraction; 4 | use crate::core::geometry::vector::Vector3f; 5 | use crate::core::transform::Transform; 6 | use crate::core::paramset::TextureParams; 7 | use std::sync::Arc; 8 | 9 | pub struct FBmTexture { 10 | omega : Float, 11 | octaves : usize, 12 | mapping : TextureMapping3Ds, 13 | } 14 | 15 | impl FBmTexture { 16 | pub fn new(mapping: TextureMapping3Ds, octaves: usize, omega: Float) -> Self { 17 | Self { mapping, octaves, omega } 18 | } 19 | } 20 | 21 | impl> Texture for FBmTexture { 22 | fn evaluate(&self, s: &SurfaceInteraction) -> T { 23 | let mut dpdx = Vector3f::default(); 24 | let mut dpdy = Vector3f::default(); 25 | let p = self.mapping.map(s, &mut dpdx, &mut dpdy); 26 | 27 | T::from(fbm(&p, &dpdx, &dpdy, self.omega, self.octaves)) 28 | } 29 | } 30 | 31 | pub fn create_fbm_float(t2w: &Transform, tp: &mut TextureParams) -> Option> { 32 | let map: TextureMapping3Ds = IdentityMapping3D::new(t2w).into(); 33 | let octaves = tp.find_int("octaves", 8) as usize; 34 | let roughness = tp.find_float("roughness", 0.5); 35 | 36 | Some(Arc::new(FBmTexture::new(map, octaves, roughness).into())) 37 | } 38 | 39 | pub fn create_fbm_spectrum(t2w: &Transform, tp: &mut TextureParams) -> Option> { 40 | let map: TextureMapping3Ds = IdentityMapping3D::new(t2w).into(); 41 | let octaves = tp.find_int("octaves", 8) as usize; 42 | let roughness = tp.find_float("roughness", 0.5); 43 | 44 | Some(Arc::new(FBmTexture::new(map, octaves, roughness).into())) 45 | } -------------------------------------------------------------------------------- /src/textures/marble.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureMapping3Ds, Texture, TextureMapping3D, fbm, TextureSpec, IdentityMapping3D, TextureFloat}; 2 | use crate::core::pbrt::Float; 3 | use crate::core::spectrum::Spectrum; 4 | use crate::core::interaction::SurfaceInteraction; 5 | use crate::core::geometry::vector::Vector3f; 6 | use crate::core::transform::Transform; 7 | use crate::core::paramset::TextureParams; 8 | use std::sync::Arc; 9 | 10 | const C: [[Float; 3]; 9] = [ 11 | [0.58, 0.58, 0.6], [0.58, 0.58, 0.6], [0.58, 0.58, 0.6], 12 | [0.5, 0.5, 0.5], [0.6, 0.59, 0.58], [0.58, 0.58, 0.6], 13 | [0.58, 0.58, 0.6], [0.2, 0.2, 0.33], [0.58, 0.58, 0.6], 14 | ]; 15 | 16 | pub struct MarbleTexture { 17 | omega : Float, 18 | scale : Float, 19 | variation : Float, 20 | octaves : usize, 21 | mapping : TextureMapping3Ds, 22 | } 23 | 24 | impl MarbleTexture { 25 | pub fn new( 26 | omega: Float, scale: Float, variation: Float, 27 | octaves: usize, mapping: TextureMapping3Ds) -> Self { 28 | Self { 29 | omega, scale, variation, 30 | octaves, mapping 31 | } 32 | } 33 | } 34 | 35 | impl> Texture for MarbleTexture { 36 | fn evaluate(&self, s: &SurfaceInteraction) -> T { 37 | use crate::core::spectrum::SpectrumType::*; 38 | 39 | let mut dpdx = Vector3f::default(); 40 | let mut dpdy = Vector3f::default(); 41 | let mut p = self.mapping.map(s, &mut dpdx, &mut dpdy); 42 | 43 | p *= self.scale; 44 | let fbm = fbm( 45 | &p, &(dpdx * self.scale), &(dpdy * self.scale), 46 | self.omega, self.octaves); 47 | let marble = p.y + self.variation * fbm; 48 | let t = 0.5 + 0.5 * marble.sin(); 49 | 50 | // Evaluate marble spline at t 51 | let nseg = 6; 52 | let first = std::cmp::min(5, (t * nseg as Float).floor() as usize); 53 | let c0 = Spectrum::from_rgb(C[first], Reflectance); 54 | let c1 = Spectrum::from_rgb(C[first + 1], Reflectance); 55 | let c2 = Spectrum::from_rgb(C[first + 2], Reflectance); 56 | let c3 = Spectrum::from_rgb(C[first + 3], Reflectance); 57 | // Bezier spline evaluated with Castilejau's algorithm 58 | let mut s0 = c0 * (1.0 - t) + c1 * t; 59 | let mut s1 = c1 * (1.0 - t) + c2 * t; 60 | let s2 = c2 * (1.0 - t) + c3 * t; 61 | s0 = s0 * (1.0 - t) + s1 * t; 62 | s1 = s1 * (1.0 - t) + s2 * t; 63 | 64 | T::from((s0 * (1.0 - t) + s1 * t) * 1.5) 65 | } 66 | } 67 | 68 | pub fn create_marble_float(_t2w: &Transform, _tp: &mut TextureParams) -> Option> { 69 | None 70 | } 71 | 72 | pub fn create_marble_spectrum(t2w: &Transform, tp: &mut TextureParams) -> Option> { 73 | let map: TextureMapping3Ds = IdentityMapping3D::new(t2w).into(); 74 | let octaves = tp.find_int("octaves", 8) as usize; 75 | let omega = tp.find_float("roughness", 0.5); 76 | let scale = tp.find_float("scale", 1.0); 77 | let variation = tp.find_float("variation", 0.2); 78 | 79 | Some(Arc::new(MarbleTexture::new(omega, scale, variation, octaves, map).into())) 80 | 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/textures/mix.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use crate::core::texture::{Textures, TextureFloat, Texture, SpectrumT, TextureSpec}; 3 | use crate::core::interaction::SurfaceInteraction; 4 | use crate::core::paramset::TextureParams; 5 | use crate::core::transform::Transform; 6 | use crate::core::spectrum::Spectrum; 7 | 8 | 9 | pub struct MixTexture 10 | where T: SpectrumT 11 | { 12 | tex1 : Arc>, 13 | tex2 : Arc>, 14 | amount : Arc 15 | } 16 | 17 | impl MixTexture 18 | where T: SpectrumT 19 | { 20 | pub fn new( 21 | tex1: Arc>, tex2: Arc>, 22 | amount: Arc) -> Self { 23 | Self { tex1, tex2, amount } 24 | } 25 | } 26 | 27 | impl Texture for MixTexture 28 | where T: SpectrumT 29 | { 30 | fn evaluate(&self, s: &SurfaceInteraction) -> T { 31 | let t1 = self.tex1.evaluate(s); 32 | let t2 = self.tex2.evaluate(s); 33 | let amt = self.amount.evaluate(s); 34 | 35 | t1 * (1.0 - amt) + t2 * amt 36 | } 37 | } 38 | 39 | pub fn create_mix_float(_t2w: &Transform, tp: &mut TextureParams) -> Option> { 40 | let tex1 = tp.get_floattexture("tex1", 0.0); 41 | let tex2 = tp.get_floattexture("tex2", 1.0); 42 | let amount = tp.get_floattexture("amount", 0.5); 43 | 44 | Some(Arc::new(MixTexture::new(tex1, tex2, amount).into())) 45 | } 46 | 47 | pub fn create_mix_spectrum(_t2w: &Transform, tp: &mut TextureParams) -> Option> { 48 | let tex1 = tp.get_spectrumtexture("tex1", Spectrum::new(0.0)); 49 | let tex2 = tp.get_spectrumtexture("tex2", Spectrum::new(1.0)); 50 | let amount = tp.get_floattexture("amount", 0.5); 51 | 52 | Some(Arc::new(MixTexture::new(tex1, tex2, amount).into())) 53 | } -------------------------------------------------------------------------------- /src/textures/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dots; 2 | pub mod constant; 3 | pub mod scaled; 4 | pub mod mix; 5 | pub mod biler; 6 | pub mod imagemap; 7 | pub mod uv; 8 | pub mod checkerboard; 9 | pub mod fbm; 10 | pub mod wrinkled; 11 | pub mod windy; 12 | pub mod marble; 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/textures/scaled.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use crate::core::texture::{Textures, Texture, TextureFloat, TextureSpec, SpectrumT}; 3 | use crate::core::interaction::SurfaceInteraction; 4 | use crate::core::transform::Transform; 5 | use crate::core::paramset::TextureParams; 6 | use crate::core::spectrum::Spectrum; 7 | use std::ops::{Mul}; 8 | 9 | pub struct ScaleTexture 10 | where T1: SpectrumT, 11 | T2: SpectrumT 12 | { 13 | tex1: Arc>, 14 | tex2: Arc>, 15 | } 16 | 17 | impl ScaleTexture 18 | where T1: SpectrumT, 19 | T2: SpectrumT 20 | { 21 | pub fn new(tex1: Arc>, tex2: Arc>) -> Self { 22 | Self { tex1, tex2 } 23 | } 24 | } 25 | 26 | impl Texture for ScaleTexture 27 | where T1: Mul + SpectrumT, 28 | T2: SpectrumT 29 | 30 | { 31 | fn evaluate(&self, s: &SurfaceInteraction) -> T2 { 32 | self.tex1.evaluate(s) * self.tex2.evaluate(s) 33 | } 34 | } 35 | 36 | pub fn create_scale_float( 37 | _t2w: &Transform, tp: &mut TextureParams) -> Option> { 38 | let s = ScaleTexture::new( 39 | tp.get_floattexture("tex1", 1.0), 40 | tp.get_floattexture("tex2", 1.0) 41 | ); 42 | 43 | Some(Arc::new(s.into())) 44 | } 45 | 46 | pub fn create_scale_spectrum( 47 | _t2w: &Transform, tp: &mut TextureParams) -> Option> { 48 | let s = ScaleTexture::new( 49 | tp.get_spectrumtexture("tex1", Spectrum::new(1.0)), 50 | tp.get_spectrumtexture("tex2", Spectrum::new(1.0)) 51 | ); 52 | 53 | Some(Arc::new(s.into())) 54 | } -------------------------------------------------------------------------------- /src/textures/uv.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureMapping2Ds, Texture, TextureMapping2D, TextureSpec, get_mapping2d, TextureFloat}; 2 | use crate::core::spectrum::{Spectrum, SpectrumType}; 3 | use crate::core::interaction::SurfaceInteraction; 4 | use crate::core::geometry::vector::{Vector2f}; 5 | use crate::core::transform::Transform; 6 | use crate::core::paramset::TextureParams; 7 | use std::sync::Arc; 8 | 9 | pub struct UVTexture { 10 | mapping: TextureMapping2Ds 11 | } 12 | 13 | impl UVTexture { 14 | pub fn new(mapping: TextureMapping2Ds) -> Self { 15 | Self { mapping } 16 | } 17 | } 18 | 19 | impl> Texture for UVTexture { 20 | fn evaluate(&self, s: &SurfaceInteraction) -> T{ 21 | let mut dstdx = Vector2f::default(); 22 | let mut dstdy = Vector2f::default(); 23 | let st = self.mapping.map(s, &mut dstdx, &mut dstdy); 24 | let rgb = [ 25 | st.x - st.x.floor(), 26 | st.y - st.y.floor(), 27 | 0.0 28 | ]; 29 | let s = Spectrum::from_rgb(rgb, SpectrumType::Reflectance); 30 | 31 | T::from(s) 32 | } 33 | } 34 | 35 | pub fn create_uv_float(_t2w: &Transform, _tp: &mut TextureParams) -> Option> { 36 | None 37 | } 38 | 39 | pub fn create_uv_spectrum(t2w: &Transform, tp: &mut TextureParams) -> Option> { 40 | let map = get_mapping2d(t2w, tp); 41 | 42 | Some(Arc::new(UVTexture::new(map).into())) 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/textures/windy.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureMapping3D, TextureMapping3Ds, Texture, fbm, TextureFloat, IdentityMapping3D, TextureSpec}; 2 | use crate::core::pbrt::Float; 3 | use crate::core::interaction::SurfaceInteraction; 4 | use crate::core::geometry::vector::Vector3f; 5 | use crate::core::transform::Transform; 6 | use crate::core::paramset::TextureParams; 7 | use std::sync::Arc; 8 | 9 | pub struct WindyTexture { 10 | mapping: TextureMapping3Ds 11 | } 12 | 13 | impl WindyTexture { 14 | pub fn new(mapping: TextureMapping3Ds) -> Self { 15 | Self { mapping } 16 | } 17 | } 18 | 19 | impl> Texture for WindyTexture { 20 | fn evaluate(&self, s: &SurfaceInteraction) -> T { 21 | let mut dpdx = Vector3f::default(); 22 | let mut dpdy = Vector3f::default(); 23 | let p = self.mapping.map(s, &mut dpdx, &mut dpdy); 24 | 25 | let wstrength = fbm(&(p * 0.1), &(dpdx * 0.1), &(dpdy * 0.1), 0.5, 3 ); 26 | let wheight = fbm(&p, &dpdx, &dpdy, 0.5, 6); 27 | 28 | T::from(wstrength.abs() * wheight) 29 | } 30 | } 31 | 32 | pub fn create_windy_float(t2w: &Transform, _tp: &mut TextureParams) -> Option> { 33 | let map: TextureMapping3Ds = IdentityMapping3D::new(t2w).into(); 34 | 35 | Some(Arc::new(WindyTexture::new(map).into())) 36 | } 37 | 38 | pub fn create_windy_spectrum(t2w: &Transform, _tp: &mut TextureParams) -> Option> { 39 | let map: TextureMapping3Ds = IdentityMapping3D::new(t2w).into(); 40 | 41 | Some(Arc::new(WindyTexture::new(map).into())) 42 | } -------------------------------------------------------------------------------- /src/textures/wrinkled.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::{TextureMapping3Ds, Texture, TextureMapping3D, turbulence, TextureFloat, IdentityMapping3D, TextureSpec}; 2 | use crate::core::pbrt::Float; 3 | use crate::core::interaction::SurfaceInteraction; 4 | use crate::core::geometry::vector::Vector3f; 5 | use crate::core::transform::Transform; 6 | use crate::core::paramset::TextureParams; 7 | use std::sync::Arc; 8 | 9 | pub struct WrinkledTexture { 10 | omega : Float, 11 | octaves : usize, 12 | mapping : TextureMapping3Ds, 13 | } 14 | 15 | impl WrinkledTexture { 16 | pub fn new(mapping: TextureMapping3Ds, octaves: usize, omega: Float) -> Self { 17 | Self { mapping, octaves, omega } 18 | } 19 | } 20 | 21 | impl> Texture for WrinkledTexture { 22 | fn evaluate(&self, s: &SurfaceInteraction) -> T { 23 | let mut dpdx = Vector3f::default(); 24 | let mut dpdy = Vector3f::default(); 25 | let p = self.mapping.map(s, &mut dpdx, &mut dpdy); 26 | 27 | T::from(turbulence(&p, &dpdx, &dpdy, self.omega, self.octaves)) 28 | } 29 | } 30 | 31 | pub fn create_wrinkled_float(t2w: &Transform, tp: &mut TextureParams) -> Option> { 32 | let map: TextureMapping3Ds = IdentityMapping3D::new(t2w).into(); 33 | let octaves = tp.find_int("octaves", 8) as usize; 34 | let roughness = tp.find_float("roughness", 0.5); 35 | 36 | Some(Arc::new(WrinkledTexture::new(map, octaves, roughness).into())) 37 | } 38 | 39 | pub fn create_wrinkled_spectrum(t2w: &Transform, tp: &mut TextureParams) -> Option> { 40 | let map: TextureMapping3Ds = IdentityMapping3D::new(t2w).into(); 41 | let octaves = tp.find_int("octaves", 8) as usize; 42 | let roughness = tp.find_float("roughness", 0.5); 43 | 44 | Some(Arc::new(WrinkledTexture::new(map, octaves, roughness).into())) 45 | } 46 | 47 | -------------------------------------------------------------------------------- /tests/animatedtransform.rs: -------------------------------------------------------------------------------- 1 | 2 | #[cfg(test)] 3 | mod animated_transform { 4 | use pbrt_rust::core::rng::RNG; 5 | use pbrt_rust::core::transform::{Transform, AnimatedTransform}; 6 | use pbrt_rust::core::pbrt::Float; 7 | use pbrt_rust::core::geometry::vector::Vector3f; 8 | use pbrt_rust::core::sampling::uniform_sample_sphere; 9 | use pbrt_rust::core::geometry::point::{Point2f, Point3f}; 10 | use std::sync::Arc; 11 | use pbrt_rust::core::geometry::bounds::Bounds3f; 12 | use nalgebra::base::storage::Storage; 13 | 14 | fn random_transform(rng: &mut RNG) -> Transform { 15 | let mut t = Transform::default(); 16 | 17 | macro_rules! r { 18 | () => {{ 19 | -10.0 + 20.0 * rng.uniform_float() 20 | }} 21 | } 22 | 23 | for i in 0..10 { 24 | t = match rng.uniform_int32_2(3) { 25 | 0 => t * Transform::scale(r!().abs(), r!().abs(), r!().abs()), 26 | 1 => t * Transform::translate(&Vector3f::new(r!(), r!(), r!())), 27 | 2 => t * Transform::rotate( 28 | r!() * 20.0, 29 | &uniform_sample_sphere(&Point2f::new(rng.uniform_float(), rng.uniform_float()))), 30 | _ => t 31 | } 32 | } 33 | 34 | t 35 | } 36 | 37 | #[test] 38 | fn randoms() { 39 | let mut rng = RNG::default(); 40 | 41 | macro_rules! r { 42 | () => {{ 43 | -10.0 + 20.0 * rng.uniform_float() 44 | }} 45 | } 46 | 47 | for i in 0..200 { 48 | // Generate a pair of random transformation matrices 49 | let t0 = Arc::new(random_transform(&mut rng)); 50 | let t1 = Arc::new(random_transform(&mut rng)); 51 | 52 | 53 | let at = AnimatedTransform::new(t0, t1, 0.0, 1.0); 54 | 55 | for j in 0..5 { 56 | // Generate a random bounding box and find the bounds of its motion. 57 | let bounds = Bounds3f::from_points(Point3f::new(r!(), r!(), r!()), Point3f::new(r!(), r!(), r!())); 58 | let motion_bounds = at.motion_bounds(&bounds); 59 | let mut t = 0.0; 60 | 61 | while t <= 1.0 { 62 | // Now, interpolate the transformations at a bunch of times 63 | // along the time range and then transform the bounding box 64 | // with the result. 65 | let mut tr = Transform::default(); 66 | at.interpolate(t, &mut tr); 67 | let mut tb = tr.transform_bounds(&bounds); 68 | 69 | // Add a little slop to allow for floating-point round-off 70 | // error in computing the motion extrema times. 71 | tb.p_min += tb.diagonal() * 1.0e-4; 72 | tb.p_max -= tb.diagonal() * 1.0e-4; 73 | 74 | // Now, the transformed bounds should be inside the motion 75 | // bounds. 76 | assert!(tb.p_min.x >= motion_bounds.p_min.x); 77 | assert!(tb.p_max.x <= motion_bounds.p_max.x); 78 | assert!(tb.p_min.y >= motion_bounds.p_min.y); 79 | assert!(tb.p_max.y <= motion_bounds.p_max.y); 80 | assert!(tb.p_min.z >= motion_bounds.p_min.z); 81 | assert!(tb.p_max.z <= motion_bounds.p_max.z); 82 | 83 | t += 1.0e-3 * rng.uniform_float(); 84 | } 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /tests/bitops.rs: -------------------------------------------------------------------------------- 1 | 2 | #[cfg(test)] 3 | mod bitops_test { 4 | use pbrt_rust::core::pbrt::{log2_int, log2_int64, is_power_of2, round_up_pow2_32, round_up_pow2_64}; 5 | 6 | #[test] 7 | fn log2() { 8 | for i in 0..32 { 9 | let ui = 1u32 << i; 10 | assert_eq!(i, log2_int(ui as i32)); 11 | assert_eq!(i as i64, log2_int64(ui as i64)); 12 | } 13 | 14 | for i in 1..32 { 15 | let ui = 1u32 << i; 16 | assert_eq!(i, log2_int(ui as i32 + 1)); 17 | assert_eq!(i as i64, log2_int64(ui as i64 + 1)); 18 | } 19 | 20 | for i in 0..64 { 21 | let ui = 1u64 << i; 22 | assert_eq!(i as i64, log2_int64(ui as i64)); 23 | } 24 | 25 | for i in 1..64 { 26 | let ui = 1u64 << i; 27 | assert_eq!(i as i64, log2_int64(ui as i64 + 1)); 28 | } 29 | } 30 | 31 | #[test] 32 | fn round_up_pow2() { 33 | assert_eq!(round_up_pow2_32(7), 8); 34 | 35 | let mut i = 1; 36 | while i < (1 << 24) { 37 | if is_power_of2(i) { 38 | assert_eq!(round_up_pow2_32(i), i); 39 | } else { 40 | assert_eq!(round_up_pow2_32(i), 1 << (log2_int(i) + 1)); 41 | } 42 | 43 | i += 1; 44 | } 45 | 46 | let mut i = 1i64; 47 | 48 | while i < (1 << 24) { 49 | if is_power_of2(i) { 50 | assert_eq!(round_up_pow2_64(i), i); 51 | } else { 52 | assert_eq!(round_up_pow2_64(i), 1 << (log2_int64(i) + 1)); 53 | } 54 | 55 | i += 1 56 | } 57 | 58 | for i in 0..30i32 { 59 | let v = 1i32 << i; 60 | assert_eq!(round_up_pow2_32(v), v); 61 | if v > 2 { assert_eq!(round_up_pow2_32(v - 1), v); } 62 | assert_eq!(round_up_pow2_32(v + 1), 2 * v) 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /tests/bounds.rs: -------------------------------------------------------------------------------- 1 | 2 | #[cfg(test)] 3 | mod bounds { 4 | use pbrt_rust::core::geometry::bounds::{Bounds3f, Bounds2f}; 5 | use pbrt_rust::core::geometry::point::{Point3f, Point2f}; 6 | 7 | #[test] 8 | fn bounds2_union() { 9 | let a = Bounds2f::new( 10 | &Point2f::new(-10.0, -10.0), 11 | &Point2f::new(0.0, 20.0)); 12 | let b = Bounds2f::default(); 13 | let c = a.union_boundsf(&b); 14 | assert_eq!(a, c); 15 | assert_eq!(b, b.union_boundsf(&b)); 16 | 17 | let d = Bounds2f::from_point(&Point2f::new(-15.0, 10.0)); 18 | let e = a.union_boundsf(&d); 19 | assert_eq!(Bounds2f::new(&Point2f::new(-15.0, -10.0), &Point2f::new(0.0, 20.0)), e); 20 | } 21 | 22 | #[test] 23 | fn bounds3_union() { 24 | let a = Bounds3f::from_points( 25 | Point3f::new(-10.0, -10.0, 5.0), 26 | Point3f::new(0.0, 20.0, 10.0)); 27 | let b = Bounds3f::default(); 28 | let c = a.union_bounds(&b); 29 | assert_eq!(a, c); 30 | assert_eq!(b, b.union_bounds(&b)); 31 | 32 | let d = Bounds3f::from_point(&Point3f::new(-15.0, 10.0, 30.0)); 33 | let e = a.union_bounds(&d); 34 | assert_eq!(Bounds3f::from_points(Point3f::new(-15.0, -10.0, 5.0), Point3f::new(0.0, 20.0, 30.0)), e); 35 | } 36 | } -------------------------------------------------------------------------------- /tests/find_interval.rs: -------------------------------------------------------------------------------- 1 | 2 | #[cfg(test)] 3 | mod find_interval { 4 | use pbrt_rust::core::pbrt::{find_interval, Float}; 5 | 6 | #[test] 7 | fn find_interval_test() { 8 | let a = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; 9 | 10 | // Check clamping for out of range 11 | assert_eq!(0, find_interval(a.len() as i32, |i| { a[i as usize] <= -1.0 })); 12 | assert_eq!((a.len() - 2) as i32, find_interval(a.len() as i32, |i| { a[i as usize] <= 100.0 })); 13 | 14 | for i in 0..(a.len() - 1) { 15 | assert_eq!(i as i32, find_interval(a.len() as i32, |j| a[j as usize] <= i as Float)); 16 | assert_eq!(i as i32, find_interval(a.len() as i32, |j| a[j as usize] <= i as Float + 0.5)); 17 | 18 | if i > 0 { 19 | assert_eq!( (i - 1) as i32, find_interval(a.len() as i32, |j| a[j as usize] <= i as Float - 0.5)); 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /tests/hg.rs: -------------------------------------------------------------------------------- 1 | 2 | #[cfg(test)] 3 | mod henyey_greenstein { 4 | use pbrt_rust::core::rng::RNG; 5 | use pbrt_rust::core::medium::{HenyeyGreenstein, PhaseFunction}; 6 | use pbrt_rust::core::geometry::vector::Vector3f; 7 | use pbrt_rust::core::sampling::uniform_sample_sphere; 8 | use pbrt_rust::core::geometry::point::Point2f; 9 | use approx::relative_eq; 10 | use pbrt_rust::core::pbrt::{PI, Float}; 11 | 12 | 13 | #[test] 14 | fn sampling_match() { 15 | let mut rng = RNG::default(); 16 | let mut g = -0.75; 17 | 18 | while g <= 0.75 { 19 | let hg = HenyeyGreenstein::new(g); 20 | 21 | for i in 0..100 { 22 | let wo = uniform_sample_sphere( 23 | &Point2f::new(rng.uniform_float(), rng.uniform_float())); 24 | let mut wi = Vector3f::default(); 25 | let u = Point2f::new(rng.uniform_float(), rng.uniform_float()); 26 | let p0 = hg.sample_p(&wo, &mut wi, &u); 27 | // Phase function is normalized and the sampling method should be exact 28 | relative_eq!(p0, hg.p(&wo, &wi), epsilon = 1.0e-4); 29 | } 30 | 31 | g += 0.25; 32 | } 33 | } 34 | 35 | #[test] 36 | fn sampling_orientation_forward() { 37 | let mut rng = RNG::default(); 38 | let hg = HenyeyGreenstein::new(0.95); 39 | let wo = Vector3f::new(-1.0, 0.0, 0.0); 40 | let mut nforward = 0; 41 | let mut nbackward = 0; 42 | 43 | for i in 0..100 { 44 | let u = Point2f::new(rng.uniform_float(), rng.uniform_float()); 45 | let mut wi = Vector3f::default(); 46 | hg.sample_p(&wo, &mut wi, &u); 47 | 48 | if wi.x > 0.0 { 49 | nforward += 1; 50 | } else { 51 | nbackward += 1; 52 | } 53 | } 54 | 55 | assert!(nforward > 10 * nbackward); 56 | } 57 | 58 | #[test] 59 | fn sample_orientation_backward() { 60 | let mut rng = RNG::default(); 61 | let hg = HenyeyGreenstein::new(-0.95); 62 | let wo = Vector3f::new(-1.0, 0.0, 0.0); 63 | let mut nforward = 0; 64 | let mut nbackward = 0; 65 | 66 | for i in 0..100 { 67 | let u = Point2f::new(rng.uniform_float(), rng.uniform_float()); 68 | let mut wi = Vector3f::default(); 69 | hg.sample_p(&wo, &mut wi, &u); 70 | 71 | if wi.x > 0.0 { 72 | nforward += 1 73 | } else { 74 | nbackward += 1; 75 | } 76 | } 77 | 78 | assert!(nbackward > 10 * nforward); 79 | } 80 | 81 | #[test] 82 | fn normalized() { 83 | let mut rng = RNG::default(); 84 | let mut g = -0.75; 85 | 86 | while g <= 0.75 { 87 | let hg = HenyeyGreenstein::new(g); 88 | let wo = 89 | uniform_sample_sphere(&Point2f::new(rng.uniform_float(), rng.uniform_float())); 90 | let mut sum = 0.0; 91 | let nsamples = 100000; 92 | 93 | for i in 0..nsamples { 94 | let wi = 95 | uniform_sample_sphere(&Point2f::new(rng.uniform_float(), rng.uniform_float())); 96 | sum += hg.p(&wo, &wi); 97 | } 98 | 99 | relative_eq!(sum / nsamples as Float, 1.0 / (4.0 * PI), epsilon = 1.0e-3); 100 | g += 0.25; 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /tests/imageio.rs: -------------------------------------------------------------------------------- 1 | 2 | #[cfg(test)] 3 | mod imageio { 4 | use pbrt_rust::core::geometry::point::Point2i; 5 | use pbrt_rust::core::pbrt::{Float, inverse_gamma_correct}; 6 | use pbrt_rust::core::imageio::{write_image, read_image}; 7 | use pbrt_rust::core::geometry::bounds::Bounds2i; 8 | use pbrt_rust::core::fileutil::has_extension; 9 | 10 | fn test_round_trip(fname: &str, gamma: bool) { 11 | let res = Point2i::new(16, 29); 12 | let mut pixels = vec![0.0; 3 * res.x as usize * res.y as usize]; 13 | 14 | for y in 0..res[1] { 15 | for x in 0..res[0] { 16 | let offset = 3 * (y * res[0] + x) as usize; 17 | pixels[offset] = x as Float / (res[0] - 1) as Float; 18 | pixels[offset + 1] = y as Float / (res[1] - 1) as Float; 19 | pixels[offset + 2] = -1.5; 20 | } 21 | } 22 | 23 | let obounds = Bounds2i::from_points(&Point2i::default(), &res); 24 | let r = write_image(fname, &pixels, &obounds, &res); 25 | assert!(r.is_ok()); 26 | 27 | let r = read_image(fname); 28 | assert!(r.is_ok()); 29 | let (readpixs, readres) = r.unwrap(); 30 | assert_eq!(readres, res); 31 | 32 | for y in 0..res[1] { 33 | for x in 0..res[0] { 34 | let mut rgb = readpixs[(y * res[0] + x) as usize].to_rgb(); 35 | 36 | for c in 0..3 { 37 | if gamma { rgb[c] = inverse_gamma_correct(rgb[c]) } 38 | 39 | let wrote = pixels[3 * (y * res[0] + x) as usize + c]; 40 | let delta = wrote - rgb[c]; 41 | 42 | if has_extension(fname, "pfm") { 43 | assert_eq!( 44 | 0.0, delta, 45 | "{}: ({}, {}) c = {} wrote {}, read {}, delta = {}", 46 | fname, x, y, c, wrote, rgb[c], delta); 47 | } else if has_extension(fname, "exr") { 48 | if c == 2 { 49 | assert_eq!( 50 | 0.0, delta, 51 | "({}, {}) c = {} wrote {}, read {}, delta = {}", 52 | x, y, c, wrote, rgb[c], delta); 53 | } else { 54 | assert!( 55 | delta.abs() < 0.001, 56 | "{}: ({}, {}) c = {} wrote {}, read {}, delta = {}", 57 | fname, x, y, c, wrote, rgb[c], delta); 58 | } 59 | } else { 60 | 61 | if c == 2 { 62 | assert_eq!( 63 | 0.0, rgb[c], 64 | "({}, {}) c = {} wrote {}, read {}, (expected 0 back)", 65 | x, y, c, wrote, rgb[c]); 66 | } else { 67 | // Allow a fair amount of slop, since there's an sRGB 68 | // conversion before quantization to 8-bits... 69 | assert!(delta.abs() < 0.02, 70 | "{}: ({}, {}) c = {} wrote {}, read {}, delta = {}", 71 | fname, x, y, c, wrote, rgb[c], delta) 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | let r = std::fs::remove_file(fname); 79 | assert!(r.is_ok()); 80 | } 81 | 82 | #[test] 83 | fn roundtrip_exr() { 84 | test_round_trip("out.exr", false); 85 | } 86 | 87 | #[test] 88 | fn roundtrip_pfm() { 89 | test_round_trip("out.pfm", false); 90 | } 91 | 92 | #[test] 93 | fn roundtrip_tga() { 94 | test_round_trip("out.tga", true); 95 | } 96 | 97 | #[test] 98 | fn roundtrip_png() { 99 | test_round_trip("out.png", true); 100 | } 101 | } --------------------------------------------------------------------------------