前述
又造一个轮子,为了庆祝URP终于出了TAA,撸一个简单体积光来庆祝一下。
就最最基础的用光线步进,采样阴影贴图。稍微抖动一下搭配TAA。
还是有一些URP的坑在里面的。简单记录一下,以备将来查验。
项目地址,直接PackageManager导入Git仓库就行
Renderer Feature
因为不打算再写一个RenderFeature了,直接用现成的Full Screen Pass Renderer Feature
,所以就按照这个Pass要求的Shader的写法,要写两个Pass
这里就是第一个坑。如果只写一个Pass,在编辑器是没问题的,打包出来就失效,还不报错。。。
Shader
顶点
可以直接用vertexID来计算位置。这个VERTEXID_SEMANTIC
语义还挺少见的。
记得考虑到DX与OpenGL的区别。
片元
首先是依据深度图重建世界坐标,用这个坐标作为步进的终点。
这里考虑一下米氏散射。即光线散射的方向和光线的照射方向有关。
步进前的准备
起点是相机位置,采样的位置是从起点到终点均匀的30个点。
这里针对采样点做了前后的偏移抖动,保证从侧面看体积光的分界线分明。搭配TAA效果还可以。
然后就是采样阴影,计算米氏散射系数,
最后依据强度取对数并乘以系数就完活。
聚光灯/点光源
平行光用整个屏幕步进,聚光灯与点光源就没必要全屏了,还可以通过AABB来剔除。
Mesh
点光源的网格很简单,Blender弄个棱角球等比放大即可。
聚光灯就实时根据角度和高度去生成两个锥体包裹起来。
Shader
和平行光类似,但步进的起点与终点可以手动计算,计算得出射线与光照范围的交点,再根据相机与场景深度是否在范围内,可以缩小光线步进的距离。
如果是点光源就简单一点,计算视线与球体的交点即可。
聚光灯复杂一点,要算视线与锥体侧边的交点,以及视线与一球冠(圆台)的交点。
关于直线(射线)与各种三维曲面的交点,有时间单独记录在一篇文章吧。
附加光索引
这里还有一点比较麻烦的是获取指定灯光的索引。
untiy并没有提供官方的API。但从RenderPass的renderingData.lightData.visibleLights;中可以得到一个列表。
这个列表的索引和实际索引顺序相同,只是前面要剔除平行光。
所以最后还是没能逃过写RenderFeature。
Render Feature
这里就不赘述了,借鉴了SRP的炫光的写法,为每个要添加体积光的Light添加一个PointVolumeLight或者SpotVolumeLight。
用一静态列表来记录需要渲染的体积光。
这样就能在pass中获取到要渲染的对象了。
写都写了,把平行光也做成一个pass吧。
这里顺便用了VolumeComponent,方便在场景中调整全局平行体积光的效果。
优化与改进
Blue Noise
之前抖动步进距离是采用的一般的shader内的随机数,但还是有一种更好更适合光线步进的噪声,BlueNoise。
具体这东西有多神奇请移步BlueNoise。
可惜的是这没办法实时单帧内在GPU生成,只能预先生成然后采样,好消息是仅需64*64分辨率平铺,肉眼也很难发现重复结构,我觉得这才是BlueNoise最最神奇的地方,而且也基本发现不了有什么突出的结构。
为了搭配TAA,有两种方式,一是随时间随机抖动整体偏移一张噪声图,二是用TextureArray,随时间随机采样不同的噪声图。好在噪声图不大,即使放上64张,一共也才512512。
为了方便使用,下面打包了一张512512的包含了64张64*64的噪声图,可以方便在Untiy中直接生成TextureArray,图片来源图片来源
只能说这个噪声太神奇了,原先平行光需要采样15次的效果,搭配TAA仅需4次。射灯原先10次采样,现在仅需3次就能有不错的效果。
可控性
其实在日常中,很少能发现体积光的现象,一般只有在山洞,森林等光线单一且潮湿的地方才有。
但这种效果却在游戏中经常运用。
会过来看Shader,发现其实这里也没有多么“物理”,比如平行光就没有计算衰减,最后的归一化函数也是基于经验。
所以调整一下最后的归一化函数,增加几个系数,便于调整美术效果。
- 均匀度:调整步进总量光线强度与实际显示亮度的关系
- 最大强度:便于调整整体的颜色和强度
- 距离衰减系数:是否考虑距离或角度的衰减
- 阴影反向衰减强度:阴影不仅不计入光线总量,反而会反向衰减现有的光线总量,会加深阴影的表现,但“不物理”。
升降采样与模糊
算最常见的优化方案了,确实体积光也没必要全分辨率渲染,而且模糊后再搭配TAA效果应该也不错,边缘也不会那么锐利。
这就单独再开一篇文章记录吧,打算在URP中用RenderFeature和Volume框架复刻毛星云大佬的那个模糊后处理的库。