Ray Tracing The Next Week
Ray Tracing The Next Week
- 版本:
3.2.3
,2020-12-07
- 光追框架理解
- 工程能力训练
1. 说明
- 更复杂的功能,作者博客
- 需要科学上网
2. 运动模糊
- motion blur
- 实际相机的快门开闭是有时间的,对这段时间内的光线做平均
- 实现
- 需要添加能够移动的物体
- 相机需要有一个开闭时间
- 光线上需要带一个时间戳,用于求交的时候确定物体的位置
效果图(100spp)
- 大光圈
- 大光圈 + 运动模糊
- 小光圈 + 运动模糊
- 小光圈
3. BVH
- Bounding Volume Hierarchies
- 包围盒(静态场景)
- 之前光线和场景求交:逐个遍历所有物体
- 加速方法
- 拆分空间
- 拆分物体:BVH
- 加速方法
- 基本思想:如果光线联保为何都没有击中,那么肯定不能击中物体
1 | if (ray hits bounding object) |
- 包围盒例子
- 因为有可能重合,击中左边的子结点之后,还有可能击中右边的子结点
1 | if (hits purple) { |
AABB 包围盒
- Axis-Aligned Bounding Boxes
- 轴对齐的包围盒
- 一个好的划分方式很重要
- 中点划分
- 等量划分
- SAH
判断相交
- 射线和平面求交
\[ \mathbf{P}(t) = \mathbf{A} + t \mathbf{b} \]
- \(x\) 轴为例
\[ \begin{array}{c} x_0 = A_x + t_0 b_x\\ t_0 = \dfrac{x_0 - A_x}{b_x}\\ t_1 = \dfrac{x_1 - A_x}{b_x}\\ \end{array} \]
- 三个维度的 \([t_0,t_1]\) 相交是否为空,不为空返回的就是光线和包围盒相交的部分
1 | compute (tx0, tx1); |
slab:就是指上面 \((x_0,x_1)\) 之间
注意事项
\(t_1<t_0\)
- 二者取大取小即可
除 0
- 浮点数可以自己处理这个情况
1
2
31.0/0.0 = inf;
-1.0/0.0 = -inf;
// inf > 1.0, 可以和一个实数值比大小光线起点在 slab 边界上而且恰好同时除 0
0/0 的情况,造成 NaN
- NaN 和任何数字相比都返回 false
1
2std::fmax(0.0 / 0.0, 1); // 1
std::fmin(0.0 / 0.0, 1); // 1这种情况是恰好光线掠过边界,可以当作击中,也可以当作不击中
- 我们认为不击中
建立包围盒
- 在 hittable 物体中都加上一个包围盒函数
- 返回值为 bool,不是所有的物体都包含包围盒(无限大的平面)
- 对于移动的物体,我们直接返回这段时间内移动轨迹的包围盒
总结
- 小场景中,包围盒加速效果不明显,甚至变慢
- random 场景中,100ssp
- 遍历:60s
- BVH:120s
- random 场景中,100ssp
4. 纹理
- Texture
球面纹理坐标
- 经纬度
- \(\phi\in[0,2\pi]\)
- \(-x\to+z\to+x\to-z\)
- \(\theta\in[0,\pi]\)
- \(-y\to y\)
- \(\phi\in[0,2\pi]\)
- \([\theta,\pi]\to[u,v]\)
- \(u,v\in[0,1]\)
- \((u,v)=(0,0)\):左下角
推导
\[ \begin{array}{rl} y=&-\cos{\theta}\\ x=&-\cos{\phi}\sin{\theta}\\ z=&\quad\sin{\phi}\sin{\theta}\\ \end{array} \]
\[ \begin{array}{c} \phi=\arctan\left(\dfrac{-x}{z}\right)\\ \theta=\arccos(-y)\\ \end{array} \]
1 | phi = atan2(z, -x) + pi; // [-pi, pi] + pi |
效果图
- 高清大图:1000spp
5. 柏林噪声
- Perlin Noise
- 对于一个三维的输入产生一个实数输出
- 同样的输入对应同样的输出
- 简单、快速、有重复性
- 柏林噪声参考
- 非整数坐标点
- 输入一个点 \(\to\) 根据周围的 8 个整数坐标点,三线性插值该点的噪声值
- 整数坐标点
- 每一个维度 (\(x,y,z\))有一张排列表,根据排列表找到噪声值
- 排列表可以直接保存噪声值(减少存储)
- 效果图如下
- 可以让噪声纹理更加高频
- 柏林噪声插值的实际上是向量,每一个顶点都有一个随机向量
- 将三线性插值的得到的向量点乘权重向量
- 可能出现负值
- Turbulence(湍流)
- 将不同位置的柏林噪声以某种权重叠加在一起
- 大理石样纹理
1 | color(0.5 * (1 + sin(scale * p.z() + 10 * noise.turb(p)))); |
- 加了个纹理小球(在反射中能够看到)
6. 图片纹理映射
- 使用了
stb_image.h
库进行图片读取 - 和之前没有什么不同,在材质读取 value 的时候,利用纹理坐标在图片上读取即可
7. 长方体和光源
(1) 光源
- 光源的设置
- 设置为一种材质,实现 material 类的 emitted 方法
- 默认发白光
- 光源不散射光线,因此 scatter 函数返回 false
- 设置为一种材质,实现 material 类的 emitted 方法
(2) 长方形
- 轴平齐的长方形
- Axis Aligned
1 | class xy_rect:public hitabble {} |
- cornell box 的例子
- 1000spp 使用取余数的方法对比图
- 不太清楚为什么上面会出现一条亮斑?
- 麻了麻了,竟然是随机数的问题(100spp)
- 下面直接取余数的方法,慢很多,大概时间花销在 4.5 倍左右
- 麻了麻了,竟然是随机数的问题(100spp)
1 | inline double random_double() { |
1 | inline double random_double() { |
8. 实例化
(1) Box
- box 很简单,就是直接画出 6 个面即可
- 1000spp(mt19937)
- 487 s
- 1000spp(rand)
- 1789 s
(2) 实例化
- 一些对 box 的操作,平移旋转
- 这里不支持矩阵的变换
- 平移,旋转的设计
- 包装,形成一个新的实例
- 平移为例
1 | class translate : public hittable { |
- 平移的的实现比较简单
- 保存一个内部的物体、平移的量
- 求交的时候可以先将光线的起点进行相同的平移,然后和内部物体进行判断求交
- 如果有交点,那么修正交点即可(加上平移量)
- 法线是不变的
- 旋转则比较麻烦
- 法线也会变化
- 绕一个方向旋转的时候,法线变换和旋转变换相同
- 正交矩阵:\((M^{-1})^{T}=M\)
- 光线求交的时候先逆向旋转内部物体处(起点 + 方向)
- 如果有交点,则对交点的位置和法向进行修正(初始的旋转,即正向的旋转)
- 10000spp(rand)
- 耗时 18480 s
9. 体积渲染
- 参与介质
- participating media
- volume
- 如果真的按照体渲染的方式进行渲染的话,需要对整个架构进行修改,和表面渲染大不相同
- 一个实现的 trick
- 用一个物体表示,但是它的表面以一定概率存在
- 构造虚假的穿透效果
- 光线可能直接穿过这个物体,可能在内部发生散射
- 求出光线在内部的长度(在这段区域的任何部分都有可能被散射)
- 假定密度为 1 的物体,散射距离为 \(\infty\),那么在内部光线长度为 \(l\) 的散射概率如下
1 | const auto distance_inside_boundary = (rec2.t - rec1.t) * ray_length; |
- 密度用于调整参与介质的大小,密度越大,可以散射的范围越小(分母越小),更容易散射
10. 最终场景
- 1000spp
- 400 x 400
- 26287s = 7.30h
11. 随机数生成器的问题
应该是 openmp 的理解问题,转换成单线程之后,mt19937 就没有亮带问题了
学习一下 openmp
随机数生成性能对比
- 单线程来看二者是差不多的,甚至 rand 更快一些
1
2
310spp
mt19937: 19s
rand: 16s所以感觉是 openmp 没有处理 mt19937 的问题,造成了相关性,之后再看看具体内容
27 核使用 mt19937 的并行结果快了 4 倍,但是使用 rand 和原来基本一样的速度
- rand 在实现的时候加锁了,麻了,并行了,但是完全没有并行
确实是相关性问题导致的,改进随机数的生成方式
1 | // 返回一个随机数 [0, 1) |
- 同时也发现了,inline 好像真的会有比较大的效率提升,大概是 2 倍
- 把所有的函数都修改为不是 inline 之后,运行时间是原来的 2 倍左右
- 如何真正的提高并行的效率呢?看了下别人写的代码,时间比能达到 16 倍(震撼)
- 使用最终场景测试,发现好像不改之前的代码,openmp 也能有 15 倍的提升,看不懂了