写在前面:
本文将分享我在UE4中使用蓝图复现SVGF[1](的Voxel改版)的流程。
本文将首次放出项目(可以算是开源,写得不好还请见谅)。而且不会有太多的公式,涉及的方面虽然很多但是其实在我之前的文章里都有提到。只描述这个项目是怎么用蓝图来实现的。
项目包含:
一个全蓝图和材质的光追器(包括BRDF,Pathtracing,体素求交),
一个体素化函数库(目前有点Bug但是基本不影响使用),
一个体素操作函数库
一个蓝图和材质实时去噪器。
没有额外的C++代码,全文基于蓝图。
项目写成不容易,能多点赞请多点赞。
PS:本人准备以后转到b站,在白嫖(不是)之前,关注我的b站账号并三连吧~(我会尽量更新视频/教程),项目链接也会放在b站的视频下方:
【虚幻4】基于CBR和SVGF的实时体素光线追踪_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili www.bilibili.com时间隔得比较久,实现及描述上可能会有疏漏,还请各位斧正。
目录
一、管线概述
1.GBuffer(VoxelBuffer)
2.Raytrace
3.Upsampling
4.Atrous Filtering
5.Temporal AA
6.其他(包括数据的准备)
1.Pingpong
2.体素
二、棋盘格与Upsampling
1.棋盘格
2.Upsampling
三、SV(Voxel)GF
1.Atrous Filter
2.Feature Map
3.Temporal Filter
一、管线概述
由于全程使用蓝图,效率不高,因此对原来SVGF的管线进行一点阉割,并且加上和体素有关的特性。本人在文中尽量采用理想情况(也就是用其他图形API可以实现的情况)来进行描述,并且在蓝图中最大限度地实现。
关于名称,因为有很多名词是英文的,不太好翻译。
本文里FeatureMap基本等于GBuffer或者VoxelBuffer。
Atrous的头上应该有个音标符号但是为了方便均不写出来。
有阉割的部分可能包括:
1.数据精度(蓝图里不好控制)
2.去掉方差估计
3.减少Atrous的pass次数
4.去掉部分GBuffer
Pipeline以及其Buffer占大小(均为理想状态)如下:
1.法线及粗糙度及金属度及高光及颜色及体素在法线轴上的高度坐标
Normal(3 bits)
Roughness(3bits)
Metallic(3bits)
Specular(3bits)
BaseColor(3bits x 3 = 9bits)
VoxelHeightOnNormalAxis(任意)
FeatureMap的显示,颜色没有意义,因为Encode了法线及粗糙度及金属度及高光及颜色压缩至RB通道(9bits),体素世界坐标(投影到平面)放在G通道
这里考虑到几个问题,一是UE的贴图格式在蓝图中不好控制,不能用uint(当然浮点数转uint也会出现各种意想不到的错误,因为中间有一个DrawMaterial的Pass),综上考虑只用了9Bits的数据每通道,为了留下Exponent的尾巴,防止错误。而且这里有个优化方法,可以把材质属性变成材质ID,在着色的Pass再进行读取体素的材质,但是在蓝图的管线里较为麻烦,因此还是把各种属性当成FeatureMap存在第一个Buffer里
(不要在意这些奇怪的位数,都是为了蓝图能够正常运行做出的妥协。。。)
2.Raytrace(RGBA16)
Raytrace采用了棋盘格的渲染方式,路径追踪时对体素求交使用了八叉树加速的结构,得到一个带孔的1/4spp的光追图像
3.Upsampling(RGBA16)
将半分辨率的1/4spp的光追图像向上采样至全分辨率的图像(在后面将会详细描述)
4.Atrous Filtering(RGBA16)
利用GBuffer和FeatureMap的信息,对向上采样过的图像进行滤波多次。每一次扩大Atrous的采样的范围,同时扩大Atrous的孔径。
本文的项目把最后一次Atrous Filter放在了最后显示的Pass中,为了节省一下带宽。
5.Temporal AA
和UE的做法基本一致,在YUV空间进行Clamp,具有FireFly的效果,而且能够叠加上一帧的有效信息,同时使用GBuffer和FeatureMap的信息对无效信息进行过滤,防止拖尾。
但是没有做Reprojection,因为蓝图里不太好做。
考虑到没有RW的Texture,因此很多步骤需要用到Pingpong的方法进行贴图的读取和写入,在文章里讲述会比较复杂,具体的还请看实际的项目。
6.其他(包括数据的准备)
需要Pingpong的Buffer:
FeatureMap
Raytrace Buffer
Atrous Buffer
Final Buffer(到这里可以输出了)
体素八叉树的生成与求交:
可以参考本人之前的文章:[2]
https://zhuanlan.zhihu.com/p/55964499
但是在这个项目实际实现的时候使用了另外一种方案,采用了Step优先的方案,可以参考我的ShaderToy,因为细节比较多就不在本文里赘述:[3]
https://www.shadertoy.com/view/wtKXWV
八叉树的结构较为复杂,为了放进一张2D贴图里,本文将体素的Mipmap pyramid[4]
体素的Mipmap示例图比较难画,用一张2D的代替一下从4D数据编码到2D的贴图中,基本思路是先把4D的Index转换为1D的Index,最后1D的Index转换为2D的Index,并且写入Texture,具体实现可以参考项目中的运用[5]。
二、棋盘格与Upsampling
1.棋盘格
棋盘格渲染在游戏中已经大规模应用,因此基本的方法不在本文中赘述。
本文中的实现需要达到的目标是:
1.易实现2.能够确实降低消耗3.不带来过多的画质损耗
因此在管线中,由于无法使用蓝图控制MSAA,以及无法操控Clipspace后的像素位置(实际上是可以的,比较麻烦),以及方便控制流程,以及省下两张Buffer(等原因),本文的所谓棋盘格是使用一张全分辨率的Buffer,并且每隔一帧往里面只塞入1/4的数据,也就是只运行1/4的像素,其他的像素在跑光追之前都会被return掉。在多线程的运算中,这种操作的优化实际上不能起到4倍以上的运行速度,但是依然可以达到巨量的速度提升。
具体实现如下,将像素分为2x2一组:
渲染顺序为红->绿->蓝->黄
在一帧中,除了当前渲染的像素均为黑色。
2.Upsampling
因为使用了不一样的棋盘格,这里也用了不一样的向上采样(不一定完全最优,还请斧正)
定义 是颜色的平均值
Filter一次,全分辨率 Filter一次,半分辨率(仅仅经过一次Filter,肉眼已经难以看出差别)
将时间域分为4帧一组:
第1帧:
有效信息只有红色部分,考虑到周围的其他红色区域,
第2帧:
此时绿色被填充
可以注意到,对于已经渲染过的一个像素是不进行任何操作的,因为认为他是真实值,而其他的只是估计值。
第3帧和第4帧以此类推,不在此赘述,可以参考项目中的Shader里的实现[6]。
三、SV(Voxel)GF
1.Atrous Filter
这里简单描述一下,Atrous是一种带孔卷积,它比一般的卷积有更广的感受野,直观表现为在屏幕空间中,一个像素能通过Atrous得知更多的信息。
Atrous在神经网格中的应用Atrous被广泛运用于神经网格中,在实时计算机图形学里也有运用,如SVGF的前身Edge-Avoiding A-Trous Wavelet Transform for fast Global Illumination Filtering[7] 就使用了该算法。
在实时运算里,可以通过对孔径的大小进行Jitter或者卡点(使其在四个像素中心)来获取更多的有效信息(通常是连续的表面,因为GBuffer无法简单地通过双线性插值来获取插值后的信息),不过尽管如此,在Atrous上面可以下功夫的地方还是很多。本文的实现里会采用非整数的孔径来降低Artifact。
5+1次Filter,1spp除了引入FeatureMap之外,以及去掉了方差估算,和SVGF的算法基本保持一致。
2.Feature Map
实际上就是GBuffer。这里有一个体素独有的特征,就是体素面的中心在该面的法线的轴上的坐标高度。通过这个高度可以轻易通过卷积来区分不同的体素,从而不会使体素都糊成一片。其中最终记录的高度坐标为:
这里会引入一个问题,对于每一个轴,都会有两个平面(正面反面)共用一个坐标系。但是由于渲染的时候,受限于投影矩阵,不可能使那两个面同时出现在一个屏幕之中,因此可以被忽略。但是还是会有极小概率的不同轴之间的碰撞,但是由于在Atrous的Edge Avoiding中,相互垂直的法线的面基本不互相影响,因此也可以被忽略。
在Atrous以及TemporalFilter中,FeatureMap用来区分不同的体素,不一样的体素之间将不会进行插值。直观地感受便是
不仅仅是Edge Avoiding,还有像素之间的Avoiding由于没有做方差的估算,本文的项目引入一个滤波衰减系数 ,其中 是当前帧, 是衰减系数,来模拟随着时间的累加,方差的减少,需要滤波的地方也随着减少的情况。
3.Temporal Filter
参考UE4的Temporal Filter。
在Atrous Filter和Temporal Filter进行的过程中,对于FeatureMap不一样的体素信息将会被丢弃,而不是进行插值。因此直观感受便是在渲染一个个的体素面,只不过是基于屏幕空间的。
金属与非金属之间不会有插值,保留了材质的边界总结及可以改进的地方
可以改进的地方有很多,实际上有些阉割笔者在Vulkan中实现的时候是不存在的,考虑到了蓝图这种限制性的编程语言的局限性而做出的调整。目前还没有找到一种最快的八叉树求交方式,仍然在尝试各种算法。还请各位斧正。
结尾
在蓝图中实现这个项目不容易,前前后后从Vulkan移植到蓝图里花了大概有一个多月的时间,中途对整个管线在蓝图的适应性进行了多次调整,才达到目前的效果。权当一个蓝图的小玩具吧。
有很多人对蓝图表示厌恶,觉得用C++比蓝图要高贵许多。但是本人一直不这么认为。蓝图这种限制性的编程能给本人带来不一样的满足。对笔者而言,蓝图是艺术,不是技术。
最近把之前的视频都转到了b站,并且开了一个独立游戏的坑,求大伙们关注一下,项目链接也会放在b站的视频下方:
【虚幻4】基于CBR和SVGF的实时体素光线追踪_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili www.bilibili.com求关注求关注求关注~!!~!(不准白嫖)
感谢阅读~
全文完
最后放一点渲染的图,均为实时渲染,1/4spp+Temporal Filter
参考
- ^SVGF https://cg.ivd.kit.edu/publications/2017/svgf/svgf_preprint.pdf
- ^之前的体素文章 https://zhuanlan.zhihu.com/p/55964499
- ^体素求交的ShaderToy https://www.shadertoy.com/view/wtKXWV
- ^GigaVoxel里的Mipmap Pyramid https://maverick.inria.fr/Publications/2013/Gue13/index.php
- ^体素的编码在VoxelCommon的FC_GenerateMipMap里面
- ^Upsampling的shader在MAT_UpSampling中
- ^Edge-Avoiding A-Trous Wavelet Transform for fast Global Illumination Filtering https://www.uni-ulm.de/fileadmin/website_uni_ulm/iui.inst.100/institut/Papers/atrousGIfilter.pdf
来源:oschina
链接:https://my.oschina.net/u/4398177/blog/4262769