Visitar URL original
feat(geo-layers): port Tile3DLayer mesh content to WebGPU (#10517) · visgl/deck.gl@3dc3752 · GitHub
Skip to content

Commit 3dc3752

Browse files
feat(geo-layers): port Tile3DLayer mesh content to WebGPU (#10517)
1 parent 813cfe8 commit 3dc3752

6 files changed

Lines changed: 276 additions & 5 deletions

File tree

‎modules/geo-layers/src/mesh-layer/mesh-layer-uniforms.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ layout(std140) uniform meshUniforms {
1010
} mesh;
1111
`;
1212

13+
const source = /* wgsl */ `\
14+
struct MeshUniforms {
15+
pickFeatureIds: f32,
16+
};
17+
18+
@group(0) @binding(auto) var<uniform> mesh: MeshUniforms;
19+
`;
20+
1321
export type MeshProps = {
1422
pickFeatureIds: boolean;
1523
};
@@ -18,6 +26,7 @@ export const meshUniforms = {
1826
name: 'mesh',
1927
vs: uniformBlock,
2028
fs: uniformBlock,
29+
source,
2130
uniformTypes: {
2231
pickFeatureIds: 'f32'
2332
}

‎modules/geo-layers/src/mesh-layer/mesh-layer.ts‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44

55
import type {NumericArray} from '@math.gl/core';
66
import {parsePBRMaterial, ParsedPBRMaterial} from '@luma.gl/gltf';
7-
import {pbrMaterial} from '@luma.gl/shadertools';
87
import {Model} from '@luma.gl/engine';
98
import type {MeshAttribute, MeshAttributes} from '@loaders.gl/schema';
109
import type {UpdateParameters, DefaultProps, LayerContext} from '@deck.gl/core';
1110
import {SimpleMeshLayer, SimpleMeshLayerProps} from '@deck.gl/mesh-layers';
1211

1312
import {MeshProps, meshUniforms} from './mesh-layer-uniforms';
13+
import {meshPbrMaterial} from './mesh-pbr-material';
1414
import vs from './mesh-layer-vertex.glsl';
1515
import fs from './mesh-layer-fragment.glsl';
16+
import source from './mesh-layer.wgsl';
1617

1718
export type Mesh = {
1819
attributes: MeshAttributes;
@@ -62,9 +63,13 @@ export default class MeshLayer<DataT = any, ExtraProps extends {} = {}> extends
6263

6364
getShaders() {
6465
const shaders = super.getShaders();
65-
const modules = shaders.modules;
66-
modules.push(pbrMaterial, meshUniforms);
67-
return {...shaders, vs, fs};
66+
return {
67+
...shaders,
68+
vs,
69+
fs,
70+
source,
71+
modules: [...shaders.modules, meshPbrMaterial, meshUniforms]
72+
};
6873
}
6974

7075
initializeState() {
@@ -131,7 +136,8 @@ export default class MeshLayer<DataT = any, ExtraProps extends {} = {}> extends
131136
defines: {
132137
...shaders.defines,
133138
...parsedPBRMaterial?.defines,
134-
HAS_UV_REGIONS: mesh.attributes.uvRegions ? 1 : 0
139+
HAS_UV_REGIONS: mesh.attributes.uvRegions ? 1 : 0,
140+
HAS_FEATURE_IDS: this.props.featureIds ? 1 : 0
135141
},
136142
parameters: parsedPBRMaterial?.parameters,
137143
isInstanced: true
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// deck.gl
2+
// SPDX-License-Identifier: MIT
3+
// Copyright (c) vis.gl contributors
4+
5+
export default /* wgsl */ `\
6+
struct VertexInputs {
7+
@location(0) positions: vec3<f32>,
8+
#ifdef HAS_NORMALS
9+
@location(1) normals: vec3<f32>,
10+
#endif
11+
@location(2) colors: vec4<f32>,
12+
#ifdef HAS_UV
13+
@location(3) texCoords: vec2<f32>,
14+
#endif
15+
#ifdef HAS_UV_REGIONS
16+
@location(4) uvRegions: vec4<f32>,
17+
#endif
18+
#ifdef HAS_FEATURE_IDS
19+
@location(5) rowIndexes: u32,
20+
#endif
21+
@location(6) instanceColors: vec4<f32>,
22+
@location(7) instanceModelMatrixCol0: vec3<f32>,
23+
@location(8) instanceModelMatrixCol1: vec3<f32>,
24+
@location(9) instanceModelMatrixCol2: vec3<f32>,
25+
};
26+
27+
struct FragmentInputs {
28+
@builtin(position) position: vec4<f32>,
29+
@location(0) color: vec4<f32>,
30+
@location(1) texCoord: vec2<f32>,
31+
@location(2) pbrPosition: vec3<f32>,
32+
@location(3) pbrNormal: vec3<f32>,
33+
@location(4) pickingColor: vec3<f32>,
34+
};
35+
36+
fn applyUVRegion(uv: vec2<f32>, uvRegion: vec4<f32>) -> vec2<f32> {
37+
#ifdef HAS_UV_REGIONS
38+
// https://github.com/Esri/i3s-spec/blob/master/docs/1.7/geometryUVRegion.cmn.md
39+
return fract(uv) * (uvRegion.zw - uvRegion.xy) + uvRegion.xy;
40+
#else
41+
return uv;
42+
#endif
43+
}
44+
45+
@vertex
46+
fn vertexMain(
47+
inputs: VertexInputs,
48+
@builtin(instance_index) instanceIndex: u32
49+
) -> FragmentInputs {
50+
var outputs: FragmentInputs;
51+
var texCoord = vec2<f32>(0.0);
52+
var normal = vec3<f32>(0.0, 0.0, 1.0);
53+
var uvRegion = vec4<f32>(0.0);
54+
55+
#ifdef HAS_UV
56+
texCoord = inputs.texCoords;
57+
#endif
58+
#ifdef HAS_NORMALS
59+
normal = inputs.normals;
60+
#endif
61+
#ifdef HAS_UV_REGIONS
62+
uvRegion = inputs.uvRegions;
63+
#endif
64+
65+
texCoord = applyUVRegion(texCoord, uvRegion);
66+
geometry.uv = texCoord;
67+
#ifdef HAS_FEATURE_IDS
68+
geometry.pickingColor = picking_getPickingColorFromIndex(inputs.rowIndexes);
69+
#else
70+
geometry.pickingColor = picking_getPickingColorFromIndex(instanceIndex);
71+
#endif
72+
73+
let instanceModelMatrix = mat3x3<f32>(
74+
inputs.instanceModelMatrixCol0,
75+
inputs.instanceModelMatrixCol1,
76+
inputs.instanceModelMatrixCol2
77+
);
78+
let commonPosition = vec4<f32>(project_position_vec3_f32(inputs.positions), 1.0);
79+
80+
geometry.position = commonPosition;
81+
geometry.normal = project_normal(instanceModelMatrix * normal);
82+
83+
outputs.position = project_common_position_to_clipspace(commonPosition);
84+
outputs.color = vec4<f32>(
85+
inputs.colors.rgb * inputs.instanceColors.rgb,
86+
inputs.instanceColors.a
87+
);
88+
outputs.texCoord = texCoord;
89+
outputs.pbrPosition = commonPosition.xyz;
90+
outputs.pbrNormal = geometry.normal;
91+
outputs.pickingColor = geometry.pickingColor;
92+
return outputs;
93+
}
94+
95+
@fragment
96+
fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
97+
fragmentGeometry.uv = inputs.texCoord;
98+
99+
if (picking.isActive > 0.5) {
100+
if (!picking_isColorValid(inputs.pickingColor)) {
101+
discard;
102+
}
103+
return vec4<f32>(inputs.pickingColor, 1.0);
104+
}
105+
106+
fragmentInputs.pbr_vPosition = inputs.pbrPosition;
107+
fragmentInputs.pbr_vUV0 = inputs.texCoord;
108+
fragmentInputs.pbr_vUV1 = vec2<f32>(0.0);
109+
fragmentInputs.pbr_vNormal = inputs.pbrNormal;
110+
111+
var color = inputs.color * pbr_filterColor(vec4<f32>(0.0));
112+
color.a *= layer.opacity;
113+
114+
if (picking.isHighlightActive > 0.5) {
115+
let highlightedObjectColor = picking_normalizeColor(picking.highlightedObjectColor);
116+
if (picking_isColorZero(abs(inputs.pickingColor - highlightedObjectColor))) {
117+
let highlightAlpha = picking.highlightColor.a;
118+
let blendedAlpha = highlightAlpha + color.a * (1.0 - highlightAlpha);
119+
if (blendedAlpha > 0.0) {
120+
color = vec4<f32>(
121+
mix(color.rgb, picking.highlightColor.rgb, highlightAlpha / blendedAlpha),
122+
blendedAlpha
123+
);
124+
}
125+
}
126+
}
127+
128+
return deckgl_premultiplied_alpha(color);
129+
}
130+
`;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// deck.gl
2+
// SPDX-License-Identifier: MIT
3+
// Copyright (c) vis.gl contributors
4+
5+
import {pbrMaterial} from '@luma.gl/shadertools';
6+
7+
import type {
8+
PBRMaterialBindings,
9+
PBRMaterialProps,
10+
PBRMaterialUniforms,
11+
ShaderModule
12+
} from '@luma.gl/shadertools';
13+
14+
// The stock PBR module still uses the pre-indexed UV field name in one helper and owns a
15+
// separate camera uniform. Align it with the current PBR input struct and deck.gl's project
16+
// module until these compatibility fixes can be made in luma.gl itself.
17+
const source = (pbrMaterial.source as string)
18+
.replace(
19+
/fn pbr_setPositionNormalTangentUV\([\s\S]*?\n}\n/,
20+
`fn pbr_setPositionNormalTangentUV(position: vec4f, normal: vec4f, tangent: vec4f, uv: vec2f)
21+
{
22+
fragmentInputs.pbr_vPosition = position.xyz;
23+
fragmentInputs.pbr_vNormal = normal.xyz;
24+
fragmentInputs.pbr_vTBN = mat3x3f(
25+
vec3f(1.0, 0.0, 0.0),
26+
vec3f(0.0, 1.0, 0.0),
27+
vec3f(0.0, 0.0, 1.0)
28+
);
29+
fragmentInputs.pbr_vUV0 = uv;
30+
fragmentInputs.pbr_vUV1 = uv;
31+
}
32+
`
33+
)
34+
.replace(/pbrProjection\.camera/g, 'project.cameraPosition');
35+
36+
export const meshPbrMaterial = {
37+
...pbrMaterial,
38+
source
39+
} as const satisfies ShaderModule<PBRMaterialProps, PBRMaterialUniforms, PBRMaterialBindings>;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// deck.gl
2+
// SPDX-License-Identifier: MIT
3+
// Copyright (c) vis.gl contributors
4+
5+
import {test, expect} from 'vitest';
6+
import {LayerManager, MapView} from '@deck.gl/core';
7+
import {Geometry} from '@luma.gl/engine';
8+
import {getWebGPUTestDevice} from '@luma.gl/test-utils';
9+
10+
import MeshLayer from '../../../modules/geo-layers/src/mesh-layer/mesh-layer';
11+
12+
const TEST_MATERIAL = {
13+
pbrMetallicRoughness: {
14+
baseColorFactor: [1, 1, 1, 1],
15+
metallicFactor: 0,
16+
roughnessFactor: 1
17+
}
18+
};
19+
20+
test('Tile3DLayer mesh sublayer initializes with a WebGPU device', async ({skip}) => {
21+
const webgpuDevice = await getWebGPUTestDevice();
22+
if (!webgpuDevice) {
23+
skip();
24+
return;
25+
}
26+
27+
const viewport = new MapView({}).makeViewport({
28+
width: 100,
29+
height: 100,
30+
viewState: {longitude: 0, latitude: 0, zoom: 1}
31+
});
32+
33+
for (const featureIds of [null, new Uint32Array([0, 1, 1])]) {
34+
const errors: Error[] = [];
35+
const layerManager = new LayerManager(webgpuDevice, {viewport});
36+
layerManager.setProps({onError: error => errors.push(error)});
37+
const layer = new MeshLayer({
38+
id: featureIds ? 'webgpu-tile3d-feature-mesh' : 'webgpu-tile3d-mesh',
39+
data: [0],
40+
mesh: new Geometry({
41+
topology: 'triangle-list',
42+
attributes: {
43+
positions: {
44+
size: 3,
45+
value: new Float32Array([-1, -1, 0, 1, -1, 0, 0, 1, 0])
46+
},
47+
normals: {
48+
size: 3,
49+
value: new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1])
50+
},
51+
colors: {
52+
size: 4,
53+
value: new Uint8Array([255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255]),
54+
normalized: true
55+
},
56+
texCoords: {
57+
size: 2,
58+
value: new Float32Array([0, 0, 1, 0, 0.5, 1])
59+
},
60+
...(featureIds
61+
? {
62+
uvRegions: {
63+
size: 4,
64+
value: new Float32Array([0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1])
65+
}
66+
}
67+
: {})
68+
}
69+
}),
70+
pbrMaterial: TEST_MATERIAL,
71+
featureIds,
72+
getPosition: [0, 0, 0],
73+
getColor: [255, 255, 255, 255]
74+
});
75+
76+
webgpuDevice.handle.pushErrorScope('validation');
77+
layerManager.setLayers([layer]);
78+
79+
expect(errors).toEqual([]);
80+
expect(layer.state.model).toBeDefined();
81+
expect(await webgpuDevice.handle.popErrorScope()).toBeNull();
82+
83+
layerManager.finalize();
84+
}
85+
});

‎website/src/examples/tile-3d-layer.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {makeExample} from '../components';
1111
class Tiles3DDemo extends Component {
1212
static title = 'City of Melbourne 3D Point Cloud';
1313

14+
static hasDeviceTabs = true;
15+
1416
static code = `${GITHUB_TREE}/examples/website/3d-tiles`;
1517

1618
static parameters = {};

0 commit comments

Comments
 (0)