playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
113 lines (112 loc) • 3.56 kB
JavaScript
import { Color } from "../../core/math/color.js";
import { Texture } from "../../platform/graphics/texture.js";
import { BlendState } from "../../platform/graphics/blend-state.js";
import { RenderTarget } from "../../platform/graphics/render-target.js";
import { FramePass } from "../../platform/graphics/frame-pass.js";
import { FILTER_LINEAR, ADDRESS_CLAMP_TO_EDGE } from "../../platform/graphics/constants.js";
import { RenderPassDownsample } from "./render-pass-downsample.js";
import { RenderPassUpsample } from "./render-pass-upsample.js";
import { math } from "../../core/math/math.js";
class FramePassBloom extends FramePass {
bloomTexture;
blurLevel = 16;
bloomRenderTarget;
textureFormat;
renderTargets = [];
constructor(device, sourceTexture, format) {
super(device);
this._sourceTexture = sourceTexture;
this.textureFormat = format;
this.bloomRenderTarget = this.createRenderTarget(0);
this.bloomTexture = this.bloomRenderTarget.colorBuffer;
}
destroy() {
this.destroyRenderPasses();
this.destroyRenderTargets();
}
destroyRenderTargets(startIndex = 0) {
for (let i = startIndex; i < this.renderTargets.length; i++) {
const rt = this.renderTargets[i];
rt.destroyTextureBuffers();
rt.destroy();
}
this.renderTargets.length = 0;
}
destroyRenderPasses() {
for (let i = 0; i < this.beforePasses.length; i++) {
this.beforePasses[i].destroy();
}
this.beforePasses.length = 0;
}
createRenderTarget(index) {
return new RenderTarget({
depth: false,
colorBuffer: new Texture(this.device, {
name: `BloomTexture${index}`,
width: 1,
height: 1,
format: this.textureFormat,
mipmaps: false,
minFilter: FILTER_LINEAR,
magFilter: FILTER_LINEAR,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE
})
});
}
createRenderTargets(count) {
for (let i = 0; i < count; i++) {
const rt = i === 0 ? this.bloomRenderTarget : this.createRenderTarget(i);
this.renderTargets.push(rt);
}
}
// number of levels till hitting min size
calcMipLevels(width, height, minSize) {
const min = Math.min(width, height);
return Math.floor(Math.log2(min) - Math.log2(minSize));
}
createRenderPasses(numPasses) {
const device = this.device;
let passSourceTexture = this._sourceTexture;
for (let i = 0; i < numPasses; i++) {
const pass = new RenderPassDownsample(device, passSourceTexture);
const rt = this.renderTargets[i];
pass.init(rt, {
resizeSource: passSourceTexture,
scaleX: 0.5,
scaleY: 0.5
});
pass.setClearColor(Color.BLACK);
this.beforePasses.push(pass);
passSourceTexture = rt.colorBuffer;
}
passSourceTexture = this.renderTargets[numPasses - 1].colorBuffer;
for (let i = numPasses - 2; i >= 0; i--) {
const pass = new RenderPassUpsample(device, passSourceTexture);
const rt = this.renderTargets[i];
pass.init(rt);
pass.blendState = BlendState.ADDBLEND;
this.beforePasses.push(pass);
passSourceTexture = rt.colorBuffer;
}
}
onDisable() {
this.renderTargets[0]?.resize(1, 1);
this.destroyRenderPasses();
this.destroyRenderTargets(1);
}
frameUpdate() {
super.frameUpdate();
const maxNumPasses = this.calcMipLevels(this._sourceTexture.width, this._sourceTexture.height, 1);
const numPasses = math.clamp(maxNumPasses, 1, this.blurLevel);
if (this.renderTargets.length !== numPasses) {
this.destroyRenderPasses();
this.destroyRenderTargets(1);
this.createRenderTargets(numPasses);
this.createRenderPasses(numPasses);
}
}
}
export {
FramePassBloom
};