Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit dc6b7d1

Browse files
committedFeb 2, 2025
feat(postprocessing): selective bloom effect
1 parent 9e92d42 commit dc6b7d1

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed
 

‎libs/postprocessing/src/lib/effects/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export * from './noise';
1818
export * from './outline';
1919
export * from './pixelation';
2020
export * from './scanline';
21+
export * from './selective-bloom';
2122
export * from './sepia';
2223
export * from './shock-wave';
2324
export * from './smaa';
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// const addLight = (light: Object3D, effect: SelectiveBloomEffect) => light.layers.enable(effect.selection.layer)
2+
// const removeLight = (light: Object3D, effect: SelectiveBloomEffect) => light.layers.disable(effect.selection.layer)
3+
4+
import {
5+
ChangeDetectionStrategy,
6+
Component,
7+
computed,
8+
CUSTOM_ELEMENTS_SCHEMA,
9+
effect,
10+
ElementRef,
11+
inject,
12+
input,
13+
} from '@angular/core';
14+
import { injectStore, NgtArgs, NgtSelection, pick, resolveRef } from 'angular-three';
15+
import { mergeInputs } from 'ngxtension/inject-inputs';
16+
import { BlendFunction, BloomEffectOptions, SelectiveBloomEffect } from 'postprocessing';
17+
import * as THREE from 'three';
18+
import { NgtpEffectComposer } from '../effect-composer';
19+
20+
export type SelectiveBloomOptions = BloomEffectOptions & {
21+
selectionLayer: number;
22+
inverted: boolean;
23+
ignoreBackground: boolean;
24+
};
25+
26+
const defaultOptions: SelectiveBloomOptions = {
27+
selectionLayer: 10,
28+
inverted: false,
29+
ignoreBackground: false,
30+
};
31+
32+
@Component({
33+
selector: 'ngtp-selective-bloom',
34+
template: `
35+
<ngt-primitive *args="[effect()]" [dispose]="null" />
36+
`,
37+
imports: [NgtArgs],
38+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
39+
changeDetection: ChangeDetectionStrategy.OnPush,
40+
})
41+
export class NgtpSelectiveBloom {
42+
lights = input.required<THREE.Object3D[] | ElementRef<THREE.Object3D | undefined>[]>();
43+
selection = input<
44+
| THREE.Object3D
45+
| THREE.Object3D[]
46+
| ElementRef<THREE.Object3D | undefined>
47+
| ElementRef<THREE.Object3D | undefined>[]
48+
>([]);
49+
options = input(defaultOptions, { transform: mergeInputs(defaultOptions) });
50+
51+
private blendFunction = pick(this.options, 'blendFunction');
52+
private luminanceThreshold = pick(this.options, 'luminanceThreshold');
53+
private luminanceSmoothing = pick(this.options, 'luminanceSmoothing');
54+
private mipmapBlur = pick(this.options, 'mipmapBlur');
55+
private intensity = pick(this.options, 'intensity');
56+
private radius = pick(this.options, 'radius');
57+
private levels = pick(this.options, 'levels');
58+
private kernelSize = pick(this.options, 'kernelSize');
59+
private resolutionScale = pick(this.options, 'resolutionScale');
60+
private width = pick(this.options, 'width');
61+
private height = pick(this.options, 'height');
62+
private resolutionX = pick(this.options, 'resolutionX');
63+
private resolutionY = pick(this.options, 'resolutionY');
64+
private inverted = pick(this.options, 'inverted');
65+
private ignoreBackground = pick(this.options, 'ignoreBackground');
66+
private selectionLayer = pick(this.options, 'selectionLayer');
67+
68+
private store = injectStore();
69+
private effectComposer = inject(NgtpEffectComposer);
70+
private ngtSelection = inject(NgtSelection, { optional: true });
71+
72+
private resolvedLights = computed(() => this.lights().map((light) => resolveRef(light)));
73+
private resolvedSelected = computed(() => {
74+
const selection = this.selection();
75+
if (!selection) return [];
76+
const array = Array.isArray(selection) ? selection : [selection];
77+
return array.map((selected) => resolveRef(selected));
78+
});
79+
private resolvedNgtSelected = computed(() => {
80+
if (!this.ngtSelection || !this.ngtSelection.enabled) return [];
81+
return this.ngtSelection.selected().map((selected) => resolveRef(selected));
82+
});
83+
84+
protected effect = computed(() => {
85+
const effect = new SelectiveBloomEffect(this.effectComposer.scene(), this.effectComposer.camera(), {
86+
blendFunction: this.blendFunction() || BlendFunction.ADD,
87+
luminanceThreshold: this.luminanceThreshold(),
88+
luminanceSmoothing: this.luminanceSmoothing(),
89+
mipmapBlur: this.mipmapBlur(),
90+
intensity: this.intensity(),
91+
radius: this.radius(),
92+
levels: this.levels(),
93+
kernelSize: this.kernelSize(),
94+
resolutionScale: this.resolutionScale(),
95+
width: this.width(),
96+
height: this.height(),
97+
resolutionX: this.resolutionX(),
98+
resolutionY: this.resolutionY(),
99+
});
100+
101+
effect.inverted = this.inverted();
102+
effect.ignoreBackground = this.ignoreBackground();
103+
104+
return effect;
105+
});
106+
107+
constructor() {
108+
effect((onCleanup) => {
109+
// skip input selection altogether if NgtSelection is used
110+
if (this.ngtSelection) return;
111+
const selection = this.resolvedSelected();
112+
if (!selection.length) return;
113+
114+
const [effect, invalidate] = [this.effect(), this.store.invalidate(), this.selectionLayer()];
115+
effect.selection.set(selection as THREE.Object3D[]);
116+
invalidate();
117+
118+
onCleanup(() => {
119+
effect.selection.clear();
120+
invalidate();
121+
});
122+
});
123+
124+
effect(() => {
125+
const [selectionLayer, invalidate, effect] = [this.selectionLayer(), this.store.invalidate(), this.effect()];
126+
effect.selection.layer = selectionLayer;
127+
invalidate();
128+
});
129+
130+
effect((onCleanup) => {
131+
const lights = this.resolvedLights();
132+
if (lights.length <= 0) return;
133+
134+
const [effect, invalidate] = [this.effect(), this.store.invalidate(), this.selectionLayer()];
135+
136+
lights.forEach((light) => light && this.addLight(effect, light));
137+
invalidate();
138+
139+
onCleanup(() => {
140+
lights.forEach((light) => light && this.removeLight(effect, light));
141+
invalidate();
142+
});
143+
});
144+
145+
effect((onCleanup) => {
146+
const selected = this.resolvedNgtSelected();
147+
if (!selected.length) return;
148+
149+
const [effect, invalidate] = [this.effect(), this.store.invalidate(), this.selectionLayer()];
150+
effect.selection.set(selected as THREE.Object3D[]);
151+
invalidate();
152+
153+
onCleanup(() => {
154+
effect.selection.clear();
155+
invalidate();
156+
});
157+
});
158+
}
159+
160+
private addLight(effect: SelectiveBloomEffect, light: THREE.Object3D) {
161+
light.layers.enable(effect.selection.layer);
162+
}
163+
164+
private removeLight(effect: SelectiveBloomEffect, light: THREE.Object3D) {
165+
light.layers.disable(effect.selection.layer);
166+
}
167+
}

0 commit comments

Comments
 (0)
Please sign in to comment.