UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

298 lines (295 loc) 12.2 kB
import { GAMMA_NONE, TONEMAP_NONE, LAYERID_SKYBOX, LAYERID_IMMEDIATE } from '../../scene/constants.js'; import { ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js'; import { Texture } from '../../platform/graphics/texture.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; import { RenderPassColorGrab } from '../../scene/graphics/render-pass-color-grab.js'; import { RenderPassForward } from '../../scene/renderer/render-pass-forward.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; import { RenderPassBloom } from './render-pass-bloom.js'; import { RenderPassCompose } from './render-pass-compose.js'; import { RenderPassTAA } from './render-pass-taa.js'; import { RenderPassDof } from './render-pass-dof.js'; import { RenderPassPrepass } from './render-pass-prepass.js'; import { RenderPassSsao } from './render-pass-ssao.js'; import { SSAOTYPE_NONE, SSAOTYPE_LIGHTING, SSAOTYPE_COMBINE } from './constants.js'; import { RenderPassDownsample } from './render-pass-downsample.js'; import { Color } from '../../core/math/color.js'; class CameraFrameOptions { constructor(){ this.stencil = false; this.samples = 1; this.sceneColorMap = false; this.lastGrabLayerId = LAYERID_SKYBOX; this.lastGrabLayerIsTransparent = false; this.lastSceneLayerId = LAYERID_IMMEDIATE; this.lastSceneLayerIsTransparent = true; this.taaEnabled = false; this.bloomEnabled = false; this.ssaoType = SSAOTYPE_NONE; this.ssaoBlurEnabled = true; this.prepassEnabled = false; this.dofEnabled = false; this.dofNearBlur = false; this.dofHighQuality = true; } } const _defaultOptions = new CameraFrameOptions(); class RenderPassCameraFrame extends RenderPass { constructor(app, cameraFrame, cameraComponent, options = {}){ super(app.graphicsDevice), this._renderTargetScale = 1, this.layersDirty = false, this.rt = null; this.app = app; this.cameraComponent = cameraComponent; this.cameraFrame = cameraFrame; this.options = this.sanitizeOptions(options); this.setupRenderPasses(this.options); } destroy() { this.reset(); } reset() { this.sceneTexture = null; this.sceneTextureHalf = null; if (this.rt) { this.rt.destroyTextureBuffers(); this.rt.destroy(); this.rt = null; } if (this.rtHalf) { this.rtHalf.destroyTextureBuffers(); this.rtHalf.destroy(); this.rtHalf = null; } this.beforePasses.forEach((pass)=>pass.destroy()); this.beforePasses.length = 0; this.prePass = null; this.scenePass = null; this.scenePassTransparent = null; this.colorGrabPass = null; this.composePass = null; this.bloomPass = null; this.ssaoPass = null; this.taaPass = null; this.afterPass = null; this.scenePassHalf = null; this.dofPass = null; } sanitizeOptions(options) { options = Object.assign({}, _defaultOptions, options); if (options.taaEnabled || options.ssaoType !== SSAOTYPE_NONE || options.dofEnabled) { options.prepassEnabled = true; } return options; } set renderTargetScale(value) { this._renderTargetScale = value; if (this.scenePass) { this.scenePass.scaleX = value; this.scenePass.scaleY = value; } } get renderTargetScale() { return this._renderTargetScale; } needsReset(options) { const currentOptions = this.options; const arraysNotEqual = (arr1, arr2)=>arr1 !== arr2 && (!(Array.isArray(arr1) && Array.isArray(arr2)) || arr1.length !== arr2.length || !arr1.every((value, index)=>value === arr2[index])); return options.ssaoType !== currentOptions.ssaoType || options.ssaoBlurEnabled !== currentOptions.ssaoBlurEnabled || options.taaEnabled !== currentOptions.taaEnabled || options.samples !== currentOptions.samples || options.stencil !== currentOptions.stencil || options.bloomEnabled !== currentOptions.bloomEnabled || options.prepassEnabled !== currentOptions.prepassEnabled || options.sceneColorMap !== currentOptions.sceneColorMap || options.dofEnabled !== currentOptions.dofEnabled || options.dofNearBlur !== currentOptions.dofNearBlur || options.dofHighQuality !== currentOptions.dofHighQuality || arraysNotEqual(options.formats, currentOptions.formats); } update(options) { options = this.sanitizeOptions(options); if (this.needsReset(options) || this.layersDirty) { this.layersDirty = false; this.reset(); } this.options = options; if (!this.sceneTexture) { this.setupRenderPasses(this.options); } } createRenderTarget(name, depth, stencil, samples, flipY) { const texture = new Texture(this.device, { name: name, width: 4, height: 4, format: this.hdrFormat, mipmaps: false, minFilter: FILTER_LINEAR, magFilter: FILTER_LINEAR, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE }); return new RenderTarget({ colorBuffer: texture, depth: depth, stencil: stencil, samples: samples, flipY: flipY }); } setupRenderPasses(options) { const { device } = this; const cameraComponent = this.cameraComponent; const targetRenderTarget = cameraComponent.renderTarget; this.hdrFormat = device.getRenderableHdrFormat(options.formats, true, options.samples) || PIXELFORMAT_RGBA8; this._bloomEnabled = options.bloomEnabled && this.hdrFormat !== PIXELFORMAT_RGBA8; this._sceneHalfEnabled = this._bloomEnabled || options.dofEnabled; cameraComponent.shaderParams.ssaoEnabled = options.ssaoType === SSAOTYPE_LIGHTING; const flipY = !!targetRenderTarget?.flipY; this.rt = this.createRenderTarget('SceneColor', true, options.stencil, options.samples, flipY); this.sceneTexture = this.rt.colorBuffer; if (this._sceneHalfEnabled) { this.rtHalf = this.createRenderTarget('SceneColorHalf', false, false, 1, flipY); this.sceneTextureHalf = this.rtHalf.colorBuffer; } this.sceneOptions = { resizeSource: targetRenderTarget, scaleX: this.renderTargetScale, scaleY: this.renderTargetScale }; this.createPasses(options); const allPasses = this.collectPasses(); this.beforePasses = allPasses.filter((element)=>element !== undefined && element !== null); } collectPasses() { return [ this.prePass, this.ssaoPass, this.scenePass, this.colorGrabPass, this.scenePassTransparent, this.taaPass, this.scenePassHalf, this.bloomPass, this.dofPass, this.composePass, this.afterPass ]; } createPasses(options) { this.setupScenePrepass(options); this.setupSsaoPass(options); const scenePassesInfo = this.setupScenePass(options); const sceneTextureWithTaa = this.setupTaaPass(options); this.setupSceneHalfPass(options, sceneTextureWithTaa); this.setupBloomPass(options, this.sceneTextureHalf); this.setupDofPass(options, this.sceneTexture, this.sceneTextureHalf); this.setupComposePass(options); this.setupAfterPass(options, scenePassesInfo); } setupScenePrepass(options) { if (options.prepassEnabled) { const { app, device, cameraComponent } = this; const { scene, renderer } = app; this.prePass = new RenderPassPrepass(device, scene, renderer, cameraComponent, this.sceneOptions); } } setupScenePassSettings(pass) { pass.gammaCorrection = GAMMA_NONE; pass.toneMapping = TONEMAP_NONE; } setupScenePass(options) { const { app, device, cameraComponent } = this; const { scene, renderer } = app; const composition = scene.layers; this.scenePass = new RenderPassForward(device, composition, scene, renderer); this.setupScenePassSettings(this.scenePass); this.scenePass.init(this.rt, this.sceneOptions); const lastLayerId = options.sceneColorMap ? options.lastGrabLayerId : options.lastSceneLayerId; const lastLayerIsTransparent = options.sceneColorMap ? options.lastGrabLayerIsTransparent : options.lastSceneLayerIsTransparent; const ret = { lastAddedIndex: 0, clearRenderTarget: true }; ret.lastAddedIndex = this.scenePass.addLayers(composition, cameraComponent, ret.lastAddedIndex, ret.clearRenderTarget, lastLayerId, lastLayerIsTransparent); ret.clearRenderTarget = false; if (options.sceneColorMap) { this.colorGrabPass = new RenderPassColorGrab(device); this.colorGrabPass.source = this.rt; this.scenePassTransparent = new RenderPassForward(device, composition, scene, renderer); this.setupScenePassSettings(this.scenePassTransparent); this.scenePassTransparent.init(this.rt); ret.lastAddedIndex = this.scenePassTransparent.addLayers(composition, cameraComponent, ret.lastAddedIndex, ret.clearRenderTarget, options.lastSceneLayerId, options.lastSceneLayerIsTransparent); if (!this.scenePassTransparent.rendersAnything) { this.scenePassTransparent.destroy(); this.scenePassTransparent = null; } if (this.scenePassTransparent) { if (options.prepassEnabled) { this.scenePassTransparent.depthStencilOps.storeDepth = true; } } } return ret; } setupSsaoPass(options) { const { ssaoBlurEnabled, ssaoType } = options; const { device, cameraComponent } = this; if (ssaoType !== SSAOTYPE_NONE) { this.ssaoPass = new RenderPassSsao(device, this.sceneTexture, cameraComponent, ssaoBlurEnabled); } } setupSceneHalfPass(options, sourceTexture) { if (this._sceneHalfEnabled) { this.scenePassHalf = new RenderPassDownsample(this.device, this.sceneTexture, { boxFilter: true, removeInvalid: true }); this.scenePassHalf.name = 'RenderPassSceneHalf'; this.scenePassHalf.init(this.rtHalf, { resizeSource: sourceTexture, scaleX: 0.5, scaleY: 0.5 }); this.scenePassHalf.setClearColor(Color.BLACK); } } setupBloomPass(options, inputTexture) { if (this._bloomEnabled) { this.bloomPass = new RenderPassBloom(this.device, inputTexture, this.hdrFormat); } } setupDofPass(options, inputTexture, inputTextureHalf) { if (options.dofEnabled) { this.dofPass = new RenderPassDof(this.device, this.cameraComponent, inputTexture, inputTextureHalf, options.dofHighQuality, options.dofNearBlur); } } setupTaaPass(options) { let textureWithTaa = this.sceneTexture; if (options.taaEnabled) { this.taaPass = new RenderPassTAA(this.device, this.sceneTexture, this.cameraComponent); textureWithTaa = this.taaPass.historyTexture; } return textureWithTaa; } setupComposePass(options) { this.composePass = new RenderPassCompose(this.device); this.composePass.bloomTexture = this.bloomPass?.bloomTexture; this.composePass.taaEnabled = options.taaEnabled; this.composePass.cocTexture = this.dofPass?.cocTexture; this.composePass.blurTexture = this.dofPass?.blurTexture; this.composePass.blurTextureUpscale = !this.dofPass?.highQuality; const cameraComponent = this.cameraComponent; const targetRenderTarget = cameraComponent.renderTarget; this.composePass.init(targetRenderTarget); this.composePass.ssaoTexture = options.ssaoType === SSAOTYPE_COMBINE ? this.ssaoPass.ssaoTexture : null; } setupAfterPass(options, scenePassesInfo) { const { app, cameraComponent } = this; const { scene, renderer } = app; const composition = scene.layers; const targetRenderTarget = cameraComponent.renderTarget; this.afterPass = new RenderPassForward(this.device, composition, scene, renderer); this.afterPass.init(targetRenderTarget); this.afterPass.addLayers(composition, cameraComponent, scenePassesInfo.lastAddedIndex, scenePassesInfo.clearRenderTarget); } frameUpdate() { if (this.layersDirty) { this.cameraFrame.update(); } super.frameUpdate(); const sceneTexture = this.taaPass?.update() ?? this.rt.colorBuffer; this.composePass.sceneTexture = sceneTexture; this.scenePassHalf?.setSourceTexture(sceneTexture); } } export { CameraFrameOptions, RenderPassCameraFrame };