shadowDepthWrapper.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import { Effect } from "./effect.js";
  2. import { RandomGUID } from "../Misc/guid.js";
  3. import { DrawWrapper } from "./drawWrapper.js";
  4. import { EngineStore } from "../Engines/engineStore.js";
  5. import { ShaderLanguage } from "./shaderLanguage.js";
  6. class MapMap {
  7. constructor() {
  8. this.mm = new Map();
  9. }
  10. get(a, b) {
  11. const m = this.mm.get(a);
  12. if (m !== undefined) {
  13. return m.get(b);
  14. }
  15. return undefined;
  16. }
  17. set(a, b, v) {
  18. let m = this.mm.get(a);
  19. if (m === undefined) {
  20. this.mm.set(a, (m = new Map()));
  21. }
  22. m.set(b, v);
  23. }
  24. }
  25. /**
  26. * Class that can be used to wrap a base material to generate accurate shadows when using custom vertex/fragment code in the base material
  27. */
  28. export class ShadowDepthWrapper {
  29. /** Gets the standalone status of the wrapper */
  30. get standalone() {
  31. return this._options?.standalone ?? false;
  32. }
  33. /** Gets the base material the wrapper is built upon */
  34. get baseMaterial() {
  35. return this._baseMaterial;
  36. }
  37. /** Gets the doNotInjectCode status of the wrapper */
  38. get doNotInjectCode() {
  39. return this._options?.doNotInjectCode ?? false;
  40. }
  41. /**
  42. * Instantiate a new shadow depth wrapper.
  43. * It works by injecting some specific code in the vertex/fragment shaders of the base material and is used by a shadow generator to
  44. * generate the shadow depth map. For more information, please refer to the documentation:
  45. * https://doc.babylonjs.com/features/featuresDeepDive/lights/shadows
  46. * @param baseMaterial Material to wrap
  47. * @param scene Define the scene the material belongs to
  48. * @param options Options used to create the wrapper
  49. */
  50. constructor(baseMaterial, scene, options) {
  51. this._baseMaterial = baseMaterial;
  52. this._scene = scene ?? EngineStore.LastCreatedScene;
  53. this._options = options;
  54. this._subMeshToEffect = new Map();
  55. this._subMeshToDepthWrapper = new MapMap();
  56. this._meshes = new Map();
  57. // Register for onEffectCreated to store the effect of the base material when it is (re)generated. This effect will be used
  58. // to create the depth effect later on
  59. this._onEffectCreatedObserver = this._baseMaterial.onEffectCreatedObservable.add((params) => {
  60. const mesh = params.subMesh?.getMesh();
  61. if (mesh && !this._meshes.has(mesh)) {
  62. // Register for mesh onDispose to clean up our internal maps when a mesh is disposed
  63. this._meshes.set(mesh, mesh.onDisposeObservable.add((mesh) => {
  64. const iterator = this._subMeshToEffect.keys();
  65. for (let key = iterator.next(); key.done !== true; key = iterator.next()) {
  66. const subMesh = key.value;
  67. if (subMesh?.getMesh() === mesh) {
  68. this._subMeshToEffect.delete(subMesh);
  69. this._deleteDepthWrapperEffect(subMesh);
  70. }
  71. }
  72. }));
  73. }
  74. if (this._subMeshToEffect.get(params.subMesh)?.[0] !== params.effect) {
  75. this._subMeshToEffect.set(params.subMesh, [params.effect, this._scene.getEngine().currentRenderPassId]);
  76. this._deleteDepthWrapperEffect(params.subMesh);
  77. }
  78. });
  79. }
  80. _deleteDepthWrapperEffect(subMesh) {
  81. const depthWrapperEntries = this._subMeshToDepthWrapper.mm.get(subMesh);
  82. if (depthWrapperEntries) {
  83. // find and release the previous depth effect
  84. depthWrapperEntries.forEach((depthWrapper) => {
  85. depthWrapper.mainDrawWrapper.effect?.dispose();
  86. });
  87. this._subMeshToDepthWrapper.mm.delete(subMesh); // trigger a depth effect recreation
  88. }
  89. }
  90. /**
  91. * Gets the effect to use to generate the depth map
  92. * @param subMesh subMesh to get the effect for
  93. * @param shadowGenerator shadow generator to get the effect for
  94. * @param passIdForDrawWrapper Id of the pass for which the effect from the draw wrapper must be retrieved from
  95. * @returns the effect to use to generate the depth map for the subMesh + shadow generator specified
  96. */
  97. getEffect(subMesh, shadowGenerator, passIdForDrawWrapper) {
  98. const entry = this._subMeshToDepthWrapper.mm.get(subMesh)?.get(shadowGenerator);
  99. if (!entry) {
  100. return null;
  101. }
  102. let drawWrapper = entry.drawWrapper[passIdForDrawWrapper];
  103. if (!drawWrapper) {
  104. drawWrapper = entry.drawWrapper[passIdForDrawWrapper] = new DrawWrapper(this._scene.getEngine());
  105. drawWrapper.setEffect(entry.mainDrawWrapper.effect, entry.mainDrawWrapper.defines);
  106. }
  107. return drawWrapper;
  108. }
  109. /**
  110. * Specifies that the submesh is ready to be used for depth rendering
  111. * @param subMesh submesh to check
  112. * @param defines the list of defines to take into account when checking the effect
  113. * @param shadowGenerator combined with subMesh, it defines the effect to check
  114. * @param useInstances specifies that instances should be used
  115. * @param passIdForDrawWrapper Id of the pass for which the draw wrapper should be created
  116. * @returns a boolean indicating that the submesh is ready or not
  117. */
  118. isReadyForSubMesh(subMesh, defines, shadowGenerator, useInstances, passIdForDrawWrapper) {
  119. if (this.standalone) {
  120. // will ensure the effect is (re)created for the base material
  121. if (!this._baseMaterial.isReadyForSubMesh(subMesh.getMesh(), subMesh, useInstances)) {
  122. return false;
  123. }
  124. }
  125. return this._makeEffect(subMesh, defines, shadowGenerator, passIdForDrawWrapper)?.isReady() ?? false;
  126. }
  127. /**
  128. * Disposes the resources
  129. */
  130. dispose() {
  131. this._baseMaterial.onEffectCreatedObservable.remove(this._onEffectCreatedObserver);
  132. this._onEffectCreatedObserver = null;
  133. const iterator = this._meshes.entries();
  134. for (let entry = iterator.next(); entry.done !== true; entry = iterator.next()) {
  135. const [mesh, observer] = entry.value;
  136. mesh.onDisposeObservable.remove(observer);
  137. }
  138. }
  139. _makeEffect(subMesh, defines, shadowGenerator, passIdForDrawWrapper) {
  140. const engine = this._scene.getEngine();
  141. const origEffectAndRenderPassId = this._subMeshToEffect.get(subMesh);
  142. if (!origEffectAndRenderPassId) {
  143. return null;
  144. }
  145. const [origEffect, origRenderPassId] = origEffectAndRenderPassId;
  146. let params = this._subMeshToDepthWrapper.get(subMesh, shadowGenerator);
  147. if (!params) {
  148. const mainDrawWrapper = new DrawWrapper(engine);
  149. mainDrawWrapper.defines = subMesh._getDrawWrapper(origRenderPassId)?.defines ?? null;
  150. params = {
  151. drawWrapper: [],
  152. mainDrawWrapper,
  153. depthDefines: "",
  154. token: RandomGUID(),
  155. };
  156. params.drawWrapper[passIdForDrawWrapper] = mainDrawWrapper;
  157. this._subMeshToDepthWrapper.set(subMesh, shadowGenerator, params);
  158. }
  159. const join = defines.join("\n");
  160. if (params.mainDrawWrapper.effect) {
  161. if (join === params.depthDefines) {
  162. // we already created the depth effect and it is still up to date for this submesh + shadow generator
  163. return params.mainDrawWrapper.effect;
  164. }
  165. }
  166. params.depthDefines = join;
  167. const uniforms = origEffect.getUniformNames().slice();
  168. // the depth effect is either out of date or has not been created yet
  169. let vertexCode = origEffect.vertexSourceCodeBeforeMigration, fragmentCode = origEffect.fragmentSourceCodeBeforeMigration;
  170. if (!this.doNotInjectCode) {
  171. // Declare the shadow map includes
  172. const vertexNormalBiasCode = this._options && this._options.remappedVariables
  173. ? `#include<shadowMapVertexNormalBias>(${this._options.remappedVariables.join(",")})`
  174. : `#include<shadowMapVertexNormalBias>`, vertexMetricCode = this._options && this._options.remappedVariables
  175. ? `#include<shadowMapVertexMetric>(${this._options.remappedVariables.join(",")})`
  176. : `#include<shadowMapVertexMetric>`, fragmentSoftTransparentShadow = this._options && this._options.remappedVariables
  177. ? `#include<shadowMapFragmentSoftTransparentShadow>(${this._options.remappedVariables.join(",")})`
  178. : `#include<shadowMapFragmentSoftTransparentShadow>`, fragmentBlockCode = `#include<shadowMapFragment>`, vertexExtraDeclartion = `#include<shadowMapVertexExtraDeclaration>`;
  179. // vertex code
  180. if (origEffect.shaderLanguage === ShaderLanguage.GLSL) {
  181. vertexCode = vertexCode.replace(/void\s+?main/g, `\n${vertexExtraDeclartion}\nvoid main`);
  182. }
  183. else {
  184. vertexCode = vertexCode.replace(/@vertex/g, `\n${vertexExtraDeclartion}\n@vertex`);
  185. }
  186. vertexCode = vertexCode.replace(/#define SHADOWDEPTH_NORMALBIAS|#define CUSTOM_VERTEX_UPDATE_WORLDPOS/g, vertexNormalBiasCode);
  187. if (vertexCode.indexOf("#define SHADOWDEPTH_METRIC") !== -1) {
  188. vertexCode = vertexCode.replace(/#define SHADOWDEPTH_METRIC/g, vertexMetricCode);
  189. }
  190. else {
  191. vertexCode = vertexCode.replace(/}\s*$/g, vertexMetricCode + "\n}");
  192. }
  193. vertexCode = vertexCode.replace(/#define SHADER_NAME.*?\n|out vec4 glFragColor;\n/g, "");
  194. // fragment code
  195. const hasLocationForSoftTransparentShadow = fragmentCode.indexOf("#define SHADOWDEPTH_SOFTTRANSPARENTSHADOW") >= 0 || fragmentCode.indexOf("#define CUSTOM_FRAGMENT_BEFORE_FOG") >= 0;
  196. const hasLocationForFragment = fragmentCode.indexOf("#define SHADOWDEPTH_FRAGMENT") !== -1;
  197. let fragmentCodeToInjectAtEnd = "";
  198. if (!hasLocationForSoftTransparentShadow) {
  199. fragmentCodeToInjectAtEnd = fragmentSoftTransparentShadow + "\n";
  200. }
  201. else {
  202. fragmentCode = fragmentCode.replace(/#define SHADOWDEPTH_SOFTTRANSPARENTSHADOW|#define CUSTOM_FRAGMENT_BEFORE_FOG/g, fragmentSoftTransparentShadow);
  203. }
  204. fragmentCode = fragmentCode.replace(/void\s+?main/g, Effect.IncludesShadersStore["shadowMapFragmentExtraDeclaration"] + "\nvoid main");
  205. if (hasLocationForFragment) {
  206. fragmentCode = fragmentCode.replace(/#define SHADOWDEPTH_FRAGMENT/g, fragmentBlockCode);
  207. }
  208. else {
  209. fragmentCodeToInjectAtEnd += fragmentBlockCode + "\n";
  210. }
  211. if (fragmentCodeToInjectAtEnd) {
  212. fragmentCode = fragmentCode.replace(/}\s*$/g, fragmentCodeToInjectAtEnd + "}");
  213. }
  214. uniforms.push("biasAndScaleSM", "depthValuesSM", "lightDataSM", "softTransparentShadowSM");
  215. }
  216. params.mainDrawWrapper.effect = engine.createEffect({
  217. vertexSource: vertexCode,
  218. fragmentSource: fragmentCode,
  219. vertexToken: params.token,
  220. fragmentToken: params.token,
  221. }, {
  222. attributes: origEffect.getAttributesNames(),
  223. uniformsNames: uniforms,
  224. uniformBuffersNames: origEffect.getUniformBuffersNames(),
  225. samplers: origEffect.getSamplers(),
  226. defines: join + "\n" + origEffect.defines.replace("#define SHADOWS", "").replace(/#define SHADOW\d/g, ""),
  227. indexParameters: origEffect.getIndexParameters(),
  228. shaderLanguage: origEffect.shaderLanguage,
  229. }, engine);
  230. for (let id = 0; id < params.drawWrapper.length; ++id) {
  231. if (id !== passIdForDrawWrapper) {
  232. params.drawWrapper[id]?.setEffect(params.mainDrawWrapper.effect, params.mainDrawWrapper.defines);
  233. }
  234. }
  235. return params.mainDrawWrapper.effect;
  236. }
  237. }
  238. //# sourceMappingURL=shadowDepthWrapper.js.map