Система постобработки в CesiumJS построена вокруг последовательного применения экранных эффектов к итоговому изображению сцены. Каждый эффект реализуется как отдельный этап (stage), который получает входной буфер кадра и формирует новый результат либо модифицирует существующий.
Базовая единица композиции — PostProcessStage. Он
представляет собой изолированный шейдерный проход, работающий на полном
экране. Несколько стадий объединяются в
PostProcessStageCollection, которая управляет порядком
выполнения и зависимостями.
Внутри движка используется концепция графа постобработки: результат рендеринга сцены проходит через последовательность framebuffer-переключений, где каждый этап может:
Ключевой принцип композиции — последовательное наложение эффектов с сохранением промежуточных буферов.
Каждый stage представляет собой GLSL-шейдер с унифицированным интерфейсом входных данных:
Типовая структура stage:
const grayscaleStage = new Cesium.PostProcessStage({
fragmentShader: `
uniform sampler2D colorTexture;
varying vec2 v_textureCoordinates;
void main() {
vec4 color = texture2D(colorTexture, v_textureCoordinates);
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
gl_FragColor = vec4(vec3(gray), color.a);
}
`
});
Stage не имеет состояния между кадрами. Композиция достигается за счёт внешнего управления коллекцией стадий.
Все эффекты сцены агрегируются через
scene.postProcessStages. Этот контейнер управляет:
viewer.scene.postProcessStages.add(grayscaleStage);
Порядок добавления определяет последовательность выполнения. Это критично, поскольку каждый следующий stage получает уже изменённый буфер изображения.
Композиция работает по принципу:
Scene Render → Stage 1 → Stage 2 → Stage 3 → Screen
Любое изменение порядка приводит к изменению визуального результата даже при одинаковых шейдерах.
При последовательной композиции каждый stage может использовать результат предыдущего как входной цветовой буфер. Это создаёт каскадную структуру обработки изображения.
Типовой пример цепочки:
Каждый шаг работает поверх уже изменённого изображения:
scene.postProcessStages.add(bloomStage);
scene.postProcessStages.add(vignetteStage);
scene.postProcessStages.add(colorCorrectionStage);
Внутренне Cesium переключает framebuffer между стадиями, сохраняя промежуточные результаты в текстурах.
Для уменьшения количества проходов применяется объединение логики в одном шейдере. Composite stage позволяет инкапсулировать несколько эффектов:
const compositeStage = new Cesium.PostProcessStage({
fragmentShader: `
uniform sampler2D colorTexture;
varying vec2 v_textureCoordinates;
void main() {
vec4 color = texture2D(colorTexture, v_textureCoordinates);
// vignette
float dist = distance(v_textureCoordinates, vec2(0.5));
color.rgb *= smoothstep(0.8, 0.2, dist);
// grayscale blend
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
color.rgb = mix(color.rgb, vec3(gray), 0.3);
gl_FragColor = color;
}
`
});
Такой подход уменьшает количество framebuffer-переключений и повышает производительность.
Depth texture является ключевым элементом сложных эффектов. Он позволяет учитывать пространственную структуру сцены при постобработке.
Примеры использования:
Пример эффекта глубинного тумана:
const fogStage = new Cesium.PostProcessStage({
fragmentShader: `
uniform sampler2D colorTexture;
uniform sampler2D depthTexture;
uniform float fogDensity;
varying vec2 v_textureCoordinates;
void main() {
float depth = czm_readDepth(depthTexture, v_textureCoordinates);
vec4 color = texture2D(colorTexture, v_textureCoordinates);
float fog = exp(-depth * fogDensity);
vec3 fogColor = vec3(0.7, 0.8, 0.9);
color.rgb = mix(fogColor, color.rgb, fog);
gl_FragColor = color;
}
`,
uniforms: {
fogDensity: 0.02
}
});
Глубина выступает как фактор смешивания между исходным изображением и эффектом среды.
Некоторые эффекты несовместимы при одновременном применении из-за конкуренции за framebuffer или глубинные данные. Cesium решает это через внутреннюю систему приоритетов.
Основные классы конфликтов:
При построении композиции учитывается:
Композиция эффектов часто требует не полного замещения изображения, а смешивания результатов.
Типовые режимы:
Пример эффекта свечения:
const glowStage = new Cesium.PostProcessStage({
fragmentShader: `
uniform sampler2D colorTexture;
varying vec2 v_textureCoordinates;
void main() {
vec4 color = texture2D(colorTexture, v_textureCoordinates);
vec3 glow = color.rgb * color.rgb;
gl_FragColor = vec4(color.rgb + glow * 0.5, color.a);
}
`
});
Такие эффекты усиливают яркие области без разрушения базового изображения.
Композиция не является статичной. Стадии могут добавляться и удаляться во время работы сцены.
scene.postProcessStages.remove(bloomStage);
scene.postProcessStages.add(edgeStage);
Это позволяет перестраивать граф постобработки в зависимости от состояния сцены:
Каждое изменение приводит к пересборке внутреннего pipeline.
Каждый stage добавляет:
Критическим фактором становится количество проходов. При росте цепочки эффектов линейно увеличивается нагрузка GPU.
Оптимизация достигается через:
Сложные визуальные системы используют собственную логику построения цепочки эффектов. Stages рассматриваются как узлы графа, где:
Пример структуры:
Scene
├── Tone Mapping
│ ├── Bloom
│ └── Color Grading
└── Edge Detection
└── Outline Composite
Такой подход позволяет разделять эффекты по функциональным группам и управлять их взаимодействием.
При наличии прозрачных объектов порядок композиции становится ещё более критичным. Полупрозрачные пиксели уже содержат результат смешивания геометрии, и дальнейшая постобработка должна учитывать альфа-канал.
Особое значение имеют:
Ошибки в порядке стадий приводят к визуальным артефактам, особенно в эффектах свечения и контуров.
Постобработка не существует изолированно от сценического рендеринга. Некоторые эффекты могут быть реализованы на уровне материалов (shaders материалов), а другие — только через post process.
Разделение задач:
Композиция строится как надстройка над уже сформированным изображением, что позволяет накладывать глобальные визуальные стили без изменения геометрии сцены.