diff --git a/filters/rgb-split/src/RGBSplitFilter.ts b/filters/rgb-split/src/RGBSplitFilter.ts index 7dd856802..d6f9ccd52 100644 --- a/filters/rgb-split/src/RGBSplitFilter.ts +++ b/filters/rgb-split/src/RGBSplitFilter.ts @@ -1,10 +1,29 @@ -import { vertex } from '@tools/fragments'; +import { vertex, wgslVertex } from '@tools/fragments'; import fragment from './rgb-split.frag'; -import { Filter, GlProgram } from 'pixi.js'; -import type { Point } from 'pixi.js'; +import source from './rgb-split.wgsl'; +import { Filter, GlProgram, GpuProgram, UniformGroup, Point } from 'pixi.js'; type Offset = [number, number] | Point; +export interface RGBSplitFilterOptions +{ + /** + * The amount of offset for the red channel. + * @default [-10,0] + */ + red: Offset; + /** + * The amount of offset for the green channel. + * @default [0,10] + */ + green: Offset; + /** + * The amount of offset for the blue channel. + * @default [0,0] + */ + blue: Offset; +} + /** * An RGB Split Filter.
* ![original](../tools/screenshots/dist/original.png)![filter](../tools/screenshots/dist/rgb.png) @@ -16,13 +35,39 @@ type Offset = [number, number] | Point; */ export class RGBSplitFilter extends Filter { + /** Default values for options. */ + public static readonly DEFAULT_OPTIONS: RGBSplitFilterOptions = { + red: [-10, 0], + green: [0, 10], + blue: [0, 0], + }; + /** * @param {Point | number[]} [red=[-10,0]] - Red channel offset * @param {Point | number[]} [green=[0, 10]] - Green channel offset * @param {Point | number[]} [blue=[0, 0]] - Blue channel offset */ - constructor(red: Offset = [-10, 0], green: Offset = [0, 10], blue: Offset = [0, 0]) + constructor(options?: RGBSplitFilterOptions) { + options = { ...RGBSplitFilter.DEFAULT_OPTIONS, ...options }; + + const rgbSplitUniforms = new UniformGroup({ + uRed: { value: new Float32Array(2), type: 'vec2' }, + uGreen: { value: new Float32Array(2), type: 'vec2' }, + uBlue: { value: new Float32Array(2), type: 'vec2' }, + }); + + const gpuProgram = new GpuProgram({ + vertex: { + source: wgslVertex, + entryPoint: 'mainVertex', + }, + fragment: { + source, + entryPoint: 'mainFragment', + }, + }); + const glProgram = new GlProgram({ vertex, fragment, @@ -30,53 +75,111 @@ export class RGBSplitFilter extends Filter }); super({ + gpuProgram, glProgram, - resources: {}, + resources: { + rgbSplitUniforms + }, }); - // this.red = red; - // this.green = green; - // this.blue = blue; + + this.red = options.red ?? [-10, 0]; + this.green = options.green ?? [0, 10]; + this.blue = options.blue ?? [0, 0]; } /** * Red channel offset. - * - * @member {Point | number[]} + * @default [-10,0] + */ + get red(): Offset { return this.resources.rgbSplitUniforms.uniforms.uRed; } + set red(value: Offset) + { + if (value instanceof Point) + { + this.redX = value.x; + this.redY = value.y; + + return; + } + + this.resources.rgbSplitUniforms.uniforms.uRed = value; + } + + /** + * Amount of x-axis offset for the red channel. + * @default -10 */ - // get red(): Offset - // { - // return this.uniforms.red; - // } - // set red(value: Offset) - // { - // this.uniforms.red = value; - // } + get redX(): number { return this.resources.rgbSplitUniforms.uniforms.uRed[0]; } + set redX(value: number) { this.resources.rgbSplitUniforms.uniforms.uRed[0] = value; } + + /** + * Amount of y-axis offset for the red channel. + * @default 0 + */ + get redY(): number { return this.resources.rgbSplitUniforms.uniforms.uRed[1]; } + set redY(value: number) { this.resources.rgbSplitUniforms.uniforms.uRed[1] = value; } /** * Green channel offset. - * - * @member {Point | number[]} + * @default [0,10] + */ + get green(): Offset { return this.resources.rgbSplitUniforms.uniforms.uGreen; } + set green(value: Offset) + { + if (value instanceof Point) + { + this.greenX = value.x; + this.greenY = value.y; + + return; + } + + this.resources.rgbSplitUniforms.uniforms.uGreen = value; + } + + /** + * Amount of x-axis offset for the green channel. + * @default 0 + */ + get greenX(): number { return this.resources.rgbSplitUniforms.uniforms.uGreen[0]; } + set greenX(value: number) { this.resources.rgbSplitUniforms.uniforms.uGreen[0] = value; } + + /** + * Amount of y-axis offset for the green channel. + * @default 10 + */ + get greenY(): number { return this.resources.rgbSplitUniforms.uniforms.uGreen[1]; } + set greenY(value: number) { this.resources.rgbSplitUniforms.uniforms.uGreen[1] = value; } + + /** + * Blue channel offset. + * @default [0,0] + */ + get blue(): Offset { return this.resources.rgbSplitUniforms.uniforms.uBlue; } + set blue(value: Offset) + { + if (value instanceof Point) + { + this.blueX = value.x; + this.blueY = value.y; + + return; + } + + this.resources.rgbSplitUniforms.uniforms.uBlue = value; + } + + /** + * Amount of x-axis offset for the blue channel. + * @default 0 */ - // get green(): Offset - // { - // return this.uniforms.green; - // } - // set green(value: Offset) - // { - // this.uniforms.green = value; - // } + get blueX(): number { return this.resources.rgbSplitUniforms.uniforms.uBlue[0]; } + set blueX(value: number) { this.resources.rgbSplitUniforms.uniforms.uBlue[0] = value; } /** - * Blue offset. - * - * @member {Point | number[]} + * Amount of y-axis offset for the blue channel. + * @default 0 */ - // get blue(): Offset - // { - // return this.uniforms.blue; - // } - // set blue(value: Offset) - // { - // this.uniforms.blue = value; - // } + get blueY(): number { return this.resources.rgbSplitUniforms.uniforms.uBlue[1]; } + set blueY(value: number) { this.resources.rgbSplitUniforms.uniforms.uBlue[1] = value; } } diff --git a/filters/rgb-split/src/rgb-split.frag b/filters/rgb-split/src/rgb-split.frag index 46fed0f09..c28d5d054 100644 --- a/filters/rgb-split/src/rgb-split.frag +++ b/filters/rgb-split/src/rgb-split.frag @@ -1,17 +1,18 @@ -precision mediump float; - -varying vec2 vTextureCoord; +precision highp float; +in vec2 vTextureCoord; +out vec4 finalColor; uniform sampler2D uSampler; -uniform vec4 filterArea; -uniform vec2 red; -uniform vec2 green; -uniform vec2 blue; +uniform vec4 uInputSize; +uniform vec2 uRed; +uniform vec2 uGreen; +uniform vec2 uBlue; void main(void) { - gl_FragColor.r = texture2D(uSampler, vTextureCoord + red/filterArea.xy).r; - gl_FragColor.g = texture2D(uSampler, vTextureCoord + green/filterArea.xy).g; - gl_FragColor.b = texture2D(uSampler, vTextureCoord + blue/filterArea.xy).b; - gl_FragColor.a = texture2D(uSampler, vTextureCoord).a; + float r = texture2D(uSampler, vTextureCoord + uRed/uInputSize.xy).r; + float g = texture2D(uSampler, vTextureCoord + uGreen/uInputSize.xy).g; + float b = texture2D(uSampler, vTextureCoord + uBlue/uInputSize.xy).b; + float a = texture2D(uSampler, vTextureCoord).a; + finalColor = vec4(r, g, b, a); } diff --git a/filters/rgb-split/src/rgb-split.wgsl b/filters/rgb-split/src/rgb-split.wgsl new file mode 100644 index 000000000..d7d887785 --- /dev/null +++ b/filters/rgb-split/src/rgb-split.wgsl @@ -0,0 +1,31 @@ +struct RgbSplitUniforms { + uRed: vec2, + uGreen: vec2, + uBlue: vec3, +}; + +struct GlobalFilterUniforms { + uInputSize:vec4, + uInputPixel:vec4, + uuInputClamp:vec4, + uOutputFrame:vec4, + uGlobalFrame:vec4, + uOutputTexture:vec4, +}; + +@group(0) @binding(0) var gfu: GlobalFilterUniforms; + +@group(0) @binding(1) var uSampler: texture_2d; +@group(1) @binding(0) var rgbSplitUniforms : RgbSplitUniforms; + +@fragment +fn mainFragment( + @builtin(position) position: vec4, + @location(0) uv : vec2 +) -> @location(0) vec4 { + let r = textureSample(uSampler, uSampler, vTextureCoord + rgbSplitUniforms.uRed/uInputSize.xy).r; + let g = textureSample(uSampler, uSampler, vTextureCoord + rgbSplitUniforms.uGreen/uInputSize.xy).g; + let b = textureSample(uSampler, uSampler, vTextureCoord + rgbSplitUniforms.uBlue/uInputSize.xy).b; + let a = textureSample(uSampler, uSampler, vTextureCoord).a; + return vec4(r, g, b, a); +} diff --git a/tools/demo/src/index.js b/tools/demo/src/index.js index 8ca4a000f..5aef3fe2c 100644 --- a/tools/demo/src/index.js +++ b/tools/demo/src/index.js @@ -39,6 +39,7 @@ const main = async () => filters.pixelate.call(app); filters.glow.call(app); filters.hslAdjustment.call(app); + filters.rgb.call(app); // filters.kawaseBlur.call(app); // TODO: Re-enable this in place of the above once v8 conversion is complete