import defined from "../../Core/defined.js"; import PrimitiveType from "../../Core/PrimitiveType.js"; import AttributeType from "../AttributeType.js"; import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; import GeometryStageFS from "../../Shaders/ModelExperimental/GeometryStageFS.js"; import GeometryStageVS from "../../Shaders/ModelExperimental/GeometryStageVS.js"; import FeatureIdPipelineStage from "./FeatureIdPipelineStage.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; import ModelExperimentalUtility from "./ModelExperimentalUtility.js"; /** * The geometry pipeline stage processes the vertex attributes of a primitive. * * @namespace GeometryPipelineStage * * @private */ var GeometryPipelineStage = {}; GeometryPipelineStage.name = "GeometryPipelineStage"; // Helps with debugging GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_VS = "ProcessedAttributesVS"; GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_FS = "ProcessedAttributesFS"; GeometryPipelineStage.STRUCT_NAME_PROCESSED_ATTRIBUTES = "ProcessedAttributes"; GeometryPipelineStage.FUNCTION_ID_INITIALIZE_ATTRIBUTES = "initializeAttributes"; GeometryPipelineStage.FUNCTION_SIGNATURE_INITIALIZE_ATTRIBUTES = "void initializeAttributes(out ProcessedAttributes attributes)"; GeometryPipelineStage.FUNCTION_ID_SET_DYNAMIC_VARYINGS_VS = "setDynamicVaryingsVS"; GeometryPipelineStage.FUNCTION_ID_SET_DYNAMIC_VARYINGS_FS = "setDynamicVaryingsFS"; GeometryPipelineStage.FUNCTION_SIGNATURE_SET_DYNAMIC_VARYINGS = "void setDynamicVaryings(inout ProcessedAttributes attributes)"; /** * This pipeline stage processes the vertex attributes of a primitive, adding the attribute declarations to the shaders, * the attribute objects to the render resources and setting the flags as needed. * * Processes a primitive. This stage modifies the following parts of the render resources: * * * @param {PrimitiveRenderResources} renderResources The render resources for this primitive. * @param {ModelComponents.Primitive} primitive The primitive. * * @private */ GeometryPipelineStage.process = function (renderResources, primitive) { var shaderBuilder = renderResources.shaderBuilder; // 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( FeatureIdPipelineStage.STRUCT_ID_FEATURE, FeatureIdPipelineStage.STRUCT_NAME_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 ); var index; for (var i = 0; i < primitive.attributes.length; i++) { var attribute = primitive.attributes[i]; if (attribute.semantic === VertexAttributeSemantic.POSITION) { index = 0; } else { // The attribute index is taken from the node render resources, which may have added some attributes of its own. index = renderResources.attributeIndex++; } processAttribute(renderResources, attribute, index); } 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) { var shaderBuilder = renderResources.shaderBuilder; var attributeInfo = ModelExperimentalUtility.getAttributeInfo(attribute); addAttributeToRenderResources(renderResources, attribute, attributeIndex); addAttributeDeclaration(shaderBuilder, attributeInfo); addVaryingDeclaration(shaderBuilder, attributeInfo); // For common attributes like positions, normals and tangents, the code is // already in GeometryStageVS, we just need to enable it if (defined(attribute.semantic)) { addSemanticDefine(shaderBuilder, attribute); } // Some GLSL code must be dynamically generated updateAttributesStruct(shaderBuilder, attributeInfo); updateInitialzeAttributesFunction(shaderBuilder, attributeInfo); updateSetDynamicVaryingsFunction(shaderBuilder, attributeInfo); } function addSemanticDefine(shaderBuilder, attribute) { var semantic = attribute.semantic; var setIndex = attribute.setIndex; switch (semantic) { case VertexAttributeSemantic.NORMAL: shaderBuilder.addDefine("HAS_NORMALS"); break; case VertexAttributeSemantic.TANGENT: shaderBuilder.addDefine("HAS_TANGENTS"); break; case VertexAttributeSemantic.FEATURE_ID: case VertexAttributeSemantic.TEXCOORD: case VertexAttributeSemantic.COLOR: shaderBuilder.addDefine("HAS_" + semantic + "_" + setIndex); } } function addAttributeToRenderResources( renderResources, attribute, attributeIndex ) { var quantization = attribute.quantization; var type; var componentDatatype; if (defined(quantization)) { type = quantization.type; componentDatatype = quantization.componentDatatype; } else { type = attribute.type; componentDatatype = attribute.componentDatatype; } var semantic = attribute.semantic; var setIndex = attribute.setIndex; if ( semantic === VertexAttributeSemantic.FEATURE_ID && setIndex >= renderResources.featureIdVertexAttributeSetIndex ) { renderResources.featureIdVertexAttributeSetIndex = setIndex + 1; } var vertexAttribute = { index: attributeIndex, value: defined(attribute.buffer) ? undefined : attribute.constant, vertexBuffer: attribute.buffer, componentsPerAttribute: AttributeType.getNumberOfComponents(type), componentDatatype: componentDatatype, offsetInBytes: attribute.byteOffset, strideInBytes: attribute.byteStride, normalize: attribute.normalized, }; renderResources.attributes.push(vertexAttribute); } function addVaryingDeclaration(shaderBuilder, attributeInfo) { var variableName = attributeInfo.variableName; var varyingName = "v_" + variableName; var 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) { var semantic = attributeInfo.attribute.semantic; var variableName = attributeInfo.variableName; var attributeName; var glslType; if (attributeInfo.isQuantized) { attributeName = "a_quantized_" + variableName; glslType = attributeInfo.quantizedGlslType; } else { attributeName = "a_" + variableName; glslType = attributeInfo.glslType; } if (semantic === VertexAttributeSemantic.POSITION) { shaderBuilder.setPositionAttribute(glslType, attributeName); } else { shaderBuilder.addAttribute(glslType, attributeName); } } function updateAttributesStruct(shaderBuilder, attributeInfo) { var vsStructId = GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_VS; var fsStructId = GeometryPipelineStage.STRUCT_ID_PROCESSED_ATTRIBUTES_FS; var variableName = attributeInfo.variableName; if (variableName === "color") { // Always declare color as a vec4, even if it was a vec3 shaderBuilder.addStructField(vsStructId, "vec4", "color"); shaderBuilder.addStructField(fsStructId, "vec4", "color"); } else if (variableName === "tangentMC") { // declare tangent as vec3, the w component is only used for computing // the bitangent. Also, the tangent is in model coordinates in the vertex // shader but in eye space in the fragment coordinates shaderBuilder.addStructField(vsStructId, "vec3", "tangentMC"); 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, attributeInfo.glslType, variableName ); shaderBuilder.addStructField( fsStructId, attributeInfo.glslType, variableName ); } } function updateInitialzeAttributesFunction(shaderBuilder, attributeInfo) { if (attributeInfo.isQuantized) { // Skip initialization, it will be handled in the dequantization stage. return; } var functionId = GeometryPipelineStage.FUNCTION_ID_INITIALIZE_ATTRIBUTES; var variableName = attributeInfo.variableName; var line; if (variableName === "tangentMC") { line = "attributes.tangentMC = a_tangentMC.xyz;"; } else { line = "attributes." + variableName + " = a_" + variableName + ";"; } shaderBuilder.addFunctionLines(functionId, [line]); } function updateSetDynamicVaryingsFunction(shaderBuilder, attributeInfo) { var semantic = attributeInfo.attribute.semantic; var setIndex = attributeInfo.attribute.setIndex; 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; var functionId = GeometryPipelineStage.FUNCTION_ID_SET_DYNAMIC_VARYINGS_VS; var variableName = attributeInfo.variableName; var 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) { var hasNormals = false; var hasTangents = false; for (var i = 0; i < attributes.length; i++) { var 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"); // compute the bitangent according to the formula in the glTF spec shaderBuilder.addFunctionLines( GeometryPipelineStage.FUNCTION_ID_INITIALIZE_ATTRIBUTES, [ "attributes.bitangentMC = normalize(cross(a_normalMC, a_tangentMC.xyz) * a_tangentMC.w);", ] ); 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;