Skip to content
Copied!
published on 2026-04-05

4. レイ・簡単なカメラ・背景

この章では,レイトレーサーの根幹である「レイを放ち,その方向から色を求める」処理を実装します。

レイとは

レイは数学的に次の式で表されます:

P(t)=A+tb
  • A:レイの原点(カメラ位置)
  • b:レイの方向ベクトル
  • t:実数パラメータ(t>0 の部分のみを考えることで,カメラから前方に向かう半直線になります)

t を変えると,レイ上の異なる点 P(t) が得られます。これを at(t) メソッドとして実装します。

Ray 構造体

C++ では ray.h として定義します。

cpp
class ray {
public:
  ray() {}
  ray(const point3& origin, const vec3& direction)
    : orig(origin), dir(direction) {}
  point3 origin() const  { return orig; }
  vec3 direction() const { return dir; }
  point3 at(double t) const { return orig + t*dir; }
public:
  point3 orig;
  vec3 dir;
};

個人的な好みでは,getterであるorigin()direction()メンバ関数がpublicであるなら,メンバ変数であるorigdirprivateにしてクラス外からは操作できないようにしたいところです(privateメンバは_orig/_dirと命名するところも)。しかしここは原典の定義のまま実装を進めていきます。Rust では common/src/ray.rs として実装します。

common/src/ray.rs
rust
use crate::vec3::{Point3, Vec3};

/// A ray defined by an origin and a direction.
pub struct Ray {
    /// Ray origin.
    pub orig: Point3,
    /// Ray direction.
    pub dir: Vec3,
}

impl Ray {
    /// Creates a new ray from `origin` and `direction`.
    pub fn new(origin: Point3, direction: Vec3) -> Self {
        Ray { orig: origin, dir: direction }
    }

    /// Returns the ray origin.
    pub fn origin(&self) -> Point3 { self.orig }
    /// Returns the ray direction.
    pub fn direction(&self) -> Vec3 { self.dir }

    /// Returns the point at parameter `t` along the ray: **P**(*t*) = **orig** + *t* **dir**.
    pub fn at(&self, t: f64) -> Point3 {
        self.orig + t * self.dir
    }
}

C++ と Rust の違い

const メンバ関数と &self

C++ の const メンバ関数は,this ポインタを const として受け取り,オブジェクト自体の変更を禁じます。

cpp
point3 origin() const { return orig; }
vec3 direction() const { return dir; }
point3 at(double t) const { return orig + t*dir; }

Rust では &self(不変借用)を使うことで同じ意味を表現します。

rust
pub fn origin(&self) -> Point3 { self.orig }
pub fn direction(&self) -> Vec3 { self.dir }
pub fn at(&self, t: f64) -> Point3 { self.orig + t * self.dir }

Rust のメソッドの self レシーバは 3 種類あります。

レシーバ意味
self値渡し(所有権を移動またはコピー)
&self不変借用(読み取り専用,コピーなし)
&mut self可変借用(書き込み可能)

origin()direction() の戻り値は Point3 = Vec3Copy traitをderiveしている)なので,&self でフィールドを借用しつつ,その値をコピーして返せます。

デフォルトコンストラクタなし

C++ の ray() は引数なしのデフォルトコンストラクタです。Rust に言語レベルのデフォルトコンストラクタはないため,必要な場合は Default trait を実装します。Ray を使うすべての箇所は必ず原点と方向を指定するため,本実装では省略します。

カメラの設定

カメラは次のパラメータで定義します。

パラメータ説明
アスペクト比16:9画像の横幅 : 縦幅
ビューポート高さ2.0 単位仮想スクリーンの高さ
焦点距離1.0 単位カメラ位置からビューポートまでの距離
カメラ位置(0, 0, 0)右手座標系,z 負方向が前方

ビューポート左下隅の座標(lower_left_corner)は,各ピクセルへのレイ方向を計算する基準点です:

lower_left_corner=originhorizontal2vertical2(0, 0, focal_length)

各ピクセルに対して,水平方向のパラメータ u と垂直方向のパラメータ v[0,1] で正規化し,レイの方向を求めます。

direction=lower_left_corner+uhorizontal+vverticalorigin

背景色の線形補間

ray_color 関数は,レイが何にも当たらなかった場合に返す「空の色」を計算します。レイの方向 y 成分を使って白と青をブレンドします。これを線形補間(linear interpolation,lerp)と呼びます:

blended(t)=(1t)+t(0t1)

レイの方向を正規化すると y 成分は [1.0, 1.0] の範囲になります。これを [0, 1] に変換してブレンド係数 t とします。

t=0.5×(y+1.0)
  • y=1.0(真上)→ t=1.0:青 (0.5, 0.7, 1.0)
  • y=0.0(水平)→ t=0.5:中間色
  • y=1.0(真下)→ t=0.0:白 (1.0, 1.0, 1.0)

グラデーションは y 方向だけでなく斜め方向にも現れます(正規化後の方向は x 成分によっても y が変わるため)。

C++ と Rust の違い

整数から浮動小数点への変換

C++ では整数から double への暗黙変換が行われますが,Rust では as キャストを明示的に書く必要があります。

cpp
// C++
auto u = double(i) / (image_width-1);
rust
// Rust
let u = i as f64 / (image_width - 1) as f64;

image_widthi32 型なので,image_width - 1i32 のまま演算し,as f64 で浮動小数点に変換します。

r104-ray-camera-background クレートの実装

r104-ray-camera-background/Cargo.toml を用意します。

r104-ray-camera-background/Cargo.toml
toml
[package]
name = "r104-ray-camera-background"
version = "0.1.0"
edition = "2024"

[dependencies]
common = { workspace = true }

r104-ray-camera-background/src/lib.rs に実装します。

r104-ray-camera-background/src/lib.rs
rust
use common::{Color, Point3, Vec3, unit_vector, write_color, Ray};

fn ray_color(r: &Ray) -> Color {
    let unit_direction = unit_vector(r.direction());
    let t = 0.5 * (unit_direction.y() + 1.0);
    (1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0)
}

pub fn render_image() -> String {
    // Image dimensions
    let aspect_ratio = 16.0_f64 / 9.0;
    let image_width = 384_i32;
    let image_height = (image_width as f64 / aspect_ratio) as i32;

    // Camera setup
    let viewport_height = 2.0_f64;
    let viewport_width = aspect_ratio * viewport_height;
    let focal_length = 1.0_f64;

    let origin = Point3::new(0.0, 0.0, 0.0);
    let horizontal = Vec3::new(viewport_width, 0.0, 0.0);
    let vertical = Vec3::new(0.0, viewport_height, 0.0);
    let lower_left_corner =
        origin - horizontal / 2.0 - vertical / 2.0 - Vec3::new(0.0, 0.0, focal_length);

    // Render
    let mut output = String::new();
    output.push_str("P3\n");
    output.push_str(&format!("{} {}\n", image_width, image_height));
    output.push_str("255\n");

    for j in (0..image_height).rev() {
        for i in 0..image_width {
            let u = i as f64 / (image_width - 1) as f64;
            let v = j as f64 / (image_height - 1) as f64;
            let r = Ray::new(
                origin,
                lower_left_corner + u * horizontal + v * vertical - origin,
            );
            let pixel_color = ray_color(&r);
            output.push_str(&format!("{}\n", write_color(pixel_color)));
        }
    }

    output
}

ray_color の borrowing

ray_color&Ray を受け取ります。RayCopy traitを実装していないため,値渡し(r: Ray)にすると所有権が移動してしまいます。&Ray で借用することで,レイの内容を読み取りながら呼び出し元の所有権を保持できます。

r.direction()Vec3 を返します。Vec3Copy なので,&self の中のフィールドをコピーして値として返せます。

common クレートへの追記と WASM エクスポート

common/src/ray.rs を新規作成し,common/src/lib.rs に追記します。

common/src/lib.rs
rust
// common/src/lib.rs
pub mod vec3;
pub use vec3::{Color, Point3, Vec3, cross, dot, unit_vector, write_color};

pub mod ray;
pub use ray::Ray;

raytracing-demos/src/lib.rs に WASM エクスポート関数を追記します。

raytracing-demos/src/lib.rs
rust
// Chapter 1.4: Ray, Camera, Background
#[wasm_bindgen]
pub fn render_sky() -> String {
    r104_ray_camera_background::render_image()
}