关于 cesiumjs-custom-shader
面向 CesiumJS 开发者的 CustomShader GLSL 技能,可用于 Model、Cesium3DTileset 和 VoxelPrimitive 渲染,支持通过着色器控制几何体、要素 ID 与元数据。
主要功能
- 01顶点与片元着色器入口
- 02Uniform 与 varying
- 03材质和光照模式
- 04glTF 属性与要素 ID
- 05结构化元数据访问
- 06用于模型和瓦片集的自定义着色器
适用场景
- 01用自定义 GLSL 为模型表面着色
- 02按要素 ID 为瓦片要素着色
- 03在 3D 瓦片上可视化元数据值
- 04为体素数据应用程序化着色
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cesiumgs/ cesiumjs-skills --skill cesiumjs-custom-shader安装后在 Claude Code 中输入/cesiumjs-custom-shader确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
REFERENCE.mdexamples/_sandcastle-template.html01-diffuse-tint.js02-texture-swap.js03-feature-id-tileset.js04-translucent-override.js05-vertex-displacement.js06-metadata-ramp.js07-voxel-shader.jsREADME.md详细介绍
CesiumJS CustomShader
版本基线:CesiumJS 1.144。所有导入均采用 ES 模块风格。
CustomShader 会将用户编写的 GLSL 注入 Model / Cesium3DTileset / VoxelPrimitive 渲染管线。它会向逐顶点和逐片元代码公开 glTF 属性、要素 ID 和 EXT_structural_metadata,并通过内置的 czm_modelVertexOutput 和 czm_modelMaterial 结构体返回值。
使用此技能编写着色器主体。以下情况请使用:
cesiumjs-materials-shaders— 用于 FabricMaterial、ImageBasedLighting、PostProcessStage(泛光、SSAO、FXAA、色调映射)。cesiumjs-3d-tiles— 用于通过Cesium3DTileStyle声明式地为各要素着色,以及设置/配置VoxelPrimitive。cesiumjs-models-particles— 用于Model.fromGltfAsync、动画、ModelFeature.getProperty()。
不适用范围
- 用于实体折线/多边形/墙体的 Fabric
Material— 请参阅cesiumjs-materials-shaders。 PostProcessStage屏幕空间效果 — 请参阅cesiumjs-materials-shaders。ImageBasedLighting— 请参阅cesiumjs-materials-shaders。Cesium3DTileStyle声明式 JSON 样式 — 请参阅cesiumjs-3d-tiles。不要在同一个 tileset 上同时使用它和 CustomShader。- 在 glTF 中编写
EXT_structural_metadata/EXT_mesh_features— 这属于工具链工作,而非运行时工作。
最小示例
import { CustomShader, Model } from "cesium";
const shader = new CustomShader({
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
material.diffuse = vec3(1.0, 0.0, 0.0);
}
`,
});
const model = await Model.fromGltfAsync({ url: "./aircraft.glb", customShader: shader });
viewer.scene.primitives.add(model);
**注意:**写入
material.alpha需要设置translucencyMode: CustomShaderTranslucencyMode.TRANSLUCENT— 请参阅下文的“半透明”。对于使用默认INHERIT模式的不透明模型,alpha 写入会被静默忽略。
**视觉评估取景:**添加带着色器的
Model后,请等待模型达到可渲染状态,再显式对准已知目标点。不要依赖加载后的第一帧,也不要使用默认比例很小的模型。对于公开模型评估,请设置minimumPixelSize(通常为 256-400),使用适中的scale,并在着色器效果是评判重点时添加轮廓或明显的颜色色调。
应用 CustomShader
Model — 构造函数选项或可变属性:
const model = await Model.fromGltfAsync({ url, customShader });
model.customShader = newShader; // hot-swap
model.customShader = undefined; // clear
Cesium3DTileset — 构造函数选项或可变属性。仅影响由 Model 支持的瓦片内容(不影响原生 I3S 或其他格式):
const tileset = await Cesium3DTileset.fromUrl(url, { customShader });
tileset.customShader = newShader;
根据
Cesium3DTileset.customShader的 JSDoc:“将自定义着色器与Cesium3DTileStyle一起使用可能会导致未定义行为。” 该属性还标记为@experimental— 它使用了尚未定稿的 3D Tiles 规范部分,且可能会变更,不受 Cesium 标准弃用政策的约束。
VoxelPrimitive — 仅支持片元着色器的子集(见下文“VoxelPrimitive 着色器子集”):
const voxelPrimitive = new VoxelPrimitive({ provider, customShader });
引擎会在每一帧自动调用 customShader.update(frameState)。不再使用 CustomShader 时,请调用 customShader.destroy(),以释放其 TextureManager 所拥有的 GPU 纹理资源。
构造函数参考
new CustomShader({
mode, // CustomShaderMode — default MODIFY_MATERIAL
lightingModel, // LightingModel — if omitted, model's default is preserved
translucencyMode, // CustomShaderTranslucencyMode — default INHERIT
uniforms, // { [name]: { type: UniformType, value } } — default {}
varyings, // { [name]: VaryingType } — default {}
vertexShaderText, // string or undefined
fragmentShaderText, // string or undefined
});
通常需要设置 vertexShaderText 或 fragmentShaderText。完整枚举值请参阅 REFERENCE.md。
着色器函数签名
运行时会从生成的管线阶段调用这些函数。参数名称属于接口约定 — 重命名会导致着色器失效。
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) { ... }
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) { ... }
Uniform
使用 { type, value } 声明 uniform。类型是一个 UniformType 值;JS 值类型必须与之匹配(例如,VEC3 → Cartesian3)。在 GLSL 中,可通过声明的名称访问 uniform。
import { CustomShader, UniformType, TextureUniform, Cartesian3 } from "cesium";
const shader = new CustomShader({
uniforms: {
u_tint: { type: UniformType.VEC3, value: new Cartesian3(1.0, 0.5, 0.2) },
u_time: { type: UniformType.FLOAT, value: 0.0 },
u_detail: { type: UniformType.SAMPLER_2D, value: new TextureUniform({ url: "./detail.png", repeat: true }) },
},
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
vec3 d = texture(u_detail, fsInput.attributes.texCoord_0).rgb;
material.diffuse = mix(material.diffuse, u_tint, d.r + 0.1 * sin(u_time));
}
`,
});
// Update at runtime. For Cartesian/Matrix values, setUniform clones into existing storage.
shader.setUniform("u_time", performance.now() / 1000);
TextureUniform 接受 url(字符串或 Resource),或 typedArray + width + height — 且只能二选一(否则构造函数会抛出异常)。其他选项:repeat(默认 true)、pixelFormat、pixelDatatype、minificationFilter、magnificationFilter、maximumAnisotropy。
UniformType 中声明了 SAMPLER_CUBE,但构造时会拒绝它 — 会抛出 DeveloperError("CustomShader does not support samplerCube uniforms")。仅支持 SAMPLER_2D。
变化量
声明的变化量在顶点着色器中输出为 out <type> <name>,在片元着色器中输入为 in <type> <name>。在顶点着色器中写入,在片元着色器中读取。
import { CustomShader, VaryingType } from "cesium";
const shader = new CustomShader({
varyings: { v_worldHeight: VaryingType.FLOAT },
vertexShaderText: `
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
v_worldHeight = vsInput.attributes.positionMC.z;
}
`,
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
float t = clamp(v_worldHeight / 100.0, 0.0, 1.0);
material.diffuse = mix(vec3(0.2,0.4,0.8), vec3(1.0,0.8,0.2), t);
}
`,
});
VaryingType 支持 FLOAT、VEC2–VEC4、MAT2–MAT4。不支持 INT/BOOL/SAMPLER 变体。
模式与光照
CustomShaderMode:
MODIFY_MATERIAL(默认)——在材质阶段之后、光照之前运行。czm_modelMaterial中已填充 PBR/纹理结果;着色器会进一步细化这些结果。REPLACE_MATERIAL——完全跳过材质阶段。着色器以程序化方式设置所有字段。无需使用源材质时,这种方式开销更低。
LightingModel:
UNLIT——跳过光照;material.diffuse成为最终颜色(仍会应用 alpha)。采用平面着色。PBR——基于物理的渲染,在可用时使用 IBL。- 省略 ——保留模型的默认光照。除非有意覆盖,否则请省略。
将 REPLACE_MATERIAL 与 UNLIT 搭配使用,可实现纯程序化的平面着色(不采样材质,也不应用光照)。
透明度
CustomShaderTranslucencyMode 控制 alpha 写入与渲染通道的交互方式:
INHERIT(默认)——仅当源材质为半透明时,才会采用 alpha 值。OPAQUE——强制使用不透明通道。TRANSLUCENT——强制使用半透明通道。
**注意:**在 INHERIT 模式下,对不透明模型写入 material.alpha 不会生效,且不会报错。设置 translucencyMode: CustomShaderTranslucencyMode.TRANSLUCENT,使 alpha 写入生效。
import { CustomShader, CustomShaderTranslucencyMode } from "cesium";
const shader = new CustomShader({
translucencyMode: CustomShaderTranslucencyMode.TRANSLUCENT,
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
material.diffuse = vec3(0.2, 0.6, 1.0);
material.alpha = 0.5; // honored because translucencyMode is TRANSLUCENT
}
`,
});
参见 examples/04-translucent-override.js。
属性
vsInput.attributes 和 fsInput.attributes 会公开 glTF 顶点属性。名称区分大小写,且必须带有坐标空间后缀——构造函数会拒绝不带后缀的 position/normal/tangent/bitangent。
常见字段(完整表格见 REFERENCE.md):
positionMC——模型坐标,在 VS 和 FS 中均有效positionWC——世界(ECEF)坐标,仅限片元着色器,精度较低positionEC——眼睛坐标,仅限片元着色器normalMC/normalEC——顶点 / 片元tangentMC/tangentEC、bitangentMC/bitangentECtexCoord_N、color_N、joints_N、weights_N
**坐标空间验证。**构造函数会扫描着色器文本;如果组合无效,会抛出
DeveloperError("<name> is not available in the <stage> shader. Did you mean <alt> instead?")。例如:在顶点着色器中使用positionEC,或在片元着色器中使用normalMC。
自定义、以下划线开头的 glTF 属性(_FEATURE_ID_0、_SURFACE_TEMP)会转换为小写并移除前缀:fsInput.attributes.surface_temp。
FeatureIds
vsInput.featureIds / fsInput.featureIds 会将三个 glTF 来源统一到一个结构体中:
featureId_N——来自EXT_mesh_features的要素 ID 属性和隐式属性(N 是图元featureIds数组中的索引)。也包括要素 ID 纹理,但仅限片元着色器使用。instanceFeatureId_N——来自EXT_instance_features+EXT_mesh_gpu_instancing的逐实例要素 ID。- 命名别名 ——如果 glTF 指定
"label": "perVertex",则也可以使用featureIds.perVertex。 - 旧版 3D Tiles 1.0 的
BATCH_ID/_BATCHID→ 会透明地重命名为featureId_0。
GLSL 类型始终为 int。WebGL 1 中,数值超过 2^24 时会失去精度。
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
int id = fsInput.featureIds.featureId_0;
if (id == 0) material.diffuse = vec3(1.0, 0.2, 0.2); // roof
else if (id == 1) material.diffuse = vec3(0.2, 0.8, 0.2); // wall
}
参见 examples/03-feature-id-tileset.js。
元数据
EXT_structural_metadata 提供三种源类型(自 1.139 起,均可从着色器访问):
- 属性 — 每个顶点一份。顶点着色器和片段着色器。
- 属性纹理 — 每个纹素一份。仅限片段着色器。
- 属性表 — 每个要素一份,以要素 ID 为键。在 1.139 中新增(#13124)。
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
float t = fsInput.metadata.temperature;
float tMin = fsInput.metadataStatistics.temperature.minValue;
float tMax = fsInput.metadataStatistics.temperature.maxValue;
float v = (t - tMin) / (tMax - tMin);
material.diffuse = vec3(v, 0.0, 1.0 - v);
}
属性 ID 规范化(GLSL 标识符规则):
- 非字母数字字符序列 → 单个
_(temperature ℃→temperature_) - 去掉前缀
gl_(gl_custom→custom) - 以数字开头时添加前缀
_(12345→_12345) - 规范化后发生冲突 → 行为未定义
同级结构体: metadataClass.<prop>.noData | defaultValue | minValue | maxValue(类模式边界值)和 metadataStatistics.<prop>.minValue | maxValue | mean | median | standardDeviation | variance | sum(仅当 tileset.json 声明了 statistics 时才会填充)。
1.139 破坏性变更(#13135): 无符号整数元数据不再转换为有符号 int。将
UINT属性赋值给 GLSLint(int x = fsInput.metadata.myUint;)将无法编译。请使用匹配的无符号类型。
在 .glb 上没有 EXT_structural_metadata 的公开资源十分稀少——大多数真实场景中的元数据都位于 3D Tiles 中。参见 examples/06-metadata-ramp.js(目标为 Cesium3DTileset)。
czm_modelVertexOutput 和 czm_modelMaterial
czm_modelVertexOutput(顶点着色器的 inout vsOutput):
struct czm_modelVertexOutput {
vec3 positionMC; // initialized to vsInput.attributes.positionMC
float pointSize; // overrides gl_PointSize and Cesium3DTileStyle point sizing
};
注意事项: 修改
positionMC会使顶点发生位移,但不会更新图元的包围球。大幅位移的顶点可能会被视锥剔除。
czm_modelMaterial(片段着色器的 inout material)。所有颜色均为线性 RGB。条件字段 specularWeight / anisotropic* / clearcoatFactor……仅当图元启用了 KHR_materials_specular / _anisotropy / _clearcoat 时才会出现(完整的 #ifdef 保护结构体请参见 REFERENCE.md):
struct czm_modelMaterial {
vec4 baseColor; vec3 diffuse; float alpha;
vec3 specular; float roughness;
vec3 normalEC; float occlusion; vec3 emissive;
// + conditional PBR extension fields
};
内置 czm_* 自动 uniform
无需声明即可使用。对 CustomShader 最有用的包括:czm_frameNumber、czm_pi、czm_model、czm_view、czm_projection、czm_modelView、czm_normal、czm_lightDirectionEC、czm_sunDirectionWC、czm_eyeHeight、czm_sceneMode、czm_viewerPositionWC、czm_splitPosition。完整目录见 REFERENCE.md。
VoxelPrimitive 着色器子集
仅限片段着色器。在视线方向的每个光线步进处执行;最终像素会合成所有步进结果。提供 vertexShaderText 会被静默忽略。
可用结构体有所精简:
fsInput.attributes— 仅包含positionEC和normalEC。fsInput.featureIds— 不存在。fsInput.metadata— 完全支持。fsInput.metadataClass— 不存在。fsInput.metadataStatistics— 仅包含minValue和maxValue。
将 customShader = undefined 赋值后,会回退到 VoxelPrimitive.DefaultCustomShader。参见 examples/07-voxel-shader.js。关于 VoxelPrimitive 的设置(provider、shape、modelMatrix、nearestSampling),请参见 cesiumjs-3d-tiles。
1.130 破坏性变更(#12636): 已移除
fsInput.voxel.positionUv | positionShapeUv | positionLocal。请改用fsInput.attributes.positionEC。fsInput.voxel.surfaceNormal→fsInput.attributes.normalEC。 1.142 修复(#13517): 内置默认体素着色器能更稳健地处理常见元数据类型。仅当需要显式分类、着色、过滤或光线步进逻辑时,才保留自定义着色器。
常见模式
| 文件 | 目标 | 演示内容 |
|---|---|---|
examples/01-diffuse-tint.js | Model | 时间统一变量驱动 material.diffuse |
examples/02-texture-swap.js | Model | TextureUniform、SAMPLER_2D、setUniform |
examples/03-feature-id-tileset.js | Cesium3DTileset | fsInput.featureIds.featureId_0 分类着色 |
examples/04-translucent-override.js | Model(不透明源) | CustomShaderTranslucencyMode.TRANSLUCENT |
examples/05-vertex-displacement.js | Model | vsOutput.positionMC + 法线偏移 |
examples/06-metadata-ramp.js | Cesium3DTileset | fsInput.metadata.<prop> + metadataStatistics 归一化 |
examples/07-voxel-shader.js | VoxelPrimitive | 仅 FS 子集、每个体素的元数据 |
CesiumJS 1.139-1.142 版本说明
逐字引用上游 CHANGES.md:
破坏性变更(1.139,#13135):
依赖
EXT_structural_metadata扩展派生元数据的 Custom Shader 不再将无符号整数元数据类型转换为有符号整数。任何将 UINT 类型元数据赋给本地整数的现有 Custom Shader(例如int myMetadata = vsInput.metadata.myUintMetadata)都将无法编译。
破坏性变更(1.139,#13170): 修复了在自定义片段着色器中访问点云属性时的精度问题。
新增(1.139,#13124): 支持访问属性表中的元数据(此前仅支持属性/纹理)。
新增(1.139,#13135): 通过属性纹理支持更多元数据类型。
修复(1.139,#13231): 元数据变量正则表达式扩展至 metadataClass 和 metadataStatistics。
修复(1.139.1,#13247): 修复 NGA-GPM 本地扩展与自定义着色器回归问题。
修复(1.140,#13258): 当类定义中存在元数据时,不再因图元缺少元数据而禁用自定义着色器。
新增(1.140,#13323): 通过降精度转换,有限支持双精度元数据。
修复(1.142,#13517): 改进常见元数据类型的默认体素着色器。
破坏性变更(1.130,#12636): 体素 FragmentInput 结构已调整(参见 VoxelPrimitive 部分)。
注意事项与陷阱
- 必须带有坐标空间后缀。 在顶点着色器中使用
positionEC、在片段着色器中使用normalMC,或使用任何不带后缀的position/normal/tangent/bitangent,都会在构造时抛出DeveloperError,并提供建议替代项。 positionMC在片段着色器中有效——尽管带有MC后缀。只有normalMC/tangentMC/bitangentMC会在 FS 中被拒绝。- 顶点位移不会更新包围球。 位移后的顶点可能会被视锥体意外剔除。
Cesium3DTileStyle+ CustomShader = 未定义行为。 上游 JSDoc 如此说明。每个 tileset 只能选用其中一种。- 构造时会拒绝
SAMPLER_CUBE。 仅使用SAMPLER_2D。 - 参数名称约定。
vsInput、vsOutput、fsInput、material会通过正则表达式扫描——更改名称会导致代码生成失败。 TextureUniform的 URL 与 typedArray 只能二选一。 同时提供或都不提供都会抛出异常。typedArray需要width+height。- 在
INHERIT下,不透明模型上的 Alpha 写入会被静默忽略。 设置translucencyMode: CustomShaderTranslucencyMode.TRANSLUCENT——不要只写入material.alpha并期待它生效。 - 必须调用
customShader.destroy()。 释放持有纹理统一变量的着色器时请调用,否则其TextureManager会泄漏 GPU 资源。 vsOutput.pointSize会覆盖Cesium3DTileStyle的点大小设置。 除非这是预期行为,否则不要设置它。- 元数据属性 ID 会经过清理。 非字母数字字符会替换为
_;开头的gl_会被移除;发生冲突时行为未定义。 - UINT 元数据现在会保留有符号性(1.139+,#13135)。 使用
uint x = fsInput.metadata.prop;,不要使用int x = ...。 Cesium3DTileset上的customShader属于@experimental。 可能会在不遵循 Cesium 标准弃用政策的情况下发生变化。- Tileset:只有由
Model支持的瓦片内容才会使用 CustomShader。 原生 I3S 和其他格式不受影响。
性能提示
REPLACE_MATERIAL会跳过材质阶段(不采样纹理和 PBR 输入)。LightingModel.UNLIT会跳过光照计算——与REPLACE_MATERIAL结合使用可实现平面程序化着色。- 在顶点着色器中写入 varying,而不是在每个片元中重复计算。
- 避免每帧调用
setUniform设置SAMPLER_2D——这会触发异步纹理重新加载。使用一次url并保留该引用。 - 拆除时调用
customShader.destroy(),以释放 GPU 纹理资源。
另请参阅
REFERENCE.md— 完整的结构体表格(Attributes、FeatureIds、Metadata/Class/Statistics)、枚举值表格、内置czm_*uniform 目录、坐标空间验证错误参考。examples/— 七个通过编译测试的代码片段。examples/_sandcastle-template.html是内部脚手架;examples/README.md介绍了目录布局。cesiumjs-materials-shaders— 用于 FabricMaterial、ImageBasedLighting、PostProcessStage。cesiumjs-3d-tiles—Cesium3DTileStyle、Cesium3DTileset设置、VoxelPrimitive实例化。cesiumjs-models-particles—Model.fromGltfAsync、ModelFeature.getProperty()、动画。cesiumjs-primitives— 在经典 Primitive 几何体的外观中使用 Fabric。- CesiumJS Custom Shader Guide —
CesiumGS/cesiummain分支中的Documentation/CustomShaderGuide/README.md。