跳到主要内容

簇光照

备注

从 PlayCanvas 引擎 v1.56 开始,默认启用簇光照。旧光照系统短期内仍保留,但将在未来的次要版本中弃用。

灯光可有效提升应用真实感,但实时灯光也会带来显著运行时开销,尤其是大量投射阴影的灯光。

降低开销的一种思路是限制影响单个网格的灯光数,通常为每个对象寻找并使用附近灯光。但这种策略有多个缺点:

  • 每个对象可能使用不同的灯光集合,因此必须编译对应的着色器。
  • 大对象必须拆分为小对象,这种策略才有效。
  • 大量投影灯光可能耗尽着色器中供阴影贴图使用的纹理槽。

为解决这些问题,PlayCanvas 使用簇光照高效实现点光源和聚光灯。它将灯光信息存入纹理,让 GPU 便捷地只使用当前着色片元附近的灯光。优点包括:

  • 增删场景灯光时无需重新编译着色器,因为着色器可处理多盏灯光。
  • 场景可使用大量灯光及其阴影和 cookie,因为每个像素只计算附近灯光。

注意,平行光影响所有对象,因此不使用簇光照方案。

实现概览​

簇光照的基本实现步骤如下:

  1. 使用所有相机的视锥体剔除灯光,确定当前帧可见的灯光列表。

  2. 在所有可见灯光的轴对齐包围范围上,覆盖一个世界空间三维网格。

    三维网格

  3. 每个网格单元保存与其相交的灯光索引。CPU 每帧更新这些信息,以查询影响任意位置的灯光列表。信息存入纹理,供 GPU 访问。

  4. 所有可见灯光的属性存入另一张纹理,供 GPU 访问。

  5. 阴影贴图和 cookie 纹理都渲染到图集中,而非独立纹理,使着色器可同时访问全部内容。

  6. 片元着色器计算光照时,使用片元世界位置定位三维网格单元,再计算其中保存的灯光。

编辑器选项​

在编辑器设置的 Rendering(渲染)部分可找到簇光照选项。

编辑器中的簇光照界面

可在此禁用簇光照(需要旧系统时),也可调整下文的性能与功能。

调整簇光照​

启用功能​

簇光照着色器需要处理所有受支持的灯光,因此必须包含相关功能代码。这可能让着色器比实际所需更大,编译更慢。通过以下功能选项可关闭应用不需要的功能,加快编译:

  • Shadows Enabled:启用或禁用阴影支持。
  • Cookies Enabled:启用或禁用灯光 cookie 支持。
  • Area Lights Enabled:启用或禁用面光源支持。

配置三维网格​

三维网格配置

Cells(单元数)指定各世界轴方向的单元数量。包含所有可见灯光的轴对齐包围盒会动态划分为指定数量的单元。

Max Lights Per Cell(每单元最大灯光数)指定单个单元保存的灯光数量上限,表示灯光重叠数量。划分越粗,重叠通常越多,所需上限越高。

配置图集​

可见灯光使用的所有阴影贴图和 cookie 纹理均存入图集。阴影和 cookie 各有一张图集纹理。两者可以使用不同分辨率,但内部供各灯光使用的区域划分相同。

Shadow Atlas Resolution 设置阴影图集尺寸,Cookie Atlas Resolution 设置 cookie 图集尺寸,均无需为 2 的幂。

Atlas Split(图集划分)控制如何分出各灯光使用的子纹理,有两种策略:

  • Automatic(自动):数组大小为 0 时,引擎按需自动划分,为每盏可见灯光分配相同尺寸的子纹理。例如一帧有三盏可见灯光,图集会划分为 2x2,四个子纹理中的三个分配给灯光。

    图集划分为 0

  • Manual(手动):将图集划分为固定数量、可不同大小的子纹理。使用数值数组设置,每个数值表示水平和垂直方向的划分数量。示例见下一节。

配置手动图集划分​

以数组 [2, 2] 为例:第一个数将图集划分为 2x2,共四个区域。后续数值继续划分这些区域。这里第二个数将其中一个区域再分为 2x2,即四个区域,最终共 7 个区域。

手动划分

下图展示如何指定手动图集划分。

图集划分为 2

其他示例:

  • [3, 2]:第一个数将图集划分为 3x3(9 个区域),第二个数将其中一区域再分为 2x2(4 个区域),最终共 12 个区域。
  • [4]:图集划分为 4x4,共 16 个区域。

手动划分的主要优势是可以控制细节程度。固定数量的子纹理按灯光屏幕空间大小分配,让屏幕上较大的灯光使用更大图集区域,远处较小灯光使用较小区域。如果灯光数超过区域数,屏幕空间最小的灯光将不投射阴影。

阴影类型​

所有投射阴影的灯光使用相同阴影类型,可全局控制阴影柔和度及对应性能开销。支持 PCF1、PCF3 和 PCF5。更多信息参见阴影。

最大灯光数量​

内部默认使用 8 位存储灯光索引,因此任意帧最多有 255 盏可见灯光。超过上限的灯光会被忽略,并在控制台警告。

从引擎 v2.22 开始,可通过 LightingParams 的 maxLights 提高上限。超过 255 时改用 16 位索引,存储三维网格的纹理大小翻倍。上限还受设备最大纹理尺寸限制。

// Assuming being in a script type
this.app.scene.lighting.maxLights = 4096;

编辑器设置中没有此选项,需在代码中设置。

性能注意事项​

  • 单元划分数量应尽量少,因为每帧将灯光填入网格时,更多划分会增加 CPU 开销。应按场景光照复杂度优化,理想状态是恰好有足够单元限制灯光重叠和每单元灯光数。
  • Max Lights Per Cell 应尽量小,以限制每帧更新的三维网格纹理大小。
  • 最大灯光数量应在场景允许的范围内尽量低,因为较大值有逐帧开销。
  • 使用簇光照的应用若在较旧移动设备上运行缓慢,可考虑全局关闭阴影或 cookie 等功能。

渲染调试网格​

为辅助簇光照调试和性能调整,可将输出图层的 ID 赋给 LightingParams 的 debugLayer,例如:

// Assuming being in a script type
this.app.scene.lighting.debugLayer = this.app.scene.layers.getLayerByName("World").id;

要停止渲染,将 debugLayer 属性设为 undefined:

// Assuming being in a script type
this.app.scene.lighting.debugLayer = undefined;