4. レイ・簡単なカメラ・背景
Ray Tracing in One Weekend (v3.2.3): 4 Rays, a Simple Camera, and Background / 1.4 レイ・簡単なカメラ・背景
この章では,レイトレーサーの根幹である「レイを放ち,その方向から色を求める」処理を実装します。
レイとは
レイは数学的に次の式で表されます:
:レイの原点(カメラ位置) :レイの方向ベクトル :実数パラメータ( の部分のみを考えることで,カメラから前方に向かう半直線になります)
at(t) メソッドとして実装します。
Ray 構造体
C++ では ray.h として定義します。
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であるなら,メンバ変数であるorigとdirはprivateにしてクラス外からは操作できないようにしたいところです(privateメンバは_orig/_dirと命名するところも)。しかしここは原典の定義のまま実装を進めていきます。Rust では common/src/ray.rs として実装します。
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 として受け取り,オブジェクト自体の変更を禁じます。
point3 origin() const { return orig; }
vec3 direction() const { return dir; }
point3 at(double t) const { return orig + t*dir; }Rust では &self(不変借用)を使うことで同じ意味を表現します。
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 = Vec3(Copy traitをderiveしている)なので,&self でフィールドを借用しつつ,その値をコピーして返せます。
デフォルトコンストラクタなし
C++ の ray() は引数なしのデフォルトコンストラクタです。Rust に言語レベルのデフォルトコンストラクタはないため,必要な場合は Default trait を実装します。Ray を使うすべての箇所は必ず原点と方向を指定するため,本実装では省略します。
カメラの設定
カメラは次のパラメータで定義します。
| パラメータ | 値 | 説明 |
|---|---|---|
| アスペクト比 | 16:9 | 画像の横幅 : 縦幅 |
| ビューポート高さ | 2.0 単位 | 仮想スクリーンの高さ |
| 焦点距離 | 1.0 単位 | カメラ位置からビューポートまでの距離 |
| カメラ位置 | (0, 0, 0) | 右手座標系,z 負方向が前方 |
ビューポート左下隅の座標(lower_left_corner)は,各ピクセルへのレイ方向を計算する基準点です:
各ピクセルに対して,水平方向のパラメータ
背景色の線形補間
ray_color 関数は,レイが何にも当たらなかった場合に返す「空の色」を計算します。レイの方向 y 成分を使って白と青をブレンドします。これを線形補間(linear interpolation,lerp)と呼びます:
レイの方向を正規化すると y 成分は
(真上)→ :青 (水平)→ :中間色 (真下)→ :白
グラデーションは y 方向だけでなく斜め方向にも現れます(正規化後の方向は x 成分によっても y が変わるため)。
C++ と Rust の違い
整数から浮動小数点への変換
C++ では整数から double への暗黙変換が行われますが,Rust では as キャストを明示的に書く必要があります。
// C++
auto u = double(i) / (image_width-1);// Rust
let u = i as f64 / (image_width - 1) as f64;image_width は i32 型なので,image_width - 1 も i32 のまま演算し,as f64 で浮動小数点に変換します。
r104-ray-camera-background クレートの実装
r104-ray-camera-background/Cargo.toml を用意します。
[package]
name = "r104-ray-camera-background"
version = "0.1.0"
edition = "2024"
[dependencies]
common = { workspace = true }r104-ray-camera-background/src/lib.rs に実装します。
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 を受け取ります。Ray は Copy traitを実装していないため,値渡し(r: Ray)にすると所有権が移動してしまいます。&Ray で借用することで,レイの内容を読み取りながら呼び出し元の所有権を保持できます。
r.direction() は Vec3 を返します。Vec3 は Copy なので,&self の中のフィールドをコピーして値として返せます。
common クレートへの追記と WASM エクスポート
common/src/ray.rs を新規作成し,common/src/lib.rs に追記します。
// 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 エクスポート関数を追記します。
// Chapter 1.4: Ray, Camera, Background
#[wasm_bindgen]
pub fn render_sky() -> String {
r104_ray_camera_background::render_image()
}