ModelDrawCommand.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. import BoundingSphere from "../../Core/BoundingSphere.js";
  2. import Cartesian2 from "../../Core/Cartesian2.js";
  3. import CesiumMath from "../../Core/Math.js";
  4. import Check from "../../Core/Check.js";
  5. import clone from "../../Core/clone.js";
  6. import defaultValue from "../../Core/defaultValue.js";
  7. import defined from "../../Core/defined.js";
  8. import Matrix4 from "../../Core/Matrix4.js";
  9. import WebGLConstants from "../../Core/WebGLConstants.js";
  10. import DrawCommand from "../../Renderer/DrawCommand.js";
  11. import Pass from "../../Renderer/Pass.js";
  12. import RenderState from "../../Renderer/RenderState.js";
  13. import BlendingState from "../BlendingState.js";
  14. import CullFace from "../CullFace.js";
  15. import SceneMode from "../SceneMode.js";
  16. import ShadowMode from "../ShadowMode.js";
  17. import StencilConstants from "../StencilConstants.js";
  18. import StencilFunction from "../StencilFunction.js";
  19. import StencilOperation from "../StencilOperation.js";
  20. import StyleCommandsNeeded from "./StyleCommandsNeeded.js";
  21. /**
  22. * A wrapper around the draw commands used to render a {@link ModelRuntimePrimitive}.
  23. * This manages the derived commands and pushes only the necessary commands depending
  24. * on the given frame state.
  25. *
  26. * @param {object} options An object containing the following options:
  27. * @param {DrawCommand} options.command The draw command from which to derive other commands from.
  28. * @param {PrimitiveRenderResources} options.primitiveRenderResources The render resources of the primitive associated with the command.
  29. *
  30. * @alias ModelDrawCommand
  31. * @constructor
  32. *
  33. * @private
  34. */
  35. function ModelDrawCommand(options) {
  36. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  37. const command = options.command;
  38. const renderResources = options.primitiveRenderResources;
  39. //>>includeStart('debug', pragmas.debug);
  40. Check.typeOf.object("options.command", command);
  41. Check.typeOf.object("options.primitiveRenderResources", renderResources);
  42. //>>includeEnd('debug');
  43. const model = renderResources.model;
  44. this._model = model;
  45. const runtimePrimitive = renderResources.runtimePrimitive;
  46. this._runtimePrimitive = runtimePrimitive;
  47. // If the command is translucent, or if the primitive's material is
  48. // double-sided, then back-face culling is automatically disabled for
  49. // the command. The user value for back-face culling will be ignored.
  50. const isTranslucent = command.pass === Pass.TRANSLUCENT;
  51. const isDoubleSided = runtimePrimitive.primitive.material.doubleSided;
  52. const usesBackFaceCulling = !isDoubleSided && !isTranslucent;
  53. const hasSilhouette = renderResources.hasSilhouette;
  54. // If the command was already translucent, there's no need to derive a new
  55. // translucent command. As of now, a style can't change an originally
  56. // translucent feature to opaque since the style's alpha is modulated,
  57. // not replaced. When this changes, we need to derive new opaque commands
  58. // in initialize().
  59. //
  60. // Silhouettes for primitives with both opaque and translucent features
  61. // are not yet supported.
  62. const needsTranslucentCommand = !isTranslucent && !hasSilhouette;
  63. const needsSkipLevelOfDetailCommands =
  64. renderResources.hasSkipLevelOfDetail && !isTranslucent;
  65. const needsSilhouetteCommands = hasSilhouette;
  66. this._command = command;
  67. // None of the derived commands (non-2D) use a different model matrix
  68. // or bounding volume than the original, so they all point to the
  69. // ModelDrawCommand's copy to save update time and memory.
  70. this._modelMatrix = Matrix4.clone(command.modelMatrix);
  71. this._boundingVolume = BoundingSphere.clone(command.boundingVolume);
  72. // The 2D model matrix depends on the frame state's map projection,
  73. // so it must be updated when the commands are handled in pushCommands.
  74. this._modelMatrix2D = new Matrix4();
  75. this._boundingVolume2D = new BoundingSphere();
  76. this._modelMatrix2DDirty = false;
  77. this._backFaceCulling = command.renderState.cull.enabled;
  78. this._cullFace = command.renderState.cull.face;
  79. this._shadows = model.shadows;
  80. this._debugShowBoundingVolume = command.debugShowBoundingVolume;
  81. this._usesBackFaceCulling = usesBackFaceCulling;
  82. this._needsTranslucentCommand = needsTranslucentCommand;
  83. this._needsSkipLevelOfDetailCommands = needsSkipLevelOfDetailCommands;
  84. this._needsSilhouetteCommands = needsSilhouetteCommands;
  85. // Derived commands
  86. this._originalCommand = undefined;
  87. this._translucentCommand = undefined;
  88. this._skipLodBackfaceCommand = undefined;
  89. this._skipLodStencilCommand = undefined;
  90. this._silhouetteModelCommand = undefined;
  91. this._silhouetteColorCommand = undefined;
  92. // All derived commands (including 2D commands)
  93. this._derivedCommands = [];
  94. this._has2DCommands = false;
  95. initialize(this);
  96. }
  97. function ModelDerivedCommand(options) {
  98. // The DrawCommand managed by this derived command.
  99. this.command = options.command;
  100. // These control whether the derived command should update the
  101. // values of the DrawCommand for the corresponding properties.
  102. this.updateShadows = options.updateShadows;
  103. this.updateBackFaceCulling = options.updateBackFaceCulling;
  104. this.updateCullFace = options.updateCullFace;
  105. this.updateDebugShowBoundingVolume = options.updateDebugShowBoundingVolume;
  106. // Whether this ModelDerivedCommand is in 2D.
  107. this.is2D = defaultValue(options.is2D, false);
  108. // A ModelDerivedCommand that is the 2D version of this one.
  109. this.derivedCommand2D = undefined;
  110. }
  111. ModelDerivedCommand.clone = function (derivedCommand) {
  112. return new ModelDerivedCommand({
  113. command: derivedCommand.command,
  114. updateShadows: derivedCommand.updateShadows,
  115. updateBackFaceCulling: derivedCommand.updateBackFaceCulling,
  116. updateCullFace: derivedCommand.updateCullFace,
  117. updateDebugShowBoundingVolume: derivedCommand.updateDebugShowBoundingVolume,
  118. is2D: derivedCommand.is2D,
  119. derivedCommand2D: derivedCommand.derivedCommand2D,
  120. });
  121. };
  122. function initialize(drawCommand) {
  123. const command = drawCommand._command;
  124. command.modelMatrix = drawCommand._modelMatrix;
  125. command.boundingVolume = drawCommand._boundingVolume;
  126. const model = drawCommand._model;
  127. const usesBackFaceCulling = drawCommand._usesBackFaceCulling;
  128. const derivedCommands = drawCommand._derivedCommands;
  129. drawCommand._originalCommand = new ModelDerivedCommand({
  130. command: command,
  131. updateShadows: true,
  132. updateBackFaceCulling: usesBackFaceCulling,
  133. updateCullFace: usesBackFaceCulling,
  134. updateDebugShowBoundingVolume: true,
  135. is2D: false,
  136. });
  137. derivedCommands.push(drawCommand._originalCommand);
  138. if (drawCommand._needsTranslucentCommand) {
  139. drawCommand._translucentCommand = new ModelDerivedCommand({
  140. command: deriveTranslucentCommand(command),
  141. updateShadows: true,
  142. updateBackFaceCulling: false,
  143. updateCullFace: false,
  144. updateDebugShowBoundingVolume: true,
  145. });
  146. derivedCommands.push(drawCommand._translucentCommand);
  147. }
  148. if (drawCommand._needsSkipLevelOfDetailCommands) {
  149. drawCommand._skipLodBackfaceCommand = new ModelDerivedCommand({
  150. command: deriveSkipLodBackfaceCommand(command),
  151. updateShadows: false,
  152. updateBackFaceCulling: false,
  153. updateCullFace: usesBackFaceCulling,
  154. updateDebugShowBoundingVolume: false,
  155. });
  156. drawCommand._skipLodStencilCommand = new ModelDerivedCommand({
  157. command: deriveSkipLodStencilCommand(command, model),
  158. updateShadows: true,
  159. updateBackFaceCulling: usesBackFaceCulling,
  160. updateCullFace: usesBackFaceCulling,
  161. updateDebugShowBoundingVolume: true,
  162. });
  163. derivedCommands.push(drawCommand._skipLodBackfaceCommand);
  164. derivedCommands.push(drawCommand._skipLodStencilCommand);
  165. }
  166. if (drawCommand._needsSilhouetteCommands) {
  167. drawCommand._silhouetteModelCommand = new ModelDerivedCommand({
  168. command: deriveSilhouetteModelCommand(command, model),
  169. updateShadows: true,
  170. updateBackFaceCulling: usesBackFaceCulling,
  171. updateCullFace: usesBackFaceCulling,
  172. updateDebugShowBoundingVolume: true,
  173. });
  174. drawCommand._silhouetteColorCommand = new ModelDerivedCommand({
  175. command: deriveSilhouetteColorCommand(command, model),
  176. updateShadows: false,
  177. updateBackFaceCulling: false,
  178. updateCullFace: false,
  179. updateDebugShowBoundingVolume: false,
  180. });
  181. derivedCommands.push(drawCommand._silhouetteModelCommand);
  182. derivedCommands.push(drawCommand._silhouetteColorCommand);
  183. }
  184. }
  185. Object.defineProperties(ModelDrawCommand.prototype, {
  186. /**
  187. * The main draw command that the other commands are derived from.
  188. *
  189. * @memberof ModelDrawCommand.prototype
  190. * @type {DrawCommand}
  191. *
  192. * @readonly
  193. * @private
  194. */
  195. command: {
  196. get: function () {
  197. return this._command;
  198. },
  199. },
  200. /**
  201. * The runtime primitive that the draw command belongs to.
  202. *
  203. * @memberof ModelDrawCommand.prototype
  204. * @type {ModelRuntimePrimitive}
  205. *
  206. * @readonly
  207. * @private
  208. */
  209. runtimePrimitive: {
  210. get: function () {
  211. return this._runtimePrimitive;
  212. },
  213. },
  214. /**
  215. * The model that the draw command belongs to.
  216. *
  217. * @memberof ModelDrawCommand.prototype
  218. * @type {Model}
  219. *
  220. * @readonly
  221. * @private
  222. */
  223. model: {
  224. get: function () {
  225. return this._model;
  226. },
  227. },
  228. /**
  229. * The primitive type of the draw command.
  230. *
  231. * @memberof ModelDrawCommand.prototype
  232. * @type {PrimitiveType}
  233. *
  234. * @readonly
  235. * @private
  236. */
  237. primitiveType: {
  238. get: function () {
  239. return this._command.primitiveType;
  240. },
  241. },
  242. /**
  243. * The current model matrix applied to the draw commands. If there are
  244. * 2D draw commands, their model matrix will be derived from the 3D one.
  245. *
  246. * @memberof ModelDrawCommand.prototype
  247. * @type {Matrix4}
  248. *
  249. * @readonly
  250. * @private
  251. */
  252. modelMatrix: {
  253. get: function () {
  254. return this._modelMatrix;
  255. },
  256. set: function (value) {
  257. this._modelMatrix = Matrix4.clone(value, this._modelMatrix);
  258. this._modelMatrix2DDirty = true;
  259. this._boundingVolume = BoundingSphere.transform(
  260. this.runtimePrimitive.boundingSphere,
  261. this._modelMatrix,
  262. this._boundingVolume
  263. );
  264. },
  265. },
  266. /**
  267. * The bounding volume of the main draw command. This is equivalent
  268. * to the primitive's bounding sphere transformed by the draw
  269. * command's model matrix.
  270. *
  271. * @memberof ModelDrawCommand.prototype
  272. * @type {BoundingSphere}
  273. *
  274. * @readonly
  275. * @private
  276. */
  277. boundingVolume: {
  278. get: function () {
  279. return this._boundingVolume;
  280. },
  281. },
  282. /**
  283. * Whether the geometry casts or receives shadows from light sources.
  284. *
  285. * @memberof ModelDrawCommand.prototype
  286. * @type {ShadowMode}
  287. *
  288. * @private
  289. */
  290. shadows: {
  291. get: function () {
  292. return this._shadows;
  293. },
  294. set: function (value) {
  295. this._shadows = value;
  296. updateShadows(this);
  297. },
  298. },
  299. /**
  300. * Whether to cull back-facing geometry. When true, back face culling is
  301. * determined by the material's doubleSided property; when false, back face
  302. * culling is disabled. Back faces are not culled if the command is
  303. * translucent.
  304. *
  305. * @memberof ModelDrawCommand.prototype
  306. * @type {boolean}
  307. *
  308. * @private
  309. */
  310. backFaceCulling: {
  311. get: function () {
  312. return this._backFaceCulling;
  313. },
  314. set: function (value) {
  315. if (this._backFaceCulling === value) {
  316. return;
  317. }
  318. this._backFaceCulling = value;
  319. updateBackFaceCulling(this);
  320. },
  321. },
  322. /**
  323. * Determines which faces to cull, if culling is enabled.
  324. *
  325. * @memberof ModelDrawCommand.prototype
  326. * @type {CullFace}
  327. *
  328. * @private
  329. */
  330. cullFace: {
  331. get: function () {
  332. return this._cullFace;
  333. },
  334. set: function (value) {
  335. if (this._cullFace === value) {
  336. return;
  337. }
  338. this._cullFace = value;
  339. updateCullFace(this);
  340. },
  341. },
  342. /**
  343. * Whether to draw the bounding sphere associated with this draw command.
  344. *
  345. * @memberof ModelDrawCommand.prototype
  346. * @type {boolean}
  347. *
  348. * @private
  349. */
  350. debugShowBoundingVolume: {
  351. get: function () {
  352. return this._debugShowBoundingVolume;
  353. },
  354. set: function (value) {
  355. if (this._debugShowBoundingVolume === value) {
  356. return;
  357. }
  358. this._debugShowBoundingVolume = value;
  359. updateDebugShowBoundingVolume(this);
  360. },
  361. },
  362. });
  363. function updateModelMatrix2D(drawCommand, frameState) {
  364. const modelMatrix = drawCommand._modelMatrix;
  365. drawCommand._modelMatrix2D = Matrix4.clone(
  366. modelMatrix,
  367. drawCommand._modelMatrix2D
  368. );
  369. // Change the translation's y-component so it appears on the opposite side
  370. // of the map.
  371. drawCommand._modelMatrix2D[13] -=
  372. CesiumMath.sign(modelMatrix[13]) *
  373. 2.0 *
  374. CesiumMath.PI *
  375. frameState.mapProjection.ellipsoid.maximumRadius;
  376. drawCommand._boundingVolume2D = BoundingSphere.transform(
  377. drawCommand.runtimePrimitive.boundingSphere,
  378. drawCommand._modelMatrix2D,
  379. drawCommand._boundingVolume2D
  380. );
  381. }
  382. function updateShadows(drawCommand) {
  383. const shadows = drawCommand.shadows;
  384. const castShadows = ShadowMode.castShadows(shadows);
  385. const receiveShadows = ShadowMode.receiveShadows(shadows);
  386. const derivedCommands = drawCommand._derivedCommands;
  387. for (let i = 0; i < derivedCommands.length; ++i) {
  388. const derivedCommand = derivedCommands[i];
  389. if (derivedCommand.updateShadows) {
  390. const command = derivedCommand.command;
  391. command.castShadows = castShadows;
  392. command.receiveShadows = receiveShadows;
  393. }
  394. }
  395. }
  396. function updateBackFaceCulling(drawCommand) {
  397. const backFaceCulling = drawCommand.backFaceCulling;
  398. const derivedCommands = drawCommand._derivedCommands;
  399. for (let i = 0; i < derivedCommands.length; ++i) {
  400. const derivedCommand = derivedCommands[i];
  401. if (derivedCommand.updateBackFaceCulling) {
  402. const command = derivedCommand.command;
  403. const renderState = clone(command.renderState, true);
  404. renderState.cull.enabled = backFaceCulling;
  405. command.renderState = RenderState.fromCache(renderState);
  406. }
  407. }
  408. }
  409. function updateCullFace(drawCommand) {
  410. const cullFace = drawCommand.cullFace;
  411. const derivedCommands = drawCommand._derivedCommands;
  412. for (let i = 0; i < derivedCommands.length; ++i) {
  413. const derivedCommand = derivedCommands[i];
  414. if (derivedCommand.updateCullFace) {
  415. const command = derivedCommand.command;
  416. const renderState = clone(command.renderState, true);
  417. renderState.cull.face = cullFace;
  418. command.renderState = RenderState.fromCache(renderState);
  419. }
  420. }
  421. }
  422. function updateDebugShowBoundingVolume(drawCommand) {
  423. const debugShowBoundingVolume = drawCommand.debugShowBoundingVolume;
  424. const derivedCommands = drawCommand._derivedCommands;
  425. for (let i = 0; i < derivedCommands.length; ++i) {
  426. const derivedCommand = derivedCommands[i];
  427. if (derivedCommand.updateDebugShowBoundingVolume) {
  428. const command = derivedCommand.command;
  429. command.debugShowBoundingVolume = debugShowBoundingVolume;
  430. }
  431. }
  432. }
  433. /**
  434. * Pushes the draw commands necessary to render the primitive.
  435. * This does not include the draw commands that render its silhouette.
  436. *
  437. * @param {FrameState} frameState The frame state.
  438. * @param {DrawCommand[]} result The array to push the draw commands to.
  439. *
  440. * @returns {DrawCommand[]} The modified result parameter.
  441. *
  442. * @private
  443. */
  444. ModelDrawCommand.prototype.pushCommands = function (frameState, result) {
  445. const use2D = shouldUse2DCommands(this, frameState);
  446. if (use2D && !this._has2DCommands) {
  447. derive2DCommands(this);
  448. this._has2DCommands = true;
  449. this._modelMatrix2DDirty = true;
  450. }
  451. if (this._modelMatrix2DDirty) {
  452. updateModelMatrix2D(this, frameState);
  453. this._modelMatrix2DDirty = false;
  454. }
  455. const styleCommandsNeeded = this.model.styleCommandsNeeded;
  456. if (this._needsTranslucentCommand && defined(styleCommandsNeeded)) {
  457. // StyleCommandsNeeded has three values: all opaque, all translucent, or both.
  458. if (styleCommandsNeeded !== StyleCommandsNeeded.ALL_OPAQUE) {
  459. pushCommand(result, this._translucentCommand, use2D);
  460. }
  461. // Continue only if opaque commands are needed.
  462. if (styleCommandsNeeded === StyleCommandsNeeded.ALL_TRANSLUCENT) {
  463. return;
  464. }
  465. }
  466. if (this._needsSkipLevelOfDetailCommands) {
  467. const { tileset, tile } = this._model.content;
  468. if (tileset.hasMixedContent) {
  469. if (!tile._finalResolution) {
  470. pushCommand(
  471. tileset._backfaceCommands,
  472. this._skipLodBackfaceCommand,
  473. use2D
  474. );
  475. }
  476. updateSkipLodStencilCommand(this, tile, use2D);
  477. pushCommand(result, this._skipLodStencilCommand, use2D);
  478. return;
  479. }
  480. }
  481. if (this._needsSilhouetteCommands) {
  482. pushCommand(result, this._silhouetteModelCommand, use2D);
  483. return;
  484. }
  485. pushCommand(result, this._originalCommand, use2D);
  486. return result;
  487. };
  488. /**
  489. * Pushes the draw commands necessary to render the silhouette. These should
  490. * be added to the command list after the draw commands of all primitives
  491. * in the model have been added. This way, the silhouette won't render on
  492. * top of the model.
  493. * <p>
  494. * This should only be called after pushCommands() has been invoked for
  495. * the ModelDrawCommand this frame. Otherwise, the silhouette commands may
  496. * not have been derived for 2D. The model matrix will also not have been
  497. * updated for 2D commands.
  498. * </p>
  499. *
  500. * @param {FrameState} frameState The frame state.
  501. * @param {DrawCommand[]} result The array to push the silhouette commands to.
  502. *
  503. * @returns {DrawCommand[]} The modified result parameter.
  504. *
  505. * @private
  506. */
  507. ModelDrawCommand.prototype.pushSilhouetteCommands = function (
  508. frameState,
  509. result
  510. ) {
  511. const use2D = shouldUse2DCommands(this, frameState);
  512. pushCommand(result, this._silhouetteColorCommand, use2D);
  513. return result;
  514. };
  515. function pushCommand(commandList, derivedCommand, use2D) {
  516. commandList.push(derivedCommand.command);
  517. if (use2D) {
  518. commandList.push(derivedCommand.derivedCommand2D.command);
  519. }
  520. }
  521. function shouldUse2DCommands(drawCommand, frameState) {
  522. if (frameState.mode !== SceneMode.SCENE2D || drawCommand.model._projectTo2D) {
  523. return false;
  524. }
  525. // The draw command's bounding sphere might cause primitives not to render
  526. // over the IDL, even if they are part of the same model. Use the scene graph's
  527. // bounding sphere instead.
  528. const model = drawCommand.model;
  529. const boundingSphere = model.sceneGraph._boundingSphere2D;
  530. const left = boundingSphere.center.y - boundingSphere.radius;
  531. const right = boundingSphere.center.y + boundingSphere.radius;
  532. const idl2D =
  533. frameState.mapProjection.ellipsoid.maximumRadius * CesiumMath.PI;
  534. return (left < idl2D && right > idl2D) || (left < -idl2D && right > -idl2D);
  535. }
  536. function derive2DCommand(drawCommand, derivedCommand) {
  537. if (!defined(derivedCommand)) {
  538. return;
  539. }
  540. // If the model crosses the IDL in 2D, it will be drawn in one viewport but get
  541. // clipped by the other viewport. We create a second command that translates
  542. // the model matrix to the opposite side of the map so the part that was clipped
  543. // in one viewport is drawn in the other.
  544. const derivedCommand2D = ModelDerivedCommand.clone(derivedCommand);
  545. const command2D = DrawCommand.shallowClone(derivedCommand.command);
  546. command2D.modelMatrix = drawCommand._modelMatrix2D;
  547. command2D.boundingVolume = drawCommand._boundingVolume2D;
  548. derivedCommand2D.command = command2D;
  549. derivedCommand2D.updateShadows = false; // Shadows are disabled for 2D
  550. derivedCommand2D.is2D = true;
  551. derivedCommand.derivedCommand2D = derivedCommand2D;
  552. drawCommand._derivedCommands.push(derivedCommand2D);
  553. return derivedCommand2D;
  554. }
  555. function derive2DCommands(drawCommand) {
  556. derive2DCommand(drawCommand, drawCommand._originalCommand);
  557. derive2DCommand(drawCommand, drawCommand._translucentCommand);
  558. derive2DCommand(drawCommand, drawCommand._skipLodBackfaceCommand);
  559. derive2DCommand(drawCommand, drawCommand._skipLodStencilCommand);
  560. derive2DCommand(drawCommand, drawCommand._silhouetteModelCommand);
  561. derive2DCommand(drawCommand, drawCommand._silhouetteColorCommand);
  562. }
  563. function deriveTranslucentCommand(command) {
  564. const derivedCommand = DrawCommand.shallowClone(command);
  565. derivedCommand.pass = Pass.TRANSLUCENT;
  566. const rs = clone(command.renderState, true);
  567. rs.cull.enabled = false;
  568. rs.depthMask = false;
  569. rs.blending = BlendingState.ALPHA_BLEND;
  570. derivedCommand.renderState = RenderState.fromCache(rs);
  571. return derivedCommand;
  572. }
  573. function deriveSilhouetteModelCommand(command, model) {
  574. // Wrap around after exceeding the 8-bit stencil limit.
  575. // The reference is unique to each model until this point.
  576. const stencilReference = model._silhouetteId % 255;
  577. const silhouetteModelCommand = DrawCommand.shallowClone(command);
  578. const renderState = clone(command.renderState, true);
  579. // Write the reference value into the stencil buffer.
  580. renderState.stencilTest = {
  581. enabled: true,
  582. frontFunction: WebGLConstants.ALWAYS,
  583. backFunction: WebGLConstants.ALWAYS,
  584. reference: stencilReference,
  585. mask: ~0,
  586. frontOperation: {
  587. fail: WebGLConstants.KEEP,
  588. zFail: WebGLConstants.KEEP,
  589. zPass: WebGLConstants.REPLACE,
  590. },
  591. backOperation: {
  592. fail: WebGLConstants.KEEP,
  593. zFail: WebGLConstants.KEEP,
  594. zPass: WebGLConstants.REPLACE,
  595. },
  596. };
  597. if (model.isInvisible()) {
  598. renderState.colorMask = {
  599. red: false,
  600. green: false,
  601. blue: false,
  602. alpha: false,
  603. };
  604. }
  605. silhouetteModelCommand.renderState = RenderState.fromCache(renderState);
  606. return silhouetteModelCommand;
  607. }
  608. function deriveSilhouetteColorCommand(command, model) {
  609. // Wrap around after exceeding the 8-bit stencil limit.
  610. // The reference is unique to each model until this point.
  611. const stencilReference = model._silhouetteId % 255;
  612. const silhouetteColorCommand = DrawCommand.shallowClone(command);
  613. const renderState = clone(command.renderState, true);
  614. renderState.cull.enabled = false;
  615. // Render the silhouette in the translucent pass if either the command
  616. // pass or the silhouette color is translucent. This will account for
  617. // translucent model color, since ModelColorPipelineStage sets the pass
  618. // to translucent.
  619. const silhouetteTranslucent =
  620. command.pass === Pass.TRANSLUCENT || model.silhouetteColor.alpha < 1.0;
  621. if (silhouetteTranslucent) {
  622. silhouetteColorCommand.pass = Pass.TRANSLUCENT;
  623. renderState.depthMask = false;
  624. renderState.blending = BlendingState.ALPHA_BLEND;
  625. }
  626. // Only render the pixels of the silhouette that don't conflict with
  627. // the stencil buffer. This way, the silhouette doesn't render over
  628. // the original model.
  629. renderState.stencilTest = {
  630. enabled: true,
  631. frontFunction: WebGLConstants.NOTEQUAL,
  632. backFunction: WebGLConstants.NOTEQUAL,
  633. reference: stencilReference,
  634. mask: ~0,
  635. frontOperation: {
  636. fail: WebGLConstants.KEEP,
  637. zFail: WebGLConstants.KEEP,
  638. zPass: WebGLConstants.KEEP,
  639. },
  640. backOperation: {
  641. fail: WebGLConstants.KEEP,
  642. zFail: WebGLConstants.KEEP,
  643. zPass: WebGLConstants.KEEP,
  644. },
  645. };
  646. const uniformMap = clone(command.uniformMap);
  647. uniformMap.model_silhouettePass = function () {
  648. return true;
  649. };
  650. silhouetteColorCommand.renderState = RenderState.fromCache(renderState);
  651. silhouetteColorCommand.uniformMap = uniformMap;
  652. silhouetteColorCommand.castShadows = false;
  653. silhouetteColorCommand.receiveShadows = false;
  654. return silhouetteColorCommand;
  655. }
  656. function updateSkipLodStencilCommand(drawCommand, tile, use2D) {
  657. const stencilDerivedComand = drawCommand._skipLodStencilCommand;
  658. const stencilCommand = stencilDerivedComand.command;
  659. const selectionDepth = tile._selectionDepth;
  660. const lastSelectionDepth = getLastSelectionDepth(stencilCommand);
  661. if (selectionDepth !== lastSelectionDepth) {
  662. const skipLodStencilReference = getStencilReference(selectionDepth);
  663. const renderState = clone(stencilCommand.renderState, true);
  664. renderState.stencilTest.reference = skipLodStencilReference;
  665. stencilCommand.renderState = RenderState.fromCache(renderState);
  666. if (use2D) {
  667. stencilDerivedComand.derivedCommand2D.renderState = renderState;
  668. }
  669. }
  670. }
  671. function getLastSelectionDepth(stencilCommand) {
  672. // Isolate the selection depth from the stencil reference.
  673. const reference = stencilCommand.renderState.stencilTest.reference;
  674. return (
  675. (reference & StencilConstants.SKIP_LOD_MASK) >>>
  676. StencilConstants.SKIP_LOD_BIT_SHIFT
  677. );
  678. }
  679. function getStencilReference(selectionDepth) {
  680. // Stencil test is masked to the most significant 3 bits so the reference is shifted.
  681. // Writes 0 for the terrain bit.
  682. return (
  683. StencilConstants.CESIUM_3D_TILE_MASK |
  684. (selectionDepth << StencilConstants.SKIP_LOD_BIT_SHIFT)
  685. );
  686. }
  687. function deriveSkipLodBackfaceCommand(command) {
  688. // Write just backface depth of unresolved tiles so resolved stenciled tiles
  689. // do not appear in front.
  690. const backfaceCommand = DrawCommand.shallowClone(command);
  691. const renderState = clone(command.renderState, true);
  692. renderState.cull.enabled = true;
  693. renderState.cull.face = CullFace.FRONT;
  694. // Back faces do not need to write color.
  695. renderState.colorMask = {
  696. red: false,
  697. green: false,
  698. blue: false,
  699. alpha: false,
  700. };
  701. // Push back face depth away from the camera so it is less likely that back faces and front faces of the same tile
  702. // intersect and overlap. This helps avoid flickering for very thin double-sided walls.
  703. renderState.polygonOffset = {
  704. enabled: true,
  705. factor: 5.0,
  706. units: 5.0,
  707. };
  708. // The stencil test is set in TilesetPipelineStage.
  709. const uniformMap = clone(backfaceCommand.uniformMap);
  710. const polygonOffset = new Cartesian2(5.0, 5.0);
  711. uniformMap.u_polygonOffset = function () {
  712. return polygonOffset;
  713. };
  714. backfaceCommand.renderState = RenderState.fromCache(renderState);
  715. backfaceCommand.uniformMap = uniformMap;
  716. backfaceCommand.castShadows = false;
  717. backfaceCommand.receiveShadows = false;
  718. return backfaceCommand;
  719. }
  720. function deriveSkipLodStencilCommand(command) {
  721. // Tiles only draw if their selection depth is >= the tile drawn already. They write their
  722. // selection depth to the stencil buffer to prevent ancestor tiles from drawing on top
  723. const stencilCommand = DrawCommand.shallowClone(command);
  724. const renderState = clone(command.renderState, true);
  725. // The stencil reference is updated dynamically; see updateSkipLodStencilCommand().
  726. const { stencilTest } = renderState;
  727. stencilTest.enabled = true;
  728. stencilTest.mask = StencilConstants.SKIP_LOD_MASK;
  729. stencilTest.reference = StencilConstants.CESIUM_3D_TILE_MASK;
  730. stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL;
  731. stencilTest.frontOperation.zPass = StencilOperation.REPLACE;
  732. stencilTest.backFunction = StencilFunction.GREATER_OR_EQUAL;
  733. stencilTest.backOperation.zPass = StencilOperation.REPLACE;
  734. renderState.stencilMask =
  735. StencilConstants.CESIUM_3D_TILE_MASK | StencilConstants.SKIP_LOD_MASK;
  736. stencilCommand.renderState = RenderState.fromCache(renderState);
  737. return stencilCommand;
  738. }
  739. export default ModelDrawCommand;