src/passes/BlurPass.js
import { LinearFilter, RGBFormat, Vector2, WebGLRenderTarget } from "three";
import { ConvolutionMaterial, KernelSize } from "../materials";
import { Pass } from "./Pass.js";
/**
* An efficient, incremental blur pass.
*
* Note: This pass allows the input and output buffer to be the same.
*/
export class BlurPass extends Pass {
/**
* Constructs a new blur pass.
*
* @param {Object} [options] - The options.
* @param {Number} [options.resolutionScale=0.5] - The render texture resolution scale, relative to the screen render size.
* @param {Number} [options.kernelSize=KernelSize.LARGE] - The blur kernel size.
*/
constructor(options = {}) {
super("BlurPass");
/**
* A render target.
*
* @type {WebGLRenderTarget}
* @private
*/
this.renderTargetX = new WebGLRenderTarget(1, 1, {
minFilter: LinearFilter,
magFilter: LinearFilter,
stencilBuffer: false,
depthBuffer: false
});
this.renderTargetX.texture.name = "Blur.TargetX";
this.renderTargetX.texture.generateMipmaps = false;
/**
* A second render target.
*
* @type {WebGLRenderTarget}
* @private
*/
this.renderTargetY = this.renderTargetX.clone();
this.renderTargetY.texture.name = "Blur.TargetY";
/**
* The original resolution.
*
* @type {Vector2}
* @private
*/
this.resolution = new Vector2();
/**
* The current resolution scale.
*
* @type {Number}
* @private
*/
this.resolutionScale = (options.resolutionScale !== undefined) ? options.resolutionScale : 0.5;
/**
* A convolution shader material.
*
* @type {ConvolutionMaterial}
* @private
*/
this.convolutionMaterial = new ConvolutionMaterial();
/**
* A convolution shader material that uses dithering.
*
* @type {ConvolutionMaterial}
* @private
*/
this.ditheredConvolutionMaterial = new ConvolutionMaterial();
this.ditheredConvolutionMaterial.dithering = true;
/**
* Whether the blurred result should also be dithered using noise.
*
* @type {Boolean}
*/
this.dithering = false;
this.kernelSize = options.kernelSize;
}
/**
* The absolute width of the internal render targets.
*
* @type {Number}
*/
get width() {
return this.renderTargetX.width;
}
/**
* The absolute height of the internal render targets.
*
* @type {Number}
*/
get height() {
return this.renderTargetX.height;
}
/**
* The kernel size.
*
* @type {KernelSize}
*/
get kernelSize() {
return this.convolutionMaterial.kernelSize;
}
/**
* @type {KernelSize}
*/
set kernelSize(value = KernelSize.LARGE) {
this.convolutionMaterial.kernelSize = value;
this.ditheredConvolutionMaterial.kernelSize = value;
}
/**
* Returns the current resolution scale.
*
* @return {Number} The resolution scale.
*/
getResolutionScale() {
return this.resolutionScale;
}
/**
* Sets the resolution scale.
*
* @param {Number} scale - The new resolution scale.
*/
setResolutionScale(scale) {
this.resolutionScale = scale;
this.setSize(this.resolution.x, this.resolution.y);
}
/**
* Blurs the input buffer and writes the result to the output buffer. The
* input buffer remains intact, unless its also the output buffer.
*
* @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} [delta] - 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, delta, stencilTest) {
const scene = this.scene;
const camera = this.camera;
const renderTargetX = this.renderTargetX;
const renderTargetY = this.renderTargetY;
let material = this.convolutionMaterial;
let uniforms = material.uniforms;
const kernel = material.getKernel();
let lastRT = inputBuffer;
let destRT;
let i, l;
this.setFullscreenMaterial(material);
// Apply the multi-pass blur.
for(i = 0, l = kernel.length - 1; i < l; ++i) {
// Alternate between targets.
destRT = ((i % 2) === 0) ? renderTargetX : renderTargetY;
uniforms.kernel.value = kernel[i];
uniforms.inputBuffer.value = lastRT.texture;
renderer.render(scene, camera, destRT);
lastRT = destRT;
}
if(this.dithering) {
material = this.ditheredConvolutionMaterial;
uniforms = material.uniforms;
this.setFullscreenMaterial(material);
}
uniforms.kernel.value = kernel[i];
uniforms.inputBuffer.value = lastRT.texture;
renderer.render(scene, camera, this.renderToScreen ? null : outputBuffer);
}
/**
* Updates the size of this pass.
*
* @param {Number} width - The width.
* @param {Number} height - The height.
*/
setSize(width, height) {
this.resolution.set(width, height);
width = Math.max(1, Math.floor(width * this.resolutionScale));
height = Math.max(1, Math.floor(height * this.resolutionScale));
this.renderTargetX.setSize(width, height);
this.renderTargetY.setSize(width, height);
this.convolutionMaterial.setTexelSize(1.0 / width, 1.0 / height);
this.ditheredConvolutionMaterial.setTexelSize(1.0 / width, 1.0 / height);
}
/**
* Performs initialization tasks.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {Boolean} alpha - Whether the renderer uses the alpha channel or not.
*/
initialize(renderer, alpha) {
if(!alpha) {
this.renderTargetX.texture.format = RGBFormat;
this.renderTargetY.texture.format = RGBFormat;
}
}
}