Cesium3DTileBatchTable.js 38 KB

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