Add iridescent material

This commit is contained in:
hal8174 2025-05-23 19:47:08 +02:00
parent d475c1ef04
commit c26a4bece0
Signed by: hal8174
SSH key fingerprint: SHA256:JwuqS+eVfISfKr+DkDQ6NWAbGd1jFAHkPpCM1yCnlTs
8 changed files with 276 additions and 34 deletions

3
.cargo/config.toml Normal file
View file

@ -0,0 +1,3 @@
[build]
rustflags = ["-C raget-cpu=native"]

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
target
**.exr
**.png

View file

@ -6,15 +6,23 @@
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 {
@ -31,6 +39,7 @@
python3
];
buildInputs = with pkgs; [
fontconfig
clang
llvmPackages.bintools
rustup
@ -38,7 +47,7 @@
# 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;
@ -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/''
];
};
}
);

View file

@ -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<dyn std::error::Error>> {
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(())
}

View file

@ -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<R: Rng>(&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<R: Rng> Material<R> 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
}
}

View file

@ -1,5 +1,6 @@
pub mod default;
pub mod diffuse;
pub mod iridescent;
pub mod microfacet;
pub mod mirror;
pub mod oren_nayar;

View file

@ -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<R: Rng + Debug + 'static>() -> HashMap<&'static str, Box<d
let mut map: HashMap<&str, Box<dyn ExampleScene<R>>> = 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",

View file

@ -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<R: Rng> {
pub struct StanfordDragon<R: Rng, M: Material<R>> {
scene: OnceCell<TriangleBVH<R>>,
material: Cell<Option<M>>,
}
impl<R: Rng> StanfordDragon<R> {
pub fn new() -> Self {
impl<R: Rng, M: Material<R>> StanfordDragon<R, M> {
pub fn new(material: M) -> Self {
Self {
scene: OnceCell::new(),
material: Cell::new(Some(material)),
}
}
}
impl<R: Rng + 'static> ExampleScene<R> for StanfordDragon<R> {
impl<R: Rng + 'static, M: Material<R> + 'static> ExampleScene<R> for StanfordDragon<R, M> {
fn get_scene(&self) -> Box<dyn Scene<R> + 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<R: Rng + 'static> ExampleScene<R> for StanfordDragon<R> {
.map(|t| Triangle::new(t.v, 0))
.collect::<Vec<_>>();
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))),