UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

254 lines (253 loc) 10.8 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { TRACEID_RENDER_PASS_DETAIL } from "../../core/constants.js"; import { Debug } from "../../core/debug.js"; import { now } from "../../core/time.js"; import { Tracing } from "../../core/tracing.js"; import { BlendState } from "../../platform/graphics/blend-state.js"; import { DebugGraphics } from "../../platform/graphics/debug-graphics.js"; import { RenderPass } from "../../platform/graphics/render-pass.js"; import { LayerRenderStep } from "./layer-render-step.js"; import { EVENT_POSTRENDER, EVENT_POSTRENDER_LAYER, EVENT_PRERENDER, EVENT_PRERENDER_LAYER, SHADER_FORWARD } from "../constants.js"; class RenderPassForward extends RenderPass { constructor(device, layerComposition, scene, renderer) { super(device); /** * @type {LayerComposition} */ __publicField(this, "layerComposition"); /** * @type {Scene} */ __publicField(this, "scene"); /** * @type {Renderer} */ __publicField(this, "renderer"); /** * @type {LayerRenderStep[]} */ __publicField(this, "layerRenderSteps", []); /** * The gamma correction setting for the render pass. If not set, the setting from the camera * is used. This allows render passes to override the camera's gamma correction during the * render pass. * * For HDR pipelines, scene render passes typically set this to {@link GAMMA_NONE} to output * linear values to an HDR render target, while subsequent passes (like UI) leave it undefined * to use the camera's default {@link GAMMA_SRGB} for correct display output. * * Can be: * - {@link GAMMA_NONE} * - {@link GAMMA_SRGB} * - `undefined` (uses camera setting) * * @type {number|undefined} */ __publicField(this, "gammaCorrection"); /** * The tone mapping setting for the render pass. In not set, setting from the camera is used. * * @type {number|undefined} */ __publicField(this, "toneMapping"); /** * If true, do not clear the depth buffer before rendering, as it was already primed by a depth * pre-pass. */ __publicField(this, "noDepthClear", false); this.layerComposition = layerComposition; this.scene = scene; this.renderer = renderer; } get rendersAnything() { return this.layerRenderSteps.length > 0; } addLayerRenderStep(layerRenderStep) { this.layerRenderSteps.push(layerRenderStep); } /** * Adds a layer to be rendered by this render pass. * * @param {CameraComponent} cameraComponent - The camera component that is used to render the * layers. * @param {Layer} layer - The layer to be added. * @param {boolean} transparent - True if the layer is transparent. * @param {boolean} autoClears - True if the render target should be cleared based on the camera * and layer clear flags. Defaults to true. */ addLayer(cameraComponent, layer, transparent, autoClears = true) { Debug.assert(cameraComponent); Debug.assert(this.renderTarget !== void 0, "Render pass needs to be initialized before adding layers"); Debug.assert(cameraComponent.camera.layersSet.has(layer.id), `Camera ${cameraComponent.entity.name} does not render layer ${layer.name}.`); const step = new LayerRenderStep(cameraComponent, layer, transparent, this.renderTarget); if (autoClears) { const firstStep = this.layerRenderSteps.length === 0; step.setupClears(firstStep ? cameraComponent : void 0, layer); } this.addLayerRenderStep(step); } updateDirectionalShadows() { const { renderer, layerRenderSteps } = this; for (let i = 0; i < layerRenderSteps.length; i++) { const step = layerRenderSteps[i]; const cameraComponent = step.cameraComponent; const camera = cameraComponent.camera; const shadowDirLights = this.renderer.culler.cameraDirShadowLights.get(camera); if (shadowDirLights) { for (let l = 0; l < shadowDirLights.length; l++) { const light = shadowDirLights[l]; if (renderer.culler.dirLightShadows.get(light) !== camera) { renderer.culler.dirLightShadows.set(light, camera); const shadowPass = renderer._shadowRendererDirectional.getLightRenderPass(light, camera); if (shadowPass) { this.beforePasses.push(shadowPass); } } } } } } // Collect before-passes from cameras whose first render step lives in this // RenderPassForward. Uses the existing firstCameraUse flag (set by LayerComposition) // to guarantee each camera's before-passes are scheduled exactly once, even when // multiple RenderPassForward instances reference the same camera (e.g. CameraFrame's // scenePass vs afterPass). Called after updateDirectionalShadows, so camera // before-passes execute after the directional shadow passes and can render into the // freshly updated shadow maps. updateCameraBeforePasses() { for (let i = 0; i < this.layerRenderSteps.length; i++) { const step = this.layerRenderSteps[i]; if (step.firstCameraUse) { const camera = step.cameraComponent?.camera; if (camera) { const { beforePasses } = camera; for (let j = 0; j < beforePasses.length; j++) { this.beforePasses.push(beforePasses[j]); } } } } } updateClears() { const step = this.layerRenderSteps[0]; if (step) { const cameraComponent = step.cameraComponent; const camera = cameraComponent.camera; const fullSizeClearRect = camera.fullSizeClearRect; this.setClearColor(fullSizeClearRect && step.clearColor ? camera.clearColor : void 0); this.setClearDepth(fullSizeClearRect && step.clearDepth && !this.noDepthClear ? camera.clearDepth : void 0); this.setClearStencil(fullSizeClearRect && step.clearStencil ? camera.clearStencil : void 0); } } frameUpdate() { super.frameUpdate(); this.updateDirectionalShadows(); this.updateCameraBeforePasses(); this.updateClears(); const { renderer, layerComposition, layerRenderSteps } = this; for (let i = 0; i < layerRenderSteps.length; i++) { const step = layerRenderSteps[i]; if (layerComposition.isEnabled(step.layer, step.transparent)) { renderer.culler.requestMeshInstanceCull(step.cameraComponent.camera, step.layer); } } } before() { const { layerRenderSteps } = this; for (let i = 0; i < layerRenderSteps.length; i++) { const step = layerRenderSteps[i]; if (step.firstCameraUse) { this.scene.fire(EVENT_PRERENDER, step.cameraComponent); } } } execute() { const { layerComposition, layerRenderSteps } = this; for (let i = 0; i < layerRenderSteps.length; i++) { const step = layerRenderSteps[i]; const layer = step.layer; Debug.call(() => { const compLayer = layerComposition.getLayerByName(layer.name); if (!compLayer) { Debug.warnOnce(`Layer ${layer.name} is not found in the scene and will not be rendered. Your render pass setup might need to be updated.`); } }); if (layerComposition.isEnabled(layer, step.transparent)) { this.renderLayerRenderStep(step, i === 0); } } } after() { for (let i = 0; i < this.layerRenderSteps.length; i++) { const step = this.layerRenderSteps[i]; if (step.lastCameraUse) { this.scene.fire(EVENT_POSTRENDER, step.cameraComponent); } } this.beforePasses.length = 0; } /** * @param {LayerRenderStep} step - The layer render step. * @param {boolean} firstStep - True if this is the first render step in the render pass. */ renderLayerRenderStep(step, firstStep) { const { renderer, scene } = this; const device = renderer.device; const { layer, transparent, cameraComponent } = step; DebugGraphics.pushGpuMarker(this.device, `Camera: ${cameraComponent ? cameraComponent.entity.name : "Unnamed"}, Layer: ${layer.name}(${transparent ? "TRANSP" : "OPAQUE"})`); const drawTime = now(); if (cameraComponent) { const originalGammaCorrection = cameraComponent.gammaCorrection; const originalToneMapping = cameraComponent.toneMapping; if (this.gammaCorrection !== void 0) cameraComponent.gammaCorrection = this.gammaCorrection; if (this.toneMapping !== void 0) cameraComponent.toneMapping = this.toneMapping; scene.fire(EVENT_PRERENDER_LAYER, cameraComponent, layer, transparent); const options = { lightClusters: step.lightClusters }; const shaderPass = cameraComponent.camera.shaderPassInfo?.index ?? SHADER_FORWARD; if (!firstStep || !cameraComponent.camera.fullSizeClearRect) { options.clearColor = step.clearColor; options.clearDepth = step.clearDepth; options.clearStencil = step.clearStencil; } const renderTarget = step.renderTarget ?? device.backBuffer; renderer.renderForwardLayer( cameraComponent.camera, renderTarget, layer, transparent, shaderPass, options ); device.setBlendState(BlendState.NOBLEND); device.setStencilState(null, null); device.setAlphaToCoverage(false); scene.fire(EVENT_POSTRENDER_LAYER, cameraComponent, layer, transparent); if (this.gammaCorrection !== void 0) cameraComponent.gammaCorrection = originalGammaCorrection; if (this.toneMapping !== void 0) cameraComponent.toneMapping = originalToneMapping; } DebugGraphics.popGpuMarker(this.device); layer._renderTime += now() - drawTime; } log(device, index) { super.log(device, index); if (Tracing.get(TRACEID_RENDER_PASS_DETAIL)) { const { layerComposition } = this; this.layerRenderSteps.forEach((step, index2) => { const layer = step.layer; const enabled = layer.enabled && layerComposition.isEnabled(layer, step.transparent); const cameraComponent = step.cameraComponent; Debug.trace( TRACEID_RENDER_PASS_DETAIL, ` ${index2}:${` Cam: ${cameraComponent ? cameraComponent.entity.name : "-"}`.padEnd(22, " ")}${` Lay: ${layer.name}`.padEnd(22, " ")}${step.transparent ? " TRANSP" : " OPAQUE"}${enabled ? " ENABLED" : " DISABLED"}${` Meshes: ${layer.meshInstances.length}`.padEnd(5, " ")}${step.firstCameraUse ? " CAM-FIRST" : ""}${step.lastCameraUse ? " CAM-LAST" : ""}` ); }); } } } export { RenderPassForward };