UNPKG

cesium

Version:

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

777 lines (701 loc) 33.9 kB
define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/defined', '../Core/defineProperties', '../Core/Matrix2', '../Core/Matrix3', '../Core/Matrix4', '../Core/Quaternion', '../Core/RuntimeError', '../Core/WebGLConstants', '../Renderer/ShaderSource', './AttributeType' ], function( Cartesian2, Cartesian3, Cartesian4, defined, defineProperties, Matrix2, Matrix3, Matrix4, Quaternion, RuntimeError, WebGLConstants, ShaderSource, AttributeType) { 'use strict'; /** * @private */ var ModelUtility = {}; ModelUtility.getAccessorMinMax = function(gltf, accessorId) { var accessor = gltf.accessors[accessorId]; var extensions = accessor.extensions; var accessorMin = accessor.min; var accessorMax = accessor.max; // If this accessor is quantized, we should use the decoded min and max if (defined(extensions)) { var quantizedAttributes = extensions.WEB3D_quantized_attributes; if (defined(quantizedAttributes)) { accessorMin = quantizedAttributes.decodedMin; accessorMax = quantizedAttributes.decodedMax; } } return { min : accessorMin, max : accessorMax }; }; ModelUtility.getAttributeOrUniformBySemantic = function(gltf, semantic, programId, ignoreNodes) { var techniques = gltf.techniques; var parameter; for (var techniqueName in techniques) { if (techniques.hasOwnProperty(techniqueName)) { var technique = techniques[techniqueName]; if (defined(programId) && (technique.program !== programId)) { continue; } var parameters = technique.parameters; var attributes = technique.attributes; var uniforms = technique.uniforms; for (var attributeName in attributes) { if (attributes.hasOwnProperty(attributeName)) { parameter = parameters[attributes[attributeName]]; if (defined(parameter) && parameter.semantic === semantic && (!ignoreNodes || !defined(parameter.node))) { return attributeName; } } } for (var uniformName in uniforms) { if (uniforms.hasOwnProperty(uniformName)) { parameter = parameters[uniforms[uniformName]]; if (defined(parameter) && parameter.semantic === semantic && (!ignoreNodes || !defined(parameter.node))) { return uniformName; } } } } } return undefined; }; ModelUtility.getDiffuseAttributeOrUniform = function(gltf, programId) { var diffuseUniformName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'COLOR_0', programId); if (!defined(diffuseUniformName)) { diffuseUniformName = ModelUtility.getAttributeOrUniformBySemantic(gltf, '_3DTILESDIFFUSE', programId); } return diffuseUniformName; }; var nodeTranslationScratch = new Cartesian3(); var nodeQuaternionScratch = new Quaternion(); var nodeScaleScratch = new Cartesian3(); ModelUtility.getTransform = function(node, result) { if (defined(node.matrix)) { return Matrix4.fromColumnMajorArray(node.matrix, result); } return Matrix4.fromTranslationQuaternionRotationScale( Cartesian3.fromArray(node.translation, 0, nodeTranslationScratch), Quaternion.unpack(node.rotation, 0, nodeQuaternionScratch), Cartesian3.fromArray(node.scale, 0, nodeScaleScratch), result); }; ModelUtility.getUsedExtensions = function(gltf) { var extensionsUsed = gltf.extensionsUsed; var cachedExtensionsUsed = {}; if (defined(extensionsUsed)) { var extensionsUsedLength = extensionsUsed.length; for (var i = 0; i < extensionsUsedLength; i++) { var extension = extensionsUsed[i]; cachedExtensionsUsed[extension] = true; } } return cachedExtensionsUsed; }; ModelUtility.getRequiredExtensions = function(gltf) { var extensionsRequired = gltf.extensionsRequired; var cachedExtensionsRequired = {}; if (defined(extensionsRequired)) { var extensionsRequiredLength = extensionsRequired.length; for (var i = 0; i < extensionsRequiredLength; i++) { var extension = extensionsRequired[i]; cachedExtensionsRequired[extension] = true; } } return cachedExtensionsRequired; }; ModelUtility.checkSupportedExtensions = function(extensionsRequired) { for (var extension in extensionsRequired) { if (extensionsRequired.hasOwnProperty(extension)) { if (extension !== 'CESIUM_RTC' && extension !== 'KHR_technique_webgl' && extension !== 'KHR_binary_glTF' && extension !== 'KHR_materials_common' && extension !== 'WEB3D_quantized_attributes' && extension !== 'KHR_draco_mesh_compression') { throw new RuntimeError('Unsupported glTF Extension: ' + extension); } } } }; ModelUtility.checkSupportedGlExtensions = function(extensionsUsed, context) { if (defined(extensionsUsed)) { var glExtensionsUsedLength = extensionsUsed.length; for (var i = 0; i < glExtensionsUsedLength; i++) { var extension = extensionsUsed[i]; if (extension !== 'OES_element_index_uint') { throw new RuntimeError('Unsupported WebGL Extension: ' + extension); } else if (!context.elementIndexUint) { throw new RuntimeError('OES_element_index_uint WebGL extension is not enabled.'); } } } }; function replaceAllButFirstInString(string, find, replace) { // Limit search to strings that are not a subset of other tokens. find += '(?!\\w)'; find = new RegExp(find, 'g'); var index = string.search(find); return string.replace(find, function(match, offset) { return index === offset ? match : replace; }); } function getQuantizedAttributes(gltf, accessorId) { var accessor = gltf.accessors[accessorId]; var extensions = accessor.extensions; if (defined(extensions)) { return extensions.WEB3D_quantized_attributes; } return undefined; } function getAttributeVariableName(gltf, primitive, attributeSemantic) { var materialId = primitive.material; var material = gltf.materials[materialId]; var techniqueId = material.technique; var technique = gltf.techniques[techniqueId]; for (var parameter in technique.parameters) { if (technique.parameters.hasOwnProperty(parameter)) { var semantic = technique.parameters[parameter].semantic; if (semantic === attributeSemantic) { var attributes = technique.attributes; for (var attributeVarName in attributes) { if (attributes.hasOwnProperty(attributeVarName)) { var name = attributes[attributeVarName]; if (name === parameter) { return attributeVarName; } } } } } } return undefined; } ModelUtility.modifyShaderForDracoQuantizedAttributes = function(gltf, primitive, shader, decodedAttributes) { var quantizedUniforms = {}; for (var attributeSemantic in decodedAttributes) { if (decodedAttributes.hasOwnProperty(attributeSemantic)) { var attribute = decodedAttributes[attributeSemantic]; var quantization = attribute.quantization; if (!defined(quantization)) { continue; } var attributeVarName = getAttributeVariableName(gltf, primitive, attributeSemantic); if (attributeSemantic.charAt(0) === '_') { attributeSemantic = attributeSemantic.substring(1); } var decodeUniformVarName = 'gltf_u_dec_' + attributeSemantic.toLowerCase(); if (!defined(quantizedUniforms[decodeUniformVarName])) { var newMain = 'gltf_decoded_' + attributeSemantic; var decodedAttributeVarName = attributeVarName.replace('a_', 'gltf_a_dec_'); var size = attribute.componentsPerAttribute; // replace usages of the original attribute with the decoded version, but not the declaration shader = replaceAllButFirstInString(shader, attributeVarName, decodedAttributeVarName); // declare decoded attribute var variableType; if (quantization.octEncoded) { variableType = 'vec3'; } else if (size > 1) { variableType = 'vec' + size; } else { variableType = 'float'; } shader = variableType + ' ' + decodedAttributeVarName + ';\n' + shader; // splice decode function into the shader var decode = ''; if (quantization.octEncoded) { var decodeUniformVarNameRangeConstant = decodeUniformVarName + '_rangeConstant'; shader = 'uniform float ' + decodeUniformVarNameRangeConstant + ';\n' + shader; decode = '\n' + 'void main() {\n' + // Draco oct-encoding decodes to zxy order ' ' + decodedAttributeVarName + ' = czm_octDecode(' + attributeVarName + '.xy, ' + decodeUniformVarNameRangeConstant + ').zxy;\n' + ' ' + newMain + '();\n' + '}\n'; } else { var decodeUniformVarNameNormConstant = decodeUniformVarName + '_normConstant'; var decodeUniformVarNameMin = decodeUniformVarName + '_min'; shader = 'uniform float ' + decodeUniformVarNameNormConstant + ';\n' + 'uniform ' + variableType + ' ' + decodeUniformVarNameMin + ';\n' + shader; decode = '\n' + 'void main() {\n' + ' ' + decodedAttributeVarName + ' = ' + decodeUniformVarNameMin + ' + ' + attributeVarName + ' * ' + decodeUniformVarNameNormConstant + ';\n' + ' ' + newMain + '();\n' + '}\n'; } shader = ShaderSource.replaceMain(shader, newMain); shader += decode; } } } return { shader : shader }; }; ModelUtility.modifyShaderForQuantizedAttributes = function(gltf, primitive, shader) { var quantizedUniforms = {}; var attributes = primitive.attributes; for (var attributeSemantic in attributes) { if (attributes.hasOwnProperty(attributeSemantic)) { var attributeVarName = getAttributeVariableName(gltf, primitive, attributeSemantic); var accessorId = primitive.attributes[attributeSemantic]; if (attributeSemantic.charAt(0) === '_') { attributeSemantic = attributeSemantic.substring(1); } var decodeUniformVarName = 'gltf_u_dec_' + attributeSemantic.toLowerCase(); var decodeUniformVarNameScale = decodeUniformVarName + '_scale'; var decodeUniformVarNameTranslate = decodeUniformVarName + '_translate'; if (!defined(quantizedUniforms[decodeUniformVarName]) && !defined(quantizedUniforms[decodeUniformVarNameScale])) { var quantizedAttributes = getQuantizedAttributes(gltf, accessorId); if (defined(quantizedAttributes)) { var decodeMatrix = quantizedAttributes.decodeMatrix; var newMain = 'gltf_decoded_' + attributeSemantic; var decodedAttributeVarName = attributeVarName.replace('a_', 'gltf_a_dec_'); var size = Math.floor(Math.sqrt(decodeMatrix.length)); // replace usages of the original attribute with the decoded version, but not the declaration shader = replaceAllButFirstInString(shader, attributeVarName, decodedAttributeVarName); // declare decoded attribute var variableType; if (size > 2) { variableType = 'vec' + (size - 1); } else { variableType = 'float'; } shader = variableType + ' ' + decodedAttributeVarName + ';\n' + shader; // splice decode function into the shader - attributes are pre-multiplied with the decode matrix // uniform in the shader (32-bit floating point) var decode = ''; if (size === 5) { // separate scale and translate since glsl doesn't have mat5 shader = 'uniform mat4 ' + decodeUniformVarNameScale + ';\n' + shader; shader = 'uniform vec4 ' + decodeUniformVarNameTranslate + ';\n' + shader; decode = '\n' + 'void main() {\n' + ' ' + decodedAttributeVarName + ' = ' + decodeUniformVarNameScale + ' * ' + attributeVarName + ' + ' + decodeUniformVarNameTranslate + ';\n' + ' ' + newMain + '();\n' + '}\n'; quantizedUniforms[decodeUniformVarNameScale] = {mat : 4}; quantizedUniforms[decodeUniformVarNameTranslate] = {vec : 4}; } else { shader = 'uniform mat' + size + ' ' + decodeUniformVarName + ';\n' + shader; decode = '\n' + 'void main() {\n' + ' ' + decodedAttributeVarName + ' = ' + variableType + '(' + decodeUniformVarName + ' * vec' + size + '(' + attributeVarName + ',1.0));\n' + ' ' + newMain + '();\n' + '}\n'; quantizedUniforms[decodeUniformVarName] = {mat : size}; } shader = ShaderSource.replaceMain(shader, newMain); shader += decode; } } } } return { shader : shader, uniforms : quantizedUniforms }; }; ModelUtility.toClipCoordinatesGLSL = function(gltf, shader) { var positionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'POSITION'); var decodedPositionName = positionName.replace('a_', 'gltf_a_dec_'); if (shader.indexOf(decodedPositionName) !== -1) { positionName = decodedPositionName; } var modelViewProjectionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'MODELVIEWPROJECTION', undefined, true); if (!defined(modelViewProjectionName) || shader.indexOf(modelViewProjectionName) === -1) { var projectionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'PROJECTION', undefined, true); var modelViewName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'MODELVIEW', undefined, true); if (shader.indexOf('czm_instanced_modelView ') !== -1) { modelViewName = 'czm_instanced_modelView'; } else if (!defined(modelViewName)) { modelViewName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'CESIUM_RTC_MODELVIEW', undefined, true); } modelViewProjectionName = projectionName + ' * ' + modelViewName; } return modelViewProjectionName + ' * vec4(' + positionName + '.xyz, 1.0)'; }; ModelUtility.modifyFragmentShaderForLogDepth = function(shader) { shader = ShaderSource.replaceMain(shader, 'czm_depth_main'); shader += '\n' + 'void main() \n' + '{ \n' + ' czm_depth_main(); \n' + ' czm_writeLogDepth(); \n' + '} \n'; return shader; }; ModelUtility.modifyVertexShaderForLogDepth = function(shader, toClipCoordinatesGLSL) { shader = ShaderSource.replaceMain(shader, 'czm_depth_main'); shader += '\n' + 'void main() \n' + '{ \n' + ' czm_depth_main(); \n' + ' czm_vertexLogDepth(' + toClipCoordinatesGLSL + '); \n' + '} \n'; return shader; }; function getScalarUniformFunction(value) { var that = { value : value, clone : function(source, result) { return source; }, func : function() { return that.value; } }; return that; } function getVec2UniformFunction(value) { var that = { value : Cartesian2.fromArray(value), clone : Cartesian2.clone, func : function() { return that.value; } }; return that; } function getVec3UniformFunction(value) { var that = { value : Cartesian3.fromArray(value), clone : Cartesian3.clone, func : function() { return that.value; } }; return that; } function getVec4UniformFunction(value) { var that = { value : Cartesian4.fromArray(value), clone : Cartesian4.clone, func : function() { return that.value; } }; return that; } function getMat2UniformFunction(value) { var that = { value : Matrix2.fromColumnMajorArray(value), clone : Matrix2.clone, func : function() { return that.value; } }; return that; } function getMat3UniformFunction(value) { var that = { value : Matrix3.fromColumnMajorArray(value), clone : Matrix3.clone, func : function() { return that.value; } }; return that; } function getMat4UniformFunction(value) { var that = { value : Matrix4.fromColumnMajorArray(value), clone : Matrix4.clone, func : function() { return that.value; } }; return that; } /////////////////////////////////////////////////////////////////////////// function DelayLoadedTextureUniform(value, textures, defaultTexture) { this._value = undefined; this._textureId = value.index; this._textures = textures; this._defaultTexture = defaultTexture; } defineProperties(DelayLoadedTextureUniform.prototype, { value : { get : function() { // Use the default texture (1x1 white) until the model's texture is loaded if (!defined(this._value)) { var texture = this._textures[this._textureId]; if (defined(texture)) { this._value = texture; } else { return this._defaultTexture; } } return this._value; }, set : function(value) { this._value = value; } } }); DelayLoadedTextureUniform.prototype.clone = function(source) { return source; }; DelayLoadedTextureUniform.prototype.func = undefined; /////////////////////////////////////////////////////////////////////////// function getTextureUniformFunction(value, texture, defaultTexture) { var uniform = new DelayLoadedTextureUniform(value, texture, defaultTexture); // Define function here to access closure since 'this' can't be // used when the Renderer sets uniforms. uniform.func = function() { return uniform.value; }; return uniform; } var gltfUniformFunctions = {}; gltfUniformFunctions[WebGLConstants.FLOAT] = getScalarUniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_VEC2] = getVec2UniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_VEC3] = getVec3UniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_VEC4] = getVec4UniformFunction; gltfUniformFunctions[WebGLConstants.INT] = getScalarUniformFunction; gltfUniformFunctions[WebGLConstants.INT_VEC2] = getVec2UniformFunction; gltfUniformFunctions[WebGLConstants.INT_VEC3] = getVec3UniformFunction; gltfUniformFunctions[WebGLConstants.INT_VEC4] = getVec4UniformFunction; gltfUniformFunctions[WebGLConstants.BOOL] = getScalarUniformFunction; gltfUniformFunctions[WebGLConstants.BOOL_VEC2] = getVec2UniformFunction; gltfUniformFunctions[WebGLConstants.BOOL_VEC3] = getVec3UniformFunction; gltfUniformFunctions[WebGLConstants.BOOL_VEC4] = getVec4UniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_MAT2] = getMat2UniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_MAT3] = getMat3UniformFunction; gltfUniformFunctions[WebGLConstants.FLOAT_MAT4] = getMat4UniformFunction; gltfUniformFunctions[WebGLConstants.SAMPLER_2D] = getTextureUniformFunction; // GLTF_SPEC: Support SAMPLER_CUBE. https://github.com/KhronosGroup/glTF/issues/40 ModelUtility.createUniformFunction = function(type, value, textures, defaultTexture) { return gltfUniformFunctions[type](value, textures, defaultTexture); }; function scaleFromMatrix5Array(matrix) { return [matrix[0], matrix[1], matrix[2], matrix[3], matrix[5], matrix[6], matrix[7], matrix[8], matrix[10], matrix[11], matrix[12], matrix[13], matrix[15], matrix[16], matrix[17], matrix[18]]; } function translateFromMatrix5Array(matrix) { return [matrix[20], matrix[21], matrix[22], matrix[23]]; } ModelUtility.createUniformsForDracoQuantizedAttributes = function(decodedAttributes) { var uniformMap = {}; for (var attribute in decodedAttributes) { if (decodedAttributes.hasOwnProperty(attribute)) { var decodedData = decodedAttributes[attribute]; var quantization = decodedData.quantization; if (!defined(quantization)) { continue; } if (attribute.charAt(0) === '_'){ attribute = attribute.substring(1); } var uniformVarName = 'gltf_u_dec_' + attribute.toLowerCase(); if (quantization.octEncoded) { var uniformVarNameRangeConstant = uniformVarName + '_rangeConstant'; var rangeConstant = (1 << quantization.quantizationBits) - 1.0; uniformMap[uniformVarNameRangeConstant] = getScalarUniformFunction(rangeConstant).func; continue; } var uniformVarNameNormConstant = uniformVarName + '_normConstant'; var normConstant = quantization.range / (1 << quantization.quantizationBits); uniformMap[uniformVarNameNormConstant] = getScalarUniformFunction(normConstant).func; var uniformVarNameMin = uniformVarName + '_min'; switch (decodedData.componentsPerAttribute) { case 1: uniformMap[uniformVarNameMin] = getScalarUniformFunction(quantization.minValues).func; break; case 2: uniformMap[uniformVarNameMin] = getVec2UniformFunction(quantization.minValues).func; break; case 3: uniformMap[uniformVarNameMin] = getVec3UniformFunction(quantization.minValues).func; break; case 4: uniformMap[uniformVarNameMin] = getVec4UniformFunction(quantization.minValues).func; break; } } } return uniformMap; }; ModelUtility.createUniformsForQuantizedAttributes = function(gltf, primitive, quantizedUniforms) { var accessors = gltf.accessors; var setUniforms = {}; var uniformMap = {}; var attributes = primitive.attributes; for (var attribute in attributes) { if (attributes.hasOwnProperty(attribute)) { var accessorId = attributes[attribute]; var a = accessors[accessorId]; var extensions = a.extensions; if (attribute.charAt(0) === '_') { attribute = attribute.substring(1); } if (defined(extensions)) { var quantizedAttributes = extensions.WEB3D_quantized_attributes; if (defined(quantizedAttributes)) { var decodeMatrix = quantizedAttributes.decodeMatrix; var uniformVariable = 'gltf_u_dec_' + attribute.toLowerCase(); switch (a.type) { case AttributeType.SCALAR: uniformMap[uniformVariable] = getMat2UniformFunction(decodeMatrix).func; setUniforms[uniformVariable] = true; break; case AttributeType.VEC2: uniformMap[uniformVariable] = getMat3UniformFunction(decodeMatrix).func; setUniforms[uniformVariable] = true; break; case AttributeType.VEC3: uniformMap[uniformVariable] = getMat4UniformFunction(decodeMatrix).func; setUniforms[uniformVariable] = true; break; case AttributeType.VEC4: // VEC4 attributes are split into scale and translate because there is no mat5 in GLSL var uniformVariableScale = uniformVariable + '_scale'; var uniformVariableTranslate = uniformVariable + '_translate'; uniformMap[uniformVariableScale] = getMat4UniformFunction(scaleFromMatrix5Array(decodeMatrix)).func; uniformMap[uniformVariableTranslate] = getVec4UniformFunction(translateFromMatrix5Array(decodeMatrix)).func; setUniforms[uniformVariableScale] = true; setUniforms[uniformVariableTranslate] = true; break; } } } } } // If there are any unset quantized uniforms in this program, they should be set to the identity for (var quantizedUniform in quantizedUniforms) { if (quantizedUniforms.hasOwnProperty(quantizedUniform)) { if (!setUniforms[quantizedUniform]) { var properties = quantizedUniforms[quantizedUniform]; if (defined(properties.mat)) { if (properties.mat === 2) { uniformMap[quantizedUniform] = getMat2UniformFunction(Matrix2.IDENTITY).func; } else if (properties.mat === 3) { uniformMap[quantizedUniform] = getMat3UniformFunction(Matrix3.IDENTITY).func; } else if (properties.mat === 4) { uniformMap[quantizedUniform] = getMat4UniformFunction(Matrix4.IDENTITY).func; } } if (defined(properties.vec)) { if (properties.vec === 4) { uniformMap[quantizedUniform] = getVec4UniformFunction([0, 0, 0, 0]).func; } } } } } return uniformMap; }; // This doesn't support LOCAL, which we could add if it is ever used. var scratchTranslationRtc = new Cartesian3(); var gltfSemanticUniforms = { MODEL : function(uniformState, model) { return function() { return uniformState.model; }; }, VIEW : function(uniformState, model) { return function() { return uniformState.view; }; }, PROJECTION : function(uniformState, model) { return function() { return uniformState.projection; }; }, MODELVIEW : function(uniformState, model) { return function() { return uniformState.modelView; }; }, CESIUM_RTC_MODELVIEW : function(uniformState, model) { // CESIUM_RTC extension var mvRtc = new Matrix4(); return function() { if (defined(model._rtcCenter)) { Matrix4.getTranslation(uniformState.model, scratchTranslationRtc); Cartesian3.add(scratchTranslationRtc, model._rtcCenter, scratchTranslationRtc); Matrix4.multiplyByPoint(uniformState.view, scratchTranslationRtc, scratchTranslationRtc); return Matrix4.setTranslation(uniformState.modelView, scratchTranslationRtc, mvRtc); } return uniformState.modelView; }; }, MODELVIEWPROJECTION : function(uniformState, model) { return function() { return uniformState.modelViewProjection; }; }, MODELINVERSE : function(uniformState, model) { return function() { return uniformState.inverseModel; }; }, VIEWINVERSE : function(uniformState, model) { return function() { return uniformState.inverseView; }; }, PROJECTIONINVERSE : function(uniformState, model) { return function() { return uniformState.inverseProjection; }; }, MODELVIEWINVERSE : function(uniformState, model) { return function() { return uniformState.inverseModelView; }; }, MODELVIEWPROJECTIONINVERSE : function(uniformState, model) { return function() { return uniformState.inverseModelViewProjection; }; }, MODELINVERSETRANSPOSE : function(uniformState, model) { return function() { return uniformState.inverseTransposeModel; }; }, MODELVIEWINVERSETRANSPOSE : function(uniformState, model) { return function() { return uniformState.normal; }; }, VIEWPORT : function(uniformState, model) { return function() { return uniformState.viewportCartesian4; }; } // JOINTMATRIX created in createCommand() }; ModelUtility.getGltfSemanticUniforms = function() { return gltfSemanticUniforms; }; return ModelUtility; });