playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
170 lines (169 loc) • 6.36 kB
JavaScript
import { TRACEID_RENDER_PASS_DETAIL } from "../../core/constants.js";
import { now } from "../../core/time.js";
import { Tracing } from "../../core/tracing.js";
import { BlendState } from "../../platform/graphics/blend-state.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 {
layerComposition;
scene;
renderer;
layerRenderSteps = [];
gammaCorrection;
toneMapping;
noDepthClear = false;
constructor(device, layerComposition, scene, renderer) {
super(device);
this.layerComposition = layerComposition;
this.scene = scene;
this.renderer = renderer;
}
get rendersAnything() {
return this.layerRenderSteps.length > 0;
}
addLayerRenderStep(layerRenderStep) {
this.layerRenderSteps.push(layerRenderStep);
}
addLayer(cameraComponent, layer, transparent, autoClears = true) {
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;
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;
}
renderLayerRenderStep(step, firstStep) {
const { renderer, scene } = this;
const device = renderer.device;
const { layer, transparent, cameraComponent } = step;
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;
}
}
}
export {
RenderPassForward
};