@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.
351 lines (350 loc) • 16.5 kB
JavaScript
import { backbufferColorTextureHandle, backbufferDepthStencilTextureHandle } from "../../frameGraphTypes.js";
import { FrameGraphTask } from "../../frameGraphTask.js";
import { ObjectRenderer } from "../../../Rendering/objectRenderer.js";
import { FrameGraphCascadedShadowGeneratorTask } from "./csmShadowGeneratorTask.js";
/**
* Task used to render objects to a texture.
*/
export class FrameGraphObjectRendererTask extends FrameGraphTask {
/**
* Gets or sets the camera used to render the objects.
*/
get camera() {
return this._camera;
}
set camera(camera) {
this._camera = camera;
this._renderer.activeCamera = this.camera;
}
/**
* If image processing should be disabled (default is false).
* false means that the default image processing configuration will be applied (the one from the scene)
*/
get disableImageProcessing() {
return this._disableImageProcessing;
}
set disableImageProcessing(value) {
if (value === this._disableImageProcessing) {
return;
}
this._disableImageProcessing = value;
this._renderer.disableImageProcessing = value;
}
/**
* Defines if meshes should be rendered (default is true).
*/
get renderMeshes() {
return this._renderMeshes;
}
set renderMeshes(value) {
if (value === this._renderMeshes) {
return;
}
this._renderMeshes = value;
this._renderer.renderMeshes = value;
}
/**
* Defines if particles should be rendered (default is true).
*/
get renderParticles() {
return this._renderParticles;
}
set renderParticles(value) {
if (value === this._renderParticles) {
return;
}
this._renderParticles = value;
this._renderer.renderParticles = value;
}
/**
* Defines if sprites should be rendered (default is true).
*/
get renderSprites() {
return this._renderSprites;
}
set renderSprites(value) {
if (value === this._renderSprites) {
return;
}
this._renderSprites = value;
this._renderer.renderSprites = value;
}
/**
* Forces checking the layerMask property even if a custom list of meshes is provided (ie. if renderList is not undefined). Default is true.
*/
get forceLayerMaskCheck() {
return this._forceLayerMaskCheck;
}
set forceLayerMaskCheck(value) {
if (value === this._forceLayerMaskCheck) {
return;
}
this._forceLayerMaskCheck = value;
this._renderer.forceLayerMaskCheck = value;
}
/**
* Enables the rendering of bounding boxes for meshes (still subject to Mesh.showBoundingBox or scene.forceShowBoundingBoxes). Default is true.
*/
get enableBoundingBoxRendering() {
return this._enableBoundingBoxRendering;
}
set enableBoundingBoxRendering(value) {
if (value === this._enableBoundingBoxRendering) {
return;
}
this._enableBoundingBoxRendering = value;
this._renderer.enableBoundingBoxRendering = value;
}
/**
* Enables the rendering of outlines/overlays for meshes (still subject to Mesh.renderOutline/Mesh.renderOverlay). Default is true.
*/
get enableOutlineRendering() {
return this._enableOutlineRendering;
}
set enableOutlineRendering(value) {
if (value === this._enableOutlineRendering) {
return;
}
this._enableOutlineRendering = value;
this._renderer.enableOutlineRendering = value;
}
/**
* The object renderer used to render the objects.
*/
get objectRenderer() {
return this._renderer;
}
get name() {
return this._name;
}
set name(value) {
this._name = value;
if (this._renderer) {
this._renderer.name = value;
}
}
/**
* Constructs a new object renderer task.
* @param name The name of the task.
* @param frameGraph The frame graph the task belongs to.
* @param scene The scene the frame graph is associated with.
* @param options The options of the object renderer.
* @param existingObjectRenderer An existing object renderer to use (optional). If provided, the options parameter will be ignored.
*/
constructor(name, frameGraph, scene, options, existingObjectRenderer) {
super(name, frameGraph);
/**
* The shadow generators used to render the objects (optional).
*/
this.shadowGenerators = [];
/**
* If depth testing should be enabled (default is true).
*/
this.depthTest = true;
/**
* If depth writing should be enabled (default is true).
*/
this.depthWrite = true;
/**
* If shadows should be disabled (default is false).
*/
this.disableShadows = false;
this._disableImageProcessing = false;
/**
* Sets this property to true if this task is the main object renderer of the frame graph.
* It will help to locate the main object renderer in the frame graph when multiple object renderers are used.
* This is useful for the inspector to know which object renderer to use for additional rendering features like wireframe rendering or frustum light debugging.
* It is also used to determine the main camera used by the frame graph: this is the camera used by the main object renderer.
*/
this.isMainObjectRenderer = false;
this._renderMeshes = true;
this._renderParticles = true;
this._renderSprites = true;
this._forceLayerMaskCheck = true;
this._enableBoundingBoxRendering = true;
this._enableOutlineRendering = true;
/**
* If true, targetTexture will be resolved at the end of the render pass, if this/these texture(s) is/are MSAA (default: true)
*/
this.resolveMSAAColors = true;
/**
* If true, depthTexture will be resolved at the end of the render pass, if this texture is provided and is MSAA (default: false).
*/
this.resolveMSAADepth = false;
this._onBeforeRenderObservable = null;
this._onAfterRenderObservable = null;
this._externalObjectRenderer = false;
this._scene = scene;
this._engine = scene.getEngine();
this._externalObjectRenderer = !!existingObjectRenderer;
this._renderer = existingObjectRenderer ?? new ObjectRenderer(name, scene, options);
this.name = name;
this._renderer.disableImageProcessing = this._disableImageProcessing;
this._renderer.renderParticles = this._renderParticles;
this._renderer.renderSprites = this._renderSprites;
this._renderer.enableBoundingBoxRendering = this._enableBoundingBoxRendering;
this._renderer.forceLayerMaskCheck = this._forceLayerMaskCheck;
if (!this._externalObjectRenderer) {
this._renderer.onBeforeRenderingManagerRenderObservable.add(() => {
if (!this._renderer.options.doNotChangeAspectRatio) {
scene.updateTransformMatrix(true);
}
});
}
this.outputTexture = this._frameGraph.textureManager.createDanglingHandle();
this.outputDepthTexture = this._frameGraph.textureManager.createDanglingHandle();
}
isReady() {
return this._renderer.isReadyForRendering(this._textureWidth, this._textureHeight);
}
getClassName() {
return "FrameGraphObjectRendererTask";
}
record(skipCreationOfDisabledPasses = false, additionalExecute) {
this._checkParameters();
// Make sure the renderList / particleSystemList are set when FrameGraphObjectRendererTask.isReady() is called!
this._renderer.renderList = this.objectList.meshes;
this._renderer.particleSystemList = this.objectList.particleSystems;
const targetTextures = this._getTargetHandles();
const depthEnabled = this._checkTextureCompatibility(targetTextures);
this._resolveDanglingHandles(targetTextures);
this._setLightsForShadow();
const pass = this._frameGraph.addRenderPass(this.name);
pass.setRenderTarget(targetTextures);
pass.setRenderTargetDepth(this.depthTexture);
pass.setExecuteFunc((context) => {
this._renderer.renderList = this.objectList.meshes;
this._renderer.particleSystemList = this.objectList.particleSystems;
const renderTargetWrapper = pass.frameGraphRenderTarget.renderTargetWrapper;
if (renderTargetWrapper) {
renderTargetWrapper.resolveMSAAColors = this.resolveMSAAColors;
renderTargetWrapper.resolveMSAADepth = this.resolveMSAADepth;
}
// The cast to "any" is to avoid an error in ES6 in case you don't import boundingBoxRenderer
const boundingBoxRenderer = this.getBoundingBoxRenderer?.();
const currentBoundingBoxMeshList = boundingBoxRenderer && boundingBoxRenderer.renderList.length > 0 ? boundingBoxRenderer.renderList.data.slice() : [];
if (boundingBoxRenderer) {
currentBoundingBoxMeshList.length = boundingBoxRenderer.renderList.length;
}
this._prepareRendering(context, depthEnabled);
const camera = this._renderer.activeCamera;
if (camera && camera.cameraRigMode !== 0 && !camera._renderingMultiview) {
for (let index = 0; index < camera._rigCameras.length; index++) {
const rigCamera = camera._rigCameras[index];
rigCamera.rigParent = undefined; // for some reasons, ObjectRenderer uses the rigParent viewport if rigParent is defined (we want to use rigCamera.viewport instead)
this._renderer.activeCamera = rigCamera;
context.render(this._renderer, this._textureWidth, this._textureHeight);
rigCamera.rigParent = camera;
}
this._renderer.activeCamera = camera;
}
else {
context.render(this._renderer, this._textureWidth, this._textureHeight);
}
additionalExecute?.(context);
if (boundingBoxRenderer) {
boundingBoxRenderer.renderList.data = currentBoundingBoxMeshList;
boundingBoxRenderer.renderList.length = currentBoundingBoxMeshList.length;
}
});
if (!skipCreationOfDisabledPasses) {
const passDisabled = this._frameGraph.addRenderPass(this.name + "_disabled", true);
passDisabled.setRenderTarget(targetTextures);
passDisabled.setRenderTargetDepth(this.depthTexture);
passDisabled.setExecuteFunc((_context) => { });
}
return pass;
}
dispose() {
this._renderer.onBeforeRenderObservable.remove(this._onBeforeRenderObservable);
this._renderer.onAfterRenderObservable.remove(this._onAfterRenderObservable);
if (!this._externalObjectRenderer) {
this._renderer.dispose();
}
super.dispose();
}
_resolveDanglingHandles(targetTextures) {
if (targetTextures.length > 0) {
this._frameGraph.textureManager.resolveDanglingHandle(this.outputTexture, targetTextures[0]);
}
if (this.depthTexture !== undefined) {
this._frameGraph.textureManager.resolveDanglingHandle(this.outputDepthTexture, this.depthTexture);
}
}
_checkParameters() {
if (this.targetTexture === undefined || this.objectList === undefined || this.camera === undefined) {
throw new Error(`FrameGraphObjectRendererTask ${this.name}: targetTexture, objectList, and camera are required`);
}
}
_checkTextureCompatibility(targetTextures) {
const className = this.getClassName();
let outputTextureDescription = targetTextures.length > 0 ? this._frameGraph.textureManager.getTextureDescription(targetTextures[0]) : null;
let depthEnabled = false;
if (this.depthTexture !== undefined) {
if (outputTextureDescription && this.depthTexture !== backbufferDepthStencilTextureHandle && targetTextures[0] === backbufferColorTextureHandle) {
throw new Error(`${className} ${this.name}: the back buffer depth/stencil texture is the only depth texture allowed when the target is the back buffer color`);
}
const depthTextureDescription = this._frameGraph.textureManager.getTextureDescription(this.depthTexture);
if (!outputTextureDescription) {
outputTextureDescription = depthTextureDescription;
}
if (depthTextureDescription.options.samples !== outputTextureDescription.options.samples) {
throw new Error(`${className} ${this.name}: the depth texture "${depthTextureDescription.options.labels?.[0] ?? "noname"}" (${depthTextureDescription.options.samples} samples) and the output texture "${outputTextureDescription.options.labels?.[0] ?? "noname"}" (${outputTextureDescription.options.samples} samples) must have the same number of samples`);
}
if (depthTextureDescription.size.width !== outputTextureDescription.size.width || depthTextureDescription.size.height !== outputTextureDescription.size.height) {
throw new Error(`${className} ${this.name}: the depth texture (size: ${depthTextureDescription.size.width}x${depthTextureDescription.size.height}) and the target texture (size: ${outputTextureDescription.size.width}x${outputTextureDescription.size.height}) must have the same dimensions.`);
}
depthEnabled = true;
}
this._textureWidth = outputTextureDescription?.size.width ?? 1;
this._textureHeight = outputTextureDescription?.size.height ?? 1;
return depthEnabled;
}
_getTargetHandles() {
return Array.isArray(this.targetTexture) ? this.targetTexture : [this.targetTexture];
}
_prepareRendering(context, depthEnabled) {
context.setDepthStates(this.depthTest && depthEnabled, this.depthWrite && depthEnabled);
}
_setLightsForShadow() {
const lightsForShadow = new Set();
const shadowEnabled = new Map();
if (this.shadowGenerators) {
for (const shadowGeneratorTask of this.shadowGenerators) {
const shadowGenerator = shadowGeneratorTask.shadowGenerator;
const light = shadowGenerator.getLight();
if (light.isEnabled() && light.shadowEnabled) {
lightsForShadow.add(light);
if (FrameGraphCascadedShadowGeneratorTask.IsCascadedShadowGenerator(shadowGeneratorTask)) {
light._shadowGenerators.set(shadowGeneratorTask.camera, shadowGenerator);
}
else {
light._shadowGenerators.set(null, shadowGenerator);
}
}
}
}
this._renderer.onBeforeRenderObservable.remove(this._onBeforeRenderObservable);
this._onBeforeRenderObservable = this._renderer.onBeforeRenderObservable.add(() => {
for (let i = 0; i < this._scene.lights.length; i++) {
const light = this._scene.lights[i];
if (!light.setShadowProjectionMatrix) {
continue; // Ignore lights that cannot cast shadows
}
shadowEnabled.set(light, light.shadowEnabled);
light.shadowEnabled = !this.disableShadows && lightsForShadow.has(light);
}
});
this._renderer.onAfterRenderObservable.remove(this._onAfterRenderObservable);
this._onAfterRenderObservable = this._renderer.onAfterRenderObservable.add(() => {
for (let i = 0; i < this._scene.lights.length; i++) {
const light = this._scene.lights[i];
if (!light.setShadowProjectionMatrix) {
continue; // Ignore lights that cannot cast shadows
}
light.shadowEnabled = shadowEnabled.get(light);
}
});
}
}
//# sourceMappingURL=objectRendererTask.js.map