В этой части список поддерживаемых материалов пополнится диэлектриками.
Оглавление
Преломление света
Преломлением называется изменение направления луча, возникающее на границе двух сред. Оно возникает, когда фазовые скорости электромагнитных волн в контактирующих средах различаются. Отношение фазовых скоростей света в вакууме и в данной среде называется показателем преломления $$n = c/v,$$ где $$c$$ - фазовая скорость света в вакууме. Различные материалы обладают разными показателями преломления, например:
Среда | Значение |
---|---|
Алмаз белый | 2.417 |
Берилл | 1.571—1.599 |
Изумруд | 1.588—1.595 |
Воздух | 1 |
Для описания преломления света воспользуемся законом Снеллиуса (Snell’s law ), согласно которому $$n_1 sin \Theta_1 = n_2 sin \Theta_2.$$
Пусть $$\overline{u}$$ - входящий вектор, $$\overline{n}$$ - нормаль в точке касания, $$\overline{n}_{\bot}$$ - вектор, перпендикулярный нормали, $$\overline{r}$$ - преломлённый луч, $$n_1, n_2$$ - коэффициенты преломления между средами.
$$ \overline{u} = -\cos{\Theta_1}\overline{n}+\sin{\Theta_1}\overline{n}_{\bot} $$
$$ \overline{r} = -\cos{\Theta_2}\overline{n}+\sin{\Theta_2}\overline{n}_{\bot} $$
Выразим $$\overline{n}_{\bot}$$ через вектора $$\overline{u}$$ и $$\overline{r}$$:
$$ \overline{n}_{\bot} = \cosec{\Theta_1}\overline{u} + \ctg{\Theta_1}\overline{n} $$
$$ \overline{n}_{\bot} = \cosec{\Theta_2}\overline{r} + \ctg{\Theta_2}\overline{n} $$
Из последнего получим соотношение между $$\overline{r}$$ и $$\overline{u}:$$
$$ \cosec{\Theta_1}\overline{u} + \ctg{\Theta_1}\overline{n} = \cosec{\Theta_2}\overline{r} + \ctg{\Theta_2}\overline{n}, $$
$$ \overline{r} = \frac{\sin{\Theta_2}}{\sin{\Theta_1}}\overline{u}+(\cos{\Theta_1}\frac{\sin{\Theta_2}}{\sin{\Theta_1}}-\cos{\Theta_2})\overline{n}. $$
Из закона Снеллиуса следует, что
$$ \overline{r} = \frac{n_1}{n_2}\overline{u}+(\cos{\Theta_1}\frac{n_1}{n_2}-\cos{\Theta_2})\overline{n}, $$
$$ \overline{r} = \frac{n_1}{n_2}\overline{u}+\left(\cos{\Theta_1}\frac{n_1}{n_2}-\sqrt{1-\sin^2{\Theta_2}}\right)\overline{n}. $$
В силу того, что $$\cos{\Theta_1} = \overline{u} \cdot \overline{n}$$, а $$\sin{\Theta_2} = n_1\sqrt{1-\cos^2{\Theta_1}}/n_2:$$
$$ \overline{r} = r_i\overline{u}+\left(r_i\overline{u} \cdot \overline{n}-\sqrt{1-r^2_i + r^2_i(\overline{u} \cdot \overline{n})^2}\right)\overline{n}, $$
где $$r_i=n_1/n_2.$$
Теперь мы можем реализовать функцию расчёта преломлённого луча
// /src/materials/mod.rs
fn refract(v: &Vec3, n: &Vec3, ni_over_nt: f64) -> Option<Vec3> {
let uv = v.unit_vector();
let dt = uv.dot(n);
let discriminant = 1_f64 - ni_over_nt * ni_over_nt * (1_f64 - dt * dt);
if discriminant > 0_f64 {
Some(ni_over_nt * (uv - *n * dt) - *n * discriminant.sqrt())
} else {
None
}
}
Диэлектрики
Прозрачные материалы, такие как вода, стекло, алмазы являются диэлектриками. Когда луч попадает на поверхность диэлектрика он расщепляется на два: отражённый луч и преломлённый луч. Чтобы смоделировать это поведение мы будем случайно выбирать в качестве производного луча отраженный, либо преломлённый луч.
Сначала реализуем диэлектрик, который всегда, при возможности, преломляет проходящий через него луч:
// /src/materials/dielectric.rs
use crate::structs::ray::Ray;
use crate::structs::vec3::Vec3;
use super::Scatterable;
use crate::obj::HitRecord;
#[derive(Debug, Clone)]
pub struct Dielectric {
pub reflection_index: f64,
}
impl Scatterable for Dielectric {
fn scatter(
&self,
ray: &Ray,
hit: &HitRecord,
_rng: &mut impl Iterator<Item = f64>,
) -> Option<(Ray, Vec3)> {
let (outward_normal, ni_over_nt) = if ray.direction.dot(&hit.n) > 0_f64 {
(-1_f64 * hit.n, self.reflection_index)
} else {
(hit.n, 1_f64 / self.reflection_index)
};
match super::refract(&ray.direction, &outward_normal, ni_over_nt) {
Some(refracted) => Some((Ray::new(hit.p, refracted), Vec3::new(1_f64, 1_f64, 1_f64))),
None => Some((
Ray::new(hit.p, super::reflect(&ray.direction, &hit.n)),
Vec3::new(1_f64, 1_f64, 1_f64),
)),
}
}
}
Корректирующий показатель для диэлектрика всегда равен $$(1,1,1)$$, так как мы полагаем, что наша поверхность не поглощает энергию входящего луча.
Изменив материал одной из сфер на диэлектрик с индексом преломления равным 1.5, получим следующий результат:
В реальности коэффициент отражения для прозрачных поверхностей зависит от угла, под которым наблюдатель смотрит на объект. Популярная аппроксимация этого явления была предложена Шликом (Schlick’s approximation ).
// /src/materials/mod.rs
fn schlick(cosine: f64, reflection_index: f64) -> f64 {
let mut r0 = (1_f64 - reflection_index) / (1_f64 + reflection_index);
r0 = r0 * r0;
r0 + (1_f64 - r0) * (1_f64 - cosine).powi(5)
}
Далее доработаем функцию генерации производного луча для диэлектриков
// /src/materials/dielectric.rs
fn scatter(
&self,
ray: &Ray,
hit: &HitRecord,
rng: &mut impl Iterator<Item = f64>,
) -> Option<(Ray, Vec3)> {
let (outward_normal, ni_over_nt, cosine) = if ray.direction.dot(&hit.n) > 0_f64 {
(
-1_f64 * hit.n,
self.reflection_index,
self.reflection_index * ray.direction.dot(&hit.n) / ray.direction.length(),
)
} else {
(
hit.n,
1_f64 / self.reflection_index,
-ray.direction.dot(&hit.n) / ray.direction.length(),
)
};
let (refracted, reflection_probability) =
match super::refract(&ray.direction, &outward_normal, ni_over_nt) {
Some(refracted) => (
Some(Ray::new(hit.p, refracted)),
super::schlick(cosine, self.reflection_index),
),
None => (None, 1_f64),
};
if refracted.is_none() {
return Some((
Ray::new(hit.p, super::reflect(&ray.direction, &hit.n)),
Vec3::new(1_f64, 1_f64, 1_f64),
));
} else {
if rng.next().unwrap() < reflection_probability {
return Some((
Ray::new(hit.p, super::reflect(&ray.direction, &hit.n)),
Vec3::new(1_f64, 1_f64, 1_f64),
));
} else {
return Some((refracted.unwrap(), Vec3::new(1_f64, 1_f64, 1_f64)));
}
}
}
Отметим, что, задав радиус сферы отрицательным числом, мы изменим направление нормалей, это позволит смоделировать полый объект добавив ещё одну сферу отрицательного радиуса:
// /src/main.rs
let world = HittableList {
objects: vec![
Box::new(Sphere::new(
Vec3::new(0_f64, 0_f64, -1_f64),
0.5,
Material::Lambertian(Lambertian {
albedo: Vec3::new(0.8_f64, 0.3_f64, 0.3_f64),
}),
)),
Box::new(Sphere::new(
Vec3::new(0_f64, -100.5_f64, -1_f64),
100_f64,
Material::Lambertian(Lambertian {
albedo: Vec3::new(0.8_f64, 0.8_f64, 0_f64),
}),
)),
Box::new(Sphere::new(
Vec3::new(1_f64, 0_f64, -1_f64),
0.5_f64,
Material::Metal(Metal {
albedo: Vec3::new(0.8_f64, 0.8_f64, 0.8_f64),
fuzz: 0_f64,
}),
)),
Box::new(Sphere::new(
Vec3::new(-1_f64, 0_f64, -1_f64),
0.5_f64,
Material::Dielectric(Dielectric {
reflection_index: 1.5,
}),
)),
Box::new(Sphere::new(
Vec3::new(-1_f64, 0_f64, -1_f64),
-0.45_f64,
Material::Dielectric(Dielectric {
reflection_index: 1.5,
}),
)),
],
};
Приложения
Примечание
Данная заметка написана в рамках реализации трассировки лучей на Rust. Остальные статьи из этой серии можно найти по следующему тегу или в первой публикации из цикла .
Исходный код проекта доступен на github.