diff --git a/result_image.png b/result_image.png index df85266..c3e6261 100644 Binary files a/result_image.png and b/result_image.png differ diff --git a/scenes/cube_on_plane.bin b/scenes/cube_on_plane.bin new file mode 100644 index 0000000..8b24e3d Binary files /dev/null and b/scenes/cube_on_plane.bin differ diff --git a/scenes/cube_on_plane.blend b/scenes/cube_on_plane.blend index ffb9fdb..b6c28ec 100644 Binary files a/scenes/cube_on_plane.blend and b/scenes/cube_on_plane.blend differ diff --git a/scenes/cube_on_plane.blend1 b/scenes/cube_on_plane.blend1 new file mode 100644 index 0000000..dbe35c2 Binary files /dev/null and b/scenes/cube_on_plane.blend1 differ diff --git a/scenes/cube_on_plane.glb b/scenes/cube_on_plane.glb deleted file mode 100644 index da2f056..0000000 Binary files a/scenes/cube_on_plane.glb and /dev/null differ diff --git a/scenes/cube_on_plane.gltf b/scenes/cube_on_plane.gltf new file mode 100644 index 0000000..8945edb --- /dev/null +++ b/scenes/cube_on_plane.gltf @@ -0,0 +1,538 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.1.34", + "version":"2.0" + }, + "extensionsUsed":[ + "KHR_lights_punctual" + ], + "extensionsRequired":[ + "KHR_lights_punctual" + ], + "extensions":{ + "KHR_lights_punctual":{ + "lights":[ + { + "color":[ + 1, + 1, + 1 + ], + "intensity":54351.41306588226, + "type":"point", + "name":"Light" + } + ] + } + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Cube" + }, + { + "extensions":{ + "KHR_lights_punctual":{ + "light":0 + } + }, + "name":"Light", + "rotation":[ + -0.28416627645492554, + 0.7269423007965088, + 0.34203392267227173, + 0.5232754945755005 + ], + "translation":[ + 4.076245307922363, + 5.903861999511719, + -1.0054539442062378 + ] + }, + { + "camera":0, + "name":"Camera", + "rotation":[ + 0, + 1, + 0, + 0 + ], + "translation":[ + 2, + 10, + -1.9999992847442627 + ] + }, + { + "mesh":1, + "name":"Cube.001" + }, + { + "mesh":2, + "name":"Cube.002", + "translation":[ + 2, + 10, + -10 + ] + }, + { + "mesh":3, + "name":"Cube.003", + "rotation":[ + 0, + 0.3826834559440613, + 0, + 0.9238795638084412 + ], + "translation":[ + 2, + 10, + 10 + ] + }, + { + "mesh":4, + "name":"Suzanne", + "translation":[ + -10, + 10, + 0 + ] + } + ], + "cameras":[ + { + "name":"Camera", + "perspective":{ + "aspectRatio":1, + "yfov":0.7853981852531433, + "zfar":100, + "znear":0.0010000000474974513 + }, + "type":"perspective" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + }, + { + "doubleSided":true, + "name":"Material.001", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.8002041578292847, + 0, + 0, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + }, + { + "doubleSided":true, + "name":"Material.003", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.019036345183849335, + 0, + 0.8002663850784302, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + }, + { + "doubleSided":true, + "name":"Material.002", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.008347976952791214, + 0.8001724481582642, + 0, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"Cube", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + }, + { + "name":"Cube.001", + "primitives":[ + { + "attributes":{ + "POSITION":4, + "NORMAL":5, + "TEXCOORD_0":6 + }, + "indices":3, + "material":0 + } + ] + }, + { + "name":"Cube.002", + "primitives":[ + { + "attributes":{ + "POSITION":7, + "NORMAL":8, + "TEXCOORD_0":9 + }, + "indices":10, + "material":1 + } + ] + }, + { + "name":"Cube.003", + "primitives":[ + { + "attributes":{ + "POSITION":11, + "NORMAL":12, + "TEXCOORD_0":13 + }, + "indices":10, + "material":2 + } + ] + }, + { + "name":"Suzanne", + "primitives":[ + { + "attributes":{ + "POSITION":14, + "NORMAL":15, + "TEXCOORD_0":16 + }, + "indices":17, + "material":3 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":14, + "max":[ + 1.4142135381698608, + 2, + 1.4142135381698608 + ], + "min":[ + -1.4142135381698608, + 0, + -1.4142135381698608 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":14, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":14, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":36, + "type":"SCALAR" + }, + { + "bufferView":4, + "componentType":5126, + "count":14, + "max":[ + -0.5857864618301392, + 2, + -0.5857864618301392 + ], + "min":[ + -3.4142136573791504, + 0, + -3.4142136573791504 + ], + "type":"VEC3" + }, + { + "bufferView":5, + "componentType":5126, + "count":14, + "type":"VEC3" + }, + { + "bufferView":6, + "componentType":5126, + "count":14, + "type":"VEC2" + }, + { + "bufferView":7, + "componentType":5126, + "count":14, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":8, + "componentType":5126, + "count":14, + "type":"VEC3" + }, + { + "bufferView":9, + "componentType":5126, + "count":14, + "type":"VEC2" + }, + { + "bufferView":10, + "componentType":5123, + "count":36, + "type":"SCALAR" + }, + { + "bufferView":11, + "componentType":5126, + "count":14, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":12, + "componentType":5126, + "count":14, + "type":"VEC3" + }, + { + "bufferView":13, + "componentType":5126, + "count":14, + "type":"VEC2" + }, + { + "bufferView":14, + "componentType":5126, + "count":555, + "max":[ + 1.3671875, + 0.984375, + 0.8515625 + ], + "min":[ + -1.3671875, + -0.984375, + -0.8515625 + ], + "type":"VEC3" + }, + { + "bufferView":15, + "componentType":5126, + "count":555, + "type":"VEC3" + }, + { + "bufferView":16, + "componentType":5126, + "count":555, + "type":"VEC2" + }, + { + "bufferView":17, + "componentType":5123, + "count":2904, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":168, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":168, + "byteOffset":168, + "target":34962 + }, + { + "buffer":0, + "byteLength":112, + "byteOffset":336, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":448, + "target":34963 + }, + { + "buffer":0, + "byteLength":168, + "byteOffset":520, + "target":34962 + }, + { + "buffer":0, + "byteLength":168, + "byteOffset":688, + "target":34962 + }, + { + "buffer":0, + "byteLength":112, + "byteOffset":856, + "target":34962 + }, + { + "buffer":0, + "byteLength":168, + "byteOffset":968, + "target":34962 + }, + { + "buffer":0, + "byteLength":168, + "byteOffset":1136, + "target":34962 + }, + { + "buffer":0, + "byteLength":112, + "byteOffset":1304, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":1416, + "target":34963 + }, + { + "buffer":0, + "byteLength":168, + "byteOffset":1488, + "target":34962 + }, + { + "buffer":0, + "byteLength":168, + "byteOffset":1656, + "target":34962 + }, + { + "buffer":0, + "byteLength":112, + "byteOffset":1824, + "target":34962 + }, + { + "buffer":0, + "byteLength":6660, + "byteOffset":1936, + "target":34962 + }, + { + "buffer":0, + "byteLength":6660, + "byteOffset":8596, + "target":34962 + }, + { + "buffer":0, + "byteLength":4440, + "byteOffset":15256, + "target":34962 + }, + { + "buffer":0, + "byteLength":5808, + "byteOffset":19696, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":25504, + "uri":"cube_on_plane.bin" + } + ] +} diff --git a/src/geometry.rs b/src/geometry.rs index 4b4d38d..a387d4f 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -1,87 +1,69 @@ -use cgmath::{Angle, InnerSpace, Matrix4, SquareMatrix, Vector3, Vector4}; -use easy_gltf::{Camera, Projection}; +use cgmath::{InnerSpace, Vector3}; +use cgmath::num_traits::abs; use easy_gltf::model::Triangle; +use crate::ray::Ray; -pub struct Ray { - source: Vector3, - direction: Vector3, -} +const EPSILON: f32 = 0.000001; pub trait Intersectable { /* tests whether the ray intersects the Intersectable. returns: The intersection point or empty if there is none */ - fn test_isec(&self, ray: &Ray) -> Option>; + fn test_isec(&self, ray: &Ray) -> Option<(f32, Vector3)>; } impl Intersectable for Triangle { //perform muller trumbore intersection - fn test_isec(&self, ray: &Ray) -> Option> { - //TODO: implement correct intersection here - if ray.direction.x > 0.5 && - ray.direction.x < 0.6 && - ray.direction.y > 0.1 && - ray.direction.y < 0.6 { - return Some(Vector3::new(1.0, 1.0, 1.0)); + fn test_isec(&self, ray: &Ray) -> Option<(f32, Vector3)> { + assert!( + abs(ray.direction.dot(ray.direction) - 1.0) < EPSILON, + "Ray direction not normalized" + ); + + //get triangle vertices + let p0 = self[0].position; + let p1 = self[1].position; + let p2 = self[2].position; + + let edge1 = p1 - p0; + let edge2 = p2 - p0; + + let ray_cross_e2 = ray.direction.cross(edge2); + let determinant = edge1.dot(ray_cross_e2); + + //ray is parallel to triangle + if determinant > -EPSILON && determinant < EPSILON { + return None; } + + let inverse_determinant = 1.0 / determinant; + + let s = ray.source - p0; + let u = s.dot(ray_cross_e2) * inverse_determinant; + + //early out 2 + if u < 0.0 || u > 1.0 { + return None; + } + + let s_cross_e1 = s.cross(edge1); + let v = ray.direction.dot(s_cross_e1) * inverse_determinant; + + //early out 3 + if v < 0.0 || u + v > 1.0 { + return None; + } + + //compute ray parameter t + + let t = edge2.dot(s_cross_e1) * inverse_determinant; + + if t > EPSILON { + return Option::from((t, ray.source + ray.direction * t)); + } + // This means that there is a line intersection but not a ray intersection. return None; } } -pub fn construct_primary_rays(camera: &Camera, - (width, height): (usize, usize), - (pixel_x_coord, pixel_y_coord): (usize, usize), -) -> Vec { - //only allow perspective rendering - //TODO: ignoring aspect ratio here - let fovy = match camera.projection { - Projection::Perspective { yfov, aspect_ratio: _aspect_ratio } => { yfov } - Projection::Orthographic { .. } => { panic!("Orthographic rendering not supported.") } - }; - - //ray origin in world space - let origin_world_space = camera.position(); - - //seems to be the distance from the camera origin to the view plane - let z: f32 = height as f32 / fovy.tan(); - - //obtain the inverse transformation Matrix - let inverse_transform = camera.transform.invert().unwrap_or_else(|| - panic!("Non invertible transform Matrix. giving up.") - ); - - //TODO: take ray multiplier per pixel into account here - let mut rays: Vec = Vec::with_capacity(height * width); - - //generate all rays for this pixel and add them to the rays vector - //TODO: use blue noise here to generate multiple rays per pixel - rays.push(generate_ray( - width, - height, - &inverse_transform, - z, - pixel_x_coord, - pixel_y_coord, - origin_world_space)); - - rays -} - -fn generate_ray(image_width: usize, - image_height: usize, - inverse_transform: &Matrix4, - focal_length: f32, - u: usize, - v: usize, - ray_origin: Vector3) -> Ray { - //calculate the ray direction and translate it to world space - let direction_view_space: Vector4 = - Vector4::new(u as f32 - (image_width as f32 / 2.0), - v as f32 - (image_height as f32 / 2.0), - -focal_length, - 0.0); - let direction_world_space = inverse_transform * direction_view_space; - - Ray { source: ray_origin, direction: direction_world_space.normalize().truncate() } -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 741de35..a4e58bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod renderer; mod geometry; +mod ray; use std::string::String; use std::sync::{Arc, Mutex}; diff --git a/src/ray.rs b/src/ray.rs new file mode 100644 index 0000000..33d9299 --- /dev/null +++ b/src/ray.rs @@ -0,0 +1,69 @@ +use std::ops::Mul; +use cgmath::{Angle, InnerSpace, Matrix4, SquareMatrix, Vector3, Vector4}; +use easy_gltf::{Camera, Projection}; + +pub struct Ray { + pub(crate) source: Vector3, + pub(crate) direction: Vector3, +} + +pub fn construct_primary_rays(camera: &Camera, + (width, height): (usize, usize), + (pixel_x_coord, pixel_y_coord): (usize, usize), +) -> Vec { + //only allow perspective rendering + //TODO: ignoring aspect ratio here + let (fovy, aspect_ratio) = match camera.projection { + Projection::Perspective { yfov, aspect_ratio } => { + (yfov, aspect_ratio) } + Projection::Orthographic { .. } => { panic!("Orthographic rendering not supported.") } + }; + + //ray origin in world space + let origin_world_space = camera.position(); + //dbg!(camera.transform); + + // the distance from the camera origin to the view plane + let z: f32 = height as f32 / (2.0 * fovy.mul(0.5).tan()); + + //obtain the inverse transformation Matrix + let inverse_transform = camera.transform.invert().unwrap_or_else(|| + panic!("Non invertible transform Matrix. giving up.") + ); + + //TODO: take ray multiplier per pixel into account here + let mut rays: Vec = Vec::with_capacity(height * width); + + //generate all rays for this pixel and add them to the rays vector + //TODO: use blue noise here to generate multiple rays per pixel + rays.push(generate_single_primary_ray( + width, + height, + &inverse_transform, + z, + pixel_x_coord, + pixel_y_coord, + origin_world_space)); + + rays +} + +fn generate_single_primary_ray(image_width: usize, + image_height: usize, + inverse_transform: &Matrix4, + focal_length: f32, + u: usize, + v: usize, + ray_origin: Vector3) -> Ray { + //calculate the ray direction and translate it to world space + let direction_view_space: Vector4 = + Vector4::new(u as f32 - (image_width as f32 / 2.0), + v as f32 - (image_height as f32 / 2.0), + -focal_length, + 0.0); + //TODO: Rotation is fucked + //x rotation has sign wrong, y and z are flipped + let direction_world_space = inverse_transform * direction_view_space.normalize(); + + Ray { source: ray_origin, direction: direction_world_space.truncate().normalize() } +} \ No newline at end of file diff --git a/src/renderer.rs b/src/renderer.rs index ee61fe5..26c8e87 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,11 +1,13 @@ +use std::cmp::max; use std::sync::{Arc, Mutex}; +use cgmath::{Vector2, Vector3, Vector4, Zero}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use easy_gltf::model::{Mode}; use easy_gltf::{Camera, Scene}; use image::{DynamicImage, GenericImage, Rgba}; use crate::Args; -use crate::geometry::{construct_primary_rays, Intersectable, Ray}; - +use crate::geometry::{Intersectable}; +use crate::ray::{construct_primary_rays, Ray}; pub fn render(scenes: &Vec, cl_args: &Args, @@ -23,8 +25,8 @@ pub fn render(scenes: &Vec, (px, py), ); - //let the initial pixel color be white and opaque - let mut pixel_color: Rgba = Rgba([0, 0, 0, 255]); + //let the initial pixel color be black and transparent + let mut pixel_color: Rgba = Rgba::from([0, 0, 0, 255]); //cast each ray and get the output color //TODO: in the end we will want to average the colors out here @@ -45,7 +47,12 @@ pub fn render(scenes: &Vec, fn raytrace(ray: &Ray, scene: &Scene) -> Rgba { - let mut pixel_color: Rgba = Rgba([0, 0, 0, 255]); + let mut pixel_color: Rgba = Rgba::from([0, 0, 0, 255]); + + + let mut smallest_t: f32 = f32::MAX; + let mut clostest_intersection_point: Option> = None; + let mut color_at_isec = [0, 0, 0, 0]; //test intersection with all models in the scene //TODO: Improve, to avoid iterating all models @@ -64,12 +71,29 @@ fn raytrace(ray: &Ray, scene: &Scene) -> Rgba { match triangle.test_isec(&ray) { //boilerplate implementation to set pixel to red if any intersection happened None => {} - Some(point) => { - pixel_color.0 = [255, 255, 255, 255]; - return + Some((t, isec)) => { + // a new closer Point is found + if t < smallest_t { + smallest_t = t; + clostest_intersection_point = Option::from(isec); + let color_vec = model.material().get_base_color_alpha( + Vector2::new(0.0, 0.0) + ).map(|comp| comp * 255.0); + + color_at_isec = [color_vec[0] as u8, color_vec[1] as u8, color_vec[2] as u8, color_vec[3] as u8] + } } }; }); }); + + //make pixel opaque if intersection is found + match clostest_intersection_point { + Some(_) => { + pixel_color = Rgba::from(color_at_isec); + } + None {} => {} + } + pixel_color } \ No newline at end of file