What happened?
CloudCollection.prototype.update → createDrawCommands (packages/engine/Source/Scene/CloudCollection.js) pushes a Pass.TRANSLUCENT DrawCommand for every cloud without ever assigning command.boundingVolume:
https://github.com/CesiumGS/cesium/blob/main/packages/engine/Source/Scene/CloudCollection.js
function createDrawCommands(cloudCollection, frameState) {
const that = cloudCollection;
const pass = frameState.passes;
const uniforms = that._uniforms;
const commandList = frameState.commandList;
if (pass.render) {
const colorList = that._colorCommands;
const va = that._vaf.va;
const vaLength = va.length;
colorList.length = vaLength;
for (let i = 0; i < vaLength; i++) {
let command = colorList[i];
if (!defined(command)) {
command = colorList[i] = new DrawCommand();
}
command.pass = Pass.TRANSLUCENT;
command.owner = cloudCollection;
command.uniformMap = uniforms;
command.count = va[i].indicesCount;
command.vertexArray = va[i].va;
command.shaderProgram = that._sp;
command.renderState = that._rs;
if (that._instanced) {
command.count = 6;
command.instanceCount = that._clouds.length;
}
commandList.push(command);
// <-- command.boundingVolume is never set here
}
}
}
Compare to BillboardCollection.update and PointPrimitiveCollection.update, which both explicitly set command.boundingVolume = boundingVolume; in the equivalent loop before pushing to commandList.
Why it's usually invisible: when the scene uses GPU-based Order Independent Translucency (OIT — the default on desktop browsers with WebGL2/float-texture support), translucent commands are drawn without ever being distance-sorted, so command.boundingVolume is never read and the missing assignment has no effect.
On a device/context that falls back to the CPU depth-sort path (no OIT support — in practice this means mobile Safari / Chrome-iOS, since Chrome-iOS is WebKit under the hood), Scene's executeTranslucentCommandsBackToFront/FrontToBack sorts frameState.commandList's translucent commands with a comparator that reads command.boundingVolume.distanceSquaredTo(position) on every command — and throws on the cloud command, since boundingVolume is undefined.
Expected behavior: CloudCollection's translucent commands should be safely sortable like any other translucent primitive's commands, regardless of which translucency path the device uses.
Suggested fix: set command.boundingVolume in createDrawCommands, the same way BillboardCollection/PointPrimitiveCollection already do — presumably derived from each cloud's position + maximumSize/scale (a bounding sphere per cloud, or per-batch if commands are batched).
Reproduction steps
- Add a
CloudCollection with at least one CumulusCloud to a scene.
- View it on a device/browser that doesn't use Cesium's OIT path (e.g. a real iPhone, Safari or Chrome — desktop-Chromium device emulation does not reproduce this, since emulation doesn't change which translucency path Cesium actually picks).
- The very first frame the cloud is visible, rendering throws and (with default
Viewer settings) the render loop stops permanently:
TypeError: undefined is not an object (evaluating 'e.boundingVolume.distanceSquaredTo')
at backToFront (Scene.js, inside executeTranslucentCommandsBackToFront)
at mergeSort (mergeSort.js)
...
at executeTranslucentCommandsBackToFront
at executeCommandsInViewport
at updateAndExecuteCommands
at Scene.prototype.render
Sandcastle example
Not provided — this was found via source-reading rather than an isolated live repro. The missing assignment is visible directly in the linked source, and the failure mode only manifests on non-OIT devices (real mobile hardware), which Sandcastle's usual desktop-browser testing wouldn't catch either.
Environment
Browser: Chrome on iOS (CriOS/WebKit)
CesiumJS Version: 1.144.0 (bug present verbatim on main as of 2026-08-23, see linked source above)
Operating System: iOS (iPhone)
Not reproducible on: desktop Chrome, including with device-metrics/UA emulation of an iPhone (Chromium's OIT path is unaffected by that emulation).
AI acknowledgment
What happened?
CloudCollection.prototype.update→createDrawCommands(packages/engine/Source/Scene/CloudCollection.js) pushes aPass.TRANSLUCENTDrawCommandfor every cloud without ever assigningcommand.boundingVolume:https://github.com/CesiumGS/cesium/blob/main/packages/engine/Source/Scene/CloudCollection.js
Compare to
BillboardCollection.updateandPointPrimitiveCollection.update, which both explicitly setcommand.boundingVolume = boundingVolume;in the equivalent loop before pushing tocommandList.Why it's usually invisible: when the scene uses GPU-based Order Independent Translucency (OIT — the default on desktop browsers with WebGL2/float-texture support), translucent commands are drawn without ever being distance-sorted, so
command.boundingVolumeis never read and the missing assignment has no effect.On a device/context that falls back to the CPU depth-sort path (no OIT support — in practice this means mobile Safari / Chrome-iOS, since Chrome-iOS is WebKit under the hood),
Scene'sexecuteTranslucentCommandsBackToFront/FrontToBacksortsframeState.commandList's translucent commands with a comparator that readscommand.boundingVolume.distanceSquaredTo(position)on every command — and throws on the cloud command, sinceboundingVolumeisundefined.Expected behavior:
CloudCollection's translucent commands should be safely sortable like any other translucent primitive's commands, regardless of which translucency path the device uses.Suggested fix: set
command.boundingVolumeincreateDrawCommands, the same wayBillboardCollection/PointPrimitiveCollectionalready do — presumably derived from each cloud's position +maximumSize/scale (a bounding sphere per cloud, or per-batch if commands are batched).Reproduction steps
CloudCollectionwith at least oneCumulusCloudto a scene.Viewersettings) the render loop stops permanently:Sandcastle example
Not provided — this was found via source-reading rather than an isolated live repro. The missing assignment is visible directly in the linked source, and the failure mode only manifests on non-OIT devices (real mobile hardware), which Sandcastle's usual desktop-browser testing wouldn't catch either.
Environment
Browser: Chrome on iOS (CriOS/WebKit)
CesiumJS Version: 1.144.0 (bug present verbatim on
mainas of 2026-08-23, see linked source above)Operating System: iOS (iPhone)
Not reproducible on: desktop Chrome, including with device-metrics/UA emulation of an iPhone (Chromium's OIT path is unaffected by that emulation).
AI acknowledgment