UNPKG

cesium

Version:

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

506 lines (468 loc) 14.1 kB
import defined from "../../Core/defined.js"; import defaultValue from "../../Core/defaultValue.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; import AlphaMode from "../AlphaMode.js"; import LightingModel from "./LightingModel.js"; import MaterialStageFS from "../../Shaders/ModelExperimental/MaterialStageFS.js"; import Pass from "../../Renderer/Pass.js"; import Matrix3 from "../../Core/Matrix3.js"; import Cartesian3 from "../../Core/Cartesian3.js"; import Cartesian4 from "../../Core/Cartesian4.js"; import ModelComponents from "../ModelComponents.js"; import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; import ModelExperimentalUtility from "./ModelExperimentalUtility.js"; const Material = ModelComponents.Material; const MetallicRoughness = ModelComponents.MetallicRoughness; const SpecularGlossiness = ModelComponents.SpecularGlossiness; /** * The material pipeline stage processes textures and other uniforms needed * to render a primitive. This handles the following material types: * <ul> * <li>Basic glTF materials (PBR metallic roughness model)</li> * <li>The `KHR_materials_pbrSpecularGlossiness` glTF extension</li> * <li>The `KHR_materials_unlit` glTF extension</li> * </ul> * * @namespace MaterialPipelineStage * * @private */ const MaterialPipelineStage = {}; MaterialPipelineStage.name = "MaterialPipelineStage"; // Helps with debugging /** * Process a primitive. This modifies the following parts of the render * resources: * <ul> * <li>Modifies the shader to include the material processing stage</li> * <li>Modifies the shader to include additional uniforms for textures and other rendering details</li> * <li>Modifies the lighting options to set either PBR or unlit lighting</li> * <li>Sets the render state for back-face culling</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 */ MaterialPipelineStage.process = function ( renderResources, primitive, frameState ) { // gltf-pipeline automatically creates a default material so this will always // be defined. const material = primitive.material; const uniformMap = renderResources.uniformMap; const shaderBuilder = renderResources.shaderBuilder; // When textures are loaded incrementally, fall back to a default 1x1 texture const defaultTexture = frameState.context.defaultTexture; const defaultNormalTexture = frameState.context.defaultNormalTexture; const defaultEmissiveTexture = frameState.context.defaultEmissiveTexture; processMaterialUniforms( material, uniformMap, shaderBuilder, defaultTexture, defaultNormalTexture, defaultEmissiveTexture ); if (defined(material.specularGlossiness)) { processSpecularGlossinessUniforms( material, uniformMap, shaderBuilder, defaultTexture ); } else { processMetallicRoughnessUniforms( material, uniformMap, shaderBuilder, defaultTexture ); } // If the primitive does not have normals, fall back to unlit lighting. const hasNormals = ModelExperimentalUtility.getAttributeBySemantic( primitive, VertexAttributeSemantic.NORMAL ); const lightingOptions = renderResources.lightingOptions; if (material.unlit || !hasNormals) { lightingOptions.lightingModel = LightingModel.UNLIT; } else { lightingOptions.lightingModel = LightingModel.PBR; } // Configure back-face culling const model = renderResources.model; const cull = model.backFaceCulling && !material.doubleSided; const translucent = defined(model.color) && model.color.alpha < 1.0; renderResources.renderStateOptions.cull = { enabled: cull && !translucent, }; const alphaOptions = renderResources.alphaOptions; if (!defined(alphaOptions.alphaMode)) { alphaOptions.alphaMode = material.alphaMode; if (material.alphaMode === AlphaMode.BLEND) { alphaOptions.pass = Pass.TRANSLUCENT; } else if (material.alphaMode === AlphaMode.MASK) { alphaOptions.alphaCutoff = material.alphaCutoff; } } shaderBuilder.addFragmentLines([MaterialStageFS]); // Check if the model's debug wireframe is enabled. If so, add a define // to disable normal mapping. if (model.debugWireframe) { shaderBuilder.addDefine( "USE_WIREFRAME", undefined, ShaderDestination.FRAGMENT ); } }; /** * Process a single texture transformation and add it to the shader and uniform map. * * @param {ShaderBuilder} shaderBuilder The shader builder to modify * @param {Object.<String, Function>} uniformMap The uniform map to modify. * @param {ModelComponents.TextureReader} textureReader The texture to add to the shader * @param {String} uniformName The name of the sampler uniform such as <code>u_baseColorTexture</code> * @param {String} defineName The name of the texture for use in the defines, minus any prefix or suffix. For example, "BASE_COLOR" or "EMISSIVE" * * @private */ function processTextureTransform( shaderBuilder, uniformMap, textureReader, uniformName, defineName ) { // Add a define to enable the texture transformation code in the shader. const transformDefine = `HAS_${defineName}_TEXTURE_TRANSFORM`; shaderBuilder.addDefine( transformDefine, undefined, ShaderDestination.FRAGMENT ); // Add a uniform for the transformation matrix const transformUniformName = `${uniformName}Transform`; shaderBuilder.addUniform( "mat3", transformUniformName, ShaderDestination.FRAGMENT ); uniformMap[transformUniformName] = function () { return textureReader.transform; }; } /** * Process a single texture and add it to the shader and uniform map. * * @param {ShaderBuilder} shaderBuilder The shader builder to modify * @param {Object.<String, Function>} uniformMap The uniform map to modify. * @param {ModelComponents.TextureReader} textureReader The texture to add to the shader * @param {String} uniformName The name of the sampler uniform such as <code>u_baseColorTexture</code> * @param {String} defineName The name of the texture for use in the defines, minus any prefix or suffix. For example, "BASE_COLOR" or "EMISSIVE" * * @private */ function processTexture( shaderBuilder, uniformMap, textureReader, uniformName, defineName, defaultTexture ) { // Add a uniform for the texture itself shaderBuilder.addUniform( "sampler2D", uniformName, ShaderDestination.FRAGMENT ); uniformMap[uniformName] = function () { return defaultValue(textureReader.texture, defaultTexture); }; // Add a #define directive to enable using the texture in the shader const textureDefine = `HAS_${defineName}_TEXTURE`; shaderBuilder.addDefine(textureDefine, undefined, ShaderDestination.FRAGMENT); // Add a #define to tell the shader which texture coordinates varying to use. const texCoordIndex = textureReader.texCoord; const texCoordVarying = `v_texCoord_${texCoordIndex}`; const texCoordDefine = `TEXCOORD_${defineName}`; shaderBuilder.addDefine( texCoordDefine, texCoordVarying, ShaderDestination.FRAGMENT ); // Some textures have matrix transforms (e.g. for texture atlases). Add those // to the shader if present. const textureTransform = textureReader.transform; if ( defined(textureTransform) && !Matrix3.equals(textureTransform, Matrix3.IDENTITY) ) { processTextureTransform( shaderBuilder, uniformMap, textureReader, uniformName, defineName ); } } function processMaterialUniforms( material, uniformMap, shaderBuilder, defaultTexture, defaultNormalTexture, defaultEmissiveTexture ) { const emissiveTexture = material.emissiveTexture; if (defined(emissiveTexture)) { processTexture( shaderBuilder, uniformMap, emissiveTexture, "u_emissiveTexture", "EMISSIVE", defaultEmissiveTexture ); } const emissiveFactor = material.emissiveFactor; if ( defined(emissiveFactor) && !Cartesian3.equals(emissiveFactor, Material.DEFAULT_EMISSIVE_FACTOR) ) { shaderBuilder.addUniform( "vec3", "u_emissiveFactor", ShaderDestination.FRAGMENT ); uniformMap.u_emissiveFactor = function () { return material.emissiveFactor; }; shaderBuilder.addDefine( "HAS_EMISSIVE_FACTOR", undefined, ShaderDestination.FRAGMENT ); } const normalTexture = material.normalTexture; if (defined(normalTexture)) { processTexture( shaderBuilder, uniformMap, normalTexture, "u_normalTexture", "NORMAL", defaultNormalTexture ); } const occlusionTexture = material.occlusionTexture; if (defined(occlusionTexture)) { processTexture( shaderBuilder, uniformMap, occlusionTexture, "u_occlusionTexture", "OCCLUSION", defaultTexture ); } } function processSpecularGlossinessUniforms( material, uniformMap, shaderBuilder, defaultTexture ) { const specularGlossiness = material.specularGlossiness; shaderBuilder.addDefine( "USE_SPECULAR_GLOSSINESS", undefined, ShaderDestination.FRAGMENT ); const diffuseTexture = specularGlossiness.diffuseTexture; if (defined(diffuseTexture)) { processTexture( shaderBuilder, uniformMap, diffuseTexture, "u_diffuseTexture", "DIFFUSE", defaultTexture ); } const diffuseFactor = specularGlossiness.diffuseFactor; if ( defined(diffuseFactor) && !Cartesian4.equals(diffuseFactor, SpecularGlossiness.DEFAULT_DIFFUSE_FACTOR) ) { shaderBuilder.addUniform( "vec4", "u_diffuseFactor", ShaderDestination.FRAGMENT ); uniformMap.u_diffuseFactor = function () { return specularGlossiness.diffuseFactor; }; shaderBuilder.addDefine( "HAS_DIFFUSE_FACTOR", undefined, ShaderDestination.FRAGMENT ); } const specularGlossinessTexture = specularGlossiness.specularGlossinessTexture; if (defined(specularGlossinessTexture)) { processTexture( shaderBuilder, uniformMap, specularGlossinessTexture, "u_specularGlossinessTexture", "SPECULAR_GLOSSINESS", defaultTexture ); } const specularFactor = specularGlossiness.specularFactor; if ( defined(specularFactor) && !Cartesian3.equals( specularFactor, SpecularGlossiness.DEFAULT_SPECULAR_FACTOR ) ) { shaderBuilder.addUniform( "vec3", "u_specularFactor", ShaderDestination.FRAGMENT ); uniformMap.u_specularFactor = function () { return specularGlossiness.specularFactor; }; shaderBuilder.addDefine( "HAS_SPECULAR_FACTOR", undefined, ShaderDestination.FRAGMENT ); } const glossinessFactor = specularGlossiness.glossinessFactor; if ( defined(glossinessFactor) && glossinessFactor !== SpecularGlossiness.DEFAULT_GLOSSINESS_FACTOR ) { shaderBuilder.addUniform( "float", "u_glossinessFactor", ShaderDestination.FRAGMENT ); uniformMap.u_glossinessFactor = function () { return specularGlossiness.glossinessFactor; }; shaderBuilder.addDefine( "HAS_GLOSSINESS_FACTOR", undefined, ShaderDestination.FRAGMENT ); } } function processMetallicRoughnessUniforms( material, uniformMap, shaderBuilder, defaultTexture ) { const metallicRoughness = material.metallicRoughness; shaderBuilder.addDefine( "USE_METALLIC_ROUGHNESS", undefined, ShaderDestination.FRAGMENT ); const baseColorTexture = metallicRoughness.baseColorTexture; if (defined(baseColorTexture)) { processTexture( shaderBuilder, uniformMap, baseColorTexture, "u_baseColorTexture", "BASE_COLOR", defaultTexture ); } const baseColorFactor = metallicRoughness.baseColorFactor; if ( defined(baseColorFactor) && !Cartesian4.equals( baseColorFactor, MetallicRoughness.DEFAULT_BASE_COLOR_FACTOR ) ) { shaderBuilder.addUniform( "vec4", "u_baseColorFactor", ShaderDestination.FRAGMENT ); uniformMap.u_baseColorFactor = function () { return metallicRoughness.baseColorFactor; }; shaderBuilder.addDefine( "HAS_BASE_COLOR_FACTOR", undefined, ShaderDestination.FRAGMENT ); } const metallicRoughnessTexture = metallicRoughness.metallicRoughnessTexture; if (defined(metallicRoughnessTexture)) { processTexture( shaderBuilder, uniformMap, metallicRoughnessTexture, "u_metallicRoughnessTexture", "METALLIC_ROUGHNESS", defaultTexture ); } const metallicFactor = metallicRoughness.metallicFactor; if ( defined(metallicFactor) && metallicFactor !== MetallicRoughness.DEFAULT_METALLIC_FACTOR ) { shaderBuilder.addUniform( "float", "u_metallicFactor", ShaderDestination.FRAGMENT ); uniformMap.u_metallicFactor = function () { return metallicRoughness.metallicFactor; }; shaderBuilder.addDefine( "HAS_METALLIC_FACTOR", undefined, ShaderDestination.FRAGMENT ); } const roughnessFactor = metallicRoughness.roughnessFactor; if ( defined(roughnessFactor) && roughnessFactor !== MetallicRoughness.DEFAULT_ROUGHNESS_FACTOR ) { shaderBuilder.addUniform( "float", "u_roughnessFactor", ShaderDestination.FRAGMENT ); uniformMap.u_roughnessFactor = function () { return metallicRoughness.roughnessFactor; }; shaderBuilder.addDefine( "HAS_ROUGHNESS_FACTOR", undefined, ShaderDestination.FRAGMENT ); } } // Exposed for testing MaterialPipelineStage._processTexture = processTexture; MaterialPipelineStage._processTextureTransform = processTextureTransform; export default MaterialPipelineStage;