Games202(12`作业5)
作业5
前言
本节我们将实现实时光线追踪的时间累计降噪算法
对于光线追踪的渲染结果,G-Buffer 及其他相关信息会以文件的形式提供(202作业5发布公告的作业数据百度网盘资源,将它解压到项目目录)
运行build.bat 或 build. sh 来构建和编译作业框架,里面的指令和以前一样,通过cmake生成工程和vs编译即可生成可执行文件
工程输入和输出都是.exr图片格式的文件,我们可以用作业内的image2video脚本把输出结果转换为视频,使用前提是电脑装有ffmpeg
作业中的所有内容都在denoiser.cpp里实现,几个流程分别对应Filter(),Reprojection(),TemporalAccumulation() 这3个函数
代码解读
Denoiser降噪类:
成员变量:
- m_useTemportal = false,是否使用时间降噪
- m_preFrameInfo上一帧信息
- m_accColor当前时间累计后颜色(仅依赖于< i 的帧,也就是前i-1的acc结果)
- m_valid临时buffer,在每次执行ProcessFrame都会被重置,记录每个像素本次是否有效(可以投影上一帧结果),
- m_misc临时buffer,在每次执行ProcessFrame都会被重置,作为m_accColor的临时缓冲区,而m_accColor则负责记录真正的函数间传输结果
成员函数:
- init()初始化
- m_accColor初始为首帧联合双边减噪后的结果,首帧没有acc,只有单帧降噪
- 根据m_accColor的宽高,初始化m_valid和m_misc缓冲区的大小
- Maintain()更新上一次帧信息为传入的帧信息
- Filter()单帧降噪,返回对输入帧信息联合双边减噪后的结果
- Reprojection()投影上一帧结果
- TemporalAccumulation() 时间累计降噪
- ProcessFrame()
- 接受帧信息
- 对帧降噪,并用局部变量保存联合双边减噪后的结果
- 如果是首次执行,m_useTemportal为false,会执行init,之后不会再执行了
- 如果不是首次执行,会调用Reprojection和TemporalAccumulation
- 调用Maintain,m_preFrameInfo更新为此帧信息,以便下一帧时获得上一帧的帧信息
- 输出时间累计降噪后的帧信息Buffer2D< Float3 >(在buffer.h文件中定义)
main.cpp
- Denoise():
- 创建Denoiser降噪类,循环帧数量次,遍历每一帧
- 每次都会通过LoadFrameInfo加载帧信息,通过ProcessFrame()实现这一帧的时间累计降噪,通过filename设置路径并写入图像
- main():读取examples/ 目录下的输入,通过Denoise()降噪后,将返回的降噪结果传入输出路径
FrameInfo帧信息 / 图像:
结构体内部每个成员都已数组形式呈现
- m_beauty 代表渲染结果。
- m_depth 代表每个像素的深度值。
- m_normal 代表每个像素的法线向量(模长为1)。
- m_position 代表每个像素在世界坐标系中的位置。
- m_id 代表每个像素对应的物体标号,对于没有物体的部分 (背景) 其标号为-1。
- m_matrix中保存了多个矩阵
- m_matrix[i]标号为i的物体从局部坐标系到世界坐标系的矩阵
- 倒数第2个,世界坐标系到摄像机坐标系的矩阵
- 倒数第1个,世界坐标系到屏幕坐标系的矩阵
联合双边滤波(Joint Bilateral filtering)
联合双边滤波核:
世界位置,颜色,法线,深度,任意一项差距越大,贡献越小,用exp^-x来简化表示公式,x越大值越小
法线夹角为0——90时,贡献为1——0,当夹角超过90,贡献为负,应将此值clamp为0
在简单的深度检测上做了优化:利用i的法线和 i->j 单位向量 的点积:
- 比如书和桌子的平面完全平行,但它们位于不同的平面上,深度值差异小贡献大,但由于非同面理应贡献小,我们的法线和两点向量点乘的结果会减小贡献
- 比如Cornell Box 的侧面墙,深度差异大贡献小,但由于同面理应贡献大,我们的法线和两点向量点乘的结果会让贡献 == 1(90度)
代码部分:
- 遍历每个像素,根据frameInfo获取像素信息
- 对像素卷积核内的像素,根据frameInfo获取像素信息,根据联合双边滤波核计算此像素weight权重
- 结果保存在buffer中
投影上一帧结果
代码部分:
- 遍历每个像素,
- 首先根据Inverse(m_matrix[id])获取world_to_local矩阵,将像素对应的顶点的world坐标转为局部坐标
- 通过m_preFrameInfo.m_matrix[id]获取local_to_world矩阵,将顶点变换到上一帧的世界位置(因为本质是物体移动而非相机移动)
- 通过m_preFrameInfo.m_matrix[size() - 1]获取World_To_Screen矩阵(PV),即找到了顶点在上一帧的像素位置
- 会对pre_screen_position,进行 0 <= p <= width/height的边缘检测
- 会对pre_id == id,对应的物体id是否一致
- 当满足两个条件,会从m_accColor查询上一帧投影的颜色
- 遍历了每个像素后,通过swap后,m_accColor = 上一帧投影的颜色buffer
temporal accumulation 时间累计
代码部分:
- 遍历每个像素,
- 如果像素是有效的,m_accColor(x, y)获取像素的上一帧投影的颜色
- 遍历核的范围,mu+= 当前像素的颜色, sigma +=
- mu / count 获得 本帧 下均值,sigma / count 获得 本帧 下方差
- 并且为了防止过亮/过暗的噪点情况,会对上一帧投影的颜色做(µ−kσ,µ+kσ)的颜色限制
- 将本帧的去噪图像和上一帧去噪图像按照alpha混合(lerp),结果会存储在m_accColor作为本帧acc去噪的结果
A-Trous Wavelet加速单帧降噪
对于过滤器使用逐渐增长的尺寸Progressively Growing Sizes,将减少过滤的时间
- 和Filter()相比在kernel外又多了一层for循环,做pass个过滤器
- 每次都会查询n’ * n’次,只是它的间隔为pow(2, pass),获得采样的像素位置,其他的都一样了
本文由作者按照 CC BY 4.0 进行授权