From 7b721a80a9055fa8681ffb43a5bf2bd4b6b213e3 Mon Sep 17 00:00:00 2001
From: mehmetcanakbay <73856544+mehmetcanakbay@users.noreply.github.com>
Date: Tue, 7 Jan 2025 03:37:15 +0300
Subject: [PATCH] feat: Add Simplex Noise Filter (#482)
---
README.md | 2 +
examples/src/filters/index.mjs | 1 +
examples/src/filters/simplex-noise.mjs | 16 +++
package.json | 5 +
scripts/screenshots/config.json | 8 ++
src/index.ts | 1 +
src/simplex-noise/SimplexNoiseFilter.ts | 143 ++++++++++++++++++++++++
src/simplex-noise/index.ts | 1 +
src/simplex-noise/simplex.frag | 61 ++++++++++
src/simplex-noise/simplex.wgsl | 61 ++++++++++
10 files changed, 299 insertions(+)
create mode 100644 examples/src/filters/simplex-noise.mjs
create mode 100644 src/simplex-noise/SimplexNoiseFilter.ts
create mode 100644 src/simplex-noise/index.ts
create mode 100644 src/simplex-noise/simplex.frag
create mode 100644 src/simplex-noise/simplex.wgsl
diff --git a/README.md b/README.md
index 30525df39..e2f1ce05a 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**
_pixi-filters/rgb-split_
[View demo][RGBSplit_demo] | ![rgb split](https://filters.pixijs.download/main/screenshots/rgb.png?v=3) | |
| **ShockwaveFilter**
_pixi-filters/shockwave_
[View demo][Shockwave_demo] | ![shockwave](https://filters.pixijs.download/main/screenshots/shockwave.gif?v=3) |
| **SimpleLightmapFilter**
_pixi-filters/simple-lightmap_
[View demo][SimpleLightmap_demo] | ![simple-lightmap](https://filters.pixijs.download/main/screenshots/simple-lightmap.png?v=3) |
+| **SimplexNoiseFilter**
_pixi-filters/simplex-noise_
[View demo][SimplexNoise_demo] | ![simplex-noise](https://filters.pixijs.download/main/screenshots/simplex-noise.png?v=3) |
| **TiltShiftFilter**
_pixi-filters/tilt-shift_
[View demo][TiltShift_demo] | ![tilt-shift](https://filters.pixijs.download/main/screenshots/tilt-shift.png?v=3) |
| **TwistFilter**
_pixi-filters/twist_
[View demo][Twist_demo] | ![twist](https://filters.pixijs.download/main/screenshots/twist.png?v=3) |
| **ZoomBlurFilter**
_pixi-filters/zoom-blur_
[View demo][ZoomBlur_demo] | ![zoom-blur](https://filters.pixijs.download/main/screenshots/zoom-blur.png?v=4) |
@@ -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 e294af188..7c3c42a5b 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 000000000..53855a457
--- /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 8472e0338..14adc066f 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 5d4ef0678..c3d5623dc 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 3a96e4b4a..9fc52c18f 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 000000000..e02f339e8
--- /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.
+ * ![original](../screenshots/original.png)![filter](../screenshots/simplex-noise.png)
+ * @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 000000000..e79f99fd3
--- /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 000000000..827f4a361
--- /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 000000000..ac089c332
--- /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,
+ uInputPixel:vec4,
+ uInputClamp:vec4,
+ uOutputFrame:vec4,
+ uGlobalFrame:vec4,
+ uOutputTexture:vec4,
+};
+
+@group(0) @binding(0) var gfu: GlobalFilterUniforms;
+
+@group(0) @binding(1) var uTexture: texture_2d;
+@group(0) @binding(2) var uSampler: sampler;
+@group(1) @binding(0) var simplexUniforms : SimplexUniforms;
+
+@fragment
+fn mainFragment(
+ @location(0) uv: vec2,
+ @builtin(position) position: vec4
+) -> @location(0) vec4 {
+ var noise: f32 = simplex_noise(vec3(uv * simplexUniforms.uNoiseScale + vec2(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 = vec3(0.1031, 0.11369, 0.13787);
+fn hash33(p3: vec3) -> vec3 {
+ 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((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 {
+ let K1: f32 = 0.33333334;
+ let K2: f32 = 0.16666667;
+ let i: vec3 = floor(p + (p.x + p.y + p.z) * K1);
+ let d0: vec3 = p - (i - (i.x + i.y + i.z) * K2);
+ let e: vec3 = step(vec3(0.), d0 - d0.yzx);
+ let i1: vec3 = e * (1. - e.zxy);
+ let i2: vec3 = 1. - e.zxy * (1. - e);
+ let d1: vec3 = d0 - (i1 - 1. * K2);
+ let d2: vec3 = d0 - (i2 - 2. * K2);
+ let d3: vec3 = d0 - (1. - 3. * K2);
+ let h: vec4 = max(vec4(0.6) - vec4(dot(d0, d0), dot(d1, d1), dot(d2, d2), dot(d3, d3)), vec4(0.0));
+ let n: vec4 = h * h * h * h * vec4(dot(d0, hash33(i)), dot(d1, hash33(i + i1)), dot(d2, hash33(i + i2)), dot(d3, hash33(i + 1.)));
+ return dot(vec4(31.316), n);
+}
\ No newline at end of file