Change to WebGPU

This commit is contained in:
Fuhrmann 2025-04-10 15:16:41 +02:00
parent 523cf0945a
commit 4dadaf470c
6 changed files with 170 additions and 160 deletions

View file

@ -8,7 +8,6 @@ import {
LineBasicMaterial,
LineSegments,
Mesh,
MeshBasicMaterial,
MeshStandardMaterial,
Object3DEventMap,
PerspectiveCamera,
@ -17,11 +16,17 @@ import {
Scene,
Vector2,
Vector3,
WebGLRenderer,
} from "three";
import { DragControls, OrbitControls } from "three/examples/jsm/Addons.js";
import { Extent } from "./build-scene";
import earcut from "earcut";
import {
ClippingGroup,
MeshBasicNodeMaterial,
MeshStandardNodeMaterial,
WebGPURenderer,
} from "three/webgpu";
import { Fn, uniform, vec4 } from "three/tsl";
export enum Orientation {
X = "X",
@ -32,7 +37,7 @@ export enum Orientation {
NZ = "NZ",
}
type PlaneMesh = Mesh<PlaneGeometry, MeshBasicMaterial, Object3DEventMap>;
type PlaneMesh = Mesh<PlaneGeometry, MeshBasicNodeMaterial, Object3DEventMap>;
type EdgeMesh = LineSegments<
EdgesGeometry<PlaneGeometry>,
LineBasicMaterial,
@ -49,7 +54,7 @@ let currentExtent: Extent;
const BUFFER = 500;
export function buildClippingplanes(
renderer: WebGLRenderer,
renderer: WebGPURenderer,
camera: PerspectiveCamera,
orbitControls: OrbitControls,
extent: Extent,
@ -148,7 +153,7 @@ export function buildClippingplanes(
const planeGeometry = new PlaneGeometry(width, height);
const planeMesh = new Mesh(
planeGeometry,
new MeshBasicMaterial({
new MeshBasicNodeMaterial({
visible: true,
color: 0xa92a4e,
transparent: true,
@ -190,6 +195,19 @@ export function buildClippingplanes(
edgeMeshMap[p.orientation] = edges;
}
for (const o in Orientation) {
const capMeshGroupName = `cap-mesh-group-${o}`;
let capMeshGroup = scene.getObjectByName(capMeshGroupName) as ClippingGroup;
if (capMeshGroup) {
capMeshGroup.clear();
} else {
capMeshGroup = new ClippingGroup();
capMeshGroup.name = capMeshGroupName;
capMeshGroup.clippingPlanes = planes;
scene.add(capMeshGroup);
}
}
// Add meshes to the scene
const planeMeshGroup = new Group();
planeMeshGroup.name = "clipping-planes";
@ -388,29 +406,21 @@ export function buildClippingplanes(
}
// Remove existing cap meshes
const capMeshGroupName = `cap-mesh-group-${object.name}`;
let capMeshGroup = scene.getObjectByName(capMeshGroupName);
while (capMeshGroup) {
scene.remove(capMeshGroup);
capMeshGroup = scene.getObjectByName(capMeshGroupName);
}
const capMeshGroupName = `cap-mesh-group-${orientation}`;
const capMeshGroup = scene.getObjectByName(
capMeshGroupName
) as ClippingGroup;
if (capMeshGroup) {
capMeshGroup.clear();
// Generate new cap meshes
const capMeshes = generateCapMeshes(
meshes,
plane.clone(),
planes,
orientation,
scene
);
// Add new cap meshes
if (capMeshes.length > 0) {
const newCapMeshGroup = new Group();
newCapMeshGroup.add(...capMeshes);
newCapMeshGroup.name = capMeshGroupName;
scene.add(newCapMeshGroup);
// Generate new cap meshes
generateCapMeshes(
meshes,
plane.clone(),
orientation,
scene,
capMeshGroup
);
}
});
@ -576,12 +586,10 @@ function resizeClippingPlane(
function generateCapMeshes(
meshes: Mesh[],
plane: Plane,
planes: Plane[],
orientation: Orientation,
scene: Scene
scene: Scene,
capMeshGroup: ClippingGroup
) {
const capMeshes: Mesh[] = [];
// Rescale to local coordinates
if (orientation === Orientation.Z || orientation === Orientation.NZ)
plane.constant /= scene.scale.z;
@ -640,9 +648,6 @@ function generateCapMeshes(
// Intersection surface can be a multipolygon consisting of disconnected polygons
const polygons: Vector3[][] = buildPolygons(edges);
// Clip cap surfaces with clipping planes
const clippingPlanes = planes.filter((p) => !p.normal.equals(plane.normal));
const offset =
orientation === Orientation.NX ||
orientation === Orientation.NY ||
@ -651,24 +656,30 @@ function generateCapMeshes(
: -1;
const color =
mesh.material instanceof MeshStandardMaterial
mesh.material instanceof MeshStandardNodeMaterial
? mesh.material.color
: new Color(1, 1, 1);
const material = new MeshStandardMaterial({
const material = new MeshStandardNodeMaterial({
color,
side: DoubleSide,
metalness: 0.0,
roughness: 1.0,
metalness: 0.1,
roughness: 0.5,
flatShading: true,
polygonOffset: true,
polygonOffsetFactor: offset,
polygonOffsetUnits: offset,
clippingPlanes,
wireframe: scene.userData.wireframe,
alphaToCoverage: true,
});
const localMeshes = polygons.map((polygon) => {
const tColor = uniform(new Color(color));
const fragmentShader = Fn(() => {
return vec4(tColor.r, tColor.g, tColor.b, 1.0);
});
material.fragmentNode = fragmentShader();
polygons.forEach((polygon) => {
const geometry = triangulatePolygon(polygon, plane);
const capMesh = new Mesh(geometry, material);
@ -687,13 +698,11 @@ function generateCapMeshes(
}
positionAttr.needsUpdate = true;
return capMesh;
if (capMesh) {
capMeshGroup.add(capMesh);
}
});
capMeshes.push(...localMeshes);
}
return capMeshes;
}
// Build polygons by grouping connected intersection edges

View file

@ -1,14 +1,16 @@
import {
BufferAttribute,
BufferGeometry,
Color,
DoubleSide,
Mesh,
MeshStandardMaterial,
} from "three";
import { fetchVertices, fetchTriangleIndices, transform } from "./utils";
import { TRIANGLE_INDICES_URL, VERTICES_URL } from "../config";
import { shaderMaterial } from "../ShaderMaterial";
import { topoNodeMaterial } from "../ShaderMaterial";
import { MeshStandardNodeMaterial } from "three/webgpu";
import { Fn, uniform, vec3, vec4 } from "three/tsl";
interface MappedFeature {
featuregeom_id: number;
@ -24,6 +26,8 @@ export async function buildMeshes(mappedFeatures: MappedFeature[]) {
const mesh = await buildMesh(layerData);
if (layerData.name === "Topography") {
mesh.visible = false;
} else {
mesh.visible = true;
}
meshes.push(mesh);
}
@ -58,19 +62,26 @@ async function buildMesh(layerData: MappedFeature) {
const indices = new BufferAttribute(indexArray, 1);
geometry.setIndex(indices);
geometry.computeVertexNormals();
const material = new MeshStandardMaterial({
const material = new MeshStandardNodeMaterial({
color: color,
metalness: 0.1,
roughness: 0.5,
flatShading: true,
side: DoubleSide,
wireframe: false,
alphaToCoverage: true,
});
const tColor = uniform(new Color(color));
const fragmentShader = Fn(() => {
return vec4(tColor.r, tColor.g, tColor.b, 1.0);
});
material.colorNode = fragmentShader();
const mesh = new Mesh(
geometry,
name === "Topography" ? shaderMaterial : material
name === "Topography" ? topoNodeMaterial : material
);
mesh.name = name;
mesh.userData.layerId = geomId;

View file

@ -1,7 +1,6 @@
import {
PerspectiveCamera,
Scene,
WebGLRenderer,
AmbientLight,
DirectionalLight,
Group,
@ -19,6 +18,7 @@ import {
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { getCenter3D, getMaxSize } from "./utils";
import { WebGPURenderer } from "three/webgpu";
export interface Extent {
xmin: number;
@ -30,7 +30,7 @@ export interface Extent {
}
let controls: OrbitControls;
let renderer: WebGLRenderer;
let renderer: WebGPURenderer;
let camera: PerspectiveCamera;
let scene: Scene;
let overlayCamera: OrthographicCamera;
@ -54,15 +54,15 @@ export function buildScene(container: HTMLElement, extent: Extent) {
camera.lookAt(center);
// Initialize the renderer
renderer = new WebGLRenderer({
renderer = new WebGPURenderer({
logarithmicDepthBuffer: true,
antialias: true,
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
renderer.localClippingEnabled = true;
renderer.autoClear = false;
// renderer.setAnimationLoop(animate);
// Handle window resize event to adapt the aspect ratio
window.addEventListener("resize", () => onWindowResize(container));
@ -117,7 +117,7 @@ function onWindowResize(container: HTMLElement) {
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
// required if controls.enableDamping or controls.autoRotate are set to true
// Required if controls.enableDamping or controls.autoRotate are set to true
controls.update();
}
@ -152,7 +152,12 @@ function renderOverlay() {
);
// Render the overlay scene to the screen (position it in the bottom left)
renderer.setViewport(10, 10, UI_WIDTH, UI_HEIGHT);
renderer.setViewport(
10,
renderer.domElement.height - UI_HEIGHT - 10,
UI_WIDTH,
UI_HEIGHT
);
renderer.render(overlayScene, overlayCamera);
renderer.setViewport(
0,
@ -179,7 +184,7 @@ function buildDefaultLights(scene: Scene, extent: Extent) {
lights.push(ambient);
// Directional lights
const directionalLight = new DirectionalLight(0xffffff, 1.5);
const directionalLight = new DirectionalLight(0xffffff, 2);
directionalLight.position.set(
lightPosition.x,
lightPosition.y,