@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.
475 lines (474 loc) • 22.4 kB
JavaScript
import { backbufferColorTextureHandle, backbufferDepthStencilTextureHandle } from "../../frameGraphTypes.js";
import { FrameGraphTaskMultiRenderTarget } from "../../frameGraphTaskMultiRenderTarget.js";
import { ObjectRenderer } from "../../../Rendering/objectRenderer.js";
import { ThinDepthPeelingRenderer } from "../../../Rendering/thinDepthPeelingRenderer.js";
import { FrameGraphRenderTarget } from "../../frameGraphRenderTarget.js";
/**
* Task used to render objects to a texture.
*/
export class FrameGraphObjectRendererTask extends FrameGraphTaskMultiRenderTarget {
/**
* 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 depth only meshes should be rendered (default is true). Always subject to the renderMeshes property, though.
*/
get renderDepthOnlyMeshes() {
return this._renderDepthOnlyMeshes;
}
set renderDepthOnlyMeshes(value) {
if (value === this._renderDepthOnlyMeshes) {
return;
}
this._renderDepthOnlyMeshes = value;
this._renderer.renderDepthOnlyMeshes = value;
}
/**
* Defines if opaque meshes should be rendered (default is true). Always subject to the renderMeshes property, though.
*/
get renderOpaqueMeshes() {
return this._renderOpaqueMeshes;
}
set renderOpaqueMeshes(value) {
if (value === this._renderOpaqueMeshes) {
return;
}
this._renderOpaqueMeshes = value;
this._renderer.renderOpaqueMeshes = value;
}
/**
* Defines if alpha test meshes should be rendered (default is true). Always subject to the renderMeshes property, though.
*/
get renderAlphaTestMeshes() {
return this._renderAlphaTestMeshes;
}
set renderAlphaTestMeshes(value) {
if (value === this._renderAlphaTestMeshes) {
return;
}
this._renderAlphaTestMeshes = value;
this._renderer.renderAlphaTestMeshes = value;
}
/**
* Defines if transparent meshes should be rendered (default is true). Always subject to the renderMeshes property, though.
*/
get renderTransparentMeshes() {
return this._renderTransparentMeshes;
}
set renderTransparentMeshes(value) {
if (value === this._renderTransparentMeshes) {
return;
}
this._renderTransparentMeshes = value;
this._renderer.renderTransparentMeshes = value;
}
/**
* Defines if Order Independent Transparency should be used for transparent meshes (default is false).
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
get useOITForTransparentMeshes() {
return this._useOITForTransparentMeshes;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
set useOITForTransparentMeshes(value) {
if (value === this._useOITForTransparentMeshes) {
return;
}
this._useOITForTransparentMeshes = value;
this._renderer.customRenderTransparentSubMeshes = this._useOITForTransparentMeshes
? (transparentSubMeshes, renderingGroup) => this._renderTransparentMeshesWithOIT(transparentSubMeshes, renderingGroup)
: undefined;
this._oitRenderer.blendOutput = value && this._rtForOrderIndependentTransparency ? this._rtForOrderIndependentTransparency.renderTargetWrapper : null;
}
/**
* Defines the number of passes to use for Order Independent Transparency (default is 5).
*/
get oitPassCount() {
return this._oitRenderer.passCount;
}
set oitPassCount(value) {
if (value === this._oitRenderer.passCount) {
return;
}
this._oitRenderer.passCount = 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._renderDepthOnlyMeshes = true;
this._renderOpaqueMeshes = true;
this._renderAlphaTestMeshes = true;
this._renderTransparentMeshes = true;
// eslint-disable-next-line @typescript-eslint/naming-convention
this._useOITForTransparentMeshes = false;
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._oitRenderer = new ThinDepthPeelingRenderer(scene);
this._oitRenderer.useRenderPasses = true;
this.outputTexture = this._frameGraph.textureManager.createDanglingHandle();
this.outputDepthTexture = this._frameGraph.textureManager.createDanglingHandle();
}
isReady() {
this._renderer.renderList = this.objectList.meshes;
this._renderer.particleSystemList = this.objectList.particleSystems;
return this._renderer.isReadyForRendering(this._textureWidth, this._textureHeight);
}
getClassName() {
return "FrameGraphObjectRendererTask";
}
record(skipCreationOfDisabledPasses = false, additionalExecute) {
this._checkParameters();
const targetTextures = this._getTargetHandles();
const depthEnabled = this._checkTextureCompatibility(targetTextures);
this._resolveDanglingHandles(targetTextures);
this._setLightsForShadow();
this._rtForOrderIndependentTransparency?.dispose();
const pass = this._frameGraph.addRenderPass(this.name);
pass.setRenderTarget(targetTextures);
pass.setRenderTargetDepth(this.depthTexture);
pass.setInitializeFunc(() => {
// Note: we don't use pass.frameGraphRenderTarget.renderTargetWrapper for OIT but recreate our own render target wrapper because this.targetTexture may not be the first one of the wrapper in the geometry renderer task case
this._rtForOrderIndependentTransparency = new FrameGraphRenderTarget(this.name + "_oitRT", this._frameGraph.textureManager, this.targetTexture, this.depthTexture);
});
pass.setExecuteFunc((context) => {
this._renderer.renderList = this.objectList.meshes;
this._renderer.particleSystemList = this.objectList.particleSystems;
this._updateLayerAndFaceIndices(pass);
const renderTargetWrapper = pass.frameGraphRenderTarget.renderTargetWrapper;
if (renderTargetWrapper) {
renderTargetWrapper.resolveMSAAColors = this.resolveMSAAColors;
renderTargetWrapper.resolveMSAADepth = this.resolveMSAADepth;
}
if (this._useOITForTransparentMeshes && this._oitRenderer.blendOutput !== this._rtForOrderIndependentTransparency.renderTargetWrapper) {
this._oitRenderer.blendOutput = this._rtForOrderIndependentTransparency.renderTargetWrapper;
}
// 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;
}
const attachments = this._prepareRendering(context, depthEnabled);
const currentOITRenderer = this._scene._depthPeelingRenderer;
this._scene._depthPeelingRenderer = this._oitRenderer;
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.pushDebugGroup(`Render objects for camera rig ${index} "${rigCamera.name}"`);
context.bindRenderTarget(pass.frameGraphRenderTarget);
attachments && context.bindAttachments(attachments);
context.render(this._renderer, this._textureWidth, this._textureHeight, true);
context.popDebugGroup();
rigCamera.rigParent = camera;
}
this._renderer.activeCamera = camera;
}
else {
context.pushDebugGroup(`Render objects for camera "${this._renderer.activeCamera?.name ?? "undefined"}"`);
context.bindRenderTarget(pass.frameGraphRenderTarget);
attachments && context.bindAttachments(attachments);
context.render(this._renderer, this._textureWidth, this._textureHeight, true);
context.popDebugGroup();
}
additionalExecute?.(context);
this._scene._depthPeelingRenderer = currentOITRenderer;
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();
}
this._oitRenderer.dispose();
this._rtForOrderIndependentTransparency?.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);
return null;
}
_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 (shadowGeneratorTask.getClassName() === "FrameGraphCascadedShadowGeneratorTask") {
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);
}
});
}
// eslint-disable-next-line @typescript-eslint/naming-convention
_renderTransparentMeshesWithOIT(transparentSubMeshes, renderingGroup) {
const saveOIT = this._scene._useOrderIndependentTransparency;
this._scene._useOrderIndependentTransparency = true;
const excludedMeshes = this._oitRenderer.render(transparentSubMeshes);
if (excludedMeshes.length) {
// Render leftover meshes that could not be processed by depth peeling
renderingGroup._renderTransparent(excludedMeshes);
}
this._scene._useOrderIndependentTransparency = saveOIT;
}
}
//# sourceMappingURL=objectRendererTask.js.map