src/materials/RealisticBokehMaterial.js
import { ShaderMaterial, Uniform, Vector2 } from "three";
import fragment from "./glsl/realistic-bokeh/shader.frag";
import vertex from "./glsl/realistic-bokeh/shader.vert";
/**
* Depth of Field shader v2.4.
*
* Original shader code by Martins Upitis:
* http://blenderartists.org/forum/showthread.php?237488-GLSL-depth-of-field-with-bokeh-v2-4-(update)
*/
export class RealisticBokehMaterial extends ShaderMaterial {
/**
* Constructs a new bokeh2 material.
*
* @param {PerspectiveCamera} [camera] - The main camera.
* @param {Object} [options] - Additional options.
* @param {Vector2} [options.texelSize] - The absolute screen texel size.
* @param {Boolean} [options.rings=3] - The number of blurring iterations.
* @param {Boolean} [options.samples=2] - The amount of samples taken per ring.
* @param {Boolean} [options.showFocus=false] - Whether the focus point should be highlighted.
* @param {Boolean} [options.manualDoF=false] - Enables manual depth of field blur.
* @param {Boolean} [options.vignette=false] - Enables a vignette effect.
* @param {Boolean} [options.pentagon=false] - Enable to use a pentagonal shape to scale gathered texels.
* @param {Boolean} [options.shaderFocus=true] - Disable if you compute your own focalDepth (in metres!).
* @param {Boolean} [options.noise=true] - Disable if you don't want noise patterns for dithering.
* @param {Number} [options.maxBlur=1.0] - The maximum blur strength.
* @param {Number} [options.luminanceThreshold=0.5] - A luminance threshold.
* @param {Number} [options.luminanceGain=2.0] - A luminance gain factor.
* @param {Number} [options.bias=0.5] - A blur bias.
* @param {Number} [options.fringe=0.7] - A blur offset.
* @param {Number} [options.ditherStrength=0.0001] - The dither strength.
*/
constructor(camera = null, options = {}) {
const settings = Object.assign({
texelSize: null,
rings: 3,
samples: 2,
showFocus: false,
manualDoF: false,
vignette: false,
pentagon: false,
shaderFocus: true,
noise: true,
maxBlur: 1.0,
luminanceThreshold: 0.5,
luminanceGain: 2.0,
bias: 0.5,
fringe: 0.7,
ditherStrength: 0.0001
}, options);
super({
type: "RealisticBokehMaterial",
defines: {
RINGS_INT: settings.rings.toFixed(0),
RINGS_FLOAT: settings.rings.toFixed(1),
SAMPLES_INT: settings.samples.toFixed(0),
SAMPLES_FLOAT: settings.samples.toFixed(1)
},
uniforms: {
tDiffuse: new Uniform(null),
tDepth: new Uniform(null),
texelSize: new Uniform(new Vector2()),
halfTexelSize: new Uniform(new Vector2()),
cameraNear: new Uniform(0.1),
cameraFar: new Uniform(2000),
focalLength: new Uniform(24.0),
focalStop: new Uniform(0.9),
maxBlur: new Uniform(settings.maxBlur),
luminanceThreshold: new Uniform(settings.luminanceThreshold),
luminanceGain: new Uniform(settings.luminanceGain),
bias: new Uniform(settings.bias),
fringe: new Uniform(settings.fringe),
ditherStrength: new Uniform(settings.ditherStrength),
focusCoords: new Uniform(new Vector2(0.5, 0.5)),
focalDepth: new Uniform(1.0)
},
fragmentShader: fragment,
vertexShader: vertex,
depthWrite: false,
depthTest: false
});
this.setShowFocusEnabled(settings.showFocus);
this.setManualDepthOfFieldEnabled(settings.manualDoF);
this.setVignetteEnabled(settings.vignette);
this.setPentagonEnabled(settings.pentagon);
this.setShaderFocusEnabled(settings.shaderFocus);
this.setNoiseEnabled(settings.noise);
if(settings.texelSize !== null) {
this.setTexelSize(settings.texelSize.x, settings.texelSize.y);
}
this.adoptCameraSettings(camera);
}
/**
* Defines whether the focus should be shown.
*
* @param {Boolean} enabled - True if the focus should be shown, false otherwise.
*/
setShowFocusEnabled(enabled) {
if(enabled) {
this.defines.SHOW_FOCUS = "1";
} else {
delete this.defines.SHOW_FOCUS;
}
this.needsUpdate = true;
}
/**
* Defines whether manual Depth of Field should be enabled.
*
* @param {Boolean} enabled - Whether manual DoF should be enabled.
*/
setManualDepthOfFieldEnabled(enabled) {
if(enabled) {
this.defines.MANUAL_DOF = "1";
} else {
delete this.defines.MANUAL_DOF;
}
this.needsUpdate = true;
}
/**
* Defines whether the Vignette effect should be enabled.
*
* @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;
}
/**
* Defines whether the pentagonal blur effect should be enabled.
*
* @param {Boolean} enabled - Whether the pentagonal blur effect should be enabled.
*/
setPentagonEnabled(enabled) {
if(enabled) {
this.defines.PENTAGON = "1";
} else {
delete this.defines.PENTAGON;
}
this.needsUpdate = true;
}
/**
* Enables or disables the automatic shader focus.
*
* @param {Boolean} enabled - Whether the shader focus should be enabled.
*/
setShaderFocusEnabled(enabled) {
if(enabled) {
this.defines.SHADER_FOCUS = "1";
} else {
delete this.defines.SHADER_FOCUS;
}
this.needsUpdate = true;
}
/**
* Defines whether the dithering should compute noise.
*
* @param {Boolean} enabled - Whether noise-based dithering should be enabled.
*/
setNoiseEnabled(enabled) {
if(enabled) {
this.defines.NOISE = "1";
} else {
delete this.defines.NOISE;
}
this.needsUpdate = true;
}
/**
* Sets the texel size.
*
* @param {Number} x - The texel width.
* @param {Number} y - The texel height.
*/
setTexelSize(x, y) {
this.uniforms.texelSize.value.set(x, y);
this.uniforms.halfTexelSize.value.set(x, y).multiplyScalar(0.5);
}
/**
* Adopts the near and far plane and the focal length of the given camera.
*
* @param {PerspectiveCamera} camera - The main camera.
*/
adoptCameraSettings(camera) {
if(camera !== null) {
this.uniforms.cameraNear.value = camera.near;
this.uniforms.cameraFar.value = camera.far;
this.uniforms.focalLength.value = camera.getFocalLength(); // unit: mm.
}
}
}