Cesium3DTileBatchTable.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143
  1. import Cartesian2 from "../Core/Cartesian2.js";
  2. import Check from "../Core/Check.js";
  3. import clone from "../Core/clone.js";
  4. import Color from "../Core/Color.js";
  5. import combine from "../Core/combine.js";
  6. import defaultValue from "../Core/defaultValue.js";
  7. import defined from "../Core/defined.js";
  8. import deprecationWarning from "../Core/deprecationWarning.js";
  9. import destroyObject from "../Core/destroyObject.js";
  10. import DeveloperError from "../Core/DeveloperError.js";
  11. import CesiumMath from "../Core/Math.js";
  12. import RuntimeError from "../Core/RuntimeError.js";
  13. import ContextLimits from "../Renderer/ContextLimits.js";
  14. import DrawCommand from "../Renderer/DrawCommand.js";
  15. import Pass from "../Renderer/Pass.js";
  16. import RenderState from "../Renderer/RenderState.js";
  17. import ShaderSource from "../Renderer/ShaderSource.js";
  18. import BatchTexture from "./BatchTexture.js";
  19. import BatchTableHierarchy from "./BatchTableHierarchy.js";
  20. import BlendingState from "./BlendingState.js";
  21. import Cesium3DTileColorBlendMode from "./Cesium3DTileColorBlendMode.js";
  22. import CullFace from "./CullFace.js";
  23. import getBinaryAccessor from "./getBinaryAccessor.js";
  24. import StencilConstants from "./StencilConstants.js";
  25. import StencilFunction from "./StencilFunction.js";
  26. import StencilOperation from "./StencilOperation.js";
  27. const DEFAULT_COLOR_VALUE = BatchTexture.DEFAULT_COLOR_VALUE;
  28. const DEFAULT_SHOW_VALUE = BatchTexture.DEFAULT_SHOW_VALUE;
  29. /**
  30. * @private
  31. * @constructor
  32. */
  33. function Cesium3DTileBatchTable(
  34. content,
  35. featuresLength,
  36. batchTableJson,
  37. batchTableBinary,
  38. colorChangedCallback
  39. ) {
  40. /**
  41. * @readonly
  42. */
  43. this.featuresLength = featuresLength;
  44. let extensions;
  45. if (defined(batchTableJson)) {
  46. extensions = batchTableJson.extensions;
  47. }
  48. this._extensions = defaultValue(extensions, {});
  49. const properties = initializeProperties(batchTableJson);
  50. this._properties = properties;
  51. this._batchTableHierarchy = initializeHierarchy(
  52. this,
  53. batchTableJson,
  54. batchTableBinary
  55. );
  56. this._batchTableBinaryProperties = getBinaryProperties(
  57. featuresLength,
  58. properties,
  59. batchTableBinary
  60. );
  61. this._content = content;
  62. this._batchTexture = new BatchTexture({
  63. featuresLength: featuresLength,
  64. colorChangedCallback: colorChangedCallback,
  65. owner: content,
  66. statistics: content.tileset.statistics,
  67. });
  68. }
  69. // This can be overridden for testing purposes
  70. Cesium3DTileBatchTable._deprecationWarning = deprecationWarning;
  71. Object.defineProperties(Cesium3DTileBatchTable.prototype, {
  72. memorySizeInBytes: {
  73. get: function () {
  74. return this._batchTexture.memorySizeInBytes;
  75. },
  76. },
  77. });
  78. function initializeProperties(jsonHeader) {
  79. const properties = {};
  80. if (!defined(jsonHeader)) {
  81. return properties;
  82. }
  83. for (const propertyName in jsonHeader) {
  84. if (
  85. jsonHeader.hasOwnProperty(propertyName) &&
  86. propertyName !== "HIERARCHY" && // Deprecated HIERARCHY property
  87. propertyName !== "extensions" &&
  88. propertyName !== "extras"
  89. ) {
  90. properties[propertyName] = clone(jsonHeader[propertyName], true);
  91. }
  92. }
  93. return properties;
  94. }
  95. function initializeHierarchy(batchTable, jsonHeader, binaryBody) {
  96. if (!defined(jsonHeader)) {
  97. return;
  98. }
  99. let hierarchy = batchTable._extensions["3DTILES_batch_table_hierarchy"];
  100. const legacyHierarchy = jsonHeader.HIERARCHY;
  101. if (defined(legacyHierarchy)) {
  102. Cesium3DTileBatchTable._deprecationWarning(
  103. "batchTableHierarchyExtension",
  104. "The batch table HIERARCHY property has been moved to an extension. Use extensions.3DTILES_batch_table_hierarchy instead."
  105. );
  106. batchTable._extensions["3DTILES_batch_table_hierarchy"] = legacyHierarchy;
  107. hierarchy = legacyHierarchy;
  108. }
  109. if (!defined(hierarchy)) {
  110. return;
  111. }
  112. return new BatchTableHierarchy({
  113. extension: hierarchy,
  114. binaryBody: binaryBody,
  115. });
  116. }
  117. function getBinaryProperties(featuresLength, properties, binaryBody) {
  118. let binaryProperties;
  119. for (const name in properties) {
  120. if (properties.hasOwnProperty(name)) {
  121. const property = properties[name];
  122. const byteOffset = property.byteOffset;
  123. if (defined(byteOffset)) {
  124. // This is a binary property
  125. const componentType = property.componentType;
  126. const type = property.type;
  127. if (!defined(componentType)) {
  128. throw new RuntimeError("componentType is required.");
  129. }
  130. if (!defined(type)) {
  131. throw new RuntimeError("type is required.");
  132. }
  133. if (!defined(binaryBody)) {
  134. throw new RuntimeError(
  135. `Property ${name} requires a batch table binary.`
  136. );
  137. }
  138. const binaryAccessor = getBinaryAccessor(property);
  139. const componentCount = binaryAccessor.componentsPerAttribute;
  140. const classType = binaryAccessor.classType;
  141. const typedArray = binaryAccessor.createArrayBufferView(
  142. binaryBody.buffer,
  143. binaryBody.byteOffset + byteOffset,
  144. featuresLength
  145. );
  146. if (!defined(binaryProperties)) {
  147. binaryProperties = {};
  148. }
  149. // Store any information needed to access the binary data, including the typed array,
  150. // componentCount (e.g. a VEC4 would be 4), and the type used to pack and unpack (e.g. Cartesian4).
  151. binaryProperties[name] = {
  152. typedArray: typedArray,
  153. componentCount: componentCount,
  154. type: classType,
  155. };
  156. }
  157. }
  158. }
  159. return binaryProperties;
  160. }
  161. Cesium3DTileBatchTable.getBinaryProperties = function (
  162. featuresLength,
  163. batchTableJson,
  164. batchTableBinary
  165. ) {
  166. return getBinaryProperties(featuresLength, batchTableJson, batchTableBinary);
  167. };
  168. Cesium3DTileBatchTable.prototype.setShow = function (batchId, show) {
  169. this._batchTexture.setShow(batchId, show);
  170. };
  171. Cesium3DTileBatchTable.prototype.setAllShow = function (show) {
  172. this._batchTexture.setAllShow(show);
  173. };
  174. Cesium3DTileBatchTable.prototype.getShow = function (batchId) {
  175. return this._batchTexture.getShow(batchId);
  176. };
  177. Cesium3DTileBatchTable.prototype.setColor = function (batchId, color) {
  178. this._batchTexture.setColor(batchId, color);
  179. };
  180. Cesium3DTileBatchTable.prototype.setAllColor = function (color) {
  181. this._batchTexture.setAllColor(color);
  182. };
  183. Cesium3DTileBatchTable.prototype.getColor = function (batchId, result) {
  184. return this._batchTexture.getColor(batchId, result);
  185. };
  186. Cesium3DTileBatchTable.prototype.getPickColor = function (batchId) {
  187. return this._batchTexture.getPickColor(batchId);
  188. };
  189. const scratchColor = new Color();
  190. Cesium3DTileBatchTable.prototype.applyStyle = function (style) {
  191. if (!defined(style)) {
  192. this.setAllColor(DEFAULT_COLOR_VALUE);
  193. this.setAllShow(DEFAULT_SHOW_VALUE);
  194. return;
  195. }
  196. const content = this._content;
  197. const length = this.featuresLength;
  198. for (let i = 0; i < length; ++i) {
  199. const feature = content.getFeature(i);
  200. const color = defined(style.color)
  201. ? defaultValue(
  202. style.color.evaluateColor(feature, scratchColor),
  203. DEFAULT_COLOR_VALUE
  204. )
  205. : DEFAULT_COLOR_VALUE;
  206. const show = defined(style.show)
  207. ? defaultValue(style.show.evaluate(feature), DEFAULT_SHOW_VALUE)
  208. : DEFAULT_SHOW_VALUE;
  209. this.setColor(i, color);
  210. this.setShow(i, show);
  211. }
  212. };
  213. function getBinaryProperty(binaryProperty, index) {
  214. const typedArray = binaryProperty.typedArray;
  215. const componentCount = binaryProperty.componentCount;
  216. if (componentCount === 1) {
  217. return typedArray[index];
  218. }
  219. return binaryProperty.type.unpack(typedArray, index * componentCount);
  220. }
  221. function setBinaryProperty(binaryProperty, index, value) {
  222. const typedArray = binaryProperty.typedArray;
  223. const componentCount = binaryProperty.componentCount;
  224. if (componentCount === 1) {
  225. typedArray[index] = value;
  226. } else {
  227. binaryProperty.type.pack(value, typedArray, index * componentCount);
  228. }
  229. }
  230. function checkBatchId(batchId, featuresLength) {
  231. if (!defined(batchId) || batchId < 0 || batchId >= featuresLength) {
  232. throw new DeveloperError(
  233. `batchId is required and between zero and featuresLength - 1 (${featuresLength}` -
  234. +")."
  235. );
  236. }
  237. }
  238. Cesium3DTileBatchTable.prototype.isClass = function (batchId, className) {
  239. //>>includeStart('debug', pragmas.debug);
  240. checkBatchId(batchId, this.featuresLength);
  241. Check.typeOf.string("className", className);
  242. //>>includeEnd('debug');
  243. const hierarchy = this._batchTableHierarchy;
  244. if (!defined(hierarchy)) {
  245. return false;
  246. }
  247. return hierarchy.isClass(batchId, className);
  248. };
  249. Cesium3DTileBatchTable.prototype.isExactClass = function (batchId, className) {
  250. //>>includeStart('debug', pragmas.debug);
  251. Check.typeOf.string("className", className);
  252. //>>includeEnd('debug');
  253. return this.getExactClassName(batchId) === className;
  254. };
  255. Cesium3DTileBatchTable.prototype.getExactClassName = function (batchId) {
  256. //>>includeStart('debug', pragmas.debug);
  257. checkBatchId(batchId, this.featuresLength);
  258. //>>includeEnd('debug');
  259. const hierarchy = this._batchTableHierarchy;
  260. if (!defined(hierarchy)) {
  261. return undefined;
  262. }
  263. return hierarchy.getClassName(batchId);
  264. };
  265. Cesium3DTileBatchTable.prototype.hasProperty = function (batchId, name) {
  266. //>>includeStart('debug', pragmas.debug);
  267. checkBatchId(batchId, this.featuresLength);
  268. Check.typeOf.string("name", name);
  269. //>>includeEnd('debug');
  270. return (
  271. defined(this._properties[name]) ||
  272. (defined(this._batchTableHierarchy) &&
  273. this._batchTableHierarchy.hasProperty(batchId, name))
  274. );
  275. };
  276. /**
  277. * @private
  278. */
  279. Cesium3DTileBatchTable.prototype.hasPropertyBySemantic = function () {
  280. // Cesium 3D Tiles 1.0 formats do not have semantics
  281. return false;
  282. };
  283. Cesium3DTileBatchTable.prototype.getPropertyNames = function (
  284. batchId,
  285. results
  286. ) {
  287. //>>includeStart('debug', pragmas.debug);
  288. checkBatchId(batchId, this.featuresLength);
  289. //>>includeEnd('debug');
  290. results = defined(results) ? results : [];
  291. results.length = 0;
  292. const scratchPropertyNames = Object.keys(this._properties);
  293. results.push.apply(results, scratchPropertyNames);
  294. if (defined(this._batchTableHierarchy)) {
  295. results.push.apply(
  296. results,
  297. this._batchTableHierarchy.getPropertyIds(batchId, scratchPropertyNames)
  298. );
  299. }
  300. return results;
  301. };
  302. /**
  303. * @private
  304. */
  305. Cesium3DTileBatchTable.prototype.getPropertyBySemantic = function (
  306. batchId,
  307. name
  308. ) {
  309. // Cesium 3D Tiles 1.0 formats do not have semantics
  310. return undefined;
  311. };
  312. Cesium3DTileBatchTable.prototype.getProperty = function (batchId, name) {
  313. //>>includeStart('debug', pragmas.debug);
  314. checkBatchId(batchId, this.featuresLength);
  315. Check.typeOf.string("name", name);
  316. //>>includeEnd('debug');
  317. if (defined(this._batchTableBinaryProperties)) {
  318. const binaryProperty = this._batchTableBinaryProperties[name];
  319. if (defined(binaryProperty)) {
  320. return getBinaryProperty(binaryProperty, batchId);
  321. }
  322. }
  323. const propertyValues = this._properties[name];
  324. if (defined(propertyValues)) {
  325. return clone(propertyValues[batchId], true);
  326. }
  327. if (defined(this._batchTableHierarchy)) {
  328. const hierarchyProperty = this._batchTableHierarchy.getProperty(
  329. batchId,
  330. name
  331. );
  332. if (defined(hierarchyProperty)) {
  333. return hierarchyProperty;
  334. }
  335. }
  336. return undefined;
  337. };
  338. Cesium3DTileBatchTable.prototype.setProperty = function (batchId, name, value) {
  339. const featuresLength = this.featuresLength;
  340. //>>includeStart('debug', pragmas.debug);
  341. checkBatchId(batchId, featuresLength);
  342. Check.typeOf.string("name", name);
  343. //>>includeEnd('debug');
  344. if (defined(this._batchTableBinaryProperties)) {
  345. const binaryProperty = this._batchTableBinaryProperties[name];
  346. if (defined(binaryProperty)) {
  347. setBinaryProperty(binaryProperty, batchId, value);
  348. return;
  349. }
  350. }
  351. if (defined(this._batchTableHierarchy)) {
  352. if (this._batchTableHierarchy.setProperty(batchId, name, value)) {
  353. return;
  354. }
  355. }
  356. let propertyValues = this._properties[name];
  357. if (!defined(propertyValues)) {
  358. // Property does not exist. Create it.
  359. this._properties[name] = new Array(featuresLength);
  360. propertyValues = this._properties[name];
  361. }
  362. propertyValues[batchId] = clone(value, true);
  363. };
  364. function getGlslComputeSt(batchTable) {
  365. // GLSL batchId is zero-based: [0, featuresLength - 1]
  366. if (batchTable._batchTexture.textureDimensions.y === 1) {
  367. return (
  368. "uniform vec4 tile_textureStep; \n" +
  369. "vec2 computeSt(float batchId) \n" +
  370. "{ \n" +
  371. " float stepX = tile_textureStep.x; \n" +
  372. " float centerX = tile_textureStep.y; \n" +
  373. " return vec2(centerX + (batchId * stepX), 0.5); \n" +
  374. "} \n"
  375. );
  376. }
  377. return (
  378. "uniform vec4 tile_textureStep; \n" +
  379. "uniform vec2 tile_textureDimensions; \n" +
  380. "vec2 computeSt(float batchId) \n" +
  381. "{ \n" +
  382. " float stepX = tile_textureStep.x; \n" +
  383. " float centerX = tile_textureStep.y; \n" +
  384. " float stepY = tile_textureStep.z; \n" +
  385. " float centerY = tile_textureStep.w; \n" +
  386. " float xId = mod(batchId, tile_textureDimensions.x); \n" +
  387. " float yId = floor(batchId / tile_textureDimensions.x); \n" +
  388. " return vec2(centerX + (xId * stepX), centerY + (yId * stepY)); \n" +
  389. "} \n"
  390. );
  391. }
  392. Cesium3DTileBatchTable.prototype.getVertexShaderCallback = function (
  393. handleTranslucent,
  394. batchIdAttributeName,
  395. diffuseAttributeOrUniformName
  396. ) {
  397. if (this.featuresLength === 0) {
  398. return;
  399. }
  400. const that = this;
  401. return function (source) {
  402. // If the color blend mode is HIGHLIGHT, the highlight color will always be applied in the fragment shader.
  403. // No need to apply the highlight color in the vertex shader as well.
  404. const renamedSource = modifyDiffuse(
  405. source,
  406. diffuseAttributeOrUniformName,
  407. false
  408. );
  409. let newMain;
  410. if (ContextLimits.maximumVertexTextureImageUnits > 0) {
  411. // When VTF is supported, perform per-feature show/hide in the vertex shader
  412. newMain = "";
  413. if (handleTranslucent) {
  414. newMain += "uniform bool tile_translucentCommand; \n";
  415. }
  416. newMain +=
  417. `${
  418. "uniform sampler2D tile_batchTexture; \n" +
  419. "varying vec4 tile_featureColor; \n" +
  420. "varying vec2 tile_featureSt; \n" +
  421. "void main() \n" +
  422. "{ \n" +
  423. " vec2 st = computeSt("
  424. }${batchIdAttributeName}); \n` +
  425. ` vec4 featureProperties = texture2D(tile_batchTexture, st); \n` +
  426. ` tile_color(featureProperties); \n` +
  427. ` float show = ceil(featureProperties.a); \n` + // 0 - false, non-zeo - true
  428. ` gl_Position *= show; \n`; // Per-feature show/hide
  429. if (handleTranslucent) {
  430. newMain +=
  431. " bool isStyleTranslucent = (featureProperties.a != 1.0); \n" +
  432. " if (czm_pass == czm_passTranslucent) \n" +
  433. " { \n" +
  434. " if (!isStyleTranslucent && !tile_translucentCommand) \n" + // Do not render opaque features in the translucent pass
  435. " { \n" +
  436. " gl_Position *= 0.0; \n" +
  437. " } \n" +
  438. " } \n" +
  439. " else \n" +
  440. " { \n" +
  441. " if (isStyleTranslucent) \n" + // Do not render translucent features in the opaque pass
  442. " { \n" +
  443. " gl_Position *= 0.0; \n" +
  444. " } \n" +
  445. " } \n";
  446. }
  447. newMain +=
  448. " tile_featureColor = featureProperties; \n" +
  449. " tile_featureSt = st; \n" +
  450. "}";
  451. } else {
  452. // 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
  453. newMain =
  454. `${
  455. "varying vec2 tile_featureSt; \n" +
  456. "void main() \n" +
  457. "{ \n" +
  458. " tile_color(vec4(1.0)); \n" +
  459. " tile_featureSt = computeSt("
  460. }${batchIdAttributeName}); \n` + `}`;
  461. }
  462. return `${renamedSource}\n${getGlslComputeSt(that)}${newMain}`;
  463. };
  464. };
  465. function getDefaultShader(source, applyHighlight) {
  466. source = ShaderSource.replaceMain(source, "tile_main");
  467. if (!applyHighlight) {
  468. return (
  469. `${source}void tile_color(vec4 tile_featureColor) \n` +
  470. `{ \n` +
  471. ` tile_main(); \n` +
  472. `} \n`
  473. );
  474. }
  475. // The color blend mode is intended for the RGB channels so alpha is always just multiplied.
  476. // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight)
  477. return (
  478. `${source}uniform float tile_colorBlend; \n` +
  479. `void tile_color(vec4 tile_featureColor) \n` +
  480. `{ \n` +
  481. ` tile_main(); \n` +
  482. ` tile_featureColor = czm_gammaCorrect(tile_featureColor); \n` +
  483. ` gl_FragColor.a *= tile_featureColor.a; \n` +
  484. ` float highlight = ceil(tile_colorBlend); \n` +
  485. ` gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n` +
  486. `} \n`
  487. );
  488. }
  489. function replaceDiffuseTextureCalls(source, diffuseAttributeOrUniformName) {
  490. const functionCall = `texture2D(${diffuseAttributeOrUniformName}`;
  491. let fromIndex = 0;
  492. let startIndex = source.indexOf(functionCall, fromIndex);
  493. let endIndex;
  494. while (startIndex > -1) {
  495. let nestedLevel = 0;
  496. for (let i = startIndex; i < source.length; ++i) {
  497. const character = source.charAt(i);
  498. if (character === "(") {
  499. ++nestedLevel;
  500. } else if (character === ")") {
  501. --nestedLevel;
  502. if (nestedLevel === 0) {
  503. endIndex = i + 1;
  504. break;
  505. }
  506. }
  507. }
  508. const extractedFunction = source.slice(startIndex, endIndex);
  509. const replacedFunction = `tile_diffuse_final(${extractedFunction}, tile_diffuse)`;
  510. source =
  511. source.slice(0, startIndex) + replacedFunction + source.slice(endIndex);
  512. fromIndex = startIndex + replacedFunction.length;
  513. startIndex = source.indexOf(functionCall, fromIndex);
  514. }
  515. return source;
  516. }
  517. function modifyDiffuse(source, diffuseAttributeOrUniformName, applyHighlight) {
  518. // If the glTF does not specify the _3DTILESDIFFUSE semantic, return the default shader.
  519. // Otherwise if _3DTILESDIFFUSE is defined prefer the shader below that can switch the color mode at runtime.
  520. if (!defined(diffuseAttributeOrUniformName)) {
  521. return getDefaultShader(source, applyHighlight);
  522. }
  523. // Find the diffuse uniform. Examples matches:
  524. // uniform vec3 u_diffuseColor;
  525. // uniform sampler2D diffuseTexture;
  526. let regex = new RegExp(
  527. `(uniform|attribute|in)\\s+(vec[34]|sampler2D)\\s+${diffuseAttributeOrUniformName};`
  528. );
  529. const uniformMatch = source.match(regex);
  530. if (!defined(uniformMatch)) {
  531. // Could not find uniform declaration of type vec3, vec4, or sampler2D
  532. return getDefaultShader(source, applyHighlight);
  533. }
  534. const declaration = uniformMatch[0];
  535. const type = uniformMatch[2];
  536. source = ShaderSource.replaceMain(source, "tile_main");
  537. source = source.replace(declaration, ""); // Remove uniform declaration for now so the replace below doesn't affect it
  538. // If the tile color is white, use the source color. This implies the feature has not been styled.
  539. // Highlight: tile_colorBlend is 0.0 and the source color is used
  540. // Replace: tile_colorBlend is 1.0 and the tile color is used
  541. // Mix: tile_colorBlend is between 0.0 and 1.0, causing the source color and tile color to mix
  542. const finalDiffuseFunction =
  543. "bool isWhite(vec3 color) \n" +
  544. "{ \n" +
  545. " return all(greaterThan(color, vec3(1.0 - czm_epsilon3))); \n" +
  546. "} \n" +
  547. "vec4 tile_diffuse_final(vec4 sourceDiffuse, vec4 tileDiffuse) \n" +
  548. "{ \n" +
  549. " vec4 blendDiffuse = mix(sourceDiffuse, tileDiffuse, tile_colorBlend); \n" +
  550. " vec4 diffuse = isWhite(tileDiffuse.rgb) ? sourceDiffuse : blendDiffuse; \n" +
  551. " return vec4(diffuse.rgb, sourceDiffuse.a); \n" +
  552. "} \n";
  553. // The color blend mode is intended for the RGB channels so alpha is always just multiplied.
  554. // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight)
  555. const highlight =
  556. " tile_featureColor = czm_gammaCorrect(tile_featureColor); \n" +
  557. " gl_FragColor.a *= tile_featureColor.a; \n" +
  558. " float highlight = ceil(tile_colorBlend); \n" +
  559. " gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n";
  560. let setColor;
  561. if (type === "vec3" || type === "vec4") {
  562. const sourceDiffuse =
  563. type === "vec3"
  564. ? `vec4(${diffuseAttributeOrUniformName}, 1.0)`
  565. : diffuseAttributeOrUniformName;
  566. const replaceDiffuse =
  567. type === "vec3" ? "tile_diffuse.xyz" : "tile_diffuse";
  568. regex = new RegExp(diffuseAttributeOrUniformName, "g");
  569. source = source.replace(regex, replaceDiffuse);
  570. setColor =
  571. ` vec4 source = ${sourceDiffuse}; \n` +
  572. ` tile_diffuse = tile_diffuse_final(source, tile_featureColor); \n` +
  573. ` tile_main(); \n`;
  574. } else if (type === "sampler2D") {
  575. // Handles any number of nested parentheses
  576. // E.g. texture2D(u_diffuse, uv)
  577. // E.g. texture2D(u_diffuse, computeUV(index))
  578. source = replaceDiffuseTextureCalls(source, diffuseAttributeOrUniformName);
  579. setColor =
  580. " tile_diffuse = tile_featureColor; \n" + " tile_main(); \n";
  581. }
  582. source =
  583. `${
  584. "uniform float tile_colorBlend; \n" + "vec4 tile_diffuse = vec4(1.0); \n"
  585. }${finalDiffuseFunction}${declaration}\n${source}\n` +
  586. `void tile_color(vec4 tile_featureColor) \n` +
  587. `{ \n${setColor}`;
  588. if (applyHighlight) {
  589. source += highlight;
  590. }
  591. source += "} \n";
  592. return source;
  593. }
  594. Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function (
  595. handleTranslucent,
  596. diffuseAttributeOrUniformName,
  597. hasPremultipliedAlpha
  598. ) {
  599. if (this.featuresLength === 0) {
  600. return;
  601. }
  602. return function (source) {
  603. source = modifyDiffuse(source, diffuseAttributeOrUniformName, true);
  604. if (ContextLimits.maximumVertexTextureImageUnits > 0) {
  605. // When VTF is supported, per-feature show/hide already happened in the fragment shader
  606. source +=
  607. "uniform sampler2D tile_pickTexture; \n" +
  608. "varying vec2 tile_featureSt; \n" +
  609. "varying vec4 tile_featureColor; \n" +
  610. "void main() \n" +
  611. "{ \n" +
  612. " tile_color(tile_featureColor); \n";
  613. if (hasPremultipliedAlpha) {
  614. source += " gl_FragColor.rgb *= gl_FragColor.a; \n";
  615. }
  616. source += "}";
  617. } else {
  618. if (handleTranslucent) {
  619. source += "uniform bool tile_translucentCommand; \n";
  620. }
  621. source +=
  622. "uniform sampler2D tile_pickTexture; \n" +
  623. "uniform sampler2D tile_batchTexture; \n" +
  624. "varying vec2 tile_featureSt; \n" +
  625. "void main() \n" +
  626. "{ \n" +
  627. " vec4 featureProperties = texture2D(tile_batchTexture, tile_featureSt); \n" +
  628. " if (featureProperties.a == 0.0) { \n" + // show: alpha == 0 - false, non-zeo - true
  629. " discard; \n" +
  630. " } \n";
  631. if (handleTranslucent) {
  632. source +=
  633. " bool isStyleTranslucent = (featureProperties.a != 1.0); \n" +
  634. " if (czm_pass == czm_passTranslucent) \n" +
  635. " { \n" +
  636. " if (!isStyleTranslucent && !tile_translucentCommand) \n" + // Do not render opaque features in the translucent pass
  637. " { \n" +
  638. " discard; \n" +
  639. " } \n" +
  640. " } \n" +
  641. " else \n" +
  642. " { \n" +
  643. " if (isStyleTranslucent) \n" + // Do not render translucent features in the opaque pass
  644. " { \n" +
  645. " discard; \n" +
  646. " } \n" +
  647. " } \n";
  648. }
  649. source += " tile_color(featureProperties); \n";
  650. if (hasPremultipliedAlpha) {
  651. source += " gl_FragColor.rgb *= gl_FragColor.a; \n";
  652. }
  653. source += "} \n";
  654. }
  655. return source;
  656. };
  657. };
  658. Cesium3DTileBatchTable.prototype.getClassificationFragmentShaderCallback = function () {
  659. if (this.featuresLength === 0) {
  660. return;
  661. }
  662. return function (source) {
  663. source = ShaderSource.replaceMain(source, "tile_main");
  664. if (ContextLimits.maximumVertexTextureImageUnits > 0) {
  665. // When VTF is supported, per-feature show/hide already happened in the fragment shader
  666. source +=
  667. "uniform sampler2D tile_pickTexture;\n" +
  668. "varying vec2 tile_featureSt; \n" +
  669. "varying vec4 tile_featureColor; \n" +
  670. "void main() \n" +
  671. "{ \n" +
  672. " tile_main(); \n" +
  673. " gl_FragColor = tile_featureColor; \n" +
  674. " gl_FragColor.rgb *= gl_FragColor.a; \n" +
  675. "}";
  676. } else {
  677. source +=
  678. "uniform sampler2D tile_batchTexture; \n" +
  679. "uniform sampler2D tile_pickTexture;\n" +
  680. "varying vec2 tile_featureSt; \n" +
  681. "void main() \n" +
  682. "{ \n" +
  683. " tile_main(); \n" +
  684. " vec4 featureProperties = texture2D(tile_batchTexture, tile_featureSt); \n" +
  685. " if (featureProperties.a == 0.0) { \n" + // show: alpha == 0 - false, non-zeo - true
  686. " discard; \n" +
  687. " } \n" +
  688. " gl_FragColor = featureProperties; \n" +
  689. " gl_FragColor.rgb *= gl_FragColor.a; \n" +
  690. "} \n";
  691. }
  692. return source;
  693. };
  694. };
  695. function getColorBlend(batchTable) {
  696. const tileset = batchTable._content.tileset;
  697. const colorBlendMode = tileset.colorBlendMode;
  698. const colorBlendAmount = tileset.colorBlendAmount;
  699. if (colorBlendMode === Cesium3DTileColorBlendMode.HIGHLIGHT) {
  700. return 0.0;
  701. }
  702. if (colorBlendMode === Cesium3DTileColorBlendMode.REPLACE) {
  703. return 1.0;
  704. }
  705. if (colorBlendMode === Cesium3DTileColorBlendMode.MIX) {
  706. // The value 0.0 is reserved for highlight, so clamp to just above 0.0.
  707. return CesiumMath.clamp(colorBlendAmount, CesiumMath.EPSILON4, 1.0);
  708. }
  709. //>>includeStart('debug', pragmas.debug);
  710. throw new DeveloperError(`Invalid color blend mode "${colorBlendMode}".`);
  711. //>>includeEnd('debug');
  712. }
  713. Cesium3DTileBatchTable.prototype.getUniformMapCallback = function () {
  714. if (this.featuresLength === 0) {
  715. return;
  716. }
  717. const that = this;
  718. return function (uniformMap) {
  719. const batchUniformMap = {
  720. tile_batchTexture: function () {
  721. // PERFORMANCE_IDEA: we could also use a custom shader that avoids the texture read.
  722. return defaultValue(
  723. that._batchTexture.batchTexture,
  724. that._batchTexture.defaultTexture
  725. );
  726. },
  727. tile_textureDimensions: function () {
  728. return that._batchTexture.textureDimensions;
  729. },
  730. tile_textureStep: function () {
  731. return that._batchTexture.textureStep;
  732. },
  733. tile_colorBlend: function () {
  734. return getColorBlend(that);
  735. },
  736. tile_pickTexture: function () {
  737. return that._batchTexture.pickTexture;
  738. },
  739. };
  740. return combine(uniformMap, batchUniformMap);
  741. };
  742. };
  743. Cesium3DTileBatchTable.prototype.getPickId = function () {
  744. return "texture2D(tile_pickTexture, tile_featureSt)";
  745. };
  746. ///////////////////////////////////////////////////////////////////////////
  747. const StyleCommandsNeeded = {
  748. ALL_OPAQUE: 0,
  749. ALL_TRANSLUCENT: 1,
  750. OPAQUE_AND_TRANSLUCENT: 2,
  751. };
  752. Cesium3DTileBatchTable.prototype.addDerivedCommands = function (
  753. frameState,
  754. commandStart
  755. ) {
  756. const commandList = frameState.commandList;
  757. const commandEnd = commandList.length;
  758. const tile = this._content._tile;
  759. const finalResolution = tile._finalResolution;
  760. const tileset = tile.tileset;
  761. const bivariateVisibilityTest =
  762. tileset._skipLevelOfDetail &&
  763. tileset._hasMixedContent &&
  764. frameState.context.stencilBuffer;
  765. const styleCommandsNeeded = getStyleCommandsNeeded(this);
  766. for (let i = commandStart; i < commandEnd; ++i) {
  767. const command = commandList[i];
  768. if (command.pass === Pass.COMPUTE) {
  769. continue;
  770. }
  771. let derivedCommands = command.derivedCommands.tileset;
  772. if (!defined(derivedCommands) || command.dirty) {
  773. derivedCommands = {};
  774. command.derivedCommands.tileset = derivedCommands;
  775. derivedCommands.originalCommand = deriveCommand(command);
  776. command.dirty = false;
  777. }
  778. const originalCommand = derivedCommands.originalCommand;
  779. if (
  780. styleCommandsNeeded !== StyleCommandsNeeded.ALL_OPAQUE &&
  781. command.pass !== Pass.TRANSLUCENT
  782. ) {
  783. if (!defined(derivedCommands.translucent)) {
  784. derivedCommands.translucent = deriveTranslucentCommand(originalCommand);
  785. }
  786. }
  787. if (
  788. styleCommandsNeeded !== StyleCommandsNeeded.ALL_TRANSLUCENT &&
  789. command.pass !== Pass.TRANSLUCENT
  790. ) {
  791. if (!defined(derivedCommands.opaque)) {
  792. derivedCommands.opaque = deriveOpaqueCommand(originalCommand);
  793. }
  794. if (bivariateVisibilityTest) {
  795. if (!finalResolution) {
  796. if (!defined(derivedCommands.zback)) {
  797. derivedCommands.zback = deriveZBackfaceCommand(
  798. frameState.context,
  799. originalCommand
  800. );
  801. }
  802. tileset._backfaceCommands.push(derivedCommands.zback);
  803. }
  804. if (
  805. !defined(derivedCommands.stencil) ||
  806. tile._selectionDepth !==
  807. getLastSelectionDepth(derivedCommands.stencil)
  808. ) {
  809. if (command.renderState.depthMask) {
  810. derivedCommands.stencil = deriveStencilCommand(
  811. originalCommand,
  812. tile._selectionDepth
  813. );
  814. } else {
  815. // Ignore if tile does not write depth
  816. derivedCommands.stencil = derivedCommands.opaque;
  817. }
  818. }
  819. }
  820. }
  821. const opaqueCommand = bivariateVisibilityTest
  822. ? derivedCommands.stencil
  823. : derivedCommands.opaque;
  824. const translucentCommand = derivedCommands.translucent;
  825. // If the command was originally opaque:
  826. // * If the styling applied to the tile is all opaque, use the opaque command
  827. // (with one additional uniform needed for the shader).
  828. // * If the styling is all translucent, use new (cached) derived commands (front
  829. // and back faces) with a translucent render state.
  830. // * If the styling causes both opaque and translucent features in this tile,
  831. // then use both sets of commands.
  832. if (command.pass !== Pass.TRANSLUCENT) {
  833. if (styleCommandsNeeded === StyleCommandsNeeded.ALL_OPAQUE) {
  834. commandList[i] = opaqueCommand;
  835. }
  836. if (styleCommandsNeeded === StyleCommandsNeeded.ALL_TRANSLUCENT) {
  837. commandList[i] = translucentCommand;
  838. }
  839. if (styleCommandsNeeded === StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT) {
  840. // PERFORMANCE_IDEA: if the tile has multiple commands, we do not know what features are in what
  841. // commands so this case may be overkill.
  842. commandList[i] = opaqueCommand;
  843. commandList.push(translucentCommand);
  844. }
  845. } else {
  846. // Command was originally translucent so no need to derive new commands;
  847. // as of now, a style can't change an originally translucent feature to
  848. // opaque since the style's alpha is modulated, not a replacement. When
  849. // this changes, we need to derive new opaque commands here.
  850. commandList[i] = originalCommand;
  851. }
  852. }
  853. };
  854. function getStyleCommandsNeeded(batchTable) {
  855. const translucentFeaturesLength =
  856. batchTable._batchTexture.translucentFeaturesLength;
  857. if (translucentFeaturesLength === 0) {
  858. return StyleCommandsNeeded.ALL_OPAQUE;
  859. } else if (translucentFeaturesLength === batchTable.featuresLength) {
  860. return StyleCommandsNeeded.ALL_TRANSLUCENT;
  861. }
  862. return StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT;
  863. }
  864. function deriveCommand(command) {
  865. const derivedCommand = DrawCommand.shallowClone(command);
  866. // Add a uniform to indicate if the original command was translucent so
  867. // the shader knows not to cull vertices that were originally transparent
  868. // even though their style is opaque.
  869. const translucentCommand = derivedCommand.pass === Pass.TRANSLUCENT;
  870. derivedCommand.uniformMap = defined(derivedCommand.uniformMap)
  871. ? derivedCommand.uniformMap
  872. : {};
  873. derivedCommand.uniformMap.tile_translucentCommand = function () {
  874. return translucentCommand;
  875. };
  876. return derivedCommand;
  877. }
  878. function deriveTranslucentCommand(command) {
  879. const derivedCommand = DrawCommand.shallowClone(command);
  880. derivedCommand.pass = Pass.TRANSLUCENT;
  881. derivedCommand.renderState = getTranslucentRenderState(command.renderState);
  882. return derivedCommand;
  883. }
  884. function deriveOpaqueCommand(command) {
  885. const derivedCommand = DrawCommand.shallowClone(command);
  886. derivedCommand.renderState = getOpaqueRenderState(command.renderState);
  887. return derivedCommand;
  888. }
  889. function getLogDepthPolygonOffsetFragmentShaderProgram(context, shaderProgram) {
  890. let shader = context.shaderCache.getDerivedShaderProgram(
  891. shaderProgram,
  892. "zBackfaceLogDepth"
  893. );
  894. if (!defined(shader)) {
  895. const fs = shaderProgram.fragmentShaderSource.clone();
  896. fs.defines = defined(fs.defines) ? fs.defines.slice(0) : [];
  897. fs.defines.push("POLYGON_OFFSET");
  898. fs.sources.unshift(
  899. "#ifdef GL_OES_standard_derivatives\n#extension GL_OES_standard_derivatives : enable\n#endif\n"
  900. );
  901. shader = context.shaderCache.createDerivedShaderProgram(
  902. shaderProgram,
  903. "zBackfaceLogDepth",
  904. {
  905. vertexShaderSource: shaderProgram.vertexShaderSource,
  906. fragmentShaderSource: fs,
  907. attributeLocations: shaderProgram._attributeLocations,
  908. }
  909. );
  910. }
  911. return shader;
  912. }
  913. function deriveZBackfaceCommand(context, command) {
  914. // Write just backface depth of unresolved tiles so resolved stenciled tiles do not appear in front
  915. const derivedCommand = DrawCommand.shallowClone(command);
  916. const rs = clone(derivedCommand.renderState, true);
  917. rs.cull.enabled = true;
  918. rs.cull.face = CullFace.FRONT;
  919. // Back faces do not need to write color.
  920. rs.colorMask = {
  921. red: false,
  922. green: false,
  923. blue: false,
  924. alpha: false,
  925. };
  926. // Push back face depth away from the camera so it is less likely that back faces and front faces of the same tile
  927. // intersect and overlap. This helps avoid flickering for very thin double-sided walls.
  928. rs.polygonOffset = {
  929. enabled: true,
  930. factor: 5.0,
  931. units: 5.0,
  932. };
  933. // Set the 3D Tiles bit
  934. rs.stencilTest = StencilConstants.setCesium3DTileBit();
  935. rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK;
  936. derivedCommand.renderState = RenderState.fromCache(rs);
  937. derivedCommand.castShadows = false;
  938. derivedCommand.receiveShadows = false;
  939. derivedCommand.uniformMap = clone(command.uniformMap);
  940. const polygonOffset = new Cartesian2(5.0, 5.0);
  941. derivedCommand.uniformMap.u_polygonOffset = function () {
  942. return polygonOffset;
  943. };
  944. // Make the log depth depth fragment write account for the polygon offset, too.
  945. // Otherwise, the back face commands will cause the higher resolution
  946. // tiles to disappear.
  947. derivedCommand.shaderProgram = getLogDepthPolygonOffsetFragmentShaderProgram(
  948. context,
  949. command.shaderProgram
  950. );
  951. return derivedCommand;
  952. }
  953. function deriveStencilCommand(command, reference) {
  954. // Tiles only draw if their selection depth is >= the tile drawn already. They write their
  955. // selection depth to the stencil buffer to prevent ancestor tiles from drawing on top
  956. const derivedCommand = DrawCommand.shallowClone(command);
  957. const rs = clone(derivedCommand.renderState, true);
  958. // Stencil test is masked to the most significant 3 bits so the reference is shifted. Writes 0 for the terrain bit
  959. rs.stencilTest.enabled = true;
  960. rs.stencilTest.mask = StencilConstants.SKIP_LOD_MASK;
  961. rs.stencilTest.reference =
  962. StencilConstants.CESIUM_3D_TILE_MASK |
  963. (reference << StencilConstants.SKIP_LOD_BIT_SHIFT);
  964. rs.stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL;
  965. rs.stencilTest.frontOperation.zPass = StencilOperation.REPLACE;
  966. rs.stencilTest.backFunction = StencilFunction.GREATER_OR_EQUAL;
  967. rs.stencilTest.backOperation.zPass = StencilOperation.REPLACE;
  968. rs.stencilMask =
  969. StencilConstants.CESIUM_3D_TILE_MASK | StencilConstants.SKIP_LOD_MASK;
  970. derivedCommand.renderState = RenderState.fromCache(rs);
  971. return derivedCommand;
  972. }
  973. function getLastSelectionDepth(stencilCommand) {
  974. // Isolate the selection depth from the stencil reference.
  975. const reference = stencilCommand.renderState.stencilTest.reference;
  976. return (
  977. (reference & StencilConstants.SKIP_LOD_MASK) >>>
  978. StencilConstants.SKIP_LOD_BIT_SHIFT
  979. );
  980. }
  981. function getTranslucentRenderState(renderState) {
  982. const rs = clone(renderState, true);
  983. rs.cull.enabled = false;
  984. rs.depthTest.enabled = true;
  985. rs.depthMask = false;
  986. rs.blending = BlendingState.ALPHA_BLEND;
  987. rs.stencilTest = StencilConstants.setCesium3DTileBit();
  988. rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK;
  989. return RenderState.fromCache(rs);
  990. }
  991. function getOpaqueRenderState(renderState) {
  992. const rs = clone(renderState, true);
  993. rs.stencilTest = StencilConstants.setCesium3DTileBit();
  994. rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK;
  995. return RenderState.fromCache(rs);
  996. }
  997. Cesium3DTileBatchTable.prototype.update = function (tileset, frameState) {
  998. this._batchTexture.update(tileset, frameState);
  999. };
  1000. Cesium3DTileBatchTable.prototype.isDestroyed = function () {
  1001. return false;
  1002. };
  1003. Cesium3DTileBatchTable.prototype.destroy = function () {
  1004. this._batchTexture = this._batchTexture && this._batchTexture.destroy();
  1005. return destroyObject(this);
  1006. };
  1007. export default Cesium3DTileBatchTable;