Skip to content

Commit

Permalink
Add Halftone Effect (#619)
Browse files Browse the repository at this point in the history
* Add Halftone Effect

* Use preprocessor conditional block for HalftoneEffect shape configuration
  • Loading branch information
balraj-johal authored Apr 10, 2024
1 parent 5a8f27b commit 6c9a289
Show file tree
Hide file tree
Showing 8 changed files with 439 additions and 15 deletions.
34 changes: 20 additions & 14 deletions manual/assets/js/src/demos/halftone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ import {

import {
ClearPass,
EffectPass,
GeometryPass,
RenderPipeline
HalftoneEffect,
HalftoneShape,
RenderPipeline,
ToneMappingEffect
} from "postprocessing";

import { Pane } from "tweakpane";
Expand Down Expand Up @@ -65,7 +69,7 @@ window.addEventListener("load", () => void load().then((assets) => {
settings.rotation.sensitivity = 2.2;
settings.rotation.damping = 0.05;
settings.translation.damping = 0.1;
controls.position.set(1, 10, 1);
controls.position.set(-1, 10, -1);
controls.lookAt(0, 10, 0);

// Scene, Lights, Objects
Expand All @@ -79,32 +83,34 @@ window.addEventListener("load", () => void load().then((assets) => {

// Post Processing

const effect = new HalftoneEffect();

const pipeline = new RenderPipeline(renderer);
pipeline.add(
new ClearPass(),
new GeometryPass(scene, camera, {
frameBufferType: HalfFloatType,
samples: 4
})
}),
new EffectPass(effect, new ToneMappingEffect())
);

/*
const effect = new HalftoneEffect({ angle: 1.4, scale: 0.7 });
pipeline.addPass(new EffectPass(effect, new ToneMappingEffect()));
*/

// Settings

const pane = new Pane({ container: container.querySelector(".tp") as HTMLElement });
const fpsGraph = Utils.createFPSGraph(pane);

/*

const folder = pane.addFolder({ title: "Settings" });
folder.addBinding(effect, "angle", { min: 0, max: Math.PI, step: 1e-3 });
folder.addBinding(effect, "scale", { min: 0, max: 2, step: 1e-3 });
folder.addBinding(effect.blendMode, "opacity", { min: 0, max: 1, step: 0.01 });
folder.addBinding(effect.blendMode, "blendFunction", { options: BlendFunction });
*/
folder.addBinding(effect, "radius", { min: 1, max: 25, step: 1e-3 });
folder.addBinding(effect, "samples", { min: 0, max: 24, step: 1 });
folder.addBinding(effect, "scatterFactor", { min: 0, max: 1, step: 1e-3 });
folder.addBinding(effect, "rotationR", { min: 0, max: Math.PI / 2, step: 1e-3 });
folder.addBinding(effect, "rotationG", { min: 0, max: Math.PI / 2, step: 1e-3 });
folder.addBinding(effect, "rotationB", { min: 0, max: Math.PI / 2, step: 1e-3 });
folder.addBinding(effect, "shape", { options: Utils.enumToRecord(HalftoneShape) });

Utils.addBlendModeBindings(folder, effect.blendMode);

// Resize Handler

Expand Down
2 changes: 1 addition & 1 deletion manual/content/demos/special-effects/halftone.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
layout: single
collection: sections
title: Halftone
draft: true
draft: false
menu:
demos:
parent: special-effects
Expand Down
190 changes: 190 additions & 0 deletions src/effects/HalftoneEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { HalftoneShape } from "../enums/HalftoneShape.js";
import { Effect } from "./Effect.js";
import { OverlayBlendFunction } from "./blending/blend-functions/OverlayBlendFunction.js";

import fragmentShader from "./shaders/halftone.frag";
import { Uniform, Vector3 } from "three";

/**
* HalftoneEffect options.
*
* @category Effects
*/

export interface HalftoneEffectOptions {
shape?: HalftoneShape;
radius?: number;
rotationR?: number;
rotationG?: number;
rotationB?: number;
scatterFactor?: number;
samples?: number;
}

/**
* A halftone effect.
*
* Based on the implementation found in Three.js:
* https://github.com/mrdoob/three.js/blob/0bf3908b73b2cf73d7361cce17cfc8b816cb2a00/examples/jsm/postprocessing/HalftonePass.js
*
* @category Effects
*/

export class HalftoneEffect extends Effect {

/**
* Constructs a new halftone effect.
*
* @param options - The options.
*/

constructor({
shape = HalftoneShape.ELLIPSE,
radius = 6,
rotationR = 14,
rotationG = 45,
rotationB = 30,
scatterFactor = 0,
samples = 8
}: HalftoneEffectOptions = {}) {

super("HalftoneEffect");

this.fragmentShader = fragmentShader;
this.samples = samples;
this.shape = shape;

this.blendMode.blendFunction = new OverlayBlendFunction();

const uniforms = this.input.uniforms;
uniforms.set("radius", new Uniform(radius));
uniforms.set("rotationRGB", new Uniform(new Vector3(rotationR, rotationG, rotationB)));
uniforms.set("scatterFactor", new Uniform(scatterFactor));

}

/**
* The halftone dot radius.
*/

get radius() {

return this.input.uniforms.get("radius")!.value as number;

}

set radius(value: number) {

this.input.uniforms.get("radius")!.value = value;

}

/**
* The halftone dot scatterFactor.
*/

get scatterFactor() {

return this.input.uniforms.get("scatterFactor")!.value as number;

}

set scatterFactor(value: number) {

this.input.uniforms.get("scatterFactor")!.value = value;

}

/**
* The halftone dot grid rotation in the red channel.
*/

get rotationR() {

const rotationRGB = this.input.uniforms.get("rotationRGB")!.value as Vector3;
return rotationRGB.x;

}

set rotationR(value: number) {

const rotationRGB = this.input.uniforms.get("rotationRGB")!.value as Vector3;
rotationRGB.x = value;
this.input.uniforms.get("rotationRGB")!.value = rotationRGB;

}

/**
* The halftone dot grid rotation in the green channel.
*/

get rotationG() {

const rotationRGB = this.input.uniforms.get("rotationRGB")!.value as Vector3;
return rotationRGB.y;

}

set rotationG(value: number) {

const rotationRGB = this.input.uniforms.get("rotationRGB")!.value as Vector3;
rotationRGB.y = value;
this.input.uniforms.get("rotationRGB")!.value = rotationRGB;

}

/**
* The halftone dot grid rotation in the red channel.
*/

get rotationB() {

const rotationRGB = this.input.uniforms.get("rotationRGB")!.value as Vector3;
return rotationRGB.z;

}

set rotationB(value: number) {

const rotationRGB = this.input.uniforms.get("rotationRGB")!.value as Vector3;
rotationRGB.z = value;
this.input.uniforms.get("rotationRGB")!.value = rotationRGB;

}

/**
* The halftone dot shape.
*/

get shape() {

return this.input.defines.get("SHAPE") as number;

}

set shape(value: HalftoneShape) {


this.input.defines.set("SHAPE", value);
this.setChanged();

}

/**
* The amount of samples.
*/

get samples(): number {

return this.input.defines.get("SAMPLES") as number;

}

set samples(value: number) {

this.input.defines.set("SAMPLES", value);
this.setChanged();

}

}
1 change: 1 addition & 0 deletions src/effects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from "./BloomEffect.js";
export * from "./ColorDepthEffect.js";
export * from "./Effect.js";
export * from "./FXAAEffect.js";
export * from "./HalftoneEffect.js";
export * from "./ScanlineEffect.js";
export * from "./SMAAEffect.js";
export * from "./TextureEffect.js";
Expand Down
Loading

0 comments on commit 6c9a289

Please sign in to comment.