Пользовательские материалы

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

Архитектурно материал в CesiumJS представляет собой декларативное описание «fabric» (ткань), которое компилируется в GLSL-код фрагментного шейдера с набором унифицированных параметров (uniforms). Такой подход позволяет отделить описание внешнего вида от низкоуровневого WebGL-кода.


Система Material Fabric

Базовая модель материала в CesiumJS строится вокруг объекта Material, содержащего описание в формате fabric:

  • type — идентификатор материала
  • uniforms — параметры, передаваемые в шейдер
  • source — GLSL-фрагмент кода, определяющий czm_material

Структура czm_material является стандартной точкой взаимодействия с движком:

czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);

    material.diffuse = vec3(1.0, 0.0, 0.0);
    material.alpha = 1.0;

    return material;
}

czm_materialInput содержит данные о текущем фрагменте: UV-координаты, позицию, нормали и дополнительные вычисленные значения.


Регистрация пользовательского материала

Создание нового типа материала выполняется через регистрацию:

import * as Cesium from "cesium";

Cesium.Material.StripeType = "StripeMaterial";

Cesium.Material.StripeSource = `
czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);

    float stripe = step(0.5, fract(materialInput.st.s * 10.0));
    vec3 color = mix(vec3(1.0), vec3(0.0, 0.6, 1.0), stripe);

    material.diffuse = color;
    material.alpha = 1.0;

    return material;
}
`;

Cesium.Material._materialCache.addMaterial(Cesium.Material.StripeType, {
    fabric: {
        type: Cesium.Material.StripeType,
        uniforms: {
            repeat: 10.0
        },
        source: Cesium.Material.StripeSource
    },
    translucent: false
});

После регистрации материал становится доступен через Material.fromType.


Использование материала в примитивах

Материалы применяются через Appearance объектов:

const material = Cesium.Material.fromType("StripeMaterial", {
    repeat: 20.0
});

const appearance = new Cesium.PerInstanceColorAppearance({
    translucent: false
});

appearance.material = material;

Для геометрии:

const primitive = new Cesium.Primitive({
    geometryInstances: new Cesium.GeometryInstance({
        geometry: new Cesium.RectangleGeometry({
            rectangle: Cesium.Rectangle.fromDegrees(-100, 30, -90, 40)
        })
    }),
    appearance: new Cesium.EllipsoidSurfaceAppearance({
        material: material
    })
});

Procedural-материалы и работа с UV

Основной входной параметр большинства материалов — materialInput.st, где:

  • st.s — горизонтальная координата (U)
  • st.t — вертикальная координата (V)

Это позволяет строить процедурные текстуры без использования изображений:

float grid(vec2 st)
{
    vec2 grid = fract(st * 20.0);
    return step(0.95, grid.x) + step(0.95, grid.y);
}

Анимация через uniform-параметры времени

Cesium автоматически предоставляет ряд встроенных uniform-переменных:

  • czm_frameNumber
  • czm_frameTimeDelta
  • czm_time

Пример анимированного материала:

czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);

    float t = czm_frameNumber * 0.02;

    float wave = sin(materialInput.st.s * 10.0 + t);
    vec3 color = vec3(0.2, 0.6, 1.0) * (0.5 + 0.5 * wave);

    material.diffuse = color;
    material.alpha = 1.0;

    return material;
}

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


MaterialProperty и динамические материалы

Для сущностей (Entity API) используется MaterialProperty, обеспечивающий реактивное поведение материала во времени.

const entity = viewer.entities.add({
    rectangle: {
        coordinates: Cesium.Rectangle.fromDegrees(-120, 30, -100, 40),
        material: new Cesium.ColorMaterialProperty(
            Cesium.Color.BLUE.withAlpha(0.5)
        )
    }
});

Для пользовательской логики:

function PulseMaterialProperty() {
    this.definitionChanged = new Cesium.Event();
}

PulseMaterialProperty.prototype.getType = function () {
    return "StripeMaterial";
};

PulseMaterialProperty.prototype.getValue = function (time, result) {
    result = result || {};
    result.repeat = 10.0 + Math.sin(Date.now() * 0.001) * 5.0;
    return result;
};

PulseMaterialProperty.prototype.isConstant = false;

Такой подход позволяет связывать материалы с временем и внешними данными (например, потоками сенсоров или API).


Работа с текстурами в материалах

Материалы могут использовать текстуры через sampler2D:

uniform sampler2D image;

czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);

    vec4 color = texture2D(image, materialInput.st);

    material.diffuse = color.rgb;
    material.alpha = color.a;

    return material;
}

JavaScript-часть:

Cesium.Material.fromType("ImageMaterial", {
    image: "texture.png"
});

Полилинии и кастомные appearance

Для линий используется отдельный класс материалов через PolylineMaterialAppearance.

const polyline = new Cesium.Primitive({
    geometryInstances: new Cesium.GeometryInstance({
        geometry: new Cesium.PolylineGeometry({
            positions: Cesium.Cartesian3.fromDegreesArray([
                -75, 35,
                -125, 35
            ]),
            width: 5
        })
    }),
    appearance: new Cesium.PolylineMaterialAppearance({
        material: Cesium.Material.fromType("PolylineGlow", {
            glowPower: 0.3,
            color: Cesium.Color.CYAN
        })
    })
});

Кастомные полилинейные материалы используют те же принципы GLSL, но учитывают особенности геометрии ленты.


Структура GLSL материала

Любой материал в CesiumJS компилируется в следующую логическую структуру:

  • объявление uniforms
  • функция czm_getMaterial
  • вычисление diffuse
  • вычисление alpha
  • дополнительные параметры (emission, specular, etc.)

Пример полной структуры:

uniform float speed;

czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);

    float value = sin(materialInput.st.t * 20.0 + czm_time * speed);

    material.diffuse = vec3(value);
    material.alpha = 1.0;

    return material;
}

Передача пользовательских uniforms

Uniforms связываются с JavaScript-объектом:

const material = Cesium.Material.fromType("CustomMaterial", {
    speed: 2.0
});

Изменение значения uniforms в runtime приводит к немедленной перекомпиляции или обновлению шейдера в зависимости от типа материала.


Использование noise-функций

Процедурные материалы часто используют шумовые функции:

float noise(vec2 st)
{
    return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453);
}

Применение:

float n = noise(materialInput.st * 10.0);
material.diffuse = vec3(n);

Такие методы позволяют создавать облака, воду, дымовые и термические эффекты.


Материалы и освещение

Материалы могут учитывать освещение сцены через нормали:

  • materialInput.normalEC
  • materialInput.positionEC

Пример диффузного освещения:

vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float diffuse = max(dot(materialInput.normalEC, lightDir), 0.0);

material.diffuse *= diffuse;

Производительность и компиляция шейдеров

Материалы в CesiumJS компилируются в отдельные WebGL программы. Это означает:

  • каждый уникальный material.type может создать отдельный shader program
  • избыточное разнообразие материалов увеличивает нагрузку на GPU
  • динамические uniforms дешевле, чем пересборка шейдера
  • сложные procedural эффекты требуют балансировки между качеством и количеством операций

Оптимизация часто сводится к:

  • минимизации branching в GLSL
  • переиспользованию материалов
  • кэшированию fabric-конфигураций
  • сокращению количества уникальных appearance

Комбинирование материалов

Материалы могут комбинироваться через смешивание цветов:

material.diffuse = mix(colorA, colorB, materialInput.st.s);

или через альфа-композицию:

material.alpha = baseAlpha * textureAlpha;

Такая модель используется для наложения нескольких эффектов: градиенты, шум + текстура, анимация + маска.


Расширенные сценарии применения

Процедурные материалы применяются для:

  • визуализации геопространственных данных (тепловые карты, плотности)
  • динамических маршрутов и потоков
  • симуляции природных эффектов
  • стилизованного рендеринга карт
  • визуализации телеметрии в реальном времени

Каждый из этих сценариев опирается на одну и ту же архитектуру fabric + GLSL + uniforms, что делает систему масштабируемой и предсказуемой в поведении.