From 8cba35eae896d9a257e957da938d0862d85a68a6 Mon Sep 17 00:00:00 2001 From: Daniel Oom Date: Mon, 6 May 2024 20:01:31 +0200 Subject: [PATCH] Add texture support --- Cargo.lock | 1 + kdtree-tester-cli/src/main.rs | 3 ++- scene/Cargo.toml | 1 + scene/src/lib.rs | 35 +++++++++++++++++++++++++++-------- scene/src/material.rs | 2 ++ tracing/src/material.rs | 29 +++++++++++++++++++++++------ tracing/src/pathtracer.rs | 5 +++-- tracing/src/raytracer.rs | 6 ++++-- 8 files changed, 63 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7f2bfe6..6c18bc4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -551,6 +551,7 @@ name = "scene" version = "1.0.0" dependencies = [ "geometry", + "image", "nalgebra", "simba", "wavefront", diff --git a/kdtree-tester-cli/src/main.rs b/kdtree-tester-cli/src/main.rs index 79147432..5b1b696f 100644 --- a/kdtree-tester-cli/src/main.rs +++ b/kdtree-tester-cli/src/main.rs @@ -137,6 +137,7 @@ impl RayBouncer { let wi = -ray.direction; let n = triangle.normals.lerp(intersection.u, intersection.v); + let uv = triangle.texcoords.lerp(intersection.u, intersection.v); let material = triangle.material.as_ref(); // TODO: How to chose offset? @@ -159,7 +160,7 @@ impl RayBouncer { return Some(*checked); } - let sample = material.sample(&IncomingRay { wi, n }, rng); + let sample = material.sample(&IncomingRay { wi, n, uv }, rng); let next_ray = Ray { origin: if sample.wo.dot(&n) >= 0.0 { point_above diff --git a/scene/Cargo.toml b/scene/Cargo.toml index d50e4602..1a7c9002 100644 --- a/scene/Cargo.toml +++ b/scene/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] geometry = { version = "1.0.0", path = "../geometry" } +image = { version = "0.25.1", default-features = false } nalgebra = { version = "0.32.5", default-features = false } simba = { version = "0.8.1", default-features = false, features = ["std"] } wavefront = { version = "1.0.0", path = "../wavefront" } diff --git a/scene/src/lib.rs b/scene/src/lib.rs index f51a4728..2cc89f49 100644 --- a/scene/src/lib.rs +++ b/scene/src/lib.rs @@ -36,6 +36,12 @@ pub struct TriangleTexcoords { pub uv2: Vector2, } +impl TriangleTexcoords { + pub fn lerp(&self, u: f32, v: f32) -> Vector2 { + (1.0 - (u + v)) * self.uv0 + u * self.uv1 + v * self.uv2 + } +} + pub struct TriangleData { pub triangle: Triangle, pub normals: TriangleNormals, @@ -50,8 +56,12 @@ pub struct Scene { pub environment: Vector3, } -fn collect_triangle_data(obj: &obj::Obj, mtl: &mtl::Mtl) -> Vec { - let materials = materials_from_mtl(mtl); +fn collect_triangle_data( + image_directory: &Path, + obj: &obj::Obj, + mtl: &mtl::Mtl, +) -> Vec { + let materials = materials_from_mtl(image_directory, mtl); obj.chunks .iter() .flat_map(|chunk| { @@ -82,9 +92,15 @@ fn collect_triangle_data(obj: &obj::Obj, mtl: &mtl::Mtl) -> Vec { .collect::>() } -fn blend_from_mtl(material: &mtl::Material) -> Arc { +fn blend_from_mtl(image_directory: &Path, material: &mtl::Material) -> Arc { + let texture = (!material.diffuse_map.is_empty()).then(|| { + image::open(image_directory.join(&material.diffuse_map)) + .unwrap() + .to_rgb32f() + }); let reflection = DiffuseReflectiveMaterial { reflectance: material.diffuse_reflection.into(), + texture, }; let refraction = SpecularRefractiveMaterial { index_of_refraction: material.index_of_refraction, @@ -110,10 +126,13 @@ fn blend_from_mtl(material: &mtl::Material) -> Arc { Arc::new(material) } -fn materials_from_mtl(mtl: &mtl::Mtl) -> BTreeMap<&str, Arc> { +fn materials_from_mtl<'p, 'm>( + image_directory: &'p Path, + mtl: &'m mtl::Mtl, +) -> BTreeMap<&'m str, Arc> { mtl.materials .iter() - .map(|m| (m.name.as_str(), blend_from_mtl(m))) + .map(|m| (m.name.as_str(), blend_from_mtl(image_directory, m))) .collect::>() } @@ -146,9 +165,9 @@ fn collect_lights(mtl: &mtl::Mtl) -> Vec { } impl Scene { - pub fn from_wavefront(obj: &obj::Obj, mtl: &mtl::Mtl) -> Scene { + pub fn from_wavefront(image_directory: &Path, obj: &obj::Obj, mtl: &mtl::Mtl) -> Scene { Scene { - triangle_data: collect_triangle_data(obj, mtl), + triangle_data: collect_triangle_data(image_directory, obj, mtl), cameras: collect_cameras(mtl), lights: collect_lights(mtl), environment: Vector3::new(0.8, 0.8, 0.8), @@ -171,7 +190,7 @@ impl Scene { println!(" Cameras: {}", mtl.cameras.len()); println!("Collecting scene..."); - let scene = Scene::from_wavefront(&obj, &mtl); + let scene = Scene::from_wavefront(mtl_path.parent().unwrap(), &obj, &mtl); println!(" Triangles: {}", scene.triangle_data.len()); scene } diff --git a/scene/src/material.rs b/scene/src/material.rs index efc4406f..ce7e8720 100644 --- a/scene/src/material.rs +++ b/scene/src/material.rs @@ -1,3 +1,4 @@ +use image::Rgb32FImage; use nalgebra::{UnitVector3, Vector3}; #[derive(Debug, PartialEq)] @@ -10,6 +11,7 @@ pub struct MaterialSample { #[derive(Clone, Debug)] pub struct DiffuseReflectiveMaterial { pub reflectance: Vector3, + pub texture: Option, } #[derive(Clone, Debug)] diff --git a/tracing/src/material.rs b/tracing/src/material.rs index 2481735b..2589d02a 100644 --- a/tracing/src/material.rs +++ b/tracing/src/material.rs @@ -1,4 +1,4 @@ -use nalgebra::{RealField, UnitVector3, Vector3}; +use nalgebra::{RealField, UnitVector3, Vector2, Vector3}; use rand::{rngs::SmallRng, Rng}; use scene::material::{ BlendMaterial, DiffuseReflectiveMaterial, FresnelBlendMaterial, MaterialSample, @@ -23,17 +23,23 @@ fn is_same_sign(a: f32, b: f32) -> bool { pub struct IncomingRay { pub wi: Vector3, pub n: UnitVector3, + pub uv: Vector2, } impl IncomingRay { fn with_normal(&self, n: UnitVector3) -> IncomingRay { - IncomingRay { wi: self.wi, n } + IncomingRay { + wi: self.wi, + n, + uv: self.uv, + } } fn with_wo(&self, wo: Vector3) -> OutgoingRay { OutgoingRay { wi: self.wi, n: self.n, + uv: self.uv, wo, } } @@ -47,6 +53,7 @@ impl IncomingRay { pub struct OutgoingRay { pub wi: Vector3, pub n: UnitVector3, + pub uv: Vector2, pub wo: Vector3, } @@ -59,6 +66,7 @@ impl OutgoingRay { IncomingRay { wi: self.wi, n: self.n, + uv: self.uv, } } } @@ -70,8 +78,15 @@ pub trait Material { } impl Material for DiffuseReflectiveMaterial { - fn brdf(&self, _: &OutgoingRay) -> Vector3 { - self.reflectance * f32::frac_1_pi() + fn brdf(&self, outgoing: &OutgoingRay) -> Vector3 { + if let Some(texture) = &self.texture { + let px = (texture.width() as f32 * outgoing.uv.x).floor(); + let py = (texture.height() as f32 * outgoing.uv.y).floor(); + let reflectance: Vector3 = texture[(px as u32, py as u32)].0.into(); + reflectance * f32::frac_1_pi() + } else { + self.reflectance * f32::frac_1_pi() + } } fn sample(&self, incoming: &IncomingRay, rng: &mut SmallRng) -> MaterialSample { @@ -205,7 +220,8 @@ mod specular_refractive_material_tests { }; let wi = Vector3::new(-1.0, 2.0, 0.0).normalize(); let n = UnitVector3::new_normalize(Vector3::new(0.0, 1.0, 0.0)); - let incoming = IncomingRay { wi, n }; + let uv = Vector2::zeros(); + let incoming = IncomingRay { wi, n, uv }; let mut rng = SmallRng::seed_from_u64(0); let actual = material.sample(&incoming, &mut rng); @@ -225,7 +241,8 @@ mod specular_refractive_material_tests { }; let wi = Vector3::new(-1.0, -2.0, 0.0).normalize(); let n = UnitVector3::new_normalize(Vector3::new(0.0, 1.0, 0.0)); - let incoming = IncomingRay { wi, n }; + let uv = Vector2::zeros(); + let incoming = IncomingRay { wi, n, uv }; let mut rng = SmallRng::seed_from_u64(0); let actual = material.sample(&incoming, &mut rng); diff --git a/tracing/src/pathtracer.rs b/tracing/src/pathtracer.rs index 08a0c9a4..8a6c1717 100644 --- a/tracing/src/pathtracer.rs +++ b/tracing/src/pathtracer.rs @@ -77,6 +77,7 @@ impl Pathtracer { let wi = -ray.direction; let n = triangle.normals.lerp(intersection.u, intersection.v); + let uv = triangle.texcoords.lerp(intersection.u, intersection.v); let material = triangle.material.as_ref(); // TODO: How to chose offset? @@ -97,7 +98,7 @@ impl Pathtracer { let wo = shadow_ray.direction.normalize(); let radiance = light.emitted(point); material - .brdf(&OutgoingRay { wi, n, wo }) + .brdf(&OutgoingRay { wi, n, wo, uv }) .component_mul(&radiance) * wo.dot(&n).abs() }) @@ -106,7 +107,7 @@ impl Pathtracer { let accumulated_radiance = accumulated_radiance + accumulated_transport.component_mul(&incoming_radiance); - let sample = material.sample(&IncomingRay { wi, n }, pixel.rng); + let sample = material.sample(&IncomingRay { wi, n, uv }, pixel.rng); let next_ray = Ray { origin: if sample.wo.dot(&n) >= 0.0 { point_above diff --git a/tracing/src/raytracer.rs b/tracing/src/raytracer.rs index fb198c0f..02ae85df 100644 --- a/tracing/src/raytracer.rs +++ b/tracing/src/raytracer.rs @@ -31,6 +31,7 @@ impl Raytracer { offset_point: Vector3, wi: Vector3, n: UnitVector3, + uv: Vector2, light: &SphericalLight, ) -> Vector3 { let direction = light.center - target; @@ -44,7 +45,7 @@ impl Raytracer { let wo = direction.normalize(); let radiance = light.emitted(target); material - .brdf(&OutgoingRay { wi, n, wo }) + .brdf(&OutgoingRay { wi, n, wo, uv }) .component_mul(&radiance) * wo.dot(&n).abs() } @@ -60,6 +61,7 @@ impl Raytracer { let wi = -ray.direction; let point = ray.param(intersection.t); let n = triangle.normals.lerp(intersection.u, intersection.v); + let uv = triangle.texcoords.lerp(intersection.u, intersection.v); // TODO: How to chose offset? let offset_point = point + 0.0001 * n.into_inner(); @@ -68,7 +70,7 @@ impl Raytracer { self.scene .lights .iter() - .map(|light| self.light_contribution(material, point, offset_point, wi, n, light)) + .map(|light| self.light_contribution(material, point, offset_point, wi, n, uv, light)) .sum() }