playcanvas
Version:
PlayCanvas WebGL game engine
298 lines (295 loc) • 12.2 kB
JavaScript
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 };