cesium/Documentation/CustomShaderGuide/README.md

901 lines
35 KiB
Markdown

# `CustomShader` Documentation
## Constructor
```js
const customShader = new Cesium.CustomShader({
// Any custom uniforms the user wants to add to the shader.
// these can be changed at runtime via customShader.setUniform()
uniforms: {
u_time: {
value: 0, // initial value
type: Cesium.UniformType.FLOAT
},
// Textures can be loaded from a URL, a Resource, or a TypedArray.
// See the Uniforms section for more detail
u_externalTexture: {
value: new Cesium.TextureUniform({
url: "http://example.com/image.png"
}),
type: Cesium.UniformType.SAMPLER_2D
}
}
// Custom varyings that will appear in the custom vertex and fragment shader
// text.
varyings: {
v_customTexCoords: Cesium.VaryingType.VEC2
},
// configure where in the fragment shader's materials/lighting pipeline the
// custom shader goes. More on this below.
mode: Cesium.CustomShaderMode.MODIFY_MATERIAL,
// either PBR (physically-based rendering) or UNLIT depending on the desired
// results.
lightingModel: Cesium.LightingModel.PBR,
// Force the shader to render as transparent, even if the primitive had
// an opaque material
translucencyMode: Cesium.CustomShaderTranslucencyMode.TRANSLUCENT,
// Custom vertex shader. This is a function from model space -> model space.
// VertexInput is documented below
vertexShaderText: `
// IMPORTANT: the function signature must use these parameter names. This
// makes it easier for the runtime to generate the shader and make optimizations.
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
// code goes here. An empty body is a no-op.
}
`,
// Custom fragment shader.
// FragmentInput will be documented below
// Regardless of the mode, this always takes in a material and modifies it in place.
fragmentShaderText: `
// IMPORTANT: the function signature must use these parameter names. This
// makes it easier for the runtime to generate the shader and make optimizations.
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
// code goes here. e.g. to set the diffuse color to a translucent red:
material.diffuse = vec3(1.0, 0.0, 0.0);
material.alpha = 0.5;
}
`,
});
```
## Applying A Custom Shader
Custom shaders can be applied to either 3D Tiles, a `Model`, or
a `VoxelPrimitive` as follows:
```js
const customShader = new Cesium.CustomShader(/* ... */);
// Applying to all tiles in a tileset.
const tileset = await Cesium.Cesium3DTileset.fromUrl(
"http://example.com/tileset.json", {
customShader: customShader,
});
viewer.scene.primitives.add(tileset);
// Applying to a model directly
const model = await Cesium.Model.fromGltfAsync({,
url: "http://example.com/model.gltf",
customShader: customShader,
});
// Applying to a VoxelPrimitive
const provider = await Cesium.Cesium3DTilesVoxelProvider.fromUrl(
"http://example.com/tileset.json",
);
const voxelPrimitive = new Cesium.VoxelPrimitive({
provider: provider,
customShader: customShader,
});
```
Voxels only support a subset of custom shader functionality. See [Using custom shaders for voxel rendering](#using-custom-shaders-for-voxel-rendering).
## Uniforms
Custom Shaders currently supports the following uniform types:
| UniformType | GLSL type | JS type |
| ------------ | ----------- | ---------------- |
| `FLOAT` | `float` | `Number` |
| `VEC2` | `vec2` | `Cartesian2` |
| `VEC3` | `vec3` | `Cartesian3` |
| `VEC4` | `vec4` | `Cartesian4` |
| `INT` | `int` | `Number` |
| `INT_VEC2` | `ivec2` | `Cartesian2` |
| `INT_VEC3` | `ivec3` | `Cartesian3` |
| `INT_VEC4` | `ivec4` | `Cartesian4` |
| `BOOL` | `bool` | `Boolean` |
| `BOOL_VEC2` | `bvec2` | `Cartesian2` |
| `BOOL_VEC3` | `bvec3` | `Cartesian3` |
| `BOOL_VEC4` | `bvec4` | `Cartesian4` |
| `MAT2` | `mat2` | `Matrix2` |
| `MAT3` | `mat3` | `Matrix3` |
| `MAT4` | `mat4` | `Matrix4` |
| `SAMPLER_2D` | `sampler2D` | `TextureUniform` |
### Texture Uniforms
Texture uniforms have more options, which have been encapsulated in the
`TextureUniform` class. Textures can be loaded from a URL, a `Resource` or a
typed array. Here are some examples:
```js
const textureFromUrl = new Cesium.TextureUniform({
url: "https://example.com/image.png",
});
const textureFromTypedArray = new Cesium.TextureUniform({
typedArray: new Uint8Array([255, 0, 0, 255]),
width: 1,
height: 1,
pixelFormat: Cesium.PixelFormat.RGBA,
pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE,
});
// TextureUniform also provides options for controlling the sampler
const textureWithSampler = new Cesium.TextureUniform({
url: "https://example.com/image.png",
repeat: false,
minificationFilter: Cesium.TextureMinificationFilter.NEAREST,
magnificationFilter: Cesium.TextureMagnificationFilter.NEAREST,
});
```
## Varyings
Varyings are declared in the `CustomShader` constructor. This automatically
adds lines such as `out float v_userDefinedVarying;` and `in float v_userDefinedVarying;` to the top of the
GLSL vertex and fragment shaders respectively.
The user is responsible for assigning a value to this varying in
`vertexShaderText` and using it in `fragmentShaderText`. For example:
```js
const customShader = new Cesium.CustomShader({
// Varying is declared here
varyings: {
v_selectedColor: Cesium.VaryingType.VEC4,
},
// User assigns the varying in the vertex shader
vertexShaderText: `
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
float positiveX = step(0.0, vsOutput.positionMC.x);
v_selectedColor = mix(
vsInput.attributes.color_0,
vsInput.attributes.color_1,
vsOutput.positionMC.x
);
}
`,
// User uses the varying in the fragment shader
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
material.diffuse = v_selectedColor.rgb;
}
`,
});
```
Custom Shaders supports the following varying types:
| VaryingType | GLSL type |
| ----------- | --------- |
| `FLOAT` | `float` |
| `VEC2` | `vec2` |
| `VEC3` | `vec3` |
| `VEC4` | `vec4` |
| `MAT2` | `mat2` |
| `MAT3` | `mat3` |
| `MAT4` | `mat4` |
## Custom Shader Modes
The custom fragment shader is configurable so it can go before/after materials or lighting. here's a summary of what
modes are available.
| Mode | Fragment shader pipeline | Description |
| --------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------- |
| `MODIFY_MATERIAL` (default) | material -> custom shader -> lighting | The custom shader modifies the results of the material stage |
| `REPLACE_MATERIAL` | custom shader -> lighting | Don't run the material stage at all, but procedurally generate it in the custom shader |
In the above, "material" does preprocessing of textures, resulting in a `czm_modelMaterial`. This is mostly relevant for PBR, but even for UNLIT, the base color texture is handled.
## `VertexInput` struct
An automatically-generated GLSL struct that contains attributes.
```glsl
struct VertexInput {
// Processed attributes. See the Attributes Struct section below.
Attributes attributes;
// Feature IDs/Batch IDs. See the FeatureIds Struct section below.
FeatureIds featureIds;
// Metadata properties. See the Metadata Struct section below.
Metadata metadata;
// Metadata class properties. See the MetadataClass Struct section below.
MetadataClass metadataClass;
// Metadata statistics. See the Metadata Statistics Struct section below
MetadataStatistics metadataStatistics;
};
```
## `FragmentInput` struct
This struct is similar to `VertexInput`, but there are a few more automatic
variables for positions in various coordinate spaces.
```glsl
struct FragmentInput {
// Processed attribute values. See the Attributes Struct section below.
Attributes attributes;
// Feature IDs/Batch IDs. See the FeatureIds Struct section below.
FeatureIds featureIds;
// Metadata properties. See the Metadata Struct section below.
Metadata metadata;
// Metadata class properties. See the MetadataClass Struct section below.
MetadataClass metadataClass;
// Metadata statistics. See the Metadata Statistics Struct section below
MetadataStatistics metadataStatistics;
};
```
## `Attributes` Struct
The `Attributes` struct is dynamically generated given the variables used in
the custom shader and the attributes available in the primitive to render.
For example, if the user uses `fsInput.attributes.texCoord_0` in the shader,
the runtime will generate the code needed to supply this value from the
attribute `TEXCOORD_0` in the model (where available)
If a primitive does not have the attributes necessary to satisfy the custom
shader, a default value will be inferred where possible so the shader still
compiles. Otherwise, the custom vertex/fragment shader portion will be disabled
for that primitive.
The full list of built-in attributes are as follows. Some attributes have a set
index, which is one of `0, 1, 2, ...` (e.g. `texCoord_0`), these are denoted
with an `N`.
| Corresponding Attribute in Model | variable in shader | Type | Available in Vertex Shader? | Available in Fragment Shader? | Description |
| -------------------------------- | ------------------ | ------- | --------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `POSITION` | `positionMC` | `vec3` | Yes | Yes | Position in model coordinates |
| `POSITION` | `positionWC` | `vec3` | No | Yes | Position in world coordinates (WGS84 ECEF `(x, y, z)`). Low precision. |
| `POSITION` | `positionEC` | `vec3` | No | Yes | Position in eye coordinates. |
| `NORMAL` | `normalMC` | `vec3` | Yes | No | Unit-length normal vector in model coordinates. Only available in the vertex shader |
| `NORMAL` | `normalEC` | `vec3` | No | Yes | Unit-length normal vector in eye coordinates. Only available in the fragment shader |
| `TANGENT` | `tangentMC` | `vec3` | Yes | No | Unit-length tangent vector in model coordinates. This is always a `vec3`. For models that provide a `w` component, that is removed after computing the bitangent vector. |
| `TANGENT` | `tangentEC` | `vec3` | No | Yes | Unit-length tangent vector in eye coordinates. This is always a `vec3`. For models that provide a `w` component, that is removed after computing the bitangent vector. |
| `NORMAL` & `TANGENT` | `bitangentMC` | `vec3` | Yes | No | Unit-length bitangent vector in model coordinates. Only available when both normal and tangent vectors are available. |
| `NORMAL` & `TANGENT` | `bitangentEC` | `vec3` | No | Yes | Unit-length bitangent vector in eye coordinates. Only available when both normal and tangent vectors are available. |
| `TEXCOORD_N` | `texCoord_N` | `vec2` | Yes | Yes | `N`-th set of texture coordinates. |
| `COLOR_N` | `color_N` | `vec4` | Yes | Yes | `N`-th set of vertex colors. This is always a `vec4`; if the model does not specify an alpha value, it is assumed to be 1. |
| `JOINTS_N` | `joints_N` | `ivec4` | Yes | Yes | `N`-th set of joint indices |
| `WEIGHTS_N` | `weights_N` | `vec4` | Yes | Yes | `N`-th set of weights |
Custom attributes are also available, though they are renamed to use lowercase
letters and underscores. For example, an attribute called `_SURFACE_TEMPERATURE`
in the model would become `fsInput.attributes.surface_temperature` in the shader.
## `FeatureIds` struct
This struct is dynamically generated to gather all the various feature IDs into
a single collection, regardless of whether the value came from an attribute,
texture or varying.
Feature IDs are represented as a GLSL `int`, though in WebGL 1 this has a couple
limitations:
- Above `2^24`, values may have a loss of precision since WebGL 1 implements
`highp int` as a floating point value.
- Ideally the type would be `uint` but this is not available until WebGL 2
### 3D Tiles 1.0 Batch IDs
In 3D Tiles 1.0, the same concept of identifying features within a primitive
was called `BATCH_ID` or the legacy `_BATCHID`. These batch IDs are renamed
to a single feature ID, always with index 0:
- `vsInput.featureIds.featureId_0` (Vertex shader)
- `fsInput.featureIds.featureId_0` (Fragment shader)
### `EXT_mesh_features`/`EXT_instance_features` Feature IDs
When the glTF extensions `EXT_mesh_features` or `EXT_instance_features` are used,
feature IDs appear in two places:
1. Any glTF primitive can have a `featureIds` array. The `featureIds` array may
contain feature ID attributes, implicit feature ID attributes, and/or feature
ID textures. Regardless of the type of feature IDs, they all appear in the
custom shader as `(vsInput|fsInput).featureIds.featureId_N` where `N` is the
index of the feature IDs in the `featureIds` array.
2. Any glTF node with the `EXT_mesh_gpu_instancing` and `EXT_instance_features` may
define feature IDs. These may be feature ID attributes or implicit feature ID
attributes, but not feature ID textures. These will appear in the custom
shader as `(vsInput|fsInput).featureIds.instanceFeatureId_N` where `N` is the
index of the feature IDs in the `featureIds` array.
Furthermore, feature ID textures are only supported in the fragment shader.
If a set of feature IDs includes a `label` property (new in `EXT_mesh_features`),
that label will be available as an alias. For example, if `label: "alias"`, then
`(vsInput|fsInput).featureIds.alias` would be available in the shader along
with `featureId_N`.
For example, suppose we had a glTF primitive with the following feature IDs:
```jsonc
"nodes": [
{
"mesh": 0
"extensions": {
"EXT_mesh_gpu_instancing": {
"attributes": {
"TRANSLATION": 3,
"_FEATURE_ID_0": 4
}
},
"EXT_instance_features": {
"featureIds": [
{
// Default feature IDs (instance ID)
//
// Vertex Shader:
// vsInput.featureIds.instanceFeatureId_0 OR
// vsInput.featureIds.perInstance
// Fragment Shader:
// fsInput.featureIds.instanceFeatureId_0 OR
// fsInput.featureIds.perInstance
"label": "perInstance",
"propertyTable": 0
},
{
// Feature ID attribute. This corresponds to _FEATURE_ID_0 from the
// instancing extension above. Note that this is
// labeled as instanceFeatureId_1 since it is the second feature ID
// set in the featureIds array
//
// Vertex Shader: vsInput.featureIds.instanceFeatureId_1
// Fragment Shader: fsInput.featureIds.instanceFeatureId_1
//
// Since there is no label field, instanceFeatureId_1 must be used.
"propertyTable": 1,
"attribute": 0
},
]
}
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0,
"_FEATURE_ID_0": 1,
"_FEATURE_ID_1": 2
},
"extensions": {
"EXT_mesh_features": {
"featureIds": [
{
// Feature ID Texture
//
// Vertex Shader: (Not supported)
// Fragment Shader:
// fsInput.featureIds.featureId_0 OR
// fsInput.featureIds.texture
"label": "texture",
"propertyTable": 2,
"index": 0,
"texCoord": 0,
"channel": 0
},
{
// Default Feature IDs (vertex ID)
//
// Vertex Shader:
// vsInput.featureIds.featureId_1 OR
// vsInput.featureIds.perVertex
// Fragment Shader:
// fsInput.featureIds.featureId_1 OR
// fsInput.featureIds.perVertex
"label": "perVertex",
"propertyTable": 3,
},
{
// Feature ID Attribute (_FEATURE_ID_0). Note that this
// is labeled featureId_2 for its index in the featureIds
// array
//
// Vertex Shader: vsInput.featureIds.featureId_2
// Fragment Shader: fsInput.featureIds.featureId_2
//
// Since there is no label, featureId_2 must be used.
"propertyTable": 4,
"attribute": 0
},
{
// Feature ID Attribute (_FEATURE_ID_1). Note that this
// is labeled featureId_3 for its index in the featureIds
// array
//
// Vertex Shader: vsInput.featureIds.featureId_3
// Fragment Shader: fsInput.featureIds.featureId_3
"propertyTable": 5,
"attribute": 1
}
]
}
}
},
]
}
]
```
### Legacy `EXT_feature_metadata` Feature IDs
`EXT_feature_metadata` was an earlier draft of `EXT_mesh_features`. Though
the feature ID concepts have not changed much, the JSON structure is a little
different. In the older extension, `featureIdAttributes` and `featureIdTextures`
were stored separately. In this CesiumJS implementation, the feature attributes
and feature textures are concatenated into one list, essentially
`featureIds = featureIdAttributes.concat(featureIdTextures)`. Besides this
difference in the extension JSON, the feature ID sets are labeled the same
way as `EXT_mesh_features`, i.e.
- `(vsInput|fsInput).featureIds.featureId_N` corresponds to the `N`-th feature
ID set from the combined `featureIds` array from each primitive.
- `(vsInput|fsInput).featureIds.instanceFeatureId_N` corresponds to the `N`-th
feature ID set from the `featureIds` array from the node with the
`EXT_mesh_gpu_instancing` extension.
For comparison, here is the same example as in the previous section, translated
to the `EXT_feature_metadata` extension:
```jsonc
"nodes": [
{
"mesh": 0,
"extensions": {
"EXT_mesh_gpu_instancing": {
"attributes": {
"TRANSLATION": 3,
"_FEATURE_ID_0": 4
},
"extensions": {
"EXT_feature_metadata": {
"featureIdAttributes": [
{
// Feature ID attribute from implicit range
//
// Vertex Shader: vsInput.featureIds.instanceFeatureId_0
// Fragment Shader: fsInput.featureIds.instanceFeatureId_0
"featureTable": "perInstanceTable",
"featureIds": {
"constant": 0,
"divisor": 1
}
},
{
// Feature ID attribute. This corresponds to _FEATURE_ID_0 from the
// instancing extension above. Note that this is
// labeled as instanceFeatureId_1 since it is the second feature ID
// set in the featureIds array
//
// Vertex Shader: vsInput.featureIds.instanceFeatureId_1
// Fragment Shader: fsInput.featureIds.instanceFeatureId_1
"featureTable": "perInstanceGroupTable",
"featureIds": {
"attribute": "_FEATURE_ID_0"
}
}
],
}
}
}
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0,
"_FEATURE_ID_0": 1,
"_FEATURE_ID_1": 2
},
"extensions": {
"EXT_feature_metadata": {
"featureIdAttributes": [
{
// Implicit Feature ID attribute
//
// Vertex Shader: vsInput.featureIds.featureId_0
// Fragment Shader: fsInput.featureIds.featureId_0
"featureTable": "perFaceTable",
"featureIds": {
"constant": 0,
"divisor": 3
}
},
{
// Feature ID Attribute (_FEATURE_ID_0). Note that this
// is labeled featureId_1 for its index in the featureIds
// array
//
// Vertex Shader: vsInput.featureIds.featureId_1
// Fragment Shader: fsInput.featureIds.featureId_1
"featureTable": "perFeatureTable",
"featureIds": {
"attribute": "_FEATURE_ID_0"
}
},
{
// Feature ID Attribute (_FEATURE_ID_1). Note that this
// is labeled featureId_2 for its index in the featureIds
// array
//
// Vertex Shader: vsInput.featureIds.featureId_2
// Fragment Shader: fsInput.featureIds.featureId_2
"featureTable": "otherFeatureTable",
"featureIds": {
"attribute": "_FEATURE_ID_1"
}
}
],
"featureIdTextures": [
{
// Feature ID Texture. Note that this is labeled featureId_3
// since the list of feature ID textures is concatenated to the
// list of feature ID attributes
//
// Vertex Shader: (Not supported)
// Fragment Shader: fsInput.featureIds.featureId_3
"featureTable": "perTexelTable",
"featureIds": {
"texture": {
"texCoord": 0,
"index": 0
},
"channels": "r"
}
}
]
}
}
},
]
}
]
```
## `Metadata` struct
This struct contains the relevant metadata properties accessible to the
model from the
[`EXT_structural_metadata`](https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_structural_metadata)
glTF extension (or the older
[`EXT_feature_metadata`](https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_feature_metadata) extension).
The following types of metadata are currently supported:
- property attributes from the `EXT_structural_metadata` glTF extension.
- property textures from the `EXT_structural_metadata` glTF extension. Only
types with `componentType: UINT8` are currently supported.
Regardless of the source of metadata, the properties are collected into a single
struct by property ID. Consider the following metadata class:
```jsonc
"schema": {
"classes": {
"wall": {
"properties": {
"temperature": {
"name": "Surface Temperature",
"type": "SCALAR",
"componentType": "FLOAT32"
}
}
}
}
}
```
This will show up in the shader as the struct field as follows:
```glsl
struct Metadata {
float temperature;
}
```
Now the temperature can be accessed as `vsInput.metadata.temperature` or
`fsInput.metadata.temperature`.
### Normalized values
If the class property specifies `normalized: true`, the property will appear
in the shader as the appropriate floating point type (e.g. `float` or `vec3`).
All components will be between the range of `[0, 1]` (unsigned) or `[-1, 1]`
(signed).
For example,
```jsonc
"schema": {
"classes": {
"wall": {
"properties": {
// damage normalized between 0.0 and 1.0 though stored as a UINT8 in
// the glTF
"damageAmount": {
"name": "Wall damage (normalized)",
"type": "SCALAR",
"componentType": "UINT32",
"normalized": true
}
}
}
}
}
```
This will appear as a `float` value from 0.0 to 1.0, accessible via
`(vsInput|fsInput).metadata.damageAmount`
### Offset and scale
If the property provides an `offset` or `scale`, this is automatically applied
after normalization (when applicable). This is useful to pre-scale values into
a convenient range.
For example, consider taking a normalized temperature value and automatically
converting this to Celsius or Fahrenheit:
```jsonc
"schema": {
"classes": {
"wall": {
"properties": {
// scaled to the range [0, 100] in °C
"temperatureCelsius": {
"name": "Temperature (°C)",
"type": "SCALAR",
"componentType": "UINT32",
"normalized": true,
// offset defaults to 0, scale defaults to 1
"scale": 100
},
// scaled/shifted to the range [32, 212] in °F
"temperatureFahrenheit": {
"name": "Temperature (°C)",
"type": "SCALAR",
"componentType": "UINT32",
"normalized": true,
"offset": 32,
"scale": 180
}
}
}
}
}
```
In the shader, `(vsInput|fsInput).metadata.temperatureCelsius` will be a `float`
with a value between 0.0 and 100.0, while
`(vsInput|fsInput).metadata.temperatureFahrenheit` will be a `float` with a
range of `[32.0, 212.0]`.
### Property ID Sanitization
GLSL only supports alphanumeric identifiers, i.e. identifiers that do not
start with a number. Additionally, identifiers with consecutive
underscores (`__`), as well as identifiers with the `gl_` prefix, are
reserved in GLSL. To circumvent these limitations, the property IDs are
modified as follows:
1. Replace all sequences of non-alphanumeric characters with a single `_`.
2. Remove the reserved `gl_` prefix if present.
3. If the identifier begins with a digit (`[0-9]`), prefix with an `_`
Here are a couple examples of property ID and the resulting variable name in
the custom shader in the `(vsInput|fsInput).metadata` struct:
- `temperature ℃` -> `metadata.temperature_`
- `custom__property` -> `metadata.custom_property`
- `gl_customProperty` -> `metadata.customProperty`
- `12345` -> `metadata._12345`
- `gl_12345` -> `metadata._12345`
If the above results in an empty string or a name collision with other
property IDs, the behavior is undefined. For example:
- `✖️✖️✖️` maps to the empty string, so the behavior is undefined.
- Two properties with names `temperature ℃` and `temperature ℉` would both
map to `metadata.temperature`, so the behavior is undefined
When using the Point Cloud (`.pnts`) format, per-point properties are transcoded
as property attributes. These property IDs follow the same convention.
## `MetadataClass` struct
This struct contains constants for each metadata property, as defined
in the class schema.
Regardless of the source of metadata, the properties are collected into a single
struct by property ID. Consider the following metadata class:
```json
"schema": {
"classes": {
"wall": {
"properties": {
"temperature": {
"name": "Surface Temperature",
"type": "SCALAR",
"componentType": "FLOAT32",
"noData": -9999.0,
"default": 72.0,
"min": -40.0,
"max": 500.0,
}
}
}
}
}
```
This will show up in the shader in the struct field as follows:
```glsl
struct floatMetadataClass {
float noData;
float defaultValue; // 'default' is a reserved word in GLSL
float minValue; // 'min' is a reserved word in GLSL
float maxValue; // 'max' is a reserved word in GLSL
}
struct MetadataClass {
floatMetadataClass temperature;
}
```
The sub-struct for each property will be chosen such that the individual properties
(such as `noData` and `defaultValue`) will have the same type as the actual values of the
property.
Now the noData and default values can be accessed as follows in the vertex shader:
```glsl
float noData = vsInput.metadataClass.temperature.noData; // == -9999.0
float defaultTemp = vsInput.metadataClass.temperature.defaultValue; // == 72.0
float minTemp = vsInput.metadataClass.temperature.minValue; // == -40.0
float maxTemp = vsInput.metadataClass.temperature.maxValue; // == 500.0
```
or similarly from the `fsInput` struct in the fragment shader.
## `MetadataStatistics` struct
If the model was loaded from a [3D Tiles tileset](https://github.com/CesiumGS/3d-tiles/tree/main/specification), it may have statistics defined in the `statistics` property of the tileset.json. These will be available in a Custom Shader from the `MetadataStatistics` struct.
### Organization
Regardless of the source of the metadata, the properties are collected into a single source by property ID. Consider the following metadata class:
```json
"statistics": {
"classes": {
"exampleMetadataClass": {
"count": 29338,
"properties": {
"intensity": {
"min": 0.0,
"max": 0.6333333849906921,
"mean": 0.28973701532415364,
"median": 0.25416669249534607,
"standardDeviation": 0.18222664489583626,
"variance": 0.03320655011,
"sum": 8500.30455558002,
},
"classification": {
"occurrences": {
"MediumVegetation": 6876,
"Buildings": 22462
}
}
}
}
}
}
```
This will show up in the shader in the struct field as follows:
```glsl
struct floatMetadataStatistics {
float minValue; // 'min' is a reserved word in GLSL
float maxValue; // 'max' is a reserved word in GLSL
float mean;
float median;
float standardDeviation;
float variance;
float sum;
}
struct MetadataStatistics {
floatMetadataStatistics intensity;
}
```
The statistics values can be accessed from within the vertex shader as follows:
```glsl
float minValue = vsInput.metadataStatistics.intensity.minValue;
float mean = vsInput.metadataStatistics.intensity.mean;
```
or similarly from the `fsInput` struct in the fragment shader.
### Types
For `SCALAR`, `VECN`, and `MATN` type properties, the statistics struct fields `minValue`, `maxValue`, `median`, and `sum` will be declared with the same type as the metadata property they describe. The fields `mean`, `standardDeviation`, and `variance` are declared with a type of the same dimension as the metadata property, but with floating-point components.
For `ENUM` type metadata, the statistics struct for that property should contain an `occurrence` field, but this field is not yet implemented.
## `czm_modelVertexOutput` struct
This struct is built-in, see the [documentation comment](../../packages/engine/Source/Shaders/Builtin/Structs/modelVertexOutput.glsl).
This struct contains the output of the custom vertex shader. This includes:
- `positionMC` - The vertex position in model space coordinates. This struct
field can be used e.g. to perturb or animate vertices. It is initialized to
`vsInput.attributes.positionMC`. The custom shader may modify this, and the
result is used to compute `gl_Position`.
- `pointSize` - corresponds to `gl_PointSize`. This is only applied for models
rendered as `gl.POINTS`, and ignored otherwise. This overrides any point size
style that is applied to the model by `Cesium3DTileStyle`.
> **Implementation Note**: `positionMC` does not modify the primitive's bounding
> sphere. If vertices are moved outside the bounding sphere, the primitive may
> be unintentionally culled depending on the view frustum.
## `czm_modelMaterial` struct
This struct is a built-in, see the [documentation comment](../../packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl). This is similar to `czm_material` from the old Fabric system, but has slightly different fields as this one supports PBR lighting.
This struct serves as the basic input/output of the fragment shader pipeline stages. For example:
- the material stage produces a material
- the lighting stage takes in a material, computes lighting, and stores the result into `material.diffuse`
- Custom shaders (regardless of where in the pipeline it is) takes in a material (even if it's a material with default values) and modifies this.
### Material color space
Material colors (such as `material.diffuse`) are always in linear color space,
even when `lightingModel` is `LightingModel.UNLIT`.
When `scene.highDynamicRange` is `false`, the final computed color
(after custom shaders and lighting) is converted to `sRGB`.
## Using custom shaders for voxel rendering
Voxel rendering uses a subset of custom shader functionality.
The supplied shader is executed in the fragment shader _only_. (If a `vertexShaderText` is supplied, it will be ignored.)
The supplied fragment shader is executed at each step of the raymarching through the voxel.
The final rendered color at a given pixel will be an alpha-blended composition of the shader executions at all steps along the ray.
The `FragmentInput` struct in a voxel shader has some differences with other custom shaders:
- `Attributes`: Only `positionEC` and `normalEC` properties are supported.
- `FeatureIds` is not present in voxel shaders.
- `Metadata` is fully supported.
- `MetadataClass` is not present.
- `MetadataStatistics`: The only supported statistics are `min` and `max`.