文章

Games202(12`作业5)

作业5

前言

本节我们将实现实时光线追踪的时间累计降噪算法

对于光线追踪的渲染结果,G-Buffer 及其他相关信息会以文件的形式提供(202作业5发布公告的作业数据百度网盘资源,将它解压到项目目录)

运行build.bat 或 build. sh 来构建和编译作业框架,里面的指令和以前一样,通过cmake生成工程和vs编译即可生成可执行文件

工程输入和输出都是.exr图片格式的文件,我们可以用作业内的image2video脚本把输出结果转换为视频,使用前提是电脑装有ffmpeg

1742720387942

作业中的所有内容都在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)

联合双边滤波核:

1742722731295

世界位置,颜色,法线,深度,任意一项差距越大,贡献越小,用exp^-x来简化表示公式,x越大值越小

1742722736272

法线夹角为0——90时,贡献为1——0,当夹角超过90,贡献为负,应将此值clamp为0

1742722742703

在简单的深度检测上做了优化:利用i的法线和 i->j 单位向量 的点积:

  • 比如书和桌子的平面完全平行,但它们位于不同的平面上,深度值差异小贡献大,但由于非同面理应贡献小,我们的法线和两点向量点乘的结果会减小贡献
  • 比如Cornell Box 的侧面墙,深度差异大贡献小,但由于同面理应贡献大,我们的法线和两点向量点乘的结果会让贡献 == 1(90度)

代码部分:

  • 遍历每个像素,根据frameInfo获取像素信息
  • 对像素卷积核内的像素,根据frameInfo获取像素信息,根据联合双边滤波核计算此像素weight权重
  • 结果保存在buffer中

投影上一帧结果

1742780932342

代码部分:

  • 遍历每个像素,
  • 首先根据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 时间累计

1742801357306

代码部分:

  • 遍历每个像素,
  • 如果像素是有效的,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 进行授权
本页总访问量