rustray/src/main.rs
2019-03-19 22:39:51 +01:00

288 lines
7.1 KiB
Rust

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<f32>; 3],
}
impl Triangle {
pub fn new(v0: Vector3<f32>, v1: Vector3<f32>, v2: Vector3<f32>) -> Triangle {
Triangle {
points: [v0, v1, v2],
}
}
}
struct View {
position: Vector3<f32>,
look_at: Vector3<f32>,
fov: f32,
}
struct Ray {
origin: Vector3<f32>,
direction: Vector3<f32>,
inv_direction: Vector3<f32>,
signs: Vector3<usize>,
}
impl Ray {
pub fn new(origin: Vector3<f32>, direction: Vector3<f32>) -> 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<f32>; 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<AABB> {
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<AABB>) -> Vector3<f32> {
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<f32>,
v1: Vector3<f32>,
v2: Vector3<f32>,
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
}