一个周末的光线追踪 - 折射问题

äym*_*ymm 0 raytracing rust

我目前正在一个周末通过光线追踪来熟悉 Rust。添加介电材料(玻璃)让我有些头痛:我的折射没有上下翻转! 1

这是我用于 Vector 结构的代码:

impl Vec3 {
    pub fn new(x: f64, y: f64, z: f64) -> Vec3 { Vec3 {x, y, z} }
    pub fn x(&self) -> f64 { self.x }
    pub fn y(&self) -> f64 { self.y }
    pub fn z(&self) -> f64 { self.z }
    pub fn length_squared(&self) -> f64 {
        self.x * self.x + self.y * self.y + self.z * self.z
    }
    pub fn length(&self) -> f64 { self.distance(&Vec3::default()) }
    pub fn unit_vector(&self) -> Vec3 {
        let length = self.length();
        Vec3::new(self.x / length, self.y / length, self.z / length)
    }
    pub fn dot(&self, v:&Vec3) -> f64 {
        self.x * v.x + self.y * v.y + self.z * v.z
    }
    pub fn cross(&self, v:&Vec3) -> Vec3 {
        Vec3::new(
            self.y * v.z - self.z * v.y,
            self.z * v.x - self.x * v.z,
            self.x * v.y - self.y * v.x
        )
    }
    pub fn distance(&self, other: &Vec3) -> f64 {
        let dx = self.x - other.x();
        let dy = self.y - other.y();
        let dz = self.z - other.z();
        (dx * dx + dy * dy + dz * dz).sqrt()
    }

    pub fn random(min: f64, max:f64) -> Self {
        let between = Uniform::from(min..max);
        let mut rng = rand::thread_rng();
        Vec3::new(
            between.sample(&mut rng),
            between.sample(&mut rng),
            between.sample(&mut rng))
    }
    pub fn random_in_unit_sphere() -> Self {
        loop {
            let v = Vec3::random(-1.0, 1.0);
            if v.length_squared() < 1.0 {
                return v;
            }
        }
    }
    pub fn random_in_hemisphere(normal: &Vec3) -> Self {
        let vec = Vec3::random_in_unit_sphere();
        if vec.dot(normal) > 0.0 {
            vec
        } else {
            -vec
        }
    }
    pub fn random_unit_vector() -> Self { Vec3::random_in_unit_sphere().unit_vector() }
    pub fn near_zero(&self) -> bool {
        const MAXIMUM_DISTANCE_FROM_ZERO:f64 = 1e-8;
        self.x.abs() < MAXIMUM_DISTANCE_FROM_ZERO &&
            self.y.abs() < MAXIMUM_DISTANCE_FROM_ZERO &&
            self.z.abs() < MAXIMUM_DISTANCE_FROM_ZERO
    }
    pub fn reflected(&self, normal: &Vec3) -> Vec3 {
        let dp = self.dot(normal);
        let dp = dp * 2.0 * (*normal);
        *self - dp
    }
    pub fn refract(&self, normal: &Vec3, etai_over_etat: f64) -> Vec3 {
        let dot = (-(*self)).dot(normal);
        let cos_theta = dot.min(1.0);
        let out_perp = etai_over_etat * ((*self) + cos_theta * (*normal));
        let inner =  1.0 - out_perp.length_squared();
        let abs = inner.abs();
        let r = -(abs.sqrt());
        let out_parallel = r * (*normal);
        out_perp + out_paralle
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我对材质的分散函数:

fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
        let refraction_ratio = if hit_record.front_face {
            1.0/self.index_of_refraction
        } else {
            self.index_of_refraction
        };
        let unit_direction = ray.direction().unit_vector();
        let cos_theta = ((-unit_direction).dot(&hit_record.normal)).min(1.0);
        let sin_theta = (1.0 - cos_theta*cos_theta).sqrt();
        let cannot_refract = refraction_ratio * sin_theta > 1.0;
        let reflectance = Dielectric::reflectance(cos_theta, refraction_ratio);
        let mut rng = rand::thread_rng();
        let color = Color::new(1.0, 1.0, 1.0);
        if cannot_refract || reflectance > rng.gen::<f64>() {
            let reflected = unit_direction.reflected(&hit_record.normal);
            let scattered = Ray::new(hit_record.point, reflected);
            Some((Some(scattered), color))
        } else {
            let direction = unit_direction.refract(&hit_record.normal, refraction_ratio);
            let scattered = Ray::new(hit_record.point, direction);
            Some((Some(scattered), color))
        }
    }
Run Code Online (Sandbox Code Playgroud)

如果我否定折射结果,它会起作用,但看起来仍然明显错误此外,如果我在书中后退几步并使用 100% 折射玻璃,我的球体将是纯黑色的,并且我必须取消 z 轴才能看到任何东西。所以我的折射代码出了问题,但我无法弄清楚xy

2

完整代码位于:https://phlaym.net/git/phlaym/rustracer/src/commit/89a2333644a82f2645e4ad554eadf7d4f142f2c0/src

Ale*_*exN 8

在检查球体是否被击中的方法中src/hittable.rs,c 代码如下所示。

// Find the nearest root that lies in the acceptable range.
auto root = (-half_b - sqrtd) / a;
if (root < t_min || t_max < root) {
    root = (-half_b + sqrtd) / a;
    if (root < t_min || t_max < root)
        return false;
}
Run Code Online (Sandbox Code Playgroud)

您已将其移植到 Rust 代码,清单如下:

let root = (-half_b - sqrtd) / a;
if root < t_min || t_max < root {
    let root = (-half_b + sqrtd) / a;
    if root < t_min || t_max < root {
       return None;
    }
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是第二个let root。您已经创建了一个新变量,其内括号有自己的作用域,但没有更改之前定义的已创建变量。要做到这一点,你必须做到mutable

let mut root = (-half_b - sqrtd) / a;
if root < t_min || t_max < root {
    root = (-half_b + sqrtd) / a;
    if root < t_min || t_max < root {
       return None;
    }
}
Run Code Online (Sandbox Code Playgroud)

另外我改变了以下内容src/ray.rs

return match scattered {
    Some((scattered_ray, albedo)) => {
        match scattered_ray {
            Some(sr) => {
                albedo * sr.pixel_color(world, depth-1)
            },
            None => albedo
        }
    },
    None => { return Color::default() }
};
Run Code Online (Sandbox Code Playgroud)

来匹配相应的C代码。请注意unwrap使用过的。

let scattered = rect.material.scatter(self, &rect);
if let Some((scattered_ray, albedo)) = scattered {
    return albedo * scattered_ray.unwrap().pixel_color(world, depth - 1)
}
return Color::default()
Run Code Online (Sandbox Code Playgroud)

并删除您纠正反射的尝试:

let reflected = Vec3::new(-reflected.x(), reflected.y(), -reflected.z());
Run Code Online (Sandbox Code Playgroud)

正确的玻璃球