Скриншоты сцены

В CesiumJS визуализация сцены опирается на WebGL-рендеринг, где каждый кадр представляет собой результат последовательного выполнения этапов обновления состояния сцены и отрисовки примитивов. Ключевую роль играет объект Viewer, инкапсулирующий Scene, Camera, коллекции примитивов и слои тайлов.

Жизненный цикл кадра включает:

  • обновление состояния сцены (scene.update)
  • вычисление матрицы камеры (camera.update)
  • отрисовку (scene.render)

Фактическое изображение, доступное для захвата, существует только в момент завершения этапа рендеринга, когда WebGL-контекст содержит финальный буфер цвета.


Подготовка сцены к захвату изображения

Захват скриншота требует стабильного состояния кадра. В динамических сценах (анимации, потоковые данные, морфинг террейна) важным становится контроль момента фиксации.

Основные условия корректного захвата:

  • завершённый рендер кадра
  • отсутствие незавершённых асинхронных текстур
  • стабилизированная камера
  • отключённые переходные анимации (если требуется статический кадр)

В CesiumJS рендер может происходить непрерывно либо по запросу. В режиме requestRenderMode сцена обновляется только при изменениях состояния, что уменьшает риск захвата промежуточного кадра.

const viewer = new Cesium.Viewer("container", {
    requestRenderMode: true,
    maximumRenderTimeChange: Infinity
});

Захват изображения через WebGL canvas

Основной способ получения скриншота основан на использовании HTML canvas, связанного с WebGL-контекстом:

const canvas = viewer.scene.canvas;
const image = canvas.toDataURL("image/png");

Этот метод извлекает содержимое буфера цвета и кодирует его в формат Base64.

Однако корректность результата зависит от параметров контекста WebGL. При стандартной инициализации браузер может не сохранять буфер кадров после рендера.


Настройка сохранения буфера кадра

Для обеспечения стабильного захвата используется параметр preserveDrawingBuffer:

const viewer = new Cesium.Viewer("container", {
    contextOptions: {
        webgl: {
            preserveDrawingBuffer: true
        }
    }
});

Этот флаг заставляет WebGL сохранять содержимое буфера после завершения рендера, что делает возможным извлечение изображения в любой момент времени.

Недостатком является снижение производительности из-за отключения оптимизаций GPU.


Принудительный рендер перед захватом

При использовании режима requestRenderMode кадр может отсутствовать в буфере до явного вызова отрисовки:

viewer.render();
const image = viewer.scene.canvas.toDataURL("image/png");

Внутренне это инициирует выполнение scene.render(), что гарантирует актуальность изображения.


Использование событий рендера для фиксации кадра

Наиболее точный способ синхронизации захвата связан с событием завершения рендеринга:

  • postRender — вызывается после отрисовки сцены
  • preRender — перед отрисовкой

Пример фиксации изображения после завершения кадра:

viewer.scene.postRender.addEventListener(function () {
    const canvas = viewer.scene.canvas;
    const image = canvas.toDataURL("image/png");
});

Этот подход обеспечивает захват уже полностью сформированного изображения, исключая частично отрисованные состояния.


Масштабирование разрешения скриншота

Стандартный canvas соответствует размеру экрана, однако часто требуется увеличение детализации изображения. Это достигается через временное изменение devicePixelRatio или использование масштабирования canvas.

Подход через увеличение размера сцены:

viewer.resolutionScale = 2.0;
viewer.render();
const image = viewer.scene.canvas.toDataURL("image/png");
viewer.resolutionScale = 1.0;

Увеличение разрешения приводит к более плотной отрисовке сцены, что улучшает качество итогового изображения, особенно при картографических данных и текстурах высокого разрешения.


Экспорт изображения в файл

Полученная строка Base64 может быть преобразована в файл:

function downloadImage(dataUrl) {
    const link = document.createElement("a");
    link.href = dataUrl;
    link.download = "scene.png";
    link.click();
}

Альтернативный подход использует Blob для более эффективной работы с памятью:

const canvas = viewer.scene.canvas;

canvas.toBlob(function (blob) {
    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = "scene.png";
    link.click();
});

Особенности WebGL-контекста и ограничения

При работе с CesiumJS возникают ограничения, связанные с безопасностью браузера и особенностями WebGL:

Tainted canvas

Если сцена использует ресурсы с других доменов без CORS-заголовков, canvas становится «загрязнённым», и методы toDataURL и toBlob блокируются.

Источники риска:

  • тайлы из внешних серверов без CORS
  • текстуры с изображениями без разрешений
  • динамические шейдеры с внешними ресурсами

Ограничения preserveDrawingBuffer

Флаг preserveDrawingBuffer:

  • снижает производительность
  • увеличивает потребление памяти GPU
  • может быть отключён на некоторых мобильных устройствах

Снимки в анимационных сценах

В динамических сценах (перемещение камеры, анимация времени суток, движение объектов) момент захвата должен быть синхронизирован с завершением кадра.

Часто используется комбинация:

  • остановка анимации (clock.shouldAnimate = false)
  • фиксация камеры
  • ожидание postRender
  • захват canvas

Это позволяет получить кадр, соответствующий конкретному состоянию временной шкалы сцены.


Захват высокого разрешения через offscreen рендер

В сложных сценариях применяется рендеринг вне экрана с использованием OffscreenCanvas или headless-браузеров (например, Puppeteer). CesiumJS может быть инициализирован в среде без DOM, если доступен WebGL-контекст.

Принцип работы:

  • создание виртуального canvas
  • инициализация Viewer
  • принудительный рендер сцены
  • экспорт буфера

Этот подход применяется для генерации картографических изображений на сервере.


Синхронизация с потоковыми данными

При использовании динамических источников (например, CZML или 3D Tiles) важно учитывать асинхронную подгрузку данных. Скриншот, сделанный до завершения загрузки плиток, может содержать пустые области.

Типичный контроль включает:

  • ожидание события tileset.readyPromise
  • проверку состояния scene.globe.tilesLoaded
  • дополнительный рендер кадра после загрузки

Работа с несколькими слоями сцены

CesiumJS объединяет множество визуальных слоёв:

  • imagery layers
  • terrain provider
  • primitives
  • entities
  • post-processing stages

Скриншот фиксирует итоговый композит всех слоёв, поэтому любые изменения визуального состояния сцены должны быть завершены до момента захвата.

Особое внимание требуется при использовании постобработки (bloom, depth of field), так как она применяется после основного рендера и может не попасть в захват без корректной синхронизации с postRender.


Контроль консистентности кадра

Для получения детерминированного изображения используется стратегия фиксации состояния сцены:

  • отключение интерактивного ввода камеры
  • блокировка анимации времени
  • ожидание завершения всех промисов загрузки
  • принудительный вызов viewer.render()

Такой подход обеспечивает стабильный визуальный результат независимо от внешних событий обновления сцены.