import Cartesian2 from "../Core/Cartesian2.js"; import Check from "../Core/Check.js"; import clone from "../Core/clone.js"; import Color from "../Core/Color.js"; import combine from "../Core/combine.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import deprecationWarning from "../Core/deprecationWarning.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import CesiumMath from "../Core/Math.js"; import RuntimeError from "../Core/RuntimeError.js"; import ContextLimits from "../Renderer/ContextLimits.js"; import DrawCommand from "../Renderer/DrawCommand.js"; import Pass from "../Renderer/Pass.js"; import RenderState from "../Renderer/RenderState.js"; import ShaderSource from "../Renderer/ShaderSource.js"; import BatchTexture from "./BatchTexture.js"; import BatchTableHierarchy from "./BatchTableHierarchy.js"; import BlendingState from "./BlendingState.js"; import Cesium3DTileColorBlendMode from "./Cesium3DTileColorBlendMode.js"; import CullFace from "./CullFace.js"; import getBinaryAccessor from "./getBinaryAccessor.js"; import StencilConstants from "./StencilConstants.js"; import StencilFunction from "./StencilFunction.js"; import StencilOperation from "./StencilOperation.js"; const DEFAULT_COLOR_VALUE = BatchTexture.DEFAULT_COLOR_VALUE; const DEFAULT_SHOW_VALUE = BatchTexture.DEFAULT_SHOW_VALUE; /** * @private * @constructor */ function Cesium3DTileBatchTable( content, featuresLength, batchTableJson, batchTableBinary, colorChangedCallback ) { /** * @readonly */ this.featuresLength = featuresLength; let extensions; if (defined(batchTableJson)) { extensions = batchTableJson.extensions; } this._extensions = defaultValue(extensions, {}); const properties = initializeProperties(batchTableJson); this._properties = properties; this._batchTableHierarchy = initializeHierarchy( this, batchTableJson, batchTableBinary ); this._batchTableBinaryProperties = getBinaryProperties( featuresLength, properties, batchTableBinary ); this._content = content; this._batchTexture = new BatchTexture({ featuresLength: featuresLength, colorChangedCallback: colorChangedCallback, owner: content, statistics: content.tileset.statistics, }); } // This can be overridden for testing purposes Cesium3DTileBatchTable._deprecationWarning = deprecationWarning; Object.defineProperties(Cesium3DTileBatchTable.prototype, { memorySizeInBytes: { get: function () { return this._batchTexture.memorySizeInBytes; }, }, }); function initializeProperties(jsonHeader) { const properties = {}; if (!defined(jsonHeader)) { return properties; } for (const propertyName in jsonHeader) { if ( jsonHeader.hasOwnProperty(propertyName) && propertyName !== "HIERARCHY" && // Deprecated HIERARCHY property propertyName !== "extensions" && propertyName !== "extras" ) { properties[propertyName] = clone(jsonHeader[propertyName], true); } } return properties; } function initializeHierarchy(batchTable, jsonHeader, binaryBody) { if (!defined(jsonHeader)) { return; } let hierarchy = batchTable._extensions["3DTILES_batch_table_hierarchy"]; const legacyHierarchy = jsonHeader.HIERARCHY; if (defined(legacyHierarchy)) { Cesium3DTileBatchTable._deprecationWarning( "batchTableHierarchyExtension", "The batch table HIERARCHY property has been moved to an extension. Use extensions.3DTILES_batch_table_hierarchy instead." ); batchTable._extensions["3DTILES_batch_table_hierarchy"] = legacyHierarchy; hierarchy = legacyHierarchy; } if (!defined(hierarchy)) { return; } return new BatchTableHierarchy({ extension: hierarchy, binaryBody: binaryBody, }); } function getBinaryProperties(featuresLength, properties, binaryBody) { let binaryProperties; for (const name in properties) { if (properties.hasOwnProperty(name)) { const property = properties[name]; const byteOffset = property.byteOffset; if (defined(byteOffset)) { // This is a binary property const componentType = property.componentType; const type = property.type; if (!defined(componentType)) { throw new RuntimeError("componentType is required."); } if (!defined(type)) { throw new RuntimeError("type is required."); } if (!defined(binaryBody)) { throw new RuntimeError( `Property ${name} requires a batch table binary.` ); } const binaryAccessor = getBinaryAccessor(property); const componentCount = binaryAccessor.componentsPerAttribute; const classType = binaryAccessor.classType; const typedArray = binaryAccessor.createArrayBufferView( binaryBody.buffer, binaryBody.byteOffset + byteOffset, featuresLength ); if (!defined(binaryProperties)) { binaryProperties = {}; } // Store any information needed to access the binary data, including the typed array, // componentCount (e.g. a VEC4 would be 4), and the type used to pack and unpack (e.g. Cartesian4). binaryProperties[name] = { typedArray: typedArray, componentCount: componentCount, type: classType, }; } } } return binaryProperties; } Cesium3DTileBatchTable.getBinaryProperties = function ( featuresLength, batchTableJson, batchTableBinary ) { return getBinaryProperties(featuresLength, batchTableJson, batchTableBinary); }; Cesium3DTileBatchTable.prototype.setShow = function (batchId, show) { this._batchTexture.setShow(batchId, show); }; Cesium3DTileBatchTable.prototype.setAllShow = function (show) { this._batchTexture.setAllShow(show); }; Cesium3DTileBatchTable.prototype.getShow = function (batchId) { return this._batchTexture.getShow(batchId); }; Cesium3DTileBatchTable.prototype.setColor = function (batchId, color) { this._batchTexture.setColor(batchId, color); }; Cesium3DTileBatchTable.prototype.setAllColor = function (color) { this._batchTexture.setAllColor(color); }; Cesium3DTileBatchTable.prototype.getColor = function (batchId, result) { return this._batchTexture.getColor(batchId, result); }; Cesium3DTileBatchTable.prototype.getPickColor = function (batchId) { return this._batchTexture.getPickColor(batchId); }; const scratchColor = new Color(); Cesium3DTileBatchTable.prototype.applyStyle = function (style) { if (!defined(style)) { this.setAllColor(DEFAULT_COLOR_VALUE); this.setAllShow(DEFAULT_SHOW_VALUE); return; } const content = this._content; const length = this.featuresLength; for (let i = 0; i < length; ++i) { const feature = content.getFeature(i); const color = defined(style.color) ? defaultValue( style.color.evaluateColor(feature, scratchColor), DEFAULT_COLOR_VALUE ) : DEFAULT_COLOR_VALUE; const show = defined(style.show) ? defaultValue(style.show.evaluate(feature), DEFAULT_SHOW_VALUE) : DEFAULT_SHOW_VALUE; this.setColor(i, color); this.setShow(i, show); } }; function getBinaryProperty(binaryProperty, index) { const typedArray = binaryProperty.typedArray; const componentCount = binaryProperty.componentCount; if (componentCount === 1) { return typedArray[index]; } return binaryProperty.type.unpack(typedArray, index * componentCount); } function setBinaryProperty(binaryProperty, index, value) { const typedArray = binaryProperty.typedArray; const componentCount = binaryProperty.componentCount; if (componentCount === 1) { typedArray[index] = value; } else { binaryProperty.type.pack(value, typedArray, index * componentCount); } } function checkBatchId(batchId, featuresLength) { if (!defined(batchId) || batchId < 0 || batchId >= featuresLength) { throw new DeveloperError( `batchId is required and between zero and featuresLength - 1 (${featuresLength}` - +")." ); } } Cesium3DTileBatchTable.prototype.isClass = function (batchId, className) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); Check.typeOf.string("className", className); //>>includeEnd('debug'); const hierarchy = this._batchTableHierarchy; if (!defined(hierarchy)) { return false; } return hierarchy.isClass(batchId, className); }; Cesium3DTileBatchTable.prototype.isExactClass = function (batchId, className) { //>>includeStart('debug', pragmas.debug); Check.typeOf.string("className", className); //>>includeEnd('debug'); return this.getExactClassName(batchId) === className; }; Cesium3DTileBatchTable.prototype.getExactClassName = function (batchId) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); //>>includeEnd('debug'); const hierarchy = this._batchTableHierarchy; if (!defined(hierarchy)) { return undefined; } return hierarchy.getClassName(batchId); }; Cesium3DTileBatchTable.prototype.hasProperty = function (batchId, name) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); Check.typeOf.string("name", name); //>>includeEnd('debug'); return ( defined(this._properties[name]) || (defined(this._batchTableHierarchy) && this._batchTableHierarchy.hasProperty(batchId, name)) ); }; /** * @private */ Cesium3DTileBatchTable.prototype.hasPropertyBySemantic = function () { // Cesium 3D Tiles 1.0 formats do not have semantics return false; }; Cesium3DTileBatchTable.prototype.getPropertyNames = function ( batchId, results ) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); //>>includeEnd('debug'); results = defined(results) ? results : []; results.length = 0; const scratchPropertyNames = Object.keys(this._properties); results.push.apply(results, scratchPropertyNames); if (defined(this._batchTableHierarchy)) { results.push.apply( results, this._batchTableHierarchy.getPropertyIds(batchId, scratchPropertyNames) ); } return results; }; /** * @private */ Cesium3DTileBatchTable.prototype.getPropertyBySemantic = function ( batchId, name ) { // Cesium 3D Tiles 1.0 formats do not have semantics return undefined; }; Cesium3DTileBatchTable.prototype.getProperty = function (batchId, name) { //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, this.featuresLength); Check.typeOf.string("name", name); //>>includeEnd('debug'); if (defined(this._batchTableBinaryProperties)) { const binaryProperty = this._batchTableBinaryProperties[name]; if (defined(binaryProperty)) { return getBinaryProperty(binaryProperty, batchId); } } const propertyValues = this._properties[name]; if (defined(propertyValues)) { return clone(propertyValues[batchId], true); } if (defined(this._batchTableHierarchy)) { const hierarchyProperty = this._batchTableHierarchy.getProperty( batchId, name ); if (defined(hierarchyProperty)) { return hierarchyProperty; } } return undefined; }; Cesium3DTileBatchTable.prototype.setProperty = function (batchId, name, value) { const featuresLength = this.featuresLength; //>>includeStart('debug', pragmas.debug); checkBatchId(batchId, featuresLength); Check.typeOf.string("name", name); //>>includeEnd('debug'); if (defined(this._batchTableBinaryProperties)) { const binaryProperty = this._batchTableBinaryProperties[name]; if (defined(binaryProperty)) { setBinaryProperty(binaryProperty, batchId, value); return; } } if (defined(this._batchTableHierarchy)) { if (this._batchTableHierarchy.setProperty(batchId, name, value)) { return; } } let propertyValues = this._properties[name]; if (!defined(propertyValues)) { // Property does not exist. Create it. this._properties[name] = new Array(featuresLength); propertyValues = this._properties[name]; } propertyValues[batchId] = clone(value, true); }; function getGlslComputeSt(batchTable) { // GLSL batchId is zero-based: [0, featuresLength - 1] if (batchTable._batchTexture.textureDimensions.y === 1) { return ( "uniform vec4 tile_textureStep; \n" + "vec2 computeSt(float batchId) \n" + "{ \n" + " float stepX = tile_textureStep.x; \n" + " float centerX = tile_textureStep.y; \n" + " return vec2(centerX + (batchId * stepX), 0.5); \n" + "} \n" ); } return ( "uniform vec4 tile_textureStep; \n" + "uniform vec2 tile_textureDimensions; \n" + "vec2 computeSt(float batchId) \n" + "{ \n" + " float stepX = tile_textureStep.x; \n" + " float centerX = tile_textureStep.y; \n" + " float stepY = tile_textureStep.z; \n" + " float centerY = tile_textureStep.w; \n" + " float xId = mod(batchId, tile_textureDimensions.x); \n" + " float yId = floor(batchId / tile_textureDimensions.x); \n" + " return vec2(centerX + (xId * stepX), centerY + (yId * stepY)); \n" + "} \n" ); } Cesium3DTileBatchTable.prototype.getVertexShaderCallback = function ( handleTranslucent, batchIdAttributeName, diffuseAttributeOrUniformName ) { if (this.featuresLength === 0) { return; } const that = this; return function (source) { // If the color blend mode is HIGHLIGHT, the highlight color will always be applied in the fragment shader. // No need to apply the highlight color in the vertex shader as well. const renamedSource = modifyDiffuse( source, diffuseAttributeOrUniformName, false ); let newMain; if (ContextLimits.maximumVertexTextureImageUnits > 0) { // When VTF is supported, perform per-feature show/hide in the vertex shader newMain = ""; if (handleTranslucent) { newMain += "uniform bool tile_translucentCommand; \n"; } newMain += `${ "uniform sampler2D tile_batchTexture; \n" + "varying vec4 tile_featureColor; \n" + "varying vec2 tile_featureSt; \n" + "void main() \n" + "{ \n" + " vec2 st = computeSt(" }${batchIdAttributeName}); \n` + ` vec4 featureProperties = texture2D(tile_batchTexture, st); \n` + ` tile_color(featureProperties); \n` + ` float show = ceil(featureProperties.a); \n` + // 0 - false, non-zeo - true ` gl_Position *= show; \n`; // Per-feature show/hide if (handleTranslucent) { newMain += " bool isStyleTranslucent = (featureProperties.a != 1.0); \n" + " if (czm_pass == czm_passTranslucent) \n" + " { \n" + " if (!isStyleTranslucent && !tile_translucentCommand) \n" + // Do not render opaque features in the translucent pass " { \n" + " gl_Position *= 0.0; \n" + " } \n" + " } \n" + " else \n" + " { \n" + " if (isStyleTranslucent) \n" + // Do not render translucent features in the opaque pass " { \n" + " gl_Position *= 0.0; \n" + " } \n" + " } \n"; } newMain += " tile_featureColor = featureProperties; \n" + " tile_featureSt = st; \n" + "}"; } else { // When VTF is not supported, color blend mode MIX will look incorrect due to the feature's color not being available in the vertex shader newMain = `${ "varying vec2 tile_featureSt; \n" + "void main() \n" + "{ \n" + " tile_color(vec4(1.0)); \n" + " tile_featureSt = computeSt(" }${batchIdAttributeName}); \n` + `}`; } return `${renamedSource}\n${getGlslComputeSt(that)}${newMain}`; }; }; function getDefaultShader(source, applyHighlight) { source = ShaderSource.replaceMain(source, "tile_main"); if (!applyHighlight) { return ( `${source}void tile_color(vec4 tile_featureColor) \n` + `{ \n` + ` tile_main(); \n` + `} \n` ); } // The color blend mode is intended for the RGB channels so alpha is always just multiplied. // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight) return ( `${source}uniform float tile_colorBlend; \n` + `void tile_color(vec4 tile_featureColor) \n` + `{ \n` + ` tile_main(); \n` + ` tile_featureColor = czm_gammaCorrect(tile_featureColor); \n` + ` gl_FragColor.a *= tile_featureColor.a; \n` + ` float highlight = ceil(tile_colorBlend); \n` + ` gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n` + `} \n` ); } function replaceDiffuseTextureCalls(source, diffuseAttributeOrUniformName) { const functionCall = `texture2D(${diffuseAttributeOrUniformName}`; let fromIndex = 0; let startIndex = source.indexOf(functionCall, fromIndex); let endIndex; while (startIndex > -1) { let nestedLevel = 0; for (let i = startIndex; i < source.length; ++i) { const character = source.charAt(i); if (character === "(") { ++nestedLevel; } else if (character === ")") { --nestedLevel; if (nestedLevel === 0) { endIndex = i + 1; break; } } } const extractedFunction = source.slice(startIndex, endIndex); const replacedFunction = `tile_diffuse_final(${extractedFunction}, tile_diffuse)`; source = source.slice(0, startIndex) + replacedFunction + source.slice(endIndex); fromIndex = startIndex + replacedFunction.length; startIndex = source.indexOf(functionCall, fromIndex); } return source; } function modifyDiffuse(source, diffuseAttributeOrUniformName, applyHighlight) { // If the glTF does not specify the _3DTILESDIFFUSE semantic, return the default shader. // Otherwise if _3DTILESDIFFUSE is defined prefer the shader below that can switch the color mode at runtime. if (!defined(diffuseAttributeOrUniformName)) { return getDefaultShader(source, applyHighlight); } // Find the diffuse uniform. Examples matches: // uniform vec3 u_diffuseColor; // uniform sampler2D diffuseTexture; let regex = new RegExp( `(uniform|attribute|in)\\s+(vec[34]|sampler2D)\\s+${diffuseAttributeOrUniformName};` ); const uniformMatch = source.match(regex); if (!defined(uniformMatch)) { // Could not find uniform declaration of type vec3, vec4, or sampler2D return getDefaultShader(source, applyHighlight); } const declaration = uniformMatch[0]; const type = uniformMatch[2]; source = ShaderSource.replaceMain(source, "tile_main"); source = source.replace(declaration, ""); // Remove uniform declaration for now so the replace below doesn't affect it // If the tile color is white, use the source color. This implies the feature has not been styled. // Highlight: tile_colorBlend is 0.0 and the source color is used // Replace: tile_colorBlend is 1.0 and the tile color is used // Mix: tile_colorBlend is between 0.0 and 1.0, causing the source color and tile color to mix const finalDiffuseFunction = "bool isWhite(vec3 color) \n" + "{ \n" + " return all(greaterThan(color, vec3(1.0 - czm_epsilon3))); \n" + "} \n" + "vec4 tile_diffuse_final(vec4 sourceDiffuse, vec4 tileDiffuse) \n" + "{ \n" + " vec4 blendDiffuse = mix(sourceDiffuse, tileDiffuse, tile_colorBlend); \n" + " vec4 diffuse = isWhite(tileDiffuse.rgb) ? sourceDiffuse : blendDiffuse; \n" + " return vec4(diffuse.rgb, sourceDiffuse.a); \n" + "} \n"; // The color blend mode is intended for the RGB channels so alpha is always just multiplied. // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight) const highlight = " tile_featureColor = czm_gammaCorrect(tile_featureColor); \n" + " gl_FragColor.a *= tile_featureColor.a; \n" + " float highlight = ceil(tile_colorBlend); \n" + " gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n"; let setColor; if (type === "vec3" || type === "vec4") { const sourceDiffuse = type === "vec3" ? `vec4(${diffuseAttributeOrUniformName}, 1.0)` : diffuseAttributeOrUniformName; const replaceDiffuse = type === "vec3" ? "tile_diffuse.xyz" : "tile_diffuse"; regex = new RegExp(diffuseAttributeOrUniformName, "g"); source = source.replace(regex, replaceDiffuse); setColor = ` vec4 source = ${sourceDiffuse}; \n` + ` tile_diffuse = tile_diffuse_final(source, tile_featureColor); \n` + ` tile_main(); \n`; } else if (type === "sampler2D") { // Handles any number of nested parentheses // E.g. texture2D(u_diffuse, uv) // E.g. texture2D(u_diffuse, computeUV(index)) source = replaceDiffuseTextureCalls(source, diffuseAttributeOrUniformName); setColor = " tile_diffuse = tile_featureColor; \n" + " tile_main(); \n"; } source = `${ "uniform float tile_colorBlend; \n" + "vec4 tile_diffuse = vec4(1.0); \n" }${finalDiffuseFunction}${declaration}\n${source}\n` + `void tile_color(vec4 tile_featureColor) \n` + `{ \n${setColor}`; if (applyHighlight) { source += highlight; } source += "} \n"; return source; } Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function ( handleTranslucent, diffuseAttributeOrUniformName, hasPremultipliedAlpha ) { if (this.featuresLength === 0) { return; } return function (source) { source = modifyDiffuse(source, diffuseAttributeOrUniformName, true); if (ContextLimits.maximumVertexTextureImageUnits > 0) { // When VTF is supported, per-feature show/hide already happened in the fragment shader source += "uniform sampler2D tile_pickTexture; \n" + "varying vec2 tile_featureSt; \n" + "varying vec4 tile_featureColor; \n" + "void main() \n" + "{ \n" + " tile_color(tile_featureColor); \n"; if (hasPremultipliedAlpha) { source += " gl_FragColor.rgb *= gl_FragColor.a; \n"; } source += "}"; } else { if (handleTranslucent) { source += "uniform bool tile_translucentCommand; \n"; } source += "uniform sampler2D tile_pickTexture; \n" + "uniform sampler2D tile_batchTexture; \n" + "varying vec2 tile_featureSt; \n" + "void main() \n" + "{ \n" + " vec4 featureProperties = texture2D(tile_batchTexture, tile_featureSt); \n" + " if (featureProperties.a == 0.0) { \n" + // show: alpha == 0 - false, non-zeo - true " discard; \n" + " } \n"; if (handleTranslucent) { source += " bool isStyleTranslucent = (featureProperties.a != 1.0); \n" + " if (czm_pass == czm_passTranslucent) \n" + " { \n" + " if (!isStyleTranslucent && !tile_translucentCommand) \n" + // Do not render opaque features in the translucent pass " { \n" + " discard; \n" + " } \n" + " } \n" + " else \n" + " { \n" + " if (isStyleTranslucent) \n" + // Do not render translucent features in the opaque pass " { \n" + " discard; \n" + " } \n" + " } \n"; } source += " tile_color(featureProperties); \n"; if (hasPremultipliedAlpha) { source += " gl_FragColor.rgb *= gl_FragColor.a; \n"; } source += "} \n"; } return source; }; }; Cesium3DTileBatchTable.prototype.getClassificationFragmentShaderCallback = function () { if (this.featuresLength === 0) { return; } return function (source) { source = ShaderSource.replaceMain(source, "tile_main"); if (ContextLimits.maximumVertexTextureImageUnits > 0) { // When VTF is supported, per-feature show/hide already happened in the fragment shader source += "uniform sampler2D tile_pickTexture;\n" + "varying vec2 tile_featureSt; \n" + "varying vec4 tile_featureColor; \n" + "void main() \n" + "{ \n" + " tile_main(); \n" + " gl_FragColor = tile_featureColor; \n" + " gl_FragColor.rgb *= gl_FragColor.a; \n" + "}"; } else { source += "uniform sampler2D tile_batchTexture; \n" + "uniform sampler2D tile_pickTexture;\n" + "varying vec2 tile_featureSt; \n" + "void main() \n" + "{ \n" + " tile_main(); \n" + " vec4 featureProperties = texture2D(tile_batchTexture, tile_featureSt); \n" + " if (featureProperties.a == 0.0) { \n" + // show: alpha == 0 - false, non-zeo - true " discard; \n" + " } \n" + " gl_FragColor = featureProperties; \n" + " gl_FragColor.rgb *= gl_FragColor.a; \n" + "} \n"; } return source; }; }; function getColorBlend(batchTable) { const tileset = batchTable._content.tileset; const colorBlendMode = tileset.colorBlendMode; const colorBlendAmount = tileset.colorBlendAmount; if (colorBlendMode === Cesium3DTileColorBlendMode.HIGHLIGHT) { return 0.0; } if (colorBlendMode === Cesium3DTileColorBlendMode.REPLACE) { return 1.0; } if (colorBlendMode === Cesium3DTileColorBlendMode.MIX) { // The value 0.0 is reserved for highlight, so clamp to just above 0.0. return CesiumMath.clamp(colorBlendAmount, CesiumMath.EPSILON4, 1.0); } //>>includeStart('debug', pragmas.debug); throw new DeveloperError(`Invalid color blend mode "${colorBlendMode}".`); //>>includeEnd('debug'); } Cesium3DTileBatchTable.prototype.getUniformMapCallback = function () { if (this.featuresLength === 0) { return; } const that = this; return function (uniformMap) { const batchUniformMap = { tile_batchTexture: function () { // PERFORMANCE_IDEA: we could also use a custom shader that avoids the texture read. return defaultValue( that._batchTexture.batchTexture, that._batchTexture.defaultTexture ); }, tile_textureDimensions: function () { return that._batchTexture.textureDimensions; }, tile_textureStep: function () { return that._batchTexture.textureStep; }, tile_colorBlend: function () { return getColorBlend(that); }, tile_pickTexture: function () { return that._batchTexture.pickTexture; }, }; return combine(uniformMap, batchUniformMap); }; }; Cesium3DTileBatchTable.prototype.getPickId = function () { return "texture2D(tile_pickTexture, tile_featureSt)"; }; /////////////////////////////////////////////////////////////////////////// const StyleCommandsNeeded = { ALL_OPAQUE: 0, ALL_TRANSLUCENT: 1, OPAQUE_AND_TRANSLUCENT: 2, }; Cesium3DTileBatchTable.prototype.addDerivedCommands = function ( frameState, commandStart ) { const commandList = frameState.commandList; const commandEnd = commandList.length; const tile = this._content._tile; const finalResolution = tile._finalResolution; const tileset = tile.tileset; const bivariateVisibilityTest = tileset._skipLevelOfDetail && tileset._hasMixedContent && frameState.context.stencilBuffer; const styleCommandsNeeded = getStyleCommandsNeeded(this); for (let i = commandStart; i < commandEnd; ++i) { const command = commandList[i]; if (command.pass === Pass.COMPUTE) { continue; } let derivedCommands = command.derivedCommands.tileset; if (!defined(derivedCommands) || command.dirty) { derivedCommands = {}; command.derivedCommands.tileset = derivedCommands; derivedCommands.originalCommand = deriveCommand(command); command.dirty = false; } const originalCommand = derivedCommands.originalCommand; if ( styleCommandsNeeded !== StyleCommandsNeeded.ALL_OPAQUE && command.pass !== Pass.TRANSLUCENT ) { if (!defined(derivedCommands.translucent)) { derivedCommands.translucent = deriveTranslucentCommand(originalCommand); } } if ( styleCommandsNeeded !== StyleCommandsNeeded.ALL_TRANSLUCENT && command.pass !== Pass.TRANSLUCENT ) { if (!defined(derivedCommands.opaque)) { derivedCommands.opaque = deriveOpaqueCommand(originalCommand); } if (bivariateVisibilityTest) { if (!finalResolution) { if (!defined(derivedCommands.zback)) { derivedCommands.zback = deriveZBackfaceCommand( frameState.context, originalCommand ); } tileset._backfaceCommands.push(derivedCommands.zback); } if ( !defined(derivedCommands.stencil) || tile._selectionDepth !== getLastSelectionDepth(derivedCommands.stencil) ) { if (command.renderState.depthMask) { derivedCommands.stencil = deriveStencilCommand( originalCommand, tile._selectionDepth ); } else { // Ignore if tile does not write depth derivedCommands.stencil = derivedCommands.opaque; } } } } const opaqueCommand = bivariateVisibilityTest ? derivedCommands.stencil : derivedCommands.opaque; const translucentCommand = derivedCommands.translucent; // If the command was originally opaque: // * If the styling applied to the tile is all opaque, use the opaque command // (with one additional uniform needed for the shader). // * If the styling is all translucent, use new (cached) derived commands (front // and back faces) with a translucent render state. // * If the styling causes both opaque and translucent features in this tile, // then use both sets of commands. if (command.pass !== Pass.TRANSLUCENT) { if (styleCommandsNeeded === StyleCommandsNeeded.ALL_OPAQUE) { commandList[i] = opaqueCommand; } if (styleCommandsNeeded === StyleCommandsNeeded.ALL_TRANSLUCENT) { commandList[i] = translucentCommand; } if (styleCommandsNeeded === StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT) { // PERFORMANCE_IDEA: if the tile has multiple commands, we do not know what features are in what // commands so this case may be overkill. commandList[i] = opaqueCommand; commandList.push(translucentCommand); } } else { // Command was originally translucent so no need to derive new commands; // as of now, a style can't change an originally translucent feature to // opaque since the style's alpha is modulated, not a replacement. When // this changes, we need to derive new opaque commands here. commandList[i] = originalCommand; } } }; function getStyleCommandsNeeded(batchTable) { const translucentFeaturesLength = batchTable._batchTexture.translucentFeaturesLength; if (translucentFeaturesLength === 0) { return StyleCommandsNeeded.ALL_OPAQUE; } else if (translucentFeaturesLength === batchTable.featuresLength) { return StyleCommandsNeeded.ALL_TRANSLUCENT; } return StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT; } function deriveCommand(command) { const derivedCommand = DrawCommand.shallowClone(command); // Add a uniform to indicate if the original command was translucent so // the shader knows not to cull vertices that were originally transparent // even though their style is opaque. const translucentCommand = derivedCommand.pass === Pass.TRANSLUCENT; derivedCommand.uniformMap = defined(derivedCommand.uniformMap) ? derivedCommand.uniformMap : {}; derivedCommand.uniformMap.tile_translucentCommand = function () { return translucentCommand; }; return derivedCommand; } function deriveTranslucentCommand(command) { const derivedCommand = DrawCommand.shallowClone(command); derivedCommand.pass = Pass.TRANSLUCENT; derivedCommand.renderState = getTranslucentRenderState(command.renderState); return derivedCommand; } function deriveOpaqueCommand(command) { const derivedCommand = DrawCommand.shallowClone(command); derivedCommand.renderState = getOpaqueRenderState(command.renderState); return derivedCommand; } function getLogDepthPolygonOffsetFragmentShaderProgram(context, shaderProgram) { let shader = context.shaderCache.getDerivedShaderProgram( shaderProgram, "zBackfaceLogDepth" ); if (!defined(shader)) { const fs = shaderProgram.fragmentShaderSource.clone(); fs.defines = defined(fs.defines) ? fs.defines.slice(0) : []; fs.defines.push("POLYGON_OFFSET"); fs.sources.unshift( "#ifdef GL_OES_standard_derivatives\n#extension GL_OES_standard_derivatives : enable\n#endif\n" ); shader = context.shaderCache.createDerivedShaderProgram( shaderProgram, "zBackfaceLogDepth", { vertexShaderSource: shaderProgram.vertexShaderSource, fragmentShaderSource: fs, attributeLocations: shaderProgram._attributeLocations, } ); } return shader; } function deriveZBackfaceCommand(context, command) { // Write just backface depth of unresolved tiles so resolved stenciled tiles do not appear in front const derivedCommand = DrawCommand.shallowClone(command); const rs = clone(derivedCommand.renderState, true); rs.cull.enabled = true; rs.cull.face = CullFace.FRONT; // Back faces do not need to write color. rs.colorMask = { red: false, green: false, blue: false, alpha: false, }; // Push back face depth away from the camera so it is less likely that back faces and front faces of the same tile // intersect and overlap. This helps avoid flickering for very thin double-sided walls. rs.polygonOffset = { enabled: true, factor: 5.0, units: 5.0, }; // Set the 3D Tiles bit rs.stencilTest = StencilConstants.setCesium3DTileBit(); rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK; derivedCommand.renderState = RenderState.fromCache(rs); derivedCommand.castShadows = false; derivedCommand.receiveShadows = false; derivedCommand.uniformMap = clone(command.uniformMap); const polygonOffset = new Cartesian2(5.0, 5.0); derivedCommand.uniformMap.u_polygonOffset = function () { return polygonOffset; }; // Make the log depth depth fragment write account for the polygon offset, too. // Otherwise, the back face commands will cause the higher resolution // tiles to disappear. derivedCommand.shaderProgram = getLogDepthPolygonOffsetFragmentShaderProgram( context, command.shaderProgram ); return derivedCommand; } function deriveStencilCommand(command, reference) { // Tiles only draw if their selection depth is >= the tile drawn already. They write their // selection depth to the stencil buffer to prevent ancestor tiles from drawing on top const derivedCommand = DrawCommand.shallowClone(command); const rs = clone(derivedCommand.renderState, true); // Stencil test is masked to the most significant 3 bits so the reference is shifted. Writes 0 for the terrain bit rs.stencilTest.enabled = true; rs.stencilTest.mask = StencilConstants.SKIP_LOD_MASK; rs.stencilTest.reference = StencilConstants.CESIUM_3D_TILE_MASK | (reference << StencilConstants.SKIP_LOD_BIT_SHIFT); rs.stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL; rs.stencilTest.frontOperation.zPass = StencilOperation.REPLACE; rs.stencilTest.backFunction = StencilFunction.GREATER_OR_EQUAL; rs.stencilTest.backOperation.zPass = StencilOperation.REPLACE; rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK | StencilConstants.SKIP_LOD_MASK; derivedCommand.renderState = RenderState.fromCache(rs); return derivedCommand; } function getLastSelectionDepth(stencilCommand) { // Isolate the selection depth from the stencil reference. const reference = stencilCommand.renderState.stencilTest.reference; return ( (reference & StencilConstants.SKIP_LOD_MASK) >>> StencilConstants.SKIP_LOD_BIT_SHIFT ); } function getTranslucentRenderState(renderState) { const rs = clone(renderState, true); rs.cull.enabled = false; rs.depthTest.enabled = true; rs.depthMask = false; rs.blending = BlendingState.ALPHA_BLEND; rs.stencilTest = StencilConstants.setCesium3DTileBit(); rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK; return RenderState.fromCache(rs); } function getOpaqueRenderState(renderState) { const rs = clone(renderState, true); rs.stencilTest = StencilConstants.setCesium3DTileBit(); rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK; return RenderState.fromCache(rs); } Cesium3DTileBatchTable.prototype.update = function (tileset, frameState) { this._batchTexture.update(tileset, frameState); }; Cesium3DTileBatchTable.prototype.isDestroyed = function () { return false; }; Cesium3DTileBatchTable.prototype.destroy = function () { this._batchTexture = this._batchTexture && this._batchTexture.destroy(); return destroyObject(this); }; export default Cesium3DTileBatchTable;