Пользовательские шейдеры

Роль шейдеров в графическом конвейере CesiumJS

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

Основные задачи шейдеров в CesiumJS:

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

CesiumJS предоставляет несколько уровней интеграции с шейдерами: от декларативных материалов до полного переопределения программ рендеринга.


Архитектура шейдерной системы

Шейдерный код в CesiumJS распределяется по нескольким слоям:

  • Material System — процедурные и параметрические материалы;
  • Appearance API — управление шейдерной программой примитивов;
  • ShaderSource — композиция GLSL-кода;
  • PostProcessStage — постобработка изображения сцены;
  • Custom shaders для 3D Tiles — стилизация и условный рендеринг тайлов.

Каждый слой решает свою задачу и может использоваться независимо или совместно с другими.


Material System и процедурные шейдеры

Материалы в CesiumJS представляют собой параметризованные шейдеры, описываемые через JavaScript-объекты. Они компилируются в GLSL автоматически.

Базовая структура материала:

const material = new Cesium.Material({
    fabric: {
        type: 'Color',
        uniforms: {
            color: new Cesium.Color(1.0, 0.0, 0.0, 1.0)
        }
    }
});

Fabric-модель

Fabric определяет:

  • тип материала;
  • uniforms (параметры);
  • GLSL-код (source или components).

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

const pulseMaterial = new Cesium.Material({
    fabric: {
        type: 'Pulse',
        uniforms: {
            time: 0.0,
            color: Cesium.Color.BLUE
        },
        source: `
            czm_material czm_getMaterial(czm_materialInput materialInput)
            {
                czm_material material = czm_getDefaultMaterial(materialInput);

                float intensity = abs(sin(materialInput.st.s * 10.0 + time));
                material.diffuse = color.rgb * intensity;
                material.alpha = intensity;

                return material;
            }
        `
    }
});

В данном случае GLSL-функция czm_getMaterial переопределяет поведение фрагментного шейдера.


Appearance API и низкоуровневое управление шейдерами

Appearance используется при работе с Primitive API и позволяет задавать собственные шейдеры напрямую.

Базовая структура:

const appearance = new Cesium.PerInstanceColorAppearance({
    fragmentShaderSource: `
        varying vec4 v_color;

        void main(void)
        {
            gl_FragColor = v_color;
        }
    `
});

Ключевые компоненты:

  • vertexShaderSource — логика вершинного шейдера;
  • fragmentShaderSource — логика фрагментного шейдера;
  • renderState — настройки WebGL (blending, depth test).

Пример полного кастомного appearance:

const customAppearance = new Cesium.Appearance({
    vertexShaderSource: `
        attribute vec3 position;
        void main() {
            gl_Position = czm_modelViewProjection * vec4(position, 1.0);
        }
    `,
    fragmentShaderSource: `
        void main() {
            gl_FragColor = vec4(0.2, 0.6, 1.0, 1.0);
        }
    `,
    renderState: {
        depthTest: {
            enabled: true
        },
        blending: Cesium.BlendingState.ALPHA_BLEND
    }
});

ShaderSource и композиция GLSL-кода

CesiumJS использует систему ShaderSource для сборки итогового шейдера из нескольких фрагментов.

ShaderSource позволяет:

  • инжектировать код в стандартный pipeline;
  • расширять встроенные функции;
  • переопределять части вычислений.

Пример расширения:

const shaderSource = new Cesium.ShaderSource({
    defines: ['CUSTOM_LIGHTING'],
    sources: [
        `
        vec3 customLight(vec3 position) {
            return normalize(position) * 0.5 + 0.5;
        }
        `
    ]
});

Эта система используется внутри материалов и appearance, обеспечивая модульность GLSL-кода.


Работа с вершинными шейдерами

Вершинный шейдер отвечает за трансформацию геометрии:

  • локальные координаты → мировые;
  • мировые → камерные;
  • камерные → clip space.

Пример кастомной деформации:

const vertexShader = `
    attribute vec3 position;
    uniform float amplitude;

    void main()
    {
        vec3 displaced = position;
        displaced.z += sin(position.x * 10.0) * amplitude;

        gl_Position = czm_modelViewProjection * vec4(displaced, 1.0);
    }
`;

Типовые задачи вершинных шейдеров:

  • волновые деформации поверхностей;
  • процедурная анимация;
  • смещение вершин по высоте;
  • визуализация данных (height maps, heatmaps).

Фрагментные шейдеры и управление пиксельной логикой

Фрагментный шейдер определяет итоговый цвет пикселя.

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

  • выбор цвета;
  • альфа-канал;
  • освещение;
  • процедурные текстуры;
  • шумовые функции.

Пример градиентного шейдера:

const fragmentShader = `
    varying vec2 v_st;

    void main()
    {
        float t = v_st.t;
        gl_FragColor = vec4(t, 0.3, 1.0 - t, 1.0);
    }
`;

Часто используется система встроенных функций Cesium:

  • czm_materialInput
  • czm_getDefaultMaterial
  • czm_writeLogDepth
  • czm_modelViewProjection

Интеграция шейдеров с Primitive API

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

Пример:

const geometry = new Cesium.BoxGeometry({
    vertexFormat: Cesium.VertexFormat.POSITION_ONLY
});

const instance = new Cesium.GeometryInstance({
    geometry: geometry,
    modelMatrix: Cesium.Matrix4.IDENTITY
});

const primitive = new Cesium.Primitive({
    geometryInstances: instance,
    appearance: new Cesium.PerInstanceColorAppearance({
        flat: true
    })
});

Appearance здесь определяет GLSL-поведение для всей геометрии.


Шейдеры и 3D Tiles Styling

3D Tiles используют не только шейдеры, но и declarative styling, который транслируется в GPU-логику.

Пример стиля:

tileset.style = new Cesium.Cesium3DTileStyle({
    color: {
        conditions: [
            ['${height} > 1000', 'color("red")'],
            ['true', 'color("blue")']
        ]
    }
});

Под капотом стиль преобразуется в GLSL-выражения, влияющие на фрагментный шейдер.


Постобработка сцены через PostProcessStage

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

Пример эффекта инверсии:

const invertStage = new Cesium.PostProcessStage({
    fragmentShader: `
        uniform sampler2D colorTexture;

        void main()
        {
            vec4 color = texture2D(colorTexture, v_textureCoordinates);
            gl_FragColor = vec4(1.0 - color.rgb, 1.0);
        }
    `
});

Типовые эффекты:

  • bloom;
  • blur;
  • edge detection;
  • color grading;
  • depth-based effects.

Передача данных в шейдеры (Uniforms)

Uniform-переменные используются для передачи данных из JavaScript в GLSL.

Пример:

material.uniforms.time = performance.now() * 0.001;

В GLSL:

uniform float time;

Типы данных:

  • float / int;
  • vec2 / vec3 / vec4;
  • matrices;
  • textures (sampler2D).

Текстуры и sampler2D в CesiumJS

Текстуры являются ключевым элементом визуализации.

Пример использования:

uniform sampler2D image;

void main()
{
    vec4 texColor = texture2D(image, v_st);
    gl_FragColor = texColor;
}

Cesium автоматически управляет:

  • загрузкой изображений;
  • кэшированием;
  • трансформацией координат текстур.

Освещение и встроенные функции czm_

Cesium предоставляет набор встроенных GLSL-функций с префиксом czm_.

Основные:

  • czm_getLambertDiffuse;
  • czm_phongSpecular;
  • czm_computePosition;
  • czm_eyeToWindowCoordinates.

Пример использования освещения:

vec3 normal = normalize(v_normal);
vec3 light = czm_sunDirectionWC;
float diffuse = max(dot(normal, light), 0.0);

Оптимизация пользовательских шейдеров

Производительность критична в WebGL-среде.

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

  • минимизация условных операторов в fragment shader;
  • использование lowp/mediump precision;
  • уменьшение числа texture lookups;
  • избегание динамических циклов;
  • кэширование вычислений в vertex shader.

Типовые архитектурные паттерны кастомных шейдеров

Используемые подходы:

  • Procedural patterning — генерация текстур и поверхностей без изображений;
  • Data-driven shading — визуализация данных через цвета и высоты;
  • Hybrid material pipeline — сочетание Cesium Material и Appearance;
  • Post-process composition — каскад эффектов через несколько стадий.

Ошибки и ограничения GLSL в CesiumJS

Частые ограничения:

  • отсутствие поддержки WebGL2 фич в некоторых контекстах;
  • лимит uniforms;
  • precision issues на мобильных GPU;
  • ограничения глубины стека функций GLSL;
  • необходимость совместимости с czm pipeline.

Шейдеры должны учитывать интеграцию с Cesium render loop, иначе возможны артефакты и несогласованность кадров.