├── rustfmt.toml ├── .gitattributes ├── src ├── sample.rs ├── ray.rs ├── constant.rs ├── math │ ├── mod.rs │ ├── traits.rs │ ├── vector4.rs │ ├── vector3.rs │ └── matrix4.rs ├── intersection.rs ├── material │ ├── mod.rs │ ├── traits.rs │ ├── phong.rs │ ├── blinn_phong.rs │ ├── lambert.rs │ ├── ggx.rs │ └── ideal_refraction.rs ├── shape.rs ├── img.rs ├── objects.rs ├── sky.rs ├── sphere.rs ├── aabb.rs ├── util.rs ├── bvh.rs ├── main.rs ├── triangle.rs ├── scene.rs ├── scene_loader.rs ├── description.rs └── camera.rs ├── media ├── model.png ├── sample.png └── ggx-conductor.png ├── docker-compose.yml ├── .gitignore ├── Dockerfile ├── Cargo.toml ├── scenes ├── primitive.toml ├── vr.toml ├── debug-nee.toml ├── sample.toml ├── new-cbox.toml ├── welcome-2018-geo.toml ├── ridaisai-2018.toml ├── welcome-2018.toml └── brdf.toml └── README.md /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.obj binary 2 | *.mtl binary 3 | -------------------------------------------------------------------------------- /src/sample.rs: -------------------------------------------------------------------------------- 1 | pub struct Sample { 2 | pub value: T, 3 | pub pdf: f32, 4 | } 5 | -------------------------------------------------------------------------------- /media/model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnlybubbles/LumillyRender/HEAD/media/model.png -------------------------------------------------------------------------------- /media/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnlybubbles/LumillyRender/HEAD/media/sample.png -------------------------------------------------------------------------------- /media/ggx-conductor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnlybubbles/LumillyRender/HEAD/media/ggx-conductor.png -------------------------------------------------------------------------------- /src/ray.rs: -------------------------------------------------------------------------------- 1 | use math::vector::Vector3; 2 | 3 | pub struct Ray { 4 | pub origin: Vector3, 5 | pub direction: Vector3, 6 | } 7 | -------------------------------------------------------------------------------- /src/constant.rs: -------------------------------------------------------------------------------- 1 | pub const PI: f32 = 3.14159265358979323846264338327950288_f32; 2 | pub const EPS: f32 = 1e-3; 3 | pub const INF: f32 = 1e5; 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | main: 4 | build: . 5 | volumes: 6 | - .:/main 7 | command: bash -c 'cargo build --release' 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | /target/ 4 | *.bk 5 | 6 | Cargo.lock 7 | 8 | diff*.png 9 | image*.png 10 | reference*.png 11 | image*.ppm 12 | *.hdr 13 | *.exr 14 | images 15 | dist/ 16 | models 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazonlinux:latest 2 | 3 | WORKDIR /main 4 | RUN yum -y groupinstall "Development Tools" 5 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly 6 | ENV PATH ~/.cargo/bin:$PATH 7 | 8 | CMD /bin/bash 9 | -------------------------------------------------------------------------------- /src/math/mod.rs: -------------------------------------------------------------------------------- 1 | mod traits; 2 | mod vector3; 3 | mod vector4; 4 | mod matrix4; 5 | 6 | pub mod vector { 7 | pub use super::traits::*; 8 | pub use super::vector3::*; 9 | pub use super::vector4::*; 10 | } 11 | 12 | pub mod matrix { 13 | pub use super::matrix4::*; 14 | } 15 | -------------------------------------------------------------------------------- /src/intersection.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use math::vector::Vector3; 3 | use material::material::Material; 4 | 5 | pub struct Intersection { 6 | pub position: Vector3, 7 | pub distance: f32, 8 | pub normal: Vector3, 9 | pub material: Arc, 10 | } 11 | -------------------------------------------------------------------------------- /src/material/mod.rs: -------------------------------------------------------------------------------- 1 | mod traits; 2 | mod lambert; 3 | mod phong; 4 | mod blinn_phong; 5 | mod ggx; 6 | mod ideal_refraction; 7 | 8 | pub mod material { 9 | pub use super::traits::*; 10 | pub use super::lambert::*; 11 | pub use super::phong::*; 12 | pub use super::blinn_phong::*; 13 | pub use super::ggx::*; 14 | pub use super::ideal_refraction::*; 15 | } 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["pnlybubbles "] 3 | name = "lumilly_render" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | image = "0.18.0" 8 | num_cpus = "1.6.2" 9 | ordered-float = "0.5.0" 10 | rand = "0.3.15" 11 | scoped_threadpool = "0.1.7" 12 | serde = "1.0.27" 13 | serde_derive = "1.0.27" 14 | threadpool = "1.7.1" 15 | time = "0.1.38" 16 | tobj = "0.1.6" 17 | toml = "0.4.5" 18 | -------------------------------------------------------------------------------- /src/shape.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use intersection::Intersection; 3 | use ray::Ray; 4 | use aabb::AABB; 5 | use material::material::Material; 6 | use math::vector::Vector3; 7 | use sample::Sample; 8 | 9 | pub trait SurfaceShape: Shape { 10 | fn material(&self) -> Arc; 11 | fn area(&self) -> f32; 12 | fn sample(&self) -> Sample; 13 | } 14 | 15 | pub trait Shape { 16 | fn intersect(&self, ray: &Ray) -> Option; 17 | fn aabb(&self) -> &AABB; 18 | } 19 | -------------------------------------------------------------------------------- /src/material/traits.rs: -------------------------------------------------------------------------------- 1 | use math::vector::Vector3; 2 | use sample::Sample; 3 | 4 | // (ω_o) 出射ベクトル(視線ベクトル) 5 | // (ω_i) 入射ベクトル(光源ベクトル) 6 | 7 | pub trait Material { 8 | // 物体自体の放射成分 9 | fn emission(&self) -> Vector3; 10 | // 出射ベクトル, 物体法線ベクトル -> 法線ベクトル 11 | fn orienting_normal(&self, Vector3, Vector3) -> Vector3; 12 | // 出射ベクトル, 入射ベクトル, 法線ベクトル, 座標 -> BRDF 13 | fn brdf(&self, Vector3, Vector3, Vector3, Vector3) -> Vector3; 14 | // 出射ベクトル, 法線ベクトル -> 入射ベクトル, 確率密度 15 | fn sample(&self, Vector3, Vector3) -> Sample; 16 | // 再帰継続用ロシアンルーレットの重み 17 | fn weight(&self) -> f32; 18 | // 輝度に乗算する係数 19 | // 出射ベクトル, 法線ベクトル, 飛行距離 20 | fn coef(&self, Vector3, Vector3, f32) -> Vector3 { 21 | Vector3::new(1.0, 1.0, 1.0) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/math/traits.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Div}; 2 | 3 | pub trait Dot { 4 | fn dot(self, Self) -> f32; 5 | } 6 | 7 | pub trait Cross { 8 | fn cross(self, Self) -> Self; 9 | } 10 | 11 | pub trait Norm { 12 | fn norm(self) -> f32; 13 | fn sqr_norm(self) -> f32; 14 | } 15 | 16 | pub trait Normalize { 17 | fn normalize(self) -> Self; 18 | } 19 | 20 | pub trait Zero { 21 | fn zero() -> Self; 22 | } 23 | 24 | impl Norm for T 25 | where T: Copy + Dot 26 | { 27 | fn norm(self) -> f32 { 28 | self.sqr_norm().sqrt() 29 | } 30 | 31 | fn sqr_norm(self) -> f32 { 32 | self.dot(self) 33 | } 34 | } 35 | 36 | impl Normalize for T 37 | where T: Copy + Norm + Div 38 | { 39 | fn normalize(self) -> T { 40 | self / self.norm() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /scenes/primitive.toml: -------------------------------------------------------------------------------- 1 | [renderer] 2 | samples = 64 3 | depth = 5 4 | depth-limit = 64 5 | no-direct-emitter = true 6 | threads = 0 7 | integrator = "pt" 8 | 9 | [film] 10 | resolution = [2048, 2048] 11 | output = "png" 12 | gamma = 1.0 13 | sensitivity = [1, 1, 1] 14 | 15 | [sky] 16 | type = "uniform" 17 | color = [1, 1, 1] 18 | 19 | [camera] 20 | type = "ideal-pinhole" 21 | fov = 53.13 22 | [[camera.transform]] 23 | type = "look-at" 24 | origin = [0, 0, 10] 25 | target = [0, 0, 0] 26 | up = [0, 1, 0] 27 | 28 | [[object]] 29 | material = "red" 30 | mesh = "sphere" 31 | 32 | [[object]] 33 | material = "white" 34 | mesh = "ground" 35 | [[object.transform]] 36 | type = "translate" 37 | vector = [0, -100001, 0] 38 | 39 | [[material]] 40 | name = "white" 41 | type = "lambert" 42 | albedo = [1, 1, 1] 43 | 44 | [[material]] 45 | name = "red" 46 | type = "lambert" 47 | albedo = [0.4, 0.4, 0.4] 48 | 49 | [[mesh]] 50 | name = "sphere" 51 | type = "sphere" 52 | radius = 1 53 | 54 | [[mesh]] 55 | name = "ground" 56 | type = "sphere" 57 | radius = 100000 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lumilly Render 2 | 3 | Monte Carlo path tracing implementation on Rust 4 | 5 | ![image](media/sample.png) 6 | 7 | 2102x1500 (1920x1370) 10000 samples per pixel 8 | 9 | ## Run 10 | 11 | ``` 12 | RUSTFLAGS='--emit asm -C target-feature=+avx' cargo run --release 13 | ``` 14 | 15 | ## Feature 16 | 17 | - Light transport 18 | - pt (Pure path-tracing) 19 | - pt-direct (Next event estimation) 20 | - Acceleration structure 21 | - BVH (SAH) 22 | - BSDF 23 | - Lambert 24 | - Phong 25 | - BlinnPhong 26 | - GGX [Walter et al. 2007] 27 | - Camera 28 | - Ideal pinhole 29 | - Realistic pinhole 30 | - Thin lens model 31 | - Mesh 32 | - Wavefront .obj (.mtl) 33 | - Sphere 34 | - Sky 35 | - Uniform 36 | - Image based lighting 37 | - Output 38 | - png 39 | - hdr 40 | - Scene file support (scenes/*.toml) 41 | 42 | ## Gallery 43 | 44 | ![model](media/model.png) 45 | 46 | 1024x1024 512 samples per pixel 47 | 48 | ![ggx-conductor](media/ggx-conductor.png) 49 | 50 | 960x540 4096 samples per pixel 51 | 52 | ## References 53 | 54 | - [Walter et al. 2007, “Microfacet Models for Refraction through Rough Surfaces”, Eurographics Symposium on Rendering.](https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf) 55 | -------------------------------------------------------------------------------- /scenes/vr.toml: -------------------------------------------------------------------------------- 1 | [renderer] 2 | samples = 64 3 | depth = 5 4 | depth-limit = 64 5 | no-direct-emitter = false 6 | threads = 0 7 | integrator = "pt-direct" 8 | 9 | [film] 10 | resolution = [512, 512] 11 | output = "hdr" 12 | sensitivity = [1, 1, 1] 13 | 14 | [sky] 15 | type = "uniform" 16 | color = [0, 0, 0] 17 | 18 | [camera] 19 | type = "omnidirectional" 20 | [[camera.transform]] 21 | type = "look-at" 22 | origin = [278, 273, 100] 23 | target = [278, 273, 0] 24 | up = [0, 1, 0] 25 | 26 | [[light]] 27 | type = "area" 28 | object = "light" 29 | emission = [40.0, 30.901960, 22.431360] 30 | 31 | [[object]] 32 | mesh = "dragon" 33 | material = "glossy" 34 | [[object.transform]] 35 | type = "scale" 36 | vector = [20, 20, 20] 37 | [[object.transform]] 38 | type = "translate" 39 | vector = [278, 0, 278] 40 | 41 | [[object]] 42 | mesh = "cbox" 43 | 44 | [[object]] 45 | name = "light" 46 | mesh = "light" 47 | 48 | [[material]] 49 | name = "glossy" 50 | type = "ggx" 51 | reflectance = [1.0, 1.0, 1.0] 52 | roughness = 0.8 53 | ior = 100000 54 | 55 | [[mesh]] 56 | name = "dragon" 57 | type = "obj" 58 | path = "models/stanford_dragon/dragon.obj" 59 | 60 | [[mesh]] 61 | name = "cbox" 62 | type = "obj" 63 | # obj: https://cl.ly/omBj mtl: https://cl.ly/oluU 64 | path = "models/simple/cbox.obj" 65 | 66 | [[mesh]] 67 | name = "light" 68 | type = "obj" 69 | # obj: https://cl.ly/olil 70 | path = "models/simple/cbox_luminaire.obj" 71 | -------------------------------------------------------------------------------- /scenes/debug-nee.toml: -------------------------------------------------------------------------------- 1 | [renderer] 2 | samples = 256 3 | depth = 5 4 | depth-limit = 64 5 | no-direct-emitter = true 6 | threads = 0 7 | integrator = "pt-direct" 8 | 9 | [film] 10 | resolution = [512, 512] 11 | output = "png" 12 | gamma = 2.2 13 | sensitivity = [1, 1, 1] 14 | 15 | [sky] 16 | type = "uniform" 17 | color = [0, 0, 0] 18 | 19 | [camera] 20 | type = "ideal-pinhole" 21 | fov = 39.3077 22 | [[camera.transform]] 23 | type = "look-at" 24 | origin = [278, 273, -800] 25 | target = [278, 273, 0] 26 | up = [0, 1, 0] 27 | 28 | [[light]] 29 | type = "area" 30 | object = "light" 31 | emission = [10, 10, 10] 32 | 33 | [[object]] 34 | name = "light" 35 | mesh = "light" 36 | material = "light" 37 | [[object.transform]] 38 | type = "translate" 39 | vector = [278, 590, 278] 40 | 41 | [[object]] 42 | mesh = "quad" 43 | material = "white" 44 | [[object.transform]] 45 | type = "scale" 46 | vector = [250, 250, 250] 47 | [[object.transform]] 48 | type = "translate" 49 | vector = [278, 0, 278] 50 | 51 | [[object]] 52 | mesh = "quad" 53 | material = "white" 54 | [[object.transform]] 55 | type = "scale" 56 | vector = [250, 250, 250] 57 | [[object.transform]] 58 | type = "translate" 59 | vector = [278, 549, 278] 60 | 61 | [[material]] 62 | name = "light" 63 | type = "lambert" 64 | albedo = [0, 0, 0] 65 | 66 | [[material]] 67 | name = "white" 68 | type = "lambert" 69 | albedo = [1, 1, 1] 70 | 71 | [[mesh]] 72 | name = "quad" 73 | type = "obj" 74 | path = "models/simple/quad.obj" 75 | 76 | [[mesh]] 77 | name = "light" 78 | type = "sphere" 79 | radius = 130 80 | -------------------------------------------------------------------------------- /scenes/sample.toml: -------------------------------------------------------------------------------- 1 | [renderer] 2 | samples = 64 3 | depth = 5 4 | depth-limit = 64 5 | no-direct-emitter = false 6 | threads = 0 7 | integrator = "pt" 8 | 9 | [film] 10 | resolution = [512, 512] 11 | output = "png" 12 | gamma = 2.2 13 | sensitivity = [1, 1, 1] 14 | 15 | [sky] 16 | type = "uniform" 17 | color = [1, 1, 1] 18 | 19 | [camera] 20 | type = "ideal-pinhole" 21 | fov = 39.3077 22 | [[camera.transform]] 23 | type = "look-at" 24 | origin = [278, 273, -800] 25 | target = [278, 273, 0] 26 | up = [0, 1, 0] 27 | 28 | [[light]] 29 | type = "area" 30 | object = "light" 31 | emission = [40.0, 30.901960, 22.431360] 32 | 33 | [[object]] 34 | mesh = "bunny" 35 | material = "blue" 36 | [[object.transform]] 37 | type = "axis-angle" 38 | axis = [0, 1, 0] 39 | angle = 180 40 | [[object.transform]] 41 | type = "scale" 42 | vector = [130, 130, 130] 43 | [[object.transform]] 44 | type = "translate" 45 | vector = [255, -6, 278] 46 | 47 | [[object]] 48 | mesh = "cbox" 49 | 50 | [[object]] 51 | name = "light" 52 | mesh = "light" 53 | 54 | [[material]] 55 | name = "blue" 56 | type = "lambert" 57 | albedo = [0.2, 0.2, 0.6] 58 | 59 | [[mesh]] 60 | name = "bunny" 61 | type = "obj" 62 | # https://g3d.cs.williams.edu/g3d/data10/research/model/bunny/bunny.zip 63 | path = "models/bunny/bunny.obj" 64 | 65 | [[mesh]] 66 | name = "cbox" 67 | type = "obj" 68 | # obj: https://cl.ly/omBj mtl: https://cl.ly/oluU 69 | path = "models/simple/cbox.obj" 70 | 71 | [[mesh]] 72 | name = "light" 73 | type = "obj" 74 | # obj: https://cl.ly/olil 75 | path = "models/simple/cbox_luminaire.obj" 76 | -------------------------------------------------------------------------------- /src/img.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | 3 | use std::fs::File; 4 | use std::path::Path; 5 | 6 | pub struct Img { 7 | data: Vec>, 8 | height: usize, 9 | width: usize, 10 | } 11 | 12 | impl Img { 13 | pub fn new(init: T, width: usize, height: usize) -> Img { 14 | Img { 15 | data: vec![vec![init; width]; height], 16 | height: height, 17 | width: width, 18 | } 19 | } 20 | 21 | pub fn get(&self, x: usize, y: usize) -> T { 22 | self.data[y][x] 23 | } 24 | 25 | pub fn set(&mut self, x: usize, y: usize, v: T) { 26 | self.data[y][x] = v; 27 | } 28 | 29 | pub fn each_pixel(&self, f: F) 30 | where 31 | F: Fn(usize, usize, usize), 32 | { 33 | for x in 0..self.width { 34 | for y in 0..self.height { 35 | f(x, y, x * self.width + y) 36 | } 37 | } 38 | } 39 | 40 | pub fn save_hdr(&self, path: &Path, f: F) 41 | where 42 | F: Fn(T) -> [f32; 3], 43 | { 44 | let buf = self.data.iter().flat_map( |row| 45 | row.iter().map( |cell| image::Rgb(f(*cell)) ) 46 | ).collect::>(); 47 | let ref mut file = File::create(path).unwrap(); 48 | let encoder = image::hdr::HDREncoder::new(file); 49 | encoder.encode(&buf, self.width, self.height).unwrap(); 50 | } 51 | 52 | pub fn save_png(&self, path: &Path, f: F) 53 | where 54 | F: Fn(T) -> [u8; 3], 55 | { 56 | let mut buf = image::ImageBuffer::new(self.width as u32, self.height as u32); 57 | for (x, y, pixel) in buf.enumerate_pixels_mut() { 58 | let output_pixel = self.get(x as usize, y as usize); 59 | *pixel = image::Rgb(f(output_pixel)); 60 | } 61 | let ref mut file = File::create(path).unwrap(); 62 | image::ImageRgb8(buf).save(file, image::PNG).unwrap(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /scenes/new-cbox.toml: -------------------------------------------------------------------------------- 1 | [renderer] 2 | samples = 64 3 | depth = 5 4 | depth-limit = 64 5 | no-direct-emitter = true 6 | threads = 0 7 | integrator = "pt-direct" 8 | 9 | [film] 10 | resolution = [256, 256] 11 | output = "png" 12 | gamma = 2.2 13 | sensitivity = [1, 1, 1] 14 | 15 | [sky] 16 | type = "uniform" 17 | color = [0, 0, 0] 18 | 19 | [camera] 20 | type = "ideal-pinhole" 21 | fov = 39.3077 22 | [[camera.transform]] 23 | type = "look-at" 24 | origin = [278, 273, -800] 25 | target = [278, 273, 0] 26 | up = [0, 1, 0] 27 | 28 | [[light]] 29 | type = "area" 30 | object = "light" 31 | emission = [40.0, 30.901960, 22.431360] 32 | intensity = 0.7 33 | 34 | [[object]] 35 | mesh = "cbox" 36 | 37 | [[object]] 38 | name = "light" 39 | mesh = "light" 40 | material = "light" 41 | [[object.transform]] 42 | type = "axis-angle" 43 | axis = [0, 0, 1] 44 | angle = 160 45 | [[object.transform]] 46 | type = "scale" 47 | vector = [100, 100, 100] 48 | [[object.transform]] 49 | type = "translate" 50 | vector = [350, 540, 278] 51 | 52 | [[object]] 53 | material = "white" 54 | mesh = "sphere" 55 | [[object.transform]] 56 | type = "translate" 57 | vector = [140, 100, 300] 58 | 59 | [[object]] 60 | material = "white" 61 | mesh = "sphere" 62 | [[object.transform]] 63 | type = "translate" 64 | vector = [380, 100, 200] 65 | 66 | [[material]] 67 | name = "white" 68 | type = "lambert" 69 | albedo = [0.740063, 0.742313, 0.733934] 70 | 71 | [[material]] 72 | name = "light" 73 | type = "lambert" 74 | albedo = [0, 0, 0] 75 | 76 | [[mesh]] 77 | name = "sphere" 78 | type = "sphere" 79 | radius = 100 80 | 81 | [[mesh]] 82 | name = "cbox" 83 | type = "obj" 84 | # obj: https://cl.ly/omBj mtl: https://cl.ly/oluU 85 | path = "models/simple/cbox.obj" 86 | 87 | [[mesh]] 88 | name = "light" 89 | type = "obj" 90 | path = "models/simple/quad.obj" 91 | -------------------------------------------------------------------------------- /src/material/phong.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use super::traits::Material; 4 | use math::vector::*; 5 | use sample::Sample; 6 | use constant::*; 7 | use util::{BoundaryResponse, OrthonormalBasis}; 8 | 9 | pub struct PhongMaterial { 10 | // 反射率 11 | pub reflectance: Vector3, 12 | // ラフネス 13 | pub roughness: f32, 14 | } 15 | 16 | impl Material for PhongMaterial { 17 | fn orienting_normal(&self, out_: Vector3, normal: Vector3) -> Vector3 { 18 | // 物体の内外を考慮した法線方向から拡散反射面としての法線方向を求める 19 | if normal.dot(out_) < 0.0 { 20 | normal * -1.0 21 | } else { 22 | normal 23 | } 24 | } 25 | 26 | fn emission(&self) -> Vector3 { 27 | Vector3::zero() 28 | } 29 | 30 | fn weight(&self) -> f32 { 31 | // 反射率のうち最大のものをつかう 32 | self.reflectance.x.max(self.reflectance.y).max( 33 | self.reflectance.z, 34 | ) 35 | } 36 | 37 | fn brdf(&self, out_: Vector3, in_: Vector3, n: Vector3, _pos: Vector3) -> Vector3 { 38 | let on = self.orienting_normal(out_, n); 39 | if in_.dot(on) <= 0.0 { return Vector3::zero() } 40 | let r = out_.reflect(on); 41 | let cos = r.dot(in_); 42 | let a = self.roughness; 43 | // modified phong 44 | self.reflectance * ((a + 2.0) / (2.0 * PI) * cos.powf(a)) 45 | } 46 | 47 | fn sample(&self, out_: Vector3, n: Vector3) -> Sample { 48 | let on = self.orienting_normal(out_, n); 49 | let a = self.roughness; 50 | let r = out_.reflect(on); 51 | // 鏡面反射方向を基準にした正規直交基底を生成 52 | let w = r; 53 | let (u, v) = w.orthonormal_basis(); 54 | // 球面極座標を用いて反射点から単位半球面上のある一点へのベクトルを生成 55 | // (brdfの分布にしたがって重点的にサンプル) 56 | let r1 = 2.0 * PI * rand::random::(); 57 | let r2 = rand::random::(); 58 | let t = r2.powf(1.0 / (a + 2.0)); 59 | let ts = (1.0 - t * t).sqrt(); 60 | let in_ = u * r1.cos() * ts + v * r1.sin() * ts + w * t; 61 | let cos = r.dot(in_); 62 | // 確率密度関数 63 | let pdf = (a + 2.0) / (2.0 * PI) * cos.powf(a); 64 | Sample { 65 | value: in_, 66 | pdf: pdf, 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/objects.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use ray::Ray; 4 | use intersection::Intersection; 5 | use shape::*; 6 | use bvh::BVH; 7 | use math::vector::*; 8 | use sample::Sample; 9 | use aabb::AABB; 10 | 11 | pub struct Objects<'a> { 12 | bvh: BVH<'a>, 13 | emission: Vec<&'a Box>, 14 | emission_area: f32, 15 | } 16 | 17 | impl<'a> Objects<'a> { 18 | pub fn new(objects: &'a Vec>) -> Objects<'a> { 19 | let emission = objects 20 | .iter() 21 | .filter( |v| v.material().emission().sqr_norm() > 0.0 ) 22 | .collect::>(); 23 | let emission_area = emission.iter().map( |v| v.area() ).sum(); 24 | Objects { 25 | bvh: BVH::new(objects), 26 | emission: emission, 27 | emission_area: emission_area, 28 | } 29 | } 30 | 31 | // pub fn new(objects: Vec>) -> Objects { 32 | // Objects { 33 | // objects: objects, 34 | // } 35 | // } 36 | 37 | pub fn sample_emission(&self) -> Sample { 38 | let roulette = self.emission_area * rand::random::(); 39 | let mut area = 0.0; 40 | for obj in &self.emission { 41 | area += obj.area(); 42 | if roulette <= area { 43 | let sample = obj.sample(); 44 | return Sample { 45 | value: sample.value, 46 | pdf: sample.pdf * obj.area() / self.emission_area, 47 | }; 48 | } 49 | } 50 | unreachable!(); 51 | } 52 | 53 | pub fn has_emission(&self) -> bool { 54 | self.emission_area > 0.0 55 | } 56 | } 57 | 58 | impl<'a> Shape for Objects<'a> { 59 | fn aabb(&self) -> &AABB { 60 | self.bvh.aabb() 61 | } 62 | 63 | fn intersect(&self, ray: &Ray) -> Option { 64 | self.bvh.intersect(&ray) 65 | } 66 | 67 | // pub fn intersect(&self, ray: &Ray) -> Option { 68 | // self.objects.iter().flat_map(|v| v.intersect(&ray)).min_by( 69 | // |a, b| { 70 | // a.distance.partial_cmp(&b.distance).unwrap() 71 | // }, 72 | // ) 73 | // } 74 | } 75 | -------------------------------------------------------------------------------- /src/material/blinn_phong.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use super::traits::Material; 4 | use math::vector::*; 5 | use sample::Sample; 6 | use constant::*; 7 | use util::OrthonormalBasis; 8 | 9 | pub struct BlinnPhongMaterial { 10 | // 反射率 11 | pub reflectance: Vector3, 12 | // ラフネス 13 | pub roughness: f32, 14 | } 15 | 16 | impl Material for BlinnPhongMaterial { 17 | fn orienting_normal(&self, out_: Vector3, normal: Vector3) -> Vector3 { 18 | // 物体の内外を考慮した法線方向から拡散反射面としての法線方向を求める 19 | if normal.dot(out_) < 0.0 { 20 | normal * -1.0 21 | } else { 22 | normal 23 | } 24 | } 25 | 26 | fn emission(&self) -> Vector3 { 27 | Vector3::zero() 28 | } 29 | 30 | fn weight(&self) -> f32 { 31 | // 反射率のうち最大のものをつかう 32 | self.reflectance.x.max(self.reflectance.y).max( 33 | self.reflectance.z, 34 | ) 35 | } 36 | 37 | fn brdf(&self, out_: Vector3, in_: Vector3, n: Vector3, _pos: Vector3) -> Vector3 { 38 | let on = self.orienting_normal(out_, n); 39 | if in_.dot(on) <= 0.0 { return Vector3::zero() } 40 | // ハーフベクトル 41 | let h = (in_ + out_).normalize(); 42 | let cos = h.dot(on); 43 | let a = self.roughness; 44 | debug_assert!(cos >= 0.0 && cos <= 1.0 && cos.is_finite(), "cos: {}", cos); 45 | // blinn phong 46 | self.reflectance * ((a + 2.0) * (a + 4.0) / (8.0 * PI * (2.0f32.powf(-a / 2.0) + a)) * cos.powf(a)) 47 | } 48 | 49 | fn sample(&self, out_: Vector3, n: Vector3) -> Sample { 50 | let on = self.orienting_normal(out_, n); 51 | let a = self.roughness; 52 | // 法線方向を基準にした正規直交基底を生成 53 | let w = on; 54 | let (u, v) = w.orthonormal_basis(); 55 | // 球面極座標を用いて反射点から単位半球面上のある一点へのベクトルを生成 56 | // (brdfの分布にしたがって重点的にサンプル) 57 | let r1 = 2.0 * PI * rand::random::(); 58 | let r2 = rand::random::(); 59 | let t = r2.powf(1.0 / (a + 2.0)); 60 | let ts = (1.0 - t * t).sqrt(); 61 | // ハーフベクトルをサンプリング 62 | let h = u * r1.cos() * ts + v * r1.sin() * ts + w * t; 63 | // 入射ベクトル 64 | let in_ = h * (2.0 * out_.dot(h)) - out_; 65 | let cos = on.dot(h); 66 | // 確率密度関数 67 | let pdf = (a + 2.0) / (2.0 * PI) * cos.powf(a); 68 | Sample { 69 | value: in_, 70 | pdf: pdf, 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/sky.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | 3 | use ray::Ray; 4 | use math::vector::*; 5 | use constant::*; 6 | use std::fs::File; 7 | use std::io::BufReader; 8 | 9 | pub trait Sky { 10 | fn radiance(&self, &Ray) -> Vector3; 11 | } 12 | 13 | pub struct UniformSky { 14 | pub emission: Vector3, 15 | } 16 | 17 | impl Sky for UniformSky { 18 | fn radiance(&self, _: &Ray) -> Vector3 { 19 | self.emission 20 | } 21 | } 22 | 23 | pub struct SimpleSky { 24 | pub meridian: Vector3, 25 | pub horizon: Vector3, 26 | } 27 | 28 | impl Sky for SimpleSky { 29 | fn radiance(&self, ray: &Ray) -> Vector3 { 30 | let weight = ray.direction.dot(Vector3::new(0.0, 1.0, 0.0)).abs(); 31 | self.meridian * weight + self.horizon * (1.0 - weight) 32 | } 33 | } 34 | 35 | pub struct IBLSky { 36 | hdr_image: Vec>, 37 | height: usize, 38 | longitude_offset: f32, 39 | } 40 | 41 | impl IBLSky { 42 | pub fn new(path: &str, longitude_offset: f32) -> IBLSky { 43 | println!("loading hdr image..."); 44 | let image_file = File::open(path).unwrap(); 45 | let decoder = image::hdr::HDRDecoder::new(BufReader::new(image_file)).unwrap(); 46 | println!("{:?}", decoder.metadata()); 47 | let height = decoder.metadata().height as usize; 48 | let image = decoder.read_image_hdr().unwrap(); 49 | IBLSky { 50 | hdr_image: image, 51 | height: height, 52 | longitude_offset: longitude_offset, 53 | } 54 | } 55 | } 56 | 57 | impl Sky for IBLSky { 58 | fn radiance(&self, ray: &Ray) -> Vector3 { 59 | // 0 <= theta <= pi 60 | let theta = (ray.direction.y).acos(); 61 | // -pi < phi <= pi 62 | let phi = ray.direction.z.atan2(ray.direction.x); 63 | // 0 <= (u, v) < 1 64 | let u = ((phi + PI + self.longitude_offset) / (2.0 * PI)) % 1.0; 65 | let v = (theta / PI) % 1.0; 66 | let height = self.height; 67 | let width = self.height * 2; 68 | let all = width * height; 69 | let x = (width as f32 * u).floor() as usize; 70 | let y = (height as f32 * v).floor() as usize; 71 | let index = y * width + x; 72 | let color = self.hdr_image[index % all]; 73 | return Vector3::new( 74 | color.data[0] as f32, 75 | color.data[1] as f32, 76 | color.data[2] as f32, 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/sphere.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use math::vector::*; 3 | use ray::Ray; 4 | use material::material::Material; 5 | use intersection::Intersection; 6 | use constant::*; 7 | use shape::*; 8 | use aabb::AABB; 9 | use sample::Sample; 10 | use util::*; 11 | 12 | pub struct Sphere { 13 | pub radius: f32, 14 | pub position: Vector3, 15 | pub material: Arc, 16 | aabb: AABB, 17 | area: f32, 18 | } 19 | 20 | impl Sphere { 21 | pub fn new(position: Vector3, radius: f32, material: Arc) -> Sphere { 22 | Sphere { 23 | position: position, 24 | radius: radius, 25 | area: 4.0 * PI * radius.powi(2), 26 | material: material, 27 | aabb: Self::aabb(position, radius), 28 | } 29 | } 30 | 31 | fn aabb(position: Vector3, radius: f32) -> AABB { 32 | let r = Vector3::new(radius, radius, radius); 33 | AABB { 34 | min: position - r, 35 | max: position + r, 36 | center: position, 37 | } 38 | } 39 | } 40 | 41 | impl Shape for Sphere { 42 | fn intersect(&self, ray: &Ray) -> Option { 43 | let co = ray.origin - self.position; 44 | let cod = co.dot(ray.direction); 45 | let det = cod * cod - co.sqr_norm() + self.radius * self.radius; 46 | if det <= 0.0 { 47 | return None; 48 | } 49 | let t1 = -cod - det.sqrt(); 50 | let t2 = -cod + det.sqrt(); 51 | if t1 < EPS && t2 < EPS { 52 | return None; 53 | } 54 | let distance = if t1 > EPS { t1 } else { t2 }; 55 | let position = ray.origin + ray.direction * distance; 56 | let outer_normal = (position - self.position).normalize(); 57 | Some(Intersection { 58 | distance: distance, 59 | position: position, 60 | normal: outer_normal, 61 | material: self.material.clone(), 62 | }) 63 | } 64 | 65 | fn aabb(&self) -> &AABB { 66 | &self.aabb 67 | } 68 | } 69 | 70 | impl SurfaceShape for Sphere { 71 | fn material(&self) -> Arc { 72 | self.material.clone() 73 | } 74 | 75 | fn area(&self) -> f32 { 76 | self.area 77 | } 78 | 79 | fn sample(&self) -> Sample { 80 | Sample { 81 | value: self.position + self.radius * Sampler::sphere_uniform(), 82 | pdf: 1.0 / self.area, 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/material/lambert.rs: -------------------------------------------------------------------------------- 1 | use super::traits::Material; 2 | use math::vector::*; 3 | use sample::Sample; 4 | use constant::*; 5 | use util::{Sampler, OrthonormalBasis}; 6 | 7 | pub struct LambertianMaterial { 8 | pub emission: Vector3, 9 | // 拡散反射率 10 | pub albedo: Vector3, 11 | } 12 | 13 | impl Material for LambertianMaterial { 14 | fn orienting_normal(&self, out_: Vector3, normal: Vector3) -> Vector3 { 15 | // 物体の内外を考慮した法線方向から拡散反射面としての法線方向を求める 16 | if normal.dot(out_) < 0.0 { 17 | normal * -1.0 18 | } else { 19 | normal 20 | } 21 | } 22 | 23 | fn emission(&self) -> Vector3 { 24 | self.emission 25 | } 26 | 27 | fn weight(&self) -> f32 { 28 | // 拡散反射の時は各色の反射率のうち最大のものを使う 29 | self.albedo.x.max(self.albedo.y).max(self.albedo.z) 30 | } 31 | 32 | fn brdf(&self, _out_: Vector3, _in_: Vector3, _n_: Vector3, pos: Vector3) -> Vector3 { 33 | // BRDFは半球全体に一様に散乱するDiffuse面を考えると ρ / π 34 | self.albedo * checker((pos.x, pos.z)) / PI 35 | } 36 | 37 | fn sample(&self, out_: Vector3, n: Vector3) -> Sample { 38 | // 反射点での法線方向を基準にした正規直交基底を生成 39 | let on = self.orienting_normal(out_, n); 40 | let w = on; 41 | let (u, v) = w.orthonormal_basis(); 42 | // 球面極座標を用いて反射点から単位半球面上のある一点へのベクトルを生成 43 | // (cosにしたがって重点的にサンプル) 44 | let sample = Sampler::hemisphere_cos_importance(); 45 | let in_ = u * sample.x + v * sample.y + w * sample.z; 46 | // cos項 47 | let cos_term = in_.dot(n); 48 | // 確率密度関数 49 | // (cosにしたがって重点的にサンプル) cosθ / π 50 | let pdf = cos_term / PI; 51 | Sample { 52 | value: in_, 53 | pdf: pdf, 54 | } 55 | } 56 | } 57 | 58 | fn signed_mod(base: f32, module: f32) -> f32 { 59 | if base > 0.0 { 60 | base % module 61 | } else { 62 | module - (-base) % module 63 | } 64 | } 65 | 66 | fn checker(uv: (f32, f32)) -> Vector3 { 67 | let lw = 2.0; 68 | let li = 150.0; 69 | let sw = 1.0; 70 | let si = 30.0; 71 | let cw = 150.0; 72 | let ci = 300.0; 73 | let u = uv.0; 74 | let v = uv.1; 75 | let lu = signed_mod(u, li); 76 | let lv = signed_mod(v, li); 77 | let su = signed_mod(u, si); 78 | let sv = signed_mod(v, si); 79 | let cu = signed_mod(u, ci); 80 | let cv = signed_mod(v, ci); 81 | if lu < lw || lv < lw { 82 | Vector3::new(0.5, 0.5, 0.5) 83 | } else if su < sw || sv < sw { 84 | Vector3::new(0.6, 0.6, 0.6) 85 | } else if (cu < cw || cv < cw) && !(cu < cw && cv < cw) { 86 | Vector3::new(0.8, 0.8, 0.8) 87 | } else { 88 | Vector3::new(1.0, 1.0, 1.0) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /scenes/welcome-2018-geo.toml: -------------------------------------------------------------------------------- 1 | [renderer] 2 | samples = 64 3 | depth = 5 4 | depth-limit = 64 5 | no-direct-emitter = false 6 | threads = 0 7 | integrator = "pt-direct" 8 | 9 | [film] 10 | resolution = [2138, 1536] 11 | output = "hdr" 12 | gamma = 2.2 13 | sensitivity = [1, 1, 1] 14 | 15 | [sky] 16 | type = "uniform" 17 | color = [0.7, 0.7, 0.7] 18 | 19 | [camera] 20 | type = "ideal-pinhole" 21 | fov = 39.3077 22 | [[camera.transform]] 23 | type = "look-at" 24 | origin = [278, 273, -1600] 25 | target = [278, 273, 0] 26 | up = [0, 1, 0] 27 | 28 | [[light]] 29 | type = "area" 30 | object = "light" 31 | emission = [2000, 1540, 1120] 32 | 33 | [[object]] 34 | mesh = "bunny" 35 | material = "mat" 36 | [[object.transform]] 37 | type = "axis-angle" 38 | axis = [0, 1, 0] 39 | angle = 180 40 | [[object.transform]] 41 | type = "scale" 42 | vector = [130, 130, 130] 43 | [[object.transform]] 44 | type = "translate" 45 | vector = [255, -6, 278] 46 | 47 | [[object]] 48 | mesh = "cbox" 49 | material = "mat" 50 | 51 | [[object]] 52 | mesh = "quad" 53 | material = "mat" 54 | [[object.transform]] 55 | type = "scale" 56 | vector = [550, 1, 550] 57 | [[object.transform]] 58 | type = "translate" 59 | vector = [278, 1, 278] 60 | 61 | [[object]] 62 | mesh = "quad" 63 | material = "mat" 64 | [[object.transform]] 65 | type = "scale" 66 | vector = [550, 1, 550] 67 | [[object.transform]] 68 | type = "axis-angle" 69 | axis = [1, 0, 0] 70 | angle = -90 71 | [[object.transform]] 72 | type = "translate" 73 | vector = [278, -550, -272] 74 | 75 | [[object]] 76 | name = "light" 77 | mesh = "quad" 78 | material = "mat" 79 | [[object.transform]] 80 | type = "axis-angle" 81 | axis = [1, 0, 0] 82 | angle = 180 83 | [[object.transform]] 84 | type = "scale" 85 | vector = [100, 1, 100] 86 | [[object.transform]] 87 | type = "translate" 88 | vector = [1700, 2500, -1000] 89 | 90 | [[material]] 91 | name = "mat" 92 | type = "lambert" 93 | albedo = [0.5, 0.5, 0.5] 94 | 95 | [[material]] 96 | name = "black" 97 | type = "lambert" 98 | albedo = [0, 0, 0] 99 | 100 | [[mesh]] 101 | name = "bunny" 102 | type = "obj" 103 | # https://g3d.cs.williams.edu/g3d/data10/research/model/bunny/bunny.zip 104 | path = "models/bunny/bunny.obj" 105 | 106 | [[mesh]] 107 | name = "cbox" 108 | type = "obj" 109 | # obj: https://cl.ly/omBj mtl: https://cl.ly/oluU 110 | path = "models/simple/cbox.obj" 111 | 112 | [[mesh]] 113 | name = "quad" 114 | type = "obj" 115 | path = "models/simple/quad.obj" 116 | 117 | [[mesh]] 118 | name = "light" 119 | type = "obj" 120 | # obj: https://cl.ly/olil 121 | path = "models/simple/cbox_luminaire.obj" 122 | -------------------------------------------------------------------------------- /src/aabb.rs: -------------------------------------------------------------------------------- 1 | extern crate ordered_float; 2 | 3 | use std::f32; 4 | use math::vector::*; 5 | use ray::Ray; 6 | use self::ordered_float::OrderedFloat; 7 | use constant::*; 8 | 9 | #[derive(Clone)] 10 | pub struct AABB { 11 | pub min: Vector3, 12 | pub max: Vector3, 13 | pub center: Vector3, 14 | } 15 | 16 | impl AABB { 17 | pub fn side(&self) -> Vector3 { 18 | Vector3::new( 19 | (self.max.x - self.min.x).abs(), 20 | (self.max.y - self.min.y).abs(), 21 | (self.max.z - self.min.z).abs(), 22 | ) 23 | } 24 | 25 | pub fn surface_area(&self) -> f32 { 26 | let side = self.side(); 27 | 2.0 * (side.x * side.y + side.y * side.z + side.z * side.x) 28 | } 29 | 30 | pub fn merge(list: &Vec<&AABB>) -> AABB { 31 | let min = Vector3::new( 32 | *list.iter().map(|v| OrderedFloat(v.min.x)).min().unwrap(), 33 | *list.iter().map(|v| OrderedFloat(v.min.y)).min().unwrap(), 34 | *list.iter().map(|v| OrderedFloat(v.min.z)).min().unwrap(), 35 | ); 36 | let max = Vector3::new( 37 | *list.iter().map(|v| OrderedFloat(v.max.x)).max().unwrap(), 38 | *list.iter().map(|v| OrderedFloat(v.max.y)).max().unwrap(), 39 | *list.iter().map(|v| OrderedFloat(v.max.z)).max().unwrap(), 40 | ); 41 | AABB { 42 | min: min, 43 | max: max, 44 | center: (min + max) / 2.0, 45 | } 46 | } 47 | 48 | pub fn merge_with(&self, v: &AABB) -> AABB { 49 | let min = Vector3::new( 50 | self.min.x.min(v.min.x), 51 | self.min.y.min(v.min.y), 52 | self.min.z.min(v.min.z), 53 | ); 54 | let max = Vector3::new( 55 | self.max.x.max(v.max.x), 56 | self.max.y.max(v.max.y), 57 | self.max.z.max(v.max.z), 58 | ); 59 | AABB { 60 | min: min, 61 | max: max, 62 | center: (min + max) / 2.0, 63 | } 64 | } 65 | 66 | pub fn empty() -> AABB { 67 | AABB { 68 | min: Vector3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY), 69 | max: Vector3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY), 70 | center: Vector3::zero(), 71 | } 72 | } 73 | 74 | #[inline] 75 | pub fn is_intersect(&self, ray: &Ray) -> bool { 76 | let mut min = -INF; 77 | let mut max = INF; 78 | for i in 0..3 { 79 | let inv_d = 1.0 / ray.direction[i]; 80 | let t1 = (self.min[i] - ray.origin[i]) * inv_d; 81 | let t2 = (self.max[i] - ray.origin[i]) * inv_d; 82 | let (t_min, t_max) = if t1 > t2 { (t2, t1) } else { (t1, t2) }; 83 | if min < t_min { 84 | min = t_min 85 | } 86 | if max > t_max { 87 | max = t_max 88 | } 89 | if min > max { return false } 90 | } 91 | true 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /scenes/ridaisai-2018.toml: -------------------------------------------------------------------------------- 1 | [renderer] 2 | samples = 4 3 | depth = 5 4 | depth-limit = 64 5 | no-direct-emitter = false 6 | threads = 0 7 | integrator = "pt" 8 | 9 | [film] 10 | # resolution = [40, 20] 11 | resolution = [2138, 1536] 12 | # resolution = [512, 512] 13 | output = "hdr" 14 | gamma = 2.2 15 | sensitivity = [1, 1, 1] 16 | 17 | [sky] 18 | type = "ibl" 19 | path = "models/ibl/PaperMill_Ruins_E.hdr" 20 | # path = "models/ibl/hotel_room_4k.hdr" 21 | longitude-offset = 6.0 22 | # longitude-offset = 6.0 23 | 24 | [camera] 25 | type = "thin-lens" 26 | fov = 39.3077 27 | # fov = 80 28 | focus-distance = 850 29 | f-number = 1.8 30 | # f-number = 1000 31 | [[camera.transform]] 32 | type = "look-at" 33 | origin = [278, 400, -600] 34 | target = [278, 400, 0] 35 | up = [0, 1, 0] 36 | [[camera.transform]] 37 | type = "axis-angle" 38 | axis = [1, 0, 0] 39 | angle = 20 40 | 41 | [[object]] 42 | mesh = "bunny" 43 | material = "translucent-orange" 44 | [[object.transform]] 45 | type = "axis-angle" 46 | axis = [0, 1, 0] 47 | angle = 185 48 | [[object.transform]] 49 | type = "scale" 50 | vector = [130, 130, 130] 51 | [[object.transform]] 52 | type = "translate" 53 | vector = [155, -6, 218] 54 | 55 | [[object]] 56 | mesh = "bunny" 57 | material = "translucent-black" 58 | [[object.transform]] 59 | type = "axis-angle" 60 | axis = [0, 1, 0] 61 | angle = 185 62 | [[object.transform]] 63 | type = "scale" 64 | vector = [130, 130, 130] 65 | [[object.transform]] 66 | type = "translate" 67 | vector = [355, -6, 218] 68 | 69 | # [[object]] 70 | # mesh = "sphere" 71 | # material = "translucent-black" 72 | # [[object.transform]] 73 | # type = "translate" 74 | # vector = [255, 100, 278] 75 | 76 | [[object]] 77 | mesh = "quad" 78 | material = "white" 79 | [[object.transform]] 80 | type = "scale" 81 | vector = [400, 1, 400] 82 | [[object.transform]] 83 | type = "translate" 84 | vector = [278, 1, 278] 85 | 86 | [[material]] 87 | name = "white" 88 | type = "lambert" 89 | albedo = [0.740063, 0.742313, 0.733934] 90 | 91 | [[material]] 92 | name = "black" 93 | type = "lambert" 94 | albedo = [0, 0, 0] 95 | 96 | [[material]] 97 | name = "translucent-orange" 98 | type = "ideal-refraction" 99 | reflectance = [0.98, 0.90, 0.60] 100 | absorbtance = 0.1 101 | ior = 1.55 102 | 103 | [[material]] 104 | name = "translucent-black" 105 | type = "ideal-refraction" 106 | reflectance = [0.96, 0.96, 0.95] 107 | absorbtance = 0.5 108 | ior = 1.4 109 | 110 | [[mesh]] 111 | name = "bunny" 112 | type = "obj" 113 | # https://g3d.cs.williams.edu/g3d/data10/research/model/bunny/bunny.zip 114 | path = "models/bunny/bunny.obj" 115 | 116 | # [[mesh]] 117 | # name = "sphere" 118 | # type = "sphere" 119 | # radius = 100 120 | 121 | [[mesh]] 122 | name = "quad" 123 | type = "obj" 124 | path = "models/simple/quad.obj" 125 | -------------------------------------------------------------------------------- /scenes/welcome-2018.toml: -------------------------------------------------------------------------------- 1 | [renderer] 2 | samples = 64 3 | depth = 5 4 | depth-limit = 64 5 | no-direct-emitter = false 6 | threads = 0 7 | integrator = "pt-direct" 8 | 9 | [film] 10 | resolution = [2138, 1536] 11 | output = "hdr" 12 | gamma = 2.2 13 | sensitivity = [1, 1, 1] 14 | 15 | [sky] 16 | type = "ibl" 17 | path = "models/ibl/14-Hamarikyu_Bridge_B_3k.hdr" 18 | 19 | [camera] 20 | type = "thin-lens" 21 | fov = 39.3077 22 | focus_distance = 1800 23 | f_number = 1.8 24 | [[camera.transform]] 25 | type = "look-at" 26 | origin = [278, 273, -1600] 27 | target = [278, 273, 0] 28 | up = [0, 1, 0] 29 | 30 | [[light]] 31 | type = "area" 32 | object = "light" 33 | emission = [2000, 1540, 1120] 34 | 35 | [[object]] 36 | mesh = "bunny" 37 | material = "glossy" 38 | [[object.transform]] 39 | type = "axis-angle" 40 | axis = [0, 1, 0] 41 | angle = 180 42 | [[object.transform]] 43 | type = "scale" 44 | vector = [130, 130, 130] 45 | [[object.transform]] 46 | type = "translate" 47 | vector = [255, -6, 278] 48 | 49 | [[object]] 50 | mesh = "cbox" 51 | 52 | [[object]] 53 | mesh = "quad" 54 | material = "white" 55 | [[object.transform]] 56 | type = "scale" 57 | vector = [550, 1, 550] 58 | [[object.transform]] 59 | type = "translate" 60 | vector = [278, 1, 278] 61 | 62 | [[object]] 63 | mesh = "quad" 64 | material = "white" 65 | [[object.transform]] 66 | type = "scale" 67 | vector = [550, 1, 550] 68 | [[object.transform]] 69 | type = "axis-angle" 70 | axis = [1, 0, 0] 71 | angle = -90 72 | [[object.transform]] 73 | type = "translate" 74 | vector = [278, -550, -272] 75 | 76 | [[object]] 77 | name = "light" 78 | mesh = "quad" 79 | material = "black" 80 | [[object.transform]] 81 | type = "axis-angle" 82 | axis = [1, 0, 0] 83 | angle = 180 84 | [[object.transform]] 85 | type = "scale" 86 | vector = [100, 1, 100] 87 | [[object.transform]] 88 | type = "translate" 89 | vector = [1700, 2500, -1000] 90 | 91 | [[material]] 92 | name = "white" 93 | type = "lambert" 94 | albedo = [0.740063, 0.742313, 0.733934] 95 | 96 | [[material]] 97 | name = "glossy" 98 | type = "ggx" 99 | reflectance = [1.0, 1.0, 1.0] 100 | roughness = 0.8 101 | ior = 100000 102 | 103 | [[material]] 104 | name = "black" 105 | type = "lambert" 106 | albedo = [0, 0, 0] 107 | 108 | [[mesh]] 109 | name = "bunny" 110 | type = "obj" 111 | # https://g3d.cs.williams.edu/g3d/data10/research/model/bunny/bunny.zip 112 | path = "models/bunny/bunny.obj" 113 | 114 | [[mesh]] 115 | name = "cbox" 116 | type = "obj" 117 | # obj: https://cl.ly/omBj mtl: https://cl.ly/oluU 118 | path = "models/simple/cbox.obj" 119 | 120 | [[mesh]] 121 | name = "quad" 122 | type = "obj" 123 | path = "models/simple/quad.obj" 124 | 125 | [[mesh]] 126 | name = "light" 127 | type = "obj" 128 | # obj: https://cl.ly/olil 129 | path = "models/simple/cbox_luminaire.obj" 130 | -------------------------------------------------------------------------------- /scenes/brdf.toml: -------------------------------------------------------------------------------- 1 | [renderer] 2 | samples = 64 3 | no-direct-emitter = false 4 | integrator = "pt-direct" 5 | 6 | [film] 7 | resolution = [960, 540] 8 | output = "hdr" 9 | 10 | [sky] 11 | type = "uniform" 12 | color = [0, 0, 0] 13 | 14 | [camera] 15 | type = "ideal-pinhole" 16 | fov = 39.3077 17 | [[camera.transform]] 18 | type = "look-at" 19 | origin = [0, 110, -500] 20 | target = [0, 60, 0] 21 | up = [0, 1, 0] 22 | 23 | [[light]] 24 | type = "area" 25 | object = "light" 26 | emission = [15, 13, 11] 27 | 28 | [[object]] 29 | mesh = "sphere" 30 | material = "lambert" 31 | [[object.transform]] 32 | type = "translate" 33 | vector = [-120, 25, 0] 34 | 35 | [[object]] 36 | mesh = "sphere" 37 | material = "glossy1" 38 | [[object.transform]] 39 | type = "translate" 40 | vector = [-60, 25, 0] 41 | 42 | [[object]] 43 | mesh = "sphere" 44 | material = "glossy5" 45 | [[object.transform]] 46 | type = "translate" 47 | vector = [0, 25, 0] 48 | 49 | [[object]] 50 | mesh = "sphere" 51 | material = "glossy10" 52 | [[object.transform]] 53 | type = "translate" 54 | vector = [60, 25, 0] 55 | 56 | [[object]] 57 | mesh = "sphere" 58 | material = "glossy20" 59 | [[object.transform]] 60 | type = "translate" 61 | vector = [120, 25, 0] 62 | 63 | [[object]] 64 | mesh = "quad" 65 | [[object.transform]] 66 | type = "scale" 67 | vector = [200, 1, 100] 68 | 69 | [[object]] 70 | mesh = "quad" 71 | material = "floor" 72 | [[object.transform]] 73 | type = "scale" 74 | vector = [200, 1, 100] 75 | [[object.transform]] 76 | type = "translate" 77 | vector = [0, 0, 100] 78 | [[object.transform]] 79 | type = "axis-angle" 80 | axis = [1, 0, 0] 81 | angle = -80 82 | [[object.transform]] 83 | type = "translate" 84 | vector = [0, 0, 100] 85 | 86 | [[object]] 87 | name = "light" 88 | mesh = "quad" 89 | [[object.transform]] 90 | type = "axis-angle" 91 | axis = [1, 0, 0] 92 | angle = 180 93 | [[object.transform]] 94 | type = "scale" 95 | vector = [100, 1, 7] 96 | [[object.transform]] 97 | type = "translate" 98 | vector = [0, 120, 0] 99 | 100 | [[material]] 101 | name = "floor" 102 | type = "lambert" 103 | albedo = [0.5, 0.5, 0.4] 104 | 105 | [[material]] 106 | name = "lambert" 107 | type = "lambert" 108 | albedo = [0.5, 0.5, 0.5] 109 | 110 | [[material]] 111 | name = "glossy1" 112 | type = "ggx" 113 | reflectance = [1.0, 1.0, 1.0] 114 | roughness = 0.8 115 | ior = 100000 116 | 117 | [[material]] 118 | name = "glossy5" 119 | type = "ggx" 120 | reflectance = [1.0, 1.0, 1.0] 121 | roughness = 0.6 122 | ior = 100000 123 | 124 | [[material]] 125 | name = "glossy10" 126 | type = "ggx" 127 | reflectance = [1.0, 1.0, 1.0] 128 | roughness = 0.4 129 | ior = 100000 130 | 131 | [[material]] 132 | name = "glossy20" 133 | type = "ggx" 134 | reflectance = [1.0, 1.0, 1.0] 135 | roughness = 0.2 136 | ior = 100000 137 | 138 | [[mesh]] 139 | name = "quad" 140 | type = "obj" 141 | path = "models/simple/quad.obj" 142 | 143 | [[mesh]] 144 | name = "sphere" 145 | type = "sphere" 146 | radius = 25 147 | -------------------------------------------------------------------------------- /src/material/ggx.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use super::traits::Material; 4 | use math::vector::*; 5 | use sample::Sample; 6 | use constant::*; 7 | use util::OrthonormalBasis; 8 | 9 | pub struct GGXMaterial { 10 | // 反射率 11 | pub reflectance: Vector3, 12 | // 屈折率 13 | pub ior: f32, 14 | // ラフネス 15 | pub roughness: f32, 16 | } 17 | 18 | impl GGXMaterial { 19 | fn alpha(&self) -> f32 { 20 | self.roughness * self.roughness 21 | } 22 | 23 | fn gaf_smith(&self, out_: Vector3, in_: Vector3, n: Vector3) -> f32 { 24 | self.g_ggx(in_, n) * self.g_ggx(out_, n) 25 | } 26 | 27 | fn g_ggx(&self, v: Vector3, n: Vector3) -> f32 { 28 | let a2 = self.alpha() * self.alpha(); 29 | let cos = v.dot(n); 30 | let tan = 1.0 / (cos * cos) - 1.0; 31 | 2.0 / (1.0 + (1.0 + a2 * tan * tan).sqrt()) 32 | } 33 | 34 | fn ndf(&self, m: Vector3, n: Vector3) -> f32 { 35 | let a2 = self.alpha() * self.alpha(); 36 | let mdn = m.dot(n); 37 | let x = (a2 - 1.0) * mdn * mdn + 1.0; 38 | a2 / (PI * x * x) 39 | } 40 | 41 | fn fresnel_schlick(&self, in_: Vector3, m: Vector3) -> f32 { 42 | let nnn = 1.0 - self.ior; 43 | let nnp = 1.0 + self.ior; 44 | let f_0 = (nnn * nnn) / (nnp * nnp); 45 | let c = in_.dot(m); 46 | f_0 + (1.0 - f_0) * (1.0 - c).powi(5) 47 | } 48 | } 49 | 50 | impl Material for GGXMaterial { 51 | fn orienting_normal(&self, out_: Vector3, normal: Vector3) -> Vector3 { 52 | // 物体の内外を考慮した法線方向から拡散反射面としての法線方向を求める 53 | if normal.dot(out_) < 0.0 { 54 | normal * -1.0 55 | } else { 56 | normal 57 | } 58 | } 59 | 60 | fn emission(&self) -> Vector3 { 61 | Vector3::zero() 62 | } 63 | 64 | fn weight(&self) -> f32 { 65 | // 反射率のうち最大のものをつかう 66 | self.reflectance.x.max(self.reflectance.y).max( 67 | self.reflectance.z, 68 | ) 69 | } 70 | 71 | fn brdf(&self, out_: Vector3, in_: Vector3, n: Vector3, _pos: Vector3) -> Vector3 { 72 | let on = self.orienting_normal(out_, n); 73 | if in_.dot(on) <= 0.0 { return Vector3::zero() } 74 | debug_assert!(out_.dot(on) > 0.0, "o.n = {}", out_.dot(on)); 75 | // ハーフベクトル 76 | let h = (in_ + out_).normalize(); 77 | // Torrance-Sparrow model 78 | let f = self.fresnel_schlick(in_, h); 79 | debug_assert!(f >= 0.0 && f <= 1.0 && f.is_finite(), "f: {}", f); 80 | let g = self.gaf_smith(out_, in_, on); 81 | debug_assert!(g >= 0.0 && g <= 1.0 && g.is_finite(), "g: {}", g); 82 | let d = self.ndf(h, on); 83 | debug_assert!(d >= 0.0 && d.is_finite() , "d: {}", d); 84 | self.reflectance * f * g * d / (4.0 * in_.dot(on) * out_.dot(on)) 85 | } 86 | 87 | fn sample(&self, out_: Vector3, n: Vector3) -> Sample { 88 | let on = self.orienting_normal(out_, n); 89 | // 法線方向を基準にした正規直交基底を生成 90 | let w = on; 91 | let (u, v) = w.orthonormal_basis(); 92 | // 球面極座標を用いて反射点から単位半球面上のある一点へのベクトルを生成 93 | // (brdfの分布にしたがって重点的にサンプル) 94 | let r1 = 2.0 * PI * rand::random::(); 95 | let r2 = rand::random::(); 96 | let tan = self.alpha() * (r2 / (1.0 - r2)).sqrt(); 97 | let x = 1.0 + tan * tan; 98 | let cos = 1.0 / x.sqrt(); 99 | let sin = tan / x.sqrt(); 100 | // ハーフベクトルをサンプリング 101 | let h = u * r1.cos() * sin + v * r1.sin() * sin + w * cos; 102 | // 入射ベクトル 103 | let o_h = out_.dot(h); 104 | let in_ = h * (2.0 * o_h) - out_; 105 | // ヤコビアン 106 | let jacobian = 1.0 / (4.0 * o_h); 107 | // 確率密度関数 108 | let pdf = self.ndf(h, on) * h.dot(on) * jacobian; 109 | Sample { 110 | value: in_, 111 | pdf: pdf, 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use math::vector::*; 4 | use constant::*; 5 | 6 | pub trait OrthonormalBasis: Sized { 7 | fn orthonormal_basis(&self) -> (Self, Self); 8 | } 9 | 10 | impl OrthonormalBasis for Vector3 { 11 | // 自身(normal)を基準として正規直交基底を生成 (正規化済み前提) 12 | fn orthonormal_basis(&self) -> (Vector3, Vector3) { 13 | let tangent = if self.x.abs() > EPS { 14 | Vector3::new(0.0, 1.0, 0.0) 15 | } else { 16 | Vector3::new(1.0, 0.0, 0.0) 17 | }.cross(*self) 18 | .normalize(); 19 | let bionrmal = self.cross(tangent); 20 | (tangent, bionrmal) 21 | } 22 | } 23 | 24 | pub trait BoundaryResponse where Self: Sized { 25 | fn reflect(&self, Self) -> Self; 26 | fn refract(&self, Self, f32) -> Option; 27 | } 28 | 29 | impl BoundaryResponse for Vector3 { 30 | fn reflect(&self, normal: Vector3) -> Vector3 { 31 | -*self + normal * ((*self).dot(normal) * 2.0) 32 | } 33 | 34 | fn refract(&self, normal: Vector3, from_per_to_ior: f32) -> Option { 35 | let dn = self.dot(normal); 36 | let cos2theta = 1.0 - from_per_to_ior.powi(2) * (1.0 - dn.powi(2)); 37 | if cos2theta > 0.0 { 38 | Some(-*self * from_per_to_ior - normal * (from_per_to_ior * -dn + cos2theta.sqrt())) 39 | } else { 40 | None 41 | } 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod test { 47 | use super::*; 48 | 49 | #[test] 50 | fn reflect_test() { 51 | let v = Vector3::new(1.0, 0.0, 1.0).normalize(); 52 | let n = Vector3::new(0.0, 0.0, 1.0); 53 | let r = v.reflect(n); 54 | let expoect = Vector3::new(-1.0, 0.0, 1.0).normalize(); 55 | assert!((r - expoect).norm() < EPS); 56 | } 57 | 58 | #[test] 59 | fn refract_total_reflection_test() { 60 | let v = Vector3::new(1.0, 0.0, 0.1).normalize(); 61 | let n = Vector3::new(0.0, 0.0, 1.0); 62 | let r = v.refract(n, 1.5 / 1.0); 63 | assert!(r.is_none()); 64 | } 65 | 66 | #[test] 67 | fn refract_test() { 68 | // 屈折率 69 | let n1 = 1.0; 70 | let n2 = 1.5; 71 | // 30 deg 72 | let t1 = 30.0 / 180.0 * PI; 73 | let sin_t1 = t1.sin(); 74 | let v = Vector3::new(t1.tan(), 0.0, 1.0).normalize(); 75 | let n = Vector3::new(0.0, 0.0, 1.0); 76 | let r = v.refract(n, 1.5 / 1.0).unwrap(); 77 | let sin_t2 = r.cross(-n).norm(); 78 | // sin(t1) / sin(t2) == n1 / n2 79 | assert!((sin_t1 / sin_t2 - n1 / n2).abs() < EPS, "{} {} {} {}", sin_t1, sin_t2, n1, n2); 80 | assert!((r.norm() - 1.0).abs() < EPS, "{} {}", r, r.norm()); 81 | } 82 | } 83 | 84 | pub struct Sampler; 85 | 86 | impl Sampler { 87 | pub fn hemisphere_cos_importance() -> Vector3 { 88 | // 乱数を生成 89 | // (cosにしたがって重点的にサンプル) 90 | let r1 = 2.0 * PI * rand::random::(); 91 | let r2 = rand::random::(); 92 | let r2s = r2.sqrt(); 93 | // 球面極座標を用いて反射点から単位半球面上のある一点へのベクトルを生成 94 | // (cosにしたがって重点的にサンプル) 95 | Vector3::new(r1.cos() * r2s, r1.sin() * r2s, (1.0 - r2).sqrt()) 96 | } 97 | 98 | pub fn hemisphere_uniform() -> Vector3 { 99 | // 乱数を生成 100 | let r1 = 2.0 * PI * rand::random::(); 101 | let r2 = rand::random::(); 102 | let r2s = (1.0 - r2 * r2).sqrt(); 103 | // 球面極座標を用いて反射点から単位半球面上のある一点へのベクトルを生成 104 | // (一様サンプル) 105 | Vector3::new(r1.cos() * r2s, r1.sin() * r2s, r2.sqrt()) 106 | } 107 | 108 | pub fn sphere_uniform() -> Vector3 { 109 | // 乱数を生成 110 | let r1 = 2.0 * PI * rand::random::(); 111 | let r2 = rand::random::() * 2.0 - 1.0; 112 | let r2s = (1.0 - r2 * r2).sqrt(); 113 | // 球面極座標を用いて反射点から単位半球面上のある一点へのベクトルを生成 114 | // (一様サンプル) 115 | Vector3::new(r1.cos() * r2s, r1.sin() * r2s, r2) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/math/vector4.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::{Neg, Add, Sub, Mul, Div, Index}; 3 | use super::traits::{Dot, Zero}; 4 | use super::vector::Vector3; 5 | 6 | #[derive(Debug, Clone, Copy)] 7 | pub struct Vector4 { 8 | pub x: f32, 9 | pub y: f32, 10 | pub z: f32, 11 | pub w: f32, 12 | } 13 | 14 | impl Vector4 { 15 | pub fn new(x: f32, y: f32, z: f32, w: f32) -> Vector4 { 16 | Vector4 { x: x, y: y, z: z, w: w } 17 | } 18 | } 19 | 20 | impl Zero for Vector4 { 21 | fn zero() -> Vector4 { 22 | Vector4::new(0.0, 0.0, 0.0, 0.0) 23 | } 24 | } 25 | 26 | impl<'a> From<&'a [f32]> for Vector4 { 27 | fn from(v: &[f32]) -> Vector4 { 28 | if v.len() > 4 { panic!("Slice must have length more than 4.") } 29 | Vector4 { x: v[0], y: v[1], z: v[2], w: v[3] } 30 | } 31 | } 32 | 33 | impl From> for Vector4 { 34 | fn from(v: Vec) -> Vector4 { 35 | if v.len() > 4 { panic!("Vec must have length more than 4.") } 36 | Vector4 { x: v[0], y: v[1], z: v[2], w: v[3] } 37 | } 38 | } 39 | 40 | impl From for Vector4 { 41 | fn from(v: Vector3) -> Vector4 { 42 | Vector4 { x: v.x, y: v.y, z: v.z, w: 1.0 } 43 | } 44 | } 45 | 46 | impl Into<[f32; 4]> for Vector4 { 47 | fn into(self) -> [f32; 4] { 48 | [self.x, self.y, self.z, self.w] 49 | } 50 | } 51 | 52 | impl fmt::Display for Vector4 { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | write!(f, "({}, {}, {}, {})", self.x, self.y, self.z, self.w) 55 | } 56 | } 57 | 58 | impl Index for Vector4 { 59 | type Output = f32; 60 | 61 | fn index(&self, i: usize) -> &f32 { 62 | match i { 63 | 0 => &self.x, 64 | 1 => &self.y, 65 | 2 => &self.z, 66 | 3 => &self.w, 67 | _ => panic!("Vector4 index out of bounds."), 68 | } 69 | } 70 | } 71 | 72 | impl Dot for Vector4 { 73 | fn dot(self, rhs: Vector4) -> f32 { 74 | self.x * rhs.x + self.y * rhs.y + self.z * rhs.z + self.w * rhs.w 75 | } 76 | } 77 | 78 | impl Neg for Vector4 { 79 | type Output = Vector4; 80 | 81 | fn neg(self) -> Vector4 { 82 | Vector4::new(-self.x, -self.y, -self.z, -self.w) 83 | } 84 | } 85 | 86 | impl Add for Vector4 { 87 | type Output = Vector4; 88 | 89 | fn add(self, rhs: Vector4) -> Vector4 { 90 | Vector4::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z, self.w + rhs.w) 91 | } 92 | } 93 | 94 | impl Sub for Vector4 { 95 | type Output = Vector4; 96 | 97 | fn sub(self, rhs: Vector4) -> Vector4 { 98 | Vector4::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z, self.w - rhs.w) 99 | } 100 | } 101 | 102 | impl Mul for Vector4 { 103 | type Output = Vector4; 104 | 105 | fn mul(self, rhs: f32) -> Vector4 { 106 | Vector4::new(self.x * rhs, self.y * rhs, self.z * rhs, self.w * rhs) 107 | } 108 | } 109 | 110 | impl Mul for f32 { 111 | type Output = Vector4; 112 | 113 | fn mul(self, rhs: Vector4) -> Vector4 { 114 | Vector4::new(self * rhs.x, self * rhs.y, self * rhs.z, self * rhs.w) 115 | } 116 | } 117 | 118 | impl Mul for Vector4 { 119 | type Output = Vector4; 120 | 121 | fn mul(self, rhs: Vector4) -> Vector4 { 122 | Vector4::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z, self.w * rhs.w) 123 | } 124 | } 125 | 126 | impl Div for Vector4 { 127 | type Output = Vector4; 128 | 129 | fn div(self, rhs: f32) -> Vector4 { 130 | Vector4::new(self.x / rhs, self.y / rhs, self.z / rhs, self.w / rhs) 131 | } 132 | } 133 | 134 | impl Div for f32 { 135 | type Output = Vector4; 136 | 137 | fn div(self, rhs: Vector4) -> Vector4 { 138 | Vector4::new(self / rhs.x, self / rhs.y, self / rhs.z, self / rhs.w) 139 | } 140 | } 141 | 142 | impl Div for Vector4 { 143 | type Output = Vector4; 144 | 145 | fn div(self, rhs: Vector4) -> Vector4 { 146 | Vector4::new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z, self.w / rhs.w) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/math/vector3.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::{Neg, Add, Sub, Mul, Div, Index}; 3 | use super::traits::{Dot, Cross, Zero}; 4 | use super::vector::Vector4; 5 | 6 | #[derive(Debug, Clone, Copy)] 7 | pub struct Vector3 { 8 | pub x: f32, 9 | pub y: f32, 10 | pub z: f32, 11 | } 12 | 13 | impl Vector3 { 14 | pub fn new(x: f32, y: f32, z: f32) -> Vector3 { 15 | Vector3 { x: x, y: y, z: z } 16 | } 17 | } 18 | 19 | impl Zero for Vector3 { 20 | fn zero() -> Vector3 { 21 | Vector3::new(0.0, 0.0, 0.0) 22 | } 23 | } 24 | 25 | impl<'a> From<&'a [f32]> for Vector3 { 26 | fn from(v: &[f32]) -> Vector3 { 27 | if v.len() > 4 { panic!("Slice must have length more than 3.") } 28 | Vector3 { x: v[0], y: v[1], z: v[2] } 29 | } 30 | } 31 | 32 | impl From> for Vector3 { 33 | fn from(v: Vec) -> Vector3 { 34 | if v.len() > 4 { panic!("Vec must have length more than 3.") } 35 | Vector3 { x: v[0], y: v[1], z: v[2] } 36 | } 37 | } 38 | 39 | impl From<(f32, f32, f32)> for Vector3 { 40 | fn from(v: (f32, f32, f32)) -> Vector3 { 41 | Vector3 { x: v.0, y: v.1, z: v.2 } 42 | } 43 | } 44 | 45 | impl Into<[f32; 3]> for Vector3 { 46 | fn into(self) -> [f32; 3] { 47 | [self.x, self.y, self.z] 48 | } 49 | } 50 | 51 | impl From for Vector3 { 52 | fn from(v: Vector4) -> Vector3 { 53 | Vector3 { x: v.x, y: v.y, z: v.z } 54 | } 55 | } 56 | 57 | impl fmt::Display for Vector3 { 58 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 59 | write!(f, "({}, {}, {})", self.x, self.y, self.z) 60 | } 61 | } 62 | 63 | impl Index for Vector3 { 64 | type Output = f32; 65 | 66 | fn index(&self, i: usize) -> &f32 { 67 | match i { 68 | 0 => &self.x, 69 | 1 => &self.y, 70 | 2 => &self.z, 71 | _ => panic!("Vector3 index out of bounds."), 72 | } 73 | } 74 | } 75 | 76 | impl Dot for Vector3 { 77 | fn dot(self, rhs: Vector3) -> f32 { 78 | self.x * rhs.x + self.y * rhs.y + self.z * rhs.z 79 | } 80 | } 81 | 82 | impl Cross for Vector3 { 83 | fn cross(self, rhs: Vector3) -> Vector3 { 84 | Vector3::new( 85 | self.y * rhs.z - self.z * rhs.y, 86 | self.z * rhs.x - self.x * rhs.z, 87 | self.x * rhs.y - self.y * rhs.x, 88 | ) 89 | } 90 | } 91 | 92 | impl Neg for Vector3 { 93 | type Output = Vector3; 94 | 95 | fn neg(self) -> Vector3 { 96 | Vector3::new(-self.x, -self.y, -self.z) 97 | } 98 | } 99 | 100 | impl Add for Vector3 { 101 | type Output = Vector3; 102 | 103 | fn add(self, rhs: Vector3) -> Vector3 { 104 | Vector3::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) 105 | } 106 | } 107 | 108 | impl Sub for Vector3 { 109 | type Output = Vector3; 110 | 111 | fn sub(self, rhs: Vector3) -> Vector3 { 112 | Vector3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) 113 | } 114 | } 115 | 116 | impl Mul for Vector3 { 117 | type Output = Vector3; 118 | 119 | fn mul(self, rhs: f32) -> Vector3 { 120 | Vector3::new(self.x * rhs, self.y * rhs, self.z * rhs) 121 | } 122 | } 123 | 124 | impl Mul for f32 { 125 | type Output = Vector3; 126 | 127 | fn mul(self, rhs: Vector3) -> Vector3 { 128 | Vector3::new(self * rhs.x, self * rhs.y, self * rhs.z) 129 | } 130 | } 131 | 132 | impl Mul for Vector3 { 133 | type Output = Vector3; 134 | 135 | fn mul(self, rhs: Vector3) -> Vector3 { 136 | Vector3::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z) 137 | } 138 | } 139 | 140 | impl Div for Vector3 { 141 | type Output = Vector3; 142 | 143 | fn div(self, rhs: f32) -> Vector3 { 144 | Vector3::new(self.x / rhs, self.y / rhs, self.z / rhs) 145 | } 146 | } 147 | 148 | impl Div for f32 { 149 | type Output = Vector3; 150 | 151 | fn div(self, rhs: Vector3) -> Vector3 { 152 | Vector3::new(self / rhs.x, self / rhs.y, self / rhs.z) 153 | } 154 | } 155 | 156 | impl Div for Vector3 { 157 | type Output = Vector3; 158 | 159 | fn div(self, rhs: Vector3) -> Vector3 { 160 | Vector3::new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/bvh.rs: -------------------------------------------------------------------------------- 1 | extern crate ordered_float; 2 | 3 | use aabb::AABB; 4 | use shape::*; 5 | use ray::Ray; 6 | use intersection::Intersection; 7 | use self::ordered_float::OrderedFloat; 8 | 9 | #[derive(Clone)] 10 | struct Leaf { 11 | aabb: AABB, 12 | index: usize, 13 | } 14 | 15 | trait Branch { 16 | fn may_intersect(&self, &Ray, &mut Vec); 17 | fn aabb(&self) -> &AABB; 18 | } 19 | 20 | impl Branch for Leaf { 21 | fn may_intersect(&self, ray: &Ray, candidate: &mut Vec) { 22 | if self.aabb.is_intersect(ray) { 23 | candidate.push(self.index) 24 | } 25 | } 26 | 27 | fn aabb(&self) -> &AABB { 28 | &self.aabb 29 | } 30 | } 31 | 32 | struct Node { 33 | aabb: AABB, 34 | left: Box, 35 | right: Box, 36 | } 37 | 38 | impl Branch for Node { 39 | fn may_intersect(&self, ray: &Ray, mut candidate: &mut Vec) { 40 | if self.aabb.is_intersect(ray) { 41 | self.left.may_intersect(ray, &mut candidate); 42 | self.right.may_intersect(ray, &mut candidate); 43 | } 44 | } 45 | 46 | fn aabb(&self) -> &AABB { 47 | &self.aabb 48 | } 49 | } 50 | 51 | pub struct BVH<'a> { 52 | list: &'a Vec>, 53 | root: Box, 54 | } 55 | 56 | impl<'a> BVH<'a> { 57 | pub fn new(list: &Vec>) -> BVH { 58 | let mut leaf = list.iter().enumerate().map( |(i, v)| Leaf { 59 | aabb: v.aabb().clone(), 60 | index: i, 61 | }).collect::>(); 62 | let root = Self::construct(&mut leaf); 63 | BVH { 64 | list: list, 65 | root: root, 66 | } 67 | } 68 | 69 | fn construct(list: &mut [Leaf]) -> Box { 70 | // TODO 71 | let t_aabb = 1.0; 72 | let t_tri = 2.0; 73 | // セットアップ 74 | let n = list.len(); 75 | // 要素が1つのときは葉 76 | if n == 1 { 77 | return box list[0].clone(); 78 | } 79 | // 全体のAABB 80 | let mut aabb = AABB::empty(); 81 | // SAHに基づいた最良の分割軸とインデックスを取得 82 | let (partition_axis, partition_index, _) = (0..3).map( |axis| { 83 | // 基準の軸でソート 84 | list.sort_unstable_by_key( |v| { 85 | OrderedFloat(v.aabb.center[axis]) 86 | }); 87 | // S1のAABBの表面積 88 | let mut s1_aabb = list[0].aabb.clone(); 89 | let mut s1_a = Vec::with_capacity(n - 1); 90 | for leaf in list[0..n].iter() { 91 | s1_aabb = s1_aabb.merge_with(&leaf.aabb); 92 | s1_a.push(s1_aabb.surface_area()); 93 | } 94 | // S2のAABBの表面積 95 | let mut s2_aabb = list[n - 1].aabb.clone(); 96 | let mut s2_a = Vec::with_capacity(n - 1); 97 | for leaf in list[1..].iter().rev() { 98 | s2_aabb = s2_aabb.merge_with(&leaf.aabb); 99 | s2_a.push(s2_aabb.surface_area()); 100 | } 101 | // 全体SのAABBの表面積 102 | aabb = s1_aabb.merge_with(&list[n - 1].aabb); 103 | let s_a = aabb.surface_area(); 104 | // SAHのスコアを評価 105 | (0..n - 1).map( |i| { 106 | // ポリゴン数 107 | let s1_n = (i + 1) as f32; 108 | let s2_n = (n - i - 1) as f32; 109 | // Surface Area Heuristics 110 | // T = 2 * T_aabb + (A(S1) * N(S1) + A(S2) * N(S2)) * T_tri / A(S) 111 | OrderedFloat(2.0 * t_aabb + (s1_a[i] * s1_n + s2_a[n - i - 2] * s2_n) * t_tri / s_a) 112 | }).enumerate().min_by_key( |&(_, t)| t ).unwrap() 113 | }).enumerate().min_by_key( |&(_, (_, t))| t ).map( |(a, (i, t))| (a, i + 1, t) ).unwrap(); 114 | // 基準の軸でソート 115 | list.sort_unstable_by_key( |v| { 116 | OrderedFloat(v.aabb.center[partition_axis]) 117 | }); 118 | // 再帰的に子要素を生成 119 | debug_assert!(partition_index != 0 && partition_index != n); 120 | let left = Self::construct(&mut list[0..partition_index]); 121 | let right = Self::construct(&mut list[partition_index..]); 122 | box Node { 123 | aabb: aabb, 124 | left: left, 125 | right: right, 126 | } 127 | } 128 | } 129 | 130 | impl<'a> Shape for BVH<'a> { 131 | fn intersect(&self, ray: &Ray) -> Option { 132 | let mut candidate = Vec::new(); 133 | self.root.may_intersect(ray, &mut candidate); 134 | candidate.iter().flat_map( |&i| { 135 | self.list[i].intersect(&ray) 136 | }).min_by( 137 | |a, b| { 138 | a.distance.partial_cmp(&b.distance).unwrap() 139 | }, 140 | ) 141 | } 142 | 143 | fn aabb(&self) -> &AABB { 144 | self.root.aabb() 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/math/matrix4.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Neg, Add, Sub, Mul}; 2 | use super::vector::*; 3 | 4 | pub struct Matrix4 { 5 | v: Vec, 6 | } 7 | 8 | impl Matrix4 { 9 | pub fn unit() -> Matrix4 { 10 | Matrix4 { 11 | v: vec![ 12 | 1.0, 0.0, 0.0, 0.0, 13 | 0.0, 1.0, 0.0, 0.0, 14 | 0.0, 0.0, 1.0, 0.0, 15 | 0.0, 0.0, 0.0, 1.0, 16 | ] 17 | } 18 | } 19 | 20 | pub fn translate(v: Vector3) -> Matrix4 { 21 | Matrix4 { 22 | v: vec![ 23 | 1.0, 0.0, 0.0, v.x, 24 | 0.0, 1.0, 0.0, v.y, 25 | 0.0, 0.0, 1.0, v.z, 26 | 0.0, 0.0, 0.0, 1.0, 27 | ] 28 | } 29 | } 30 | 31 | pub fn scale(v: Vector3) -> Matrix4 { 32 | Matrix4 { 33 | v: vec![ 34 | v.x, 0.0, 0.0, 0.0, 35 | 0.0, v.y, 0.0, 0.0, 36 | 0.0, 0.0, v.z, 0.0, 37 | 0.0, 0.0, 0.0, 1.0, 38 | ] 39 | } 40 | } 41 | 42 | pub fn axis_angle(a: Vector3, t: f32) -> Matrix4 { 43 | // ロドリゲスの回転公式 (Rodrigues' rotation formula) 44 | let c = t.cos(); 45 | let s = t.sin(); 46 | Matrix4 { 47 | v: vec![ 48 | c + a.x * a.x * (1.0 - c), a.x * a.y * (1.0 - c) - a.z * s, a.x * a.z * (1.0 - c) + a.y * s, 0.0, 49 | a.y * a.x * (1.0 - c) + a.z * s, c + a.y * a.y * (1.0 - c), a.y * a.z * (1.0 - c) - a.x * s, 0.0, 50 | a.z * a.x * (1.0 - c) - a.y * s, a.z * a.y * (1.0 - c) + a.x * s, c + a.z * a.z * (1.0 - c), 0.0, 51 | 0.0, 0.0, 0.0, 1.0, 52 | ] 53 | } 54 | } 55 | 56 | pub fn look_at(origin: Vector3, target: Vector3, up: Vector3) -> Matrix4 { 57 | let za = (origin - target).normalize(); 58 | let xa = up.cross(za).normalize(); 59 | let ya = za.cross(xa); 60 | Matrix4 { 61 | v: vec![ 62 | xa.x, xa.y, xa.z, 0.0, 63 | ya.x, ya.y, ya.z, 0.0, 64 | za.x, za.y, za.z, 0.0, 65 | origin.x, origin.y, origin.z, 1.0, 66 | ] 67 | } 68 | } 69 | 70 | pub fn col(&self, x: usize) -> Vector4 { 71 | (0..4).map( |i| self.v[x + i * 4]).collect::>().into() 72 | } 73 | 74 | pub fn row(&self, y: usize) -> Vector4 { 75 | (0..4).map( |i| self.v[4 * y + i]).collect::>().into() 76 | } 77 | } 78 | 79 | impl Neg for Matrix4 { 80 | type Output = Matrix4; 81 | 82 | fn neg(self) -> Matrix4 { 83 | Matrix4 { 84 | v: self.v.iter().map( |v| -v ).collect() 85 | } 86 | } 87 | } 88 | 89 | impl<'a> Neg for &'a Matrix4 { 90 | type Output = Matrix4; 91 | 92 | fn neg(self) -> Matrix4 { 93 | Matrix4 { 94 | v: self.v.iter().map( |v| -v ).collect() 95 | } 96 | } 97 | } 98 | 99 | impl Add for Matrix4 { 100 | type Output = Matrix4; 101 | 102 | fn add(self, rhs: Matrix4) -> Matrix4 { 103 | Matrix4 { 104 | v: self.v.iter().zip(rhs.v).map( |(v1, v2)| v1 + v2 ).collect() 105 | } 106 | } 107 | } 108 | 109 | impl<'a> Add for &'a Matrix4 { 110 | type Output = Matrix4; 111 | 112 | fn add(self, rhs: &'a Matrix4) -> Matrix4 { 113 | Matrix4 { 114 | v: self.v.iter().zip(&rhs.v).map( |(v1, v2)| v1 + v2 ).collect() 115 | } 116 | } 117 | } 118 | 119 | impl Sub for Matrix4 { 120 | type Output = Matrix4; 121 | 122 | fn sub(self, rhs: Matrix4) -> Matrix4 { 123 | Matrix4 { 124 | v: self.v.iter().zip(rhs.v).map( |(v1, v2)| v1 - v2 ).collect() 125 | } 126 | } 127 | } 128 | 129 | impl<'a> Sub for &'a Matrix4 { 130 | type Output = Matrix4; 131 | 132 | fn sub(self, rhs: &'a Matrix4) -> Matrix4 { 133 | Matrix4 { 134 | v: self.v.iter().zip(&rhs.v).map( |(v1, v2)| v1 - v2 ).collect() 135 | } 136 | } 137 | } 138 | 139 | impl Mul for Matrix4 { 140 | type Output = Matrix4; 141 | 142 | fn mul(self, rhs: f32) -> Matrix4 { 143 | Matrix4 { 144 | v: self.v.iter().map( |v| v * rhs ).collect() 145 | } 146 | } 147 | } 148 | 149 | impl<'a> Mul for &'a Matrix4 { 150 | type Output = Matrix4; 151 | 152 | fn mul(self, rhs: f32) -> Matrix4 { 153 | Matrix4 { 154 | v: self.v.iter().map( |v| v * rhs ).collect() 155 | } 156 | } 157 | } 158 | 159 | impl Mul for f32 { 160 | type Output = Matrix4; 161 | 162 | fn mul(self, rhs: Matrix4) -> Matrix4 { 163 | Matrix4 { 164 | v: rhs.v.iter().map( |v| self * v ).collect() 165 | } 166 | } 167 | } 168 | 169 | impl Mul for Matrix4 { 170 | type Output = Vector4; 171 | 172 | fn mul(self, rhs: Vector4) -> Vector4 { 173 | (0..4).map( |i| self.row(i).dot(rhs) ).collect::>().into() 174 | } 175 | } 176 | 177 | impl<'a> Mul for &'a Matrix4 { 178 | type Output = Vector4; 179 | 180 | fn mul(self, rhs: Vector4) -> Vector4 { 181 | (0..4).map( |i| self.row(i).dot(rhs) ).collect::>().into() 182 | } 183 | } 184 | 185 | impl Mul for Matrix4 { 186 | type Output = Vector3; 187 | 188 | fn mul(self, rhs: Vector3) -> Vector3 { 189 | (0..4).map( |i| self.row(i).dot(rhs.into()) ).collect::>().into() 190 | } 191 | } 192 | 193 | impl<'a> Mul for &'a Matrix4 { 194 | type Output = Vector3; 195 | 196 | fn mul(self, rhs: Vector3) -> Vector3 { 197 | (0..4).map( |i| self.row(i).dot(rhs.into()) ).collect::>().into() 198 | } 199 | } 200 | 201 | impl Mul for Matrix4 { 202 | type Output = Matrix4; 203 | 204 | fn mul(self, rhs: Matrix4) -> Matrix4 { 205 | let r = (0..4).map( |i| self.row(i) ).collect::>(); 206 | let c = (0..4).map( |i| rhs.col(i) ).collect::>(); 207 | Matrix4 { 208 | v: (0..4).flat_map( |y| (0..4).map( |x| r[y].dot(c[x]) ).collect::>() ).collect() 209 | } 210 | } 211 | } 212 | 213 | impl<'a> Mul for &'a Matrix4 { 214 | type Output = Matrix4; 215 | 216 | fn mul(self, rhs: &'a Matrix4) -> Matrix4 { 217 | let r = (0..4).map( |i| self.row(i) ).collect::>(); 218 | let c = (0..4).map( |i| rhs.col(i) ).collect::>(); 219 | Matrix4 { 220 | v: (0..4).flat_map( |y| (0..4).map( |x| r[y].dot(c[x]) ).collect::>() ).collect() 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(box_syntax)] 2 | #![feature(test)] 3 | #![allow(dead_code)] 4 | 5 | #[macro_use] 6 | extern crate serde_derive; 7 | extern crate toml; 8 | 9 | extern crate time; 10 | extern crate scoped_threadpool; 11 | extern crate num_cpus; 12 | 13 | mod constant; 14 | mod img; 15 | mod math; 16 | mod ray; 17 | mod sample; 18 | mod camera; 19 | mod intersection; 20 | mod material; 21 | mod scene; 22 | mod sphere; 23 | mod triangle; 24 | mod objects; 25 | mod sky; 26 | mod description; 27 | mod util; 28 | mod shape; 29 | mod aabb; 30 | mod bvh; 31 | mod scene_loader; 32 | 33 | use scoped_threadpool::Pool; 34 | use std::sync::mpsc::{channel, Sender, Receiver}; 35 | use img::*; 36 | use math::vector::*; 37 | use std::path::Path; 38 | use std::sync::{Arc, Mutex}; 39 | use std::io::{self, Write}; 40 | use description::Description; 41 | use std::env; 42 | 43 | fn main() { 44 | let start_time = time::now(); 45 | println!("start: {}", start_time.strftime("%+").unwrap()); 46 | let args: Vec = env::args().collect(); 47 | if args.len() <= 1 { 48 | panic!("Path for .toml must be specified.") 49 | } 50 | println!("loading: {}", args[1]); 51 | let description = Description::new(&args[1]); 52 | let width = description.config.film.resolution.0; 53 | let height = description.config.film.resolution.1; 54 | let mut output = Img::new(Vector3::zero(), width, height); 55 | let cam = Arc::new(description.camera()); 56 | // println!("{:?}", cam.info()); 57 | let spp = description.config.renderer.samples; 58 | println!("resolution: {}x{}", width, height); 59 | println!("spp: {}", spp); 60 | let (tx, rx): (Sender<(usize, usize, Vector3)>, Receiver<(usize, usize, Vector3)>) = channel(); 61 | let num_cpus = num_cpus::get(); 62 | let num_threads_config = description.config.renderer.threads.unwrap_or(0); 63 | let num_threads = if num_threads_config <= 0 { num_cpus } else { num_threads_config }; 64 | println!("threads: {}", num_threads); 65 | let mut pool = Pool::new(num_threads as u32); 66 | let integrator = description.config.renderer.integrator.as_ref().map( |v| v.as_str() ).unwrap_or("pt-direct"); 67 | println!("integrator: {}", integrator); 68 | let all = height * width; 69 | // let progress = Arc::new(Mutex::new(0)); 70 | pool.scoped( |scope| { 71 | let scene = Arc::new(description.scene()); 72 | // モンテカルロ積分 73 | output.each_pixel( |x, y, _| { 74 | let tx = tx.clone(); 75 | // let progress = progress.clone(); 76 | let cam = cam.clone(); 77 | let scene = scene.clone(); 78 | match integrator { 79 | "pt" => { 80 | scope.execute(move || { 81 | // let mut stdout = io::stdout(); 82 | // let mut progress = progress.lock().unwrap(); 83 | // *progress += 1; 84 | // let _ = write!( 85 | // &mut stdout.lock(), 86 | // "\rprocessing... ({}/{} : {:.0}%) ", 87 | // *progress, 88 | // all, 89 | // *progress as f32 / all as f32 * 100.0 90 | // ); 91 | // stdout.flush().ok(); 92 | let estimated_sum = (0..spp).fold(Vector3::zero(), |sum, _| { 93 | // センサーの1画素に入射する放射輝度を立体角測度でモンテカルロ積分し放射照度を得る 94 | // カメラから出射されるレイをサンプリング 95 | let (ray, g_term) = cam.sample(x, y); 96 | // 開口部に入射する放射輝度 (W sr^-1 m^-2) 97 | let l_into_sensor = scene.radiance(&ray.value); 98 | // センサーに入射する放射照度 99 | let e_into_sensor = l_into_sensor * g_term; 100 | // 今回のサンプリングでの放射照度の推定値 101 | let delta_e_into_sensor = e_into_sensor * (cam.sensor_sensitivity() / ray.pdf); 102 | sum + delta_e_into_sensor 103 | }); 104 | tx.send((x, y, estimated_sum / spp as f32)).unwrap() 105 | }); 106 | }, 107 | "pt-direct" => { 108 | scope.execute(move || { 109 | let estimated_sum = (0..spp).fold(Vector3::zero(), |sum, _| { 110 | // センサーの1画素に入射する放射輝度を立体角測度でモンテカルロ積分し放射照度を得る 111 | // カメラから出射されるレイをサンプリング 112 | let (ray, g_term) = cam.sample(x, y); 113 | // 開口部に入射する放射輝度 (W sr^-1 m^-2) 114 | let l_into_sensor = scene.radiance_nee(&ray.value); 115 | // センサーに入射する放射照度 116 | let e_into_sensor = l_into_sensor * g_term; 117 | // 今回のサンプリングでの放射照度の推定値 118 | let delta_e_into_sensor = e_into_sensor * (cam.sensor_sensitivity() / ray.pdf); 119 | sum + delta_e_into_sensor 120 | }); 121 | tx.send((x, y, estimated_sum / spp as f32)).unwrap() 122 | }); 123 | }, 124 | _ => panic!(format!("Unknown integrator type `{}`", integrator)), 125 | } 126 | }); 127 | }); 128 | 129 | for _i in 0..all { 130 | let (x, y, pixel) = rx.recv().unwrap(); 131 | output.set(x, y, pixel); 132 | } 133 | 134 | println!(""); 135 | println!("saving..."); 136 | let gamma = description.config.film.gamma.unwrap_or(2.2); 137 | save(&output, &description.config.film.output, gamma, spp); 138 | 139 | let end_time = time::now(); 140 | println!("end: {}", end_time.strftime("%+").unwrap()); 141 | println!( 142 | "elapse: {}s", 143 | (end_time - start_time).num_milliseconds() as f32 / 1000.0 144 | ); 145 | } 146 | 147 | fn save(output: &Img, format: &str, gamma: f32, spp: usize) { 148 | let file_path = &format!( 149 | "images/image_{}_{}.{}", 150 | time::now().strftime("%Y%m%d%H%M%S").unwrap(), 151 | spp, 152 | format, 153 | ); 154 | match format { 155 | "hdr" => { 156 | output.save_hdr(&Path::new(file_path), |pixel| { 157 | [pixel.x, pixel.y, pixel.z] 158 | }); 159 | }, 160 | "png" => { 161 | output.save_png(&Path::new(file_path), |pixel| { 162 | [to_color(pixel.x, gamma), to_color(pixel.y, gamma), to_color(pixel.z, gamma)] 163 | }); 164 | }, 165 | _ => { 166 | panic!(format!("Unsupported output type `{}`", format)); 167 | } 168 | } 169 | } 170 | 171 | fn to_color(x: f32, gamma: f32) -> u8 { 172 | (x.max(0.0).min(1.0).powf(1.0 / gamma) * 255.0) as u8 173 | } 174 | -------------------------------------------------------------------------------- /src/triangle.rs: -------------------------------------------------------------------------------- 1 | extern crate test; 2 | extern crate rand; 3 | 4 | use std::sync::Arc; 5 | use intersection::Intersection; 6 | use shape::*; 7 | use constant::*; 8 | use ray::Ray; 9 | use material::material::Material; 10 | use math::vector::*; 11 | use aabb::AABB; 12 | use sample::Sample; 13 | 14 | pub struct Triangle { 15 | pub p0: Vector3, 16 | pub p1: Vector3, 17 | pub p2: Vector3, 18 | aabb: AABB, 19 | pub normal: Vector3, 20 | pub area: f32, 21 | pub material: Arc, 22 | } 23 | 24 | impl Triangle { 25 | pub fn new( 26 | p0: Vector3, 27 | p1: Vector3, 28 | p2: Vector3, 29 | material: Arc, 30 | ) -> Triangle { 31 | Triangle { 32 | p0: p0, 33 | p1: p1, 34 | p2: p2, 35 | aabb: Self::aabb(p0, p1, p2), 36 | normal: (p1 - p0).cross(p2 - p0).normalize(), 37 | area: (p1 - p0).cross(p2 - p0).norm() * 0.5, 38 | material: material, 39 | } 40 | } 41 | 42 | fn intersect_3c(&self, ray: &Ray) -> Option { 43 | let dn = ray.direction.dot(self.normal); 44 | let t = (self.p0 - ray.origin).dot(self.normal) / dn; 45 | if t < EPS { 46 | return None; 47 | } 48 | let p = ray.origin + ray.direction * t; 49 | let c0 = (self.p1 - self.p0).cross(p - self.p0); 50 | if c0.dot(self.normal) < 0.0 { 51 | return None; 52 | } 53 | let c1 = (self.p2 - self.p1).cross(p - self.p1); 54 | if c1.dot(self.normal) < 0.0 { 55 | return None; 56 | } 57 | let c2 = (self.p0 - self.p2).cross(p - self.p2); 58 | if c2.dot(self.normal) < 0.0 { 59 | return None; 60 | } 61 | Some(Intersection { 62 | distance: t, 63 | normal: self.normal, 64 | position: p, 65 | material: self.material.clone(), 66 | }) 67 | } 68 | 69 | fn intersect_mt(&self, ray: &Ray) -> Option { 70 | // Möller–Trumbore intersection algorithm 71 | let e1 = self.p1 - self.p0; 72 | let e2 = self.p2 - self.p0; 73 | let pv = ray.direction.cross(e2); 74 | let det = e1.dot(pv); // クラメルの分母 75 | if det.abs() < EPS { 76 | return None; 77 | } 78 | let invdet = 1.0 / det; 79 | let tv = ray.origin - self.p0; 80 | let u = tv.dot(pv) * invdet; 81 | if u < 0.0 || u > 1.0 { 82 | return None; 83 | } 84 | let qv = tv.cross(e1); 85 | let v = ray.direction.dot(qv) * invdet; 86 | if v < 0.0 || u + v > 1.0 { 87 | return None; 88 | } 89 | let t = e2.dot(qv) * invdet; 90 | if t < EPS { 91 | return None; 92 | } 93 | let p = ray.origin + ray.direction * t; 94 | Some(Intersection { 95 | distance: t, 96 | normal: self.normal, 97 | position: p, 98 | material: self.material.clone(), 99 | }) 100 | } 101 | 102 | fn aabb(p0: Vector3, p1: Vector3, p2: Vector3) -> AABB { 103 | let min = Vector3::new( 104 | p0.x.min(p1.x).min(p2.x), 105 | p0.y.min(p1.y).min(p2.y), 106 | p0.z.min(p1.z).min(p2.z), 107 | ); 108 | let max = Vector3::new( 109 | p0.x.max(p1.x).max(p2.x), 110 | p0.y.max(p1.y).max(p2.y), 111 | p0.z.max(p1.z).max(p2.z), 112 | ); 113 | AABB { 114 | min: min, 115 | max: max, 116 | center: (max + min) / 2.0, 117 | } 118 | } 119 | } 120 | 121 | impl Shape for Triangle { 122 | fn intersect(&self, ray: &Ray) -> Option { 123 | self.intersect_mt(&ray) 124 | } 125 | 126 | fn aabb(&self) -> &AABB { 127 | &self.aabb 128 | } 129 | } 130 | 131 | impl SurfaceShape for Triangle { 132 | fn material(&self) -> Arc { 133 | self.material.clone() 134 | } 135 | 136 | fn area(&self) -> f32 { 137 | self.area 138 | } 139 | 140 | fn sample(&self) -> Sample { 141 | let u = rand::random::(); 142 | let v = rand::random::(); 143 | let min = u.min(v); 144 | let max = u.max(v); 145 | Sample { 146 | value: self.p0 * min + self.p1 * (1.0 - max) + self.p2 * (max - min), 147 | pdf: 1.0 / self.area, 148 | } 149 | } 150 | } 151 | 152 | #[cfg(test)] 153 | mod tests { 154 | use super::*; 155 | use material::material::*; 156 | 157 | #[test] 158 | fn intersect_mt_front() { 159 | let t = Triangle::new( 160 | Vector3::new(5.0, 0.0, 0.0), 161 | Vector3::new(0.0, 0.0, 0.0), 162 | Vector3::new(0.0, 0.0, 5.0), 163 | Arc::new(LambertianMaterial { emission: Vector3::zero(), albedo: Vector3::zero() }), 164 | ); 165 | let ray = Ray { 166 | origin: Vector3::new(1.0, 5.0, 1.0), 167 | direction: Vector3::new(0.0, -1.0, 0.0).normalize(), 168 | }; 169 | let i1 = t.intersect_3c(&ray).unwrap(); 170 | let i2 = t.intersect_mt(&ray).unwrap(); 171 | assert!((i1.normal - i2.normal).norm() < 1e-3); 172 | assert!((i1.position - i2.position).norm() < 1e-3); 173 | assert!((i1.distance - i2.distance).abs() < 1e-3); 174 | } 175 | 176 | #[test] 177 | fn intersect_mt_back() { 178 | let t = Triangle::new( 179 | Vector3::new(5.0, 0.0, 0.0), 180 | Vector3::new(0.0, 0.0, 0.0), 181 | Vector3::new(0.0, 0.0, 5.0), 182 | Arc::new(LambertianMaterial { emission: Vector3::zero(), albedo: Vector3::zero() }), 183 | ); 184 | let ray = Ray { 185 | origin: Vector3::new(1.0, -5.0, 1.0), 186 | direction: Vector3::new(0.0, 1.0, 0.0).normalize(), 187 | }; 188 | let i1 = t.intersect_3c(&ray).unwrap(); 189 | let i2 = t.intersect_mt(&ray).unwrap(); 190 | assert!((i1.normal - i2.normal).norm() < 1e-3); 191 | assert!((i1.position - i2.position).norm() < 1e-3); 192 | assert!((i1.distance - i2.distance).abs() < 1e-3); 193 | } 194 | 195 | #[test] 196 | fn intersect_3c_near() { 197 | let t = Triangle::new( 198 | Vector3::new(5.0, 0.0, 0.0), 199 | Vector3::new(0.0, 0.0, 0.0), 200 | Vector3::new(0.0, 0.0, 5.0), 201 | Arc::new(LambertianMaterial { emission: Vector3::zero(), albedo: Vector3::zero() }), 202 | ); 203 | let ray = Ray { 204 | origin: Vector3::new(1.0, 5.0, 1.0), 205 | direction: Vector3::new(0.0, -1.0, 0.0).normalize(), 206 | }; 207 | let i1 = t.intersect_3c(&ray).unwrap(); 208 | let near_ray = Ray { 209 | origin: i1.position, 210 | direction: Vector3::new(0.0, 1.0, 0.0), 211 | }; 212 | let i2 = t.intersect_3c(&near_ray); 213 | assert!(i2.is_none()); 214 | } 215 | 216 | #[test] 217 | fn intersect_mt_near() { 218 | let t = Triangle::new( 219 | Vector3::new(5.0, 0.0, 0.0), 220 | Vector3::new(0.0, 0.0, 0.0), 221 | Vector3::new(0.0, 0.0, 5.0), 222 | Arc::new(LambertianMaterial { emission: Vector3::zero(), albedo: Vector3::zero() }), 223 | ); 224 | let ray = Ray { 225 | origin: Vector3::new(1.0, 5.0, 1.0), 226 | direction: Vector3::new(0.0, -1.0, 0.0).normalize(), 227 | }; 228 | let i1 = t.intersect_mt(&ray).unwrap(); 229 | let near_ray = Ray { 230 | origin: i1.position, 231 | direction: Vector3::new(0.0, 1.0, 0.0), 232 | }; 233 | let i2 = t.intersect_mt(&near_ray); 234 | assert!(i2.is_none()); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/scene.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use math::vector::*; 4 | use sky::Sky; 5 | use ray::Ray; 6 | use objects::Objects; 7 | use shape::Shape; 8 | use intersection::Intersection; 9 | use constant::*; 10 | 11 | pub struct Scene<'a> { 12 | pub objects: Objects<'a>, 13 | pub depth: usize, 14 | pub depth_limit: usize, 15 | pub sky: Box, 16 | pub no_direct_emitter: bool, 17 | } 18 | 19 | impl<'a> Scene<'a> { 20 | pub fn radiance(&self, ray: &Ray) -> Vector3 { 21 | self.radiance_recursive(ray, 0) 22 | } 23 | 24 | fn radiance_recursive(&self, ray: &Ray, depth: usize) -> Vector3 { 25 | // すべてのオブジェクトと当たり判定を行う 26 | let maybe_intersect = self.objects.intersect(&ray); 27 | // 当たらなかった場合は背景色を返す 28 | match maybe_intersect { 29 | None => self.sky.radiance(&ray), 30 | Some(i) => self.intersect_radiance(&i, &ray, depth), 31 | } 32 | } 33 | 34 | pub fn radiance_nee(&self, ray: &Ray) -> Vector3 { 35 | self.radiance_nee_recursive(ray, 0, false) 36 | } 37 | 38 | fn radiance_nee_recursive(&self, ray: &Ray, depth: usize, no_emission: bool) -> Vector3 { 39 | // すべてのオブジェクトと当たり判定を行う 40 | let maybe_intersect = self.objects.intersect(&ray); 41 | // 当たらなかった場合は背景色を返す 42 | match maybe_intersect { 43 | None => self.sky.radiance(&ray), 44 | Some(i) => self.intersect_radiance_nee(&i, &ray, depth, no_emission), 45 | } 46 | } 47 | 48 | pub fn normal(&self, ray: &Ray) -> Vector3 { 49 | let maybe_intersect = self.objects.intersect(&ray); 50 | match maybe_intersect { 51 | None => Vector3::zero(), 52 | Some(i) => i.normal / 2.0 + Vector3::new(0.5, 0.5, 0.5), 53 | } 54 | } 55 | 56 | pub fn depth(&self, ray: &Ray) -> f32 { 57 | let maybe_intersect = self.objects.intersect(&ray); 58 | match maybe_intersect { 59 | None => 0.0, 60 | Some(i) => i.distance, 61 | } 62 | } 63 | 64 | fn russian_roulette(&self, init: f32, depth: usize) -> f32 { 65 | // 再帰抑制用のロシアンルーレットの確率を決定する 66 | let mut continue_rr_prob = init; 67 | // スタックオーバーフロー対策のために反射回数の限界値を超えたら極端に確率を下げる 68 | if depth > self.depth_limit { 69 | continue_rr_prob *= (0.5f32).powi((depth - self.depth_limit) as i32); 70 | } 71 | // 最初の数回の反射では必ず次のパスをトレースするようにする 72 | if depth <= self.depth && continue_rr_prob > 0.0 { 73 | continue_rr_prob = 1.0; 74 | } 75 | continue_rr_prob 76 | } 77 | 78 | fn material_interaction_radiance(&self, i: &Intersection, ray: &Ray, f: F) -> Vector3 79 | where F: Fn(Ray) -> Vector3 80 | { 81 | let out_ = -ray.direction; 82 | // BRDFに応じたサンプリング 83 | let sample = i.material.sample(out_, i.normal); 84 | let in_ = sample.value; 85 | let pdf = sample.pdf; 86 | // BRDF 87 | let brdf = i.material.brdf(out_, in_, i.normal, i.position); 88 | // 係数 89 | let coef = i.material.coef(out_, i.normal, i.distance); 90 | // コサイン項 91 | let cos = in_.dot(i.normal); 92 | // assert!(brdf.x * cos < 1.0 && brdf.x * cos > 0.0, "{} {} {}", brdf.x * cos, brdf.x, cos); 93 | // 放射輝度の圧縮で透過の場合は1を超えてもおかしくない 94 | let new_ray = Ray { 95 | direction: in_, 96 | origin: i.position, 97 | }; 98 | // 再帰的にレイを追跡 99 | let l_i = f(new_ray); 100 | // レンダリング方程式にしたがって放射輝度を計算する 101 | brdf * coef * l_i * cos / pdf 102 | } 103 | 104 | fn direct_light_radiance(&self, i: &Intersection, ray: &Ray) -> Vector3 { 105 | if i.material.emission().sqr_norm() > 0.0 || !self.objects.has_emission() { 106 | // 交差したマテリアルが放射を持っているとき、NEE対象の光源が存在しないとき 107 | return Vector3::zero() 108 | } 109 | // 光源上から1点をサンプリング (確率密度は面積測度) 110 | let direct_sample = self.objects.sample_emission(); 111 | // 交差した座標と光源上の1点のパスを接続 112 | let direct_path = direct_sample.value - i.position; 113 | // 可視関数のテストレイを生成 114 | let direct_ray = Ray { 115 | origin: i.position, 116 | direction: direct_path.normalize(), 117 | }; 118 | // 面に対して可視であるかテスト 119 | let point_in = direct_ray.direction; 120 | let point_out = -ray.direction; 121 | let point_normal = i.material.orienting_normal(point_out, i.normal); 122 | if point_in.dot(point_normal) <= 0.0 { 123 | // レイの入射方向とは逆の方向にレイを接続した場合は遮蔽 124 | return Vector3::zero() 125 | } 126 | // 直接光のみのサンプリングなので可視の場合のみ寄与 127 | match self.objects.intersect(&direct_ray) { 128 | Some(direct_i) => { 129 | if (direct_i.distance - direct_path.norm()).abs() > EPS { 130 | // 接続したパスとテストレイの距離が違う場合は遮蔽 131 | return Vector3::zero() 132 | } 133 | let light_out = -direct_ray.direction; 134 | let light_normal = direct_i.normal; 135 | let light_cos = light_out.dot(light_normal); 136 | if light_cos <= 0.0 { 137 | // 光源の裏面は寄与なし 138 | return Vector3::zero() 139 | } 140 | // ジオメトリターム (測度の変換) 141 | let point_cos = point_in.dot(point_normal); 142 | let g_term = point_cos * light_cos / direct_path.sqr_norm(); 143 | // BRDF 144 | let brdf = i.material.brdf(point_out, point_in, point_normal, i.position); 145 | let l_i = direct_i.material.emission(); 146 | let pdf = direct_sample.pdf; 147 | brdf * l_i * g_term / pdf 148 | }, 149 | None => Vector3::zero(), 150 | } 151 | } 152 | 153 | fn intersect_radiance(&self, i: &Intersection, ray: &Ray, depth: usize) -> Vector3 { 154 | // 放射 155 | let l_e = if !(self.no_direct_emitter && depth == 0) && (-ray.direction).dot(i.normal) > 0.0 { 156 | i.material.emission() 157 | } else { 158 | Vector3::zero() 159 | }; 160 | // ロシアンルーレットで再帰を抑制 161 | let continue_rr_prob = self.russian_roulette(i.material.weight(), depth); 162 | if continue_rr_prob != 1.0 && rand::random::() >= continue_rr_prob { 163 | return l_e; 164 | } 165 | // マテリアルに応じたサンプリングによる寄与 166 | let material_radiance = self.material_interaction_radiance(&i, &ray, |new_ray| { 167 | self.radiance_recursive(&new_ray, depth + 1) 168 | }); 169 | // ロシアンルーレットを用いた評価で期待値を満たすために確率で割る (再帰抑制用) 170 | return l_e + material_radiance / continue_rr_prob; 171 | } 172 | 173 | fn intersect_radiance_nee(&self, i: &Intersection, ray: &Ray, depth: usize, no_emission: bool) -> Vector3 { 174 | // 放射 175 | let l_e = if !(self.no_direct_emitter && depth == 0) && !no_emission && (-ray.direction).dot(i.normal) > 0.0 { 176 | i.material.emission() 177 | } else { 178 | Vector3::zero() 179 | }; 180 | // ロシアンルーレットで再帰を抑制 181 | let continue_rr_prob = self.russian_roulette(i.material.weight(), depth); 182 | if continue_rr_prob != 1.0 && rand::random::() >= continue_rr_prob { 183 | return l_e; 184 | } 185 | // 直接光のサンプリングによる寄与 186 | let direct_light_radiance = self.direct_light_radiance(&i, &ray); 187 | // マテリアルに応じたサンプリングによる寄与 188 | let material_radiance = self.material_interaction_radiance(&i, &ray, |new_ray| { 189 | self.radiance_nee_recursive(&new_ray, depth + 1, true) 190 | }); 191 | // ロシアンルーレットを用いた評価で期待値を満たすために確率で割る (再帰抑制用) 192 | return l_e + (direct_light_radiance + material_radiance) / continue_rr_prob; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/scene_loader.rs: -------------------------------------------------------------------------------- 1 | use math::matrix::*; 2 | use math::vector::*; 3 | use constant::PI; 4 | 5 | type Name = String; 6 | type Vec3 = (f32, f32, f32); 7 | 8 | #[derive(Debug, Deserialize)] 9 | #[serde(rename_all = "kebab-case")] 10 | pub struct Renderer { 11 | pub samples: usize, 12 | pub depth: Option, 13 | pub depth_limit: Option, 14 | pub no_direct_emitter: Option, 15 | pub threads: Option, 16 | pub integrator: Option, 17 | } 18 | 19 | #[derive(Debug, Deserialize)] 20 | #[serde(rename_all = "kebab-case")] 21 | pub struct Film { 22 | pub resolution: (usize, usize), 23 | pub output: String, 24 | pub gamma: Option, 25 | pub sensitivity: Option, 26 | } 27 | 28 | #[derive(Debug, Deserialize)] 29 | #[serde(tag = "type", rename_all = "kebab-case")] 30 | pub enum Sky { 31 | Uniform { 32 | color: Vec3, 33 | }, 34 | #[serde(rename_all = "kebab-case")] 35 | Ibl { 36 | path: String, 37 | #[serde(default)] 38 | longitude_offset: f32 39 | }, 40 | } 41 | 42 | #[derive(Debug, Deserialize)] 43 | #[serde(tag = "type", rename_all = "kebab-case")] 44 | enum Light { 45 | Area { 46 | object: Name, 47 | emission: Vec3, 48 | intensity: Option, 49 | }, 50 | } 51 | 52 | #[derive(Debug, Deserialize)] 53 | #[serde(rename_all = "kebab-case")] 54 | struct Object { 55 | name: Option, 56 | mesh: Name, 57 | material: Option, 58 | #[serde(default)] 59 | transform: Vec, 60 | } 61 | 62 | impl HasTransform for Object { 63 | fn transform(&self) -> &Vec { 64 | &self.transform 65 | } 66 | } 67 | 68 | #[derive(Debug, Deserialize)] 69 | #[serde(tag = "type", rename_all = "kebab-case")] 70 | pub enum Transform { 71 | Translate { 72 | vector: Vec3, 73 | }, 74 | Scale { 75 | vector: Vec3, 76 | }, 77 | AxisAngle { 78 | axis: Vec3, 79 | angle: f32, 80 | }, 81 | LookAt { 82 | origin: Vec3, 83 | target: Vec3, 84 | up: Vec3, 85 | }, 86 | } 87 | 88 | impl Transform { 89 | pub fn matrix(&self) -> Matrix4 { 90 | match *self { 91 | Transform::Translate { vector } => Matrix4::translate(vector.into()), 92 | Transform::Scale { vector } => Matrix4::scale(vector.into()), 93 | Transform::AxisAngle { axis, angle } => Matrix4::axis_angle(axis.into(), angle * PI / 180.0), 94 | Transform::LookAt { origin, target, up } => Matrix4::look_at(origin.into(), target.into(), up.into()), 95 | } 96 | } 97 | } 98 | 99 | pub trait HasTransform { 100 | fn transform(&self) -> &Vec; 101 | fn matrix(&self) -> Matrix4 { 102 | self.transform().iter().map( |t| t.matrix() ).fold(Matrix4::unit(), |p, c| c * p ) 103 | } 104 | } 105 | 106 | #[derive(Debug, Deserialize)] 107 | #[serde(tag = "type", rename_all = "kebab-case")] 108 | pub enum Camera { 109 | IdealPinhole { 110 | fov: f32, 111 | #[serde(default)] 112 | transform: Vec, 113 | }, 114 | #[serde(rename_all = "kebab-case")] 115 | ThinLens { 116 | fov: f32, 117 | focus_distance: f32, 118 | f_number: f32, 119 | #[serde(default)] 120 | transform: Vec, 121 | }, 122 | Omnidirectional { 123 | transform: Vec, 124 | }, 125 | } 126 | 127 | impl HasTransform for Camera { 128 | fn transform(&self) -> &Vec { 129 | match *self { 130 | Camera::IdealPinhole { ref transform, .. } => transform, 131 | Camera::ThinLens { ref transform, .. } => transform, 132 | Camera::Omnidirectional { ref transform, .. } => transform, 133 | } 134 | } 135 | } 136 | 137 | pub trait HasName { 138 | fn name(&self) -> Name; 139 | } 140 | 141 | #[derive(Debug, Deserialize)] 142 | #[serde(tag = "type", rename_all = "kebab-case")] 143 | pub enum Material { 144 | Lambert { 145 | name: Name, 146 | albedo: Vec3, 147 | }, 148 | Phong { 149 | name: Name, 150 | reflectance: Vec3, 151 | alpha: f32, 152 | }, 153 | BlinnPhong { 154 | name: Name, 155 | reflectance: Vec3, 156 | alpha: f32, 157 | }, 158 | Ggx { 159 | name: Name, 160 | reflectance: Vec3, 161 | roughness: f32, 162 | ior: f32, 163 | }, 164 | IdealRefraction { 165 | name: Name, 166 | reflectance: Vec3, 167 | #[serde(default)] 168 | absorbtance: f32, 169 | ior: f32, 170 | } 171 | } 172 | 173 | impl HasName for Material { 174 | fn name(&self) -> Name { 175 | match *self { 176 | Material::Lambert { ref name, .. } => name.clone(), 177 | Material::Phong { ref name, .. } => name.clone(), 178 | Material::BlinnPhong { ref name, ..} => name.clone(), 179 | Material::Ggx { ref name, ..} => name.clone(), 180 | Material::IdealRefraction { ref name, ..} => name.clone(), 181 | } 182 | } 183 | } 184 | 185 | #[derive(Debug, Deserialize)] 186 | #[serde(tag = "type", rename_all = "kebab-case")] 187 | pub enum Mesh { 188 | Obj { 189 | name: Name, 190 | path: String, 191 | }, 192 | Sphere { 193 | name: Name, 194 | radius: f32, 195 | }, 196 | } 197 | 198 | impl HasName for Mesh { 199 | fn name(&self) -> Name { 200 | match *self { 201 | Mesh::Obj { ref name, .. } => name.clone(), 202 | Mesh::Sphere { ref name, .. } => name.clone(), 203 | } 204 | } 205 | } 206 | 207 | #[derive(Debug, Deserialize)] 208 | #[serde(rename_all = "kebab-case")] 209 | pub struct Config { 210 | pub renderer: Renderer, 211 | pub sky: Option, 212 | pub film: Film, 213 | pub camera: Camera, 214 | #[serde(default)] 215 | light: Vec, 216 | #[serde(default)] 217 | object: Vec, 218 | #[serde(default)] 219 | material: Vec, 220 | #[serde(default)] 221 | mesh: Vec, 222 | } 223 | 224 | pub struct ObjectDescriptor<'a> { 225 | pub mesh: &'a Mesh, 226 | pub material: Option<&'a Material>, 227 | pub transform: &'a Vec, 228 | pub emission: Option, 229 | } 230 | 231 | impl<'a> HasTransform for ObjectDescriptor<'a> { 232 | fn transform(&self) -> &Vec { 233 | &self.transform 234 | } 235 | } 236 | 237 | impl Config { 238 | fn find_mesh_by_name(&self, name: &str) -> Result<&Mesh, String> { 239 | let mesh = self.mesh.iter().find( |m| m.name() == name ); 240 | mesh.ok_or(format!("Mesh named `{}` is not found.", name)) 241 | } 242 | 243 | fn find_material_by_name(&self, name: &str) -> Result<&Material, String> { 244 | let material = self.material.iter().find( |m| m.name() == name ); 245 | material.ok_or(format!("Material named `{}` is not found.", name)) 246 | } 247 | 248 | pub fn object(&self) -> Vec { 249 | self.object.iter().map( |v| { 250 | let mesh = self.find_mesh_by_name(&v.mesh).unwrap(); 251 | let material = v.material.as_ref().map( |name| 252 | self.find_material_by_name(name).unwrap() 253 | ); 254 | let emission = self.light.iter().find( |l| match **l { 255 | Light::Area { ref object, .. } => { 256 | v.name.as_ref().map( |name| name.as_str() == object ).unwrap_or(false) 257 | }, 258 | } ).map( |l| match *l { 259 | Light::Area { ref emission, ref intensity, .. } => { 260 | Vector3::from(*emission) * (*intensity).unwrap_or(1.0) 261 | } 262 | }); 263 | ObjectDescriptor { 264 | mesh: &mesh, 265 | material: material, 266 | transform: &v.transform, 267 | emission: emission, 268 | } 269 | }).collect() 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/description.rs: -------------------------------------------------------------------------------- 1 | extern crate tobj; 2 | extern crate time; 3 | extern crate toml; 4 | 5 | use std::sync::Arc; 6 | use camera::*; 7 | use math::vector::*; 8 | use math::matrix::*; 9 | use material::material::*; 10 | use scene::Scene; 11 | use shape::SurfaceShape; 12 | use triangle::Triangle; 13 | use sphere::Sphere; 14 | use objects::Objects; 15 | use std::path::Path; 16 | use sky::*; 17 | use std::fs::File; 18 | use std::collections::HashMap; 19 | use std::io::prelude::*; 20 | use scene_loader::{Config, HasTransform}; 21 | use scene_loader::Mesh as CMesh; 22 | use scene_loader::Material as CMaterial; 23 | use scene_loader::Sky as CSky; 24 | use scene_loader::Camera as CCamera; 25 | 26 | pub struct Description { 27 | pub config: Config, 28 | loader: Loader, 29 | } 30 | 31 | impl Description { 32 | pub fn new(path: &str) -> Description { 33 | let p = Path::new(path); 34 | if !p.is_file() { panic!(format!("File `{}` is not found.", path)) } 35 | let mut file = File::open(p).unwrap(); 36 | let mut toml_str = String::new(); 37 | file.read_to_string(&mut toml_str).unwrap(); 38 | let config: Config = toml::from_str(toml_str.as_str()).unwrap(); 39 | let loader = Loader::new(&config); 40 | Description { 41 | loader: loader, 42 | config: config, 43 | } 44 | } 45 | 46 | pub fn camera(&self) -> Box { 47 | let width = self.config.film.resolution.0; 48 | let height = self.config.film.resolution.1; 49 | let matrix = self.config.camera.matrix(); 50 | match self.config.camera { 51 | CCamera::IdealPinhole { fov, .. } => box IdealPinholeCamera::new(matrix, fov, [width, height]), 52 | CCamera::ThinLens { fov, focus_distance, f_number, .. } => box LensCamera::new(matrix, fov, focus_distance, f_number, [width, height]), 53 | CCamera::Omnidirectional { .. } => box OmnidirectionalCamera::new(matrix, [width, height]), 54 | } 55 | } 56 | 57 | pub fn scene<'a>(&'a self) -> Scene<'a> { 58 | let sky = self.config.sky.as_ref().map( |v| match *v { 59 | CSky::Uniform { color } => box UniformSky { 60 | emission: color.into(), 61 | } as Box, 62 | CSky::Ibl { ref path, longitude_offset } => box IBLSky::new(path, longitude_offset), 63 | } ).unwrap_or(box UniformSky { 64 | emission: Vector3::zero(), 65 | }); 66 | println!("polygons: {}", self.loader.instances.len()); 67 | let start_time = time::now(); 68 | let objects = Objects::new(&self.loader.instances); 69 | let end_time = time::now(); 70 | println!( 71 | "bvh construction: {}s", 72 | (end_time - start_time).num_milliseconds() as f32 / 1000.0 73 | ); 74 | Scene { 75 | depth: self.config.renderer.depth.unwrap_or(5), 76 | depth_limit: self.config.renderer.depth_limit.unwrap_or(64), 77 | sky: sky, 78 | objects: objects, 79 | no_direct_emitter: self.config.renderer.no_direct_emitter.unwrap_or(false), 80 | } 81 | } 82 | } 83 | 84 | struct Loader { 85 | instances: Vec>, 86 | } 87 | 88 | impl Loader { 89 | fn new(config: &Config) -> Loader { 90 | let mut instances = Vec::new(); 91 | let obj = Self::load_obj(config.object().iter().map( |o| o.mesh ).collect()); 92 | for o in config.object() { 93 | let transform = o.matrix(); 94 | let emission = o.emission.unwrap_or(Vector3::zero()); 95 | let material = o.material.map( |m| { 96 | match *m { 97 | CMaterial::Lambert { albedo, .. } => { 98 | Arc::new(LambertianMaterial { 99 | albedo: albedo.into(), 100 | emission: emission, 101 | }) as Arc 102 | }, 103 | CMaterial::Phong { reflectance, alpha, .. } => { 104 | Arc::new(PhongMaterial { 105 | reflectance: reflectance.into(), 106 | roughness: alpha, 107 | }) 108 | }, 109 | CMaterial::BlinnPhong { reflectance, alpha, .. } => { 110 | Arc::new(BlinnPhongMaterial { 111 | reflectance: reflectance.into(), 112 | roughness: alpha, 113 | }) 114 | }, 115 | CMaterial::Ggx { reflectance, roughness, ior, .. } => { 116 | Arc::new(GGXMaterial { 117 | reflectance: reflectance.into(), 118 | roughness: roughness, 119 | ior: ior, 120 | }) 121 | }, 122 | CMaterial::IdealRefraction { reflectance, absorbtance, ior, .. } => { 123 | Arc::new(IdealRefractionMaterial { 124 | reflectance: reflectance.into(), 125 | absorbtance: absorbtance, 126 | ior: ior, 127 | }) 128 | } 129 | } 130 | }); 131 | match *o.mesh { 132 | CMesh::Obj { ref name, .. } => { 133 | let value = obj.get(name).unwrap(); 134 | let mut m = Self::obj(&value.0, &value.1, &transform, material, emission); 135 | instances.append(&mut m); 136 | }, 137 | CMesh::Sphere { ref radius, ref name } => { 138 | let position = transform * Vector3::zero(); 139 | let mat = material.ok_or(format!("Material must be specified for object `{}`", name)).unwrap(); 140 | let sphere = Sphere::new(position, *radius, mat); 141 | instances.push(box sphere); 142 | }, 143 | } 144 | } 145 | Loader { 146 | instances: instances, 147 | } 148 | } 149 | 150 | fn load_obj(mesh: Vec<&CMesh>) -> HashMap, Vec)> { 151 | let mut obj = HashMap::new(); 152 | for m in mesh { 153 | match *m { 154 | CMesh::Obj { ref name, ref path } => { 155 | let path = Path::new(&path); 156 | obj.insert(name.clone(), tobj::load_obj(&path).unwrap()); 157 | }, 158 | _ => {}, 159 | } 160 | } 161 | obj 162 | } 163 | 164 | fn obj(models: &Vec, materials: &Vec, transform: &Matrix4, default_material: Option>, emission: Vector3) -> Vec> { 165 | let material = materials.iter().map( |v| 166 | Arc::new(LambertianMaterial { 167 | emission: emission, 168 | albedo: v.diffuse[..].into(), 169 | }) as Arc 170 | ).collect::>(); 171 | let mut instances: Vec> = Vec::with_capacity( 172 | models.iter().map( |m| m.mesh.indices.len() / 3).sum() 173 | ); 174 | for m in models { 175 | let mat = match default_material.clone() { 176 | None => m.mesh.material_id 177 | .map( |v| material[v].clone() ) 178 | .ok_or("Specified material is not found in mlt file.") 179 | .unwrap(), 180 | Some(v) => v, 181 | }; 182 | for f in 0..m.mesh.indices.len() / 3 { 183 | let mut polygon = [Vector3::zero(); 3]; 184 | for i in 0..3 { 185 | let index: usize = f * 3 + i; 186 | let potition = Vector3::new( 187 | m.mesh.positions[m.mesh.indices[index] as usize * 3], 188 | m.mesh.positions[m.mesh.indices[index] as usize * 3 + 1], 189 | m.mesh.positions[m.mesh.indices[index] as usize * 3 + 2], 190 | ); 191 | polygon[i] = transform * potition; 192 | } 193 | instances.push(box Triangle::new(polygon[0], polygon[1], polygon[2], mat.clone())); 194 | } 195 | } 196 | instances 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/material/ideal_refraction.rs: -------------------------------------------------------------------------------- 1 | use super::traits::Material; 2 | use math::vector::*; 3 | use sample::Sample; 4 | use util::{BoundaryResponse}; 5 | 6 | pub struct IdealRefractionMaterial { 7 | // スペキュラー反射率 8 | pub reflectance: Vector3, 9 | pub absorbtance: f32, 10 | // 屈折率 11 | pub ior: f32, 12 | } 13 | // ディラックのデルタ関数 14 | const DELTA_FUNC: f32 = 1.0; 15 | 16 | impl Material for IdealRefractionMaterial { 17 | fn orienting_normal(&self, out_: Vector3, normal: Vector3) -> Vector3 { 18 | // 物体の内外を考慮した法線方向から拡散反射面としての法線方向を求める 19 | if normal.dot(out_) < 0.0 { 20 | normal * -1.0 21 | } else { 22 | normal 23 | } 24 | } 25 | 26 | fn emission(&self) -> Vector3 { 27 | Vector3::zero() 28 | } 29 | 30 | fn weight(&self) -> f32 { 31 | // 反射率のうち最大のものをつかう 32 | self 33 | .reflectance 34 | .x 35 | .max(self.reflectance.y) 36 | .max(self.reflectance.z) 37 | } 38 | 39 | fn brdf(&self, out_: Vector3, in_: Vector3, n: Vector3, _pos: Vector3) -> Vector3 { 40 | // out_: 入射 41 | // in_: 出射(透過/反射) 42 | // n: surface normal 43 | let on = self.orienting_normal(out_, n); 44 | let (from_ior, to_ior) = self.ior_pair(out_, n); 45 | let from_per_to_ior = from_ior / to_ior; 46 | match out_.refract(on, from_per_to_ior) { 47 | Some(r) => { 48 | // 屈折 49 | // 反射率 50 | let fr = Self::fresnel(from_ior, to_ior, out_, r, on); 51 | if in_.dot(on) > 0.0 { 52 | // 反射 53 | self.reflectance * DELTA_FUNC / in_.dot(n) * fr 54 | } else { 55 | // 透過率 56 | let ft = (1.0 - fr) * (to_ior / from_ior).powi(2); 57 | // ロシアンルーレットで反射と屈折のどちらかの寄与を取る 58 | self.reflectance * DELTA_FUNC / in_.dot(n) * ft 59 | } 60 | }, 61 | None => { 62 | // 全反射 63 | self.reflectance * DELTA_FUNC / in_.dot(n) 64 | } 65 | } 66 | } 67 | 68 | fn sample(&self, out_: Vector3, n: Vector3) -> Sample { 69 | // out_: 入射 70 | // n: surface normal 71 | let (from_ior, to_ior) = self.ior_pair(out_, n); 72 | let from_per_to_ior = from_ior / to_ior; 73 | let on = self.orienting_normal(out_, n); 74 | // 鏡面反射レイ 75 | match out_.refract(on, from_per_to_ior) { 76 | Some(r) => { 77 | // 反射率 78 | let fr = Self::fresnel(from_ior, to_ior, out_, r, on); 79 | // ロシアンルーレットで反射と屈折のどちらかの寄与を取る 80 | let rr_prob = fr; 81 | if rand::random::() < rr_prob { 82 | // 反射 83 | Sample { 84 | value: out_.reflect(on), 85 | pdf: DELTA_FUNC * rr_prob 86 | } 87 | } else { 88 | // 透過 89 | Sample { 90 | value: r, 91 | pdf: DELTA_FUNC * (1.0 - rr_prob) 92 | } 93 | } 94 | }, 95 | None => { 96 | // 全半射 97 | let r = out_.reflect(on); 98 | Sample { 99 | value: r, 100 | pdf: DELTA_FUNC 101 | } 102 | } 103 | } 104 | } 105 | 106 | fn coef(&self, out_: Vector3, n: Vector3, fly_distance: f32) -> Vector3 { 107 | if out_.dot(n) < 0.0 { 108 | let v = -(Vector3::new(1.0, 1.0, 1.0) - self.reflectance) * self.absorbtance * fly_distance; 109 | Vector3::new(v.x.exp(), v.y.exp(), v.z.exp()) 110 | } else { 111 | Vector3::new(1.0, 1.0, 1.0) 112 | } 113 | } 114 | } 115 | 116 | impl IdealRefractionMaterial { 117 | fn ior_pair(&self, out_: Vector3, n: Vector3) -> (f32, f32) { 118 | // n: surface normal 119 | // 真空屈折率 120 | let ior_v = 1.0; 121 | // 物体屈折率 122 | let ior = self.ior; 123 | // 入射媒質屈折率 / 出射媒質屈折率 124 | let from_ior; 125 | let to_ior; 126 | if out_.dot(n) > 0.0 { 127 | // 表から物体へ 128 | from_ior = ior_v; 129 | to_ior = ior; 130 | } else { 131 | // 裏から物体外へ 132 | from_ior = ior; 133 | to_ior = ior_v; 134 | } 135 | (from_ior, to_ior) 136 | } 137 | 138 | fn fresnel(from_ior: f32, to_ior: f32, out_: Vector3, in_: Vector3, on: Vector3) -> f32 { 139 | // out_: 入射 140 | // in_: 透過 141 | // on: orienting normal 142 | // Fresnelの式 143 | let cos1 = out_.dot(on); 144 | let cos2 = in_.dot(-on); 145 | let n1 = from_ior; 146 | let n2 = to_ior; 147 | let rs = ((n1 * cos1 - n2 * cos2) / (n1 * cos1 + n2 * cos2)).powi(2); 148 | let rp = ((n1 * cos2 - n2 * cos1) / (n1 * cos2 + n2 * cos1)).powi(2); 149 | (rs + rp) / 2.0 150 | // Fresnelの式(Schlickの近似)より 151 | // 反射率 152 | // let cos = if from_ior < to_ior { 153 | // out_.dot(on) 154 | // } else { 155 | // in_.dot(-on) 156 | // }; 157 | // let f_0 = (from_ior - to_ior).powi(2) / (from_ior + to_ior).powi(2); 158 | // f_0 + (1.0 - f_0) * (1.0 - cos).powi(5) 159 | } 160 | } 161 | 162 | #[cfg(test)] 163 | mod tests { 164 | use super::*; 165 | use constant::*; 166 | 167 | #[test] 168 | fn ior_pair_into_test() { 169 | let ior = 1.5; 170 | let mat = IdealRefractionMaterial { 171 | reflectance: Vector3::new(1.0, 1.0, 1.0), 172 | absorbtance: 0.0, 173 | ior: ior, 174 | }; 175 | let n = Vector3::new(0.0, 0.0, 1.0); 176 | let out_ = Vector3::new(1.0, 0.0, 1.0).normalize(); 177 | let (from_ior, to_ior) = mat.ior_pair(out_, n); 178 | assert!(from_ior == 1.0); 179 | assert!(to_ior == ior); 180 | } 181 | 182 | #[test] 183 | fn ior_pair_outgoing_test() { 184 | let ior = 1.5; 185 | let mat = IdealRefractionMaterial { 186 | reflectance: Vector3::new(1.0, 1.0, 1.0), 187 | absorbtance: 0.0, 188 | ior: ior, 189 | }; 190 | let n = Vector3::new(0.0, 0.0, -1.0); 191 | let out_ = Vector3::new(1.0, 0.0, 1.0).normalize(); 192 | let (from_ior, to_ior) = mat.ior_pair(out_, n); 193 | assert!(from_ior == ior); 194 | assert!(to_ior == 1.0); 195 | } 196 | 197 | #[test] 198 | fn brdf_reflecting_test() { 199 | let mat = IdealRefractionMaterial { 200 | reflectance: Vector3::new(1.0, 1.0, 1.0), 201 | absorbtance: 0.0, 202 | ior: INF, 203 | }; 204 | let n = Vector3::new(0.0, 0.0, -1.0); 205 | let out_ = Vector3::new(1.0, 0.0, 1.0).normalize(); 206 | let on = mat.orienting_normal(out_, n); 207 | let in_ = mat.sample(out_, n).value; 208 | let expect = out_.reflect(on); 209 | assert!((expect - in_).norm() < EPS); 210 | let brdf = mat.brdf(out_, in_, n, Vector3::zero()); 211 | let expect_brdf = Vector3::new(1.0, 1.0, 1.0) / in_.dot(n); 212 | assert!((expect_brdf - brdf).norm() < EPS); 213 | } 214 | 215 | // #[test] 216 | // fn brdf_refracting_test() { 217 | // let mat = IdealRefractionMaterial { 218 | // reflectance: Vector3::new(1.0, 1.0, 1.0), 219 | // ior: 1.5, 220 | // }; 221 | // let n = Vector3::new(0.0, 0.0, 1.0); 222 | // let out_ = Vector3::new(1.0, 0.0, 1.0).normalize(); 223 | // let on = mat.orienting_normal(out_, n); 224 | // let (from_ior, to_ior) = mat.ior_pair(out_, n); 225 | // let transmit = out_.refract(on, from_ior / to_ior).unwrap(); 226 | // let reflect = out_.reflect(on); 227 | // let brdf_t = mat.brdf(out_, transmit, n); 228 | // let brdf_r = mat.brdf(out_, reflect, n); 229 | // // assert!(brdf_t.dot(Vector3::new(1.0, 1.0, 1.0)) > 0.0); 230 | // // assert!(brdf_r.dot(Vector3::new(1.0, 1.0, 1.0)) > 0.0); 231 | // let cos_term_r = reflect.dot(n); 232 | // let cos_term_t = transmit.dot(n); 233 | // println!("{}", transmit); 234 | // assert!(brdf_r.x * cos_term_r > 0.0, "{} {}", brdf_r.x, cos_term_r); 235 | // assert!(brdf_t.x * cos_term_t > 0.0, "{} {}", brdf_t.x, cos_term_t); 236 | // assert!(brdf_r.x * cos_term_r < 1.0, "{} {}", brdf_r.x, cos_term_r); 237 | // assert!(brdf_t.x * cos_term_t < 1.0, "{} {}", brdf_t.x, cos_term_t); 238 | // } 239 | 240 | // #[test] 241 | // fn brdf_refracting_outgoing_test() { 242 | // let mat = IdealRefractionMaterial { 243 | // reflectance: Vector3::new(1.0, 1.0, 1.0), 244 | // ior: 1.5, 245 | // }; 246 | // let n = Vector3::new(0.0, 0.0, -1.0); 247 | // let out_ = Vector3::new(0.5, 0.0, 1.0).normalize(); 248 | // let on = mat.orienting_normal(out_, n); 249 | // let (from_ior, to_ior) = mat.ior_pair(out_, n); 250 | // let transmit = out_.refract(on, from_ior / to_ior).unwrap(); 251 | // let reflect = out_.reflect(on); 252 | // let brdf_t = mat.brdf(out_, transmit, n); 253 | // let brdf_r = mat.brdf(out_, reflect, n); 254 | // assert!(brdf_t.dot(Vector3::new(1.0, 1.0, 1.0)) > 0.0); 255 | // assert!(brdf_r.dot(Vector3::new(1.0, 1.0, 1.0)) > 0.0); 256 | // let cos_term = reflect.dot(n).abs(); 257 | // assert!(brdf_r.x * cos_term < 1.0); 258 | // assert!(brdf_t.x * cos_term < 1.0); 259 | // } 260 | 261 | #[test] 262 | fn fresnel_45_test() { 263 | let from_ior = 1.0; 264 | let to_ior = 1.5; 265 | let on = Vector3::new(0.0, 0.0, 1.0); 266 | let out_ = Vector3::new(1.0, 0.0, 1.0).normalize(); 267 | let in_ = out_.refract(on, from_ior / to_ior).unwrap(); 268 | let fr = IdealRefractionMaterial::fresnel(from_ior, to_ior, out_, in_, on); 269 | assert!(fr <= 1.0 && fr > 0.0); 270 | } 271 | 272 | #[test] 273 | fn fresnel_test() { 274 | let from_ior = 1.0; 275 | let to_ior = 1.5; 276 | let on = Vector3::new(0.0, 0.0, 1.0); 277 | for i in 0..100 { 278 | let t = i as f32 / 100.0 * PI / 2.0; 279 | let out_ = Vector3::new(t.sin(), 0.0, t.cos()).normalize(); 280 | let in_ = out_.refract(on, from_ior / to_ior).unwrap(); 281 | let fr = IdealRefractionMaterial::fresnel(from_ior, to_ior, out_, in_, on); 282 | assert!(fr <= 1.0 && fr > 0.0, "{}", fr); 283 | } 284 | } 285 | 286 | #[test] 287 | fn fresnel_outgoing_test() { 288 | let from_ior = 1.5; 289 | let to_ior = 1.0; 290 | let on = Vector3::new(0.0, 0.0, 1.0); 291 | for i in 0..100 { 292 | let t = i as f32 / 100.0 * PI / 2.0; 293 | let out_ = Vector3::new(t.sin(), 0.0, t.cos()); 294 | out_.refract(on, from_ior / to_ior).map( |in_| { 295 | let fr = IdealRefractionMaterial::fresnel(from_ior, to_ior, out_, in_, on); 296 | assert!(fr <= 1.0 && fr > 0.0, "{} {} {}", fr, in_, on); 297 | }); 298 | } 299 | } 300 | 301 | #[test] 302 | fn sample_test() { 303 | let mat = IdealRefractionMaterial { 304 | reflectance: Vector3::new(1.0, 1.0, 1.0), 305 | absorbtance: 0.0, 306 | ior: 1.5, 307 | }; 308 | let n = Vector3::new(0.0, 0.0, -1.0); 309 | let out_ = Vector3::new(1.0, 0.0, 1.0).normalize(); 310 | let in_ = mat.sample(out_, n).value; 311 | assert!((in_.norm() - 1.0).abs() < EPS); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/camera.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use constant::*; 4 | use math::vector::*; 5 | use math::matrix::*; 6 | use ray::Ray; 7 | use sample::Sample; 8 | 9 | pub trait Camera { 10 | fn sample(&self, x: usize, y: usize) -> (Sample, f32); 11 | fn sensor_sensitivity(&self) -> f32; 12 | fn info(&self) -> CameraInfo; 13 | } 14 | 15 | #[derive(Debug)] 16 | pub struct IdealPinholeCamera { 17 | // カメラの方向を基準とした正規直交基底 18 | pub forward: Vector3, 19 | pub right: Vector3, 20 | pub up: Vector3, 21 | // センサーの中心座標(m) 22 | pub position: Vector3, 23 | // センサーの解像度 24 | pub resolution: [usize; 2], 25 | // センサーの物理的な大きさ(m) 26 | pub sensor_size: [f32; 2], 27 | // 入射口の中心座標(m) 28 | pub aperture_position: Vector3, 29 | // 入射口とセンサー間の距離(m) 30 | pub aperture_sensor_distance: f32, 31 | } 32 | 33 | impl IdealPinholeCamera { 34 | pub fn new( 35 | matrix: Matrix4, 36 | xfov: f32, 37 | resolution: [usize; 2], 38 | ) -> IdealPinholeCamera { 39 | let aperture_position: Vector3 = matrix.row(3).into(); 40 | // カメラの入射の方向を基準(forward)に正規直交基底 41 | let forward = &matrix * Vector3::new(0.0, 0.0, -1.0); 42 | let right = &matrix * Vector3::new(1.0, 0.0, 0.0); 43 | let up = &matrix * Vector3::new(0.0, 1.0, 0.0); 44 | // 便宜的な開口部までの距離 45 | let direction = forward * 50.0; 46 | let position = aperture_position - direction; 47 | // 入射口とセンサー間の距離(m) 48 | let aperture_sensor_distance = direction.norm(); 49 | // 便宜的なセンサーサイズ 50 | let sensor_size_x = 2.0 * aperture_sensor_distance * (xfov * PI / 180.0 / 2.0).tan(); 51 | let sensor_size_y = sensor_size_x * resolution[1] as f32 / resolution[0] as f32; 52 | IdealPinholeCamera { 53 | forward: forward, 54 | right: right, 55 | up: up, 56 | position: position, 57 | resolution: resolution, 58 | sensor_size: [sensor_size_x, sensor_size_y], 59 | aperture_position: aperture_position, 60 | aperture_sensor_distance: aperture_sensor_distance, 61 | } 62 | } 63 | 64 | fn sample_sensor(&self, left: usize, top: usize) -> Sample { 65 | // イメージセンサー1画素内の点の座標を取得(一様分布) 66 | // 原点はセンサーの中心 67 | // 画素内の1点を一様分布でサンプリング(0~1の乱数) 68 | let u = rand::random::(); 69 | let v = rand::random::(); 70 | // センサー中心を基準とした平面座標でのサンプリング点の座標(m) 71 | let px = (((left as f32 + u) / self.resolution[0] as f32) - 0.5) * self.sensor_size[0]; 72 | let py = (((top as f32 + v) / self.resolution[1] as f32) - 0.5) * self.sensor_size[1]; 73 | // 空間でのサンプリング点の座標(m) 74 | let point = self.position - self.right * px + self.up * py; 75 | // 画素内の1点を一様分布でサンプリングした時の確率密度(m^-2) 76 | let pdf = 1.0; 77 | Sample { 78 | value: point, 79 | pdf: pdf, 80 | } 81 | } 82 | 83 | fn sample_aperture(&self) -> Sample { 84 | // 空間でのサンプリング点の座標(m) 85 | let point = self.aperture_position; 86 | // 入射口内の1点を一様分布でサンプリングした時の確率密度(m^-2) 87 | let pdf = 1.0; 88 | Sample { 89 | value: point, 90 | pdf: pdf, 91 | } 92 | } 93 | 94 | fn geometry_term(&self, _direction: Vector3) -> f32 { 95 | 1.0 96 | } 97 | } 98 | 99 | impl Camera for IdealPinholeCamera { 100 | fn sample(&self, x: usize, y: usize) -> (Sample, f32) { 101 | let sensor_sample = self.sample_sensor(x, y); 102 | let aperture_sample = self.sample_aperture(); 103 | let ray = Ray { 104 | origin: aperture_sample.value, 105 | direction: (aperture_sample.value - sensor_sample.value).normalize(), 106 | }; 107 | let direction_to_sensor = ray.direction; 108 | ( 109 | Sample { 110 | value: ray, 111 | pdf: sensor_sample.pdf * aperture_sample.pdf, 112 | }, 113 | self.geometry_term(direction_to_sensor), 114 | ) 115 | } 116 | 117 | fn sensor_sensitivity(&self) -> f32 { 118 | 1.0 119 | } 120 | 121 | fn info(&self) -> CameraInfo { 122 | // FoV 123 | let sensor_diagonal = (self.sensor_size[0].powi(2) + self.sensor_size[1].powi(2)).sqrt(); 124 | let fov = 2.0 * (sensor_diagonal / 2.0 / self.aperture_sensor_distance).atan() * 180.0 / PI; 125 | let xfov = 2.0 * (self.sensor_size[0] / 2.0 / self.aperture_sensor_distance).atan() * 180.0 / PI; 126 | CameraInfo { 127 | focal_length: self.aperture_sensor_distance, 128 | sensor_diagonal: sensor_diagonal, 129 | fov: fov, 130 | xfov: xfov, 131 | f_number: 1.0 / 0.0, 132 | } 133 | } 134 | } 135 | 136 | #[derive(Debug)] 137 | pub struct OmnidirectionalCamera { 138 | // カメラの方向を基準とした正規直交基底 139 | pub forward: Vector3, 140 | pub right: Vector3, 141 | pub up: Vector3, 142 | // センサーの解像度 143 | pub resolution: [usize; 2], 144 | // 入射口の中心座標(m) 145 | pub aperture_position: Vector3, 146 | } 147 | 148 | impl OmnidirectionalCamera { 149 | pub fn new( 150 | matrix: Matrix4, 151 | resolution: [usize; 2], 152 | ) -> OmnidirectionalCamera { 153 | let aperture_position: Vector3 = matrix.row(3).into(); 154 | // カメラの入射の方向を基準(forward)に正規直交基底 155 | let forward = &matrix * Vector3::new(0.0, 0.0, -1.0); 156 | let right = &matrix * Vector3::new(1.0, 0.0, 0.0); 157 | let up = &matrix * Vector3::new(0.0, 1.0, 0.0); 158 | OmnidirectionalCamera { 159 | forward: forward, 160 | right: right, 161 | up: up, 162 | resolution: resolution, 163 | aperture_position: aperture_position, 164 | } 165 | } 166 | } 167 | 168 | impl Camera for OmnidirectionalCamera { 169 | fn sample(&self, x: usize, y: usize) -> (Sample, f32) { 170 | // 画素内の1点を一様分布でサンプリング(0~1の乱数) 171 | let u = rand::random::(); 172 | let v = rand::random::(); 173 | // センサー中心を基準とした平面座標でのサンプリング点の座標(m) 174 | let p = (x as f32 + u) / self.resolution[0] as f32 * PI * 2.0; 175 | let t = (y as f32 + v) / self.resolution[1] as f32 * PI; 176 | let direction = Vector3::new(t.sin() * p.cos(), t.sin() * p.sin(), t.cos()); 177 | let ray = Ray { 178 | origin: self.aperture_position, 179 | direction: direction, 180 | }; 181 | ( 182 | Sample { 183 | value: ray, 184 | pdf: 1.0, 185 | }, 186 | 1.0, 187 | ) 188 | } 189 | 190 | fn sensor_sensitivity(&self) -> f32 { 191 | 1.0 192 | } 193 | 194 | fn info(&self) -> CameraInfo { 195 | unimplemented!() 196 | } 197 | } 198 | 199 | #[derive(Debug)] 200 | pub struct PinholeCamera { 201 | // カメラの方向を基準とした正規直交基底 202 | pub forward: Vector3, 203 | pub right: Vector3, 204 | pub up: Vector3, 205 | // センサーの中心座標(m) 206 | pub position: Vector3, 207 | // センサーの解像度 208 | pub resolution: [usize; 2], 209 | // センサーの物理的な大きさ(m) 210 | pub sensor_size: [f32; 2], 211 | // 入射口の中心座標(m) 212 | pub aperture_position: Vector3, 213 | // 入射口の半径(m) 214 | pub aperture_radius: f32, 215 | // 入射口とセンサー間の距離(m) 216 | pub aperture_sensor_distance: f32, 217 | // センサー1画素の面積(m^2) 218 | pub sensor_pixel_area: f32, 219 | // センサー感度(m^2) 220 | pub sensor_sensitivity: f32, 221 | } 222 | 223 | impl PinholeCamera { 224 | pub fn new( 225 | position: Vector3, 226 | aperture_position: Vector3, 227 | sensor_size: [f32; 2], 228 | resolution: [usize; 2], 229 | aperture_radius: f32, 230 | ) -> PinholeCamera { 231 | // レンズの方向(m) 232 | let direction = aperture_position - position; 233 | // 入射口とセンサー間の距離(m) 234 | let aperture_sensor_distance = direction.norm(); 235 | // カメラの入射の方向を基準(forward)に正規直交基底 236 | let forward = direction.normalize(); 237 | let right = forward 238 | .cross(if forward.y.abs() < 1.0 - EPS { 239 | Vector3::new(0.0, 1.0, 0.0) 240 | } else { 241 | Vector3::new(1.0, 0.0, 0.0) 242 | }) 243 | .normalize(); 244 | let up = right.cross(forward); 245 | // 1画素の面積 = センサーの面積 / センサーの画素数 246 | let sensor_pixel_area = (sensor_size[0] * sensor_size[1]) as f32 / 247 | (resolution[0] * resolution[1]) as f32; 248 | // センサー感度はpdfを打ち消すように設定(m^2 m^-2 m^-2) 249 | let sensor_sensitivity = aperture_sensor_distance * aperture_sensor_distance / 250 | (sensor_pixel_area * PI * aperture_radius * aperture_radius); 251 | PinholeCamera { 252 | forward: forward, 253 | right: right, 254 | up: up, 255 | position: position, 256 | resolution: resolution, 257 | sensor_size: sensor_size, 258 | aperture_position: aperture_position, 259 | aperture_radius: aperture_radius, 260 | aperture_sensor_distance: aperture_sensor_distance, 261 | sensor_pixel_area: sensor_pixel_area, 262 | sensor_sensitivity: sensor_sensitivity, 263 | } 264 | } 265 | 266 | fn sample_sensor(&self, left: usize, top: usize) -> Sample { 267 | // イメージセンサー1画素内の点の座標を取得(一様分布) 268 | // 原点はセンサーの中心 269 | // 画素内の1点を一様分布でサンプリング(0~1の乱数) 270 | let u = rand::random::(); 271 | let v = rand::random::(); 272 | // センサー中心を基準とした平面座標でのサンプリング点の座標(m) 273 | let px = (((left as f32 + u) / self.resolution[0] as f32) - 0.5) * self.sensor_size[0]; 274 | let py = (((top as f32 + v) / self.resolution[1] as f32) - 0.5) * self.sensor_size[1]; 275 | // 空間でのサンプリング点の座標(m) 276 | let point = self.position - self.right * px + self.up * py; 277 | // 画素内の1点を一様分布でサンプリングした時の確率密度(m^-2) 278 | let pdf = 1.0 / self.sensor_pixel_area; 279 | Sample { 280 | value: point, 281 | pdf: pdf, 282 | } 283 | } 284 | 285 | fn sample_aperture(&self) -> Sample { 286 | // 光が入射してくる入射口内の点の座標を取得(一様分布) 287 | let u = 2.0 * PI * rand::random::(); 288 | let v = rand::random::().sqrt() * self.aperture_radius; 289 | // 入射口の中心を基準とした平面極座標でのサンプリング点の座標(m) 290 | let px = u.cos() * v; 291 | let py = u.sin() * v; 292 | // 空間でのサンプリング点の座標(m) 293 | let point = self.aperture_position + self.right * px + self.up * py; 294 | // 入射口内の1点を一様分布でサンプリングした時の確率密度(m^-2) 295 | let pdf = 1.0 / (PI * self.aperture_radius * self.aperture_radius); 296 | Sample { 297 | value: point, 298 | pdf: pdf, 299 | } 300 | } 301 | 302 | fn geometry_term(&self, direction: Vector3) -> f32 { 303 | // cos項 304 | let cos_term = direction.dot(self.forward); 305 | // センサー面と開口部それぞれのサンプリング点同士の距離 306 | let d = self.aperture_sensor_distance / cos_term; 307 | // ジオメトリ項(m^-2) 308 | cos_term * cos_term / (d * d) 309 | } 310 | } 311 | 312 | impl Camera for PinholeCamera { 313 | fn sample(&self, x: usize, y: usize) -> (Sample, f32) { 314 | let sensor_sample = self.sample_sensor(x, y); 315 | let aperture_sample = self.sample_aperture(); 316 | let ray = Ray { 317 | origin: aperture_sample.value, 318 | direction: (aperture_sample.value - sensor_sample.value).normalize(), 319 | }; 320 | let direction_to_sensor = ray.direction; 321 | ( 322 | Sample { 323 | value: ray, 324 | pdf: sensor_sample.pdf * aperture_sample.pdf, 325 | }, 326 | self.geometry_term(direction_to_sensor), 327 | ) 328 | } 329 | 330 | fn sensor_sensitivity(&self) -> f32 { 331 | self.sensor_sensitivity 332 | } 333 | 334 | fn info(&self) -> CameraInfo { 335 | unimplemented!(); 336 | } 337 | } 338 | 339 | #[derive(Debug)] 340 | pub struct LensCamera { 341 | // カメラの方向を基準とした正規直交基底 342 | pub forward: Vector3, 343 | pub right: Vector3, 344 | pub up: Vector3, 345 | // センサーの中心座標(m) 346 | pub position: Vector3, 347 | // センサーの解像度 348 | pub resolution: [usize; 2], 349 | // センサーの物理的な大きさ(m) 350 | pub sensor_size: [f32; 2], 351 | // 入射口の中心座標(m) 352 | pub aperture_position: Vector3, 353 | // 入射口の半径(m) 354 | pub aperture_radius: f32, 355 | // 入射口とセンサー間の距離(m) 356 | pub aperture_sensor_distance: f32, 357 | // センサー1画素の面積(m^2) 358 | pub sensor_pixel_area: f32, 359 | // センサー感度(m^2) 360 | pub sensor_sensitivity: f32, 361 | // 焦点の合う場所の入射口からの距離 362 | pub focus_distance: f32, 363 | } 364 | 365 | impl LensCamera { 366 | pub fn new( 367 | matrix: Matrix4, 368 | xfov: f32, 369 | focus_distance: f32, 370 | f_number: f32, 371 | resolution: [usize; 2], 372 | ) -> LensCamera { 373 | let aperture_position: Vector3 = matrix.row(3).into(); 374 | // カメラの入射の方向を基準(forward)に正規直交基底 375 | let forward = &matrix * Vector3::new(0.0, 0.0, -1.0); 376 | let right = &matrix * Vector3::new(1.0, 0.0, 0.0); 377 | let up = &matrix * Vector3::new(0.0, 1.0, 0.0); 378 | // 便宜的な開口部までの距離 379 | let direction = forward * 50.0; // TODO: センサーサイズorセンサーまでの距離を受け取る 380 | let position = aperture_position - direction; 381 | // 入射口とセンサー間の距離(m) 382 | let aperture_sensor_distance = direction.norm(); 383 | // 便宜的なセンサーサイズ 384 | let sensor_size_x = 2.0 * aperture_sensor_distance * (xfov * PI / 180.0 / 2.0).tan(); 385 | let sensor_size_y = sensor_size_x * resolution[1] as f32 / resolution[0] as f32; 386 | // 焦点距離 387 | let focal_length = 1.0 / (1.0 / aperture_sensor_distance + 1.0 / focus_distance); 388 | // 開口部半径 389 | let aperture_radius = focal_length / f_number / 2.0; 390 | // 1画素の面積 = センサーの面積 / センサーの画素数 391 | let sensor_pixel_area = (sensor_size_x * sensor_size_y) / (resolution[0] * resolution[1]) as f32; 392 | // センサー感度はpdfを打ち消すように設定(m^2 m^-2 m^-[1] 393 | let sensor_sensitivity = aperture_sensor_distance * aperture_sensor_distance / 394 | (sensor_pixel_area * PI * aperture_radius * aperture_radius); 395 | LensCamera { 396 | forward: forward, 397 | right: right, 398 | up: up, 399 | position: position, 400 | resolution: resolution, 401 | sensor_size: [sensor_size_x, sensor_size_y], 402 | aperture_position: aperture_position, 403 | aperture_radius: aperture_radius, 404 | aperture_sensor_distance: aperture_sensor_distance, 405 | sensor_pixel_area: sensor_pixel_area, 406 | sensor_sensitivity: sensor_sensitivity, 407 | focus_distance: focus_distance, 408 | } 409 | } 410 | 411 | fn sample_sensor(&self, left: usize, top: usize) -> Sample { 412 | // イメージセンサー1画素内の点の座標を取得(一様分布) 413 | // 原点はセンサーの中心 414 | // 画素内の1点を一様分布でサンプリング(0~1の乱数) 415 | let u = rand::random::(); 416 | let v = rand::random::(); 417 | // センサー中心を基準とした平面座標でのサンプリング点の座標(m) 418 | let px = (((left as f32 + u) / self.resolution[0] as f32) - 0.5) * self.sensor_size[0]; 419 | let py = (((top as f32 + v) / self.resolution[1] as f32) - 0.5) * self.sensor_size[1]; 420 | // 空間でのサンプリング点の座標(m) 421 | let point = self.position - self.right * px + self.up * py; 422 | // 画素内の1点を一様分布でサンプリングした時の確率密度(m^-2) 423 | let pdf = 1.0 / self.sensor_pixel_area; 424 | Sample { 425 | value: point, 426 | pdf: pdf, 427 | } 428 | } 429 | 430 | fn sample_aperture(&self) -> Sample { 431 | // 光が入射してくる入射口内の点の座標を取得(一様分布) 432 | let u = 2.0 * PI * rand::random::(); 433 | let v = rand::random::().sqrt() * self.aperture_radius; 434 | // 入射口の中心を基準とした平面極座標でのサンプリング点の座標(m) 435 | let px = u.cos() * v; 436 | let py = u.sin() * v; 437 | // 空間でのサンプリング点の座標(m) 438 | let point = self.aperture_position + self.right * px + self.up * py; 439 | // 入射口内の1点を一様分布でサンプリングした時の確率密度(m^-2) 440 | let pdf = 1.0 / (PI * self.aperture_radius * self.aperture_radius); 441 | Sample { 442 | value: point, 443 | pdf: pdf, 444 | } 445 | } 446 | 447 | fn geometry_term(&self, direction: Vector3) -> f32 { 448 | // cos項 449 | let cos_term = direction.dot(self.forward); 450 | // センサー面と開口部それぞれのサンプリング点同士の距離 451 | let d = self.aperture_sensor_distance / cos_term; 452 | // ジオメトリ項(m^-2) 453 | cos_term * cos_term / (d * d) 454 | } 455 | } 456 | 457 | impl Camera for LensCamera { 458 | fn sample(&self, x: usize, y: usize) -> (Sample, f32) { 459 | let sensor_sample = self.sample_sensor(x, y); 460 | let aperture_sample = self.sample_aperture(); 461 | // センサー上の点から開口部中心 462 | let sensor_center = self.aperture_position - sensor_sample.value; 463 | // 開口部中心からオブジェクトプレーン上 464 | let object_plane = sensor_center * (self.focus_distance / sensor_center.dot(self.forward)); 465 | let ray = Ray { 466 | origin: aperture_sample.value, 467 | direction: (self.aperture_position + object_plane - aperture_sample.value).normalize(), 468 | }; 469 | ( 470 | Sample { 471 | value: ray, 472 | pdf: sensor_sample.pdf * aperture_sample.pdf, 473 | }, 474 | self.geometry_term((aperture_sample.value - sensor_sample.value).normalize()), 475 | ) 476 | } 477 | 478 | fn sensor_sensitivity(&self) -> f32 { 479 | self.sensor_sensitivity 480 | } 481 | 482 | fn info(&self) -> CameraInfo { 483 | // 焦点距離 484 | let focal_length = 1.0 / (1.0 / self.aperture_sensor_distance + 1.0 / self.focus_distance); 485 | // FoV 486 | let sensor_diagonal = (self.sensor_size[0].powi(2) + self.sensor_size[1].powi(2)).sqrt(); 487 | let fov = 2.0 * (sensor_diagonal / 2.0 / self.aperture_sensor_distance).atan() * 180.0 / PI; 488 | let xfov = 2.0 * (self.sensor_size[0] / 2.0 / self.aperture_sensor_distance).atan() * 180.0 / PI; 489 | // F値 490 | let f_number = focal_length / self.aperture_radius / 2.0; 491 | CameraInfo { 492 | focal_length: focal_length, 493 | sensor_diagonal: sensor_diagonal, 494 | fov: fov, 495 | xfov: xfov, 496 | f_number: f_number, 497 | } 498 | } 499 | } 500 | 501 | #[derive(Debug)] 502 | pub struct CameraInfo { 503 | pub focal_length: f32, 504 | pub sensor_diagonal: f32, 505 | pub fov: f32, 506 | pub xfov: f32, 507 | pub f_number: f32, 508 | } 509 | --------------------------------------------------------------------------------