diff --git a/Cargo.lock b/Cargo.lock index 4f75a9b3..db3a8f08 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/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 5d118674..6e05aaf9 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, @@ -85,6 +91,7 @@ fn collect_triangle_data(obj: &obj::Obj, mtl: &mtl::Mtl) -> Vec { fn blend_from_mtl(material: &mtl::Material) -> Arc { let reflection = DiffuseReflectiveMaterial { reflectance: material.diffuse_reflection.into(), + texture: material.diffuse_map.as_ref().map(|img| img.to_rgb32f()), }; let refraction = SpecularRefractiveMaterial { index_of_refraction: material.index_of_refraction, 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() }