Skip to content

Commit 0be9718

Browse files
committed
Implement NoiseEffect
Addresses #597 and #692
1 parent ecb6d57 commit 0be9718

5 files changed

Lines changed: 373 additions & 1 deletion

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import {
2+
LoadingManager,
3+
Mesh,
4+
MeshBasicMaterial,
5+
PerspectiveCamera,
6+
PlaneGeometry,
7+
SRGBColorSpace,
8+
Scene,
9+
Texture,
10+
TextureLoader,
11+
WebGLRenderer
12+
} from "three";
13+
14+
import {
15+
ClearPass,
16+
EffectPass,
17+
GeometryPass,
18+
MixBlendFunction,
19+
NoiseEffect,
20+
RenderPipeline
21+
} from "postprocessing";
22+
23+
import { Pane } from "tweakpane";
24+
import { ControlMode, SpatialControls } from "spatial-controls";
25+
import * as Utils from "../utils/index.js";
26+
27+
function load(): Promise<Map<string, Texture>> {
28+
29+
const assets = new Map<string, Texture>();
30+
const loadingManager = new LoadingManager();
31+
const textureLoader = new TextureLoader(loadingManager);
32+
33+
return new Promise<Map<string, Texture>>((resolve, reject) => {
34+
35+
loadingManager.onLoad = () => resolve(assets);
36+
loadingManager.onError = (url) => reject(new Error(`Failed to load ${url}`));
37+
38+
textureLoader.load(`${document.baseURI}img/textures/photos/GEDC0053.jpg`, (t) => {
39+
40+
t.colorSpace = SRGBColorSpace;
41+
assets.set("photo", t);
42+
43+
});
44+
45+
});
46+
47+
}
48+
49+
window.addEventListener("load", () => void load().then((assets) => {
50+
51+
// Renderer
52+
53+
const renderer = new WebGLRenderer({
54+
powerPreference: "high-performance",
55+
antialias: false,
56+
stencil: false,
57+
depth: false
58+
});
59+
60+
renderer.setPixelRatio(window.devicePixelRatio);
61+
renderer.debug.checkShaderErrors = Utils.isLocalhost;
62+
63+
// Camera & Controls
64+
65+
const camera = new PerspectiveCamera();
66+
const controls = new SpatialControls(camera.position, camera.quaternion, renderer.domElement);
67+
const settings = controls.settings;
68+
settings.general.mode = ControlMode.THIRD_PERSON;
69+
settings.zoom.sensitivity = 0.05;
70+
settings.zoom.damping = 0.1;
71+
settings.rotation.sensitivity = 0;
72+
settings.translation.enabled = false;
73+
controls.position.set(0, 0, 1.4);
74+
75+
// Scene & Objects
76+
77+
const scene = new Scene();
78+
const mesh = new Mesh(
79+
new PlaneGeometry(),
80+
new MeshBasicMaterial({
81+
map: assets.get("photo")!
82+
})
83+
);
84+
85+
mesh.scale.x = 2;
86+
scene.add(mesh);
87+
88+
// Post Processing
89+
90+
const effect = new NoiseEffect();
91+
//effect.blendMode.blendFunction = new MixBlendFunction();
92+
93+
const pipeline = new RenderPipeline(renderer);
94+
pipeline.add(
95+
new ClearPass(),
96+
new GeometryPass(scene, camera, { samples: 4 }),
97+
new EffectPass(effect)
98+
);
99+
100+
// Settings
101+
102+
const container = document.getElementById("viewport")!;
103+
const pane = new Pane({ container: container.querySelector<HTMLElement>(".tp")! });
104+
const fpsGraph = Utils.createFPSGraph(pane);
105+
const folder = pane.addFolder({ title: "Settings" });
106+
folder.addBinding(effect, "rgb");
107+
folder.addBinding(effect, "premultiply");
108+
folder.addBinding(effect, "fps", { min: 0, max: 240, step: 1 });
109+
Utils.addBlendModeBindings(folder, effect.blendMode);
110+
111+
// Resize Handler
112+
113+
function onResize(): void {
114+
115+
const width = container.clientWidth;
116+
const height = container.clientHeight;
117+
camera.aspect = width / height;
118+
camera.fov = Utils.calculateVerticalFoV(90, Math.max(camera.aspect, 16 / 9));
119+
camera.updateProjectionMatrix();
120+
pipeline.setSize(width, height);
121+
122+
}
123+
124+
window.addEventListener("resize", onResize);
125+
onResize();
126+
127+
// Render Loop
128+
129+
pipeline.compile().then(() => {
130+
131+
container.prepend(renderer.domElement);
132+
133+
renderer.setAnimationLoop((timestamp) => {
134+
135+
fpsGraph.begin();
136+
controls.update(timestamp);
137+
pipeline.render(timestamp);
138+
fpsGraph.end();
139+
140+
});
141+
142+
}).catch((e) => console.error(e));
143+
144+
}));

manual/content/demos/special-effects/noise.en.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
layout: single
33
collection: sections
44
title: Noise
5-
draft: true
5+
draft: false
66
menu:
77
demos:
88
parent: special-effects

src/effects/NoiseEffect.ts

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { Uniform } from "three";
2+
import { SoftLightBlendFunction } from "./blending/index.js";
3+
import { Effect } from "./Effect.js";
4+
5+
import fragmentShader from "./shaders/noise.frag";
6+
7+
/**
8+
* A noise effect options
9+
*
10+
* @category Effects
11+
*/
12+
13+
export interface NoiseEffectOptions {
14+
15+
/**
16+
* Enables or disables RGB noise.
17+
*
18+
* @defaultValue true
19+
*/
20+
21+
rgb?: boolean;
22+
23+
/**
24+
* Controls whether the noise should be multiplied with the input colors prior to blending.
25+
*
26+
* @defaultValue false
27+
*/
28+
29+
premultiply?: boolean;
30+
31+
/**
32+
* The animation update rate expressed in frames per second.
33+
*
34+
* This value does not affect performance. The update rate is limited by the actual frame rate.
35+
*
36+
* @defaultValue 24
37+
*/
38+
39+
fps?: number;
40+
41+
}
42+
43+
/**
44+
* A noise effect.
45+
*
46+
* @see https://www.shadertoy.com/view/w3B3Wz
47+
* @category Effects
48+
*/
49+
50+
export class NoiseEffect extends Effect implements NoiseEffectOptions {
51+
52+
/**
53+
* @see {@link fps}
54+
*/
55+
56+
private _fps: number;
57+
58+
/**
59+
* The animation update timeout in seconds.
60+
*/
61+
62+
private timeout: number;
63+
64+
/**
65+
* A time accumulator.
66+
*/
67+
68+
private acc: number;
69+
70+
/**
71+
* Constructs a new noise effect.
72+
*/
73+
74+
constructor({
75+
rgb = true,
76+
premultiply = false,
77+
fps = 24
78+
}: NoiseEffectOptions = {}) {
79+
80+
super("NoiseEffect");
81+
82+
this.fragmentShader = fragmentShader;
83+
this.blendMode.blendFunction = new SoftLightBlendFunction();
84+
85+
this.input.uniforms.set("page", new Uniform(1.0));
86+
this.input.defines.set("SEED", Math.round(Math.random() * 1000.0).toFixed(1));
87+
88+
this._fps = 0;
89+
this.timeout = 0.0;
90+
this.acc = 0.0;
91+
92+
this.rgb = rgb;
93+
this.premultiply = premultiply;
94+
this.fps = fps;
95+
96+
}
97+
98+
/**
99+
* The current noise page.
100+
*/
101+
102+
private get page(): number {
103+
104+
return this.input.uniforms.get("page")!.value as number;
105+
106+
}
107+
108+
private set page(value: number) {
109+
110+
this.input.uniforms.get("page")!.value = value;
111+
112+
}
113+
114+
get fps(): number {
115+
116+
return this._fps;
117+
118+
}
119+
120+
set fps(value: number) {
121+
122+
this._fps = value;
123+
this.timeout = (value !== 0.0) ? 1.0 / value : 0.0;
124+
125+
}
126+
127+
get rgb(): boolean {
128+
129+
return this.input.defines.has("RGB");
130+
131+
}
132+
133+
set rgb(value: boolean) {
134+
135+
if(this.rgb !== value) {
136+
137+
if(value) {
138+
139+
this.input.defines.set("RGB", true);
140+
141+
} else {
142+
143+
this.input.defines.delete("RGB");
144+
145+
}
146+
147+
this.setChanged();
148+
149+
}
150+
151+
}
152+
153+
get premultiply(): boolean {
154+
155+
return this.input.defines.has("PREMULTIPLY");
156+
157+
}
158+
159+
set premultiply(value: boolean) {
160+
161+
if(this.premultiply !== value) {
162+
163+
if(value) {
164+
165+
this.input.defines.set("PREMULTIPLY", true);
166+
167+
} else {
168+
169+
this.input.defines.delete("PREMULTIPLY");
170+
171+
}
172+
173+
this.setChanged();
174+
175+
}
176+
177+
}
178+
179+
override render(): void {
180+
181+
if(this.timer === null || this.fps === 0) {
182+
183+
return;
184+
185+
}
186+
187+
this.acc += this.timer.getDelta();
188+
189+
if(this.acc >= this.timeout) {
190+
191+
this.page = (this.page + 1.0) % 1000.0;
192+
this.acc = 0.0;
193+
194+
}
195+
196+
}
197+
198+
}

src/effects/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from "./HalftoneEffect.js";
99
export * from "./LensDistortionEffect.js";
1010
export * from "./LUT1DEffect.js";
1111
export * from "./LUT3DEffect.js";
12+
export * from "./NoiseEffect.js";
1213
export * from "./ScanlineEffect.js";
1314
export * from "./SMAAEffect.js";
1415
export * from "./TextureEffect.js";

src/effects/shaders/noise.frag

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
uniform float page;
2+
3+
vec4 mainImage(const in vec4 inputColor, const in vec2 uv, const in GData gData) {
4+
5+
vec2 p = uv * (SEED + page);
6+
7+
#ifdef RGB
8+
9+
vec2 pR = p;
10+
vec2 pG = vec2(p.x + 1.0, p.y);
11+
vec2 pB = vec2(p.x, p.y + 1.0);
12+
13+
vec3 noise = vec3(rand(pR), rand(pG), rand(pB));
14+
15+
#else
16+
17+
vec3 noise = vec3(rand(p));
18+
19+
#endif
20+
21+
#ifdef PREMULTIPLY
22+
23+
noise *= inputColor.rgb;
24+
25+
#endif
26+
27+
return vec4(noise, inputColor.a);
28+
29+
}

0 commit comments

Comments
 (0)