UNPKG

cesium

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

326 lines (285 loc) 11 kB
import defined from "../../Core/defined.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; import MetadataStageFS from "../../Shaders/ModelExperimental/MetadataStageFS.js"; import MetadataStageVS from "../../Shaders/ModelExperimental/MetadataStageVS.js"; import ModelExperimentalUtility from "./ModelExperimentalUtility.js"; /** * The metadata pipeline stage processes metadata properties from * EXT_structural_metadata and inserts them into a struct in the shader. * This struct will be used by {@link CustomShaderPipelineStage} to allow the * user to access metadata using {@link CustomShader} * * @namespace MetadataPipelineStage * * @private */ const MetadataPipelineStage = {}; MetadataPipelineStage.name = "MetadataPipelineStage"; MetadataPipelineStage.STRUCT_ID_METADATA_VS = "MetadataVS"; MetadataPipelineStage.STRUCT_ID_METADATA_FS = "MetadataFS"; MetadataPipelineStage.STRUCT_NAME_METADATA = "Metadata"; MetadataPipelineStage.FUNCTION_ID_INITIALIZE_METADATA_VS = "initializeMetadataVS"; MetadataPipelineStage.FUNCTION_ID_INITIALIZE_METADATA_FS = "initializeMetadataFS"; MetadataPipelineStage.FUNCTION_SIGNATURE_INITIALIZE_METADATA = "void initializeMetadata(out Metadata metadata, ProcessedAttributes attributes)"; MetadataPipelineStage.FUNCTION_ID_SET_METADATA_VARYINGS = "setMetadataVaryings"; MetadataPipelineStage.FUNCTION_SIGNATURE_SET_METADATA_VARYINGS = "void setMetadataVaryings()"; /** * Process a primitive. This modifies the following parts of the render * resources: * <ul> * <li>Adds a Metadata struct to the shader</li> * <li>If the primitive has structural metadata, properties are added to the Metadata struct</li> * <li>dynamic functions are added to the shader to initialize the metadata properties</li> * <li>Adds uniforms for property textures to the uniform map as needed</li> * <li>Adds uniforms for offset/scale to the uniform map as needed</li> * </ul> * @param {PrimitiveRenderResources} renderResources The render resources for the primitive * @param {ModelComponents.Primitive} primitive The primitive to be rendered * @param {FrameState} frameState The frame state * @private */ MetadataPipelineStage.process = function ( renderResources, primitive, frameState ) { const shaderBuilder = renderResources.shaderBuilder; // Always declare structs, even if not used declareStructsAndFunctions(shaderBuilder); shaderBuilder.addVertexLines([MetadataStageVS]); shaderBuilder.addFragmentLines([MetadataStageFS]); const structuralMetadata = renderResources.model.structuralMetadata; if (!defined(structuralMetadata)) { return; } processPropertyAttributes(renderResources, primitive, structuralMetadata); processPropertyTextures(renderResources, structuralMetadata); }; function declareStructsAndFunctions(shaderBuilder) { // Declare the Metadata struct. shaderBuilder.addStruct( MetadataPipelineStage.STRUCT_ID_METADATA_VS, MetadataPipelineStage.STRUCT_NAME_METADATA, ShaderDestination.VERTEX ); shaderBuilder.addStruct( MetadataPipelineStage.STRUCT_ID_METADATA_FS, MetadataPipelineStage.STRUCT_NAME_METADATA, ShaderDestination.FRAGMENT ); // declare the initializeMetadata() function. The details may differ // between vertex and fragment shader shaderBuilder.addFunction( MetadataPipelineStage.FUNCTION_ID_INITIALIZE_METADATA_VS, MetadataPipelineStage.FUNCTION_SIGNATURE_INITIALIZE_METADATA, ShaderDestination.VERTEX ); shaderBuilder.addFunction( MetadataPipelineStage.FUNCTION_ID_INITIALIZE_METADATA_FS, MetadataPipelineStage.FUNCTION_SIGNATURE_INITIALIZE_METADATA, ShaderDestination.FRAGMENT ); // declare the setMetadataVaryings() function in the vertex shader only. shaderBuilder.addFunction( MetadataPipelineStage.FUNCTION_ID_SET_METADATA_VARYINGS, MetadataPipelineStage.FUNCTION_SIGNATURE_SET_METADATA_VARYINGS, ShaderDestination.VERTEX ); } function processPropertyAttributes( renderResources, primitive, structuralMetadata ) { const propertyAttributes = structuralMetadata.propertyAttributes; if (!defined(propertyAttributes)) { return; } for (let i = 0; i < propertyAttributes.length; i++) { const propertyAttribute = propertyAttributes[i]; const properties = propertyAttribute.properties; for (const propertyId in properties) { if (properties.hasOwnProperty(propertyId)) { const property = properties[propertyId]; // Get information about the attribute the same way as the // GeometryPipelineStage to ensure we have the correct GLSL type and // variable name. const modelAttribute = ModelExperimentalUtility.getAttributeByName( primitive, property.attribute ); const attributeInfo = ModelExperimentalUtility.getAttributeInfo( modelAttribute ); addPropertyAttributeProperty( renderResources, attributeInfo, propertyId, property ); } } } } function addPropertyAttributeProperty( renderResources, attributeInfo, propertyId, property ) { const metadataVariable = sanitizeGlslIdentifier(propertyId); const attributeVariable = attributeInfo.variableName; // in WebGL 1, attributes must have floating point components, so it's safe // to assume here that the types will match. Even if the property was // normalized, this is handled at upload time, not in the shader. const glslType = attributeInfo.glslType; const shaderBuilder = renderResources.shaderBuilder; // declare the struct field, e.g. // struct Metadata { // float property; // } shaderBuilder.addStructField( MetadataPipelineStage.STRUCT_ID_METADATA_VS, glslType, metadataVariable ); shaderBuilder.addStructField( MetadataPipelineStage.STRUCT_ID_METADATA_FS, glslType, metadataVariable ); let unpackedValue = `attributes.${attributeVariable}`; // handle offset/scale transform. This wraps the GLSL expression with // the czm_valueTransform() call. if (property.hasValueTransform) { unpackedValue = addValueTransformUniforms(unpackedValue, { renderResources: renderResources, glslType: glslType, metadataVariable: metadataVariable, shaderDestination: ShaderDestination.BOTH, offset: property.offset, scale: property.scale, }); } // assign the result to the metadata struct property. // e.g. metadata.property = unpackingSteps(attributes.property); const initializationLine = `metadata.${metadataVariable} = ${unpackedValue};`; shaderBuilder.addFunctionLines( MetadataPipelineStage.FUNCTION_ID_INITIALIZE_METADATA_VS, [initializationLine] ); shaderBuilder.addFunctionLines( MetadataPipelineStage.FUNCTION_ID_INITIALIZE_METADATA_FS, [initializationLine] ); } function processPropertyTextures(renderResources, structuralMetadata) { const propertyTextures = structuralMetadata.propertyTextures; if (!defined(propertyTextures)) { return; } for (let i = 0; i < propertyTextures.length; i++) { const propertyTexture = propertyTextures[i]; const properties = propertyTexture.properties; for (const propertyId in properties) { if (properties.hasOwnProperty(propertyId)) { const property = properties[propertyId]; if (property.isGpuCompatible()) { addPropertyTextureProperty(renderResources, propertyId, property); } } } } } function addPropertyTextureProperty(renderResources, propertyId, property) { // Property texture properties may share the same physical texture, so only // add the texture uniform the first time we encounter it. const textureReader = property.textureReader; const textureIndex = textureReader.index; const textureUniformName = `u_propertyTexture_${textureIndex}`; if (!renderResources.uniformMap.hasOwnProperty(textureUniformName)) { addPropertyTextureUniform( renderResources, textureUniformName, textureReader ); } const metadataVariable = sanitizeGlslIdentifier(propertyId); const glslType = property.getGlslType(); const shaderBuilder = renderResources.shaderBuilder; shaderBuilder.addStructField( MetadataPipelineStage.STRUCT_ID_METADATA_FS, glslType, metadataVariable ); const texCoord = textureReader.texCoord; const texCoordVariable = `attributes.texCoord_${texCoord}`; const channels = textureReader.channels; let unpackedValue = `texture2D(${textureUniformName}, ${texCoordVariable}).${channels}`; // Some types need an unpacking step or two. For example, since texture reads // are always normalized, UINT8 (not normalized) properties need to be // un-normalized in the shader. unpackedValue = property.unpackInShader(unpackedValue); // handle offset/scale transform. This wraps the GLSL expression with // the czm_valueTransform() call. if (property.hasValueTransform) { unpackedValue = addValueTransformUniforms(unpackedValue, { renderResources: renderResources, glslType: glslType, metadataVariable: metadataVariable, shaderDestination: ShaderDestination.FRAGMENT, offset: property.offset, scale: property.scale, }); } const initializationLine = `metadata.${metadataVariable} = ${unpackedValue};`; shaderBuilder.addFunctionLines( MetadataPipelineStage.FUNCTION_ID_INITIALIZE_METADATA_FS, [initializationLine] ); } function addPropertyTextureUniform( renderResources, uniformName, textureReader ) { const shaderBuilder = renderResources.shaderBuilder; shaderBuilder.addUniform( "sampler2D", uniformName, ShaderDestination.FRAGMENT ); const uniformMap = renderResources.uniformMap; uniformMap[uniformName] = function () { return textureReader.texture; }; } function addValueTransformUniforms(valueExpression, options) { const metadataVariable = options.metadataVariable; const offsetUniformName = `u_${metadataVariable}_offset`; const scaleUniformName = `u_${metadataVariable}_scale`; const renderResources = options.renderResources; const shaderBuilder = renderResources.shaderBuilder; const glslType = options.glslType; const shaderDestination = options.shaderDestination; shaderBuilder.addUniform(glslType, offsetUniformName, shaderDestination); shaderBuilder.addUniform(glslType, scaleUniformName, shaderDestination); const uniformMap = renderResources.uniformMap; uniformMap[offsetUniformName] = function () { return options.offset; }; uniformMap[scaleUniformName] = function () { return options.scale; }; return `czm_valueTransform(${offsetUniformName}, ${scaleUniformName}, ${valueExpression})`; } function sanitizeGlslIdentifier(identifier) { // for use in the shader, the property ID must be a valid GLSL identifier, // so replace invalid characters with _ return identifier.replaceAll(/[^_a-zA-Z0-9]+/g, "_"); } export default MetadataPipelineStage;