@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
444 lines (442 loc) • 18.5 kB
JavaScript
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();
if (renderList) {
for (const mesh of renderList) {
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 {
for (const mesh of this._scene.meshes) {
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();
}
/**
* Gets a boolean indicating that the plugin is compatible with a give shader language.
* @returns true if the plugin is compatible with the shader language
*/
isCompatible() {
return true;
}
/**
* 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;
defines.SCENE_MRT_COUNT = 3;
}
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, shaderLanguage) {
if (shaderType === "vertex") {
return null;
}
if (shaderLanguage === 1 /* ShaderLanguage.WGSL */) {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
CUSTOM_FRAGMENT_DEFINITIONS: `
#ifdef RSMCREATE
#ifdef RSMCREATE_PROJTEXTURE
var rsmTextureProjectionSamplerSampler: sampler;
var rsmTextureProjectionSampler: texture_2d<f32>;
#endif
#endif
`,
// eslint-disable-next-line @typescript-eslint/naming-convention
CUSTOM_FRAGMENT_BEFORE_FRAGCOLOR: `
#ifdef RSMCREATE
var rsmColor = ${this._varAlbedoName} * uniforms.rsmLightColor;
#ifdef RSMCREATE_PROJTEXTURE
{
var strq = uniforms.rsmTextureProjectionMatrix * vec4f(fragmentInputs.vPositionW, 1.0);
strq /= strq.w;
rsmColor *= textureSample(rsmTextureProjectionSampler, rsmTextureProjectionSamplerSampler, strq.xy).rgb;
}
#endif
#ifdef RSMCREATE_LIGHT_IS_SPOT
{
var cosAngle = max(0., dot(uniforms.rsmSpotInfo.xyz, normalize(fragmentInputs.vPositionW - uniforms.rsmLightPosition)));
rsmColor = sign(cosAngle - uniforms.rsmSpotInfo.w) * rsmColor;
}
#endif
#define MRT_AND_COLOR
fragmentOutputs.fragData0 = vec4f(fragmentInputs.vPositionW, 1.);
fragmentOutputs.fragData1 = vec4f(normalize(normalW) * 0.5 + 0.5, 1.);
fragmentOutputs.fragData2 = vec4f(rsmColor, 1.);
#endif
`,
};
}
return {
// 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