4
9

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でコンピューターグラフィックスの基礎を学ぶ その3

Last updated at Posted at 2026-01-21

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

今回はここから。

球をレンダリングする

まずはレイと球が衝突したら赤くするだけのコード

fn hit_sphere(center: Point3, radius: f64, r: &Ray) -> bool {
    let oc: Vec3 = r.origin() - center;
    let di: Vec3 = r.direction();
    let a: f64 = di.dot(di);
    let b: f64 = 2.0 * di.dot(oc);
    let c: f64 = oc.dot(oc) - radius * radius;
    let discriminant: f64 = b * b - 4.0 * a *c;

    return discriminant > 0.0;
}

fn ray_color(r: Ray) -> Vec3 {
    if hit_sphere(Point3::new(0.0, 0.0, -1.0), 0.5, &r) {
	return Color::new(1.0, 0.0, 0.0);
    }

    let unit_direction: Vec3 = r.direction().unit_vector();
    let t: f64 = 0.5 * (unit_direction.y + 1.0);
    return (1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0);
}

output.png

メモ

  • 同じベクトルの内積が a.dot(a) という表記方法になってやや気持ち悪い。いい糖衣構文があればいいんだけど、Rubyでも同じ表記か。内積は演算結果がf64になって型が変わってしまうので、演算子を使わない方がいいのか。
  • & の有り無しがまだ腹落ちしてない。コンパイラの言われるがまま修正しているだけ。

法線を使ったシェーディング

コードを書くのはそこまで難しくないが、理論が難しいです。

fn hit_sphere(center: Point3, radius: f64, r: &Ray) -> f64 {
    let oc: Vec3 = r.origin() - center;
    let di: Vec3 = r.direction();
    let a: f64 = di.dot(di);
    let b: f64 = 2.0 * di.dot(oc);
    let c: f64 = oc.dot(oc) - radius * radius;
    let discriminant: f64 = b * b - 4.0 * a *c;

    if discriminant < 0.0 {
	return -1.0;
    } else {
	return ( -b - discriminant.sqrt() ) / (2.0 * a);
    }
}

fn ray_color(r: Ray) -> Vec3 {
    let t: f64 = hit_sphere(Point3::new(0.0, 0.0, -1.0), 0.5, &r);
    if t > 0.0 {
	let n: Vec3 = (r.at(t) - Vec3::new(0.0, 0.0, -1.0)).unit_vector();
	return 0.5 * Color::new(n.x() + 1.0, n.y() + 1.0, n.z() + 1.0);
    }

    let unit_direction: Vec3 = r.direction().unit_vector();
    let t: f64 = 0.5 * (unit_direction.y + 1.0);
    return (1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0);
}

式の簡略化については、そこまで性能に厳しいわけではないのでスキップする。

output.png

Hittable モジュール

まるっとGemini CLIに書いてもらった。何も言われてないのにSome()使うの偉い

use crate::ray::Ray;
use crate::vec3::{Point3, Vec3};

pub struct HitRecord {
    pub p: Point3,
    pub normal: Vec3,
    pub t: f64,
}

pub trait Hittable {
    fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
}

pub struct Sphere {
    pub center: Point3,
    pub radius: f64,
}

impl Sphere {
    pub fn new(center: Point3, radius: f64) -> Self {
        Self { center, radius }
    }
}

impl Hittable for Sphere {
    fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
        let oc = r.origin() - self.center;
        let a = r.direction().length_squared();
        let half_b = oc.dot(r.direction());
        let c = oc.length_squared() - self.radius * self.radius;
        let discriminant = half_b * half_b - a * c;

        if discriminant > 0.0 {
            let root = discriminant.sqrt();

            let temp = (-half_b - root) / a;
            if temp < t_max && temp > t_min {
                let p = r.at(temp);
                let normal = (p - self.center) / self.radius;
                return Some(HitRecord { p, normal, t });
            } else {
                let p = r.at(temp);
                let normal = (p - self.center) / self.radius;
                return Some(HitRecord { p, normal, t });
            }
        }
        None
    }
}

法線の向きに対応

commitはこれ

Gemini CLIに任せたけど、mut周りがややこしくてよく分からない。normalへの値への代入を無駄に複雑にしてる気もするので、後でリファクタリングできないか考える。

次の章でC++の共有ポインタが出てくる。Rustでは共有所有権になるはず。一旦Rustに戻って勉強しないといけないので、今回はここまで。

その4に続く。

4
9
1

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
4
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?