浅谈光线追踪

作者:willyan
2018-10-24
23 19 0

基于 RadeonRays 的光线追踪全局光照实现方案

最近半年一直在做全局光照方面的工作,陆续实现了辐射度算法和光线追踪两套方案,最终由于辐射度算法的局限性(只能基于漫反射)还是使用了光线追踪的方案,是时候写个小小的总结了。

先列一下提纲吧,希望从以下几个方面讲一讲这些工作:

  1. 为什么选择光线追踪
  2. RadeonRays 简介
  3. 实时光线追踪与离线光线追踪的区别
  4. 光线追踪全局光照方案的渲染管线
  5. 光线追踪方案实现过程中的经验和教训
  6. 一些效果展示

为什么选择光线追踪

相信很多游戏开发者,特别是做游戏引擎的猿,会对渲染效果很痴迷,而全局光照是渲染效果呈现的一个很重要的因素,在2017GDC 中 Unity 和 AMD 发布了基于 RadeonRays 的 Progressive Lightmap 的全局光照实现方案,和 Unity 之前的 Enlighten 的方案研究对比了一下,无论在效率还是效果方面都有了很大的提升,再加上自己对于新技术的热爱(其实是 Leader 的逼迫),有了把 RadeonRays 方案移植到我们引擎的想法。其实在这之前实现了辐射度算法的方案,但是效率和效果实在是难以忍受,所以开始光线追踪的预研,半个多月之后居然移植过来简单的跑通了,极大的增强了自信心,于是继续攻克难关实现了它。后来看到报道2018GDC 中 UE4和 NVIDIA 联合发布了实时光线追踪的解决方案,DX 也适配了光线追踪的渲染管线,感觉光线追踪应该是现在的趋势。

RaydeonRays 简介

RaydeonRays 是 AMD 的一套光线追踪的解决方案,它支持三个后端:OpenCL, Vulkan,以及 Embree, OpenCL 使用支持至少 OpenCL 1.2的 GPU 和 CPU,Vulkan 支持 Vulkan 1.0以上的 GPU, Embree 支持 Intel 优化的 CPU 光线追踪设备软件。当然,在如今 GPU 性能越来越好的时代,能用 GPU 就别用 CPU 了,我选用了 GPU 的 OpenCL 方案,当然,选它也是因为 RaydeonRays 在 Github 开源了,虽然无法照搬,但是很多东西还是可以借鉴学习的。

据 Unity 所说,使用 RadeonRays 的 Lightmap 烘培方案比之前快了10-20倍,之前需要一天才能渲完的场景现在一个小时就能渲染完了,具体是真是假我也没实验过。

实时光线追踪与离线光线追踪的区别

所谓的实时光线追踪,就是随着摄像机视角的变动,后端需要实时发射追踪光线来重新计算光照信息,如果屏幕分别率很高,这个计算量是很大的,对 GPU 的性能要求是很高的,如果性能达不到游戏直接会卡死。而离线光线追踪则不会造成这种情况,即使设置了很高的光线追踪采样率,很多的反射次数,无非是烘焙 Lightmap 的时间会变长,最终还是可以渲染出效果很好的的 Lightmap 供场景使用。所以现阶段还是全局光照中的光线追踪方案占主流,NVIDIA 虽然发布了新的 TURING 架构显卡引入光线追踪框架,但是实时光线追踪真正进入游戏普及估计还是任重道远(希望能很快打脸)。

光线追踪全局光照方案的渲染管线

关于光线追踪的基本原理,其实还是比较简单的,简单来说就是向场景发射 N 条光线,然后根据碰撞点的材质进行 BXDF,BRDF 的运算,然后(根据俄罗斯轮盘)再进行漫反射,镜面反射,或者折射,如此循环直到光线逃离场景或者到达最大反射次数,最后对 N 条光线进行蒙特卡洛积分即可获得结果。对于实时光线追踪和离线光线追踪,这里发射光线的方式还是有些差异的:实时光线追踪是从视点发射光线,光线数量一般是屏幕的大小,比如屏幕是1920*1080, 则需要发射1920*1080条射线,每条射线对应一个像素点,依照需求,可能要发射多次来采样平均得到理想的结果;而对于离线光线追踪,每个静态物体都要根据光照 UV 生成 M 个 Patch(数量和 Lightmap 大小有关),每个 Patch 要向法线方向的半球发射 N 条射线(数量由用户采样数量决定),最终对 N 条射线进行蒙特卡洛积分,得到这个物体的 Lightmap。

具体的实现过程可以参照 Unity 的实现方案,贴一个他们方案的伪代码:

本来想自己画一个 PipeLine 的流程图的,但是忽然想起之前看的 UE4的视频中看到过类似的,就回头找了一下直接把他们的贴上来:

需要补充的是,对于 Miss 的光线,我做的处理是给它赋予环境球(当作天光)的颜色,而每次处理完最近的碰撞之后,需要发射阴影检测射线以及重新计算的反射光线,底层的加速结构我用的 bvh,可以加包围盒也可以直接把三角形挂在上面。

光线追踪方案实现过程中的经验和教训

因为是从一穷二白的状态开始的,所以过程中遇到的坑实在是太多太多,很多可能都没记录流失了,这里就搬一下印象比较深刻,记录在案的吧。

  1. 刚开始生成 Lightmap 是逐像素的,现在想想当时的思路是太蠢了,没有合理运用好 GPU 的特性,后来用逐 Lightmap 的方式,效率提升了成百上千倍。
  2. 材质层开始没搞的很明白,结果就是 Lightmap 渲染出来效果总是不理想,比如石头边缘会漏光等等,后来仔细看了看底层的材质算法,才算弄好了。后来又恶补了一下 BRDF 的一些算法和逻辑,感觉这方面做好还是挺不容易的,最近 Google 开源了 filament 的项目代码,还有一些文档,特别是对于移动端的优化写的还是挺好的,在这里推荐一下。
  3. 随机光线的生成,开始是用的 c++ rand 方法直接在半球生成光线,后来用了 cosineSampleHemisphere 算法,最好再加个分层采样,比之前的效果噪点少一些。
  4. 一些并行的运算,只要能在 GPU 里面计算,尽量放到 GPU,放到 kernel 里面计算比 CPU 快的多得多,CPU 和 GPU 交互读取数据会比较耗费性能,这方面的操作能少做就尽量少做。
  5. 还有就是材质内存方面,尽量所有物体设置成同一属性 Instance,不然内存老是崩溃,这是底层的问题,另外在引擎客户端方面做了一些场景物体和灯光的剔除,也有改善作用。
  6. 对于 AlphaTest,开始想了好多办法,不想把 alpha 贴图传进射线检测层,但是后来效果都不好,还是乖乖传了 alpha 贴图。
  7. 一些漏光问题,很多时候是因为模型 UV 划分问题,或者 Lightmap 大小不够,像素太少导致的。
  8. 还有噪点问题,一开始采样灯光是用的随机采样,这样灯光多了很容易出现噪点,最后直接遍历所有灯光,这样虽然效率方面降低了不少,但是最后噪点基本很少,用效率换效果,最后其实效率也还可以接受,这样所有路径最终只需要遍历一次就够了。

当然还有很多服务器和各种自己粗心失误的问题,印象比较深的一次是 Github 代码改了之后找不到原版了,下的还不是 Release 版本,幸亏 Git 比 SVN 好一些,能恢复到某一时间点之前的代码,万幸啊……

一些效果展示

首先贴一个木屋的测试效果:

在左侧面还是可以看到光线追踪的反射效果的。


然后尝试渲染了一个项目比较大的场景:

光线追踪渲染出来的效果整体会更丰富生动一些,当然这个版本还是当时不成熟的版本,仔细看还是有些问题的。


最后贴一个受到 UE4启发做的一个面光效果:

面光的虚化阴影效果还是挺好的。


欢迎大家分享交流。