Height maps

Высотные карты в CesiumJS являются основным механизмом формирования рельефа на глобусе и в локальных сценах. Они определяют геометрию поверхности Земли или любой другой планеты через регулярную сетку значений высот, которые интерпретируются движком как отклонение от эллипсоида или геоида. Внутри движка высотные данные тесно связаны с системой тайлов, где каждый участок поверхности разбивается на иерархическую структуру плиток, соответствующих уровням детализации.

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

Ключевой принцип заключается в том, что:

  • каждая плитка terrain содержит регулярную сетку высот
  • геометрия строится через триангуляцию сетки
  • уровень детализации (LOD) определяет плотность сетки

При увеличении масштаба CesiumJS автоматически подгружает более детализированные тайлы, заменяя грубую сетку более плотной.

Геометрическая интерпретация heightmap

Внутренне высотная карта преобразуется в трёхмерные координаты с использованием эллипсоида WGS84. Каждая точка сетки задаётся координатами:

  • широта
  • долгота
  • высота

Функция преобразования выглядит концептуально следующим образом:

Cartesian3 = Ellipsoid.WGS84.cartographicToCartesian(
    new Cartographic(longitude, latitude, height)
);

Однако в случае terrain pipeline высота применяется уже после проекции координат тайла, что позволяет оптимизировать вычисления и использовать батчинг.

Форматы высотных данных

CesiumJS поддерживает несколько форматов terrain, каждый из которых по-разному кодирует heightmap.

Quantized-Mesh

Наиболее распространённый формат. Использует квантизацию высот и топологические соседства треугольников.

Особенности:

  • хранение нормалей и скалярных полей
  • сжатие через quantization (обычно 16-bit)
  • поддержка skirts для устранения щелей между тайлами

Структура включает:

  • vertex heights
  • triangle indices
  • edge stitching metadata

Heightmap Terrain

Более простой формат, где высоты представлены прямоугольной сеткой.

Используется в случаях:

  • кастомных terrain серверов
  • упрощённых моделей мира
  • процедурных поверхностей

Пример загрузки heightmap-тайлов:

const terrainProvider = new Cesium.HeightmapTerrainProvider({
    url: "https://server.example.com/terrain",
    width: 65,
    height: 65
});

const viewer = new Cesium.Viewer("cesiumContainer", {
    terrainProvider: terrainProvider
});

Иерархия LOD и subdivision

Система terrain в CesiumJS использует quadtree-структуру. Каждый тайл может быть рекурсивно разделён на четыре дочерних.

Основные принципы:

  • root tile покрывает всю планету
  • каждый уровень увеличивает детализацию в 4 раза
  • выбор LOD основан на screen-space error (SSE)

Формула оценки ошибки:

SSE ≈ geometricError * screenSpaceFactor

Где geometricError зависит от максимального отклонения высот внутри тайла.

При превышении порога SSE тайл subdivide-ится.

Встраивание heightmap в рендеринг сцены

После загрузки terrain данные проходят несколько стадий:

  1. декодирование бинарного массива высот
  2. построение grid mesh
  3. тесселяция в треугольники
  4. трансформация в world coordinates
  5. передача в WebGL pipeline

На уровне GPU terrain представлен как набор треугольников, где высота уже встроена в vertex position.

Skirts и устранение швов

Одной из ключевых проблем heightmap-рендеринга является появление щелей между тайлами разного уровня детализации.

CesiumJS решает это через skirts — дополнительные вертикальные полигоны по краям тайла.

Принцип:

  • дублируются крайние вершины
  • им задаётся искусственно заниженная высота
  • формируется «юбка», скрывающая разрывы

Это позволяет избежать необходимости сложного edge stitching в runtime.

Квантование высот

Для уменьшения объёма данных высоты часто хранятся в нормализованном виде.

Типичная схема:

  • uint16 диапазон: 0–65535
  • декодирование в метры через min/max bounds тайла

Формула восстановления:

height = minHeight + (quantizedValue / 65535) * (maxHeight - minHeight)

Это позволяет резко сократить сетевой трафик при передаче terrain.

Подключение кастомных heightmap источников

CesiumJS позволяет интегрировать собственные источники высот через TerrainProvider API.

Пример кастомного провайдера:

class CustomTerrainProvider {
    constructor() {
        this.tilingScheme = new Cesium.GeographicTilingScheme();
        this.errorEvent = new Cesium.Event();
        this.ready = true;
        this.hasWaterMask = false;
        this.hasVertexNormals = false;
    }

    requestTileGeometry(x, y, level) {
        const url = `/terrain/${level}/${x}/${y}.bin`;

        return Cesium.Resource.fetchArrayBuffer(url)
            .then(buffer => {
                return Cesium.HeightmapTessellator.computeVertices({
                    buffer: new Uint16Array(buffer),
                    width: 65,
                    height: 65,
                    skirtHeight: 2000
                });
            });
    }
}

Система геометрических ошибок

LOD-выбор основан на метрике ошибки, которая измеряет максимальное отклонение поверхности тайла от аппроксимации.

Каждый тайл хранит:

  • geometricError
  • boundingVolume
  • minimumHeight / maximumHeight

Bounding volume может быть:

  • sphere
  • box (oriented bounding box)

Чем выше error, тем раньше тайл будет subdivide-нут.

Интеграция с imagery layers

Heightmaps не существуют изолированно: они тесно связаны с текстурными слоями.

Порядок рендеринга:

  • terrain задаёт геометрию
  • imagery накладывает текстуры
  • shader объединяет оба слоя

UV-координаты вычисляются на основе географической проекции тайла.

Обработка больших данных рельефа

При работе с глобальными моделями применяются оптимизации:

  • lazy loading тайлов
  • caching в IndexedDB
  • компрессия quantized mesh
  • progressive refinement

CesiumJS поддерживает автоматическое управление памятью, выгружая невидимые тайлы.

Влияние проекции на heightmap

Используемая проекция (обычно WGS84 ellipsoid) влияет на распределение сетки.

Особенности:

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

Это требует адаптивного LOD для равномерного качества визуализации.

WebGL-представление terrain

На уровне GPU heightmap превращается в vertex buffer objects:

  • VBO хранит позиции
  • IBO хранит индексы треугольников
  • vertex shader применяет финальную трансформацию

Фрагмент шейдера концептуально:

vec3 positionWC = czm_ellipsoidScale * positionMC;
gl_Position = czm_modelViewProjection * vec4(positionWC, 1.0);

Высота уже встроена в positionMC.

Производительность и узкие места

Основные ограничения при работе с heightmaps:

  • bandwidth загрузки terrain tiles
  • CPU тесселяция
  • GPU fill rate при высокой детализации
  • память под кеш тайлов

Оптимизация достигается через:

  • уменьшение geometricError
  • использование более грубых уровней LOD
  • prefetch соседних тайлов
  • ограничение maxScreenSpaceError

Процедурные heightmaps

Помимо серверных данных, heightmap может быть сгенерирован процедурно.

Используется:

  • Perlin noise
  • simplex noise
  • fractal Brownian motion

Пример генерации:

function getHeight(x, y) {
    return noise(x * 0.01, y * 0.01) * 1200;
}

Такие данные интегрируются через custom terrain provider.

Согласование высот между тайлами

Проблема несоответствия границ возникает при разных LOD. CesiumJS решает её через:

  • skirts
  • vertex snapping
  • shared edge sampling

В некоторых форматах quantized mesh дополнительно хранит edge adjacency.

Использование heightmaps в аналитике

Heightmaps применяются не только для визуализации:

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

CesiumJS предоставляет доступ к высотным данным через sampling API:

viewer.scene.sampleHeight(
    new Cesium.Cartographic(longitude, latitude)
);