From 0076c3f77e227ac99003c441db813a5c03c5c95c Mon Sep 17 00:00:00 2001 From: Baz Utsahajit Date: Wed, 3 Jan 2024 11:52:28 +0000 Subject: [PATCH] Update Reflection Filter --- filters/reflection/src/ReflectionFilter.ts | 246 ++++++++++++++------- filters/reflection/src/reflection.frag | 52 ++--- filters/reflection/src/reflection.wgsl | 64 ++++++ tools/demo/src/filters/reflection.js | 12 +- tools/demo/src/index.js | 1 + 5 files changed, 258 insertions(+), 117 deletions(-) create mode 100644 filters/reflection/src/reflection.wgsl diff --git a/filters/reflection/src/ReflectionFilter.ts b/filters/reflection/src/ReflectionFilter.ts index a6abcb316..f73cd9484 100644 --- a/filters/reflection/src/ReflectionFilter.ts +++ b/filters/reflection/src/ReflectionFilter.ts @@ -1,18 +1,45 @@ -import { vertex } from '@tools/fragments'; +import { vertex, wgslVertex } from '@tools/fragments'; import fragment from './reflection.frag'; -import { Filter, GlProgram } from 'pixi.js'; +import source from './reflection.wgsl'; +import { Filter, GlProgram, GpuProgram } from 'pixi.js'; import type { FilterSystem, RenderSurface, Texture } from 'pixi.js'; -type Range = number[] | Float32Array; +/** [MIN, MAX] */ +type Range = [number, number]; export interface ReflectionFilterOptions { - mirror: boolean; - boundary: number; - amplitude: Range; - waveLength: Range; - alpha: Range; - time: number; + /** + * `true` to reflect the image, `false` for waves-only + * @default true + */ + mirror?: boolean; + /** + * Vertical position of the reflection point, `0.5` equates to the middle + * smaller numbers produce a larger reflection, larger numbers produce a smaller reflection + * @default 0.5 + */ + boundary?: number; + /** + * Starting and ending amplitude of waves + * @default [0,20] + */ + amplitude?: Range; + /** + * Starting and ending length of waves + * @default [30,100] + */ + waveLength?: Range; + /** + * Starting and ending alpha values + * @default [1,1] + */ + alpha?: Range; + /** + * Time for animating position of waves + * @default 0 + */ + time?: number; } /** @@ -26,8 +53,8 @@ export interface ReflectionFilterOptions */ export class ReflectionFilter extends Filter { - /** Default constructor options */ - public static readonly defaults: ReflectionFilterOptions = { + /** Default values for options. */ + public static readonly DEFAULT_OPTIONS: ReflectionFilterOptions = { mirror: true, boundary: 0.5, amplitude: [0, 20], @@ -36,21 +63,36 @@ export class ReflectionFilter extends Filter time: 0, }; - /** Time for animating position of waves */ - public time = 0; + public uniforms: { + uMirror: number; + uBoundary: number; + uAmplitude: Float32Array; + uWavelength: Float32Array; + uAlpha: Float32Array; + uTime: number; + uDimensions: Float32Array; + }; /** - * @param {object} [options] - The optional parameters of Reflection effect. - * @param {number} [options.mirror=true] - `true` to reflect the image, `false` for waves-only - * @param {number} [options.boundary=0.5] - Vertical position of the reflection point, default is 50% (middle) - * smaller numbers produce a larger reflection, larger numbers produce a smaller reflection. - * @param {number} [options.amplitude=[0, 20]] - Starting and ending amplitude of waves - * @param {number} [options.waveLength=[30, 100]] - Starting and ending length of waves - * @param {number} [options.alpha=[1, 1]] - Starting and ending alpha values - * @param {number} [options.time=0] - Time for animating position of waves - */ - constructor(options?: Partial) + * Time for animating position of waves + * @default 0 + */ + public time = 0; + + constructor(options?: ReflectionFilterOptions) { + options = { ...ReflectionFilter.DEFAULT_OPTIONS, ...options }; + + const gpuProgram = new GpuProgram({ + vertex: { + source: wgslVertex, + entryPoint: 'mainVertex', + }, + fragment: { + source, + entryPoint: 'mainFragment', + }, + }); const glProgram = new GlProgram({ vertex, fragment, @@ -58,101 +100,133 @@ export class ReflectionFilter extends Filter }); super({ + gpuProgram, glProgram, - resources: {}, + resources: { + reflectionUniforms: { + uMirror: { value: options.mirror ? 1 : 0, type: 'f32' }, + uBoundary: { value: options.boundary, type: 'f32' }, + uAmplitude: { value: options.amplitude, type: 'vec2' }, + uWavelength: { value: options.waveLength, type: 'vec2' }, + uAlpha: { value: options.alpha, type: 'vec2' }, + uTime: { value: options.time, type: 'f32' }, + uDimensions: { value: new Float32Array(2), type: 'vec2' }, + } + }, }); - // this.uniforms.amplitude = new Float32Array(2); - // this.uniforms.waveLength = new Float32Array(2); - // this.uniforms.alpha = new Float32Array(2); - // this.uniforms.dimensions = new Float32Array(2); + this.uniforms = this.resources.reflectionUniforms.uniforms; - Object.assign(this, ReflectionFilter.defaults, options); + Object.assign(this, options); } /** - * Override existing apply method in Filter - * @private + * Override existing apply method in `Filter` + * @override + * @ignore */ - apply(filterManager: FilterSystem, input: Texture, output: RenderSurface, clear: boolean): void + public override apply( + filterManager: FilterSystem, + input: Texture, + output: RenderSurface, + clearMode: boolean + ): void { - // this.uniforms.dimensions[0] = input.filterFrame?.width; - // this.uniforms.dimensions[1] = input.filterFrame?.height; + this.uniforms.uDimensions[0] = input.frame.width; + this.uniforms.uDimensions[1] = input.frame.height; - // this.uniforms.time = this.time; + this.uniforms.uTime = this.time; - // filterManager.applyFilter(this, input, output, clear); + filterManager.applyFilter(this, input, output, clearMode); } /** * `true` to reflect the image, `false` for waves-only * @default true */ - // set mirror(value: boolean) - // { - // this.uniforms.mirror = value; - // } - // get mirror(): boolean - // { - // return this.uniforms.mirror; - // } + get mirror(): boolean { return this.uniforms.uMirror > 0.5; } + set mirror(value: boolean) { this.uniforms.uMirror = value ? 1 : 0; } /** * Vertical position of the reflection point, default is 50% (middle) * smaller numbers produce a larger reflection, larger numbers produce a smaller reflection. * @default 0.5 */ - // set boundary(value: number) - // { - // this.uniforms.boundary = value; - // } - // get boundary(): number - // { - // return this.uniforms.boundary; - // } + get boundary(): number { return this.uniforms.uBoundary; } + set boundary(value: number) { this.uniforms.uBoundary = value; } /** * Starting and ending amplitude of waves - * @member {number[]} - * @default [0, 20] + * @default [0,20] */ - // set amplitude(value: Range) - // { - // this.uniforms.amplitude[0] = value[0]; - // this.uniforms.amplitude[1] = value[1]; - // } - // get amplitude(): Range - // { - // return this.uniforms.amplitude; - // } + get amplitude(): Range { return Array.from(this.uniforms.uAmplitude) as Range; } + set amplitude(value: Range) + { + this.uniforms.uAmplitude[0] = value[0]; + this.uniforms.uAmplitude[1] = value[1]; + } + + /** + * Starting amplitude of waves + * @default 0 + */ + get amplitudeStart(): number { return this.uniforms.uAmplitude[0]; } + set amplitudeStart(value: number) { this.uniforms.uAmplitude[0] = value; } + + /** + * Starting amplitude of waves + * @default 20 + */ + get amplitudeEnd(): number { return this.uniforms.uAmplitude[1]; } + set amplitudeEnd(value: number) { this.uniforms.uAmplitude[1] = value; } /** * Starting and ending length of waves - * @member {number[]} - * @default [30, 100] + * @default [30,100] + */ + get waveLength(): Range { return Array.from(this.uniforms.uWavelength) as Range; } + set waveLength(value: Range) + { + this.uniforms.uWavelength[0] = value[0]; + this.uniforms.uWavelength[1] = value[1]; + } + + /** + * Starting wavelength of waves + * @default 30 */ - // set waveLength(value: Range) - // { - // this.uniforms.waveLength[0] = value[0]; - // this.uniforms.waveLength[1] = value[1]; - // } - // get waveLength(): Range - // { - // return this.uniforms.waveLength; - // } + get wavelengthStart(): number { return this.uniforms.uWavelength[0]; } + set wavelengthStart(value: number) { this.uniforms.uWavelength[0] = value; } + + /** + * Starting wavelength of waves + * @default 100 + */ + get wavelengthEnd(): number { return this.uniforms.uWavelength[1]; } + set wavelengthEnd(value: number) { this.uniforms.uWavelength[1] = value; } /** * Starting and ending alpha values - * @member {number[]} - * @default [1, 1] - */ - // set alpha(value: Range) - // { - // this.uniforms.alpha[0] = value[0]; - // this.uniforms.alpha[1] = value[1]; - // } - // get alpha(): Range - // { - // return this.uniforms.alpha; - // } + * @default [1,1] + */ + get alpha(): Range { return Array.from(this.uniforms.uAlpha) as Range; } + set alpha(value: Range) + { + this.uniforms.uAlpha[0] = value[0]; + this.uniforms.uAlpha[1] = value[1]; + } + + /** + * Starting wavelength of waves + * @default 1 + */ + get alphaStart(): number { return this.uniforms.uAlpha[0]; } + set alphaStart(value: number) { this.uniforms.uAlpha[0] = value; } + + /** + * Starting wavelength of waves + * @default 1 + */ + get alphaEnd(): number { return this.uniforms.uAlpha[1]; } + set alphaEnd(value: number) { this.uniforms.uAlpha[1] = value; } } diff --git a/filters/reflection/src/reflection.frag b/filters/reflection/src/reflection.frag index ae3ee32c2..dd1078632 100644 --- a/filters/reflection/src/reflection.frag +++ b/filters/reflection/src/reflection.frag @@ -1,16 +1,18 @@ -varying vec2 vTextureCoord; -uniform sampler2D uSampler; +precision highp float; +in vec2 vTextureCoord; +out vec4 finalColor; -uniform vec4 filterArea; -uniform vec4 filterClamp; -uniform vec2 dimensions; +uniform sampler2D uSampler; +uniform float uMirror; +uniform float uBoundary; +uniform vec2 uAmplitude; +uniform vec2 uWavelength; +uniform vec2 uAlpha; +uniform float uTime; +uniform vec2 uDimensions; -uniform bool mirror; -uniform float boundary; -uniform vec2 amplitude; -uniform vec2 waveLength; -uniform vec2 alpha; -uniform float time; +uniform vec4 uInputSize; +uniform vec4 uInputClamp; float rand(vec2 co) { return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); @@ -18,27 +20,27 @@ float rand(vec2 co) { void main(void) { - vec2 pixelCoord = vTextureCoord.xy * filterArea.xy; - vec2 coord = pixelCoord / dimensions; + vec2 pixelCoord = vTextureCoord.xy * uInputSize.xy; + vec2 coord = pixelCoord / uDimensions; - if (coord.y < boundary) { - gl_FragColor = texture2D(uSampler, vTextureCoord); + if (coord.y < uBoundary) { + finalColor = texture(uSampler, vTextureCoord); return; } - float k = (coord.y - boundary) / (1. - boundary + 0.0001); - float areaY = boundary * dimensions.y / filterArea.y; + float k = (coord.y - uBoundary) / (1. - uBoundary + 0.0001); + float areaY = uBoundary * uDimensions.y / uInputSize.y; float v = areaY + areaY - vTextureCoord.y; - float y = mirror ? v : vTextureCoord.y; + float y = uMirror > 0.5 ? v : vTextureCoord.y; - float _amplitude = ((amplitude.y - amplitude.x) * k + amplitude.x ) / filterArea.x; - float _waveLength = ((waveLength.y - waveLength.x) * k + waveLength.x) / filterArea.y; - float _alpha = (alpha.y - alpha.x) * k + alpha.x; + float _amplitude = ((uAmplitude.y - uAmplitude.x) * k + uAmplitude.x ) / uInputSize.x; + float _waveLength = ((uWavelength.y - uWavelength.x) * k + uWavelength.x) / uInputSize.y; + float _alpha = (uAlpha.y - uAlpha.x) * k + uAlpha.x; - float x = vTextureCoord.x + cos(v * 6.28 / _waveLength - time) * _amplitude; - x = clamp(x, filterClamp.x, filterClamp.z); + float x = vTextureCoord.x + cos(v * 6.28 / _waveLength - uTime) * _amplitude; + x = clamp(x, uInputClamp.x, uInputClamp.z); - vec4 color = texture2D(uSampler, vec2(x, y)); + vec4 color = texture(uSampler, vec2(x, y)); - gl_FragColor = color * _alpha; + finalColor = color * _alpha; } diff --git a/filters/reflection/src/reflection.wgsl b/filters/reflection/src/reflection.wgsl new file mode 100644 index 000000000..600b4f302 --- /dev/null +++ b/filters/reflection/src/reflection.wgsl @@ -0,0 +1,64 @@ +struct ReflectionUniforms { + uMirror: f32, + uBoundary: f32, + uAmplitude: vec2, + uWavelength: vec2, + uAlpha: vec2, + uTime: f32, + uDimensions: vec2, +}; + +struct GlobalFilterUniforms { + uInputSize:vec4, + uInputPixel:vec4, + uInputClamp: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 reflectionUniforms : ReflectionUniforms; + +@fragment +fn mainFragment( + @builtin(position) position: vec4, + @location(0) uv : vec2 +) -> @location(0) vec4 { + let uDimensions: vec2 = reflectionUniforms.uDimensions; + let uBoundary: f32 = reflectionUniforms.uBoundary; + let uMirror: bool = reflectionUniforms.uMirror > 0.5; + let uAmplitude: vec2 = reflectionUniforms.uAmplitude; + let uWavelength: vec2 = reflectionUniforms.uWavelength; + let uAlpha: vec2 = reflectionUniforms.uAlpha; + let uTime: f32 = reflectionUniforms.uTime; + + let pixelCoord: vec2 = uv * gfu.uInputSize.xy; + let coord: vec2 = pixelCoord /uDimensions; + var returnColorOnly: bool = false; + + if (coord.y < uBoundary) { + returnColorOnly = true; + } + + let k: f32 = (coord.y - uBoundary) / (1. - uBoundary + 0.0001); + let areaY: f32 = uBoundary * uDimensions.y / gfu.uInputSize.y; + let v: f32 = areaY + areaY - uv.y; + let y: f32 = select(uv.y, v, uMirror); + + let amplitude: f32 = ((uAmplitude.y - uAmplitude.x) * k + uAmplitude.x ) / gfu.uInputSize.x; + let waveLength: f32 = ((uWavelength.y - uWavelength.x) * k + uWavelength.x) / gfu.uInputSize.y; + let alpha: f32 = (uAlpha.y - uAlpha.x) * k + uAlpha.x; + + var x: f32 = uv.x + cos(v * 6.28 / waveLength - uTime) * amplitude; + x = clamp(x, gfu.uInputClamp.x, gfu.uInputClamp.z); + + return textureSample(uSampler, uSampler, select(vec2(x, y), uv, returnColorOnly)) * alpha; +} + +fn rand(co: vec2) -> f32 +{ + return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); +} \ No newline at end of file diff --git a/tools/demo/src/filters/reflection.js b/tools/demo/src/filters/reflection.js index f75c2964c..bf1a28d32 100644 --- a/tools/demo/src/filters/reflection.js +++ b/tools/demo/src/filters/reflection.js @@ -29,12 +29,12 @@ export default function () folder.add(this, 'mirror'); folder.add(this, 'boundary', 0, 1); - folder.add(this.amplitude, '0', 0, 50).name('amplitude.start'); - folder.add(this.amplitude, '1', 0, 50).name('amplitude.end'); - folder.add(this.waveLength, '0', 10, 200).name('waveLength.start'); - folder.add(this.waveLength, '1', 10, 200).name('waveLength.end'); - folder.add(this.alpha, '0', 0, 1).name('alpha.start'); - folder.add(this.alpha, '1', 0, 1).name('alpha.end'); + folder.add(this, 'amplitudeStart', 0, 50).name('amplitude.start'); + folder.add(this, 'amplitudeEnd', 0, 50).name('amplitude.end'); + folder.add(this, 'wavelengthStart', 10, 200).name('waveLength.start'); + folder.add(this, 'wavelengthEnd', 10, 200).name('waveLength.end'); + folder.add(this, 'alphaStart', 0, 1).name('alpha.start'); + folder.add(this, 'alphaEnd', 0, 1).name('alpha.end'); folder.add(this, 'time', 0, 20); }, }); diff --git a/tools/demo/src/index.js b/tools/demo/src/index.js index 0817fec74..c7bd36de8 100644 --- a/tools/demo/src/index.js +++ b/tools/demo/src/index.js @@ -55,6 +55,7 @@ const main = async () => filters.bevel.call(app); filters.crt.call(app); filters.godray.call(app); + filters.reflection.call(app); // filters.kawaseBlur.call(app); // TODO: Re-enable this in place of the above once v8 conversion is complete