use cgmath::prelude::*; use cgmath::{vec3, vec4, InnerSpace, Matrix4, Point3, Vector3, Zero}; use image::ImageBuffer; use utilities::prelude::*; use std::f32::{consts::PI as M_PI, MAX, MIN}; struct Triangle { points: [Vector3; 3], } impl Triangle { pub fn new(v0: Vector3, v1: Vector3, v2: Vector3) -> Triangle { Triangle { points: [v0, v1, v2], } } } struct View { position: Vector3, look_at: Vector3, fov: f32, } struct Ray { origin: Vector3, direction: Vector3, inv_direction: Vector3, signs: Vector3, } impl Ray { pub fn new(origin: Vector3, direction: Vector3) -> Ray { let inv_direction = 1.0 / direction; let x = (inv_direction.x < 0.0) as usize; let y = (inv_direction.y < 0.0) as usize; let z = (inv_direction.z < 0.0) as usize; Ray { origin, direction, inv_direction, signs: Vector3::new(x, y, z), } } } struct AABB { bounds: [Vector3; 2], start_index: usize, end_index: usize, } impl AABB { fn new() -> AABB { AABB { bounds: [Vector3::zero(), Vector3::zero()], start_index: 0, end_index: 0, } } fn intersect(&self, ray: &Ray) -> bool { let mut tmin = (self.bounds[ray.signs.x].x - ray.origin.x) * ray.inv_direction.x; let mut tmax = (self.bounds[1 - ray.signs.x].x - ray.origin.x) * ray.inv_direction.x; let tymin = (self.bounds[ray.signs.y].y - ray.origin.y) * ray.inv_direction.y; let tymax = (self.bounds[1 - ray.signs.y].y - ray.origin.y) * ray.inv_direction.y; if tmin > tymax || tymin > tmax { return false; } if tymin > tmin { tmin = tymin; } if tymax < tmax { tmax = tymax; } let tzmin = (self.bounds[ray.signs.z].z - ray.origin.z) * ray.inv_direction.z; let tzmax = (self.bounds[1 - ray.signs.z].z - ray.origin.z) * ray.inv_direction.z; if tmin > tzmax || tzmin > tmax { return false; } if tzmin > tmin { tmin = tzmin; } if tzmax < tmax { tmax = tzmax; } let mut t = tmin; if t < 0.0 { t = tmax; if t < 0.0 { return false; } } true } } fn main() { let input_data = [Triangle::new( vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 1.0, 0.0), )]; let view = View { position: vec3(0.0, -4.0, 4.0), look_at: vec3(0.0, 0.0, 0.0), fov: 45.0, }; debug_raytracer(1280, 720, &view, &input_data); } fn f_to_u(color: f32) -> u8 { ((color * 255.0) / 1.0) as u8 } fn calculate_ray(x: u32, y: u32, dim_x: u32, dim_y: u32, view: &View) -> Ray { let aspect_ratio = dim_x as f32 / dim_y as f32; let fov = view.fov / 180.0 * M_PI * 2.0; let p_x = (1.0 - 2.0 * ((x as f32 + 0.5) / dim_x as f32)) * fov * aspect_ratio; let p_y = (2.0 * (((y) as f32 + 0.5) / dim_y as f32) - 1.0) * fov; let camera_to_world = Matrix4::look_at( Point3::from_vec(view.position), Point3::from_vec(view.look_at), vec3(0.0, 0.0, 1.0), ) .invert() .unwrap(); let origin = camera_to_world * vec4(0.0, 0.0, 0.0, 1.0); let direction = camera_to_world * vec4(p_x, p_y, -1.0, 1.0); Ray::new(origin.truncate(), -direction.truncate().normalize()) } fn debug_raytracer(dim_x: u32, dim_y: u32, view: &View, data: &[Triangle]) { let mut imgbuf = ImageBuffer::new(dim_x, dim_y); for (x, y, pixel) in imgbuf.enumerate_pixels_mut() { let ray = calculate_ray(x, y, dim_x, dim_y, view); let acceleration_data = create_acceleration_data(data); let color = pixel_color(&ray, data, acceleration_data); *pixel = image::Rgb([f_to_u(color.x), f_to_u(color.y), f_to_u(color.z)]); } imgbuf.save("raytrace.png").unwrap(); } fn create_acceleration_data(input_data: &[Triangle]) -> Vec { let mut acceleration_data = Vec::new(); let accel_count = (input_data.len() as f32 / 24.0).ceil() as usize; for i in 0..accel_count { let mut accel = AABB::new(); let mut bbmin = vec3(MAX, MAX, MAX); let mut bbmax = vec3(MIN, MIN, MIN); // 3 vertices per triangles // 8 triangles per acceleration structure let start_index = 8 * i; let mut end_index = 8 * (i + 1); // check if end_index exceeds input data length if end_index > input_data.len() { end_index = input_data.len(); } // set index information accel.start_index = start_index; accel.end_index = end_index; // create bounding box for k in start_index..end_index { for vertex in input_data[k].points.iter() { // check minimum values bbmin.x = bbmin.x.min(vertex.x); bbmin.y = bbmin.y.min(vertex.y); bbmin.z = bbmin.z.min(vertex.z); // check maximum values bbmax.x = bbmax.x.max(vertex.x); bbmax.y = bbmax.y.max(vertex.y); bbmax.z = bbmax.z.max(vertex.z); } } accel.bounds[0] = bbmin; accel.bounds[1] = bbmax; acceleration_data.push(accel); } acceleration_data } fn pixel_color(ray: &Ray, data: &[Triangle], acceleration_data: Vec) -> Vector3 { let mut final_color = vec3(0.0, 1.0, 0.0); let mut closest_value = MAX; let mut closest_index = -1; for accel in acceleration_data.iter() { if accel.intersect(ray) { for i in accel.start_index..accel.end_index { let v0 = data[i].points[0]; let v1 = data[i].points[1]; let v2 = data[i].points[2]; let mut t = 0.0; if ray_triangle_intersect_mt(ray, v0, v1, v2, &mut t) { if t < closest_value { closest_index = i as i32; closest_value = t; } } } } } if closest_index != -1 { final_color = vec3(1.0, 0.0, 0.0); } return final_color; } // source: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection fn ray_triangle_intersect_mt( ray: &Ray, v0: Vector3, v1: Vector3, v2: Vector3, t: &mut f32, ) -> bool { let v0v1 = v1 - v0; let v0v2 = v2 - v0; let pvec = ray.direction.cross(v0v2); let det = v0v1.dot(pvec); // ray and triangle are parallel if det is close to 0 if det.abs() < 0.001 { return false; } let inv_det = 1.0 / det; let tvec = ray.origin - v0; let u = tvec.dot(pvec) * inv_det; if u < 0.0 || u > 1.0 { return false; } let qvec = tvec.cross(v0v1); let v = ray.direction.dot(qvec) * inv_det; if v < 0.0 || u + v > 1.0 { return false; } *t = v0v2.dot(qvec) * inv_det; true }