Skip to content

Commit b8ab640

Browse files
committed
docs: webgpu demo
1 parent 618e0b6 commit b8ab640

File tree

3 files changed

+155
-0
lines changed

3 files changed

+155
-0
lines changed

apps/examples/src/app/misc/misc.routes.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ const routes: Routes = [
2727
},
2828
},
2929
},
30+
{
31+
path: 'webgpu-renderer',
32+
loadComponent: () => import('./webgpu-renderer/webgpu-renderer'),
33+
data: {
34+
credits: {
35+
title: "Threlte's WebGPU Renderer example",
36+
link: 'https://threlte.xyz/docs/learn/advanced/webgpu',
37+
},
38+
},
39+
},
3040
{
3141
path: '',
3242
redirectTo: 'basic',
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, viewChild } from '@angular/core';
2+
import { extend, injectBeforeRender, NgtAfterAttach, NgtArgs } from 'angular-three';
3+
import { NgtsOrbitControls } from 'angular-three-soba/controls';
4+
import * as THREE from 'three/webgpu';
5+
6+
@Component({
7+
selector: 'app-scene-graph',
8+
template: `
9+
<ngt-color *args="['#c1c1c1']" attach="background" />
10+
11+
<ngt-group #group static>
12+
@for (obj of objects; track $index) {
13+
<ngt-mesh
14+
[geometry]="obj.geometry"
15+
[frustumCulled]="false"
16+
[userData]="obj.userData"
17+
(attached)="onAttached($event)"
18+
>
19+
<ngt-mesh-toon-material [color]="obj.color" [side]="DoubleSide" />
20+
</ngt-mesh>
21+
}
22+
</ngt-group>
23+
24+
<ngt-directional-light [intensity]="Math.PI" />
25+
26+
<ngts-orbit-controls [options]="{ autoRotate: true, enableZoom: false, autoRotateSpeed: 1 }" />
27+
`,
28+
imports: [NgtArgs, NgtsOrbitControls],
29+
changeDetection: ChangeDetectionStrategy.OnPush,
30+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
31+
})
32+
export class SceneGraph {
33+
protected readonly Math = Math;
34+
protected readonly DoubleSide = THREE.DoubleSide;
35+
36+
protected readonly geometries = [
37+
new THREE.ConeGeometry(1.0, 2.0, 3, 1),
38+
new THREE.BoxGeometry(2.0, 2.0, 2.0),
39+
new THREE.PlaneGeometry(2.0, 2, 1, 1),
40+
new THREE.CapsuleGeometry(),
41+
new THREE.CircleGeometry(1.0, 3),
42+
new THREE.CylinderGeometry(1.0, 1.0, 2.0, 3, 1),
43+
new THREE.DodecahedronGeometry(1.0, 0),
44+
new THREE.IcosahedronGeometry(1.0, 0),
45+
new THREE.OctahedronGeometry(1.0, 0),
46+
new THREE.PolyhedronGeometry([0, 0, 0], [0, 0, 0], 1, 0),
47+
new THREE.RingGeometry(1.0, 1.5, 3),
48+
new THREE.SphereGeometry(1.0, 3, 2),
49+
new THREE.TetrahedronGeometry(1.0, 0),
50+
new THREE.TorusGeometry(1.0, 0.5, 3, 3),
51+
new THREE.TorusKnotGeometry(1.0, 0.5, 20, 3, 1, 1),
52+
];
53+
54+
protected readonly objects = Array.from({ length: 3000 }, (_, i) => {
55+
const color = Math.random() * 0xffffff;
56+
const geometry = this.geometries[i % this.geometries.length];
57+
const rotationSpeed = this.randomizeRotationSpeed(new THREE.Euler());
58+
59+
return {
60+
color,
61+
geometry,
62+
userData: { rotationSpeed },
63+
};
64+
});
65+
66+
private groupRef = viewChild.required<ElementRef<THREE.Group>>('group');
67+
68+
private position = new THREE.Vector3();
69+
private rotation = new THREE.Euler();
70+
private quaternion = new THREE.Quaternion();
71+
private scale = new THREE.Vector3();
72+
73+
constructor() {
74+
extend(THREE);
75+
76+
injectBeforeRender(() => {
77+
const group = this.groupRef().nativeElement;
78+
for (const child of group.children) {
79+
const { rotationSpeed } = child.userData;
80+
child.rotation.set(
81+
child.rotation.x + rotationSpeed.x,
82+
child.rotation.y + rotationSpeed.y,
83+
child.rotation.z + rotationSpeed.z,
84+
);
85+
}
86+
});
87+
}
88+
89+
protected onAttached(event: NgtAfterAttach<THREE.Mesh>) {
90+
this.randomizeMatrix(event.node.matrix);
91+
event.node.matrix.decompose(event.node.position, event.node.quaternion, event.node.scale);
92+
}
93+
94+
private randomizeMatrix(matrix: THREE.Matrix4) {
95+
this.position.x = Math.random() * 80 - 40;
96+
this.position.y = Math.random() * 80 - 40;
97+
this.position.z = Math.random() * 80 - 40;
98+
this.rotation.x = Math.random() * 2 * Math.PI;
99+
this.rotation.y = Math.random() * 2 * Math.PI;
100+
this.rotation.z = Math.random() * 2 * Math.PI;
101+
this.quaternion.setFromEuler(this.rotation);
102+
const factorScale = 1;
103+
this.scale.x = this.scale.y = this.scale.z = 0.35 * factorScale + Math.random() * 0.5 * factorScale;
104+
return matrix.compose(this.position, this.quaternion, this.scale);
105+
}
106+
107+
private randomizeRotationSpeed(rotation: THREE.Euler) {
108+
rotation.x = Math.random() * 0.05;
109+
rotation.y = Math.random() * 0.05;
110+
rotation.z = Math.random() * 0.05;
111+
return rotation;
112+
}
113+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
2+
import { NgtFrameloop, NgtGLOptions } from 'angular-three';
3+
import { NgtCanvas, NgtCanvasContent } from 'angular-three/dom';
4+
import * as THREE from 'three/webgpu';
5+
import { SceneGraph } from './scene';
6+
7+
@Component({
8+
template: `
9+
<ngt-canvas [gl]="glFactory" [frameloop]="frameloop()">
10+
<app-scene-graph *canvasContent />
11+
</ngt-canvas>
12+
`,
13+
changeDetection: ChangeDetectionStrategy.OnPush,
14+
imports: [NgtCanvas, NgtCanvasContent, SceneGraph],
15+
host: { class: 'webgpu-renderer' },
16+
})
17+
export default class WebGPURenderer {
18+
protected frameloop = signal<NgtFrameloop>('never');
19+
protected glFactory: NgtGLOptions = (canvas) => {
20+
const renderer = new THREE.WebGPURenderer({
21+
canvas: canvas as HTMLCanvasElement,
22+
antialias: true,
23+
forceWebGL: false,
24+
});
25+
26+
renderer.init().then(() => {
27+
this.frameloop.set('always');
28+
});
29+
30+
return renderer;
31+
};
32+
}

0 commit comments

Comments
 (0)