5. 球のレンダリング
Ray Tracing in One Weekend (v3.2.3): 5 Adding a Sphere / 1.5 球のレンダリング
この章では,初めて「物体」をシーンに配置します。題材は最もシンプルな立体である球です。レイと球の交差判定を数学的に導出し,当たった部分を赤く塗ります。
球とレイの交差判定
球の方程式
中心
これは「
レイを代入する
レイ
展開すると:
これは
| 変数 | 式 |
|---|---|
判別式
二次方程式の判別式
| 判別式 | 意味 | 交差の様子 |
|---|---|---|
| 2個の実数解 | レイが球を貫通 | |
| 1個の実数解 | レイが球に接する | |
| 実数解なし | レイが球を外れる |
この章では
hit_sphere 関数
C++ の実装です。
bool hit_sphere(const point3& center, double radius, const ray& r) {
vec3 oc = r.origin() - center;
auto a = dot(r.direction(), r.direction());
auto b = 2.0 * dot(oc, r.direction());
auto c = dot(oc, oc) - radius*radius;
auto discriminant = b*b - 4*a*c;
return (discriminant > 0);
}Rust の実装です。
fn hit_sphere(center: Point3, radius: f64, r: &Ray) -> bool {
let oc = r.origin() - center;
let a = dot(r.direction(), r.direction());
let b = 2.0 * dot(oc, r.direction());
let c = dot(oc, oc) - radius * radius;
let discriminant = b * b - 4.0 * a * c;
discriminant > 0.0
}C++ と Rust の違い
最後の式が戻り値になる
Rust の関数では,最後に置いたセミコロンなしの式がそのまま戻り値になります。
fn hit_sphere(...) -> bool {
// ...
discriminant > 0.0 // No semicolon → this bool value is the return value
}return discriminant > 0.0; と書いても同じですが,Rust では慣例として関数末尾の return を省略します。セミコロンの有無が「式(値を生成する)」か「文(値を捨てる)」かを区別するのが Rust の文法上の特徴です。
一方,ray_color のように途中で早期リターンする場合は明示的に return が必要です。
fn ray_color(r: &Ray) -> Color {
if hit_sphere(Point3::new(0.0, 0.0, -1.0), 0.5, r) {
return Color::new(1.0, 0.0, 0.0); // Early return
}
// ...
(1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0)
// The trailing expression is the return value
}浮動小数点との比較
C++ の discriminant > 0 は double と int の比較ですが,暗黙の型変換により問題なくコンパイルされます。
return (discriminant > 0); // double > int: implicit conversion is fineRust では異なる型の比較はコンパイルエラーになります。discriminant は f64 なので,比較対象も f64 でなければなりません。
discriminant > 0.0 // f64 > f64:OK
// discriminant > 0 // f64 > i32: compile error引数の値渡しと参照渡し
C++ の引数と Rust での対応を整理します。
bool hit_sphere(const point3& center, double radius, const ray& r)fn hit_sphere(center: Point3, radius: f64, r: &Ray) -> boolcenter: Point3:Point3(=Vec3)はCopyを実装しているため,値渡しでもコストなくコピーされます。C++ のconst point3&と実質同等です。radius: f64:f64もCopyなので値渡しで問題ありません。r: &Ray:RayはCopyを実装していないため,所有権を渡さず&Rayで借用します。
if のブレース必須
C++ では if の本体が一文の場合,ブレースを省略できます。Rust では常にブレースが必要です。
// C++: braces can be omitted
if (hit_sphere(point3(0,0,-1), 0.5, r))
return color(1, 0, 0);// Rust: braces are required
if hit_sphere(Point3::new(0.0, 0.0, -1.0), 0.5, r) {
return Color::new(1.0, 0.0, 0.0);
}r105-sphere クレートの実装
r105-sphere/Cargo.toml を用意します。
[package]
name = "r105-sphere"
version = "0.1.0"
edition = "2024"
[dependencies]
common = { workspace = true }r105-sphere/src/lib.rs に完全な実装を置きます。カメラ設定とレンダリングループは前章の r104-ray-camera-background と同一で,hit_sphere 関数の追加と ray_color 関数の更新が変更点です。
use common::{Color, Point3, Vec3, dot, unit_vector, write_color, Ray};
fn hit_sphere(center: Point3, radius: f64, r: &Ray) -> bool {
let oc = r.origin() - center;
let a = dot(r.direction(), r.direction());
let b = 2.0 * dot(oc, r.direction());
let c = dot(oc, oc) - radius * radius;
let discriminant = b * b - 4.0 * a * c;
discriminant > 0.0
}
fn ray_color(r: &Ray) -> Color {
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 = 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 {
let aspect_ratio = 16.0_f64 / 9.0;
let image_width = 384_i32;
let image_height = (image_width as f64 / aspect_ratio) as i32;
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);
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
}WASM エクスポート
raytracing-demos/src/lib.rs に追記します。
// Chapter 1.5: Rendering a Sphere
#[wasm_bindgen]
pub fn render_sphere() -> String {
r105_sphere::render_image()
}