From c26a4bece09b05deb0caf7af43f9625d6caaddd0 Mon Sep 17 00:00:00 2001 From: hal8174 Date: Fri, 23 May 2025 19:47:08 +0200 Subject: [PATCH] Add iridescent material --- .cargo/config.toml | 3 + .gitignore | 1 + flake.nix | 60 ++--- ray-tracing-material-visualizer/src/main.rs | 4 + ray-tracing-material/src/iridescent.rs | 207 ++++++++++++++++++ ray-tracing-material/src/lib.rs | 1 + ray-tracing-scene/src/examples/mod.rs | 19 +- .../src/examples/stanford_dragon.rs | 15 +- 8 files changed, 276 insertions(+), 34 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 ray-tracing-material/src/iridescent.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..da48fe7 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ + +[build] +rustflags = ["-C raget-cpu=native"] diff --git a/.gitignore b/.gitignore index 9d702b2..32c4d8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target **.exr +**.png diff --git a/flake.nix b/flake.nix index 094e41e..c939bad 100644 --- a/flake.nix +++ b/flake.nix @@ -6,22 +6,30 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachDefaultSystem (system: + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: let pkgs = nixpkgs.legacyPackages.${system}; # Read the file relative to the flake's root overrides = (builtins.fromTOML (builtins.readFile (self + "/rust-toolchain.toml"))); - libPath = with pkgs; lib.makeLibraryPath [ - # load external libraries that you need in your rust project here - ]; + libPath = + with pkgs; + lib.makeLibraryPath [ + # load external libraries that you need in your rust project here + ]; in { devShells.default = pkgs.mkShell rec { nativeBuildInputs = [ pkgs.pkg-config ]; packages = with pkgs; [ - + tev shaderc vulkan-headers @@ -31,6 +39,7 @@ python3 ]; buildInputs = with pkgs; [ + fontconfig clang llvmPackages.bintools rustup @@ -38,14 +47,14 @@ # VULKAN_SDK = "${vulkan-validation-layers}/share/vulkan/explicit_layer.d"; - LD_LIBRARY_PATH="${pkgs.wayland}/lib:${pkgs.libxkbcommon}/lib:${pkgs.vulkan-loader}/lib:${pkgs.vulkan-validation-layers}/lib"; + LD_LIBRARY_PATH = "${pkgs.wayland}/lib:${pkgs.libxkbcommon}/lib:${pkgs.vulkan-loader}/lib:${pkgs.vulkan-validation-layers}/lib"; VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d"; RUSTC_VERSION = overrides.toolchain.channel; - + # https://github.com/rust-lang/rust-bindgen#environment-variables LIBCLANG_PATH = pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ]; - + shellHook = '' export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/ @@ -53,26 +62,27 @@ ''; # Add precompiled library to rustc search path - RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [ - # add libraries here (e.g. pkgs.libvmi) - ]); - + RUSTFLAGS = ( + builtins.map (a: ''-L ${a}/lib'') [ + # add libraries here (e.g. pkgs.libvmi) + ] + ); + # LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (buildInputs ++ nativeBuildInputs); - # Add glibc, clang, glib, and other headers to bindgen search path BINDGEN_EXTRA_CLANG_ARGS = - # Includes normal include path - (builtins.map (a: ''-I"${a}/include"'') [ - # add dev libraries here (e.g. pkgs.libvmi.dev) - pkgs.glibc.dev - ]) - # Includes with special directory paths - ++ [ - ''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"'' - ''-I"${pkgs.glib.dev}/include/glib-2.0"'' - ''-I${pkgs.glib.out}/lib/glib-2.0/include/'' - ]; + # Includes normal include path + (builtins.map (a: ''-I"${a}/include"'') [ + # add dev libraries here (e.g. pkgs.libvmi.dev) + pkgs.glibc.dev + ]) + # Includes with special directory paths + ++ [ + ''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"'' + ''-I"${pkgs.glib.dev}/include/glib-2.0"'' + ''-I${pkgs.glib.out}/lib/glib-2.0/include/'' + ]; }; } ); diff --git a/ray-tracing-material-visualizer/src/main.rs b/ray-tracing-material-visualizer/src/main.rs index d536963..58be9e1 100644 --- a/ray-tracing-material-visualizer/src/main.rs +++ b/ray-tracing-material-visualizer/src/main.rs @@ -10,6 +10,7 @@ use rand::{rngs::SmallRng, SeedableRng}; use ray_tracing_core::prelude::*; use ray_tracing_material::{ diffuse::DiffuseMaterial, + iridescent::Iridescent, microfacet::{BeckmannDistribution, Microfacet}, mirror::Mirror, oren_nayar::OrenNayar, @@ -35,6 +36,9 @@ fn main() -> Result<(), Box> { let m = OrenNayar::new(0.5, color); generate_chart("oren-nayar.png", &m, 100, w_in)?; + let m = Iridescent::new(300.0, 300.0, 1.0, 1.5, 10); + generate_chart("iridescent.png", &m, 2, w_in)?; + Ok(()) } diff --git a/ray-tracing-material/src/iridescent.rs b/ray-tracing-material/src/iridescent.rs new file mode 100644 index 0000000..df667cd --- /dev/null +++ b/ray-tracing-material/src/iridescent.rs @@ -0,0 +1,207 @@ +use core::f32; + +use ray_tracing_core::prelude::*; + +#[derive(Debug)] +pub struct Iridescent { + d1: f32, + d2: f32, + n1: f32, + n2: f32, + n: f32, +} + +impl Iridescent { + pub fn new(d1: f32, d2: f32, n1: f32, n2: f32, n: u32) -> Self { + Self { + d1, + d2, + n1, + n2, + n: n as f32, + } + } + + fn fresnel_reflection(&self, theta1: f32, theta2: f32, s_polaized: bool) -> f32 { + if s_polaized { + (self.n1 * theta1.cos() - self.n2 * theta2.cos()) + / (self.n1 * theta1.cos() + self.n2 * theta2.cos()) + } else { + (self.n2 * theta1.cos() - self.n1 * theta2.cos()) + / (self.n2 * theta1.cos() + self.n1 * theta2.cos()) + } + } + + fn abs_r_1_squared(&self, l: f32, theta1: f32, theta2: f32, s_polaized: bool) -> f32 { + let r12 = self.fresnel_reflection(theta1, theta2, s_polaized); + + let phi = 2.0 * f32::consts::PI * self.n2 * self.d2 * theta2.cos() / l; + + let num_real = r12 * (1.0 - f32::cos(-2.0 * phi)); + let num_imag = r12 * (1.0 - f32::sin(-2.0 * phi)); + + let denom_real = 1.0 - r12 * r12 * f32::cos(-2.0 * phi); + let denom_imag = 1.0 - r12 * r12 * f32::sin(-2.0 * phi); + + let real = (num_real * denom_real + num_imag * denom_imag) + / (denom_real * denom_real + denom_imag * denom_imag); + + let imag = (num_imag * denom_real - num_real * denom_imag) + / (denom_real * denom_real + denom_imag * denom_imag); + + real * real + imag * imag + } + + fn abs_C_squared(&self, l: f32, theta1: f32, theta2: f32, s_polaized: bool) -> f32 { + let local_abs_r_1_squared = self.abs_r_1_squared(l, theta1, theta2, s_polaized); + + local_abs_r_1_squared / (local_abs_r_1_squared + 1.0) + } + + fn cos_K_Delta(&self, l: f32, theta1: f32, theta2: f32, s_polaized: bool) -> f32 { + let k1z = 2.0 * f32::consts::PI * self.n1 * theta1.cos() / l; + let k2z = 2.0 * f32::consts::PI * self.n2 * theta2.cos() / l; + + let omega = if s_polaized { + k2z / k1z + k1z / k2z + } else { + (self.n1 * self.n1 * k2z) / (self.n2 * self.n2 * k1z) + + (self.n2 * self.n2 * k1z) / (self.n1 * self.n1 * k2z) + }; + + f32::cos(k1z * self.d1) * f32::cos(k2z * self.d2) + - 0.5 * omega * f32::sin(k1z * self.d1) * f32::sin(k2z * self.d2) + } + + fn reflectance(&self, l: f32, theta1: f32, s_polaized: bool) -> f32 { + let theta2 = f32::asin(f32::sin(theta1) * self.n1 / self.n2); + + let local_cos_k_delta = self.cos_K_Delta(l, theta1, theta2, s_polaized); + + let local_c_squred = self.abs_C_squared(l, theta1, theta2, s_polaized); + + if !local_c_squred.is_normal() || !local_cos_k_delta.is_normal() { + dbg!(( + l, + theta1, + theta2, + local_cos_k_delta, + local_c_squred, + s_polaized + )); + } + + if local_cos_k_delta.abs() <= 1.0 { + let k_delta = f32::acos(local_cos_k_delta); + + if !k_delta.is_normal() { + dbg!(( + l, + theta1, + theta2, + local_cos_k_delta, + local_c_squred, + s_polaized, + k_delta, + )); + } + let u = f32::sin(k_delta) / f32::sin(self.n * k_delta); + + local_c_squred / (local_c_squred + u * u) + } else { + let imk_delta = -f32::ln(f32::abs( + local_cos_k_delta - f32::sqrt(local_cos_k_delta * local_cos_k_delta - 1.0), + )); + + if !imk_delta.is_normal() { + dbg!(( + l, + theta1, + theta2, + local_cos_k_delta, + local_c_squred, + s_polaized, + imk_delta, + )); + } + let u = f32::sinh(imk_delta) / f32::sinh(self.n * imk_delta); + + local_c_squred / (local_c_squred + u * u) + } + } + + fn averaged_reflectance(&self, l: f32, theta1: f32) -> f32 { + 0.5 * (self.reflectance(l, theta1, true) + self.reflectance(l, theta1, false)) + } + + fn piecewise_gaussian(l: f32, mu: f32, tau1: f32, tau2: f32) -> f32 { + if l < mu { + let s = tau1 * (l - mu); + f32::exp(-0.5 * s * s) + } else { + let s = tau2 * (l - mu); + f32::exp(-0.5 * s * s) + } + } + + fn color_matching_function(l: f32) -> Color { + Color::new( + 1.056 * Self::piecewise_gaussian(l, 599.8, 0.0264, 0.0323) + + 0.362 * Self::piecewise_gaussian(l, 442.0, 0.0624, 0.0374) + - 0.065 * Self::piecewise_gaussian(l, 501.1, 0.0490, 0.0382), + 0.821 * Self::piecewise_gaussian(l, 568.8, 0.0213, 0.0247) + + 0.286 * Self::piecewise_gaussian(l, 530.9, 0.0613, 0.0322), + 1.217 * Self::piecewise_gaussian(l, 437.0, 0.0845, 0.0278) + + 0.681 * Self::piecewise_gaussian(l, 459.0, 0.0385, 0.0725), + ) + } + + fn monte_carlo_reflectance(&self, theta1: f32, rng: &mut R) -> Color { + let range = 400.0..800.0; + let size = range.end - range.start; + + let l = rng.gen_range(range); + + let r = self.averaged_reflectance(l, theta1); + + if !r.is_normal() { + dbg!((theta1, l, r)); + } + + let color = Self::color_matching_function(l); + + if !color.r().is_normal() || !color.g().is_normal() || !color.b().is_normal() { + dbg!(color); + } + + color * r + } +} + +impl Material for Iridescent { + fn eval( + &self, + w_in: ray_tracing_core::prelude::Dir3, + w_out: ray_tracing_core::prelude::Dir3, + rng: &mut R, + ) -> ray_tracing_core::prelude::Color { + let _ = w_in; + let _ = w_out; + let _ = rng; + Color::black() + } + + fn sample(&self, w_in: Dir3, rng: &mut R) -> ray_tracing_core::material::SampleResult { + let w_out = (2.0 * Dir3::up() * w_in.y()) - w_in; + + let color = self.monte_carlo_reflectance(f32::acos(w_out.z()), rng); + + ray_tracing_core::material::SampleResult::new(w_out, color, true) + } + + fn pdf(&self, w_in: Dir3, w_out: Dir3) -> Float { + let _ = w_out; + let _ = w_in; + 0.0 + } +} diff --git a/ray-tracing-material/src/lib.rs b/ray-tracing-material/src/lib.rs index 4a11b33..992ea1d 100644 --- a/ray-tracing-material/src/lib.rs +++ b/ray-tracing-material/src/lib.rs @@ -1,5 +1,6 @@ pub mod default; pub mod diffuse; +pub mod iridescent; pub mod microfacet; pub mod mirror; pub mod oren_nayar; diff --git a/ray-tracing-scene/src/examples/mod.rs b/ray-tracing-scene/src/examples/mod.rs index a1dc5df..327d832 100644 --- a/ray-tracing-scene/src/examples/mod.rs +++ b/ray-tracing-scene/src/examples/mod.rs @@ -1,4 +1,7 @@ use ray_tracing_core::{prelude::*, scene::Scene}; +use ray_tracing_material::iridescent::Iridescent; +use ray_tracing_material::microfacet::{BeckmannDistribution, Microfacet}; +use ray_tracing_material::mirror::Mirror; use std::collections::HashMap; use std::fmt::Debug; @@ -19,9 +22,21 @@ pub fn example_scenes() -> HashMap<&'static str, Box>> = HashMap::new(); map.insert("basic_cornel", Box::new(basic_cornell::BasicCornell::new())); map.insert("cornel2", Box::new(cornell2::Cornell2::new())); + let color = Color::new(0.2, 0.2, 0.9); + let material = Microfacet::new(BeckmannDistribution::new(0.01), color); map.insert( - "stanford_dragon", - Box::new(stanford_dragon::StanfordDragon::new()), + "stanford_dragon_microfacet", + Box::new(stanford_dragon::StanfordDragon::new(material)), + ); + let material = Iridescent::new(300.0, 300.0, 1.0, 1.5, 10); + map.insert( + "stanford_dragon_iridescent", + Box::new(stanford_dragon::StanfordDragon::new(material)), + ); + let material = Mirror::new(Color::white()); + map.insert( + "stanford_dragon_mirror", + Box::new(stanford_dragon::StanfordDragon::new(material)), ); map.insert( "stanford_dragon_as", diff --git a/ray-tracing-scene/src/examples/stanford_dragon.rs b/ray-tracing-scene/src/examples/stanford_dragon.rs index 78ba90b..e263d53 100644 --- a/ray-tracing-scene/src/examples/stanford_dragon.rs +++ b/ray-tracing-scene/src/examples/stanford_dragon.rs @@ -4,7 +4,7 @@ use ray_tracing_material::{ microfacet::{BeckmannDistribution, Microfacet}, oren_nayar::OrenNayar, }; -use std::cell::OnceCell; +use std::cell::{Cell, OnceCell}; use crate::{ parse_obj::ObjData, @@ -13,19 +13,21 @@ use crate::{ use super::ExampleScene; -pub struct StanfordDragon { +pub struct StanfordDragon> { scene: OnceCell>, + material: Cell>, } -impl StanfordDragon { - pub fn new() -> Self { +impl> StanfordDragon { + pub fn new(material: M) -> Self { Self { scene: OnceCell::new(), + material: Cell::new(Some(material)), } } } -impl ExampleScene for StanfordDragon { +impl + 'static> ExampleScene for StanfordDragon { fn get_scene(&self) -> Box + Sync> { let s = self.scene.get_or_init(|| { let obj = ObjData::new("ray-tracing-scene/obj/stanford_dragon.obj").unwrap(); @@ -35,9 +37,8 @@ impl ExampleScene for StanfordDragon { .map(|t| Triangle::new(t.v, 0)) .collect::>(); - let color = Color::new(0.2, 0.2, 0.9); let materials = vec![ - BVHMaterial::new_material(Microfacet::new(BeckmannDistribution::new(0.01), color)), + BVHMaterial::new_material(self.material.take().unwrap()), BVHMaterial::new_material(OrenNayar::new(0.5, Color::new(0.8, 0.8, 0.8))), BVHMaterial::new_material(OrenNayar::new(0.5, Color::new(0.9, 0.0, 0.0))), BVHMaterial::new_material(OrenNayar::new(0.5, Color::new(0.0, 0.9, 0.0))),