depthPeelingRenderer.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. /**
  2. * Implementation based on https://medium.com/@shrekshao_71662/dual-depth-peeling-implementation-in-webgl-11baa061ba4b
  3. */
  4. import { MultiRenderTarget } from "../Materials/Textures/multiRenderTarget.js";
  5. import { Color4 } from "../Maths/math.color.js";
  6. import { SmartArray } from "../Misc/smartArray.js";
  7. import { ThinTexture } from "../Materials/Textures/thinTexture.js";
  8. import { EffectRenderer, EffectWrapper } from "../Materials/effectRenderer.js";
  9. import { RenderTargetTexture } from "../Materials/Textures/renderTargetTexture.js";
  10. import { Logger } from "../Misc/logger.js";
  11. import { Material } from "../Materials/material.js";
  12. import "../Shaders/postprocess.vertex.js";
  13. import "../Shaders/oitFinal.fragment.js";
  14. import "../Shaders/oitBackBlend.fragment.js";
  15. class DepthPeelingEffectConfiguration {
  16. constructor() {
  17. /**
  18. * Is this effect enabled
  19. */
  20. this.enabled = true;
  21. /**
  22. * Name of the configuration
  23. */
  24. this.name = "depthPeeling";
  25. /**
  26. * Textures that should be present in the MRT for this effect to work
  27. */
  28. this.texturesRequired = [4];
  29. }
  30. }
  31. /**
  32. * The depth peeling renderer that performs
  33. * Order independant transparency (OIT).
  34. * This should not be instanciated directly, as it is part of a scene component
  35. */
  36. export class DepthPeelingRenderer {
  37. /**
  38. * Number of depth peeling passes. As we are using dual depth peeling, each pass two levels of transparency are processed.
  39. */
  40. get passCount() {
  41. return this._passCount;
  42. }
  43. set passCount(count) {
  44. if (this._passCount === count) {
  45. return;
  46. }
  47. this._passCount = count;
  48. this._createRenderPassIds();
  49. }
  50. /**
  51. * Instructs the renderer to use render passes. It is an optimization that makes the rendering faster for some engines (like WebGPU) but that consumes more memory, so it is disabled by default.
  52. */
  53. get useRenderPasses() {
  54. return this._useRenderPasses;
  55. }
  56. set useRenderPasses(usePasses) {
  57. if (this._useRenderPasses === usePasses) {
  58. return;
  59. }
  60. this._useRenderPasses = usePasses;
  61. this._createRenderPassIds();
  62. }
  63. /**
  64. * Add a mesh in the exclusion list to prevent it to be handled by the depth peeling renderer
  65. * @param mesh The mesh to exclude from the depth peeling renderer
  66. */
  67. addExcludedMesh(mesh) {
  68. if (this._excludedMeshes.indexOf(mesh.uniqueId) === -1) {
  69. this._excludedMeshes.push(mesh.uniqueId);
  70. }
  71. }
  72. /**
  73. * Remove a mesh from the exclusion list of the depth peeling renderer
  74. * @param mesh The mesh to remove
  75. */
  76. removeExcludedMesh(mesh) {
  77. const index = this._excludedMeshes.indexOf(mesh.uniqueId);
  78. if (index !== -1) {
  79. this._excludedMeshes.splice(index, 1);
  80. }
  81. }
  82. /**
  83. * Instanciates the depth peeling renderer
  84. * @param scene Scene to attach to
  85. * @param passCount Number of depth layers to peel
  86. * @returns The depth peeling renderer
  87. */
  88. constructor(scene, passCount = 5) {
  89. this._thinTextures = [];
  90. this._currentPingPongState = 0;
  91. this._layoutCacheFormat = [[true], [true, true], [true, true, true]];
  92. this._layoutCache = [];
  93. this._candidateSubMeshes = new SmartArray(10);
  94. this._excludedSubMeshes = new SmartArray(10);
  95. this._excludedMeshes = [];
  96. this._colorCache = [
  97. new Color4(DepthPeelingRenderer._DEPTH_CLEAR_VALUE, DepthPeelingRenderer._DEPTH_CLEAR_VALUE, 0, 0),
  98. new Color4(-DepthPeelingRenderer._MIN_DEPTH, DepthPeelingRenderer._MAX_DEPTH, 0, 0),
  99. new Color4(0, 0, 0, 0),
  100. ];
  101. this._scene = scene;
  102. this._engine = scene.getEngine();
  103. this._passCount = passCount;
  104. // We need a depth texture for opaque
  105. if (!scene.enablePrePassRenderer()) {
  106. Logger.Warn("Depth peeling for order independant transparency could not enable PrePass, aborting.");
  107. return;
  108. }
  109. for (let i = 0; i < this._layoutCacheFormat.length; ++i) {
  110. this._layoutCache[i] = this._engine.buildTextureLayout(this._layoutCacheFormat[i]);
  111. }
  112. this._renderPassIds = [];
  113. this.useRenderPasses = false;
  114. this._prePassEffectConfiguration = new DepthPeelingEffectConfiguration();
  115. this._createTextures();
  116. this._createEffects();
  117. }
  118. _createRenderPassIds() {
  119. this._releaseRenderPassIds();
  120. if (this._useRenderPasses) {
  121. for (let i = 0; i < this._passCount + 1; ++i) {
  122. if (!this._renderPassIds[i]) {
  123. this._renderPassIds[i] = this._engine.createRenderPassId(`DepthPeelingRenderer - pass #${i}`);
  124. }
  125. }
  126. }
  127. }
  128. _releaseRenderPassIds() {
  129. for (let i = 0; i < this._renderPassIds.length; ++i) {
  130. this._engine.releaseRenderPassId(this._renderPassIds[i]);
  131. }
  132. this._renderPassIds = [];
  133. }
  134. _createTextures() {
  135. const size = {
  136. width: this._engine.getRenderWidth(),
  137. height: this._engine.getRenderHeight(),
  138. };
  139. // 2 for ping pong
  140. this._depthMrts = [
  141. new MultiRenderTarget("depthPeelingDepth0MRT", size, 3, this._scene, undefined, [
  142. "depthPeelingDepth0MRT_depth",
  143. "depthPeelingDepth0MRT_frontColor",
  144. "depthPeelingDepth0MRT_backColor",
  145. ]),
  146. new MultiRenderTarget("depthPeelingDepth1MRT", size, 3, this._scene, undefined, [
  147. "depthPeelingDepth1MRT_depth",
  148. "depthPeelingDepth1MRT_frontColor",
  149. "depthPeelingDepth1MRT_backColor",
  150. ]),
  151. ];
  152. this._colorMrts = [
  153. new MultiRenderTarget("depthPeelingColor0MRT", size, 2, this._scene, { generateDepthBuffer: false }, [
  154. "depthPeelingColor0MRT_frontColor",
  155. "depthPeelingColor0MRT_backColor",
  156. ]),
  157. new MultiRenderTarget("depthPeelingColor1MRT", size, 2, this._scene, { generateDepthBuffer: false }, [
  158. "depthPeelingColor1MRT_frontColor",
  159. "depthPeelingColor1MRT_backColor",
  160. ]),
  161. ];
  162. this._blendBackMrt = new MultiRenderTarget("depthPeelingBackMRT", size, 1, this._scene, { generateDepthBuffer: false }, ["depthPeelingBackMRT_blendBack"]);
  163. this._outputRT = new RenderTargetTexture("depthPeelingOutputRTT", size, this._scene, false);
  164. // 0 is a depth texture
  165. // 1 is a color texture
  166. const optionsArray = [
  167. {
  168. format: 7,
  169. samplingMode: 1,
  170. type: this._engine.getCaps().textureFloatLinearFiltering ? 1 : 2,
  171. label: "DepthPeelingRenderer-DepthTexture",
  172. },
  173. {
  174. format: 5,
  175. samplingMode: 1,
  176. type: 2,
  177. label: "DepthPeelingRenderer-ColorTexture",
  178. },
  179. ];
  180. for (let i = 0; i < 2; i++) {
  181. const depthTexture = this._engine._createInternalTexture(size, optionsArray[0], false);
  182. const frontColorTexture = this._engine._createInternalTexture(size, optionsArray[1], false);
  183. const backColorTexture = this._engine._createInternalTexture(size, optionsArray[1], false);
  184. this._depthMrts[i].setInternalTexture(depthTexture, 0);
  185. this._depthMrts[i].setInternalTexture(frontColorTexture, 1);
  186. this._depthMrts[i].setInternalTexture(backColorTexture, 2);
  187. this._colorMrts[i].setInternalTexture(frontColorTexture, 0);
  188. this._colorMrts[i].setInternalTexture(backColorTexture, 1);
  189. this._thinTextures.push(new ThinTexture(depthTexture), new ThinTexture(frontColorTexture), new ThinTexture(backColorTexture));
  190. }
  191. }
  192. // TODO : explore again MSAA with depth peeling when
  193. // we are able to fetch individual samples in a multisampled renderbuffer
  194. // public set samples(value: number) {
  195. // for (let i = 0; i < 2; i++) {
  196. // this._depthMrts[i].samples = value;
  197. // this._colorMrts[i].samples = value;
  198. // }
  199. // this._scene.prePassRenderer!.samples = value;
  200. // }
  201. _disposeTextures() {
  202. for (let i = 0; i < this._thinTextures.length; i++) {
  203. if (i === 6) {
  204. // Do not dispose the shared texture with the prepass
  205. continue;
  206. }
  207. this._thinTextures[i].dispose();
  208. }
  209. for (let i = 0; i < 2; i++) {
  210. this._depthMrts[i].dispose(true);
  211. this._colorMrts[i].dispose(true);
  212. this._blendBackMrt.dispose(true);
  213. }
  214. this._outputRT.dispose();
  215. this._thinTextures = [];
  216. this._colorMrts = [];
  217. this._depthMrts = [];
  218. }
  219. _updateTextures() {
  220. if (this._depthMrts[0].getSize().width !== this._engine.getRenderWidth() || this._depthMrts[0].getSize().height !== this._engine.getRenderHeight()) {
  221. this._disposeTextures();
  222. this._createTextures();
  223. }
  224. return this._updateTextureReferences();
  225. }
  226. _updateTextureReferences() {
  227. const prePassRenderer = this._scene.prePassRenderer;
  228. if (!prePassRenderer) {
  229. return false;
  230. }
  231. // Retrieve opaque color texture
  232. const textureIndex = prePassRenderer.getIndex(4);
  233. const prePassTexture = prePassRenderer.defaultRT.textures?.length ? prePassRenderer.defaultRT.textures[textureIndex].getInternalTexture() : null;
  234. if (!prePassTexture) {
  235. return false;
  236. }
  237. if (this._blendBackTexture !== prePassTexture) {
  238. this._blendBackTexture = prePassTexture;
  239. this._blendBackMrt.setInternalTexture(this._blendBackTexture, 0);
  240. if (this._thinTextures[6]) {
  241. this._thinTextures[6].dispose();
  242. }
  243. this._thinTextures[6] = new ThinTexture(this._blendBackTexture);
  244. prePassRenderer.defaultRT.renderTarget.shareDepth(this._depthMrts[0].renderTarget);
  245. }
  246. return true;
  247. }
  248. _createEffects() {
  249. this._blendBackEffectWrapper = new EffectWrapper({
  250. fragmentShader: "oitBackBlend",
  251. useShaderStore: true,
  252. engine: this._engine,
  253. samplerNames: ["uBackColor"],
  254. uniformNames: [],
  255. });
  256. this._blendBackEffectWrapperPingPong = new EffectWrapper({
  257. fragmentShader: "oitBackBlend",
  258. useShaderStore: true,
  259. engine: this._engine,
  260. samplerNames: ["uBackColor"],
  261. uniformNames: [],
  262. });
  263. this._finalEffectWrapper = new EffectWrapper({
  264. fragmentShader: "oitFinal",
  265. useShaderStore: true,
  266. engine: this._engine,
  267. samplerNames: ["uFrontColor", "uBackColor"],
  268. uniformNames: [],
  269. });
  270. this._effectRenderer = new EffectRenderer(this._engine);
  271. }
  272. /**
  273. * Links to the prepass renderer
  274. * @param prePassRenderer The scene PrePassRenderer
  275. */
  276. setPrePassRenderer(prePassRenderer) {
  277. prePassRenderer.addEffectConfiguration(this._prePassEffectConfiguration);
  278. }
  279. /**
  280. * Binds depth peeling textures on an effect
  281. * @param effect The effect to bind textures on
  282. */
  283. bind(effect) {
  284. effect.setTexture("oitDepthSampler", this._thinTextures[this._currentPingPongState * 3]);
  285. effect.setTexture("oitFrontColorSampler", this._thinTextures[this._currentPingPongState * 3 + 1]);
  286. }
  287. _renderSubMeshes(transparentSubMeshes) {
  288. let mapMaterialContext;
  289. if (this._useRenderPasses) {
  290. mapMaterialContext = {};
  291. }
  292. for (let j = 0; j < transparentSubMeshes.length; j++) {
  293. const material = transparentSubMeshes.data[j].getMaterial();
  294. let previousShaderHotSwapping = true;
  295. let previousBFC = false;
  296. const subMesh = transparentSubMeshes.data[j];
  297. let drawWrapper;
  298. let firstDraw = false;
  299. if (this._useRenderPasses) {
  300. drawWrapper = subMesh._getDrawWrapper();
  301. firstDraw = !drawWrapper;
  302. }
  303. if (material) {
  304. previousShaderHotSwapping = material.allowShaderHotSwapping;
  305. previousBFC = material.backFaceCulling;
  306. material.allowShaderHotSwapping = false;
  307. material.backFaceCulling = false;
  308. }
  309. subMesh.render(false);
  310. if (firstDraw) {
  311. // first time we draw this submesh: we replace the material context
  312. drawWrapper = subMesh._getDrawWrapper(); // we are sure it is now non empty as we just rendered the submesh
  313. if (drawWrapper.materialContext) {
  314. let newMaterialContext = mapMaterialContext[drawWrapper.materialContext.uniqueId];
  315. if (!newMaterialContext) {
  316. newMaterialContext = mapMaterialContext[drawWrapper.materialContext.uniqueId] = this._engine.createMaterialContext();
  317. }
  318. subMesh._getDrawWrapper().materialContext = newMaterialContext;
  319. }
  320. }
  321. if (material) {
  322. material.allowShaderHotSwapping = previousShaderHotSwapping;
  323. material.backFaceCulling = previousBFC;
  324. }
  325. }
  326. }
  327. _finalCompose(writeId) {
  328. const output = this._scene.prePassRenderer?.setCustomOutput(this._outputRT);
  329. if (output) {
  330. this._engine.bindFramebuffer(this._outputRT.renderTarget);
  331. }
  332. else {
  333. this._engine.restoreDefaultFramebuffer();
  334. }
  335. this._engine.setAlphaMode(0);
  336. this._engine.applyStates();
  337. this._engine.enableEffect(this._finalEffectWrapper._drawWrapper);
  338. this._finalEffectWrapper.effect.setTexture("uFrontColor", this._thinTextures[writeId * 3 + 1]);
  339. this._finalEffectWrapper.effect.setTexture("uBackColor", this._thinTextures[6]);
  340. this._effectRenderer.render(this._finalEffectWrapper);
  341. }
  342. /**
  343. * Checks if the depth peeling renderer is ready to render transparent meshes
  344. * @returns true if the depth peeling renderer is ready to render the transparent meshes
  345. */
  346. isReady() {
  347. return (this._blendBackEffectWrapper.effect.isReady() &&
  348. this._blendBackEffectWrapperPingPong.effect.isReady() &&
  349. this._finalEffectWrapper.effect.isReady() &&
  350. this._updateTextures());
  351. }
  352. /**
  353. * Renders transparent submeshes with depth peeling
  354. * @param transparentSubMeshes List of transparent meshes to render
  355. * @returns The array of submeshes that could not be handled by this renderer
  356. */
  357. render(transparentSubMeshes) {
  358. this._candidateSubMeshes.length = 0;
  359. this._excludedSubMeshes.length = 0;
  360. if (!this.isReady()) {
  361. return this._excludedSubMeshes;
  362. }
  363. if (this._scene.activeCamera) {
  364. this._engine.setViewport(this._scene.activeCamera.viewport);
  365. }
  366. for (let i = 0; i < transparentSubMeshes.length; i++) {
  367. const subMesh = transparentSubMeshes.data[i];
  368. const material = subMesh.getMaterial();
  369. const fillMode = material && subMesh.getRenderingMesh()._getRenderingFillMode(material.fillMode);
  370. if (material &&
  371. (fillMode === Material.TriangleFanDrawMode || fillMode === Material.TriangleFillMode || fillMode === Material.TriangleStripDrawMode) &&
  372. this._excludedMeshes.indexOf(subMesh.getMesh().uniqueId) === -1) {
  373. this._candidateSubMeshes.push(subMesh);
  374. }
  375. else {
  376. this._excludedSubMeshes.push(subMesh);
  377. }
  378. }
  379. if (!this._candidateSubMeshes.length) {
  380. this._engine.bindFramebuffer(this._colorMrts[1].renderTarget);
  381. this._engine.bindAttachments(this._layoutCache[1]);
  382. this._engine.clear(this._colorCache[2], true, false, false);
  383. this._engine.unBindFramebuffer(this._colorMrts[1].renderTarget);
  384. this._finalCompose(1);
  385. return this._excludedSubMeshes;
  386. }
  387. const currentRenderPassId = this._engine.currentRenderPassId;
  388. this._scene.prePassRenderer._enabled = false;
  389. if (this._useRenderPasses) {
  390. this._engine.currentRenderPassId = this._renderPassIds[0];
  391. }
  392. // Clears
  393. this._engine.bindFramebuffer(this._depthMrts[0].renderTarget);
  394. this._engine.bindAttachments(this._layoutCache[0]);
  395. this._engine.clear(this._colorCache[0], true, false, false);
  396. this._engine.unBindFramebuffer(this._depthMrts[0].renderTarget);
  397. this._engine.bindFramebuffer(this._depthMrts[1].renderTarget);
  398. this._engine.bindAttachments(this._layoutCache[0]);
  399. this._engine.clear(this._colorCache[1], true, false, false);
  400. this._engine.unBindFramebuffer(this._depthMrts[1].renderTarget);
  401. this._engine.bindFramebuffer(this._colorMrts[0].renderTarget);
  402. this._engine.bindAttachments(this._layoutCache[1]);
  403. this._engine.clear(this._colorCache[2], true, false, false);
  404. this._engine.unBindFramebuffer(this._colorMrts[0].renderTarget);
  405. this._engine.bindFramebuffer(this._colorMrts[1].renderTarget);
  406. this._engine.bindAttachments(this._layoutCache[1]);
  407. this._engine.clear(this._colorCache[2], true, false, false);
  408. this._engine.unBindFramebuffer(this._colorMrts[1].renderTarget);
  409. // Draw depth for first pass
  410. this._engine.bindFramebuffer(this._depthMrts[0].renderTarget);
  411. this._engine.bindAttachments(this._layoutCache[0]);
  412. this._engine.setAlphaMode(11); // in WebGPU, when using MIN or MAX equation, the src / dst color factors should not use SRC_ALPHA and the src / dst alpha factors must be 1 else WebGPU will throw a validation error
  413. this._engine.setAlphaEquation(3);
  414. this._engine.depthCullingState.depthMask = false;
  415. this._engine.depthCullingState.depthTest = true;
  416. this._engine.applyStates();
  417. this._currentPingPongState = 1;
  418. // Render
  419. this._renderSubMeshes(this._candidateSubMeshes);
  420. this._engine.unBindFramebuffer(this._depthMrts[0].renderTarget);
  421. this._scene.resetCachedMaterial();
  422. // depth peeling ping-pong
  423. let readId = 0;
  424. let writeId = 0;
  425. for (let i = 0; i < this._passCount; i++) {
  426. readId = i % 2;
  427. writeId = 1 - readId;
  428. this._currentPingPongState = readId;
  429. if (this._useRenderPasses) {
  430. this._engine.currentRenderPassId = this._renderPassIds[i + 1];
  431. }
  432. if (this._scene.activeCamera) {
  433. this._engine.setViewport(this._scene.activeCamera.viewport);
  434. }
  435. // Clears
  436. this._engine.bindFramebuffer(this._depthMrts[writeId].renderTarget);
  437. this._engine.bindAttachments(this._layoutCache[0]);
  438. this._engine.clear(this._colorCache[0], true, false, false);
  439. this._engine.unBindFramebuffer(this._depthMrts[writeId].renderTarget);
  440. this._engine.bindFramebuffer(this._colorMrts[writeId].renderTarget);
  441. this._engine.bindAttachments(this._layoutCache[1]);
  442. this._engine.clear(this._colorCache[2], true, false, false);
  443. this._engine.unBindFramebuffer(this._colorMrts[writeId].renderTarget);
  444. this._engine.bindFramebuffer(this._depthMrts[writeId].renderTarget);
  445. this._engine.bindAttachments(this._layoutCache[2]);
  446. this._engine.setAlphaMode(11); // the value does not matter (as MAX operation does not use them) but the src and dst color factors should not use SRC_ALPHA else WebGPU will throw a validation error
  447. this._engine.setAlphaEquation(3);
  448. this._engine.depthCullingState.depthTest = false;
  449. this._engine.applyStates();
  450. // Render
  451. this._renderSubMeshes(this._candidateSubMeshes);
  452. this._engine.unBindFramebuffer(this._depthMrts[writeId].renderTarget);
  453. this._scene.resetCachedMaterial();
  454. // Back color
  455. this._engine.bindFramebuffer(this._blendBackMrt.renderTarget);
  456. this._engine.bindAttachments(this._layoutCache[0]);
  457. this._engine.setAlphaEquation(0);
  458. this._engine.setAlphaMode(17);
  459. this._engine.applyStates();
  460. const blendBackEffectWrapper = writeId === 0 || !this._useRenderPasses ? this._blendBackEffectWrapper : this._blendBackEffectWrapperPingPong;
  461. this._engine.enableEffect(blendBackEffectWrapper._drawWrapper);
  462. blendBackEffectWrapper.effect.setTexture("uBackColor", this._thinTextures[writeId * 3 + 2]);
  463. this._effectRenderer.render(blendBackEffectWrapper);
  464. this._engine.unBindFramebuffer(this._blendBackMrt.renderTarget);
  465. }
  466. this._engine.currentRenderPassId = currentRenderPassId;
  467. // Final composition on default FB
  468. this._finalCompose(writeId);
  469. this._scene.prePassRenderer._enabled = true;
  470. this._engine.depthCullingState.depthMask = true;
  471. this._engine.depthCullingState.depthTest = true;
  472. return this._excludedSubMeshes;
  473. }
  474. /**
  475. * Disposes the depth peeling renderer and associated ressources
  476. */
  477. dispose() {
  478. this._disposeTextures();
  479. this._blendBackEffectWrapper.dispose();
  480. this._finalEffectWrapper.dispose();
  481. this._effectRenderer.dispose();
  482. this._releaseRenderPassIds();
  483. }
  484. }
  485. DepthPeelingRenderer._DEPTH_CLEAR_VALUE = -99999.0;
  486. DepthPeelingRenderer._MIN_DEPTH = 0;
  487. DepthPeelingRenderer._MAX_DEPTH = 1;
  488. //# sourceMappingURL=depthPeelingRenderer.js.map