volumetricLightScatteringPostProcess.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. import { __decorate } from "../tslib.es6.js";
  2. import { serializeAsVector3, serialize, serializeAsMeshReference } from "../Misc/decorators.js";
  3. import { Logger } from "../Misc/logger.js";
  4. import { Vector2, Vector3, Matrix } from "../Maths/math.vector.js";
  5. import { VertexBuffer } from "../Buffers/buffer.js";
  6. import { AbstractMesh } from "../Meshes/abstractMesh.js";
  7. import { Material } from "../Materials/material.js";
  8. import { StandardMaterial } from "../Materials/standardMaterial.js";
  9. import { Texture } from "../Materials/Textures/texture.js";
  10. import { RenderTargetTexture } from "../Materials/Textures/renderTargetTexture.js";
  11. import { PostProcess } from "./postProcess.js";
  12. import { CreatePlane } from "../Meshes/Builders/planeBuilder.js";
  13. import "../Shaders/depth.vertex.js";
  14. import "../Shaders/volumetricLightScattering.fragment.js";
  15. import "../Shaders/volumetricLightScatteringPass.vertex.js";
  16. import "../Shaders/volumetricLightScatteringPass.fragment.js";
  17. import { Color4, Color3 } from "../Maths/math.color.js";
  18. import { Viewport } from "../Maths/math.viewport.js";
  19. import { RegisterClass } from "../Misc/typeStore.js";
  20. import { PushAttributesForInstances } from "../Materials/materialHelper.functions.js";
  21. /**
  22. * Inspired by https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-13-volumetric-light-scattering-post-process
  23. */
  24. export class VolumetricLightScatteringPostProcess extends PostProcess {
  25. /**
  26. * @internal
  27. * VolumetricLightScatteringPostProcess.useDiffuseColor is no longer used, use the mesh material directly instead
  28. */
  29. get useDiffuseColor() {
  30. Logger.Warn("VolumetricLightScatteringPostProcess.useDiffuseColor is no longer used, use the mesh material directly instead");
  31. return false;
  32. }
  33. set useDiffuseColor(useDiffuseColor) {
  34. Logger.Warn("VolumetricLightScatteringPostProcess.useDiffuseColor is no longer used, use the mesh material directly instead");
  35. }
  36. /**
  37. * @constructor
  38. * @param name The post-process name
  39. * @param ratio The size of the post-process and/or internal pass (0.5 means that your postprocess will have a width = canvas.width 0.5 and a height = canvas.height 0.5)
  40. * @param camera The camera that the post-process will be attached to
  41. * @param mesh The mesh used to create the light scattering
  42. * @param samples The post-process quality, default 100
  43. * @param samplingMode The post-process filtering mode
  44. * @param engine The babylon engine
  45. * @param reusable If the post-process is reusable
  46. * @param scene The constructor needs a scene reference to initialize internal components. If "camera" is null a "scene" must be provided
  47. */
  48. constructor(name, ratio, camera, mesh, samples = 100, samplingMode = Texture.BILINEAR_SAMPLINGMODE, engine, reusable, scene) {
  49. super(name, "volumetricLightScattering", ["decay", "exposure", "weight", "meshPositionOnScreen", "density"], ["lightScatteringSampler"], ratio.postProcessRatio || ratio, camera, samplingMode, engine, reusable, "#define NUM_SAMPLES " + samples);
  50. this._screenCoordinates = Vector2.Zero();
  51. /**
  52. * Custom position of the mesh. Used if "useCustomMeshPosition" is set to "true"
  53. */
  54. this.customMeshPosition = Vector3.Zero();
  55. /**
  56. * Set if the post-process should use a custom position for the light source (true) or the internal mesh position (false)
  57. */
  58. this.useCustomMeshPosition = false;
  59. /**
  60. * If the post-process should inverse the light scattering direction
  61. */
  62. this.invert = true;
  63. /**
  64. * Array containing the excluded meshes not rendered in the internal pass
  65. */
  66. this.excludedMeshes = [];
  67. /**
  68. * Array containing the only meshes rendered in the internal pass.
  69. * If this array is not empty, only the meshes from this array are rendered in the internal pass
  70. */
  71. this.includedMeshes = [];
  72. /**
  73. * Controls the overall intensity of the post-process
  74. */
  75. this.exposure = 0.3;
  76. /**
  77. * Dissipates each sample's contribution in range [0, 1]
  78. */
  79. this.decay = 0.96815;
  80. /**
  81. * Controls the overall intensity of each sample
  82. */
  83. this.weight = 0.58767;
  84. /**
  85. * Controls the density of each sample
  86. */
  87. this.density = 0.926;
  88. scene = camera?.getScene() ?? scene ?? this._scene; // parameter "scene" can be null.
  89. engine = scene.getEngine();
  90. this._viewPort = new Viewport(0, 0, 1, 1).toGlobal(engine.getRenderWidth(), engine.getRenderHeight());
  91. // Configure mesh
  92. this.mesh = mesh ?? VolumetricLightScatteringPostProcess.CreateDefaultMesh("VolumetricLightScatteringMesh", scene);
  93. // Configure
  94. this._createPass(scene, ratio.passRatio || ratio);
  95. this.onActivate = (camera) => {
  96. if (!this.isSupported) {
  97. this.dispose(camera);
  98. }
  99. this.onActivate = null;
  100. };
  101. this.onApplyObservable.add((effect) => {
  102. this._updateMeshScreenCoordinates(scene);
  103. effect.setTexture("lightScatteringSampler", this._volumetricLightScatteringRTT);
  104. effect.setFloat("exposure", this.exposure);
  105. effect.setFloat("decay", this.decay);
  106. effect.setFloat("weight", this.weight);
  107. effect.setFloat("density", this.density);
  108. effect.setVector2("meshPositionOnScreen", this._screenCoordinates);
  109. });
  110. }
  111. /**
  112. * Returns the string "VolumetricLightScatteringPostProcess"
  113. * @returns "VolumetricLightScatteringPostProcess"
  114. */
  115. getClassName() {
  116. return "VolumetricLightScatteringPostProcess";
  117. }
  118. _isReady(subMesh, useInstances) {
  119. const mesh = subMesh.getMesh();
  120. // Render this.mesh as default
  121. if (mesh === this.mesh && mesh.material) {
  122. return mesh.material.isReady(mesh);
  123. }
  124. const renderingMaterial = mesh._internalAbstractMeshDataInfo._materialForRenderPass?.[this._scene.getEngine().currentRenderPassId];
  125. if (renderingMaterial) {
  126. return renderingMaterial.isReadyForSubMesh(mesh, subMesh, useInstances);
  127. }
  128. const defines = [];
  129. const attribs = [VertexBuffer.PositionKind];
  130. const material = subMesh.getMaterial();
  131. // Alpha test
  132. if (material) {
  133. if (material.needAlphaTesting()) {
  134. defines.push("#define ALPHATEST");
  135. }
  136. if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
  137. attribs.push(VertexBuffer.UVKind);
  138. defines.push("#define UV1");
  139. }
  140. if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind)) {
  141. attribs.push(VertexBuffer.UV2Kind);
  142. defines.push("#define UV2");
  143. }
  144. }
  145. // Bones
  146. if (mesh.useBones && mesh.computeBonesUsingShaders) {
  147. attribs.push(VertexBuffer.MatricesIndicesKind);
  148. attribs.push(VertexBuffer.MatricesWeightsKind);
  149. defines.push("#define NUM_BONE_INFLUENCERS " + mesh.numBoneInfluencers);
  150. defines.push("#define BonesPerMesh " + (mesh.skeleton ? mesh.skeleton.bones.length + 1 : 0));
  151. }
  152. else {
  153. defines.push("#define NUM_BONE_INFLUENCERS 0");
  154. }
  155. // Instances
  156. if (useInstances) {
  157. defines.push("#define INSTANCES");
  158. PushAttributesForInstances(attribs);
  159. if (subMesh.getRenderingMesh().hasThinInstances) {
  160. defines.push("#define THIN_INSTANCES");
  161. }
  162. }
  163. // Get correct effect
  164. const drawWrapper = subMesh._getDrawWrapper(undefined, true);
  165. const cachedDefines = drawWrapper.defines;
  166. const join = defines.join("\n");
  167. if (cachedDefines !== join) {
  168. drawWrapper.setEffect(mesh
  169. .getScene()
  170. .getEngine()
  171. .createEffect("volumetricLightScatteringPass", attribs, ["world", "mBones", "viewProjection", "diffuseMatrix"], ["diffuseSampler"], join, undefined, undefined, undefined, { maxSimultaneousMorphTargets: mesh.numBoneInfluencers }), join);
  172. }
  173. return drawWrapper.effect.isReady();
  174. }
  175. /**
  176. * Sets the new light position for light scattering effect
  177. * @param position The new custom light position
  178. */
  179. setCustomMeshPosition(position) {
  180. this.customMeshPosition = position;
  181. }
  182. /**
  183. * Returns the light position for light scattering effect
  184. * @returns Vector3 The custom light position
  185. */
  186. getCustomMeshPosition() {
  187. return this.customMeshPosition;
  188. }
  189. /**
  190. * Disposes the internal assets and detaches the post-process from the camera
  191. * @param camera The camera from which to detach the post-process
  192. */
  193. dispose(camera) {
  194. const rttIndex = camera.getScene().customRenderTargets.indexOf(this._volumetricLightScatteringRTT);
  195. if (rttIndex !== -1) {
  196. camera.getScene().customRenderTargets.splice(rttIndex, 1);
  197. }
  198. this._volumetricLightScatteringRTT.dispose();
  199. super.dispose(camera);
  200. }
  201. /**
  202. * Returns the render target texture used by the post-process
  203. * @returns the render target texture used by the post-process
  204. */
  205. getPass() {
  206. return this._volumetricLightScatteringRTT;
  207. }
  208. // Private methods
  209. _meshExcluded(mesh) {
  210. if ((this.includedMeshes.length > 0 && this.includedMeshes.indexOf(mesh) === -1) || (this.excludedMeshes.length > 0 && this.excludedMeshes.indexOf(mesh) !== -1)) {
  211. return true;
  212. }
  213. return false;
  214. }
  215. _createPass(scene, ratio) {
  216. const engine = scene.getEngine();
  217. this._volumetricLightScatteringRTT = new RenderTargetTexture("volumetricLightScatteringMap", { width: engine.getRenderWidth() * ratio, height: engine.getRenderHeight() * ratio }, scene, false, true, 0);
  218. this._volumetricLightScatteringRTT.wrapU = Texture.CLAMP_ADDRESSMODE;
  219. this._volumetricLightScatteringRTT.wrapV = Texture.CLAMP_ADDRESSMODE;
  220. this._volumetricLightScatteringRTT.renderList = null;
  221. this._volumetricLightScatteringRTT.renderParticles = false;
  222. this._volumetricLightScatteringRTT.ignoreCameraViewport = true;
  223. const camera = this.getCamera();
  224. if (camera) {
  225. camera.customRenderTargets.push(this._volumetricLightScatteringRTT);
  226. }
  227. else {
  228. scene.customRenderTargets.push(this._volumetricLightScatteringRTT);
  229. }
  230. // Custom render function for submeshes
  231. const renderSubMesh = (subMesh) => {
  232. const renderingMesh = subMesh.getRenderingMesh();
  233. const effectiveMesh = subMesh.getEffectiveMesh();
  234. if (this._meshExcluded(renderingMesh)) {
  235. return;
  236. }
  237. effectiveMesh._internalAbstractMeshDataInfo._isActiveIntermediate = false;
  238. const material = subMesh.getMaterial();
  239. if (!material) {
  240. return;
  241. }
  242. const scene = renderingMesh.getScene();
  243. const engine = scene.getEngine();
  244. // Culling
  245. engine.setState(material.backFaceCulling, undefined, undefined, undefined, material.cullBackFaces);
  246. // Managing instances
  247. const batch = renderingMesh._getInstancesRenderList(subMesh._id, !!subMesh.getReplacementMesh());
  248. if (batch.mustReturn) {
  249. return;
  250. }
  251. const hardwareInstancedRendering = engine.getCaps().instancedArrays && (batch.visibleInstances[subMesh._id] !== null || renderingMesh.hasThinInstances);
  252. if (this._isReady(subMesh, hardwareInstancedRendering)) {
  253. const renderingMaterial = effectiveMesh._internalAbstractMeshDataInfo._materialForRenderPass?.[engine.currentRenderPassId];
  254. let drawWrapper = subMesh._getDrawWrapper();
  255. if (renderingMesh === this.mesh && !drawWrapper) {
  256. drawWrapper = material._getDrawWrapper();
  257. }
  258. if (!drawWrapper) {
  259. return;
  260. }
  261. const effect = drawWrapper.effect;
  262. engine.enableEffect(drawWrapper);
  263. if (!hardwareInstancedRendering) {
  264. renderingMesh._bind(subMesh, effect, material.fillMode);
  265. }
  266. if (renderingMesh === this.mesh) {
  267. material.bind(effectiveMesh.getWorldMatrix(), renderingMesh);
  268. }
  269. else if (renderingMaterial) {
  270. renderingMaterial.bindForSubMesh(effectiveMesh.getWorldMatrix(), effectiveMesh, subMesh);
  271. }
  272. else {
  273. effect.setMatrix("viewProjection", scene.getTransformMatrix());
  274. // Alpha test
  275. if (material && material.needAlphaTesting()) {
  276. const alphaTexture = material.getAlphaTestTexture();
  277. effect.setTexture("diffuseSampler", alphaTexture);
  278. if (alphaTexture) {
  279. effect.setMatrix("diffuseMatrix", alphaTexture.getTextureMatrix());
  280. }
  281. }
  282. // Bones
  283. if (renderingMesh.useBones && renderingMesh.computeBonesUsingShaders && renderingMesh.skeleton) {
  284. effect.setMatrices("mBones", renderingMesh.skeleton.getTransformMatrices(renderingMesh));
  285. }
  286. }
  287. if (hardwareInstancedRendering && renderingMesh.hasThinInstances) {
  288. effect.setMatrix("world", effectiveMesh.getWorldMatrix());
  289. }
  290. // Draw
  291. renderingMesh._processRendering(effectiveMesh, subMesh, effect, Material.TriangleFillMode, batch, hardwareInstancedRendering, (isInstance, world) => {
  292. if (!isInstance) {
  293. effect.setMatrix("world", world);
  294. }
  295. });
  296. }
  297. };
  298. // Render target texture callbacks
  299. let savedSceneClearColor;
  300. const sceneClearColor = new Color4(0.0, 0.0, 0.0, 1.0);
  301. this._volumetricLightScatteringRTT.onBeforeRenderObservable.add(() => {
  302. savedSceneClearColor = scene.clearColor;
  303. scene.clearColor = sceneClearColor;
  304. });
  305. this._volumetricLightScatteringRTT.onAfterRenderObservable.add(() => {
  306. scene.clearColor = savedSceneClearColor;
  307. });
  308. this._volumetricLightScatteringRTT.customIsReadyFunction = (mesh, refreshRate, preWarm) => {
  309. if ((preWarm || refreshRate === 0) && mesh.subMeshes) {
  310. for (let i = 0; i < mesh.subMeshes.length; ++i) {
  311. const subMesh = mesh.subMeshes[i];
  312. const material = subMesh.getMaterial();
  313. const renderingMesh = subMesh.getRenderingMesh();
  314. if (!material) {
  315. continue;
  316. }
  317. const batch = renderingMesh._getInstancesRenderList(subMesh._id, !!subMesh.getReplacementMesh());
  318. const hardwareInstancedRendering = engine.getCaps().instancedArrays && (batch.visibleInstances[subMesh._id] !== null || renderingMesh.hasThinInstances);
  319. if (!this._isReady(subMesh, hardwareInstancedRendering)) {
  320. return false;
  321. }
  322. }
  323. }
  324. return true;
  325. };
  326. this._volumetricLightScatteringRTT.customRenderFunction = (opaqueSubMeshes, alphaTestSubMeshes, transparentSubMeshes, depthOnlySubMeshes) => {
  327. const engine = scene.getEngine();
  328. let index;
  329. if (depthOnlySubMeshes.length) {
  330. engine.setColorWrite(false);
  331. for (index = 0; index < depthOnlySubMeshes.length; index++) {
  332. renderSubMesh(depthOnlySubMeshes.data[index]);
  333. }
  334. engine.setColorWrite(true);
  335. }
  336. for (index = 0; index < opaqueSubMeshes.length; index++) {
  337. renderSubMesh(opaqueSubMeshes.data[index]);
  338. }
  339. for (index = 0; index < alphaTestSubMeshes.length; index++) {
  340. renderSubMesh(alphaTestSubMeshes.data[index]);
  341. }
  342. if (transparentSubMeshes.length) {
  343. // Sort sub meshes
  344. for (index = 0; index < transparentSubMeshes.length; index++) {
  345. const submesh = transparentSubMeshes.data[index];
  346. const boundingInfo = submesh.getBoundingInfo();
  347. if (boundingInfo && scene.activeCamera) {
  348. submesh._alphaIndex = submesh.getMesh().alphaIndex;
  349. submesh._distanceToCamera = boundingInfo.boundingSphere.centerWorld.subtract(scene.activeCamera.position).length();
  350. }
  351. }
  352. const sortedArray = transparentSubMeshes.data.slice(0, transparentSubMeshes.length);
  353. sortedArray.sort((a, b) => {
  354. // Alpha index first
  355. if (a._alphaIndex > b._alphaIndex) {
  356. return 1;
  357. }
  358. if (a._alphaIndex < b._alphaIndex) {
  359. return -1;
  360. }
  361. // Then distance to camera
  362. if (a._distanceToCamera < b._distanceToCamera) {
  363. return 1;
  364. }
  365. if (a._distanceToCamera > b._distanceToCamera) {
  366. return -1;
  367. }
  368. return 0;
  369. });
  370. // Render sub meshes
  371. engine.setAlphaMode(2);
  372. for (index = 0; index < sortedArray.length; index++) {
  373. renderSubMesh(sortedArray[index]);
  374. }
  375. engine.setAlphaMode(0);
  376. }
  377. };
  378. }
  379. _updateMeshScreenCoordinates(scene) {
  380. const transform = scene.getTransformMatrix();
  381. let meshPosition;
  382. if (this.useCustomMeshPosition) {
  383. meshPosition = this.customMeshPosition;
  384. }
  385. else if (this.attachedNode) {
  386. meshPosition = this.attachedNode.position;
  387. }
  388. else {
  389. meshPosition = this.mesh.parent ? this.mesh.getAbsolutePosition() : this.mesh.position;
  390. }
  391. const pos = Vector3.Project(meshPosition, Matrix.Identity(), transform, this._viewPort);
  392. this._screenCoordinates.x = pos.x / this._viewPort.width;
  393. this._screenCoordinates.y = pos.y / this._viewPort.height;
  394. if (this.invert) {
  395. this._screenCoordinates.y = 1.0 - this._screenCoordinates.y;
  396. }
  397. }
  398. // Static methods
  399. /**
  400. * Creates a default mesh for the Volumeric Light Scattering post-process
  401. * @param name The mesh name
  402. * @param scene The scene where to create the mesh
  403. * @returns the default mesh
  404. */
  405. static CreateDefaultMesh(name, scene) {
  406. const mesh = CreatePlane(name, { size: 1 }, scene);
  407. mesh.billboardMode = AbstractMesh.BILLBOARDMODE_ALL;
  408. const material = new StandardMaterial(name + "Material", scene);
  409. material.emissiveColor = new Color3(1, 1, 1);
  410. mesh.material = material;
  411. return mesh;
  412. }
  413. }
  414. __decorate([
  415. serializeAsVector3()
  416. ], VolumetricLightScatteringPostProcess.prototype, "customMeshPosition", void 0);
  417. __decorate([
  418. serialize()
  419. ], VolumetricLightScatteringPostProcess.prototype, "useCustomMeshPosition", void 0);
  420. __decorate([
  421. serialize()
  422. ], VolumetricLightScatteringPostProcess.prototype, "invert", void 0);
  423. __decorate([
  424. serializeAsMeshReference()
  425. ], VolumetricLightScatteringPostProcess.prototype, "mesh", void 0);
  426. __decorate([
  427. serialize()
  428. ], VolumetricLightScatteringPostProcess.prototype, "excludedMeshes", void 0);
  429. __decorate([
  430. serialize()
  431. ], VolumetricLightScatteringPostProcess.prototype, "includedMeshes", void 0);
  432. __decorate([
  433. serialize()
  434. ], VolumetricLightScatteringPostProcess.prototype, "exposure", void 0);
  435. __decorate([
  436. serialize()
  437. ], VolumetricLightScatteringPostProcess.prototype, "decay", void 0);
  438. __decorate([
  439. serialize()
  440. ], VolumetricLightScatteringPostProcess.prototype, "weight", void 0);
  441. __decorate([
  442. serialize()
  443. ], VolumetricLightScatteringPostProcess.prototype, "density", void 0);
  444. RegisterClass("BABYLON.VolumetricLightScatteringPostProcess", VolumetricLightScatteringPostProcess);
  445. //# sourceMappingURL=volumetricLightScatteringPostProcess.js.map