From 5de74f3dacbdfc0ac1d541fd19ab660b7617ed4d Mon Sep 17 00:00:00 2001 From: Baz Utsahajit Date: Wed, 3 Jan 2024 10:48:42 +0000 Subject: [PATCH] Update CRT Filter --- filters/crt/src/CRTFilter.ts | 277 ++++++++++++++++++---------------- filters/crt/src/crt.frag | 104 +++++++------ filters/crt/src/crt.wgsl | 107 +++++++++++++ tools/demo/src/filters/crt.js | 4 +- tools/demo/src/index.js | 1 + 5 files changed, 320 insertions(+), 173 deletions(-) create mode 100644 filters/crt/src/crt.wgsl diff --git a/filters/crt/src/CRTFilter.ts b/filters/crt/src/CRTFilter.ts index 1a23e2e97..2f88cd618 100644 --- a/filters/crt/src/CRTFilter.ts +++ b/filters/crt/src/CRTFilter.ts @@ -1,21 +1,68 @@ -import { vertex } from '@tools/fragments'; +import { vertex, wgslVertex } from '@tools/fragments'; import fragment from './crt.frag'; -import { Filter, GlProgram } from 'pixi.js'; -import type { FilterSystem, RenderSurface, RenderTexture, Texture } from 'pixi.js'; +import source from './crt.wgsl'; +import { Filter, GlProgram, GpuProgram } from 'pixi.js'; +import type { FilterSystem, RenderSurface, Texture } from 'pixi.js'; export interface CRTFilterOptions { - curvature: number; - lineWidth: number; - lineContrast: number; - verticalLine: boolean; - noise: number; - noiseSize: number; - seed: number; - vignetting: number; - vignettingAlpha: number; - vignettingBlur: number; - time: number; + /** + * Bend of interlaced lines, higher value means more bend + * @default 1 + */ + curvature?: number, + /** + * Width of the interlaced lines + * @default 1 + */ + lineWidth?: number, + /** + * Contrast of interlaced lines + * @default 0.25 + */ + lineContrast?: number, + /** + * The orientation of the line: + * + * `true` create vertical lines, `false` creates horizontal lines + * @default false + */ + verticalLine?: boolean, + /** + * For animating interlaced lines + * @default 0 + */ + time?: number, + /** + * Opacity/intensity of the noise effect between `0` and `1` + * @default 0.3 + */ + noise?: number, + /** + * The size of the noise particles + * @default 1 + */ + noiseSize?: number, + /** + * A seed value to apply to the random noise generation + * @default 0 + */ + seed?: number, + /** + * The radius of the vignette effect, smaller values produces a smaller vignette + * @default 0.3 + */ + vignetting?: number, + /** + * Amount of opacity on the vignette + * @default 1 + */ + vignettingAlpha?: number, + /** + * Blur intensity of the vignette + * @default 0.3 + */ + vignettingBlur?: number, } /** @@ -29,44 +76,57 @@ export interface CRTFilterOptions */ export class CRTFilter extends Filter { - /** Default constructor options */ - public static readonly defaults: CRTFilterOptions = { + /** Default values for options. */ + public static readonly DEFAULT_OPTIONS: CRTFilterOptions = { curvature: 1.0, lineWidth: 1.0, lineContrast: 0.25, verticalLine: false, noise: 0.0, noiseSize: 1.0, - seed: 0.0, vignetting: 0.3, vignettingAlpha: 1.0, vignettingBlur: 0.3, time: 0.0, + seed: 0.0, }; - /** For animating interlaced lines */ - public time = 0; + public uniforms: { + uLine: Float32Array; + uNoise: Float32Array; + uVignette: Float32Array; + uSeed: number; + uTime: number; + uDimensions: Float32Array; + }; - /** A seed value to apply to the random noise generation */ - public seed = 0; + /** + * A seed value to apply to the random noise generation + * @default 0 + */ + public seed!: number; /** - * @param {object} [options] - The optional parameters of CRT effect - * @param {number} [options.curvature=1.0] - Bent of interlaced lines, higher value means more bend - * @param {number} [options.lineWidth=1.0] - Width of the interlaced lines - * @param {number} [options.lineContrast=0.25] - Contrast of interlaced lines - * @param {number} [options.verticalLine=false] - `true` is vertical lines, `false` is horizontal - * @param {number} [options.noise=0.3] - Opacity/intensity of the noise effect between `0` and `1` - * @param {number} [options.noiseSize=1.0] - The size of the noise particles - * @param {number} [options.seed=0] - A seed value to apply to the random noise generation - * @param {number} [options.vignetting=0.3] - The radius of the vignette effect, smaller - * values produces a smaller vignette - * @param {number} [options.vignettingAlpha=1.0] - Amount of opacity of vignette - * @param {number} [options.vignettingBlur=0.3] - Blur intensity of the vignette - * @param {number} [options.time=0] - For animating interlaced lines + * Opacity/intensity of the noise effect between `0` and `1` + * @default 0.3 */ - constructor(options?: Partial) + public time!: number; + + constructor(options?: CRTFilterOptions) { + options = { ...CRTFilter.DEFAULT_OPTIONS, ...options }; + + const gpuProgram = new GpuProgram({ + vertex: { + source: wgslVertex, + entryPoint: 'mainVertex', + }, + fragment: { + source, + entryPoint: 'mainFragment', + }, + }); + const glProgram = new GlProgram({ vertex, fragment, @@ -74,147 +134,108 @@ export class CRTFilter extends Filter }); super({ + gpuProgram, glProgram, - resources: {}, + resources: { + crtUniforms: { + uLine: { value: new Float32Array(4), type: 'vec4' }, + uNoise: { value: new Float32Array(2), type: 'vec2' }, + uVignette: { value: new Float32Array(3), type: 'vec3' }, + uSeed: { value: options.seed, type: 'f32' }, + uTime: { value: options.time, type: 'f32' }, + uDimensions: { value: new Float32Array(2), type: 'vec2' }, + } + }, }); - // this.uniforms.dimensions = new Float32Array(2); + this.uniforms = this.resources.crtUniforms.uniforms; - Object.assign(this, CRTFilter.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 { - // const { width, height } = input.filterFrame as Rectangle; + this.uniforms.uDimensions[0] = input.frame.width; + this.uniforms.uDimensions[1] = input.frame.height; - // this.uniforms.dimensions[0] = width; - // this.uniforms.dimensions[1] = height; + this.uniforms.uSeed = this.seed; + this.uniforms.uTime = this.time; - // this.uniforms.seed = this.seed; - // this.uniforms.time = this.time; - - // filterManager.applyFilter(this, input, output, clear); + filterManager.applyFilter(this, input, output, clearMode); } /** - * Bent of interlaced lines, higher value means more bend + * Bend of interlaced lines, higher value means more bend * @default 1 */ - // set curvature(value: number) - // { - // this.uniforms.curvature = value; - // } - // get curvature(): number - // { - // return this.uniforms.curvature; - // } + get curvature(): number { return this.uniforms.uLine[0]; } + set curvature(value: number) { this.uniforms.uLine[0] = value; } /** * Width of interlaced lines * @default 1 */ - // set lineWidth(value: number) - // { - // this.uniforms.lineWidth = value; - // } - // get lineWidth(): number - // { - // return this.uniforms.lineWidth; - // } + get lineWidth(): number { return this.uniforms.uLine[1]; } + set lineWidth(value: number) { this.uniforms.uLine[1] = value; } /** * Contrast of interlaced lines * @default 0.25 */ - // set lineContrast(value: number) - // { - // this.uniforms.lineContrast = value; - // } - // get lineContrast(): number - // { - // return this.uniforms.lineContrast; - // } + get lineContrast(): number { return this.uniforms.uLine[2]; } + set lineContrast(value: number) { this.uniforms.uLine[2] = value; } /** - * `true` for vertical lines, `false` for horizontal lines + * The orientation of the line: + * + * `true` create vertical lines, `false` creates horizontal lines * @default false */ - // set verticalLine(value: boolean) - // { - // this.uniforms.verticalLine = value; - // } - // get verticalLine(): boolean - // { - // return this.uniforms.verticalLine; - // } + get verticalLine(): boolean { return this.uniforms.uLine[3] > 0.5; } + set verticalLine(value: boolean) { this.uniforms.uLine[3] = value ? 1 : 0; } /** * Opacity/intensity of the noise effect between `0` and `1` - * @default 0 + * @default 0.3 */ - // set noise(value: number) - // { - // this.uniforms.noise = value; - // } - // get noise(): number - // { - // return this.uniforms.noise; - // } + get noise(): number { return this.uniforms.uNoise[0]; } + set noise(value: number) { this.uniforms.uNoise[0] = value; } /** * The size of the noise particles * @default 0 */ - // set noiseSize(value: number) - // { - // this.uniforms.noiseSize = value; - // } - // get noiseSize(): number - // { - // return this.uniforms.noiseSize; - // } + get noiseSize(): number { return this.uniforms.uNoise[1]; } + set noiseSize(value: number) { this.uniforms.uNoise[1] = value; } /** - * The radius of the vignette effect, smaller - * values produces a smaller vignette - * @default 0 + * The radius of the vignette effect, smaller values produces a smaller vignette + * @default 0.3 */ - // set vignetting(value: number) - // { - // this.uniforms.vignetting = value; - // } - // get vignetting(): number - // { - // return this.uniforms.vignetting; - // } + get vignetting(): number { return this.uniforms.uVignette[0]; } + set vignetting(value: number) { this.uniforms.uVignette[0] = value; } /** * Amount of opacity of vignette - * @default 0 + * @default 1 */ - // set vignettingAlpha(value: number) - // { - // this.uniforms.vignettingAlpha = value; - // } - // get vignettingAlpha(): number - // { - // return this.uniforms.vignettingAlpha; - // } + get vignettingAlpha(): number { return this.uniforms.uVignette[1]; } + set vignettingAlpha(value: number) { this.uniforms.uVignette[1] = value; } /** * Blur intensity of the vignette - * @default 0 + * @default 0.3 */ - // set vignettingBlur(value: number) - // { - // this.uniforms.vignettingBlur = value; - // } - // get vignettingBlur(): number - // { - // return this.uniforms.vignettingBlur; - // } + get vignettingBlur(): number { return this.uniforms.uVignette[2]; } + set vignettingBlur(value: number) { this.uniforms.uVignette[2] = value; } } diff --git a/filters/crt/src/crt.frag b/filters/crt/src/crt.frag index ad2bc6821..af9f016ac 100644 --- a/filters/crt/src/crt.frag +++ b/filters/crt/src/crt.frag @@ -1,66 +1,84 @@ -varying vec2 vTextureCoord; +precision highp float; +in vec2 vTextureCoord; +out vec4 finalColor; + uniform sampler2D uSampler; +uniform vec4 uLine; +uniform vec2 uNoise; +uniform vec3 uVignette; +uniform float uSeed; +uniform float uTime; +uniform vec2 uDimensions; -uniform vec4 filterArea; -uniform vec2 dimensions; +uniform vec4 uInputSize; const float SQRT_2 = 1.414213; -const float light = 1.0; +float rand(vec2 co) { + return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); +} + +float vignette(vec3 co, vec2 coord) +{ + float outter = SQRT_2 - uVignette[0] * SQRT_2; + vec2 dir = vec2(0.5) - coord; + dir.y *= uDimensions.y / uDimensions.x; + float darker = clamp((outter - length(dir) * SQRT_2) / ( 0.00001 + uVignette[2] * SQRT_2), 0.0, 1.0); + return darker + (1.0 - darker) * (1.0 - uVignette[1]); +} + +float noise(vec2 coord) +{ + vec2 pixelCoord = coord * uInputSize.xy; + pixelCoord.x = floor(pixelCoord.x / uNoise[1]); + pixelCoord.y = floor(pixelCoord.y / uNoise[1]); + return (rand(pixelCoord * uNoise[1] * uSeed) - 0.5) * uNoise[0]; +} -uniform float curvature; -uniform float lineWidth; -uniform float lineContrast; -uniform bool verticalLine; -uniform float noise; -uniform float noiseSize; +vec3 interlaceLines(vec3 co, vec2 coord) +{ + vec3 color = co; -uniform float vignetting; -uniform float vignettingAlpha; -uniform float vignettingBlur; + float curvature = uLine[0]; + float lineWidth = uLine[1]; + float lineContrast = uLine[2]; + float verticalLine = uLine[3]; -uniform float seed; -uniform float time; + vec2 dir = vec2(coord * uInputSize.xy / uDimensions - 0.5); -float rand(vec2 co) { - return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); + float _c = curvature > 0. ? curvature : 1.; + float k = curvature > 0. ? (length(dir * dir) * 0.25 * _c * _c + 0.935 * _c) : 1.; + vec2 uv = dir * k; + float v = verticalLine > 0.5 ? uv.x * uDimensions.x : uv.y * uDimensions.y; + v *= min(1.0, 2.0 / lineWidth ) / _c; + float j = 1. + cos(v * 1.2 - uTime) * 0.5 * lineContrast; + color *= j; + + float segment = verticalLine > 0.5 ? mod((dir.x + .5) * uDimensions.x, 4.) : mod((dir.y + .5) * uDimensions.y, 4.); + color *= 0.99 + ceil(segment) * 0.015; + + return color; } void main(void) { - vec2 pixelCoord = vTextureCoord.xy * filterArea.xy; - vec2 dir = vec2(vTextureCoord.xy * filterArea.xy / dimensions - vec2(0.5, 0.5)); - - gl_FragColor = texture2D(uSampler, vTextureCoord); - vec3 rgb = gl_FragColor.rgb; + finalColor = texture(uSampler, vTextureCoord); + vec2 coord = vTextureCoord * uInputSize.xy / uDimensions; - if (noise > 0.0 && noiseSize > 0.0) + if (uNoise[0] > 0.0 && uNoise[1] > 0.0) { - pixelCoord.x = floor(pixelCoord.x / noiseSize); - pixelCoord.y = floor(pixelCoord.y / noiseSize); - float _noise = rand(pixelCoord * noiseSize * seed) - 0.5; - rgb += _noise * noise; + float n = noise(vTextureCoord); + finalColor += vec4(n, n, n, finalColor.a); } - if (lineWidth > 0.0) + if (uVignette[0] > 0.) { - float _c = curvature > 0. ? curvature : 1.; - float k = curvature > 0. ?(length(dir * dir) * 0.25 * _c * _c + 0.935 * _c) : 1.; - vec2 uv = dir * k; - - float v = (verticalLine ? uv.x * dimensions.x : uv.y * dimensions.y) * min(1.0, 2.0 / lineWidth ) / _c; - float j = 1. + cos(v * 1.2 - time) * 0.5 * lineContrast; - rgb *= j; - float segment = verticalLine ? mod((dir.x + .5) * dimensions.x, 4.) : mod((dir.y + .5) * dimensions.y, 4.); - rgb *= 0.99 + ceil(segment) * 0.015; + float v = vignette(finalColor.rgb, coord); + finalColor *= vec4(v, v, v, finalColor.a); } - if (vignetting > 0.0) + if (uLine[1] > 0.0) { - float outter = SQRT_2 - vignetting * SQRT_2; - float darker = clamp((outter - length(dir) * SQRT_2) / ( 0.00001 + vignettingBlur * SQRT_2), 0.0, 1.0); - rgb *= darker + (1.0 - darker) * (1.0 - vignettingAlpha); + finalColor = vec4(interlaceLines(finalColor.rgb, vTextureCoord), finalColor.a); } - - gl_FragColor.rgb = rgb; } diff --git a/filters/crt/src/crt.wgsl b/filters/crt/src/crt.wgsl new file mode 100644 index 000000000..89e6d984e --- /dev/null +++ b/filters/crt/src/crt.wgsl @@ -0,0 +1,107 @@ +struct CRTUniforms { + uLine: vec4, + uNoise: vec2, + uVignette: vec3, + uSeed: f32, + 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 crtUniforms : CRTUniforms; + +@fragment +fn mainFragment( + @builtin(position) position: vec4, + @location(0) uv : vec2 +) -> @location(0) vec4 { + + var color: vec4 = textureSample(uSampler, uSampler, uv); + let coord: vec2 = uv * gfu.uInputSize.xy / crtUniforms.uDimensions; + + let uNoise = crtUniforms.uNoise; + + if (uNoise[0] > 0.0 && uNoise[1] > 0.0) + { + color += vec4(vec3(noise(uv)), color.a); + } + + if (crtUniforms.uVignette[0] > 0.) + { + color *= vec4(vec3(vignette(color.rgb, coord)), color.a); + } + + if (crtUniforms.uLine[1] > 0.0) + { + color = vec4(vec3(interlaceLines(color.rgb, uv)), color.a); + } + + return color; +} + +const SQRT_2: f32 = 1.414213; + +fn rand(co: vec2) -> f32 +{ + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +fn vignette(co: vec3, coord: vec2) -> f32 +{ + let uVignette = crtUniforms.uVignette; + let uDimensions = crtUniforms.uDimensions; + + let outter: f32 = SQRT_2 - uVignette[0] * SQRT_2; + var dir: vec2 = vec2(0.5) - coord; + dir.y *= uDimensions.y / uDimensions.x; + let darker: f32 = clamp((outter - length(dir) * SQRT_2) / ( 0.00001 + uVignette[2] * SQRT_2), 0.0, 1.0); + return darker + (1.0 - darker) * (1.0 - uVignette[1]); +} + +fn noise(coord: vec2) -> f32 +{ + let uNoise = crtUniforms.uNoise; + let uSeed = crtUniforms.uSeed; + + var pixelCoord: vec2 = coord * gfu.uInputSize.xy; + pixelCoord.x = floor(pixelCoord.x / uNoise[1]); + pixelCoord.y = floor(pixelCoord.y / uNoise[1]); + return (rand(pixelCoord * uNoise[1] * uSeed) - 0.5) * uNoise[0]; +} + +fn interlaceLines(co: vec3, coord: vec2) -> vec3 +{ + var color = co; + + let uDimensions = crtUniforms.uDimensions; + + let curvature: f32 = crtUniforms.uLine[0]; + let lineWidth: f32 = crtUniforms.uLine[1]; + let lineContrast: f32 = crtUniforms.uLine[2]; + let verticalLine: f32 = crtUniforms.uLine[3]; + + let dir: vec2 = vec2(coord * gfu.uInputSize.xy / uDimensions - 0.5); + + let _c: f32 = select(1., curvature, curvature > 0.); + let k: f32 = select(1., (length(dir * dir) * 0.25 * _c * _c + 0.935 * _c), curvature > 0.); + let uv: vec2 = dir * k; + let v: f32 = select(uv.y * uDimensions.y, uv.x * uDimensions.x, verticalLine > 0.5) * min(1.0, 2.0 / lineWidth ) / _c; + let j: f32 = 1. + cos(v * 1.2 - crtUniforms.uTime) * 0.5 * lineContrast; + color *= j; + + let segment: f32 = select(modulo((dir.y + .5) * uDimensions.y, 4.), modulo((dir.x + .5) * uDimensions.x, 4.), verticalLine > 0.5); + color *= 0.99 + ceil(segment) * 0.015; + + return color; +} \ No newline at end of file diff --git a/tools/demo/src/filters/crt.js b/tools/demo/src/filters/crt.js index 8d84c31fa..1d190981f 100644 --- a/tools/demo/src/filters/crt.js +++ b/tools/demo/src/filters/crt.js @@ -3,12 +3,12 @@ export default function () const app = this; app.addFilter('CRTFilter', { - args: [{ + args: { lineWidth: 3, lineContrast: 0.3, noise: 0.2, time: 0.5, - }], + }, oncreate(folder) { const filter = this; diff --git a/tools/demo/src/index.js b/tools/demo/src/index.js index e35101d8d..560eb20cd 100644 --- a/tools/demo/src/index.js +++ b/tools/demo/src/index.js @@ -53,6 +53,7 @@ const main = async () => filters.colorGradient.call(app); filters.bulgePinch.call(app); filters.bevel.call(app); + filters.crt.call(app); // filters.kawaseBlur.call(app); // TODO: Re-enable this in place of the above once v8 conversion is complete