├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── examples ├── plane.rs └── sphere.rs ├── scenes ├── cornell_box.json ├── logo_shadow.json ├── logo_with_friends.json ├── models │ └── cube.obj ├── smallpt.json ├── suzanne_scene.json └── tr15.json └── src ├── bxdf ├── bsdf.rs ├── fresnel.rs ├── lambertian.rs ├── merl.rs ├── microfacet │ ├── beckmann.rs │ ├── ggx.rs │ └── mod.rs ├── microfacet_transmission.rs ├── mod.rs ├── oren_nayar.rs ├── specular_reflection.rs ├── specular_transmission.rs └── torrance_sparrow.rs ├── exec ├── distrib │ ├── master.rs │ ├── mod.rs │ └── worker.rs ├── mod.rs └── multithreaded.rs ├── film ├── animated_color.rs ├── camera.rs ├── color.rs ├── filter │ ├── gaussian.rs │ ├── mitchell_netravali.rs │ └── mod.rs ├── image.rs ├── mod.rs └── render_target.rs ├── geometry ├── animated_mesh.rs ├── bbox.rs ├── bvh.rs ├── differential_geometry.rs ├── disk.rs ├── emitter.rs ├── instance.rs ├── intersection.rs ├── mesh.rs ├── mod.rs ├── receiver.rs ├── rectangle.rs └── sphere.rs ├── integrator ├── mod.rs ├── normals_debug.rs ├── path.rs └── whitted.rs ├── lib.rs ├── light └── mod.rs ├── linalg ├── animated_transform.rs ├── keyframe.rs ├── matrix4.rs ├── mod.rs ├── normal.rs ├── point.rs ├── quaternion.rs ├── ray.rs ├── transform.rs └── vector.rs ├── main.rs ├── material ├── glass.rs ├── matte.rs ├── merl.rs ├── metal.rs ├── mod.rs ├── plastic.rs ├── rough_glass.rs └── specular_metal.rs ├── mc.rs ├── partition.rs ├── sampler ├── adaptive.rs ├── block_queue.rs ├── ld.rs ├── mod.rs ├── morton.rs └── uniform.rs ├── scene.rs └── texture ├── animated_image.rs ├── image.rs └── mod.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build_linux: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Build 14 | run: cargo build --verbose 15 | - name: Run tests 16 | run: cargo test --verbose 17 | build_windows: 18 | runs-on: windows-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | build_macos: 26 | runs-on: macos-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Build 30 | run: cargo build --verbose 31 | - name: Run tests 32 | run: cargo test --verbose 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | /target 3 | *.ppm 4 | *.png 5 | *.bmp 6 | *.obj 7 | *.mtl 8 | *.binary 9 | batch_run/ 10 | *.mp4 11 | worker_output/ 12 | raytracing_compo/ 13 | 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tray_rust" 3 | version = "0.0.1" 4 | authors = ["Will Usher "] 5 | 6 | [[bin]] 7 | name = "tray_rust" 8 | doc = false 9 | 10 | [dependencies] 11 | enum-set = "0.0.7" 12 | rand = "0.4.2" 13 | serde = "1.0" 14 | serde_derive = "1.0" 15 | serde_json = "1.0" 16 | docopt = "0.8.3" 17 | image = "0.18.0" 18 | num_cpus = "1.8" 19 | tobj = "0.1.6" 20 | byteorder = "1.2" 21 | scoped_threadpool = "0.1.8" 22 | bspline = "0.2.2" 23 | bincode = "0.9" 24 | mio = "0.5.1" 25 | la = "0.2.0" 26 | light_arena = "1.0.1" 27 | 28 | [profile.release] 29 | # How to send extra compiler flag for -march=native equivalent in Rust 30 | # flag is `-C target-cpu=native` 31 | opt-level = 3 32 | debug = false 33 | rpath = false 34 | 35 | [features] 36 | unstable = [] 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Will Usher 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tray\_rust - A Toy Ray Tracer in Rust 2 | === 3 | tray\_rust is a toy physically based ray tracer built off of the techniques 4 | discussed in [Physically Based Rendering](http://pbrt.org/). It began life as a port of 5 | [tray](https://github.com/Twinklebear/tray) to [Rust](http://www.rust-lang.org) to check out the language 6 | but has surpassed it in a few ways. 7 | The renderer is currently capable of path tracing, supports triangle meshes (MTL support coming soon), 8 | and various physically based material models (including measured data from the 9 | [MERL BRDF Database](http://www.merl.com/brdf/)). tray\_rust also supports rigid body animation along 10 | B-spline paths and distributed rendering. 11 | 12 | ![Build Status](https://github.com/Twinklebear/tray_rust/workflows/CI/badge.svg) 13 | 14 | Running 15 | --- 16 | Running and passing `--help` or `-h` will print out options you can pass to the renderer which are documented in the help. 17 | For the more complicated use cases I hope to do some write ups and guides on how to use them (e.g. distributed rendering, 18 | animation) but this may take a while. I strongly recommend running the release build as the debug version will be very slow. 19 | 20 | Building Your Own Scenes 21 | --- 22 | To position and animate objects, the camera and so on the 23 | [Blender plugin](https://github.com/Twinklebear/tray_rust_blender) is the easiest to use. However the plugin 24 | is still in development and missing some features like setting materials, changing light properties and such so 25 | you'll still currently need to do those by hand in the exported JSON file. For materials take a look at 26 | the [materials documentation](http://www.willusher.io/tray_rust/tray_rust/material/index.html) for lights 27 | you'll likely just want to change the emission color which is an RGB color plus a strength term. 28 | 29 | Start at the documentation for the [scene module](http://www.willusher.io/tray_rust/tray_rust/scene/index.html), 30 | there are also a few example [scenes](scenes/) included but not all the models are provided. From a clean `git clone` you 31 | should be able to run [cornell\_box.json](scenes/cornell_box.json) and [smallpt.json](scenes/smallpt.json). I plan to add some 32 | more simple scenes that show usage of other features like animation to provide examples. The rigid body animation 33 | feature is relatively new though so I haven't had time to document it properly yet. 34 | 35 | Documentation 36 | --- 37 | Documentation can be found on the [project site](http://www.willusher.io/tray_rust/tray_rust/). 38 | 39 | TODO 40 | --- 41 | - More material models (eg. more microfacet models, rough glass, etc.) 42 | - Textures 43 | - Support for using an OBJ's associated MTL files 44 | - Bump mapping 45 | - [Subsurface scattering?](http://en.wikipedia.org/wiki/Subsurface_scattering) 46 | - [Vertex Connection and Merging?](http://iliyan.com/publications/VertexMerging) 47 | 48 | Sample Renders 49 | --- 50 | In the samples the Buddha, Dragon, Bunny and Lucy statue are from 51 | [The Stanford Scanning Repository](http://graphics.stanford.edu/data/3Dscanrep/). 52 | The Rust logo model was made by 53 | [Nylithius on BlenderArtists](http://blenderartists.org/forum/showthread.php?362836-Rust-language-3D-logo). 54 | The Utah teapot used is from [Morgan McGuire's page](http://graphics.cs.williams.edu/data/meshes.xml) and 55 | the monkey head is Blender's Suzanne. I've made minor tweaks to some of the models so for convenience 56 | you can find versions that can be easily loaded into the sample scenes [here](https://drive.google.com/folderview?id=0B-l_lLEMo1YeflUzUndCd01hOHhRNUhrQUowM3hVd2pCc3JrSXRiS3FQSzRYLWtGcGM0eGc&usp=sharing), though the 57 | cube model for the Cornell box scene is included. 58 | The materials on the Rust logo, Buddha, Dragon and Lucy are from the 59 | [MERL BRDF Database](http://www.merl.com/brdf/). Models for running the scenes and links to model sources 60 | can be found [here](https://drive.google.com/file/d/0B-l_lLEMo1YecFdtRlM5WEY3eE0/view?usp=sharing). 61 | 62 | Render times are formatted as hh:mm:ss and were measured using 144 threads on a machine with four 63 | [Xeon E7-8890 v3](http://ark.intel.com/products/84685/Intel-Xeon-Processor-E7-8890-v3-45M-Cache-2_50-GHz) 64 | CPUs. The machine is an early/engineering sample from Intel so your results may differ, but massive thanks to 65 | Intel for the hardware! Some older renders are shown as well without timing since they were 66 | run on a different machine. 67 | 68 | Some more sample renders can be found [here](http://imgur.com/a/3qNBc). I've also used tray\_rust 69 | for the past two years at [Utah's Teapot Rendering Competition](http://graphics.cs.utah.edu/trc/), 70 | view my animations for [2015](http://www.willusher.io/courses/cs6620/tr15.html) 71 | and [2016](http://www.willusher.io/courses/cs6620/tr16.html). The latter was 72 | made using the Blender plugin for modeling and contains more complex motion and sequences. 73 | 74 | [![Model gallery](http://i.imgur.com/X5y8oIq.png)](http://i.imgur.com/X5y8oIq.png) 75 | 76 | 1920x1080, 4096 samples/pixel. Rendering: 00:43:36.45. 77 | 78 | [![Rust Logo with friends, disk](http://i.imgur.com/E1ylrZW.png)](http://i.imgur.com/E1ylrZW.png) 79 | 80 | 1920x1080, 4096 samples/pixel. Rendering: 00:49:33.514. 81 | 82 | -------------------------------------------------------------------------------- /examples/plane.rs: -------------------------------------------------------------------------------- 1 | extern crate tray_rust; 2 | extern crate rand; 3 | extern crate image; 4 | 5 | use std::sync::Arc; 6 | use rand::StdRng; 7 | 8 | use tray_rust::linalg::{AnimatedTransform, Transform, Point, Vector}; 9 | use tray_rust::film::{Colorf, RenderTarget, Camera, ImageSample}; 10 | use tray_rust::film::filter::MitchellNetravali; 11 | use tray_rust::geometry::{Rectangle, Instance}; 12 | use tray_rust::material::Matte; 13 | use tray_rust::sampler::{BlockQueue, LowDiscrepancy, Sampler}; 14 | use tray_rust::texture; 15 | 16 | fn main() { 17 | let width = 800usize; 18 | let height = 600usize; 19 | let filter = 20 | Box::new(MitchellNetravali::new(2.0, 2.0, 0.333333333333333333, 0.333333333333333333)); 21 | let rt = RenderTarget::new((width, height), (20, 20), filter); 22 | let transform = 23 | AnimatedTransform::unanimated(&Transform::look_at(&Point::new(0.0, 0.0, -10.0), 24 | &Point::new(0.0, 0.0, 0.0), 25 | &Vector::new(0.0, 1.0, 0.0))); 26 | let camera = Camera::new(transform, 40.0, rt.dimensions(), 0.5, 0); 27 | let plane = Rectangle::new(2.0, 2.0); 28 | let geometry_lock = Arc::new(plane); 29 | // TODO: From a code usage standpoint it might be nice to have a constant version 30 | // of the material ctor exposed which takes the plain types and builds the textures internally 31 | let texture = Arc::new(texture::ConstantColor::new(Colorf::new(0.740063, 0.742313, 0.733934))); 32 | let roughness = Arc::new(texture::ConstantScalar::new(1.0)); 33 | let white_wall = Matte::new(texture, roughness); 34 | let material_lock = Arc::new(white_wall); 35 | let position_transform = 36 | AnimatedTransform::unanimated(&Transform::translate(&Vector::new(0.0, 2.0, 0.0))); 37 | let instance = Instance::receiver(geometry_lock, 38 | material_lock, 39 | position_transform, 40 | "single_plane".to_string()); 41 | 42 | let dim = rt.dimensions(); 43 | // A block queue is how work is distributed among threads, it's a list of tiles 44 | // of the image that have yet to be rendered. Each thread will pull a block from 45 | // this queue and render it. 46 | let block_queue = BlockQueue::new((dim.0 as u32, dim.1 as u32), (8, 8), (0, 0)); 47 | let block_dim = block_queue.block_dim(); 48 | // A sample is responsible for choosing randomly placed locations within a pixel to 49 | // get a good sampling of the image. Using a poor quality sampler will resuly in a 50 | // noiser and more aliased image that converges slower. The LowDiscrepency sampler 51 | // is a good choice for quality. 52 | let mut sampler = LowDiscrepancy::new(block_dim, 2); 53 | let mut sample_pos = Vec::with_capacity(sampler.max_spp()); 54 | let mut block_samples = Vec::with_capacity(sampler.max_spp() * 55 | (block_dim.0 * block_dim.1) as usize); 56 | let mut rng = match StdRng::new() { 57 | Ok(r) => r, 58 | Err(e) => { 59 | println!("Failed to get StdRng, {}", e); 60 | return; 61 | } 62 | }; 63 | // Grab a block from the queue and start working on it, submitting samples 64 | // to the render target thread after each pixel 65 | for b in block_queue.iter() { 66 | sampler.select_block(b); 67 | // While the sampler has samples left to take for this pixel, take some samples 68 | while sampler.has_samples() { 69 | // Get samples for a pixel and render them 70 | sampler.get_samples(&mut sample_pos, &mut rng); 71 | for s in &sample_pos[..] { 72 | let mut ray = camera.generate_ray(s, 0.0); 73 | if let Some(_) = instance.intersect(&mut ray) { 74 | block_samples.push(ImageSample::new(s.0, s.1, Colorf::broadcast(1.0))); 75 | } else { 76 | // For correct filtering we also MUST set a background color of some kind 77 | // if we miss, otherwise the pixel weights will be wrong and we'll see object 78 | // fringes and artifacts at object boundaries w/ nothing. Try removing this 79 | // line and rendering again. 80 | block_samples.push(ImageSample::new(s.0, s.1, Colorf::black())); 81 | } 82 | } 83 | } 84 | // We write all samples at once so we don't need to lock the render target tiles as often 85 | rt.write(&block_samples, sampler.get_region()); 86 | block_samples.clear(); 87 | } 88 | 89 | // Get the sRGB8 render buffer from the floating point framebuffer and save it 90 | let img = rt.get_render(); 91 | match image::save_buffer("plane.png", 92 | &img[..], 93 | dim.0 as u32, 94 | dim.1 as u32, 95 | image::RGB(8)) { 96 | Ok(_) => {} 97 | Err(e) => println!("Error saving image, {}", e), 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /examples/sphere.rs: -------------------------------------------------------------------------------- 1 | extern crate tray_rust; 2 | extern crate rand; 3 | extern crate image; 4 | 5 | use std::sync::Arc; 6 | use rand::StdRng; 7 | 8 | use tray_rust::linalg::{AnimatedTransform, Transform, Point, Vector}; 9 | use tray_rust::film::{Colorf, RenderTarget, Camera, ImageSample}; 10 | use tray_rust::film::filter::MitchellNetravali; 11 | use tray_rust::geometry::{Sphere, Instance}; 12 | use tray_rust::material::Matte; 13 | use tray_rust::sampler::{BlockQueue, LowDiscrepancy, Sampler}; 14 | use tray_rust::texture; 15 | 16 | fn main() { 17 | let width = 800usize; 18 | let height = 600usize; 19 | let filter = 20 | Box::new(MitchellNetravali::new(2.0, 2.0, 0.333333333333333333, 0.333333333333333333)); 21 | let rt = RenderTarget::new((width, height), (20, 20), filter); 22 | let transform = 23 | AnimatedTransform::unanimated(&Transform::look_at(&Point::new(0.0, 0.0, -10.0), 24 | &Point::new(0.0, 0.0, 0.0), 25 | &Vector::new(0.0, 1.0, 0.0))); 26 | let camera = Camera::new(transform, 40.0, rt.dimensions(), 0.5, 0); 27 | let sphere = Sphere::new(1.5); 28 | let geometry_lock = Arc::new(sphere); 29 | // TODO: From a code usage standpoint it might be nice to have a constant version 30 | // of the material ctor exposed which takes the plain types and builds the textures internally 31 | let texture = Arc::new(texture::ConstantColor::new(Colorf::new(0.740063, 0.742313, 0.733934))); 32 | let roughness = Arc::new(texture::ConstantScalar::new(1.0)); 33 | let white_wall = Matte::new(texture, roughness); 34 | let material_lock = Arc::new(white_wall); 35 | let position_transform = 36 | AnimatedTransform::unanimated(&Transform::translate(&Vector::new(0.0, 2.0, 0.0))); 37 | let instance = Instance::receiver(geometry_lock, 38 | material_lock, 39 | position_transform, 40 | "single_sphere".to_string()); 41 | 42 | let dim = rt.dimensions(); 43 | // A block queue is how work is distributed among threads, it's a list of tiles 44 | // of the image that have yet to be rendered. Each thread will pull a block from 45 | // this queue and render it. 46 | let block_queue = BlockQueue::new((dim.0 as u32, dim.1 as u32), (8, 8), (0, 0)); 47 | let block_dim = block_queue.block_dim(); 48 | // A sample is responsible for choosing randomly placed locations within a pixel to 49 | // get a good sampling of the image. Using a poor quality sampler will resuly in a 50 | // noiser and more aliased image that converges slower. The LowDiscrepency sampler 51 | // is a good choice for quality. 52 | let mut sampler = LowDiscrepancy::new(block_dim, 2); 53 | let mut sample_pos = Vec::with_capacity(sampler.max_spp()); 54 | let mut block_samples = Vec::with_capacity(sampler.max_spp() * 55 | (block_dim.0 * block_dim.1) as usize); 56 | let mut rng = match StdRng::new() { 57 | Ok(r) => r, 58 | Err(e) => { 59 | println!("Failed to get StdRng, {}", e); 60 | return; 61 | } 62 | }; 63 | // Grab a block from the queue and start working on it, submitting samples 64 | // to the render target thread after each pixel 65 | for b in block_queue.iter() { 66 | sampler.select_block(b); 67 | // While the sampler has samples left to take for this pixel, take some samples 68 | while sampler.has_samples() { 69 | // Get samples for a pixel and render them 70 | sampler.get_samples(&mut sample_pos, &mut rng); 71 | for s in &sample_pos[..] { 72 | let mut ray = camera.generate_ray(s, 0.0); 73 | if let Some(_) = instance.intersect(&mut ray) { 74 | block_samples.push(ImageSample::new(s.0, s.1, Colorf::broadcast(1.0))); 75 | } else { 76 | // For correct filtering we also MUST set a background color of some kind 77 | // if we miss, otherwise the pixel weights will be wrong and we'll see object 78 | // fringes and artifacts at object boundaries w/ nothing. Try removing this 79 | // line and rendering again. 80 | block_samples.push(ImageSample::new(s.0, s.1, Colorf::black())); 81 | } 82 | } 83 | } 84 | // We write all samples at once so we don't need to lock the render target tiles as often 85 | rt.write(&block_samples, sampler.get_region()); 86 | block_samples.clear(); 87 | } 88 | 89 | // Get the sRGB8 render buffer from the floating point framebuffer and save it 90 | let img = rt.get_render(); 91 | match image::save_buffer("sphere.png", 92 | &img[..], 93 | dim.0 as u32, 94 | dim.1 as u32, 95 | image::RGB(8)) { 96 | Ok(_) => {} 97 | Err(e) => println!("Error saving image, {}", e), 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /scenes/cornell_box.json: -------------------------------------------------------------------------------- 1 | { 2 | "film": { 3 | "width": 800, 4 | "height": 600, 5 | "samples": 4, 6 | "frames": 1, 7 | "start_frame": 0, 8 | "end_frame": 0, 9 | "scene_time": 0, 10 | "filter" : { 11 | "type": "mitchell_netravali", 12 | "width": 2.0, 13 | "height": 2.0, 14 | "b": 0.333333333333333333, 15 | "c": 0.333333333333333333 16 | } 17 | }, 18 | "camera": { 19 | "fov": 30, 20 | "transform": [ 21 | { 22 | "type": "translate", 23 | "translation": [0, 12, -60] 24 | } 25 | ] 26 | }, 27 | "integrator": { 28 | "type": "pathtracer", 29 | "min_depth": 4, 30 | "max_depth": 8 31 | }, 32 | "materials": [ 33 | { 34 | "type": "matte", 35 | "name": "white_wall", 36 | "diffuse": [0.740063, 0.742313, 0.733934], 37 | "roughness": 1.0 38 | }, 39 | { 40 | "type": "matte", 41 | "name": "red_wall", 42 | "diffuse": [0.366046, 0.0371827, 0.0416385], 43 | "roughness": 1.0 44 | }, 45 | { 46 | "type": "matte", 47 | "name": "green_wall", 48 | "diffuse": [0.162928, 0.408903, 0.0833759], 49 | "roughness": 1.0 50 | }, 51 | { 52 | "type": "plastic", 53 | "name": "white_plastic", 54 | "diffuse": [0.8, 0.8, 0.8], 55 | "gloss": [0.6, 0.6, 0.6], 56 | "roughness": 0.5 57 | } 58 | ], 59 | "objects": [ 60 | { 61 | "type": "group", 62 | "name": "walls", 63 | "transform": [ 64 | { 65 | "type": "translate", 66 | "translation": [0, 12, 0] 67 | } 68 | ], 69 | "objects": [ 70 | { 71 | "name": "back_wall", 72 | "type": "receiver", 73 | "material": "white_wall", 74 | "geometry": { 75 | "type": "plane" 76 | }, 77 | "transform": [ 78 | { 79 | "type": "scale", 80 | "scaling": [15, 12, 1] 81 | }, 82 | { 83 | "type": "translate", 84 | "translation": [0, 0, 20] 85 | } 86 | ] 87 | }, 88 | { 89 | "name": "left_wall", 90 | "type": "receiver", 91 | "material": "red_wall", 92 | "geometry": { 93 | "type": "plane" 94 | }, 95 | "transform": [ 96 | { 97 | "type": "scale", 98 | "scaling": [20, 12, 1] 99 | }, 100 | { 101 | "type": "rotate_y", 102 | "rotation": 90.0 103 | }, 104 | { 105 | "type": "translate", 106 | "translation": [-15.0, 0, 0] 107 | } 108 | ] 109 | }, 110 | { 111 | "name": "right_wall", 112 | "type": "receiver", 113 | "material": "green_wall", 114 | "geometry": { 115 | "type": "plane" 116 | }, 117 | "transform": [ 118 | { 119 | "type": "scale", 120 | "scaling": [20, 12, 1] 121 | }, 122 | { 123 | "type": "rotate_y", 124 | "rotation": -90.0 125 | }, 126 | { 127 | "type": "translate", 128 | "translation": [15.0, 0, 0] 129 | } 130 | ] 131 | }, 132 | { 133 | "name": "top_wall", 134 | "type": "receiver", 135 | "material": "white_wall", 136 | "geometry": { 137 | "type": "plane" 138 | }, 139 | "transform": [ 140 | { 141 | "type": "scale", 142 | "scaling": [15, 20, 1] 143 | }, 144 | { 145 | "type": "rotate_x", 146 | "rotation": 90.0 147 | }, 148 | { 149 | "type": "translate", 150 | "translation": [0.0, 12, 0] 151 | } 152 | ] 153 | }, 154 | { 155 | "name": "bottom_wall", 156 | "type": "receiver", 157 | "material": "white_wall", 158 | "geometry": { 159 | "type": "plane" 160 | }, 161 | "transform": [ 162 | { 163 | "type": "scale", 164 | "scaling": [15, 20, 1] 165 | }, 166 | { 167 | "type": "rotate_x", 168 | "rotation": 90 169 | }, 170 | { 171 | "type": "translate", 172 | "translation": [0.0, -12, 0] 173 | } 174 | ] 175 | } 176 | ] 177 | }, 178 | { 179 | "name": "light", 180 | "type": "emitter", 181 | "material": "white_wall", 182 | "emitter": "area", 183 | "emission": [1, 0.772549, 0.560784, 40], 184 | "geometry": { 185 | "type": "rectangle", 186 | "width": 6, 187 | "height": 6 188 | }, 189 | "transform": [ 190 | { 191 | "type": "rotate_x", 192 | "rotation": 90 193 | }, 194 | { 195 | "type": "translate", 196 | "translation": [0, 23.8, 0] 197 | } 198 | ] 199 | }, 200 | { 201 | "name": "tall_cube", 202 | "type": "receiver", 203 | "material": "white_plastic", 204 | "geometry": { 205 | "type": "mesh", 206 | "file": "models/cube.obj", 207 | "model": "Cube" 208 | }, 209 | "transform": [ 210 | { 211 | "type": "scale", 212 | "scaling": [4, 10, 4] 213 | }, 214 | { 215 | "type": "rotate_y", 216 | "rotation": -20 217 | }, 218 | { 219 | "type": "translate", 220 | "translation": [-6, 5, 6] 221 | } 222 | ] 223 | }, 224 | { 225 | "name": "short_block", 226 | "type": "receiver", 227 | "material": "white_plastic", 228 | "geometry": { 229 | "type": "mesh", 230 | "file": "models/cube.obj", 231 | "model": "Cube" 232 | }, 233 | "transform": [ 234 | { 235 | "type": "scale", 236 | "scaling": [4, 5, 4] 237 | }, 238 | { 239 | "type": "rotate_y", 240 | "rotation": 15 241 | }, 242 | { 243 | "type": "translate", 244 | "translation": [4, 2.5, -3.0] 245 | } 246 | ] 247 | } 248 | ] 249 | } 250 | 251 | -------------------------------------------------------------------------------- /scenes/models/cube.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.63 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | o Cube 4 | v 1.000000 -1.000000 -1.000000 5 | v 1.000000 -1.000000 1.000000 6 | v -1.000000 -1.000000 1.000000 7 | v -1.000000 -1.000000 -1.000000 8 | v 1.000000 1.000000 -0.999999 9 | v 0.999999 1.000000 1.000001 10 | v -1.000000 1.000000 1.000000 11 | v -1.000000 1.000000 -1.000000 12 | vt 0.000000 0.334353 13 | vt 0.332314 0.333333 14 | vt 0.333333 0.665647 15 | vt 0.001019 0.666667 16 | vt 1.000000 0.001019 17 | vt 0.998981 0.333333 18 | vt 0.666667 0.332314 19 | vt 0.667686 0.000000 20 | vt 1.000000 0.665647 21 | vt 0.667686 0.666667 22 | vt 0.666667 0.334353 23 | vt 0.334353 0.666667 24 | vt 0.333333 0.334353 25 | vt 0.665647 0.333333 26 | vt 0.666667 0.665647 27 | vt 0.333333 0.332314 28 | vt 0.001020 0.333333 29 | vt 0.000000 0.001020 30 | vt 0.332314 0.000000 31 | vt 0.333333 0.001019 32 | vt 0.665647 0.000000 33 | vt 0.334353 0.333333 34 | vn 0.000000 -1.000000 0.000000 35 | vn 0.000000 1.000000 0.000000 36 | vn 1.000000 0.000000 0.000000 37 | vn -0.000000 -0.000000 1.000000 38 | vn -1.000000 -0.000000 -0.000000 39 | vn 0.000000 0.000000 -1.000000 40 | s off 41 | f 1/1/1 2/2/1 3/3/1 4/4/1 42 | f 5/5/2 8/6/2 7/7/2 6/8/2 43 | f 1/6/3 5/9/3 6/10/3 2/11/3 44 | f 2/12/4 6/13/4 7/14/4 3/15/4 45 | f 3/16/5 7/17/5 8/18/5 4/19/5 46 | f 5/20/6 1/21/6 4/7/6 8/22/6 47 | -------------------------------------------------------------------------------- /scenes/smallpt.json: -------------------------------------------------------------------------------- 1 | { 2 | "film": { 3 | "width": 800, 4 | "height": 600, 5 | "samples": 16, 6 | "frames": 1, 7 | "start_frame": 0, 8 | "end_frame": 0, 9 | "scene_time": 0, 10 | "filter" : { 11 | "type": "mitchell_netravali", 12 | "width": 2.0, 13 | "height": 2.0, 14 | "b": 0.333333333333333333, 15 | "c": 0.333333333333333333 16 | } 17 | }, 18 | "camera": { 19 | "fov": 30, 20 | "transform": [ 21 | { 22 | "type": "translate", 23 | "translation": [0, 12, -60] 24 | } 25 | ] 26 | }, 27 | "integrator": { 28 | "type": "pathtracer", 29 | "min_depth": 4, 30 | "max_depth": 8 31 | }, 32 | "materials": [ 33 | { 34 | "type": "matte", 35 | "name": "white_wall", 36 | "diffuse": [1.0, 1.0, 1.0], 37 | "roughness": 1.0 38 | }, 39 | { 40 | "type": "matte", 41 | "name": "red_wall", 42 | "diffuse": [1.0, 0.2, 0.2], 43 | "roughness": 1.0 44 | }, 45 | { 46 | "type": "matte", 47 | "name": "blue_wall", 48 | "diffuse": [0.2, 0.2, 1.0], 49 | "roughness": 1.0 50 | }, 51 | { 52 | "type": "metal", 53 | "name": "metal", 54 | "refractive_index": [0.155265, 0.116723, 0.138381], 55 | "absorption_coefficient": [4.82835, 3.12225, 2.14696], 56 | "roughness": 0.2 57 | }, 58 | { 59 | "type": "plastic", 60 | "name": "plastic", 61 | "gloss": [0.8, 0.8, 0.8], 62 | "diffuse": [0.8, 0.2, 0.2], 63 | "roughness": 0.02 64 | }, 65 | { 66 | "type": "glass", 67 | "name": "glass", 68 | "reflect": [1.0, 1.0, 1.0], 69 | "transmit": [1.0, 1.0, 1.0], 70 | "eta": 1.52 71 | } 72 | ], 73 | "objects": [ 74 | { 75 | "type": "group", 76 | "name": "walls", 77 | "transform": [ 78 | { 79 | "type": "translate", 80 | "translation": [0, 12, 0] 81 | } 82 | ], 83 | "objects": [ 84 | { 85 | "name": "back_wall", 86 | "type": "receiver", 87 | "material": "white_wall", 88 | "geometry": { 89 | "type": "plane" 90 | }, 91 | "transform": [ 92 | { 93 | "type": "scale", 94 | "scaling": 32.0 95 | }, 96 | { 97 | "type": "translate", 98 | "translation": [0, 0, 20] 99 | } 100 | ] 101 | }, 102 | { 103 | "name": "left_wall", 104 | "type": "receiver", 105 | "material": "red_wall", 106 | "geometry": { 107 | "type": "plane" 108 | }, 109 | "transform": [ 110 | { 111 | "type": "scale", 112 | "scaling": 32 113 | }, 114 | { 115 | "type": "rotate_y", 116 | "rotation": 90.0 117 | }, 118 | { 119 | "type": "translate", 120 | "translation": [-15.0, 0, 0] 121 | } 122 | ] 123 | }, 124 | { 125 | "name": "right_wall", 126 | "type": "receiver", 127 | "material": "blue_wall", 128 | "geometry": { 129 | "type": "plane" 130 | }, 131 | "transform": [ 132 | { 133 | "type": "scale", 134 | "scaling": 32.0 135 | }, 136 | { 137 | "type": "rotate_y", 138 | "rotation": -90.0 139 | }, 140 | { 141 | "type": "translate", 142 | "translation": [15.0, 0, 0] 143 | } 144 | ] 145 | }, 146 | { 147 | "name": "top_wall", 148 | "type": "receiver", 149 | "material": "white_wall", 150 | "geometry": { 151 | "type": "plane" 152 | }, 153 | "transform": [ 154 | { 155 | "type": "scale", 156 | "scaling": 32.0 157 | }, 158 | { 159 | "type": "rotate_x", 160 | "rotation": 90.0 161 | }, 162 | { 163 | "type": "translate", 164 | "translation": [0.0, 12, 0] 165 | } 166 | ] 167 | }, 168 | { 169 | "name": "bottom_wall", 170 | "type": "receiver", 171 | "material": "white_wall", 172 | "geometry": { 173 | "type": "plane" 174 | }, 175 | "transform": [ 176 | { 177 | "type": "scale", 178 | "scaling": 32.0 179 | }, 180 | { 181 | "type": "rotate_x", 182 | "rotation": 90 183 | }, 184 | { 185 | "type": "translate", 186 | "translation": [0.0, -12, 0] 187 | } 188 | ] 189 | } 190 | ] 191 | }, 192 | { 193 | "name": "metal_sphere", 194 | "type": "receiver", 195 | "material": "metal", 196 | "geometry": { 197 | "type": "sphere", 198 | "radius": 1.0 199 | }, 200 | "transform": [ 201 | { 202 | "type": "scale", 203 | "scaling": 5.0 204 | }, 205 | { 206 | "type": "translate", 207 | "translation": [-6.0, 5.0, 8.0] 208 | } 209 | ] 210 | }, 211 | { 212 | "name": "glass_sphere", 213 | "type": "receiver", 214 | "material": "glass", 215 | "geometry": { 216 | "type": "sphere", 217 | "radius": 1.0 218 | }, 219 | "transform": [ 220 | { 221 | "type": "scale", 222 | "scaling": 5.0 223 | }, 224 | { 225 | "type": "translate", 226 | "translation": [6.0, 5.0, -2.0] 227 | } 228 | ] 229 | }, 230 | { 231 | "name": "light", 232 | "type": "emitter", 233 | "material": "white_wall", 234 | "emitter": "area", 235 | "emission": [0.780131, 0.780409, 0.775833, 60], 236 | "geometry": { 237 | "type": "sphere", 238 | "radius": 1.0 239 | }, 240 | "transform": [ 241 | { 242 | "type": "translate", 243 | "translation": [0.0, 22, 0] 244 | } 245 | ] 246 | } 247 | ] 248 | } 249 | 250 | -------------------------------------------------------------------------------- /src/bxdf/fresnel.rs: -------------------------------------------------------------------------------- 1 | //! Provides the Fresnel term trait and implementations for conductors and dielectric materials 2 | 3 | use std::f32; 4 | 5 | use film::Colorf; 6 | use linalg; 7 | 8 | /// Compute the Fresnel term for a dielectric material given the incident and transmission 9 | /// angles and refractive indices 10 | fn dielectric(cos_i: f32, cos_t: f32, eta_i: f32, eta_t: f32) -> Colorf { 11 | let r_par = (eta_t * cos_i - eta_i * cos_t) / (eta_t * cos_i + eta_i * cos_t); 12 | let r_perp = (eta_i * cos_i - eta_t * cos_t) / (eta_i * cos_i + eta_t * cos_t); 13 | Colorf::broadcast(0.5 * (r_par * r_par + r_perp * r_perp)) 14 | } 15 | /// Compute the Fresnel term for a conductor given the incident angle and the material properties 16 | fn conductor(cos_i: f32, eta: &Colorf, k: &Colorf) -> Colorf { 17 | let a = (*eta * *eta + *k * *k) * cos_i * cos_i; 18 | let col = Colorf::broadcast(1.0); 19 | let r_par = (a - *eta * cos_i * 2.0 + col) / (a + *eta * cos_i * 2.0 + col); 20 | let b = *eta * *eta + *k * *k; 21 | let col = Colorf::broadcast(cos_i * cos_i); 22 | let r_perp = (b - *eta * cos_i * 2.0 + col) / (b + *eta * cos_i * 2.0 + col); 23 | //These are actually r_par^2 and r_perp^2, so don't square here 24 | (r_par + r_perp) * 0.5 25 | } 26 | 27 | /// The Fresnel trait implemented by the various Fresnel term components 28 | pub trait Fresnel { 29 | /// Compute the fresnel term for light incident to the object at angle `cos_i` 30 | fn fresnel(&self, cos_i: f32) -> Colorf; 31 | } 32 | 33 | /// Computes the Fresnel term for dielectric materials 34 | #[derive(Clone, Copy, Debug)] 35 | pub struct Dielectric { 36 | /// Refractive index of the material the light is coming from 37 | pub eta_i: f32, 38 | /// Refractive index of the material the light is hitting/entering 39 | pub eta_t: f32, 40 | } 41 | 42 | impl Dielectric { 43 | /// Create a new Dielectric Fresnel term for the boundary between two objects. 44 | /// `eta_i`: refractive index of the material the light is coming from. 45 | /// `eta_t`: refractive index of the material the light is entering. 46 | pub fn new(eta_i: f32, eta_t: f32) -> Dielectric { Dielectric { eta_i: eta_i, eta_t: eta_t } } 47 | } 48 | 49 | impl Fresnel for Dielectric { 50 | fn fresnel(&self, cos_i: f32) -> Colorf { 51 | // We need to find out which side of the material we're incident on so 52 | // we can pass the correct indices of refraction 53 | let ci = linalg::clamp(cos_i, -1.0, 1.0); 54 | let (ei, et) = 55 | if ci > 0.0 { 56 | (self.eta_i, self.eta_t) 57 | } else { 58 | (self.eta_t, self.eta_i) 59 | }; 60 | let sin_t = ei / et * f32::sqrt(f32::max(0.0, 1.0 - ci * ci)); 61 | // Handle total internal reflection 62 | if sin_t >= 1.0 { 63 | Colorf::broadcast(1.0) 64 | } else { 65 | let ct = f32::sqrt(f32::max(0.0, 1.0 - sin_t * sin_t)); 66 | dielectric(f32::abs(ci), ct, ei, et) 67 | } 68 | } 69 | } 70 | 71 | /// Computes the Fresnel term for conductive materials 72 | #[derive(Clone, Copy, Debug)] 73 | pub struct Conductor { 74 | /// Refractive index of the material being hit 75 | pub eta: Colorf, 76 | /// Absorption coefficient of the material being hit 77 | pub k: Colorf, 78 | } 79 | 80 | impl Conductor { 81 | /// Create a new Conductor Fresnel term for the object. 82 | /// `eta`: refractive index of the material. 83 | /// `k`: absorption coefficient of the material. 84 | pub fn new(eta: &Colorf, k: &Colorf) -> Conductor { Conductor { eta: *eta, k: *k } } 85 | } 86 | 87 | impl Fresnel for Conductor { 88 | fn fresnel(&self, cos_i: f32) -> Colorf { conductor(f32::abs(cos_i), &self.eta, &self.k) } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/bxdf/lambertian.rs: -------------------------------------------------------------------------------- 1 | //! Defines a Lambertion BRDF that describes perfectly diffuse surfaces. 2 | //! See [Lambertian reflectance](https://en.wikipedia.org/wiki/Lambertian_reflectance) 3 | 4 | use std::f32; 5 | use enum_set::EnumSet; 6 | 7 | use linalg::Vector; 8 | use film::Colorf; 9 | use bxdf::{BxDF, BxDFType}; 10 | 11 | /// Lambertian BRDF that implements the Lambertian reflectance model 12 | #[derive(Clone, Copy, Debug)] 13 | pub struct Lambertian { 14 | /// Color of the diffuse material 15 | reflectance: Colorf, 16 | } 17 | 18 | impl Lambertian { 19 | /// Create a new Lambertian BRDF with the desired reflective color property 20 | pub fn new(c: &Colorf) -> Lambertian { 21 | Lambertian { reflectance: *c } 22 | } 23 | } 24 | 25 | impl BxDF for Lambertian { 26 | fn bxdf_type(&self) -> EnumSet { 27 | let mut e = EnumSet::new(); 28 | e.insert(BxDFType::Diffuse); 29 | e.insert(BxDFType::Reflection); 30 | e 31 | } 32 | fn eval(&self, _: &Vector, _: &Vector) -> Colorf { 33 | self.reflectance * f32::consts::FRAC_1_PI 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/bxdf/merl.rs: -------------------------------------------------------------------------------- 1 | //! The MERL BRDF represents the surface's properties through data loaded from a 2 | //! [MERL BRDF Database file](http://www.merl.com/brdf/). The BRDF itself just stores 3 | //! the data loaded from the BRDF file while actual loading is done by the MERL material 4 | //! when it's created. 5 | 6 | use std::f32; 7 | use enum_set::EnumSet; 8 | 9 | use linalg::{self, Vector}; 10 | use film::Colorf; 11 | use bxdf::{self, BxDF, BxDFType}; 12 | 13 | /// BRDF that uses measured data to model the surface reflectance properties. 14 | /// The measured data is from "A Data-Driven Reflectance Model", 15 | /// by Wojciech Matusik, Hanspeter Pfister, Matt Brand and Leonard McMillan, 16 | /// in ACM Transactions on Graphics 22, 3(2003), 759-769 17 | #[derive(Copy, Clone)] 18 | pub struct Merl<'a> { 19 | /// Vec containing the BRDF values for various incident/exiting angles 20 | brdf: &'a [f32], 21 | /// Number of theta_h measurements in `brdf` 22 | n_theta_h: usize, 23 | /// Number of theta_d measurements in `brdf` 24 | n_theta_d: usize, 25 | /// Number of phi_d measurements in `brdf` 26 | n_phi_d: usize, 27 | } 28 | 29 | impl<'a> Merl<'a> { 30 | /// Create a MERL BRDF to use data loaded from a MERL BRDF data file 31 | pub fn new(brdf: &'a [f32], n_theta_h: usize, n_theta_d: usize, n_phi_d: usize) -> Merl<'a> { 32 | Merl { brdf: brdf, n_theta_h: n_theta_h, n_theta_d: n_theta_d, n_phi_d: n_phi_d } 33 | } 34 | /// Re-map values from an angular value to the index in the MERL data table 35 | fn map_index(val: f32, max: f32, n_vals: usize) -> usize { 36 | linalg::clamp((val / max * n_vals as f32) as usize, 0, n_vals - 1) 37 | } 38 | } 39 | 40 | impl<'a> BxDF for Merl<'a> { 41 | fn bxdf_type(&self) -> EnumSet { 42 | let mut e = EnumSet::new(); 43 | e.insert(BxDFType::Glossy); 44 | e.insert(BxDFType::Reflection); 45 | e 46 | } 47 | fn eval(&self, w_oi: &Vector, w_ii: &Vector) -> Colorf { 48 | // Find the half-vector and transform into the half angle coordinate system used by MERL 49 | // BRDF files 50 | let mut w_i = *w_ii; 51 | let mut w_h = *w_oi + w_i; 52 | if w_h.z < 0.0 { 53 | w_i = -w_i; 54 | w_h = -w_h; 55 | } 56 | if w_h.length_sqr() == 0.0 { 57 | return Colorf::black(); 58 | } 59 | 60 | let w_h = w_h.normalized(); 61 | // Directly compute the rows of the matrix performing the rotation of w_h to (0, 0, 1) 62 | let theta_h = linalg::spherical_theta(&w_h); 63 | let cos_phi_h = bxdf::cos_phi(&w_h); 64 | let sin_phi_h = bxdf::sin_phi(&w_h); 65 | let cos_theta_h = bxdf::cos_theta(&w_h); 66 | let sin_theta_h = bxdf::sin_theta(&w_h); 67 | let w_hx = Vector::new(cos_phi_h * cos_theta_h, sin_phi_h * cos_theta_h, -sin_theta_h); 68 | let w_hy = Vector::new(-sin_phi_h, cos_phi_h, 0.0); 69 | let w_d = Vector::new(linalg::dot(&w_i, &w_hx), linalg::dot(&w_i, &w_hy), linalg::dot(&w_i, &w_h)); 70 | let theta_d = linalg::spherical_theta(&w_d); 71 | // Wrap phi_d if needed to keep it in range 72 | let phi_d = match linalg::spherical_phi(&w_d) { 73 | d if d > f32::consts::PI => d - f32::consts::PI, 74 | d => d, 75 | }; 76 | let theta_h_idx = Merl::map_index(f32::sqrt(f32::max(0.0, 2.0 * theta_h / f32::consts::PI)), 1.0, self.n_theta_h); 77 | let theta_d_idx = Merl::map_index(theta_d, f32::consts::PI / 2.0, self.n_theta_d); 78 | let phi_d_idx = Merl::map_index(phi_d, f32::consts::PI, self.n_phi_d); 79 | let i = phi_d_idx + self.n_phi_d * (theta_d_idx + theta_h_idx * self.n_theta_d); 80 | assert!(i < self.brdf.len()); 81 | Colorf::new(self.brdf[3 * i], self.brdf[3 * i + 1], self.brdf[3 * i + 2]) 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /src/bxdf/microfacet/beckmann.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a Beckmann microfacet distribution with a 2 | //! Smith shadowing-masking term 3 | 4 | use std::f32; 5 | 6 | use bxdf; 7 | use linalg::{self, Vector}; 8 | use bxdf::microfacet::MicrofacetDistribution; 9 | 10 | /// Beckmann microfacet distribution with Smith shadowing-masking. This is the 11 | /// microfacet model described by [Walter et al.](https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf) 12 | #[derive(Copy, Clone)] 13 | pub struct Beckmann { 14 | width: f32, 15 | } 16 | 17 | impl Beckmann { 18 | /// Create a new Beckmann distribution with the desired width 19 | pub fn new(w: f32) -> Beckmann { 20 | let roughness = f32::max(w, 0.000001); 21 | Beckmann { width: roughness } 22 | } 23 | } 24 | 25 | impl MicrofacetDistribution for Beckmann { 26 | fn normal_distribution(&self, w_h: &Vector) -> f32 { 27 | let tan_sqr = bxdf::tan_theta_sqr(w_h); 28 | if f32::is_infinite(tan_sqr) { 29 | 0.0 30 | } else { 31 | let cos_theta_4 = f32::powf(bxdf::cos_theta_sqr(w_h), 2.0); 32 | let width_sqr = f32::powf(self.width, 2.0); 33 | f32::exp(-tan_sqr / width_sqr) / (f32::consts::PI * width_sqr * cos_theta_4) 34 | } 35 | } 36 | fn sample(&self, _: &Vector, samples: &(f32, f32)) -> Vector { 37 | let log_sample = match f32::ln(1.0 - samples.0) { 38 | x if f32::is_infinite(x) => 0.0, 39 | x => x, 40 | }; 41 | let tan_theta_sqr = -f32::powf(self.width, 2.0) * log_sample; 42 | let phi = 2.0 * f32::consts::PI * samples.1; 43 | let cos_theta = 1.0 / f32::sqrt(1.0 + tan_theta_sqr); 44 | let sin_theta = f32::sqrt(f32::max(0.0, 1.0 - cos_theta * cos_theta)); 45 | linalg::spherical_dir(sin_theta, cos_theta, phi) 46 | } 47 | fn pdf(&self, w_h: &Vector) -> f32 { 48 | f32::abs(w_h.z) * self.normal_distribution(w_h) 49 | } 50 | fn shadowing_masking(&self, w_i: &Vector, w_o: &Vector, w_h: &Vector) -> f32 { 51 | self.monodir_shadowing(w_i, w_h) * self.monodir_shadowing(w_o, w_h) 52 | } 53 | /// Monodirectional shadowing function from Walter et al., we use the Smith 54 | /// shadowing-masking which uses the reciprocity of this function. 55 | /// `w` is the incident/outgoing light direction and `w_h` is the microfacet normal 56 | fn monodir_shadowing(&self, v: &Vector, w_h: &Vector) -> f32 { 57 | let a = 1.0 / (self.width * f32::abs(bxdf::tan_theta(v))); 58 | if a < 1.6 { 59 | let a_sqr = f32::powf(a, 2.0); 60 | (3.535 * a + 2.181 * a_sqr) / (1.0 + 2.276 * a + 2.577 * a_sqr) 61 | } else { 62 | 1.0 63 | } 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/bxdf/microfacet/ggx.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a GGX microfacet distribution with a 2 | //! Smith shadowing-masking term. The GGX microfacet distribution 3 | //! is also sometimes referred to as Trowbridge-Reitz. 4 | 5 | use std::f32; 6 | 7 | use bxdf; 8 | use linalg::{self, Vector}; 9 | use bxdf::microfacet::MicrofacetDistribution; 10 | 11 | /// GGX microfacet distribution with Smith shadowing-masking. This is the 12 | /// microfacet model described by [Walter et al.](https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf) 13 | #[derive(Copy, Clone)] 14 | pub struct GGX { 15 | width: f32, 16 | } 17 | 18 | impl GGX { 19 | /// Create a new GGX distribution with the desired width 20 | pub fn new(w: f32) -> GGX { 21 | let roughness = f32::max(w, 0.000001); 22 | GGX { width: roughness } 23 | } 24 | } 25 | 26 | impl MicrofacetDistribution for GGX { 27 | fn normal_distribution(&self, w_h: &Vector) -> f32 { 28 | if bxdf::cos_theta(w_h) > 0.0 { 29 | let width_sqr = f32::powf(self.width, 2.0); 30 | let denom = f32::consts::PI * f32::powf(bxdf::cos_theta(w_h), 4.0) 31 | * f32::powf(width_sqr + f32::powf(bxdf::tan_theta(w_h), 2.0), 2.0); 32 | width_sqr / denom 33 | } else { 34 | 0.0 35 | } 36 | } 37 | fn sample(&self, _: &Vector, samples: &(f32, f32)) -> Vector { 38 | let tan_theta_sqr = f32::powf(self.width * f32::sqrt(samples.0) / f32::sqrt(1.0 - samples.0), 2.0); 39 | let cos_theta = 1.0 / f32::sqrt(1.0 + tan_theta_sqr); 40 | let sin_theta = f32::sqrt(f32::max(0.0, 1.0 - cos_theta * cos_theta)); 41 | let phi = 2.0 * f32::consts::PI * samples.1; 42 | linalg::spherical_dir(sin_theta, cos_theta, phi) 43 | } 44 | fn pdf(&self, w_h: &Vector) -> f32 { 45 | f32::abs(bxdf::cos_theta(w_h)) * self.normal_distribution(w_h) 46 | } 47 | fn shadowing_masking(&self, w_i: &Vector, w_o: &Vector, w_h: &Vector) -> f32 { 48 | self.monodir_shadowing(w_i, w_h) * self.monodir_shadowing(w_o, w_h) 49 | } 50 | /// Monodirectional shadowing function from Walter et al., we use the Smith 51 | /// shadowing-masking which uses the reciprocity of this function. 52 | /// `w` is the incident/outgoing light direction and `w_h` is the microfacet normal 53 | fn monodir_shadowing(&self, v: &Vector, w_h: &Vector) -> f32 { 54 | 2.0 / (1.0 + f32::sqrt(1.0 + f32::powf(self.width * f32::abs(bxdf::tan_theta(v)), 2.0))) 55 | 56 | } 57 | } 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/bxdf/microfacet/mod.rs: -------------------------------------------------------------------------------- 1 | //! Module providing various microfacet distribution functions and trait that's 2 | //! implemented by all provided distributions 3 | 4 | use linalg::Vector; 5 | 6 | pub use self::beckmann::Beckmann; 7 | pub use self::ggx::GGX; 8 | 9 | pub mod beckmann; 10 | pub mod ggx; 11 | 12 | /// Trait implemented by all microfacet distributions 13 | pub trait MicrofacetDistribution { 14 | /// Compute the probability that microfacets are 15 | /// oriented with normal `w_h` in this distribution 16 | fn normal_distribution(&self, w_h: &Vector) -> f32; 17 | /// Sample the distribution for some outgoing light direction `w_o`. 18 | /// returns the sampled microfacet normal 19 | fn sample(&self, w_o: &Vector, samples: &(f32, f32)) -> Vector; 20 | /// Compute the probability of sampling a certain microfacet normal 21 | /// from the distribution, `w_h` 22 | fn pdf(&self, w_h: &Vector) -> f32; 23 | /// Compute the shadowing masking function for the incident and outgoing 24 | /// directions `w_i` and `w_o` for microfacets with normal `w_h`. 25 | /// Returns what fraction of the microfacets with the normal are visible 26 | /// in both directions. 27 | fn shadowing_masking(&self, w_i: &Vector, w_o: &Vector, w_h: &Vector) -> f32; 28 | /// Return the monodirectional shadowing function, G_1 29 | /// `v` is the reflected/incident direction, `w_h` is the microfacet normal 30 | fn monodir_shadowing(&self, v: &Vector, w_h: &Vector) -> f32; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/bxdf/microfacet_transmission.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a microfacet model for transmission (a BTDF), see 2 | //! [Walter et al. 07](https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf) 3 | //! for details. 4 | 5 | use std::f32; 6 | use enum_set::EnumSet; 7 | 8 | use linalg::{self, Vector}; 9 | use film::Colorf; 10 | use bxdf::{self, BxDF, BxDFType}; 11 | use bxdf::fresnel::{Dielectric, Fresnel}; 12 | use bxdf::microfacet::{MicrofacetDistribution}; 13 | 14 | /// Struct providing the microfacet BTDF, implemented as described in 15 | /// [Walter et al. 07](https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf) 16 | #[derive(Copy, Clone)] 17 | pub struct MicrofacetTransmission<'a> { 18 | reflectance: Colorf, 19 | fresnel: &'a Dielectric, 20 | /// Microfacet distribution describing the structure of the microfacets of 21 | /// the material 22 | microfacet: &'a MicrofacetDistribution, 23 | } 24 | 25 | impl<'a> MicrofacetTransmission<'a> { 26 | /// Create a new transmissive microfacet BRDF 27 | pub fn new(c: &Colorf, fresnel: &'a Dielectric, microfacet: &'a MicrofacetDistribution) 28 | -> MicrofacetTransmission<'a> { 29 | MicrofacetTransmission { reflectance: *c, fresnel: fresnel, microfacet: microfacet } 30 | } 31 | /// Convenience method for getting `eta_i` and `eta_t` in the right order for if 32 | /// we're entering or exiting this material based on the direction of the outgoing 33 | /// ray. 34 | fn eta_for_interaction(&self, w_o: &Vector) -> (f32, f32) { 35 | if bxdf::cos_theta(w_o) > 0.0 { 36 | (self.fresnel.eta_i, self.fresnel.eta_t) 37 | } else { 38 | (self.fresnel.eta_t, self.fresnel.eta_i) 39 | } 40 | } 41 | /// Compute the Jacobian for the change of variables (see [Walter et al 07] section 4.2), 42 | /// here we compute equation 17 in that section. 43 | fn jacobian(w_o: &Vector, w_i: &Vector, w_h: &Vector, eta: (f32, f32)) -> f32 { 44 | let wi_dot_h = linalg::dot(w_i, w_h); 45 | let wo_dot_h = linalg::dot(w_o, w_h); 46 | let denom = f32::powf(eta.1 * wi_dot_h + eta.0 * wo_dot_h, 2.0); 47 | if denom != 0.0 { 48 | f32::abs(f32::powf(eta.0, 2.0) * f32::abs(wo_dot_h) / denom) 49 | } else { 50 | 0.0 51 | } 52 | } 53 | fn half_vector(w_o: &Vector, w_i: &Vector, eta: (f32, f32)) -> Vector { 54 | (-eta.1 * *w_i - eta.0 * *w_o).normalized() 55 | } 56 | } 57 | 58 | impl<'a> BxDF for MicrofacetTransmission<'a> { 59 | fn bxdf_type(&self) -> EnumSet { 60 | let mut e = EnumSet::new(); 61 | e.insert(BxDFType::Glossy); 62 | e.insert(BxDFType::Transmission); 63 | e 64 | } 65 | fn eval(&self, w_o: &Vector, w_i: &Vector) -> Colorf { 66 | if bxdf::same_hemisphere(w_o, w_i) { 67 | return Colorf::black(); 68 | } 69 | let cos_to = bxdf::cos_theta(w_o); 70 | let cos_ti = bxdf::cos_theta(w_i); 71 | if cos_to == 0.0 || cos_ti == 0.0 { 72 | return Colorf::black(); 73 | } 74 | let eta = self.eta_for_interaction(w_o); 75 | let w_h = MicrofacetTransmission::half_vector(w_o, w_i, eta); 76 | let d = self.microfacet.normal_distribution(&w_h); 77 | let f = Colorf::broadcast(1.0) - self.fresnel.fresnel(linalg::dot(w_i, &w_h)); 78 | let g = self.microfacet.shadowing_masking(w_i, w_o, &w_h); 79 | let wi_dot_h = linalg::dot(w_i, &w_h); 80 | let jacobian = MicrofacetTransmission::jacobian(w_o, w_i, &w_h, eta); 81 | self.reflectance * (f32::abs(wi_dot_h) / (f32::abs(w_i.z) * f32::abs(w_o.z))) 82 | * (f * g * d) * jacobian 83 | } 84 | fn sample(&self, w_o: &Vector, samples: &(f32, f32)) -> (Colorf, Vector, f32) { 85 | let mut w_h = self.microfacet.sample(w_o, samples); 86 | if !bxdf::same_hemisphere(w_o, &w_h) { 87 | w_h = -w_h; 88 | } 89 | let eta = self.eta_for_interaction(w_o); 90 | if let Some(w_i) = linalg::refract(w_o, &w_h, eta.0 / eta.1) { 91 | if bxdf::same_hemisphere(w_o, &w_i) { 92 | (Colorf::black(), Vector::broadcast(0.0), 0.0) 93 | } else { 94 | (self.eval(w_o, &w_i), w_i, self.pdf(w_o, &w_i)) 95 | } 96 | } else { 97 | (Colorf::black(), Vector::broadcast(0.0), 0.0) 98 | } 99 | } 100 | fn pdf(&self, w_o: &Vector, w_i: &Vector) -> f32 { 101 | if bxdf::same_hemisphere(w_o, w_i) { 102 | 0.0 103 | } else { 104 | let eta = self.eta_for_interaction(w_o); 105 | let w_h = MicrofacetTransmission::half_vector(w_o, w_i, eta); 106 | self.microfacet.pdf(&w_h) * MicrofacetTransmission::jacobian(w_o, w_i, &w_h, eta) 107 | } 108 | } 109 | } 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/bxdf/oren_nayar.rs: -------------------------------------------------------------------------------- 1 | //! Defines an Oren-Nayar BRDF that describes diffuse reflection from rough surfaces 2 | //! See [Oren-Nayar reflectance model](https://en.wikipedia.org/wiki/Oren%E2%80%93Nayar_reflectance_model) 3 | 4 | use std::f32; 5 | use enum_set::EnumSet; 6 | 7 | use linalg::{self, Vector}; 8 | use film::Colorf; 9 | use bxdf::{self, BxDF, BxDFType}; 10 | 11 | /// Oren-Nayar BRDF that implements the Oren-Nayar reflectance model 12 | #[derive(Clone, Copy, Debug)] 13 | pub struct OrenNayar { 14 | /// Color of the diffuse material 15 | albedo: Colorf, 16 | /// Precomputed and stored value of the A constant 17 | a: f32, 18 | /// Precomputed and stored value of the B constant 19 | b: f32, 20 | } 21 | 22 | impl OrenNayar { 23 | /// Create a new Oren-Nayar BRDF with the desired color and roughness 24 | /// `roughness` should be the variance of the Gaussian describing the 25 | /// microfacet distribution 26 | pub fn new(c: &Colorf, roughness: f32) -> OrenNayar { 27 | let mut sigma = linalg::to_radians(roughness); 28 | sigma *= sigma; 29 | OrenNayar { albedo: *c, 30 | a: 1.0 - 0.5 * sigma / (sigma + 0.33), 31 | b: 0.45 * sigma / (sigma + 0.09), 32 | } 33 | } 34 | } 35 | 36 | impl BxDF for OrenNayar { 37 | fn bxdf_type(&self) -> EnumSet { 38 | let mut e = EnumSet::new(); 39 | e.insert(BxDFType::Diffuse); 40 | e.insert(BxDFType::Reflection); 41 | e 42 | } 43 | fn eval(&self, w_o: &Vector, w_i: &Vector) -> Colorf { 44 | let sin_theta_o = bxdf::sin_theta(w_o); 45 | let sin_theta_i = bxdf::sin_theta(w_i); 46 | let max_cos = 47 | if sin_theta_i > 1e-4 && sin_theta_o > 1e-4 { 48 | f32::max(0.0, bxdf::cos_phi(w_i) * bxdf::cos_phi(w_o) 49 | + bxdf::sin_phi(w_i) * bxdf::sin_phi(w_o)) 50 | } else { 51 | 0.0 52 | }; 53 | let (sin_alpha, tan_beta) = 54 | if f32::abs(bxdf::cos_theta(w_i)) > f32::abs(bxdf::cos_theta(w_o)) { 55 | (sin_theta_o, sin_theta_i / f32::abs(bxdf::cos_theta(w_i))) 56 | } else { 57 | (sin_theta_i, sin_theta_o / f32::abs(bxdf::cos_theta(w_o))) 58 | }; 59 | self.albedo * f32::consts::FRAC_1_PI * (self.a + self.b * max_cos * sin_alpha * tan_beta) 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/bxdf/specular_reflection.rs: -------------------------------------------------------------------------------- 1 | //! Defines a BRDF that describes specular reflection 2 | 3 | use std::f32; 4 | use enum_set::EnumSet; 5 | 6 | use linalg::Vector; 7 | use film::Colorf; 8 | use bxdf::{self, BxDF, BxDFType}; 9 | use bxdf::fresnel::Fresnel; 10 | 11 | /// Specular reflection BRDF that implements a specularly reflective material model 12 | #[derive(Copy, Clone)] 13 | pub struct SpecularReflection<'a> { 14 | /// Color of the reflective material 15 | reflectance: Colorf, 16 | /// Fresnel term for the reflection model 17 | fresnel: &'a Fresnel, 18 | } 19 | 20 | impl<'a> SpecularReflection<'a> { 21 | /// Create a specularly reflective BRDF with the reflective color and Fresnel term 22 | pub fn new(c: &Colorf, fresnel: &'a Fresnel) -> SpecularReflection<'a> { 23 | SpecularReflection { reflectance: *c, fresnel: fresnel } 24 | } 25 | } 26 | 27 | impl<'a> BxDF for SpecularReflection<'a> { 28 | fn bxdf_type(&self) -> EnumSet { 29 | let mut e = EnumSet::new(); 30 | e.insert(BxDFType::Specular); 31 | e.insert(BxDFType::Reflection); 32 | e 33 | } 34 | /// We'll never exactly hit the specular reflection direction with some pair 35 | /// so this just returns black. Use `sample` instead 36 | fn eval(&self, _: &Vector, _: &Vector) -> Colorf { Colorf::broadcast(0.0) } 37 | /// Sampling the specular BRDF just returns the specular reflection direction 38 | /// for the light leaving along `w_o` 39 | fn sample(&self, w_o: &Vector, _: &(f32, f32)) -> (Colorf, Vector, f32) { 40 | let w_i = Vector::new(-w_o.x, -w_o.y, w_o.z); 41 | // TODO: is this an expected but super rare case? or does it imply some error 42 | // in the sphere intersection? Such a glancing angle shouldn't really be counted right? 43 | if w_i.z != 0.0 { 44 | let c = self.fresnel.fresnel(bxdf::cos_theta(w_o)) * self.reflectance 45 | / f32::abs(bxdf::cos_theta(&w_i)); 46 | (c, w_i, 1.0) 47 | } else { 48 | (Colorf::black(), w_i, 0.0) 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/bxdf/specular_transmission.rs: -------------------------------------------------------------------------------- 1 | //! Defines a BTDF that describes specular transmission 2 | 3 | use std::f32; 4 | use enum_set::EnumSet; 5 | 6 | use linalg::{self, Vector}; 7 | use film::Colorf; 8 | use bxdf::{self, BxDF, BxDFType}; 9 | use bxdf::fresnel::{Fresnel, Dielectric}; 10 | 11 | /// Specular transmission BTDF that implements a specularly transmissive material model 12 | #[derive(Clone, Copy)] 13 | pub struct SpecularTransmission<'a> { 14 | /// Color of the transmissited light 15 | transmission: Colorf, 16 | /// Fresnel term for the tranmission model, only dielectrics make sense here 17 | fresnel: &'a Dielectric, 18 | } 19 | 20 | impl<'a> SpecularTransmission<'a> { 21 | /// Create a specularly transmissive BTDF with the color and Fresnel term 22 | pub fn new(c: &Colorf, fresnel: &'a Dielectric) -> SpecularTransmission<'a> { 23 | SpecularTransmission { transmission: *c, fresnel: fresnel } 24 | } 25 | } 26 | 27 | impl<'a> BxDF for SpecularTransmission<'a> { 28 | fn bxdf_type(&self) -> EnumSet { 29 | let mut e = EnumSet::new(); 30 | e.insert(BxDFType::Specular); 31 | e.insert(BxDFType::Transmission); 32 | e 33 | } 34 | /// We'll never exactly hit the specular transmission direction with some pair 35 | /// so this just returns black. Use `sample` instead 36 | fn eval(&self, _: &Vector, _: &Vector) -> Colorf { Colorf::broadcast(0.0) } 37 | /// Sampling the specular BTDF just returns the specular transmission direction 38 | /// for the light leaving along `w_o` 39 | fn sample(&self, w_o: &Vector, _: &(f32, f32)) -> (Colorf, Vector, f32) { 40 | // Select the incident and transmited indices of refraction based on whether 41 | // we're entering or exiting the material 42 | let entering = bxdf::cos_theta(w_o) > 0.0; 43 | let (ei, et, n) = 44 | if entering { 45 | (self.fresnel.eta_i, self.fresnel.eta_t, Vector::new(0.0, 0.0, 1.0)) 46 | } else { 47 | (self.fresnel.eta_t, self.fresnel.eta_i, Vector::new(0.0, 0.0, -1.0)) 48 | }; 49 | if let Some(w_i) = linalg::refract(w_o, &n, ei / et) { 50 | let f = Colorf::broadcast(1.0) - self.fresnel.fresnel(bxdf::cos_theta(&w_i)); 51 | let c = f * self.transmission / f32::abs(bxdf::cos_theta(&w_i)); 52 | (c, w_i, 1.0) 53 | } else { 54 | (Colorf::black(), Vector::broadcast(0.0), 0.0) 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/bxdf/torrance_sparrow.rs: -------------------------------------------------------------------------------- 1 | //! This module provides the Torrance Sparrow microfacet BRDF, see 2 | //! [Walter et al. 07](https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf) 3 | //! for details. 4 | 5 | use std::f32; 6 | use enum_set::EnumSet; 7 | 8 | use linalg::{self, Vector}; 9 | use film::Colorf; 10 | use bxdf::{self, BxDF, BxDFType}; 11 | use bxdf::fresnel::Fresnel; 12 | use bxdf::microfacet::{MicrofacetDistribution}; 13 | 14 | /// Struct providing the Torrance Sparrow BRDF, implemented as described in 15 | /// [Walter et al. 07](https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf) 16 | #[derive(Copy, Clone)] 17 | pub struct TorranceSparrow<'a> { 18 | reflectance: Colorf, 19 | fresnel: &'a Fresnel, 20 | /// Microfacet distribution describing the structure of the microfacets of 21 | /// the material 22 | microfacet: &'a MicrofacetDistribution, 23 | } 24 | 25 | impl<'a> TorranceSparrow<'a> { 26 | /// Create a new Torrance Sparrow microfacet BRDF 27 | pub fn new(c: &Colorf, fresnel: &'a Fresnel, microfacet: &'a MicrofacetDistribution) 28 | -> TorranceSparrow<'a> { 29 | TorranceSparrow { reflectance: *c, fresnel: fresnel, microfacet: microfacet } 30 | } 31 | } 32 | 33 | impl<'a> BxDF for TorranceSparrow<'a> { 34 | fn bxdf_type(&self) -> EnumSet { 35 | let mut e = EnumSet::new(); 36 | e.insert(BxDFType::Glossy); 37 | e.insert(BxDFType::Reflection); 38 | e 39 | } 40 | fn eval(&self, w_o: &Vector, w_i: &Vector) -> Colorf { 41 | let cos_to = f32::abs(bxdf::cos_theta(w_o)); 42 | let cos_ti = f32::abs(bxdf::cos_theta(w_i)); 43 | if cos_to == 0.0 || cos_ti == 0.0 { 44 | return Colorf::new(0.0, 0.0, 0.0) 45 | } 46 | let mut w_h = *w_i + *w_o; 47 | if w_h == Vector::broadcast(0.0) { 48 | return Colorf::new(0.0, 0.0, 0.0) 49 | } 50 | w_h = w_h.normalized(); 51 | let d = self.microfacet.normal_distribution(&w_h); 52 | let f = self.fresnel.fresnel(linalg::dot(w_i, &w_h)); 53 | let g = self.microfacet.shadowing_masking(w_i, w_o, &w_h); 54 | (self.reflectance * f * d * g / (4.0 * cos_ti * cos_to)) 55 | } 56 | fn sample(&self, w_o: &Vector, samples: &(f32, f32)) -> (Colorf, Vector, f32) { 57 | if w_o.z == 0.0 { 58 | return (Colorf::black(), Vector::broadcast(0.0), 0.0) 59 | } 60 | let mut w_h = self.microfacet.sample(w_o, samples); 61 | if !bxdf::same_hemisphere(w_o, &w_h) { 62 | w_h = -w_h; 63 | } 64 | let w_i = linalg::reflect(w_o, &w_h); 65 | if !bxdf::same_hemisphere(w_o, &w_i) { 66 | (Colorf::black(), Vector::broadcast(0.0), 0.0) 67 | } else { 68 | (self.eval(w_o, &w_i), w_i, self.pdf(w_o, &w_i)) 69 | } 70 | } 71 | fn pdf(&self, w_o: &Vector, w_i: &Vector) -> f32 { 72 | if !bxdf::same_hemisphere(w_o, w_i) { 73 | 0.0 74 | } else { 75 | let w_h = (*w_o + *w_i).normalized(); 76 | // This term is p_o(o) in eq. 38 of Walter et al's 07 paper and is for reflection so 77 | // we use the Jacobian for reflection, eq. 14 78 | let jacobian = 1.0 / (4.0 * f32::abs(linalg::dot(w_o, &w_h))); 79 | self.microfacet.pdf(&w_h) * jacobian 80 | } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/exec/distrib/mod.rs: -------------------------------------------------------------------------------- 1 | //! The distrib module provides methods for executing the rendering in a 2 | //! distributed environment across multiple machines. The worker module provides 3 | //! the Worker which does the actual job of rendering a subregion of the image. 4 | //! The master module provides the Master which instructs the Workers what to render 5 | //! and collects their results to save out the final image. 6 | //! 7 | //! # Usage 8 | //! 9 | //! The worker process takes very few arguments, just a flag indicating it's a worker 10 | //! and optionally the number of threads to use with `-n`. 11 | //! 12 | //! ```text 13 | //! ./tray_rust --worker 14 | //! ``` 15 | //! 16 | //! The worker processes will listen on a hard-coded port for the master to send them instructions 17 | //! about what parts of the image they should render. This is `exec::distrib::worker::PORT` which 18 | //! you can change and re-compile if the default of 63234 conflicts with other applications. 19 | //! 20 | //! The master process can be run on the same machine as a worker since it doesn't take 21 | //! up too much CPU time. To run the master you'll pass it the scene file, a list of the 22 | //! worker hostnames or IP addresses and optionally an output path and start/end frame numbers. 23 | //! You can also run tray\_rust with the `-h` or `--help` flag to see a list of options. 24 | //! 25 | //! ```text 26 | //! ./tray_rust cornell_box.json --master worker1 worker2 192.168.32.129 27 | //! ``` 28 | //! 29 | //! The master will send the workers the location of the scene file which is assumed to 30 | //! be on some shared filesystem or otherwise available at the same path on all the workers. 31 | //! 32 | //! # Running on GCE or EC2 33 | //! 34 | //! You can run on any network of home machines but you can also run on virtual machines from 35 | //! Google or Amazon if you want to rent a mini cluster. On GCE or EC2 you'll want machines in the 36 | //! same region for faster communication and will then pass the local IPs of the workers to 37 | //! the master. For example on GCE you're given a virtual local network, you would use 38 | //! these IP addresses instead of the public IPs of the worker nodes. 39 | //! 40 | 41 | use bincode::serialized_size; 42 | 43 | pub use self::worker::Worker; 44 | pub use self::master::Master; 45 | 46 | pub mod worker; 47 | pub mod master; 48 | 49 | /// Stores instructions sent to a worker about which blocks it should be rendering, 50 | /// block size is assumed to be 8x8 51 | #[derive(Debug, Clone, Serialize, Deserialize)] 52 | struct Instructions { 53 | /// Size header for binary I/O with bincode 54 | pub encoded_size: u64, 55 | /// Scene file for the worker to load 56 | pub scene: String, 57 | /// Frames to be rendered (inclusive) 58 | pub frames: (usize, usize), 59 | /// Block in the z-order queue of blocks this worker will 60 | /// start at 61 | pub block_start: usize, 62 | /// Number of blocks this worker will render 63 | pub block_count: usize, 64 | } 65 | 66 | impl Instructions { 67 | pub fn new(scene: &str, frames: (usize, usize), block_start: usize, 68 | block_count: usize) -> Instructions { 69 | let mut instr = Instructions { encoded_size: 0, scene: scene.to_owned(), frames: frames, 70 | block_start: block_start, block_count: block_count }; 71 | instr.encoded_size = serialized_size(&instr); 72 | instr 73 | } 74 | } 75 | 76 | /// Frame is used by the worker to send its results back to the master. Sends information 77 | /// about which frame is being sent, which blocks were rendered and the data for the blocks 78 | #[derive(Serialize, Deserialize)] 79 | struct Frame { 80 | /// Size header for binary I/O with bincode 81 | pub encoded_size: u64, 82 | /// Which frame the worker is sending its results for 83 | pub frame: usize, 84 | /// Block size of the blocks being sent 85 | pub block_size: (usize, usize), 86 | /// Starting locations of each block 87 | pub blocks: Vec<(usize, usize)>, 88 | /// Sample data for each block, RGBW_F32 (W = weight) 89 | pub pixels: Vec, 90 | } 91 | 92 | impl Frame { 93 | pub fn new(frame: usize, block_size: (usize, usize), blocks: Vec<(usize, usize)>, 94 | pixels: Vec) -> Frame { 95 | let mut frame = Frame { encoded_size: 0, frame: frame, block_size: block_size, 96 | blocks: blocks, pixels: pixels }; 97 | frame.encoded_size = serialized_size(&frame); 98 | frame 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/exec/distrib/worker.rs: -------------------------------------------------------------------------------- 1 | //! The worker module provides the Worker struct which receives instructions from 2 | //! the master, renders and reports back its results 3 | 4 | use std::path::PathBuf; 5 | use std::io::prelude::*; 6 | use std::net::{TcpListener, TcpStream}; 7 | use std::iter; 8 | 9 | use bincode::{Infinite, serialize, deserialize}; 10 | 11 | use scene::Scene; 12 | use film::RenderTarget; 13 | use exec::Config; 14 | use exec::distrib::{Instructions, Frame}; 15 | 16 | /// Port that the workers listen for the master on 17 | pub static PORT: u16 = 63234; 18 | 19 | /// A worker process for distributed rendering. Accepts instructions from 20 | /// the master process telling it what to render, after each frame is finished 21 | /// results are sent back to the master and the next frame is started. Once all 22 | /// frames are finished the worker exits 23 | pub struct Worker { 24 | instructions: Instructions, 25 | /// Render target the worker will write the current frame too 26 | pub render_target: RenderTarget, 27 | pub scene: Scene, 28 | pub config: Config, 29 | /// Our connection to the master 30 | master: TcpStream, 31 | } 32 | 33 | impl Worker { 34 | /// Listen on the worker `PORT` for the master to contact us 35 | /// and send us instructions about the scene we should render and 36 | /// what parts of it we've been assigned 37 | pub fn listen_for_master(num_threads: u32) -> Worker { 38 | let (instructions, master) = get_instructions(); 39 | let (scene, rt, spp, mut frame_info) = Scene::load_file(&instructions.scene); 40 | frame_info.start = instructions.frames.0; 41 | frame_info.end = instructions.frames.1; 42 | let config = Config::new(PathBuf::from("/tmp"), instructions.scene.clone(), spp, 43 | num_threads, frame_info, 44 | (instructions.block_start, instructions.block_count)); 45 | Worker { instructions: instructions, render_target: rt, scene: scene, 46 | config: config, master: master } 47 | } 48 | /// Send our blocks back to the master 49 | pub fn send_results(&mut self) { 50 | let (block_size, blocks, pixels) = self.render_target.get_rendered_blocks(); 51 | let frame = Frame::new(self.config.current_frame, block_size, blocks, pixels); 52 | let bytes = serialize(&frame, Infinite).unwrap(); 53 | if let Err(e) = self.master.write_all(&bytes[..]) { 54 | panic!("Failed to send frame to {:?}: {}", self.master, e); 55 | } 56 | } 57 | } 58 | 59 | fn get_instructions() -> (Instructions, TcpStream) { 60 | let listener = TcpListener::bind(("0.0.0.0", PORT)).expect("Worker failed to get port"); 61 | println!("Worker listening for master on {}", PORT); 62 | match listener.accept() { 63 | Ok((mut stream, _)) => { 64 | let mut buf: Vec<_> = iter::repeat(0u8).take(8).collect(); 65 | let mut expected_size = 8; 66 | let mut currently_read = 0; 67 | // Read the size header 68 | while currently_read < expected_size { 69 | match stream.read(&mut buf[currently_read..]) { 70 | Ok(n) => currently_read += n, 71 | Err(e) => panic!("Failed to read from master, {:?}", e), 72 | } 73 | } 74 | // How many bytes we expect to get from the worker for a frame 75 | expected_size = deserialize(&buf[..]).unwrap(); 76 | buf.extend(iter::repeat(0u8).take(expected_size - 8)); 77 | // Now read the rest 78 | while currently_read < expected_size { 79 | match stream.read(&mut buf[currently_read..]) { 80 | Ok(n) => currently_read += n, 81 | Err(e) => panic!("Failed to read from master, {:?}", e), 82 | } 83 | } 84 | let instr = deserialize(&buf[..]).unwrap(); 85 | println!("Received instructions: {:?}", instr); 86 | (instr, stream) 87 | }, 88 | Err(e) => panic!("Error accepting: {:?}", e), 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/exec/mod.rs: -------------------------------------------------------------------------------- 1 | //! The exec module provides an abstraction of the execution backends 2 | //! used to actually render the image 3 | 4 | use std::path::PathBuf; 5 | 6 | use film::{FrameInfo, RenderTarget}; 7 | use scene::Scene; 8 | 9 | pub use self::multithreaded::MultiThreaded; 10 | 11 | pub mod multithreaded; 12 | pub mod distrib; 13 | 14 | /// Config passed to set up the execution environment with information 15 | /// on what it should be rendering and where to put the results 16 | #[derive(Debug, Clone)] 17 | pub struct Config { 18 | // TODO: Maybe this should be Option 19 | pub out_path: PathBuf, 20 | pub scene_file: String, 21 | pub num_threads: u32, 22 | pub spp: usize, 23 | pub frame_info: FrameInfo, 24 | pub current_frame: usize, 25 | /// Which blocks the executor should render, stored 26 | /// as (start, count) of the block indices 27 | pub select_blocks: (usize, usize) 28 | } 29 | 30 | impl Config { 31 | pub fn new(out_path: PathBuf, scene_file: String, spp: usize, num_threads: u32, 32 | frame_info: FrameInfo, select_blocks: (usize, usize)) -> Config { 33 | Config { out_path: out_path, scene_file: scene_file, spp: spp, 34 | num_threads: num_threads, frame_info: frame_info, 35 | current_frame: frame_info.start, select_blocks: select_blocks } 36 | } 37 | } 38 | 39 | /// Trait implemented by different execution environments that provides 40 | /// a method to call and render the scene, given the rendering arguments 41 | pub trait Exec { 42 | /// Render the scene using this rendering backend, will render out 43 | /// all frames of the image and save them out as instructed by 44 | /// the command line arguments 45 | /// TODO: In order to have a cleaner seperation we should pass more parameters 46 | /// to render. E.g. the scene. Or maybe a callback to a function that gets the 47 | /// frame's render target and can save it out? 48 | fn render(&mut self, scene: &mut Scene, rt: &mut RenderTarget, config: &Config); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/exec/multithreaded.rs: -------------------------------------------------------------------------------- 1 | //! The multithreaded module provides a multithreaded execution for rendering 2 | //! the image. 3 | 4 | use std::iter; 5 | use std::time::SystemTime; 6 | 7 | use scoped_threadpool::Pool; 8 | use rand::StdRng; 9 | use light_arena; 10 | 11 | use sampler::BlockQueue; 12 | use film::{RenderTarget, ImageSample, Colorf}; 13 | use geometry::{Instance, Emitter}; 14 | use sampler::{self, Sampler}; 15 | use scene::Scene; 16 | use exec::{Config, Exec}; 17 | 18 | /// The `MultiThreaded` execution uses a configurable number of threads in 19 | /// a threadpool to render each frame 20 | pub struct MultiThreaded { 21 | pool: Pool, 22 | } 23 | 24 | impl MultiThreaded { 25 | /// Create a new multithreaded renderer which will use `num_threads` to render the image 26 | pub fn new(num_threads: u32) -> MultiThreaded { 27 | MultiThreaded { pool: Pool::new(num_threads) } 28 | } 29 | /// Launch a rendering job in parallel across the threads and wait for it to finish 30 | fn render_parallel(&mut self, scene: &Scene, rt: &RenderTarget, config: &Config) { 31 | let dim = rt.dimensions(); 32 | let block_queue = BlockQueue::new((dim.0 as u32, dim.1 as u32), (8, 8), config.select_blocks); 33 | let light_list: Vec<_> = scene.bvh.iter().filter_map(|x| { 34 | match *x { 35 | Instance::Emitter(ref e) => Some(e), 36 | _ => None, 37 | } 38 | }).collect(); 39 | assert!(!light_list.is_empty(), "At least one light is required"); 40 | let n = self.pool.thread_count(); 41 | self.pool.scoped(|scope| { 42 | for _ in 0..n { 43 | let b = &block_queue; 44 | let r = &rt; 45 | let l = &light_list; 46 | scope.execute(move || { 47 | thread_work(config.spp, b, scene, r, l); 48 | }); 49 | } 50 | }); 51 | } 52 | } 53 | 54 | impl Exec for MultiThreaded { 55 | fn render(&mut self, scene: &mut Scene, rt: &mut RenderTarget, config: &Config) { 56 | println!("Rendering using {} threads\n--------------------", self.pool.thread_count()); 57 | let time_step = config.frame_info.time / config.frame_info.frames as f32; 58 | let frame_start_time = config.current_frame as f32 * time_step; 59 | let frame_end_time = (config.current_frame as f32 + 1.0) * time_step; 60 | scene.update_frame(config.current_frame, frame_start_time, frame_end_time); 61 | 62 | println!("Frame {}: rendering for {} to {}", config.current_frame, 63 | frame_start_time, frame_end_time); 64 | let scene_start = SystemTime::now(); 65 | self.render_parallel(scene, rt, config); 66 | let time = scene_start.elapsed().expect("Failed to get render time?"); 67 | println!("Frame {}: rendering took {:4}s", config.current_frame, 68 | time.as_secs() as f64 + time.subsec_nanos() as f64 * 1e-9); 69 | } 70 | } 71 | 72 | fn thread_work(spp: usize, queue: &BlockQueue, scene: &Scene, 73 | target: &RenderTarget, light_list: &[&Emitter]) { 74 | let mut sampler = sampler::LowDiscrepancy::new(queue.block_dim(), spp); 75 | let mut sample_pos = Vec::with_capacity(sampler.max_spp()); 76 | let mut time_samples: Vec<_> = iter::repeat(0.0).take(sampler.max_spp()).collect(); 77 | let block_dim = queue.block_dim(); 78 | let mut block_samples = Vec::with_capacity(sampler.max_spp() * (block_dim.0 * block_dim.1) as usize); 79 | let mut rng = match StdRng::new() { 80 | Ok(r) => r, 81 | Err(e) => { println!("Failed to get StdRng, {}", e); return } 82 | }; 83 | let mut arena = light_arena::MemoryArena::new(8); 84 | let camera = scene.active_camera(); 85 | // Grab a block from the queue and start working on it, submitting samples 86 | // to the render target thread after each pixel 87 | for b in queue.iter() { 88 | sampler.select_block(b); 89 | let mut pixel_samples = 0; 90 | while sampler.has_samples() { 91 | // Get samples for a pixel and render them 92 | sampler.get_samples(&mut sample_pos, &mut rng); 93 | sampler.get_samples_1d(&mut time_samples[..], &mut rng); 94 | for (s, t) in sample_pos.iter().zip(time_samples.iter()) { 95 | let alloc = arena.allocator(); 96 | let mut ray = camera.generate_ray(s, *t); 97 | if let Some(hit) = scene.intersect(&mut ray) { 98 | let c = scene.integrator.illumination(scene, light_list, &ray, &hit, 99 | &mut sampler, &mut rng, &alloc).clamp(); 100 | block_samples.push(ImageSample::new(s.0, s.1, c)); 101 | } else { 102 | block_samples.push(ImageSample::new(s.0, s.1, Colorf::black())); 103 | } 104 | } 105 | // If the samples are ok the samples for the next pixel start at the end of the current 106 | // pixel's samples 107 | if sampler.report_results(&block_samples[pixel_samples..]) { 108 | pixel_samples = block_samples.len(); 109 | } 110 | } 111 | target.write(&block_samples, sampler.get_region()); 112 | block_samples.clear(); 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /src/film/animated_color.rs: -------------------------------------------------------------------------------- 1 | //! Provides an animated color value, so you can have colors change over time 2 | 3 | use std::cmp::{Eq, Ord, PartialOrd, PartialEq, Ordering}; 4 | 5 | use linalg; 6 | use film::Colorf; 7 | 8 | /// `ColorKeyframe` is a color associated with a specific time 9 | #[derive(Debug, Copy, Clone)] 10 | pub struct ColorKeyframe { 11 | pub color: Colorf, 12 | pub time: f32, 13 | } 14 | 15 | impl ColorKeyframe { 16 | pub fn new(color: &Colorf, time: f32) -> ColorKeyframe { 17 | ColorKeyframe { color: *color, time: time } 18 | } 19 | } 20 | impl Ord for ColorKeyframe { 21 | fn cmp(&self, other: &ColorKeyframe) -> Ordering { 22 | self.partial_cmp(other).unwrap() 23 | } 24 | } 25 | impl PartialOrd for ColorKeyframe { 26 | fn partial_cmp(&self, other: &ColorKeyframe) -> Option { 27 | self.time.partial_cmp(&other.time) 28 | } 29 | } 30 | impl Eq for ColorKeyframe {} 31 | impl PartialEq for ColorKeyframe { 32 | fn eq(&self, other: &ColorKeyframe) -> bool { 33 | self.time == other.time 34 | } 35 | } 36 | 37 | /// `AnimatedColor` is a list of colors associated with time points in the scene 38 | /// that will compute the color at the desired time by blending the two nearest ones 39 | #[derive(Debug, Clone)] 40 | pub struct AnimatedColor { 41 | /// List of color keyframes in time order 42 | keyframes: Vec, 43 | } 44 | 45 | impl AnimatedColor { 46 | /// Create an animated transform that will blend between the passed keyframes 47 | pub fn with_keyframes(mut keyframes: Vec) -> AnimatedColor { 48 | keyframes.sort(); 49 | AnimatedColor { keyframes: keyframes } 50 | } 51 | /// Compute the color at the desired time 52 | pub fn color(&self, time: f32) -> Colorf { 53 | if self.keyframes.is_empty() { 54 | Colorf::black() 55 | } else if self.keyframes.len() == 1 { 56 | self.keyframes[0].color 57 | } else { 58 | // TODO: Binary search here? 59 | let first = self.keyframes.iter().take_while(|k| k.time < time).last(); 60 | let second = self.keyframes.iter().skip_while(|k| k.time < time).next(); 61 | if first.is_none() { 62 | self.keyframes.first().unwrap().color 63 | } else if second.is_none() { 64 | self.keyframes.last().unwrap().color 65 | } else { 66 | let mut color = Colorf::black(); 67 | let fk = first.unwrap(); 68 | let sk = second.unwrap(); 69 | let t = (time - fk.time) / (sk.time - fk.time); 70 | color.r = linalg::lerp(t, &fk.color.r, &sk.color.r); 71 | color.g = linalg::lerp(t, &fk.color.g, &sk.color.g); 72 | color.b = linalg::lerp(t, &fk.color.b, &sk.color.b); 73 | color.a = linalg::lerp(t, &fk.color.a, &sk.color.a); 74 | color 75 | } 76 | } 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/film/color.rs: -------------------------------------------------------------------------------- 1 | //! Defines types for operating with floating point and 8 bit RGB colors 2 | 3 | use std::f32; 4 | use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut}; 5 | 6 | use linalg; 7 | 8 | /// Colorf is a floating point RGBA color type 9 | /// `a` is typically used to store the weight of a color eg. in the 10 | /// render target for multisampling we need to track the weight to 11 | /// normalize in the end and is always initialized to 0 12 | #[derive(Debug, Copy, Clone, PartialEq)] 13 | pub struct Colorf { 14 | pub r: f32, 15 | pub g: f32, 16 | pub b: f32, 17 | pub a: f32, 18 | } 19 | 20 | impl Colorf { 21 | /// Create an RGB color 22 | pub fn new(r: f32, g: f32, b: f32) -> Colorf { 23 | Colorf { r: r, g: g, b: b, a: 1.0 } 24 | } 25 | /// Create an RGB color 26 | pub fn with_alpha(r: f32, g: f32, b: f32, a: f32) -> Colorf { 27 | Colorf { r: r, g: g, b: b, a: a } 28 | } 29 | /// Create an RGB color using the same value for all channels 30 | pub fn broadcast(r: f32) -> Colorf { 31 | Colorf { r: r, g: r, b: r, a: r } 32 | } 33 | /// Create a black color 34 | pub fn black() -> Colorf { Colorf::broadcast(0.0) } 35 | /// Clamp the color values between [0, 1] 36 | pub fn clamp(&self) -> Colorf { 37 | Colorf { r: linalg::clamp(self.r, 0.0, 1.0), 38 | g: linalg::clamp(self.g, 0.0, 1.0), 39 | b: linalg::clamp(self.b, 0.0, 1.0), 40 | a: linalg::clamp(self.a, 0.0, 1.0) } 41 | } 42 | /// Compute the luminance of the color 43 | pub fn luminance(&self) -> f32 { 44 | 0.2126 * self.r + 0.7152 * self.g + 0.0722 * self.b 45 | } 46 | /// Check if the color is black 47 | pub fn is_black(&self) -> bool { 48 | self.r == 0f32 && self.g == 0f32 && self.b == 0f32 49 | } 50 | /// Check if any of the color channels are NaN 51 | pub fn has_nans(&self) -> bool { 52 | f32::is_nan(self.r) || f32::is_nan(self.g) || f32::is_nan(self.b) || f32::is_nan(self.a) 53 | } 54 | /// Check if any of the color channels are infinite 55 | pub fn has_infs(&self) -> bool { 56 | f32::is_infinite(self.r) || f32::is_infinite(self.g) || f32::is_infinite(self.b) || f32::is_infinite(self.a) 57 | } 58 | /// Convert the linear RGB color to sRGB 59 | pub fn to_srgb(&self) -> Colorf { 60 | let a = 0.055f32; 61 | let b = 1f32 / 2.4; 62 | let mut srgb = Colorf::broadcast(0.0); 63 | for i in 0..3 { 64 | if self[i] <= 0.0031308 { 65 | srgb[i] = 12.92 * self[i]; 66 | } else { 67 | srgb[i] = (1.0 + a) * f32::powf(self[i], b) - a; 68 | } 69 | } 70 | srgb 71 | } 72 | /// Return the color with values { e^r, e^g, e^b } 73 | pub fn exp(&self) -> Colorf { 74 | Colorf { r: f32::exp(self.r), g: f32::exp(self.g), 75 | b: f32::exp(self.b), a: f32::exp(self.a) } 76 | } 77 | } 78 | 79 | impl Add for Colorf { 80 | type Output = Colorf; 81 | /// Add two colors together 82 | fn add(self, rhs: Colorf) -> Colorf { 83 | Colorf { r: self.r + rhs.r, g: self.g + rhs.g, b: self.b + rhs.b, a: self.a + rhs.a } 84 | } 85 | } 86 | 87 | impl Sub for Colorf { 88 | type Output = Colorf; 89 | /// Subtract the two colors 90 | fn sub(self, rhs: Colorf) -> Colorf { 91 | Colorf { r: self.r - rhs.r, g: self.g - rhs.g, b: self.b - rhs.b, a: self.a - rhs.a } 92 | } 93 | } 94 | 95 | impl Mul for Colorf { 96 | type Output = Colorf; 97 | /// Multiply the two colors 98 | fn mul(self, rhs: Colorf) -> Colorf { 99 | Colorf { r: self.r * rhs.r, g: self.g * rhs.g, b: self.b * rhs.b, a: self.a * rhs.a } 100 | } 101 | } 102 | 103 | impl Mul for Colorf { 104 | type Output = Colorf; 105 | /// Scale the color by the float 106 | fn mul(self, rhs: f32) -> Colorf { 107 | Colorf { r: self.r * rhs, g: self.g * rhs, b: self.b * rhs, a: self.a * rhs } 108 | } 109 | } 110 | 111 | impl Mul for f32 { 112 | type Output = Colorf; 113 | /// Scale the color by the float 114 | fn mul(self, rhs: Colorf) -> Colorf { 115 | Colorf { r: self * rhs.r, g: self * rhs.g, b: self * rhs.b, a: self * rhs.a } 116 | } 117 | } 118 | 119 | impl Div for Colorf { 120 | type Output = Colorf; 121 | /// Divide the channels of one color by another 122 | fn div(self, rhs: Colorf) -> Colorf { 123 | Colorf { r: self.r / rhs.r, g: self.g / rhs.g, b: self.b / rhs.b, a: self.a / rhs.a } 124 | } 125 | } 126 | 127 | impl Div for Colorf { 128 | type Output = Colorf; 129 | /// Divide the channels of the color by the float 130 | fn div(self, rhs: f32) -> Colorf { 131 | Colorf { r: self.r / rhs, g: self.g / rhs, b: self.b / rhs, a: self.a / rhs } 132 | } 133 | } 134 | 135 | impl Neg for Colorf { 136 | type Output = Colorf; 137 | /// Negate the color channels 138 | fn neg(self) -> Colorf { 139 | Colorf { r: -self.r, g: -self.g, b: -self.b, a: -self.a } 140 | } 141 | } 142 | 143 | impl Index for Colorf { 144 | type Output = f32; 145 | /// Access the channels by index 146 | /// 147 | /// - 0 = r 148 | /// - 1 = g 149 | /// - 2 = b 150 | /// - 3 = a 151 | fn index(&self, i: usize) -> &f32 { 152 | match i { 153 | 0 => &self.r, 154 | 1 => &self.g, 155 | 2 => &self.b, 156 | 3 => &self.a, 157 | _ => panic!("Invalid index into color"), 158 | } 159 | } 160 | } 161 | 162 | impl IndexMut for Colorf { 163 | /// Access the channels by index 164 | /// 165 | /// - 0 = r 166 | /// - 1 = g 167 | /// - 2 = b 168 | /// - 3 = a 169 | fn index_mut(&mut self, i: usize) -> &mut f32 { 170 | match i { 171 | 0 => &mut self.r, 172 | 1 => &mut self.g, 173 | 2 => &mut self.b, 174 | 3 => &mut self.a, 175 | _ => panic!("Invalid index into color"), 176 | } 177 | } 178 | } 179 | 180 | -------------------------------------------------------------------------------- /src/film/filter/gaussian.rs: -------------------------------------------------------------------------------- 1 | //! Provides a Gaussian reconstruction filter. 2 | 3 | use std::f32; 4 | 5 | use film::filter::Filter; 6 | 7 | /// A Gaussian reconstruction filter. 8 | /// Recommended parameters to try: w = 2.0, h = 2.0, alpha = 2.0 9 | #[derive(Copy, Clone, Debug)] 10 | pub struct Gaussian { 11 | w: f32, 12 | h: f32, 13 | inv_w: f32, 14 | inv_h: f32, 15 | alpha: f32, 16 | exp_x: f32, 17 | exp_y: f32 18 | } 19 | 20 | impl Gaussian { 21 | pub fn new(w: f32, h: f32, alpha: f32) -> Gaussian { 22 | Gaussian { w: w, h: h, inv_w: 1.0 / w, inv_h: 1.0 / h, 23 | alpha: alpha, exp_x: f32::exp(-alpha * w * w), 24 | exp_y: f32::exp(-alpha * h * h) 25 | } 26 | } 27 | fn weight_1d(&self, x: f32, e: f32) -> f32 { 28 | f32::max(0.0, f32::exp(-self.alpha * x * x) - e) 29 | } 30 | } 31 | 32 | impl Filter for Gaussian { 33 | fn weight(&self, x: f32, y: f32) -> f32 { 34 | self.weight_1d(x, self.exp_x) * self.weight_1d(y, self.exp_y) 35 | } 36 | fn width(&self) -> f32 { self.w } 37 | fn inv_width(&self) -> f32 { self.inv_w } 38 | fn height(&self) -> f32 { self.h } 39 | fn inv_height(&self) -> f32 { self.inv_h } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/film/filter/mitchell_netravali.rs: -------------------------------------------------------------------------------- 1 | //! Provides an implementation of the Mitchell-Netravali reconstruction filter. 2 | //! See [Reconstruction Filters in Computer Graphics](http://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf). 3 | 4 | use std::f32; 5 | 6 | use linalg; 7 | use film::filter::Filter; 8 | 9 | /// A Mitchell-Netravali reconstruction filter. 10 | /// Recommended parameters to try: w = 2.0, h = 2.0, b = 1.0 / 3.0, c = 1.0 / 3.0 11 | #[derive(Copy, Clone, Debug)] 12 | pub struct MitchellNetravali { 13 | w: f32, 14 | h: f32, 15 | inv_w: f32, 16 | inv_h: f32, 17 | b: f32, 18 | c: f32, 19 | } 20 | 21 | impl MitchellNetravali { 22 | pub fn new(w: f32, h: f32, b: f32, c: f32) -> MitchellNetravali { 23 | if b < 0.0 || b > 1.0 { 24 | println!("Warning! Mitchell-Netravali b param = {} is out of bounds, clamping in range", b); 25 | } 26 | if c < 0.0 || c > 1.0 { 27 | println!("Warning! Mitchell-Netravali c param = {} is out of bounds, clamping in range", c); 28 | } 29 | MitchellNetravali { w: w, h: h, inv_w: 1.0 / w, inv_h: 1.0 / h, 30 | b: linalg::clamp(b, 0.0, 1.0), c: linalg::clamp(c, 0.0, 1.0) 31 | } 32 | } 33 | /// Compute a 1d weight for the filter. Note that the Mitchell-Netravali 34 | /// filter is defined on [-2, 2] so x should be in this range 35 | fn weight_1d(&self, x: f32) -> f32 { 36 | let abs_x = f32::abs(x); 37 | if x >= 2.0 { 38 | 0.0 39 | } else if x >= 1.0 { 40 | 1.0 / 6.0 * ((-self.b - 6.0 * self.c) * f32::powf(abs_x, 3.0) 41 | + (6.0 * self.b + 30.0 * self.c) * f32::powf(abs_x, 2.0) 42 | + (-12.0 * self.b - 48.0 * self.c) * abs_x 43 | + (8.0 * self.b + 24.0 * self.c)) 44 | } else { 45 | 1.0 / 6.0 * ((12.0 - 9.0 * self.b - 6.0 * self.c) * f32::powf(abs_x, 3.0) 46 | + (-18.0 + 12.0 * self.b + 6.0 * self.c) * f32::powf(abs_x, 2.0) 47 | + (6.0 - 2.0 * self.b)) 48 | } 49 | } 50 | } 51 | 52 | impl Filter for MitchellNetravali { 53 | fn weight(&self, x: f32, y: f32) -> f32 { 54 | self.weight_1d(2.0 * x * self.inv_w) * self.weight_1d(2.0 * y * self.inv_h) 55 | } 56 | fn width(&self) -> f32 { self.w } 57 | fn inv_width(&self) -> f32 { self.inv_w } 58 | fn height(&self) -> f32 { self.h } 59 | fn inv_height(&self) -> f32 { self.inv_h } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/film/filter/mod.rs: -------------------------------------------------------------------------------- 1 | //! The filter module provides reconstruction filters to be used 2 | //! when writing samples to the render target. The filter width and 3 | //! height refer to how many pixels the filter covers, where a single 4 | //! pixel is 0.5x0.5 5 | 6 | pub use self::gaussian::Gaussian; 7 | pub use self::mitchell_netravali::MitchellNetravali; 8 | 9 | pub mod gaussian; 10 | pub mod mitchell_netravali; 11 | 12 | /// Trait implemented by all reconstructon filters. Provides methods for getting 13 | /// the width/height and computing the weight at some point relative to the filter 14 | /// center. 15 | pub trait Filter { 16 | /// Compute the weight of this filter at some point (x, y) relative 17 | /// to the center of the filter 18 | fn weight(&self, x: f32, y: f32) -> f32; 19 | /// Return the width of the filter 20 | fn width(&self) -> f32; 21 | /// Return the inverse width of the filter 22 | fn inv_width(&self) -> f32; 23 | /// Return the height of the filter 24 | fn height(&self) -> f32; 25 | /// Return the inverse height of the filter 26 | fn inv_height(&self) -> f32; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/film/image.rs: -------------------------------------------------------------------------------- 1 | //! Provides a simple RGBA_F32 image, used by the distributed master to store results 2 | //! from the worker processes 3 | 4 | use std::iter; 5 | 6 | use film::Colorf; 7 | 8 | #[derive(Debug)] 9 | pub struct Image { 10 | dim: (usize, usize), 11 | pixels: Vec, 12 | } 13 | 14 | impl Image { 15 | pub fn new(dimensions: (usize, usize)) -> Image { 16 | let pixels = iter::repeat(Colorf::broadcast(0.0)).take(dimensions.0 * dimensions.1).collect(); 17 | Image { dim: dimensions, pixels: pixels } 18 | } 19 | /// Add the floating point RGBAf32 pixels to the image. It is assumed that `pixels` contains 20 | /// a `dim.0` by `dim.1` pixel image. 21 | pub fn add_pixels(&mut self, pixels: &[f32]) { 22 | for y in 0..self.dim.1 { 23 | for x in 0..self.dim.0 { 24 | let c = &mut self.pixels[y * self.dim.0 + x]; 25 | let px = y * self.dim.0 * 4 + x * 4; 26 | for i in 0..4 { 27 | c[i] += pixels[px + i]; 28 | } 29 | } 30 | } 31 | } 32 | /// Add the blocks of RGBAf32 pixels to the image. It's assumed that the block information 33 | /// passed is equivalent to that returned by RenderTarget::get_blocks. `block_size` specifies 34 | /// the size of the blocks being passed, `blocks` contains the start points of each block and 35 | /// `pixels` contains `block_size.0 * block_size.1 * 4` floats for each block. 36 | pub fn add_blocks(&mut self, block_size: (usize, usize), blocks: &[(usize, usize)], pixels: &[f32]) { 37 | let block_stride = block_size.0 * block_size.1 * 4; 38 | for (i, b) in blocks.iter().enumerate() { 39 | let block_px = &pixels[block_stride * i..block_stride * (i + 1)]; 40 | for by in 0..block_size.1 { 41 | for bx in 0..block_size.0 { 42 | let c = &mut self.pixels[(by + b.1) * self.dim.0 + bx + b.0]; 43 | let px = by * block_size.0 * 4 + bx * 4; 44 | for i in 0..4 { 45 | c[i] += block_px[px + i]; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | /// Convert the Image to sRGB8 format and return it 52 | pub fn get_srgb8(&self) -> Vec { 53 | let mut render: Vec = iter::repeat(0u8).take(self.dim.0 * self.dim.1 * 3).collect(); 54 | for y in 0..self.dim.1 { 55 | for x in 0..self.dim.0 { 56 | let c = &self.pixels[y * self.dim.0 + x]; 57 | if c.a > 0.0 { 58 | let cn = (*c / c.a).clamp().to_srgb(); 59 | let px = y * self.dim.0 * 3 + x * 3; 60 | for i in 0..3 { 61 | render[px + i] = (cn[i] * 255.0) as u8; 62 | } 63 | } 64 | } 65 | } 66 | render 67 | } 68 | pub fn dimensions(&self) -> (usize, usize) { 69 | self.dim 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/film/mod.rs: -------------------------------------------------------------------------------- 1 | //! The film module provides color types and a render target that the image 2 | //! is written too. 3 | 4 | pub use self::color::Colorf; 5 | pub use self::render_target::RenderTarget; 6 | pub use self::camera::Camera; 7 | pub use self::render_target::ImageSample; 8 | pub use self::animated_color::{ColorKeyframe, AnimatedColor}; 9 | pub use self::image::Image; 10 | 11 | pub mod color; 12 | pub mod render_target; 13 | pub mod camera; 14 | pub mod filter; 15 | pub mod animated_color; 16 | pub mod image; 17 | 18 | /// Struct to store various parameters for the frame timing 19 | #[derive(Debug, Copy, Clone)] 20 | pub struct FrameInfo { 21 | /// Total number of frames for the scene 22 | pub frames: usize, 23 | /// Total time for all the entire scene (ie. for all frames) 24 | pub time: f32, 25 | /// Frame number to start rendering at 26 | pub start: usize, 27 | /// Frame number to stop rendering at 28 | pub end: usize, 29 | } 30 | 31 | impl FrameInfo { 32 | pub fn new(frames: usize, time: f32, start: usize, end: usize) -> FrameInfo { 33 | FrameInfo { frames: frames, time: time, start: start, end: end } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/geometry/bbox.rs: -------------------------------------------------------------------------------- 1 | //! Provide an Axis-Aligned Bounding Box type, `BBox`, with an optimized intersection test 2 | //! targeted for usage in a BVH 3 | //! TODO: Should I also implement the Geometry trait? 4 | 5 | use std::f32; 6 | use std::ops::{Index, IndexMut}; 7 | 8 | use linalg::{self, Point, Vector, Ray, Axis}; 9 | 10 | /// A box between the min and max points 11 | #[derive(Clone, Copy, Debug)] 12 | pub struct BBox { 13 | pub min: Point, 14 | pub max: Point, 15 | } 16 | 17 | impl BBox { 18 | /// Create a new degenerate box 19 | pub fn new() -> BBox { 20 | BBox { min: Point::broadcast(f32::INFINITY), max: Point::broadcast(f32::NEG_INFINITY) } 21 | } 22 | /// Create a new box containing only the point passed 23 | pub fn singular(p: Point) -> BBox { 24 | BBox { min: p, max: p } 25 | } 26 | /// Create a new box spanning [min, max] 27 | pub fn span(min: Point, max: Point) -> BBox { 28 | BBox { min: min, max: max } 29 | } 30 | /// Get a box representing the union of this box with the one passed 31 | pub fn box_union(&self, b: &BBox) -> BBox { 32 | BBox { min: Point::new(f32::min(self.min.x, b.min.x), f32::min(self.min.y, b.min.y), 33 | f32::min(self.min.z, b.min.z)), 34 | max: Point::new(f32::max(self.max.x, b.max.x), f32::max(self.max.y, b.max.y), 35 | f32::max(self.max.z, b.max.z)) 36 | } 37 | } 38 | /// Get a box that contains the passed point, by expanding this box to reach the point 39 | pub fn point_union(&self, p: &Point) -> BBox { 40 | BBox { min: Point::new(f32::min(self.min.x, p.x), f32::min(self.min.y, p.y), 41 | f32::min(self.min.z, p.z)), 42 | max: Point::new(f32::max(self.max.x, p.x), f32::max(self.max.y, p.y), 43 | f32::max(self.max.z, p.z)) 44 | } 45 | } 46 | /// Compute the axis along which the box is longest 47 | pub fn max_extent(&self) -> Axis { 48 | let d = self.max - self.min; 49 | if d.x > d.y && d.x > d.z { 50 | Axis::X 51 | } else if d.y > d.z { 52 | Axis::Y 53 | } else { 54 | Axis::Z 55 | } 56 | } 57 | /// Compute the point in the box at some t value along each axis 58 | pub fn lerp(&self, tx: f32, ty: f32, tz: f32) -> Point { 59 | Point::new(linalg::lerp(tx, &self.min.x, &self.max.x), linalg::lerp(ty, &self.min.y, &self.max.y), 60 | linalg::lerp(tz, &self.min.z, &self.max.z)) 61 | } 62 | /// Find the position of the point relative to the box, with `min` being the origin 63 | pub fn offset(&self, p: &Point) -> Vector { 64 | (*p - self.min) / (self.max - self.min) 65 | } 66 | /// Compute the surface area of the box 67 | pub fn surface_area(&self) -> f32 { 68 | let d = self.max - self.min; 69 | 2.0 * (d.x * d.y + d.x * d.z + d.y * d.z) 70 | } 71 | /// Optimized ray-box intersection test, for use in the BVH traversal where we have 72 | /// pre-computed the ray's inverse direction and which directions are negative, indicated 73 | /// by a 1 for negative and 0 for non-negative 74 | /// Returns true if the box was hit 75 | pub fn fast_intersect(&self, r: &Ray, inv_dir: &Vector, neg_dir: &[usize; 3]) -> bool { 76 | // Check X & Y intersection 77 | let mut tmin = (self[neg_dir[0]].x - r.o.x) * inv_dir.x; 78 | let mut tmax = (self[1 - neg_dir[0]].x - r.o.x) * inv_dir.x; 79 | let tymin = (self[neg_dir[1]].y - r.o.y) * inv_dir.y; 80 | let tymax = (self[1 - neg_dir[1]].y - r.o.y) * inv_dir.y; 81 | if tmin > tymax || tymin > tmax { 82 | return false; 83 | } 84 | if tymin > tmin { 85 | tmin = tymin; 86 | } 87 | if tymax < tmax { 88 | tmax = tymax; 89 | } 90 | 91 | // Check Z intersection 92 | let tzmin = (self[neg_dir[2]].z - r.o.z) * inv_dir.z; 93 | let tzmax = (self[1 - neg_dir[2]].z - r.o.z) * inv_dir.z; 94 | if tmin > tzmax || tzmin > tmax { 95 | return false; 96 | } 97 | if tzmin > tmin { 98 | tmin = tzmin; 99 | } 100 | if tzmax < tmax { 101 | tmax = tzmax; 102 | } 103 | tmin < r.max_t && tmax > r.min_t 104 | } 105 | } 106 | 107 | impl Index for BBox { 108 | type Output = Point; 109 | /// Access the BBox's min/max points by index 110 | /// 111 | /// - 0 = min 112 | /// - 1 = max 113 | fn index(&self, i: usize) -> &Point { 114 | match i { 115 | 0 => &self.min, 116 | 1 => &self.max, 117 | _ => panic!("Invalid index into point"), 118 | } 119 | } 120 | } 121 | 122 | impl IndexMut for BBox { 123 | /// Access the BBox's min/max points by index 124 | /// 125 | /// - 0 = min 126 | /// - 1 = max 127 | fn index_mut(&mut self, i: usize) -> &mut Point { 128 | match i { 129 | 0 => &mut self.min, 130 | 1 => &mut self.max, 131 | _ => panic!("Invalid index into point"), 132 | } 133 | } 134 | } 135 | 136 | -------------------------------------------------------------------------------- /src/geometry/differential_geometry.rs: -------------------------------------------------------------------------------- 1 | //! Defines the `DifferentialGeometry` type which is used to pass information 2 | //! about the hit piece of geometry back from the intersection to the shading 3 | 4 | use linalg::{self, Point, Normal, Vector}; 5 | use geometry::Geometry; 6 | 7 | /// Stores information about a hit piece of geometry of some object in the scene 8 | #[derive(Clone, Copy)] 9 | pub struct DifferentialGeometry<'a> { 10 | /// The hit point 11 | pub p: Point, 12 | /// The shading normal 13 | pub n: Normal, 14 | /// The geometry normal 15 | pub ng: Normal, 16 | /// Surface parameterization u, v for texture mapping 17 | pub u: f32, 18 | pub v: f32, 19 | /// The intersection time 20 | pub time: f32, 21 | /// Derivative of the point with respect to the u parameterization coord of the surface 22 | pub dp_du: Vector, 23 | /// Derivative of the point with respect to the v parameterization coord of the surface 24 | pub dp_dv: Vector, 25 | /// The geometry that was hit 26 | pub geom: &'a (Geometry + 'a), 27 | } 28 | 29 | impl<'a> DifferentialGeometry<'a> { 30 | /// Setup the differential geometry. Note that the normal will be computed 31 | /// using cross(dp_du, dp_dv) 32 | pub fn new(p: &Point, ng: &Normal, u: f32, v: f32, time: f32, 33 | dp_du: &Vector, dp_dv: &Vector, geom: &'a (Geometry + 'a)) -> DifferentialGeometry<'a> 34 | { 35 | let n = linalg::cross(dp_du, dp_dv).normalized(); 36 | DifferentialGeometry { 37 | p: *p, 38 | n: Normal::new(n.x, n.y, n.z), 39 | ng: ng.normalized(), 40 | u: u, 41 | v: v, 42 | time: time, 43 | dp_du: *dp_du, 44 | dp_dv: *dp_dv, 45 | geom: geom 46 | } 47 | } 48 | /// Setup the differential geometry using the normal passed for the surface normal 49 | pub fn with_normal(p: &Point, n: &Normal, u: f32, v: f32, time: f32, 50 | dp_du: &Vector, dp_dv: &Vector, geom: &'a (Geometry + 'a)) -> DifferentialGeometry<'a> 51 | { 52 | let nn = n.normalized(); 53 | DifferentialGeometry { 54 | p: *p, 55 | n: nn, 56 | ng: nn, 57 | u: u, 58 | v: v, 59 | time: time, 60 | dp_du: *dp_du, 61 | dp_dv: *dp_dv, 62 | geom: geom 63 | } 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/geometry/disk.rs: -------------------------------------------------------------------------------- 1 | //! Defines a Disk type which implements the Geometry, Boundable and Sampleable traits 2 | //! A disk with some inner and outer radius allowing it to 3 | //! have a hole in the middle. The disk is oriented with the center 4 | //! at the origin and the normal pointing along +Z. 5 | //! 6 | //! # Scene Usage Example 7 | //! The disk requires two parameters, to specify the radius of the disk and the 8 | //! radius of the hole cut out of the middle of it. Set the inner radius to 0 to 9 | //! get a solid disk. 10 | //! 11 | //! ```json 12 | //! "geometry": { 13 | //! "type": "disk", 14 | //! "radius": 4.0, 15 | //! "inner_radius": 1.0 16 | //! } 17 | /// ``` 18 | 19 | use std::f32; 20 | 21 | use geometry::{Geometry, DifferentialGeometry, Boundable, BBox, Sampleable}; 22 | use linalg::{self, Normal, Vector, Ray, Point}; 23 | use mc; 24 | 25 | /// A disk with some inner and outer radius allowing it to 26 | /// have a hole in the middle. The disk is oriented with the center 27 | /// at the origin and the normal pointing along +Z. 28 | #[derive(Clone, Copy)] 29 | pub struct Disk { 30 | radius: f32, 31 | inner_radius: f32, 32 | } 33 | 34 | impl Disk { 35 | /// Create a new disk with some inner and outer radius 36 | pub fn new(radius: f32, inner_radius: f32) -> Disk { 37 | Disk { radius: radius, inner_radius: inner_radius } 38 | } 39 | } 40 | 41 | impl Geometry for Disk { 42 | fn intersect(&self, ray: &mut Ray) -> Option { 43 | // The disk lies in the XY plane so if the ray doesn't cross this plane 44 | // there won't be any intersection 45 | if f32::abs(ray.d.z) == 0.0 { 46 | return None; 47 | } 48 | // We still treat the disk as an infinite XY plane for just a little longer 49 | // and here find the point where the ray crosses this plane 50 | let t = -ray.o.z / ray.d.z; 51 | if t < ray.min_t || t > ray.max_t { 52 | return None; 53 | } 54 | // We've hit the plane so now see if that hit is on the disk 55 | let p = ray.at(t); 56 | let dist_sqr = p.x * p.x + p.y * p.y; 57 | if dist_sqr > self.radius * self.radius || dist_sqr < self.inner_radius * self.inner_radius { 58 | return None; 59 | } 60 | let mut phi = f32::atan2(p.y, p.x); 61 | if phi < 0.0 { 62 | phi += f32::consts::PI * 2.0; 63 | } 64 | if phi > f32::consts::PI * 2.0 { 65 | return None; 66 | } 67 | ray.max_t = t; 68 | let hit_radius = f32::sqrt(dist_sqr); 69 | let u = phi / (2.0 * f32::consts::PI); 70 | let v = 1.0 - (hit_radius - self.inner_radius) / (self.radius - self.inner_radius); 71 | let dp_du = Vector::new(-f32::consts::PI * 2.0 * p.y, f32::consts::PI * 2.0 * p.x, 0.0); 72 | let dp_dv = ((self.inner_radius - self.radius) / hit_radius) * Vector::new(p.x, p.y, 0.0); 73 | Some(DifferentialGeometry::new(&p, &Normal::new(0.0, 0.0, 1.0), 74 | u, v, ray.time, &dp_du, &dp_dv, self)) 75 | } 76 | } 77 | 78 | impl Boundable for Disk { 79 | fn bounds(&self, _: f32, _: f32) -> BBox { 80 | BBox::span(Point::new(-self.radius, -self.radius, -0.1), Point::new(self.radius, self.radius, 0.1)) 81 | } 82 | } 83 | 84 | impl Sampleable for Disk { 85 | fn sample_uniform(&self, samples: &(f32, f32)) -> (Point, Normal) { 86 | let disk_pos = mc::concentric_sample_disk(samples); 87 | let p = Point::new(disk_pos.0 * self.radius, disk_pos.1 * self.radius, 0.0); 88 | let n = Normal::new(0.0, 0.0, 1.0); 89 | (p, n) 90 | } 91 | fn sample(&self, _: &Point, samples: &(f32, f32)) -> (Point, Normal) { 92 | self.sample_uniform(samples) 93 | } 94 | fn surface_area(&self) -> f32 { 95 | f32::consts::PI * (self.radius * self.radius - self.inner_radius * self.inner_radius) 96 | } 97 | fn pdf(&self, p: &Point, w_i: &Vector) -> f32 { 98 | // Time doesn't matter here, we're already in the object's space so we're moving 99 | // with it so to speak 100 | let mut ray = Ray::segment(p, w_i, 0.001, f32::INFINITY, 0.0); 101 | match self.intersect(&mut ray) { 102 | Some(d) => { 103 | let w = -*w_i; 104 | let pdf = p.distance_sqr(&ray.at(ray.max_t)) 105 | / (f32::abs(linalg::dot(&d.n, &w)) * self.surface_area()); 106 | if f32::is_finite(pdf) { pdf } else { 0.0 } 107 | }, 108 | None => 0.0 109 | } 110 | } 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/geometry/instance.rs: -------------------------------------------------------------------------------- 1 | //! Defines an instance of some piece of geometry in the scene, instances 2 | //! can re-use loaded geometry but apply different transformations and materials 3 | //! to them 4 | //! 5 | //! # Scene Usage Example 6 | //! An instance is an instantion of geometry in your scene. The instance needs to know 7 | //! its type (emitter/receiver), its name along with the geometry to use, material to apply 8 | //! and transformation to place the object in the scene. The instances are specified in the 9 | //! objects list in the scene file. 10 | //! 11 | //! The transform for the object is specified in the order in which the transformations should 12 | //! be applied. For information on emitters see the emitter documentation. 13 | //! 14 | //! ```json 15 | //! "objects": [ 16 | //! { 17 | //! "name": "back_wall", 18 | //! "type": "receiver", 19 | //! "material": "white_wall", 20 | //! "geometry": { 21 | //! "type": "plane" 22 | //! }, 23 | //! "transform": [ 24 | //! { 25 | //! "type": "scaling", 26 | //! "scaling": 15.0 27 | //! }, 28 | //! { 29 | //! "type": "translate", 30 | //! "translation": [0.0, 1.0, 20] 31 | //! } 32 | //! ] 33 | //! }, 34 | //! ... 35 | //! ] 36 | //! ``` 37 | //! 38 | //! # Object Group Example 39 | //! You can also specify groups of objects to have the same transformation applied to all of them. 40 | //! This is done with a 'group' type object followed by a list of objects in the group. For a full 41 | //! example see `scenes/cornell_box.json`. 42 | //! 43 | //! ```json 44 | //! "objects": [ 45 | //! { 46 | //! "name": "my_group", 47 | //! "type": "group", 48 | //! "transform": [ 49 | //! { 50 | //! "type": "translate", 51 | //! "translation": [0.0, 1.0, 20] 52 | //! } 53 | //! ], 54 | //! "objects": [ 55 | //! ... 56 | //! ] 57 | //! }, 58 | //! ... 59 | //! ] 60 | //! ``` 61 | //! 62 | 63 | use std::sync::Arc; 64 | 65 | use geometry::{Intersection, Boundable, BBox, BoundableGeom, Receiver, Emitter, 66 | SampleableGeom}; 67 | use material::Material; 68 | use linalg::{Ray, AnimatedTransform}; 69 | use film::AnimatedColor; 70 | 71 | /// Defines an instance of some geometry with its own transform and material 72 | pub enum Instance { 73 | Emitter(Emitter), 74 | Receiver(Receiver), 75 | } 76 | 77 | impl Instance { 78 | /// Create an instance of the geometry in the scene that will only receive light. 79 | pub fn receiver(geom: Arc, material: Arc, 80 | transform: AnimatedTransform, tag: String) -> Instance { 81 | Instance::Receiver(Receiver::new(geom, material, transform, tag)) 82 | } 83 | /// Create an instance of the geometry in the scene that will emit and receive light 84 | pub fn area_light(geom: Arc, material: Arc, 85 | emission: AnimatedColor, transform: AnimatedTransform, tag: String) -> Instance { 86 | Instance::Emitter(Emitter::area(geom, material, emission, transform, tag)) 87 | } 88 | /// Create a point light at the origin that is transformed by `transform` to its location 89 | /// in the world 90 | pub fn point_light(transform: AnimatedTransform, emission: AnimatedColor, tag: String) -> Instance { 91 | Instance::Emitter(Emitter::point(transform, emission, tag)) 92 | } 93 | /// Test the ray for intersection against this insance of geometry. 94 | /// returns Some(Intersection) if an intersection was found and None if not. 95 | /// If an intersection is found `ray.max_t` will be set accordingly 96 | pub fn intersect(&self, ray: &mut Ray) -> Option { 97 | let hit = match *self { 98 | Instance::Emitter(ref e) => e.intersect(ray), 99 | Instance::Receiver(ref r) => r.intersect(ray), 100 | }; 101 | match hit { 102 | Some((dg, mat)) => Some(Intersection::new(dg, self, mat)), 103 | None => None, 104 | } 105 | } 106 | /// Get the tag for this instance 107 | pub fn tag(&self) -> &str { 108 | match *self { 109 | Instance::Emitter(ref e) => &e.tag[..], 110 | Instance::Receiver(ref r) => &r.tag[..], 111 | } 112 | } 113 | /// Get the transform for this instance 114 | pub fn get_transform(&self) -> &AnimatedTransform { 115 | match *self { 116 | Instance::Emitter(ref e) => e.get_transform(), 117 | Instance::Receiver(ref r) => r.get_transform() 118 | } 119 | } 120 | /// Set the transform for this instance 121 | pub fn set_transform(&mut self, transform: AnimatedTransform) { 122 | match *self { 123 | Instance::Emitter(ref mut e) => e.set_transform(transform), 124 | Instance::Receiver(ref mut r) => r.set_transform(transform) 125 | } 126 | } 127 | } 128 | 129 | impl Boundable for Instance { 130 | fn bounds(&self, start: f32, end: f32) -> BBox { 131 | match *self { 132 | Instance::Emitter(ref e) => e.bounds(start, end), 133 | Instance::Receiver(ref r) => r.bounds(start, end), 134 | } 135 | } 136 | } 137 | 138 | -------------------------------------------------------------------------------- /src/geometry/intersection.rs: -------------------------------------------------------------------------------- 1 | //! Defines the Intersection type which stores information about 2 | //! a full intersection, eg. hit info about the geometry and instance 3 | //! that was intersected 4 | 5 | use geometry::{Instance, DifferentialGeometry}; 6 | use material::Material; 7 | 8 | /// Stores information about an intersection that occured with some instance 9 | /// of geometry in the scene 10 | #[derive(Clone, Copy)] 11 | pub struct Intersection<'a, 'b> { 12 | /// The differential geometry holding information about the piece of geometry 13 | /// that was hit 14 | pub dg: DifferentialGeometry<'a>, 15 | /// The instance of geometry that was hit 16 | pub instance: &'b Instance, 17 | /// The material of the instance that was hit 18 | pub material: &'b Material, 19 | } 20 | 21 | impl<'a, 'b> Intersection<'a, 'b> { 22 | /// Construct the Intersection from a potential hit stored in a 23 | /// Option. Returns None if `dg` is None 24 | /// or if the instance member of `dg` is None 25 | pub fn new(dg: DifferentialGeometry<'a>, inst: &'b Instance, mat: &'b Material) 26 | -> Intersection<'a, 'b> { 27 | Intersection { dg: dg, instance: inst, material: mat } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | //! The geometry module defines the Geometry trait implemented by 2 | //! the various geometry in the ray tracer and provides some standard 3 | //! geometry for rendering 4 | //! 5 | //! # Scene Usage Example 6 | //! All geometry will appear within an object specification and requires the type 7 | //! of geometry being specified along with any parameters for that geometry. 8 | //! 9 | //! An instance has a geometry along with additional information like a material 10 | //! and transformation to place it in the world, see the instance module for more. 11 | //! 12 | //! ```json 13 | //! "objects": [ 14 | //! { 15 | //! "type": "The_Instance_Type", 16 | //! ... 17 | //! "geometry": { 18 | //! "type": "The_Geometry_Type", 19 | //! ... 20 | //! } 21 | //! }, 22 | //! ... 23 | //! ] 24 | //! ``` 25 | 26 | use linalg::{Point, Vector, Ray, Normal}; 27 | 28 | pub use self::differential_geometry::DifferentialGeometry; 29 | pub use self::intersection::Intersection; 30 | pub use self::instance::Instance; 31 | pub use self::sphere::Sphere; 32 | pub use self::disk::Disk; 33 | pub use self::rectangle::Rectangle; 34 | pub use self::bbox::BBox; 35 | pub use self::bvh::BVH; 36 | pub use self::mesh::Mesh; 37 | pub use self::animated_mesh::AnimatedMesh; 38 | pub use self::receiver::Receiver; 39 | pub use self::emitter::Emitter; 40 | 41 | pub mod differential_geometry; 42 | pub mod intersection; 43 | pub mod instance; 44 | pub mod sphere; 45 | pub mod disk; 46 | pub mod rectangle; 47 | pub mod bbox; 48 | pub mod bvh; 49 | pub mod mesh; 50 | pub mod animated_mesh; 51 | pub mod receiver; 52 | pub mod emitter; 53 | 54 | /// Trait implemented by geometric primitives 55 | pub trait Geometry { 56 | /// Test a ray for intersection with the geometry. 57 | /// The ray should have been previously transformed into the geometry's 58 | /// object space otherwise the test will be incorrect. 59 | /// Returns the differential geometry containing the hit information if the 60 | /// ray hit the object and set's the ray's `max_t` member accordingly 61 | fn intersect(&self, ray: &mut Ray) -> Option; 62 | } 63 | 64 | /// Trait implemented by scene objects that can report an AABB describing their bounds 65 | pub trait Boundable { 66 | /// Get an AABB reporting the object's bounds over the time period 67 | /// The default implementation assumes the object isn't animated and 68 | /// simply returns its bounds. This is kind of a hack to use 69 | /// the BVH for animated geomtry (instances) and non-animated geometry (triangles). 70 | fn bounds(&self, start: f32, end: f32) -> BBox; 71 | /// Have the object recompute its bounds for the time range. In the case 72 | /// of deforming geometry this can rebuild acceleration structures for example. 73 | fn update_deformation(&mut self, start: f32, end: f32) {} 74 | } 75 | 76 | /// Trait implemented by geometry that can sample a point on its surface 77 | pub trait Sampleable { 78 | /// Uniformly sample a position and normal on the surface using the samples passed 79 | fn sample_uniform(&self, samples: &(f32, f32)) -> (Point, Normal); 80 | /// Sample the object using the probability density of the solid angle 81 | /// from `p` to the sampled point on the surface. 82 | /// Returns the sampled point and the surface normal at that point 83 | fn sample(&self, p: &Point, samples: &(f32, f32)) -> (Point, Normal); 84 | /// Return the surface area of the shape 85 | fn surface_area(&self) -> f32; 86 | /// Compute the PDF that the ray from `p` with direction `w_i` intersects 87 | /// the shape 88 | fn pdf(&self, p: &Point, w_i: &Vector) -> f32; 89 | } 90 | 91 | pub trait BoundableGeom: Geometry + Boundable {} 92 | impl BoundableGeom for T where T: Geometry + Boundable {} 93 | 94 | pub trait SampleableGeom: Geometry + Boundable + Sampleable {} 95 | impl SampleableGeom for T where T: Geometry + Boundable + Sampleable {} 96 | -------------------------------------------------------------------------------- /src/geometry/receiver.rs: -------------------------------------------------------------------------------- 1 | //! A receiver is an instance of geometry that does not emit any light 2 | 3 | use std::sync::Arc; 4 | use geometry::{Boundable, BBox, BoundableGeom, DifferentialGeometry}; 5 | use material::Material; 6 | use linalg::{Ray, AnimatedTransform}; 7 | 8 | /// An instance of geometry in the scene that only receives light 9 | pub struct Receiver { 10 | /// The geometry that's being instanced. 11 | geom: Arc, 12 | /// The material being used by this instance. 13 | pub material: Arc, 14 | /// The transform to world space 15 | transform: AnimatedTransform, 16 | /// Tag to identify the instance 17 | pub tag: String, 18 | } 19 | 20 | impl Receiver { 21 | /// Create a new instance of some geometry in the scene 22 | pub fn new(geom: Arc, material: Arc, 23 | transform: AnimatedTransform, tag: String) -> Receiver { 24 | Receiver { geom: geom, material: material, transform: transform, tag: tag } 25 | } 26 | /// Test the ray for intersection against this insance of geometry. 27 | /// returns Some(Intersection) if an intersection was found and None if not. 28 | /// If an intersection is found `ray.max_t` will be set accordingly 29 | pub fn intersect(&self, ray: &mut Ray) -> Option<(DifferentialGeometry, &Material)> { 30 | let transform = self.transform.transform(ray.time); 31 | let mut local = transform.inv_mul_ray(ray); 32 | let mut dg = match self.geom.intersect(&mut local) { 33 | Some(dg) => dg, 34 | None => return None, 35 | }; 36 | ray.max_t = local.max_t; 37 | dg.p = transform * dg.p; 38 | dg.n = transform * dg.n; 39 | dg.ng = transform * dg.ng; 40 | dg.dp_du = transform * dg.dp_du; 41 | dg.dp_dv = transform * dg.dp_dv; 42 | Some((dg, &*self.material)) 43 | } 44 | /// Get the transform to place the receiver into world space 45 | pub fn get_transform(&self) -> &AnimatedTransform { 46 | &self.transform 47 | } 48 | /// Set the transform to place the receiver into world space 49 | pub fn set_transform(&mut self, transform: AnimatedTransform) { 50 | self.transform = transform; 51 | } 52 | } 53 | 54 | impl Boundable for Receiver { 55 | fn bounds(&self, start: f32, end: f32) -> BBox { 56 | self.transform.animation_bounds(&self.geom.bounds(start, end), start, end) 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/geometry/rectangle.rs: -------------------------------------------------------------------------------- 1 | //! Defines a rectangle centered at the origin, specified by its horizontal 2 | //! and vertical lengths 3 | //! 4 | //! # Scene Usage Example 5 | //! The rectangle takes two parameters, specifying its width and height. The 6 | //! rectangle will be centered at the origin and will have its normal facing 7 | //! along [0, 0, 1] 8 | //! 9 | //! ```json 10 | //! "geometry": { 11 | //! "type": "rectangle", 12 | //! "width": 1.2, 13 | //! "height" 2.5 14 | //! } 15 | //! ``` 16 | 17 | use std::f32; 18 | 19 | use geometry::{Geometry, DifferentialGeometry, Boundable, Sampleable, BBox}; 20 | use linalg::{self, Normal, Vector, Ray, Point}; 21 | 22 | /// A rectangle centered at the origin spanning [-width / 2, -height / 2] 23 | /// to [width / 2, height / 2] with a normal along [0, 0, 1] 24 | #[derive(Clone, Copy)] 25 | pub struct Rectangle { 26 | width: f32, 27 | height: f32, 28 | } 29 | 30 | impl Rectangle { 31 | /// Create a new rectangle with the desired width and height 32 | pub fn new(width: f32, height: f32) -> Rectangle { 33 | Rectangle { width: width, height: height } 34 | } 35 | } 36 | 37 | impl Geometry for Rectangle { 38 | fn intersect(&self, ray: &mut Ray) -> Option { 39 | // If the ray is perpindicular to the normal it can't intersect 40 | if f32::abs(ray.d.z) < 1e-8 { 41 | return None; 42 | } 43 | // Test for intersection against an infinite plane. Later we will 44 | // check that the hit found here is in the finite plane's extent 45 | let t = -ray.o.z / ray.d.z; 46 | if t < ray.min_t || t > ray.max_t { 47 | return None; 48 | } 49 | let p = ray.at(t); 50 | let half_width = self.width / 2.0; 51 | let half_height = self.height / 2.0; 52 | if p.x >= -half_width && p.x <= half_width && p.y >= -half_height && p.y <= half_height { 53 | ray.max_t = t; 54 | let n = Normal::new(0.0, 0.0, 1.0); 55 | let u = (p.x + half_width) / (2.0 * half_width); 56 | let v = (p.y + half_height) / (2.0 * half_height); 57 | let dp_du = Vector::new(half_width * 2.0, 0.0, 0.0); 58 | let dp_dv = Vector::new(0.0, half_height * 2.0, 0.0); 59 | Some(DifferentialGeometry::new(&p, &n, u, v, ray.time, &dp_du, &dp_dv, self)) 60 | } else { 61 | None 62 | } 63 | } 64 | } 65 | 66 | impl Boundable for Rectangle { 67 | fn bounds(&self, _: f32, _: f32) -> BBox { 68 | let half_width = self.width / 2.0; 69 | let half_height = self.height / 2.0; 70 | BBox::span(Point::new(-half_width, -half_height, 0.0), Point::new(half_width, half_height, 0.0)) 71 | } 72 | } 73 | 74 | impl Sampleable for Rectangle { 75 | /// Uniform sampling for a rect is simple: just scale the two samples into the 76 | /// rectangle's space and return them as the x,y coordinates of the point chosen 77 | fn sample_uniform(&self, samples: &(f32, f32)) -> (Point, Normal) { 78 | (Point::new(samples.0 * self.width - self.width / 2.0, samples.1 * self.height - self.height / 2.0, 0.0), 79 | Normal::new(0.0, 0.0, 1.0)) 80 | } 81 | fn sample(&self, _: &Point, samples: &(f32, f32)) -> (Point, Normal) { 82 | self.sample_uniform(samples) 83 | } 84 | /// Compute the sphere's surface area 85 | fn surface_area(&self) -> f32 { 86 | self.width * self.height 87 | } 88 | /// Compute the PDF that the ray from `p` with direction `w_i` intersects 89 | /// the shape. This is the same as disk for computing PDF, we just use the 90 | /// rectangle's surface area instead 91 | fn pdf(&self, p: &Point, w_i: &Vector) -> f32 { 92 | // Time doesn't matter here, we're already in the object's space so we're moving 93 | // with it so to speak 94 | let mut ray = Ray::segment(p, w_i, 0.001, f32::INFINITY, 0.0); 95 | match self.intersect(&mut ray) { 96 | Some(d) => { 97 | let w = -*w_i; 98 | let pdf = p.distance_sqr(&ray.at(ray.max_t)) 99 | / (f32::abs(linalg::dot(&d.n, &w)) * self.surface_area()); 100 | if f32::is_finite(pdf) { pdf } else { 0.0 } 101 | }, 102 | None => 0.0 103 | } 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/integrator/normals_debug.rs: -------------------------------------------------------------------------------- 1 | //! Defines the `NormalsDebug` integrator which renders out the object's normals 2 | //! 3 | //! # Scene Usage Example 4 | //! The `NormalsDebug` integrator just needs a maximum ray depth to terminate specular reflection 5 | //! and transmission rays. 6 | //! 7 | //! ```json 8 | //! "integrator": { 9 | //! "type": "normals_debug" 10 | //! } 11 | //! ``` 12 | 13 | use rand::StdRng; 14 | use light_arena::Allocator; 15 | 16 | use scene::Scene; 17 | use linalg::Ray; 18 | use geometry::{Intersection, Emitter}; 19 | use film::Colorf; 20 | use integrator::Integrator; 21 | use sampler::Sampler; 22 | 23 | /// The `NormalsDebug` integrator implementing the `NormalsDebug` recursive ray tracing algorithm 24 | #[derive(Clone, Copy, Debug)] 25 | pub struct NormalsDebug; 26 | 27 | impl Integrator for NormalsDebug { 28 | fn illumination(&self, _: &Scene, _: &[&Emitter], _: &Ray, 29 | hit: &Intersection, _: &mut Sampler, _: &mut StdRng, 30 | alloc: &Allocator) -> Colorf { 31 | let bsdf = hit.material.bsdf(hit, alloc); 32 | (Colorf::new(bsdf.n.x, bsdf.n.y, bsdf.n.z) + Colorf::broadcast(1.0)) / 2.0 33 | } 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/integrator/path.rs: -------------------------------------------------------------------------------- 1 | //! Defines the Path integrator which implements path tracing with 2 | //! explicit light sampling 3 | //! 4 | //! See [Kajiya, The Rendering Equation](http://dl.acm.org/citation.cfm?id=15902) 5 | //! 6 | //! # Scene Usage Example 7 | //! The pathtracer integrator needs a maximum ray depth to terminate rays at and 8 | //! a minimum ray depth to start applying Russian Roulette to terminate rays early. 9 | //! 10 | //! ```json 11 | //! "integrator": { 12 | //! "type": "pathtracer", 13 | //! "min_depth": 3, 14 | //! "max_depth": 8 15 | //! } 16 | //! ``` 17 | 18 | use std::f32; 19 | use rand::{StdRng, Rng}; 20 | use light_arena::Allocator; 21 | 22 | use scene::Scene; 23 | use linalg::{self, Ray}; 24 | use geometry::{Intersection, Emitter, Instance}; 25 | use film::Colorf; 26 | use integrator::Integrator; 27 | use bxdf::BxDFType; 28 | use sampler::{Sampler, Sample}; 29 | 30 | /// The path integrator implementing Path tracing with explicit light sampling 31 | #[derive(Clone, Copy, Debug)] 32 | pub struct Path { 33 | min_depth: usize, 34 | max_depth: usize, 35 | } 36 | 37 | impl Path { 38 | /// Create a new path integrator with the min and max length desired for paths 39 | pub fn new(min_depth: u32, max_depth: u32) -> Path { 40 | Path { min_depth: min_depth as usize, max_depth: max_depth as usize } 41 | } 42 | } 43 | 44 | impl Integrator for Path { 45 | fn illumination(&self, scene: &Scene, light_list: &[&Emitter], r: &Ray, 46 | hit: &Intersection, sampler: &mut Sampler, rng: &mut StdRng, 47 | alloc: &Allocator) -> Colorf { 48 | let num_samples = self.max_depth as usize + 1; 49 | let l_samples = alloc.alloc_slice::<(f32, f32)>(num_samples); 50 | let l_samples_comp = alloc.alloc_slice::(num_samples); 51 | let bsdf_samples = alloc.alloc_slice::<(f32, f32)>(num_samples); 52 | let bsdf_samples_comp = alloc.alloc_slice::(num_samples); 53 | let path_samples = alloc.alloc_slice::<(f32, f32)>(num_samples); 54 | let path_samples_comp = alloc.alloc_slice::(num_samples); 55 | sampler.get_samples_2d(l_samples, rng); 56 | sampler.get_samples_2d(bsdf_samples, rng); 57 | sampler.get_samples_2d(path_samples, rng); 58 | sampler.get_samples_1d(l_samples_comp, rng); 59 | sampler.get_samples_1d(bsdf_samples_comp, rng); 60 | sampler.get_samples_1d(path_samples_comp, rng); 61 | 62 | let mut illum = Colorf::black(); 63 | let mut path_throughput = Colorf::broadcast(1.0); 64 | // Track if the previous bounce was a specular one 65 | let mut specular_bounce = false; 66 | let mut current_hit = *hit; 67 | let mut ray = *r; 68 | let mut bounce = 0; 69 | loop { 70 | if bounce == 0 || specular_bounce { 71 | if let Instance::Emitter(ref e) = *current_hit.instance { 72 | let w = -ray.d; 73 | illum = illum + path_throughput * e.radiance(&w, &hit.dg.p, &hit.dg.ng, ray.time); 74 | } 75 | } 76 | let bsdf = current_hit.material.bsdf(¤t_hit, alloc); 77 | let w_o = -ray.d; 78 | let light_sample = Sample::new(&l_samples[bounce], l_samples_comp[bounce]); 79 | let bsdf_sample = Sample::new(&bsdf_samples[bounce], bsdf_samples_comp[bounce]); 80 | let li = self.sample_one_light(scene, light_list, &w_o, ¤t_hit.dg.p, &bsdf, 81 | &light_sample, &bsdf_sample, ray.time); 82 | illum = illum + path_throughput * li; 83 | 84 | // Determine the next direction to take the path by sampling the BSDF 85 | let path_sample = Sample::new(&path_samples[bounce], path_samples_comp[bounce]); 86 | let (f, w_i, pdf, sampled_type) = bsdf.sample(&w_o, BxDFType::all(), &path_sample); 87 | if f.is_black() || pdf == 0.0 { 88 | break; 89 | } 90 | specular_bounce = sampled_type.contains(&BxDFType::Specular); 91 | path_throughput = path_throughput * f * f32::abs(linalg::dot(&w_i, &bsdf.n)) / pdf; 92 | 93 | // Check if we're beyond the min depth at which point we start trying to 94 | // terminate rays using Russian Roulette 95 | // TODO: Am I re-weighting properly? The Russian roulette results don't look quite as 96 | // nice, eg. damping light in transparent objects and such. 97 | if bounce > self.min_depth { 98 | let cont_prob = f32::max(0.5, path_throughput.luminance()); 99 | if rng.next_f32() > cont_prob { 100 | break; 101 | } 102 | // Re-weight the sum terms accordingly with the Russian roulette weight 103 | path_throughput = path_throughput / cont_prob; 104 | } 105 | if bounce == self.max_depth { 106 | break; 107 | } 108 | 109 | ray = ray.child(&bsdf.p, &w_i.normalized()); 110 | ray.min_t = 0.001; 111 | // Find the next vertex on the path 112 | match scene.intersect(&mut ray) { 113 | Some(h) => current_hit = h, 114 | None => break, 115 | } 116 | bounce += 1; 117 | } 118 | illum 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /src/integrator/whitted.rs: -------------------------------------------------------------------------------- 1 | //! Defines the Whitted integrator which implements Whitted recursive ray tracing 2 | //! See [Whitted, An improved illumination model for shaded display](http://dl.acm.org/citation.cfm?id=358882) 3 | //! 4 | //! # Scene Usage Example 5 | //! The Whitted integrator just needs a maximum ray depth to terminate specular reflection 6 | //! and transmission rays. 7 | //! 8 | //! ```json 9 | //! "integrator": { 10 | //! "type": "whitted", 11 | //! "max_depth": 8 12 | //! } 13 | //! ``` 14 | 15 | use std::f32; 16 | use rand::StdRng; 17 | use light_arena::Allocator; 18 | 19 | use scene::Scene; 20 | use linalg::{self, Ray}; 21 | use geometry::{Intersection, Emitter, Instance}; 22 | use film::Colorf; 23 | use integrator::Integrator; 24 | use bxdf::BxDFType; 25 | use light::Light; 26 | use sampler::Sampler; 27 | 28 | /// The Whitted integrator implementing the Whitted recursive ray tracing algorithm 29 | #[derive(Clone, Copy, Debug)] 30 | pub struct Whitted { 31 | /// The maximum recursion depth for rays 32 | max_depth: u32, 33 | } 34 | 35 | impl Whitted { 36 | /// Create a new Whitted integrator with the desired maximum recursion depth for rays 37 | pub fn new(max_depth: u32) -> Whitted { Whitted { max_depth: max_depth } } 38 | } 39 | 40 | impl Integrator for Whitted { 41 | fn illumination(&self, scene: &Scene, light_list: &[&Emitter], ray: &Ray, 42 | hit: &Intersection, sampler: &mut Sampler, rng: &mut StdRng, 43 | alloc: &Allocator) -> Colorf { 44 | let bsdf = hit.material.bsdf(hit, alloc); 45 | let w_o = -ray.d; 46 | let mut sample_2d = [(0.0, 0.0)]; 47 | sampler.get_samples_2d(&mut sample_2d[..], rng); 48 | let mut illum = Colorf::broadcast(0.0); 49 | if ray.depth == 0 { 50 | if let Instance::Emitter(ref e) = *hit.instance { 51 | let w = -ray.d; 52 | illum = illum + e.radiance(&w, &hit.dg.p, &hit.dg.ng, ray.time); 53 | } 54 | } 55 | 56 | for light in light_list { 57 | let (li, w_i, pdf, occlusion) = light.sample_incident(&hit.dg.p, &sample_2d[0], ray.time); 58 | let f = bsdf.eval(&w_o, &w_i, BxDFType::all()); 59 | if !li.is_black() && !f.is_black() && !occlusion.occluded(scene) { 60 | illum = illum + f * li * f32::abs(linalg::dot(&w_i, &bsdf.n)) / pdf; 61 | } 62 | } 63 | if ray.depth < self.max_depth { 64 | illum = illum + self.specular_reflection(scene, light_list, ray, &bsdf, sampler, rng, alloc); 65 | illum = illum + self.specular_transmission(scene, light_list, ray, &bsdf, sampler, rng, alloc); 66 | } 67 | illum 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | //! # tray\_rust - A Toy Ray Tracer in Rust 4 | //! 5 | //! tray\_rust is a toy physically based ray tracer built off of the techniques 6 | //! discussed in [Physically Based Rendering](http://pbrt.org/). It began life as a port of 7 | //! [tray](https://github.com/Twinklebear/tray) to [Rust](http://www.rust-lang.org) to check out the language. 8 | //! The renderer is currently capable of path tracing, supports triangle meshes (MTL support coming soon), 9 | //! and various physically based material models (including measured data from the 10 | //! [MERL BRDF Database](http://www.merl.com/brdf/)). tray\_rust also supports rigid body animation along 11 | //! B-spline paths and distributed rendering. 12 | //! 13 | //! [![Build Status](https://travis-ci.org/Twinklebear/tray_rust.svg?branch=master)](https://travis-ci.org/Twinklebear/tray_rust) 14 | //! 15 | //! ## Running 16 | //! 17 | //! Running and passing `--help` or `-h` will print out options you can pass to the renderer which are documented in the help. 18 | //! For the more complicated use cases I hope to do some write ups and guides on how to use them (e.g. distributed rendering, 19 | //! animation) but this may take a while. I strongly recommend running the release build as the debug version will be very slow. 20 | //! 21 | //! ## Building Your Own Scenes 22 | //! 23 | //! To position and animate objects, the camera and so on the 24 | //! [Blender plugin](https://github.com/Twinklebear/tray_rust_blender) is the easiest to use. However the plugin 25 | //! is still in development and missing some features like setting materials, changing light properties and such so 26 | //! you'll still currently need to do those by hand in the exported JSON file. For materials take a look at 27 | //! the [materials documentation](http://www.willusher.io/tray_rust/tray_rust/material/index.html) for lights 28 | //! you'll likely just want to change the emission color which is an RGB color plus a strength term. 29 | //! 30 | //! Start at the documentation for the [scene module](http://www.willusher.io/tray_rust/tray_rust/scene/index.html), 31 | //! there are also a few example [scenes](https://github.com/Twinklebear/tray_rust/tree/master/scenes) included but not all 32 | //! the models are provided. From a clean `git clone` you should be able to run 33 | //! [cornell\_box.json](https://github.com/Twinklebear/tray_rust/blob/master/scenes/cornell_box.json) and 34 | //! [smallpt.json](https://github.com/Twinklebear/tray_rust/blob/master/scenes/smallpt.json). I plan to add some 35 | //! more simple scenes that show usage of other features like animation to provide examples. The rigid body animation 36 | //! feature is relatively new though so I haven't had time to document it properly yet. 37 | //! 38 | //! ## TODO 39 | //! 40 | //! - More material models (eg. more microfacet models, rough glass, etc.) 41 | //! - Textures 42 | //! - Support for using an OBJ's associated MTL files 43 | //! - Bump mapping 44 | //! - [Subsurface scattering?](http://en.wikipedia.org/wiki/Subsurface_scattering) 45 | //! - [Vertex Connection and Merging?](http://iliyan.com/publications/VertexMerging) 46 | //! 47 | //! ## Sample Renders 48 | //! 49 | //! In the samples the the Buddha, Dragon, Bunny and Lucy statue are from 50 | //! [The Stanford Scanning Repository](http://graphics.stanford.edu/data/3Dscanrep/). 51 | //! The Rust logo model was made by 52 | //! [Nylithius on BlenderArtists](http://blenderartists.org/forum/showthread.php?362836-Rust-language-3D-logo). 53 | //! The Utah teapot used is from [Morgan McGuire's page](http://graphics.cs.williams.edu/data/meshes.xml) and 54 | //! the monkey head is Blender's Suzanne. I've made minor tweaks to some of the models so for convenience 55 | //! you can find versions that can be easily loaded into the sample scenes [here](https://drive.google.com/folderview?id=0B-l_lLEMo1YeflUzUndCd01hOHhRNUhrQUowM3hVd2pCc3JrSXRiS3FQSzRYLWtGcGM0eGc&usp=sharing), though the 56 | //! cube model for the Cornell box scene is included. 57 | //! The materials on the Rust logo, Buddha, Dragon and Lucy are from the 58 | //! [MERL BRDF Database](http://www.merl.com/brdf/). 59 | //! 60 | //! Render times are formatted as hh:mm:ss and were measured using 144 threads on a machine with four 61 | //! [Xeon E7-8890 v3](http://ark.intel.com/products/84685/Intel-Xeon-Processor-E7-8890-v3-45M-Cache-2_50-GHz) 62 | //! CPUs. The machine is an early/engineering sample from Intel so your results may differ, but massive thanks to 63 | //! Intel for the hardware! Some older renders are shown as well without timing since they were 64 | //! run on a different machine. 65 | //! 66 | //! Some more sample renders can be found [here](http://imgur.com/a/3qNBc). 67 | //! 68 | //! 69 | //! Model gallery 71 | //! 72 | //! 73 | //! 1920x1080, 4096 samples/pixel. Rendering: 00:43:36.45. 74 | //! 75 | //! 76 | //! Rust Logo with friends, disk 78 | //! 79 | //! 80 | //! 1920x1080, 4096 samples/pixel. Rendering: 00:49:33.514. 81 | //! 82 | 83 | extern crate enum_set as enum_set; 84 | extern crate rand; 85 | extern crate byteorder; 86 | extern crate serde_json; 87 | extern crate bspline; 88 | extern crate docopt; 89 | #[macro_use] 90 | extern crate serde_derive; 91 | extern crate scoped_threadpool; 92 | extern crate image; 93 | extern crate bincode; 94 | extern crate mio; 95 | extern crate la; 96 | extern crate light_arena; 97 | 98 | pub mod linalg; 99 | pub mod film; 100 | pub mod geometry; 101 | pub mod sampler; 102 | pub mod integrator; 103 | pub mod scene; 104 | pub mod bxdf; 105 | pub mod material; 106 | pub mod light; 107 | pub mod mc; 108 | pub mod partition; 109 | pub mod exec; 110 | pub mod texture; 111 | 112 | -------------------------------------------------------------------------------- /src/light/mod.rs: -------------------------------------------------------------------------------- 1 | //! Defines the light interface implemented by all lights in `tray_rust` and 2 | //! the `OcclusionTester` which provides a convenient interface for doing 3 | //! shadow tests for lights 4 | 5 | use std::f32; 6 | 7 | use linalg::{Point, Vector, Ray}; 8 | use film::Colorf; 9 | use scene::Scene; 10 | 11 | /// The `OcclusionTester` provides a simple interface for setting up and executing 12 | /// occlusion queries in the scene 13 | #[derive(Clone, Copy, Debug)] 14 | pub struct OcclusionTester { 15 | /// The ray (or ray segment) that the occlusion test is performed on 16 | pub ray: Ray, 17 | } 18 | 19 | impl OcclusionTester { 20 | /// Create an occlusion tester to perform the test between two points 21 | pub fn test_points(a: &Point, b: &Point, time: f32) -> OcclusionTester { 22 | OcclusionTester { ray: Ray::segment(a, &(*b - *a), 0.001, 0.999, time) } 23 | } 24 | /// Create an occlusion tester to perform the test along the ray starting at `p` 25 | /// and in direction `d` 26 | pub fn test_ray(p: &Point, d: &Vector, time: f32) -> OcclusionTester { 27 | OcclusionTester { ray: Ray::segment(p, d, 0.001, f32::INFINITY, time) } 28 | } 29 | /// Perform the occlusion test in the scene 30 | pub fn occluded(&self, scene: &Scene) -> bool { 31 | let mut r = self.ray; 32 | if let Some(_) = scene.intersect(&mut r) { 33 | true 34 | } else { 35 | false 36 | } 37 | } 38 | } 39 | 40 | /// Trait implemented by all lights in `tray_rust`. Provides methods for sampling 41 | /// the light and in the future ones for checking if it's a delta light, computing 42 | /// its power and so on. 43 | pub trait Light { 44 | /// Sample the illumination from the light arriving at the point `p` 45 | /// Returns the color, incident light direction, pdf and occlusion tester object 46 | /// `samples` will be used to randomly sample the light. 47 | fn sample_incident(&self, p: &Point, samples: &(f32, f32), time: f32) 48 | -> (Colorf, Vector, f32, OcclusionTester); 49 | /// Determine if the light is described by a delta distribution 50 | fn delta_light(&self) -> bool; 51 | /// Compute the PDF for sampling the point with incident direction `w_i` 52 | fn pdf(&self, p: &Point, w_i: &Vector, time: f32) -> f32; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/linalg/animated_transform.rs: -------------------------------------------------------------------------------- 1 | //! Provides an animated transformation that moves an object between a 2 | //! set of specified keyframes. 3 | 4 | use std::ops::Mul; 5 | 6 | use bspline::BSpline; 7 | 8 | use linalg::{self, quaternion, Keyframe, Transform}; 9 | use geometry::BBox; 10 | 11 | /// An animated transform that blends between the keyframes in its transformation 12 | /// list over time. 13 | #[derive(Clone, Debug)] 14 | pub struct AnimatedTransform { 15 | /// List of animated transforms in hierarchical order, e.g. the lowest 16 | /// index is the object's, index 1 holds its direct parent's transform, etc. 17 | keyframes: Vec>, 18 | } 19 | 20 | impl AnimatedTransform { 21 | /// Create an animated transformation blending between the passed keyframes 22 | pub fn with_keyframes(mut keyframes: Vec, knots: Vec, degree: usize) -> AnimatedTransform { 23 | // so we know what degree and so on. 24 | // Step through and make sure all rotations take the shortest path 25 | for i in 1..keyframes.len() { 26 | // If the dot product is negative flip the current quaternion to 27 | // take the shortest path through the rotation 28 | if quaternion::dot(&keyframes[i - 1].rotation, &keyframes[i].rotation) < 0.0 { 29 | keyframes[i].rotation = -keyframes[i].rotation; 30 | } 31 | } 32 | AnimatedTransform { keyframes: vec![BSpline::new(degree, keyframes, knots)] } 33 | } 34 | pub fn unanimated(transform: &Transform) -> AnimatedTransform { 35 | let key = Keyframe::new(transform); 36 | AnimatedTransform { keyframes: vec![BSpline::new(0, vec![key], vec![0.0, 1.0])] } 37 | } 38 | /// Compute the transformation matrix for the animation at some time point using B-Spline 39 | /// interpolation. 40 | pub fn transform(&self, time: f32) -> Transform { 41 | let mut transform = Transform::identity(); 42 | // Step through the transform stack, applying each animation transform at this 43 | // time as we move up 44 | for spline in &self.keyframes { 45 | let domain = spline.knot_domain(); 46 | let t = 47 | if spline.control_points().count() == 1 { 48 | spline.control_points().next().unwrap().transform() 49 | } else { 50 | let t_val = linalg::clamp(time, domain.0, domain.1); 51 | spline.point(t_val).transform() 52 | }; 53 | transform = t * transform; 54 | } 55 | transform 56 | } 57 | /// Compute the bounds of the box moving through the animation sequence by sampling time 58 | pub fn animation_bounds(&self, b: &BBox, start: f32, end: f32) -> BBox { 59 | if !self.is_animated() { 60 | let t = self.transform(start); 61 | t * *b 62 | } else { 63 | let mut ret = BBox::new(); 64 | for i in 0..128 { 65 | let time = linalg::lerp((i as f32) / 127.0, &start, &end); 66 | let t = self.transform(time); 67 | ret = ret.box_union(&(t * *b)); 68 | } 69 | ret 70 | } 71 | } 72 | /// Check if the transform is actually animated 73 | pub fn is_animated(&self) -> bool { 74 | self.keyframes.is_empty() || self.keyframes.iter().fold(true, |b, spline| b && spline.control_points().count() > 1) 75 | } 76 | } 77 | 78 | impl Mul for AnimatedTransform { 79 | type Output = AnimatedTransform; 80 | /// Compose the animated transformations 81 | fn mul(self, mut rhs: AnimatedTransform) -> AnimatedTransform { 82 | for l in &self.keyframes[..] { 83 | rhs.keyframes.push(l.clone()); 84 | } 85 | rhs 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/linalg/keyframe.rs: -------------------------------------------------------------------------------- 1 | //! Provides a keyframe transformation which is a transform associated 2 | //! with a specific point in time 3 | 4 | use bspline; 5 | use la; 6 | 7 | use linalg::{quaternion, Vector, Matrix4, Quaternion, Transform}; 8 | 9 | /// A transformation associated with a specific point in time. Note 10 | /// that this transform is now more implicit since they keyframe's times 11 | /// are stored as knots in the b-spline animation path 12 | #[derive(Debug, Copy, Clone)] 13 | pub struct Keyframe { 14 | pub translation: Vector, 15 | pub rotation: Quaternion, 16 | pub scaling: Vector, 17 | } 18 | 19 | impl Keyframe { 20 | /// Construct a new keyframe transformation, The transform will 21 | /// be stored in a decomposed form, M = TRS. 22 | pub fn new(transform: &Transform) -> Keyframe { 23 | let (t, r, s) = Keyframe::decompose(transform); 24 | Keyframe { translation: t, rotation: r, scaling: s } 25 | } 26 | /// Construct the keyframe from the decomposed transformation 27 | pub fn from_parts(translation: &Vector, rotation: &Quaternion, scaling: &Vector) -> Keyframe { 28 | Keyframe { translation: *translation, rotation: *rotation, scaling: *scaling } 29 | } 30 | /// Decompose the transformation into its component translation, rotation and 31 | /// scaling operations. 32 | fn decompose(transform: &Transform) -> (Vector, Quaternion, Vector) { 33 | let m = transform.mat; 34 | let translation = Vector::new(*m.at(0, 3), *m.at(1, 3), *m.at(2, 3)); 35 | // Robust matrix decomposition, based on Mitsuba: 36 | // We use SVD to extract rotation and scaling matrices that properly account for flip 37 | let la_mat = la::Matrix::::new(3, 3, vec![*m.at(0, 0) as f64, *m.at(0, 1) as f64, *m.at(0, 2) as f64, 38 | *m.at(1, 0) as f64, *m.at(1, 1) as f64, *m.at(1, 2) as f64, 39 | *m.at(2, 0) as f64, *m.at(2, 1) as f64, *m.at(2, 2) as f64]); 40 | // TODO: More explanation of the math going on here, why do we choose these matrices for 41 | // q and p. q is the basis transform of the matrix without scaling so it represents the 42 | // rotation, while p is the scaling transformed from the basis into the canonical basis 43 | // for R^3, giving scaling in the canonical basis. Is this intuition correct? 44 | let svd = la::SVD::::new(&la_mat); 45 | let mut q = svd.get_u() * svd.get_v().t(); 46 | let mut p = svd.get_v() * svd.get_s() * svd.get_v().t(); 47 | if q.det() < 0.0 { 48 | q = -q; 49 | p = -p; 50 | } 51 | let rotation = Quaternion::from_matrix( 52 | &Matrix4::new([q.get(0, 0) as f32, q.get(0, 1) as f32, q.get(0, 2) as f32, 0.0, 53 | q.get(1, 0) as f32, q.get(1, 1) as f32, q.get(1, 2) as f32, 0.0, 54 | q.get(2, 0) as f32, q.get(2, 1) as f32, q.get(2, 2) as f32, 0.0, 55 | 0.0, 0.0, 0.0, 1.0])); 56 | let scaling = Vector::new(p.get(0, 0) as f32, p.get(1, 1) as f32, p.get(2, 2) as f32); 57 | (translation, rotation, scaling) 58 | } 59 | /// Return the transformation stored for this keyframe 60 | pub fn transform(&self) -> Transform { 61 | let m = self.rotation.to_matrix(); 62 | Transform::translate(&self.translation) * Transform::from_mat(&m) * Transform::scale(&self.scaling) 63 | } 64 | } 65 | 66 | impl bspline::Interpolate for Keyframe { 67 | fn interpolate(&self, other: &Keyframe, t: f32) -> Keyframe { 68 | let translation = (1.0 - t) * self.translation + t * other.translation; 69 | let rotation = quaternion::slerp(t, &self.rotation, &other.rotation); 70 | let scaling = (1.0 - t) * self.scaling + t * other.scaling; 71 | Keyframe::from_parts(&translation, &rotation, &scaling) 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/linalg/mod.rs: -------------------------------------------------------------------------------- 1 | //! The linalg module provides some basic linear algebra functionality for 2 | //! transforming 3D geometry 3 | 4 | use std::f32; 5 | use std::ops::{Index, Mul, Add}; 6 | 7 | // Re-export the linalg types from the internal modules 8 | pub use self::vector::Vector; 9 | pub use self::normal::Normal; 10 | pub use self::point::Point; 11 | pub use self::ray::Ray; 12 | pub use self::matrix4::Matrix4; 13 | pub use self::transform::Transform; 14 | pub use self::quaternion::Quaternion; 15 | pub use self::keyframe::Keyframe; 16 | pub use self::animated_transform::AnimatedTransform; 17 | 18 | pub mod vector; 19 | pub mod normal; 20 | pub mod point; 21 | pub mod ray; 22 | pub mod matrix4; 23 | pub mod transform; 24 | pub mod quaternion; 25 | pub mod keyframe; 26 | pub mod animated_transform; 27 | 28 | /// Enum representing on of the 3 spatial axes 29 | #[derive(Copy, Clone, Debug)] 30 | pub enum Axis { X, Y, Z } 31 | 32 | /// Convert value in degrees to radians 33 | /// TODO: See where `f32::to_radians` ends up, if it gets stabilized into Rust switch back to it 34 | pub fn to_radians(d: f32) -> f32 { 35 | f32::consts::PI / 180.0 * d 36 | } 37 | /// Compute the cross product of two vectors 38 | pub fn cross, B: Index>(a: &A, b: &B) -> vector::Vector { 39 | Vector::new(a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]) 40 | } 41 | /// Compute the dot product of two vectors 42 | pub fn dot, B: Index>(a: &A, b: &B) -> f32 { 43 | a[0] * b[0] + a[1] * b[1] + a[2] * b[2] 44 | } 45 | /// Lerp between `a` and `b` at some distance `t` where t is in [0, 1] 46 | /// and t = 0 returns `a` and t = 1 returns `b` 47 | pub fn lerp + Add + Copy>(t: f32, a: &T, b: &T) -> T { 48 | *a * (1.0 - t) + *b * t 49 | } 50 | /// Clamp `x` to be between `min` and `max` 51 | pub fn clamp(x: T, min: T, max: T) -> T { 52 | if x < min { min } else if x > max { max } else { x } 53 | } 54 | /// Compute the direction specified by `theta` and `phi` in the spherical coordinate system 55 | pub fn spherical_dir(sin_theta: f32, cos_theta: f32, phi: f32) -> Vector { 56 | Vector::new(sin_theta * f32::cos(phi), sin_theta * f32::sin(phi), 57 | cos_theta) 58 | } 59 | /// Compute the direction specified by `theta` and `phi` in the coordinate system 60 | /// formed by `x`, `y` and `z` 61 | pub fn spherical_dir_coords(sin_theta: f32, cos_theta: f32, phi: f32, x: &Vector, y: &Vector, z: &Vector) 62 | -> Vector { 63 | sin_theta * f32::cos(phi) * *x + sin_theta * f32::sin(phi) * *y + cos_theta * *z 64 | } 65 | /// Compute the value of theta for the vector in the spherical coordinate system 66 | pub fn spherical_theta(v: &vector::Vector) -> f32 { 67 | f32::acos(clamp(v.z, -1f32, 1f32)) 68 | } 69 | /// Compute the value of phi for the vector in the spherical coordinate system 70 | pub fn spherical_phi(v: &vector::Vector) -> f32 { 71 | match f32::atan2(v.y, v.x) { 72 | x if x < 0f32 => x + f32::consts::PI * 2.0, 73 | x => x, 74 | } 75 | } 76 | /// Try to solve the quadratic equation `a*t^2 + b*t + c = 0` and return the two 77 | /// real roots if a solution exists 78 | pub fn solve_quadratic(a: f32, b: f32, c: f32) -> Option<(f32, f32)> { 79 | let discrim_sqr = b * b - 4.0 * a * c; 80 | if discrim_sqr < 0.0 { 81 | None 82 | } else { 83 | let discrim = f32::sqrt(discrim_sqr); 84 | let q = if b < 0.0 { 85 | -0.5 * (b - discrim) 86 | } else { 87 | -0.5 * (b + discrim) 88 | }; 89 | match (q / a, c / q) { 90 | (x, y) if x > y => Some((y, x)), 91 | (x, y) => Some((x, y)), 92 | } 93 | } 94 | } 95 | /// Compute a local ortho-normal coordinate system from a single vector. 96 | pub fn coordinate_system(e1: &Vector) -> (Vector, Vector) { 97 | let e2 = 98 | if f32::abs(e1.x) > f32::abs(e1.y) { 99 | let inv_len = 1.0 / f32::sqrt(e1.x * e1.x + e1.z * e1.z); 100 | Vector::new(-e1.z * inv_len, 0.0, e1.x * inv_len) 101 | } 102 | else { 103 | let inv_len = 1.0 / f32::sqrt(e1.y * e1.y + e1.z * e1.z); 104 | Vector::new(0.0, e1.z * inv_len, -e1.y * inv_len) 105 | }; 106 | let e3 = cross(e1, &e2); 107 | (e2, e3) 108 | } 109 | /// Compute the reflection of `w` about `v`, both vectors should be normalized 110 | pub fn reflect(w: &Vector, v: &Vector) -> Vector { 111 | 2.0 * dot(w, v) * *v - *w 112 | } 113 | /// Compute the refraction of `w` entering surface with normal `n` where 114 | /// `eta` is the refractive index of the incident material divided 115 | /// by that of the exiting materia. In the case of total internal 116 | /// refraction this will return None. 117 | pub fn refract(w: &Vector, n: &Vector, eta: f32) -> Option { 118 | let cos_t1 = dot(n, w); 119 | let sin_t1_sqr = f32::max(0.0, 1.0 - f32::powf(cos_t1, 2.0)); 120 | let sin_t2_sqr = f32::powf(eta, 2.0) * sin_t1_sqr; 121 | if sin_t2_sqr >= 1.0 { 122 | None 123 | } else { 124 | let cos_t2 = f32::sqrt(1.0 - sin_t2_sqr); 125 | Some(eta * -*w + (eta * cos_t1 - cos_t2) * *n) 126 | } 127 | } 128 | 129 | #[test] 130 | fn test_cross() { 131 | let a = Vector::new(1f32, 0f32, 0f32); 132 | let b = Vector::new(0f32, 1f32, 0f32); 133 | let c = cross(&a, &b); 134 | assert!(c == Vector::new(0f32, 0f32, 1f32)); 135 | } 136 | 137 | #[test] 138 | fn test_dot() { 139 | let a = Vector::new(1f32, 2f32, 3f32); 140 | let b = Vector::new(4f32, 5f32, 6f32); 141 | assert!(dot(&a, &b) == 1f32 * 4f32 + 2f32 * 5f32 + 3f32 * 6f32); 142 | } 143 | 144 | -------------------------------------------------------------------------------- /src/linalg/normal.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut}; 3 | 4 | use linalg::{self, Vector}; 5 | 6 | /// Normal is a standard 3 component normal but transforms as a normal 7 | /// normal when transformations are applied 8 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] 9 | pub struct Normal { 10 | pub x: f32, 11 | pub y: f32, 12 | pub z: f32, 13 | } 14 | 15 | impl Normal { 16 | /// Initialize the normal and set values for x, y, z 17 | pub fn new(x: f32, y: f32, z: f32) -> Normal { 18 | Normal { x: x, y: y, z: z } 19 | } 20 | /// Initialize the normal with the same value of x, y, z 21 | pub fn broadcast(x: f32) -> Normal { 22 | Normal { x: x, y: x, z: x } 23 | } 24 | /// Compute the squared length of the normal 25 | pub fn length_sqr(&self) -> f32 { 26 | self.x * self.x + self.y * self.y + self.z * self.z 27 | } 28 | /// Compute the length of the normal 29 | pub fn length(&self) -> f32 { 30 | f32::sqrt(self.length_sqr()) 31 | } 32 | /// Get a normalized copy of this normal 33 | pub fn normalized(&self) -> Normal { 34 | let len = self.length(); 35 | Normal { x: self.x / len, y: self.y / len, z: self.z / len } 36 | } 37 | /// Return a normal facing along the same direction as v 38 | pub fn face_forward(&self, v: &Vector) -> Normal { 39 | if linalg::dot(self, v) < 0f32 { -*self } else { *self } 40 | } 41 | } 42 | 43 | impl Add for Normal { 44 | type Output = Normal; 45 | /// Add two normals together 46 | fn add(self, rhs: Normal) -> Normal { 47 | Normal { x: self.x + rhs.x, y: self.y + rhs.y, z: self.z + rhs.z } 48 | } 49 | } 50 | 51 | impl Sub for Normal { 52 | type Output = Normal; 53 | /// Subtract two normals 54 | fn sub(self, rhs: Normal) -> Normal { 55 | Normal { x: self.x - rhs.x, y: self.y - rhs.y, z: self.z - rhs.z } 56 | } 57 | } 58 | 59 | impl Mul for Normal { 60 | type Output = Normal; 61 | /// Multiply two normals 62 | fn mul(self, rhs: Normal) -> Normal { 63 | Normal { x: self.x * rhs.x, y: self.y * rhs.y, z: self.z * rhs.z } 64 | } 65 | } 66 | 67 | impl Mul for Normal { 68 | type Output = Normal; 69 | /// Scale the normal by some value 70 | fn mul(self, rhs: f32) -> Normal { 71 | Normal { x: self.x * rhs, y: self.y * rhs, z: self.z * rhs } 72 | } 73 | } 74 | 75 | impl Mul for f32 { 76 | type Output = Normal; 77 | /// Scale the normal by some value 78 | fn mul(self, rhs: Normal) -> Normal { 79 | Normal { x: self * rhs.x, y: self * rhs.y, z: self * rhs.z } 80 | } 81 | } 82 | 83 | impl Div for Normal { 84 | type Output = Normal; 85 | /// Divide the normals components by the right hand side's components 86 | fn div(self, rhs: Normal) -> Normal { 87 | Normal { x: self.x / rhs.x, y: self.y / rhs.y, z: self.z / rhs.z } 88 | } 89 | } 90 | 91 | impl Div for Normal { 92 | type Output = Normal; 93 | /// Divide the normals components by scalar 94 | fn div(self, rhs: f32) -> Normal { 95 | Normal { x: self.x / rhs, y: self.y / rhs, z: self.z / rhs } 96 | } 97 | } 98 | 99 | impl Neg for Normal { 100 | type Output = Normal; 101 | /// Negate the normal 102 | fn neg(self) -> Normal { 103 | Normal { x: -self.x, y: -self.y, z: -self.z } 104 | } 105 | } 106 | 107 | impl Index for Normal { 108 | type Output = f32; 109 | /// Access the normal by index 110 | /// 111 | /// - 0 = x 112 | /// - 1 = y 113 | /// - 2 = z 114 | fn index(&self, i: usize) -> &f32 { 115 | match i { 116 | 0 => &self.x, 117 | 1 => &self.y, 118 | 2 => &self.z, 119 | _ => panic!("Invalid index into normal"), 120 | } 121 | } 122 | } 123 | 124 | impl IndexMut for Normal { 125 | /// Access the normal by index 126 | /// 127 | /// - 0 = x 128 | /// - 1 = y 129 | /// - 2 = z 130 | fn index_mut(&mut self, i: usize) -> &mut f32 { 131 | match i { 132 | 0 => &mut self.x, 133 | 1 => &mut self.y, 134 | 2 => &mut self.z, 135 | _ => panic!("Invalid index into normal"), 136 | } 137 | } 138 | } 139 | 140 | #[test] 141 | fn test_face_fwd() { 142 | let n = Normal::new(1f32, 0f32, 0f32); 143 | let v = Vector::new(-1f32, 0f32, 0f32); 144 | let n_fwd = n.face_forward(&v); 145 | assert!(n_fwd == Normal::new(-1f32, 0f32, 0f32)); 146 | } 147 | 148 | -------------------------------------------------------------------------------- /src/linalg/point.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut}; 2 | use linalg::{Vector, Axis}; 3 | 4 | /// Point is a standard 3 component point but transforms as a point 5 | /// point when transformations are applied 6 | #[derive(Debug, Copy, Clone, PartialEq)] 7 | pub struct Point { 8 | pub x: f32, 9 | pub y: f32, 10 | pub z: f32, 11 | } 12 | 13 | impl Point { 14 | /// Initialize the point and set values for x, y, z 15 | pub fn new(x: f32, y: f32, z: f32) -> Point { 16 | Point { x: x, y: y, z: z } 17 | } 18 | /// Initialize the point with the same value of x, y, z 19 | pub fn broadcast(x: f32) -> Point { 20 | Point { x: x, y: x, z: x } 21 | } 22 | /// Initialize a point to be all 0 values 23 | pub fn origin() -> Point { 24 | Point::broadcast(0.0) 25 | } 26 | /// Compute the squared distance between this point and another 27 | pub fn distance_sqr(&self, a: &Point) -> f32 { 28 | (*self - *a).length_sqr() 29 | } 30 | /// Compute the distance between this point and another 31 | pub fn distance(&self, a: &Point) -> f32 { 32 | (*self - *a).length() 33 | } 34 | } 35 | 36 | impl Add for Point { 37 | type Output = Point; 38 | /// Add two points together 39 | fn add(self, rhs: Point) -> Point { 40 | Point { x: self.x + rhs.x, y: self.y + rhs.y, z: self.z + rhs.z } 41 | } 42 | } 43 | 44 | impl Add for Point { 45 | type Output = Point; 46 | /// Add two points together 47 | fn add(self, rhs: Vector) -> Point { 48 | Point { x: self.x + rhs.x, y: self.y + rhs.y, z: self.z + rhs.z } 49 | } 50 | } 51 | 52 | impl Sub for Point { 53 | type Output = Vector; 54 | /// Subtract two points to get the vector between them 55 | fn sub(self, rhs: Point) -> Vector { 56 | Vector { x: self.x - rhs.x, y: self.y - rhs.y, z: self.z - rhs.z } 57 | } 58 | } 59 | 60 | impl Sub for Point { 61 | type Output = Point; 62 | /// Subtract a vector from a point, translating the point by -vector 63 | fn sub(self, rhs: Vector) -> Point { 64 | Point { x: self.x - rhs.x, y: self.y - rhs.y, z: self.z - rhs.z } 65 | } 66 | } 67 | 68 | impl Mul for Point { 69 | type Output = Point; 70 | /// Scale the point by some value 71 | fn mul(self, rhs: f32) -> Point { 72 | Point { x: self.x * rhs, y: self.y * rhs, z: self.z * rhs } 73 | } 74 | } 75 | 76 | impl Mul for f32 { 77 | type Output = Point; 78 | /// Scale the vector by some value 79 | fn mul(self, rhs: Point) -> Point { 80 | Point { x: self * rhs.x, y: self * rhs.y, z: self * rhs.z } 81 | } 82 | } 83 | 84 | impl Mul for Point { 85 | type Output = Point; 86 | /// Scale the vector by some value 87 | fn mul(self, rhs: Vector) -> Point { 88 | Point { x: self.x * rhs.x, y: self.y * rhs.y, z: self.z * rhs.z } 89 | } 90 | } 91 | 92 | impl Div for Point { 93 | type Output = Point; 94 | /// Divide the points components by the right hand side's components 95 | fn div(self, rhs: Point) -> Point { 96 | Point { x: self.x / rhs.x, y: self.y / rhs.y, z: self.z / rhs.z } 97 | } 98 | } 99 | 100 | impl Div for Point { 101 | type Output = Point; 102 | /// Divide the points components by scalar 103 | fn div(self, rhs: f32) -> Point { 104 | Point { x: self.x / rhs, y: self.y / rhs, z: self.z / rhs } 105 | } 106 | } 107 | 108 | impl Neg for Point { 109 | type Output = Point; 110 | /// Negate the point 111 | fn neg(self) -> Point { 112 | Point { x: -self.x, y: -self.y, z: -self.z } 113 | } 114 | } 115 | 116 | impl Index for Point { 117 | type Output = f32; 118 | /// Access the point by index 119 | /// 120 | /// - 0 = x 121 | /// - 1 = y 122 | /// - 2 = z 123 | fn index(&self, i: usize) -> &f32 { 124 | match i { 125 | 0 => &self.x, 126 | 1 => &self.y, 127 | 2 => &self.z, 128 | _ => panic!("Invalid index into point"), 129 | } 130 | } 131 | } 132 | 133 | impl Index for Point { 134 | type Output = f32; 135 | /// Access the point by index 136 | /// 137 | /// - 0 = x 138 | /// - 1 = y 139 | /// - 2 = z 140 | fn index(&self, i: Axis) -> &f32 { 141 | match i { 142 | Axis::X => &self.x, 143 | Axis::Y => &self.y, 144 | Axis::Z => &self.z, 145 | } 146 | } 147 | } 148 | 149 | impl IndexMut for Point { 150 | /// Access the point by index 151 | /// 152 | /// - 0 = x 153 | /// - 1 = y 154 | /// - 2 = z 155 | fn index_mut(&mut self, i: usize) -> &mut f32 { 156 | match i { 157 | 0 => &mut self.x, 158 | 1 => &mut self.y, 159 | 2 => &mut self.z, 160 | _ => panic!("Invalid index into point"), 161 | } 162 | } 163 | } 164 | 165 | #[test] 166 | fn test_distance_sqr() { 167 | let a = Point::new(0f32, 0f32, 0f32); 168 | let b = Point::new(3f32, 4f32, 0f32); 169 | assert!(b.distance_sqr(&a) == 25f32); 170 | } 171 | 172 | -------------------------------------------------------------------------------- /src/linalg/ray.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use linalg::{Point, Vector}; 4 | 5 | /// Ray is a standard 3D ray, starting at origin `o` and heading in direction `d` 6 | /// The min and max points along the ray can be specified with `min_t` and `max_t` 7 | /// `depth` is the recursion depth of the ray 8 | #[derive(Debug, Copy, Clone, PartialEq)] 9 | pub struct Ray { 10 | /// Origin of the ray 11 | pub o: Point, 12 | /// Direction the ray is heading 13 | pub d: Vector, 14 | /// Point along the ray that the actual ray starts at, `p = o + min_t * d` 15 | pub min_t: f32, 16 | /// Point along the ray at which it stops, will be inf if the ray is infinite 17 | pub max_t: f32, 18 | /// Recursion depth of the ray 19 | pub depth: u32, 20 | /// Time point sampled by this ray 21 | pub time: f32, 22 | } 23 | 24 | impl Ray { 25 | /// Create a new ray from `o` heading in `d` with infinite length 26 | pub fn new(o: &Point, d: &Vector, time: f32) -> Ray { 27 | Ray { o: *o, d: *d, min_t: 0f32, max_t: f32::INFINITY, depth: 0, time: time } 28 | } 29 | /// Create a new segment ray from `o + min_t * d` to `o + max_t * d` 30 | pub fn segment(o: &Point, d: &Vector, min_t: f32, max_t: f32, time: f32) -> Ray { 31 | Ray { o: *o, d: *d, min_t: min_t, max_t: max_t, depth: 0, time: time } 32 | } 33 | /// Create a child ray from the parent starting at `o` and heading in `d` 34 | pub fn child(&self, o: &Point, d: &Vector) -> Ray { 35 | Ray { o: *o, d: *d, min_t: 0f32, max_t: f32::INFINITY, depth: self.depth + 1, time: self.time } 36 | } 37 | /// Create a child ray segment from `o + min_t * d` to `o + max_t * d` 38 | pub fn child_segment(&self, o: &Point, d: &Vector, min_t: f32, max_t: f32) -> Ray { 39 | Ray { o: *o, d: *d, min_t: min_t, max_t: max_t, depth: self.depth + 1, time: self.time } 40 | } 41 | /// Evaulate the ray equation at some t value and return the point 42 | /// returns result of `self.o + t * self.d` 43 | pub fn at(&self, t: f32) -> Point { 44 | self.o + self.d * t 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/linalg/vector.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut}; 3 | 4 | use linalg::Point; 5 | 6 | /// Vector is a standard 3 component vector 7 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] 8 | pub struct Vector { 9 | pub x: f32, 10 | pub y: f32, 11 | pub z: f32, 12 | } 13 | 14 | impl Vector { 15 | /// Initialize the vector and set values for x, y, z 16 | pub fn new(x: f32, y: f32, z: f32) -> Vector { 17 | Vector { x: x, y: y, z: z } 18 | } 19 | /// Initialize the vector with the same value of x, y, z 20 | pub fn broadcast(x: f32) -> Vector { 21 | Vector { x: x, y: x, z: x } 22 | } 23 | /// Compute the squared length of the vector 24 | pub fn length_sqr(&self) -> f32 { 25 | self.x * self.x + self.y * self.y + self.z * self.z 26 | } 27 | /// Compute the length of the vector 28 | pub fn length(&self) -> f32 { 29 | f32::sqrt(self.length_sqr()) 30 | } 31 | /// Get a normalized copy of this vector 32 | pub fn normalized(&self) -> Vector { 33 | let len = self.length(); 34 | Vector { x: self.x / len, y: self.y / len, z: self.z / len } 35 | } 36 | } 37 | 38 | impl Add for Vector { 39 | type Output = Vector; 40 | /// Add two vectors together 41 | fn add(self, rhs: Vector) -> Vector { 42 | Vector { x: self.x + rhs.x, y: self.y + rhs.y, z: self.z + rhs.z } 43 | } 44 | } 45 | 46 | impl Sub for Vector { 47 | type Output = Vector; 48 | /// Subtract two vectors 49 | fn sub(self, rhs: Vector) -> Vector { 50 | Vector { x: self.x - rhs.x, y: self.y - rhs.y, z: self.z - rhs.z } 51 | } 52 | } 53 | 54 | impl Mul for Vector { 55 | type Output = Vector; 56 | /// Multiply two vectors 57 | fn mul(self, rhs: Vector) -> Vector { 58 | Vector { x: self.x * rhs.x, y: self.y * rhs.y, z: self.z * rhs.z } 59 | } 60 | } 61 | 62 | impl Mul for Vector { 63 | type Output = Vector; 64 | /// Scale the vector by some value 65 | fn mul(self, rhs: f32) -> Vector { 66 | Vector { x: self.x * rhs, y: self.y * rhs, z: self.z * rhs } 67 | } 68 | } 69 | 70 | impl Mul for f32 { 71 | type Output = Vector; 72 | /// Scale the vector by some value 73 | fn mul(self, rhs: Vector) -> Vector { 74 | Vector { x: self * rhs.x, y: self * rhs.y, z: self * rhs.z } 75 | } 76 | } 77 | 78 | impl Mul for Vector { 79 | type Output = Point; 80 | /// Scale the vector by some value 81 | fn mul(self, rhs: Point) -> Point { 82 | Point { x: self.x * rhs.x, y: self.y * rhs.y, z: self.z * rhs.z } 83 | } 84 | } 85 | 86 | impl Div for Vector { 87 | type Output = Vector; 88 | /// Divide the vectors components by the right hand side's components 89 | fn div(self, rhs: Vector) -> Vector { 90 | Vector { x: self.x / rhs.x, y: self.y / rhs.y, z: self.z / rhs.z } 91 | } 92 | } 93 | 94 | impl Div for Vector { 95 | type Output = Vector; 96 | /// Divide the vectors components by a scalar 97 | fn div(self, rhs: f32) -> Vector { 98 | Vector { x: self.x / rhs, y: self.y / rhs, z: self.z / rhs } 99 | } 100 | } 101 | 102 | impl Neg for Vector { 103 | type Output = Vector; 104 | /// Negate the vector 105 | fn neg(self) -> Vector { 106 | Vector { x: -self.x, y: -self.y, z: -self.z } 107 | } 108 | } 109 | 110 | impl Index for Vector { 111 | type Output = f32; 112 | /// Access the vector by index 113 | /// 114 | /// - 0 = x 115 | /// - 1 = y 116 | /// - 2 = z 117 | fn index(&self, i: usize) -> &f32 { 118 | match i { 119 | 0 => &self.x, 120 | 1 => &self.y, 121 | 2 => &self.z, 122 | _ => panic!("Invalid index into vector"), 123 | } 124 | } 125 | } 126 | 127 | impl IndexMut for Vector { 128 | /// Access the vector by index 129 | /// 130 | /// - 0 = x 131 | /// - 1 = y 132 | /// - 2 = z 133 | fn index_mut(&mut self, i: usize) -> &mut f32 { 134 | match i { 135 | 0 => &mut self.x, 136 | 1 => &mut self.y, 137 | 2 => &mut self.z, 138 | _ => panic!("Invalid index into vector"), 139 | } 140 | } 141 | } 142 | 143 | #[test] 144 | fn test_len_sqr() { 145 | let v = Vector::new(1f32, 2f32, 3f32); 146 | assert!(v.length_sqr() == 1f32 + 4f32 + 9f32); 147 | } 148 | 149 | #[test] 150 | fn test_idx() { 151 | let mut v = Vector::new(1f32, 2f32, 3f32); 152 | assert!(v[0] == 1f32 && v[1] == 2f32 && v[2] == 3f32); 153 | { 154 | let x = &mut v[1]; 155 | *x = 5f32; 156 | } 157 | assert!(v[1] == 5f32); 158 | } 159 | 160 | -------------------------------------------------------------------------------- /src/material/glass.rs: -------------------------------------------------------------------------------- 1 | //! Defines a specular glass material 2 | //! 3 | //! # Scene Usage Example 4 | //! The specular glass material describes a thin glass surface type of material, 5 | //! not a solid block of glass (there is no absorption of light). The glass requires 6 | //! a reflective and emissive color along with a refrective index, eta. 7 | //! 8 | //! ```json 9 | //! "materials": [ 10 | //! { 11 | //! "name": "clear_glass", 12 | //! "type": "glass", 13 | //! "reflect": [1, 1, 1], 14 | //! "transmit": [1, 1, 1], 15 | //! "eta": 1.52 16 | //! }, 17 | //! ... 18 | //! ] 19 | //! ``` 20 | 21 | use std::sync::Arc; 22 | 23 | use light_arena::Allocator; 24 | 25 | use geometry::Intersection; 26 | use bxdf::{BxDF, BSDF, SpecularReflection, SpecularTransmission}; 27 | use bxdf::fresnel::Dielectric; 28 | use material::Material; 29 | use texture::Texture; 30 | 31 | /// The Glass material describes specularly transmissive and reflective glass material 32 | pub struct Glass { 33 | reflect: Arc, 34 | transmit: Arc, 35 | eta: Arc, 36 | } 37 | 38 | impl Glass { 39 | /// Create the glass material with the desired color and index of refraction 40 | /// `reflect`: color of reflected light 41 | /// `transmit`: color of transmitted light 42 | /// `eta`: refractive index of the material 43 | pub fn new(reflect: Arc, 44 | transmit: Arc, 45 | eta: Arc) -> Glass { 46 | Glass { reflect: reflect, transmit: transmit, eta: eta } 47 | } 48 | } 49 | 50 | impl Material for Glass { 51 | fn bsdf<'a, 'b, 'c>(&'a self, hit: &Intersection<'a, 'b>, 52 | alloc: &'c Allocator) -> BSDF<'c> where 'a: 'c { 53 | // TODO: I don't like this counting and junk we have to do to figure out 54 | // the slice size and then the indices. Is there a better way? 55 | let reflect = self.reflect.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 56 | let transmit = self.transmit.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 57 | let eta = self.eta.sample_f32(hit.dg.u, hit.dg.v, hit.dg.time); 58 | 59 | let mut num_bxdfs = 0; 60 | if !reflect.is_black() { 61 | num_bxdfs += 1; 62 | } 63 | if !transmit.is_black() { 64 | num_bxdfs += 1; 65 | } 66 | let bxdfs = alloc.alloc_slice::<&BxDF>(num_bxdfs); 67 | 68 | let mut i = 0; 69 | let fresnel = alloc.alloc(Dielectric::new(1.0, eta)); 70 | if !reflect.is_black() { 71 | bxdfs[i] = alloc.alloc(SpecularReflection::new(&reflect, fresnel)); 72 | i += 1; 73 | } 74 | if !transmit.is_black() { 75 | bxdfs[i] = alloc.alloc(SpecularTransmission::new(&transmit, fresnel)); 76 | } 77 | BSDF::new(bxdfs, eta, &hit.dg) 78 | } 79 | } 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/material/matte.rs: -------------------------------------------------------------------------------- 1 | //! Defines a matte material used to describe diffuse materials 2 | //! 3 | //! # Scene Usage Example 4 | //! The matte material requires a diffuse color and a roughness for the material. A roughness of 0 5 | //! will select a [Lambertian](https://en.wikipedia.org/wiki/Lambertian_reflectance) model 6 | //! while a roughness > 0 will select an 7 | //! [Oren-Nayar](https://en.wikipedia.org/wiki/Oren%E2%80%93Nayar_reflectance_model) 8 | //! reflectance model. 9 | //! 10 | //! ```json 11 | //! "materials": [ 12 | //! { 13 | //! "name": "purple_matte", 14 | //! "type": "matte", 15 | //! "diffuse": [1, 0, 1], 16 | //! "roughness" 0.5 17 | //! }, 18 | //! ... 19 | //! ] 20 | //! ``` 21 | 22 | use std::sync::Arc; 23 | 24 | use light_arena::Allocator; 25 | 26 | use geometry::Intersection; 27 | use bxdf::{BxDF, BSDF, Lambertian, OrenNayar}; 28 | use material::Material; 29 | use texture::Texture; 30 | 31 | /// The Matte material describes diffuse materials with either a Lambertian or 32 | /// Oren-Nayar BRDF. The Lambertian BRDF is used for materials with no roughness 33 | /// while Oren-Nayar is used for those with some roughness. 34 | pub struct Matte { 35 | diffuse: Arc, 36 | roughness: Arc, 37 | } 38 | 39 | impl Matte { 40 | /// Create a new Matte material with the desired diffuse color and roughness 41 | pub fn new(diffuse: Arc, 42 | roughness: Arc) -> Matte 43 | { 44 | Matte { 45 | diffuse: diffuse.clone(), 46 | roughness: roughness.clone() 47 | } 48 | } 49 | } 50 | 51 | impl Material for Matte { 52 | fn bsdf<'a, 'b, 'c>(&'a self, hit: &Intersection<'a, 'b>, 53 | alloc: &'c Allocator) -> BSDF<'c> where 'a: 'c 54 | { 55 | let diffuse = self.diffuse.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 56 | let roughness = self.roughness.sample_f32(hit.dg.u, hit.dg.v, hit.dg.time); 57 | 58 | let bsdfs = alloc.alloc_slice::<&'c BxDF>(1); 59 | if roughness == 0.0 { 60 | bsdfs[0] = alloc.alloc(Lambertian::new(&diffuse)); 61 | } else { 62 | bsdfs[0] = alloc.alloc(OrenNayar::new(&diffuse, roughness)); 63 | } 64 | BSDF::new(bsdfs, 1.0, &hit.dg) 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/material/merl.rs: -------------------------------------------------------------------------------- 1 | //! The MERL Material represents the surface's properties through data loaded from a 2 | //! [MERL BRDF Database file](http://www.merl.com/brdf/). The material loads and parses 3 | //! the data then hands it off to its BRDF which will be used to actually compute the 4 | //! surface properties 5 | //! 6 | //! # Scene Usage Example 7 | //! The MERL material requires a filepath to a BRDF table downloaded from the 8 | //! [MERL BRDF Database](http://www.merl.com/brdf/). 9 | //! 10 | //! ```json 11 | //! "materials": [ 12 | //! { 13 | //! "name": "oxidized_steel", 14 | //! "type": "merl", 15 | //! "file": "./black-oxidized-steel.binary" 16 | //! }, 17 | //! ... 18 | //! ] 19 | //! ``` 20 | 21 | use std::iter; 22 | use std::path::Path; 23 | use std::fs::File; 24 | use std::io::BufReader; 25 | 26 | use byteorder::{LittleEndian, ReadBytesExt}; 27 | use light_arena::Allocator; 28 | 29 | use bxdf::{self, BSDF, BxDF}; 30 | use material::Material; 31 | use geometry::Intersection; 32 | 33 | /// Material that uses measured data to model the surface reflectance properties. 34 | /// The measured data is from "A Data-Driven Reflectance Model", 35 | /// by Wojciech Matusik, Hanspeter Pfister, Matt Brand and Leonard McMillan, 36 | /// in ACM Transactions on Graphics 22, 3(2003), 759-769 37 | pub struct Merl { 38 | /// Vec containing the BRDF values for various incident/exiting angles 39 | brdf: Vec, 40 | /// Number of theta_h measurements in `brdf` 41 | n_theta_h: usize, 42 | /// Number of theta_d measurements in `brdf` 43 | n_theta_d: usize, 44 | /// Number of phi_d measurements in `brdf` 45 | n_phi_d: usize, 46 | } 47 | 48 | impl Merl { 49 | /// Create a new MERL BRDF by loading the refletance data from a MERL BRDF 50 | /// database file 51 | pub fn load_file(path: &Path) -> Merl { 52 | let file = match File::open(path) { 53 | Ok(f) => f, 54 | Err(e) => { 55 | panic!("material::Merl::load_file - failed to open {:?} due to {}", path, e); 56 | }, 57 | }; 58 | let mut reader = BufReader::new(file); 59 | // Values we expect to read from a MERL BRDF file for each dimension 60 | let n_theta_h = 90; 61 | let n_theta_d = 90; 62 | let n_phi_d = 180; 63 | let dims = [reader.read_i32::().unwrap() as usize, 64 | reader.read_i32::().unwrap() as usize, 65 | reader.read_i32::().unwrap() as usize]; 66 | if n_theta_h != dims[0] || n_theta_d != dims[1] || n_phi_d != dims[2] { 67 | panic!("material::Merl::load_file - Invalid MERL file header, aborting"); 68 | } 69 | 70 | let n_vals = n_theta_h * n_theta_d * n_phi_d; 71 | let mut brdf = Vec::with_capacity(3 * n_vals); 72 | brdf.extend(iter::repeat(0.0).take(3 * n_vals)); 73 | let scaling = [1.0 / 1500.0, 1.0 / 1500.0, 1.66 / 1500.0]; 74 | // Read the n_vals corresponding to the red, green or blue component 75 | for (c, s) in scaling.iter().enumerate() { 76 | for i in 0..n_vals { 77 | // The BRDF data is stored in double precision with these odd scaling factors 78 | // so decode the value 79 | let x = (reader.read_f64::().unwrap() * s) as f32; 80 | brdf[3 * i + c] = f32::max(0.0, x); 81 | } 82 | } 83 | Merl { brdf: brdf, n_theta_h: n_theta_h, n_theta_d: n_theta_d, n_phi_d: n_phi_d } 84 | } 85 | } 86 | 87 | impl Material for Merl { 88 | fn bsdf<'a, 'b, 'c>(&'a self, hit: &Intersection<'a, 'b>, 89 | alloc: &'c Allocator) -> BSDF<'c> where 'a: 'c { 90 | let bxdfs = alloc.alloc_slice::<&BxDF>(1); 91 | bxdfs[0] = alloc.alloc(bxdf::Merl::new(&self.brdf[..], self.n_theta_h, self.n_theta_d, self.n_phi_d)); 92 | BSDF::new(bxdfs, 1.0, &hit.dg) 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/material/metal.rs: -------------------------------------------------------------------------------- 1 | //! Provides a material for modelling metal surfaces of varying roughness 2 | //! using the Torrance Sparrow BRDF and a Blinn microfacet distribution 3 | //! TODO: Add Ashikman-Shirley (spelling?) anisotropic microfacet model 4 | //! 5 | //! # Scene Usage Example 6 | //! The metal material requires a refractive index and absorption coefficient 7 | //! that describe the physical properties of the metal along with a roughness 8 | //! to specify how rough the surface of the metal is. 9 | //! 10 | //! ```json 11 | //! "materials": [ 12 | //! { 13 | //! "name": "rough_silver", 14 | //! "type": "metal", 15 | //! "refractive_index": [0.155265, 0.116723, 0.138381], 16 | //! "absorption_coefficient": [4.82835, 3.12225, 2.14696], 17 | //! "roughness": 0.3 18 | //! }, 19 | //! ... 20 | //! ] 21 | //! ``` 22 | 23 | use std::sync::Arc; 24 | 25 | use light_arena::Allocator; 26 | 27 | use film::Colorf; 28 | use geometry::Intersection; 29 | use bxdf::{BxDF, BSDF, TorranceSparrow}; 30 | use bxdf::microfacet::Beckmann; 31 | use bxdf::fresnel::Conductor; 32 | use material::Material; 33 | use texture::Texture; 34 | 35 | /// The Metal material describes metals of varying roughness 36 | pub struct Metal { 37 | eta: Arc, 38 | k: Arc, 39 | roughness: Arc, 40 | } 41 | 42 | impl Metal { 43 | /// Create a new metal material specifying the reflectance properties of the metal 44 | pub fn new(eta: Arc, 45 | k: Arc, 46 | roughness: Arc) -> Metal 47 | { 48 | Metal { eta: eta.clone(), 49 | k: k.clone(), 50 | roughness: roughness.clone() 51 | } 52 | } 53 | } 54 | 55 | impl Material for Metal { 56 | fn bsdf<'a, 'b, 'c>(&self, hit: &Intersection<'a, 'b>, 57 | alloc: &'c Allocator) -> BSDF<'c> where 'a: 'c { 58 | let eta = self.eta.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 59 | let k = self.k.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 60 | let roughness = self.roughness.sample_f32(hit.dg.u, hit.dg.v, hit.dg.time); 61 | 62 | let bxdfs = alloc.alloc_slice::<&BxDF>(1); 63 | let fresnel = alloc.alloc(Conductor::new(&eta, &k)); 64 | let microfacet = alloc.alloc(Beckmann::new(roughness)); 65 | bxdfs[0] = alloc.alloc(TorranceSparrow::new(&Colorf::broadcast(1.0), fresnel, microfacet)); 66 | BSDF::new(bxdfs, 1.0, &hit.dg) 67 | } 68 | } 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/material/mod.rs: -------------------------------------------------------------------------------- 1 | //! Defines the trait implemented by all materials and exports various 2 | //! supported material types. Materials are used to define how BxDFs are 3 | //! composed to create the desired appearance 4 | //! 5 | //! # Scene Usage Example 6 | //! The material will be specified within the materials list of the scene object. A type 7 | //! and name for the material along with any additional parameters is required to specify one. 8 | //! The name is used when specifying which material should be used by an object in the scene. 9 | //! 10 | //! ```json 11 | //! "materials": [ 12 | //! { 13 | //! "name": "my_material", 14 | //! "type": "The_Material_Type", 15 | //! ... 16 | //! } 17 | //! ... 18 | //! ] 19 | //! ``` 20 | 21 | use light_arena::Allocator; 22 | 23 | use geometry::Intersection; 24 | use bxdf::BSDF; 25 | 26 | pub use self::matte::Matte; 27 | pub use self::specular_metal::SpecularMetal; 28 | pub use self::glass::Glass; 29 | pub use self::merl::Merl; 30 | pub use self::plastic::Plastic; 31 | pub use self::metal::Metal; 32 | pub use self::rough_glass::RoughGlass; 33 | 34 | pub mod matte; 35 | pub mod specular_metal; 36 | pub mod glass; 37 | pub mod merl; 38 | pub mod plastic; 39 | pub mod metal; 40 | pub mod rough_glass; 41 | 42 | /// Trait implemented by materials. Provides method to get the BSDF describing 43 | /// the material properties at the intersection 44 | pub trait Material { 45 | /// Get the BSDF for the material which defines its properties at the hit point. 46 | /// 47 | /// We have the lifetime constraint on the returned BSDF to enforce it does not 48 | /// outlive the material which produced it. This allows us to borrow things from 49 | /// the parent material in the BxDFs making up the BSDF. 50 | fn bsdf<'a, 'b, 'c>(&'a self, hit: &Intersection<'a, 'b>, 51 | alloc: &'c Allocator) -> BSDF<'c> where 'a: 'c; 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/material/plastic.rs: -------------------------------------------------------------------------------- 1 | //! A material that models plastic of varying roughness using 2 | //! the Torrance Sparrow BRDF and a Blinn microfacet distribution 3 | //! TODO: Add Ashikman-Shirley (spelling?) anisotropic microfacet model 4 | //! 5 | //! # Scene Usage Example 6 | //! The plastic material requires a diffuse and glossy color. The diffuse color 7 | //! is used by a Lambertian model and the gloss color is used by a Torrance-Sparrow 8 | //! microfacet model with a Blinn microfacet distribution. The roughness will specify 9 | //! how reflective the gloss color is while the diffuse color provides a uniform base color 10 | //! for the object. 11 | //! 12 | //! ```json 13 | //! "materials": [ 14 | //! { 15 | //! "name": "red_plastic", 16 | //! "type": "plastic", 17 | //! "diffuse": [0.8, 0, 0], 18 | //! "gloss": [1, 1, 1], 19 | //! "roughness": 0.05 20 | //! }, 21 | //! ... 22 | //! ] 23 | //! ``` 24 | 25 | use std::sync::Arc; 26 | 27 | use light_arena::Allocator; 28 | 29 | use geometry::Intersection; 30 | use bxdf::{BxDF, BSDF, TorranceSparrow, Lambertian}; 31 | use bxdf::microfacet::Beckmann; 32 | use bxdf::fresnel::Dielectric; 33 | use material::Material; 34 | use texture::Texture; 35 | 36 | /// The Plastic material describes plastic materials of varying roughness 37 | pub struct Plastic { 38 | diffuse: Arc, 39 | gloss: Arc, 40 | roughness: Arc, 41 | } 42 | 43 | impl Plastic { 44 | /// Create a new plastic material specifying the diffuse and glossy colors 45 | /// along with the roughness of the surface 46 | pub fn new(diffuse: Arc, 47 | gloss: Arc, 48 | roughness: Arc) -> Plastic 49 | { 50 | Plastic { 51 | diffuse: diffuse.clone(), 52 | gloss: gloss.clone(), 53 | roughness: roughness.clone() 54 | } 55 | } 56 | } 57 | 58 | impl Material for Plastic { 59 | fn bsdf<'a, 'b, 'c>(&self, hit: &Intersection<'a, 'b>, 60 | alloc: &'c Allocator) -> BSDF<'c> where 'a: 'c 61 | { 62 | let diffuse = self.diffuse.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 63 | let gloss = self.gloss.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 64 | let roughness = self.roughness.sample_f32(hit.dg.u, hit.dg.v, hit.dg.time); 65 | 66 | // TODO: I don't like this counting and junk we have to do to figure out 67 | // the slice size and then the indices. Is there a better way? 68 | let mut num_bxdfs = 0; 69 | if !diffuse.is_black() { 70 | num_bxdfs += 1; 71 | } 72 | if !gloss.is_black() { 73 | num_bxdfs += 1; 74 | } 75 | let bxdfs = alloc.alloc_slice::<&BxDF>(num_bxdfs); 76 | 77 | let mut i = 0; 78 | if !diffuse.is_black() { 79 | bxdfs[i] = alloc.alloc(Lambertian::new(&diffuse)); 80 | i += 1; 81 | } 82 | if !gloss.is_black() { 83 | let fresnel = alloc.alloc(Dielectric::new(1.0, 1.5)); 84 | let microfacet = alloc.alloc(Beckmann::new(roughness)); 85 | bxdfs[i] = alloc.alloc(TorranceSparrow::new(&gloss, fresnel, microfacet)); 86 | } 87 | BSDF::new(bxdfs, 1.0, &hit.dg) 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/material/rough_glass.rs: -------------------------------------------------------------------------------- 1 | //! Defines a rough glass material 2 | //! 3 | //! # Scene Usage Example 4 | //! The rough glass material describes a thin glass surface material, 5 | //! not a solid block of glass (there is no absorption of light). The glass requires 6 | //! a reflective and emissive color along with a refrective index, eta and roughness. 7 | //! 8 | //! ```json 9 | //! "materials": [ 10 | //! { 11 | //! "name": "foggy_glass", 12 | //! "type": "rough_glass", 13 | //! "reflect": [1, 1, 1], 14 | //! "transmit": [1, 1, 1], 15 | //! "eta": 1.52, 16 | //! "roughness": 0.5, 17 | //! }, 18 | //! ... 19 | //! ] 20 | //! ``` 21 | 22 | use std::sync::Arc; 23 | 24 | use light_arena::Allocator; 25 | 26 | use geometry::Intersection; 27 | use bxdf::{BxDF, BSDF, MicrofacetTransmission, TorranceSparrow}; 28 | use bxdf::microfacet::Beckmann; 29 | use bxdf::fresnel::Dielectric; 30 | use material::Material; 31 | use texture::Texture; 32 | 33 | /// The `RoughGlass` material describes specularly transmissive and reflective glass material 34 | pub struct RoughGlass { 35 | reflect: Arc, 36 | transmit: Arc, 37 | eta: Arc, 38 | roughness: Arc, 39 | } 40 | 41 | impl RoughGlass { 42 | /// Create the `RoughGlass` material with the desired color and index of refraction 43 | /// `reflect`: color of reflected light 44 | /// `transmit`: color of transmitted light 45 | /// `eta`: refractive index of the material 46 | /// `roughness`: roughness of the material 47 | pub fn new(reflect: Arc, 48 | transmit: Arc, 49 | eta: Arc, 50 | roughness: Arc) -> RoughGlass 51 | { 52 | RoughGlass { reflect: reflect, transmit: transmit, eta: eta, roughness: roughness } 53 | } 54 | } 55 | 56 | impl Material for RoughGlass { 57 | fn bsdf<'a, 'b, 'c>(&self, hit: &Intersection<'a, 'b>, 58 | alloc: &'c Allocator) -> BSDF<'c> where 'a: 'c { 59 | let reflect = self.reflect.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 60 | let transmit = self.transmit.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 61 | let eta = self.eta.sample_f32(hit.dg.u, hit.dg.v, hit.dg.time); 62 | let roughness = self.roughness.sample_f32(hit.dg.u, hit.dg.v, hit.dg.time); 63 | 64 | let mut num_bxdfs = 0; 65 | if !reflect.is_black() { 66 | num_bxdfs += 1; 67 | } 68 | if !transmit.is_black() { 69 | num_bxdfs += 1; 70 | } 71 | 72 | let bxdfs = alloc.alloc_slice::<&BxDF>(num_bxdfs); 73 | let mut i = 0; 74 | let fresnel = alloc.alloc(Dielectric::new(1.0, eta)); 75 | let microfacet = alloc.alloc(Beckmann::new(roughness)); 76 | if !reflect.is_black() { 77 | bxdfs[i] = alloc.alloc(TorranceSparrow::new(&reflect, fresnel, microfacet)); 78 | i += 1; 79 | } 80 | if !transmit.is_black() { 81 | bxdfs[i] = alloc.alloc(MicrofacetTransmission::new(&transmit, fresnel, microfacet)); 82 | } 83 | BSDF::new(bxdfs, eta, &hit.dg) 84 | } 85 | } 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/material/specular_metal.rs: -------------------------------------------------------------------------------- 1 | //! Defines a specular metal material 2 | //! 3 | //! # Scene Usage Example 4 | //! The specular metal material requires a refractive index and absorption coefficient 5 | //! that describe the physical properties of the metal. 6 | //! 7 | //! ```json 8 | //! "materials": [ 9 | //! { 10 | //! "name": "specular_silver", 11 | //! "type": "specular_metal", 12 | //! "refractive_index": [0.155265, 0.116723, 0.138381], 13 | //! "absorption_coefficient": [4.82835, 3.12225, 2.14696] 14 | //! }, 15 | //! ... 16 | //! ] 17 | //! ``` 18 | 19 | use std::sync::Arc; 20 | 21 | use light_arena::Allocator; 22 | 23 | use film::Colorf; 24 | use geometry::Intersection; 25 | use bxdf::{BxDF, BSDF, SpecularReflection}; 26 | use bxdf::fresnel::Conductor; 27 | use material::Material; 28 | use texture::Texture; 29 | 30 | /// The Specular Metal material describes specularly reflective metals using their 31 | /// refractive index and absorption coefficient 32 | pub struct SpecularMetal { 33 | eta: Arc, 34 | k: Arc, 35 | } 36 | 37 | impl SpecularMetal { 38 | /// Create a new specular metal with the desired metal properties. 39 | /// `eta`: refractive index of the metal 40 | /// `k`: absorption coefficient of the metal 41 | pub fn new(eta: Arc, 42 | k: Arc) -> SpecularMetal 43 | { 44 | SpecularMetal { eta: eta.clone(), k: k.clone() } 45 | } 46 | } 47 | 48 | impl Material for SpecularMetal { 49 | fn bsdf<'a, 'b, 'c>(&'a self, hit: &Intersection<'a, 'b>, 50 | alloc: &'c Allocator) -> BSDF<'c> where 'a: 'c { 51 | let eta = self.eta.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 52 | let k = self.k.sample_color(hit.dg.u, hit.dg.v, hit.dg.time); 53 | 54 | let bxdfs = alloc.alloc_slice::<&BxDF>(1); 55 | let fresnel = alloc.alloc(Conductor::new(&eta, &k)); 56 | bxdfs[0] = alloc.alloc(SpecularReflection::new(&Colorf::broadcast(1.0), fresnel)); 57 | BSDF::new(bxdfs, 1.0, &hit.dg) 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/mc.rs: -------------------------------------------------------------------------------- 1 | //! Defines various Monte Carlo sampling functions for sampling 2 | //! points/directions on objects and computing the corresponding pdfs 3 | 4 | use std::f32; 5 | 6 | use linalg::{self, Vector}; 7 | 8 | /// Sample a hemisphere using a cosine distribution to produce cosine weighted samples 9 | /// `samples` should be two random samples in range [0, 1) 10 | /// directions returned will be in the hemisphere around (0, 0, 1) 11 | pub fn cos_sample_hemisphere(u: &(f32, f32)) -> Vector { 12 | //We use Malley's method here, generate samples on a disk then project 13 | //them up to the hemisphere 14 | let d = concentric_sample_disk(u); 15 | Vector::new(d.0, d.1, f32::sqrt(f32::max(0.0, 1.0 - d.0 * d.0 - d.1 * d.1))) 16 | } 17 | /// Compute the PDF of the cosine weighted hemisphere sampling 18 | pub fn cos_hemisphere_pdf(cos_theta: f32) -> f32 { cos_theta * f32::consts::FRAC_1_PI } 19 | /// Compute concentric sample positions on a unit disk mapping input from range [0, 1) 20 | /// to sample positions on a disk 21 | /// `samples` should be two random samples in range [0, 1) 22 | /// See: [Shirley and Chiu, A Low Distortion Map Between Disk and Square](https://mediatech.aalto.fi/~jaakko/T111-5310/K2013/JGT-97.pdf) 23 | pub fn concentric_sample_disk(u: &(f32, f32)) -> (f32, f32) { 24 | let s = (2.0 * u.0 - 1.0, 2.0 * u.1 - 1.0); 25 | let radius; 26 | let theta; 27 | if s.0 == 0.0 && s.1 == 0.0 { 28 | return s; 29 | } 30 | if s.0 >= -s.1 { 31 | if s.0 > s.1 { 32 | radius = s.0; 33 | if s.1 > 0.0 { 34 | theta = s.1 / s.0; 35 | } else { 36 | theta = 8.0 + s.1 / s.0; 37 | } 38 | } else { 39 | radius = s.1; 40 | theta = 2.0 - s.0 / s.1; 41 | } 42 | } else if s.0 <= s.1 { 43 | radius = -s.0; 44 | theta = 4.0 + s.1 / s.0; 45 | } else { 46 | radius = -s.1; 47 | theta = 6.0 - s.0 / s.1; 48 | } 49 | let theta = theta * f32::consts::FRAC_PI_4; 50 | (radius * f32::cos(theta), radius * f32::sin(theta)) 51 | } 52 | /// Power heuristic for multiple importance sampling for two functions being sampled, f & g 53 | /// where beta is hard-coded to be two following PBR & Veach 54 | /// - `n_f`, `n_g` number of samples taken of each 55 | /// - `pdf_f`, `pdf_g` pdf of each function 56 | pub fn power_heuristic(n_f: f32, pdf_f: f32, n_g: f32, pdf_g: f32) -> f32 { 57 | let f = n_f * pdf_f; 58 | let g = n_g * pdf_g; 59 | (f * f) / (f * f + g * g) 60 | } 61 | /// Return the PDF for uniformly sampling a cone with some max solid angle 62 | pub fn uniform_cone_pdf(cos_theta: f32) -> f32 { 63 | 1.0 / (f32::consts::PI * 2.0 * (1.0 - cos_theta)) 64 | } 65 | /// Uniformly sample a direction in a cone with max angle `cos_theta_max` where 66 | /// the cone lies along the z-axis 67 | pub fn uniform_sample_cone(samples: &(f32, f32), cos_theta_max: f32) -> Vector { 68 | let cos_theta = linalg::lerp(samples.0, &cos_theta_max, &1.0); 69 | let sin_theta = f32::sqrt(1.0 - cos_theta * cos_theta); 70 | let phi = samples.1 * f32::consts::PI * 2.0; 71 | Vector::new(f32::cos(phi) * sin_theta, f32::sin(phi) * sin_theta, cos_theta) 72 | } 73 | /// Uniformly sample a direction in a cone with max angle `cos_theta_max` where 74 | /// the cone looks down the `w_z` vector provided, with `w_x`, `w_y` forming the rest 75 | /// of the coordinate frame for the cone 76 | pub fn uniform_sample_cone_frame(samples: &(f32, f32), cos_theta_max: f32, w_x: &Vector, 77 | w_y: &Vector, w_z: &Vector) -> Vector { 78 | let cos_theta = linalg::lerp(samples.0, &cos_theta_max, &1.0); 79 | let sin_theta = f32::sqrt(1.0 - cos_theta * cos_theta); 80 | let phi = samples.1 * f32::consts::PI * 2.0; 81 | f32::cos(phi) * sin_theta * *w_x + f32::sin(phi) * sin_theta * *w_y + cos_theta * *w_z 82 | } 83 | /// Uniformly sample a direction on the unit sphere about the origin 84 | pub fn uniform_sample_sphere(samples: &(f32, f32)) -> Vector { 85 | let z = 1.0 - 2.0 * samples.0; 86 | let r = f32::sqrt(f32::max(0.0, 1.0 - z * z)); 87 | let phi = f32::consts::PI * 2.0 * samples.1; 88 | Vector::new(f32::cos(phi) * r, f32::sin(phi) * r, z) 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/partition.rs: -------------------------------------------------------------------------------- 1 | //! Provides a general partitioning function that implements C++'s 2 | //! [std::partition](http://en.cppreference.com/w/cpp/algorithm/partition) 3 | use std::mem; 4 | 5 | /// Re-orders elements in the range yielded by `it` based on `pred`. All elements 6 | /// that the predicate returns true for will be placed before all elements 7 | /// that the predicate returned false for. Also returns the index of the 8 | /// first element in the false group 9 | pub fn partition<'a, T: 'a, I, F>(mut it: I, pred: F) -> usize 10 | where I: DoubleEndedIterator, 11 | F: Fn(&T) -> bool { 12 | let mut split_idx = 0; 13 | loop { 14 | let mut front = None; 15 | let mut back = None; 16 | while let Some(f) = it.next() { 17 | if !pred(f) { 18 | front = Some(f); 19 | break; 20 | } else { 21 | split_idx += 1; 22 | } 23 | } 24 | while let Some(b) = it.next_back() { 25 | if pred(b) { 26 | back = Some(b); 27 | break; 28 | } 29 | } 30 | match (front, back) { 31 | (Some(f), Some(b)) => { 32 | mem::swap(f, b); 33 | split_idx += 1; 34 | }, 35 | _ => break, 36 | } 37 | } 38 | split_idx 39 | } 40 | 41 | #[test] 42 | fn test_partition() { 43 | // This test just partitions the odd and even numbers 44 | let mut vals = vec![1u32, 2, 3, 4, 5, 6]; 45 | let idx = partition(vals.iter_mut(), |x| *x % 2 == 0); 46 | println!("Partition idx = {}", idx); 47 | // The first 3 items should be even the next 3 should be odd 48 | println!("Partitioned: {:?}", vals); 49 | assert_eq!(idx, 3); 50 | assert!(vals.iter().take(3).fold(true, |f, x| *x % 2 == 0 && f)); 51 | assert!(vals.iter().skip(3).fold(true, |f, x| *x % 2 != 0 && f)); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/sampler/block_queue.rs: -------------------------------------------------------------------------------- 1 | //! Provides a queue of block indices that the sampler positions can be initialized 2 | //! from for the worker threads. The queue itself is not changed after creation 3 | //! we simply work through it with an atomic counter to track the index of the next 4 | //! block to work on 5 | 6 | use std::vec::Vec; 7 | use std::sync::atomic::{AtomicUsize, Ordering}; 8 | use sampler::morton; 9 | 10 | /// The queue of blocks to be worked on shared immutably between worker threads. 11 | pub struct BlockQueue { 12 | /// The block indices of blocks to work on for the image 13 | blocks: Vec<(u32, u32)>, 14 | /// Get the dimensions of an individual block 15 | dimensions: (u32, u32), 16 | /// Index of the next block to be worked on 17 | next: AtomicUsize, 18 | } 19 | 20 | /// Iterator to work through the queue safely 21 | pub struct BlockQueueIterator<'a> { 22 | queue: &'a BlockQueue, 23 | } 24 | 25 | impl BlockQueue { 26 | /// Create a block queue for the image with dimensions `img`. 27 | /// Panics if the image is not evenly broken into blocks of dimension `dim` 28 | pub fn new(img: (u32, u32), dim: (u32, u32), select_blocks: (usize, usize)) -> BlockQueue { 29 | if img.0 % dim.0 != 0 || img.1 % dim.1 != 0 { 30 | panic!("Image with dimension {:?} not evenly divided by blocks of {:?}", img, dim); 31 | } 32 | let num_blocks = (img.0 / dim.0, img.1 / dim.1); 33 | // TODO: the .. operator precedence is very low so we need this paren here at the moment 34 | // once (hopefully) it's raised we can remove the parens 35 | let mut blocks: Vec<(u32, u32)> = (0..num_blocks.0 * num_blocks.1) 36 | .map(|i| (i % num_blocks.0, i / num_blocks.0)).collect(); 37 | blocks.sort_by(|a, b| morton::morton2(a).cmp(&morton::morton2(b))); 38 | // If we're only rendering a subset of the blocks then filter our list down 39 | if select_blocks.1 > 0 { 40 | blocks = blocks.into_iter().skip(select_blocks.0).take(select_blocks.1).collect(); 41 | } 42 | if blocks.is_empty() { 43 | println!("Warning: This block queue is empty!"); 44 | } 45 | BlockQueue { blocks: blocks, dimensions: dim, next: AtomicUsize::new(0) } 46 | } 47 | /// Get the dimensions of an individual block in the queue 48 | pub fn block_dim(&self) -> (u32, u32) { self.dimensions } 49 | /// Get an iterator to work through the queue 50 | pub fn iter(&self) -> BlockQueueIterator { BlockQueueIterator { queue: self } } 51 | /// Get the next block in the queue or None if the queue is finished 52 | fn next(&self) -> Option<(u32, u32)> { 53 | let i = self.next.fetch_add(1, Ordering::AcqRel); 54 | if i >= self.blocks.len() { 55 | None 56 | } else { 57 | Some(self.blocks[i]) 58 | } 59 | } 60 | /// Get the length of the queue 61 | pub fn len(&self) -> usize { self.blocks.len() } 62 | /// Check if the queue is empty 63 | pub fn is_empty(&self) -> bool { 64 | self.next.load(Ordering::AcqRel) >= self.blocks.len() 65 | } 66 | } 67 | 68 | impl<'a> Iterator for BlockQueueIterator<'a> { 69 | type Item = (u32, u32); 70 | fn next(&mut self) -> Option<(u32, u32)> { 71 | self.queue.next() 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/sampler/ld.rs: -------------------------------------------------------------------------------- 1 | //! Provides a high quality sampling scheme based on (0, 2)-sequences 2 | //! See sec. 7.4.3 of Physically Based Rendering 3 | 4 | use std::{u32, f32, iter}; 5 | use rand::{Rng, StdRng}; 6 | use rand::distributions::{IndependentSample, Range}; 7 | 8 | use sampler::{Sampler, Region}; 9 | 10 | /// Low discrepancy sampler that makes use of the (0, 2) sequence to generate 11 | /// well distributed samples 12 | pub struct LowDiscrepancy { 13 | region: Region, 14 | /// Number of samples to take per pixel 15 | spp: usize, 16 | scramble_range: Range, 17 | } 18 | 19 | impl LowDiscrepancy { 20 | /// Create a low discrepancy sampler to sample the image in `dim.0 * dim.1` sized blocks 21 | pub fn new(dim: (u32, u32), mut spp: usize) -> LowDiscrepancy { 22 | if !spp.is_power_of_two() { 23 | spp = spp.next_power_of_two(); 24 | print!("Warning: LowDiscrepancy sampler requires power of two samples per pixel, "); 25 | println!("rounding up to {}", spp); 26 | } 27 | LowDiscrepancy { region: Region::new((0, 0), dim), spp: spp, 28 | scramble_range: Range::new(0, u32::MAX) } 29 | } 30 | } 31 | 32 | impl Sampler for LowDiscrepancy { 33 | fn get_samples(&mut self, samples: &mut Vec<(f32, f32)>, rng: &mut StdRng) { 34 | samples.clear(); 35 | if !self.has_samples() { 36 | return; 37 | } 38 | if samples.len() < self.spp { 39 | let len = self.spp - samples.len(); 40 | samples.extend(iter::repeat((0.0, 0.0)).take(len)); 41 | } 42 | self.get_samples_2d(&mut samples[..], rng); 43 | for s in samples.iter_mut() { 44 | s.0 += self.region.current.0 as f32; 45 | s.1 += self.region.current.1 as f32; 46 | } 47 | 48 | self.region.current.0 += 1; 49 | if self.region.current.0 == self.region.end.0 { 50 | self.region.current.0 = self.region.start.0; 51 | self.region.current.1 += 1; 52 | } 53 | } 54 | fn get_samples_2d(&mut self, samples: &mut [(f32, f32)], rng: &mut StdRng) { 55 | let scramble = (self.scramble_range.ind_sample(rng), 56 | self.scramble_range.ind_sample(rng)); 57 | sample_2d(samples, scramble, 0); 58 | rng.shuffle(samples); 59 | } 60 | fn get_samples_1d(&mut self, samples: &mut [f32], rng: &mut StdRng) { 61 | let scramble = self.scramble_range.ind_sample(rng); 62 | sample_1d(samples, scramble, 0); 63 | rng.shuffle(samples); 64 | } 65 | fn max_spp(&self) -> usize { self.spp } 66 | fn has_samples(&self) -> bool { self.region.current.1 != self.region.end.1 } 67 | fn dimensions(&self) -> (u32, u32) { self.region.dim } 68 | fn select_block(&mut self, start: (u32, u32)) { 69 | self.region.select_region(start); 70 | } 71 | fn get_region(&self) -> &Region { 72 | &self.region 73 | } 74 | } 75 | 76 | /// Generate a 2D pattern of low discrepancy samples to fill the slice 77 | /// sample values will be normalized between [0, 1] 78 | pub fn sample_2d(samples: &mut [(f32, f32)], scramble: (u32, u32), offset: u32) { 79 | for s in samples.iter_mut().enumerate() { 80 | *s.1 = sample_02(s.0 as u32 + offset, scramble); 81 | } 82 | } 83 | /// Generate a 1D pattern of low discrepancy samples to fill the slice 84 | /// sample values will be normalized between [0, 1] 85 | pub fn sample_1d(samples: &mut [f32], scramble: u32, offset: u32) { 86 | for s in samples.iter_mut().enumerate() { 87 | *s.1 = van_der_corput(s.0 as u32 + offset, scramble); 88 | } 89 | } 90 | /// Generate a sample from a scrambled (0, 2) sequence 91 | pub fn sample_02(n: u32, scramble: (u32, u32)) -> (f32, f32) { 92 | (van_der_corput(n, scramble.0), sobol(n, scramble.1)) 93 | } 94 | /// Generate a scrambled Van der Corput sequence value 95 | /// as described by Kollig & Keller (2002) and in PBR 96 | /// method is specialized for base 2 97 | pub fn van_der_corput(mut n: u32, scramble: u32) -> f32 { 98 | n = (n << 16) | (n >> 16); 99 | n = ((n & 0x00ff00ff) << 8) | ((n & 0xff00ff00) >> 8); 100 | n = ((n & 0x0f0f0f0f) << 4) | ((n & 0xf0f0f0f0) >> 4); 101 | n = ((n & 0x33333333) << 2) | ((n & 0xcccccccc) >> 2); 102 | n = ((n & 0x55555555) << 1) | ((n & 0xaaaaaaaa) >> 1); 103 | n ^= scramble; 104 | f32::min(((n >> 8) & 0xffffff) as f32 / ((1 << 24) as f32), 1.0 - f32::EPSILON) 105 | } 106 | /// Generate a scrambled Sobol' sequence value 107 | /// as described by Kollig & Keller (2002) and in PBR 108 | /// method is specialized for base 2 109 | pub fn sobol(mut n: u32, mut scramble: u32) -> f32 { 110 | let mut i = 1 << 31; 111 | while n != 0 { 112 | if n & 0x1 != 0 { 113 | scramble ^= i; 114 | } 115 | n >>= 1; 116 | i ^= i >> 1; 117 | } 118 | f32::min(((scramble >> 8) & 0xffffff) as f32 / ((1 << 24) as f32), 1.0 - f32::EPSILON) 119 | } 120 | 121 | -------------------------------------------------------------------------------- /src/sampler/mod.rs: -------------------------------------------------------------------------------- 1 | //! Provides the Sampler trait which is implemented by the various samplers 2 | //! to provide stratified, low-discrepancy, adaptive sampling methods and so 3 | //! on through a simple trait interface 4 | 5 | use rand::StdRng; 6 | use film::ImageSample; 7 | 8 | pub use self::uniform::Uniform; 9 | pub use self::ld::LowDiscrepancy; 10 | pub use self::adaptive::Adaptive; 11 | pub use self::block_queue::BlockQueue; 12 | 13 | pub mod morton; 14 | pub mod uniform; 15 | pub mod ld; 16 | pub mod adaptive; 17 | pub mod block_queue; 18 | 19 | /// Provides the interface for all samplers to implement. Defines functions for 20 | /// getting samples from the sampler and checking the sampler has finished sampling 21 | /// the region 22 | pub trait Sampler { 23 | /// Fill the vector with 2D pixel coordinate samples for a single pixel 24 | /// in the region being sampled. If the sampler doesn't have any more samples 25 | /// for the region the vector will be empty 26 | /// Samplers that use randomness to compute samples will use the thread rng 27 | fn get_samples(&mut self, samples: &mut Vec<(f32, f32)>, rng: &mut StdRng); 28 | /// Fill the slice with 2D samples from the sampler 29 | fn get_samples_2d(&mut self, samples: &mut [(f32, f32)], rng: &mut StdRng); 30 | /// Fill the slice with 1D samples from the sampler 31 | fn get_samples_1d(&mut self, samples: &mut [f32], rng: &mut StdRng); 32 | /// Get the max number of samples this sampler will take per pixel 33 | fn max_spp(&self) -> usize; 34 | /// Check if the sampler has more samples for the region being sampled 35 | fn has_samples(&self) -> bool; 36 | /// Get the dimensions of the region being sampled in pixels 37 | fn dimensions(&self) -> (u32, u32); 38 | /// Move to a new block of the image to sample with this sampler by specifying 39 | /// the starting `(x, y)` block index for the new block. The block starting 40 | /// position will be calculated as `dimensions * start` 41 | fn select_block(&mut self, start: (u32, u32)); 42 | /// Get the region being samples 43 | fn get_region(&self) -> &Region; 44 | /// Let the sampler inspect the results of sampling the pixel so it can 45 | /// determine if more samples should be taken. Returns true if these samples 46 | /// are ok to use, false if more need to be taken. The default implementation 47 | /// just returns true. 48 | fn report_results(&mut self, _samples: &[ImageSample]) -> bool { true } 49 | } 50 | 51 | /// Provides a simple way to pass around a 3 component sample consisting of one 2D and 52 | /// one 1D sample 53 | #[derive(Debug)] 54 | pub struct Sample { 55 | /// The 2D sample 56 | pub two_d: (f32, f32), 57 | /// The 1D sample 58 | pub one_d: f32, 59 | } 60 | 61 | impl Sample { 62 | /// Create a new sample taking the 2D sample values from the slice 63 | pub fn new(two_d: &(f32, f32), one_d: f32) -> Sample { 64 | Sample { two_d: *two_d, one_d: one_d } 65 | } 66 | } 67 | 68 | /// Defines a region of the image being sampled in pixel coordinates 69 | #[derive(Clone, Copy, Debug)] 70 | pub struct Region { 71 | /// Current coordinates of the pixel to sample (x, y) 72 | pub current: (u32, u32), 73 | /// Coordinates of the start of region being sampled (x, y) 74 | pub start: (u32, u32), 75 | /// Coordinates of the end of the region being sampled (x, y) 76 | pub end: (u32, u32), 77 | /// Dimensions of the region being sampled 78 | pub dim: (u32, u32), 79 | } 80 | 81 | impl Region { 82 | /// Create a new region starting at `start` with dimension `dim` 83 | pub fn new(start: (u32, u32), dim: (u32, u32)) -> Region { 84 | Region { current: start, start: start, 85 | end: (start.0 + dim.0, start.1 + dim.1), dim: dim } 86 | } 87 | /// Select a new region starting at region indices `start` with the same dimensions 88 | /// eg. with blocks of width 8 the 2nd region along x is at 16 so to get 89 | /// this block you'd set start.0 = 2 90 | pub fn select_region(&mut self, start: (u32, u32)) { 91 | self.start.0 = start.0 * self.dim.0; 92 | self.start.1 = start.1 * self.dim.1; 93 | self.end.0 = self.start.0 + self.dim.0; 94 | self.end.1 = self.start.1 + self.dim.1; 95 | self.current.0 = self.start.0; 96 | self.current.1 = self.start.1; 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/sampler/morton.rs: -------------------------------------------------------------------------------- 1 | ///! Provides utilities for 2D Morton code generation using Fabian Giesen's Morton 2 | ///! code decoding functions, see [his post on Morton codes](https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/) 3 | 4 | /// Insert a 0 bit between each of the low 16 bits of x 5 | pub fn part1_by1(mut x: u32) -> u32 { 6 | // x = ---- ---- ---- ---- fedc ba98 7654 3210 7 | x &= 0x0000ffff; 8 | // x = ---- ---- fedc ba98 ---- ---- 7654 3210 9 | x = (x ^ (x << 8)) & 0x00ff00ff; 10 | // x = ---- fedc ---- ba98 ---- 7654 ---- 3210 11 | x = (x ^ (x << 4)) & 0x0f0f0f0f; 12 | // x = --fe --dc --ba --98 --76 --54 --32 --10 13 | x = (x ^ (x << 2)) & 0x33333333; 14 | // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0 15 | (x ^ (x << 1)) & 0x55555555 16 | } 17 | /// Compute the Morton code for the `(x, y)` position 18 | pub fn morton2(p: &(u32, u32)) -> u32 { 19 | (part1_by1(p.1) << 1) + part1_by1(p.0) 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/sampler/uniform.rs: -------------------------------------------------------------------------------- 1 | //! Provides the simplest and worst sampling method, the Uniform sampler takes 2 | //! a single sample at the center of each pixel in its region 3 | 4 | use rand::StdRng; 5 | use rand::distributions::{Range, IndependentSample}; 6 | 7 | use sampler::{Sampler, Region}; 8 | 9 | /// Uniform sampler that takes one sample per pixel at the center of each pixel 10 | pub struct Uniform { 11 | region: Region, 12 | float_range: Range, 13 | } 14 | 15 | impl Uniform { 16 | /// Create a uniform sampler to sample the image in `dim.0 * dim.1` sized blocks 17 | pub fn new(dim: (u32, u32)) -> Uniform { 18 | Uniform { region: Region::new((0, 0), dim), float_range: Range::new(0.0, 1.0) } 19 | } 20 | } 21 | 22 | impl Sampler for Uniform { 23 | fn get_samples(&mut self, samples: &mut Vec<(f32, f32)>, _: &mut StdRng) { 24 | samples.clear(); 25 | if !self.has_samples() { 26 | return; 27 | } 28 | samples.push((self.region.current.0 as f32 + 0.5, self.region.current.1 as f32 + 0.5)); 29 | self.region.current.0 += 1; 30 | if self.region.current.0 == self.region.end.0 { 31 | self.region.current.0 = self.region.start.0; 32 | self.region.current.1 += 1; 33 | } 34 | } 35 | fn get_samples_2d(&mut self, samples: &mut [(f32, f32)], rng: &mut StdRng) { 36 | for s in samples.iter_mut() { 37 | s.0 = self.float_range.ind_sample(rng); 38 | s.1 = self.float_range.ind_sample(rng); 39 | } 40 | } 41 | fn get_samples_1d(&mut self, samples: &mut [f32], rng: &mut StdRng) { 42 | for s in samples.iter_mut() { 43 | *s = self.float_range.ind_sample(rng); 44 | } 45 | } 46 | fn max_spp(&self) -> usize { 1 } 47 | fn has_samples(&self) -> bool { self.region.current.1 != self.region.end.1 } 48 | fn dimensions(&self) -> (u32, u32) { self.region.dim } 49 | fn select_block(&mut self, start: (u32, u32)) { 50 | self.region.select_region(start); 51 | } 52 | fn get_region(&self) -> &Region{ 53 | &self.region 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/texture/animated_image.rs: -------------------------------------------------------------------------------- 1 | use linalg::lerp; 2 | use film::Colorf; 3 | use texture::{Texture, Image}; 4 | 5 | /// An `AnimatedImage` texture is a `Texture` whose samples come 6 | /// from a series of `Image`s which are played through over time. 7 | pub struct AnimatedImage { 8 | // I wonder how much sense it would make, and it what it 9 | // would look like to do a B-spline interpolation between the 10 | // images 11 | frames: Vec<(f32, Image)>, 12 | } 13 | 14 | impl AnimatedImage { 15 | pub fn new(frames: Vec<(f32, Image)>) -> AnimatedImage { 16 | assert!(frames.len() >= 2); 17 | AnimatedImage { frames: frames } 18 | } 19 | pub fn active_keyframes(&self, time: f32) -> (usize, Option) { 20 | match self.frames.binary_search_by(|&(t, _)| t.partial_cmp(&time).unwrap()) { 21 | Ok(i) => (i, None), 22 | Err(i) => { 23 | if i == self.frames.len() { 24 | (i - 1, None) 25 | } else if i == 0 { 26 | (0, None) 27 | } else { 28 | (i - 1, Some(i)) 29 | } 30 | }, 31 | } 32 | } 33 | } 34 | 35 | impl Texture for AnimatedImage { 36 | fn sample_f32(&self, u: f32, v: f32, time: f32) -> f32 { 37 | match self.active_keyframes(time) { 38 | (lo, None) => self.frames[lo].1.sample_f32(u, v, time), 39 | (lo, Some(hi)) => { 40 | let x = (time - self.frames[lo].0) 41 | / (self.frames[hi].0 - self.frames[lo].0); 42 | lerp(x, &self.frames[lo].1.sample_f32(u, v, time), 43 | &self.frames[hi].1.sample_f32(u, v, time)) 44 | } 45 | } 46 | } 47 | fn sample_color(&self, u: f32, v: f32, time: f32) -> Colorf { 48 | match self.active_keyframes(time) { 49 | (lo, None) => self.frames[lo].1.sample_color(u, v, time), 50 | (lo, Some(hi)) => { 51 | let x = (time - self.frames[lo].0) 52 | / (self.frames[hi].0 - self.frames[lo].0); 53 | lerp(x, &self.frames[lo].1.sample_color(u, v, time), 54 | &self.frames[hi].1.sample_color(u, v, time)) 55 | } 56 | } 57 | } 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/texture/image.rs: -------------------------------------------------------------------------------- 1 | use image::{self, GenericImage}; 2 | 3 | use linalg::clamp; 4 | use film::Colorf; 5 | use texture::{Texture, bilinear_interpolate}; 6 | 7 | /// An `Image` texture is a `Texture` whose samples come 8 | /// from an image file. 9 | pub struct Image { 10 | img: image::DynamicImage, 11 | } 12 | 13 | impl Image { 14 | pub fn new(img: image::DynamicImage) -> Image { 15 | Image { img: img } 16 | } 17 | fn get_float(&self, x: u32, y: u32) -> f32 { 18 | let dims = self.img.dimensions(); 19 | let x = clamp(x, 0, dims.0 - 1); 20 | let y = clamp(y, 0, dims.1 - 1); 21 | self.img.get_pixel(x, y).data[0] as f32 / 255.0 22 | } 23 | fn get_color(&self, x: u32, y: u32) -> Colorf { 24 | let dims = self.img.dimensions(); 25 | let x = clamp(x, 0, dims.0 - 1); 26 | let y = clamp(y, 0, dims.1 - 1); 27 | let px = self.img.get_pixel(x, y); 28 | Colorf::with_alpha(px.data[0] as f32 / 255.0, 29 | px.data[1] as f32 / 255.0, 30 | px.data[2] as f32 / 255.0, 31 | px.data[3] as f32 / 255.0) 32 | } 33 | } 34 | 35 | impl Texture for Image { 36 | fn sample_f32(&self, u: f32, v: f32, _: f32) -> f32 { 37 | let dims = self.img.dimensions(); 38 | let x = u * dims.0 as f32; 39 | let y = v * dims.1 as f32; 40 | bilinear_interpolate(x, y, |px, py| self.get_float(px, py)) 41 | } 42 | fn sample_color(&self, u: f32, v: f32, _: f32) -> Colorf { 43 | let x = u * self.img.dimensions().0 as f32; 44 | let y = v * self.img.dimensions().1 as f32; 45 | bilinear_interpolate(x, y, |px, py| self.get_color(px, py)) 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/texture/mod.rs: -------------------------------------------------------------------------------- 1 | //! Defines the trait implemented by all textured values 2 | 3 | use std::ops::{Add, Mul}; 4 | 5 | use film::Colorf; 6 | 7 | pub use self::image::Image; 8 | pub use self::animated_image::AnimatedImage; 9 | 10 | pub mod image; 11 | pub mod animated_image; 12 | 13 | /// scalars or Colors can be computed on some image texture 14 | /// or procedural generator 15 | pub trait Texture { 16 | /// Sample the textured value at texture coordinates u,v 17 | /// at some time. u and v should be in [0, 1] 18 | fn sample_f32(&self, u: f32, v: f32, time: f32) -> f32; 19 | fn sample_color(&self, u: f32, v: f32, time: f32) -> Colorf; 20 | } 21 | 22 | fn bilinear_interpolate(x: f32, y: f32, get: F) -> T 23 | where T: Copy + Add + Mul, 24 | F: Fn(u32, u32) -> T 25 | { 26 | let p00 = (x as u32, y as u32); 27 | let p10 = (p00.0 + 1, p00.1); 28 | let p01 = (p00.0, p00.1 + 1); 29 | let p11 = (p00.0 + 1, p00.1 + 1); 30 | 31 | let s00 = get(p00.0, p00.1); 32 | let s10 = get(p10.0, p10.1); 33 | let s01 = get(p01.0, p01.1); 34 | let s11 = get(p11.0, p11.1); 35 | 36 | let sx = x - p00.0 as f32; 37 | let sy = y - p00.1 as f32; 38 | s00 * (1.0 - sx) * (1.0 - sy) + s10 * sx * (1.0 - sy) 39 | + s01 * (1.0 - sx) * sy + s11 * sx * sy 40 | } 41 | 42 | /// A single valued, solid scalar texture 43 | pub struct ConstantScalar { 44 | val: f32, 45 | } 46 | impl ConstantScalar { 47 | pub fn new(val: f32) -> ConstantScalar { 48 | ConstantScalar { val: val } 49 | } 50 | } 51 | impl Texture for ConstantScalar { 52 | fn sample_f32(&self, _: f32, _: f32, _: f32) -> f32 { 53 | self.val 54 | } 55 | fn sample_color(&self, _: f32, _: f32, _: f32) -> Colorf { 56 | Colorf::broadcast(self.val) 57 | } 58 | } 59 | 60 | /// A single valued, solid color texture 61 | pub struct ConstantColor { 62 | val: Colorf, 63 | } 64 | impl ConstantColor { 65 | pub fn new(val: Colorf) -> ConstantColor { 66 | ConstantColor { val: val } 67 | } 68 | } 69 | impl Texture for ConstantColor { 70 | fn sample_f32(&self, _: f32, _: f32, _: f32) -> f32 { 71 | self.val.luminance() 72 | } 73 | fn sample_color(&self, _: f32, _: f32, _: f32) -> Colorf { 74 | self.val 75 | } 76 | } 77 | 78 | pub struct UVColor; 79 | impl Texture for UVColor { 80 | fn sample_f32(&self, u: f32, v: f32, _: f32) -> f32 { 81 | Colorf::new(u, v, 0.0).luminance() 82 | } 83 | fn sample_color(&self, u: f32, v: f32, _: f32) -> Colorf { 84 | Colorf::new(u, v, 0.0) 85 | } 86 | } 87 | 88 | --------------------------------------------------------------------------------