Skip to content

Commit

Permalink
feat: mesh refarction but it's half working (maybe caustics issue)
Browse files Browse the repository at this point in the history
  • Loading branch information
nartc committed Mar 6, 2023
1 parent df72d63 commit 80b2b10
Show file tree
Hide file tree
Showing 19 changed files with 1,610 additions and 22 deletions.
Binary file not shown.
1 change: 1 addition & 0 deletions libs/angular-three-soba/abstractions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './lib/billboard/billboard';
export * from './lib/catmull-rom-line/catmull-rom-line';
export * from './lib/cubic-bezier-line/cubic-bezier-line';
export * from './lib/edges/edges';
export * from './lib/gizmo-helper/gizmo-helper';
export * from './lib/gizmo-helper/gizmo-viewcube/gizmo-viewcube';
export * from './lib/gizmo-helper/gizmo-viewport/gizmo-viewport';
Expand Down
75 changes: 75 additions & 0 deletions libs/angular-three-soba/abstractions/src/lib/edges/edges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { NgIf } from '@angular/common';
import { Component, CUSTOM_ELEMENTS_SCHEMA, Input, OnInit } from '@angular/core';
import { extend, injectNgtRef, NgtAnyRecord, NgtRxStore } from 'angular-three';
import * as THREE from 'three';
import { LineBasicMaterial, LineSegments } from 'three';

extend({ LineSegments, LineBasicMaterial });

@Component({
selector: 'ngts-edges',
standalone: true,
template: `
<ngt-line-segments [ref]="edgesRef" [raycast]="noop" ngtCompound>
<ng-container *ngIf="withChildren; else noChildren">
<ng-content />
</ng-container>
<ng-template #noChildren>
<ngt-line-basic-material [color]="color" />
</ng-template>
</ngt-line-segments>
`,
imports: [NgIf],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class NgtsEdges extends NgtRxStore implements OnInit {
@Input() edgesRef = injectNgtRef<THREE.LineSegments>();

@Input() set threshold(threshold: number) {
this.set({ threshold });
}

@Input() set color(color: THREE.ColorRepresentation) {
this.set({ color });
}

@Input() set geometry(geometry: THREE.BufferGeometry) {
this.set({ geometry });
}

@Input() set userData(userData: NgtAnyRecord) {
this.set({ userData });
}

@Input() withChildren = false;

readonly noop = () => null;

override initialize(): void {
super.initialize();
this.set({
threshold: 15,
color: 'black',
userData: {},
});
}

ngOnInit(): void {
this.setupGeometry();
}

private setupGeometry(): void {
this.hold(this.edgesRef.$, (segments) => {
const parent = segments.parent as THREE.Mesh;
if (parent) {
const geom = this.get('geometry') || parent.geometry;
const threshold = this.get('threshold');
if (geom !== segments.userData['currentGeom'] || threshold !== segments.userData['currentThreshold']) {
segments.userData['currentGeom'] = geom;
segments.userData['currentThreshold'] = threshold;
segments.geometry = new THREE.EdgesGeometry(geom, threshold);
}
}
});
}
}
1 change: 1 addition & 0 deletions libs/angular-three-soba/cameras/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './lib/camera/camera-content';
export * from './lib/cube-camera/cube-camera';
export * from './lib/orthographic-camera/orthographic-camera';
export * from './lib/perspective-camera/perspective-camera';
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class NgtsCameraContent {
static ngTemplateContextGuard(
_: NgtsCameraContent,
ctx: unknown
): ctx is { target: THREE.WebGLRenderTarget; group?: THREE.Group } {
): ctx is { fbo: THREE.WebGLRenderTarget; group?: THREE.Group } {
return true;
}
}
111 changes: 111 additions & 0 deletions libs/angular-three-soba/cameras/src/lib/cube-camera/cube-camera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { NgIf, NgTemplateOutlet } from '@angular/common';
import { Component, ContentChild, CUSTOM_ELEMENTS_SCHEMA, ElementRef, inject, Input, ViewChild } from '@angular/core';
import { extend, injectBeforeRender, injectNgtRef, NgtArgs, NgtRxStore, NgtStore } from 'angular-three';
import { combineLatest, map } from 'rxjs';
import * as THREE from 'three';
import { CubeCamera, Group } from 'three';
import { NgtsCameraContent } from '../camera/camera-content';

extend({ Group, CubeCamera });

@Component({
selector: 'ngts-cube-camera',
standalone: true,
template: `
<ngt-group ngtCompound>
<ngt-cube-camera [ref]="cameraRef" *args="get('cameraArgs')" />
<ngt-group #group>
<ng-container
*ngIf="cameraContent && cameraContent.ngtsCameraContent && get('fbo')"
[ngTemplateOutlet]="cameraContent.template"
[ngTemplateOutletContext]="{ fbo: get('fbo').texture, group }"
/>
</ngt-group>
</ngt-group>
`,
imports: [NgIf, NgTemplateOutlet, NgtArgs],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class NgtsCubeCamera extends NgtRxStore {
@ViewChild('group', { static: true }) groupRef!: ElementRef<THREE.Group>;
@ContentChild(NgtsCameraContent) cameraContent?: NgtsCameraContent;

readonly cameraRef = injectNgtRef<THREE.CubeCamera>();

/** Number of frames to render, Infinity */
@Input() set frames(frames: number) {
this.set({ frames });
}
/** Resolution of the FBO, 256 */
@Input() set resolution(resolution: number) {
this.set({ resolution });
}
/** Camera near, 0.1 */
@Input() set near(near: number) {
this.set({ near });
}
/** Camera far, 1000 */
@Input() set far(far: number) {
this.set({ far });
}
/** Custom environment map that is temporarily set as the scenes background */
@Input() set envMap(envMap: THREE.Texture) {
this.set({ envMap });
}
/** Custom fog that is temporarily set as the scenes fog */
@Input() set fog(fog: THREE.Fog | THREE.FogExp2) {
this.set({ fog });
}

private readonly store = inject(NgtStore);

override initialize(): void {
super.initialize();
this.set({
frames: Infinity,
resolution: 256,
near: 0.1,
far: 1000,
});
}

constructor() {
super();
this.connect(
'fbo',
this.select('resolution').pipe(
map((resolution) => {
const fbo = new THREE.WebGLCubeRenderTarget(resolution);
fbo.texture.encoding = this.store.get('gl').outputEncoding;
fbo.texture.type = THREE.HalfFloatType;
return fbo;
})
)
);
this.connect('cameraArgs', combineLatest([this.select('near'), this.select('far'), this.select('fbo')]));

let count = 0;
let originalFog: THREE.Scene['fog'];
let originalBackground: THREE.Scene['background'];
injectBeforeRender(({ scene, gl }) => {
const { frames, envMap, fog } = this.get();
if (
envMap &&
this.cameraRef.nativeElement &&
this.groupRef.nativeElement &&
(frames === Infinity || count < frames)
) {
this.groupRef.nativeElement.visible = false;
originalFog = scene.fog;
originalBackground = scene.background;
scene.background = envMap || originalBackground;
scene.fog = fog || originalFog;
this.cameraRef.nativeElement.update(gl, scene);
scene.fog = originalFog;
scene.background = originalBackground;
this.groupRef.nativeElement.visible = true;
count++;
}
});
}
}
2 changes: 2 additions & 0 deletions libs/angular-three-soba/materials/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './lib/mesh-distort-material/mesh-distort-material';
export * from './lib/mesh-reflector-material/mesh-reflector-material';
export * from './lib/mesh-refraction-material/mesh-refraction-material';
export * from './lib/mesh-transmission-material/mesh-transmission-material';
export * from './lib/mesh-wobble-material/mesh-wobble-material';
Original file line number Diff line number Diff line change
@@ -1,21 +1,131 @@
import { NgIf } from '@angular/common';
import { Component, CUSTOM_ELEMENTS_SCHEMA, Input } from '@angular/core';
import { extend, injectNgtRef, NgtPush } from 'angular-three';
import { Component, CUSTOM_ELEMENTS_SCHEMA, inject, Input, OnInit } from '@angular/core';
import { extend, getLocalState, injectBeforeRender, injectNgtRef, NgtPush, NgtRxStore, NgtStore } from 'angular-three';
import { MeshRefractionMaterial } from 'angular-three-soba/shaders';
import { combineLatest, map } from 'rxjs';
import { MeshBVH, SAH } from 'three-mesh-bvh';

extend({ MeshRefractionMaterial });

const isCubeTexture = (def: THREE.CubeTexture | THREE.Texture): def is THREE.CubeTexture =>
def && (def as THREE.CubeTexture).isCubeTexture;

@Component({
selector: 'ngts-mesh-refraction-material',
standalone: true,
template: `
<ngt-mesh-refraction-material>
<ngt-mesh-refraction-material
*ngIf="defines$ | ngtPush as defines"
[ref]="materialRef"
[defines]="defines"
[resolution]="get('resolution')"
[aberrationStrength]="get('aberrationStrength')"
[envMap]="get('envMap')"
[bounces]="get('bounces')"
[ior]="get('ior')"
[fresnel]="get('fresnel')"
[color]="get('color')"
[fastChroma]="get('fastChroma')"
ngtCompound
attach="material"
>
<ng-content />
</ngt-mesh-refraction-material>
`,
imports: [NgtPush, NgIf],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class NgtsMeshRefractionMaterial {
@Input() materialRef = injectNgtRef<typeof MeshRefractionMaterial>();
export class NgtsMeshRefractionMaterial extends NgtRxStore implements OnInit {
@Input() materialRef = injectNgtRef<typeof MeshRefractionMaterial.prototype>();
/** Environment map */
@Input() set envMap(envMap: THREE.CubeTexture | THREE.Texture) {
this.set({ envMap });
}
/** Number of ray-cast bounces, it can be expensive to have too many, 2 */
@Input() set bounces(bounces: number) {
this.set({ bounces });
}
/** Refraction index, 2.4 */
@Input() set ior(ior: number) {
this.set({ ior });
}
/** Fresnel (strip light), 0 */
@Input() set fresnel(fresnel: number) {
this.set({ fresnel });
}
/** RGB shift intensity, can be expensive, 0 */
@Input() set aberrationStrength(aberrationStrength: number) {
this.set({ aberrationStrength });
}
/** Color, white */
@Input() set color(color: THREE.ColorRepresentation) {
this.set({ color });
}
/** If this is on it uses fewer ray casts for the RGB shift sacrificing physical accuracy, true */
@Input() set fastChroma(fastChroma: boolean) {
this.set({ fastChroma });
}

readonly defines$ = this.select('defines');

private readonly store = inject(NgtStore);

override initialize(): void {
super.initialize();
this.set({
aberrationStrength: 0,
fastChroma: true,
});
}

constructor() {
super();
this.connect(
'defines',
combineLatest([this.select('aberrationStrength'), this.select('fastChroma'), this.select('envMap')]).pipe(
map(([aberrationStrength, fastChroma, envMap]) => {
const temp = {} as { [key: string]: string };
// Sampler2D and SamplerCube need different defines
const isCubeMap = isCubeTexture(envMap);
const w = (isCubeMap ? envMap.image[0]?.width : envMap.image.width) ?? 1024;
const cubeSize = w / 4;
const _lodMax = Math.floor(Math.log2(cubeSize));
const _cubeSize = Math.pow(2, _lodMax);
const width = 3 * Math.max(_cubeSize, 16 * 7);
const height = 4 * _cubeSize;
if (isCubeMap) temp['ENVMAP_TYPE_CUBEM'] = '';
temp['CUBEUV_TEXEL_WIDTH'] = `${1.0 / width}`;
temp['CUBEUV_TEXEL_HEIGHT'] = `${1.0 / height}`;
temp['CUBEUV_MAX_MIP'] = `${_lodMax}.0`;
// Add defines from chromatic aberration
if (aberrationStrength > 0) temp['CHROMATIC_ABERRATIONS'] = '';
if (fastChroma) temp['FAST_CHROMA'] = '';
return temp;
})
)
);
this.connect('resolution', this.store.select('size').pipe(map((size) => [size.width, size.height])));

injectBeforeRender(({ camera }) => {
if (this.materialRef.nativeElement) {
(this.materialRef.nativeElement as any)!.viewMatrixInverse = camera.matrixWorld;
(this.materialRef.nativeElement as any)!.projectionMatrixInverse = camera.projectionMatrixInverse;
}
});
}

ngOnInit() {
this.setupGeometry();
}

private setupGeometry() {
this.hold(this.materialRef.$, (material) => {
const geometry = getLocalState(material).parent?.geometry;
if (geometry) {
(material as any).bvh.updateFrom(
new MeshBVH(geometry.toNonIndexed(), { lazyGeneration: false, strategy: SAH } as any)
);
}
});
}
}
Loading

0 comments on commit 80b2b10

Please sign in to comment.