簇光照
从 PlayCanvas 引擎 v1.56 开始,默认启用簇光照。旧光照系统短期内仍保留,但将在未来的次要版本中弃用。
灯光可有效提升应用真实感,但实时灯光也会带来显著运行时开销,尤其是大量投射阴影的灯光。
降低开销的一种思路是限制影响单个网格的灯光数,通常为每个对象寻找并使用附近灯光。但这种策略有多个缺点:
- 每个对象可能使用不同的灯光集合,因此必须编译对应的着色器。
- 大对象必须拆分为小对象,这种策略才有效。
- 大量投影灯光可能耗尽着色器中供阴影贴图使用的纹理槽。
为解决这些问题,PlayCanvas 使用簇光照高效实现点光源和聚光灯。它将灯光信息存入纹理,让 GPU 便捷地只使用当前着色片元附近的灯光。优点包括:
- 增删场景灯光时无需重新编译着色器,因为着色器可处理多盏灯光。
- 场景可使用大量灯光及其阴影和 cookie,因为每个像素只计算附近灯光。
注意,平行光影响所有对象,因此不使用簇光照方案。
实现概览
簇光照的基本实现步骤如下:
-
使用所有相机的视锥体剔除灯光,确定当前帧可见的灯光列表。
-
在所有可见灯光的轴对齐包围范围上,覆盖一个世界空间三维网格。

-
每个网格单元保存与其相交的灯光索引。CPU 每帧更新这些信息,以查询影响任意位置的灯光列表。信息存入纹理,供 GPU 访问。
-
所有可见灯光的属性存入另一张纹理,供 GPU 访问。
-
阴影贴图和 cookie 纹理都渲染到图集中,而非独立纹理,使着色器可同时访问全部内容。
-
片元着色器计算光照时,使用片元世界位置定位三维网格单元,再计算其中保存的灯光。
编辑器选项
在编辑器设置的 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,四个子纹理中的三个分配给灯光。

-
Manual(手动):将图集划分为固定数量、可不同大小的子纹理。使用数值数组设置,每个数值表示水平和垂直方向的划分数量。示例见下一节。
配置手动图集划分
以数组 [2, 2] 为例:第一个数将图集划分为 2x2,共四个区域。后续数值继续划分这些区域。这里第二个数将其中一个区域再分为 2x2,即四个区域,最终共 7 个区域。

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

其他示例:
- [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;