В основе рендеринга лежит WebGL-пайплайн, где вся визуализация
выполняется на GPU через вершинные и фрагментные шейдеры. CesiumJS
абстрагирует низкоуровневый WebGL, но сохраняет прямую возможность
вмешательства в шейдерную стадию через систему материалов,
Appearance и современный API CustomShader.
Шейдеры в CesiumJS строятся на GLSL ES 3.0 (в зависимости от
контекста WebGL2) и используют набор встроенных утилит с префиксом
czm_, которые предоставляют доступ к матрицам, освещению,
геометрии и системным данным сцены.
В CesiumJS существует несколько уровней кастомизации GPU-пайплайна:
PrimitiveНаиболее гибким и актуальным подходом считается
CustomShader, позволяющий модифицировать входные/выходные
данные шейдера без переписывания всей программы рендеринга.
Шейдеры CesiumJS не являются «чистым GLSL». Они компонуются из множества включаемых модулей:
Типичный фрагмент шейдера включает:
czm_materialInput materialInput;
czm_material material = czm_getMaterial(materialInput);
Ключевой момент: Cesium управляет входными структурами, а
пользователь расширяет поведение через модификацию
material.
CustomShader позволяет подключать GLSL-код, который
выполняется на этапе обработки фрагмента или вершины.
Основная идея:
Структура:
const customShader = new Cesium.CustomShader({
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
material.diffuse = vec3(1.0, 0.0, 0.0);
}
`
});
Cesium предоставляет структурированный доступ к данным модели:
fsInput.attributes.positionMCfsInput.attributes.normalMCfsInput.attributes.colorfsInput.st — UV-координатыЭти данные являются точкой входа для процедурных эффектов.
Основной объект модификации — czm_modelMaterial.
Пример изменения альбедо:
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
material.diffuse = vec3(0.2, 0.6, 1.0);
material.alpha = 1.0;
}
Материал поддерживает:
diffuse — базовый цветemission — самосвечениеspecular — отражениеalpha — прозрачностьCesium передаёт глобальные uniform-переменные, включая
czm_frameNumber и czm_time.
Пример анимированного эффекта:
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
float pulse = 0.5 + 0.5 * sin(czm_frameNumber * 0.05);
material.diffuse *= pulse;
}
Это позволяет создавать:
Нормали используются для построения эффектов освещения и поверхностных модификаций.
Пример Fresnel-эффекта:
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
vec3 normal = normalize(fsInput.attributes.normalMC);
vec3 viewDir = normalize(czm_viewerPositionWC - fsInput.attributes.positionMC);
float fresnel = pow(1.0 - dot(normal, viewDir), 3.0);
material.emission = vec3(0.0, 0.4, 1.0) * fresnel;
}
Для генерации сложных эффектов применяются шумовые функции:
Cesium не включает встроенный шум в API CustomShader, поэтому его добавляют вручную:
float hash(vec2 p)
{
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
Использование шума:
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
float n = hash(fsInput.st * 10.0);
material.diffuse *= n;
}
UV-координаты (st) позволяют строить:
Пример радиального градиента:
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
float d = distance(fsInput.st, vec2(0.5));
material.diffuse = mix(vec3(0.0, 0.2, 1.0), vec3(1.0), d);
}
Шейдеры особенно важны при работе с 3D Tiles, где сцены состоят из больших массивов геометрии.
CustomShader применяется на уровне модели:
tileset.customShader = new Cesium.CustomShader({
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
material.diffuse = vec3(1.0, 1.0, 1.0);
}
`
});
Использование:
Cesium позволяет прокидывать пользовательские атрибуты в шейдер:
customShader = new Cesium.CustomShader({
varyings: {
v_height: Cesium.VaryingType.FLOAT
},
vertexShaderText: `
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput)
{
v_height = vsInput.attributes.positionMC.z;
}
`,
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
material.diffuse = vec3(v_height * 0.1);
}
`
});
GPU-оптимизация критична при больших сценах:
ifmix() вместо условийПроблемные паттерны:
sin/cos без необходимостиfloat edge = 1.0 - dot(normalize(fsInput.attributes.normalMC), vec3(0.0, 0.0, 1.0));
material.diffuse = vec3(edge);
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
float n = fract(sin(fsInput.st.x * 10.0) * 43758.0);
if (n < czm_frameNumber * 0.001)
discard;
}
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
float h = fsInput.attributes.positionMC.z;
material.diffuse = vec3(h * 0.1, 0.2, 1.0 - h * 0.1);
}
Cesium использует физически приближённое освещение (PBR-подобная модель). CustomShader может:
Но при этом сохраняется глобальная система освещения сцены:
Внутренне pipeline выглядит так:
CustomShader встраивается на этапе (5), не нарушая общий pipeline.
Часто используется композиция:
Пример комбинированного эффекта:
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
float h = fsInput.attributes.positionMC.z;
float n = fract(sin(h * 12.3) * 43758.0);
float fresnel = pow(1.0 - dot(normalize(fsInput.attributes.normalMC), vec3(0.0,0.0,1.0)), 2.0);
material.diffuse = mix(vec3(0.1, 0.2, 1.0), vec3(1.0, 0.3, 0.0), h);
material.emission = vec3(fresnel * n);
}