欢迎参与讨论,转载请注明出处。
前言
自《拉维瓦纳》技术性Demo演示公布后,得到不少反馈与总结。其中内部达成共识的一处便是目前的场景效果仍需提升,于是便围绕此展开了新的计划。其中明显的一点便是场景的着色表现太过平滑,近乎为Unlit。于是需要为此增添不少变化,其中一项措施便是全局光照。
而采用Unity自带的全局光照方案:Lightmap时则遇到不少问题与效果的不尽人意:
- UV overlap问题
- GPU烘焙时功能不齐全(如不支持TextureArray),而CPU烘焙则速度不佳
- 对于动态物体需使用Light Probes方案,效果不够一体化
- 对于场景内会动、会破坏的部分的支持度不佳
- 烘焙器不开源,效果的可定制性不强
基于以上原因,最终选择考虑其他的全局光照方案。以安柏霖的游戏中的Irradiance Volume为引,得出了一种自我魔改的全局光照方案,并不代表真正的Irradiance Volume方案,仅供参考,附上工程链接。
分析
根据安柏霖一文大致可以看出,Irradiance Volume是一种将场景划分为多个区域,每个区域记录关键信息,最终应用于区域内的对象的一种全局光照方案。这听起来很像Light Probes,只不过Light Probes是逐对象的(整个模型着色),而Irradiance Volume能做到逐顶点/片元。
经过一番研究,参考了论文、半条命2、AMD、COD等诸多资料后,得出一点:这Irradiance Volume如同ECS一般,只有大致的概念,并无标准的实现。网上亦无太多相关开源实现,那么只好按照自己的理解去发挥了。
其核心概念在上文也已说明,现落地为实际方案:
- 按固定大小的格子划分场景
- 使用ReflectionProbe拍摄每个格子下的CubeMap,提取6个面的代表色
- 将每个面的代表色按位置存储到3D纹理,由于有6个面,所以需要6张
- 具体模型着色时,根据顶点坐标找到所属格子,根据法线方向采样对应面的颜色进行混合,最终着色
构建格子
首先是按固定大小的格子划分场景,为完成这一点,我们构造一个专门的MonoBehavior ProbeMgr
,并构造格子的专属数据结构 ProbeData
:
|
|
通过在ProbeMgr定义格子在场景的数量(XYZ)、格子的大小、设置存储格子的容器,最后加上预览:
|
|
上图已是烘焙好的结果,仅供参考,如此格子的构建便完成了。
提取代表色
所谓提取格子六个面的代表色,这种做法其实有个专属名词:Ambient Cube。其核心思想就是简化某个区域内的光照信息,这很显然是非常物理不正确且粗暴的,但有道是图形学第一理论:看起来对了,那就是对了。类似替代方案还可以采用二阶球谐,两者在效果上较为接近,这并非本文重点,不再展开。
在上文也提到对此的具体方案:使用ReflectionProbe拍摄每个格子下的CubeMap,最后采样Cubemap的每个面的颜色求平均值即可。看起来这是个可并行化的任务:为每个格子都创建ReflectionProbe对象进行拍摄,然后使用Compute Shader对Cubemap进行采样提取颜色。可惜事与愿违,在Unity内部实现中,ReflectionProbe的拍摄同一时刻只有一个,而类似的Camera拍摄Cubemap更是非异步的,可见拍摄这一块想达到真正的并行化是做不到了。所幸Compute Shader那一块还行,也懒得改成非并行写法了(万一有天支持了),附上相关代码:
|
|
|
|
上图是拍摄现场,可惜拍摄这块无法达成并行化,显得有点捞,只能将就了。
着色
我们所需的数据都已构建完成,接下来便是着色了。首先需要将相关数据传到Shader,作为全局变量使用:
|
|
然后便是核心Shader代码:
|
|
如此,通过调用GetIrradiance
函数,传入顶点世界坐标与法线便可获取相关颜色,然后根据个人喜好进行着色即可:
|
|
成果展示
以下是成果展示:
相较于传统GI方案来说,这样的效果未免过于浓郁了,这是我故意加了魔改代码后的结果:要的就是这种效果。毕竟GI对于我的目的而言并非为了什么物理正确,只是想让场景增添更多的颜色变化而已罢了。
后记
这套GI方案的好处便是可控性强,有着做出更具风味效果的可能性。当然就性能消耗而言实际上是较传统Lightmap要高的(采样三张图),一般项目估计也用不上,仅供一乐。