Raytracing The Next Week
Chapter 1:Motion Blur
1.1 运动模糊
光线多了一个属性
time
,用于记录该光线射入相机的时间class Ray {
public:
Ray() {};
Ray(const Point3& orig, const Vec3& dir, const double time = 0) :origin(orig), direction(dir), time(time) {}
Ray(const Ray& r) :origin(r.origin), direction(r.direction) {}
Point3 Origin() const { return origin; }
Vec3 Direction() const { return direction; }
double Time() const { return time; }
// P(t) = origin + t * direction
Point3 At(double t) const {
return origin + t * direction;
}
private:
Point3 origin;
Vec3 direction;
double time;
};time
的取值由Camera
的曝光时间决定,为曝光时间内的一个随机值Ray Camera::GetRay(double u, double v) const {
// 光圈随机偏移
Vec3 rd = lens_radius * Random::rand_unit_sphere();
Vec3 offset = u * rd.x() + v * rd.y();
// 光线的起点为原点, 方向指向观察平面上的当前像素
// 当前时间为 [time1, time2] 之间的随机值
Vec3 start = position + offset;
Vec3 target = low_left_corner + u * horizontal + v * vertical;
double time = time1 + Random::rand01() * (time2 - time1);
return Ray(start, target - start, time);
}
1.2 Moving Sphere
- 通过球心位置,确定球的位置
- 需要给定两个时间分别对应的球心位置,某个确定时间球心所在位置,即为两者插值
- 对于当前时刻,根据之前球的求根公式,计算交点
bool SphereMoving::hit(const Ray& r, double t_min, double t_max, HitInfo& info) const { |
1.3 场景中的物体&相机配置
namespace { |
Chapter 2:Bounding Volume Hierarchies
2.1 AABB包围盒
bool AABB::hit(const Ray& r, double t_min, double t_max) const { |
2.2 构建BVH
- 将场景中的物体编号,
0~n-1
- 类似线段树,每个阶段管辖一个区间
[L,R]
,并根据区间内的物体建立AABB包围盒 - 递归构建每一个节点
void ObjectWorld::build(Ref<BVHnode> u, int L, int R, int deep) { |
2.3 碰撞检测
- 如果到达叶节点,则与当前节点对应的物体进行碰撞检测
- 首先与当前节点的AABB包围盒进行碰撞检测,如果没有碰撞,则忽略该子树
- 然后递归判断左右子树,并返回两者中,
t
更小的那个
bool BVHnode::hit(const Ray& r, double t_min, double t_max, HitInfo& info) const { |
Chapter 3:Solid Textures
3.1 Texture的功能
- 给定:纹理坐标
(u,v)
,坐标p
- 返回:颜色
class Texture { |
3.2
常量纹理:见Texture/TextureConstant.h
Value
函数返回定值
class TextureConstant : public Texture { |
- 修改材质类:
attenuation
的值由纹理返回
class Lambertian : public Material { |
3.3
棋盘纹理,见:Texture/TextureChecker.h
- 棋盘纹理就是交错的双色格子,呈现一定的规律性
- 使用正弦函数,将位置映射为
[0/1]
Color TextureChecker::Value(double u, double v, const Point3& p) const { |
Chapter 4:Perlin Noise
4.1 柏林噪声
柏林噪声是用来生成一些看似杂乱无章其实有些变换规律的图形(更加贴近自然),比如海水、地形、雾等
例如,2D柏林噪声可以生成
柏林噪声有2个关键的特点:
- 输入相同的3D点,总能返回相同的随机值
- 简单快捷,使用一些hack的方法,达到快速近似的效果
4.2
柏林噪声的类,见Math/Perlin.h
噪声函数
noise(p)
:给定一个位置p
,返回该位置对应的噪声函数值double Perlin::noise(const Point3& p) const {
int i = floor(p.x());
int j = floor(p.y());
int k = floor(p.z());
double u = p.x() - i;
double v = p.y() - j;
double w = p.z() - k;
Point3 list[2][2][2];
for(int a = 0; a < 2; a++)
for(int b = 0; b < 2; b++)
for(int c = 0; c < 2; c++)
list[a][b][c] = random_value[
perm_x[(i + a) & 255] ^
perm_y[(j + b) & 255] ^
perm_z[(k + c) & 255]
];
return trilinear_interp(list, u, v, w);
}噪声函数
turb(p, depth)
:给定一个位置p
,返回该位置对应的复合噪声函数值double Perlin::turb(const Point3& p, int depth) const {
double accumulate = 0;
Point3 t = p;
double weight = 1.0;
for (int i = 0; i < depth; i++) {
accumulate += weight * noise(t);
weight *= 0.5;
t *= 2;
}
return abs(accumulate);
}三线性插值函数
trilinear_interp(list, u, v, w)
:根据uvw
进行插值double Perlin::trilinear_interp(Point3 list[2][2][2], double u, double v, double w) const {
double uu = u * u * (3 - 2 * u);
double vv = v * v * (3 - 2 * v);
double ww = w * w * (3 - 2 * w);
double accumulate = 0;
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
for (int k = 0; k < 2; k++) {
Point3 weight(u - i, v - j, w - k);
accumulate +=
(i * uu + (1 - i) * (1 - uu)) *
(j * vv + (1 - j) * (1 - vv)) *
(k * ww + (1 - k) * (1 - ww)) * list[i][j][k].dot(weight);
}
return accumulate;
}
4.3
噪声纹理,见Texture/TextureNoise.h
class TextureNoise : public Texture { |
Chapter 5:Image Texture Mapping
5.1 基础知识
利用2D纹理坐标(u,v)
,确定当前点的attenuation
,(u,v)
需要保存到HitInfo
中
nx*ny
图像上的某个像素点(i,j)
的纹理坐标: \[ u=\frac{i}{nx-1}, \ v=\frac{j}{ny-1} \]球面上的某点
(x,y,z) / (r,θ,φ)
的纹理坐标: \[ \begin{aligned} x&=\cos θ \cos Φ \\ z&=\cos θ \sin Φ \\ y&=\sin θ \end{aligned} \]- 将
θ,φ
规格化到[0,1]
:
\[ u=\frac{Φ}{2\pi}, \ v=\frac{θ}{\pi}\\ \]
- 将
5.2 对Sphere的碰撞的修改
bool Sphere::hit(const Ray& r, double t_min, double t_max, HitInfo& info) const { |
5.3
图片纹理,见Texture/TextureImage
class TextureImage : public Texture { |
|
Chapter 6:Rectangles and Lights
6.1
发光材质,见Material/Emit
class Emit : public Material { |
bool Emit::scatter(const Ray& r_in, const HitInfo& info, Color& attenuation, Ray& r_out) const { |
相应的,修改GetColor()
Color ObjectWorld::GetColor(const Ray& r, int depth) { |
6.2 长方形物体(平行于坐标轴轴)
以平行于
z
轴为例,平行于x、y
轴同理
\[ p(t)=position+t*direction\\ p(t).z=position.z+t*direction.z=k\\ t=\frac{k-position.z}{direction.z} \]
class RectXY : public Object { |
bool RectXY::hit(const Ray& r, double t_min, double t_max, HitInfo& info) const { |
6.3 Cornell box
void AddObjects() { |
/* 相机设置 */ |
Chapter 7:Instance
7.1 轴对齐 Box
用六个长方形,拼成一个长方体,注意法线的朝向
class Box : public Object { |
Box::Box(const Vec3& point_min, const Vec3& point_max, Ref<Material> material) |
7.2 平移
在计算碰撞点的时候把 ray
往反方向移动
class Translate : public Object { |
bool Translate::hit(const Ray& r, double t_min, double t_max, HitInfo& info) const { |
7.3 旋转
绕Z轴转: \[
x'=\cos θ * x - \sin θ * y\\
y'=\sin θ * x + \cos θ * y
\] 绕Y轴转: \[
x'=\cos θ * x + \sin θ * z\\
z'=-\sin θ * x + \cos θ * z
\] 绕X轴转: \[
y'=\cos θ * y - \sin θ * z\\
z'=\sin θ * y + \cos θ * z
\] 在计算碰撞点的时候把 ray
往反方向旋转
以绕Y轴旋转为例
class RotateY : public Object { |
RotateY::RotateY(double angle, Ref<Object> object) : object(object) { |
Chapter 8:Volumes
8.1
次表面散射材质,见Material/Isotropic.h
- 对于一个恒定密度体,一条光线通过其中的时候,在烟雾体中传播的时候也会发生散射
- 光线在烟雾体中能传播多远,是由烟雾体的密度决定的
- 密度越高,光线穿透性越差,光线传播的距离也越短
当光线通过体积时,它可能在任何点散射。 光线在单位距离\(dL\)中散射的概率为: \[ P=C*dL \]
- 其中\(C\)与体积的光密度成比例
对于恒定体积,我们只需要密度\(C\)和边界
class Isotropic : public Material { |
bool Isotropic::scatter(const Ray& r_in, const HitInfo& info, Color& attenuation, Ray& r_out) const { |
- 这个材质的散射原理和漫反射材质的大同小异,均属于碰撞点转换为新视点,沿任意方向发射新的视线,区别就在于
- 漫反射的散射光线不可能指到物体内部,它一定是散射到表面外部(视线方向指向外切球体表面)
- isotropic材质的散射光线可以沿原来的方向一往前,以此视线透光性
- 因为烟雾内部只是颗粒而不存在真正不可穿透的几何实体,所以漫反射实体不可穿透,只能散射到表面外部,而烟雾可穿透
8.2
烟雾体,见Object/Transform/ConstantMedium.h
class ConstantMedium : public Object { |
bool ConstantMedium::hit(const Ray& r, double t_min, double t_max, HitInfo& info) const { |
8.3 场景测试代码
void Cornell_smoke() { |
Chapter 9:A Scene Testing All New Features
void Final_Scene() { |