reflectiveShadowMap.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. import { __decorate } from "../tslib.es6.js";
  2. /**
  3. * Reflective Shadow Maps were first described in http://www.klayge.org/material/3_12/GI/rsm.pdf by Carsten Dachsbacher and Marc Stamminger
  4. * The ReflectiveShadowMap class only implements the position / normal / flux texture generation part.
  5. * For the global illumination effect, see the GIRSMManager class.
  6. */
  7. import { MultiRenderTarget } from "../Materials/Textures/multiRenderTarget.js";
  8. import { Color3, Color4 } from "../Maths/math.color.js";
  9. import { Matrix, TmpVectors } from "../Maths/math.vector.js";
  10. import { MaterialPluginBase } from "../Materials/materialPluginBase.js";
  11. import { MaterialDefines } from "../Materials/materialDefines.js";
  12. import { PBRBaseMaterial } from "../Materials/PBR/pbrBaseMaterial.js";
  13. import { expandToProperty, serialize } from "../Misc/decorators.js";
  14. import { RegisterClass } from "../Misc/typeStore.js";
  15. import { Light } from "../Lights/light.js";
  16. /**
  17. * Class used to generate the RSM (Reflective Shadow Map) textures for a given light.
  18. * The textures are: position (in world space), normal (in world space) and flux (light intensity)
  19. */
  20. export class ReflectiveShadowMap {
  21. /**
  22. * Enables or disables the RSM generation.
  23. */
  24. get enable() {
  25. return this._enable;
  26. }
  27. set enable(value) {
  28. if (this._enable === value) {
  29. return;
  30. }
  31. this._enable = value;
  32. this._customRenderTarget(value);
  33. }
  34. /**
  35. * Gets the position texture generated by the RSM process.
  36. */
  37. get positionWorldTexture() {
  38. return this._mrt.textures[0];
  39. }
  40. /**
  41. * Gets the normal texture generated by the RSM process.
  42. */
  43. get normalWorldTexture() {
  44. return this._mrt.textures[1];
  45. }
  46. /**
  47. * Gets the flux texture generated by the RSM process.
  48. */
  49. get fluxTexture() {
  50. return this._mrt.textures[2];
  51. }
  52. /**
  53. * Gets the render list used to generate the RSM textures.
  54. */
  55. get renderList() {
  56. return this._mrt.renderList;
  57. }
  58. /**
  59. * Gets the light used to generate the RSM textures.
  60. */
  61. get light() {
  62. return this._light;
  63. }
  64. /**
  65. * Creates a new RSM for the given light.
  66. * @param scene The scene
  67. * @param light The light to use to generate the RSM textures
  68. * @param textureDimensions The dimensions of the textures to generate. Default: \{ width: 512, height: 512 \}
  69. */
  70. constructor(scene, light, textureDimensions = { width: 512, height: 512 }) {
  71. this._lightTransformMatrix = Matrix.Identity();
  72. this._enable = false;
  73. /**
  74. * Gets or sets a boolean indicating if the light parameters should be recomputed even if the light parameters (position, direction) did not change.
  75. * You should not set this value to true, except for debugging purpose (if you want to see changes from the inspector, for eg).
  76. * Instead, you should call updateLightParameters() explicitely at the right time (once the light parameters changed).
  77. */
  78. this.forceUpdateLightParameters = false;
  79. this._scene = scene;
  80. this._light = light;
  81. this._textureDimensions = textureDimensions;
  82. this._regularMatToMatWithPlugin = new Map();
  83. this._counters = [{ name: "RSM Generation " + light.name, value: 0 }];
  84. this._createMultiRenderTarget();
  85. this._recomputeLightTransformationMatrix();
  86. this.enable = true;
  87. }
  88. /**
  89. * Sets the dimensions of the textures to generate.
  90. * @param dimensions The dimensions of the textures to generate.
  91. */
  92. setTextureDimensions(dimensions) {
  93. const renderList = this._mrt.renderList;
  94. this._textureDimensions = dimensions;
  95. this._disposeMultiRenderTarget();
  96. this._createMultiRenderTarget();
  97. renderList?.forEach((mesh) => {
  98. this._addMeshToMRT(mesh);
  99. });
  100. }
  101. /**
  102. * Adds the given mesh to the render list used to generate the RSM textures.
  103. * @param mesh The mesh to add to the render list used to generate the RSM textures. If not provided, all scene meshes will be added to the render list.
  104. */
  105. addMesh(mesh) {
  106. if (mesh) {
  107. this._addMeshToMRT(mesh);
  108. }
  109. else {
  110. this._scene.meshes.forEach((mesh) => {
  111. this._addMeshToMRT(mesh);
  112. });
  113. }
  114. this._recomputeLightTransformationMatrix();
  115. }
  116. /**
  117. * Recomputes the light transformation matrix. Call this method if you manually changed the light position / direction / etc. and you want to update the RSM textures accordingly.
  118. * You should also call this method if you add/remove meshes to/from the render list.
  119. */
  120. updateLightParameters() {
  121. this._recomputeLightTransformationMatrix();
  122. }
  123. /**
  124. * Gets the light transformation matrix used to generate the RSM textures.
  125. */
  126. get lightTransformationMatrix() {
  127. if (this.forceUpdateLightParameters) {
  128. this.updateLightParameters();
  129. }
  130. return this._lightTransformMatrix;
  131. }
  132. /**
  133. * Gets the GPU time spent to generate the RSM textures.
  134. */
  135. get countersGPU() {
  136. return this._counters;
  137. }
  138. /**
  139. * Disposes the RSM.
  140. */
  141. dispose() {
  142. this._disposeMultiRenderTarget();
  143. }
  144. _createMultiRenderTarget() {
  145. const name = this._light.name;
  146. const caps = this._scene.getEngine().getCaps();
  147. const fluxTextureType = caps.rg11b10ufColorRenderable ? 13 : 2;
  148. const fluxTextureFormat = caps.rg11b10ufColorRenderable ? 4 : 5;
  149. this._mrt = new MultiRenderTarget("RSMmrt_" + name, this._textureDimensions, 3, // number of RTT - position / normal / flux
  150. this._scene, {
  151. types: [2, 11, fluxTextureType],
  152. samplingModes: [2, 2, 2],
  153. generateMipMaps: false,
  154. targetTypes: [3553, 3553, 3553],
  155. formats: [5, 5, fluxTextureFormat],
  156. }, ["RSMPosition_" + name, "RSMNormal_" + name, "RSMFlux_" + name]);
  157. this._mrt.renderList = [];
  158. this._mrt.clearColor = new Color4(0, 0, 0, 1);
  159. this._mrt.noPrePassRenderer = true;
  160. let sceneUBO;
  161. let currentSceneUBO;
  162. const useUBO = this._scene.getEngine().supportsUniformBuffers;
  163. if (useUBO) {
  164. sceneUBO = this._scene.createSceneUniformBuffer(`Scene for RSM (light "${name}")`);
  165. }
  166. let shadowEnabled;
  167. this._mrt.onBeforeBindObservable.add(() => {
  168. currentSceneUBO = this._scene.getSceneUniformBuffer();
  169. shadowEnabled = this._light.shadowEnabled;
  170. this._light.shadowEnabled = false; // we render from the light point of view, so we won't have any shadow anyway!
  171. });
  172. this._mrt.onBeforeRenderObservable.add((faceIndex) => {
  173. if (sceneUBO) {
  174. this._scene.setSceneUniformBuffer(sceneUBO);
  175. }
  176. const viewMatrix = this._light.getViewMatrix(faceIndex);
  177. const projectionMatrix = this._light.getProjectionMatrix(viewMatrix || undefined, this._mrt.renderList || undefined);
  178. if (viewMatrix && projectionMatrix) {
  179. this._scene.setTransformMatrix(viewMatrix, projectionMatrix);
  180. }
  181. if (useUBO) {
  182. this._scene.getSceneUniformBuffer().unbindEffect();
  183. this._scene.finalizeSceneUbo();
  184. }
  185. });
  186. this._mrt.onAfterUnbindObservable.add(() => {
  187. if (sceneUBO) {
  188. this._scene.setSceneUniformBuffer(currentSceneUBO);
  189. }
  190. this._scene.updateTransformMatrix(); // restore the view/projection matrices of the active camera
  191. this._light.shadowEnabled = shadowEnabled;
  192. this._counters[0].value = this._mrt.renderTarget.gpuTimeInFrame?.counter.lastSecAverage ?? 0;
  193. });
  194. this._customRenderTarget(true);
  195. }
  196. _customRenderTarget(add) {
  197. const idx = this._scene.customRenderTargets.indexOf(this._mrt);
  198. if (add) {
  199. if (idx === -1) {
  200. this._scene.customRenderTargets.push(this._mrt);
  201. }
  202. }
  203. else if (idx !== -1) {
  204. this._scene.customRenderTargets.splice(idx, 1);
  205. }
  206. }
  207. _recomputeLightTransformationMatrix() {
  208. const viewMatrix = this._light.getViewMatrix();
  209. const projectionMatrix = this._light.getProjectionMatrix(viewMatrix || undefined, this._mrt.renderList || undefined);
  210. if (viewMatrix && projectionMatrix) {
  211. viewMatrix.multiplyToRef(projectionMatrix, this._lightTransformMatrix);
  212. }
  213. }
  214. _addMeshToMRT(mesh) {
  215. this._mrt.renderList?.push(mesh);
  216. const material = mesh.material;
  217. if (mesh.getTotalVertices() === 0 || !material) {
  218. return;
  219. }
  220. let rsmMaterial = this._regularMatToMatWithPlugin.get(material);
  221. if (!rsmMaterial) {
  222. rsmMaterial = material.clone("RSMCreate_" + material.name) || undefined;
  223. if (rsmMaterial) {
  224. // Disable the prepass renderer for this material
  225. Object.defineProperty(rsmMaterial, "canRenderToMRT", {
  226. get: function () {
  227. return false;
  228. },
  229. enumerable: true,
  230. configurable: true,
  231. });
  232. rsmMaterial.disableLighting = true;
  233. const rsmCreatePlugin = new RSMCreatePluginMaterial(rsmMaterial);
  234. rsmCreatePlugin.isEnabled = true;
  235. rsmCreatePlugin.light = this._light;
  236. this._regularMatToMatWithPlugin.set(material, rsmMaterial);
  237. }
  238. }
  239. this._mrt.setMaterialForRendering(mesh, rsmMaterial);
  240. }
  241. _disposeMultiRenderTarget() {
  242. this._customRenderTarget(false);
  243. this._mrt.dispose();
  244. }
  245. }
  246. /**
  247. * @internal
  248. */
  249. class MaterialRSMCreateDefines extends MaterialDefines {
  250. constructor() {
  251. super(...arguments);
  252. this.RSMCREATE = false;
  253. this.RSMCREATE_PROJTEXTURE = false;
  254. this.RSMCREATE_LIGHT_IS_SPOT = false;
  255. }
  256. }
  257. /**
  258. * Plugin that implements the creation of the RSM textures
  259. */
  260. export class RSMCreatePluginMaterial extends MaterialPluginBase {
  261. _markAllSubMeshesAsTexturesDirty() {
  262. this._enable(this._isEnabled);
  263. this._internalMarkAllSubMeshesAsTexturesDirty();
  264. }
  265. /**
  266. * Create a new RSMCreatePluginMaterial
  267. * @param material Parent material of the plugin
  268. */
  269. constructor(material) {
  270. super(material, RSMCreatePluginMaterial.Name, 300, new MaterialRSMCreateDefines());
  271. this._lightColor = new Color3();
  272. this._hasProjectionTexture = false;
  273. this._isEnabled = false;
  274. /**
  275. * Defines if the plugin is enabled in the material.
  276. */
  277. this.isEnabled = false;
  278. this._internalMarkAllSubMeshesAsTexturesDirty = material._dirtyCallbacks[1];
  279. this._varAlbedoName = material instanceof PBRBaseMaterial ? "surfaceAlbedo" : "baseColor.rgb";
  280. }
  281. prepareDefines(defines) {
  282. defines.RSMCREATE = this._isEnabled;
  283. this._hasProjectionTexture = false;
  284. const isSpot = this.light.getTypeID() === Light.LIGHTTYPEID_SPOTLIGHT;
  285. if (isSpot) {
  286. const spot = this.light;
  287. this._hasProjectionTexture = spot.projectionTexture ? spot.projectionTexture.isReady() : false;
  288. }
  289. defines.RSMCREATE_PROJTEXTURE = this._hasProjectionTexture;
  290. defines.RSMCREATE_LIGHT_IS_SPOT = isSpot;
  291. }
  292. getClassName() {
  293. return "RSMCreatePluginMaterial";
  294. }
  295. getUniforms() {
  296. return {
  297. ubo: [
  298. { name: "rsmTextureProjectionMatrix", size: 16, type: "mat4" },
  299. { name: "rsmSpotInfo", size: 4, type: "vec4" },
  300. { name: "rsmLightColor", size: 3, type: "vec3" },
  301. { name: "rsmLightPosition", size: 3, type: "vec3" },
  302. ],
  303. fragment: `#ifdef RSMCREATE
  304. uniform mat4 rsmTextureProjectionMatrix;
  305. uniform vec4 rsmSpotInfo;
  306. uniform vec3 rsmLightColor;
  307. uniform vec3 rsmLightPosition;
  308. #endif`,
  309. };
  310. }
  311. getSamplers(samplers) {
  312. samplers.push("rsmTextureProjectionSampler");
  313. }
  314. bindForSubMesh(uniformBuffer) {
  315. if (!this._isEnabled) {
  316. return;
  317. }
  318. this.light.diffuse.scaleToRef(this.light.getScaledIntensity(), this._lightColor);
  319. uniformBuffer.updateColor3("rsmLightColor", this._lightColor);
  320. if (this.light.getTypeID() === Light.LIGHTTYPEID_SPOTLIGHT) {
  321. const spot = this.light;
  322. if (this._hasProjectionTexture) {
  323. uniformBuffer.updateMatrix("rsmTextureProjectionMatrix", spot.projectionTextureMatrix);
  324. uniformBuffer.setTexture("rsmTextureProjectionSampler", spot.projectionTexture);
  325. }
  326. const normalizeDirection = TmpVectors.Vector3[0];
  327. if (spot.computeTransformedInformation()) {
  328. uniformBuffer.updateFloat3("rsmLightPosition", this.light.transformedPosition.x, this.light.transformedPosition.y, this.light.transformedPosition.z);
  329. spot.transformedDirection.normalizeToRef(normalizeDirection);
  330. }
  331. else {
  332. uniformBuffer.updateFloat3("rsmLightPosition", this.light.position.x, this.light.position.y, this.light.position.z);
  333. spot.direction.normalizeToRef(normalizeDirection);
  334. }
  335. uniformBuffer.updateFloat4("rsmSpotInfo", normalizeDirection.x, normalizeDirection.y, normalizeDirection.z, Math.cos(spot.angle * 0.5));
  336. }
  337. }
  338. getCustomCode(shaderType) {
  339. return shaderType === "vertex"
  340. ? null
  341. : {
  342. // eslint-disable-next-line @typescript-eslint/naming-convention
  343. CUSTOM_FRAGMENT_BEGIN: `
  344. #ifdef RSMCREATE
  345. #extension GL_EXT_draw_buffers : require
  346. #endif
  347. `,
  348. // eslint-disable-next-line @typescript-eslint/naming-convention
  349. CUSTOM_FRAGMENT_DEFINITIONS: `
  350. #ifdef RSMCREATE
  351. #ifdef RSMCREATE_PROJTEXTURE
  352. uniform highp sampler2D rsmTextureProjectionSampler;
  353. #endif
  354. layout(location = 0) out highp vec4 glFragData[3];
  355. vec4 glFragColor;
  356. #endif
  357. `,
  358. // eslint-disable-next-line @typescript-eslint/naming-convention
  359. CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR: `
  360. #ifdef RSMCREATE
  361. vec3 rsmColor = ${this._varAlbedoName} * rsmLightColor;
  362. #ifdef RSMCREATE_PROJTEXTURE
  363. {
  364. vec4 strq = rsmTextureProjectionMatrix * vec4(vPositionW, 1.0);
  365. strq /= strq.w;
  366. rsmColor *= texture2D(rsmTextureProjectionSampler, strq.xy).rgb;
  367. }
  368. #endif
  369. #ifdef RSMCREATE_LIGHT_IS_SPOT
  370. {
  371. float cosAngle = max(0., dot(rsmSpotInfo.xyz, normalize(vPositionW - rsmLightPosition)));
  372. rsmColor = sign(cosAngle - rsmSpotInfo.w) * rsmColor;
  373. }
  374. #endif
  375. glFragData[0] = vec4(vPositionW, 1.);
  376. glFragData[1] = vec4(normalize(normalW) * 0.5 + 0.5, 1.);
  377. glFragData[2] = vec4(rsmColor, 1.);
  378. #endif
  379. `,
  380. };
  381. }
  382. }
  383. /**
  384. * Defines the name of the plugin.
  385. */
  386. RSMCreatePluginMaterial.Name = "RSMCreate";
  387. __decorate([
  388. serialize()
  389. ], RSMCreatePluginMaterial.prototype, "light", void 0);
  390. __decorate([
  391. serialize(),
  392. expandToProperty("_markAllSubMeshesAsTexturesDirty")
  393. ], RSMCreatePluginMaterial.prototype, "isEnabled", void 0);
  394. RegisterClass(`BABYLON.RSMCreatePluginMaterial`, RSMCreatePluginMaterial);
  395. //# sourceMappingURL=reflectiveShadowMap.js.map