跳到主要内容

阴影

阴影让场景对象具有落地感,传达深度和空间关系,同时增强真实感和视觉品质。

PlayCanvas 通过阴影映射技术渲染实时动态阴影。所有灯光类型都支持,且适用于从桌面到移动端的所有平台。本页介绍如何启用阴影、选择投射和接收对象、选择阴影类型及调整质量。

启用阴影​

PlayCanvas 默认禁用灯光投射阴影,需要显式启用。先确定哪些灯光应投影,每盏灯都提供 Cast Shadows(投射阴影)选项。再选择哪些图形对象投射和接收阴影:渲染与模型组件默认都投射并接收,可逐实体切换。Gsplat 组件也能投射阴影,但不能接收,且默认禁用投影。

// Enable shadow casting on a light
entity.light.castShadows = true;

// Mesh entities cast & receive shadows by default (render or model component);
// toggle per entity as needed
meshEntity.render.castShadows = true;
meshEntity.render.receiveShadows = true;

// Gsplat entities can cast shadows too (off by default)
splatEntity.gsplat.castShadows = true;

阴影类型​

阴影滤波技术按灯光选择,在边缘柔和度、质量和性能之间权衡。PlayCanvas 提供三种技术:

备注

默认启用簇光照时,逐灯光阴影类型仅适用于平行光。聚光灯和点光源共享一个在场景光照设置中指定的全局阴影类型,且只能使用 PCF。

PCF(百分比渐近滤波)​

阴影从暗到亮的柔和过渡区域称为半影。默认的 PCF 在阴影贴图局部读取多个样本并求平均,以固定程度柔化边缘。

硬阴影与软阴影

滤波核尺寸决定权衡:1×1 边缘最硬,3×3 为默认,5×5 最柔和。更大的核采样更多纹素,GPU 开销也更高。

将阴影类型设为某个 PCF 变体:

// SHADOW_PCF1_32F | SHADOW_PCF3_32F (default) | SHADOW_PCF5_32F
entity.light.shadowType = SHADOW_PCF5_32F;

VSM(方差阴影贴图)​

方差阴影贴图存储可预先模糊的深度统计信息,产生平滑软边,适合平行光阴影等大范围区域。提供 16 位和更高精度的 32 位变体,某些场景中可能产生漏光伪影。

备注

VSM 仅适用于平行光和非簇光照的聚光灯。

将阴影类型设为 VSM 并调整:

entity.light.shadowType = SHADOW_VSM_16F;

// Optional tuning
entity.light.vsmBlurSize = 11; // blur kernel size, 1-25
entity.light.vsmBlurMode = BLUR_GAUSSIAN; // or BLUR_BOX (cheaper)
entity.light.vsmBias = 0.0025; // reduces shadow acne, 0-1

PCSS(百分比渐近软阴影)​

PCF 的软边宽度固定。真实阴影在两个对象接触处清晰,投射者离接收表面越远就越柔和。PCSS 根据两者距离改变半影宽度,重现这种接触硬化行为。

使用 PCSS 的软阴影

备注

PCSS 仅适用于平行光。

PCSS 还要求设备支持向浮点纹理渲染并进行线性滤波。现代桌面 GPU 普遍支持,但并非所有设备都支持,特别是旧款或低端移动设备。不支持时灯光自动回退到 PCF,因此可以安全启用。

将阴影类型设为 PCSS 并细调:

entity.light.shadowType = SHADOW_PCSS_32F;

// Optional fine-tuning
entity.light.penumbraSize = 2; // overall penumbra size (softness)
entity.light.penumbraFalloff = 1; // how fast softness grows with distance, >= 1
entity.light.shadowSamples = 16; // filter samples; higher = smoother, costlier
entity.light.shadowBlockerSamples = 16; // 0 disables contact hardening

调整阴影​

PlayCanvas 的阴影映射只有有限分辨率,因此可能需要调整参数以改善外观。以下属性均可通过编辑器灯光组件界面或代码(entity.light.*)设置。

阴影分辨率​

每盏灯通过阴影贴图投影。分辨率(light.shadowResolution)范围为 16x16 到 4096x4096,也可在组件界面设置。分辨率越高,阴影越清晰,但渲染更昂贵,应平衡性能和质量。

阴影距离​

阴影距离(light.shadowDistance)表示从视点起、平行光阴影不再渲染的距离。值越小,阴影越清晰;但视点移动时,读者可能看到阴影突然出现。应根据玩家可见距离和实际观感权衡。

阴影级联​

平行光阴影覆盖大范围时,经常出现近相机阴影分辨率不足的锯齿。若只用一张贴图改善,需要不切实际的高分辨率。

阴影级联沿视线方向划分相机视锥体,每段使用独立阴影贴图。近处对象使用一张,远处使用另一张,还可在中间增加贴图。

级联数量影响性能,因为每个投射阴影的网格可能需要渲染到多张贴图。

级联数(light.numCascades)表示视锥体分段数,可为 1、2、3 或 4。默认 1 表示单张阴影贴图。

单个阴影级联的截图:

一个级联

四个阴影级联的截图:

四个级联

light.cascadeDistribution 控制各级联对相机视锥体的划分分布,取值 0 到 1。0 为线性分布,1 为对数分布。视觉上,较高值将更多分辨率分配到前景,较低值分配到更远对象。

阴影边界​

默认平行光为各相机视图拟合阴影贴图,将分辨率分配到相机前方的阴影距离内。如果仅在小部分场景需要阴影,例如烘焙光照场景中的角色周围,可改设阴影边界(light.shadowBounds):这是包围投射者的世界空间包围盒。灯光按自身视角将单张贴图拟合到边界,让所有分辨率集中到需要阴影处。贴图为所有相机只渲染一次,阴影不受相机距离限制。

// shadows of the casters in a 10 x 4 x 10 area around the origin
entity.light.shadowBounds = new BoundingBox(new Vec3(0, 2, 0), new Vec3(5, 2, 5));

边界只需包含投射者,无需包含接收者。阴影沿灯光方向延伸,因此沿该方向位于边界之后的接收者,例如角色下方地面,仍接收边界内对象的阴影。灯光与边界之间的投射者也会投影。从灯光看位于边界侧面,或沿光方向位于边界之后的投射者则不会。

灯光保存边界副本,因此更改时需修改包围盒后重新赋值。例如要保持移动角色阴影清晰,每帧随角色移动边界:

const bounds = new BoundingBox(new Vec3(), new Vec3(1, 1.5, 1));

app.on('update', () => {
bounds.center.copy(character.getPosition());
entity.light.shadowBounds = bounds;
});

设置边界期间,灯光不使用 numCascades、shadowDistance、cascadeBlend 和 cascadeDistribution。将边界设为 null 可恢复按相机拟合。阴影按 light.shadowUpdateMode 更新,边界变化时也会重新渲染,因此 SHADOWUPDATE_NONE 下静态投射者的阴影只渲染一次,之后仅在边界移动时更新。

阴影边界仅适用于平行光,支持所有阴影类型。编辑器没有对应设置,需通过脚本设置。

引擎的阴影边界示例比较按边界拟合与按相机拟合的效果:

阴影边界

阴影强度​

阴影强度(light.shadowIntensity)为 1 时表示完整强度,为 0 时表示没有阴影。

阴影强度

修复阴影伪影​

阴影映射容易产生难看的渲染伪影,以下属性有助于消除它们。

阴影偏移​

若不该出现阴影的地方有条带或斑点,可调整阴影偏移(light.shadowBias)解决。

法线偏移​

“阴影痤疮”是常见问题,阴影偏移可有效缓解,但通常会引入一定程度的“彼得潘”现象,即阴影让对象看起来悬空。

法线偏移(light.normalOffsetBias)可解决这一问题。除深度偏移外,还对阴影贴图查询所用 UV 做微调,将片元位置沿几何法线偏移,从而同时避免阴影痤疮和阴影脱离。该技术比仅使用固定阴影偏移的结果好得多。

性能注意事项​

启用阴影会影响性能:

  • 每盏投影的平行光或聚光灯,每帧都需将场景渲染到阴影贴图一次。点光源更昂贵,每盏需渲染六次,保存为六面的立方体贴图。这会同时增加 CPU 和 GPU 负载。
  • 更高分辨率让阴影更清晰,但 GPU 必须填充更多像素,可能影响帧率。
  • 阴影类型影响开销:较大 PCF 核、VSM 模糊,尤其每像素采样多次的 PCSS,都比单次采样硬阴影更耗 GPU。
  • 平行光每增加一个阴影级联,可能要求投射者渲染到更多贴图,从而增加开销。
  • 默认各相机为自身视图渲染平行光阴影;具有阴影边界的灯光则为所有相机只渲染一张。
  • 环境静态部分产生的阴影,可考虑烘焙到光照贴图。