diff --git a/Cargo.lock b/Cargo.lock index ab697d0..f248cc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,12 +657,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "hermit-abi" version = "0.3.3" @@ -755,12 +749,6 @@ dependencies = [ "hashbrown 0.14.2", ] -[[package]] -name = "indoc" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" - [[package]] name = "instant" version = "0.1.12" @@ -907,15 +895,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "metal" version = "0.24.0" @@ -1078,7 +1057,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -1091,7 +1070,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -1297,67 +1276,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f89dff0959d98c9758c88826cc002e2c3d0b9dfac4139711d1f30de442f1139b" -[[package]] -name = "pyo3" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" -dependencies = [ - "cfg-if", - "indoc", - "libc", - "memoffset 0.9.0", - "parking_lot", - "pyo3-build-config", - "pyo3-ffi", - "pyo3-macros", - "unindent", -] - -[[package]] -name = "pyo3-build-config" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" -dependencies = [ - "once_cell", - "target-lexicon", -] - -[[package]] -name = "pyo3-ffi" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" -dependencies = [ - "libc", - "pyo3-build-config", -] - -[[package]] -name = "pyo3-macros" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" -dependencies = [ - "proc-macro2", - "pyo3-macros-backend", - "quote", - "syn 2.0.39", -] - -[[package]] -name = "pyo3-macros-backend" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.39", -] - [[package]] name = "quote" version = "1.0.33" @@ -1459,7 +1377,6 @@ dependencies = [ "nalgebra", "pixels", "pollster", - "pyo3", "roots", "winit", "winit_input_helper", @@ -1670,12 +1587,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "target-lexicon" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" - [[package]] name = "termcolor" version = "1.4.0" @@ -1780,12 +1691,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" -[[package]] -name = "unindent" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" - [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index e6a2384..7d6b0c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ pollster = "0.3" anyhow = "1.0" nalgebra = "0.32.3" roots = "0.0.8" -pyo3 = "0.20.0" imgui = "0.11" imgui-wgpu = "0.23" diff --git a/src/camera.rs b/src/camera.rs index 7848470..cc68aba 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,9 +1,12 @@ use crate::ray::Ray; use crate::{EPSILON, INFINITY}; +use log::error; use nalgebra as nm; use nalgebra::Matrix4; +use nalgebra::Perspective3; use nalgebra::Point3; use nalgebra::Vector3; +use std::env; #[rustfmt::skip] pub const OPENGL_TO_WGPU_MATRIX: Matrix4 = Matrix4::new( @@ -35,8 +38,8 @@ impl Camera { ) -> Self { let znear = EPSILON; let zfar = INFINITY; - let matrix = self.build_view_projection_matrix(eye, target, up, aspect, fovy, znear, zfar); - let inverse = self.build_inverse_view_projection_matrix(eye, target, up, aspect, fovy, znear, zfar); + let (matrix, inverse) = + Camera::build_matrix_and_inverse(&eye, &target, &up, aspect, fovy, znear, zfar); Camera { eye, target, @@ -45,42 +48,73 @@ impl Camera { aspect, znear, zfar, + matrix, + inverse, } } - pub fn build_view_projection_matrix(eye: Point3, target: Point3, up: Vector3, aspect: f32, fovy: f32, znear: f32, zfar: f32) -> Matrix4 { + pub fn build_matrix_and_inverse( + eye: &Point3, + target: &Point3, + up: &Vector3, + aspect: f32, + fovy: f32, + znear: f32, + zfar: f32, + ) -> (Matrix4, Matrix4) { let view = Matrix4::look_at_lh(eye, target, up); - let proj = Matrix4::new_perspective(aspect, fovy,znear, zfar); - proj * view + let proj = Perspective3::new(aspect, fovy, znear, zfar); + let matrix = proj.as_matrix() * view; + let inverse = view.try_inverse().expect("No view") * proj.inverse(); + (matrix, inverse) } - pub fn build_inverse_view_projection_matrix(eye: Point3, target: Point3, up: Vector3, aspect: f32, fovy: f32, znear: f32, zfar: f32) -> Matrix4 { - let view_proj = self.build_view_projection_matrix(eye, target, up, aspect, fovy, znear, zfar); - view_proj.try_inverse().expect("Cannot invert!") - } - pub fn cast_rays(&self, width: u32, height: u32) -> Vec { - let inverse_matrix = self.build_inverse_view_projection_matrix(); - - let dx = 2.0 / width as f32; - let dy = 2.0 / height as f32; + pub fn cast_rays(&self, width: i32, height: i32) -> Vec { + let aspect = width as f64 / height as f64; + let fovy_radians = (self.fovy as f64).to_radians(); + let fovh_radians = 2.0 * ((fovy_radians / 2.0).tan() * aspect).atan(); + let view_direction = (self.target - self.eye).normalize(); // Normalize the view direction vector + let hor = view_direction.cross(&self.up).normalize(); // pointing right + let vert = view_direction.cross(&hor).normalize(); // pointing up + let h_width = 2.0 * (fovh_radians / 2.0).tan(); + let v_height = 2.0 * (fovy_radians / 2.0).tan(); + let d_hor_vec = hor * (h_width / width as f64) as f32; + let d_vert_vec = vert * (v_height / height as f64) as f32; let mut rays = Vec::with_capacity(width as usize * height as usize); - for i in 0..width { - for j in 0..height { - let x = -1.0 + i as f32 * dx; - let y = 1.0 - j as f32 * dy; - - let a = inverse_matrix.transform_point(&Point3::new(x, y, -1.0)); - let b = inverse_matrix.transform_vector(&Vector3::new(0.0, 0.0, 1.0)); - - let ray = Ray { a, b }; + for j in 0..height { + for i in 0..width { + let horizontal = (i as f32 - width as f32 / 2.0) * d_hor_vec; + let vertical = (j as f32 - height as f32 / 2.0) * d_vert_vec; + let direction = view_direction + horizontal + vertical; + let ray = Ray::new(self.eye, direction); rays.push(ray); } } rays } - pub fn cast_ray(&self, width: u32, height: u32, x: u32, y: u32) -> Ray { + + pub fn cast_ray(&self, width: i32, height: i32, x: i32, y: i32) -> Ray { + let aspect = width as f64 / height as f64; + let fovy_radians = (self.fovy as f64).to_radians(); + let fovh_radians = 2.0 * ((fovy_radians / 2.0).tan() * aspect).atan(); + let view_direction = (self.target - self.eye).normalize(); // Normalize the view direction vector let dx = 2.0 / width as f32; let dy = 2.0 / height as f32; + let hor = view_direction.cross(&self.up).normalize(); // pointing right + let vert = view_direction.cross(&hor).normalize(); // pointing up + let h_width = 2.0 * (fovh_radians / 2.0).tan(); + let v_height = 2.0 * (fovy_radians / 2.0).tan(); + let d_hor_vec = hor * (h_width / width as f64) as f32; + let d_vert_vec = vert * (v_height / height as f64) as f32; + + // Calculate the offsets for the pixel's position on the image plane + let horizontal = ((x as f32 / width as f32) - 0.5) * h_width as f32; + let vertical = ((y as f32 / height as f32) - 0.5) * v_height as f32; + + // Calculate the ray direction by summing up the components + let direction = view_direction + (horizontal * d_hor_vec) + (vertical * d_vert_vec); + + Ray::new(self.eye, direction) } } diff --git a/src/gui.rs b/src/gui.rs index e90e507..23e5d08 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -9,6 +9,8 @@ pub(crate) struct Gui { last_frame: Instant, last_cursor: Option, about_open: bool, + + pub num_rays: i32, } impl Gui { @@ -58,6 +60,7 @@ impl Gui { last_frame: Instant::now(), last_cursor: None, about_open: true, + num_rays: 8, } } @@ -93,17 +96,11 @@ impl Gui { // Draw windows and GUI elements here let mut about_open = false; ui.main_menu_bar(|| { - ui.menu("Help", || { + ui.menu("Options", || { about_open = ui.menu_item("About..."); }); }); - if about_open { - self.about_open = true; - } - - if self.about_open { - ui.show_about_window(&mut self.about_open); - } + ui.slider("Num rays", 1, 100, &mut self.num_rays); // Render Dear ImGui with WGPU let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { diff --git a/src/main.rs b/src/main.rs index 6895ad9..3beff4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,16 +5,13 @@ //Cameras -use crate::camera::Camera; -use crate::gui::Gui; -use crate::light::Light; -use crate::primitive::*; -use crate::scene::Scene; +use crate::{camera::Camera, gui::Gui, light::Light, primitive::*, ray::Ray, scene::Scene}; +use log::error; use error_iter::ErrorIter as _; -use log::error; use nalgebra::{Point3, Vector3}; use pixels::{Error, Pixels, SurfaceTexture}; +use std::env; use std::sync::Arc; use winit::dpi::LogicalSize; use winit::event::{Event, VirtualKeyCode}; @@ -30,8 +27,8 @@ mod ray; mod raytracer; mod scene; -const START_WIDTH: u32 = 640; -const START_HEIGHT: u32 = 480; +const START_WIDTH: i32 = 600; +const START_HEIGHT: i32 = 555; const BOX_SIZE: i16 = 64; const EPSILON: f32 = 1e-7; @@ -39,12 +36,16 @@ const INFINITY: f32 = 1e7; struct State { scene: Scene, - width: u32, - height: u32, + camera: Camera, + rays: Vec, + index: usize, + width: i32, + height: i32, } fn main() -> Result<(), Error> { env_logger::init(); + //Window let event_loop = EventLoop::new(); let mut input = WinitInputHelper::new(); let window = { @@ -56,22 +57,56 @@ fn main() -> Result<(), Error> { .build(&event_loop) .unwrap() }; - + //Pixel surface let mut pixels = { let window_size = window.inner_size(); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); - Pixels::new(START_WIDTH, START_HEIGHT, surface_texture)? + Pixels::new(START_WIDTH as u32, START_HEIGHT as u32, surface_texture)? }; - let mut state = State::new(START_WIDTH, START_HEIGHT); - + //Camera + let eye = Point3::new(0.0, 0.0, 3.0); + let target = Point3::new(0.0, 0.0, 0.0); + let up = Vector3::new(0.0, 1.0, 0.0); + let arc_camera = Arc::new(Camera::new( + eye, + target, + up, + 180.0, + (START_WIDTH as f32 / START_HEIGHT as f32) as f32, + )); + let camera = Camera::new( + eye, + target, + up, + 180.0, + (START_WIDTH as f32 / START_HEIGHT as f32) as f32, + ); + let cameras: Vec> = vec![arc_camera.clone()]; + //Primitive + let arc_material = Arc::new(Material::magenta()); + let mut primitives: Vec> = Vec::new(); + let arc_sphere = Arc::new(Sphere::unit(arc_material.clone())); + let arc_cone = Arc::new(Cone::unit(arc_material.clone())); + primitives.push(arc_sphere.clone()); + primitives.push(arc_cone.clone()); + //Lights + let light: Arc; + let light = Arc::new(Light::white()); + let lights = vec![light]; + let ambient_light = Arc::new(Vector3::new(1.0, 1.0, 0.0)); + //State + let scene = Scene::new(primitives, lights, cameras, ambient_light); + let mut state = State::new(START_WIDTH, START_HEIGHT, scene, camera); // Set up Dear ImGui let mut gui = Gui::new(&window, &pixels); event_loop.run(move |event, _, control_flow| { // Draw the current frame if let Event::RedrawRequested(_) = event { + for i in 0..gui.num_rays { + state.draw(pixels.frame_mut()); + } // Draw the world - state.draw(pixels.frame_mut()); // Prepare Dear ImGui gui.prepare(&window).expect("gui.prepare() failed"); // Render everything together @@ -80,6 +115,7 @@ fn main() -> Result<(), Error> { context.scaling_renderer.render(encoder, render_target); // Render Dear ImGui gui.render(&window, encoder, render_target, context)?; + // *control_flow = ControlFlow::Exit; Ok(()) }); // Basic error handling @@ -98,6 +134,7 @@ fn main() -> Result<(), Error> { *control_flow = ControlFlow::Exit; return; } + if input.key_pressed(VirtualKeyCode::A) {} // Resize the window if let Some(size) = input.window_resized() { if size.width > 0 && size.height > 0 { @@ -108,13 +145,7 @@ fn main() -> Result<(), Error> { return; } - // Resize the world - state.resize(size.width, size.height); - if let Err(err) = pixels.resize_buffer(size.width, size.height) { - log_error("pixels.resize_buffer", err); - *control_flow = ControlFlow::Exit; - return; - } + state.resize(size.width as i32, size.height as i32); } } @@ -134,12 +165,16 @@ fn log_error(method_name: &str, err: E) { impl State { /// Create a new `World` instance that can draw a moving box. - fn new(width: u32, height: u32) -> Self { - let scene = Scene::empty(); + fn new(width: i32, height: i32, scene: Scene, camera: Camera) -> Self { + let index = 0; + let rays = camera.cast_rays(width, height); Self { width, height, + index, + rays, scene, + camera, } } @@ -147,7 +182,7 @@ impl State { fn update(&mut self) {} /// Resize the world - fn resize(&mut self, width: u32, height: u32) { + fn resize(&mut self, width: i32, height: i32) { self.width = width; self.height = height; } @@ -155,61 +190,23 @@ impl State { /// Draw the `World` state to the frame buffer. /// /// Assumes the default texture format: `wgpu::TextureFormat::Rgba8UnormSrgb` - fn draw(&self, frame: &mut [u8]) { - for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { - let x = (i % self.width as usize) as i16; - let y = (i / self.width as usize) as i16; + fn draw(&mut self, frame: &mut [u8]) { + let ray = &self.rays[self.index]; + let colour = raytracer::shade_ray(&self.scene, &ray); + let pixel = &mut frame[self.index * 4..(self.index + 1) * 4] + .copy_from_slice(&[colour.x, colour.y, colour.z, 255]); + self.index += 1; + } - //Create our scene - let eye = Point3::new(0.0, 0.0, -1.0); - let target = Point3::new(0.0, 0.0, 0.0); - let up = Vector3::new(0.0, 1.0, 0.0); - let arc_camera = Arc::new(Camera::new( - eye, - target, - up, - 90.0, - (self.width / self.height) as f32, - )); - let cameras: Vec> = vec![arc_camera.clone()]; - - let arc_material = Arc::new(Material::magenta()); - let arc_cone = Arc::new(Cone::unit(arc_material)); - let primitives: Vec> = vec![arc_cone] - .into_iter() - .map(|arc| arc as Arc) - .collect(); - - let light: Arc; - let light = Arc::new(Light::white()); - let lights = vec![light]; - - let ambient_light = Arc::new(Vector3::new(1.0, 1.0, 1.0)); - - let scene = Scene::new(primitives, lights, cameras, ambient_light); - - let rays = arc_camera.as_ref().cast_rays(self.width, self.height); - - let colours = raytracer::shade_rays(&scene, &rays, self.width, self.height); - println!("{}", colours.len()); - // - // let pixels = pixels.frame().chunks_exact_mut(4); - // for (i, colour) in colours.iter().enumerate() { - // let colour = colours[i]; - // let pixel = &mut pixels[i]; - // pixel[0] = colour.x; - // pixel[1] = colour.y; - // pixel[2] = colour.z; - // } - // - // // Render the frame - // if pixels.render().is_err() { - // eprintln!("Failed to render frame"); - // } - - let rgba = [0x48, 0xb2, 0xe8, 0xff]; - - pixel.copy_from_slice(&rgba); + fn draw_all(&mut self, frame: &mut [u8]) { + let rays = self.camera.cast_rays(self.width, self.height); + let colours = raytracer::shade_rays(&self.scene, &rays, self.width, self.height); + for (i, colour) in colours.iter().enumerate() { + let colour = colours[i]; + // pixel[0] = colour.x; + // pixel[1] = colour.y; + // pixel[2] = colour.z; + // pixel[3] = 255; } } } diff --git a/src/primitive.rs b/src/primitive.rs index 6777250..750c918 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -82,7 +82,7 @@ pub trait Primitive { } // SPHERE ----------------------------------------------------------------- -struct Sphere { +pub struct Sphere { position: Point3, radius: f32, bounding_box: BoundingBox, @@ -103,7 +103,7 @@ impl Sphere { } } - fn unit(material: Arc) -> Self { + pub fn unit(material: Arc) -> Self { Sphere::new(Point3::new(0.0, 0.0, 0.0), 1.0, material) } } @@ -185,7 +185,7 @@ impl Circle { } } - fn unit(material: Arc) -> Self { + pub fn unit(material: Arc) -> Self { let position = Point3::new(0.0, 0.0, 0.0); let normal = Vector3::new(0.0, 1.0, 0.0); let radius = 1.0; @@ -413,7 +413,7 @@ impl Rectangle { bounding_box: BoundingBox { bln, trf }, } } - fn unit(material: Arc) -> Self { + pub fn unit(material: Arc) -> Self { Rectangle::new( Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 0.0), @@ -466,7 +466,7 @@ impl Primitive for Rectangle { } // BOX ----------------------------------------------------------------- -struct Box { +pub struct Box { width: f32, height: f32, depth: f32, @@ -486,7 +486,7 @@ impl Box { bounding_box: BoundingBox { bln, trf }, } } - fn unit(material: Arc) -> Self { + pub fn unit(material: Arc) -> Self { Box::new(2.0, 2.0, 2.0, material) } } @@ -580,7 +580,7 @@ impl Triangle { bounding_box, } } - fn unit(material: Arc) -> Self { + pub fn unit(material: Arc) -> Self { let u = Point3::new(-1.0, 0.0, -1.0); let v = Point3::new(0.0, 0.0, 1.0); let w = Point3::new(1.0, 0.0, -1.0); diff --git a/src/raytracer.rs b/src/raytracer.rs index a40172f..52a8815 100644 --- a/src/raytracer.rs +++ b/src/raytracer.rs @@ -8,57 +8,59 @@ use crate::{ use std::sync::Arc; use nalgebra::{distance, Matrix4, Point3, Vector3, Vector4}; +static ZERO_VECTOR: Vector3 = Vector3::new(0.0, 0.0, 0.0); +static ONE_VECTOR: Vector3 = Vector3::new(1.0, 1.0, 1.0); -pub fn shade_rays(scene: &Scene, rays: &Vec, width: u32, height: u32) -> Vec> { - let mut pixel_data = vec![]; +pub fn shade_rays(scene: &Scene, rays: &Vec, width: i32, height: i32) -> Vec> { + let mut pixel_data = vec![Vector3::new(0, 0, 0); (width * height) as usize]; - for ray in rays { - let intersect = get_closest_intersection(scene, ray); - match intersect { - Some(interect) => { - let colour = phong_shade_point(scene, &interect); - pixel_data.push(colour); - } + for (pixel_index, ray) in rays.iter().enumerate() { + let intersect = get_closest_intersection(scene.primitives.clone(), ray); + let colour = match intersect { + Some(intersect) => phong_shade_point(scene, &intersect), None => { - let colour = Vector3::new(0, 0, 0); - pixel_data.push(colour); + // Handle rays that miss objects (e.g., use a background color or environment map) + Vector3::new(0, 0, 0) } - } + }; + pixel_data[pixel_index] = colour; } - pixel_data } +//Shade a single ray +pub fn shade_ray(scene: &Scene, ray: &Ray) -> Vector3 { + let intersect = get_closest_intersection(scene.primitives.clone(), ray); + match intersect { + Some(intersect) => phong_shade_point(&scene, &intersect), + None => Vector3::new(0, 0, 0), + } +} // Find the closest intersection, given a ray in world coordinates -pub fn get_closest_intersection(scene: &Scene, ray: &Ray) -> Option { +pub fn get_closest_intersection( + primitives: Vec>, + ray: &Ray, +) -> Option { let mut closest_distance = INFINITY; let mut closest_intersect: Option = None; - for arc_primitive in &scene.primitives { + + for arc_primitive in primitives { let primitive = arc_primitive.clone(); - if primitive.intersect_ray(ray).is_none() { - continue; - }; - - let intersect = primitive.intersect_ray(ray); - if intersect.is_none() { - continue; - }; - - let intersect = intersect.unwrap(); - if intersect.distance < closest_distance { - closest_distance = intersect.distance; - closest_intersect = Some(intersect); + if let Some(intersect) = primitive.intersect_ray(ray) { + if intersect.distance < closest_distance { + closest_distance = intersect.distance; + closest_intersect = Some(intersect); + } } } + closest_intersect } // We want to shade a point placed in our scene pub fn phong_shade_point(scene: &Scene, intersect: &Intersection) -> Vector3 { //Useful vectors !!!! CHECK IF WE CAN OPTIMISE - let zero_vector = Vector3::new(0.0, 0.0, 0.0); - let one_vector = Vector3::new(1.0, 1.0, 1.0); //Unpack the intersection data let Intersection { point, @@ -102,7 +104,7 @@ pub fn phong_shade_point(scene: &Scene, intersect: &Intersection) -> Vector3 let diffuse = if n_dot_l > 0.0 { kd * n_dot_l } else { - zero_vector + ZERO_VECTOR }; // Compute specular @@ -111,7 +113,7 @@ pub fn phong_shade_point(scene: &Scene, intersect: &Intersection) -> Vector3 let specular = if n_dot_h > 0.0 { ks * n_dot_h.powf(shininess) } else { - zero_vector + ZERO_VECTOR }; colour += light_colour.component_mul(&((diffuse + specular) * falloff));