Real-Time Ray-Tracing
RTX: 硬件的突破 在显卡加装了一个用来做光追的部件 光线追踪做的是光线与场景的求交 做一个树的遍历从而快速判断光线是否与三角形求交,对于GPU来说不好做,NVIDA设计了专门的硬件来帮助我们每秒可以trace更多的光线
每秒可以trace 10 Giga的rays 除以分辨率 除以每秒的帧数 1秒中并不是全部时间都在做ray tracing
每个像素采样一个样本
至少要有四条光线才能构成一个最基本的光路
1 SPP path tracing =
- 1 rasterization (primary) 光栅化与primary ray做的工作是一样的,与其每个pixel去trace一条primary ray,不如直接将整个场景光栅化出来 +
- 1 ray (primary visibility) 和光源之间连接进行light sampling并判断是否有遮挡+
- 1 ray (secondary bounce)在Hitpoint根据材质采样出一个方向打出一根光线,会打到一个物体上从而得到secondary hitpoint +
- 1 ray (secondary vis.)从secondary hitpoint与光源连接判断是否会被光源看到
path tracing本身是一种蒙特卡洛积分的方法,本身会产生噪声,采样的样本越多,噪声越小,但在games101中用64 spp都得不到很好的结果,何况1spp
RTRT的核心技术是Denoising降噪
1 spp是在20系的特点,之后30系以及更高可能会支持更多的spp
1 SPP 渲染
质量 (no overblur 过降噪导致模糊, no artifacts 像裂缝这种可见的bug, keep all details…)
速度 (<2ms)
Industrial Sulution: Temporal
假设整个看的场景的运动是连续的
motion vector 用来表示物体在帧与帧之间如何运动
认为前一帧是滤波好了的
用motion vectors找当前帧某一点在上一帧的对应位置
上一帧得到降噪好的结果比如颜色之类的,可以在当前帧复用,相当于增加了spp 递归
时间上的复用
The G-Buffers
几何缓冲区 Geometry buffer
轻量 屏幕空间在渲染过程中可以免费得到的额外信息 per pixel depth, normal, world coordinate, etc 需要时拿来用 只是screen space的信息(记录的是camera能看到的信息)
Back Projection
找当前帧一个像素里包含的内容在上一帧的哪一个像素里
- 求出这个点的世界坐标
- 若G-buffer中有世界坐标信息的图直接用
- 一个点的世界坐标通过MVP矩阵和视口矩阵得到在screen space上的坐标,可以逆向让screen space这个点按逆顺序乘以他们的逆矩阵就得到了世界坐标
$s = M^{-1}V^{-1}P^{-1}E^{-1}x$(screen space是2D坐标,需要深度这一信息来作为Z值)
- 将当前帧世界坐标乘以帧移动的逆矩阵就得到了上一帧中这个点的世界坐标
- 再乘MVP矩阵和视口矩阵变回屏幕坐标上
- 求出motion vector
Temporal Accum./Denoising
先做当前帧自己的降噪 再找上一帧的像素做一个线性的blending
80%-90%来自上一帧
滤波不会让图变亮 只是因为噪点使得能量砍掉所以有噪声的图会更暗(HDR显示器则不会)
Temporal Failure
switching scenes
突然切换场景或光源
burn-in period 需要一段时间预热walking backwards in a hallway
倒退走 新出现的物体找不到对应suddenly appearing background (disocclusion)
从被遮挡状态变成未被遮挡状态
强行用上一帧会造成Lagging残影/拖尾
solution:- Clamping
把上一帧拉到接近当前帧的结果(对上一帧的颜色拉近成这一帧的颜色)
和下文中的outlier removal一样 当前帧在spatial filter之后在对应点周围找一个很小的范围,找出均值和方差定一个有效范围 超出范围则clamp 再blending
是一个介于noisy和残影的tradeoff Detection
判断是否仍要使用上一帧的结果
增加一步操作来判断所对应的是不是同一个物体(给每个物体编号)会重新引入噪声
- Clamping
shading时也会有问题:
想追踪的是shading的变换,但其几何不动因此motion vector为0
detached/lagging shadows
反射滞后
Implementing of filtering
针对当前帧的滤波 滤波实际上就是对原图应用一次 Low-Pass Filter(滤除高频信息)
低通滤波 降噪
cons:
高频信息丢失
低频噪声
滤波核 filter kernel K
高斯滤波核 Gaussian filtering
类似于正态分布,中心值高,向两边衰减1
2
3
4
5
6
7For each pixel i
sum_of_weights = sum_of_weighted_values = 0.0
For each pixel j around i
Calculate the weight w_ij = G(|i - j|, sigma)
sum_of_weighted_values += w_ij * C^{input}[j]
sum_of_weights += w_ij
C^{output}[I] = sum_of_weighted_values / sum_of_weights
中心像素成为i,其余的像素成为j,像素j贡献给i的值通过i和j之间的距离在高斯上找对应的值得出
权值和(sum_of_weights),加权贡献值的和(sum_of_weighted_values)
对于任何一个中心像素i
我们需要定义 权值和(sum_of_weights),加权贡献值的和(sum_of_weighted_values)
对于中心像素i周围一圈的任意像素j(包括像素i本身)
我们需要根据像素i和像素j之间的距离和高斯的σ找对应的j贡献给i的值(权值) w_ij
将权值w_ij与像素j对应的颜色rgb值相乘得到j的加权贡献值,并加到sum_of_weighted_values里
将权值加到sum_of_weights
进行归一化sum_of_weighted_values/sum_of_weights从而得到像素i最终的结果
高斯的滤波核下,sum_of_weights不会为0,但在其他的滤波核下可能会为0,因此在进行归一化从而得到像素i最终的结果之前通常会判断,sum_of_weights是否为0,如果为0则直接输出像素i的值为0
Bilateral Filtering 双边滤波
高斯得到整体被模糊 但想边界仍然锐利(保留边界的高频信息)
认为颜色变化特别剧烈的地方是边界
i和j颜色差异大则让像素j给i的贡献变小
i和j代表一个像素,k和l代表另一个像素
前一项仍旧是高斯滤波核
后一项I(i,j)表示第一个像素的值,I(k,l)表示第二个像素的值,他们之间的差异就是分子,如果差异过大,就相当于在原本的高斯核上乘上了一个指数^-(距离)平方,那么距离差异越大,使得其整体变小接近于0
cons: 分不清噪声和边界
Cross/Joint Bilateral Filtering 联合双边滤波
Gaussian filtering: 高斯滤波核是通过判断两个像素之间的绝对距离distance来查找其需要贡献多少
Bilateral filtering: 两个不同的标准,两个像素之间的距离position,和颜色之间的距离color从而在只保留低频信息时候保留了边界的信息
Joint Bilateral filtering 考虑更多标准features
利用G-buffers 完全无噪声 且是光栅化代替光追第一次bounce顺便得到的
联合双边滤波也不需要考虑核是不是normalize的,在代码中实现时最后会进行归一化操作
不一定必须要使用高斯,只要这个函数满足随着距离衰减就可以使用
联合双边滤波的本质就是在kernel里多算几组不同feature(深度 normal 等)下的贡献,并将其相乘得到最后的结果
Implementing Large Filters
实现比较大的滤波器 filter将变慢
Sol. 1: Separate Passes 拆分实现
先将它在水平方向上filter一遍,之后再在竖直方向上filter一遍
总共访问了2N个纹理(对比之前$N^2$)deeper understanding:
2D的高斯函数本来就是拆开定义的 可拆分
filtering == convoluation 滤波 == 卷积
理论上只适用于高斯 双边滤波,两个高斯相乘,X和Y不容易拆分出来
分开是需要存贮横向之后的结果的,本质上就是空间换时间Sol. 2: Progressively Growing Sizes
用一个逐步增大的filter,比如先用一个小的filter,然后用中号的filter,最后是大号的filter,通过多趟的filter得到N*N的filter得到的结果a-trous wavelet
第一趟是i=0,第二趟i=1依此类推,不同趟数的5*5的filter中间是有间隔的,比如i=0时,每隔2^0,也就是1个间隔采样一个,i=1时,2^1间隔采样一个但filter大小仍旧是55的.依此类推,在第i趟时,样本之间的间隔是2^i
假设我们现在要做5趟,每趟都是5 5的filter,那么i在第五层时候,样本之间的间隔是 2^4 = 16,第五层一共要五个样本,也就是4个间隔,因此大小为64,即第五层时相当于做了一个64 64的filter,但对于这个像素来说,我们做了五趟,每趟5 5大小的filter,一共做了125次的纹理查询,对于 64 * 64 = 4096次这个数字来说是很小
deeper understanding:
用更大的filter == 除掉低频信息(所以不一上来就使用第五趟中间间隔16个样本的filter)
采样 = 重复的搬移频谱
第一趟pass中除去了高频蓝色区域,然后在第二趟pass中相当于在9 9的filter中采样出55的filter,我们采样的间隔对应到频谱上正好是右半图的蓝色部分,也就是在第一趟除掉高频信息后的区域的2倍(tips:频谱是左右对称的),正好是尾对着下一个频谱的首,避免了走样
Outlier Removal
用蒙特卡洛方法渲染一张图时,得到的结果会出现一些点过亮 filter处理后会使这块区域变亮
在filter之前处理掉这些过亮或者过暗的点
Outlier Detection and Clamping
- Detection
对于每个像素取周围一个小范围的区域,计算这个范围内颜色的均值(mean)和方差(variance) 正常的范围在均值+-若干方差内,超过这个范围认为是outlier - Clamping(outlier removal)
把outlier点的值给clamp到接近范围的值
Specific Filtering Approaches for RTRT
Spatiotemporal Variance-Guided Filtering (SVGF)
三个指导filtering的重要因素:
Depth:
SVGF用来判断深度贡献权值的公式
公式返回的是-x次方,所以差异越大,贡献越小
$\epsilon$ : filter时候考虑一个点Q周围所有的点P,也包括这个点Q,因此它是有可能为0的,避免分母为0的情况,而且如果两点足够接近会导致最后的数值太大,为了避免一些数值上的问题加上一个 $\epsilon$
$\sigma_z$ : 用来控制指数衰减的快慢的参数,或者理解为控制深度的影响大还是小
$\Delta z(p) \cdot (p-q)$ 如图AB两点在一个侧向面上按理应该相互贡献但因为深度有差异计算出来不会有太大贡献 考虑A和B在平面法线方向上的深度变化 深度的梯度就是往某一方向上的变化率 用 深度梯度 X 距离 = 实际深度变化量 (垂直于平面法线上的变化 * 距离 = 实际深度变化量)Normal:
用两个点法线向量求一个点积,由于求出来的值有可能是负值,因此使用max()把负的值给clamp到0
点乘结果为正时则使用这个结果
$\sigma_n$ 判断点乘的这个cos函数从1到0是快还是慢 判断法线之间的差异的严格程度
如果场景中运用了法线贴图来制造凹凸效果,判断时运用平面原本的法线Luminance
应用双边滤波里给的颜色差异来考虑,将RGB转换为grayscale(灰度),这种颜色我们称其为luminance,只是一个名字,就认为其是颜色
颜色差异过大,则认为边界
可能刚好选择的点是一个噪声,也就是其特别亮,此时A特别亮,B也特别亮,那么A和B就会互相贡献
variance (方差) 当variance比较大时,不应该去过多的相信两点间的颜色差异
考虑A点是否贡献到B点时,先看A和B点之间的颜色差异,并且除以B点周围的一个标准差
进行 spatial filter —> temporal filter —> spatial filter 从而得到B点精准的variance
cons: 只移动光源 复用上一帧的阴影 产生“残影“
Recurrent AutoEncoder (RAE)
对Monte carlo路径追踪得到的结果进行reconstruction,也就是对RTRT做一个filter
后期处理降噪的方法,将一个noisy的图输入最后输出一张clean的图
神经网络会自动将temporal的结果累积起来(用到G-buffer)
…