├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── app ├── Cargo.toml ├── scenes │ ├── checkerboard.png │ └── test.json └── src │ └── main.rs ├── ffi ├── Cargo.toml └── src │ └── lib.rs ├── python ├── .gitignore ├── raytracer.py └── runner.py └── src ├── lib.rs ├── matrix.rs ├── point.rs ├── rendering.rs ├── scene.rs └── vector.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "raytracer" 3 | version = "0.1.0" 4 | authors = ["Brook Heisler "] 5 | 6 | [dependencies] 7 | serde = "0.9.7" 8 | serde_derive = "0.9.7" 9 | image = "0.12.3" 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Brook Heisler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a toy raytracer I wrote in rust to learn how raytracers work. I also 2 | wrote a series of posts on it starting 3 | [here](https://bheisler.github.io/post/writing-raytracer-in-rust-part-1/). 4 | 5 | ### Quickstart: 6 | 7 | First, you'll need to have [cargo](https://rustup.rs/) installed. Then you can 8 | clone the repository and run the raytracer: 9 | 10 | cd raytracer/app 11 | cargo run --release scenes/test.json out.png 12 | 13 | You can modify the rendered scene by editing test.json. Enjoy! 14 | 15 | ### Python Quickstart: 16 | 17 | This repository also contains code for exposing the raytracer through a C 18 | interface to Python. If you'd like to define your scenes in python instead, 19 | you can do this (note that on Unix-like OS's you may need to edit the path 20 | in python/raytracer.py to point to the `*.so` file instead of a dll): 21 | 22 | cd raytracer/ffi 23 | cargo build --release 24 | cd ../python 25 | python runner.py 26 | -------------------------------------------------------------------------------- /app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "raytracer-app" 3 | version = "0.1.0" 4 | authors = ["Brook Heisler "] 5 | 6 | [dependencies] 7 | raytracer = { path = ".." } 8 | serde = "0.9.7" 9 | serde_json = "0.9.6" 10 | clap = "2.20" 11 | image = "0.12.3" 12 | 13 | [profile.release] 14 | debug=true 15 | -------------------------------------------------------------------------------- /app/scenes/checkerboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bheisler/raytracer/7130556181de7fc59eaa29346f5d4134db3e720e/app/scenes/checkerboard.png -------------------------------------------------------------------------------- /app/scenes/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "width": 32000, 3 | "height": 24000, 4 | "fov": 90.0, 5 | "elements": [ 6 | { 7 | "Sphere" : { 8 | "center": { 9 | "x": 0.0, 10 | "y": 0.0, 11 | "z": -5.0 12 | }, 13 | "radius": 1.0, 14 | "material": { 15 | "coloration" : { 16 | "Color": { 17 | "red": 0.2, 18 | "green": 1.0, 19 | "blue": 0.2 20 | } 21 | }, 22 | "albedo": 0.18, 23 | "surface": { 24 | "Reflective": { 25 | "reflectivity": 0.7 26 | } 27 | } 28 | } 29 | } 30 | }, 31 | { 32 | "Sphere" : { 33 | "center": { 34 | "x": -3.0, 35 | "y": 1.0, 36 | "z": -6.0 37 | }, 38 | "radius": 2.0, 39 | "material": { 40 | "coloration": { 41 | "Texture": { 42 | "path": "scenes/checkerboard.png" 43 | } 44 | }, 45 | "albedo": 0.58, 46 | "surface": "Diffuse" 47 | } 48 | } 49 | }, 50 | { 51 | "Sphere": { 52 | "center": { 53 | "x": 2.0, 54 | "y": 1.0, 55 | "z": -4.0 56 | }, 57 | "radius": 1.5, 58 | "material": { 59 | "coloration": { 60 | "Color": { 61 | "red": 1.0, 62 | "green": 1.0, 63 | "blue": 1.0 64 | } 65 | }, 66 | "albedo": 0.18, 67 | "surface": { 68 | "Refractive": { 69 | "index": 1.5, 70 | "transparency": 1.0 71 | } 72 | } 73 | } 74 | } 75 | }, 76 | { 77 | "Plane": { 78 | "origin": { 79 | "x": 0.0, 80 | "y": -2.0, 81 | "z": -5.0 82 | }, 83 | "normal": { 84 | "x": 0.0, 85 | "y": -1.0, 86 | "z": 0.0 87 | }, 88 | "material": { 89 | "coloration": { 90 | "Texture": { 91 | "path": "scenes/checkerboard.png" 92 | } 93 | }, 94 | "albedo": 0.18, 95 | "surface": { 96 | "Reflective": { 97 | "reflectivity": 0.5 98 | } 99 | } 100 | } 101 | } 102 | }, 103 | { 104 | "Plane": { 105 | "origin": { 106 | "x": 0.0, 107 | "y": 0.0, 108 | "z": -20.0 109 | }, 110 | "normal": { 111 | "x": 0.0, 112 | "y": 0.0, 113 | "z": -1.0 114 | }, 115 | "material": { 116 | "coloration": { 117 | "Color": { 118 | "red": 0.2, 119 | "green": 0.3, 120 | "blue": 1.0 121 | } 122 | }, 123 | "albedo": 0.38, 124 | "surface": "Diffuse" 125 | } 126 | } 127 | } 128 | ], 129 | "lights": [ 130 | { 131 | "Spherical": { 132 | "position": { 133 | "x": -2.0, 134 | "y": 10.0, 135 | "z": -3.0 136 | }, 137 | "color": { 138 | "red": 0.3, 139 | "green": 0.8, 140 | "blue": 0.3 141 | }, 142 | "intensity": 10000.0 143 | } 144 | }, 145 | { 146 | "Spherical": { 147 | "position": { 148 | "x": 0.25, 149 | "y": 0.0, 150 | "z": -2.0 151 | }, 152 | "color": { 153 | "red": 0.8, 154 | "green": 0.3, 155 | "blue": 0.3 156 | }, 157 | "intensity": 250.0 158 | } 159 | }, 160 | { 161 | "Directional": { 162 | "direction": { 163 | "x": 0.0, 164 | "y": 0.0, 165 | "z": -1.0 166 | }, 167 | "color": { 168 | "red": 1.0, 169 | "green": 1.0, 170 | "blue": 1.0 171 | }, 172 | "intensity": 0.0 173 | } 174 | } 175 | ], 176 | "shadow_bias": 1e-13, 177 | "max_recursion_depth": 10 178 | } 179 | -------------------------------------------------------------------------------- /app/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate serde; 3 | extern crate serde_json; 4 | extern crate raytracer; 5 | extern crate image; 6 | 7 | use clap::{Arg, App}; 8 | use std::fs::{File, OpenOptions}; 9 | use raytracer::scene::*; 10 | use image::ImageFormat; 11 | 12 | fn main() { 13 | let app = App::new("raytracer") 14 | .version("0.1") 15 | .author("bheisler ") 16 | .about("Basic Raytracer") 17 | .arg(Arg::with_name("scene") 18 | .help("Sets the scene file to use") 19 | .required(true) 20 | .index(1)) 21 | .arg(Arg::with_name("image") 22 | .help("Sets the output image file") 23 | .required(true) 24 | .index(2)); 25 | let matches = app.get_matches(); 26 | 27 | let scene_path = matches.value_of("scene").unwrap(); 28 | let scene_file = File::open(scene_path).expect("File not found"); 29 | 30 | let image_path = matches.value_of("image").unwrap(); 31 | 32 | let scene: Scene = serde_json::from_reader(scene_file).unwrap(); 33 | 34 | let block = raytracer::ViewBlock { 35 | x: 0, 36 | y: 0, 37 | width: scene.width, 38 | height: scene.height, 39 | }; 40 | 41 | let image = raytracer::render(&block, &scene); 42 | 43 | let mut image_file = 44 | OpenOptions::new().write(true).truncate(true).create(true).open(image_path).unwrap(); 45 | image.save(&mut image_file, ImageFormat::PNG).unwrap(); 46 | } 47 | -------------------------------------------------------------------------------- /ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "raytracer-ffi" 3 | version = "0.1.0" 4 | authors = ["Brook Heisler "] 5 | 6 | [dependencies] 7 | raytracer = { path = ".." } 8 | image = "0.12.3" 9 | serde_json = "0.9.6" 10 | 11 | [lib] 12 | name = "raytracer_ffi" 13 | crate-type = ["dylib"] 14 | -------------------------------------------------------------------------------- /ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate raytracer; 2 | extern crate image; 3 | extern crate serde_json; 4 | 5 | use raytracer::point::Point; 6 | use raytracer::vector::Vector3; 7 | use raytracer::scene::*; 8 | use raytracer::ViewBlock; 9 | use std::path::PathBuf; 10 | use std::ffi::{CStr, CString}; 11 | use std::os::raw::c_char; 12 | use std::ptr; 13 | use std::slice; 14 | 15 | #[no_mangle] 16 | pub extern "C" fn scene_from_json(json: *const c_char) -> *mut Scene { 17 | if json.is_null() { 18 | return ptr::null_mut(); 19 | } 20 | let c_json = unsafe { CStr::from_ptr(json) }; 21 | if let Ok(json) = c_json.to_str() { 22 | if let Ok(scene) = serde_json::from_str(json) { 23 | let scene: Scene = scene; 24 | return Box::into_raw(Box::new(scene)); 25 | } 26 | } 27 | ptr::null_mut() 28 | } 29 | 30 | #[no_mangle] 31 | pub extern "C" fn scene_new(width: u32, 32 | height: u32, 33 | fov: f64, 34 | shadow_bias: f64, 35 | max_recursion_depth: u32) 36 | -> *mut Scene { 37 | let scene = Box::new(Scene { 38 | width: width, 39 | height: height, 40 | fov: fov, 41 | 42 | elements: vec![], 43 | lights: vec![], 44 | 45 | shadow_bias: shadow_bias, 46 | max_recursion_depth: max_recursion_depth, 47 | }); 48 | Box::into_raw(scene) 49 | } 50 | 51 | #[no_mangle] 52 | pub extern "C" fn scene_add_sphere(scene: *mut Scene, 53 | center: *const Point, 54 | radius: f64, 55 | material: *const CMaterial) { 56 | if scene.is_null() || center.is_null() || material.is_null() { 57 | return; 58 | } 59 | let mut scene = unsafe { Box::from_raw(scene) }; 60 | 61 | if let Some(rust_material) = unsafe { (&*material) }.to_rust() { 62 | let sphere = Sphere { 63 | center: unsafe { &*center }.clone(), 64 | radius: radius, 65 | material: rust_material, 66 | }; 67 | let mut scene_ref = &mut *scene; 68 | scene_ref.elements.push(Element::Sphere(sphere)); 69 | } 70 | 71 | //Don't free the scene 72 | Box::into_raw(scene); 73 | } 74 | 75 | #[no_mangle] 76 | pub extern "C" fn scene_add_plane(scene: *mut Scene, 77 | origin: *const Point, 78 | normal: *const Vector3, 79 | material: *const CMaterial) { 80 | if scene.is_null() || origin.is_null() || normal.is_null() || material.is_null() { 81 | return; 82 | } 83 | let mut scene = unsafe { Box::from_raw(scene) }; 84 | 85 | if let Some(rust_material) = unsafe { (&*material) }.to_rust() { 86 | let plane = Plane { 87 | origin: unsafe { (&*origin) }.clone(), 88 | normal: unsafe { (&*normal) }.normalize(), 89 | material: rust_material, 90 | }; 91 | let mut scene_ref = &mut *scene; 92 | scene_ref.elements.push(Element::Plane(plane)); 93 | } 94 | 95 | //Don't free the scene 96 | Box::into_raw(scene); 97 | } 98 | 99 | #[no_mangle] 100 | pub extern "C" fn scene_add_spherical_light(scene: *mut Scene, 101 | position: *const Point, 102 | color: *const Color, 103 | intensity: f32) { 104 | if scene.is_null() || position.is_null() || color.is_null() { 105 | return; 106 | } 107 | let mut scene = unsafe { Box::from_raw(scene) }; 108 | let light = SphericalLight { 109 | position: unsafe { &*position }.clone(), 110 | color: unsafe { &*color }.clone(), 111 | intensity: intensity, 112 | }; 113 | { 114 | let mut scene_ref = &mut *scene; 115 | scene_ref.lights.push(Light::Spherical(light)); 116 | } 117 | 118 | //Don't free the scene 119 | Box::into_raw(scene); 120 | } 121 | 122 | #[no_mangle] 123 | pub extern "C" fn scene_add_directional_light(scene: *mut Scene, 124 | direction: *const Vector3, 125 | color: *const Color, 126 | intensity: f32) { 127 | if scene.is_null() || direction.is_null() || color.is_null() { 128 | return; 129 | } 130 | let mut scene = unsafe { Box::from_raw(scene) }; 131 | let light = DirectionalLight { 132 | direction: unsafe { &*direction }.normalize(), 133 | color: unsafe { &*color }.clone(), 134 | intensity: intensity, 135 | }; 136 | { 137 | let mut scene_ref = &mut *scene; 138 | scene_ref.lights.push(Light::Directional(light)); 139 | } 140 | 141 | //Don't free the scene 142 | Box::into_raw(scene); 143 | } 144 | 145 | #[no_mangle] 146 | pub extern "C" fn scene_get_json(scene: *mut Scene) -> *mut c_char { 147 | if scene.is_null() { 148 | return ptr::null_mut(); 149 | } 150 | let scene = unsafe { Box::from_raw(scene) }; 151 | 152 | let mut result = ptr::null_mut(); 153 | if let Ok(json) = serde_json::to_string(&*scene) { 154 | result = CString::new(json).unwrap().into_raw(); 155 | } 156 | 157 | //Don't free the scene 158 | Box::into_raw(scene); 159 | result 160 | } 161 | 162 | #[no_mangle] 163 | pub extern "C" fn scene_render(scene: *mut Scene, 164 | block: *const ViewBlock, 165 | buffer: *mut u8, 166 | length: usize) { 167 | if scene.is_null() || block.is_null() || buffer.is_null() { 168 | return; 169 | } 170 | let scene = unsafe { Box::from_raw(scene) }; 171 | let block = unsafe { &*block }; 172 | let buffer = unsafe { slice::from_raw_parts_mut(buffer, length) }; 173 | 174 | if let Some(mut image) = image::ImageBuffer::from_raw(block.width, block.height, buffer) { 175 | raytracer::render_into(block, &*scene, &mut image); 176 | } 177 | 178 | //Don't free the scene 179 | Box::into_raw(scene); 180 | } 181 | 182 | #[no_mangle] 183 | pub extern "C" fn scene_free(ptr: *mut Scene) { 184 | if ptr.is_null() { 185 | return; 186 | } 187 | unsafe { 188 | Box::from_raw(ptr); 189 | } 190 | } 191 | 192 | #[no_mangle] 193 | pub extern "C" fn string_free(ptr: *mut c_char) { 194 | if ptr.is_null() { 195 | return; 196 | } 197 | unsafe { 198 | CString::from_raw(ptr); 199 | } 200 | } 201 | 202 | pub enum CColoration { 203 | CColor { color: Color }, 204 | CTexture { path: PathBuf }, 205 | } 206 | impl CColoration { 207 | fn to_rust(&self) -> Option { 208 | match *self { 209 | CColoration::CColor { ref color } => Some(Coloration::Color(color.clone())), 210 | CColoration::CTexture { ref path } => { 211 | if let Ok(texture) = image::open(path.clone()) { 212 | Some(Coloration::Texture(Texture { 213 | path: path.clone(), 214 | texture: texture, 215 | })) 216 | } else { 217 | None 218 | } 219 | } 220 | } 221 | } 222 | } 223 | 224 | #[no_mangle] 225 | pub extern "C" fn coloration_color_new(red: f32, green: f32, blue: f32) -> *mut CColoration { 226 | let coloration = Box::new(CColoration::CColor { 227 | color: Color { 228 | red: red, 229 | green: green, 230 | blue: blue, 231 | }, 232 | }); 233 | Box::into_raw(coloration) 234 | } 235 | 236 | #[no_mangle] 237 | pub extern "C" fn coloration_texture_new(s: *const c_char) -> *mut CColoration { 238 | if s.is_null() { 239 | return ptr::null_mut(); 240 | } 241 | let c_str = unsafe { CStr::from_ptr(s) }; 242 | if let Ok(str) = c_str.to_str() { 243 | let coloration = Box::new(CColoration::CTexture { path: PathBuf::from(str) }); 244 | Box::into_raw(coloration) 245 | } else { 246 | return ptr::null_mut(); 247 | } 248 | } 249 | 250 | #[no_mangle] 251 | pub extern "C" fn coloration_free(ptr: *mut CColoration) { 252 | if ptr.is_null() { 253 | return; 254 | } 255 | unsafe { 256 | Box::from_raw(ptr); 257 | } 258 | } 259 | 260 | #[no_mangle] 261 | pub extern "C" fn surfacetype_diffuse_new() -> *mut SurfaceType { 262 | let surface = Box::new(SurfaceType::Diffuse); 263 | Box::into_raw(surface) 264 | } 265 | 266 | #[no_mangle] 267 | pub extern "C" fn surfacetype_reflective_new(reflectivity: f32) -> *mut SurfaceType { 268 | let surface = Box::new(SurfaceType::Reflective { reflectivity: reflectivity }); 269 | Box::into_raw(surface) 270 | } 271 | 272 | #[no_mangle] 273 | pub extern "C" fn surfacetype_refractive_new(index: f32, transparency: f32) -> *mut SurfaceType { 274 | let surface = Box::new(SurfaceType::Refractive { 275 | index: index, 276 | transparency: transparency, 277 | }); 278 | Box::into_raw(surface) 279 | } 280 | 281 | #[no_mangle] 282 | pub extern "C" fn surfacetype_free(ptr: *mut SurfaceType) { 283 | if ptr.is_null() { 284 | return; 285 | } 286 | unsafe { 287 | Box::from_raw(ptr); 288 | } 289 | } 290 | 291 | #[repr(C)] 292 | pub struct CMaterial { 293 | coloration: *const CColoration, 294 | surface: *const SurfaceType, 295 | albedo: f32, 296 | } 297 | impl CMaterial { 298 | pub fn to_rust(&self) -> Option { 299 | if self.coloration.is_null() || self.surface.is_null() { 300 | return None; 301 | } 302 | if let Some(coloration) = unsafe { &*self.coloration }.to_rust() { 303 | Some(Material { 304 | coloration: coloration, 305 | albedo: self.albedo, 306 | surface: unsafe { &*self.surface }.clone(), 307 | }) 308 | } else { 309 | None 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.png 3 | -------------------------------------------------------------------------------- /python/raytracer.py: -------------------------------------------------------------------------------- 1 | from cffi import FFI 2 | from PIL import Image 3 | 4 | ffi = FFI() 5 | ffi.cdef(""" 6 | typedef struct { 7 | double x, y, z; 8 | } point_t; 9 | 10 | typedef struct { 11 | double x, y, z; 12 | } vector_t; 13 | 14 | typedef struct { 15 | float red, green, blue; 16 | } color_t; 17 | 18 | typedef void* coloration; 19 | coloration coloration_color_new(float red, float green, float blue); 20 | coloration coloration_texture_new(char *path); 21 | void coloration_free(coloration); 22 | 23 | typedef void* surfacetype; 24 | surfacetype surfacetype_diffuse_new(); 25 | surfacetype surfacetype_reflective_new(float reflectivity); 26 | surfacetype surfacetype_refractive_new(float index, float transparency); 27 | void surfacetype_free(surfacetype); 28 | 29 | typedef struct { 30 | coloration coloration; 31 | surfacetype surface; 32 | float albedo; 33 | } material_t; 34 | 35 | typedef struct { 36 | uint32_t x, y, width, height; 37 | } block_t; 38 | 39 | typedef void* scene; 40 | scene scene_new(uint32_t width, uint32_t height, 41 | double fov, double shadow_bias, uint32_t max_recursion_depth); 42 | scene scene_from_json(char *buffer); 43 | void scene_add_sphere(scene, const point_t *center, double radius, 44 | const material_t *material); 45 | void scene_add_plane(scene, const point_t *origin, const vector_t *normal, 46 | const material_t *material); 47 | void scene_add_spherical_light(scene, const point_t *position, 48 | const color_t *color, float intensity); 49 | void scene_add_directional_light(scene, const vector_t *direction, 50 | const color_t *color, float intensity); 51 | void scene_render(scene, const block_t *block, char *buffer, size_t length); 52 | char *scene_get_json(scene); 53 | void scene_free(scene); 54 | void string_free(char *string); 55 | """) 56 | 57 | C = ffi.dlopen("./../raytracer/ffi/target/release/raytracer_ffi.dll") 58 | 59 | def point(x, y, z): 60 | point = ffi.new("point_t *") 61 | point.x = x 62 | point.y = y 63 | point.z = z 64 | return point 65 | 66 | def vector(x, y, z): 67 | vector = ffi.new("vector_t *") 68 | vector.x = x 69 | vector.y = y 70 | vector.z = z 71 | return vector 72 | 73 | def color(red, green, blue): 74 | color = ffi.new("color_t *") 75 | color.red = red 76 | color.green = green 77 | color.blue = blue 78 | return color 79 | 80 | def material(coloration, surface, albedo): 81 | material = ffi.new("material_t *") 82 | material.coloration = coloration.get_raw() 83 | material.surface = surface.get_raw() 84 | material.albedo = albedo 85 | return material 86 | 87 | def block(x, y, width, height): 88 | block = ffi.new("block_t *") 89 | block.x = x 90 | block.y = y 91 | block.width = width 92 | block.height = height 93 | return block 94 | 95 | class Scene(object): 96 | def __init__(self, width, height, obj): 97 | self.__x = 0 98 | self.__y = 0 99 | self.__width = width 100 | self.__height = height 101 | self.__obj = obj 102 | 103 | @property 104 | def width(self): 105 | return self.__width 106 | 107 | @property 108 | def height(self): 109 | return self.__height 110 | 111 | def __enter__(self): 112 | return self 113 | 114 | def __exit__(self, exc_type, exc_value, traceback): 115 | C.scene_free(self.__obj) 116 | self.__obj = None 117 | 118 | def add_sphere(self, center, radius, material): 119 | C.scene_add_sphere(self.__obj, center, radius, material) 120 | 121 | def add_plane(self, origin, normal, material): 122 | C.scene_add_plane(self.__obj, origin, normal, material) 123 | 124 | def add_spherical_light(self, position, color, intensity): 125 | C.scene_add_spherical_light(self.__obj, position, color, intensity) 126 | 127 | def add_directional_light(self, direction, color, intensity): 128 | C.scene_add_directional_light(self.__obj, direction, color, intensity) 129 | 130 | def set_viewport(self, x, y, width, height): 131 | self.__x = x 132 | self.__y = y 133 | self.__width = width 134 | self.__height = height 135 | 136 | def render_image(self): 137 | pixel_format = "RGBA" #The raytracer only supports one format 138 | return Image.frombuffer(pixel_format, (self.__width, self.__height), 139 | self.render_bytes(), "raw", pixel_format, 0, 1) 140 | 141 | def render_bytes(self): 142 | bytes_per_pixel = 4 143 | buffer_len = self.__width * self.__height * bytes_per_pixel 144 | buffer = ffi.new("char[]", buffer_len) 145 | view_block = block(self.__x, self.__y, self.__width, self.__height) 146 | C.scene_render(self.__obj, view_block, buffer, buffer_len) 147 | return ffi.buffer(buffer) 148 | 149 | def get_json(self): 150 | json_raw = C.scene_get_json(self.__obj) 151 | try: 152 | json_str = ffi.string(json_raw) 153 | return json_str 154 | finally: 155 | C.string_free(json_raw) 156 | 157 | @staticmethod 158 | def from_json(json): 159 | c_json = ffi.new("char[]", json) 160 | obj = C.scene_from_json(c_json) 161 | return Scene(None, None, obj) 162 | 163 | @staticmethod 164 | def create(width, height, fov, shadow_bias, max_recursion_depth): 165 | obj = C.scene_new(width, height, fov, shadow_bias, max_recursion_depth) 166 | return Scene(width, height, obj) 167 | 168 | class Coloration(object): 169 | @staticmethod 170 | def color(red, green, blue): 171 | coloration = C.coloration_color_new(red, green, blue) 172 | return Coloration(coloration) 173 | 174 | @staticmethod 175 | def texture(path): 176 | c_path = ffi.new("char[]", str(path).encode()) 177 | coloration = C.coloration_texture_new(c_path) 178 | return Coloration(coloration) 179 | 180 | def __init__(self, obj): 181 | self.__obj = obj; 182 | 183 | def __enter__(self): 184 | return self 185 | 186 | def __exit__(self, exc_type, exc_value, traceback): 187 | C.coloration_free(self.__obj) 188 | self.__obj = None 189 | 190 | def get_raw(self): 191 | return self.__obj 192 | 193 | class SurfaceType(object): 194 | @staticmethod 195 | def diffuse(): 196 | surfacetype = C.surfacetype_diffuse_new(); 197 | return SurfaceType(surfacetype) 198 | 199 | @staticmethod 200 | def reflective(reflectivity): 201 | surfacetype = C.surfacetype_reflective_new(reflectivity); 202 | return SurfaceType(surfacetype) 203 | 204 | @staticmethod 205 | def refractive(index, transparency): 206 | surfacetype = C.surfacetype_refractive_new(index, transparency); 207 | return SurfaceType(surfacetype) 208 | 209 | def __init__(self, obj): 210 | self.__obj = obj; 211 | 212 | def __enter__(self): 213 | return self 214 | 215 | def __exit__(self, exc_type, exc_value, traceback): 216 | C.surfacetype_free(self.__obj) 217 | self.__obj = None 218 | 219 | def get_raw(self): 220 | return self.__obj 221 | -------------------------------------------------------------------------------- /python/runner.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import raytracer as rt 3 | 4 | with rt.Scene.create(800, 800, 45.0, 1e-13, 10) as scene, \ 5 | rt.Coloration.color(1.0, 1.0, 1.0) as white, \ 6 | rt.Coloration.color(1.0, 0, 0) as red, \ 7 | rt.Coloration.color(0, 1.0, 0) as green, \ 8 | rt.Coloration.color(0, 0, 1.0) as blue, \ 9 | rt.Coloration.color(0.3, 0.3, 0.3) as gray, \ 10 | rt.Coloration.texture("../app/scenes/checkerboard.png") as checkerboard, \ 11 | rt.SurfaceType.diffuse() as diffuse, \ 12 | rt.SurfaceType.reflective(0.95) as chrome, \ 13 | rt.SurfaceType.reflective(0.45) as gloss, \ 14 | rt.SurfaceType.refractive(1.5, 0.5) as glass: 15 | 16 | colorations = itertools.cycle([white, red, green, blue, checkerboard]) 17 | surfaces = itertools.cycle([diffuse, chrome, glass, gloss]) 18 | 19 | for y in range(-2, 3): 20 | for x in range(-2, 3): 21 | scene.add_sphere( 22 | rt.point(x, y, -5.0), 23 | 0.4, 24 | rt.material(colorations.next(), surfaces.next(), 0.18) 25 | ) 26 | 27 | scene.add_plane( 28 | rt.point(0.0, 0.0, -10.0), 29 | rt.vector(0.0, 0.0, -1.0), 30 | rt.material(gray, diffuse, 0.01)) 31 | scene.add_spherical_light( 32 | rt.point(0.0, 0.0, -7.5), 33 | rt.color(0.25, 1.0, 0.25), 34 | 10000) 35 | scene.add_directional_light( 36 | rt.vector(0.0, 0.0, -1.0), 37 | rt.color(1.0, 1.0, 1.0), 38 | 5 39 | ) 40 | scene.render_image().save("temp2.png") 41 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | extern crate image; 4 | extern crate serde; 5 | 6 | pub mod scene; 7 | pub mod vector; 8 | pub mod point; 9 | mod rendering; 10 | mod matrix; 11 | 12 | use scene::Scene; 13 | use image::{DynamicImage, GenericImage, ImageBuffer, Rgba}; 14 | 15 | use rendering::{Ray, cast_ray}; 16 | 17 | #[repr(C)] 18 | #[derive(Debug)] 19 | pub struct ViewBlock { 20 | pub x: u32, 21 | pub y: u32, 22 | pub width: u32, 23 | pub height: u32, 24 | } 25 | 26 | pub fn render(block: &ViewBlock, scene: &Scene) -> DynamicImage { 27 | let mut image = DynamicImage::new_rgb8(block.width, block.height); 28 | for y in 0..block.height { 29 | for x in 0..block.width { 30 | let ray = Ray::create_prime(x + block.x, y + block.y, scene); 31 | image.put_pixel(x, y, cast_ray(scene, &ray, 0).to_rgba()); 32 | } 33 | } 34 | image 35 | } 36 | 37 | pub fn render_into(block: &ViewBlock, 38 | scene: &Scene, 39 | image: &mut ImageBuffer, &mut [u8]>) { 40 | for y in 0..block.height { 41 | for x in 0..block.width { 42 | let ray = Ray::create_prime(x + block.x, y + block.y, scene); 43 | image.put_pixel(x, y, cast_ray(scene, &ray, 0).to_rgba()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/matrix.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Mul, Index, IndexMut}; 2 | use point::Point; 3 | use vector::Vector3; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct Matrix44 { 7 | elements: [[f64; 4]; 4], 8 | } 9 | impl Matrix44 { 10 | #[cfg_attr(rustfmt, rustfmt_skip)] 11 | pub fn identity() -> Matrix44 { 12 | Matrix44 { 13 | elements: [[1.0, 0.0, 0.0, 0.0], 14 | [0.0, 1.0, 0.0, 0.0], 15 | [0.0, 0.0, 1.0, 0.0], 16 | [0.0, 0.0, 0.0, 1.0]] 17 | } 18 | } 19 | 20 | pub fn scale_linear(s: f64) -> Matrix44 { 21 | Matrix44::scale(s, s, s) 22 | } 23 | 24 | #[cfg_attr(rustfmt, rustfmt_skip)] 25 | pub fn scale(sx: f64, sy: f64, sz: f64) -> Matrix44 { 26 | Matrix44 { 27 | elements: [[ sx, 0.0, 0.0, 0.0], 28 | [0.0, sy, 0.0, 0.0], 29 | [0.0, 0.0, sz, 0.0], 30 | [0.0, 0.0, 0.0, 1.0]] 31 | } 32 | } 33 | 34 | #[cfg_attr(rustfmt, rustfmt_skip)] 35 | pub fn rotate_x(t: f64) -> Matrix44 { 36 | let sin = t.sin(); 37 | let cos = t.cos(); 38 | Matrix44 { 39 | elements: [[1.0, 0.0, 0.0, 0.0], 40 | [0.0, cos, sin, 0.0], 41 | [0.0,-sin, cos, 0.0], 42 | [0.0, 0.0, 0.0, 1.0]], 43 | } 44 | } 45 | 46 | #[cfg_attr(rustfmt, rustfmt_skip)] 47 | pub fn rotate_y(t: f64) -> Matrix44 { 48 | let sin = t.sin(); 49 | let cos = t.cos(); 50 | Matrix44 { 51 | elements: [[cos, 0.0, -sin, 0.0], 52 | [0.0, 1.0, 0.0, 0.0], 53 | [sin, 0.0, cos, 0.0], 54 | [0.0, 0.0, 0.0, 1.0]], 55 | } 56 | } 57 | 58 | #[cfg_attr(rustfmt, rustfmt_skip)] 59 | pub fn rotate_z(t: f64) -> Matrix44 { 60 | let sin = t.sin(); 61 | let cos = t.cos(); 62 | Matrix44 { 63 | elements: [[cos, sin, 0.0, 0.0], 64 | [-sin, cos, 0.0, 0.0], 65 | [0.0, 0.0, 1.0, 0.0], 66 | [0.0, 0.0, 0.0, 1.0]], 67 | } 68 | } 69 | 70 | #[cfg_attr(rustfmt, rustfmt_skip)] 71 | pub fn translate(tx: f64, ty:f64, tz: f64) -> Matrix44 { 72 | Matrix44 { 73 | elements: [[0.0, 0.0, 0.0, tx], 74 | [0.0, 0.0, 0.0, ty], 75 | [0.0, 0.0, 0.0, tz], 76 | [0.0, 0.0, 0.0, 0.0]], 77 | } 78 | } 79 | 80 | pub fn inverse(&self) -> Matrix44 { 81 | let mut s = Matrix44::identity(); 82 | let mut t = self.clone(); 83 | // Forward elimination 84 | for i in 0..3 { 85 | let mut pivot = i; 86 | let mut pivotsize = t[i][i].abs(); 87 | for j in (i + 1)..4 { 88 | let tmp = t[j][i].abs(); 89 | if tmp > pivotsize { 90 | pivot = j; 91 | pivotsize = tmp; 92 | } 93 | } 94 | 95 | if pivotsize == 0.0 { 96 | return Matrix44::identity(); 97 | } 98 | if pivot != i { 99 | for j in 0..4 { 100 | let mut tmp: f64; 101 | 102 | tmp = t[i][j]; 103 | t[i][j] = t[pivot][j]; 104 | t[pivot][j] = tmp; 105 | 106 | tmp = s[i][j]; 107 | s[i][j] = s[pivot][j]; 108 | s[pivot][j] = tmp; 109 | } 110 | } 111 | for j in (i + 1)..4 { 112 | let f = t[j][i] / t[i][i]; 113 | 114 | for k in 0..4 { 115 | t[j][k] -= f * t[i][k]; 116 | s[j][k] -= f * s[i][k]; 117 | } 118 | } 119 | } 120 | // Backward substitution 121 | for i in (0..4).rev() { 122 | let mut f: f64 = t[i][i]; 123 | 124 | if f == 0.0 { 125 | // Cannot invert singular matrix 126 | return Matrix44::identity(); 127 | } 128 | 129 | for j in 0..4 { 130 | t[i][j] /= f; 131 | s[i][j] /= f; 132 | } 133 | 134 | for j in 0..i { 135 | f = t[j][i]; 136 | 137 | for k in 0..4 { 138 | t[j][k] -= f * t[i][k]; 139 | s[j][k] -= f * s[i][k]; 140 | } 141 | } 142 | } 143 | 144 | return s; 145 | } 146 | } 147 | impl Index for Matrix44 { 148 | type Output = [f64; 4]; 149 | 150 | fn index(&self, idx: usize) -> &[f64; 4] { 151 | &self.elements[idx] 152 | } 153 | } 154 | impl IndexMut for Matrix44 { 155 | fn index_mut(&mut self, idx: usize) -> &mut [f64; 4] { 156 | &mut self.elements[idx] 157 | } 158 | } 159 | impl Mul for Matrix44 { 160 | type Output = Matrix44; 161 | 162 | #[cfg_attr(rustfmt, rustfmt_skip)] 163 | fn mul(self, other: Matrix44) -> Matrix44 { 164 | let mut result = Matrix44::identity(); 165 | for i in 0..4 { 166 | for j in 0..4 { 167 | result[i][j] = self[i][0] * other[0][j] + 168 | self[i][1] * other[1][j] + 169 | self[i][2] * other[2][j] + 170 | self[i][3] * other[3][j]; 171 | } 172 | } 173 | result 174 | } 175 | } 176 | impl Mul for Matrix44 { 177 | type Output = Point; 178 | 179 | fn mul(self, other: Point) -> Point { 180 | //Going to just ignore w for now. 181 | Point { 182 | x: other.x * self[0][0] + other.y * self[1][0] + other.z * self[2][0] + self[3][0], 183 | y: other.x * self[0][1] + other.y * self[1][1] + other.z * self[2][1] + self[3][1], 184 | z: other.x * self[0][2] + other.y * self[1][2] + other.z * self[2][2] + self[3][2], 185 | } 186 | } 187 | } 188 | impl Mul for Point { 189 | type Output = Point; 190 | 191 | fn mul(self, other: Matrix44) -> Point { 192 | other * self 193 | } 194 | } 195 | 196 | impl Mul for Matrix44 { 197 | type Output = Vector3; 198 | 199 | fn mul(self, other: Vector3) -> Vector3 { 200 | Vector3 { 201 | x: other.x * self[0][0] + other.y * self[1][0] + other.z * self[2][0], 202 | y: other.x * self[0][1] + other.y * self[1][1] + other.z * self[2][1], 203 | z: other.x * self[0][2] + other.y * self[1][2] + other.z * self[2][2], 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/point.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Sub}; 2 | use vector::Vector3; 3 | 4 | #[derive(Copy, Clone, Debug, Deserialize, Serialize)] 5 | #[repr(C)] 6 | pub struct Point { 7 | pub x: f64, 8 | pub y: f64, 9 | pub z: f64, 10 | } 11 | impl Point { 12 | pub fn zero() -> Point { 13 | Point::from_one(0.0) 14 | } 15 | 16 | pub fn from_one(v: f64) -> Point { 17 | Point { x: v, y: v, z: v } 18 | } 19 | } 20 | 21 | impl Add for Point { 22 | type Output = Point; 23 | 24 | fn add(self, other: Vector3) -> Point { 25 | Point { 26 | x: self.x + other.x, 27 | y: self.y + other.y, 28 | z: self.z + other.z, 29 | } 30 | } 31 | } 32 | impl Add for Vector3 { 33 | type Output = Point; 34 | 35 | fn add(self, other: Point) -> Point { 36 | other + self 37 | } 38 | } 39 | 40 | impl Sub for Point { 41 | type Output = Point; 42 | 43 | fn sub(self, other: Vector3) -> Point { 44 | Point { 45 | x: self.x - other.x, 46 | y: self.y - other.y, 47 | z: self.z - other.z, 48 | } 49 | } 50 | } 51 | impl Sub for Vector3 { 52 | type Output = Point; 53 | 54 | fn sub(self, other: Point) -> Point { 55 | other - self 56 | } 57 | } 58 | 59 | impl Sub for Point { 60 | type Output = Vector3; 61 | 62 | fn sub(self, other: Point) -> Vector3 { 63 | Vector3 { 64 | x: self.x - other.x, 65 | y: self.y - other.y, 66 | z: self.z - other.z, 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/rendering.rs: -------------------------------------------------------------------------------- 1 | use point::Point; 2 | use vector::Vector3; 3 | use scene::{Scene, Element, Sphere, Plane, Color, Intersection, SurfaceType}; 4 | use std::f32; 5 | 6 | #[derive(Debug)] 7 | pub struct Ray { 8 | pub origin: Point, 9 | pub direction: Vector3, 10 | } 11 | 12 | impl Ray { 13 | pub fn create_prime(x: u32, y: u32, scene: &Scene) -> Ray { 14 | assert!(scene.width >= scene.height); 15 | let fov_adjustment = (scene.fov.to_radians() / 2.0).tan(); 16 | let aspect_ratio = (scene.width as f64) / (scene.height as f64); 17 | let sensor_x = ((((x as f64 + 0.5) / scene.width as f64) * 2.0 - 1.0) * aspect_ratio) * 18 | fov_adjustment; 19 | let sensor_y = (1.0 - ((y as f64 + 0.5) / scene.height as f64) * 2.0) * fov_adjustment; 20 | 21 | Ray { 22 | origin: Point::zero(), 23 | direction: Vector3 { 24 | x: sensor_x, 25 | y: sensor_y, 26 | z: -1.0, 27 | } 28 | .normalize(), 29 | } 30 | } 31 | 32 | pub fn create_reflection(normal: Vector3, 33 | incident: Vector3, 34 | intersection: Point, 35 | bias: f64) 36 | -> Ray { 37 | Ray { 38 | origin: intersection + (normal * bias), 39 | direction: incident - (2.0 * incident.dot(&normal) * normal), 40 | } 41 | } 42 | 43 | pub fn create_transmission(normal: Vector3, 44 | incident: Vector3, 45 | intersection: Point, 46 | bias: f64, 47 | index: f32) 48 | -> Option { 49 | let mut ref_n = normal; 50 | let mut eta_t = index as f64; 51 | let mut eta_i = 1.0; 52 | let mut i_dot_n = incident.dot(&normal); 53 | if i_dot_n < 0.0 { 54 | //Outside the surface 55 | i_dot_n = -i_dot_n; 56 | } else { 57 | //Inside the surface; invert the normal and swap the indices of refraction 58 | ref_n = -normal; 59 | eta_i = eta_t; 60 | eta_t = 1.0; 61 | } 62 | 63 | let eta = eta_i / eta_t; 64 | let k = 1.0 - (eta * eta) * (1.0 - i_dot_n * i_dot_n); 65 | if k < 0.0 { 66 | None 67 | } else { 68 | Some(Ray { 69 | origin: intersection + (ref_n * -bias), 70 | direction: (incident + i_dot_n * ref_n) * eta - ref_n * k.sqrt(), 71 | }) 72 | } 73 | } 74 | } 75 | 76 | #[derive(Debug)] 77 | pub struct TextureCoords { 78 | pub x: f32, 79 | pub y: f32, 80 | } 81 | 82 | pub trait Intersectable { 83 | fn intersect(&self, ray: &Ray) -> Option; 84 | 85 | fn surface_normal(&self, hit_point: &Point) -> Vector3; 86 | fn texture_coords(&self, hit_point: &Point) -> TextureCoords; 87 | } 88 | 89 | impl Intersectable for Element { 90 | fn intersect(&self, ray: &Ray) -> Option { 91 | match *self { 92 | Element::Sphere(ref s) => s.intersect(ray), 93 | Element::Plane(ref p) => p.intersect(ray), 94 | } 95 | } 96 | 97 | fn surface_normal(&self, hit_point: &Point) -> Vector3 { 98 | match *self { 99 | Element::Sphere(ref s) => s.surface_normal(hit_point), 100 | Element::Plane(ref p) => p.surface_normal(hit_point), 101 | } 102 | } 103 | 104 | fn texture_coords(&self, hit_point: &Point) -> TextureCoords { 105 | match *self { 106 | Element::Sphere(ref s) => s.texture_coords(hit_point), 107 | Element::Plane(ref p) => p.texture_coords(hit_point), 108 | } 109 | } 110 | } 111 | impl Intersectable for Sphere { 112 | fn intersect(&self, ray: &Ray) -> Option { 113 | let l: Vector3 = self.center - ray.origin; 114 | let adj = l.dot(&ray.direction); 115 | let d2 = l.dot(&l) - (adj * adj); 116 | let radius2 = self.radius * self.radius; 117 | if d2 > radius2 { 118 | return None; 119 | } 120 | let thc = (radius2 - d2).sqrt(); 121 | let t0 = adj - thc; 122 | let t1 = adj + thc; 123 | 124 | if t0 < 0.0 && t1 < 0.0 { 125 | None 126 | } else if t0 < 0.0 { 127 | Some(t1) 128 | } else if t1 < 0.0 { 129 | Some(t0) 130 | } else { 131 | let distance = if t0 < t1 { t0 } else { t1 }; 132 | Some(distance) 133 | } 134 | } 135 | 136 | fn surface_normal(&self, hit_point: &Point) -> Vector3 { 137 | (*hit_point - self.center).normalize() 138 | } 139 | 140 | fn texture_coords(&self, hit_point: &Point) -> TextureCoords { 141 | let hit_vec = *hit_point - self.center; 142 | TextureCoords { 143 | x: (1.0 + (hit_vec.z.atan2(hit_vec.x) as f32) / f32::consts::PI) * 0.5, 144 | y: (hit_vec.y / self.radius).acos() as f32 / f32::consts::PI, 145 | } 146 | } 147 | } 148 | impl Intersectable for Plane { 149 | fn intersect(&self, ray: &Ray) -> Option { 150 | let normal = &self.normal; 151 | let denom = normal.dot(&ray.direction); 152 | if denom > 1e-6 { 153 | let v = self.origin - ray.origin; 154 | let distance = v.dot(&normal) / denom; 155 | if distance >= 0.0 { 156 | return Some(distance); 157 | } 158 | } 159 | None 160 | } 161 | 162 | fn surface_normal(&self, _: &Point) -> Vector3 { 163 | -self.normal 164 | } 165 | 166 | fn texture_coords(&self, hit_point: &Point) -> TextureCoords { 167 | let mut x_axis = self.normal.cross(&Vector3 { 168 | x: 0.0, 169 | y: 0.0, 170 | z: 1.0, 171 | }); 172 | if x_axis.length() == 0.0 { 173 | x_axis = self.normal.cross(&Vector3 { 174 | x: 0.0, 175 | y: 1.0, 176 | z: 0.0, 177 | }); 178 | } 179 | let y_axis = self.normal.cross(&x_axis); 180 | let hit_vec = *hit_point - self.origin; 181 | 182 | TextureCoords { 183 | x: hit_vec.dot(&x_axis) as f32, 184 | y: hit_vec.dot(&y_axis) as f32, 185 | } 186 | } 187 | } 188 | 189 | const BLACK: Color = Color { 190 | red: 0.0, 191 | green: 0.0, 192 | blue: 0.0, 193 | }; 194 | 195 | fn shade_diffuse(scene: &Scene, 196 | element: &Element, 197 | hit_point: Point, 198 | surface_normal: Vector3) 199 | -> Color { 200 | let texture_coords = element.texture_coords(&hit_point); 201 | let mut color = BLACK; 202 | for light in &scene.lights { 203 | let direction_to_light = light.direction_from(&hit_point); 204 | 205 | let shadow_ray = Ray { 206 | origin: hit_point + (surface_normal * scene.shadow_bias), 207 | direction: direction_to_light, 208 | }; 209 | let shadow_intersection = scene.trace(&shadow_ray); 210 | let in_light = shadow_intersection.is_none() || 211 | shadow_intersection.unwrap().distance > light.distance(&hit_point); 212 | 213 | let light_intensity = if in_light { 214 | light.intensity(&hit_point) 215 | } else { 216 | 0.0 217 | }; 218 | let material = element.material(); 219 | let light_power = (surface_normal.dot(&direction_to_light) as f32).max(0.0) * 220 | light_intensity; 221 | let light_reflected = material.albedo / f32::consts::PI; 222 | 223 | let light_color = light.color() * light_power * light_reflected; 224 | color = color + (material.coloration.color(&texture_coords) * light_color); 225 | } 226 | color.clamp() 227 | } 228 | 229 | fn get_color(scene: &Scene, ray: &Ray, intersection: &Intersection, depth: u32) -> Color { 230 | let hit = ray.origin + (ray.direction * intersection.distance); 231 | let normal = intersection.element.surface_normal(&hit); 232 | 233 | let material = intersection.element.material(); 234 | match material.surface { 235 | SurfaceType::Diffuse => shade_diffuse(scene, intersection.element, hit, normal), 236 | SurfaceType::Reflective { reflectivity } => { 237 | let mut color = shade_diffuse(scene, intersection.element, hit, normal); 238 | let reflection_ray = 239 | Ray::create_reflection(normal, ray.direction, hit, scene.shadow_bias); 240 | color = color * (1.0 - reflectivity); 241 | color = color + (cast_ray(scene, &reflection_ray, depth + 1) * reflectivity); 242 | color 243 | } 244 | SurfaceType::Refractive { index, transparency } => { 245 | let mut refraction_color = BLACK; 246 | let kr = fresnel(ray.direction, normal, index) as f32; 247 | let surface_color = material.coloration 248 | .color(&intersection.element.texture_coords(&hit)); 249 | 250 | if kr < 1.0 { 251 | let transmission_ray = 252 | Ray::create_transmission(normal, ray.direction, hit, scene.shadow_bias, index) 253 | .unwrap(); 254 | refraction_color = cast_ray(scene, &transmission_ray, depth + 1); 255 | } 256 | 257 | let reflection_ray = 258 | Ray::create_reflection(normal, ray.direction, hit, scene.shadow_bias); 259 | let reflection_color = cast_ray(scene, &reflection_ray, depth + 1); 260 | let mut color = reflection_color * kr + refraction_color * (1.0 - kr); 261 | color = color * transparency * surface_color; 262 | color 263 | } 264 | } 265 | } 266 | 267 | fn fresnel(incident: Vector3, normal: Vector3, index: f32) -> f64 { 268 | let i_dot_n = incident.dot(&normal); 269 | let mut eta_i = 1.0; 270 | let mut eta_t = index as f64; 271 | if i_dot_n > 0.0 { 272 | eta_i = eta_t; 273 | eta_t = 1.0; 274 | } 275 | 276 | let sin_t = eta_i / eta_t * (1.0 - i_dot_n * i_dot_n).max(0.0).sqrt(); 277 | if sin_t > 1.0 { 278 | //Total internal reflection 279 | return 1.0; 280 | } else { 281 | let cos_t = (1.0 - sin_t * sin_t).max(0.0).sqrt(); 282 | let cos_i = cos_t.abs(); 283 | let r_s = ((eta_t * cos_i) - (eta_i * cos_t)) / ((eta_t * cos_i) + (eta_i * cos_t)); 284 | let r_p = ((eta_i * cos_i) - (eta_t * cos_t)) / ((eta_i * cos_i) + (eta_t * cos_t)); 285 | return (r_s * r_s + r_p * r_p) / 2.0; 286 | } 287 | } 288 | 289 | pub fn cast_ray(scene: &Scene, ray: &Ray, depth: u32) -> Color { 290 | if depth >= scene.max_recursion_depth { 291 | return BLACK; 292 | } 293 | 294 | let intersection = scene.trace(&ray); 295 | intersection.map(|i| get_color(scene, &ray, &i, depth)) 296 | .unwrap_or(BLACK) 297 | } 298 | -------------------------------------------------------------------------------- /src/scene.rs: -------------------------------------------------------------------------------- 1 | use point::Point; 2 | use vector::Vector3; 3 | use rendering::{Intersectable, Ray, TextureCoords}; 4 | use std::ops::{Add, Mul}; 5 | use std::path::PathBuf; 6 | use image; 7 | use image::{DynamicImage, GenericImage, Pixel, Rgba}; 8 | use std::fmt; 9 | use serde::{Deserialize, Deserializer}; 10 | 11 | const GAMMA: f32 = 2.2; 12 | 13 | fn gamma_encode(linear: f32) -> f32 { 14 | linear.powf(1.0 / GAMMA) 15 | } 16 | 17 | fn gamma_decode(encoded: f32) -> f32 { 18 | encoded.powf(GAMMA) 19 | } 20 | 21 | #[derive(Deserialize, Serialize, Debug, Clone, Copy)] 22 | #[repr(C)] 23 | pub struct Color { 24 | pub red: f32, 25 | pub green: f32, 26 | pub blue: f32, 27 | } 28 | impl Color { 29 | pub fn clamp(&self) -> Color { 30 | Color { 31 | red: self.red.min(1.0).max(0.0), 32 | blue: self.blue.min(1.0).max(0.0), 33 | green: self.green.min(1.0).max(0.0), 34 | } 35 | } 36 | 37 | pub fn to_rgba(&self) -> Rgba { 38 | Rgba::from_channels( 39 | (gamma_encode(self.red) * 255.0) as u8, 40 | (gamma_encode(self.green) * 255.0) as u8, 41 | (gamma_encode(self.blue) * 255.0) as u8, 42 | 255, 43 | ) 44 | } 45 | 46 | pub fn from_rgba(rgba: Rgba) -> Color { 47 | Color { 48 | red: gamma_decode((rgba.data[0] as f32) / 255.0), 49 | green: gamma_decode((rgba.data[1] as f32) / 255.0), 50 | blue: gamma_decode((rgba.data[2] as f32) / 255.0), 51 | } 52 | } 53 | } 54 | impl Mul for Color { 55 | type Output = Color; 56 | 57 | fn mul(self, other: Color) -> Color { 58 | Color { 59 | red: self.red * other.red, 60 | blue: self.blue * other.blue, 61 | green: self.green * other.green, 62 | } 63 | } 64 | } 65 | impl Mul for Color { 66 | type Output = Color; 67 | 68 | fn mul(self, other: f32) -> Color { 69 | Color { 70 | red: self.red * other, 71 | blue: self.blue * other, 72 | green: self.green * other, 73 | } 74 | } 75 | } 76 | impl Mul for f32 { 77 | type Output = Color; 78 | fn mul(self, other: Color) -> Color { 79 | other * self 80 | } 81 | } 82 | impl Add for Color { 83 | type Output = Color; 84 | fn add(self, other: Color) -> Color { 85 | Color { 86 | red: self.red + other.red, 87 | blue: self.blue + other.blue, 88 | green: self.green + other.green, 89 | } 90 | } 91 | } 92 | 93 | #[derive(Serialize, Deserialize)] 94 | pub struct Texture { 95 | pub path: PathBuf, 96 | 97 | #[serde(skip_serializing, skip_deserializing, default = "dummy_texture")] 98 | pub texture: DynamicImage, 99 | } 100 | fn dummy_texture() -> DynamicImage { 101 | DynamicImage::new_rgb8(0, 0) 102 | } 103 | impl fmt::Debug for Texture { 104 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 105 | write!(f, "Texture({:?})", self.path) 106 | } 107 | } 108 | fn load_texture(deserializer: D) -> Result 109 | where 110 | D: Deserializer, 111 | { 112 | let texture = Texture::deserialize(deserializer)?; 113 | if let Ok(img) = image::open(texture.path.clone()) { 114 | Ok(Texture { 115 | path: texture.path, 116 | texture: img, 117 | }) 118 | } else { 119 | Err(::serde::de::Error::custom(format!( 120 | "Unable to open texture file: {:?}", 121 | texture.path 122 | ))) 123 | } 124 | } 125 | 126 | #[derive(Debug, Deserialize, Serialize)] 127 | pub enum Coloration { 128 | Color(Color), 129 | Texture(#[serde(deserialize_with = "load_texture")] Texture), 130 | } 131 | 132 | fn wrap(val: f32, bound: u32) -> u32 { 133 | let signed_bound = bound as i32; 134 | let float_coord = val * bound as f32; 135 | let wrapped_coord = (float_coord as i32) % signed_bound; 136 | if wrapped_coord < 0 { 137 | (wrapped_coord + signed_bound) as u32 138 | } else { 139 | wrapped_coord as u32 140 | } 141 | } 142 | 143 | impl Coloration { 144 | pub fn color(&self, coords: &TextureCoords) -> Color { 145 | match *self { 146 | Coloration::Color(ref c) => c.clone(), 147 | Coloration::Texture(ref texture) => { 148 | let tex_x = wrap(coords.x, texture.texture.width()); 149 | let tex_y = wrap(coords.y, texture.texture.height()); 150 | 151 | Color::from_rgba(texture.texture.get_pixel(tex_x, tex_y)) 152 | } 153 | } 154 | } 155 | } 156 | 157 | #[derive(Deserialize, Serialize, Debug, Clone)] 158 | pub enum SurfaceType { 159 | Diffuse, 160 | Reflective { reflectivity: f32 }, 161 | Refractive { index: f32, transparency: f32 }, 162 | } 163 | 164 | #[derive(Deserialize, Serialize, Debug)] 165 | pub struct Material { 166 | pub coloration: Coloration, 167 | pub albedo: f32, 168 | pub surface: SurfaceType, 169 | } 170 | 171 | #[derive(Deserialize, Serialize, Debug)] 172 | pub struct Sphere { 173 | pub center: Point, 174 | pub radius: f64, 175 | pub material: Material, 176 | } 177 | 178 | #[derive(Deserialize, Serialize, Debug)] 179 | pub struct Plane { 180 | pub origin: Point, 181 | #[serde(deserialize_with = "Vector3::deserialize_normalized")] 182 | pub normal: Vector3, 183 | pub material: Material, 184 | } 185 | 186 | #[derive(Deserialize, Serialize, Debug)] 187 | pub enum Element { 188 | Sphere(Sphere), 189 | Plane(Plane), 190 | } 191 | impl Element { 192 | pub fn material(&self) -> &Material { 193 | match *self { 194 | Element::Sphere(ref s) => &s.material, 195 | Element::Plane(ref p) => &p.material, 196 | } 197 | } 198 | 199 | pub fn material_mut(&mut self) -> &mut Material { 200 | match *self { 201 | Element::Sphere(ref mut s) => &mut s.material, 202 | Element::Plane(ref mut p) => &mut p.material, 203 | } 204 | } 205 | } 206 | 207 | #[derive(Deserialize, Serialize, Debug)] 208 | pub struct DirectionalLight { 209 | #[serde(deserialize_with = "Vector3::deserialize_normalized")] 210 | pub direction: Vector3, 211 | pub color: Color, 212 | pub intensity: f32, 213 | } 214 | 215 | #[derive(Deserialize, Serialize, Debug)] 216 | pub struct SphericalLight { 217 | pub position: Point, 218 | pub color: Color, 219 | pub intensity: f32, 220 | } 221 | 222 | #[derive(Deserialize, Serialize, Debug)] 223 | pub enum Light { 224 | Directional(DirectionalLight), 225 | Spherical(SphericalLight), 226 | } 227 | impl Light { 228 | pub fn color(&self) -> Color { 229 | match *self { 230 | Light::Directional(ref d) => d.color, 231 | Light::Spherical(ref s) => s.color, 232 | } 233 | } 234 | 235 | pub fn direction_from(&self, hit_point: &Point) -> Vector3 { 236 | match *self { 237 | Light::Directional(ref d) => -d.direction, 238 | Light::Spherical(ref s) => (s.position - *hit_point).normalize(), 239 | } 240 | } 241 | 242 | pub fn intensity(&self, hit_point: &Point) -> f32 { 243 | match *self { 244 | Light::Directional(ref d) => d.intensity, 245 | Light::Spherical(ref s) => { 246 | let r2 = (s.position - *hit_point).norm() as f32; 247 | s.intensity / (4.0 * ::std::f32::consts::PI * r2) 248 | } 249 | } 250 | } 251 | 252 | pub fn distance(&self, hit_point: &Point) -> f64 { 253 | match *self { 254 | Light::Directional(_) => ::std::f64::INFINITY, 255 | Light::Spherical(ref s) => (s.position - *hit_point).length(), 256 | } 257 | } 258 | } 259 | 260 | #[derive(Deserialize, Serialize, Debug)] 261 | pub struct Scene { 262 | pub width: u32, 263 | pub height: u32, 264 | pub fov: f64, 265 | pub elements: Vec, 266 | pub lights: Vec, 267 | 268 | pub shadow_bias: f64, 269 | pub max_recursion_depth: u32, 270 | } 271 | 272 | pub struct Intersection<'a> { 273 | pub distance: f64, 274 | pub element: &'a Element, 275 | 276 | //Prevent outside code from constructing this; should use the new method and check the distance. 277 | _secret: (), 278 | } 279 | impl<'a> Intersection<'a> { 280 | pub fn new<'b>(distance: f64, element: &'b Element) -> Intersection<'b> { 281 | if !distance.is_finite() { 282 | panic!("Intersection must have a finite distance."); 283 | } 284 | Intersection { 285 | distance: distance, 286 | element: element, 287 | _secret: (), 288 | } 289 | } 290 | } 291 | 292 | impl Scene { 293 | pub fn trace(&self, ray: &Ray) -> Option { 294 | self.elements 295 | .iter() 296 | .filter_map(|e| e.intersect(ray).map(|d| Intersection::new(d, e))) 297 | .min_by(|i1, i2| i1.distance.partial_cmp(&i2.distance).unwrap()) 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/vector.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Sub, Mul, Neg}; 2 | use serde::{Deserialize, Deserializer}; 3 | 4 | #[derive(Copy, Clone, Debug, Deserialize, Serialize)] 5 | #[repr(C)] 6 | pub struct Vector3 { 7 | pub x: f64, 8 | pub y: f64, 9 | pub z: f64, 10 | } 11 | impl Vector3 { 12 | pub fn zero() -> Vector3 { 13 | Vector3::from_one(0.0) 14 | } 15 | 16 | pub fn from_one(v: f64) -> Vector3 { 17 | Vector3 { x: v, y: v, z: v } 18 | } 19 | 20 | pub fn length(&self) -> f64 { 21 | self.norm().sqrt() 22 | } 23 | 24 | pub fn norm(&self) -> f64 { 25 | (self.x * self.x + self.y * self.y + self.z * self.z) 26 | } 27 | 28 | pub fn normalize(&self) -> Vector3 { 29 | let inv_len = self.length().recip(); 30 | Vector3 { 31 | x: self.x * inv_len, 32 | y: self.y * inv_len, 33 | z: self.z * inv_len, 34 | } 35 | } 36 | 37 | pub fn dot(&self, other: &Vector3) -> f64 { 38 | self.x * other.x + self.y * other.y + self.z * other.z 39 | } 40 | 41 | pub fn cross(&self, other: &Vector3) -> Vector3 { 42 | Vector3 { 43 | x: self.y * other.z - self.z * other.y, 44 | y: self.z * other.x - self.x * other.z, 45 | z: self.x * other.y - self.y * other.x, 46 | } 47 | } 48 | 49 | pub fn deserialize_normalized(deserializer: D) -> Result 50 | where D: Deserializer 51 | { 52 | let v3 = Vector3::deserialize(deserializer)?; 53 | Ok(v3.normalize()) 54 | } 55 | } 56 | 57 | impl Add for Vector3 { 58 | type Output = Vector3; 59 | 60 | fn add(self, other: Vector3) -> Vector3 { 61 | Vector3 { 62 | x: self.x + other.x, 63 | y: self.y + other.y, 64 | z: self.z + other.z, 65 | } 66 | } 67 | } 68 | 69 | impl Sub for Vector3 { 70 | type Output = Vector3; 71 | 72 | fn sub(self, other: Vector3) -> Vector3 { 73 | Vector3 { 74 | x: self.x - other.x, 75 | y: self.y - other.y, 76 | z: self.z - other.z, 77 | } 78 | } 79 | } 80 | 81 | impl Mul for Vector3 { 82 | type Output = Vector3; 83 | 84 | fn mul(self, other: Vector3) -> Vector3 { 85 | Vector3 { 86 | x: self.x * other.x, 87 | y: self.y * other.y, 88 | z: self.z * other.z, 89 | } 90 | } 91 | } 92 | 93 | impl Mul for Vector3 { 94 | type Output = Vector3; 95 | 96 | fn mul(self, other: f64) -> Vector3 { 97 | Vector3 { 98 | x: self.x * other, 99 | y: self.y * other, 100 | z: self.z * other, 101 | } 102 | } 103 | } 104 | 105 | impl Mul for f64 { 106 | type Output = Vector3; 107 | 108 | fn mul(self, other: Vector3) -> Vector3 { 109 | other * self 110 | } 111 | } 112 | 113 | impl Neg for Vector3 { 114 | type Output = Vector3; 115 | 116 | fn neg(self) -> Vector3 { 117 | Vector3 { 118 | x: -self.x, 119 | y: -self.y, 120 | z: -self.z, 121 | } 122 | } 123 | } 124 | --------------------------------------------------------------------------------