v2.22.0

CoreBunch/Instaticv2.22.0Sep 4, 2026by mvaligursky

AI Summary

A major feature release focusing on WebGPU performance enhancements, advanced rendering capabilities like flat shading and volumetric fog, and significant updates to the Gaussian Splatting and physics systems.

Key Highlights

  • Added WebGPU support for multisampled textures and independent blending for multiple render targets
  • Enhanced Gaussian Splatting with LOD modes, depth writing, and tonemapping options
  • Introduced `WireRenderer` for debug shapes and added physics `timeScale` controls
  • Added Camera projection offset for off-center projections

Breaking Changes

  • Store frustum planes in a typed array
  • Remove wire drawing methods from `AppBase`

New Features

  • Dual-source blending support
  • Camera projection offset for off-center projections
  • Volumetric fog support for clustered lights
  • Independent blending for multiple render targets
  • WireRenderer for debug shapes

Full Release Notes

## Breaking Changes
* [BREAKING] Store frustum planes in a typed array by @mvaligursky in #9196
* [BREAKING] Remove wire drawing methods from `AppBase` by @mvaligursky in #9289


## Changes

* Remove `Scene#forcePassThroughSpecular` compatibility flag by @mvaligursky in #9250
* Remove passive event listener detection and `platform.passiveEvents` by @willeastcott in #9252
* Remove unused `BatchManager#clone` method by @mvaligursky in #9106
* Add dual-source blending support by @mvaligursky in #9096
* Add `Camera.projectionOffset` for off-center (shift lens) projection by @mvaligursky in #9098
* Add `RenderTarget` origin option, deprecate `flipY` by @mvaligursky in #9101
* Expose Bayer 2/4 dither modes and add Bayer 16 dither mode by @mvaligursky in #9117
* Add shadow pass support for `ShaderMaterial` using `#include` by @mvaligursky in #9116
* Cull omni shadow casters against all six faces in a single pass by @mvaligursky in #9194
* Cull mesh instances by their bounding box instead of bounding sphere by @mvaligursky in #9197
* Add flat shading support by @mvaligursky in #9191
* Flat shade glTF primitives without vertex normals by @mvaligursky in #9193
* Add `shadowCascadeMask` to the render component by @mvaligursky in #9195
* Add `shadowCascadeMask` in batch by @AlexAPPi in #9104
* Configurable maximum number of clustered lights by @mvaligursky in #9188
* Add volumetric fog support for clustered omni and spot lights by @mvaligursky in #9137
* Support refraction without the metalness workflow by @mvaligursky in #9147
* Add independent blending for multiple render targets by @mvaligursky in #9140
* Add per-attachment clear colors for multiple render targets by @mvaligursky in #9142
* Add clear support for integer format color attachments by @mvaligursky in #9143
* Render the scene depth from the scene pass, replacing the depth prepass where possible by @mvaligursky in #9174
* Support writing the scene textures from the forward pass by @mvaligursky in #9166
* Add `SceneDepthReader` for reading a camera's scene depth to the CPU by @mvaligursky in #9274
* Add a scene depth mode to `CameraFrame` debug rendering by @mvaligursky in #9182
* Wire `alphaToCoverage` into the WebGPU render pipeline state by @mvaligursky in #9208
* Add `sample_mask` builtin support to WGSL fragment shaders by @mvaligursky in #9222
* Add `samples` option to `Texture` for multisampled textures on WebGPU by @mvaligursky in #9217
* Support multisampled texture bindings in WGSL reflection and bind groups by @SashaRX in #9167
* Support multisampled color buffers and custom resolves on `RenderTarget` by @mvaligursky in #9221
* Support multisampled depth buffers and depth resolve targets on `RenderTarget` by @mvaligursky in #9230
* Add `RenderTarget.depthResolveMode` controlling multisampled depth resolve by @mvaligursky in #9226
* Support blendable and R/RG float formats in `getRenderableHdrFormat` by @mvaligursky in #9164
* Expose `textureFloatBlendable` capability on all devices by @mvaligursky in #9160
* Support the `RG32F` texture format on WebGL by @mvaligursky in #9218
* Add readonly detection for the WebGPU subgroup-size-control feature by @mvaligursky in #9158
* Fix parallax mapping correctness and promote to public by @mvaligursky in #9207
* Add parallax occlusion mapping as an opt-in parallax mode by @mvaligursky in #9213
* Add soft self shadowing to parallax occlusion mapping by @mvaligursky in #9225
* Add `heightMapBase` to unify the parallax zero level across modes by @mvaligursky in #9245
* Add `WireRenderer` for wireframe debug shapes and a debug examples category by @mvaligursky in #9288
* Add `AppOptions.physicsWorld` for explicit physics backend selection by @willeastcott in #9266
* Add `timeScale` and public `step()` to `RigidBodyComponentSystem` by @willeastcott in #9300
* Wake constrained bodies when a joint constraint is created or destroyed by @willeastcott in #9177
* Add justified text support to text elements by @mvaligursky in #9185
* Allow typed arrays in geometry and mesh data APIs by @mvaligursky in #9121
* Add support for transform feedback separate buffer mode by @AlexAPPi in #9109
* Support multiple transform feedback buffers by @mvaligursky in #9131
* Add rotation attribute to the `ProceduralSky` script by @mvaligursky in #9150
* Detach element component graph nodes on destroy by @mvaligursky in #9183
* Release application references on destroy by @mvaligursky in #9190
* Destroy the asset registry with the application by @mvaligursky in #9189
* Relocate deprecated API shims so the ESM engine tree-shakes by @willeastcott in #9251
* Remove WebGL1-era POT checks from texture mipmap upload and `gpuSize` by @willeastcott in #9262
* Use `Geometry#calculateTangents` in procedural geometry subclasses by @willeastcott in #9253
* Stop the `Shader` constructor modifying the supplied definition by @willeastcott in #9281

## Gaussian Splatting

* Error-driven GSplat LOD selection with a derived fallback by @mvaligursky in #9233
* Add a LOD mode and per-splat detail falloff for streamed GSplats by @mvaligursky in #9267
* Gaussian splats write the scene depth by @mvaligursky in #9175
* Add `scene.gsplat.useTonemap` to opt GSplats out of tonemapping and exposure by @mvaligursky in #9229
* Use `shadowCasterPS` chunk for gsplat shadow output by @mvaligursky in #9119
* Fix unified CPU splat sort for non-uniformly scaled splats by @AriaLian in #9268
* Fix stale gsplat projector pipeline after a `dataFormat` swap by @mvaligursky in #9269
* Fix gsplat rendering on Firefox/Windows WebGPU by @mvaligursky in #9148
* Fix SOG parsers throwing when a load completes after app teardown by @mvaligursky in #9232
* Fix radix sort value buffer rotation with initial values by @slimbuck in #9276
* Fix blending disabled when generating SOG centers into an integer render target by @slimbuck in #9224

## Fixes

* Fix inverted normal mapping on WebGPU (derivative TBN) by @mvaligursky in #9099
* Fix MSDF coverage ramp exceeding field range at small sizes by @willeastcott in #9100
* Fix reported WebGPU sampled texture limit by @mvaligursky in #9103
* Discard out of range depth in VSM shadow casters by @mvaligursky in #9124
* Fix `textureBias` redefinition in tiled nine-slice shaders by @mvaligursky in #9129
* Fix the bright outline around MSDF text glyphs by @mvaligursky in #9139
* Fix vertex array object cache key collisions by @mvaligursky in #9127
* Fix instancing/draw-commands mismatch skipping indirect draws by @mvaligursky in #9159
* Fix phantom sampler binding for a trailing texture in WGSL reflection by @mvaligursky in #9198
* Fix `ScriptComponent` clone dropping scripts awaiting their script type by @mvaligursky in #9200 #9201 #9202 #9203 #9204
* Fix `AnimationComponent` leaking asset event handlers by @mvaligursky in #9141
* Fix component event leak when removing an enabled component by @mvaligursky in #9145
* Fix layer event leak when the scene layer composition is replaced by @mvaligursky in #9146
* Fix `XrMenu` rendering outside of XR sessions by @willeastcott in #9155
* Fix looping sounds that specify a start time and duration by @willeastcott in #9231
* Correct sound duration and playback timing by @mvaligursky in #9249
* Fix inverted entity name guard in `AnimComponentBinder` by @willeastcott in #9187
* Fix volumetric fog missing for one frame after a canvas resize by @mvaligursky in #9283
* Fix `'set:outline'` event reporting the text color by @willeastcott in #9260
* Handle `mapAsync` rejection when the WebGPU device is lost by @mvaligursky in #9115
* Fix `ComponentSystemRegistry.remove` leaving the system in the list by @willeastcott in #9280
* Settle the WebGL texture read when the device is destroyed by @mvaligursky in #9275
* Recalculate particle local bounds when the spawn volume changes by @mvaligursky in #9192
* Anchor the AppleWebKit 15.4 antialias workaround UA match by @willeastcott in #9265

## Examples

* Add VAT character crowd rendering scripts and example by @mvaligursky in #9123
* Add catapult siege demo example by @mvaligursky in #9130
* Add a parallax terrain example by @mvaligursky in #9223
* Support multiple transform feedback buffers and add a visual example by @mvaligursky in #9131
* Point the downtown example at a single full-scene streamed SOG bundle by @mvaligursky in #9290
* Make the examples MiniStats toggle drive example-owned instances by @mvaligursky in #9292
* Fix painter example render target orientation across APIs by @mvaligursky in #9108

## Engine Dependencies

* Update all npm dependencies (major) by @renovate[bot] in #9056

### Contributors

* @AlexAPPi
* @AriaLian
* @kpal81xd
* @mvaligursky
* @SashaRX
* @slimbuck
* @willeastcott

## New Contributors

* @SashaRX made their first contribution in #9167
* @AriaLian made their first contribution in #9268

**Full Changelog**: https://github.com/playcanvas/engine/compare/v2.21.4...v2.22.0