| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- // @ts-check
-
- import Color from "../Core/Color.js";
- import createColorRamp from "../Core/createColorRamp.js";
- import CustomShader from "./Model/CustomShader.js";
- import defined from "../Core/defined.js";
- import MetadataComponentType from "./MetadataComponentType.js";
- import MetadataType from "./MetadataType.js";
- import TextureUniform from "./Model/TextureUniform.js";
- import UniformType from "./Model/UniformType.js";
-
- /** @import VoxelProvider from "./VoxelProvider.js"; */
-
- /**
- * Builds a custom shader for a voxel primitive based on information
- * from the VoxelProvider about the metadata properties.
- *
- * Supports scalar float properties, or vec4 properties with float components.
- *
- * @function
- *
- * @param {VoxelProvider} provider The VoxelProvider for the primitive.
- * @returns {CustomShader|undefined} A custom shader for the primitive, or undefined if a shader could not be constructed.
- *
- * @private
- */
- function buildVoxelCustomShader(provider) {
- const { names, types, componentTypes, minimumValues, maximumValues } =
- provider;
-
- // Loop over metadata properties and build a shader for the first property
- // that has a supported type and component type.
- for (let i = 0; i < names.length; i++) {
- if (!rangeIsValid(minimumValues?.[i], maximumValues?.[i])) {
- continue;
- }
- const shader = constructMetadataShader(
- names[i],
- types[i],
- componentTypes[i],
- minimumValues[i],
- maximumValues[i],
- );
- if (defined(shader)) {
- return shader;
- }
- }
- }
-
- /**
- * Check if a range of values is valid for constructing a meaningful shader.
- *
- * @param {number[]} minimumValue The minimum value of the range.
- * @param {number[]} maximumValue The maximum value of the range.
- * @returns {boolean} True if the range is valid, false otherwise.
- *
- * @private
- */
- function rangeIsValid(minimumValue, maximumValue) {
- if (!Array.isArray(minimumValue) || !Array.isArray(maximumValue)) {
- return false;
- }
- const minimumComponent = Math.min(...minimumValue);
- const maximumComponent = Math.max(...maximumValue);
- return minimumComponent < maximumComponent;
- }
-
- /**
- * Construct a metadata shader for a single metadata property.
- *
- * @param {string} name The name of the metadata property.
- * @param {MetadataType} type The type of the metadata property.
- * @param {MetadataComponentType} componentType The component type of the metadata property.
- * @param {number[]} minimumValue The minimum value of the metadata property.
- * @param {number[]} maximumValue The maximum value of the metadata property.
- * @returns {CustomShader|undefined} A custom shader for the metadata property, or undefined if a shader could not be constructed.
- *
- * @private
- */
- function constructMetadataShader(
- name,
- type,
- componentType,
- minimumValue,
- maximumValue,
- ) {
- if (
- type === MetadataType.VEC4 &&
- componentType === MetadataComponentType.FLOAT32
- ) {
- return buildColorShader(name, minimumValue, maximumValue);
- } else if (
- type === MetadataType.SCALAR &&
- componentType === MetadataComponentType.FLOAT32
- ) {
- return buildColorMapShader(name, minimumValue, maximumValue);
- }
- }
-
- /**
- * Build a color shader for a metadata property of type VEC4 and component type FLOAT32.
- *
- * @param {string} name The name of the metadata property.
- * @param {number[]} minimumValue The minimum value of the metadata property.
- * @param {number[]} maximumValue The maximum value of the metadata property.
- * @returns {CustomShader|undefined} A custom shader for the metadata property, or undefined if a shader could not be constructed.
- *
- * @private
- */
- function buildColorShader(name, minimumValue, maximumValue) {
- // Check if the values are in a range that would be meaningful to interpret as colors.
- const minComponent = Math.min(...minimumValue);
- const maxComponent = Math.max(...maximumValue);
- if (minComponent < 0 || maxComponent > 1) {
- // The data is out of the range expected for colors, so a color shader will be meaningless.
- return;
- }
-
- return new CustomShader({
- fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
- {
- material.diffuse = fsInput.metadata.${name}.rgb;
- material.alpha = fsInput.metadata.${name}.a;
- }`,
- });
- }
-
- /**
- * Build a color map shader for a metadata property of type SCALAR and component type FLOAT32.
- *
- * @param {string} name The name of the metadata property.
- * @param {number[]} minimumValue The minimum value of the metadata property.
- * @param {number[]} maximumValue The maximum value of the metadata property.
- * @returns {CustomShader} A custom shader for the metadata property.
- *
- * @private
- */
- function buildColorMapShader(name, minimumValue, maximumValue) {
- // Perceptually uniform color map similar to the "viridis" color map used in scientific visualization.
- const colorMapWidth = 128;
- const colorMap = createColorRamp(
- [
- new Color(0.267, 0.004, 0.329),
- new Color(0.282, 0.14, 0.457),
- new Color(0.254, 0.265, 0.53),
- new Color(0.207, 0.372, 0.553),
- new Color(0.164, 0.471, 0.558),
- new Color(0.128, 0.567, 0.551),
- new Color(0.135, 0.659, 0.518),
- new Color(0.194, 0.741, 0.443),
- new Color(0.282, 0.819, 0.369),
- new Color(0.396, 0.898, 0.301),
- new Color(0.538, 0.965, 0.236),
- new Color(0.741, 0.998, 0.149),
- new Color(0.993, 1.0, 0.144),
- ],
- colorMapWidth,
- );
-
- const textureUniform = new TextureUniform({
- typedArray: colorMap,
- width: colorMapWidth,
- height: 1,
- });
-
- return new CustomShader({
- uniforms: {
- u_colorMap: {
- type: UniformType.SAMPLER_2D,
- value: textureUniform,
- },
- // The starting values are also available in the shader from the metadata statistics.
- // But by using uniforms, we enable later dynamic changes to the range.
- u_minimumValue: {
- type: UniformType.FLOAT,
- value: minimumValue[0],
- },
- u_maximumValue: {
- type: UniformType.FLOAT,
- value: maximumValue[0],
- },
- },
- fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
- {
- float value = fsInput.metadata.${name};
- vec3 voxelNormal = fsInput.attributes.normalEC;
- float diffuse = max(0.0, dot(voxelNormal, czm_lightDirectionEC));
- float lighting = 0.5 + 0.5 * diffuse;
-
- if (value >= u_minimumValue && value <= u_maximumValue) {
- float lerp = (value - u_minimumValue) / (u_maximumValue - u_minimumValue);
- material.diffuse = texture(u_colorMap, vec2(lerp, 0.5)).rgb * lighting;
- material.alpha = 1.0;
- }
- }`,
- });
- }
-
- export default buildVoxelCustomShader;
|