import { __decorate } from "../tslib.es6.js"; /** * Reflective Shadow Maps were first described in http://www.klayge.org/material/3_12/GI/rsm.pdf by Carsten Dachsbacher and Marc Stamminger * The ReflectiveShadowMap class only implements the position / normal / flux texture generation part. * For the global illumination effect, see the GIRSMManager class. */ import { MultiRenderTarget } from "../Materials/Textures/multiRenderTarget.js"; import { Color3, Color4 } from "../Maths/math.color.js"; import { Matrix, TmpVectors } from "../Maths/math.vector.js"; import { MaterialPluginBase } from "../Materials/materialPluginBase.js"; import { MaterialDefines } from "../Materials/materialDefines.js"; import { PBRBaseMaterial } from "../Materials/PBR/pbrBaseMaterial.js"; import { expandToProperty, serialize } from "../Misc/decorators.js"; import { RegisterClass } from "../Misc/typeStore.js"; import { Light } from "../Lights/light.js"; /** * Class used to generate the RSM (Reflective Shadow Map) textures for a given light. * The textures are: position (in world space), normal (in world space) and flux (light intensity) */ export class ReflectiveShadowMap { /** * Enables or disables the RSM generation. */ get enable() { return this._enable; } set enable(value) { if (this._enable === value) { return; } this._enable = value; this._customRenderTarget(value); } /** * Gets the position texture generated by the RSM process. */ get positionWorldTexture() { return this._mrt.textures[0]; } /** * Gets the normal texture generated by the RSM process. */ get normalWorldTexture() { return this._mrt.textures[1]; } /** * Gets the flux texture generated by the RSM process. */ get fluxTexture() { return this._mrt.textures[2]; } /** * Gets the render list used to generate the RSM textures. */ get renderList() { return this._mrt.renderList; } /** * Gets the light used to generate the RSM textures. */ get light() { return this._light; } /** * Creates a new RSM for the given light. * @param scene The scene * @param light The light to use to generate the RSM textures * @param textureDimensions The dimensions of the textures to generate. Default: \{ width: 512, height: 512 \} */ constructor(scene, light, textureDimensions = { width: 512, height: 512 }) { this._lightTransformMatrix = Matrix.Identity(); this._enable = false; /** * Gets or sets a boolean indicating if the light parameters should be recomputed even if the light parameters (position, direction) did not change. * You should not set this value to true, except for debugging purpose (if you want to see changes from the inspector, for eg). * Instead, you should call updateLightParameters() explicitely at the right time (once the light parameters changed). */ this.forceUpdateLightParameters = false; this._scene = scene; this._light = light; this._textureDimensions = textureDimensions; this._regularMatToMatWithPlugin = new Map(); this._counters = [{ name: "RSM Generation " + light.name, value: 0 }]; this._createMultiRenderTarget(); this._recomputeLightTransformationMatrix(); this.enable = true; } /** * Sets the dimensions of the textures to generate. * @param dimensions The dimensions of the textures to generate. */ setTextureDimensions(dimensions) { const renderList = this._mrt.renderList; this._textureDimensions = dimensions; this._disposeMultiRenderTarget(); this._createMultiRenderTarget(); renderList?.forEach((mesh) => { this._addMeshToMRT(mesh); }); } /** * Adds the given mesh to the render list used to generate the RSM textures. * @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. */ addMesh(mesh) { if (mesh) { this._addMeshToMRT(mesh); } else { this._scene.meshes.forEach((mesh) => { this._addMeshToMRT(mesh); }); } this._recomputeLightTransformationMatrix(); } /** * 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. * You should also call this method if you add/remove meshes to/from the render list. */ updateLightParameters() { this._recomputeLightTransformationMatrix(); } /** * Gets the light transformation matrix used to generate the RSM textures. */ get lightTransformationMatrix() { if (this.forceUpdateLightParameters) { this.updateLightParameters(); } return this._lightTransformMatrix; } /** * Gets the GPU time spent to generate the RSM textures. */ get countersGPU() { return this._counters; } /** * Disposes the RSM. */ dispose() { this._disposeMultiRenderTarget(); } _createMultiRenderTarget() { const name = this._light.name; const caps = this._scene.getEngine().getCaps(); const fluxTextureType = caps.rg11b10ufColorRenderable ? 13 : 2; const fluxTextureFormat = caps.rg11b10ufColorRenderable ? 4 : 5; this._mrt = new MultiRenderTarget("RSMmrt_" + name, this._textureDimensions, 3, // number of RTT - position / normal / flux this._scene, { types: [2, 11, fluxTextureType], samplingModes: [2, 2, 2], generateMipMaps: false, targetTypes: [3553, 3553, 3553], formats: [5, 5, fluxTextureFormat], }, ["RSMPosition_" + name, "RSMNormal_" + name, "RSMFlux_" + name]); this._mrt.renderList = []; this._mrt.clearColor = new Color4(0, 0, 0, 1); this._mrt.noPrePassRenderer = true; let sceneUBO; let currentSceneUBO; const useUBO = this._scene.getEngine().supportsUniformBuffers; if (useUBO) { sceneUBO = this._scene.createSceneUniformBuffer(`Scene for RSM (light "${name}")`); } let shadowEnabled; this._mrt.onBeforeBindObservable.add(() => { currentSceneUBO = this._scene.getSceneUniformBuffer(); shadowEnabled = this._light.shadowEnabled; this._light.shadowEnabled = false; // we render from the light point of view, so we won't have any shadow anyway! }); this._mrt.onBeforeRenderObservable.add((faceIndex) => { if (sceneUBO) { this._scene.setSceneUniformBuffer(sceneUBO); } const viewMatrix = this._light.getViewMatrix(faceIndex); const projectionMatrix = this._light.getProjectionMatrix(viewMatrix || undefined, this._mrt.renderList || undefined); if (viewMatrix && projectionMatrix) { this._scene.setTransformMatrix(viewMatrix, projectionMatrix); } if (useUBO) { this._scene.getSceneUniformBuffer().unbindEffect(); this._scene.finalizeSceneUbo(); } }); this._mrt.onAfterUnbindObservable.add(() => { if (sceneUBO) { this._scene.setSceneUniformBuffer(currentSceneUBO); } this._scene.updateTransformMatrix(); // restore the view/projection matrices of the active camera this._light.shadowEnabled = shadowEnabled; this._counters[0].value = this._mrt.renderTarget.gpuTimeInFrame?.counter.lastSecAverage ?? 0; }); this._customRenderTarget(true); } _customRenderTarget(add) { const idx = this._scene.customRenderTargets.indexOf(this._mrt); if (add) { if (idx === -1) { this._scene.customRenderTargets.push(this._mrt); } } else if (idx !== -1) { this._scene.customRenderTargets.splice(idx, 1); } } _recomputeLightTransformationMatrix() { const viewMatrix = this._light.getViewMatrix(); const projectionMatrix = this._light.getProjectionMatrix(viewMatrix || undefined, this._mrt.renderList || undefined); if (viewMatrix && projectionMatrix) { viewMatrix.multiplyToRef(projectionMatrix, this._lightTransformMatrix); } } _addMeshToMRT(mesh) { this._mrt.renderList?.push(mesh); const material = mesh.material; if (mesh.getTotalVertices() === 0 || !material) { return; } let rsmMaterial = this._regularMatToMatWithPlugin.get(material); if (!rsmMaterial) { rsmMaterial = material.clone("RSMCreate_" + material.name) || undefined; if (rsmMaterial) { // Disable the prepass renderer for this material Object.defineProperty(rsmMaterial, "canRenderToMRT", { get: function () { return false; }, enumerable: true, configurable: true, }); rsmMaterial.disableLighting = true; const rsmCreatePlugin = new RSMCreatePluginMaterial(rsmMaterial); rsmCreatePlugin.isEnabled = true; rsmCreatePlugin.light = this._light; this._regularMatToMatWithPlugin.set(material, rsmMaterial); } } this._mrt.setMaterialForRendering(mesh, rsmMaterial); } _disposeMultiRenderTarget() { this._customRenderTarget(false); this._mrt.dispose(); } } /** * @internal */ class MaterialRSMCreateDefines extends MaterialDefines { constructor() { super(...arguments); this.RSMCREATE = false; this.RSMCREATE_PROJTEXTURE = false; this.RSMCREATE_LIGHT_IS_SPOT = false; } } /** * Plugin that implements the creation of the RSM textures */ export class RSMCreatePluginMaterial extends MaterialPluginBase { _markAllSubMeshesAsTexturesDirty() { this._enable(this._isEnabled); this._internalMarkAllSubMeshesAsTexturesDirty(); } /** * Create a new RSMCreatePluginMaterial * @param material Parent material of the plugin */ constructor(material) { super(material, RSMCreatePluginMaterial.Name, 300, new MaterialRSMCreateDefines()); this._lightColor = new Color3(); this._hasProjectionTexture = false; this._isEnabled = false; /** * Defines if the plugin is enabled in the material. */ this.isEnabled = false; this._internalMarkAllSubMeshesAsTexturesDirty = material._dirtyCallbacks[1]; this._varAlbedoName = material instanceof PBRBaseMaterial ? "surfaceAlbedo" : "baseColor.rgb"; } prepareDefines(defines) { defines.RSMCREATE = this._isEnabled; this._hasProjectionTexture = false; const isSpot = this.light.getTypeID() === Light.LIGHTTYPEID_SPOTLIGHT; if (isSpot) { const spot = this.light; this._hasProjectionTexture = spot.projectionTexture ? spot.projectionTexture.isReady() : false; } defines.RSMCREATE_PROJTEXTURE = this._hasProjectionTexture; defines.RSMCREATE_LIGHT_IS_SPOT = isSpot; } getClassName() { return "RSMCreatePluginMaterial"; } getUniforms() { return { ubo: [ { name: "rsmTextureProjectionMatrix", size: 16, type: "mat4" }, { name: "rsmSpotInfo", size: 4, type: "vec4" }, { name: "rsmLightColor", size: 3, type: "vec3" }, { name: "rsmLightPosition", size: 3, type: "vec3" }, ], fragment: `#ifdef RSMCREATE uniform mat4 rsmTextureProjectionMatrix; uniform vec4 rsmSpotInfo; uniform vec3 rsmLightColor; uniform vec3 rsmLightPosition; #endif`, }; } getSamplers(samplers) { samplers.push("rsmTextureProjectionSampler"); } bindForSubMesh(uniformBuffer) { if (!this._isEnabled) { return; } this.light.diffuse.scaleToRef(this.light.getScaledIntensity(), this._lightColor); uniformBuffer.updateColor3("rsmLightColor", this._lightColor); if (this.light.getTypeID() === Light.LIGHTTYPEID_SPOTLIGHT) { const spot = this.light; if (this._hasProjectionTexture) { uniformBuffer.updateMatrix("rsmTextureProjectionMatrix", spot.projectionTextureMatrix); uniformBuffer.setTexture("rsmTextureProjectionSampler", spot.projectionTexture); } const normalizeDirection = TmpVectors.Vector3[0]; if (spot.computeTransformedInformation()) { uniformBuffer.updateFloat3("rsmLightPosition", this.light.transformedPosition.x, this.light.transformedPosition.y, this.light.transformedPosition.z); spot.transformedDirection.normalizeToRef(normalizeDirection); } else { uniformBuffer.updateFloat3("rsmLightPosition", this.light.position.x, this.light.position.y, this.light.position.z); spot.direction.normalizeToRef(normalizeDirection); } uniformBuffer.updateFloat4("rsmSpotInfo", normalizeDirection.x, normalizeDirection.y, normalizeDirection.z, Math.cos(spot.angle * 0.5)); } } getCustomCode(shaderType) { return shaderType === "vertex" ? null : { // eslint-disable-next-line @typescript-eslint/naming-convention CUSTOM_FRAGMENT_BEGIN: ` #ifdef RSMCREATE #extension GL_EXT_draw_buffers : require #endif `, // eslint-disable-next-line @typescript-eslint/naming-convention CUSTOM_FRAGMENT_DEFINITIONS: ` #ifdef RSMCREATE #ifdef RSMCREATE_PROJTEXTURE uniform highp sampler2D rsmTextureProjectionSampler; #endif layout(location = 0) out highp vec4 glFragData[3]; vec4 glFragColor; #endif `, // eslint-disable-next-line @typescript-eslint/naming-convention CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR: ` #ifdef RSMCREATE vec3 rsmColor = ${this._varAlbedoName} * rsmLightColor; #ifdef RSMCREATE_PROJTEXTURE { vec4 strq = rsmTextureProjectionMatrix * vec4(vPositionW, 1.0); strq /= strq.w; rsmColor *= texture2D(rsmTextureProjectionSampler, strq.xy).rgb; } #endif #ifdef RSMCREATE_LIGHT_IS_SPOT { float cosAngle = max(0., dot(rsmSpotInfo.xyz, normalize(vPositionW - rsmLightPosition))); rsmColor = sign(cosAngle - rsmSpotInfo.w) * rsmColor; } #endif glFragData[0] = vec4(vPositionW, 1.); glFragData[1] = vec4(normalize(normalW) * 0.5 + 0.5, 1.); glFragData[2] = vec4(rsmColor, 1.); #endif `, }; } } /** * Defines the name of the plugin. */ RSMCreatePluginMaterial.Name = "RSMCreate"; __decorate([ serialize() ], RSMCreatePluginMaterial.prototype, "light", void 0); __decorate([ serialize(), expandToProperty("_markAllSubMeshesAsTexturesDirty") ], RSMCreatePluginMaterial.prototype, "isEnabled", void 0); RegisterClass(`BABYLON.RSMCreatePluginMaterial`, RSMCreatePluginMaterial); //# sourceMappingURL=reflectiveShadowMap.js.map