src/effects/BloomEffect.js
import { SRGBColorSpace, Uniform, WebGLRenderTarget } from "three";
import { Resolution } from "../core/Resolution.js";
import { BlendFunction } from "../enums/BlendFunction.js";
import { KernelSize } from "../enums/KernelSize.js";
import { KawaseBlurPass } from "../passes/KawaseBlurPass.js";
import { LuminancePass } from "../passes/LuminancePass.js";
import { MipmapBlurPass } from "../passes/MipmapBlurPass.js";
import { Effect } from "./Effect.js";
import fragmentShader from "./glsl/bloom.frag";
/**
* A bloom effect.
*
* Based on an article by Fabrice Piquet:
* https://www.froyok.fr/blog/2021-12-ue4-custom-bloom/
*/
export class BloomEffect extends Effect {
/**
* Constructs a new bloom effect.
*
* @param {Object} [options] - The options.
* @param {BlendFunction} [options.blendFunction=BlendFunction.SCREEN] - The blend function of this effect.
* @param {Number} [options.luminanceThreshold=0.9] - The luminance threshold. Raise this value to mask out darker elements in the scene.
* @param {Number} [options.luminanceSmoothing=0.025] - Controls the smoothness of the luminance threshold.
* @param {Boolean} [options.mipmapBlur=false] - Enables or disables mipmap blur.
* @param {Number} [options.intensity=1.0] - The bloom intensity.
* @param {Number} [options.radius=0.85] - The blur radius. Only applies to mipmap blur.
* @param {Number} [options.levels=8] - The amount of MIP levels. Only applies to mipmap blur.
* @param {KernelSize} [options.kernelSize=KernelSize.LARGE] - Deprecated. Use mipmapBlur instead.
* @param {Number} [options.resolutionScale=0.5] - Deprecated. Use mipmapBlur instead.
* @param {Number} [options.resolutionX=Resolution.AUTO_SIZE] - Deprecated. Use mipmapBlur instead.
* @param {Number} [options.resolutionY=Resolution.AUTO_SIZE] - Deprecated. Use mipmapBlur instead.
* @param {Number} [options.width=Resolution.AUTO_SIZE] - Deprecated. Use mipmapBlur instead.
* @param {Number} [options.height=Resolution.AUTO_SIZE] - Deprecated. Use mipmapBlur instead.
*/
constructor({
blendFunction = BlendFunction.SCREEN,
luminanceThreshold = 0.9,
luminanceSmoothing = 0.025,
mipmapBlur = false,
intensity = 1.0,
radius = 0.85,
levels = 8,
kernelSize = KernelSize.LARGE,
resolutionScale = 0.5,
width = Resolution.AUTO_SIZE,
height = Resolution.AUTO_SIZE,
resolutionX = width,
resolutionY = height
} = {}) {
super("BloomEffect", fragmentShader, {
blendFunction,
uniforms: new Map([
["map", new Uniform(null)],
["intensity", new Uniform(intensity)]
])
});
/**
* A render target.
*
* @type {WebGLRenderTarget}
* @private
*/
this.renderTarget = new WebGLRenderTarget(1, 1, { depthBuffer: false });
this.renderTarget.texture.name = "Bloom.Target";
/**
* A blur pass.
*
* @type {KawaseBlurPass}
* @readonly
* @deprecated Use mipmapBlurPass instead.
*/
this.blurPass = new KawaseBlurPass({ kernelSize });
/**
* A luminance pass.
*
* Disable to skip luminance filtering.
*
* @type {LuminancePass}
* @readonly
*/
this.luminancePass = new LuminancePass({ colorOutput: true });
this.luminanceMaterial.threshold = luminanceThreshold;
this.luminanceMaterial.smoothing = luminanceSmoothing;
/**
* A mipmap blur pass.
*
* @type {MipmapBlurPass}
* @readonly
*/
this.mipmapBlurPass = new MipmapBlurPass();
this.mipmapBlurPass.enabled = mipmapBlur;
this.mipmapBlurPass.radius = radius;
this.mipmapBlurPass.levels = levels;
this.uniforms.get("map").value = mipmapBlur ? this.mipmapBlurPass.texture : this.renderTarget.texture;
/**
* The render resolution.
*
* @type {Resolution}
* @readonly
*/
const resolution = this.resolution = new Resolution(this, resolutionX, resolutionY, resolutionScale);
resolution.addEventListener("change", (e) => this.setSize(resolution.baseWidth, resolution.baseHeight));
}
/**
* A texture that contains the intermediate result of this effect.
*
* @type {Texture}
*/
get texture() {
return this.mipmapBlurPass.enabled ? this.mipmapBlurPass.texture : this.renderTarget.texture;
}
/**
* Returns the generated bloom texture.
*
* @deprecated Use texture instead.
* @return {Texture} The texture.
*/
getTexture() {
return this.texture;
}
/**
* Returns the resolution settings.
*
* @deprecated Use resolution instead.
* @return {Resolution} The resolution.
*/
getResolution() {
return this.resolution;
}
/**
* Returns the blur pass.
*
* @deprecated
* @return {KawaseBlurPass} The blur pass.
*/
getBlurPass() {
return this.blurPass;
}
/**
* Returns the luminance pass.
*
* @deprecated Use luminancePass instead.
* @return {LuminancePass} The luminance pass.
*/
getLuminancePass() {
return this.luminancePass;
}
/**
* The luminance material.
*
* @type {LuminanceMaterial}
*/
get luminanceMaterial() {
return this.luminancePass.fullscreenMaterial;
}
/**
* Returns the luminance material.
*
* @deprecated Use luminanceMaterial instead.
* @return {LuminanceMaterial} The material.
*/
getLuminanceMaterial() {
return this.luminancePass.fullscreenMaterial;
}
/**
* The current width of the internal render targets.
*
* @type {Number}
* @deprecated
*/
get width() {
return this.resolution.width;
}
set width(value) {
this.resolution.preferredWidth = value;
}
/**
* The current height of the internal render targets.
*
* @type {Number}
* @deprecated
*/
get height() {
return this.resolution.height;
}
set height(value) {
this.resolution.preferredHeight = value;
}
/**
* Indicates whether dithering is enabled.
*
* @type {Boolean}
* @deprecated Use EffectPass.dithering instead.
*/
get dithering() {
return this.blurPass.dithering;
}
set dithering(value) {
this.blurPass.dithering = value;
}
/**
* The blur kernel size.
*
* @type {KernelSize}
* @deprecated
*/
get kernelSize() {
return this.blurPass.kernelSize;
}
set kernelSize(value) {
this.blurPass.kernelSize = value;
}
/**
* @type {Number}
* @deprecated
*/
get distinction() {
console.warn(this.name, "distinction was removed");
return 1.0;
}
set distinction(value) {
console.warn(this.name, "distinction was removed");
}
/**
* The bloom intensity.
*
* @type {Number}
*/
get intensity() {
return this.uniforms.get("intensity").value;
}
set intensity(value) {
this.uniforms.get("intensity").value = value;
}
/**
* The bloom intensity.
*
* @deprecated Use intensity instead.
* @return {Number} The intensity.
*/
getIntensity() {
return this.intensity;
}
/**
* Sets the bloom intensity.
*
* @deprecated Use intensity instead.
* @param {Number} value - The intensity.
*/
setIntensity(value) {
this.intensity = value;
}
/**
* Returns the current resolution scale.
*
* @return {Number} The resolution scale.
* @deprecated
*/
getResolutionScale() {
return this.resolution.scale;
}
/**
* Sets the resolution scale.
*
* @param {Number} scale - The new resolution scale.
* @deprecated
*/
setResolutionScale(scale) {
this.resolution.scale = scale;
}
/**
* Updates this effect.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} inputBuffer - A frame buffer that contains the result of the previous pass.
* @param {Number} [deltaTime] - The time between the last frame and the current one in seconds.
*/
update(renderer, inputBuffer, deltaTime) {
const renderTarget = this.renderTarget;
const luminancePass = this.luminancePass;
if(luminancePass.enabled) {
luminancePass.render(renderer, inputBuffer);
if(this.mipmapBlurPass.enabled) {
this.mipmapBlurPass.render(renderer, luminancePass.renderTarget);
} else {
this.blurPass.render(renderer, luminancePass.renderTarget, renderTarget);
}
} else {
if(this.mipmapBlurPass.enabled) {
this.mipmapBlurPass.render(renderer, inputBuffer);
} else {
this.blurPass.render(renderer, inputBuffer, renderTarget);
}
}
}
/**
* Updates the size of internal render targets.
*
* @param {Number} width - The width.
* @param {Number} height - The height.
*/
setSize(width, height) {
const resolution = this.resolution;
resolution.setBaseSize(width, height);
this.renderTarget.setSize(resolution.width, resolution.height);
this.blurPass.resolution.copy(resolution);
this.luminancePass.setSize(width, height);
this.mipmapBlurPass.setSize(width, height);
}
/**
* Performs initialization tasks.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {Boolean} alpha - Whether the renderer uses the alpha channel or not.
* @param {Number} frameBufferType - The type of the main frame buffers.
*/
initialize(renderer, alpha, frameBufferType) {
this.blurPass.initialize(renderer, alpha, frameBufferType);
this.luminancePass.initialize(renderer, alpha, frameBufferType);
this.mipmapBlurPass.initialize(renderer, alpha, frameBufferType);
if(frameBufferType !== undefined) {
this.renderTarget.texture.type = frameBufferType;
if(renderer !== null && renderer.outputColorSpace === SRGBColorSpace) {
this.renderTarget.texture.colorSpace = SRGBColorSpace;
}
}
}
}