# Cesiumjs Custom Shader

> CustomShader authoring — vertexShaderText and fragmentShaderText against VertexInput, FragmentInput, FeatureIds, Metadata, czm_modelMaterial. Use when reading EXT_mesh_features or EXT_structural_metadata property textures/tables, vertex displacement, or shading VoxelPrimitive.

- **Type:** Skill
- **Install:** `agentstack add skill-cesiumgs-cesiumjs-skills-cesiumjs-custom-shader`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [CesiumGS](https://agentstack.voostack.com/s/cesiumgs)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [CesiumGS](https://github.com/CesiumGS)
- **Source:** https://github.com/CesiumGS/cesiumjs-skills/tree/main/skills/cesiumjs-custom-shader

## Install

```sh
agentstack add skill-cesiumgs-cesiumjs-skills-cesiumjs-custom-shader
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# CesiumJS CustomShader

Version baseline: CesiumJS 1.142. All imports use ES module style.

`CustomShader` injects user GLSL into the `Model` / `Cesium3DTileset` / `VoxelPrimitive` rendering pipeline. It exposes glTF attributes, feature IDs, and `EXT_structural_metadata` to per-vertex and per-fragment code, and returns values through the built-in `czm_modelVertexOutput` and `czm_modelMaterial` structs.

Use this skill for **writing the shader body**. Use:
- `cesiumjs-materials-shaders` — for Fabric `Material`, `ImageBasedLighting`, `PostProcessStage` (bloom, SSAO, FXAA, tonemapping).
- `cesiumjs-3d-tiles` — for declarative per-feature coloring via `Cesium3DTileStyle`, and for `VoxelPrimitive` setup/configuration.
- `cesiumjs-models-particles` — for `Model.fromGltfAsync`, animations, `ModelFeature.getProperty()`.

## Out of scope

- **Fabric `Material`** for entity polylines/polygons/walls — see `cesiumjs-materials-shaders`.
- **`PostProcessStage`** screen-space effects — see `cesiumjs-materials-shaders`.
- **`ImageBasedLighting`** — see `cesiumjs-materials-shaders`.
- **`Cesium3DTileStyle`** declarative JSON styling — see `cesiumjs-3d-tiles`. **Do not combine with CustomShader on the same tileset.**
- **Authoring `EXT_structural_metadata` / `EXT_mesh_features` in glTF** — tooling concern, not runtime.

## Minimal example

```js
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);
      material.alpha = 0.8;
    }
  `,
});

const model = await Model.fromGltfAsync({ url: "./aircraft.glb", customShader: shader });
viewer.scene.primitives.add(model);
```

## Applying a CustomShader

**Model** — constructor option or mutable property:

```js
const model = await Model.fromGltfAsync({ url, customShader });
model.customShader = newShader;     // hot-swap
model.customShader = undefined;     // clear
```

**Cesium3DTileset** — constructor option or mutable property. Only `Model`-backed tile content is affected (not native I3S or other formats):

```js
const tileset = await Cesium3DTileset.fromUrl(url, { customShader });
tileset.customShader = newShader;
```

> Per the `Cesium3DTileset.customShader` JSDoc: *"Using custom shaders with a `Cesium3DTileStyle` may lead to undefined behavior."* The property is also marked `@experimental` — it uses 3D Tiles spec surface that is not final and may change without Cesium's standard deprecation policy.

**VoxelPrimitive** — fragment-only subset (see "VoxelPrimitive shader subset" below):

```js
const voxelPrimitive = new VoxelPrimitive({ provider, customShader });
```

The engine calls `customShader.update(frameState)` automatically each frame. When finished with a CustomShader, call `customShader.destroy()` to release GPU texture resources owned by its `TextureManager`.

## Constructor reference

```js
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
});
```

Either `vertexShaderText` or `fragmentShaderText` is typically required. See `REFERENCE.md` for exhaustive enum values.

## Shader function signatures

The runtime calls these from generated pipeline stages. Parameter names are part of the contract — renaming them breaks the shader.

```glsl
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) { ... }
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) { ... }
```

## Uniforms

Declare uniforms with `{ type, value }`. The type is a `UniformType` value; the JS value type must match (e.g. `VEC3` → `Cartesian3`). Uniforms are accessible in GLSL by their declared name.

```js
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` accepts either `url` (string or `Resource`) or `typedArray` + `width` + `height` — **exactly one** (constructor throws otherwise). Other options: `repeat` (default `true`), `pixelFormat`, `pixelDatatype`, `minificationFilter`, `magnificationFilter`, `maximumAnisotropy`.

**`SAMPLER_CUBE` is declared on `UniformType` but rejected at construction** — throws `DeveloperError("CustomShader does not support samplerCube uniforms")`. Only `SAMPLER_2D` is supported.

## Varyings

Declared varyings are emitted as `out  ` in the vertex shader and `in  ` in the fragment shader. Write in vertex, read in fragment.

```js
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` supports `FLOAT`, `VEC2`–`VEC4`, `MAT2`–`MAT4`. No `INT`/`BOOL`/`SAMPLER` variants.

## Modes & lighting

**`CustomShaderMode`:**
- `MODIFY_MATERIAL` (default) — runs after the material stage, before lighting. `czm_modelMaterial` is populated with PBR/texture results; the shader refines them.
- `REPLACE_MATERIAL` — skips the material stage entirely. Shader sets every field procedurally. Cheaper when the source material is not needed.

**`LightingModel`:**
- `UNLIT` — skip lighting; `material.diffuse` becomes the final color (alpha still applied). Flat-shaded.
- `PBR` — physically-based with IBL when available.
- *Omitted* — preserves the model's default lighting. Omit unless overriding intentionally.

Pair `REPLACE_MATERIAL` + `UNLIT` for pure procedural flat shading (no material sampling, no lighting).

## Translucency

`CustomShaderTranslucencyMode` governs how alpha writes interact with the render pass:

- `INHERIT` (default) — alpha is only honored if the source material is translucent.
- `OPAQUE` — force opaque pass.
- `TRANSLUCENT` — force translucent pass.

**Pitfall:** writing `material.alpha` on an opaque model with `INHERIT` silently does nothing. Set `translucencyMode: CustomShaderTranslucencyMode.TRANSLUCENT` to make alpha writes effective. See `examples/04-translucent-override.js`.

## Attributes

`vsInput.attributes` and `fsInput.attributes` expose glTF vertex attributes. Names are case-sensitive and coordinate-space suffixes are required — the constructor rejects bare `position`/`normal`/`tangent`/`bitangent`.

Common fields (full table in `REFERENCE.md`):
- `positionMC` — model coords, valid in VS and FS
- `positionWC` — world (ECEF) coords, **fragment only**, low-precision
- `positionEC` — eye coords, **fragment only**
- `normalMC` / `normalEC` — vertex / fragment
- `tangentMC` / `tangentEC`, `bitangentMC` / `bitangentEC`
- `texCoord_N`, `color_N`, `joints_N`, `weights_N`

> **Coordinate-space validation.** The constructor scans shader text and throws `DeveloperError(" is not available in the  shader. Did you mean  instead?")` for invalid combinations. Examples: `positionEC` in vertex, `normalMC` in fragment.

Custom underscore-prefixed glTF attributes (`_FEATURE_ID_0`, `_SURFACE_TEMP`) are lowercased and un-prefixed: `fsInput.attributes.surface_temp`.

## FeatureIds

`vsInput.featureIds` / `fsInput.featureIds` unify three glTF sources into one struct:

- `featureId_N` — feature ID attributes and implicit attributes from `EXT_mesh_features` (N is the array index in the primitive's `featureIds` array). Also covers feature ID **textures**, which are fragment-shader-only.
- `instanceFeatureId_N` — per-instance feature IDs from `EXT_instance_features` + `EXT_mesh_gpu_instancing`.
- Named aliases — if glTF specifies `"label": "perVertex"`, then `featureIds.perVertex` is also available.
- Legacy 3D Tiles 1.0 `BATCH_ID` / `_BATCHID` → transparently renamed to `featureId_0`.

GLSL type is always `int`. WebGL 1 loses precision above 2^24.

```glsl
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
}
```

See `examples/03-feature-id-tileset.js`.

## Metadata

`EXT_structural_metadata` surfaces three source types (all addressable from shaders as of 1.139):

- **Property attributes** — per-vertex. Vertex and fragment shaders.
- **Property textures** — per-texel. **Fragment only.**
- **Property tables** — per-feature, keyed by feature ID. **Added in 1.139 (#13124).**

```glsl
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);
}
```

**Property ID sanitization** (GLSL identifier rules):
- Non-alphanumeric runs → single `_` (`temperature ℃` → `temperature_`)
- Leading `gl_` stripped (`gl_custom` → `custom`)
- Leading digit prefixed with `_` (`12345` → `_12345`)
- Post-sanitization collisions → undefined behavior

**Sibling structs:** `metadataClass..noData | defaultValue | minValue | maxValue` (class-schema bounds) and `metadataStatistics..minValue | maxValue | mean | median | standardDeviation | variance | sum` (populated only when `tileset.json` declares `statistics`).

> **1.139 breaking change (#13135):** unsigned integer metadata is no longer cast to signed int. Assigning a `UINT` property to a GLSL `int` (`int x = fsInput.metadata.myUint;`) no longer compiles. Use the matching unsigned type.

**Public assets without `EXT_structural_metadata` on a `.glb` are scarce** — most real-world metadata lives on 3D Tiles. See `examples/06-metadata-ramp.js` (Cesium3DTileset target).

## czm_modelVertexOutput & czm_modelMaterial

**`czm_modelVertexOutput`** (vertex shader's `inout vsOutput`):
```glsl
struct czm_modelVertexOutput {
  vec3 positionMC;    // initialized to vsInput.attributes.positionMC
  float pointSize;    // overrides gl_PointSize and Cesium3DTileStyle point sizing
};
```

> **Gotcha:** mutating `positionMC` displaces vertices but does **not** update the primitive's bounding sphere. Heavily displaced vertices can be frustum-culled.

**`czm_modelMaterial`** (fragment shader's `inout material`). All colors are linear RGB. Conditional fields `specularWeight` / `anisotropic*` / `clearcoatFactor`... appear only when `KHR_materials_specular` / `_anisotropy` / `_clearcoat` is active on the primitive (see `REFERENCE.md` for the full `#ifdef`-guarded struct):

```glsl
struct czm_modelMaterial {
  vec4 baseColor; vec3 diffuse; float alpha;
  vec3 specular;  float roughness;
  vec3 normalEC;  float occlusion;  vec3 emissive;
  // + conditional PBR extension fields
};
```

## Built-in `czm_*` automatic uniforms

Available without declaration. Most useful for 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`. Full catalog in `REFERENCE.md`.

## VoxelPrimitive shader subset

Fragment-only. Executes at every raymarching step along the view ray; the final pixel composites all steps. Supplying `vertexShaderText` is silently ignored.

Reduced struct availability:
- `fsInput.attributes` — only `positionEC` and `normalEC`.
- `fsInput.featureIds` — **not present**.
- `fsInput.metadata` — fully supported.
- `fsInput.metadataClass` — **not present**.
- `fsInput.metadataStatistics` — only `minValue` and `maxValue`.

Assigning `customShader = undefined` falls back to `VoxelPrimitive.DefaultCustomShader`. See `examples/07-voxel-shader.js`. For `VoxelPrimitive` setup (provider, shape, modelMatrix, nearestSampling), see `cesiumjs-3d-tiles`.

> **1.130 breaking change (#12636):** `fsInput.voxel.positionUv | positionShapeUv | positionLocal` were removed. Use `fsInput.attributes.positionEC` instead. `fsInput.voxel.surfaceNormal` → `fsInput.attributes.normalEC`.
> **1.142 fix (#13517):** the built-in default voxel shader handles common metadata types more robustly. Keep a custom shader only when you need explicit classification, coloring, filtering, or raymarch-step logic.

## Common patterns

| File | Target | Demonstrates |
|---|---|---|
| `examples/01-diffuse-tint.js` | Model | Time uniform driving `material.diffuse` |
| `examples/02-texture-swap.js` | Model | `TextureUniform`, `SAMPLER_2D`, `setUniform` |
| `examples/03-feature-id-tileset.js` | Cesium3DTileset | `fsInput.featureIds.featureId_0` classification coloring |
| `examples/04-translucent-override.js` | Model (opaque source) | `CustomShaderTranslucencyMode.TRANSLUCENT` |
| `examples/05-vertex-displacement.js` | Model | `vsOutput.positionMC` + normal offset |
| `examples/06-metadata-ramp.js` | Cesium3DTileset | `fsInput.metadata.` + `metadataStatistics` normalization |
| `examples/07-voxel-shader.js` | VoxelPrimitive | FS-only subset, per-voxel metadata |

## CesiumJS 1.139-1.142 version notes

Verbatim from upstream `CHANGES.md`:

**Breaking (1.139, #13135):**
> Custom Shaders that rely on metadata derived from the `EXT_structural_metadata` extension no longer cast unsigned integer metadata types to signed integers. Any existing custom shaders that assign UINT-type metadata to local integers (e.g. `int myMetadata = vsInput.metadata.myUintMetadata`) will no longer compile.

**Breaking (1.139, #13170):** Fixed precision of point cloud attributes when accessed in a custom fragment shader.

**Addition (1.139, #13124):** Access to metadata from property tables (previously only attributes/textures).

**Addition (1.139, #13135):** More metadata types via property textures.

**Fix (1.139, #13231):** Metadata variable regex extended to `metadataClass` and `metadataStatistics`.

**Fix (1.139.1, #13247):** NGA-GPM local extension + custom shader regression fix.

**Fix (1.140, #13258):** Custom shaders are no longer disabled for primitives with missing metadata when the metadata exists on the class definition.

**Addition (1.140, #13323):** Limited double-precision metadata support via downcasting.

**Fix (1.142, #13517):** Improved default voxel shader for common metadata types.

**Breaking (1.130, #12636):** Voxel `FragmentInput` restructured (see VoxelPrimitive section).

## Gotchas & pitfalls

1. **Coordinate-space suffix required.** `positionEC` in vertex, `normalMC` in fragment, or any bare `position`/`normal`/`tangent`/`bitangent` throws `DeveloperError` at construction with a suggested alternative.
2. **`positionMC` is valid in fragment shader** — despi

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [CesiumGS](https://github.com/CesiumGS)
- **Source:** [CesiumGS/cesiumjs-skills](https://github.com/CesiumGS/cesiumjs-skills)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-cesiumgs-cesiumjs-skills-cesiumjs-custom-shader
- Seller: https://agentstack.voostack.com/s/cesiumgs
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
