271 lines
7.9 KiB
JavaScript
271 lines
7.9 KiB
JavaScript
/**
|
|
* A Gaussian blur filter. With this filter, you can blur an area of a PIXI.DisplayObject. This cannot
|
|
* be done with the PIXI.filters.BlurFilter (when you use the PIXI.filters.BlurFilter with
|
|
* an filter area, all pixels outside of the area are not displayed). Attention: The area of
|
|
* the filter is always in global scope, NOT relative to the PIXI.DisplayObject the filter
|
|
* is assigned to!
|
|
*
|
|
* @example
|
|
* // Create the app
|
|
* const app = new PIXIApp({
|
|
* view: canvas,
|
|
* width: 480,
|
|
* height: 270,
|
|
* transparent: false
|
|
* }).setup().run()
|
|
*
|
|
* // Add a video sprite
|
|
* const sprite = new PIXI.Sprite(PIXI.Texture.fromVideo("assets/blurfilter.mp4"))
|
|
* sprite.width = app.size.width
|
|
* sprite.height = app.size.height
|
|
* app.scene.addChild(sprite)
|
|
*
|
|
* // Create the filter and assign it to the scene
|
|
* const blurFilter = new BlurFilter(new PIXI.Rectangle(20, 20, 80, 60))
|
|
* app.scene.filters = [blurFilter]
|
|
*
|
|
* @class
|
|
* @extends PIXI.Filter
|
|
* @param {PIXI.Rectangle|PIXI.Circle|PIXI.DisplayObject} shape The area where the blur effect should be applied to. Relative to the
|
|
* canvas, NOT relative to the PIXI.DisplayObject where the blur effect is assigned to!
|
|
* @param {number} [blur=50] The strength of the blur.
|
|
*/
|
|
export default class BlurFilter extends PIXI.Filter {
|
|
constructor(shape, blur = 50) {
|
|
super()
|
|
|
|
const normalized = this.normalize(shape)
|
|
|
|
this.tiltShiftXFilter = new TiltShiftXFilter(normalized, blur)
|
|
this.tiltShiftYFilter = new TiltShiftYFilter(normalized, blur)
|
|
}
|
|
|
|
apply(filterManager, input, output) {
|
|
let renderTarget = filterManager.getRenderTarget(true)
|
|
this.tiltShiftXFilter.apply(filterManager, input, renderTarget)
|
|
this.tiltShiftYFilter.apply(filterManager, renderTarget, output)
|
|
filterManager.returnRenderTarget(renderTarget)
|
|
}
|
|
|
|
/**
|
|
* The strength of the blur.
|
|
*
|
|
* @member {number}
|
|
*/
|
|
get blur() {
|
|
return this.tiltShiftXFilter.blur
|
|
}
|
|
set blur(value) {
|
|
this.tiltShiftXFilter.blur = this.tiltShiftYFilter.blur = value
|
|
}
|
|
|
|
/**
|
|
* The blur shape.
|
|
*
|
|
* @member {PIXI.Rectangle|PIXI.Circle|PIXI.DisplayObject}
|
|
*/
|
|
get shape() {
|
|
return this.tiltShiftXFilter.shape
|
|
}
|
|
set shape(value) {
|
|
this.tiltShiftXFilter.shape = this.tiltShiftYFilter.shape = this.normalize(value)
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @private
|
|
* @param {PIXI.Rectangle|PIXI.Circle|PIXI.DisplayObject} value
|
|
* @returns {Object}
|
|
*/
|
|
normalize(value) {
|
|
let shape = null
|
|
|
|
if (value instanceof PIXI.Circle) {
|
|
shape = { type: 'circle', x: value.x, y: value.y, r: value.radius }
|
|
} else if (value instanceof PIXI.Rectangle) {
|
|
shape = {
|
|
type: 'rectangle',
|
|
x: value.x,
|
|
y: value.y,
|
|
width: value.width,
|
|
height: value.height
|
|
}
|
|
} else {
|
|
const bounds = value.getBounds()
|
|
shape = {
|
|
type: 'rectangle',
|
|
x: bounds.x,
|
|
y: bounds.y,
|
|
width: bounds.width,
|
|
height: bounds.height
|
|
}
|
|
}
|
|
|
|
return shape
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A TiltShiftAxisFilter.
|
|
*
|
|
* @class
|
|
* @extends PIXI.Filter
|
|
* @abstract
|
|
* @private
|
|
*/
|
|
class TiltShiftAxisFilter extends PIXI.Filter {
|
|
constructor(shape, blur) {
|
|
const vertex = `
|
|
attribute vec2 aVertexPosition;
|
|
attribute vec2 aTextureCoord;
|
|
|
|
uniform mat3 projectionMatrix;
|
|
|
|
varying vec2 vTextureCoord;
|
|
|
|
void main(void) {
|
|
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
|
|
vTextureCoord = aTextureCoord;
|
|
}
|
|
`
|
|
|
|
const fragment = `
|
|
varying vec2 vTextureCoord;
|
|
|
|
uniform vec4 filterArea;
|
|
uniform sampler2D uSampler;
|
|
uniform int shape;
|
|
uniform vec4 rectangle;
|
|
uniform vec3 circle;
|
|
uniform float blur;
|
|
uniform vec2 delta;
|
|
uniform vec2 texSize;
|
|
|
|
float random(vec3 scale, float seed) {
|
|
return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);
|
|
}
|
|
|
|
void main(void) {
|
|
// textureCoord to pixelCoord
|
|
vec2 pixelCoord = vTextureCoord * filterArea.xy - vec2(4.0, 4.0); // FIXME: There's a shift of 4 * 4 pixels, don't know why...
|
|
|
|
bool inside = false;
|
|
|
|
if (shape == 1) {
|
|
inside = distance(pixelCoord, circle.xy) <= circle.z;
|
|
} else if (shape == 2) {
|
|
inside = pixelCoord.x >= rectangle.x && pixelCoord.x <= rectangle.z && pixelCoord.y >= rectangle.y && pixelCoord.y <= rectangle.w;
|
|
}
|
|
|
|
if (inside) {
|
|
vec4 color = vec4(0.0);
|
|
float total = 0.0;
|
|
|
|
float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0);
|
|
|
|
for (float t = -30.0; t <= 30.0; t++) {
|
|
float percent = (t + offset - 0.5) / 30.0;
|
|
float weight = 1.0 - abs(percent);
|
|
vec4 sample = texture2D(uSampler, vTextureCoord + delta / texSize * percent * blur);
|
|
sample.rgb *= sample.a;
|
|
color += sample * weight;
|
|
total += weight;
|
|
}
|
|
|
|
gl_FragColor = color / total;
|
|
gl_FragColor.rgb /= gl_FragColor.a + 0.00001;
|
|
} else {
|
|
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
|
|
}
|
|
}
|
|
`
|
|
|
|
super(vertex, fragment)
|
|
|
|
if (shape.type === 'circle') {
|
|
this.uniforms.shape = 1
|
|
this.uniforms.circle = [shape.x, shape.y, shape.r]
|
|
} else {
|
|
this.uniforms.shape = 2
|
|
this.uniforms.rectangle = [shape.x, shape.y, shape.x + shape.width, shape.y + shape.height]
|
|
}
|
|
this.uniforms.blur = blur
|
|
this.uniforms.delta = new PIXI.Point(0, 0)
|
|
this.uniforms.texSize = new PIXI.Point(480, 270)
|
|
|
|
this.updateDelta()
|
|
}
|
|
|
|
/**
|
|
* The strength of the blur.
|
|
*
|
|
* @member {number}
|
|
* @memberof PIXI.filters.TiltShiftAxisFilter#
|
|
*/
|
|
get blur() {
|
|
return this.uniforms.blur
|
|
}
|
|
set blur(value) {
|
|
this.uniforms.blur = value
|
|
}
|
|
|
|
/**
|
|
* The blur shape.
|
|
*
|
|
* @member {PIXI.Rectangle}
|
|
* @memberof PIXI.filters.TiltShiftAxisFilter#
|
|
*/
|
|
get shape() {
|
|
if (this.uniforms.shape === 1) {
|
|
const circle = this.uniforms.circle
|
|
return new PIXI.Circle(circle[0], circle[1], circle[2])
|
|
} else {
|
|
const rectangle = this.uniforms.rectangle
|
|
return new PIXI.Rectangle(rectangle[0], rectangle[1], rectangle[2], rectangle[3])
|
|
}
|
|
}
|
|
set shape(value) {
|
|
if (value.type === 'circle') {
|
|
this.uniforms.shape = 1
|
|
this.uniforms.circle = [value.x, value.y, value.r]
|
|
} else {
|
|
this.uniforms.shape = 2
|
|
this.uniforms.rectangle = [value.x, value.y, value.x + value.width, value.y + value.height]
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A TiltShiftXFilter.
|
|
*
|
|
* @class
|
|
* @extends PIXI.TiltShiftAxisFilter
|
|
* @private
|
|
*/
|
|
class TiltShiftXFilter extends TiltShiftAxisFilter {
|
|
/**
|
|
* Updates the filter delta values.
|
|
*/
|
|
updateDelta() {
|
|
this.uniforms.delta.x = 0.1
|
|
this.uniforms.delta.y = 0
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A TiltShiftYFilter.
|
|
*
|
|
* @class
|
|
* @extends PIXI.TiltShiftAxisFilter
|
|
* @private
|
|
*/
|
|
class TiltShiftYFilter extends TiltShiftAxisFilter {
|
|
/**
|
|
* Updates the filter delta values.
|
|
*/
|
|
updateDelta() {
|
|
this.uniforms.delta.x = 0
|
|
this.uniforms.delta.y = 0.1
|
|
}
|
|
}
|