playcanvas / playcanvas/engine
ShaderMaterial: support omni light and spot VSM/PCSS shadow casting
@mvaligursky is already working on this.
Since Jul 24, 2026.
- Dominant language
- JavaScript
- Stars
- 16.8k
- Forks
- 2k
- Avg merge
- 4h 32m
- Merged PRs (30d)
- 222
Description
Follow-up to #6835 and #9116.
The shadowCasterPS chunk added in #9116 lets a custom shader (ShaderMaterial) cast shadows via #include "shadowCasterPS" and getShadowOutput(). The initial implementation covers:
- Directional lights — all shadow types (PCF, VSM, PCSS)
- Spot lights — PCF only
Not yet supported:
- Omni lights — no shadow types
- Spot lights — VSM and PCSS
Why these were skipped
These cases don't use rasterized perspective depth. They need distance-based depth, min(distance(view_position, vPositionW) / light_radius, 0.99999), which requires:
- a world-space position varying supplied by the user's vertex shader (there is no fixed naming convention to rely on for custom shaders),
- the
view_positionandlight_radiusuniforms, - for the non-VSM/PCSS path, writing the computed depth to
gl_FragDepth.
The gl_FragDepth requirement is the main API design problem: in WGSL, output.fragDepth lives on the main function's FragmentOutput struct and cannot be written from a chunk helper function, so the clean no-argument getShadowOutput() API used for the perspective-depth cases does not extend to it symmetrically. Spot PCSS additionally needs linearized depth.
Possible approaches
- A statement-style include used inside
main()(likelitUserMainEndPSis used inlitShadowMain), which writesgl_FragColor/gl_FragDepth(GLSL) oroutput.color/output.fragDepth(WGSL), relying on the engine convention that the WGSL output variable is namedoutput. - Or an explicit
getShadowDepth(worldPos)helper that returns the depth, which the user assigns tooutput.fragDepth/gl_FragDepththemselves, gated behind a chunk-provided define.
Either way the user's vertex shader needs to provide the world position.
See the existing lit implementation for reference: src/scene/shader-lib/glsl/chunks/lit/frag/pass-shadow/litShadowMain.js (and the WGSL equivalent).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.