[Rust] レイトレーシング8-2

Rust

はじめに

こんにちは、お疲れ様です。
時間がなかなか取れず、更新が全くかけませんでした。不覚を取りました。

前回までが、どこまでやったかうる覚えですが、
続きをやっていきます。

確か、円柱を前回まで作成していて上下の蓋をしていなかった気がします。
久しぶりなので、軽くやっていきます。

Cylinder実装の続き

本日の実装は、大きく分けて二つです。

  • 底面の交差判定ロジック
  • 交差判定に組み込む

底面の交差判定のロジックを実装する

図説できれば良かったのですが、分量的にコードの中にコメントアウトでいいかと思い、
手を抜きました。申し訳ない。

impl Cylinder {
    pub fn new(center: Point3, axis: Vector3, radius: f64, height: f64) -> Self {
         // 略 [Rust] レイトレーシング 8-1 を参照しておくれ
    }

    fn intersect_side(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<f64> {
         // 略 [Rust] レイトレーシング 8-1 を参照しておくれ
         // 今気づきましが、 「//TODO」ってww todo!() がありますよね。
    }
   
    fn nomal_at(&self, point: &Point3) -> Vector3 {
        // 略 [Rust] レイトレーシング 8-1 を参照しておくれ
    }
    
    //円柱の底面との交差判定
    fn intersect_caps(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<f64> {
        // 0に近いと底面とほぼ並行なので、交差点なしと判断する
        let dir_dot_axis = ray.get_direction().dot(&self.axis);
        if dir_dot_axis.abs() < f64::EPSILON {
            return None;
        }

        let mut t_min_cap = f64::MAX;
        let mut found_intersection = false;
    
    // centerを基準に真逆にあるので、これで両方判定できるはず
        for &cap_sign in &[-1.0, 1.0] {
            // self.axisは単位ベクトルなんでそのまま,
      // 底面までの距離かければ欲しい情報を得られるはず
            let cap_center = self.center + self.axis * (cap_sign * self.height / 2.0);
            let t = (cap_center - ray.get_origin()).dot(&self.axis) / dir_dot_axis;
            
            // 他のオブジェクトが遮っていなければ、、
            if t >= t_min && t <= t_max {
                let hit_point = ray.at(t);
                let distance_form_center = (hit_point - cap_center).length();

                if distance_form_center <= self.radius {
                    if t < t_min_cap {
                        t_min_cap = t;
                        found_intersection = true;
                    }
                }
            }
        }
        if found_intersection {
            Some(t_min_cap)
        } else {
            None
        }
    }
}

上記の実装を交差判定しているところに組み込む

組み込んでいきます

impl Shape for Cylinder {
    fn intersect(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<Intersection> {
        // 結果をOptionで受け取ってます
        let side_intersection = self.intersect_side(ray, t_min, t_max);
        let cap_intersection = self.intersect_caps(ray, t_min, t_max);
        
        // matchで場合わけっす
        let t = match(side_intersection, cap_intersection) {
      // 両方交差しそうな時は、手前を採用
            (Some(t_side), Some(t_cap)) => t_side.min(t_cap), 
            (Some(t_side), None) => t_side,
            (None, Some(t_cap)) => t_cap,
            (None, None) => return None,
        };

        if t < t_min || t > t_max {
            return None;
        }

        let hit_point = ray.at(t);
        let normal = self.normal_at(&hit_point);

        let mut intersection = Intersection {
            t,
            point: hit_point,
            normal,
            front_face: false,
        };
        intersection.set_face_normal(ray, normal);

        Some(intersection)
    }
}

動作確認

前回と同じコードでできるかな?

確認コード1
pub fn main() -> Result<(), Box<dyn Error>> {
    let width = 256;
    let height = 256;

    let film = Film::new(width, height);

    let pos = Point3::new(0.0, 0.5, 3.0);
    let look_at = Point3::new(0.0, 0.5, -1.0);
    let up = Vector3::new(0.0, 1.0, 0.0);
    let fov = 90.0_f64.to_radians();

    let mut cam = Camera::new(pos, look_at, up, fov, film);

    let mut scene = Scene::new();
    scene.add(Box::new(Cylinder::new(
                Point3::new(1.5, 1.0, -1.0),
                Vector3::new(0.0, 0.25, -1.0),
                1.0,
                2.0,
            )));
    scene.add(Box::new(Cylinder::new(
                Point3::new(-1.5, 1.0, -1.0),
                Vector3::new(0.0, 1.0, 0.0),
                1.0,
                2.0,
            )));


    for y in 0..height {
        for x in 0..width {
            let u = x as f64 / (width - 1) as f64;
            let v = y as f64 / (height - 1) as f64;

            let ray = cam.generate_ray(u, v);
            let mut color = Color3::zero();

            if let Some((_, intersection)) =  scene.intersect(&ray, f64::EPSILON, f64::INFINITY) {
                let normal = intersection.normal;
                color = Rgb::new(
                    0.5 * (normal.get_x() + 1.0),
                    0.5 * (normal.get_y() + 1.0),
                    0.5 * (normal.get_z() + 1.0),
                );
            } else {
                let unit_direction = ray.get_direction().unit();
                let t = 0.5 * (unit_direction.get_y() + 1.0);

                let white = Rgb::new(1.0, 1.0, 1.0);
                let blue = Rgb::new(0.5, 0.7, 1.0);

                color = white*(1.0 - t) + blue*t;
            }

            cam.record_pixel(x, y, color);
        }
    }

    let ppm_output = PpmOutput;
    ppm_output.save(cam.get_film(), "build/cylinder_test.ppm").unwrap();

    Ok(())
}

上記を実行して、下記が出力されればOKです。

確認コード2
// ここの部分です    
    let mut scene = Scene::new();
    scene.add(Box::new(Cylinder::new(
                Point3::new(1.5, 1.0, -1.0),
                Vector3::new(0.0, 1.0, -0.3),
                1.0,
                2.0,
            )));
    scene.add(Box::new(Cylinder::new(
                Point3::new(-1.5, 1.0, -1.0),
                Vector3::new(0.0, 1.0, 0.0),
                1.0,
                2.0,
            )));

上記を実行して、下記が出力されればOKです。気持ち斜めにしてみました。

さいごに

お疲れ様です。

久しぶりの更新になりました、長くても一月更新できるように流石に頑張ろうと思います。
流石にサバーレンタル代がもったいねですからね(°_°)