BlueRose
文章97
标签28
分类7
《Ray Tracing in One Weekend》阅读笔记

《Ray Tracing in One Weekend》阅读笔记

此文写于今年5月份,写了一些感觉会对之后读者有帮助的东西,以及一些笔记。因为要参加秦春林的线下交流会,所以看得比较匆忙。有些错误在所难免,还请见谅。
原作者的代码使用的是浮点类型是float,但实际上c++默认的浮点类型是double(IDE里可以设置默认浮点类型),如果直接抄代码渲染结果会不正确。

解决方案:

  1. 使用f作为字面量的后缀,例如:1.0f。
  2. 将所有类型都改成double。

原作者书中的sphere.h代码错误

double temp = (-b - sqrt(b*b-a*c)) / a;

其实这个并不是错误,b变量少乘以了2,这里其实是简化了计算步骤,将2约去了。
详细推导过程

抗锯齿

原作者用的抗锯齿方式是SSAA,没必使用100次采样,64次足以。

提高渲染速度

如果用原作者渲染2000X1000的图,只怕渲染10MIN都没完成。原因是系统只使用了一个核心来渲染。可以采用多线程的库来运用所有核心来渲染。同时使用release模式来渲染速度会更快一些(测试下来结果是快很多)

反射模糊

反射模糊采用了给反射光线添加随机方向(向量)偏移来实现。

折射

最后部分的修改的代码作者没有写全

hitable *list[5];

list[0] = new sphere(vec3(0,0,-1), 0.5, new lambertian(vec3(0.1, 0.2,0.5)));
list[1] = new sphere(vec3(0,-100.5,-1), 100, new lambertian(vec3(0.8,0.8, 0.0)));
list[2] = new sphere(vec3(1,0,-1), 0.5, new metal(vec3(0.8, 0.6, 0.2),0.0));
list[3] = new sphere(vec3(-1,0,-1), 0.5, new dielectric(1.5));
list[4] = new sphere(vec3(-1,0,-1),-0.45, new dielectric(1.5));

hitable *world = new hitable_list(list,5);

推荐去看 https://www.cnblogs.com/night-ride-depart/p/7429618.html

使用M_PI

使用M_PI,除了需要#include 外(其实只需要#include ,cmath也包含了这个文件)还需要#define _USE_MATH_DEFINES,具体可以参考math.h中的宏定义。但是需要注意的,你的main()函数所在的cpp文件中的头文件包含顺序需要改变,你需要把camera.h的包含顺序调整到别的包含cmath的头文件之前,保证要#define _USE_MATH_DEFINES处于最前位置。

简单的说只要把camera.h包含放到最前位置就可以了(预编译头文件后面)

FOV

double theta = vfor*M_PI / 180;
double half_height = tan(theta / 2);
double half_width = aspect*half_height;

lower_left_corner=vec3(-half_width,-half_height, -1.0);
horizontal = vec3(2*half_width, 0.0, 0.0);
vertical = vec3(0.0, 2*half_height, 0.0);

相机焦距模糊

通过设置焦距以及随机向量,来模拟光圈大小。光圈越大,景深越大。

最终封面图

在pdf中并没有说摄像机参数

vec3 lookfrom(13, 2, 3);
vec3 lookat(0, 0, 0);
double dist_to_focus = 10;
double aperture = 0.1;

camera cam(lookfrom , lookat, vec3(0, 1, 0), 20, nx / ny,aperture,dist_to_focus);

动态模糊

首先在camera类中计算快门时间,同时乘以一个随机值(0~1.0)赋予给ray,以及生成随机射线。

创建一个moving_sphere类,通过time计算出位置(这样就可以模拟去物体运动了,所以不需要配合摄像机快门的时间。)。并且在hit函数中的光线撞击判断中也使用

最终在随机场景函数中加入随机center0~1的moving_sphere来实现动态模糊。

层次包围盒BVH Bounding Volume Hierarchy

使用平行于轴线的平面构建一个包围盒。用于加快射线求交效率。下文较为详细的介绍了3D游戏引擎中常见的三维场景管理方法

3D游戏引擎中常见的三维场景管理方法 - 水煮鱼丸 - 博客园

AABB(axis-aligned bounding boxes,轴对齐包围盒)的碰撞解析:

AABB(axis-aligned bounding box)

注意:作者并没有在代码中使用BVH

hitable_list::bounding_box 有误

if (list[i]->bounding_box(t0, t1, temp_box)) {
    box = surrounding_box(box, temp_box);
}

贴图球面映射

$\theta$ 是纵向角(俯仰角)$-\pi/2 \pi/2$,$$\phi$ 是横向角(方位角)$2\pi$。

书中的公式一下子较难理解,这里你需要用投射的方式来思考,因为p(x,y,z)是球上任意一点,所以向量pCenter是一个三维向量,对于x,y你只有,具体的可以参考这个网址的图(他坐标系与书中的不同)。球面UV坐标 - aa20274270的博客 - CSDN博客

然后通过反三角函数反求。

这样我们就可以通过hit点的三维坐标来求贴图UV,在此之前还需要对UV进行区间转换:

//航向角之类的是以球体中心向外为准,而我们观察方向是相反的,所以:
//PI~-PI=>1~0=>0~1
u = 1 - (phi + M_PI) / (2 * M_PI);
//-PI/2~PI/2=>0~1
v = (theta + M_PI / 2) / M_PI;

作者最后将get_sphere_uv函数写到了hitable中了,然后在sphere中使用。

体渲染

//雾计算公式,t为距离
//Transmittance = e ^ (-t * d).
double hit_distance = -(1 density)*log(uni_dist(reng));

作者在这里有个细节没说:那就是当color有一个通道的数值超过1的时候该如何处理。我这里是这么做的:

  1. 当其中一个通道超过1,进行normalize操作。
  2. 如果三个通道都超过1,这强制返回vec3(1,1,1)

分层抽样

作者推荐:对于单次反射、阴影、一些2D问题可以使用使用分层方式的蒙特卡洛方法,来增加效率。但是这种方式会因为维度的增加而效率降低。

材质采样

灯光的随机采样会造成许多噪点,这是因为采样不足所造成的,我们可以只采样需要采样的地方,所以我们需要一个概率密度函数。
probability density function =pdf
计算出对应样本的概率,其反函数,通过概率计算出对应的样本。

推荐去看这个蒙特卡洛(Monte Carlo)法求定积分

灯光采样

//这里是因为是与Y轴垂直的平面才能这么求的
double light_cosine = fabs(dot(to_light,rec.normal));
//A为灯光的的面积
//如果我们在面光源上均匀采样,则pdf 为1/A
//立体角:dw = dA cos(alpha) / (distance(p,q)^2)
//因为立体角dw与dA是相同的
//可得:p(direction)*cos(alpha)*dA / (distance(p,q)^2 ) = p_q(q)*dA = dA / A
//pdf=distance(p,q)^2 / ( cos(alpha) *A )

混合pdf

因为需要混合pdf,所以设计一个pdf类,这个类需要做:

  1. 在对应位置的值
  2. 返回适当分配的随机数。

看到第三本书时,只推荐敲一下代码。他这里的重要性采样会让英文不好的人相当困惑。其实就是给光线追踪计算出来的值乘以一个权重,这个权重就是当前计算光线与采样平面,贡献辐射照度的比率。这里涉及到辐射度的问题,推荐去看文刀秋二的PBR系列文章。
Ph0en1x:Importance Sampling (重要性采样)
文刀秋二:基于物理着色(一)