From 442d11235f507763b4befea6ac7c62132a496029 Mon Sep 17 00:00:00 2001 From: Baz Utsahajit Date: Tue, 2 Jan 2024 15:39:48 +0000 Subject: [PATCH] Update Color Replace Filter --- .../color-replace/src/ColorReplaceFilter.ts | 208 ++++++++++-------- filters/color-replace/src/color-replace.frag | 15 ++ filters/color-replace/src/color-replace.wgsl | 22 ++ filters/color-replace/src/colorReplace.frag | 12 - tools/demo/src/filters/color-replace.js | 2 +- tools/demo/src/index.js | 1 + 6 files changed, 155 insertions(+), 105 deletions(-) create mode 100644 filters/color-replace/src/color-replace.frag create mode 100644 filters/color-replace/src/color-replace.wgsl delete mode 100644 filters/color-replace/src/colorReplace.frag diff --git a/filters/color-replace/src/ColorReplaceFilter.ts b/filters/color-replace/src/ColorReplaceFilter.ts index a41b074f2..ee8aeafe1 100644 --- a/filters/color-replace/src/ColorReplaceFilter.ts +++ b/filters/color-replace/src/ColorReplaceFilter.ts @@ -1,8 +1,33 @@ -import { vertex } from '@tools/fragments'; -import fragment from './colorReplace.frag'; -import { Filter, GlProgram } from 'pixi.js'; +import { vertex, wgslVertex } from '@tools/fragments'; +import fragment from './color-replace.frag'; +import source from './color-replace.wgsl'; +import { Color, ColorSource, Filter, FilterOptions, GlProgram, GpuProgram } from 'pixi.js'; -type Color = number | number[] | Float32Array; +/** + * This WebGPU filter has been ported from the WebGL renderer that was originally created by mishaa, updated by timetocode + * http://www.html5gamedevs.com/topic/10640-outline-a-sprite-change-certain-colors/?p=69966 + */ + +export interface ColorReplaceFilterOptions +{ + /** + * The color that will be changed. + * @example [1.0, 1.0, 1.0] = 0xffffff + * @default 0xff0000 + */ + originalColor?: ColorSource; + /** + * The resulting color. + * @example [1.0, 1.0, 1.0] = 0xffffff + * @default 0x000000 + */ + newColor?: ColorSource; + /** + * Tolerance/sensitivity of the floating-point comparison between colors (lower = more exact, higher = more inclusive) + * @default 0.4 + */ + tolerance?: number; +} /** * ColorReplaceFilter, originally by mishaa, updated by timetocode @@ -16,36 +41,55 @@ type Color = number | number[] | Float32Array; * * @example * // replaces true red with true blue - * someSprite.filters = [new ColorReplaceFilter( - * [1, 0, 0], - * [0, 0, 1], - * 0.001 - * )]; + * someSprite.filters = [new ColorReplaceFilter({ + * originalColor: [1, 0, 0], + * newColor: [0, 0, 1], + * tolerance: 0.001 + * })]; * // replaces the RGB color 220, 220, 220 with the RGB color 225, 200, 215 - * someOtherSprite.filters = [new ColorReplaceFilter( - * [220/255.0, 220/255.0, 220/255.0], - * [225/255.0, 200/255.0, 215/255.0], - * 0.001 - * )]; + * someOtherSprite.filters = [new ColorReplaceFilter({ + * originalColor: [220/255.0, 220/255.0, 220/255.0], + * newColor: [225/255.0, 200/255.0, 215/255.0], + * tolerance: 0.001 + * })]; * // replaces the RGB color 220, 220, 220 with the RGB color 225, 200, 215 - * someOtherSprite.filters = [new ColorReplaceFilter(0xdcdcdc, 0xe1c8d7, 0.001)]; + * someOtherSprite.filters = [new ColorReplaceFilter({ originalColor: 0xdcdcdc, newColor: 0xe1c8d7, tolerance: 0.001 })]; * */ export class ColorReplaceFilter extends Filter { - private _originalColor = 0xff0000; - private _newColor = 0x0; + /** Default values for options. */ + public static readonly DEFAULT_OPTIONS: ColorReplaceFilterOptions & Partial = { + ...Filter.defaultOptions, + originalColor: 0xff0000, + newColor: 0x000000, + tolerance: 0.4 + }; - /** - * @param {number|Array|Float32Array} [originalColor=0xFF0000] - The color that will be changed, - * as a 3 component RGB e.g. `[1.0, 1.0, 1.0]` - * @param {number|Array|Float32Array} [newColor=0x000000] - The resulting color, as a 3 component - * RGB e.g. `[1.0, 0.5, 1.0]` - * @param {number} [epsilon=0.4] - Tolerance/sensitivity of the floating-point comparison between colors - * (lower = more exact, higher = more inclusive) - */ - constructor(originalColor: Color = 0xFF0000, newColor: Color = 0x000000, epsilon = 0.4) + public uniforms: { + uOriginalColor: Float32Array, + uNewColor: Float32Array, + uTolerance: number, + }; + + private _originalColor: Color; + private _newColor: Color; + + constructor(options: ColorReplaceFilterOptions = {}) { + options = { ...ColorReplaceFilter.DEFAULT_OPTIONS, ...options }; + + const gpuProgram = new GpuProgram({ + vertex: { + source: wgslVertex, + entryPoint: 'mainVertex', + }, + fragment: { + source, + entryPoint: 'mainFragment', + }, + }); + const glProgram = new GlProgram({ vertex, fragment, @@ -53,81 +97,61 @@ export class ColorReplaceFilter extends Filter }); super({ + gpuProgram, glProgram, - resources: {}, + resources: { + colorReplaceUniforms: { + uOriginalColor: { value: new Float32Array(3), type: 'vec3' }, + uNewColor: { value: new Float32Array(3), type: 'vec3' }, + uTolerance: { value: options.tolerance, type: 'f32' }, + } + }, }); - // this.uniforms.originalColor = new Float32Array(3); - // this.uniforms.newColor = new Float32Array(3); - // this.originalColor = originalColor; - // this.newColor = newColor; - // this.epsilon = epsilon; + this.uniforms = this.resources.colorReplaceUniforms.uniforms; + + this._originalColor = new Color(); + this._newColor = new Color(); + + Object.assign(this, options); } /** - * The color that will be changed, as a 3 component RGB e.g. [1.0, 1.0, 1.0] - * @member {number|Array|Float32Array} - * @default 0xFF0000 + * The color that will be changed. + * @example [1.0, 1.0, 1.0] = 0xffffff + * @default 0xff0000 */ - // set originalColor(value: Color) - // { - // const arr = this.uniforms.originalColor; - - // if (typeof value === 'number') - // { - // utils.hex2rgb(value, arr); - // this._originalColor = value; - // } - // else - // { - // arr[0] = value[0]; - // arr[1] = value[1]; - // arr[2] = value[2]; - // this._originalColor = utils.rgb2hex(arr); - // } - // } - // get originalColor(): Color - // { - // return this._originalColor; - // } + get originalColor(): ColorSource { return this._originalColor.value as ColorSource; } + set originalColor(value: ColorSource) + { + this._originalColor.setValue(value); + const [r, g, b] = this._originalColor.toArray(); + + this.uniforms.uOriginalColor[0] = r; + this.uniforms.uOriginalColor[1] = g; + this.uniforms.uOriginalColor[2] = b; + } /** - * The resulting color, as a 3 component RGB e.g. [1.0, 0.5, 1.0] - * @member {number|Array|Float32Array} - * @default 0x000000 - */ - // set newColor(value: Color) - // { - // const arr = this.uniforms.newColor; - - // if (typeof value === 'number') - // { - // utils.hex2rgb(value, arr); - // this._newColor = value; - // } - // else - // { - // arr[0] = value[0]; - // arr[1] = value[1]; - // arr[2] = value[2]; - // this._newColor = utils.rgb2hex(arr); - // } - // } - // get newColor(): Color - // { - // return this._newColor; - // } + * The resulting color. + * @example [1.0, 1.0, 1.0] = 0xffffff + * @default 0x000000 + */ + get newColor(): ColorSource { return this._newColor.value as ColorSource; } + set newColor(value: ColorSource) + { + this._newColor.setValue(value); + const [r, g, b] = this._newColor.toArray(); + + this.uniforms.uNewColor[0] = r; + this.uniforms.uNewColor[1] = g; + this.uniforms.uNewColor[2] = b; + } /** - * Tolerance/sensitivity of the floating-point comparison between colors (lower = more exact, higher = more inclusive) - * @default 0.4 - */ - // set epsilon(value: number) - // { - // this.uniforms.epsilon = value; - // } - // get epsilon(): number - // { - // return this.uniforms.epsilon; - // } + * Tolerance/sensitivity of the floating-point comparison between colors (lower = more exact, higher = more inclusive) + * @default 0.4 + */ + get tolerance(): number { return this.uniforms.uTolerance; } + set tolerance(value: number) { this.uniforms.uTolerance = value; } } diff --git a/filters/color-replace/src/color-replace.frag b/filters/color-replace/src/color-replace.frag new file mode 100644 index 000000000..501be0123 --- /dev/null +++ b/filters/color-replace/src/color-replace.frag @@ -0,0 +1,15 @@ +in vec2 vTextureCoord; +out vec4 finalColor; + +uniform sampler2D uSampler; +uniform vec3 uOriginalColor; +uniform vec3 uNewColor; +uniform float uTolerance; + +void main(void) { + vec4 c = texture(uSampler, vTextureCoord); + vec3 colorDiff = uOriginalColor - (c.rgb / max(c.a, 0.0000000001)); + float colorDistance = length(colorDiff); + float doReplace = step(colorDistance, uTolerance); + finalColor = vec4(mix(c.rgb, (uNewColor + colorDiff) * c.a, doReplace), c.a); +} diff --git a/filters/color-replace/src/color-replace.wgsl b/filters/color-replace/src/color-replace.wgsl new file mode 100644 index 000000000..501d4320d --- /dev/null +++ b/filters/color-replace/src/color-replace.wgsl @@ -0,0 +1,22 @@ +struct ColorReplaceUniforms { + uOriginalColor: vec3, + uNewColor: vec3, + uTolerance: f32, +}; + +@group(0) @binding(1) var uSampler: texture_2d; +@group(1) @binding(0) var colorReplaceUniforms : ColorReplaceUniforms; + +@fragment +fn mainFragment( + @builtin(position) position: vec4, + @location(0) uv : vec2 +) -> @location(0) vec4 { + let sample: vec4 = textureSample(uSampler, uSampler, uv); + + let colorDiff: vec3 = colorReplaceUniforms.uOriginalColor - (sample.rgb / max(sample.a, 0.0000000001)); + let colorDistance: f32 = length(colorDiff); + let doReplace: f32 = step(colorDistance, colorReplaceUniforms.uTolerance); + + return vec4(mix(sample.rgb, (colorReplaceUniforms.uNewColor + colorDiff) * sample.a, doReplace), sample.a); +} \ No newline at end of file diff --git a/filters/color-replace/src/colorReplace.frag b/filters/color-replace/src/colorReplace.frag deleted file mode 100644 index 80c64028d..000000000 --- a/filters/color-replace/src/colorReplace.frag +++ /dev/null @@ -1,12 +0,0 @@ -varying vec2 vTextureCoord; -uniform sampler2D uSampler; -uniform vec3 originalColor; -uniform vec3 newColor; -uniform float epsilon; -void main(void) { - vec4 currentColor = texture2D(uSampler, vTextureCoord); - vec3 colorDiff = originalColor - (currentColor.rgb / max(currentColor.a, 0.0000000001)); - float colorDistance = length(colorDiff); - float doReplace = step(colorDistance, epsilon); - gl_FragColor = vec4(mix(currentColor.rgb, (newColor + colorDiff) * currentColor.a, doReplace), currentColor.a); -} diff --git a/tools/demo/src/filters/color-replace.js b/tools/demo/src/filters/color-replace.js index 7dfe278d7..5d72d5c34 100644 --- a/tools/demo/src/filters/color-replace.js +++ b/tools/demo/src/filters/color-replace.js @@ -4,6 +4,6 @@ export default function () { folder.addColor(this, 'originalColor'); folder.addColor(this, 'newColor'); - folder.add(this, 'epsilon', 0, 1); + folder.add(this, 'tolerance', 0, 1); }); } diff --git a/tools/demo/src/index.js b/tools/demo/src/index.js index f9c276577..8b8219c0f 100644 --- a/tools/demo/src/index.js +++ b/tools/demo/src/index.js @@ -47,6 +47,7 @@ const main = async () => filters.shockwave.call(app); filters.zoomBlur.call(app); filters.colorOverlay.call(app); + filters.colorReplace.call(app); // filters.kawaseBlur.call(app); // TODO: Re-enable this in place of the above once v8 conversion is complete