src/passes/GlitchPass.js
import { DataTexture, RGBFormat, FloatType } from "three";
import { GlitchMaterial } from "../materials";
import { Pass } from "./Pass.js";
/**
* Returns a random integer in the specified range.
*
* @private
* @param {Number} low - The lowest possible value.
* @param {Number} high - The highest possible value.
* @return {Number} The random value.
*/
function randomInt(low, high) {
return low + Math.floor(Math.random() * (high - low + 1));
}
/**
* Returns a random float in the specified range.
*
* @private
* @param {Number} low - The lowest possible value.
* @param {Number} high - The highest possible value.
* @return {Number} The random value.
*/
function randomFloat(low, high) {
return low + Math.random() * (high - low);
}
/**
* A glitch pass.
*/
export class GlitchPass extends Pass {
/**
* Constructs a new glitch pass.
*
* @param {Object} [options] - The options.
* @param {Texture} [options.perturbMap] - A perturbation map. If none is provided, a noise texture will be created.
* @param {Number} [options.dtSize=64] - The size of the generated noise map. Will be ignored if a perturbation map is provided.
*/
constructor(options = {}) {
super("GlitchPass");
this.setFullscreenMaterial(new GlitchMaterial());
/**
* A perturbation map.
*
* @type {Texture}
* @private
*/
this.texture = null;
this.perturbMap = (options.perturbMap !== undefined) ? options.perturbMap : this.generatePerturbMap(options.dtSize);
this.perturbMap.name = "Glitch.Perturbation";
this.perturbMap.generateMipmaps = false;
/**
* The effect mode.
*
* @type {GlitchMode}
*/
this.mode = GlitchMode.SPORADIC;
/**
* A counter for the glitch activation and deactivation.
*
* @type {Number}
* @private
*/
this.counter = 0;
/**
* A random break point for the sporadic glitch activation.
*
* @type {Number}
* @private
*/
this.breakPoint = randomInt(120, 240);
}
/**
* The current perturbation map.
*
* @type {Texture}
*/
get perturbMap() {
return this.texture;
}
/**
* Assigning a new perturbation map does not destroy the current one!
*
* @type {Texture}
*/
set perturbMap(value) {
this.texture = value;
this.getFullscreenMaterial().uniforms.tPerturb.value = value;
}
/**
* Destroys the current perturbation map and replaces it with a new one.
*
* @param {Number} [size=64] - The texture size.
* @return {DataTexture} The perturbation texture.
*/
generatePerturbMap(size = 64) {
const pixels = size * size;
const data = new Float32Array(pixels * 3);
let dt = this.perturbMap;
let i, x;
for(i = 0; i < pixels; ++i) {
x = Math.random();
data[i * 3] = x;
data[i * 3 + 1] = x;
data[i * 3 + 2] = x;
}
if(dt !== null) {
dt.dispose();
}
dt = new DataTexture(data, size, size, RGBFormat, FloatType);
dt.needsUpdate = true;
this.perturbMap = dt;
return dt;
}
/**
* Renders the effect.
*
* @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 mode = this.mode;
const counter = this.counter;
const breakPoint = this.breakPoint;
const uniforms = this.getFullscreenMaterial().uniforms;
uniforms.tDiffuse.value = inputBuffer.texture;
uniforms.seed.value = Math.random();
uniforms.active.value = true;
if(counter % breakPoint === 0 || mode === GlitchMode.CONSTANT_WILD) {
uniforms.amount.value = Math.random() / 30.0;
uniforms.angle.value = randomFloat(-Math.PI, Math.PI);
uniforms.seedX.value = randomFloat(-1.0, 1.0);
uniforms.seedY.value = randomFloat(-1.0, 1.0);
uniforms.distortionX.value = randomFloat(0.0, 1.0);
uniforms.distortionY.value = randomFloat(0.0, 1.0);
this.breakPoint = randomInt(120, 240);
this.counter = 0;
} else {
if(counter % breakPoint < breakPoint / 5 || mode === GlitchMode.CONSTANT_MILD) {
uniforms.amount.value = Math.random() / 90.0;
uniforms.angle.value = randomFloat(-Math.PI, Math.PI);
uniforms.distortionX.value = randomFloat(0.0, 1.0);
uniforms.distortionY.value = randomFloat(0.0, 1.0);
uniforms.seedX.value = randomFloat(-0.3, 0.3);
uniforms.seedY.value = randomFloat(-0.3, 0.3);
} else {
// Sporadic.
uniforms.active.value = false;
}
}
++this.counter;
renderer.render(this.scene, this.camera, this.renderToScreen ? null : outputBuffer);
}
}
/**
* A glitch mode enumeration.
*
* @type {Object}
* @property {Number} SPORADIC - Sporadic glitches.
* @property {Number} CONSTANT_MILD - Constant mild glitches.
* @property {Number} CONSTANT_WILD - Constant wild glitches.
*/
export const GlitchMode = {
SPORADIC: 0,
CONSTANT_MILD: 1,
CONSTANT_WILD: 2
};