现在到了我们的最后一个特性:散焦模糊(所有摄影师都称之为“景深”)。
我们在真正的相机中进行散焦模糊的原因是因为它们需要一个大的孔(而不仅仅是一个针孔)来收集光线。这将使所有的东西都散焦,但是如果我们把一个透镜插入孔中,所有的东西都会在一定的距离上聚焦。你可以这样想象一个镜头:所有来自焦点距离上的特定点的光线——击中镜头的光线——将被弯曲回图像传感器上的一个单点。
投影点到完美聚焦的平面之间的距离称为焦点距离(focus distance)。注意焦点距离(focus distance)并不等于焦距(focus length),焦距(focus length)是投影点到像面的距离。
在物理相机中,焦距(focus distance)是由镜头和胶片/传感器之间的距离控制的。这就是为什么当你改变对焦时,你会看到镜头相对于相机移动(这可能也会发生在你的手机相机上,但传感器会移动)。“光圈”是一个孔,用来有效地控制镜头的大小。对于真正的相机,如果你需要更多的光线,你可以让光圈更大,这样就会产生更多的散焦模糊。对于我们的虚拟相机,我们可以有一个完美的传感器,永远不需要更多的光线,所以当我们想要散焦模糊时,我们只有一个光圈。
11.1 薄透镜近似
真正的照相机有一个复杂的复合镜头。对于我们的代码,我们可以模拟顺序:传感器,然后镜头,然后孔径。然后我们就可以弄清楚射线的发射方向,并在计算完成后翻转图像(图像被倒过来投影在胶片上)。然而,图形人员通常使用薄透镜近似:
我们不需要模拟相机内部的任何东西。对于渲染相机外的图像,这将是不必要的复杂性。相反,我通常从镜头开始发出光线,然后将它们射向焦平面(远离镜头的focus_dist),在这个平面上所有的东西都是完美聚焦的。
11.2 产生样本射线
通常情况下,所有的场景光线从lookfrom发出。为了实现散焦模糊,在以lookfrom为圆心的圆盘上随机发出场景射线。圆盘半径越大,散焦模糊越大。你可以把我们最初的相机想象成一个半径为零的散焦盘(完全没有模糊),所以所有的光线都来自于盘中心(lookfrom)。
// 网页上的代码 [vec3.h] Generate random point inside unit disk
vec3 random_in_unit_disk() {
while (true) {
auto p = vec3(random_double(-1,1), random_double(-1,1), 0);
if (p.length_squared() >= 1) continue;
return p;
}
}
// 网页上的代码 [camera.h] Camera with adjustable depth-of-field (dof)
class camera {
public:
camera(
point3 lookfrom,
point3 lookat,
vec3 vup,
double vfov, // vertical field-of-view in degrees
double aspect_ratio,
double aperture,
double focus_dist
) {
auto theta = degrees_to_radians(vfov);
auto h = tan(theta/2);
auto viewport_height = 2.0 * h;
auto viewport_width = aspect_ratio * viewport_height;
w = unit_vector(lookfrom - lookat);
u = unit_vector(cross(vup, w));
v = cross(w, u);
origin = lookfrom;
horizontal = focus_dist * viewport_width * u;
vertical = focus_dist * viewport_height * v;
lower_left_corner = origin - horizontal/2 - vertical/2 - focus_dist*w;
lens_radius = aperture / 2;
}
ray get_ray(double s, double t) const {
vec3 rd = lens_radius * random_in_unit_disk();
vec3 offset = u * rd.x() + v * rd.y();
return ray(
origin + offset,
lower_left_corner + s*horizontal + t*vertical - origin - offset
);
}
private:
point3 origin;
point3 lower_left_corner;
vec3 horizontal;
vec3 vertical;
vec3 u, v, w;
double lens_radius;
};
使用大光圈:
//网页上的代码 [main.cc] Scene camera with depth-of-field
point3 lookfrom(3,3,2);
point3 lookat(0,0,-1);
vec3 vup(0,1,0);
auto dist_to_focus = (lookfrom-lookat).length();
auto aperture = 2.0;
camera cam(lookfrom, lookat, vup, 20, aspect_ratio, aperture, dist_to_focus);