首页 > 其他分享 >UE5.1 Lumen Indirect Diffuse Lighting

UE5.1 Lumen Indirect Diffuse Lighting

时间:2024-04-07 17:44:33浏览次数:17  
标签:Voxel UE5.1 Cache Mesh Lighting 光照 Lumen Indirect Card

目录
Lumen学习笔记,有错误的地方欢迎各位大佬指正

由于工作内容的原因,本文章主要讲的是UE5.1的 Lumen Indirect Diffuse部分

Indirect Diffuse部分由下面五个部分组成

image

  • Mesh Card

Mesh Card是一种高精度的全局空间存储结构,它可以缓存包裹物体的材质和光照信息(Surface Cache)。当光线击中这个结构时,可以快速获取颜色信息。在近距离处,我们需要高精度的光照结果,因此需要追踪Mesh Card。

  • Voxel

Voxel是一种低精度的全局空间存储结构,它可以存储与Voxel相交的六个方向上的Mesh Card的颜色信息。对于远处的间接光照信息,我们不需要过于精确的结果,使用Voxel可以以较小的时间和空间代价获得粗糙的光照结果。

  • Direct Lighting & Indirect Lighting

有了Mesh Card这个存储结构,我们就需要为其注入光照信息。Mesh Card使用Surface Cache来记录光照信息。这里的Direct Lighting(直接光照)和Indirect Lighting(间接光照)共同计算了Surface Cache内的最终光照。

  • Screen Probe

Screen Probe Gather是一种精度较低的屏幕空间结构,用于收集周围Surface Cache和Voxel的光照信息。最后,屏幕空间的颜色是通过插值Screen Probe得到的。

概述

全局光照技术(Global Illumination, GI)旨在模拟光在环境中的传播,包括直接来自光源的光线(直接光)和经过一次或多次反射、折射或散射后到达物体表面的光线(间接光)。实时计算间接光一直是个挑战,因为在物理层面上,每个点的间接光实际上是由无数束光经过无数次的相互作用所形成。为了解决这一复杂的光线交互问题,已经提出了多种GI算法和技术。

Lumen代表了全局光照技术的一次重大飞跃,它整合了多种先进的GI方案,通过这种混合方法,不仅提高了性能,还实现了接近实时的渲染效果。Lumen以其高效的处理方式,为游戏和视觉效果行业带来了前所未有的光影表现力。

image

接下来讲一下Lumen到底是怎么做到实时渲染高质量画面的。

先从屏幕成像说起,假设我们现在的屏幕需要绘制一张图像,最直接的方法逐Pixel去Shading,每个点都计算直接光和间接光并累加,这样得到的结果是准确却十分耗时的,直接光好算,但要算间接光不可避免的就要去Trace射线,就以现在的2K屏幕为标准,如果每个Pixel都去Trace,哪怕是1spp渲染整个画面都需要2560×1440约等于360万根射线,而1spp得到的图形质量完全是不够看的,高质量的GI效果需要更多的光线,而这是目前计算机承受不起的。

image

由于间接光信息是不需要很准确的,所以完全没必要每个Pixel都去Trace,而Lumen采用Screen Probe的方式去以低分辨率收集间接光,Screen Probe会每帧像周围发射64根射线去收集附近的光照信息,我们可以把Screen Probe理解为“眼睛”,我们在物体表面每隔一定距离遍布一只“眼睛”来替我们收集周围的间接光信息,最后眼“眼睛”附近的点都从眼睛里获取颜色,具体说就是每16×16个像素的颜色从一个Screen Probe里插值处理(这里先不考虑滤波),有了这种方式,我们只需要有少量的Screen Probe就可以配合Gbuffer插值出整个屏幕的颜色了。

image

由于Screen Probe每帧都需要更新,Lumen采用了一种额外的类似结构World Probe来加快Screen Probe Trace射线收集光照的速度,World Probe是一种放在世界空间视角无关的Probe结构,使用World Probe的好处在于Screen Probe不需要再长距离步进去获取光线了,这个过程交给World Probe来做,射线超过一定距离直接去附近的World Probe借光就行了。且由于Wordl Probe位置是不会随着屏幕动的,所以每帧获取到的颜色都是非常稳定的,这样最后插值处理的屏幕颜色会更平滑。

image

而有了“眼睛”Screen Probe去收集光照信息,接下来的问题就是要如何去Trace和Trace到了要如何拿到光照信息这两个问题了。如何去Trace这个问题我们跳过,具体可以看看我全局光照加速结构那篇文章,接下来讲讲如何拿到光照信息。

假设现在我们已经有了非常完备的Ray Marching和Ray Intersection方案,接下来的就是要获取Hit Point的颜色作为间接光了,这里最容易想到的其实是SSRT(Screen Space Ray Tracing),直接近距离Trace 上一帧的颜色来作为间接光,因为GBuffer本来就要算,获取GBuffer的过程几乎免费而且上一帧和这一帧光照大部分情况不会有太大差异,所以SSRT是非常快且准确的。但是SSRT不能获取到物体背面信息以及屏幕外的信息,只用SSRT造成背面以及移动时候新场景的光照信息缺失。

image

不只是背面的光照信息,很多时候屏幕外的光照其实也会对屏幕内物体产生影响,所以Lumen采用了SDF Tracing来补充世界空间的光照信息。

image

既然要获取世界空间的光照信息,那就需要与屏幕没有关系且可存储世界空间多次弹射的间接光的结构,Lumen采用了Surface Cache(Mesh Card里存储光照的结构)以及Voxel来存储世界空间的光照信息。为什么需要用两种结构呢?首先我们要明白计算间接光的终极目标是快和带宽占用少,正是如此Lumen采用了大量的hack来达到这一目标。间接光本来就不需要非常准确,所以Lumen对于近处的物体用了采高质量间接光而远处物体采低质量间接光的方式来综合考虑间接光,也就是在近处距离Trace的时候会去找Surface Cache上的间接光信息,这保留了一定精度,而当光线Trace了一定距离以后还没有找到Hit Point,那会去Trace远处的Voxel来获取粗糙的光照信息了,距离较远的物体与物体之间间接光影响实际上非常小,所以这种双重结构带来高性能的同时也能保证最终画面的效果。

image

image

有了存储光照的结构了,下一个问题就是如何让把弹来弹去的光线收集起来,因为间接光的弹射往往不止一次,但是如果递归追踪光线就很难做到实时了,Lumen采用了一种非常巧妙的分帧累积的方法,举个例子,第0帧只计算直接光并记录到Surface Cache上,Voxel去收集Surface Cache上的光照给下一帧使用。第1帧计算直接光同时Trace光线到附近的Surface Cache 和上一帧算好的Voxel,以他们为光源去算间接光,再用Voxel去收集当前Surface Cache收集好的光照信息给下一帧用,以此类推,就可以做到每帧只算1次Bounce,多帧以后达到无数次反弹的效果。

image

有了分帧累积的思想,只需要按算法流程去Trace光线就可以获取到无数次弹射的间接光结果了。因为只有光照注入后才能Trace到光照,所以接下来需要离屏注入Surface Cache以及Voxel内的光照信息。光照注入又分为直接光和间接光两个部分,这两个部分Lumen都是在Mesh Card内做(写入到Surface Cache),Voxel直接Trace 6个面首次命中的Mesh Card就可以得到光照信息了。

要收集Mesh Card表面的直接光加间接光其实就可以理解为我在Mesh Card上面放了一个正交摄像机去着色,最后再把颜色存入Surface Cache里面,而为了快速着色就可以用上文提到的Screen Probe Gather,Lumen的具体做法是在世界空间内的Mesh Card表面放置一堆的Probe去Trace光线,用少量的Probe插值出整个表面的颜色,略有不同的是Mesh Card上的Probe更密集且发射的射线更少,具体是每4×4个像素上放一个Probe,每个Probe只发射16根射线最后通过16个Trace的结果再去插值得到64个方向的颜色。

image

Lumen有一套复杂的排队机制,每帧只更新最重要最需要更新的部分Mesh Card内的Surface Cache和Voxel且它们都会被持久化存储,这样可以大大的加快收集光照的速度。

同时光照质量也是非常重要的,Lumen做了很多工作来减少误差的传递,后面主要说说这些细节部分。

Mesh Card

SDF加速在我全局光照加速结构那篇文章有介绍。

为了方便讲述,下文把Surface Cache拆成两个部分,Surface Cache存储材质信息,Radiance Cache存储光照信息,实际上材质和光照都是存在Surface Cache这一大结构里的。

为什么需要用Mesh Card

SDF已经大大加速了Ray Marching,但是SDF只存空间中的点到物体的最近距离,也就是这时只有HIt Point的Positon,没办法获取到Material信息,没有材质自然也就无法计算光照,因此我们需要一种结构,在SDF获取到Hit Point的同时也能找到对应的Material。

MeshCard便是这种数据结构,他不仅能够在获取到Hit Positon,还能通过Hit Positon映射到Mesh Card对应的位置获取Surface Cache和Radiance Cache。

Mesh Card还有一个重要的作用,就是它是与视角无关的世界空间的结构,世界空间内的光照信息会被持久存储到Mesh Card里,这种在世界空间的结构体可以很有效的补全屏幕空间的缺失的信息。

MeshCard原理

Mesh Card是一种结构的统称,在Lumens里面,我们每个导入的物体都会离线生成一套Mesh Card。Mesh Card 是Mesh的一种属性结构,用于记录数据。要注意的是,MeshCard同时存储材质属性和光照信息,Material属性虽然一般都不会改变,但是离线生成Mesh Card的时候只会生成表面对应的捕获光线的位置,其中的Material属性一般会在加载时捕获,这是因为不同的Mesh之间的重叠遮挡会使Mesh Card表面信息没办法在离线时就确定。Radiance会每帧变化,所以每帧都会选择部分需要更新的Mesh Card更新Radiance Cache。

UE里面捕获Mesh Card的方式是通过对6个轴对齐方向(上、下、左、右、前、后)进行光栅化,获取Mesh对应的材质中的Material Attributes(Albedo、Opacity、Normal、Emissive)并存储到Surface Cache上,同时需要捕获的还有对应观察角度的Hit Point,每个面的Hit Point数据需要进行物理地址转换到Surface Cache图集空间中执行采样。

image

每个Mesh至少有6个Card,复杂物体可能会生成多个。

image

Mesh Card用于记录Material的Attribute和Lighting的Radiance,我们把这两种数据分为Surface Cache和Radiance Cache,而我们最后把数据存入Mesh Card里面。

Card LOD

大世界中往往存在很多的Mesh,对每个Mesh都进行细致的光栅化是不切实际的,所以对于远距离的Mesh一般采用Card LOD,也就是远处物体不需要生成那么多Card,往往6-8个足够了,这样可以减少光栅化的次数。

image

每个 Mesh 的每个 LOD 最多可以有 32 个 Card。

Surface Cache

Surface Cache受摄像机距离影响,相机离物体比较近的时候Surface Cache分辨率会高一点。

image

Surface Cache内的Material属性一般都会经过硬件压缩来减少显存的占用

image

Surface Cache属性通常类似于GBuffer的属性,在导入模型的时候就预加载进Surface Cache里面了,Material属性是Surface Cache里面基本不变的数据

image

对于较远的物体,在光栅化记录Surface Cache中Material Attribute的时候,可以采用更低分辨率进一步减少光栅化和存储,这就是Surface Mipmap。与传统 Mipmap 存储方式不同,Surface Mipmap 在 Surface Cache 平铺展开存储,因此在采样时需要额外的地址转换。

下面左右分别是同一个Mesh的不同Mipmap

image

Surface Cache资源管理

以下篇幅引用了丛越大佬的原话

Card Atlas

Lumen通过Lumen.SceneXXX来存储Material Cache和Radiance Cache,这种数据结构叫Card Atlas

Material Cache对应的RDG资源名称为

  • Lumen.SceneAlbedo
  • Lumen.SceneOpacity
  • Lumen.SceneDepth
  • Lumen.SceneNormal
  • Lumen.SceneEmissive

image

Radiance Cache对应的资源名称为

  • Lumen.SceneDirectLighting
  • Lumen.SceneIndirectLighting
  • Lumen.SceneNumFramesAccumulatedAtlas
  • Lumen.SceneFinalLighting

image

Card Capture Atlas

需要一提的是,Lumen通过光栅化对Mesh六个面的进行capture后,会把对应的Albedl、Normal、Emissive、Depth分别存储在四张大型的Render Target上,这就把所有的Mesh的Material Attribute都打包到一起了,存储在一个大图集上(Atlas),这个Atlas 被称为Card Capture Atlas,这么做可以降低RT交换的损耗。

上面的Card Capture Atlas只是一个临时结构,用于记录每帧更新的Surface数据,最终会通过一个叫CopyCardsToSurfaceCache的Pass将Card Capture Atlas的数据Copy到Card Atlas上。

Card Atlas的存储

为了保证Card Atlas有足够的空间存储Material Attribute,Lumen中默认每个Card Atlas的大小为4K×4K,共有Depth、Albedo、Opacity、Normal、Emissive五张Atlas。

如果每张贴图32位,共需要320MB显存,这是比较占用空间的,所以Lumen会通过硬件压缩的格式节约资源

image

除了Depth之外都用了硬件压缩格式,这是因为需要通过Depth精确还原世界空间的位置以及精确计算Radiance Occlusion,因此不能用有损格式。

Radiance Cache

Mesh Card不只记录Material(Surface )的属性,还需要记录光照的Radiance属性,这是为了间接光的复用,我们不可能在相交点发射无数光线去不停的递归计算间接光,所以我们把每一次的光照结果固定下来,Radiance Cache算的是Irradiance。

要注意的是,计算Radiance Cache的方式并不是直接获取Hit Position的Material直接算光照,而是通过下面用Probe做分帧计算,最后结果再存进Radiance Cache,Ray打到Hit Point的时候,我们直接取Radiance Cache里的值就行了。这么做的原因是提高光线追踪的并行效率,因为每条光线碰到的Hit Point的材质可能相差很大,很难保证局部性,所以Lumen将Surface的光照解耦,将其分摊到多帧中计算,而这些Radiance Cache可以被后续帧复用。

分帧算法

image

第一帧先把直接光记录到Radiance Cache里面

第二帧以第一帧记录了直接光的Mesh Card作为光源,发射射线去Trace,得到的结果当作下一次的光源

每次得到的结果加上上一帧的结果得到Final Lighting,以此递归,相当于把递归算法拆解到每一帧

image

如果对每一个Pexel都去Trace光线收集Radiance是不现实的,哪怕是分帧,每个Pexel都去发射光线还是会造成很大能耗,因为间接光材质默认是Diffues的,所以我们以每4呈4为一个单位生成Probe去Trace拿到光照信息,每个Probe做16次Cone Tracing再通过插值的方式得到64个结果

image

最后转换成SH来降低存储,这样可以大幅度加快实时Trace效率,又因为是低频信号,所以结果也很不错。

image

最后就是直接光和间接光的Combine了,这里的DirectLighting和IndirectLing涉及的内容非常多,会再下面VoxelLighting讲完后一起说。

image

Sort Mesh Cards

Mesh Cards里面的Lighting Cache需要实时更新,但如果对整个Surface Cache都更新的话是十分耗时的,因此Lumen里面规定,每帧最多不超过 1024 x 1024 个texels更新,因此我们就需要对Mesh Cards进行桶排序,大概只有前面10%的Mesh Cards可以被更新。

image

Voxel Lighting(UE5.1已砍)

为什么需要Voxel Lighting

  • 适配Global SDF

由于Lumen加速结构的划分问题,对于近处的物体,其采用的是遍历附近物体的Mesh Distance Field找到到最近距离的点来作为步进距离,虽然要遍历多次,但是这可以直接获取到对应物体Mesh Card上的交点,有了交点就可以获取到对应的材质和光照信息了,但是对于远处的物体,Lumen采用的是Global Distance Field,由于是全局的距离场不需要遍历对应Mesh Card我们没办法跟Per-Mesh SDF一样直接获取到对应的Mesh Card的材质和光照,所以Lumen构建了专门针对于Global SDF Tracing的结构,也就是Voxel,Hit到直接可以获取到光照信息。

  • 作为间接结构

Lumen里采用逐帧累积的方式来表示不断反弹的光线,Voxel其实是一种间接结构,第一帧的Voxel的Radiance其实是从Surface Cache中的Final Lighting中采样的Irradiance。第一帧需要从Surface Cache里面去获取直接光照并注入Voxel里,这时Voxel里的光照数据在第二帧用,第二帧Surface Cache以上一帧算出来的Voxel为二次光源算间接光,更新Voxel下一帧用,以此类推。

Voxel Lighting原理

Voxel Lighting是采用Voxel(体素)来存储光照,光照分帧累计,最终得到全局光照效果的一种GI方案。

Lumen在运行时将Camera周围一定范围内的Lumen Scene体素化,然后通过对Voxel六个方向分别Trace射线找到与Voxel相交的最近Mesh Card,通过获取其交点的Surface Cache的Final Lighting作为二次光源,对Voxel对应的那个面做光照注入。

Voxel Lighting分为三步

  1. 场景体素化
  2. 发射ray采样
  3. 光照注入以及更新

复杂的场景往往伴随着大量的Voxel,为了加速Voxel的构建和更新,先介绍下面一些Voxel相关的优化方法。

Voxel的存储方式

Lumen采用3D Texture的手段存储Voxel各个方向的光照Irradiance,这张3D纹理上的每一个Texel都代表一个Voxel在某个方向上的Lighting。

要注意这边的3D Texture内xyz轴并不是存储对应轴向的光照信息。

在3D Texture内,X轴存的是不同的Voxel,Y轴存的是同一个Voxel不同Clipmap级别的Lighting信息,Z轴存的是同一个Voxel不同的方向,需要3维的信息(Voxel,Direction,Clipmap)才能表示某一个Tiexl的Lighting信息。

image

Voxel生成与更新的优化

直接对所有场景和物体进行体素化是不合理的,通常我们只对摄像机视野内可见的物体做体素化,在Lumen里面我们对所有视野范围内的场景进行Clipmap分层,再体素化并用3Dtexture来存储Voxel里的光照,当相机移出视野后,Voxel光照不变,可被下次trace复用。

当摄像机位置改变,Clipmap也会改变,所以Lumen对ClipMap进行了再分层,用Tile的形式存储Voxel,这样更新等操作都是通过对Tile进行检测并进行,就不需要每帧检测所有体素了,这种方式类似于BVH等的加速结构。

Tile

Lumen通过网格化Clipmap的形式生成Tile,一个Tile包含4×4×4的Voxel,又因为每个Clipmap有 64x64x64 个 Voxels,所以每个Clipmap可划分为 16x16x16 个 Tiles。

划分为Tile的目的有三个

  1. 在Camera移动的时候可以离散化的更新Clipmap
  2. 提供层次化更新机制,有利于提高GPU并行度,获得更好的性能。
  3. 用Tile的方式去构建Voxel

划分Tile的方式给Lumen提供了更极致的性能,相比于用Voxel与记录了空间信息的PrimitiveModifiedBounds做相交测试来筛选变化Voxel,用更粗粒度的Tile去做相交测试可以更快选出相交的Tile,筛选速度大大加快了,同时每个Tile里面有64个Voxel,而GPU每个warp有64个通道,每个warp计算一个Tile这样充分发挥了GPU的并行性。

ClipMap

ClipMap是一种比MipMap存储量更小的存储结构,所有ClipMap 具有相同的分辨率,所以ClipMap也能很好的保留场景的细节,Voxel的生成一般就通过ClipMap生成。

image

用ClipMap生成Voxel意味着,越远的物体的Voxel就越大,Voxel里面的信息也就越粗糙,但结果是可以接受的,因为远处的物体对近处我们看到的东西影响确实很小。

下面是ClipMap和MipMap的存储对比。

image

Lumen将Voxel分为多个Clipmap,默认每个Clipmap里面有64×64×64个Voxel,每4×4×4个Voxel又分为一个Tile,更新一般是按Tile更新的。

第一级Clipmap 0覆盖的区域一般为2500cm,覆盖范围是(25×2)^3立方米,Clipmap一般有四级,每级覆盖的区域都是上一级的两倍。

每个 Clipmap 对应的 Voxel 大小为 Clipmap Size / Clipmap Resolution,例如最细级别的 Clipmap 的 Voxel 大小为:(2500/64) x 2~= 78,即最小可覆盖 0.78^3 立方米的空间。

image

不同的ClipMap内Voxel更新规则也不一样,越靠近摄像机越精细的ClipMap更新频率就高,而远处没那么精细的ClipMap内的Voxel更新频率就低,这是因为人眼对近处变化远比对远处变化要敏感。

ClipMap可以以更小的存储代价带来不比MipMap差的效果。

但是有一个问题,ClipMap是以相机为中心的,当相机移动的时候ClipMap也会更新。如果不做处理这就会导致如果摄像机一动那Voxel Visibility Buffer就得更新,这会导致性能降低且光照闪烁不稳定,Lumen做出的处理不以摄像机位置为ClipMap原点,而用Tile为最小移动粒度,ClipMap的原点只会对齐Tile的中心,这样只有移动超出一个Tile的范围的时候才会更新ClipMap。

Voxel Visibility Buffer

为了完成采样 Surface Cache 的 Final Lighting,需要知道每个 Voxel 在每个方向上 Trace 到的 Mesh DF 信息,这个信息存储在 Voxel Visibility Buffer 中。与我们熟知的 Visibility Buffer 不同的是,这里存储的内容是 Hit Object ID 以及归一化的 Hit Distance,在 Injecting Lighting 时就根据这些数据对 Final Lighting 采样。

Lumen 将所有 Clipmap 的 Voxel 的 Visibility 都存储在同一个 Buffer 中,并且 Visibility Buffer 是跨帧持久化的,因此为了性能每帧会按需更新部分内容。

但有一个问题,每个Clipmap里面有64×64×64个Voxel,每个Voxel有6个方向的光照需要算,如果每帧都对每个Voxel更新就需要做64×64×64×6~=157万次计算,Lumen采用了只用变化了的Voxel更新Visibility Buffer的策略。

Lumen会对记录变化物体的AABB包围盒,然后计算这些AABB包围盒和哪些Tile相交,先做一个粗粒度检测,找出变化的Tile,再逐Tile做Voxel的相交测试,找出所有变化的Voxel再更新他们。

场景体素化

传统做法

要体素化场景,我们先找都每个面元在哪个Clipmap上(因为ClipMap上的Voxel大小不同,所以要分开做),然后把整个像素的WorldPosition转换成屏幕UV,查询该面元在哪个Voxel范围内,一个Voxel往往包括了非常多的面元,我们要做的就是把Voxel里面所有面元的影响都通过某种方式累积到对应的Voxel上。

我们在Shader里可以算出这个Pixel属于哪个Voxel,但无法确定这个Pixel是否会对Voxel产生影响,因为多个Pixel重叠的时候我们只保留深度最小的,为了能在Voxel累积尽可能多的信息,我们要选一个能生成最多Pixel的投影方向进行计算。

image

如上图采用跟法线最接近垂直的面进行投影,可以获得最多的Pixel,这样也能累积最多的信息来减少Voxel的误差。

需要注意的是,上面的投影用保守光栅化的方式可以保留更多的信息,可以在对ClipMap内全部物体做光栅化时用完整的几何信息做MSAA来实现保守光栅化,VXGI就是这么做的。

Ray cast构建

上面已经说过Voxel更新的方式了,体素化的时候其实可以理解为Voxel第一次更新。

而Lumen体素化Voxel的方式不是保守光栅化,而是用发射射线的方式。

image

这里补充一下Visibility Buffer的更新,由于需要对Tile内的物体做相交测试来得到需要更新的物体,Lumen会先用列表记录每个Tile内的物体,这样在Tile内部做Voxel求交的时候只需要遍历一小部分物体。

体素化的构建规则是对Tile内每个Voxel每条边上随机发射一条Ray,如果能打中任意一个Mesh,就说明这个Voxel不能为空,我们把这个Voxel标记为需要更新。

后续对需要更新的Voxel每个面都做采样,把求交结果也就是Hit Object ID 以及归一化的 Hit Distance信息记录到Visibility Buffer内,方便后续的光照注入。

image

采样

Cone Tracing

Lumen里采样Voxel用的方法是Cone Tracing,Cone Tracing其实就是根据圆锥体来采样,一般来说知道BRDF可以通过求Lobe(包含有贡献的光源的锥体)范围内光源来计算光照。

image

在Voxel里,可以理解这个圆锥体包含的光源是由不同级别的Voxel组合而成的,因为Voxel记录的其实是对应范围内的光源的综合影响,所以锥体采样(Cone Tracing)也就可以转换为采样不同级别的Voxel按一定权重带来的影响。

image

这种方式根据距离来决定要采样哪张ClipMap上的Voxel,再通过UV Scale 和UV Bias计算3D里的UV,最终可以从对应的ClipMap里采样到对应的方向Voxel,而这个方向一般是由对应方向附近的3条法线(可以想象成坐标轴基底)按一定权重混合得到的。

image

Voxel的权重一般根据遮挡程度来计算

image

光照注入

在构建体素的时候就会朝各个面朝内发射射线,射线命中的Hit Object ID 以及 Hit Distance会被记录到Visibility Buffer里,有了这两个信息就可以从对应的Surface Cache里面拿光照信息了。

Scene Dirct Lighting

Lumen里对直接光的处理分为三步

  1. 对多光源的处理
  2. 对阴影的处理
  3. 计算Final Gather

多光源处理

Tile Base&Cluster Culling

在Deferred Rendering中,为了进一步优化性能,一般都会做多光源优化,比较常见的方式是Screen Space Tile Culling和Cluster Culling。

Screen Space Tile Culling 是基于屏幕空间对光源进行划分,把屏幕分成一定数量的Tile,每个Tile通过光源索引找出对这个Tile里面的Mesh有影响的光源,这个方法适合处理有着大量光源的场景,但对于某些特殊情况,如地铁隧道里面由于透视的关系大部分的光源都在一个Tile里面,这就会出现很远处的光源会对很近的物体造成影响的问题。
image

而Cluster Culling的做法就是在Tile Base的基础上根据距离在横向再划分一次Tile,这个Tile的划分由距离决定,这样一块Tile表示的不再是一整个锥体,而是锥体里的一小块空间,用这种办法筛选的光源就准确很多了。

image

Card Page Tile Culling

Lumen对多光源的处理用的是Card Page Tile的方式,这种方式不再是在屏幕空间划分区域找区域内光源,而是找出光源会影响哪些区域。

Mesh Card生成好以后会储存在一张Card Page里面,Lumen在Card Page上进行Tile划分,可以理解为在Card Page上的每个Tile表示的是某个区域内的Mesh Card,只有在对应范围内的光源才会对这片区域的Mesh Card造成影响。

Lumen会在对应的Card Page Tile范围生成包围盒,通过包围盒判断光源是否能造成影响,如果有影响,将Light影响的所有Tile连续存储进RWLightTilesPerCardTile这个Buffer中。

一般来说每个Card Page Tile 含有8×8=64个Texels。

阴影处理

通常情况下,Dense SM和VSM对阴影的计算都是Camera Visibility的,我们只要算可见场景的阴影,而且一般Deferred Rendering的Gbuffer只有视野范围内最表层的信息,不可见的Pixel都被剔除了。所以Lumen额外补充了一个叫Off Screen Shadow 的方法来获取离屏阴影。

VSM

常规的ShadowMap存在精度问题,如果精度很低,阴影会出现严重的锯齿,如果精度高,这存储量对显存就很不友好。

Virtual Shadow Map 思想很简单,就是用同样的空间去存储更多的细节,而传统的Shadow Map存在一个问题,就是存在冗余的情况,因为传统Shadow Map是在光源视角去看形成的一张深度图,而光源看到的很多位置其实摄像机视角是看不到的,所以其实我们如果能做到把摄像机看不到的部分的Shadow Map给不要了,就可以节省很多的空间,VSM就是利用节省下来的空间放更高精度的Shadow Map。

VSM通过分Tile的思想来做高精度的Shadow Map,通过世界空间Pixel投影到光源视角下再判断深度来标记该Pixel位于哪块Virtual Tile上,再选择是否要保留这块Tile。

image

最终Shadow Map上只保留和摄像机视角下的Pixel重合的部分,看不见的部分全部剔除掉。

image

尽管Shadow Map的精度很高,但是最终占用的内存可能跟完整的低精度Shadow Map占用内存一样,生成高精度Shadow Map代价不算高,但显存却很珍贵,所以VSM是一种时间换空间的方案。

Off Screen Shadow

由于Mesh Card在收集直接光照的时候往往需要从屏幕外的光源里采样(用的是整个Lumen Scenen的Shadow),如果不考虑屏幕外的遮挡情况很容易出现漏光现象,举个例子,下图里光源在屏幕外,如果不考虑屏幕外的遮挡情况,下面的阴影区域就可能被照亮,从而导致漏光。

image

Off Screen Shadow 主要是通过距离场来做的,主要原因有两个

  1. 离屏阴影不需要太准确
  2. SDF算阴影速度非常快

Lumen对Off Screen Shadow的实现分为两步

  1. CullMeshObjectsForLightCards
  2. Distance Field Shadow Trace Pass

第一步是CullMeshObjectsForLightCards,计算阴影也需要做多光源处理,把光源影响之外的Mesh先剔除掉再计算Shadow,第二步就是Distance Field Shadow Trace Pass,通过SDF直接算阴影

Final Gather

直接光的Final Gather依赖上两个步骤的结果,再CPU端会先执行Batched Light这个Pass,先遍历收集所有有效光源,使用前面CullLightingTiles阶段生成IndirectArgs以GPU Driven的方式计算Radiance并输出到Tile所处Card Page对应的区域上,依次对每个光源进行绘制,对应光源会根据阴影处理生成的Shadow Mask去计算并混合光源的结果,最终的Final Gather会被存进RWDirectLightingAtlas中。

Scene Indirect Lighting [TODO]

Screen Probe Gather [TODO]

参考文章

最强分析 |一文理解Lumen及全局光照的实现机制
游戏引擎随笔 0x29:UE5 Lumen 源码解析(一)原理篇
游戏引擎随笔 0x30:UE5 Lumen 源码解析(二)Surface Cache 篇
游戏引擎随笔 0x33:UE5 Lumen 源码解析(五)Voxel Lighting 篇
KillerAery博客
实时全局光照VXGI技术介绍
UE5中VirtualShadowMap的简易实现原理(一)

标签:Voxel,UE5.1,Cache,Mesh,Lighting,光照,Lumen,Indirect,Card
From: https://www.cnblogs.com/Gr-blogs/p/18119521

相关文章

  • UE5中简易的UI管理框架(c++版,UE5.1)
    需求说明:在UE项目开发中,当UI界面较多的时候,就需要有一个管理功能出现,负责UI的生成、销毁、禁用等功能。基于此需求,采用栈先进后出的数据接口,编写了一个简易的UI管理框架。功能说明:1.支持UI的自动创建2.支持UI的按开启顺序关闭3.支持一件关闭所有UI4.支持开启当前UI后,禁......
  • Excel-定义名称 & INDIRECT 函数& 下拉选单设定
    1.定义名称意义:储存格的定义名称,可以将储存格的范围转换成一个容易理解和记忆的名字,比如A1:A5~姓名,将五笔金额设定为一个名称~金额,使我们在设定与维护公式时更加方便,此后再建立公式的时候,不用再用鼠标框选范围,可以直接在括号的后面,输入我们命名的名字。设定:①框选范围-公式-定......
  • Current Outdoor Lighting Policies in China: Measures to Control Light Pollution
    AbstractLightpollutionisaseriousenvironmentalissuewithmanyadverseeffectsonhumanhealthandtheecosystemasawhole.Accordingly,manycountrieshaveissuedlawsandregulationstolimittheeffectsofartificiallightingatnight(ALAN).Chin......
  • Pset_SpaceLightingRequirements
    Pset_SpaceLightingRequirements空间照明要求:与适用于IfcSpace或IfcZone引用的照明要求相关的特性。这包括所需的人工照明、照度等。  NameTypeDescriptionArtificialLightingP_SINGLEVALUE / IfcBooleankünstlicheBeleuchtungAngabe,obdieserRaumeine......
  • UE5.1 One or more errors occurred. (Found no script module records.)
    问题UE5.1源码版本烘培资源,提示缺少SDK,通过日志查看,错误打印如下:Unhandledexception:Oneormoreerrorsoccurred.(Foundnoscriptmodulerecords.)UATHelper:正在安装Sdk(Windows):Initializingscriptmodules...UATHelper:正在安装Sdk(Windows):Unhandledexceptio......
  • 无涯教程-JavaScript - INDIRECT函数
    描述INDIRECT函数返回由文本字符串指定的引用。如果您在Excel公式中键入引用B1,则Excel会理解这引用了单元格B1。但是,Excel无法将文本字符串"B1"理解为引用。因此,如果单元格引用采用文本字符串的形式,则需要使用INDIRECT函数将其转换为实际的单元格引用。立即判断引用以显......
  • Xstream操作xml_The type org.xmlpull.v1.XmlPullParser cannot be resolved. It is i
    //1.再使用xstream解析xml时发现总是报错Thetypeorg.xmlpull.v1.XmlPullParsercannotberesolved.Itisindirectlyreferencedfromrequired.classfiles//从bug中可以看出 XmlPullParser这个类为找打xstream需要依赖这个类从网上搜了一下这个类看看属于哪个jar包发现......
  • Lighting web 测试使用
    作者:WalterWj背景如果不想使用Lighting命令行模式来导入数据,而是集中到自己的管理平台上,那么可以将lighting启动为一个服务,然后调用即可。APIlightningweb模式api:POST/tasks,body是配置文件,返回json,字段id表示taskid。GET/progress/task手动测试启用服务器......
  • P4 UVA11400 Lighting System Design
    很好的一道DP题。首先按照电压排序。然后考虑\(dp[i]\)表示前\(i\)盏灯的最小花费,则应该有\(dp[i]=min(dp[j]+(s[i]-s[j])*c[i]+k[i])\),其中\(s[i]\)表示前\(i\)盏灯的总需求数。为什么可以这样子直接用前缀,而不用考虑"跳着选"呢?是因为如果跳着选,就说明有一盏灯在......
  • UE5.1 中 Runtime Data Layer 的设置
    UE5.1中RuntimeDataLayer的设置UE5.1中奇葩的设置逻辑创建DataLayer默认是Editor的,5.1中如果想要修改为Runtime,需要进行如下设置在创建DataLayer之后,在ContentBrowser中右键点击它,点击Edit然后把DataLayerType设为Runtime我在DataLayerOutline......