Peter Shirley有三本光线追踪的书: 《Ray Tracing In The Weekend》:用两天学光线追踪 《Ray Tracing The Next Week》:用一周学光线追踪 《Ray Tracing The Rest Of Your Life》:用余生学光线追踪 接下来的一系列笔记是第二本书,这篇讲动态模糊。 在现实生活中,我们拍照时相机会打开一段时间,在其期间会因为摄像机抖动或拍摄物体运动而造成相片模糊,我们来实现一下这种效果。 首先要使我们的射线能存储这段时间:

#ifndef RAYH
#define RAYH
#include “RT/vec3.h”

class ray
{
public:
ray() { }
//给_time赋值
ray(const vec3& a, const vec3& b, float ti = 0.0) { A = a; B = b; _time = ti;}
vec3 origin() const { return A; }
vec3 direction() const { return B; }

//存储时间的变量
float time() const    { return \_time; }
vec3 point\_at\_parameter(float t) const { return A + t\* B; }

vec3 A;
vec3 B;
float \_time;

};

#endif

现在将我们的摄像机在time1到time2的随机时间生成射线,相机不需要移动,只需要在一段时间内发出射线:

class camera
{
vec3 origin;
vec3 lower_left_corner;
vec3 horizontal;
vec3 vertical;
vec3 u, v, w;
float lens_radius;
float time0, time1; //射线开始时间和结束时间

public:
//构造函数中加入时间t0和t1
camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect, float aperture,
float focus_dist,float t0, float t1)
{
time0 = t0;
time1 = t1;
lens_radius = aperture / 2;
float theta = vfov * M_PI / 180;
float half_height = tan(theta / 2);
float half_width = aspect * half_height;
origin = lookfrom;
w = unit_vector(lookfrom - lookat);
u = unit_vector(cross(vup, w));
v = cross(w, u);
lower_left_corner = origin - half_width * focus_dist * u - half_height * focus_dist * v - focus_dist * w;
horizontal = 2 * half_width * focus_dist * u;
vertical = 2 * half_height * focus_dist * v;
}

//time为一个时间在time0到time1的随机时间
ray getray(float s, float t)
{
    vec3 rd = lens\_radius \* random\_in\_unit\_disk();
    vec3 offset = u \* rd.x() + v \* rd.y();
    float time = time0 + (rand() % (100) / (float)(100)) \* (time1 - time0);
    return ray(origin + offset, lower\_left\_corner + s \* horizontal + t \* vertical - origin - offset, time);
}

};

我们还需要一个移动的物体, 我将创建一个以线性为中心的球体类从time0的center0到time1的center1, 在这段时间之外它不需要配合相机光圈的开合。

#include “RT/hitable.h”

//物体在移动,在time0时在地点center0,在time1时在地点center1
class moving_sphere : public hitable {
public:
moving_sphere(){ }
moving_sphere(vec3 cen0, vec3 cen1, float t0, float t1, float r, material* m)
: center0(cen0), center1(cen1), time0(t0),time1(t1), radius(r), mat_ptr(m) { };
virtual bool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
vec3 center(float time) const;
vec3 center0, center1;
float time0, time1;
float radius;
material* mat_ptr;
};

//获取某一时刻的中心点坐标
vec3 moving_sphere::center(float time) const{
return center0 + ((time - time0) / (time1 - time0))*(center1 - center0);
}

我们也可以让所有的物体都实现动态模糊,给他们相同的开始点和结束点。在hit函数里将”center” 替换成 “center(r.time())“

//让所有的普通物体都可以实现动态模糊
// replace “center” with “center(r.time())”
bool moving_sphere::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
vec3 oc = r.origin() - center(r.time());
float a = dot(r.direction(), r.direction());
float b = dot(oc, r.direction());
float c = dot(oc, oc) - radius*radius;
float discriminant = b*b - a*c;
if (discriminant > 0) {
float temp = (-b - sqrt(discriminant))/a;
if (temp < t_max && temp > t_min) {
rec.t = temp;
rec.p = r.point_at_parameter(rec.t);
rec.normal = (rec.p - center(r.time())) / radius;
rec.mat_ptr = mat_ptr;
return true;
}
temp = (-b + sqrt(discriminant))/a;
if (temp < t_max && temp > t_min) {
rec.t = temp;
rec.p = r.point_at_parameter(rec.t);
rec.normal = (rec.p - center(r.time())) / radius;
rec.mat_ptr = mat_ptr;
return true;
}
}
return false;
}

将漫反射球在time0时刻的center位置移动到time1时刻的center+vec3(0,0.5*drand48(), 0)位置:

hitable* RandomScene()
{
int n = 500;
hitable** list = new hitable*[n + 1];
/*定义一个包含n+1个元素的数组,数组的每个元素是指向hitable对象的指针。然后将数组的指针赋值给list。所以,list是指针的指针。*/
list[0] = new sphere(vec3(0, -1000, 0), 1000, new lambertian(vec3(0.5, 0.5, 0.5)));
/*先创建一个中心在(0,-1000,0)半径为1000的超大漫射球,将其指针保存在list的第一个元素中。*/
int i = 1;
for (int a = -11; a < 11; a++)
{
for (int b = -11; b < 11; b++)
{
/*两个for循环中会产生(11+11)*(11+11)=484个随机小球*/
float choose_mat = (rand() % (100) / (float)(100));
/*产生一个(0,1)的随机数,作为设置小球材料的阀值*/
vec3 center(a + 0.9 * (rand() % (100) / (float)(100)), 0.2, b + 0.9 * (rand() % (100) / (float)(100)));
/*” a+0.9*(rand()%(100)/(float)(100))”配合[-11,11]产生(-11,11)之间的随机数,
* 而不是[-11,11)之间的22个整数。使得球心的x,z坐标是(-11,11)之间的随机数*/
if ((center - vec3(4, 0.2, 0)).length() > 0.9)
{
/*避免小球的位置和最前面的大球的位置太靠近*/
if (choose_mat < 0.8)
{
//diffuse
/*材料阀值小于0.8,则设置为漫反射球,漫反射球的衰减系数x,y,z都是(0,1)之间的随机数的平方*/
list[i++] = new moving_sphere(center, center + 0.4 * vec3(0, (rand() % 100 / (float)(100)), 0), 0.0f, 1.0f, 0.2f,
new lambertian(vec3(
(rand() % (100) / (float)(100)) * (rand() % (100) / (float)(100)),
(rand() % (100) / (float)(100)) * (rand() % (100) / (float)(100)),
(rand() % (100) / (float)(100)) * (rand() % (100) / (float)(100)))));
}
else if (choose_mat < 0.95)
{
/*材料阀值大于等于0.8小于0.95,则设置为镜面反射球,
* 镜面反射球的衰减系数x,y,z及模糊系数都是(0,1)之间的随机数加一再除以2*/
list[i++] = new sphere(center, 0.2,
new metal(vec3(0.5 * (1 + (rand() % (100) / (float)(100))),
0.5 * (1 + (rand() % (100) / (float)(100))),
0.5 * (1 + (rand() % (100) / (float)(100)))),
0.5 * (1 + (rand() % (100) / (float)(100)))));
}
else
{
/*材料阀值大于等于0.95,则设置为介质球*/
list[i++] = new sphere(center, 0.2, new dielectric(1.5));
}
}
}
}

list\[i++\] = new sphere(vec3(0, 1, 0), 1.0, new dielectric(1.5));
list\[i++\] = new sphere(vec3(-4, 1, 0), 1.0, new lambertian(vec3(0.4, 0.2, 0.1)));
list\[i++\] = new sphere(vec3(4, 1, 0), 1.0, new metal(vec3(0.7, 0.6, 0.5), 0.0));
/\*定义三个大球\*/
return new hitable\_list(list, i);

}

调整摄像机:

vec3 lookform(13.0f, 2.0f, 3.0f);
vec3 lookat(0, 0, 0);
float dist_to_focus = (lookform - lookat).length();
float aperture = 0.0f;

camera cam(lookform, lookat, vec3(0, 1, 0), 20,
float(nx) / float(ny), aperture, 0.7*dist_to_focus,0.0,1.0);

最终效果: 参考书籍:《Ray Tracing The Next Week》 RTTNW系列项目地址:GitHub