ソリッドテクスチャ
グラフィックスにおける「テクスチャ」は物体表面の色を何らかの規則に従ってプロシージャルに生成する関数を表す。この規則には画像を合成するコードや既存の画像のルックアップが関係し、両方が含まれる場合もある。この節ではまずこれまでのプログラムに含まれる全ての色をテクスチャに変更する。定数の RGB 色とテクスチャを異なるクラスで管理するプログラムも多いので、別の方法を取っても構わない。ただ任意の色をテクスチャとして扱えるのは美しいので、私はこのアーキテクチャを大いに気に入っている。
最初のテクスチャクラス: 定数テクスチャ
#include "rtweekend.h"
class texture {
public:
virtual ~texture() {}
virtual color value(double u, double v, const point3& p) const = 0;
};
class solid_color : public texture {
public:
solid_color() {}
solid_color(color c) : color_value(c) {}
solid_color(double red, double green, double blue)
: solid_color(color(red,green,blue)) {}
virtual color value(double u, double v, const vec3& p) const {
return color_value;
}
private:
color color_value;
};
texture.h
] texture
クラス
レイとオブジェクトの交点のテクスチャ座標 \((u, v)\) を保存するよう hit_record
構造体を変更する必要がある:
struct hit_record {
vec3 p;
vec3 normal;
shared_ptr<material> mat_ptr;
double t;
double u;
double v;
bool front_face;
...
hittable.h
] hit_record
にテクスチャ座標を追加する
続いて hittable
を継承するクラスでテクスチャ座標 \((u, v)\) の計算を行う。
球のテクスチャ座標
球では緯度と経度からなる球面座標を使ってテクスチャ座標を計算する。球上の点の球面座標 \((\theta, \phi)\) が分かれば、\(\theta\) と \(\phi\) に定数を乗じることでテクスチャ座標が求まる。\(\theta\) が極から下向きに測った角度で \(\phi\) が極を通る軸周りの角度なら、二つの角度から \([0,1]\) への変換は \[ u = \frac{\phi}{2\pi}, \quad v = \frac{\theta}{\pi} \] となる。
交点に対応する \(\theta\) と \(\phi\) の計算では、単位球上の点に対する球面座標の公式を利用する: \[ \begin{aligned} x & = \cos\phi \cos\theta \\ y & = \sin\phi \cos\theta \\ z & = \sin\theta \end{aligned} \] これを逆にすればよい。<cmath>
にはサインとコサインの値から角度を求める素晴らしい関数 atan2()
があるから、これに \(x\) と \(y\) を渡せば \[ \phi = \operatorname{atan2}(y, x) \] となる (\(\cos\theta\) は打ち消される)。\(\operatorname{atan2}\) は \(-\pi\) から \(\pi\) の値を取るので、この後に少し調整が必要になる。また \(\theta\) は簡単に求まる: \[ \theta = \operatorname{asin}(z) \] \(\operatorname{asin}\) は \(-\pi/2\) から \(\pi/2\) の値を返す。
sphere::hit
関数での \((u, v)\) 座標の計算はユーティリティ関数を使って行う。この関数が受け取るのは単位球上の点であり、呼び出し側では次のようにする:
get_sphere_uv((rec.p-center)/radius, rec.u, rec.v);
sphere.h
] 交点の UV 座標を求める
ユーティリティ関数の本体を示す:
void get_sphere_uv(const vec3& p, double& u, double& v) {
auto phi = atan2(p.z(), p.x());
auto theta = asin(p.y());
u = 1-(phi + pi) / (2*pi);
v = (theta + pi/2) / pi;
}
sphere.h
] get_sphere_uv
関数
後はマテリアルクラスの色を保持する変数 const color& a
をテクスチャへのポインタ shared_ptr<texture> a
に変更すれば、テクスチャを使うマテリアルが完成する:
class lambertian : public material {
public:
lambertian(shared_ptr<texture> a) : albedo(a) {}
virtual bool scatter(
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
) const {
vec3 scatter_direction = rec.normal + random_unit_vector();
scattered = ray(rec.p, scatter_direction, r_in.time());
attenuation = albedo->value(rec.u, rec.v, rec.p);
return true;
}
public:
shared_ptr<texture> albedo;
};
material.h
] テクスチャを使ったランバーティアンマテリアル
これまでのコードには次のような部分があった:
...make_shared<lambertian>(color(0.5, 0.5, 0.5))
main.cc
] color
を使ったランバーティアンマテリアル
この color(...)
を make_shared<solid_color>(...)
に入れ替えよう:
...make_shared<lambertian>(make_shared<solid_color>(0.5, 0.5, 0.5))
main.cc
] テクスチャを使ったランバーティアンマテリアル
main.cc
の random_scene()
にある三つのランバーティアンを更新すれば、以前と同じ画像をレンダリングできるようになる。
縞模様テクスチャ
サインとコサインの符号が周期的に反転することを利用すれば縞模様 (チェッカー) テクスチャを作ることができる。また各座標に三角関数を適用して積を取れば、その積の符号は三次元の縞模様となる。
class checker_texture : public texture {
public:
checker_texture() {}
checker_texture(shared_ptr<texture> t0, shared_ptr<texture> t1)
: even(t0), odd(t1) {}
virtual color value(double u, double v, const point3& p) const {
auto sines = sin(10*p.x())*sin(10*p.y())*sin(10*p.z());
if (sines < 0)
return odd->value(u, v, p);
else
return even->value(u, v, p);
}
public:
shared_ptr<texture> even;
shared_ptr<texture> odd;
};
texture.h
] 縞模様テクスチャ
この縞模様の偶奇に対応するテクスチャには定数テクスチャだけではなく任意のプロシージャルなテクスチャを設定できる。ここには Pat Hanrahan によって 1980 年代に提案されたシェーダーネットワークの考え方が生きている。
random_scene()
関数の地面代わりの球にこのテクスチャを追加しよう:
auto checker = make_shared<checker_texture>(
make_shared<solid_color>(0.2, 0.3, 0.1),
make_shared<solid_color>(0.9, 0.9, 0.9)
);
world.add(
make_shared<sphere>(point3(0,-1000,0), 1000, make_shared<lambertian>(checker))
);
main.cc
] 縞模様テクスチャを使う
次の画像が得られる:
縞模様テクスチャを使ったシーンのレンダリング
新しくシーンを追加する:
hittable_list two_spheres() {
hittable_list objects;
auto checker = make_shared<checker_texture>(
make_shared<solid_color>(0.2, 0.3, 0.1),
make_shared<solid_color>(0.9, 0.9, 0.9)
);
objects.add(
make_shared<sphere>(point3(0,-10, 0), 10, make_shared<lambertian>(checker))
);
objects.add(
make_shared<sphere>(point3(0, 10, 0), 10, make_shared<lambertian>(checker))
);
return objects;
}
main.cc
] 縞模様の球が二つあるシーン
カメラは次の位置に配置する:
const auto aspect_ratio = double(image_width) / image_height;
...
point3 lookfrom(13,2,3);
point3 lookat(0,0,0);
vec3 vup(0,1,0);
auto dist_to_focus = 10.0;
auto aperture = 0.0;
camera cam(
lookfrom, lookat, vup, 20, aspect_ratio, aperture, dist_to_focus, 0.0, 1.0
);
main.cc
] 視点に関するパラメータ
次の画像が得られる: