diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 262b0b1..5d3539d 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -68,6 +68,13 @@ /** * This namespace contains WebGL-only display filters that can be applied * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * + * All filters must extend {@link PIXI.Filter}. + * * @example * // Create a new application * const app = new PIXI.Application(); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 262b0b1..5d3539d 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -68,6 +68,13 @@ /** * This namespace contains WebGL-only display filters that can be applied * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * + * All filters must extend {@link PIXI.Filter}. + * * @example * // Create a new application * const app = new PIXI.Application(); diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js index 9d8cad8..2cceb3e 100644 --- a/packages/core/src/filters/Filter.js +++ b/packages/core/src/filters/Filter.js @@ -2,12 +2,138 @@ import Program from '../shader/Program'; import State from '../state/State'; import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; import defaultVertex from './defaultFilter.vert'; import defaultFragment from './defaultFilter.frag'; -// let math = require('../../math'); /** + * Filter is a special type of shader that is applied to the screen. + * {@link http://pixijs.io/examples/#/filters/blur-filter.js Example} of the + * {@link PIXI.filters.BlurFilter BlurFilter}. + * + * ### Usage + * Filters can be applied to any DisplayObject or Container. PixiJS' `FilterSystem` + * renders the container into temporary FrameBuffer, then filter + * renders it to the screen. Multiple filters can be added to the `filters` property + * and stacked on each other. + * + * ``` + * const filter = new PIXI.Filter(myShaderVert, myShaderFrag, { myUniform: 0.5 }); + * const container = new PIXI.Container(); + * container.filters = [filter]; + * ``` + * + * ### Previous Version Differences + * + * In PixiJS **v3**, a filter was always applied to _whole screen_. + * + * In PixiJS **v4**, a filter can be applied _only part of the screen_, developers + * had to create a set of uniforms to deal with coordinates. + * + * In PixiJS **v5** combines _both approaches_, developers can use normal coordinates of + * v3 and then allow filter to use partial FrameBuffers, bringing those extra + * uniforms into account. + * + * ### Built-in Uniforms + * + * PixiJS viewport uses screen (CSS) coordinates, `(0, 0, renderer.screen.width, renderer.screen.height)`, + * and `projectionMatrix` uniform maps it to the gl viewport. + * + * **uSampler** + * + * The most important uniform is the input texture that container was rendered into. + * _Important note: as with all PixiJS' FrameBuffers, both input and output are + * premultiplied by alpha._ + * + * By default, input FrameBuffer space coordinates are passed to fragment shader with `vTextureCoord`. + * Use it to sample the input. + * + * ``` + * const fragment = ` + * varying vec2 vTextureCoord; + * uniform sampler2D uSampler; + * void main(void) + * { + * gl_FragColor = texture2D(uSampler, vTextureCoord); + * } + * `; + * + * const myFilter = new PIXI.Filter(null, fragment); + * ``` + * + * This filter is just one uniform less than {@link PIXI.filters.AlphaFilter AlphaFilter}. + * + * **outputFrame** + * + * The `outputFrame` holds the rectangle where filter is applied in screen (CSS) coordinates. + * It's the same as `renderer.screen` for a fullscreen filter. + * Only a part of `outputFrame.zw` size of temporary FrameBuffer is used, + * `(0, 0, outputFrame.width, outputFrame.height)`, + * + * Filters uses this quad to normalized (0-1) space, its passed into `aVertexPosition` attribute. + * To calculate vertex position in screen space using normalized (0-1) space: + * + * ``` + * vec4 filterVertexPosition( void ) + * { + * vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + * return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + * } + * ``` + * + * **inputSize** + * + * Temporary FrameBuffer is different, it can be either the size of screen, either power-of-two. + * The `inputSize.xy` are size of temporary FrameBuffer that holds input. + * The `inputSize.zw` is inverted, it's a shortcut to evade division inside the shader. + * + * Set `inputSize.xy = outputFrame.zw` for a fullscreen filter. + * + * To calculate input texture coordinate in 0-1 space, you have to map it to FrameBuffer normalized space. + * Multiply by `outputFrame.zw` to get pixel coordinate in part of FrameBuffer. + * Divide by `inputSize.xy` to get FrameBuffer normalized space (input sampler space) + * + * ``` + * vec2 filterTextureCoord( void ) + * { + * return aVertexPosition * (outputFrame.zw * inputSize.zw); // same as /inputSize.xy + * } + * ``` + * **resolution** + * + * The `resolution` is the ratio of screen (CSS) pixels to real pixels. + * + * **inputPixel** + * + * `inputPixel.xy` is the size of framebuffer in real pixels, same as `inputSize.xy * resolution` + * `inputPixel.zw` is inverted `inputPixel.xy`. + * + * It's handy for filters that use neighbour pixels, like {@link PIXI.filters.FXAAFilter FXAAFilter}. + * + * **inputClamp** + * + * If you try to get info from outside of used part of FrameBuffer - you'll get undefined behaviour. + * For displacements, coordinates has to be clamped. + * + * The `inputClamp.xy` is left-top pixel center, you may ignore it, because we use left-top part of FrameBuffer + * `inputClamp.zw` is bottom-right pixel center. + * + * ``` + * vec4 color = texture2D(uSampler, clamp(modifigedTextureCoord, inputClamp.xy, inputClamp.zw)) + * ``` + * OR + * ``` + * vec4 color = texture2D(uSampler, min(modifigedTextureCoord, inputClamp.zw)) + * ``` + * + * ### Additional Information + * + * Complete documentation on Filter usage is located in + * {@link https://github.com/pixijs/pixi.js/wiki/v5-Creating-filters Wiki}. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * * @class * @memberof PIXI * @extends PIXI.Shader diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 262b0b1..5d3539d 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -68,6 +68,13 @@ /** * This namespace contains WebGL-only display filters that can be applied * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * + * All filters must extend {@link PIXI.Filter}. + * * @example * // Create a new application * const app = new PIXI.Application(); diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js index 9d8cad8..2cceb3e 100644 --- a/packages/core/src/filters/Filter.js +++ b/packages/core/src/filters/Filter.js @@ -2,12 +2,138 @@ import Program from '../shader/Program'; import State from '../state/State'; import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; import defaultVertex from './defaultFilter.vert'; import defaultFragment from './defaultFilter.frag'; -// let math = require('../../math'); /** + * Filter is a special type of shader that is applied to the screen. + * {@link http://pixijs.io/examples/#/filters/blur-filter.js Example} of the + * {@link PIXI.filters.BlurFilter BlurFilter}. + * + * ### Usage + * Filters can be applied to any DisplayObject or Container. PixiJS' `FilterSystem` + * renders the container into temporary FrameBuffer, then filter + * renders it to the screen. Multiple filters can be added to the `filters` property + * and stacked on each other. + * + * ``` + * const filter = new PIXI.Filter(myShaderVert, myShaderFrag, { myUniform: 0.5 }); + * const container = new PIXI.Container(); + * container.filters = [filter]; + * ``` + * + * ### Previous Version Differences + * + * In PixiJS **v3**, a filter was always applied to _whole screen_. + * + * In PixiJS **v4**, a filter can be applied _only part of the screen_, developers + * had to create a set of uniforms to deal with coordinates. + * + * In PixiJS **v5** combines _both approaches_, developers can use normal coordinates of + * v3 and then allow filter to use partial FrameBuffers, bringing those extra + * uniforms into account. + * + * ### Built-in Uniforms + * + * PixiJS viewport uses screen (CSS) coordinates, `(0, 0, renderer.screen.width, renderer.screen.height)`, + * and `projectionMatrix` uniform maps it to the gl viewport. + * + * **uSampler** + * + * The most important uniform is the input texture that container was rendered into. + * _Important note: as with all PixiJS' FrameBuffers, both input and output are + * premultiplied by alpha._ + * + * By default, input FrameBuffer space coordinates are passed to fragment shader with `vTextureCoord`. + * Use it to sample the input. + * + * ``` + * const fragment = ` + * varying vec2 vTextureCoord; + * uniform sampler2D uSampler; + * void main(void) + * { + * gl_FragColor = texture2D(uSampler, vTextureCoord); + * } + * `; + * + * const myFilter = new PIXI.Filter(null, fragment); + * ``` + * + * This filter is just one uniform less than {@link PIXI.filters.AlphaFilter AlphaFilter}. + * + * **outputFrame** + * + * The `outputFrame` holds the rectangle where filter is applied in screen (CSS) coordinates. + * It's the same as `renderer.screen` for a fullscreen filter. + * Only a part of `outputFrame.zw` size of temporary FrameBuffer is used, + * `(0, 0, outputFrame.width, outputFrame.height)`, + * + * Filters uses this quad to normalized (0-1) space, its passed into `aVertexPosition` attribute. + * To calculate vertex position in screen space using normalized (0-1) space: + * + * ``` + * vec4 filterVertexPosition( void ) + * { + * vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + * return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + * } + * ``` + * + * **inputSize** + * + * Temporary FrameBuffer is different, it can be either the size of screen, either power-of-two. + * The `inputSize.xy` are size of temporary FrameBuffer that holds input. + * The `inputSize.zw` is inverted, it's a shortcut to evade division inside the shader. + * + * Set `inputSize.xy = outputFrame.zw` for a fullscreen filter. + * + * To calculate input texture coordinate in 0-1 space, you have to map it to FrameBuffer normalized space. + * Multiply by `outputFrame.zw` to get pixel coordinate in part of FrameBuffer. + * Divide by `inputSize.xy` to get FrameBuffer normalized space (input sampler space) + * + * ``` + * vec2 filterTextureCoord( void ) + * { + * return aVertexPosition * (outputFrame.zw * inputSize.zw); // same as /inputSize.xy + * } + * ``` + * **resolution** + * + * The `resolution` is the ratio of screen (CSS) pixels to real pixels. + * + * **inputPixel** + * + * `inputPixel.xy` is the size of framebuffer in real pixels, same as `inputSize.xy * resolution` + * `inputPixel.zw` is inverted `inputPixel.xy`. + * + * It's handy for filters that use neighbour pixels, like {@link PIXI.filters.FXAAFilter FXAAFilter}. + * + * **inputClamp** + * + * If you try to get info from outside of used part of FrameBuffer - you'll get undefined behaviour. + * For displacements, coordinates has to be clamped. + * + * The `inputClamp.xy` is left-top pixel center, you may ignore it, because we use left-top part of FrameBuffer + * `inputClamp.zw` is bottom-right pixel center. + * + * ``` + * vec4 color = texture2D(uSampler, clamp(modifigedTextureCoord, inputClamp.xy, inputClamp.zw)) + * ``` + * OR + * ``` + * vec4 color = texture2D(uSampler, min(modifigedTextureCoord, inputClamp.zw)) + * ``` + * + * ### Additional Information + * + * Complete documentation on Filter usage is located in + * {@link https://github.com/pixijs/pixi.js/wiki/v5-Creating-filters Wiki}. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * * @class * @memberof PIXI * @extends PIXI.Shader diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js index 3174318..514ec32 100644 --- a/packages/core/src/filters/FilterSystem.js +++ b/packages/core/src/filters/FilterSystem.js @@ -78,8 +78,11 @@ * @type {UniformGroup} */ this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, + outputFrame: this.tempRect, + inputSize: new Float32Array(4), + inputPixel: new Float32Array(4), + inputClamp: new Float32Array(4), + resolution: 1, // legacy variables filterArea: new Float32Array(4), @@ -163,25 +166,39 @@ const globalUniforms = this.globalUniforms.uniforms; - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.outputFrame = state.sourceFrame; globalUniforms.resolution = state.resolution; + const inputSize = globalUniforms.inputSize; + const inputPixel = globalUniforms.inputPixel; + const inputClamp = globalUniforms.inputClamp; + + inputSize[0] = state.destinationFrame.width; + inputSize[1] = state.destinationFrame.height; + inputSize[2] = 1.0 / inputSize[0]; + inputSize[3] = 1.0 / inputSize[1]; + + inputPixel[0] = inputSize[0] * state.resolution; + inputPixel[1] = inputSize[1] * state.resolution; + inputPixel[2] = 1.0 / inputPixel[0]; + inputPixel[3] = 1.0 / inputPixel[1]; + + inputClamp[0] = 0.5 * inputPixel[2]; + inputClamp[1] = 0.5 * inputPixel[3]; + inputClamp[2] = (state.sourceFrame.width * inputSize[2]) - (0.5 * inputPixel[2]); + inputClamp[3] = (state.sourceFrame.height * inputSize[3]) - (0.5 * inputPixel[3]); + // only update the rect if its legacy.. if (state.legacy) { const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; filterArea[0] = state.destinationFrame.width; filterArea[1] = state.destinationFrame.height; filterArea[2] = state.sourceFrame.x; filterArea[3] = state.sourceFrame.y; - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + globalUniforms.filterClamp = globalUniforms.inputClamp; } this.globalUniforms.update(); diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 262b0b1..5d3539d 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -68,6 +68,13 @@ /** * This namespace contains WebGL-only display filters that can be applied * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * + * All filters must extend {@link PIXI.Filter}. + * * @example * // Create a new application * const app = new PIXI.Application(); diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js index 9d8cad8..2cceb3e 100644 --- a/packages/core/src/filters/Filter.js +++ b/packages/core/src/filters/Filter.js @@ -2,12 +2,138 @@ import Program from '../shader/Program'; import State from '../state/State'; import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; import defaultVertex from './defaultFilter.vert'; import defaultFragment from './defaultFilter.frag'; -// let math = require('../../math'); /** + * Filter is a special type of shader that is applied to the screen. + * {@link http://pixijs.io/examples/#/filters/blur-filter.js Example} of the + * {@link PIXI.filters.BlurFilter BlurFilter}. + * + * ### Usage + * Filters can be applied to any DisplayObject or Container. PixiJS' `FilterSystem` + * renders the container into temporary FrameBuffer, then filter + * renders it to the screen. Multiple filters can be added to the `filters` property + * and stacked on each other. + * + * ``` + * const filter = new PIXI.Filter(myShaderVert, myShaderFrag, { myUniform: 0.5 }); + * const container = new PIXI.Container(); + * container.filters = [filter]; + * ``` + * + * ### Previous Version Differences + * + * In PixiJS **v3**, a filter was always applied to _whole screen_. + * + * In PixiJS **v4**, a filter can be applied _only part of the screen_, developers + * had to create a set of uniforms to deal with coordinates. + * + * In PixiJS **v5** combines _both approaches_, developers can use normal coordinates of + * v3 and then allow filter to use partial FrameBuffers, bringing those extra + * uniforms into account. + * + * ### Built-in Uniforms + * + * PixiJS viewport uses screen (CSS) coordinates, `(0, 0, renderer.screen.width, renderer.screen.height)`, + * and `projectionMatrix` uniform maps it to the gl viewport. + * + * **uSampler** + * + * The most important uniform is the input texture that container was rendered into. + * _Important note: as with all PixiJS' FrameBuffers, both input and output are + * premultiplied by alpha._ + * + * By default, input FrameBuffer space coordinates are passed to fragment shader with `vTextureCoord`. + * Use it to sample the input. + * + * ``` + * const fragment = ` + * varying vec2 vTextureCoord; + * uniform sampler2D uSampler; + * void main(void) + * { + * gl_FragColor = texture2D(uSampler, vTextureCoord); + * } + * `; + * + * const myFilter = new PIXI.Filter(null, fragment); + * ``` + * + * This filter is just one uniform less than {@link PIXI.filters.AlphaFilter AlphaFilter}. + * + * **outputFrame** + * + * The `outputFrame` holds the rectangle where filter is applied in screen (CSS) coordinates. + * It's the same as `renderer.screen` for a fullscreen filter. + * Only a part of `outputFrame.zw` size of temporary FrameBuffer is used, + * `(0, 0, outputFrame.width, outputFrame.height)`, + * + * Filters uses this quad to normalized (0-1) space, its passed into `aVertexPosition` attribute. + * To calculate vertex position in screen space using normalized (0-1) space: + * + * ``` + * vec4 filterVertexPosition( void ) + * { + * vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + * return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + * } + * ``` + * + * **inputSize** + * + * Temporary FrameBuffer is different, it can be either the size of screen, either power-of-two. + * The `inputSize.xy` are size of temporary FrameBuffer that holds input. + * The `inputSize.zw` is inverted, it's a shortcut to evade division inside the shader. + * + * Set `inputSize.xy = outputFrame.zw` for a fullscreen filter. + * + * To calculate input texture coordinate in 0-1 space, you have to map it to FrameBuffer normalized space. + * Multiply by `outputFrame.zw` to get pixel coordinate in part of FrameBuffer. + * Divide by `inputSize.xy` to get FrameBuffer normalized space (input sampler space) + * + * ``` + * vec2 filterTextureCoord( void ) + * { + * return aVertexPosition * (outputFrame.zw * inputSize.zw); // same as /inputSize.xy + * } + * ``` + * **resolution** + * + * The `resolution` is the ratio of screen (CSS) pixels to real pixels. + * + * **inputPixel** + * + * `inputPixel.xy` is the size of framebuffer in real pixels, same as `inputSize.xy * resolution` + * `inputPixel.zw` is inverted `inputPixel.xy`. + * + * It's handy for filters that use neighbour pixels, like {@link PIXI.filters.FXAAFilter FXAAFilter}. + * + * **inputClamp** + * + * If you try to get info from outside of used part of FrameBuffer - you'll get undefined behaviour. + * For displacements, coordinates has to be clamped. + * + * The `inputClamp.xy` is left-top pixel center, you may ignore it, because we use left-top part of FrameBuffer + * `inputClamp.zw` is bottom-right pixel center. + * + * ``` + * vec4 color = texture2D(uSampler, clamp(modifigedTextureCoord, inputClamp.xy, inputClamp.zw)) + * ``` + * OR + * ``` + * vec4 color = texture2D(uSampler, min(modifigedTextureCoord, inputClamp.zw)) + * ``` + * + * ### Additional Information + * + * Complete documentation on Filter usage is located in + * {@link https://github.com/pixijs/pixi.js/wiki/v5-Creating-filters Wiki}. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * * @class * @memberof PIXI * @extends PIXI.Shader diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js index 3174318..514ec32 100644 --- a/packages/core/src/filters/FilterSystem.js +++ b/packages/core/src/filters/FilterSystem.js @@ -78,8 +78,11 @@ * @type {UniformGroup} */ this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, + outputFrame: this.tempRect, + inputSize: new Float32Array(4), + inputPixel: new Float32Array(4), + inputClamp: new Float32Array(4), + resolution: 1, // legacy variables filterArea: new Float32Array(4), @@ -163,25 +166,39 @@ const globalUniforms = this.globalUniforms.uniforms; - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.outputFrame = state.sourceFrame; globalUniforms.resolution = state.resolution; + const inputSize = globalUniforms.inputSize; + const inputPixel = globalUniforms.inputPixel; + const inputClamp = globalUniforms.inputClamp; + + inputSize[0] = state.destinationFrame.width; + inputSize[1] = state.destinationFrame.height; + inputSize[2] = 1.0 / inputSize[0]; + inputSize[3] = 1.0 / inputSize[1]; + + inputPixel[0] = inputSize[0] * state.resolution; + inputPixel[1] = inputSize[1] * state.resolution; + inputPixel[2] = 1.0 / inputPixel[0]; + inputPixel[3] = 1.0 / inputPixel[1]; + + inputClamp[0] = 0.5 * inputPixel[2]; + inputClamp[1] = 0.5 * inputPixel[3]; + inputClamp[2] = (state.sourceFrame.width * inputSize[2]) - (0.5 * inputPixel[2]); + inputClamp[3] = (state.sourceFrame.height * inputSize[3]) - (0.5 * inputPixel[3]); + // only update the rect if its legacy.. if (state.legacy) { const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; filterArea[0] = state.destinationFrame.width; filterArea[1] = state.destinationFrame.height; filterArea[2] = state.sourceFrame.x; filterArea[3] = state.sourceFrame.y; - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + globalUniforms.filterClamp = globalUniforms.inputClamp; } this.globalUniforms.update(); diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js index 11b1806..a0060ed 100644 --- a/packages/filters/filter-blur/src/generateBlurVertSource.js +++ b/packages/filters/filter-blur/src/generateBlurVertSource.js @@ -3,29 +3,24 @@ uniform mat3 projectionMatrix; - uniform vec4 destinationFrame; - uniform vec4 sourceFrame; - uniform float strength; - uniform float resolution; varying vec2 vBlurTexCoords[%size%]; + uniform vec4 inputSize; + uniform vec4 inputClamp; + uniform vec4 outputFrame; + vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; - + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } - + vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); - } - - vec2 size( void ) - { - return ( (sourceFrame.zw -resolution) / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } void main(void) @@ -48,11 +43,11 @@ if (x) { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), inputClamp.zw);'; } else { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), inputClamp.zw);'; } for (let i = 0; i < kernelSize; i++) diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 262b0b1..5d3539d 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -68,6 +68,13 @@ /** * This namespace contains WebGL-only display filters that can be applied * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * + * All filters must extend {@link PIXI.Filter}. + * * @example * // Create a new application * const app = new PIXI.Application(); diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js index 9d8cad8..2cceb3e 100644 --- a/packages/core/src/filters/Filter.js +++ b/packages/core/src/filters/Filter.js @@ -2,12 +2,138 @@ import Program from '../shader/Program'; import State from '../state/State'; import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; import defaultVertex from './defaultFilter.vert'; import defaultFragment from './defaultFilter.frag'; -// let math = require('../../math'); /** + * Filter is a special type of shader that is applied to the screen. + * {@link http://pixijs.io/examples/#/filters/blur-filter.js Example} of the + * {@link PIXI.filters.BlurFilter BlurFilter}. + * + * ### Usage + * Filters can be applied to any DisplayObject or Container. PixiJS' `FilterSystem` + * renders the container into temporary FrameBuffer, then filter + * renders it to the screen. Multiple filters can be added to the `filters` property + * and stacked on each other. + * + * ``` + * const filter = new PIXI.Filter(myShaderVert, myShaderFrag, { myUniform: 0.5 }); + * const container = new PIXI.Container(); + * container.filters = [filter]; + * ``` + * + * ### Previous Version Differences + * + * In PixiJS **v3**, a filter was always applied to _whole screen_. + * + * In PixiJS **v4**, a filter can be applied _only part of the screen_, developers + * had to create a set of uniforms to deal with coordinates. + * + * In PixiJS **v5** combines _both approaches_, developers can use normal coordinates of + * v3 and then allow filter to use partial FrameBuffers, bringing those extra + * uniforms into account. + * + * ### Built-in Uniforms + * + * PixiJS viewport uses screen (CSS) coordinates, `(0, 0, renderer.screen.width, renderer.screen.height)`, + * and `projectionMatrix` uniform maps it to the gl viewport. + * + * **uSampler** + * + * The most important uniform is the input texture that container was rendered into. + * _Important note: as with all PixiJS' FrameBuffers, both input and output are + * premultiplied by alpha._ + * + * By default, input FrameBuffer space coordinates are passed to fragment shader with `vTextureCoord`. + * Use it to sample the input. + * + * ``` + * const fragment = ` + * varying vec2 vTextureCoord; + * uniform sampler2D uSampler; + * void main(void) + * { + * gl_FragColor = texture2D(uSampler, vTextureCoord); + * } + * `; + * + * const myFilter = new PIXI.Filter(null, fragment); + * ``` + * + * This filter is just one uniform less than {@link PIXI.filters.AlphaFilter AlphaFilter}. + * + * **outputFrame** + * + * The `outputFrame` holds the rectangle where filter is applied in screen (CSS) coordinates. + * It's the same as `renderer.screen` for a fullscreen filter. + * Only a part of `outputFrame.zw` size of temporary FrameBuffer is used, + * `(0, 0, outputFrame.width, outputFrame.height)`, + * + * Filters uses this quad to normalized (0-1) space, its passed into `aVertexPosition` attribute. + * To calculate vertex position in screen space using normalized (0-1) space: + * + * ``` + * vec4 filterVertexPosition( void ) + * { + * vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + * return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + * } + * ``` + * + * **inputSize** + * + * Temporary FrameBuffer is different, it can be either the size of screen, either power-of-two. + * The `inputSize.xy` are size of temporary FrameBuffer that holds input. + * The `inputSize.zw` is inverted, it's a shortcut to evade division inside the shader. + * + * Set `inputSize.xy = outputFrame.zw` for a fullscreen filter. + * + * To calculate input texture coordinate in 0-1 space, you have to map it to FrameBuffer normalized space. + * Multiply by `outputFrame.zw` to get pixel coordinate in part of FrameBuffer. + * Divide by `inputSize.xy` to get FrameBuffer normalized space (input sampler space) + * + * ``` + * vec2 filterTextureCoord( void ) + * { + * return aVertexPosition * (outputFrame.zw * inputSize.zw); // same as /inputSize.xy + * } + * ``` + * **resolution** + * + * The `resolution` is the ratio of screen (CSS) pixels to real pixels. + * + * **inputPixel** + * + * `inputPixel.xy` is the size of framebuffer in real pixels, same as `inputSize.xy * resolution` + * `inputPixel.zw` is inverted `inputPixel.xy`. + * + * It's handy for filters that use neighbour pixels, like {@link PIXI.filters.FXAAFilter FXAAFilter}. + * + * **inputClamp** + * + * If you try to get info from outside of used part of FrameBuffer - you'll get undefined behaviour. + * For displacements, coordinates has to be clamped. + * + * The `inputClamp.xy` is left-top pixel center, you may ignore it, because we use left-top part of FrameBuffer + * `inputClamp.zw` is bottom-right pixel center. + * + * ``` + * vec4 color = texture2D(uSampler, clamp(modifigedTextureCoord, inputClamp.xy, inputClamp.zw)) + * ``` + * OR + * ``` + * vec4 color = texture2D(uSampler, min(modifigedTextureCoord, inputClamp.zw)) + * ``` + * + * ### Additional Information + * + * Complete documentation on Filter usage is located in + * {@link https://github.com/pixijs/pixi.js/wiki/v5-Creating-filters Wiki}. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * * @class * @memberof PIXI * @extends PIXI.Shader diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js index 3174318..514ec32 100644 --- a/packages/core/src/filters/FilterSystem.js +++ b/packages/core/src/filters/FilterSystem.js @@ -78,8 +78,11 @@ * @type {UniformGroup} */ this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, + outputFrame: this.tempRect, + inputSize: new Float32Array(4), + inputPixel: new Float32Array(4), + inputClamp: new Float32Array(4), + resolution: 1, // legacy variables filterArea: new Float32Array(4), @@ -163,25 +166,39 @@ const globalUniforms = this.globalUniforms.uniforms; - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.outputFrame = state.sourceFrame; globalUniforms.resolution = state.resolution; + const inputSize = globalUniforms.inputSize; + const inputPixel = globalUniforms.inputPixel; + const inputClamp = globalUniforms.inputClamp; + + inputSize[0] = state.destinationFrame.width; + inputSize[1] = state.destinationFrame.height; + inputSize[2] = 1.0 / inputSize[0]; + inputSize[3] = 1.0 / inputSize[1]; + + inputPixel[0] = inputSize[0] * state.resolution; + inputPixel[1] = inputSize[1] * state.resolution; + inputPixel[2] = 1.0 / inputPixel[0]; + inputPixel[3] = 1.0 / inputPixel[1]; + + inputClamp[0] = 0.5 * inputPixel[2]; + inputClamp[1] = 0.5 * inputPixel[3]; + inputClamp[2] = (state.sourceFrame.width * inputSize[2]) - (0.5 * inputPixel[2]); + inputClamp[3] = (state.sourceFrame.height * inputSize[3]) - (0.5 * inputPixel[3]); + // only update the rect if its legacy.. if (state.legacy) { const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; filterArea[0] = state.destinationFrame.width; filterArea[1] = state.destinationFrame.height; filterArea[2] = state.sourceFrame.x; filterArea[3] = state.sourceFrame.y; - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + globalUniforms.filterClamp = globalUniforms.inputClamp; } this.globalUniforms.update(); diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js index 11b1806..a0060ed 100644 --- a/packages/filters/filter-blur/src/generateBlurVertSource.js +++ b/packages/filters/filter-blur/src/generateBlurVertSource.js @@ -3,29 +3,24 @@ uniform mat3 projectionMatrix; - uniform vec4 destinationFrame; - uniform vec4 sourceFrame; - uniform float strength; - uniform float resolution; varying vec2 vBlurTexCoords[%size%]; + uniform vec4 inputSize; + uniform vec4 inputClamp; + uniform vec4 outputFrame; + vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; - + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } - + vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); - } - - vec2 size( void ) - { - return ( (sourceFrame.zw -resolution) / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } void main(void) @@ -48,11 +43,11 @@ if (x) { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), inputClamp.zw);'; } else { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), inputClamp.zw);'; } for (let i = 0; i < kernelSize; i++) diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag index 8de1030..36d559c 100644 --- a/packages/filters/filter-displacement/src/displacement.frag +++ b/packages/filters/filter-displacement/src/displacement.frag @@ -1,20 +1,19 @@ varying vec2 vFilterCoord; varying vec2 vTextureCoord; -varying vec4 vFilterClamp; uniform vec2 scale; - uniform sampler2D uSampler; uniform sampler2D mapSampler; -uniform vec4 destinationFrame; +uniform highp vec4 inputSize; +uniform vec4 inputClamp; void main(void) { vec4 map = texture2D(mapSampler, vFilterCoord); map -= 0.5; - map.xy *= scale / destinationFrame.zw; + map.xy *= scale * inputSize.zw; - gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw)); + gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), inputClamp.xy, inputClamp.zw)); } diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 262b0b1..5d3539d 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -68,6 +68,13 @@ /** * This namespace contains WebGL-only display filters that can be applied * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * + * All filters must extend {@link PIXI.Filter}. + * * @example * // Create a new application * const app = new PIXI.Application(); diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js index 9d8cad8..2cceb3e 100644 --- a/packages/core/src/filters/Filter.js +++ b/packages/core/src/filters/Filter.js @@ -2,12 +2,138 @@ import Program from '../shader/Program'; import State from '../state/State'; import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; import defaultVertex from './defaultFilter.vert'; import defaultFragment from './defaultFilter.frag'; -// let math = require('../../math'); /** + * Filter is a special type of shader that is applied to the screen. + * {@link http://pixijs.io/examples/#/filters/blur-filter.js Example} of the + * {@link PIXI.filters.BlurFilter BlurFilter}. + * + * ### Usage + * Filters can be applied to any DisplayObject or Container. PixiJS' `FilterSystem` + * renders the container into temporary FrameBuffer, then filter + * renders it to the screen. Multiple filters can be added to the `filters` property + * and stacked on each other. + * + * ``` + * const filter = new PIXI.Filter(myShaderVert, myShaderFrag, { myUniform: 0.5 }); + * const container = new PIXI.Container(); + * container.filters = [filter]; + * ``` + * + * ### Previous Version Differences + * + * In PixiJS **v3**, a filter was always applied to _whole screen_. + * + * In PixiJS **v4**, a filter can be applied _only part of the screen_, developers + * had to create a set of uniforms to deal with coordinates. + * + * In PixiJS **v5** combines _both approaches_, developers can use normal coordinates of + * v3 and then allow filter to use partial FrameBuffers, bringing those extra + * uniforms into account. + * + * ### Built-in Uniforms + * + * PixiJS viewport uses screen (CSS) coordinates, `(0, 0, renderer.screen.width, renderer.screen.height)`, + * and `projectionMatrix` uniform maps it to the gl viewport. + * + * **uSampler** + * + * The most important uniform is the input texture that container was rendered into. + * _Important note: as with all PixiJS' FrameBuffers, both input and output are + * premultiplied by alpha._ + * + * By default, input FrameBuffer space coordinates are passed to fragment shader with `vTextureCoord`. + * Use it to sample the input. + * + * ``` + * const fragment = ` + * varying vec2 vTextureCoord; + * uniform sampler2D uSampler; + * void main(void) + * { + * gl_FragColor = texture2D(uSampler, vTextureCoord); + * } + * `; + * + * const myFilter = new PIXI.Filter(null, fragment); + * ``` + * + * This filter is just one uniform less than {@link PIXI.filters.AlphaFilter AlphaFilter}. + * + * **outputFrame** + * + * The `outputFrame` holds the rectangle where filter is applied in screen (CSS) coordinates. + * It's the same as `renderer.screen` for a fullscreen filter. + * Only a part of `outputFrame.zw` size of temporary FrameBuffer is used, + * `(0, 0, outputFrame.width, outputFrame.height)`, + * + * Filters uses this quad to normalized (0-1) space, its passed into `aVertexPosition` attribute. + * To calculate vertex position in screen space using normalized (0-1) space: + * + * ``` + * vec4 filterVertexPosition( void ) + * { + * vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + * return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + * } + * ``` + * + * **inputSize** + * + * Temporary FrameBuffer is different, it can be either the size of screen, either power-of-two. + * The `inputSize.xy` are size of temporary FrameBuffer that holds input. + * The `inputSize.zw` is inverted, it's a shortcut to evade division inside the shader. + * + * Set `inputSize.xy = outputFrame.zw` for a fullscreen filter. + * + * To calculate input texture coordinate in 0-1 space, you have to map it to FrameBuffer normalized space. + * Multiply by `outputFrame.zw` to get pixel coordinate in part of FrameBuffer. + * Divide by `inputSize.xy` to get FrameBuffer normalized space (input sampler space) + * + * ``` + * vec2 filterTextureCoord( void ) + * { + * return aVertexPosition * (outputFrame.zw * inputSize.zw); // same as /inputSize.xy + * } + * ``` + * **resolution** + * + * The `resolution` is the ratio of screen (CSS) pixels to real pixels. + * + * **inputPixel** + * + * `inputPixel.xy` is the size of framebuffer in real pixels, same as `inputSize.xy * resolution` + * `inputPixel.zw` is inverted `inputPixel.xy`. + * + * It's handy for filters that use neighbour pixels, like {@link PIXI.filters.FXAAFilter FXAAFilter}. + * + * **inputClamp** + * + * If you try to get info from outside of used part of FrameBuffer - you'll get undefined behaviour. + * For displacements, coordinates has to be clamped. + * + * The `inputClamp.xy` is left-top pixel center, you may ignore it, because we use left-top part of FrameBuffer + * `inputClamp.zw` is bottom-right pixel center. + * + * ``` + * vec4 color = texture2D(uSampler, clamp(modifigedTextureCoord, inputClamp.xy, inputClamp.zw)) + * ``` + * OR + * ``` + * vec4 color = texture2D(uSampler, min(modifigedTextureCoord, inputClamp.zw)) + * ``` + * + * ### Additional Information + * + * Complete documentation on Filter usage is located in + * {@link https://github.com/pixijs/pixi.js/wiki/v5-Creating-filters Wiki}. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * * @class * @memberof PIXI * @extends PIXI.Shader diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js index 3174318..514ec32 100644 --- a/packages/core/src/filters/FilterSystem.js +++ b/packages/core/src/filters/FilterSystem.js @@ -78,8 +78,11 @@ * @type {UniformGroup} */ this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, + outputFrame: this.tempRect, + inputSize: new Float32Array(4), + inputPixel: new Float32Array(4), + inputClamp: new Float32Array(4), + resolution: 1, // legacy variables filterArea: new Float32Array(4), @@ -163,25 +166,39 @@ const globalUniforms = this.globalUniforms.uniforms; - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.outputFrame = state.sourceFrame; globalUniforms.resolution = state.resolution; + const inputSize = globalUniforms.inputSize; + const inputPixel = globalUniforms.inputPixel; + const inputClamp = globalUniforms.inputClamp; + + inputSize[0] = state.destinationFrame.width; + inputSize[1] = state.destinationFrame.height; + inputSize[2] = 1.0 / inputSize[0]; + inputSize[3] = 1.0 / inputSize[1]; + + inputPixel[0] = inputSize[0] * state.resolution; + inputPixel[1] = inputSize[1] * state.resolution; + inputPixel[2] = 1.0 / inputPixel[0]; + inputPixel[3] = 1.0 / inputPixel[1]; + + inputClamp[0] = 0.5 * inputPixel[2]; + inputClamp[1] = 0.5 * inputPixel[3]; + inputClamp[2] = (state.sourceFrame.width * inputSize[2]) - (0.5 * inputPixel[2]); + inputClamp[3] = (state.sourceFrame.height * inputSize[3]) - (0.5 * inputPixel[3]); + // only update the rect if its legacy.. if (state.legacy) { const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; filterArea[0] = state.destinationFrame.width; filterArea[1] = state.destinationFrame.height; filterArea[2] = state.sourceFrame.x; filterArea[3] = state.sourceFrame.y; - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + globalUniforms.filterClamp = globalUniforms.inputClamp; } this.globalUniforms.update(); diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js index 11b1806..a0060ed 100644 --- a/packages/filters/filter-blur/src/generateBlurVertSource.js +++ b/packages/filters/filter-blur/src/generateBlurVertSource.js @@ -3,29 +3,24 @@ uniform mat3 projectionMatrix; - uniform vec4 destinationFrame; - uniform vec4 sourceFrame; - uniform float strength; - uniform float resolution; varying vec2 vBlurTexCoords[%size%]; + uniform vec4 inputSize; + uniform vec4 inputClamp; + uniform vec4 outputFrame; + vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; - + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } - + vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); - } - - vec2 size( void ) - { - return ( (sourceFrame.zw -resolution) / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } void main(void) @@ -48,11 +43,11 @@ if (x) { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), inputClamp.zw);'; } else { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), inputClamp.zw);'; } for (let i = 0; i < kernelSize; i++) diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag index 8de1030..36d559c 100644 --- a/packages/filters/filter-displacement/src/displacement.frag +++ b/packages/filters/filter-displacement/src/displacement.frag @@ -1,20 +1,19 @@ varying vec2 vFilterCoord; varying vec2 vTextureCoord; -varying vec4 vFilterClamp; uniform vec2 scale; - uniform sampler2D uSampler; uniform sampler2D mapSampler; -uniform vec4 destinationFrame; +uniform highp vec4 inputSize; +uniform vec4 inputClamp; void main(void) { vec4 map = texture2D(mapSampler, vFilterCoord); map -= 0.5; - map.xy *= scale / destinationFrame.zw; + map.xy *= scale * inputSize.zw; - gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw)); + gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), inputClamp.xy, inputClamp.zw)); } diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert index 0682a29..114c125 100755 --- a/packages/filters/filter-displacement/src/displacement.vert +++ b/packages/filters/filter-displacement/src/displacement.vert @@ -5,21 +5,20 @@ varying vec2 vTextureCoord; varying vec2 vFilterCoord; -varying vec4 vFilterClamp; -uniform vec4 destinationFrame; -uniform vec4 sourceFrame; +uniform vec4 inputSize; +uniform vec4 outputFrame; vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } void main(void) @@ -27,6 +26,4 @@ gl_Position = filterVertexPosition(); vTextureCoord = filterTextureCoord(); vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy; - - vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw; -} \ No newline at end of file +} diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 262b0b1..5d3539d 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -68,6 +68,13 @@ /** * This namespace contains WebGL-only display filters that can be applied * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * + * All filters must extend {@link PIXI.Filter}. + * * @example * // Create a new application * const app = new PIXI.Application(); diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js index 9d8cad8..2cceb3e 100644 --- a/packages/core/src/filters/Filter.js +++ b/packages/core/src/filters/Filter.js @@ -2,12 +2,138 @@ import Program from '../shader/Program'; import State from '../state/State'; import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; import defaultVertex from './defaultFilter.vert'; import defaultFragment from './defaultFilter.frag'; -// let math = require('../../math'); /** + * Filter is a special type of shader that is applied to the screen. + * {@link http://pixijs.io/examples/#/filters/blur-filter.js Example} of the + * {@link PIXI.filters.BlurFilter BlurFilter}. + * + * ### Usage + * Filters can be applied to any DisplayObject or Container. PixiJS' `FilterSystem` + * renders the container into temporary FrameBuffer, then filter + * renders it to the screen. Multiple filters can be added to the `filters` property + * and stacked on each other. + * + * ``` + * const filter = new PIXI.Filter(myShaderVert, myShaderFrag, { myUniform: 0.5 }); + * const container = new PIXI.Container(); + * container.filters = [filter]; + * ``` + * + * ### Previous Version Differences + * + * In PixiJS **v3**, a filter was always applied to _whole screen_. + * + * In PixiJS **v4**, a filter can be applied _only part of the screen_, developers + * had to create a set of uniforms to deal with coordinates. + * + * In PixiJS **v5** combines _both approaches_, developers can use normal coordinates of + * v3 and then allow filter to use partial FrameBuffers, bringing those extra + * uniforms into account. + * + * ### Built-in Uniforms + * + * PixiJS viewport uses screen (CSS) coordinates, `(0, 0, renderer.screen.width, renderer.screen.height)`, + * and `projectionMatrix` uniform maps it to the gl viewport. + * + * **uSampler** + * + * The most important uniform is the input texture that container was rendered into. + * _Important note: as with all PixiJS' FrameBuffers, both input and output are + * premultiplied by alpha._ + * + * By default, input FrameBuffer space coordinates are passed to fragment shader with `vTextureCoord`. + * Use it to sample the input. + * + * ``` + * const fragment = ` + * varying vec2 vTextureCoord; + * uniform sampler2D uSampler; + * void main(void) + * { + * gl_FragColor = texture2D(uSampler, vTextureCoord); + * } + * `; + * + * const myFilter = new PIXI.Filter(null, fragment); + * ``` + * + * This filter is just one uniform less than {@link PIXI.filters.AlphaFilter AlphaFilter}. + * + * **outputFrame** + * + * The `outputFrame` holds the rectangle where filter is applied in screen (CSS) coordinates. + * It's the same as `renderer.screen` for a fullscreen filter. + * Only a part of `outputFrame.zw` size of temporary FrameBuffer is used, + * `(0, 0, outputFrame.width, outputFrame.height)`, + * + * Filters uses this quad to normalized (0-1) space, its passed into `aVertexPosition` attribute. + * To calculate vertex position in screen space using normalized (0-1) space: + * + * ``` + * vec4 filterVertexPosition( void ) + * { + * vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + * return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + * } + * ``` + * + * **inputSize** + * + * Temporary FrameBuffer is different, it can be either the size of screen, either power-of-two. + * The `inputSize.xy` are size of temporary FrameBuffer that holds input. + * The `inputSize.zw` is inverted, it's a shortcut to evade division inside the shader. + * + * Set `inputSize.xy = outputFrame.zw` for a fullscreen filter. + * + * To calculate input texture coordinate in 0-1 space, you have to map it to FrameBuffer normalized space. + * Multiply by `outputFrame.zw` to get pixel coordinate in part of FrameBuffer. + * Divide by `inputSize.xy` to get FrameBuffer normalized space (input sampler space) + * + * ``` + * vec2 filterTextureCoord( void ) + * { + * return aVertexPosition * (outputFrame.zw * inputSize.zw); // same as /inputSize.xy + * } + * ``` + * **resolution** + * + * The `resolution` is the ratio of screen (CSS) pixels to real pixels. + * + * **inputPixel** + * + * `inputPixel.xy` is the size of framebuffer in real pixels, same as `inputSize.xy * resolution` + * `inputPixel.zw` is inverted `inputPixel.xy`. + * + * It's handy for filters that use neighbour pixels, like {@link PIXI.filters.FXAAFilter FXAAFilter}. + * + * **inputClamp** + * + * If you try to get info from outside of used part of FrameBuffer - you'll get undefined behaviour. + * For displacements, coordinates has to be clamped. + * + * The `inputClamp.xy` is left-top pixel center, you may ignore it, because we use left-top part of FrameBuffer + * `inputClamp.zw` is bottom-right pixel center. + * + * ``` + * vec4 color = texture2D(uSampler, clamp(modifigedTextureCoord, inputClamp.xy, inputClamp.zw)) + * ``` + * OR + * ``` + * vec4 color = texture2D(uSampler, min(modifigedTextureCoord, inputClamp.zw)) + * ``` + * + * ### Additional Information + * + * Complete documentation on Filter usage is located in + * {@link https://github.com/pixijs/pixi.js/wiki/v5-Creating-filters Wiki}. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * * @class * @memberof PIXI * @extends PIXI.Shader diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js index 3174318..514ec32 100644 --- a/packages/core/src/filters/FilterSystem.js +++ b/packages/core/src/filters/FilterSystem.js @@ -78,8 +78,11 @@ * @type {UniformGroup} */ this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, + outputFrame: this.tempRect, + inputSize: new Float32Array(4), + inputPixel: new Float32Array(4), + inputClamp: new Float32Array(4), + resolution: 1, // legacy variables filterArea: new Float32Array(4), @@ -163,25 +166,39 @@ const globalUniforms = this.globalUniforms.uniforms; - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.outputFrame = state.sourceFrame; globalUniforms.resolution = state.resolution; + const inputSize = globalUniforms.inputSize; + const inputPixel = globalUniforms.inputPixel; + const inputClamp = globalUniforms.inputClamp; + + inputSize[0] = state.destinationFrame.width; + inputSize[1] = state.destinationFrame.height; + inputSize[2] = 1.0 / inputSize[0]; + inputSize[3] = 1.0 / inputSize[1]; + + inputPixel[0] = inputSize[0] * state.resolution; + inputPixel[1] = inputSize[1] * state.resolution; + inputPixel[2] = 1.0 / inputPixel[0]; + inputPixel[3] = 1.0 / inputPixel[1]; + + inputClamp[0] = 0.5 * inputPixel[2]; + inputClamp[1] = 0.5 * inputPixel[3]; + inputClamp[2] = (state.sourceFrame.width * inputSize[2]) - (0.5 * inputPixel[2]); + inputClamp[3] = (state.sourceFrame.height * inputSize[3]) - (0.5 * inputPixel[3]); + // only update the rect if its legacy.. if (state.legacy) { const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; filterArea[0] = state.destinationFrame.width; filterArea[1] = state.destinationFrame.height; filterArea[2] = state.sourceFrame.x; filterArea[3] = state.sourceFrame.y; - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + globalUniforms.filterClamp = globalUniforms.inputClamp; } this.globalUniforms.update(); diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js index 11b1806..a0060ed 100644 --- a/packages/filters/filter-blur/src/generateBlurVertSource.js +++ b/packages/filters/filter-blur/src/generateBlurVertSource.js @@ -3,29 +3,24 @@ uniform mat3 projectionMatrix; - uniform vec4 destinationFrame; - uniform vec4 sourceFrame; - uniform float strength; - uniform float resolution; varying vec2 vBlurTexCoords[%size%]; + uniform vec4 inputSize; + uniform vec4 inputClamp; + uniform vec4 outputFrame; + vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; - + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } - + vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); - } - - vec2 size( void ) - { - return ( (sourceFrame.zw -resolution) / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } void main(void) @@ -48,11 +43,11 @@ if (x) { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), inputClamp.zw);'; } else { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), inputClamp.zw);'; } for (let i = 0; i < kernelSize; i++) diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag index 8de1030..36d559c 100644 --- a/packages/filters/filter-displacement/src/displacement.frag +++ b/packages/filters/filter-displacement/src/displacement.frag @@ -1,20 +1,19 @@ varying vec2 vFilterCoord; varying vec2 vTextureCoord; -varying vec4 vFilterClamp; uniform vec2 scale; - uniform sampler2D uSampler; uniform sampler2D mapSampler; -uniform vec4 destinationFrame; +uniform highp vec4 inputSize; +uniform vec4 inputClamp; void main(void) { vec4 map = texture2D(mapSampler, vFilterCoord); map -= 0.5; - map.xy *= scale / destinationFrame.zw; + map.xy *= scale * inputSize.zw; - gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw)); + gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), inputClamp.xy, inputClamp.zw)); } diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert index 0682a29..114c125 100755 --- a/packages/filters/filter-displacement/src/displacement.vert +++ b/packages/filters/filter-displacement/src/displacement.vert @@ -5,21 +5,20 @@ varying vec2 vTextureCoord; varying vec2 vFilterCoord; -varying vec4 vFilterClamp; -uniform vec4 destinationFrame; -uniform vec4 sourceFrame; +uniform vec4 inputSize; +uniform vec4 outputFrame; vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } void main(void) @@ -27,6 +26,4 @@ gl_Position = filterVertexPosition(); vTextureCoord = filterTextureCoord(); vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy; - - vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw; -} \ No newline at end of file +} diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag index 98e06c6..c7f7d90 100644 --- a/packages/filters/filter-fxaa/src/fxaa.frag +++ b/packages/filters/filter-fxaa/src/fxaa.frag @@ -4,9 +4,9 @@ varying vec2 v_rgbSE; varying vec2 v_rgbM; -varying vec2 vTextureCoord; +varying vec2 vFragCoord; uniform sampler2D uSampler; -uniform vec4 destinationFrame; +uniform highp vec4 inputPixel; /** @@ -64,12 +64,11 @@ //optimized version for mobile, where dependent //texture reads can be a bottleneck -vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 resolution, +vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 inverseVP, vec2 v_rgbNW, vec2 v_rgbNE, vec2 v_rgbSW, vec2 v_rgbSE, vec2 v_rgbM) { vec4 color; - mediump vec2 inverseVP = vec2(1.0 / resolution.x, 1.0 / resolution.y); vec3 rgbNW = texture2D(tex, v_rgbNW).xyz; vec3 rgbNE = texture2D(tex, v_rgbNE).xyz; vec3 rgbSW = texture2D(tex, v_rgbSW).xyz; @@ -114,11 +113,9 @@ void main() { - vec2 fragCoord = vTextureCoord * destinationFrame.zw; - vec4 color; - color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); + color = fxaa(uSampler, vFragCoord, inputPixel.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); gl_FragColor = color; } diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 262b0b1..5d3539d 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -68,6 +68,13 @@ /** * This namespace contains WebGL-only display filters that can be applied * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * + * All filters must extend {@link PIXI.Filter}. + * * @example * // Create a new application * const app = new PIXI.Application(); diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js index 9d8cad8..2cceb3e 100644 --- a/packages/core/src/filters/Filter.js +++ b/packages/core/src/filters/Filter.js @@ -2,12 +2,138 @@ import Program from '../shader/Program'; import State from '../state/State'; import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; import defaultVertex from './defaultFilter.vert'; import defaultFragment from './defaultFilter.frag'; -// let math = require('../../math'); /** + * Filter is a special type of shader that is applied to the screen. + * {@link http://pixijs.io/examples/#/filters/blur-filter.js Example} of the + * {@link PIXI.filters.BlurFilter BlurFilter}. + * + * ### Usage + * Filters can be applied to any DisplayObject or Container. PixiJS' `FilterSystem` + * renders the container into temporary FrameBuffer, then filter + * renders it to the screen. Multiple filters can be added to the `filters` property + * and stacked on each other. + * + * ``` + * const filter = new PIXI.Filter(myShaderVert, myShaderFrag, { myUniform: 0.5 }); + * const container = new PIXI.Container(); + * container.filters = [filter]; + * ``` + * + * ### Previous Version Differences + * + * In PixiJS **v3**, a filter was always applied to _whole screen_. + * + * In PixiJS **v4**, a filter can be applied _only part of the screen_, developers + * had to create a set of uniforms to deal with coordinates. + * + * In PixiJS **v5** combines _both approaches_, developers can use normal coordinates of + * v3 and then allow filter to use partial FrameBuffers, bringing those extra + * uniforms into account. + * + * ### Built-in Uniforms + * + * PixiJS viewport uses screen (CSS) coordinates, `(0, 0, renderer.screen.width, renderer.screen.height)`, + * and `projectionMatrix` uniform maps it to the gl viewport. + * + * **uSampler** + * + * The most important uniform is the input texture that container was rendered into. + * _Important note: as with all PixiJS' FrameBuffers, both input and output are + * premultiplied by alpha._ + * + * By default, input FrameBuffer space coordinates are passed to fragment shader with `vTextureCoord`. + * Use it to sample the input. + * + * ``` + * const fragment = ` + * varying vec2 vTextureCoord; + * uniform sampler2D uSampler; + * void main(void) + * { + * gl_FragColor = texture2D(uSampler, vTextureCoord); + * } + * `; + * + * const myFilter = new PIXI.Filter(null, fragment); + * ``` + * + * This filter is just one uniform less than {@link PIXI.filters.AlphaFilter AlphaFilter}. + * + * **outputFrame** + * + * The `outputFrame` holds the rectangle where filter is applied in screen (CSS) coordinates. + * It's the same as `renderer.screen` for a fullscreen filter. + * Only a part of `outputFrame.zw` size of temporary FrameBuffer is used, + * `(0, 0, outputFrame.width, outputFrame.height)`, + * + * Filters uses this quad to normalized (0-1) space, its passed into `aVertexPosition` attribute. + * To calculate vertex position in screen space using normalized (0-1) space: + * + * ``` + * vec4 filterVertexPosition( void ) + * { + * vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + * return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + * } + * ``` + * + * **inputSize** + * + * Temporary FrameBuffer is different, it can be either the size of screen, either power-of-two. + * The `inputSize.xy` are size of temporary FrameBuffer that holds input. + * The `inputSize.zw` is inverted, it's a shortcut to evade division inside the shader. + * + * Set `inputSize.xy = outputFrame.zw` for a fullscreen filter. + * + * To calculate input texture coordinate in 0-1 space, you have to map it to FrameBuffer normalized space. + * Multiply by `outputFrame.zw` to get pixel coordinate in part of FrameBuffer. + * Divide by `inputSize.xy` to get FrameBuffer normalized space (input sampler space) + * + * ``` + * vec2 filterTextureCoord( void ) + * { + * return aVertexPosition * (outputFrame.zw * inputSize.zw); // same as /inputSize.xy + * } + * ``` + * **resolution** + * + * The `resolution` is the ratio of screen (CSS) pixels to real pixels. + * + * **inputPixel** + * + * `inputPixel.xy` is the size of framebuffer in real pixels, same as `inputSize.xy * resolution` + * `inputPixel.zw` is inverted `inputPixel.xy`. + * + * It's handy for filters that use neighbour pixels, like {@link PIXI.filters.FXAAFilter FXAAFilter}. + * + * **inputClamp** + * + * If you try to get info from outside of used part of FrameBuffer - you'll get undefined behaviour. + * For displacements, coordinates has to be clamped. + * + * The `inputClamp.xy` is left-top pixel center, you may ignore it, because we use left-top part of FrameBuffer + * `inputClamp.zw` is bottom-right pixel center. + * + * ``` + * vec4 color = texture2D(uSampler, clamp(modifigedTextureCoord, inputClamp.xy, inputClamp.zw)) + * ``` + * OR + * ``` + * vec4 color = texture2D(uSampler, min(modifigedTextureCoord, inputClamp.zw)) + * ``` + * + * ### Additional Information + * + * Complete documentation on Filter usage is located in + * {@link https://github.com/pixijs/pixi.js/wiki/v5-Creating-filters Wiki}. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * * @class * @memberof PIXI * @extends PIXI.Shader diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js index 3174318..514ec32 100644 --- a/packages/core/src/filters/FilterSystem.js +++ b/packages/core/src/filters/FilterSystem.js @@ -78,8 +78,11 @@ * @type {UniformGroup} */ this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, + outputFrame: this.tempRect, + inputSize: new Float32Array(4), + inputPixel: new Float32Array(4), + inputClamp: new Float32Array(4), + resolution: 1, // legacy variables filterArea: new Float32Array(4), @@ -163,25 +166,39 @@ const globalUniforms = this.globalUniforms.uniforms; - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.outputFrame = state.sourceFrame; globalUniforms.resolution = state.resolution; + const inputSize = globalUniforms.inputSize; + const inputPixel = globalUniforms.inputPixel; + const inputClamp = globalUniforms.inputClamp; + + inputSize[0] = state.destinationFrame.width; + inputSize[1] = state.destinationFrame.height; + inputSize[2] = 1.0 / inputSize[0]; + inputSize[3] = 1.0 / inputSize[1]; + + inputPixel[0] = inputSize[0] * state.resolution; + inputPixel[1] = inputSize[1] * state.resolution; + inputPixel[2] = 1.0 / inputPixel[0]; + inputPixel[3] = 1.0 / inputPixel[1]; + + inputClamp[0] = 0.5 * inputPixel[2]; + inputClamp[1] = 0.5 * inputPixel[3]; + inputClamp[2] = (state.sourceFrame.width * inputSize[2]) - (0.5 * inputPixel[2]); + inputClamp[3] = (state.sourceFrame.height * inputSize[3]) - (0.5 * inputPixel[3]); + // only update the rect if its legacy.. if (state.legacy) { const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; filterArea[0] = state.destinationFrame.width; filterArea[1] = state.destinationFrame.height; filterArea[2] = state.sourceFrame.x; filterArea[3] = state.sourceFrame.y; - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + globalUniforms.filterClamp = globalUniforms.inputClamp; } this.globalUniforms.update(); diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js index 11b1806..a0060ed 100644 --- a/packages/filters/filter-blur/src/generateBlurVertSource.js +++ b/packages/filters/filter-blur/src/generateBlurVertSource.js @@ -3,29 +3,24 @@ uniform mat3 projectionMatrix; - uniform vec4 destinationFrame; - uniform vec4 sourceFrame; - uniform float strength; - uniform float resolution; varying vec2 vBlurTexCoords[%size%]; + uniform vec4 inputSize; + uniform vec4 inputClamp; + uniform vec4 outputFrame; + vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; - + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } - + vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); - } - - vec2 size( void ) - { - return ( (sourceFrame.zw -resolution) / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } void main(void) @@ -48,11 +43,11 @@ if (x) { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), inputClamp.zw);'; } else { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), inputClamp.zw);'; } for (let i = 0; i < kernelSize; i++) diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag index 8de1030..36d559c 100644 --- a/packages/filters/filter-displacement/src/displacement.frag +++ b/packages/filters/filter-displacement/src/displacement.frag @@ -1,20 +1,19 @@ varying vec2 vFilterCoord; varying vec2 vTextureCoord; -varying vec4 vFilterClamp; uniform vec2 scale; - uniform sampler2D uSampler; uniform sampler2D mapSampler; -uniform vec4 destinationFrame; +uniform highp vec4 inputSize; +uniform vec4 inputClamp; void main(void) { vec4 map = texture2D(mapSampler, vFilterCoord); map -= 0.5; - map.xy *= scale / destinationFrame.zw; + map.xy *= scale * inputSize.zw; - gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw)); + gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), inputClamp.xy, inputClamp.zw)); } diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert index 0682a29..114c125 100755 --- a/packages/filters/filter-displacement/src/displacement.vert +++ b/packages/filters/filter-displacement/src/displacement.vert @@ -5,21 +5,20 @@ varying vec2 vTextureCoord; varying vec2 vFilterCoord; -varying vec4 vFilterClamp; -uniform vec4 destinationFrame; -uniform vec4 sourceFrame; +uniform vec4 inputSize; +uniform vec4 outputFrame; vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } void main(void) @@ -27,6 +26,4 @@ gl_Position = filterVertexPosition(); vTextureCoord = filterTextureCoord(); vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy; - - vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw; -} \ No newline at end of file +} diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag index 98e06c6..c7f7d90 100644 --- a/packages/filters/filter-fxaa/src/fxaa.frag +++ b/packages/filters/filter-fxaa/src/fxaa.frag @@ -4,9 +4,9 @@ varying vec2 v_rgbSE; varying vec2 v_rgbM; -varying vec2 vTextureCoord; +varying vec2 vFragCoord; uniform sampler2D uSampler; -uniform vec4 destinationFrame; +uniform highp vec4 inputPixel; /** @@ -64,12 +64,11 @@ //optimized version for mobile, where dependent //texture reads can be a bottleneck -vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 resolution, +vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 inverseVP, vec2 v_rgbNW, vec2 v_rgbNE, vec2 v_rgbSW, vec2 v_rgbSE, vec2 v_rgbM) { vec4 color; - mediump vec2 inverseVP = vec2(1.0 / resolution.x, 1.0 / resolution.y); vec3 rgbNW = texture2D(tex, v_rgbNW).xyz; vec3 rgbNE = texture2D(tex, v_rgbNE).xyz; vec3 rgbSW = texture2D(tex, v_rgbSW).xyz; @@ -114,11 +113,9 @@ void main() { - vec2 fragCoord = vTextureCoord * destinationFrame.zw; - vec4 color; - color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); + color = fxaa(uSampler, vFragCoord, inputPixel.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); gl_FragColor = color; } diff --git a/packages/filters/filter-fxaa/src/fxaa.vert b/packages/filters/filter-fxaa/src/fxaa.vert index 3ef457c..3f83328 100644 --- a/packages/filters/filter-fxaa/src/fxaa.vert +++ b/packages/filters/filter-fxaa/src/fxaa.vert @@ -9,28 +9,22 @@ varying vec2 v_rgbSE; varying vec2 v_rgbM; -uniform vec4 destinationFrame; -uniform vec4 sourceFrame; +varying vec2 vFragCoord; -varying vec2 vTextureCoord; +uniform vec4 inputPixel; +uniform vec4 outputFrame; vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } -vec2 filterTextureCoord( void ) -{ - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); -} - -void texcoords(vec2 fragCoord, vec2 resolution, +void texcoords(vec2 fragCoord, vec2 inverseVP, out vec2 v_rgbNW, out vec2 v_rgbNE, out vec2 v_rgbSW, out vec2 v_rgbSE, out vec2 v_rgbM) { - vec2 inverseVP = 1.0 / resolution.xy; v_rgbNW = (fragCoord + vec2(-1.0, -1.0)) * inverseVP; v_rgbNE = (fragCoord + vec2(1.0, -1.0)) * inverseVP; v_rgbSW = (fragCoord + vec2(-1.0, 1.0)) * inverseVP; @@ -42,9 +36,7 @@ gl_Position = filterVertexPosition(); - vTextureCoord = filterTextureCoord(); + vFragCoord = aVertexPosition * outputFrame.zw; - vec2 fragCoord = vTextureCoord * destinationFrame.zw; - - texcoords(fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); -} \ No newline at end of file + texcoords(vFragCoord, inputPixel.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); +} diff --git a/bundles/pixi.js/src/index.js b/bundles/pixi.js/src/index.js index 262b0b1..5d3539d 100644 --- a/bundles/pixi.js/src/index.js +++ b/bundles/pixi.js/src/index.js @@ -68,6 +68,13 @@ /** * This namespace contains WebGL-only display filters that can be applied * to DisplayObjects using the {@link PIXI.DisplayObject#filters filters} property. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * + * All filters must extend {@link PIXI.Filter}. + * * @example * // Create a new application * const app = new PIXI.Application(); diff --git a/packages/core/src/filters/Filter.js b/packages/core/src/filters/Filter.js index 9d8cad8..2cceb3e 100644 --- a/packages/core/src/filters/Filter.js +++ b/packages/core/src/filters/Filter.js @@ -2,12 +2,138 @@ import Program from '../shader/Program'; import State from '../state/State'; import { settings } from '@pixi/settings'; -// import extractUniformsFromSrc from './extractUniformsFromSrc'; import defaultVertex from './defaultFilter.vert'; import defaultFragment from './defaultFilter.frag'; -// let math = require('../../math'); /** + * Filter is a special type of shader that is applied to the screen. + * {@link http://pixijs.io/examples/#/filters/blur-filter.js Example} of the + * {@link PIXI.filters.BlurFilter BlurFilter}. + * + * ### Usage + * Filters can be applied to any DisplayObject or Container. PixiJS' `FilterSystem` + * renders the container into temporary FrameBuffer, then filter + * renders it to the screen. Multiple filters can be added to the `filters` property + * and stacked on each other. + * + * ``` + * const filter = new PIXI.Filter(myShaderVert, myShaderFrag, { myUniform: 0.5 }); + * const container = new PIXI.Container(); + * container.filters = [filter]; + * ``` + * + * ### Previous Version Differences + * + * In PixiJS **v3**, a filter was always applied to _whole screen_. + * + * In PixiJS **v4**, a filter can be applied _only part of the screen_, developers + * had to create a set of uniforms to deal with coordinates. + * + * In PixiJS **v5** combines _both approaches_, developers can use normal coordinates of + * v3 and then allow filter to use partial FrameBuffers, bringing those extra + * uniforms into account. + * + * ### Built-in Uniforms + * + * PixiJS viewport uses screen (CSS) coordinates, `(0, 0, renderer.screen.width, renderer.screen.height)`, + * and `projectionMatrix` uniform maps it to the gl viewport. + * + * **uSampler** + * + * The most important uniform is the input texture that container was rendered into. + * _Important note: as with all PixiJS' FrameBuffers, both input and output are + * premultiplied by alpha._ + * + * By default, input FrameBuffer space coordinates are passed to fragment shader with `vTextureCoord`. + * Use it to sample the input. + * + * ``` + * const fragment = ` + * varying vec2 vTextureCoord; + * uniform sampler2D uSampler; + * void main(void) + * { + * gl_FragColor = texture2D(uSampler, vTextureCoord); + * } + * `; + * + * const myFilter = new PIXI.Filter(null, fragment); + * ``` + * + * This filter is just one uniform less than {@link PIXI.filters.AlphaFilter AlphaFilter}. + * + * **outputFrame** + * + * The `outputFrame` holds the rectangle where filter is applied in screen (CSS) coordinates. + * It's the same as `renderer.screen` for a fullscreen filter. + * Only a part of `outputFrame.zw` size of temporary FrameBuffer is used, + * `(0, 0, outputFrame.width, outputFrame.height)`, + * + * Filters uses this quad to normalized (0-1) space, its passed into `aVertexPosition` attribute. + * To calculate vertex position in screen space using normalized (0-1) space: + * + * ``` + * vec4 filterVertexPosition( void ) + * { + * vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + * return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + * } + * ``` + * + * **inputSize** + * + * Temporary FrameBuffer is different, it can be either the size of screen, either power-of-two. + * The `inputSize.xy` are size of temporary FrameBuffer that holds input. + * The `inputSize.zw` is inverted, it's a shortcut to evade division inside the shader. + * + * Set `inputSize.xy = outputFrame.zw` for a fullscreen filter. + * + * To calculate input texture coordinate in 0-1 space, you have to map it to FrameBuffer normalized space. + * Multiply by `outputFrame.zw` to get pixel coordinate in part of FrameBuffer. + * Divide by `inputSize.xy` to get FrameBuffer normalized space (input sampler space) + * + * ``` + * vec2 filterTextureCoord( void ) + * { + * return aVertexPosition * (outputFrame.zw * inputSize.zw); // same as /inputSize.xy + * } + * ``` + * **resolution** + * + * The `resolution` is the ratio of screen (CSS) pixels to real pixels. + * + * **inputPixel** + * + * `inputPixel.xy` is the size of framebuffer in real pixels, same as `inputSize.xy * resolution` + * `inputPixel.zw` is inverted `inputPixel.xy`. + * + * It's handy for filters that use neighbour pixels, like {@link PIXI.filters.FXAAFilter FXAAFilter}. + * + * **inputClamp** + * + * If you try to get info from outside of used part of FrameBuffer - you'll get undefined behaviour. + * For displacements, coordinates has to be clamped. + * + * The `inputClamp.xy` is left-top pixel center, you may ignore it, because we use left-top part of FrameBuffer + * `inputClamp.zw` is bottom-right pixel center. + * + * ``` + * vec4 color = texture2D(uSampler, clamp(modifigedTextureCoord, inputClamp.xy, inputClamp.zw)) + * ``` + * OR + * ``` + * vec4 color = texture2D(uSampler, min(modifigedTextureCoord, inputClamp.zw)) + * ``` + * + * ### Additional Information + * + * Complete documentation on Filter usage is located in + * {@link https://github.com/pixijs/pixi.js/wiki/v5-Creating-filters Wiki}. + * + * Since PixiJS only had a handful of built-in filters, additional filters + * can be downloaded {@link https://github.com/pixijs/pixi-filters here} from the + * PixiJS Filters repository. + * * @class * @memberof PIXI * @extends PIXI.Shader diff --git a/packages/core/src/filters/FilterSystem.js b/packages/core/src/filters/FilterSystem.js index 3174318..514ec32 100644 --- a/packages/core/src/filters/FilterSystem.js +++ b/packages/core/src/filters/FilterSystem.js @@ -78,8 +78,11 @@ * @type {UniformGroup} */ this.globalUniforms = new UniformGroup({ - sourceFrame: this.tempRect, - destinationFrame: this.tempRect, + outputFrame: this.tempRect, + inputSize: new Float32Array(4), + inputPixel: new Float32Array(4), + inputClamp: new Float32Array(4), + resolution: 1, // legacy variables filterArea: new Float32Array(4), @@ -163,25 +166,39 @@ const globalUniforms = this.globalUniforms.uniforms; - globalUniforms.sourceFrame = state.sourceFrame; - globalUniforms.destinationFrame = state.destinationFrame; + globalUniforms.outputFrame = state.sourceFrame; globalUniforms.resolution = state.resolution; + const inputSize = globalUniforms.inputSize; + const inputPixel = globalUniforms.inputPixel; + const inputClamp = globalUniforms.inputClamp; + + inputSize[0] = state.destinationFrame.width; + inputSize[1] = state.destinationFrame.height; + inputSize[2] = 1.0 / inputSize[0]; + inputSize[3] = 1.0 / inputSize[1]; + + inputPixel[0] = inputSize[0] * state.resolution; + inputPixel[1] = inputSize[1] * state.resolution; + inputPixel[2] = 1.0 / inputPixel[0]; + inputPixel[3] = 1.0 / inputPixel[1]; + + inputClamp[0] = 0.5 * inputPixel[2]; + inputClamp[1] = 0.5 * inputPixel[3]; + inputClamp[2] = (state.sourceFrame.width * inputSize[2]) - (0.5 * inputPixel[2]); + inputClamp[3] = (state.sourceFrame.height * inputSize[3]) - (0.5 * inputPixel[3]); + // only update the rect if its legacy.. if (state.legacy) { const filterArea = globalUniforms.filterArea; - const filterClamp = globalUniforms.filterClamp; filterArea[0] = state.destinationFrame.width; filterArea[1] = state.destinationFrame.height; filterArea[2] = state.sourceFrame.x; filterArea[3] = state.sourceFrame.y; - filterClamp[0] = 0.5 / state.resolution / state.destinationFrame.width; - filterClamp[1] = 0.5 / state.resolution / state.destinationFrame.height; - filterClamp[2] = (state.sourceFrame.width - 0.5) / state.resolution / state.destinationFrame.width; - filterClamp[3] = (state.sourceFrame.height - 0.5) / state.resolution / state.destinationFrame.height; + globalUniforms.filterClamp = globalUniforms.inputClamp; } this.globalUniforms.update(); diff --git a/packages/filters/filter-blur/src/generateBlurVertSource.js b/packages/filters/filter-blur/src/generateBlurVertSource.js index 11b1806..a0060ed 100644 --- a/packages/filters/filter-blur/src/generateBlurVertSource.js +++ b/packages/filters/filter-blur/src/generateBlurVertSource.js @@ -3,29 +3,24 @@ uniform mat3 projectionMatrix; - uniform vec4 destinationFrame; - uniform vec4 sourceFrame; - uniform float strength; - uniform float resolution; varying vec2 vBlurTexCoords[%size%]; + uniform vec4 inputSize; + uniform vec4 inputClamp; + uniform vec4 outputFrame; + vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; - + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } - + vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); - } - - vec2 size( void ) - { - return ( (sourceFrame.zw -resolution) / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } void main(void) @@ -48,11 +43,11 @@ if (x) { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(%sampleIndex% * strength, 0.0), inputClamp.zw);'; } else { - template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), size());'; + template = 'vBlurTexCoords[%index%] = min( textureCoord + vec2(0.0, %sampleIndex% * strength), inputClamp.zw);'; } for (let i = 0; i < kernelSize; i++) diff --git a/packages/filters/filter-displacement/src/displacement.frag b/packages/filters/filter-displacement/src/displacement.frag index 8de1030..36d559c 100644 --- a/packages/filters/filter-displacement/src/displacement.frag +++ b/packages/filters/filter-displacement/src/displacement.frag @@ -1,20 +1,19 @@ varying vec2 vFilterCoord; varying vec2 vTextureCoord; -varying vec4 vFilterClamp; uniform vec2 scale; - uniform sampler2D uSampler; uniform sampler2D mapSampler; -uniform vec4 destinationFrame; +uniform highp vec4 inputSize; +uniform vec4 inputClamp; void main(void) { vec4 map = texture2D(mapSampler, vFilterCoord); map -= 0.5; - map.xy *= scale / destinationFrame.zw; + map.xy *= scale * inputSize.zw; - gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), vFilterClamp.xy, vFilterClamp.zw)); + gl_FragColor = texture2D(uSampler, clamp(vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y), inputClamp.xy, inputClamp.zw)); } diff --git a/packages/filters/filter-displacement/src/displacement.vert b/packages/filters/filter-displacement/src/displacement.vert index 0682a29..114c125 100755 --- a/packages/filters/filter-displacement/src/displacement.vert +++ b/packages/filters/filter-displacement/src/displacement.vert @@ -5,21 +5,20 @@ varying vec2 vTextureCoord; varying vec2 vFilterCoord; -varying vec4 vFilterClamp; -uniform vec4 destinationFrame; -uniform vec4 sourceFrame; +uniform vec4 inputSize; +uniform vec4 outputFrame; vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } void main(void) @@ -27,6 +26,4 @@ gl_Position = filterVertexPosition(); vTextureCoord = filterTextureCoord(); vFilterCoord = ( filterMatrix * vec3( vTextureCoord, 1.0) ).xy; - - vFilterClamp.zw = (sourceFrame.zw - 1.) / destinationFrame.zw; -} \ No newline at end of file +} diff --git a/packages/filters/filter-fxaa/src/fxaa.frag b/packages/filters/filter-fxaa/src/fxaa.frag index 98e06c6..c7f7d90 100644 --- a/packages/filters/filter-fxaa/src/fxaa.frag +++ b/packages/filters/filter-fxaa/src/fxaa.frag @@ -4,9 +4,9 @@ varying vec2 v_rgbSE; varying vec2 v_rgbM; -varying vec2 vTextureCoord; +varying vec2 vFragCoord; uniform sampler2D uSampler; -uniform vec4 destinationFrame; +uniform highp vec4 inputPixel; /** @@ -64,12 +64,11 @@ //optimized version for mobile, where dependent //texture reads can be a bottleneck -vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 resolution, +vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 inverseVP, vec2 v_rgbNW, vec2 v_rgbNE, vec2 v_rgbSW, vec2 v_rgbSE, vec2 v_rgbM) { vec4 color; - mediump vec2 inverseVP = vec2(1.0 / resolution.x, 1.0 / resolution.y); vec3 rgbNW = texture2D(tex, v_rgbNW).xyz; vec3 rgbNE = texture2D(tex, v_rgbNE).xyz; vec3 rgbSW = texture2D(tex, v_rgbSW).xyz; @@ -114,11 +113,9 @@ void main() { - vec2 fragCoord = vTextureCoord * destinationFrame.zw; - vec4 color; - color = fxaa(uSampler, fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); + color = fxaa(uSampler, vFragCoord, inputPixel.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); gl_FragColor = color; } diff --git a/packages/filters/filter-fxaa/src/fxaa.vert b/packages/filters/filter-fxaa/src/fxaa.vert index 3ef457c..3f83328 100644 --- a/packages/filters/filter-fxaa/src/fxaa.vert +++ b/packages/filters/filter-fxaa/src/fxaa.vert @@ -9,28 +9,22 @@ varying vec2 v_rgbSE; varying vec2 v_rgbM; -uniform vec4 destinationFrame; -uniform vec4 sourceFrame; +varying vec2 vFragCoord; -varying vec2 vTextureCoord; +uniform vec4 inputPixel; +uniform vec4 outputFrame; vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } -vec2 filterTextureCoord( void ) -{ - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); -} - -void texcoords(vec2 fragCoord, vec2 resolution, +void texcoords(vec2 fragCoord, vec2 inverseVP, out vec2 v_rgbNW, out vec2 v_rgbNE, out vec2 v_rgbSW, out vec2 v_rgbSE, out vec2 v_rgbM) { - vec2 inverseVP = 1.0 / resolution.xy; v_rgbNW = (fragCoord + vec2(-1.0, -1.0)) * inverseVP; v_rgbNE = (fragCoord + vec2(1.0, -1.0)) * inverseVP; v_rgbSW = (fragCoord + vec2(-1.0, 1.0)) * inverseVP; @@ -42,9 +36,7 @@ gl_Position = filterVertexPosition(); - vTextureCoord = filterTextureCoord(); + vFragCoord = aVertexPosition * outputFrame.zw; - vec2 fragCoord = vTextureCoord * destinationFrame.zw; - - texcoords(fragCoord, destinationFrame.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); -} \ No newline at end of file + texcoords(vFragCoord, inputPixel.zw, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); +} diff --git a/packages/fragments/src/defaultFilter.vert b/packages/fragments/src/defaultFilter.vert index 2e63617..66dd459 100644 --- a/packages/fragments/src/defaultFilter.vert +++ b/packages/fragments/src/defaultFilter.vert @@ -1,26 +1,26 @@ attribute vec2 aVertexPosition; uniform mat3 projectionMatrix; -uniform vec4 sourceFrame; -uniform vec4 destinationFrame; varying vec2 vTextureCoord; +uniform vec4 inputSize; +uniform vec4 outputFrame; + vec4 filterVertexPosition( void ) { - vec2 position = aVertexPosition * max(sourceFrame.zw, vec2(0.)) + sourceFrame.xy; + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); } vec2 filterTextureCoord( void ) { - return aVertexPosition * (sourceFrame.zw / destinationFrame.zw); + return aVertexPosition * (outputFrame.zw * inputSize.zw); } - void main(void) { gl_Position = filterVertexPosition(); vTextureCoord = filterTextureCoord(); -} \ No newline at end of file +}