1. raytracing_demos の構成
このページでは,Ray Tracingプロジェクト固有のWASM構成を扱います。VitePress一般のWASM読み込み制約(fetch + Blob URL など)は雑想ノートを参照してください。
ワークスペースの cargo-doc はここで見られます。
ワークスペース全体像
master/wasm/raytracing は Cargo ワークスペースで,以下の構成になっています。
master/wasm/raytracing/
├── Cargo.toml # ワークスペース定義
├── build.sh # wasm-pack ビルドスクリプト
├── common/ # 共通ライブラリ(wasm-bindgen 非依存)
│ └── src/
│ ├── lib.rs
│ ├── vec3.rs # Vec3, Color, Point3, 各種関数
│ ├── ray.rs # Ray
│ ├── hittable.rs # HitRecord, Hittable トレイト
│ ├── hittable_list.rs # HittableList
│ ├── sphere.rs # Sphere
│ ├── material.rs # Material トレイト, Lambertian, Metal, Dielectric
│ ├── camera.rs # Camera
│ └── utils.rs # 乱数, 数学定数
├── r102-gradient/ # 1.2章
├── r103-vec3/ # 1.3章
│ ⋮ (各章クレート)
├── r113-final-scene-hq/ # 1.13章(高品質版)
└── raytracing-demos/ # WASM エクスポート傘クレート(cdylib)
└── src/
└── lib.rs3層の依存関係は以下のとおりです。
common (rlib) ← r1XX-* (rlib) ← raytracing-demos (cdylib) → WASMVitePress から直接呼ばれるのは raytracing-demos がエクスポートする関数群のみです。
Cargo ワークスペースの設定
Cargo.toml(ワークスペース直下)は共通依存のバージョンを一元管理します。
[workspace]
members = [
"common",
"r102-gradient",
# ... 各章クレート ...
"raytracing-demos",
]
resolver = "2"
[workspace.dependencies]
common = { path = "common" }
wasm-bindgen = "0.2"[workspace.dependencies] に記載した依存は,各クレートの Cargo.toml から { workspace = true } で参照できます。章クレートが common のバージョンを個別管理しなくて済むのはこのためです。
3層構成
common:共通ライブラリ
common は純粋な Rust ライブラリで,wasm-bindgen に依存しません。
# common/Cargo.toml
[package]
name = "common"
version = "0.1.0"
edition = "2024"
[dependencies]
# wasm-bindgen はないlib.rs がすべてのモジュールをクレート外へ再エクスポートします。
// common/src/lib.rs(抜粋)
pub mod vec3;
pub use vec3::{
Color, Point3, Vec3, dot, cross, reflect, refract, unit_vector,
write_color, write_color_gamma, random_in_unit_sphere, /* ... */
};
pub mod ray; pub use ray::Ray;
pub mod hittable; pub use hittable::{HitRecord, Hittable};
pub mod sphere; pub use sphere::Sphere;
pub mod material; pub use material::{Material, Lambertian, Metal, Dielectric};
pub mod hittable_list; pub use hittable_list::HittableList;
pub mod camera; pub use camera::Camera;
pub mod utils; pub use utils::{INFINITY, PI, degrees_to_radians, random_double, /* ... */};章クレート(r1XX-*)
各章のデモを実装するクレートです。命名規則は次のとおりです。
r + 週番号 + 章番号2桁 + - + 説明
例: r102-gradient, r106-sphere-normals, r109-metal-fuzz- 先頭に
rがついているのは,Cargo はクレート名の数値始まりを禁止しているため,raytracing っぽい接頭辞をつけた - 同じ章に複数のデモがある場合は説明部分を変える(例:
r108-diffuse,r108-lambertian,r108-hemisphere) - 第2週(Ray Tracing: The Next Week)は
r2XX-*,第3週はr3XX-*と同じ規則で続く
章クレートは common だけに依存し,wasm-bindgen を持ちません。
# r109-metal/Cargo.toml(例)
[package]
name = "r109-metal"
version = "0.1.0"
edition = "2024"
[dependencies]
common = { workspace = true }各クレートは公開 API として pub fn render_image() -> String をただひとつ持ちます。
// r102-gradient/src/lib.rs(例)
pub fn render_image() -> String {
let mut output = String::new();
output.push_str("P3\n256 256\n255\n");
for j in 0..256 {
for i in 0..256 {
// R, G, B 値を計算して書き込む
output.push_str(&format!("{} {} {}\n", ir, ig, ib));
}
}
output
}wasm-bindgen への依存がないため,cargo test で通常の Rust テストとして動作確認できます。
raytracing-demos:傘クレート
全章クレートを束ねて WASM バイナリとして出力するクレートです。
# raytracing-demos/Cargo.toml
[lib]
crate-type = ["cdylib"] # WASM(.wasm)として出力するために必要
[dependencies]
r102-gradient = { version = "*", path = "../r102-gradient" }
# ... 全章クレート ...
r113-final-scene-hq = { version = "*", path = "../r113-final-scene-hq" }
wasm-bindgen.workspace = truelib.rs では各章クレートの render_image() を #[wasm_bindgen] でラップするだけです。
// raytracing-demos/src/lib.rs(抜粋)
use wasm_bindgen::prelude::*;
// 1.2: Gradient
#[wasm_bindgen] pub fn render_gradient() -> String { r102_gradient::render_image() }
// 1.4: Ray, Camera, Background
#[wasm_bindgen] pub fn render_sky() -> String { r104_ray_camera_background::render_image() }
// 1.5: Rendering a Sphere
#[wasm_bindgen] pub fn render_sphere() -> String { r105_sphere::render_image() }
// ...
// 1.13: Final Scene (high quality)
#[wasm_bindgen] pub fn render_final_scene_hq() -> String { r113_final_scene_hq::render_image() }この設計により,各章のレンダリングロジックは wasm-bindgen なしにテストでき,エクスポート宣言だけが raytracing-demos に集約されます。
新しいデモを追加する手順
新しい章が増えたとき(原典 v4 の追加章,第2週の章など)の手順は常に同じです。
1. 章クレートを作成する
# master/wasm/raytracing/ 内で実行
cargo new --lib r114-new-chapterr114-new-chapter/Cargo.toml に edition = "2024" と,必要なら common = { workspace = true } を追加します。src/lib.rs に pub fn render_image() -> String { … } を実装します。
2. ワークスペース Cargo.toml の members に追加する
[workspace]
members = [
# ...
"r114-new-chapter", # ← 追加
"raytracing-demos",
]3. raytracing-demos/Cargo.toml の [dependencies] に追加する
r114-new-chapter = { version = "*", path = "../r114-new-chapter" }4. raytracing-demos/src/lib.rs にエクスポートを追加する
#[wasm_bindgen]
pub fn render_new_chapter() -> String {
r114_new_chapter::render_image()
}以上です。あとは build.sh を実行すれば,新しい関数が含まれた WASM バイナリが生成されます。第2週(r2XX-*)以降でも手順は変わりません。
ビルド
# master/wasm/raytracing/ 内で実行
bash build.sh内部では次のコマンドが実行されます。
wasm-pack build --target web raytracing-demos \
--out-dir <repo>/docs/public/wasm/raytracing_demos --release--target web で ES Modules 形式(raytracing_demos.js + raytracing_demos_bg.wasm)が出力されます。--release を外すとデバッグビルドになりますが,レイトレーシングのような計算負荷の高い処理では速度が大幅に落ちます。
PPM 文字列を返す理由
各デモ関数が String で PPM(P3)を返す設計にしているのは,以下の理由によります。
- 原典の学習手順(C++ のターミナル文字列出力を Rust で段階的に再実装する流れ)に沿える
- WebAssembly 境界をまたいだデータ受け渡しが単純になる
- デバッグ時にヘッダー(
P3 / 幅 高さ / 255)を目視確認しやすい
VitePress 側の受け取り
VitePress では PPMRenderer コンポーネントが次を担当します。
raytracing_demos.jsをfetch + Blob URL方式で読み込み,WASM を初期化- 目的のエクスポート関数(例:
render_gradient)を呼び出す - 返ってきた PPM 文字列をパースして
canvasへ描画
canvas サイズは PPM ヘッダーから先に取得し,Vue のリアクティブ更新が完了してから描画します。順序を誤ると描画後に canvas がリセットされるためです(詳細はPPMRenderer の実装を参照)。
配置場所
build.sh は wasm-pack の出力先を docs/public/wasm/raytracing_demos/ に指定しています。生成された raytracing_demos.js / raytracing_demos_bg.wasm はここへ自動的に配置されます。