Home Reference Source

src/materials/EffectMaterial.js

import { BasicDepthPacking, NoBlending, PerspectiveCamera, REVISION, ShaderMaterial, Uniform, Vector2 } from "three";
import { EffectShaderSection as Section } from "../enums/EffectShaderSection.js";
import { updateFragmentShader } from "../utils/BackCompat.js";

import fragmentTemplate from "./glsl/effect.frag";
import vertexTemplate from "./glsl/effect.vert";

/**
 * An effect material for compound shaders. Supports dithering.
 *
 * @implements {Resizable}
 */

export class EffectMaterial extends ShaderMaterial {

	/**
	 * Constructs a new effect material.
	 *
	 * @param {Map<String, String>} [shaderParts] - Deprecated. Use setShaderData instead.
	 * @param {Map<String, String>} [defines] - Deprecated. Use setShaderData instead.
	 * @param {Map<String, Uniform>} [uniforms] - Deprecated. Use setShaderData instead.
	 * @param {Camera} [camera] - A camera.
	 * @param {Boolean} [dithering=false] - Deprecated.
	 */

	constructor(shaderParts, defines, uniforms, camera, dithering = false) {

		super({
			name: "EffectMaterial",
			defines: {
				THREE_REVISION: REVISION.replace(/\D+/g, ""),
				DEPTH_PACKING: "0",
				ENCODE_OUTPUT: "1"
			},
			uniforms: {
				inputBuffer: new Uniform(null),
				depthBuffer: new Uniform(null),
				resolution: new Uniform(new Vector2()),
				texelSize: new Uniform(new Vector2()),
				cameraNear: new Uniform(0.3),
				cameraFar: new Uniform(1000.0),
				aspect: new Uniform(1.0),
				time: new Uniform(0.0)
			},
			blending: NoBlending,
			toneMapped: false,
			depthWrite: false,
			depthTest: false,
			dithering
		});

		if(shaderParts) {

			this.setShaderParts(shaderParts);

		}

		if(defines) {

			this.setDefines(defines);

		}

		if(uniforms) {

			this.setUniforms(uniforms);

		}

		this.copyCameraSettings(camera);

	}

	/**
	 * The input buffer.
	 *
	 * @type {Texture}
	 */

	set inputBuffer(value) {

		this.uniforms.inputBuffer.value = value;

	}

	/**
	 * Sets the input buffer.
	 *
	 * @deprecated Use inputBuffer instead.
	 * @param {Texture} value - The input buffer.
	 */

	setInputBuffer(value) {

		this.uniforms.inputBuffer.value = value;

	}

	/**
	 * The depth buffer.
	 *
	 * @type {Texture}
	 */

	get depthBuffer() {

		return this.uniforms.depthBuffer.value;

	}

	set depthBuffer(value) {

		this.uniforms.depthBuffer.value = value;

	}

	/**
	 * The depth packing strategy.
	 *
	 * @type {DepthPackingStrategies}
	 */

	get depthPacking() {

		return Number(this.defines.DEPTH_PACKING);

	}

	set depthPacking(value) {

		this.defines.DEPTH_PACKING = value.toFixed(0);
		this.needsUpdate = true;

	}

	/**
	 * Sets the depth buffer.
	 *
	 * @deprecated Use depthBuffer and depthPacking instead.
	 * @param {Texture} buffer - The depth texture.
	 * @param {DepthPackingStrategies} [depthPacking=BasicDepthPacking] - The depth packing strategy.
	 */

	setDepthBuffer(buffer, depthPacking = BasicDepthPacking) {

		this.depthBuffer = buffer;
		this.depthPacking = depthPacking;

	}

	/**
	 * Sets the shader data.
	 *
	 * @param {EffectShaderData} data - The shader data.
	 * @return {EffectMaterial} This material.
	 */

	setShaderData(data) {

		this.setShaderParts(data.shaderParts);
		this.setDefines(data.defines);
		this.setUniforms(data.uniforms);
		this.setExtensions(data.extensions);

	}

	/**
	 * Sets the shader parts.
	 *
	 * @deprecated Use setShaderData instead.
	 * @param {Map<String, String>} shaderParts - A collection of shader snippets. See {@link EffectShaderSection}.
	 * @return {EffectMaterial} This material.
	 */

	setShaderParts(shaderParts) {

		this.fragmentShader = fragmentTemplate
			.replace(Section.FRAGMENT_HEAD, shaderParts.get(Section.FRAGMENT_HEAD) || "")
			.replace(Section.FRAGMENT_MAIN_UV, shaderParts.get(Section.FRAGMENT_MAIN_UV) || "")
			.replace(Section.FRAGMENT_MAIN_IMAGE, shaderParts.get(Section.FRAGMENT_MAIN_IMAGE) || "");

		this.vertexShader = vertexTemplate
			.replace(Section.VERTEX_HEAD, shaderParts.get(Section.VERTEX_HEAD) || "")
			.replace(Section.VERTEX_MAIN_SUPPORT, shaderParts.get(Section.VERTEX_MAIN_SUPPORT) || "");

		this.fragmentShader = updateFragmentShader(this.fragmentShader);

		this.needsUpdate = true;
		return this;

	}

	/**
	 * Sets the shader macros.
	 *
	 * @deprecated Use setShaderData instead.
	 * @param {Map<String, String>} defines - A collection of preprocessor macro definitions.
	 * @return {EffectMaterial} This material.
	 */

	setDefines(defines) {

		for(const entry of defines.entries()) {

			this.defines[entry[0]] = entry[1];

		}

		this.needsUpdate = true;
		return this;

	}

	/**
	 * Sets the shader uniforms.
	 *
	 * @deprecated Use setShaderData instead.
	 * @param {Map<String, Uniform>} uniforms - A collection of uniforms.
	 * @return {EffectMaterial} This material.
	 */

	setUniforms(uniforms) {

		for(const entry of uniforms.entries()) {

			this.uniforms[entry[0]] = entry[1];

		}

		return this;

	}

	/**
	 * Sets the required shader extensions.
	 *
	 * @deprecated Use setShaderData instead.
	 * @param {Set<WebGLExtension>} extensions - A collection of extensions.
	 * @return {EffectMaterial} This material.
	 */

	setExtensions(extensions) {

		this.extensions = {};

		for(const extension of extensions) {

			this.extensions[extension] = true;

		}

		return this;

	}

	/**
	 * Indicates whether output encoding is enabled.
	 *
	 * @type {Boolean}
	 */

	get encodeOutput() {

		return (this.defines.ENCODE_OUTPUT !== undefined);

	}

	set encodeOutput(value) {

		if(this.encodeOutput !== value) {

			if(value) {

				this.defines.ENCODE_OUTPUT = "1";

			} else {

				delete this.defines.ENCODE_OUTPUT;

			}

			this.needsUpdate = true;

		}

	}

	/**
	 * Indicates whether output encoding is enabled.
	 *
	 * @deprecated Use encodeOutput instead.
	 * @return {Boolean} Whether output encoding is enabled.
	 */

	isOutputEncodingEnabled(value) {

		return this.encodeOutput;

	}

	/**
	 * Enables or disables output encoding.
	 *
	 * @deprecated Use encodeOutput instead.
	 * @param {Boolean} value - Whether output encoding should be enabled.
	 */

	setOutputEncodingEnabled(value) {

		this.encodeOutput = value;

	}

	/**
	 * The time in seconds.
	 *
	 * @type {Number}
	 */

	get time() {

		return this.uniforms.time.value;

	}

	set time(value) {

		this.uniforms.time.value = value;

	}

	/**
	 * Sets the delta time.
	 *
	 * @deprecated Use time instead.
	 * @param {Number} value - The delta time in seconds.
	 */

	setDeltaTime(value) {

		this.uniforms.time.value += value;

	}

	/**
	 * Copies the settings of the given camera.
	 *
	 * @deprecated Use copyCameraSettings instead.
	 * @param {Camera} camera - A camera.
	 */

	adoptCameraSettings(camera) {

		this.copyCameraSettings(camera);

	}

	/**
	 * Copies the settings of the given camera.
	 *
	 * @param {Camera} camera - A camera.
	 */

	copyCameraSettings(camera) {

		if(camera) {

			this.uniforms.cameraNear.value = camera.near;
			this.uniforms.cameraFar.value = camera.far;

			if(camera instanceof PerspectiveCamera) {

				this.defines.PERSPECTIVE_CAMERA = "1";

			} else {

				delete this.defines.PERSPECTIVE_CAMERA;

			}

			this.needsUpdate = true;

		}

	}

	/**
	 * Sets the resolution.
	 *
	 * @param {Number} width - The width.
	 * @param {Number} height - The height.
	 */

	setSize(width, height) {

		const uniforms = this.uniforms;
		uniforms.resolution.value.set(width, height);
		uniforms.texelSize.value.set(1.0 / width, 1.0 / height);
		uniforms.aspect.value = width / height;

	}

	/**
	 * An enumeration of shader code placeholders.
	 *
	 * @deprecated Use EffectShaderSection instead.
	 * @type {Object}
	 */

	static get Section() {

		return Section;

	}

}