カメラの移動

誘電体と並んでカメラもデバッグが難しい。私はカメラを実装するとき必ずインクリメンタルに進めることにしている。まずは視野角 (field of view, fov) を実装しよう。私たちがレンダリングしている画像は正方形でないから、視野角は垂直または水平方向の角度で指定できる。私は必ず垂直方向の角度で指定する。それから視野角は弧度法で表し、コンストラクタでラジアンに変換する ──この辺は好みの問題だ。

カメラの視野

これまでのプログラムでは、原点にあるカメラから放たれるレイは平面 \(z = -1\) 上の点に向かっていた。レイが向かう点が \(z = -2\) のような他の平面上にあったとしても、平面までの距離と平面の高さの比が一定であればレイは変化しない。この平面の高さは下図の \(h\) である:

図 1.30: カメラの視野

つまり \(h = \tan \dfrac{\theta}{2} \) が成り立つ。これをカメラのコードに組み込むと次のようになる:

class camera {
public:
  camera(
    double vfov, // 垂直方向の視野角 (弧度法)
    double aspect_ratio
  ) {
    auto theta = degrees_to_radians(vfov);
    auto h = tan(theta/2);
    auto viewport_height = 2.0 * h;
    auto viewport_width = aspect_ratio * viewport_height;

    auto focal_length = 1.0;

    origin = point3(0, 0, 0);
    horizontal = vec3(viewport_width, 0.0, 0.0);
    vertical = vec3(0.0, viewport_height, 0.0);
    lower_left_corner =
      origin - horizontal/2 - vertical/2 - vec3(0, 0, focal_length);
  }

  ray get_ray(double u, double v) const {
    return ray(origin, lower_left_corner + u*horizontal + v*vertical - origin);
  }

private:
  point3 origin;
  point3 lower_left_corner;
  vec3 horizontal;
  vec3 vertical;
};
リスト 1.59 [camera.h] 視野角 (fov) が調節可能なカメラ

カメラを cam(90, double(image_width)/image_height) として、次の二つの球をシーンに加える:

auto R = cos(pi/4);
hittable_list world;
world.add(make_shared<sphere>(
  point3(-R, 0, -1), R, make_shared<lambertian>(color(0, 0, 1))));
world.add(make_shared<sphere>(
  point3( R, 0, -1), R, make_shared<lambertian>(color(1, 0, 0))));
リスト 1.60 [main.cc] 広角なカメラを使うシーン

レンダリングすると次の画像が得られる:

図 1.31: 広角なカメラでレンダリングした画像

カメラの移動と回転

視点を自由に動かすために、まず視点を定める点の名前を定めよう。カメラの位置を lookfrom として、注視する点を lookat とする (最後まで実装できたら、こうする代わりに視線の方向を定義してもよい)。

さらにカメラのロール (横方向の傾き) を指定する必要がある。これは lookatlookfrom を結ぶ軸に関する回転であり、人間で言えば同じものを見つめながら鼻を中心に顔を回転させるのに相当する。ここで必要なのはカメラの「上」を指すベクトルを指定する方法である。この「上」ベクトルはカメラを通って視線ベクトルと垂直な平面上に存在する。

図 1.32: カメラの視線ベクトル

上ベクトルがこの平面上になくても射影すればよいので、上ベクトルは任意のベクトルで表せる。よく使われる名前を借りて、このベクトルを vup (view up) と呼ぶことにする。lookfrom, lookat, vup の三つのベクトルがあれば、何度か外積を使うことでカメラの位置と向きを表す正規直交基底 \((u, v, w)\) を計算できる。

図 1.33: カメラの視線と上方向

vup, v, w は全て同一平面上に存在する。また以前の固定されたカメラが \(-\text{Z}\) 方向を向いていたのと同様に、このカメラは \(-\text{w}\) 方向を向く。それから vup にはシーン全体の上方向を表す \((0, 1, 0)\) を使うことができる。絶対にこうしなければならない理由はないが、こうしておくとカメラが常に水平になるので便利である。カメラの角度で遊び始めるまではこうしておくことを勧める。

class camera {
public:
  camera(
    point3 lookfrom,
    point3 lookat,
    vec3   vup,
    double vfov, // 垂直方向の視野角 (弧度法)
    double aspect_ratio
  ) {
    auto theta = degrees_to_radians(vfov);
    auto h = tan(theta/2);
    auto viewport_height = 2.0 * h;
    auto viewport_width = aspect_ratio * viewport_height;


    auto w = unit_vector(lookfrom - lookat);
    auto u = unit_vector(cross(vup, w));
    auto v = cross(w, u);

    origin = lookfrom;
    horizontal = viewport_width * u;
    vertical = viewport_height * v;
    lower_left_corner = origin - horizontal/2 - vertical/2 - w;
  }

  ray get_ray(double u, double v) const {
    return ray(origin, lower_left_corner + u*horizontal + v*vertical - origin);
  }

private:
  point3 origin;
  point3 lower_left_corner;
  vec3 horizontal;
  vec3 vertical;
};
リスト 1.61 [camera.h] 移動と回転が可能なカメラ

これにより視点の変更が可能になる:

camera cam(point3(-2,2,1), point3(0,0,-1), vec3(0,1,0), 90, aspect_ratio);
リスト 1.62 [main.cc] 視点のパラメータ

レンダリングすると次の画像となる:

図 1.34: ズームアウト

視野角を変えてみよう:

camera cam(point3(-2,2,1), point3(0,0,-1), vup, 20, aspect_ratio);
リスト 1.63 [main.cc] 視野角の変更

こうなる:

図 1.35: ズームイン