v2.21.0

0xsha/CloudBrutev2.21.0Jul 21, 2026by mvaligursky

AI Summary

This release introduces significant enhancements for Gaussian Splatting rendering, including support for SPZ and KTX2 formats, alongside improvements to physics backends and XR interaction capabilities.

Key Highlights

  • Add SPZ gaussian splat format support and KTX2 cubemaps
  • Decouple physics into a backend abstraction with Ammo and Null implementations
  • Add XrManipulation script for two-hand XR interactions
  • Refactor resource handlers with a pluggable parser registry

Breaking Changes

  • Remove `MeshInstance` parameter pass flags
  • Rename gsplat script classes to match core `GSplat` casing

New Features

  • Instanced wide line renderer
  • Volumetric fog rendering to CameraFrame
  • Support for KHR_gaussian_splatting glTF extension
  • Add `withCredentials` option for asset loading
  • Add `Texture.copy` for texture-to-texture copies

Full Release Notes

## Breaking Changes
* [BREAKING] Remove `MeshInstance` parameter pass flags by @mvaligursky in #9071
* [BREAKING] Rename gsplat script classes to match core `GSplat` casing by @willeastcott in #9040

## Changes

* Add instanced wide line renderer by @mvaligursky in #9090
* Add volumetric fog rendering to `CameraFrame` by @mvaligursky in #9069
* Support KTX2 cubemaps by @MotivaCG in #9065
* Add support for `KHR_gaussian_splatting` glTF extension by @mvaligursky in #9015
* Add SPZ gaussian splat format support via external parser by @mvaligursky in #9018
* Re-add `Camera.beforePasses` extension point by @mvaligursky in #9073
* Decouple physics into a backend abstraction with Ammo and Null implementations by @willeastcott in #9043
* Move simulation registration onto the rigid body system by @willeastcott in #9044
* Flatten collision type implementations into a dispatch table by @willeastcott in #9045
* Add `XrManipulation` script: two-hand drag, rotate and scale for XR scenes by @willeastcott in #9030
* Upgrade `XrNavigation` teleport visuals to ballistic arc with ring indicator by @willeastcott in #9024
* Refactor XR scripts: dedupe within-file logic and align naming by @willeastcott in #9032
* Move XR scripts into `scripts/esm/xr` folder by @willeastcott in #9038
* Add Engine Scripts API reference for `scripts/esm` by @willeastcott in #9042
* Add pluggable parser registry to `ResourceHandler` and convert built-in handlers by @mvaligursky in #9001
* Convert `MaterialHandler`, audio, anim and template handlers to the parser registry by @mvaligursky in #9004 #9005 #9006
* Extract glTF extensions from the glb parser into separate modules by @mvaligursky in #9007 #9008
* Convert the obj model parser to an ES module in `scripts/esm/parsers` by @mvaligursky in #9019
* Add `withCredentials` option for asset loading by @mvaligursky in #8983
* Add `Texture.copy` for texture-to-texture copies by @mvaligursky in #8970
* Add `CircleGeometry` procedural geometry class by @mvaligursky in #9027
* Add clear/remove API to `Curve` and `CurveSet` by @mvaligursky in #8980
* Add unclamped scalar interpolation by @mvaligursky in #9087
* Add request/execute mesh-instance culling by @mvaligursky in #8978
* Allow `Texture#mipmaps` to be changed at runtime on WebGPU by @mvaligursky in #9003
* Use uniform buffers for view-level uniforms on WebGL2 by @mvaligursky in #8987
* Route view uniforms through dynamic buffers; remove view bind groups by @mvaligursky in #8967
* Make `StorageBuffer.read` public by @mvaligursky in #8986
* Add public `app` getter to `ResourceHandler`, mark `_app` protected by @mvaligursky in #8991
* Support pen input in touch sources by @d0rianb in #9013
* Skip WebXR startup probe when `xr-spatial-tracking` is disallowed by @mvaligursky in #8964
* Warn about unknown options passed to `addComponent` by @mvaligursky in #8995
* Load MSDF font atlases without mipmaps by @mvaligursky in #9002
* Refactor `OutlineRenderer` to use dedicated shader output by @mvaligursky in #9070
* Refactor light and directional-shadow culling into separate phases by @mvaligursky in #8961
* Rework shadow update-mode lifecycle; keep THISFRAME pending until rendered by @mvaligursky in #8962
* Build the frame graph before mesh and shadow-caster culling by @mvaligursky in #8965
* Allocate local light shadow maps before the frame graph is built by @mvaligursky in #8966
* Decouple `RenderPassForward` from `RenderAction` via `LayerRenderStep` by @mvaligursky in #8975
* Extract visibility culling into a `Culler` class by @mvaligursky in #8979
* Use `Debug.removed` for nonfunctional compatibility shims by @mvaligursky in #9082
* Recommend `EventHandle.off()` for event removal by @mvaligursky in #8974
* Exclude `@ignore` typedefs from generated API docs by @mvaligursky in #8989
* Add compute shader definition types to docs by @mvaligursky in #9067

## Fixes

* Fix crash when a gsplat octree asset is unloaded while still registered in the streaming world by @slimbuck in #9011
* Fix crash when a gsplat component detaches its asset while still registered in the streaming world by @slimbuck in #9016
* Fix crash when unloading SOG assets still referenced by the unified world by @slimbuck in #9035
* Fix gsplat loading progress getting permanently stuck after a cancelled mid-flight load by @slimbuck in #8998
* Rebuild bind groups when a bound texture's GPU resource is recreated by @slimbuck in #8982
* Fix SOG bundle parser continuing after asset unloads mid-load by @mvaligursky in #9037
* Fix SOG streaming re-load returning destroyed textures from loader cache by @mvaligursky in #9039
* Fix GPU device hang when picking gsplats on Windows/NVIDIA by @mvaligursky in #9036
* Re-copy work buffer for streamed gsplat placements and all consumers by @mvaligursky in #9021
* Fix `lodRangeMin`/`lodRangeMax` not applied from `GSplatComponent` init data by @willeastcott in #8968
* Fix gsplat work buffer not re-rendered for non-streaming splats on device restore by @mvaligursky in #8973
* Free SOG bundle archive memory after texture upload by @mvaligursky in #8972
* Fix markup text color shift by storing vertex palettes in linear space by @mvaligursky in #9034
* Fix skybox staying black after cubemap asset unload and reload by @mvaligursky in #9017
* Fix specular-glossiness diffuse texture not marked as sRGB in glb parser by @mvaligursky in #9009
* Fix glTF occlusion strength support by @mvaligursky in #9066
* Fix MSDF text anti-aliasing stability at small sizes by @willeastcott in #8990
* Free font textures when a font asset is unloaded by @mvaligursky in #8969
* Fix inverted buffer usage flags in `Mesh.clear()` by @willeastcott in #9023
* Fix `AnimationComponent` crash on tracks with events by @mvaligursky in #8985
* Fix `XrMenu` finger-poke clicks and textured button tint restore by @willeastcott in #9025
* Fix `XrNavigation` castRay plane coupling and stale teleport cache by @willeastcott in #9026
* Fix compound wiring for collision children that have their own rigidbody by @willeastcott in #9048
* Fix device constructor crash when canvas lacks `getBoundingClientRect` by @mvaligursky in #9000
* Fix deprecated API migration messages by @mvaligursky in #9079
* Fix deprecated gloss aliases by @mvaligursky in #9080
* Mark live API getters readonly by @kpal81xd in #8959

## Examples

* Add gaussian splat glass refraction example by @mvaligursky in #9020
* Add gaussian splat tree wind example with reusable GsplatTrees script by @mvaligursky in #9022
* Add reusable Water script with planar reflection and refraction, and a water example by @mvaligursky in #9029
* Add KTX2 cubemap example by @mvaligursky in #9068
* Modernize the graphics/layers example by @mvaligursky in #9088
* Fix `ProceduralSky` light drifting off the visible sun near sunset by @mvaligursky in #9028
* Fix HUD-only flags in shadow-soft and shadow-cascades examples by @mvaligursky in #9092 #9093
* Fix range/radius in shader toon and wobble examples by @mvaligursky in #9094

## Engine Dependencies

* Update all npm dependencies by @renovate[bot] in #9053

### Contributors

* @d0rianb
* @hellohejinyu
* @kpal81xd
* @MotivaCG
* @mvaligursky
* @slimbuck
* @willeastcott

## New Contributors

* @d0rianb made their first contribution in #9013
* @hellohejinyu made their first contribution in #9050
* @MotivaCG made their first contribution in #9065

**Full Changelog**: https://github.com/playcanvas/engine/compare/v2.20.6...v2.21.0