FeastSC 发表于 2022-1-24 10:46

Unity3D: 大世界GI方案

Global Illumination 全局光照对画面的提升非常重要 可以照亮暗部,提升暗部的细节 增加画面的细节度以及真实度


传统的方案是Lightmap+LightProbe的方案,这个方案有些许的问题,对于大世界而言随着世界大小的不断变大,lightmap对包体大小的压力也越大,另外就是bake太慢了,另外unity自带的LightProbe是逐对象的,对于小点的物体是没有问题的,但是如果物体变大的话,就会出现不正确的现象



随便做了一个场景





大物体的效果是不正确的

可以对比上面两张图 ,小球被染上的粉红色,基本是正确的,但是下面的一个大块的物体会只响应中间的光线探针,进而影响整个物体,而期望的结果是中间变为红色 四周是偏白色的.
那么现在就需要一种新的方案,不需要lightmap,能更好的表达空间中光照信息的方法
Irradiance Volume



这个方案将空间离散化,每个小块代码这个范围的的光照信息,那么在着色过程中,就可以自动插值,
在这之前首先要一些前置知识
球谐函数

在知乎上有关球谐函数的文章非常多了,有大量的数学成分,我在提供一点个人的理解:对于频率而言,都可以用傅里叶级数展开表达,进而只使用简单的一系列系数表达原始频率,并且所使用的系数越多,还原的越精确,那把图像也类比成频率的话,那也存在着类似的数学工具能够还原原本的图像,并且类似的,系数越多,还原的越精确,同时环境光是很接近一个球面,那这么一个数学的工具就是球谐函数.有了它,我们就可以使用很少的参数就可以还原本来的光照信息了.同时因为反射的光照是一个低频信号,也就是一个模糊图像 那就可以用少量的阶数来还原



阶数越多还原的越精确

LightProbe也是使用的这个方法,unity中是使用了L2阶球谐函数来保存光照信息
URP中的EntityLighting.hlsl中就有类似的采样方法


那么对于这一块 我们就要知道如何实现光源信息<--->SH两者之间的互相转化
可以查看下面这一片文章
球谐函数的预备知识就先到这里
<hr/>本方案参考了《基于Unity Probe的大世界GI方案》了,同时存储了周围环境信息以及AO信息
实际采样是用3DTexture,






可视化GI信息颜色+AO信息
AO

AO也是LightMap做不到的,因为LightMap是二维的,但是AO信息确实3维的


有没有AO的立体感相差很多,现在想象人爬进红色区域,这个物体也会随之变暗,注意这个效果不是因为阴影导致
存储的AO 采用半球采样,记录的其实是天空盒可见性,从上半球发生一定数量的无限远射线,最终计算出哪些被阻挡了,然后除以总数量,最终得到一个0-1的值,作为AO值




游戏中的AO需要有多种方案共同作用


https://www.zhihu.com/video/1462725680777138176
压缩

首先球谐函数函数用到2阶 也就是4个分量 4*3 12个值 这样还需要3张3DTexture 另外还需要存一份AO数据
这里依旧参考了技术分享,对于L1 阶3个Vector值,可以当做颜色的亮度值来存储存储,那么9个值可以压缩成3个值
static float Lumiance(Vector3 rgb)
{
            return Vector3.Dot(rgb, new Vector3(0.2126f, 0.7152f, 0.0722f));
}
SH0 完整存储(3个分量) +SH123的亮度值(3个分量)+AO数据(一个分量 ) 一共7个通道 2张3DTexture完全够存了
由于空间中的光照信息大量都是空白的,保存的信息使用行程压缩算法,可以极大压缩空间,另外保存的信息有时候只会有一点点差别,而这些差别时可以忽略的,可以给一个差别容忍范围,直接变为一个,在配合行程压缩,另外对于数据的选择可以选择字节更少的数据类型
烘焙

大世界范围很大,对烘焙时间也有要求
这里直接使用的反射探针来获取周围信息,生成反射探针直接使用Runtime的,这样就不需要一个一个去bake了,
reflect.mode = ReflectionProbeMode.Realtime;
reflect.refreshMode = ReflectionProbeRefreshMode.ViaScripting;
这样球谐函数能非常快的烘焙出来,还能放在子线程中异步执行
AO是使用物理射线计算的,而Unity的物理计算是不能放在子线程的 最终的耗时都在计算AO上,这里的优化是提前删除掉不需要的探针,比如地表一下的,物体内部的,四周一定范围内没有碰撞体的。从减少无效探针,从而加速烘焙时间

运行时

运行时使用的3DTexture进行采样,探针2m一个 ,在创建3DTexture的时候在四周各扩展了一个像素,将一个默认的光照信息写入这些四周的像素中去,这样在采样的时候可以直接使用最边界的像素,这样就可以优化不在3DTexture覆盖范围内的物体
在运行时根据角色位置,去加载一定范围的块 比如时5*3*5的范围,这样3DTexture 尺寸大概在122*62*122,因为时3D的,更新一次的数量时非常大的,在CPU端执行会有明显的卡顿,改为使用ComputeShader来加速。

参考链接

RhinoFreak 发表于 2022-1-24 10:49

默认的lightprobe大物体其实可以用那个light probe volume来做
但还是per object + cpu evaluate的,开销高,还没法做体渲染
另外请教一下,把sh存3d texture和存structuredbuffer里哪个访问起来快一点?我感觉好像后者是不是更划算?

Baste 发表于 2022-1-24 10:52

来了老哥

jquave 发表于 2022-1-24 11:00

之前都没有想到用structbuffer,之后有空可以试一下

JamesB 发表于 2022-1-24 11:02

[机智]

TheLudGamer 发表于 2022-1-24 11:10

UAV
页: [1]
查看完整版本: Unity3D: 大世界GI方案