5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustでコンピューターグラフィックスの基礎を学ぶ その7

Last updated at Posted at 2026-01-28

フルコードはgithubにあります。

週末レイトレーシングのここをやります。

金属マテリアル

Materialモジュールに、Metal構造体を追加する。拡散の代わりに反射する。

pub struct Metal {
    pub albedo: Color,
}

impl Metal {
    pub fn new(albedo: Color) -> Self {
        Self { albedo }
    }
}

impl Material for Metal {
    fn scatter(
        &self,
        r_in: &Ray,
        rec: &HitRecord,
        attenuation: &mut Color,
        scattered: &mut Ray,
    ) -> bool {
        let reflected = Vec3::reflect(&r_in.direction.unit_vector(), &rec.normal);
        *scattered = Ray::new(rec.p, reflected);
        *attenuation = self.albedo;
        Vec3::dot(&scattered.direction, rec.normal) > 0.0
    }
}

反射計算する関数をVec3側に追加。この式難しい。

    pub fn reflect(v: &Vec3, n: &Vec3) -> Vec3 {
        *v - (*n * 2.0 * v.dot(*n))
    }

金属球をmain.rsに追加。

+    let material_left = Arc::new(Metal::new(Color::new(0.8, 0.8, 0.8)));
+    let material_right = Arc::new(Metal::new(Color::new(0.8, 0.6, 0.2)));

     let mut world = HittableList::new();
     world.add(Arc::new(Sphere::new(
@@ -71,6 +73,16 @@ fn main() {
         0.5,
         material_center,
     )));
+    world.add(Arc::new(Sphere::new(
+        Point3::new(-1.0, 0.0, -1.0),
+        0.5,
+        material_left,
+    )));
+    world.add(Arc::new(Sphere::new(
+        Point3::new(1.0, 0.0, -1.0),
+        0.5,
+        material_right,
+    )));

左右に金属球が置かれた。楕円に見えるのはカメラの近くにあるからだよなあ。

output.png

位置を調整してみる。

@@ -69,18 +69,18 @@ fn main() {
         material_ground,
     )));
     world.add(Arc::new(Sphere::new(
-        Point3::new(0.0, 0.0, -1.0),
+        Point3::new(0.0, 0.0, -1.2),
         0.5,
         material_center,
     )));
     world.add(Arc::new(Sphere::new(
-        Point3::new(-1.0, 0.0, -1.0),
-        0.5,
+        Point3::new(-0.7, -0.3, -0.8),
+        0.3,
         material_left,
     )));
     world.add(Arc::new(Sphere::new(
-        Point3::new(1.0, 0.0, -1.0),
-        0.5,
+        Point3::new(0.7, -0.3, -0.8),
+        0.3,
         material_right,
     )));

output.png

余計ひどくなった。うーん、どういうことだ。
カメラ視点をGUIでグリグリしたい。ただ、今は画像生成に一分ぐらいかかるので視点を変えてもグリグリにはならない。

後で考える。まずは先に進む。

ぼやけた反射

反射率にfuzzinessを追加。new()するときに、条件付き(1.0以上は1.0とする)の引数をどう書くのか分からなかったけど、Gemini CLIが教えてくれた。

@@ -39,11 +39,15 @@ impl Material for Lambertian {

 pub struct Metal {
     pub albedo: Color,
+    pub fuzz: f64,
 }

 impl Metal {
-    pub fn new(albedo: Color) -> Self {
-        Self { albedo }
+    pub fn new(albedo: Color, fuzz: f64) -> Self {
+        Self {
+            albedo,
+            fuzz: if fuzz < 1.0 { fuzz } else { 1.0 },
+        }
     }
 }

@@ -56,7 +60,7 @@ impl Material for Metal {
         scattered: &mut Ray,
     ) -> bool {
         let reflected = Vec3::reflect(&r_in.direction.unit_vector(), &rec.normal);
-        *scattered = Ray::new(rec.p, reflected);
+        *scattered = Ray::new(rec.p, reflected + self.fuzz * Vec3::random_in_unit_sphere());
         *attenuation = self.albedo;
         Vec3::dot(&scattered.direction, rec.normal) > 0.0
     }

main.rsで金属球を作るときにfuziness引数を追加

-    let material_left = Arc::new(Metal::new(Color::new(0.8, 0.8, 0.8)));
-    let material_right = Arc::new(Metal::new(Color::new(0.8, 0.6, 0.2)));
+    let material_left = Arc::new(Metal::new(Color::new(0.8, 0.8, 0.8), 0.3));
+    let material_right = Arc::new(Metal::new(Color::new(0.8, 0.6, 0.2), 1.0));

左右でぼやけ方が違うのが分かる。

output.png

誘電マテリアル

屈折率を扱うDielectricモジュールを追加。

impl Dielectric {
    pub fn new(ref_idx: f64) -> Self {
        Self { ref_idx }
    }
}

impl Material for Dielectric {
    fn scatter(
        &self,
        r_in: &Ray,
        rec: &HitRecord,
        attenuation: &mut Color,
        scattered: &mut Ray,
    ) -> bool {
        *attenuation = Color::new(1.0, 1.0, 1.0);
        let etai_over_etat = if rec.front_face {
            1.0 / self.ref_idx
        } else {
            self.ref_idx
        };
        let unit_direction = r_in.direction.unit_vector();
        let refracted = Vec3::refract(&unit_direction, &rec.normal, etai_over_etat);
        *scattered = Ray::new(rec.p, refracted);
        true
    }
}

真ん中と左を透明にすると、変な黒点が現れる。

-    let material_center = Arc::new(Lambertian::new(Color::new(0.7, 0.3, 0.3)));
-    let material_left = Arc::new(Metal::new(Color::new(0.8, 0.8, 0.8), 0.3));
-    let material_right = Arc::new(Metal::new(Color::new(0.8, 0.6, 0.2), 1.0));
+    let material_center = Arc::new(Dielectric::new(1.5));
+    let material_left = Arc::new(Dielectric::new(1.5));
+    let material_right = Arc::new(Metal::new(Color::new(0.8, 0.6, 0.2), 0.0));

output.png

scatter()を全反射に対応させる。もはや式の意味は理解できない。写経。

@@ -91,6 +91,15 @@ impl Material for Dielectric {
             self.ref_idx
         };
         let unit_direction = r_in.direction.unit_vector();
+        let cos_theta = rec.normal.dot(-unit_direction).min(1.0);
+        let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
+
+        if etai_over_etat * sin_theta > 1.0 {
+            let reflected = Vec3::reflect(&unit_direction, &rec.normal);
+            *scattered = Ray::new(rec.p, reflected);
+            return true;
+        }
+
         let refracted = Vec3::refract(&unit_direction, &rec.normal, etai_over_etat);
         *scattered = Ray::new(rec.p, refracted);
         true

黒点が消えた!

output.png

シュリックの近似

ガラスの反射率の近似値を求める関数を追加

    pub fn schlick(cosine: f64, ref_idx: f64) -> f64 {
        let r0 = (1.0 - ref_idx) / (1.0 + ref_idx);
        let r0 = r0 * r0;
        return r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0);
    }

続写経。

@@ -100,6 +101,13 @@ impl Material for Dielectric {
             return true;
         }

+        let reflect_prob = Vec3::schlick(cos_theta, etai_over_etat);
+        if random_double() < reflect_prob {
+            let reflected = Vec3::reflect(&unit_direction, &rec.normal);
+            *scattered = Ray::new(rec.p, reflected);
+            return true;
+        }
+
         let refracted = Vec3::refract(&unit_direction, &rec.normal, etai_over_etat);
         *scattered = Ray::new(rec.p, refracted);
         true

ガラスも描けるようになった。

output.png

その8に続く。

5
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?