UNPKG

@cesium/engine

Version:

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

530 lines (464 loc) 18.1 kB
import ComponentDatatype from "../../Core/ComponentDatatype.js"; import defined from "../../Core/defined.js"; import DeveloperError from "../../Core/DeveloperError.js"; import PrimitiveType from "../../Core/PrimitiveType.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; import GeometryStageFS from "../../Shaders/Model/GeometryStageFS.js"; import GeometryStageVS from "../../Shaders/Model/GeometryStageVS.js"; import AttributeType from "../AttributeType.js"; import SceneMode from "../SceneMode.js"; import ModelType from "./ModelType.js"; import ModelUtility from "./ModelUtility.js"; import SelectedFeatureIdPipelineStage from "./SelectedFeatureIdPipelineStage.js"; import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; /** * The geometry pipeline stage processes the vertex attributes of a primitive. * * @namespace GeometryPipelineStage * * @private */ const GeometryPipelineStage = { name: "GeometryPipelineStage", // Helps with debugging STRUCT_ID_PROCESSED_ATTRIBUTES_VS: "ProcessedAttributesVS", STRUCT_ID_PROCESSED_ATTRIBUTES_FS: "ProcessedAttributesFS", STRUCT_NAME_PROCESSED_ATTRIBUTES: "ProcessedAttributes", FUNCTION_ID_INITIALIZE_ATTRIBUTES: "initializeAttributes", FUNCTION_SIGNATURE_INITIALIZE_ATTRIBUTES: "void initializeAttributes(out ProcessedAttributes attributes)", FUNCTION_ID_SET_DYNAMIC_VARYINGS_VS: "setDynamicVaryingsVS", FUNCTION_ID_SET_DYNAMIC_VARYINGS_FS: "setDynamicVaryingsFS", FUNCTION_SIGNATURE_SET_DYNAMIC_VARYINGS: "void setDynamicVaryings(inout ProcessedAttributes attributes)", }; /** * This pipeline stage processes the vertex attributes of a primitive, * adding attribute declarations to the shaders, adding attribute objects to the * render resources, and setting define flags as needed. * * Processes a primitive. This stage modifies the following parts of the render resources: * <ul> * <li> adds attribute and varying declarations for the vertex attributes in the vertex and fragment shaders * <li> creates the objects required to create VertexArrays * <li> sets the flag for point primitive types * </ul> * * If the scene is in either 2D or CV mode, this stage also: * <ul> * <li> adds a struct field for the 2D positions * <li> adds an additional attribute object and declaration if the node containing this primitive is not instanced * </ul> * * @param {PrimitiveRenderResources} renderResources The render resources for this primitive. * @param {ModelComponents.Primitive} primitive The primitive. * @param {FrameState} frameState The frame state. * * @private */ GeometryPipelineStage.process = function ( renderResources, primitive, frameState, ) { const { shaderBuilder, model } = renderResources; // These structs are similar, though the fragment shader version has a couple // additional fields. shaderBuilder.addStruct( GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_VS, "ProcessedAttributes", ShaderDestination.VERTEX, ); shaderBuilder.addStruct( GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_FS, "ProcessedAttributes", ShaderDestination.FRAGMENT, ); // The Feature struct is always added since it's required for compilation. // It may be unused if features are not present. shaderBuilder.addStruct( SelectedFeatureIdPipelineStage.STRUCT_ID_SELECTED_FEATURE, SelectedFeatureIdPipelineStage.STRUCT_NAME_SELECTED_FEATURE, ShaderDestination.BOTH, ); // This initialization function is only needed in the vertex shader, // it assigns the non-quantized attribute struct fields from the // physical attributes shaderBuilder.addFunction( GeometryPipelineStage.FUNCTION_ID_INITIALIZE_ATTRIBUTES, GeometryPipelineStage.FUNCTION_SIGNATURE_INITIALIZE_ATTRIBUTES, ShaderDestination.VERTEX, ); // Positions in other coordinate systems need more variables shaderBuilder.addVarying("vec3", "v_positionWC"); shaderBuilder.addVarying("vec3", "v_positionEC"); shaderBuilder.addStructField( GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_FS, "vec3", "positionWC", ); shaderBuilder.addStructField( GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_FS, "vec3", "positionEC", ); // Though they have identical signatures, the implementation is different // between vertex and fragment shaders. The VS stores attributes in // varyings, while the FS unpacks the varyings for use by other stages. shaderBuilder.addFunction( GeometryPipelineStage.FUNCTION_ID_SET_DYNAMIC_VARYINGS_VS, GeometryPipelineStage.FUNCTION_SIGNATURE_SET_DYNAMIC_VARYINGS, ShaderDestination.VERTEX, ); shaderBuilder.addFunction( GeometryPipelineStage.FUNCTION_ID_SET_DYNAMIC_VARYINGS_FS, GeometryPipelineStage.FUNCTION_SIGNATURE_SET_DYNAMIC_VARYINGS, ShaderDestination.FRAGMENT, ); // .pnts point clouds store sRGB color rather than linear color if (model.type === ModelType.TILE_PNTS) { shaderBuilder.addDefine( "HAS_SRGB_COLOR", undefined, ShaderDestination.FRAGMENT, ); } // Attributes, structs, and functions will need to be modified for 2D / CV. const use2D = frameState.mode !== SceneMode.SCENE3D && !frameState.scene3DOnly && model._projectTo2D; // If the model is instanced, the work for 2D projection will have been done // in InstancingPipelineStage. The attribute struct will be updated with // position2D, but nothing else should be modified. const instanced = defined(renderResources.runtimeNode.node.instances); // If the scene is in 3D or the model is instanced, the 2D position attribute // is not needed, so don't increment attributeIndex. const incrementIndexFor2D = use2D && !instanced; const length = primitive.attributes.length; for (let i = 0; i < length; i++) { const attribute = primitive.attributes[i]; const attributeLocationCount = AttributeType.getAttributeLocationCount( attribute.type, ); //>>includeStart('debug', pragmas.debug); if (!defined(attribute.buffer) && !defined(attribute.constant)) { throw new DeveloperError( "Attributes must be provided as a Buffer or constant value", ); } //>>includeEnd('debug'); const isPositionAttribute = attribute.semantic === VertexAttributeSemantic.POSITION; let index; if (attributeLocationCount > 1) { index = renderResources.attributeIndex; renderResources.attributeIndex += attributeLocationCount; } else if (isPositionAttribute && !incrementIndexFor2D) { index = 0; } else { index = renderResources.attributeIndex++; } processAttribute( renderResources, attribute, index, attributeLocationCount, use2D, instanced, ); } handleBitangents(shaderBuilder, primitive.attributes); if (primitive.primitiveType === PrimitiveType.POINTS) { shaderBuilder.addDefine("PRIMITIVE_TYPE_POINTS"); } shaderBuilder.addVertexLines(GeometryStageVS); shaderBuilder.addFragmentLines(GeometryStageFS); }; function processAttribute( renderResources, attribute, attributeIndex, attributeLocationCount, use2D, instanced, ) { const shaderBuilder = renderResources.shaderBuilder; const attributeInfo = ModelUtility.getAttributeInfo(attribute); // This indicates to only modify the resources for 2D if the model is // not instanced. const modifyFor2D = use2D && !instanced; if (attributeLocationCount > 1) { // Matrices are stored as multiple attributes, one per column vector. addMatrixAttributeToRenderResources( renderResources, attribute, attributeIndex, attributeLocationCount, ); } else { addAttributeToRenderResources( renderResources, attribute, attributeIndex, modifyFor2D, ); } addAttributeDeclaration(shaderBuilder, attributeInfo, modifyFor2D); addVaryingDeclaration(shaderBuilder, attributeInfo); // For common attributes like normals and tangents, the code is // already in GeometryStageVS, we just need to enable it. if (defined(attribute.semantic)) { addSemanticDefine(shaderBuilder, attribute); } // Dynamically generate GLSL code for the current attribute. // For 2D projection, the position2D field will always be added // to the attributes struct, even if the model is instanced. updateAttributesStruct(shaderBuilder, attributeInfo, use2D); updateInitializeAttributesFunction(shaderBuilder, attributeInfo, modifyFor2D); updateSetDynamicVaryingsFunction(shaderBuilder, attributeInfo); } function addSemanticDefine(shaderBuilder, attribute) { const { semantic, setIndex } = attribute; switch (semantic) { case VertexAttributeSemantic.NORMAL: shaderBuilder.addDefine("HAS_NORMALS"); break; case VertexAttributeSemantic.TANGENT: shaderBuilder.addDefine("HAS_TANGENTS"); break; case VertexAttributeSemantic.FEATURE_ID: // `_FEATURE_ID starts with an underscore so no need to double the // underscore. shaderBuilder.addDefine(`HAS${semantic}_${setIndex}`); break; case VertexAttributeSemantic.TEXCOORD: case VertexAttributeSemantic.COLOR: shaderBuilder.addDefine(`HAS_${semantic}_${setIndex}`); } } function addAttributeToRenderResources( renderResources, attribute, attributeIndex, modifyFor2D, ) { const { quantization, semantic, setIndex } = attribute; const { type, componentDatatype } = defined(quantization) ? quantization : attribute; if ( semantic === VertexAttributeSemantic.FEATURE_ID && setIndex >= renderResources.featureIdVertexAttributeSetIndex ) { renderResources.featureIdVertexAttributeSetIndex = setIndex + 1; } // The position attribute should always be in the first index. const isPositionAttribute = semantic === VertexAttributeSemantic.POSITION; const index = isPositionAttribute ? 0 : attributeIndex; const componentsPerAttribute = AttributeType.getNumberOfComponents(type); const vertexAttribute = { index: index, value: defined(attribute.buffer) ? undefined : attribute.constant, vertexBuffer: attribute.buffer, count: attribute.count, componentsPerAttribute: componentsPerAttribute, componentDatatype: componentDatatype, offsetInBytes: attribute.byteOffset, strideInBytes: attribute.byteStride, normalize: attribute.normalized, }; renderResources.attributes.push(vertexAttribute); if (!isPositionAttribute || !modifyFor2D) { return; } // Add an additional attribute for the projected positions in 2D / CV. const buffer2D = renderResources.runtimePrimitive.positionBuffer2D; const positionAttribute2D = { index: attributeIndex, vertexBuffer: buffer2D, count: attribute.count, componentsPerAttribute: componentsPerAttribute, componentDatatype: ComponentDatatype.FLOAT, // Projected positions will always be floats. offsetInBytes: 0, strideInBytes: undefined, normalize: attribute.normalized, }; renderResources.attributes.push(positionAttribute2D); } function addMatrixAttributeToRenderResources( renderResources, attribute, attributeIndex, columnCount, ) { const { quantization, normalized } = attribute; const { type, componentDatatype } = defined(quantization) ? quantization : attribute; // componentCount is either 4, 9 or 16 const componentCount = AttributeType.getNumberOfComponents(type); // componentsPerColumn is either 2, 3, or 4 const componentsPerColumn = componentCount / columnCount; const componentSizeInBytes = ComponentDatatype.getSizeInBytes(componentDatatype); const columnLengthInBytes = componentsPerColumn * componentSizeInBytes; // The stride between corresponding columns of two matrices is constant // regardless of where you start const strideInBytes = attribute.byteStride; for (let i = 0; i < columnCount; i++) { const offsetInBytes = attribute.byteOffset + i * columnLengthInBytes; // upload a single column vector. const columnAttribute = { index: attributeIndex + i, vertexBuffer: attribute.buffer, componentsPerAttribute: componentsPerColumn, componentDatatype: componentDatatype, offsetInBytes: offsetInBytes, strideInBytes: strideInBytes, normalize: normalized, }; renderResources.attributes.push(columnAttribute); } } function addVaryingDeclaration(shaderBuilder, attributeInfo) { const variableName = attributeInfo.variableName; let varyingName = `v_${variableName}`; let glslType; if (variableName === "normalMC") { // though the attribute is in model coordinates, the varying is // in eye coordinates. varyingName = "v_normalEC"; glslType = attributeInfo.glslType; } else if (variableName === "tangentMC") { // Tangent's glslType is vec4, but in the shader it is split into // vec3 tangent and vec3 bitangent glslType = "vec3"; // like normalMC, the varying is converted to eye coordinates varyingName = "v_tangentEC"; } else { glslType = attributeInfo.glslType; } shaderBuilder.addVarying(glslType, varyingName); } function addAttributeDeclaration(shaderBuilder, attributeInfo, modifyFor2D) { const semantic = attributeInfo.attribute.semantic; const variableName = attributeInfo.variableName; let attributeName; let glslType; if (attributeInfo.isQuantized) { attributeName = `a_quantized_${variableName}`; glslType = attributeInfo.quantizedGlslType; } else { attributeName = `a_${variableName}`; glslType = attributeInfo.glslType; } const isPosition = semantic === VertexAttributeSemantic.POSITION; if (isPosition) { shaderBuilder.setPositionAttribute(glslType, attributeName); } else { shaderBuilder.addAttribute(glslType, attributeName); } if (isPosition && modifyFor2D) { shaderBuilder.addAttribute("vec3", "a_position2D"); } } function updateAttributesStruct(shaderBuilder, attributeInfo, use2D) { const vsStructId = GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_VS; const fsStructId = GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_FS; const { variableName, glslType } = attributeInfo; if (variableName === "tangentMC") { // The w component of the tangent is only used for computing the bitangent, // so it can be separated from the other tangent components. shaderBuilder.addStructField(vsStructId, "vec3", "tangentMC"); shaderBuilder.addStructField(vsStructId, "float", "tangentSignMC"); // The tangent is in model coordinates in the vertex shader // but in eye space in the fragment coordinates shaderBuilder.addStructField(fsStructId, "vec3", "tangentEC"); } else if (variableName === "normalMC") { // Normals are in model coordinates in the vertex shader but in eye // coordinates in the fragment shader shaderBuilder.addStructField(vsStructId, "vec3", "normalMC"); shaderBuilder.addStructField(fsStructId, "vec3", "normalEC"); } else { shaderBuilder.addStructField(vsStructId, glslType, variableName); shaderBuilder.addStructField(fsStructId, glslType, variableName); } if (variableName === "positionMC" && use2D) { shaderBuilder.addStructField(vsStructId, "vec3", "position2D"); } } function updateInitializeAttributesFunction( shaderBuilder, attributeInfo, use2D, ) { const functionId = GeometryPipelineStage.FUNCTION_ID_INITIALIZE_ATTRIBUTES; const variableName = attributeInfo.variableName; // If the scene is in 2D / CV mode, this line should always be added // regardless of whether the data is quantized. const use2DPosition = variableName === "positionMC" && use2D; if (use2DPosition) { const line = "attributes.position2D = a_position2D;"; shaderBuilder.addFunctionLines(functionId, [line]); } if (attributeInfo.isQuantized) { // Skip initialization, it will be handled in the dequantization stage. return; } const lines = []; if (variableName === "tangentMC") { lines.push("attributes.tangentMC = a_tangentMC.xyz;"); lines.push("attributes.tangentSignMC = a_tangentMC.w;"); } else { lines.push(`attributes.${variableName} = a_${variableName};`); } shaderBuilder.addFunctionLines(functionId, lines); } function updateSetDynamicVaryingsFunction(shaderBuilder, attributeInfo) { const { semantic, setIndex } = attributeInfo.attribute; if (defined(semantic) && !defined(setIndex)) { // positions, normals, and tangents are handled statically in // GeometryStageVS return; } // In the vertex shader, we want things like // v_texCoord_1 = attributes.texCoord_1; let functionId = GeometryPipelineStage.FUNCTION_ID_SET_DYNAMIC_VARYINGS_VS; const variableName = attributeInfo.variableName; let line = `v_${variableName} = attributes.${variableName};`; shaderBuilder.addFunctionLines(functionId, [line]); // In the fragment shader, we do the opposite: // attributes.texCoord_1 = v_texCoord_1; functionId = GeometryPipelineStage.FUNCTION_ID_SET_DYNAMIC_VARYINGS_FS; line = `attributes.${variableName} = v_${variableName};`; shaderBuilder.addFunctionLines(functionId, [line]); } function handleBitangents(shaderBuilder, attributes) { let hasNormals = false; let hasTangents = false; for (let i = 0; i < attributes.length; i++) { const attribute = attributes[i]; if (attribute.semantic === VertexAttributeSemantic.NORMAL) { hasNormals = true; } else if (attribute.semantic === VertexAttributeSemantic.TANGENT) { hasTangents = true; } } // Bitangents are only defined if we have normals and tangents if (!hasNormals || !hasTangents) { return; } shaderBuilder.addDefine("HAS_BITANGENTS"); shaderBuilder.addVarying("vec3", "v_bitangentEC"); shaderBuilder.addStructField( GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_VS, "vec3", "bitangentMC", ); shaderBuilder.addStructField( GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_FS, "vec3", "bitangentEC", ); } export default GeometryPipelineStage;