From d023526f79d9e30a519aa2a7982582c13d72da96 Mon Sep 17 00:00:00 2001 From: Daniel Oom Date: Mon, 6 May 2024 18:15:02 +0200 Subject: [PATCH] Add IncomingRay and OutgoingRay structs --- kdtree-tester-cli/src/main.rs | 4 +- tracing/src/material.rs | 150 +++++++++++++++++++--------------- tracing/src/pathtracer.rs | 11 +-- tracing/src/raytracer.rs | 13 ++- 4 files changed, 102 insertions(+), 76 deletions(-) diff --git a/kdtree-tester-cli/src/main.rs b/kdtree-tester-cli/src/main.rs index c75aecc0..79147432 100644 --- a/kdtree-tester-cli/src/main.rs +++ b/kdtree-tester-cli/src/main.rs @@ -17,7 +17,7 @@ use std::{ str::{self, FromStr}, }; use tracing::{ - material::Material, + material::{IncomingRay, Material}, sampling::{sample_light, uniform_sample_unit_square}, }; @@ -159,7 +159,7 @@ impl RayBouncer { return Some(*checked); } - let sample = material.sample(&wi, &n, rng); + let sample = material.sample(&IncomingRay { wi, n }, rng); let next_ray = Ray { origin: if sample.wo.dot(&n) >= 0.0 { point_above diff --git a/tracing/src/material.rs b/tracing/src/material.rs index 83f7028e..2481735b 100644 --- a/tracing/src/material.rs +++ b/tracing/src/material.rs @@ -19,50 +19,87 @@ fn is_same_sign(a: f32, b: f32) -> bool { a.signum() == b.signum() } -fn is_same_hemisphere(wi: &Vector3, wo: &Vector3, n: &UnitVector3) -> bool { - is_same_sign(wi.dot(n), wo.dot(n)) +#[derive(Debug)] +pub struct IncomingRay { + pub wi: Vector3, + pub n: UnitVector3, +} + +impl IncomingRay { + fn with_normal(&self, n: UnitVector3) -> IncomingRay { + IncomingRay { wi: self.wi, n } + } + + fn with_wo(&self, wo: Vector3) -> OutgoingRay { + OutgoingRay { + wi: self.wi, + n: self.n, + wo, + } + } + + fn reflectance(&self, r0: f32) -> f32 { + r0 + (1.0 - r0) * (1.0 - self.wi.dot(&self.n).abs()).powf(5.0) + } +} + +#[derive(Debug)] +pub struct OutgoingRay { + pub wi: Vector3, + pub n: UnitVector3, + pub wo: Vector3, +} + +impl OutgoingRay { + fn is_same_hemisphere(&self) -> bool { + is_same_sign(self.wi.dot(&self.n), self.wo.dot(&self.n)) + } + + fn as_incoming(&self) -> IncomingRay { + IncomingRay { + wi: self.wi, + n: self.n, + } + } } pub trait Material { - fn brdf(&self, wi: &Vector3, wo: &Vector3, n: &UnitVector3) -> Vector3; + fn brdf(&self, outgoing: &OutgoingRay) -> Vector3; - fn sample(&self, wi: &Vector3, n: &UnitVector3, rng: &mut SmallRng) - -> MaterialSample; + fn sample(&self, incoming: &IncomingRay, rng: &mut SmallRng) -> MaterialSample; } impl Material for DiffuseReflectiveMaterial { - fn brdf(&self, _: &Vector3, _: &Vector3, _: &UnitVector3) -> Vector3 { + fn brdf(&self, _: &OutgoingRay) -> Vector3 { self.reflectance * f32::frac_1_pi() } - fn sample( - &self, - wi: &Vector3, - n: &UnitVector3, - rng: &mut SmallRng, - ) -> MaterialSample { - let tangent = perpendicular(n).normalize(); - let bitangent = n.cross(&tangent); + fn sample(&self, incoming: &IncomingRay, rng: &mut SmallRng) -> MaterialSample { + let tangent = perpendicular(&incoming.n).normalize(); + let bitangent = incoming.n.cross(&tangent); let s = cosine_sample_hemisphere(rng); - let wo = UnitVector3::new_normalize(s.x * tangent + s.y * bitangent + s.z * n.into_inner()); + let wo = UnitVector3::new_normalize(s.x * tangent + s.y * bitangent + s.z * *incoming.n); MaterialSample { pdf: 1.0, - brdf: self.brdf(wi, &wo, n), + brdf: self.brdf(&incoming.with_wo(*wo)), wo, } } } impl Material for SpecularReflectiveMaterial { - fn brdf(&self, _: &Vector3, _: &Vector3, _: &UnitVector3) -> Vector3 { + fn brdf(&self, _: &OutgoingRay) -> Vector3 { Vector3::zeros() } - fn sample(&self, wi: &Vector3, n: &UnitVector3, _: &mut SmallRng) -> MaterialSample { - let wo = UnitVector3::new_normalize(2.0 * wi.dot(n).abs() * n.into_inner() - wi); - let pdf = if is_same_hemisphere(wi, &wo, n) { - wo.dot(n).abs() + fn sample(&self, incoming: &IncomingRay, _: &mut SmallRng) -> MaterialSample { + let wo = UnitVector3::new_normalize( + 2.0 * incoming.wi.dot(&incoming.n).abs() * *incoming.n - incoming.wi, + ); + let outgoing = incoming.with_wo(*wo); + let pdf = if outgoing.is_same_hemisphere() { + wo.dot(&outgoing.n).abs() } else { 0.0 }; @@ -75,46 +112,37 @@ impl Material for SpecularReflectiveMaterial { } impl Material for SpecularRefractiveMaterial { - fn brdf(&self, _: &Vector3, _: &Vector3, _: &UnitVector3) -> Vector3 { + fn brdf(&self, _: &OutgoingRay) -> Vector3 { Vector3::zeros() } - fn sample( - &self, - wi: &Vector3, - n: &UnitVector3, - rng: &mut SmallRng, - ) -> MaterialSample { - let (eta, n_refracted) = if (-wi).dot(n) < 0.0 { - (1.0 / self.index_of_refraction, *n) + fn sample(&self, incoming: &IncomingRay, rng: &mut SmallRng) -> MaterialSample { + let (eta, n_refracted) = if (-incoming.wi).dot(&incoming.n) < 0.0 { + (1.0 / self.index_of_refraction, incoming.n) } else { - (self.index_of_refraction, -*n) + (self.index_of_refraction, -incoming.n) }; - let w = -(-wi).dot(&n_refracted) * eta; + let w = -(-incoming.wi).dot(&n_refracted) * eta; let k = 1.0 + (w - eta) * (w + eta); if k < 0.0 { const TOTAL_INTERNAL_REFLECTION: SpecularReflectiveMaterial = SpecularReflectiveMaterial { reflectance: Vector3::new(1.0, 1.0, 1.0), }; - return TOTAL_INTERNAL_REFLECTION.sample(wi, &n_refracted, rng); + return TOTAL_INTERNAL_REFLECTION.sample(&incoming.with_normal(n_refracted), rng); } let k = k.sqrt(); - let wo = UnitVector3::new_normalize(-eta * wi + (w - k) * *n_refracted); + let wo = UnitVector3::new_normalize(-eta * incoming.wi + (w - k) * *n_refracted); MaterialSample { pdf: 1.0, - brdf: Vector3::new(1., 1., 1.), + brdf: Vector3::new(1.0, 1.0, 1.0), wo, } } } -fn reflectance(r0: f32, wi: &Vector3, n: &Vector3) -> f32 { - r0 + (1.0 - r0) * (1.0 - wi.dot(n).abs()).powf(5.0) -} - fn mix(x: Vector3, y: Vector3, a: f32) -> Vector3 { x * (1.0 - a) + y * a } @@ -124,24 +152,19 @@ where M1: Material, M2: Material, { - fn brdf(&self, wi: &Vector3, wo: &Vector3, n: &UnitVector3) -> Vector3 { + fn brdf(&self, outgoing: &OutgoingRay) -> Vector3 { mix( - self.refraction.brdf(wi, wo, n), - self.reflection.brdf(wi, wo, n), - reflectance(self.r0, wi, n), + self.refraction.brdf(outgoing), + self.reflection.brdf(outgoing), + outgoing.as_incoming().reflectance(self.r0), ) } - fn sample( - &self, - wi: &Vector3, - n: &UnitVector3, - rng: &mut SmallRng, - ) -> MaterialSample { - if rng.gen::() < reflectance(self.r0, wi, n) { - self.reflection.sample(wi, n, rng) + fn sample(&self, incoming: &IncomingRay, rng: &mut SmallRng) -> MaterialSample { + if rng.gen::() < incoming.reflectance(self.r0) { + self.reflection.sample(incoming, rng) } else { - self.refraction.sample(wi, n, rng) + self.refraction.sample(incoming, rng) } } } @@ -151,24 +174,19 @@ where M1: Material, M2: Material, { - fn brdf(&self, wi: &Vector3, wo: &Vector3, n: &UnitVector3) -> Vector3 { + fn brdf(&self, outgoing: &OutgoingRay) -> Vector3 { mix( - self.second.brdf(wi, wo, n), - self.first.brdf(wi, wo, n), + self.second.brdf(outgoing), + self.first.brdf(outgoing), self.factor, ) } - fn sample( - &self, - wi: &Vector3, - n: &UnitVector3, - rng: &mut SmallRng, - ) -> MaterialSample { + fn sample(&self, incoming: &IncomingRay, rng: &mut SmallRng) -> MaterialSample { if rng.gen::() < self.factor { - self.first.sample(wi, n, rng) + self.first.sample(incoming, rng) } else { - self.second.sample(wi, n, rng) + self.second.sample(incoming, rng) } } } @@ -187,9 +205,10 @@ 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 mut rng = SmallRng::seed_from_u64(0); - let actual = material.sample(&wi, &n, &mut rng); + let actual = material.sample(&incoming, &mut rng); let n1 = 1.0; let n2 = material.index_of_refraction; @@ -206,9 +225,10 @@ 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 mut rng = SmallRng::seed_from_u64(0); - let actual = material.sample(&wi, &n, &mut rng); + let actual = material.sample(&incoming, &mut rng); let n1 = material.index_of_refraction; let n2 = 1.0; diff --git a/tracing/src/pathtracer.rs b/tracing/src/pathtracer.rs index 3735141d..08a0c9a4 100644 --- a/tracing/src/pathtracer.rs +++ b/tracing/src/pathtracer.rs @@ -2,7 +2,7 @@ use std::ops::RangeInclusive; use crate::{ image_buffer::ImageBuffer, - material::Material, + material::{IncomingRay, Material, OutgoingRay}, raylogger::RayLogger, sampling::{sample_light, uniform_sample_unit_square}, }; @@ -94,18 +94,19 @@ impl Pathtracer { if self.intersect_any(&shadow_ray, 0.0..=1.0) { return Vector3::zeros(); } - let wo = shadow_ray.direction.normalize(); let radiance = light.emitted(point); - - material.brdf(&wi, &wo, &n).component_mul(&radiance) * wo.dot(&n).abs() + material + .brdf(&OutgoingRay { wi, n, wo }) + .component_mul(&radiance) + * wo.dot(&n).abs() }) .sum(); let accumulated_radiance = accumulated_radiance + accumulated_transport.component_mul(&incoming_radiance); - let sample = material.sample(&wi, &n, pixel.rng); + let sample = material.sample(&IncomingRay { wi, n }, 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 a2aae541..fb198c0f 100644 --- a/tracing/src/raytracer.rs +++ b/tracing/src/raytracer.rs @@ -1,6 +1,9 @@ use std::ops::RangeInclusive; -use crate::{image_buffer::ImageBuffer, material::Material}; +use crate::{ + image_buffer::ImageBuffer, + material::{Material, OutgoingRay}, +}; use geometry::{intersection::RayIntersection, ray::Ray}; use kdtree::KdTree; use nalgebra::{UnitVector3, Vector2, Vector3}; @@ -38,10 +41,12 @@ impl Raytracer { if self.intersect_any(&shadow_ray, 0.0..=1.0) { return Vector3::zeros(); } - - let wr = direction.normalize(); + let wo = direction.normalize(); let radiance = light.emitted(target); - material.brdf(&wi, &wr, &n).component_mul(&radiance) * wr.dot(&n).abs() + material + .brdf(&OutgoingRay { wi, n, wo }) + .component_mul(&radiance) + * wo.dot(&n).abs() } fn trace_ray(&self, ray: &Ray) -> Vector3 {