3. Lumen - Radiance Cache

1 概述

Lumen 中的 radiance cache 是基于 world probe 实现的,与 DDGI 不同,radiance cache probe 仅用于补充世界空间的 radiance 信息,例如 screen probe、reflection 的光线样本都有可能采样到 radiance cache。

LumenRadianceCache::UpdateRadianceCaches为radiance cache更新入口,被IrradianceFieldGather、ScreenProbe、TranslucencyVolumeLighting三处调用到,其中IrradianceFieldGather与ScreenProbe是为像素提供indirect diffuse的两种方式,本文仅讲述 ScreenProbe 部分的调用。

2 宏观逻辑

3 实现

3.1 层级分布

radiance cache 的 world probe 是基于 clipmap 的分布方式

3.1.1 Clipmap 介绍

Clipmap [1] 提出是为了解决mipmap的内存开销问题,一个非常大的mipmap实际渲染中,只会根据相机视角用到很小的部分。Clipmap的字面意思就是将原 mipmap 裁剪得到一个将会被使用的较小区域,物理显存中只会维持这个区域,根据视角加载原mipmap的相关数据。下面左图是mipmap执行clip操作的示意图,右图是clipmap的两部分,

  • Clipmap Stack:层级大小超过clip size,这部分不同层的大小都是 clip size
  • Clipmap Pyramid:层级大小低于clip size,这部分不同层的大小与原mipmap对应层级相同
image-20250214104203219

在clipmap stack部分,虽然每一层级的大小都相同,但覆盖范围会越来越大。在视角移动加载数据时,clipmap使用环形寻址的方式来避免多次加载相同的数据,如下示意图,

image-20250214104350600

3.1.2 World Probe 的 3D clipmap 结构

二维clipmap中的一个texel在3D clipmap中对应一个3D cell (probe),world probe 的3D clipmap结构是以相机为中心的包围盒区域,每个层级的probe数量相同,但覆盖范围不同,也就是层级越高的grid对应的3D区域越大。

v2-10d3d573f85b66583a847b8653946aa8_r

world probe 的分布记录在 FRadianceCacheState 的下面成员中,UpdateRadianceCacheState负责 world probe 的层级分布的更新。

1
2
3
TArray<FRadianceCacheClipmap> Clipmaps;
float ClipmapWorldExtent = 0.0f; // 覆盖范围的一半
float ClipmapDistributionBase = 0.0f;

clipmap的层级数量、每一层级的grid数量以及每一层级的覆盖范围,都有对应命令行调整

cmd 类型 说明
r.Lumen.ScreenProbeGather.RadianceCache.NumClipmaps int32 一共多少层级
r.Lumen.ScreenProbeGather.RadianceCache.ClipmapWorldExtent float 0级覆盖的世界范围的一半,立方体区域
r.Lumen.ScreenProbeGather.RadianceCache.ClipmapDistributionBase float 0级之后的世界范围指数增长的base,即 extent * pow(level)
r.Lumen.ScreenProbeGather.RadianceCache.GridResolution int32 每一层的probe数量,每个维度probe数量相同

下面是基于这些配置参数对层级分布的更新,主要是以相机位置为中心构建clipmap空间。一个clipmap空间,坐标单位是CellSize,原点为 clipmap 起点,中心点为相机所位于cell的中心。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class FRadianceCacheClipmap
{
public:
FVector Center; // clipmap 包围盒
float Extent;

FVector3f CornerTranslatedWorldSpace; // clipmap 包围盒起点相对于相机中心点的偏移,一个世界坐标点可通过此偏移变换到clipmap空间下

float ProbeTMin; // 用于光追

/** Offset applied to UVs so that only new or dirty areas of the volume texture have to be updated. */
FVector VolumeUVOffset;
float CellSize; // 两个相邻 probe 之间的距离
};

相机位置 NewViewOrigin,各成员计算为

  • Center = SnappedOrigin = floor(NewViewOrigin / CellSize) * CellSize,相机位置对齐到 CellSize 的世界坐标
  • Extent:clipmap 包围盒范围的一半
  • CornerTranslatedWorldSpace:SnappedOrigin - 0.5 * CellSize - Extent + PreViewTranslation
    clipmap 的起始位置,再偏移 0.5 * CellSize 到 cell 中心点,也就是以起点为原点的坐标系的偏移量
  • ProbeTMin:用于光追 TMin,CellLength * ProbeTMinScale (半透明情况下ProbeTMinScale 为 1,否则为 0.1)

3.1.3 shader 中的 clipmap 变换

3.1.3.1 ProbeCoord <-> ProbeWorldPosition

有了clipmap,在 shader 可以将一个世界坐标变换为指定层级的clipmap空间下 probe coord,以及将 probe coord 变换到世界空间下,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
float3 GetRadianceProbeCoordFloat(float3 ProbeWorldPosition, uint ClipmapIndex)
{
const float3 ProbeTranslatedWorldPosition = ProbeWorldPosition + DFHackToFloat(PrimaryView.PreViewTranslation);

const float3 CornerTranslatedWorldPosition = GetRadianceProbeClipmapCornerTWS(ClipmapIndex);
const float3 CornerToProbe = ProbeTranslatedWorldPosition - CornerTranslatedWorldPosition;
const float CellSize = GetRadianceProbeClipmapCellSize(ClipmapIndex);
return CornerToProbe / CellSize;
}

float3 GetProbeTranslatedWorldPositionNoOffset(uint3 ProbeCoord, uint ClipmapIndex)
{
const float3 CornerTranslatedWorldPosition = GetRadianceProbeClipmapCornerTWS(ClipmapIndex);
const float CellSize = GetRadianceProbeClipmapCellSize(ClipmapIndex);

const float3 CornerToProbe = (ProbeCoord + 0.5) * CellSize;
return CornerTranslatedWorldPosition + CornerToProbe;
}

3.1.3.2 查找给定WorldPosition

对于一个被 level i 覆盖的世界坐标,肯定会被后续大于 i 层级覆盖,那么到底采样哪一层级?优先选择最低层级,因此在仅给定世界坐标来获取probe coord以及层级时,从最低层级到最高层级依次遍历,直至找到覆盖该坐标的层级为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
FRadianceProbeCoord GetRadianceProbeCoord(float3 WorldPosition, float ClipmapDitherRandom)
{
FRadianceProbeCoord Out = (FRadianceProbeCoord)0;
Out.ClipmapIndex = NumRadianceProbeClipmapsForMark;

uint ClipmapIndex = 0;
for (; ClipmapIndex < NumRadianceProbeClipmapsForMark; ++ClipmapIndex) {
float3 ProbeCoordFloat = GetRadianceProbeCoordFloatForMark(WorldPosition, ClipmapIndex);
float3 BottomEdgeFades = saturate((ProbeCoordFloat - .5f) * InvClipmapFadeSizeForMark);
float3 TopEdgeFades = saturate(((float3)RadianceProbeClipmapResolutionForMark - .5f - ProbeCoordFloat) * InvClipmapFadeSizeForMark);
float EdgeFade = min(min3(BottomEdgeFades.x, BottomEdgeFades.y, BottomEdgeFades.z), min3(TopEdgeFades.x, TopEdgeFades.y, TopEdgeFades.z));

int3 ProbeMinCoord;
int3 ProbeMaxCoord;
ProbeMinCoord = floor(ProbeCoordFloat - 0.5f);
ProbeMaxCoord = ProbeMinCoord + 1;

if (EdgeFade > ClipmapDitherRandom) {
Out.ProbeMinCoord = ProbeMinCoord;
Out.ProbeMaxCoord = ProbeMaxCoord;
Out.ClipmapIndex = ClipmapIndex;
return Out;
}
}
return Out;
}

在实际实现中,相邻两个层级的边界上由于层级跳变,效果上会出现明显的边界感。因此在层级边缘会设置一定范围的fade区域,当采样到fade区域时,会引入一个抖动值 ClipmapDitherRandom。fade 区域定义为距clipmap边界的grid单位,默认 GLumenTranslucencyVolumeRadianceCacheClipmapFadeSize(4),上述中的 InvClipmapFadeSizeForMark 为倒数:

  • 当不在fade区域时,EdgeFade >= 1,EdgeFade > ClipmapDitherRandom 恒为 true
  • 当在fade区域时,EdgeFade < 1,最终在上下相邻层级中随机采样一个

3.2 构建probe更新列表

3.2.1 indirect 数据更新

DDGI 的world probe负责插值得到某一点的irradiance,同时在计算光线radiance时还会采样上一帧的irradiance来近似无限反弹的结果,这意味着像素会采样周围probe得到其接收的间接 irradiance,而次级反射点同样需要采样其周围world probe。在 Lumen 里,无限反弹由 surface cache 的 radiosity 部分完成,radiance cache probe 的 radiance 来采样 surface cache 得到的,已经具有无限反弹的结果,因此只需要更新被使用到的 probe。

在标记使用状态之前,先将 RadianceProbeIndirectionTexture 中的每个probe状态重置为 INVALID_PROBE_INDEX。RadianceProbeIndirectionTexture 是一个 3D texture,记录了所有clipmap层级的每个probe的使用状态。每个层级的probe数量相同,在 x 维度逐层级并排排列在贴图中,假如 probe 数量为 48x48x48,有层级0 [047, 047, 047],层级 1 [4895, 047, 047]。

ScreenGatherMarkUsedProbes表示被screen probe使用到的 world probe,主要实现在 shader MarkRadianceProbesUsedByScreenProbesCS中,遍历每个screen probe:

  • 通过变换得到 screen probe 的 WorldPosition

  • 获取覆盖screen probe的最小clipmap层级
    uint ClipmapIndex = GetRadianceProbeClipmapForMark(WorldPosition, 0);

  • 层级有效,在 indirection texture 将包裹screen probe的 8 个 world probes 标记为使用状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void MarkPositionUsedInIndirectionTexture(float3 WorldPosition, uint ClipmapIndex)
    {
    float3 ProbeCoordFloat = GetRadianceProbeCoordFloatForMark(WorldPosition, ClipmapIndex);
    int3 BottomCornerProbeCoord = floor(ProbeCoordFloat - 0.5f);

    MarkProbeIndirectionTextureCoord(BottomCornerProbeCoord + int3(0, 0, 0), ClipmapIndex);
    MarkProbeIndirectionTextureCoord(BottomCornerProbeCoord + int3(0, 0, 1), ClipmapIndex);
    MarkProbeIndirectionTextureCoord(BottomCornerProbeCoord + int3(0, 1, 0), ClipmapIndex);
    MarkProbeIndirectionTextureCoord(BottomCornerProbeCoord + int3(0, 1, 1), ClipmapIndex);
    MarkProbeIndirectionTextureCoord(BottomCornerProbeCoord + int3(1, 0, 0), ClipmapIndex);
    MarkProbeIndirectionTextureCoord(BottomCornerProbeCoord + int3(1, 0, 1), ClipmapIndex);
    MarkProbeIndirectionTextureCoord(BottomCornerProbeCoord + int3(1, 1, 0), ClipmapIndex);
    MarkProbeIndirectionTextureCoord(BottomCornerProbeCoord + int3(1, 1, 1), ClipmapIndex);
    }

    结果记录到 RadianceProbeIndirectionTexture 中,对应位置设置为 USED_PROBE_INDEX。

    1
    2
    int3 IndirectionTextureCoord = ProbeCoord + int3(ClipmapIndex * RadianceProbeClipmapResolutionForMark, 0, 0);
    RWRadianceProbeIndirectionTexture[IndirectionTextureCoord] = USED_PROBE_INDEX;

3.2.2 Probe重用、分配与释放

整个clipmap的probe数量比较多,但只会更新其中一部分,如果按照clipmap分布为所有probe分配数据会非常浪费。因此使用buffer来紧凑管理需要分配数据存储的probe列表。RWProbeFreeList 存放空闲的probe数据索引,可使用时从中申请以及释放时归还,其中的 ProbeIndex 唯一标识probe的数据位置,包括:

  • RWProbeLastUsedFrame[ProbeIndex] 记录probe上次使用的帧数
  • 光追结果 RadianceProbeAtlasTexture, DepthProbeAtlasTexture 等等

3.2.2.1 上一帧 Probe 重用与释放

如前所述,clipmap以相机为中心,会随着相机的移动而移动,往往上一帧的clipmap与当前帧有重叠,因此可以重用这部分probe。主要实现在 shader UpdateCacheForUsedProbesCS中,遍历每个LastFrameProbe:

  1. 当前遍历的 ClipmapIndex 以及 LastFrameProbeCoord可以从线程ID中得到
  2. LastFrameProbeCoord -> ProbeTranslatedWorldPosition -> ProbeCoord 变换到当前帧clipmap下
    • 从 LastFrameRadianceProbeIndirectionTexture 中得到 LastFrameProbeIndex (probe唯一ID,分配得到的,非变换得到)
    • 使用上一帧的clipmap变换,得到probe世界坐标 ProbeTranslatedWorldPosition,再变换到当前帧的clipmap中,得到 ProbeCoord。ProbeCoord有效,表示上一帧probe也在当前帧clipmap中,继续执行重用逻辑
  3. 按照 probe 在当前帧是否被使用到以及其中数据是否过旧来更新重用状态
    • 从 RadianceProbeIndirectionTexture 得到当前帧probe的ProbeUsedMarker
    • probe上次更新帧数LastUsedFrameNumber = RWProbeLastUsedFrame[LastFrameProbeIndex]
    • probe 被当前帧使用或者(FrameNumber - LastUsedFrameNumber)在阈值范围内
      • 标记为重用 bReused = true
      • 将 LastFrameProbeIndex 分配给当前帧probe,更新到 RadianceProbeIndirectionTexture 中
      • 被当前帧使用,则更新 RWProbeLastUsedFrame[LastFrameProbeIndex] = FrameNumber
  4. 如果上一帧probe没有被重用,则释放probe索引 LastFrameProbeIndex,归还到 RWProbeFreeList 中

3.2.2.2 Probe 分配索引、优先级与开销预算评估

处理完上一帧probe,接下来需要为当前帧被使用到的probe分配数据索引。这部分在 shader AllocateUsedProbesCS中,遍历 RadianceProbeIndirectionTexture 的每个texel的ProbeUsedMarker:

  • 当前probe被使用到,还未被分配数据索引 (ProbeUsedMarker == USED_PROBE_INDEX)。从 ProbeFreeList 中分配一个ProbeIndex
  • 否则表示,当前probe为重用probe,已有数据索引,即 ProbeIndex=ProbeUsedMarker
  • 每帧trace更新probe的开销是有限的,因此需要评估probe的更新优先级以及trace开销
    • 更新优先级,基于 LastTracedFrameIndex, LastUsedFrameIndex, ClipmapIndex 来划分PriorityBucketIndex,bucket越靠前优先级越高。GetPriorityBucketIndex
      • 如果probe从未trace过,返回 bucket 0
      • 否则,根据“clipmap层级越高重要性越低”、”经历越久帧未trace,优先级越高“来确定 UpdateImportance,最终得到 BucketIndex
    • ProbeTraceCost:probe的光线样本分辨率不同,根据probe到相机的距离具有不同的trace cost
      • 如果低于 SupersampleDistanceFromCameraSq 为16
      • 如果低于 DownsampleDistanceFromCameraSq 为 4
  • 累积 trace cost,RWPriorityHistogram[PriorityBucketIndex] += ProbeTraceCost

这里通过probe的上次trace帧、上次使用帧以及层级,将probe映射到bucket中,得到了一个不严格的优先级列表 RWPriorityHistogram

3.2.3 为预算内的probe构建 trace data

3.2.3.1 为probe创建trace data

首先基于probe的trace cost优先级队列 RWPriorityHistogram,确定更新预算内的bucket。在 shader SelectMaxPriorityBucketCS 中,按照bucket从前往后累积 trace cost,直至达到预设 NumTracesBudget,即查找到最后一个更新的bucket MaxUpdateBucketIndex,以及记录最后一个bucket剩余多少trace cost MaxTracesFromMaxUpdateBucket

在 shader AllocateProbeTracesCS 中,遍历 RadianceProbeIndirectionTexture 每个texel,对于分配ProbeIndex的probe:

  • 以同样的方式得到 probe 的 ProbeTraceCost, PriorityBucketIndex
  • 位于更新开销内的probe (PriorityBucketIndex <= MaxUpdateBucketIndex)
    • 如果 probe 位于最后一个bucket中MaxUpdateBucketIndex,还需要判断是否已消耗完最后一个bucket的剩余预算,已消耗完则不进行trace
    • 为能够 trace 的probe分配 TraceIndex:
      • 累积 ProbeTraceCost 到 ProbesToUpdateTraceCost[0]
      • 构建 trace data RWProbeTraceData[TraceIndex] = float4(ProbeWorldPosition, asfloat((ClipmapIndex << 24) | ProbeIndex));
      • 更新trace帧 RWProbeLastTracedFrame[ProbeIndex] = FrameNumber;

按照预算开销,构建了要执行trace的probe列表 RWProbeTraceData,其中每个元素打包了 ProbeWorldPosition, ClipmapIndex, ProbeIndex。

3.2.3.2 probe 偏移与PDF估计

避免probe距物体表面过近,会为每个需要trace的probe计算一个偏移量,实现在 shader ComputeProbeWorldOffsetsCS 中。主要思想是,通过查询 SDF 可以得知 probe 位置到最近表面的距离,若距离小于阈值,则:

  • 每个group负责一个probe trace data
  • 将一个cell划分为 4x4x4 voxels, 在不同的 voxels 方向上对于 probe 位置施加一个偏移量,再次查询SDF最近距离
  • 在所有查询结果中,取偏最近距离最大的一个偏移量

world probe 在不同的方向上重要性不同,后面会根据重要性来执行超采样逻辑,即具有更高分辨率的光线样本。重要性的其中一方面是需要PDF估计,这里使用screen probe的数据来评估world probe pdf。具体做法在 shaderScatterScreenProbeBRDFToRadianceProbesCS中,通过将 screen probe 的BRDF扩散到其周围的8个world probe实现:

  • 每个group负责一个screen probe
  • 根据 screen probe 的 WorldPosition 可以查询到其周围8个world probe。得到最小的 BottomCornerProbeCoord,再加上 int3(GroupThreadId.x & 0x1, (GroupThreadId.x & 0x2) >> 1, (GroupThreadId.x & 0x4) >> 2),就可以定位其中一个probe
  • 通过 RadianceProbeIndirectionTexture 查找到 ProbeIndex
  • ProbeIndex 可以找到 probe 的SH数据位置,将screen probe的SH系数BRDFProbabilityDensityFunctionSH累加到 RWRadianceProbeSH_PDF

3.3 构建用于更新probe的trace tile

3.3.1 创建 probe trace tile 列表

前面讲到 world probe 会根据重要性来执行超采样而达到更高分辨率的光线样本,主要是通过对probe trace tile进行不同的层级划分实现的,实现在 shader GenerateProbeTraceTilesCS

  1. 每个 group 负责一个probe。BaseTraceTileResolution 为 ProbeResolution(默认为 32) 的 1/16,即 2x2
  2. 确定 probe trace tile 的层级数量 NumLevels:
    • 如果ProbesToUpdateTraceCost超出预算的2倍,取 1
    • 否则根据probe到相机的距离 DistanceFromCameraSq:SupersampleDistanceFromCameraSq 取 3,DownsampleDistanceFromCameraSq 取 2
  3. BaseTraceTile 每个 texel 都表示一个立体角区域,划分则是为其中的立体角区域再细分一个tile,
    • NumLevels > 1 并且 TraceTileCoord 方向上PDF高于阈值,为该方向细分 1 个 L1 trace tile (2x2),添加到PendingTraceTileList,待后续进一步划分
      • TraceTileList 的每个元素打包了 uint2:
        • x: 低16位,TraceTileCoord * 2 + uint2(0/1, 0/1);高16位,Level=1
        • y: ProbeTraceIndex
    • 否则,表示不需要再细分,则添加一个 L0 trace tile 到最终结果 CompletedTraceTileList
  4. 遍历 PendingTraceTileList,继续划分NumLevels 3, 2的 trace tile 如果 PDF 高于超采样阈值,则L1划分4个L2,L2划分4个L3
    • 划分 NumLevels > 2 的 trace tile
      • 划分 L1
        • 如果TraceTileCoord 方向上 PDF 高于 SupersampleTileBRDFThreshold,添加 4 个 L2 tiles 到 PendingTraceTileList
        • 否则,添加1个 L1 tile 到最终结果 CompletedTraceTileList
      • 收集 L2:将 PendingTraceTileList 中的 L2 tile 加入到结果CompletedTraceTileList
    • 划分 NumLevels > 1 的 trace tile
      • 将 PendingTraceTileList 中的 L1 tile 加入到结果CompletedTraceTileList
  5. 按照 probe 次序,将将CompletedTraceTileList的所有trace tile写入RWProbeTraceTileData,每个元素 uint2
    • x:低 16位,TraceTileCoord * 2 + uint2(0/1, 0/1);高16位,level
    • y:ProbeTraceIndex

上述,针对probe做了以trace tile为单位的划分,每个trace tile表示一定分辨率。假如每个trace tile具有 8x8 分辨率,而一个 probe 最少有 2x2 个 trace tile,即最小分辨率为 16x16;最多有 8x8 个 trace tile,即最大分辨率为 64x64。但注意一个probe的trace tile划分不是均匀,例如一个PDF较高的方向上可能划分更多trace tile。

3.3.2 trace tile 排序

trace tile的每个texel都对应了球面上一个立体角范围,可以在该立体角范围内采样光线,执行光追得到radiance。为了光追效率,还会额外做一次trace tile排序,将方向相近的trace tile相邻存放。具体做法是:将光线方向范围划分为一定数量的bin,再把trace tile划分到对应的bin中,最后按照bin顺序排列trace tile。实现在 shader SortProbeTraceTilesCS 中:

  1. 只有一个group,一个线程负责一个trace tile,能够得到 TraceTileCoord, TraceTileLevel, ProbeTraceIndex
  2. 确定 trace tile 所位于层级的probe分辨率 TraceResolution=(RadianceProbeResolution/2) << TraceTileLevel
    • RadianceProbeResolution 默认为 32,对应了 trace tile 默认 8x8 的分辨率
  3. trace tile 在所处层级分辨率下的texel ProbeTexelCoord=TraceTileCoord * RADIANCE_CACHE_TRACE_TILE_SIZE_2D(8)。这是在给定球面分辨率下,表示trace tile方向的texel坐标
  4. 给定指定bin数量,投影trace tile到bin中:
    • DirectionalBin=ProbeTexelCoord * NUM_DIRECTION_BINS_2D(8) / TraceResolution。把整个球面分辨率划分为8份
    • 记录每个bin内的trace tile数量。DirectionalBin转为一维索引 FinalBinIndex,SharedNumTraceTileBins[FinalBinIndex] +=1
  5. 按照Bin的顺序排列trace tile
    • 统计当前 TraceTile 所属bin之前所有Bin的trace tile数量 SortedTraceTileOffset,以及累积所属bin已存放trace tile数量
    • 写入 RWProbeTraceTileData[SortedTraceTileOffset]

3.4 执行光追更新probe

3.4.1 为每个trace tile执行光追

下面就是为每个trace tile执行光追,生成每个texel方向上的radiance,实现在shader LumenRadianceCacheHardwareRayTracingCS中:

  • 一个group负责一个trace tile,一个线程负责一个光线样本,每个trace tile 8x8 分辨率

  • 线程索引得到trace tile内的texel坐标 TexelCoord,执行TraceRadianceCacheProbeTexel

    • TraceTileIndex -> ProbeTraceIndex -> TraceData -> ProbeIndex,可以获取到probe的所有相关信息

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      struct FRadianceCache
      {
      uint2 TraceTileCoord;
      uint TraceTileLevel;
      uint ProbeTraceIndex;

      float3 ProbeWorldCenter;
      uint ClipmapIndex;
      uint ProbeIndex;

      uint ProbeResolution;
      float ProbeTMin;
      uint ProbeAtlasResolutionModuloMask;
      uint ProbeAtlasResolutionDivideShift;

      bool bFarField;
      };
    • 构造光线

      • Ray.Direction:将局部 TexelCoord 变换到球面上的 ProbeTexelCoord,通过 spherical mapping 可得到texel中心点的方向
      • Ray.Origin:probe的世界坐标
      • Ray.TMin:ProbeTMin,0.1 * cell_size
      • Ray.TMax:限制到near field内
    • 追踪并采样surface cache TraceAndCalculateRayTracedLightingFromSurfaceCache

    • 追踪结果按照trace tile排列,存放在 RWTraceRadianceTexture, RWTraceHitTexture

上面的追踪同样在 far field 内执行一次

3.4.2 Resolve到统一分辨率下

如前所述的trace tile划分,为probe分配了不同的分辨率,最终需要 resolve 到统一分辨率下。实现在 shader SplatRadianceCacheIntoAtlasCS 中:

  • 一个group负责一个trace tile
  • 获取trace tile基本信息,TraceTileIndex -> ProbeTraceIndex -> TraceData -> ProbeIndex,得到 FRadianceCache

3.5 采样

Radiance Cache 本质上只提供稍远距离的radiance信息(可能是 radiance cache 分辨率不足提供近光会有比较明显的artifact吧,例如反射过于模糊?),这个由 probeTMin 控制。反射、screen probe 会首先尝试 trace probeTMin 范围内,如果没有近距离交点且被radiance cache覆盖,则会采样 radiance cache 得到 radiance 信息。shader 函数 SampleRadianceCacheAndApply 提供了采样radiance cache的功能。

References

[1] Christopher C. Tanner, Christopher J. Migdal, and Michael T. Jones. 1998. The clipmap: a virtual mipmap. In Proceedings of the 25th annual conference on Computer graphics and interactive techniques (SIGGRAPH ‘98). Association for Computing Machinery, New York, NY, USA, 151–158.


3. Lumen - Radiance Cache
http://example.com/2025/03/04/Render Engine/UE5/Lumen/3. Radiance Cache/
Author
John Doe
Posted on
March 4, 2025
Licensed under