diff --git a/__output_image.png b/__output_image.png new file mode 100644 index 0000000..5296bb4 Binary files /dev/null and b/__output_image.png differ diff --git a/output_image.png b/output_image.png new file mode 100644 index 0000000..637585d Binary files /dev/null and b/output_image.png differ diff --git a/scenes/emissive_cube.blend b/scenes/emissive_cube.blend index 63b5865..c3b341a 100644 Binary files a/scenes/emissive_cube.blend and b/scenes/emissive_cube.blend differ diff --git a/scenes/emissive_cube.blend1 b/scenes/emissive_cube.blend1 index 49561eb..7909fd1 100644 Binary files a/scenes/emissive_cube.blend1 and b/scenes/emissive_cube.blend1 differ diff --git a/scenes/mirror.blend b/scenes/mirror.blend new file mode 100644 index 0000000..43691a1 Binary files /dev/null and b/scenes/mirror.blend differ diff --git a/scenes/mirror.blend1 b/scenes/mirror.blend1 new file mode 100644 index 0000000..214b3cb Binary files /dev/null and b/scenes/mirror.blend1 differ diff --git a/scenes/mirror.glb b/scenes/mirror.glb new file mode 100644 index 0000000..9e7dc58 Binary files /dev/null and b/scenes/mirror.glb differ diff --git a/scenes/reflective.bin b/scenes/reflective.bin new file mode 100644 index 0000000..62064fe Binary files /dev/null and b/scenes/reflective.bin differ diff --git a/scenes/reflective.blend b/scenes/reflective.blend new file mode 100644 index 0000000..fc3bc9b Binary files /dev/null and b/scenes/reflective.blend differ diff --git a/scenes/reflective.blend1 b/scenes/reflective.blend1 new file mode 100644 index 0000000..3fca552 Binary files /dev/null and b/scenes/reflective.blend1 differ diff --git a/scenes/reflective.glb b/scenes/reflective.glb new file mode 100644 index 0000000..9ccd128 Binary files /dev/null and b/scenes/reflective.glb differ diff --git a/scenes/reflective.gltf b/scenes/reflective.gltf new file mode 100644 index 0000000..1b7590c --- /dev/null +++ b/scenes/reflective.gltf @@ -0,0 +1,452 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.0.44", + "version":"2.0" + }, + "extensionsUsed":[ + "KHR_materials_emissive_strength" + ], + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0, + 1, + 2, + 3, + 4 + ] + } + ], + "nodes":[ + { + "camera":0, + "name":"Camera", + "rotation":[ + -0.15949390828609467, + 0.662581741809845, + 0.1474284678697586, + 0.7168068289756775 + ], + "translation":[ + 10.606247901916504, + 4.060349464416504, + 0.7527037858963013 + ] + }, + { + "mesh":0, + "name":"Plane", + "scale":[ + 7.545622825622559, + 7.545622825622559, + 7.545622825622559 + ] + }, + { + "mesh":1, + "name":"Plane.001", + "rotation":[ + 0, + 0, + -0.7071068286895752, + 0.7071068286895752 + ], + "translation":[ + 6.896522521972656, + 1.2417194843292236, + -3.40067195892334 + ] + }, + { + "mesh":2, + "name":"Plane.002", + "rotation":[ + 0, + 0, + -0.7071068286895752, + 0.7071068286895752 + ], + "scale":[ + 3.870809316635132, + 1, + 5.133298873901367 + ], + "translation":[ + 0, + 0, + -1.2463083267211914 + ] + }, + { + "mesh":3, + "name":"Cube", + "scale":[ + 0.7817667722702026, + 0.7817667722702026, + 0.7817667722702026 + ], + "translation":[ + 0.8038264513015747, + 0.7851558327674866, + 0 + ] + } + ], + "cameras":[ + { + "name":"Camera", + "perspective":{ + "aspectRatio":1, + "yfov":0.6911112070083618, + "zfar":100, + "znear":0.10000000149011612 + }, + "type":"perspective" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Specular_black", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0, + 0, + 0, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0 + } + }, + { + "doubleSided":true, + "emissiveFactor":[ + 1, + 1, + 1 + ], + "extensions":{ + "KHR_materials_emissive_strength":{ + "emissiveStrength":60 + } + }, + "name":"Emissive", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0 + } + }, + { + "doubleSided":true, + "name":"Specular_red", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.8001416921615601, + 0, + 0.004359794314950705, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0 + } + }, + { + "doubleSided":true, + "name":"Rough_cube", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.009510930627584457, + 1, + 0, + 1 + ], + "metallicFactor":0 + } + } + ], + "meshes":[ + { + "name":"Plane", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + }, + { + "name":"Plane.001", + "primitives":[ + { + "attributes":{ + "POSITION":4, + "NORMAL":5, + "TEXCOORD_0":6 + }, + "indices":3, + "material":1 + } + ] + }, + { + "name":"Plane.002", + "primitives":[ + { + "attributes":{ + "POSITION":7, + "NORMAL":8, + "TEXCOORD_0":9 + }, + "indices":3, + "material":2 + } + ] + }, + { + "name":"Cube.001", + "primitives":[ + { + "attributes":{ + "POSITION":10, + "NORMAL":11, + "TEXCOORD_0":12 + }, + "indices":13, + "material":3 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":4, + "max":[ + 1, + 0, + 1 + ], + "min":[ + -1, + 0, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":4, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":4, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":6, + "type":"SCALAR" + }, + { + "bufferView":4, + "componentType":5126, + "count":4, + "max":[ + 1, + 0, + 1 + ], + "min":[ + -1, + 0, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":5, + "componentType":5126, + "count":4, + "type":"VEC3" + }, + { + "bufferView":6, + "componentType":5126, + "count":4, + "type":"VEC2" + }, + { + "bufferView":7, + "componentType":5126, + "count":4, + "max":[ + 1, + 0, + 1 + ], + "min":[ + -1, + 0, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":8, + "componentType":5126, + "count":4, + "type":"VEC3" + }, + { + "bufferView":9, + "componentType":5126, + "count":4, + "type":"VEC2" + }, + { + "bufferView":10, + "componentType":5126, + "count":24, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":11, + "componentType":5126, + "count":24, + "type":"VEC3" + }, + { + "bufferView":12, + "componentType":5126, + "count":24, + "type":"VEC2" + }, + { + "bufferView":13, + "componentType":5123, + "count":36, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":48, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":48, + "target":34962 + }, + { + "buffer":0, + "byteLength":32, + "byteOffset":96, + "target":34962 + }, + { + "buffer":0, + "byteLength":12, + "byteOffset":128, + "target":34963 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":140, + "target":34962 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":188, + "target":34962 + }, + { + "buffer":0, + "byteLength":32, + "byteOffset":236, + "target":34962 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":268, + "target":34962 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":316, + "target":34962 + }, + { + "buffer":0, + "byteLength":32, + "byteOffset":364, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":396, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":684, + "target":34962 + }, + { + "buffer":0, + "byteLength":192, + "byteOffset":972, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":1164, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":1236, + "uri":"reflective.bin" + } + ] +} diff --git a/src/main.rs b/src/main.rs index 35f9d15..1c79a46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod geometry; mod ray; mod scene_data; mod colors; +mod math_utils; use std::string::String; use cgmath::Vector4; diff --git a/src/math_utils.rs b/src/math_utils.rs new file mode 100644 index 0000000..28b1360 --- /dev/null +++ b/src/math_utils.rs @@ -0,0 +1,37 @@ +use std::f32::consts::PI; +use cgmath::{InnerSpace, Vector3}; +use easy_gltf::model::Triangle; +use crate::ray::Ray; + +/// return a uniform random angle in the hemisphere specified by the triangle +pub fn uniform_random_angle_triangle_hemisphere(intersected_triangle: Triangle, ray: &Ray) -> Vector3 { + let tangent = (intersected_triangle[0].position - + intersected_triangle[1].position).normalize(); + let mut face_normal: Vector3 = tangent + .cross(intersected_triangle[0].position - + intersected_triangle[2].position).normalize(); + //flip normal if face is hit from behind + if face_normal.dot(-ray.direction) < 0.0 { + face_normal *= -1.0; + } + let bitangent = tangent.cross(face_normal).normalize(); + /* + generate random direction on hemisphere + */ + let phi = rand::random::() * 2.0 * PI; + //allow arbitrary angles + let radius = rand::random::(); + let sqrt_radius = f32::sqrt(rand::random::()); + + (tangent * f32::cos(phi) * sqrt_radius + + bitangent * f32::sin(phi) * sqrt_radius + + face_normal * f32::sqrt(1.0 - radius)).normalize() +} + +/// Calculate a triangle from the three Vertices +pub fn face_normal_for_triangle(triangle: &Triangle) -> Vector3 { + let tangent = (triangle[0].position - + triangle[1].position).normalize(); + tangent.cross(triangle[0].position - + triangle[2].position).normalize() +} \ No newline at end of file diff --git a/src/renderer.rs b/src/renderer.rs index d469dec..f2012a6 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -8,6 +8,7 @@ use easy_gltf::{Camera, Projection, Scene}; use indicatif::ParallelProgressIterator; use crate::Args; use crate::geometry::{Intersectable}; +use crate::math_utils::{face_normal_for_triangle, uniform_random_angle_triangle_hemisphere}; use crate::ray::{construct_primary_rays, Ray}; use crate::scene_data::IntersectionData; @@ -150,44 +151,47 @@ fn accumulate_colors(intersection_data: &IntersectionData, recursion_depth_left: usize) -> Vector4 { let mut pixel_radiosity: Vector4 = Vector4::new(0.0, 0.0, 0.0, 1.0); + //accumulate colors at point //emissive Component pixel_radiosity += intersection_data.material().emissive.factor.extend(1.0) .mul_element_wise( intersection_data.material() + //TODO: texture sampling! .get_base_color_alpha(Vector2::new(0.0, 0.0)) ); //get the intersected triangle and calculate the face normals let intersected_triangle = intersection_data.intersected_triangle(); - let tangent = (intersected_triangle[0].position - - intersected_triangle[1].position).normalize(); - let mut face_normal: Vector3 = tangent - .cross(intersected_triangle[0].position - - intersected_triangle[2].position).normalize(); - //flip normal if face is hit from behind - if face_normal.dot(-intersection_data.ray().direction) < 0.0 { - face_normal *= -1.0; + + let decision_factor = rand::random::(); + let mut direction: Vector3; + + let face_normal = + face_normal_for_triangle(&intersection_data.intersected_triangle()); + + let rough_sampling: bool = decision_factor <= intersection_data.material().pbr.roughness_factor; + + //do we trace rough or specular? + if rough_sampling { + //generate random direction (rough sampling) + direction = uniform_random_angle_triangle_hemisphere( + intersected_triangle, + intersection_data.ray(), + ); + } else { + //mirror vector (specular sampling) + direction = intersection_data.ray().direction - + 2.0 * (intersection_data.ray().direction.dot(face_normal)) * face_normal; } - let bitangent = tangent.cross(face_normal).normalize(); - /* - generate random direction on hemisphere - */ - let phi = rand::random::() * 2.0 * PI; - //allow arbitrary angles - let radius = rand::random::(); - let sqrt_radius = f32::sqrt(rand::random::()); - - let direction = (tangent * f32::cos(phi) * sqrt_radius + - bitangent * f32::sin(phi) * sqrt_radius + - face_normal * f32::sqrt(1.0 - radius)).normalize(); + direction = direction.normalize(); let secondary_ray = Ray { //prevent self-intersection - source: intersection_data.intersection_point() + RAY_EPSILON * face_normal, + source: intersection_data.intersection_point() + RAY_EPSILON * direction, direction, }; @@ -197,15 +201,27 @@ fn accumulate_colors(intersection_data: &IntersectionData, global_scene, recursion_depth_left - 1); + //weigh by cosine term depending on view angel + let cos_weighting = direction.dot( + face_normal + ); - let cos_weighting = direction.dot(face_normal); + let brdf: Vector4; - let brdf = intersection_data.material() - .get_base_color_alpha(Vector2::new(0.0, 0.0)); - - //reflected component - pixel_radiosity += brdf.mul_element_wise(incoming_radiosity) * cos_weighting * 2.0 * PI; + if rough_sampling { + brdf = intersection_data.material() + //TODO: texture sampling! + .get_base_color_alpha(Vector2::new(0.0, 0.0)); + pixel_radiosity += brdf.mul_element_wise(incoming_radiosity) * cos_weighting * 2.0 * PI; + } else { + pixel_radiosity += incoming_radiosity * cos_weighting * 2.0 * PI + + intersection_data.material() + //TODO: texture sampling! + .get_base_color_alpha(Vector2::new(0.0, 0.0)); + } pixel_radiosity } + +