Fragment шейдеры

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

Фрагментный шейдер работает на уровне пикселя и получает интерполированные данные от вершинного шейдера. В контексте CesiumJS он тесно связан с системой материалов, физически корректным рендерингом (PBR) и глобальными эффектами среды, такими как туман, атмосфера и глубина.


Графический конвейер WebGL, на котором основан CesiumJS, можно упростить до следующих этапов:

  1. Подготовка вершин (vertex processing)
  2. Растеризация примитивов
  3. Выполнение фрагментного шейдера
  4. Тест глубины и смешивание

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

В CesiumJS этот этап дополнительно обогащается системными uniform-переменными, начинающимися с префикса czm_, которые предоставляют доступ к состоянию сцены.


Особенности GLSL в CesiumJS

CesiumJS использует GLSL ES 1.0 (WebGL 1) и GLSL ES 3.0 (WebGL 2 в современных конфигурациях). При этом движок добавляет собственный слой абстракции, расширяющий стандартный GLSL.

Ключевые элементы среды шейдеров:

  • uniform — глобальные параметры сцены (матрицы, время, освещение)
  • varying — интерполированные данные между вершинным и фрагментным этапами
  • czm_* — системные переменные CesiumJS
  • встроенные функции работы с PBR-материалами

Типичный набор входных данных фрагментного шейдера:

  • координаты в пространстве камеры
  • нормали поверхности
  • UV-координаты
  • цвет материала
  • глубина фрагмента

Системные переменные CesiumJS в фрагментных шейдерах

CesiumJS предоставляет широкий набор встроенных uniform-переменных:

  • czm_frameNumber — номер кадра, используется для анимаций
  • czm_viewer — параметры камеры
  • czm_sceneMode — режим сцены (3D, Columbus View, 2D)
  • czm_lightDirectionEC — направление света в eye coordinates
  • czm_inverseViewMatrix — обратная матрица вида
  • czm_material — структура материала

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


Базовая структура фрагментного шейдера

Фрагментный шейдер в CesiumJS обычно начинается с объявления precision и входных данных:

#ifdef GL_ES
precision highp float;
#endif

varying vec2 v_textureCoordinates;
varying vec3 v_normalEC;

uniform sampler2D colorTexture;

Финальный цвет записывается в gl_FragColor (WebGL 1) или возвращается через выходную переменную (WebGL 2).


Простейшая модификация цвета пикселя

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

void main() {
    vec4 baseColor = texture2D(colorTexture, v_textureCoordinates);

    // Усиление красного канала
    baseColor.r *= 1.2;

    gl_FragColor = baseColor;
}

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


Материальная система и фрагментные шейдеры

В CesiumJS существует система материалов, позволяющая описывать визуальные свойства объектов через JSON-подобные конструкции. Эти материалы компилируются в GLSL-код, включающий фрагментный шейдер.

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

  • uniform-параметры
  • функции вычисления цвета
  • модификации прозрачности
  • процедурные текстуры

Фрагментный шейдер материала обычно работает с объектом czm_material:

czm_materialInput materialInput;

czm_material material = czm_getMaterial(materialInput);

gl_FragColor = vec4(material.diffuse, material.alpha);

CustomShader API

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

Пример подключения:

const customShader = new Cesium.CustomShader({
    fragmentShaderText: `
        void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
            material.diffuse *= vec3(1.0, 0.5, 0.5);
        }
    `
});

В этом случае Cesium сам интегрирует пользовательский код в общий PBR-пайплайн.


Структура fragmentMain

В CustomShader используется специальная функция:

  • fragmentMain — точка входа фрагментного шейдера
  • FragmentInput — входные данные
  • czm_modelMaterial — материал для модификации

Пример доступа к данным:

void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
    vec2 uv = fsInput.attributes.texCoord_0;
    float intensity = sin(uv.x * 10.0);

    material.emissive += vec3(intensity);
}

Работа с освещением

Фрагментные шейдеры в CesiumJS тесно связаны с моделью освещения. Основные элементы:

  • нормали поверхности
  • направление света
  • интенсивность диффузного и зеркального отражения

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

float diffuse = max(dot(normalize(v_normalEC), czm_lightDirectionEC), 0.0);
material.diffuse *= diffuse;

Эта формула реализует классическое ламбертово освещение.


Атмосферные эффекты и глубина

Фрагментный шейдер активно участвует в расчёте атмосферы Земли и тумана.

Глубина используется для:

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

Пример псевдотумана:

float depth = gl_FragCoord.z / gl_FragCoord.w;
float fogFactor = exp(-depth * 0.02);

vec3 fogColor = vec3(0.7, 0.8, 1.0);
vec3 finalColor = mix(fogColor, material.diffuse, fogFactor);

Процедурные эффекты

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

Типовые применения:

  • тепловые карты
  • шум Перлина
  • градиенты высоты
  • динамические волны

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

float h = fsInput.attributes.positionMC.z;
vec3 color = mix(vec3(0.1, 0.2, 0.5), vec3(0.8, 0.9, 0.3), h);
material.diffuse = color;

Координатные системы

CesiumJS использует несколько пространств координат:

  • model coordinates (MC)
  • eye coordinates (EC)
  • world coordinates (WC)

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

Пример нормализации в eye space:

vec3 normalEC = normalize(v_normalEC);

Производительность фрагментных шейдеров

Фрагментный этап является наиболее затратным в GPU-пайплайне, так как выполняется для каждого пикселя.

Основные ограничения:

  • количество текстурных выборок
  • сложность математических операций
  • ветвления (if/else)
  • использование высокоточных операций float

Оптимизационные подходы:

  • замена условных операторов на step/smoothstep
  • предвычисление констант
  • минимизация texture lookup
  • использование lowp mediump где допустимо

Отладка фрагментных шейдеров

Отладка в CesiumJS выполняется косвенными методами:

  • визуализация нормалей
  • вывод значений как цвета
  • временное отключение освещения

Пример визуализации нормали:

gl_FragColor = vec4(normalize(v_normalEC) * 0.5 + 0.5, 1.0);

Такой подход позволяет проверять корректность геометрии и интерполяции.


Интеграция с PBR-пайплайном

CesiumJS использует физически корректный рендеринг, где фрагментный шейдер участвует в расчёте:

  • metallic
  • roughness
  • specular
  • emissive

Эти параметры комбинируются в итоговый цвет материала:

material.metallic = 0.8;
material.roughness = 0.2;
material.emissive = vec3(0.1);

Работа с прозрачностью

Фрагментный шейдер определяет alpha-компонент, влияющий на смешивание:

material.alpha *= 0.5;

При этом CesiumJS учитывает режимы blending:

  • alpha blending
  • additive blending
  • premultiplied alpha

Постобработка на уровне фрагментов

Часть эффектов реализуется как экранные фрагментные шейдеры:

  • bloom
  • tone mapping
  • color correction

Они работают не с объектами сцены напрямую, а с итоговым буфером изображения.


Влияние временных параметров

Анимация в фрагментных шейдерах часто строится на:

  • czm_frameNumber
  • uniform времени
  • синусоидальных функциях

Пример пульсации:

float pulse = sin(czm_frameNumber * 0.05);
material.emissive += vec3(pulse);

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