目录
光线追踪
为什么要使用光线追踪?
光栅化难以将以下的效果做好:
- 软阴影
- 毛玻璃材质的反射
- 间接光照
光栅化虽然较快,但是质量较低。
光线追踪的几个假设
- 光沿直线传播(虽然这是错的)
- 光线与光线之间不会发生碰撞(虽然这是错的)
- 光线从是从光源不断传播直到视锥体的(光线的可逆性)
光线投射
光线投射的假设:
- 出射点是一个点
- 光源点光源
- 场景物体中的反射为镜面反射
光线投射的步骤:
- 从初射点穿过成像平面打出一根光线到场景中
- 找到与场景的最近交点
- 将交点和光源连接, 判断物体是否在阴影中
- 计算着色情况写回像素中
Whitted-Style光线追踪
渲染速度
对于下面这张图:
- 1979年需要74分钟
- 2016年需要6秒
- 2012年只需要1/30秒
递归光线追踪
- 光线不仅仅只会反射,还会折射、然后再与其他物体进行反射
- 光线每次反射折射都会有能量损耗,不然经过无限次的累加只会变成白色
- 递归过程需要设置一个最大次数
- 两次反射和折射情况如下图可视
确定光线与场景的交点
光线方程
对于每一束光线都满足以下方程:
其中o为开始点,d为方向(单位向量),t为时间
r(t)=o+td 0<=t<∞
求交点:
对于任何隐式的集合体,将r(t)以p点带入隐式的方程中算出f(o+td)=0即可算出t求出交点。
显式几何
- 几何体由若干个面组成
- 在几何体的面上判断是否与他的面相交
- 几何上的平面由一个法线和一个点p‘表示
步骤:
- 可使用(p-p')·N=0表示一个平面
- 将光线方程以p带入光线方程中
- 求出t算出交点
- 判断交点在三角形的内还是外
- 若在内则是三角形的交点
Möller Trumbore算法
中文名: 射线三角相交算法
可以更快的求三角形与射线的交点
- 已知 光线满足r(t)=o+td
- P0、P1、P2为三角形三个顶点
- 可以得到以下等式
- 通过以下E1、E2等的参数定义可以解出等式得到交点
光线追踪加速
如果三角形面特别多,以上面的算法进行计算将会特别慢
轴对⻬包围盒(AABB)
特殊的包围盒定义
三对面的交集形成的立方体,三对面分别于xyz轴平行
使用包围盒包围与光一定轴对齐,减少了计算量
原始方案中:
需要3次减法,6次乘法和1次除法。
AABB中:
每一个轴只需要一次除法和一次减法,一共只需要3次减法和三次除法
这么定义包围盒的好处:
只有三对面都有光线相交(进和出)时间才能证明光线已经进入包围盒
这也定义可以先判断光线是否经过该包围盒,如果不经过就不再进行更多的操作,节省了计算时间。
判定光线与包围盒的交点
对于二维的包围盒
- 先判断与x这对面与光线相交(进和出)的时间tmin、tmax
- 再判断y与光线的相交时间
- 取交集(求出tmin的最大值,tmax的最小值)
三维与二维类似:
- 增加了一对平面,判断三对面的相交时间tmin、tmax
- 求出tmin的最大值,tmax的最小值
- 若tmax-tmin大于0则说明光线与包围盒相交
几种负值的情况的处理:
- tmax<0则说明包围盒在光线背面(无交点)
- tmin>=0且tmin<0则说明光源再包围盒中(有交点)
总结:AABB有交点当且仅当tmin<tmax且tmax>=0
空间划分
AABB的均匀划分
AABB均匀划分的步骤:
- 找到包围盒(最外层的正方体)
- 建立网格(黑色网格)
- 标记与包围盒相交的网格(灰色标记)
- 从光线发射方向逐个遍历网格
- 将每个遍历到的网格测试与其的交点
建立网格的目的:
- 通过网格可以判断有tmin、tmax
- 若有则对网格内的物体进行交点判断
网格的密度认为数量大约为27x物体数量
但是这种网格定义方式对物体分布不均匀的场景中不友好(即使这样的网格也很常用)。
因此引入空间划分来切割网格。
空间划分的分类
八叉树:
- 八叉树是在每个子树下面画十字,划分为四块(在二维下是分为四块,三维是八块)
- 由于是均匀划分,会出现将同一个物体划分为两块的问题。
KD树:
- 每次划分只划分为两块(类似二叉树)
- 在二维中第一次为水平的划分,第二次为竖直的,然后循环划分
- 在三维中类似,以xyz轴顺序进行划分
- 这样可以保证划分比较均匀
- 我们在AABB中主要使用KD树(曾经)
BSP树:
- 划分和kd树类似
- 由于划分不是横平竖直的不能用于AABB的划分
KD树
建立KD树
通过一次对x,y(二维)进行递归划分,得到下面的KD树
遍历KD树
- 假设有一条光线射出
- 逐个对每个子树进行判断是否有与包围盒相交
- 若相交则对他的子树继续遍历知道将找到所有与光线相交的包围盒
- 将其包围盒下的物体查找交点
KD树的问题:
- 难以判断物体与包围盒边界的相交问题
- 一个物体容易同时穿过多个包围盒被重复计算影响性能
因此KD树的应用场景越来越小。
目前广泛使用的技术名叫层次包围盒(BVH)
层次包围盒(BVH)
BVH的特点:
- 按照三角形进行划分,因此一个物体只可能出现在一个包围盒内
- 由于按照三角形进行划分,因此不会出现那一判断包围盒边界的问题
- BVH的思想更像分组,只需将三角形进行分组并对三角形较多的组进行递归分组
- 每次的划分都选择XYZ中最长的轴进行划分,这样保证划分的大小比较平均
- 总是选择中间的三角形进行划分,这样划分出来树更加接近平衡二叉树
- 划分到一个比较小的数量后停止划分(比如5个三角形)
BVH的伪代码:
Intersect(Ray ray, BVH node)
{
if (ray misses node.bbox)
return;//如果与节点光线都不相交就返回
if (node is a leaf node)//如果相交且这是一个叶子节点
{
test intersection with all objs;//将节点内三角形都做判断
return closest intersection;//返回最近的那个
}
hit1 = Intersect(ray, node.child1);//如果不是叶子节点则递归找到最近的那个
hit2 = Intersect(ray, node.child2);
return the closer of hit1, hit2;
}
辐射度量学
由于Whitted-Style形成的光线追踪体系其实并不够真实,引入辐射度量学
同时定义了以下几个属性来描述光照:
- Radiant Energy(辐射能量)
- Radiant Flux(辐射通量)功率
- Radiant Intensity(辐射强度)光源发出的某一方向上的亮度
- Radiant Irradiance(辐射光照度)某一平面所接受到的光线亮度
- Radiant Radiance(辐射亮度)一条传播光线所具有的亮度
Radiant Energy
光线照射下辐射出来的能量,单位为焦耳,用Q表示。
Radiant flux
- 单位时间内的能量消耗,类似于功率,单位为瓦/流明*
- 单位时间内通过一个感光平面光子的数量
Radiant Intensity
角
弧度制:θ=l/r
l:弧长
r:半径
一个圆形有2π个弧度
立体角
Ω=A/r2
A:球面上的面积
立体角是弧度制在三维上的延申
一个球有4π个立体弧度
微分立体角
- 首先确定空间中的方向θ和
- 其中rdθ是微分面积元的高,rsinθdϕ 是微分面积元的宽
- 两者相乘即可计算出球的投影面积A
同时可以验证立体角在球上的积分为4π
在辐射度量学中用Ω来表示方向,用θϕ定义方向
定义
- 每微分立体角(某个方向)上的功率(光强),单位为W/sr又被称作为cd(坎德拉)
- (光源在某个方向上的亮度)
应用:点光源
- 定义一个点光源所有方向的亮度都相同
- flux为I在所有方向上的积分
- 得出I=ϕ/4π
Radiant Irradiance
单位面积上(某个点)接收到的光照功率,单位为W/m2 或者lm/m2 又称Lux(勒克斯)
在此可以解释布林冯模型中光线亮度需要乘cosθ
同时也能解释模型中光线强度的衰减
Radiant Radiance
定义
单位微分立体角,单位面积的功率,单位为nit(尼特),1nit=1 cd/m²
Irradiance与Radiance的区别
- Irradiance是对于一个点所接受到的所有的光线
- Radiance是对于一个点面向某个方向接受到的光线
注意:H²为半球
双向反射分布函数(BRDF)
- BRDF定义了某个点接受到能量后反射能量与入射光的比例
- 表示指定方向的反射光和入射光的比例关系
- 材质的不同决定BRDF方程的不同
反射方程
借助BRDF可以定义出反射方程,即在某个反向接受到的所有的反射光线。
如上图,反射到的光线是由入射光乘以BRDF这一反射比例得到的。
渲染方程
定义
- 渲染方程由两部分组成,自发光Le和反射光
- 其中反射光可以由光源直射也可以由其他的物体反射
- 在反射方程的基础上加入自发光,定义了渲染方程
- 渲染方程定义了所有的光线传播规律
- n·ωi与cosθ一致
- Ω为半球
单个光源的反射方程
点光源只有一个方向有入射光,所以不用积分
多个光源的反射方程
面光源的反射方程
面光源是点光源的集合因此对面光源所在立体角进行积分可以得出面光源的反射方程
面光源的渲染方程
- 将其他物体反射过来的光当成光源,得到渲染方程
- 只需要计算反射光源其余的自发光、BRDF(材质)、cosθ均已知
方程简化
- 目前只有出射点反射出去的能量和入射点反射过来的能量未知
- 可以将渲染方程最终简化为下面的形式方便理解
最终可以将渲染方程:L=E+KL
- 在一个场景内能量守恒
- L为所有的物体辐射出的所有能量
- E为光源分辐射出来的能量
- KL为光源辐射出来的能量被反射出来的能量
解出L并通过二项式定理得到下面的式子。
- L=E为场景内自身辐射的能量
- L=KE为场景内辐射的能量+一次反射的能量
- L=KE+K²E为场景内辐射的能量+一次反射的能量+二次反射的能量
- 以此类推......
蒙特卡洛路径追踪
蒙特卡洛积分
对函数的积分域多次采样求均值作为积分的近似值
例子:
将积分均匀采样,每个采样的概率都是1/b-a,得出以下式子。
其中将b-a移到后面可以得到最开始蒙特卡洛积分的式子。
Witted-Style的问题
路径追踪解决了在Witted-Style中不正确的部分
- 无法完成Glossy的材质,在Glossy材质中光线打到材质上不完全沿着Specular的方向走
- 漫反射后仍然会多次反射,需要引入全局光照
蒙特卡洛积分应用到渲染方程
将某个渲染点p的ωi方向进行多次采样找到反射光源的角度得到蒙特卡洛积分
通过这样的方法可以算出任意着色点的渲染方程
直接光照的算法
shade(p, wo)
Randomly choose N directions wi~pdf
Lo = 0.0
For each wi
Trace a ray r(p, wi)
If ray r hit the light
Lo += (1 / N) * L_i * f_r * cosine / pdf(wi)
Return Lo
全局光照的算法
shade(p, wo)
Randomly choose N directions wi~pdf
Lo = 0.0
For each wi
Trace a ray r(p, wi)
If ray r hit the light
Lo += (1 / N) * L_i * f_r * cosine / pdf(wi)
Else If ray r hit an object at q
Lo += (1 / N) * shade(q, -wi) * f_r * cosine / pdf(wi)
Return Lo
如果采样中反射过来的不是光源而是物体则将物体也当做光源,将物体反射过来的能量来进行计算。
上述算法的产生的问题
反弹次数的上升产生射线数量爆炸
一根光线打到物体后会反射很多个光线到同一个物体,以此类推产生指数爆炸、
因此如果蒙特卡洛积分采样次数为1则不会出现指数爆炸的现象。
路径追踪算法:
shade(p, wo)
Randomly choose ONE direction wi~pdf(w)
Trace a ray r(p, wi)
If ray r hit the light
Return L_i * f_r * cosine / pdf(wi)
Else If ray r hit an object at q
Return shade(q, -wi) * f_r * cosine / pdf(wi
只采样一次的光线追踪被称之为路径追踪,上面采样N次的被称之为分布式光线追踪(Distributed Ray Tracing)
生成多个路径进行路径追踪:
对于每个像素发射N条光线(采样)做以下的算法
将每个射出去的采样接收到能量的点做蒙特卡洛积分得到平均值
ray_generation(camPos, pixel)
Uniformly choose N sample positions within the pixel
pixel_radiance = 0.0
For each sample in the pixel
Shoot a ray r(camPos, cam_to_sample)
If ray r hit the scene at p
pixel_radiance += 1 / N * shade(p, sample_to_cam)
Return pixel_radiance
由于路径追踪递归产生的死循环
- 首先增加一个结束的概率P_RR
- 每次调用shade函数的时候做一个随机数判断
- 若大于随机数则return0反之继续执行shade函数并返回L0/P_RR
- 这样算下来数学期望不会变,E(L0)=P∗(L0/P)+(1−P)∗0=Lo
- 且路径追踪总会停下来
shade(p, wo)
Manually specify a probability P_RR
Randomly select ksi in a uniform dist. in [0, 1]
If (ksi > P_RR) return 0.0;
Randomly choose ONE direction wi~pdf(w)
Trace a ray r(p, wi)
If ray r hit the light
Return L_i * f_r * cosine / pdf(wi) / P_RR
Else If ray r hit an object at q
Return shade(q, -wi) * f_r * cosine / pdf(wi) / P_RR
提高效率
每个像素中的采样点越多,形成的效果越好,比如下图中最右边的例子需要50000个采样才能找到光线,大多数的采样被浪费了
因此为了提高效率我们可以通过找到光源与方向的关系,改写渲染方程,将渲染方程写成对光源的积分
光源与方向的关系式:
改写的渲染方程:
我们最终渲染出来的光纤传播分解为两个部分
- 光源的直接光照
- 光源对其他物体的弹射(需要用到上面的随机算法)
如果光源中有物体挡住则不能渲染光源的直接光照,通过一个if来解决
shade(p, wo)
# 光源的直接光照
L_dir = 0.0
Uniformly sample the light at x’ (pdf_light = 1 / A)
Shoot a ray from p to x’
If the ray is not blocked in the middle
L_dir = L_i * f_r * cos θ * cos θ’ / |x’ - p|^2 / pdf_light
# 光源对其他物体的弹射
L_indir = 0.0
Test Russian Roulette with probability P_RR
Uniformly sample the hemisphere toward wi (pdf_hemi = 1 / 2pi)
Trace a ray r(p, wi)
If ray r hit a non-emitting object at q
L_indir = shade(q, -wi) * f_r * cos θ / pdf_hemi / P_RR
Return L_dir + L_indir
其他的知识
- 点光源不容易处理,因此建议写成一个很小的面光
- 路径追踪可以做到几乎100%的真实
- 光线追踪有很多类型
- (单向和双向)路径跟踪
- 光子映射
- Metropolis light transport(MLT)
- VCM / UPBP
- 课程中光线追踪未涉及的部分
- 函数采样理论
- 选择什么样的PDF(重要性采样)
- 随机数的生成
- 结合不同的采用结果(如光源和着色点)
- 像素发出多个路径,是否平均其着色效果即可(pixel reconstruction filter)
- 像素的radiance和color的区别(伽马矫正)
参考资料
完整笔记
- 度盘链接
- 提取码:njcf