shadowDepthWrapper.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import { Observer } from "../Misc/observable";
  2. import { Nullable } from "../types";
  3. import { Scene } from "../scene";
  4. import { SubMesh } from "../Meshes/subMesh";
  5. import { Material } from "./material";
  6. import { _TypeStore } from "../Misc/typeStore";
  7. import { Effect, IEffectCreationOptions } from './effect';
  8. import { AbstractMesh } from '../Meshes/abstractMesh';
  9. import { Node } from '../node';
  10. import { ShadowGenerator } from '../Lights/Shadows/shadowGenerator';
  11. import { GUID } from '../Misc/guid';
  12. /**
  13. * Options to be used when creating a shadow depth material
  14. */
  15. export interface IIOptionShadowDepthMaterial {
  16. /** Variables in the vertex shader code that need to have their names remapped.
  17. * The format is: ["var_name", "var_remapped_name", "var_name", "var_remapped_name", ...]
  18. * "var_name" should be either: worldPos or vNormalW
  19. * So, if the variable holding the world position in your vertex shader is not named worldPos, you must tell the system
  20. * the name to use instead by using: ["worldPos", "myWorldPosVar"] assuming the variable is named myWorldPosVar in your code.
  21. * If the normal must also be remapped: ["worldPos", "myWorldPosVar", "vNormalW", "myWorldNormal"]
  22. */
  23. remappedVariables?: string[];
  24. /** Set standalone to true if the base material wrapped by ShadowDepthMaterial is not used for a regular object but for depth shadow generation only */
  25. standalone?: boolean;
  26. }
  27. class MapMap<Ka, Kb, V> {
  28. readonly mm = new Map<Ka, Map<Kb, V>>();
  29. get(a: Ka, b: Kb): V | undefined {
  30. const m = this.mm.get(a);
  31. if (m !== undefined) {
  32. return m.get(b);
  33. }
  34. return undefined;
  35. }
  36. set(a: Ka, b: Kb, v: V): void {
  37. let m = this.mm.get(a);
  38. if (m === undefined) {
  39. this.mm.set(a, (m = new Map()));
  40. }
  41. m.set(b, v);
  42. }
  43. }
  44. /**
  45. * Class that can be used to wrap a base material to generate accurate shadows when using custom vertex/fragment code in the base material
  46. */
  47. export class ShadowDepthWrapper {
  48. private _scene: Scene;
  49. private _options?: IIOptionShadowDepthMaterial;
  50. private _baseMaterial: Material;
  51. private _onEffectCreatedObserver: Nullable<Observer<{ effect: Effect, subMesh: Nullable<SubMesh>}>>;
  52. private _subMeshToEffect: Map<Nullable<SubMesh>, Effect>;
  53. private _subMeshToDepthEffect: MapMap<Nullable<SubMesh>, ShadowGenerator, { depthEffect: Nullable<Effect>, depthDefines: string, token: string }>; // key is (subMesh + shadowGenerator)
  54. private _meshes: Map<AbstractMesh, Nullable<Observer<Node>>>;
  55. /** @hidden */
  56. public _matriceNames: any;
  57. /** Gets the standalone status of the wrapper */
  58. public get standalone(): boolean {
  59. return this._options?.standalone ?? false;
  60. }
  61. /** Gets the base material the wrapper is built upon */
  62. public get baseMaterial(): Material {
  63. return this._baseMaterial;
  64. }
  65. /**
  66. * Instantiate a new shadow depth wrapper.
  67. * It works by injecting some specific code in the vertex/fragment shaders of the base material and is used by a shadow generator to
  68. * generate the shadow depth map. For more information, please refer to the documentation:
  69. * https://doc.babylonjs.com/babylon101/shadows
  70. * @param baseMaterial Material to wrap
  71. * @param scene Define the scene the material belongs to
  72. * @param options Options used to create the wrapper
  73. */
  74. constructor(baseMaterial: Material, scene: Scene, options?: IIOptionShadowDepthMaterial) {
  75. this._baseMaterial = baseMaterial;
  76. this._scene = scene;
  77. this._options = options;
  78. this._subMeshToEffect = new Map();
  79. this._subMeshToDepthEffect = new MapMap();
  80. this._meshes = new Map();
  81. const prefix = baseMaterial.getClassName() === "NodeMaterial" ? "u_" : "";
  82. this._matriceNames = {
  83. "view": prefix + "view",
  84. "projection": prefix + "projection",
  85. "viewProjection": prefix + "viewProjection",
  86. };
  87. // Register for onEffectCreated to store the effect of the base material when it is (re)generated. This effect will be used
  88. // to create the depth effect later on
  89. this._onEffectCreatedObserver = this._baseMaterial.onEffectCreatedObservable.add((params: { effect: Effect, subMesh: Nullable<SubMesh> }) => {
  90. const mesh = params.subMesh?.getMesh();
  91. if (mesh && !this._meshes.has(mesh)) {
  92. // Register for mesh onDispose to clean up our internal maps when a mesh is disposed
  93. this._meshes.set(mesh,
  94. mesh.onDisposeObservable.add((mesh: Node) => {
  95. const iterator = this._subMeshToEffect.keys();
  96. for (let key = iterator.next(); key.done !== true; key = iterator.next()) {
  97. const subMesh = key.value;
  98. if (subMesh?.getMesh() === mesh as AbstractMesh) {
  99. this._subMeshToEffect.delete(subMesh);
  100. this._subMeshToDepthEffect.mm.delete(subMesh);
  101. }
  102. }
  103. })
  104. );
  105. }
  106. this._subMeshToEffect.set(params.subMesh, params.effect);
  107. this._subMeshToDepthEffect.mm.delete(params.subMesh); // trigger a depth effect recreation
  108. });
  109. }
  110. /**
  111. * Gets the effect to use to generate the depth map
  112. * @param subMesh subMesh to get the effect for
  113. * @param shadowGenerator shadow generator to get the effect for
  114. * @returns the effect to use to generate the depth map for the subMesh + shadow generator specified
  115. */
  116. public getEffect(subMesh: Nullable<SubMesh>, shadowGenerator: ShadowGenerator): Nullable<Effect> {
  117. return this._subMeshToDepthEffect.mm.get(subMesh)?.get(shadowGenerator)?.depthEffect ?? this._subMeshToDepthEffect.mm.get(null)?.get(shadowGenerator)?.depthEffect ?? null;
  118. }
  119. /**
  120. * Specifies that the submesh is ready to be used for depth rendering
  121. * @param subMesh submesh to check
  122. * @param defines the list of defines to take into account when checking the effect
  123. * @param shadowGenerator combined with subMesh, it defines the effect to check
  124. * @param useInstances specifies that instances should be used
  125. * @returns a boolean indicating that the submesh is ready or not
  126. */
  127. public isReadyForSubMesh(subMesh: SubMesh, defines: string[], shadowGenerator: ShadowGenerator, useInstances: boolean): boolean {
  128. if (this.standalone) {
  129. // will ensure the effect is (re)created for the base material
  130. this._baseMaterial.isReadyForSubMesh(subMesh.getMesh(), subMesh, useInstances);
  131. }
  132. return this._makeEffect(subMesh, defines, shadowGenerator)?.isReady() ?? false;
  133. }
  134. /**
  135. * Disposes the resources
  136. */
  137. public dispose(): void {
  138. this._baseMaterial.onEffectCreatedObservable.remove(this._onEffectCreatedObserver);
  139. this._onEffectCreatedObserver = null;
  140. const iterator = this._meshes.entries();
  141. for (let entry = iterator.next(); entry.done !== true; entry = iterator.next()) {
  142. const [mesh, observer] = entry.value;
  143. mesh.onDisposeObservable.remove(observer);
  144. }
  145. }
  146. private _makeEffect(subMesh: Nullable<SubMesh>, defines: string[], shadowGenerator: ShadowGenerator): Nullable<Effect> {
  147. const origEffect = this._subMeshToEffect.get(subMesh) ?? this._subMeshToEffect.get(null);
  148. if (!origEffect) {
  149. return null;
  150. }
  151. let params = this._subMeshToDepthEffect.get(subMesh, shadowGenerator);
  152. if (!params) {
  153. params = {
  154. depthEffect: null,
  155. depthDefines: "",
  156. token: GUID.RandomId()
  157. };
  158. this._subMeshToDepthEffect.set(subMesh, shadowGenerator, params);
  159. }
  160. let join = defines.join("\n");
  161. if (params.depthEffect) {
  162. if (join === params.depthDefines) {
  163. // we already created the depth effect and it is still up to date for this submesh + shadow generator
  164. return params.depthEffect;
  165. }
  166. }
  167. params.depthDefines = join;
  168. // the depth effect is either out of date or has not been created yet
  169. let vertexCode = origEffect.vertexSourceCode,
  170. fragmentCode = origEffect.fragmentSourceCode;
  171. const vertexNormalBiasCode = this._options && this._options.remappedVariables ? `#include<shadowMapVertexNormalBias>(${this._options.remappedVariables.join(",")})` : Effect.IncludesShadersStore["shadowMapVertexNormalBias"],
  172. vertexMetricCode = this._options && this._options.remappedVariables ? `#include<shadowMapVertexMetric>(${this._options.remappedVariables.join(",")})` : Effect.IncludesShadersStore["shadowMapVertexMetric"],
  173. fragmentBlockCode = Effect.IncludesShadersStore["shadowMapFragment"];
  174. vertexCode = vertexCode.replace(/void\s+?main/g, Effect.IncludesShadersStore["shadowMapVertexDeclaration"] + "\r\nvoid main");
  175. vertexCode = vertexCode.replace(/#define SHADOWDEPTH_NORMALBIAS|#define CUSTOM_VERTEX_UPDATE_WORLDPOS/g, vertexNormalBiasCode);
  176. if (vertexCode.indexOf("#define SHADOWDEPTH_METRIC") !== -1) {
  177. vertexCode = vertexCode.replace(/#define SHADOWDEPTH_METRIC/g, vertexMetricCode);
  178. } else {
  179. vertexCode = vertexCode.replace(/}\s*$/g, vertexMetricCode + "\r\n}");
  180. }
  181. vertexCode = vertexCode.replace(/#define SHADER_NAME.*?\n|out vec4 glFragColor;\n/g, "");
  182. fragmentCode = fragmentCode.replace(/void\s+?main/g, Effect.IncludesShadersStore["shadowMapFragmentDeclaration"] + "\r\nvoid main");
  183. if (fragmentCode.indexOf("#define SHADOWDEPTH_FRAGMENT") !== -1) {
  184. fragmentCode = fragmentCode.replace(/#define SHADOWDEPTH_FRAGMENT/g, fragmentBlockCode);
  185. } else {
  186. fragmentCode = fragmentCode.replace(/}\s*$/g, fragmentBlockCode + "\r\n}");
  187. }
  188. fragmentCode = fragmentCode.replace(/#define SHADER_NAME.*?\n|out vec4 glFragColor;\n/g, "");
  189. const uniforms = origEffect.getUniformNames().slice();
  190. uniforms.push("biasAndScaleSM", "depthValuesSM", "lightDataSM");
  191. params.depthEffect = this._scene.getEngine().createEffect({
  192. vertexSource: vertexCode,
  193. fragmentSource: fragmentCode,
  194. vertexToken: params.token,
  195. fragmentToken: params.token,
  196. }, <IEffectCreationOptions>{
  197. attributes: origEffect.getAttributesNames(),
  198. uniformsNames: uniforms,
  199. uniformBuffersNames: origEffect.getUniformBuffersNames(),
  200. samplers: origEffect.getSamplers(),
  201. defines: join + "\n" + origEffect.defines,
  202. indexParameters: origEffect.getIndexParameters(),
  203. }, this._scene.getEngine());
  204. return params.depthEffect;
  205. }
  206. }