和电解质一样,相机也是一个debug的痛点。首先,我们说明一个可调节的视场(fov),这是你从入口看到的角度。由于我们的图像不是正方形,fov在水平方向和垂直方向上是不同的。作者总是用垂直fov,并且在构造函数中指定它的度数并将其转化为弧度——a matter of personal taste。
10.1 相机观察几何体
一开始我们让射线从原点发出并朝向z = -1平面。我们可以让他是 z = -2平面,或者别的什么,只要能使得h于该距离成比例。这是我们的设置:
这意味着h = tan(θ/2).现在我们的camera变成了这样:
- 初始化函数:
- 参数:vfov —— 垂直方向上的fov的角度
- aspect_ratio —— 纵横比;屏幕高宽比
- 1、将角度转化为弧度
- 2、用h = tan(θ/2)求出h
- 3、设置屏幕宽、高、深
- 4、初始化类的元素
// 网页代码 camera.h - Camera with adjustable field-of-view (fov)
class camera {
public:
camera(
double vfov, // vertical field-of-view in degrees
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;
};
我们可以这样调用camera:cam(90, double(image_width)/image_height),和下面的球类:
// 网页上的代码 main.cc - Scene with wide-angle camera
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))));
我们将得到:
vfov = 90
vfov = 75
10.2 相机的定位和定向
为了获得任意一个视点,让我们先命名我们需要的点:
- lookfrom:我们放置相机的地方
- lookat:我们看的地方
(接下来,你也可以定义一个方向向量代替我们看的点来查看)
我们还需要一种方法来指定相机的转动或侧向倾斜:围绕lookat-lookfrom轴的旋转。 另一种思考的方法是,即使你保持lookfrom和lookat不变,你仍然可以绕着鼻子转动你的头。我们需要一种方法指定相机的“up”向量。这个up向量应该位于与视图方向垂直的平面上。
实际上我们可以使用任何向上的向量,然后将其投影到这个平面上,就可以得到一个相机的up向量。我使用命名一个“view up”(vup)向量的通用约定。做一些叉乘(cross(vup, w)、cross(w, u))可以得到u、v,现在我们有一个完整的标准正交基(u,v,w)来描述我们的相机的方向。
- vup,v和w在同一个平面。
- 注意,就像之前我们的固定相机面向-Z,我们的任意取景相机面向-w。
- 请记住:我们可以用world up(0, 1, 0)指定vup,但不是必要的。这是方便的,自然会让你的相机水平水平,直到你决定尝试疯狂的相机角度。
// 网页上的代码 [camera.h] Positionable and orientable camera
class camera {
public:
camera(
point3 lookfrom,
point3 lookat,
vec3 vup,
double vfov, // vertical field-of-view in degrees
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 s, double t) const {
return ray(origin, lower_left_corner + s*horizontal + t*vertical - origin);
}
private:
point3 origin;
point3 lower_left_corner;
vec3 horizontal;
vec3 vertical;
};
这使得我们可以改变视点:
// 网页上的代码 [main.cc] Scene with alternate viewpoint
camera cam(point3(-2,2,1), point3(0,0,-1), vec3(0,1,0), 90, aspect_ratio);
我们将得到:
如果我们改变视角:
// 网页上的代码 [main.cc] Change field of view
camera cam(point3(-2,2,1), point3(0,0,-1), vup, 20, aspect_ratio);
得到的图片是:
上图玻璃球与粉球相交的地方有黑点,如果他们不相切(玻璃球半径为0.49),则没有黑点,如下图所示:
(原文讲得真不错,所以斜体的部分都是译过来的)