Home Reference Source

src/effects/Effect.js

import {
	BasicDepthPacking,
	EventDispatcher,
	LinearSRGBColorSpace,
	Material,
	NoColorSpace,
	Texture,
	WebGLRenderTarget
} from "three";

import { BlendFunction } from "../enums/BlendFunction.js";
import { EffectAttribute } from "../enums/EffectAttribute.js";
import { Pass } from "../passes/Pass.js";
import { BlendMode } from "./blending/BlendMode.js";

/**
 * An abstract effect.
 *
 * Effects can be combined using the {@link EffectPass}.
 *
 * @implements {Initializable}
 * @implements {Resizable}
 * @implements {Disposable}
 */

export class Effect extends EventDispatcher {

	/**
	 * Constructs a new effect.
	 *
	 * @param {String} name - The name of this effect. Doesn't have to be unique.
	 * @param {String} fragmentShader - The fragment shader. This shader is required.
	 * @param {Object} [options] - Additional options.
	 * @param {EffectAttribute} [options.attributes=EffectAttribute.NONE] - The effect attributes that determine the execution priority and resource requirements.
	 * @param {BlendFunction} [options.blendFunction=BlendFunction.NORMAL] - The blend function of this effect.
	 * @param {Map<String, String>} [options.defines] - Custom preprocessor macro definitions. Keys are names and values are code.
	 * @param {Map<String, Uniform>} [options.uniforms] - Custom shader uniforms. Keys are names and values are uniforms.
	 * @param {Set<WebGLExtension>} [options.extensions] - WebGL extensions.
	 * @param {String} [options.vertexShader=null] - The vertex shader. Most effects don't need one.
	 */

	constructor(name, fragmentShader, {
		attributes = EffectAttribute.NONE,
		blendFunction = BlendFunction.NORMAL,
		defines = new Map(),
		uniforms = new Map(),
		extensions = null,
		vertexShader = null
	} = {}) {

		super();

		/**
		 * The name of this effect.
		 *
		 * @type {String}
		 */

		this.name = name;

		/**
		 * The renderer.
		 *
		 * @type {WebGLRenderer}
		 * @protected
		 * @deprecated
		 */

		this.renderer = null;

		/**
		 * The effect attributes.
		 *
		 * @type {EffectAttribute}
		 * @private
		 */

		this.attributes = attributes;

		/**
		 * The fragment shader.
		 *
		 * @type {String}
		 * @private
		 */

		this.fragmentShader = fragmentShader;

		/**
		 * The vertex shader.
		 *
		 * @type {String}
		 * @private
		 */

		this.vertexShader = vertexShader;

		/**
		 * Preprocessor macro definitions.
		 *
		 * Call {@link Effect.setChanged} after changing macro definitions.
		 *
		 * @type {Map<String, String>}
		 * @readonly
		 */

		this.defines = defines;

		/**
		 * Shader uniforms.
		 *
		 * Call {@link Effect.setChanged} after adding or removing uniforms.
		 *
		 * @type {Map<String, Uniform>}
		 * @readonly
		 */

		this.uniforms = uniforms;

		/**
		 * WebGL extensions that are required by this effect.
		 *
		 * Call {@link Effect.setChanged} after adding or removing extensions.
		 *
		 * @type {Set<WebGLExtension>}
		 * @readonly
		 */

		this.extensions = extensions;

		/**
		 * The blend mode of this effect.
		 *
		 * @type {BlendMode}
		 * @readonly
		 */

		this.blendMode = new BlendMode(blendFunction);
		this.blendMode.addEventListener("change", (event) => this.setChanged());

		/**
		 * Backing data for {@link inputColorSpace}.
		 *
		 * @type {ColorSpace}
		 * @private
		 */

		this._inputColorSpace = LinearSRGBColorSpace;

		/**
		 * Backing data for {@link outputColorSpace}.
		 *
		 * @type {ColorSpace}
		 * @private
		 */

		this._outputColorSpace = NoColorSpace;

	}

	/**
	 * The input color space.
	 *
	 * @type {ColorSpace}
	 * @experimental
	 */

	get inputColorSpace() {

		return this._inputColorSpace;

	}

	/**
	 * @type {ColorSpace}
	 * @protected
	 * @experimental
	 */

	set inputColorSpace(value) {

		this._inputColorSpace = value;
		this.setChanged();

	}

	/**
	 * The output color space.
	 *
	 * Should only be changed if this effect converts the input colors to a different color space.
	 *
	 * @type {ColorSpace}
	 * @experimental
	 */

	get outputColorSpace() {

		return this._outputColorSpace;

	}

	/**
	 * @type {ColorSpace}
	 * @protected
	 * @experimental
	 */

	set outputColorSpace(value) {

		this._outputColorSpace = value;
		this.setChanged();

	}

	/**
	 * Sets the main scene.
	 *
	 * @type {Scene}
	 */

	set mainScene(value) {}

	/**
	 * Sets the main camera.
	 *
	 * @type {Camera}
	 */

	set mainCamera(value) {}

	/**
	 * Returns the name of this effect.
	 *
	 * @deprecated Use name instead.
	 * @return {String} The name.
	 */

	getName() {

		return this.name;

	}

	/**
	 * Sets the renderer.
	 *
	 * @deprecated
	 * @param {WebGLRenderer} renderer - The renderer.
	 */

	setRenderer(renderer) {

		this.renderer = renderer;

	}

	/**
	 * Returns the preprocessor macro definitions.
	 *
	 * @deprecated Use defines instead.
	 * @return {Map<String, String>} The extensions.
	 */

	getDefines() {

		return this.defines;

	}

	/**
	 * Returns the uniforms of this effect.
	 *
	 * @deprecated Use uniforms instead.
	 * @return {Map<String, Uniform>} The extensions.
	 */

	getUniforms() {

		return this.uniforms;

	}

	/**
	 * Returns the WebGL extensions that are required by this effect.
	 *
	 * @deprecated Use extensions instead.
	 * @return {Set<WebGLExtension>} The extensions.
	 */

	getExtensions() {

		return this.extensions;

	}

	/**
	 * Returns the blend mode.
	 *
	 * The result of this effect will be blended with the result of the previous effect using this blend mode.
	 *
	 * @deprecated Use blendMode instead.
	 * @return {BlendMode} The blend mode.
	 */

	getBlendMode() {

		return this.blendMode;

	}

	/**
	 * Returns the effect attributes.
	 *
	 * @return {EffectAttribute} The attributes.
	 */

	getAttributes() {

		return this.attributes;

	}

	/**
	 * Sets the effect attributes.
	 *
	 * Effects that have the same attributes will be executed in the order in which they were registered. Some attributes
	 * imply a higher priority.
	 *
	 * @protected
	 * @param {EffectAttribute} attributes - The attributes.
	 */

	setAttributes(attributes) {

		this.attributes = attributes;
		this.setChanged();

	}

	/**
	 * Returns the fragment shader.
	 *
	 * @return {String} The fragment shader.
	 */

	getFragmentShader() {

		return this.fragmentShader;

	}

	/**
	 * Sets the fragment shader.
	 *
	 * @protected
	 * @param {String} fragmentShader - The fragment shader.
	 */

	setFragmentShader(fragmentShader) {

		this.fragmentShader = fragmentShader;
		this.setChanged();

	}

	/**
	 * Returns the vertex shader.
	 *
	 * @return {String} The vertex shader.
	 */

	getVertexShader() {

		return this.vertexShader;

	}

	/**
	 * Sets the vertex shader.
	 *
	 * @protected
	 * @param {String} vertexShader - The vertex shader.
	 */

	setVertexShader(vertexShader) {

		this.vertexShader = vertexShader;
		this.setChanged();

	}

	/**
	 * Informs the associated {@link EffectPass} that this effect requires a shader recompilation.
	 *
	 * Should be called after changing macros or extensions and after adding/removing uniforms.
	 *
	 * @protected
	 */

	setChanged() {

		this.dispatchEvent({ type: "change" });

	}

	/**
	 * Sets the depth texture.
	 *
	 * You may override this method if your effect requires direct access to the depth texture that is bound to the
	 * associated {@link EffectPass}.
	 *
	 * @param {Texture} depthTexture - A depth texture.
	 * @param {DepthPackingStrategies} [depthPacking=BasicDepthPacking] - The depth packing.
	 */

	setDepthTexture(depthTexture, depthPacking = BasicDepthPacking) {}

	/**
	 * Updates this effect by performing supporting operations.
	 *
	 * This method is called by the {@link EffectPass} right before the main fullscreen render operation, even if the
	 * blend function is set to `SKIP`.
	 *
	 * You may override this method if you need to update custom uniforms or render additional off-screen textures.
	 *
	 * @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) {}

	/**
	 * Updates the size of this effect.
	 *
	 * You may override this method if you want to be informed about the size of the backbuffer/canvas.
	 * This method is called before {@link initialize} and every time the size of the {@link EffectComposer} changes.
	 *
	 * @param {Number} width - The width.
	 * @param {Number} height - The height.
	 */

	setSize(width, height) {}

	/**
	 * Performs initialization tasks.
	 *
	 * This method is called when the associated {@link EffectPass} is added to an {@link EffectComposer}.
	 *
	 * @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.
	 * @example if(!alpha && frameBufferType === UnsignedByteType) { this.myRenderTarget.texture.format = RGBFormat; }
	 */

	initialize(renderer, alpha, frameBufferType) {}

	/**
	 * Performs a shallow search for properties that define a dispose method and deletes them.
	 *
	 * The {@link EffectComposer} calls this method when it is being destroyed.
	 */

	dispose() {

		for(const key of Object.keys(this)) {

			const property = this[key];
			const isDisposable = (
				property instanceof WebGLRenderTarget ||
				property instanceof Material ||
				property instanceof Texture ||
				property instanceof Pass
			);

			if(isDisposable) {

				this[key].dispose();

			}

		}

	}

}