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

4. ソリッドテクスチャ

Ray Tracing: The Next Week (v3.2.3): 4 Solid Textures / 2.4 ソリッドテクスチャ

グラフィックスにおけるテクスチャとは,物体表面の色をプロシージャルに(あるいは画像参照によって)決める関数の総称です。ソリッドテクスチャは入力に世界座標 (x,y,z) だけを使うタイプで,木目や大理石など「内側まで詰まった素材」を自然に表現できます。

本章では「色」をテクスチャに格上げし,マテリアルが定数色だけでなく任意のテクスチャを参照できるようにします。新規の道具立ては次の 4 つです。

  • Texture トレイト:value(u, v, p) で点に色を返す
  • SolidColor:定数色のテクスチャ
  • CheckerTexture:3 次元チェッカー
  • HitRecord(u,v) を追加し,Sphere / MovingSphere で計算する

Lambertian のメンバを Color から Arc<dyn Texture> に切り替えますが,Lambertian::new(Color) のシグネチャは互換維持します(内部で SolidColor に包む)。これにより第 1 編の r1XX クレートと第 2 編の r201 / r203 はそのままビルドできます。

Texture トレイトと SolidColor

common/src/texture.rs
rust
pub trait Texture: Send + Sync {
    fn value(&self, u: f64, v: f64, p: Point3) -> Color;
}

pub struct SolidColor {
    pub color_value: Color,
}

impl SolidColor {
    pub fn new(color_value: Color) -> Self {
        SolidColor { color_value }
    }
}

impl Texture for SolidColor {
    fn value(&self, _u: f64, _v: f64, _p: Point3) -> Color {
        self.color_value
    }
}

Send + Sync を要求するのは,Arc<dyn Texture> を将来的に別スレッドへ渡したり並列レンダリングへ拡張する余地を確保するためです。Material トレイトと同じ流儀です。

HitRecord(u,v) を加える

テクスチャは表面座標 (u,v) を必要とするので,HitRecord にこの 2 値を保存します。

common/src/hittable.rs
rust
pub struct HitRecord {
    pub p: Point3,
    pub normal: Vec3,
    pub t: f64,
    pub u: f64,   // ← 追加
    pub v: f64,   // ← 追加
    pub front_face: bool,
    pub mat: Option<Arc<dyn Material>>,
}

HitRecord を構築するのは Sphere::hitMovingSphere::hit の 2 箇所だけです。両方とも更新します。

球の (u,v) 座標

単位球上の点 p を緯度・経度に対応させ,それを [0,1]2 に正規化します。
球面上の点 p=(x,y,z) について

y=cosθx=cosϕsinθz=sinϕsinθ

と置くと,逆解は

θ=arccos(y),ϕ=atan2(z,x)+π

で,atan2 の値域 (π,π]π を足して [0,2π] にしてから

u=ϕ2π,v=θπ

と正規化します。これを単純な自由関数として実装します。

common/src/sphere.rs
rust
pub fn get_sphere_uv(p: Point3) -> (f64, f64) {
    // p.y() is in [-1, 1]; theta is in [0, pi]; phi is in [0, 2*pi].
    let theta = (-p.y()).acos();
    let phi = (-p.z()).atan2(p.x()) + PI;
    (phi / (2.0 * PI), theta / PI)
}

Sphere::hit では outward_normal(中心から表面への単位ベクトル)が単位球上の点に他ならないので,そのまま渡せます。

common/src/sphere.rs
rust
let outward_normal = (p - self.center) / self.radius;
let (u, v) = get_sphere_uv(outward_normal);
let mut rec = HitRecord {
    t: root,
    u, v,
    p,
    normal: outward_normal,
    front_face: false,
    mat: self.material.clone(),
};

MovingSphere::hit も同じく outward_normal から (u,v) を計算します(半径での割り算で単位球上の点になる)。

C++ と Rust の違い

C++ 原典では get_sphere_uvsphere クラスの private static メソッドとして書き,出力引数 double& u, double& v で値を返します。Rust ではタプル (f64, f64) を返すフリー関数として実装し,MovingSphere からも pub use 経由で再利用できるようにしました。出力引数は Rust ではあまり使われず,値を返すスタイルが標準的です。

Lambertian のテクスチャ化

Lambertian が保持するのは「色」ではなく「テクスチャ」になります。

common/src/material.rs
rust
pub struct Lambertian {
    pub albedo: Arc<dyn Texture>,
}

impl Lambertian {
    /// Compatibility constructor: wraps a constant color in `SolidColor`.
    pub fn new(albedo: Color) -> Self {
        Lambertian {
            albedo: Arc::new(SolidColor::new(albedo)),
        }
    }

    /// Variant that accepts any texture directly.
    pub fn with_texture(albedo: Arc<dyn Texture>) -> Self {
        Lambertian { albedo }
    }
}

impl Material for Lambertian {
    fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color, Ray)> {
        let mut scatter_direction = rec.normal + random_unit_vector();
        if scatter_direction.near_zero() {
            scatter_direction = rec.normal;
        }
        let scattered = Ray::with_time(rec.p, scatter_direction, r_in.time());
        let attenuation = self.albedo.value(rec.u, rec.v, rec.p);
        Some((attenuation, scattered))
    }
}

Lambertian::new(Color) の呼び出し箇所はすべて従来どおり動作し,テクスチャを使いたい新コードは Lambertian::with_texture(...) を呼びます。これで第 1 編・第 2 編前半のクレート群はそのままビルドできます。

C++ と Rust の違い

C++ 原典は lambertian のメンバを shared_ptr<texture> albedo にしてコンストラクタを 2 つオーバーロードします。Rust にはコンストラクタも関数オーバーロードもないので,前章までの Vec::new / Vec::with_capacity 流儀に合わせて newwith_texture で名前を分けました。Material::scatter の中で albedo->value(...) を呼ぶ流れは原典と同じです。

MetalDielectric には今のところテクスチャ化の必要がない(金属は単色アルベド,誘電体はそもそもアルベドを持たない)ので変更しません。

チェッカーテクスチャ

3 軸の正弦の積 sin(10x)sin(10y)sin(10z) の符号は,π/10 周期で 3 次元のチェッカーパターンを作ります。これを使って 2 つの子テクスチャを切り替えるだけです。

common/src/texture.rs
rust
pub struct CheckerTexture {
    pub even: Arc<dyn Texture>,
    pub odd: Arc<dyn Texture>,
}

impl CheckerTexture {
    pub fn new(even: Arc<dyn Texture>, odd: Arc<dyn Texture>) -> Self {
        CheckerTexture { even, odd }
    }

    /// Convenience constructor from two solid colors.
    pub fn from_colors(c1: Color, c2: Color) -> Self {
        CheckerTexture {
            even: Arc::new(SolidColor::new(c1)),
            odd: Arc::new(SolidColor::new(c2)),
        }
    }
}

impl Texture for CheckerTexture {
    fn value(&self, u: f64, v: f64, p: Point3) -> Color {
        let sines = (10.0 * p.x()).sin() * (10.0 * p.y()).sin() * (10.0 * p.z()).sin();
        if sines < 0.0 {
            self.odd.value(u, v, p)
        } else {
            self.even.value(u, v, p)
        }
    }
}

even / oddArc<dyn Texture> にしているので,子テクスチャに別の CheckerTexture を入れたり,将来のパーリンノイズや画像テクスチャを混ぜたりできます。Pat Hanrahan が 1980 年代に提案したシェーダーネットワークの発想です。

デモ 1:r204-checker-ground

2.3 の random_scene() の地面の球(半径 1000 の灰色球)だけを,CheckerTexture::from_colors で作ったチェッカーに差し替えます。

r204-checker-ground/src/lib.rs
rust
let checker = Arc::new(CheckerTexture::from_colors(
    Color::new(0.2, 0.3, 0.1),
    Color::new(0.9, 0.9, 0.9),
));
world.add(Box::new(Sphere::with_material(
    Point3::new(0.0, -1000.0, 0.0),
    1000.0,
    Arc::new(Lambertian::with_texture(checker)),
)));

シーンの残り(小さな球とその上の 3 つの大きな球,BVH,シャッター区間)はすべて 2.3 と同じです。BVH のおかげで 100 spp でも妥当な時間で描画できます。
ページ冒頭の WASM デモで確認できます。地面は球なので,チェッカーは大きく湾曲して見えます。

デモ 2:r204-checker-spheres

原典の Image 3 に対応する two_spheres() シーンを書き起こします。同じチェッカーテクスチャを共有する半径 10 の 2 球(中心 (0,10,0)(0,10,0))を配置するだけのシンプルなシーンです。

r204-checker-spheres/src/lib.rs
rust
fn two_spheres() -> HittableList {
    let mut objects = HittableList::new();

    let checker = Arc::new(CheckerTexture::from_colors(
        Color::new(0.2, 0.3, 0.1),
        Color::new(0.9, 0.9, 0.9),
    ));
    objects.add(Box::new(Sphere::with_material(
        Point3::new(0.0, -10.0, 0.0),
        10.0,
        Arc::new(Lambertian::with_texture(checker.clone())),
    )));
    objects.add(Box::new(Sphere::with_material(
        Point3::new(0.0, 10.0, 0.0),
        10.0,
        Arc::new(Lambertian::with_texture(checker)),
    )));

    objects
}

カメラパラメータは lookfrom = (13, 2, 3)lookat = (0, 0, 0)vfov = 20°,絞り 0(被写界深度なし),シャッター区間 [0,1] ですが動く物体は無いのでブラーも生じません。

checker.clone() で 2 つの Lambertian に同じ Arc を共有させているのは,テクスチャ実体を 1 つに保ちたいからです。Arc の参照カウントが増えるだけで,テクスチャ自体は複製されません。

まとめ

  • Texture トレイト・SolidColorCheckerTexturecommon/ に追加した。
  • HitRecord(u,v) を加え,Sphere / MovingSphereget_sphere_uv から計算するようにした。
  • Lambertian を「色」ではなく「テクスチャ」を持つマテリアルに変更した。Lambertian::new(Color) の互換性は維持し,新たに Lambertian::with_texture(Arc<dyn Texture>) を追加した。
  • r204-checker-groundr204-checker-spheres の 2 つのデモで効果を確認した。

これで「色をテクスチャとして抽象化する」基盤が整いました。次章ではこの上にパーリンノイズを載せ,木目や大理石のような自然な質感の生成へ進みます。