Skip to content

Commit 885f10e

Browse files
Chau TranChau Tran
Chau Tran
authored and
Chau Tran
committed
feat(soba): migrate refraction and transmission material
1 parent 199a722 commit 885f10e

File tree

15 files changed

+1208
-28
lines changed

15 files changed

+1208
-28
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { NgIf, NgTemplateOutlet } from '@angular/common';
2+
import {
3+
CUSTOM_ELEMENTS_SCHEMA,
4+
Component,
5+
ContentChild,
6+
ElementRef,
7+
Input,
8+
ViewChild,
9+
computed,
10+
inject,
11+
} from '@angular/core';
12+
import { NgtArgs, NgtSignalStore, NgtStore, extend, injectBeforeRender, injectNgtRef } from 'angular-three';
13+
import * as THREE from 'three';
14+
import { CubeCamera, Group } from 'three';
15+
import { NgtsCameraContent } from '../camera/camera-content';
16+
17+
extend({ Group, CubeCamera });
18+
19+
export interface NgtsCubeCameraState {
20+
/** Number of frames to render, Infinity */
21+
frames: number;
22+
/** Resolution of the FBO, 256 */
23+
resolution: number;
24+
/** Camera near, 0.1 */
25+
near: number;
26+
/** Camera far, 1000 */
27+
far: number;
28+
/** Custom environment map that is temporarily set as the scenes background */
29+
envMap: THREE.Texture;
30+
/** Custom fog that is temporarily set as the scenes fog */
31+
fog: THREE.Fog | THREE.FogExp2;
32+
}
33+
34+
@Component({
35+
selector: 'ngts-cube-camera',
36+
standalone: true,
37+
template: `
38+
<ngt-group ngtCompound>
39+
<ngt-cube-camera [ref]="cameraRef" *args="cameraArgs()" />
40+
<ngt-group #group>
41+
<ng-container
42+
*ngIf="cameraContent && cameraContent.ngtsCameraContent && fbo()"
43+
[ngTemplateOutlet]="cameraContent.template"
44+
[ngTemplateOutletContext]="{ fbo: fbo(), group }"
45+
/>
46+
</ngt-group>
47+
</ngt-group>
48+
`,
49+
imports: [NgIf, NgTemplateOutlet, NgtArgs],
50+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
51+
})
52+
export class NgtsCubeCamera extends NgtSignalStore<NgtsCubeCameraState> {
53+
@ViewChild('group', { static: true }) groupRef!: ElementRef<THREE.Group>;
54+
@ContentChild(NgtsCameraContent) cameraContent?: NgtsCameraContent;
55+
56+
@Input() cameraRef = injectNgtRef<THREE.CubeCamera>();
57+
58+
/** Number of frames to render, Infinity */
59+
@Input() set frames(frames: number) {
60+
this.set({ frames });
61+
}
62+
/** Resolution of the FBO, 256 */
63+
@Input() set resolution(resolution: number) {
64+
this.set({ resolution });
65+
}
66+
/** Camera near, 0.1 */
67+
@Input() set near(near: number) {
68+
this.set({ near });
69+
}
70+
/** Camera far, 1000 */
71+
@Input() set far(far: number) {
72+
this.set({ far });
73+
}
74+
/** Custom environment map that is temporarily set as the scenes background */
75+
@Input() set envMap(envMap: THREE.Texture) {
76+
this.set({ envMap });
77+
}
78+
/** Custom fog that is temporarily set as the scenes fog */
79+
@Input() set fog(fog: THREE.Fog | THREE.FogExp2) {
80+
this.set({ fog });
81+
}
82+
83+
readonly #store = inject(NgtStore);
84+
85+
readonly #resolution = this.select('resolution');
86+
readonly #near = this.select('near');
87+
readonly #far = this.select('far');
88+
89+
readonly fbo = computed(() => {
90+
const resolution = this.#resolution();
91+
const fbo = new THREE.WebGLCubeRenderTarget(resolution);
92+
fbo.texture.encoding = this.#store.get('gl').outputEncoding;
93+
fbo.texture.type = THREE.HalfFloatType;
94+
return fbo;
95+
});
96+
97+
readonly cameraArgs = computed(() => [this.#near(), this.#far(), this.fbo()]);
98+
99+
constructor() {
100+
super({ frames: Infinity, resolution: 256, near: 0.1, far: 1000 });
101+
102+
let count = 0;
103+
let originalFog: THREE.Scene['fog'];
104+
let originalBackground: THREE.Scene['background'];
105+
injectBeforeRender(({ scene, gl }) => {
106+
const { frames, envMap, fog } = this.get();
107+
if (
108+
envMap &&
109+
this.cameraRef.nativeElement &&
110+
this.groupRef.nativeElement &&
111+
(frames === Infinity || count < frames)
112+
) {
113+
this.groupRef.nativeElement.visible = false;
114+
originalFog = scene.fog;
115+
originalBackground = scene.background;
116+
scene.background = envMap || originalBackground;
117+
scene.fog = fog || originalFog;
118+
this.cameraRef.nativeElement.update(gl, scene);
119+
scene.fog = originalFog;
120+
scene.background = originalBackground;
121+
this.groupRef.nativeElement.visible = true;
122+
count++;
123+
}
124+
});
125+
}
126+
}

libs/soba/cameras/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './camera/camera';
22
export * from './camera/camera-content';
3+
export * from './cube-camera/cube-camera';
34
export * from './orthographic-camera/orthographic-camera';
45
export * from './perspective-camera/perspective-camera';

libs/soba/cameras/src/orthographic-camera/orthographic-camera.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ declare module '../camera/camera' {
3434
/>
3535
</ngt-orthographic-camera>
3636
<ngt-group #group *ngIf="cameraContent && cameraContent.ngtsCameraContent">
37-
<ng-container *ngTemplateOutlet="cameraContent.template; context: { fbo: fboRef.nativeElement, group }" />
37+
<ng-container *ngTemplateOutlet="cameraContent.template; context: { fbo: fboRef(), group }" />
3838
</ngt-group>
3939
`,
4040
imports: [NgIf, NgTemplateOutlet],

libs/soba/cameras/src/perspective-camera/perspective-camera.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ extend({ PerspectiveCamera, Group });
1818
/>
1919
</ngt-perspective-camera>
2020
<ngt-group #group *ngIf="cameraContent && cameraContent.ngtsCameraContent">
21-
<ng-container *ngTemplateOutlet="cameraContent.template; context: { fbo: fboRef.nativeElement, group }" />
21+
<ng-container *ngTemplateOutlet="cameraContent.template; context: { fbo: fboRef(), group }" />
2222
</ngt-group>
2323
`,
2424
imports: [NgIf, NgTemplateOutlet],

libs/soba/materials/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export * from './mesh-distort-material/mesh-distort-material';
22
export * from './mesh-reflector-material/mesh-reflector-material';
3+
export * from './mesh-refraction-material/mesh-refraction-material';
4+
export * from './mesh-transmission-material/mesh-transmission-material';
35
export * from './mesh-wobble-material/mesh-wobble-material';
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { NgIf } from '@angular/common';
2+
import { Component, computed, CUSTOM_ELEMENTS_SCHEMA, effect, inject, Input } from '@angular/core';
3+
import { extend, getLocalState, injectBeforeRender, injectNgtRef, NgtSignalStore, NgtStore } from 'angular-three';
4+
import { MeshRefractionMaterial } from 'angular-three-soba/shaders';
5+
import { MeshBVH, SAH } from 'three-mesh-bvh';
6+
7+
extend({ MeshRefractionMaterial });
8+
9+
export interface NgtsMeshRefractionMaterialState {
10+
/** Environment map */
11+
envMap: THREE.CubeTexture | THREE.Texture;
12+
/** Number of ray-cast bounces, it can be expensive to have too many, 2 */
13+
bounces: number;
14+
/** Refraction index, 2.4 */
15+
ior: number;
16+
/** Fresnel (strip light), 0 */
17+
fresnel: number;
18+
/** RGB shift intensity, can be expensive, 0 */
19+
aberrationStrength: number;
20+
/** Color, white */
21+
color: THREE.ColorRepresentation;
22+
/** If this is on it uses fewer ray casts for the RGB shift sacrificing physical accuracy, true */
23+
fastChroma: boolean;
24+
}
25+
26+
const isCubeTexture = (def: THREE.CubeTexture | THREE.Texture): def is THREE.CubeTexture =>
27+
def && (def as THREE.CubeTexture).isCubeTexture;
28+
29+
@Component({
30+
selector: 'ngts-mesh-refraction-material',
31+
standalone: true,
32+
template: `
33+
<ngt-mesh-refraction-material
34+
*ngIf="defines() as defines"
35+
[ref]="materialRef"
36+
[defines]="defines"
37+
[resolution]="resolution()"
38+
[aberrationStrength]="refractionAberrationStrength()"
39+
[envMap]="refractionEnvMap()"
40+
[bounces]="refractionBounces()"
41+
[ior]="refractionIor()"
42+
[fresnel]="refractionFresnel()"
43+
[color]="refractionColor()"
44+
[fastChroma]="refractionFastChroma()"
45+
ngtCompound
46+
attach="material"
47+
>
48+
<ng-content />
49+
</ngt-mesh-refraction-material>
50+
`,
51+
imports: [NgIf],
52+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
53+
})
54+
export class NgtsMeshRefractionMaterial extends NgtSignalStore<NgtsMeshRefractionMaterialState> {
55+
@Input() materialRef = injectNgtRef<InstanceType<typeof MeshRefractionMaterial>>();
56+
/** Environment map */
57+
@Input({ required: true }) set envMap(envMap: THREE.CubeTexture | THREE.Texture) {
58+
this.set({ envMap });
59+
}
60+
/** Number of ray-cast bounces, it can be expensive to have too many, 2 */
61+
@Input() set bounces(bounces: number) {
62+
this.set({ bounces });
63+
}
64+
/** Refraction index, 2.4 */
65+
@Input() set ior(ior: number) {
66+
this.set({ ior });
67+
}
68+
/** Fresnel (strip light), 0 */
69+
@Input() set fresnel(fresnel: number) {
70+
this.set({ fresnel });
71+
}
72+
/** RGB shift intensity, can be expensive, 0 */
73+
@Input() set aberrationStrength(aberrationStrength: number) {
74+
this.set({ aberrationStrength });
75+
}
76+
/** Color, white */
77+
@Input() set color(color: THREE.ColorRepresentation) {
78+
this.set({ color });
79+
}
80+
/** If this is on it uses fewer ray casts for the RGB shift sacrificing physical accuracy, true */
81+
@Input() set fastChroma(fastChroma: boolean) {
82+
this.set({ fastChroma });
83+
}
84+
85+
readonly refractionEnvMap = this.select('envMap');
86+
readonly refractionBounces = this.select('bounces');
87+
readonly refractionIor = this.select('ior');
88+
readonly refractionFresnel = this.select('fresnel');
89+
readonly refractionAberrationStrength = this.select('aberrationStrength');
90+
readonly refractionColor = this.select('color');
91+
readonly refractionFastChroma = this.select('fastChroma');
92+
93+
readonly #store = inject(NgtStore);
94+
readonly #size = this.#store.select('size');
95+
96+
readonly #envMap = this.select('envMap');
97+
98+
readonly defines = computed(() => {
99+
const envMap = this.#envMap();
100+
if (!envMap) return null;
101+
102+
const aberrationStrength = this.refractionAberrationStrength();
103+
const fastChroma = this.refractionFastChroma();
104+
105+
const temp = {} as { [key: string]: string };
106+
// Sampler2D and SamplerCube need different defines
107+
const isCubeMap = isCubeTexture(envMap);
108+
const w = (isCubeMap ? envMap.image[0]?.width : envMap.image.width) ?? 1024;
109+
const cubeSize = w / 4;
110+
const _lodMax = Math.floor(Math.log2(cubeSize));
111+
const _cubeSize = Math.pow(2, _lodMax);
112+
const width = 3 * Math.max(_cubeSize, 16 * 7);
113+
const height = 4 * _cubeSize;
114+
if (isCubeMap) temp['ENVMAP_TYPE_CUBEM'] = '';
115+
temp['CUBEUV_TEXEL_WIDTH'] = `${1.0 / width}`;
116+
temp['CUBEUV_TEXEL_HEIGHT'] = `${1.0 / height}`;
117+
temp['CUBEUV_MAX_MIP'] = `${_lodMax}.0`;
118+
// Add defines from chromatic aberration
119+
if (aberrationStrength > 0) temp['CHROMATIC_ABERRATIONS'] = '';
120+
if (fastChroma) temp['FAST_CHROMA'] = '';
121+
return temp;
122+
});
123+
readonly resolution = computed(() => [this.#size().width, this.#size().height]);
124+
125+
constructor() {
126+
super({ aberrationStrength: 0, fastChroma: true });
127+
injectBeforeRender(({ camera }) => {
128+
if (this.materialRef.nativeElement) {
129+
(this.materialRef.nativeElement as any)!.viewMatrixInverse = camera.matrixWorld;
130+
(this.materialRef.nativeElement as any)!.projectionMatrixInverse = camera.projectionMatrixInverse;
131+
}
132+
});
133+
this.#setupGeometry();
134+
}
135+
136+
#setupGeometry() {
137+
effect(() => {
138+
const material = this.materialRef.nativeElement;
139+
if (!material) return;
140+
const geometry = getLocalState(material).parent()?.geometry;
141+
if (geometry) {
142+
(material as any).bvh.updateFrom(
143+
new MeshBVH(geometry.toNonIndexed(), { lazyGeneration: false, strategy: SAH } as any)
144+
);
145+
}
146+
});
147+
}
148+
}

0 commit comments

Comments
 (0)