diff --git a/README.md b/README.md index 30525df3..e2f1ce05 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ If all else failes, you can manually download the bundled file from the [release | **RGBSplitFilter**<br>_pixi-filters/rgb-split_<br>[View demo][RGBSplit_demo] |  | | | **ShockwaveFilter**<br>_pixi-filters/shockwave_<br>[View demo][Shockwave_demo] |  | | **SimpleLightmapFilter**<br>_pixi-filters/simple-lightmap_<br>[View demo][SimpleLightmap_demo] |  | +| **SimplexNoiseFilter**<br>_pixi-filters/simplex-noise_<br>[View demo][SimplexNoise_demo] |  | | **TiltShiftFilter**<br>_pixi-filters/tilt-shift_<br>[View demo][TiltShift_demo] |  | | **TwistFilter**<br>_pixi-filters/twist_<br>[View demo][Twist_demo] |  | | **ZoomBlurFilter**<br>_pixi-filters/zoom-blur_<br>[View demo][ZoomBlur_demo] |  | @@ -152,6 +153,7 @@ API documention can be found [here](http://pixijs.io/filters/docs/). [RGBSplit_demo]: https://filters.pixijs.download/main/examples/index.html?enabled=RGBSplitFilter [Shockwave_demo]: https://filters.pixijs.download/main/examples/index.html?enabled=ShockwaveFilter [SimpleLightmap_demo]: https://filters.pixijs.download/main/examples/index.html?enabled=SimpleLightmapFilter +[SimplexNoise_demo]: https://filters.pixijs.download/main/examples/index.html?enabled=SimplexNoiseFilter [TiltShift_demo]: https://filters.pixijs.download/main/examples/index.html?enabled=TiltShiftFilter [Twist_demo]: https://filters.pixijs.download/main/examples/index.html?enabled=TwistFilter [ZoomBlur_demo]: https://filters.pixijs.download/main/examples/index.html?enabled=ZoomBlurFilter diff --git a/examples/src/filters/index.mjs b/examples/src/filters/index.mjs index e294af18..7c3c42a5 100644 --- a/examples/src/filters/index.mjs +++ b/examples/src/filters/index.mjs @@ -38,6 +38,7 @@ export { default as reflection } from './reflection.mjs'; export { default as rgb } from './rgb.mjs'; export { default as shockwave } from './shockwave.mjs'; export { default as simpleLightmap } from './lightmap.mjs'; +export { default as simplexNoise } from './simplex-noise.mjs'; export { default as tiltShift } from './tilt-shift.mjs'; export { default as twist } from './twist.mjs'; export { default as zoomBlur } from './zoom-blur.mjs'; diff --git a/examples/src/filters/simplex-noise.mjs b/examples/src/filters/simplex-noise.mjs new file mode 100644 index 00000000..53855a45 --- /dev/null +++ b/examples/src/filters/simplex-noise.mjs @@ -0,0 +1,16 @@ +export default function () +{ + const app = this; + + app.addFilter('SimplexNoiseFilter', { + oncreate(folder) + { + folder.add(this, 'strength', 0, 1).name('strength'); + folder.add(this, 'noiseScale', 0, 50).name('noise scale'); + folder.add(this, 'offsetX', 0, 5).name('offsetX'); + folder.add(this, 'offsetY', 0, 5).name('offsetY'); + folder.add(this, 'offsetZ', 0, 5).name('offsetZ'); + folder.add(this, 'step', -1, 1).name('step'); + }, + }); +} diff --git a/package.json b/package.json index 8472e033..14adc066 100644 --- a/package.json +++ b/package.json @@ -168,6 +168,11 @@ "require": "./lib/simple-lightmap/index.js", "types": "./lib/simple-lightmap/index.d.ts" }, + "./simplex-noise": { + "import": "./lib/simplex-noise/index.mjs", + "require": "./lib/simplex-noise/index.js", + "types": "./lib/simplex-noise/index.d.ts" + }, "./tilt-shift": { "import": "./lib/tilt-shift/index.mjs", "require": "./lib/tilt-shift/index.js", diff --git a/scripts/screenshots/config.json b/scripts/screenshots/config.json index 5d4ef067..c3d5623d 100644 --- a/scripts/screenshots/config.json +++ b/scripts/screenshots/config.json @@ -585,6 +585,14 @@ "arguments": { "strength": 4 } + }, + { + "name": "SimplexNoiseFilter", + "filename": "simplex-noise", + "arguments": { + "strength": 0.5, + "noiseScale": 10 + } } ] } diff --git a/src/index.ts b/src/index.ts index 3a96e4b4..9fc52c18 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,6 +32,7 @@ export * from './reflection'; export * from './rgb-split'; export * from './shockwave'; export * from './simple-lightmap'; +export * from './simplex-noise'; export * from './tilt-shift'; export * from './twist'; export * from './zoom-blur'; diff --git a/src/simplex-noise/SimplexNoiseFilter.ts b/src/simplex-noise/SimplexNoiseFilter.ts new file mode 100644 index 00000000..e02f339e --- /dev/null +++ b/src/simplex-noise/SimplexNoiseFilter.ts @@ -0,0 +1,143 @@ +import { Filter, GlProgram, GpuProgram } from 'pixi.js'; +import { vertex, wgslVertex } from '../defaults'; +import fragment from './simplex.frag'; +import source from './simplex.wgsl'; + +/** Options for the SimplexNoiseFilter constructor. */ +export interface SimplexNoiseFilterOptions +{ + /** + * Noise map strength. + * @default 0.5 + */ + strength?: number; + /** + * Noise map scale. + * @default 10.0 + */ + noiseScale?: number; + /** + * Horizontal offset for the noise map. + * @default 0 + */ + offsetX?: number; + /** + * Vertical offset for the noise map. + * @default 0 + */ + offsetY?: number; + /** + * Depth offset for the noise map. + * @default 0 + */ + offsetZ?: number; + /** + * The threshold used with the step function to create a blocky effect in the noise pattern. + * When this is greater than 0, the step function is used to compare the noise value to this threshold. + * @default -1 + */ + step?: number; +} + +/** + * The SimplexNoiseFilter multiplies simplex noise with the current texture data. <br> + *  + * @class + * @extends Filter + * @see {@link https://www.npmjs.com/package/pixi-filters|pixi-filters} + */ +export class SimplexNoiseFilter extends Filter +{ + /** Default constructor options. */ + public static readonly defaults: SimplexNoiseFilterOptions = { + strength: 0.5, + noiseScale: 10.0, + offsetX: 0, + offsetY: 0, + offsetZ: 0, + step: -1, + }; + + /** + * @param options - Options for the SimplexNoise constructor. + */ + constructor(options?: SimplexNoiseFilterOptions) + { + options = { ...SimplexNoiseFilter.defaults, ...options }; + + const gpuProgram = GpuProgram.from({ + vertex: { + source: wgslVertex, + entryPoint: 'mainVertex', + }, + fragment: { + source, + entryPoint: 'mainFragment', + }, + }); + + const glProgram = GlProgram.from({ + vertex, + fragment, + name: 'simplex-filter', + }); + + super({ + gpuProgram, + glProgram, + resources: { + simplexUniforms: { + uStrength: { value: options?.strength ?? 0, type: 'f32' }, + uNoiseScale: { value: options?.noiseScale ?? 0, type: 'f32' }, + uOffsetX: { value: options?.offsetX ?? 0, type: 'f32' }, + uOffsetY: { value: options?.offsetY ?? 0, type: 'f32' }, + uOffsetZ: { value: options?.offsetZ ?? 0, type: 'f32' }, + uStep: { value: options?.step ?? 0, type: 'f32' }, + } + } + }); + } + + /** + * Strength of the noise (color = (noiseMap + strength) * texture) + * @default 0.5 + */ + get strength(): number { return this.resources.simplexUniforms.uniforms.uStrength; } + set strength(value: number) { this.resources.simplexUniforms.uniforms.uStrength = value; } + + /** + * Noise map scale. + * @default 10 + */ + get noiseScale(): number { return this.resources.simplexUniforms.uniforms.uNoiseScale; } + set noiseScale(value: number) { this.resources.simplexUniforms.uniforms.uNoiseScale = value; } + + /** + * Horizontal offset for the noise map. + * @default 0 + */ + get offsetX(): number { return this.resources.simplexUniforms.uniforms.uOffsetX; } + set offsetX(value: number) { this.resources.simplexUniforms.uniforms.uOffsetX = value; } + + /** + * Vertical offset for the noise map. + * @default 0 + */ + get offsetY(): number { return this.resources.simplexUniforms.uniforms.uOffsetY; } + set offsetY(value: number) { this.resources.simplexUniforms.uniforms.uOffsetY = value; } + + /** + * Depth offset for the noise map. + * @default 0 + */ + get offsetZ(): number { return this.resources.simplexUniforms.uniforms.uOffsetZ; } + set offsetZ(value: number) { this.resources.simplexUniforms.uniforms.uOffsetZ = value; } + + /** + * The threshold used with the step function to create a blocky effect in the noise pattern. + * When this is greater than 0, the step function is used to compare the noise value to this threshold. + * @default -1 + */ + get step(): number { return this.resources.simplexUniforms.uniforms.uStep; } + set step(value: number) { this.resources.simplexUniforms.uniforms.uStep = value; } +} diff --git a/src/simplex-noise/index.ts b/src/simplex-noise/index.ts new file mode 100644 index 00000000..e79f99fd --- /dev/null +++ b/src/simplex-noise/index.ts @@ -0,0 +1 @@ +export * from './SimplexNoiseFilter'; diff --git a/src/simplex-noise/simplex.frag b/src/simplex-noise/simplex.frag new file mode 100644 index 00000000..827f4a36 --- /dev/null +++ b/src/simplex-noise/simplex.frag @@ -0,0 +1,61 @@ +precision highp float; +in vec2 vTextureCoord; +out vec4 finalColor; + +uniform sampler2D uTexture; +uniform float uStrength; +uniform float uNoiseScale; +uniform float uOffsetX; +uniform float uOffsetY; +uniform float uOffsetZ; +uniform float uStep; + +uniform vec4 uInputSize; +uniform vec4 uInputClamp; + +//Noise from: https://www.shadertoy.com/view/4sc3z2 +const vec3 MOD3 = vec3(.1031,.11369,.13787); +vec3 hash33(vec3 p3) +{ + p3 = fract(p3 * MOD3); + p3 += dot(p3, p3.yxz+19.19); + return -1.0 + 2.0 * fract(vec3((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x)); +} + +float simplex_noise(vec3 p) +{ + const float K1 = 0.333333333; + const float K2 = 0.166666667; + + vec3 i = floor(p + (p.x + p.y + p.z) * K1); + vec3 d0 = p - (i - (i.x + i.y + i.z) * K2); + + vec3 e = step(vec3(0.0), d0 - d0.yzx); + vec3 i1 = e * (1.0 - e.zxy); + vec3 i2 = 1.0 - e.zxy * (1.0 - e); + + vec3 d1 = d0 - (i1 - 1.0 * K2); + vec3 d2 = d0 - (i2 - 2.0 * K2); + vec3 d3 = d0 - (1.0 - 3.0 * K2); + + vec4 h = max(0.6 - vec4(dot(d0, d0), dot(d1, d1), dot(d2, d2), dot(d3, d3)), 0.0); + vec4 n = h * h * h * h * vec4(dot(d0, hash33(i)), dot(d1, hash33(i + i1)), dot(d2, hash33(i + i2)), dot(d3, hash33(i + 1.0))); + + return dot(vec4(31.316), n); +} + +void main(void) +{ + float noise = simplex_noise( + vec3(vTextureCoord*uNoiseScale+vec2(uOffsetX, uOffsetY), uOffsetZ) + ) * 0.5 + 0.5; + + noise += 2.0 * uStrength - 1.0; + noise = clamp(noise, 0.0, 1.0); + + if (uStep > 0.0) { //step > 0.5 + noise = 1.0 - step(noise, uStep); + } + + finalColor = texture(uTexture, vTextureCoord) * noise; +} diff --git a/src/simplex-noise/simplex.wgsl b/src/simplex-noise/simplex.wgsl new file mode 100644 index 00000000..ac089c33 --- /dev/null +++ b/src/simplex-noise/simplex.wgsl @@ -0,0 +1,61 @@ +struct SimplexUniforms { + uStrength:f32, + uNoiseScale:f32, + uOffsetX:f32, + uOffsetY:f32, + uOffsetZ:f32, + uStep:f32 +}; + +struct GlobalFilterUniforms { + uInputSize:vec4<f32>, + uInputPixel:vec4<f32>, + uInputClamp:vec4<f32>, + uOutputFrame:vec4<f32>, + uGlobalFrame:vec4<f32>, + uOutputTexture:vec4<f32>, +}; + +@group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms; + +@group(0) @binding(1) var uTexture: texture_2d<f32>; +@group(0) @binding(2) var uSampler: sampler; +@group(1) @binding(0) var<uniform> simplexUniforms : SimplexUniforms; + +@fragment +fn mainFragment( + @location(0) uv: vec2<f32>, + @builtin(position) position: vec4<f32> +) -> @location(0) vec4<f32> { + var noise: f32 = simplex_noise(vec3<f32>(uv * simplexUniforms.uNoiseScale + vec2<f32>(simplexUniforms.uOffsetX, simplexUniforms.uOffsetY), simplexUniforms.uOffsetZ)) * 0.5 + 0.5; + noise = noise + (2. * simplexUniforms.uStrength - 1.); + noise = clamp(noise, 0.0, 1.0); + if (simplexUniforms.uStep > 0.0) { + noise = 1. - step(noise, simplexUniforms.uStep); + } + return textureSample(uTexture, uSampler, uv) * noise; +} + +const MOD3: vec3<f32> = vec3<f32>(0.1031, 0.11369, 0.13787); +fn hash33(p3: vec3<f32>) -> vec3<f32> { + var p3_var = p3; + p3_var = fract(p3_var * MOD3); + p3_var = p3_var + (dot(p3_var, p3_var.yxz + 19.19)); + return -1. + 2. * fract(vec3<f32>((p3_var.x + p3_var.y) * p3_var.z, (p3_var.x + p3_var.z) * p3_var.y, (p3_var.y + p3_var.z) * p3_var.x)); +} + +fn simplex_noise(p: vec3<f32>) -> f32 { + let K1: f32 = 0.33333334; + let K2: f32 = 0.16666667; + let i: vec3<f32> = floor(p + (p.x + p.y + p.z) * K1); + let d0: vec3<f32> = p - (i - (i.x + i.y + i.z) * K2); + let e: vec3<f32> = step(vec3<f32>(0.), d0 - d0.yzx); + let i1: vec3<f32> = e * (1. - e.zxy); + let i2: vec3<f32> = 1. - e.zxy * (1. - e); + let d1: vec3<f32> = d0 - (i1 - 1. * K2); + let d2: vec3<f32> = d0 - (i2 - 2. * K2); + let d3: vec3<f32> = d0 - (1. - 3. * K2); + let h: vec4<f32> = max(vec4<f32>(0.6) - vec4<f32>(dot(d0, d0), dot(d1, d1), dot(d2, d2), dot(d3, d3)), vec4<f32>(0.0)); + let n: vec4<f32> = h * h * h * h * vec4<f32>(dot(d0, hash33(i)), dot(d1, hash33(i + i1)), dot(d2, hash33(i + i2)), dot(d3, hash33(i + 1.))); + return dot(vec4<f32>(31.316), n); +} \ No newline at end of file