Home Reference Source

src/passes/KawaseBlurPass.js

import { SRGBColorSpace, UnsignedByteType, WebGLRenderTarget } from "three";
import { Resolution } from "../core/Resolution.js";
import { KernelSize } from "../enums/KernelSize.js";
import { CopyMaterial } from "../materials/CopyMaterial.js";
import { KawaseBlurMaterial } from "../materials/KawaseBlurMaterial.js";
import { Pass } from "./Pass.js";

/**
 * A Kawase blur pass.
 *
 * Provides better performance compared to {@link GaussianBlurPass} at larger kernel sizes.
 */

export class KawaseBlurPass extends Pass {

	/**
	 * Constructs a new Kawase blur pass.
	 *
	 * @param {Object} [options] - The options.
	 * @param {KernelSize} [options.kernelSize=KernelSize.MEDIUM] - The blur kernel size.
	 * @param {Number} [options.resolutionScale=0.5] - The resolution scale.
	 * @param {Number} [options.resolutionX=Resolution.AUTO_SIZE] - The horizontal resolution.
	 * @param {Number} [options.resolutionY=Resolution.AUTO_SIZE] - The vertical resolution.
	 * @param {Number} [options.width=Resolution.AUTO_SIZE] - Deprecated. Use resolutionX instead.
	 * @param {Number} [options.height=Resolution.AUTO_SIZE] - Deprecated. Use resolutionY instead.
	 */

	constructor({
		kernelSize = KernelSize.MEDIUM,
		resolutionScale = 0.5,
		width = Resolution.AUTO_SIZE,
		height = Resolution.AUTO_SIZE,
		resolutionX = width,
		resolutionY = height
	} = {}) {

		super("KawaseBlurPass");

		/**
		 * A render target.
		 *
		 * @type {WebGLRenderTarget}
		 * @private
		 */

		this.renderTargetA = new WebGLRenderTarget(1, 1, { depthBuffer: false });
		this.renderTargetA.texture.name = "Blur.Target.A";

		/**
		 * A render target.
		 *
		 * @type {WebGLRenderTarget}
		 * @private
		 */

		this.renderTargetB = this.renderTargetA.clone();
		this.renderTargetB.texture.name = "Blur.Target.B";

		/**
		 * 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));

		/**
		 * @see {@link blurMaterial}
		 * @type {KawaseBlurMaterial}
		 * @private
		 */

		this._blurMaterial = new KawaseBlurMaterial();
		this._blurMaterial.kernelSize = kernelSize;

		/**
		 * A copy material.
		 *
		 * @type {CopyMaterial}
		 * @readonly
		 */

		this.copyMaterial = new CopyMaterial();

	}

	/**
	 * Returns the resolution settings.
	 *
	 * @deprecated Use resolution instead.
	 * @return {Resolution} The resolution.
	 */

	getResolution() {

		return this.resolution;

	}

	/**
	 * The blur material.
	 *
	 * @type {KawaseBlurMaterial}
	 */

	get blurMaterial() {

		return this._blurMaterial;

	}

	/**
	 * The blur material.
	 *
	 * @type {KawaseBlurMaterial}
	 * @protected
	 */

	set blurMaterial(value) {

		this._blurMaterial = value;

	}

	/**
	 * Indicates whether dithering is enabled.
	 *
	 * @type {Boolean}
	 * @deprecated Use copyMaterial.dithering instead.
	 */

	get dithering() {

		return this.copyMaterial.dithering;

	}

	set dithering(value) {

		this.copyMaterial.dithering = value;

	}

	/**
	 * The kernel size.
	 *
	 * @type {KernelSize}
	 * @deprecated Use blurMaterial.kernelSize instead.
	 */

	get kernelSize() {

		return this.blurMaterial.kernelSize;

	}

	set kernelSize(value) {

		this.blurMaterial.kernelSize = value;

	}

	/**
	 * The current width of the internal render targets.
	 *
	 * @type {Number}
	 * @deprecated Use resolution.width instead.
	 */

	get width() {

		return this.resolution.width;

	}

	/**
	 * Sets the render width.
	 *
	 * @type {Number}
	 * @deprecated Use resolution.preferredWidth instead.
	 */

	set width(value) {

		this.resolution.preferredWidth = value;

	}

	/**
	 * The current height of the internal render targets.
	 *
	 * @type {Number}
	 * @deprecated Use resolution.height instead.
	 */

	get height() {

		return this.resolution.height;

	}

	/**
	 * Sets the render height.
	 *
	 * @type {Number}
	 * @deprecated Use resolution.preferredHeight instead.
	 */

	set height(value) {

		this.resolution.preferredHeight = value;

	}

	/**
	 * The current blur scale.
	 *
	 * @type {Number}
	 * @deprecated Use blurMaterial.scale instead.
	 */

	get scale() {

		return this.blurMaterial.scale;

	}

	set scale(value) {

		this.blurMaterial.scale = value;

	}

	/**
	 * Returns the current blur scale.
	 *
	 * @deprecated Use blurMaterial.scale instead.
	 * @return {Number} The scale.
	 */

	getScale() {

		return this.blurMaterial.scale;

	}

	/**
	 * Sets the blur scale.
	 *
	 * @deprecated Use blurMaterial.scale instead.
	 * @param {Number} value - The scale.
	 */

	setScale(value) {

		this.blurMaterial.scale = value;

	}

	/**
	 * Returns the kernel size.
	 *
	 * @deprecated Use blurMaterial.kernelSize instead.
	 * @return {KernelSize} The kernel size.
	 */

	getKernelSize() {

		return this.kernelSize;

	}

	/**
	 * Sets the kernel size.
	 *
	 * Larger kernels require more processing power but scale well with larger render resolutions.
	 *
	 * @deprecated Use blurMaterial.kernelSize instead.
	 * @param {KernelSize} value - The kernel size.
	 */

	setKernelSize(value) {

		this.kernelSize = value;

	}

	/**
	 * Returns the current resolution scale.
	 *
	 * @return {Number} The resolution scale.
	 * @deprecated Use resolution instead.
	 */

	getResolutionScale() {

		return this.resolution.scale;

	}

	/**
	 * Sets the resolution scale.
	 *
	 * @param {Number} scale - The new resolution scale.
	 * @deprecated Use resolution instead.
	 */

	setResolutionScale(scale) {

		this.resolution.scale = scale;

	}

	/**
	 * Renders the blur.
	 *
	 * @param {WebGLRenderer} renderer - The renderer.
	 * @param {WebGLRenderTarget} inputBuffer - A frame buffer that contains the result of the previous pass.
	 * @param {WebGLRenderTarget} outputBuffer - A frame buffer that serves as the output render target unless this pass renders to screen.
	 * @param {Number} [deltaTime] - The time between the last frame and the current one in seconds.
	 * @param {Boolean} [stencilTest] - Indicates whether a stencil mask is active.
	 */

	render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) {

		const scene = this.scene;
		const camera = this.camera;
		const renderTargetA = this.renderTargetA;
		const renderTargetB = this.renderTargetB;
		const material = this.blurMaterial;
		const kernelSequence = material.kernelSequence;

		let previousBuffer = inputBuffer;
		this.fullscreenMaterial = material;

		// Render the multi-pass blur.
		for(let i = 0, l = kernelSequence.length; i < l; ++i) {

			// Alternate between targets.
			const buffer = ((i & 1) === 0) ? renderTargetA : renderTargetB;
			material.kernel = kernelSequence[i];
			material.inputBuffer = previousBuffer.texture;
			renderer.setRenderTarget(buffer);
			renderer.render(scene, camera);
			previousBuffer = buffer;

		}

		// Copy the result to the output buffer.
		this.fullscreenMaterial = this.copyMaterial;
		this.copyMaterial.inputBuffer = previousBuffer.texture;
		renderer.setRenderTarget(this.renderToScreen ? null : outputBuffer);
		renderer.render(scene, camera);

	}

	/**
	 * Updates the size of this pass.
	 *
	 * @param {Number} width - The width.
	 * @param {Number} height - The height.
	 */

	setSize(width, height) {

		const resolution = this.resolution;
		resolution.setBaseSize(width, height);
		const w = resolution.width, h = resolution.height;

		this.renderTargetA.setSize(w, h);
		this.renderTargetB.setSize(w, h);

		// Optimization: 1 / (TexelSize * ResolutionScale) = FullResolution
		this.blurMaterial.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) {

		if(frameBufferType !== undefined) {

			this.renderTargetA.texture.type = frameBufferType;
			this.renderTargetB.texture.type = frameBufferType;

			if(frameBufferType !== UnsignedByteType) {

				this.blurMaterial.defines.FRAMEBUFFER_PRECISION_HIGH = "1";
				this.copyMaterial.defines.FRAMEBUFFER_PRECISION_HIGH = "1";

			} else if(renderer !== null && renderer.outputColorSpace === SRGBColorSpace) {

				this.renderTargetA.texture.colorSpace = SRGBColorSpace;
				this.renderTargetB.texture.colorSpace = SRGBColorSpace;

			}

		}

	}

	/**
	 * An auto sizing flag.
	 *
	 * @type {Number}
	 * @deprecated Use {@link Resolution.AUTO_SIZE} instead.
	 */

	static get AUTO_SIZE() {

		return Resolution.AUTO_SIZE;

	}

}