Home Reference Source

src/materials/FilmMaterial.js

import { ShaderMaterial, Uniform, Vector2 } from "three";

import fragment from "./glsl/film/shader.frag";
import vertex from "./glsl/film/shader.vert";

/**
 * A cinematic shader that provides the following effects:
 *  - Film Grain
 *  - Scanlines
 *  - Vignette
 *  - Greyscale
 *  - Sepia
 *
 * Original scanlines algorithm by Pat "Hawthorne" Shearon.
 *  http://www.truevision3d.com/forums/showcase/staticnoise_colorblackwhite_scanline_shaders-t18698.0.html
 *
 * Optimised scanlines and noise with intensity scaling by Georg "Leviathan"
 * Steinrohder. This version was provided under a Creative Commons Attribution
 * 3.0 License: http://creativecommons.org/licenses/by/3.0.
 *
 * The sepia effect is based on:
 *  https://github.com/evanw/glfx.js
 *
 * The vignette code is based on PaintEffect postprocess from ro.me:
 *  http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js
 */

export class FilmMaterial extends ShaderMaterial {

	/**
	 * Constructs a new film material.
	 *
	 * @param {Object} [options] - The options. Disabled effects will not be included in the final shader and have no negative impact on performance.
	 * @param {Boolean} [options.greyscale=false] - Enable greyscale effect. Greyscale and sepia are mutually exclusive.
	 * @param {Boolean} [options.sepia=false] - Enable sepia effect. Greyscale and sepia are mutually exclusive.
	 * @param {Boolean} [options.vignette=false] - Apply vignette effect.
	 * @param {Boolean} [options.eskil=false] - Use Eskil's vignette approach. The default looks dusty while Eskil looks burned out.
	 * @param {Boolean} [options.screenMode=true] - Whether the screen blend mode should be used for noise and scanlines. Both of these effects are computed independently.
	 * @param {Boolean} [options.noise=true] - Show noise-based film grain.
	 * @param {Boolean} [options.scanlines=true] - Show scanlines.
	 * @param {Boolean} [options.grid=true] - Show a grid.
	 * @param {Number} [options.noiseIntensity=0.5] - The noise intensity.
	 * @param {Number} [options.scanlineIntensity=0.05] - The scanline intensity.
	 * @param {Number} [options.gridIntensity=1.0] - The grid strength. 0.0 to 1.0.
	 * @param {Number} [options.greyscaleIntensity=1.0] - The intensity of the greyscale effect. 0.0 to 1.0.
	 * @param {Number} [options.sepiaIntensity=1.0] - The intensity of the sepia effect. 0.0 to 1.0.
	 * @param {Number} [options.vignetteOffset=1.0] - The offset of the vignette effect. 0.0 to 1.0.
	 * @param {Number} [options.vignetteDarkness=1.0] - The darkness of the vignette effect. 0.0 to 1.0.
	 */

	constructor(options = {}) {

		const settings = Object.assign({

			screenMode: true,
			noise: true,
			scanlines: true,
			grid: false,

			greyscale: false,
			sepia: false,
			vignette: false,
			eskil: false,

			noiseIntensity: 0.5,
			scanlineIntensity: 0.05,
			gridIntensity: 1.0,
			greyscaleIntensity: 1.0,
			sepiaIntensity: 1.0,

			vignetteOffset: 1.0,
			vignetteDarkness: 1.0

		}, options);

		super({

			type: "FilmMaterial",

			uniforms: {

				tDiffuse: new Uniform(null),
				time: new Uniform(0.0),

				noiseIntensity: new Uniform(settings.noiseIntensity),
				scanlineIntensity: new Uniform(settings.scanlineIntensity),
				gridIntensity: new Uniform(settings.gridIntensity),

				scanlineCount: new Uniform(0.0),
				gridScale: new Uniform(new Vector2()),
				gridLineWidth: new Uniform(0.0),

				greyscaleIntensity: new Uniform(settings.greyscaleIntensity),
				sepiaIntensity: new Uniform(settings.sepiaIntensity),

				vignetteOffset: new Uniform(settings.vignetteOffset),
				vignetteDarkness: new Uniform(settings.vignetteDarkness)

			},

			fragmentShader: fragment,
			vertexShader: vertex,

			depthWrite: false,
			depthTest: false

		});

		this.setScreenModeEnabled(settings.screenMode);
		this.setNoiseEnabled(settings.noise);
		this.setScanlinesEnabled(settings.scanlines);
		this.setGridEnabled(settings.grid);
		this.setGreyscaleEnabled(settings.greyscale);
		this.setSepiaEnabled(settings.sepia);
		this.setVignetteEnabled(settings.vignette);
		this.setEskilEnabled(settings.eskil);

	}

	/**
	 * Enables or disables the Screen blend mode.
	 *
	 * @param {Boolean} enabled - Whether the Screen blend mode should be enabled.
	 */

	setScreenModeEnabled(enabled) {

		if(enabled) {

			this.defines.SCREEN_MODE = "1";

		} else {

			delete this.defines.SCREEN_MODE;

		}

		this.needsUpdate = true;

	}

	/**
	 * Enables or disables the noise effect.
	 *
	 * @param {Boolean} enabled - Whether the noise effect should be enabled.
	 */

	setNoiseEnabled(enabled) {

		if(enabled) {

			this.defines.NOISE = "1";

		} else {

			delete this.defines.NOISE;

		}

		this.needsUpdate = true;

	}

	/**
	 * Enables or disables the scanlines effect.
	 *
	 * @param {Boolean} enabled - Whether the scanlines effect should be enabled.
	 */

	setScanlinesEnabled(enabled) {

		if(enabled) {

			this.defines.SCANLINES = "1";

		} else {

			delete this.defines.SCANLINES;

		}

		this.needsUpdate = true;

	}

	/**
	 * Enables or disables the grid effect.
	 *
	 * @param {Boolean} enabled - Whether the grid effect should be enabled.
	 */

	setGridEnabled(enabled) {

		if(enabled) {

			this.defines.GRID = "1";

		} else {

			delete this.defines.GRID;

		}

		this.needsUpdate = true;

	}

	/**
	 * Enables or disables the greyscale effect.
	 *
	 * @param {Boolean} enabled - Whether the greyscale effect should be enabled.
	 */

	setGreyscaleEnabled(enabled) {

		if(enabled) {

			this.defines.GREYSCALE = "1";

		} else {

			delete this.defines.GREYSCALE;

		}

		this.needsUpdate = true;

	}

	/**
	 * Enables or disables the sepia effect.
	 *
	 * @param {Boolean} enabled - Whether the sepia effect should be enabled.
	 */

	setSepiaEnabled(enabled) {

		if(enabled) {

			this.defines.SEPIA = "1";

		} else {

			delete this.defines.SEPIA;

		}

		this.needsUpdate = true;

	}

	/**
	 * Enables or disables the Vignette effect.
	 *
	 * @param {Boolean} enabled - Whether the Vignette effect should be enabled.
	 */

	setVignetteEnabled(enabled) {

		if(enabled) {

			this.defines.VIGNETTE = "1";

		} else {

			delete this.defines.VIGNETTE;

		}

		this.needsUpdate = true;

	}

	/**
	 * Enables or disables the Eskil Vignette effect.
	 *
	 * Has no effect if Vignette is disabled.
	 *
	 * @param {Boolean} enabled - Whether the Eskil Vignette effect should be enabled.
	 */

	setEskilEnabled(enabled) {

		if(enabled) {

			this.defines.ESKIL = "1";

		} else {

			delete this.defines.ESKIL;

		}

		this.needsUpdate = true;

	}

}