rm-webgpu-compute-tasks 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +29 -0
- package/src/index.d.ts +574 -0
- package/src/index.js +2323 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,2323 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { Vector2, Vector3, Vector4, Matrix4, Matrix3, Color } from "three";
|
|
3
|
+
async function include(path, exportDefault = true) {
|
|
4
|
+
if (typeof global !== "undefined" && typeof require !== "undefined") {
|
|
5
|
+
return require(path);
|
|
6
|
+
} else {
|
|
7
|
+
let pack = await import(
|
|
8
|
+
/* @vite-ignore */
|
|
9
|
+
path
|
|
10
|
+
);
|
|
11
|
+
if (exportDefault) pack = pack.default;
|
|
12
|
+
return pack;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function arrayBufferViewToWgslType(value) {
|
|
16
|
+
if (value instanceof Float32Array) return "f32";
|
|
17
|
+
if (value instanceof Int32Array) return "i32";
|
|
18
|
+
if (value instanceof Uint32Array) return "u32";
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Unsupported ArrayBufferView type: ${value.constructor.name}`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function capitalize(str) {
|
|
24
|
+
if (!str) return str;
|
|
25
|
+
return str[0].toUpperCase() + str.slice(1);
|
|
26
|
+
}
|
|
27
|
+
function isWgslType(type) {
|
|
28
|
+
return wgslTypes.includes(type);
|
|
29
|
+
}
|
|
30
|
+
function isStruct(s) {
|
|
31
|
+
if (s && Array.isArray(s) && s.length) {
|
|
32
|
+
return s.every((v) => v && typeof v === "object" && "name" in v && "type" in v && isWgslType(v.type));
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function isStructArray(s) {
|
|
37
|
+
return s && "layout" in s && isStruct(s.layout);
|
|
38
|
+
}
|
|
39
|
+
function padding(offset, type) {
|
|
40
|
+
const alignment = TYPE_ALIGN[type];
|
|
41
|
+
return (alignment - offset % alignment) % alignment;
|
|
42
|
+
}
|
|
43
|
+
function roundUp(offset, align) {
|
|
44
|
+
return offset + (align - offset % align) % align;
|
|
45
|
+
}
|
|
46
|
+
function buildStructTypeByData(data) {
|
|
47
|
+
const keys = Object.keys(data);
|
|
48
|
+
const types = keys.map((k) => {
|
|
49
|
+
const v = data[k];
|
|
50
|
+
if (Array.isArray(v)) {
|
|
51
|
+
for (const t of ["vec2", "vec3", "vec4", "mat3x3", "mat4x4"]) {
|
|
52
|
+
if (TYPE_SIZE[t] === v.length) return t;
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`${k} 不支持的数组长度 ${v.length}`);
|
|
55
|
+
}
|
|
56
|
+
if (typeof v === "number") return "f32";
|
|
57
|
+
throw new Error(`${k} 不支持的类型`);
|
|
58
|
+
});
|
|
59
|
+
if (keys.length !== types.length) throw new Error("keys 与 types 长度不一致");
|
|
60
|
+
let offset = 0;
|
|
61
|
+
const layout = keys.map((k, i) => {
|
|
62
|
+
const type = types[i];
|
|
63
|
+
offset += padding(offset, type);
|
|
64
|
+
const info = {
|
|
65
|
+
name: k,
|
|
66
|
+
type,
|
|
67
|
+
offset,
|
|
68
|
+
size: TYPE_SIZE[type]
|
|
69
|
+
};
|
|
70
|
+
offset += TYPE_SIZE[type];
|
|
71
|
+
return info;
|
|
72
|
+
});
|
|
73
|
+
const structAlign = Math.max(...types.map((t) => TYPE_ALIGN[t]));
|
|
74
|
+
const stride = offset + (structAlign - offset % structAlign) % structAlign;
|
|
75
|
+
return {
|
|
76
|
+
stride,
|
|
77
|
+
layout
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const TYPE_SIZE = {
|
|
81
|
+
f32: 1,
|
|
82
|
+
u32: 1,
|
|
83
|
+
vec2: 2,
|
|
84
|
+
vec3: 3,
|
|
85
|
+
vec4: 4,
|
|
86
|
+
mat3x3: 12,
|
|
87
|
+
mat4x4: 16
|
|
88
|
+
// array: Infinity
|
|
89
|
+
};
|
|
90
|
+
const TYPE_ALIGN = {
|
|
91
|
+
f32: 1,
|
|
92
|
+
u32: 1,
|
|
93
|
+
vec2: 2,
|
|
94
|
+
vec3: 4,
|
|
95
|
+
vec4: 4,
|
|
96
|
+
mat3x3: 4,
|
|
97
|
+
mat4x4: 4
|
|
98
|
+
// array: 1
|
|
99
|
+
};
|
|
100
|
+
class AtomicUint32Array extends Uint32Array {
|
|
101
|
+
}
|
|
102
|
+
const wgslTypes = ["f32", "u32", "vec2", "vec3", "vec4", "mat3x3", "mat4x4"];
|
|
103
|
+
let adapter$1 = null;
|
|
104
|
+
let device$1 = null;
|
|
105
|
+
class GpuComputed {
|
|
106
|
+
template;
|
|
107
|
+
option;
|
|
108
|
+
pipeline;
|
|
109
|
+
device;
|
|
110
|
+
groupLayout;
|
|
111
|
+
code;
|
|
112
|
+
constructor(template, option) {
|
|
113
|
+
this.template = template;
|
|
114
|
+
this.option = option;
|
|
115
|
+
this.improveTemplateOption(this.template);
|
|
116
|
+
}
|
|
117
|
+
/** 完善模版数据
|
|
118
|
+
* @param template
|
|
119
|
+
*/
|
|
120
|
+
improveTemplateOption(template) {
|
|
121
|
+
const improveStruct = (struct) => {
|
|
122
|
+
let offset = 0;
|
|
123
|
+
struct.forEach((item) => {
|
|
124
|
+
offset += padding(offset, item.type);
|
|
125
|
+
item.offset = offset;
|
|
126
|
+
item.size = TYPE_SIZE[item.type];
|
|
127
|
+
offset += item.size;
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
const improveStructArray = (structArr) => {
|
|
131
|
+
improveStruct(structArr.layout);
|
|
132
|
+
const last = structArr.layout[structArr.layout.length - 1], stride = last.offset + last.size;
|
|
133
|
+
let maxAlign = 1;
|
|
134
|
+
for (const item of structArr.layout) maxAlign = Math.max(maxAlign, TYPE_ALIGN[item.type]);
|
|
135
|
+
structArr.stride = roundUp(stride, maxAlign);
|
|
136
|
+
};
|
|
137
|
+
const keys = Object.keys(template);
|
|
138
|
+
keys.forEach((key) => {
|
|
139
|
+
const value = template[key];
|
|
140
|
+
if (isStruct(value)) improveStruct(value);
|
|
141
|
+
else if (isStructArray(value)) improveStructArray(value);
|
|
142
|
+
else if (Array.isArray(value) && typeof value[0] === "number") {
|
|
143
|
+
template[key] = new Float32Array();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** 获取Gpu设备
|
|
148
|
+
* @returns
|
|
149
|
+
*/
|
|
150
|
+
async getDevice() {
|
|
151
|
+
if (!adapter$1 || !device$1) throw new Error("webgpu未初始化或不可用");
|
|
152
|
+
return { adapter: adapter$1, device: device$1 };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 初始化计算管线
|
|
156
|
+
*/
|
|
157
|
+
async initPipeline() {
|
|
158
|
+
if (!this.template) throw new Error("初始化计算管线错误,未找到可用数据模版");
|
|
159
|
+
await GpuComputed.init();
|
|
160
|
+
const template = this.template;
|
|
161
|
+
const { device: device2 } = await this.getDevice(), structCodes = [], groupCodes = [], groupLayoutOption = [];
|
|
162
|
+
this.device = device2;
|
|
163
|
+
Object.keys(template).forEach((name, index) => {
|
|
164
|
+
groupLayoutOption.push({
|
|
165
|
+
binding: index,
|
|
166
|
+
// 绑定到组里的0号位插槽
|
|
167
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
168
|
+
// 数据在哪些阶段可以使用, 计算着色器、片元着色器、顶点着色器
|
|
169
|
+
buffer: {
|
|
170
|
+
type: "storage"
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
if (isStruct(template[name])) {
|
|
174
|
+
const value = template[name], code2 = value.map((item) => `${item.name}:${item.type === "f32" ? "f32" : item.type + "<f32>"}`).join(","), structName = `${capitalize(name)}Struct`;
|
|
175
|
+
structCodes.push(`struct ${capitalize(name)}Struct {${code2}};`);
|
|
176
|
+
groupCodes.push(`@group(0) @binding(${index}) var<storage, read_write> ${name}: ${structName};`);
|
|
177
|
+
} else if (isStructArray(template[name])) {
|
|
178
|
+
const value = template[name], code2 = value.layout.map((item) => `${item.name}:${item.type === "f32" ? "f32" : item.type + "<f32>"}`).join(","), structName = `${capitalize(name)}Struct`;
|
|
179
|
+
structCodes.push(`struct ${structName} {${code2}};`);
|
|
180
|
+
groupCodes.push(`@group(0) @binding(${index}) var<storage, read_write> ${name}: array<${structName}>;`);
|
|
181
|
+
} else if (ArrayBuffer.isView(template[name]) && !(template[name] instanceof DataView)) {
|
|
182
|
+
const value = template[name];
|
|
183
|
+
const type = arrayBufferViewToWgslType(value);
|
|
184
|
+
if (value instanceof AtomicUint32Array) groupCodes.push(`@group(0) @binding(${index}) var<storage, read_write> ${name}: array<atomic<${type}>>;`);
|
|
185
|
+
else groupCodes.push(`@group(0) @binding(${index}) var<storage, read_write> ${name}: array<${type}>;`);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
const {
|
|
189
|
+
beforeCodes = [],
|
|
190
|
+
workgroupSize = [32, 1, 1],
|
|
191
|
+
globalInvocationIdName = "grid",
|
|
192
|
+
workgroupIndexName = "index",
|
|
193
|
+
code = ""
|
|
194
|
+
} = this.option ?? {};
|
|
195
|
+
const codeFull = (
|
|
196
|
+
/*wgsl*/
|
|
197
|
+
`
|
|
198
|
+
${structCodes.join("")}
|
|
199
|
+
${groupCodes.join("")}
|
|
200
|
+
${beforeCodes.join(" ") ?? ""}
|
|
201
|
+
|
|
202
|
+
@compute @workgroup_size(${workgroupSize.join(",")})
|
|
203
|
+
fn main(@builtin(global_invocation_id) ${globalInvocationIdName}: vec3<u32>) {
|
|
204
|
+
var ${workgroupIndexName} = ${globalInvocationIdName}.x;
|
|
205
|
+
${code}
|
|
206
|
+
}
|
|
207
|
+
`
|
|
208
|
+
);
|
|
209
|
+
this.code = codeFull;
|
|
210
|
+
const groupLayout = device2.createBindGroupLayout({
|
|
211
|
+
entries: groupLayoutOption
|
|
212
|
+
});
|
|
213
|
+
this.groupLayout = groupLayout;
|
|
214
|
+
this.pipeline = device2.createComputePipeline({
|
|
215
|
+
layout: device2.createPipelineLayout({
|
|
216
|
+
bindGroupLayouts: [groupLayout]
|
|
217
|
+
}),
|
|
218
|
+
compute: {
|
|
219
|
+
module: device2.createShaderModule({ code: codeFull, label: "" }),
|
|
220
|
+
entryPoint: "main"
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* 根据数据创建buffer组
|
|
226
|
+
* @param data 数据
|
|
227
|
+
* @param bindGroup 已创建的bindGroup, 会在没有传入对应数据时使用,用于Buffer重复利用,不需要重新构建,按名字判断,所以请确保名称和类型一致性
|
|
228
|
+
* @returns
|
|
229
|
+
*/
|
|
230
|
+
createBindGroup(data, bindGroup) {
|
|
231
|
+
if (!this.template) throw new Error("创建buffer组错误,未找到可用数据模版");
|
|
232
|
+
if (!this.device) throw new Error("创建buffer组错误,未找到可用的gpu设备,请确保初始化完计算管线");
|
|
233
|
+
const device2 = this.device, template = this.template, buffers = [], bufferMap = bindGroup?.buffers?.reduce((map, item) => {
|
|
234
|
+
map.set(item.name, item.buffer);
|
|
235
|
+
return map;
|
|
236
|
+
}, /* @__PURE__ */ new Map());
|
|
237
|
+
function buildStruct(valueObj, tem, offset = 0, data2) {
|
|
238
|
+
if (ArrayBuffer.isView(valueObj) || Array.isArray(valueObj)) return valueObj;
|
|
239
|
+
if (!data2) {
|
|
240
|
+
const last = tem[tem.length - 1], stride = last.offset + last.size;
|
|
241
|
+
let maxAlign = 1;
|
|
242
|
+
for (const item of tem) maxAlign = Math.max(maxAlign, TYPE_ALIGN[item.type]);
|
|
243
|
+
data2 = data2 ?? new Float32Array(roundUp(stride, maxAlign)).fill(0);
|
|
244
|
+
}
|
|
245
|
+
tem.forEach((item) => {
|
|
246
|
+
let value = valueObj[item.name];
|
|
247
|
+
if (!Array.isArray(value)) value = [value];
|
|
248
|
+
for (let i = 0; i < item.size; i++) {
|
|
249
|
+
data2[offset + item.offset + i] = Number(value[i] ?? 0);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
return data2;
|
|
253
|
+
}
|
|
254
|
+
function buildStructArray(values, tem) {
|
|
255
|
+
if (ArrayBuffer.isView(values) || typeof values[0] === "number") return values;
|
|
256
|
+
const data2 = new Float32Array(tem.stride * values.length).fill(0);
|
|
257
|
+
values.forEach((value, i) => {
|
|
258
|
+
const offset = i * tem.stride;
|
|
259
|
+
buildStruct(value, tem.layout, offset, data2);
|
|
260
|
+
});
|
|
261
|
+
return data2;
|
|
262
|
+
}
|
|
263
|
+
Object.keys(template).forEach((name) => {
|
|
264
|
+
if (!(name in data)) {
|
|
265
|
+
if (bufferMap && bufferMap.has(name)) {
|
|
266
|
+
return buffers.push({ name, buffer: bufferMap.get(name) });
|
|
267
|
+
}
|
|
268
|
+
throw new Error(`传入的数据中,不存在${name}字段`);
|
|
269
|
+
}
|
|
270
|
+
const tem = template[name];
|
|
271
|
+
const value = data[name];
|
|
272
|
+
let array = [];
|
|
273
|
+
if (isStruct(tem)) array = buildStruct(value, tem);
|
|
274
|
+
else if (isStructArray(tem)) array = buildStructArray(value, tem);
|
|
275
|
+
else if (Array.isArray(value) || ArrayBuffer.isView(value)) array = value;
|
|
276
|
+
let arrayBuffer = null;
|
|
277
|
+
if (tem instanceof Float32Array) arrayBuffer = new Float32Array(array);
|
|
278
|
+
else if (tem instanceof Uint32Array) arrayBuffer = new Uint32Array(array);
|
|
279
|
+
else if (tem instanceof Int32Array) arrayBuffer = new Int32Array(array);
|
|
280
|
+
else arrayBuffer = new Float32Array(array);
|
|
281
|
+
if (!arrayBuffer) throw new Error("不支持的数组类型" + tem);
|
|
282
|
+
const buffer = device2.createBuffer({
|
|
283
|
+
size: arrayBuffer.byteLength,
|
|
284
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
|
|
285
|
+
});
|
|
286
|
+
device2.queue.writeBuffer(buffer, 0, arrayBuffer);
|
|
287
|
+
buffers.push({ name, buffer });
|
|
288
|
+
});
|
|
289
|
+
const group = device2.createBindGroup({
|
|
290
|
+
layout: this.groupLayout,
|
|
291
|
+
entries: buffers.map((item, index) => ({
|
|
292
|
+
binding: index,
|
|
293
|
+
resource: { buffer: item.buffer }
|
|
294
|
+
}))
|
|
295
|
+
});
|
|
296
|
+
return {
|
|
297
|
+
group,
|
|
298
|
+
buffers
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/** 数据映射回模版数据
|
|
302
|
+
* @param array
|
|
303
|
+
* @param key
|
|
304
|
+
*/
|
|
305
|
+
dataMap(array, key) {
|
|
306
|
+
if (!(key in this.template)) throw new Error("未找到数据字段:" + key);
|
|
307
|
+
if (isStructArray(this.template[key])) {
|
|
308
|
+
const tems = this.template[key], count = array.length / tems.stride, list = [];
|
|
309
|
+
for (let i = 0; i < count; i++) {
|
|
310
|
+
const base = i * tems.stride;
|
|
311
|
+
const obj = {};
|
|
312
|
+
tems.layout.forEach((item) => {
|
|
313
|
+
const data = array.slice(base + item.offset, base + item.offset + item.size);
|
|
314
|
+
obj[item.name] = data.length === 1 ? data[0] : [...data];
|
|
315
|
+
});
|
|
316
|
+
list.push(obj);
|
|
317
|
+
}
|
|
318
|
+
return list;
|
|
319
|
+
}
|
|
320
|
+
if (isStruct(this.template[key])) {
|
|
321
|
+
const tem = this.template[key];
|
|
322
|
+
const obj = {};
|
|
323
|
+
tem.forEach((item) => {
|
|
324
|
+
const data = array.slice(item.offset, item.offset + item.size);
|
|
325
|
+
obj[item.name] = data.length === 1 ? data[0] : [...data];
|
|
326
|
+
});
|
|
327
|
+
return obj;
|
|
328
|
+
}
|
|
329
|
+
return array;
|
|
330
|
+
}
|
|
331
|
+
/** 开始计算
|
|
332
|
+
* @param group 数据组
|
|
333
|
+
* @param workgroupCount 工作组大小
|
|
334
|
+
* @param synchronize 需要同步的数据字段
|
|
335
|
+
* @returns
|
|
336
|
+
*/
|
|
337
|
+
async computed(group, workgroupCount, synchronize = []) {
|
|
338
|
+
if (!this.pipeline) throw new Error("未找到可用计算管线,请确保计算管线已经创建成功");
|
|
339
|
+
const device2 = this.device;
|
|
340
|
+
const pipeline = this.pipeline;
|
|
341
|
+
const encoder = device2.createCommandEncoder();
|
|
342
|
+
const pass = encoder.beginComputePass();
|
|
343
|
+
pass.setPipeline(pipeline);
|
|
344
|
+
pass.setBindGroup(0, group.group);
|
|
345
|
+
pass.dispatchWorkgroups(workgroupCount[0], workgroupCount[1], workgroupCount[2]);
|
|
346
|
+
pass.end();
|
|
347
|
+
const syncBuffers = group.buffers?.map((item) => {
|
|
348
|
+
if (synchronize?.includes(item.name)) {
|
|
349
|
+
const stagingBuffer = device2.createBuffer({
|
|
350
|
+
size: item.buffer.size,
|
|
351
|
+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
|
352
|
+
});
|
|
353
|
+
encoder.copyBufferToBuffer(item.buffer, 0, stagingBuffer, 0, stagingBuffer.size);
|
|
354
|
+
return { buffer: stagingBuffer, name: item.name };
|
|
355
|
+
}
|
|
356
|
+
}).filter((i) => !!i);
|
|
357
|
+
device2.queue.submit([encoder.finish()]);
|
|
358
|
+
await device2.queue.onSubmittedWorkDone();
|
|
359
|
+
const map = /* @__PURE__ */ new Map();
|
|
360
|
+
await Promise.all(
|
|
361
|
+
syncBuffers.map(async (item) => {
|
|
362
|
+
await item.buffer.mapAsync(GPUMapMode.READ);
|
|
363
|
+
const mappedRange = item.buffer.getMappedRange();
|
|
364
|
+
let arrayBuffer = null;
|
|
365
|
+
if (this.template[item.name] instanceof Float32Array) arrayBuffer = new Float32Array(mappedRange);
|
|
366
|
+
else if (this.template[item.name] instanceof Uint32Array) arrayBuffer = new Uint32Array(mappedRange);
|
|
367
|
+
else if (this.template[item.name] instanceof Int32Array) arrayBuffer = new Int32Array(mappedRange);
|
|
368
|
+
else arrayBuffer = new Float32Array(mappedRange);
|
|
369
|
+
map.set(item.name, arrayBuffer);
|
|
370
|
+
})
|
|
371
|
+
);
|
|
372
|
+
const results = synchronize.map((name) => map.get(name));
|
|
373
|
+
return results;
|
|
374
|
+
}
|
|
375
|
+
/** 初始化gpu设备
|
|
376
|
+
* @returns
|
|
377
|
+
*/
|
|
378
|
+
static async init(opt = {}) {
|
|
379
|
+
if (adapter$1 && device$1) return;
|
|
380
|
+
if (typeof globalThis !== "undefined" && typeof window === "undefined") {
|
|
381
|
+
const { create, globals } = await include("webgpu", false);
|
|
382
|
+
Object.assign(globalThis, globals);
|
|
383
|
+
if (!globalThis.navigator) globalThis.navigator = {};
|
|
384
|
+
Object.assign(globalThis.navigator, { gpu: create([]) });
|
|
385
|
+
}
|
|
386
|
+
if (!navigator.gpu) throw new Error("该环境不支持webgpu");
|
|
387
|
+
if (!adapter$1) adapter$1 = await navigator.gpu.requestAdapter(opt);
|
|
388
|
+
if (!adapter$1) throw new Error("获取适配器失败");
|
|
389
|
+
device$1 = await adapter$1.requestDevice(opt);
|
|
390
|
+
if (!adapter$1) throw new Error("获取设备失败");
|
|
391
|
+
}
|
|
392
|
+
static set(adapter_, device_) {
|
|
393
|
+
adapter$1 = adapter_;
|
|
394
|
+
device$1 = device_;
|
|
395
|
+
}
|
|
396
|
+
/** 注销gpu设备
|
|
397
|
+
*/
|
|
398
|
+
static destroy() {
|
|
399
|
+
if (device$1) device$1.destroy();
|
|
400
|
+
device$1 = null;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* @param data
|
|
404
|
+
*/
|
|
405
|
+
static buildBufferTypeByData(data) {
|
|
406
|
+
const bufferDataType = Object.keys(data).reduce((obj, k) => {
|
|
407
|
+
let value = data[k];
|
|
408
|
+
if (Array.isArray(value) && typeof value[0] === "number") value = new Float32Array();
|
|
409
|
+
if (Array.isArray(value)) {
|
|
410
|
+
if (typeof value[0] === "object" || value.length) {
|
|
411
|
+
const buffer = buildStructTypeByData(value[0]);
|
|
412
|
+
obj[k] = buffer;
|
|
413
|
+
} else console.log(`字段:${k}, 不支持该值对应数据类型或数组为空`);
|
|
414
|
+
} else if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
|
415
|
+
obj[k] = value;
|
|
416
|
+
} else if (typeof value === "object") {
|
|
417
|
+
const buffer = buildStructTypeByData(value);
|
|
418
|
+
obj[k] = buffer.layout;
|
|
419
|
+
} else console.log(`字段:${k}, 不支持的数据类型`);
|
|
420
|
+
return obj;
|
|
421
|
+
}, {});
|
|
422
|
+
return bufferDataType;
|
|
423
|
+
}
|
|
424
|
+
/** 通过数据创建
|
|
425
|
+
* @param opt
|
|
426
|
+
* @returns
|
|
427
|
+
*/
|
|
428
|
+
static async fromByData(opt) {
|
|
429
|
+
let { data, ...option } = opt;
|
|
430
|
+
const bufferDataType = this.buildBufferTypeByData(data);
|
|
431
|
+
const gpuComputed = new GpuComputed(bufferDataType, option);
|
|
432
|
+
await gpuComputed.initPipeline();
|
|
433
|
+
return gpuComputed;
|
|
434
|
+
}
|
|
435
|
+
/** 快捷计算方法
|
|
436
|
+
* @param opt
|
|
437
|
+
* @returns
|
|
438
|
+
*/
|
|
439
|
+
static async computed(opt) {
|
|
440
|
+
let { data, map = false, workgroupCount, synchronize, onSuccess, ...option } = opt;
|
|
441
|
+
const gpuComputed = await this.fromByData({ data, ...option }), group = gpuComputed.createBindGroup(data), results = await gpuComputed.computed(group, workgroupCount, synchronize);
|
|
442
|
+
onSuccess && onSuccess({ gpuComputed, group, results });
|
|
443
|
+
if (map) return results.map((data2, i) => gpuComputed.dataMap(data2, synchronize[i]));
|
|
444
|
+
return results;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
let adapter = null;
|
|
448
|
+
let device = null;
|
|
449
|
+
async function initWebgpu(opt = {}) {
|
|
450
|
+
if (adapter && device) return;
|
|
451
|
+
if (typeof globalThis !== "undefined" && typeof window === "undefined") {
|
|
452
|
+
const { create, globals } = await include("webgpu", false);
|
|
453
|
+
Object.assign(globalThis, globals);
|
|
454
|
+
if (!globalThis.navigator) globalThis.navigator = {};
|
|
455
|
+
Object.assign(globalThis.navigator, { gpu: create([]) });
|
|
456
|
+
}
|
|
457
|
+
if (!navigator.gpu) throw new Error("该环境不支持webgpu");
|
|
458
|
+
if (!adapter) adapter = await navigator.gpu.requestAdapter(opt);
|
|
459
|
+
if (!adapter) throw new Error("获取适配器失败");
|
|
460
|
+
device = await adapter.requestDevice(opt);
|
|
461
|
+
if (!adapter) throw new Error("获取设备失败");
|
|
462
|
+
GpuComputed.set(adapter, device);
|
|
463
|
+
return { device, adapter };
|
|
464
|
+
}
|
|
465
|
+
async function destroyWebgpu() {
|
|
466
|
+
if (adapter && device) {
|
|
467
|
+
device.destroy();
|
|
468
|
+
GpuComputed.destroy();
|
|
469
|
+
adapter = null;
|
|
470
|
+
device = null;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const getDevice = () => {
|
|
474
|
+
if (!device) throw new Error("未初始化webgpu");
|
|
475
|
+
return device;
|
|
476
|
+
};
|
|
477
|
+
const getAdapter = () => {
|
|
478
|
+
if (!adapter) throw new Error("未初始化webgpu");
|
|
479
|
+
return adapter;
|
|
480
|
+
};
|
|
481
|
+
function createBuffer(device2, options) {
|
|
482
|
+
const { data, size, usage, mappedAtCreation = false, label } = options;
|
|
483
|
+
let bufferSize = size ?? 0;
|
|
484
|
+
if (data) {
|
|
485
|
+
bufferSize = data.byteLength;
|
|
486
|
+
}
|
|
487
|
+
bufferSize = Math.ceil(bufferSize / 4) * 4;
|
|
488
|
+
const buffer = device2.createBuffer({
|
|
489
|
+
size: bufferSize,
|
|
490
|
+
usage,
|
|
491
|
+
mappedAtCreation
|
|
492
|
+
});
|
|
493
|
+
if (label) {
|
|
494
|
+
buffer.label = label;
|
|
495
|
+
}
|
|
496
|
+
if (data) {
|
|
497
|
+
if (mappedAtCreation) {
|
|
498
|
+
const mapping = buffer.getMappedRange();
|
|
499
|
+
new Uint8Array(mapping).set(
|
|
500
|
+
data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer)
|
|
501
|
+
);
|
|
502
|
+
buffer.unmap();
|
|
503
|
+
} else {
|
|
504
|
+
device2.queue.writeBuffer(
|
|
505
|
+
buffer,
|
|
506
|
+
0,
|
|
507
|
+
data instanceof ArrayBuffer ? data : data.buffer
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return buffer;
|
|
512
|
+
}
|
|
513
|
+
function createTexture(device2, options) {
|
|
514
|
+
const {
|
|
515
|
+
size,
|
|
516
|
+
format,
|
|
517
|
+
usage,
|
|
518
|
+
dimension = "2d",
|
|
519
|
+
mipLevelCount = 1,
|
|
520
|
+
sampleCount = 1,
|
|
521
|
+
// data,
|
|
522
|
+
// bytesPerRow,
|
|
523
|
+
label
|
|
524
|
+
} = options;
|
|
525
|
+
const texture = device2.createTexture({
|
|
526
|
+
size,
|
|
527
|
+
format,
|
|
528
|
+
usage,
|
|
529
|
+
dimension,
|
|
530
|
+
mipLevelCount,
|
|
531
|
+
sampleCount
|
|
532
|
+
});
|
|
533
|
+
if (label) {
|
|
534
|
+
texture.label = label;
|
|
535
|
+
}
|
|
536
|
+
return texture;
|
|
537
|
+
}
|
|
538
|
+
function createRenderPipeline(device2, options) {
|
|
539
|
+
const {
|
|
540
|
+
layout = "auto",
|
|
541
|
+
vertex: vertex2,
|
|
542
|
+
primitive,
|
|
543
|
+
depthStencil,
|
|
544
|
+
multisample,
|
|
545
|
+
fragment: fragment2,
|
|
546
|
+
label
|
|
547
|
+
} = options;
|
|
548
|
+
const pipeline = device2.createRenderPipeline({
|
|
549
|
+
layout,
|
|
550
|
+
vertex: vertex2,
|
|
551
|
+
primitive,
|
|
552
|
+
depthStencil,
|
|
553
|
+
multisample,
|
|
554
|
+
fragment: fragment2
|
|
555
|
+
});
|
|
556
|
+
if (label) {
|
|
557
|
+
pipeline.label = label;
|
|
558
|
+
}
|
|
559
|
+
return pipeline;
|
|
560
|
+
}
|
|
561
|
+
function createComputePipeline(device2, options) {
|
|
562
|
+
const { layout = "auto", compute, label } = options;
|
|
563
|
+
const pipeline = device2.createComputePipeline({
|
|
564
|
+
layout,
|
|
565
|
+
compute
|
|
566
|
+
});
|
|
567
|
+
if (label) {
|
|
568
|
+
pipeline.label = label;
|
|
569
|
+
}
|
|
570
|
+
return pipeline;
|
|
571
|
+
}
|
|
572
|
+
const FORMAT_BYTES = {
|
|
573
|
+
rgba8unorm: 4,
|
|
574
|
+
"rgba8unorm-srgb": 4,
|
|
575
|
+
bgra8unorm: 4,
|
|
576
|
+
rg8unorm: 2,
|
|
577
|
+
r8unorm: 1,
|
|
578
|
+
r16float: 2,
|
|
579
|
+
r32float: 4,
|
|
580
|
+
rgba16float: 8,
|
|
581
|
+
rgba32float: 16,
|
|
582
|
+
depth32float: 4
|
|
583
|
+
};
|
|
584
|
+
async function readGPUBuffer(buffer, size, Type = Uint8Array, device2 = getDevice()) {
|
|
585
|
+
const readBuffer = device2.createBuffer({
|
|
586
|
+
size,
|
|
587
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
588
|
+
});
|
|
589
|
+
const encoder = device2.createCommandEncoder();
|
|
590
|
+
encoder.copyBufferToBuffer(buffer, 0, readBuffer, 0, size);
|
|
591
|
+
device2.queue.submit([encoder.finish()]);
|
|
592
|
+
await readBuffer.mapAsync(GPUMapMode.READ);
|
|
593
|
+
const mapped = readBuffer.getMappedRange();
|
|
594
|
+
const copy = mapped.slice(0);
|
|
595
|
+
const result = new Type(copy);
|
|
596
|
+
readBuffer.unmap();
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
async function readTextureBuffer(texture, options = {}, device2 = getDevice()) {
|
|
600
|
+
const mipLevel = options.mipLevel ?? 0;
|
|
601
|
+
const width = Math.max(1, (texture.width ?? 1) >> mipLevel);
|
|
602
|
+
const height = Math.max(1, (texture.height ?? 1) >> mipLevel);
|
|
603
|
+
const layer = options.face ?? options.layer ?? 0;
|
|
604
|
+
const format = texture.format ?? "rgba8unorm";
|
|
605
|
+
const bytesPerPixel = FORMAT_BYTES[format] ?? 4;
|
|
606
|
+
const bytesPerRow = Math.ceil(width * bytesPerPixel / 256) * 256;
|
|
607
|
+
const bufferSize = bytesPerRow * height;
|
|
608
|
+
const buffer = device2.createBuffer({
|
|
609
|
+
size: bufferSize,
|
|
610
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
611
|
+
});
|
|
612
|
+
const encoder = device2.createCommandEncoder();
|
|
613
|
+
let srcTexture = texture;
|
|
614
|
+
try {
|
|
615
|
+
encoder.copyTextureToBuffer(
|
|
616
|
+
{
|
|
617
|
+
texture: srcTexture,
|
|
618
|
+
mipLevel,
|
|
619
|
+
origin: { x: 0, y: 0, z: layer }
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
buffer,
|
|
623
|
+
bytesPerRow,
|
|
624
|
+
rowsPerImage: height
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
width,
|
|
628
|
+
height,
|
|
629
|
+
depthOrArrayLayers: 1
|
|
630
|
+
}
|
|
631
|
+
);
|
|
632
|
+
} catch {
|
|
633
|
+
const temp = device2.createTexture({
|
|
634
|
+
size: [width, height, 1],
|
|
635
|
+
format,
|
|
636
|
+
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
|
|
637
|
+
});
|
|
638
|
+
encoder.copyTextureToTexture(
|
|
639
|
+
{ texture: srcTexture },
|
|
640
|
+
{ texture: temp },
|
|
641
|
+
{ width, height, depthOrArrayLayers: 1 }
|
|
642
|
+
);
|
|
643
|
+
encoder.copyTextureToBuffer(
|
|
644
|
+
{
|
|
645
|
+
texture: temp
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
buffer,
|
|
649
|
+
bytesPerRow,
|
|
650
|
+
rowsPerImage: height
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
width,
|
|
654
|
+
height,
|
|
655
|
+
depthOrArrayLayers: 1
|
|
656
|
+
}
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
device2.queue.submit([encoder.finish()]);
|
|
660
|
+
await buffer.mapAsync(GPUMapMode.READ);
|
|
661
|
+
const mapped = new Uint8Array(buffer.getMappedRange());
|
|
662
|
+
const result = new Uint8Array(width * height * bytesPerPixel);
|
|
663
|
+
for (let y = 0; y < height; y++) {
|
|
664
|
+
const srcOffset = y * bytesPerRow;
|
|
665
|
+
const dstOffset = y * width * bytesPerPixel;
|
|
666
|
+
result.set(
|
|
667
|
+
mapped.subarray(
|
|
668
|
+
srcOffset,
|
|
669
|
+
srcOffset + width * bytesPerPixel
|
|
670
|
+
),
|
|
671
|
+
dstOffset
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
buffer.unmap();
|
|
675
|
+
return {
|
|
676
|
+
data: result,
|
|
677
|
+
width,
|
|
678
|
+
height,
|
|
679
|
+
bytesPerPixel,
|
|
680
|
+
format
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
async function readCubeTexture(texture, face = 0, mipLevel = 0, device2 = getDevice()) {
|
|
684
|
+
const result = await readTextureBuffer(texture, {
|
|
685
|
+
face,
|
|
686
|
+
mipLevel
|
|
687
|
+
}, device2);
|
|
688
|
+
return {
|
|
689
|
+
data: result.data,
|
|
690
|
+
width: result.width,
|
|
691
|
+
height: result.height,
|
|
692
|
+
format: result.format,
|
|
693
|
+
bytesPerPixel: result.bytesPerPixel
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
const shaderLocationMap = /* @__PURE__ */ new Map();
|
|
697
|
+
shaderLocationMap.set("position", 0).set("normal ", 1).set("uv", 2).set("color", 3);
|
|
698
|
+
class BufferGeometry extends THREE.BufferGeometry {
|
|
699
|
+
gpuAttributes = /* @__PURE__ */ new Map();
|
|
700
|
+
indexBuffer;
|
|
701
|
+
indexFormat;
|
|
702
|
+
needsUpdate = true;
|
|
703
|
+
count = 0;
|
|
704
|
+
update() {
|
|
705
|
+
if (!this.needsUpdate) return;
|
|
706
|
+
const device2 = getDevice();
|
|
707
|
+
let index = 0;
|
|
708
|
+
for (const name in this.attributes) {
|
|
709
|
+
const attr = this.attributes[name];
|
|
710
|
+
if (name === "position") this.count = attr.count;
|
|
711
|
+
const array = attr.array;
|
|
712
|
+
const stride = attr.itemSize * 4;
|
|
713
|
+
const buffer = device2.createBuffer({
|
|
714
|
+
size: array.byteLength,
|
|
715
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
716
|
+
});
|
|
717
|
+
device2.queue.writeBuffer(buffer, 0, array);
|
|
718
|
+
const format = this.getVertexFormat(attr);
|
|
719
|
+
const shaderLocation = shaderLocationMap.get(name) ?? shaderLocationMap.size + 2 + index;
|
|
720
|
+
this.gpuAttributes.set(name, { buffer, stride, format, shaderLocation });
|
|
721
|
+
index++;
|
|
722
|
+
}
|
|
723
|
+
if (this.index) {
|
|
724
|
+
const array = this.index.array;
|
|
725
|
+
this.indexBuffer = device2.createBuffer({
|
|
726
|
+
size: array.byteLength,
|
|
727
|
+
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
|
|
728
|
+
});
|
|
729
|
+
device2.queue.writeBuffer(this.indexBuffer, 0, array);
|
|
730
|
+
this.indexFormat = array instanceof Uint32Array ? "uint32" : "uint16";
|
|
731
|
+
}
|
|
732
|
+
this.needsUpdate = false;
|
|
733
|
+
}
|
|
734
|
+
setAttribute(name, attribute) {
|
|
735
|
+
super.setAttribute(name, attribute);
|
|
736
|
+
this.needsUpdate = true;
|
|
737
|
+
return this;
|
|
738
|
+
}
|
|
739
|
+
getVertexFormat(attr) {
|
|
740
|
+
const itemSize = attr.itemSize;
|
|
741
|
+
const array = attr.array;
|
|
742
|
+
const type = array instanceof Float32Array ? "float32" : array instanceof Uint32Array ? "uint32" : array instanceof Uint16Array ? "uint16" : "float32";
|
|
743
|
+
if (itemSize === 1) return `${type}`;
|
|
744
|
+
if (itemSize === 2) return `${type}x2`;
|
|
745
|
+
if (itemSize === 3) return `${type}x3`;
|
|
746
|
+
if (itemSize === 4) return `${type}x4`;
|
|
747
|
+
throw new Error("unsupported attribute size");
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
const layoutCache$2 = /* @__PURE__ */ new WeakMap();
|
|
751
|
+
const OBJECT_UNIFORM_SIZE = 128;
|
|
752
|
+
class Object3D extends THREE.Object3D {
|
|
753
|
+
static get bindGroupLayout() {
|
|
754
|
+
const device2 = getDevice();
|
|
755
|
+
let layout = layoutCache$2.get(device2);
|
|
756
|
+
if (layout) return layout;
|
|
757
|
+
layout = device2.createBindGroupLayout({
|
|
758
|
+
entries: [
|
|
759
|
+
{
|
|
760
|
+
binding: 0,
|
|
761
|
+
visibility: GPUShaderStage.VERTEX,
|
|
762
|
+
buffer: { type: "uniform" }
|
|
763
|
+
}
|
|
764
|
+
]
|
|
765
|
+
});
|
|
766
|
+
layoutCache$2.set(device2, layout);
|
|
767
|
+
return layout;
|
|
768
|
+
}
|
|
769
|
+
buffer;
|
|
770
|
+
bindGroup;
|
|
771
|
+
needsUpdate = true;
|
|
772
|
+
material;
|
|
773
|
+
geometry;
|
|
774
|
+
uniformArray = new Float32Array(OBJECT_UNIFORM_SIZE / 4);
|
|
775
|
+
constructor(geometry, material) {
|
|
776
|
+
super();
|
|
777
|
+
this.material = material;
|
|
778
|
+
this.geometry = geometry;
|
|
779
|
+
const device2 = getDevice();
|
|
780
|
+
this.buffer = device2.createBuffer({
|
|
781
|
+
size: OBJECT_UNIFORM_SIZE,
|
|
782
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
783
|
+
});
|
|
784
|
+
this.bindGroup = device2.createBindGroup({
|
|
785
|
+
layout: Object3D.bindGroupLayout,
|
|
786
|
+
entries: [
|
|
787
|
+
{
|
|
788
|
+
binding: 0,
|
|
789
|
+
resource: {
|
|
790
|
+
buffer: this.buffer
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
]
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
updateMatrixWorld(force) {
|
|
797
|
+
super.updateMatrixWorld(force);
|
|
798
|
+
this.needsUpdate = true;
|
|
799
|
+
}
|
|
800
|
+
_updateBuffer() {
|
|
801
|
+
if (!this.needsUpdate) return;
|
|
802
|
+
const device2 = getDevice();
|
|
803
|
+
const model = this.matrixWorld;
|
|
804
|
+
const normal = new THREE.Matrix3().getNormalMatrix(model);
|
|
805
|
+
let offset = 0;
|
|
806
|
+
this.uniformArray.set(model.elements, offset);
|
|
807
|
+
offset += 16;
|
|
808
|
+
const n = normal.elements;
|
|
809
|
+
this.uniformArray[offset++] = n[0];
|
|
810
|
+
this.uniformArray[offset++] = n[1];
|
|
811
|
+
this.uniformArray[offset++] = n[2];
|
|
812
|
+
this.uniformArray[offset++] = 0;
|
|
813
|
+
this.uniformArray[offset++] = n[3];
|
|
814
|
+
this.uniformArray[offset++] = n[4];
|
|
815
|
+
this.uniformArray[offset++] = n[5];
|
|
816
|
+
this.uniformArray[offset++] = 0;
|
|
817
|
+
this.uniformArray[offset++] = n[6];
|
|
818
|
+
this.uniformArray[offset++] = n[7];
|
|
819
|
+
this.uniformArray[offset++] = n[8];
|
|
820
|
+
this.uniformArray[offset++] = 0;
|
|
821
|
+
device2.queue.writeBuffer(this.buffer, 0, this.uniformArray);
|
|
822
|
+
this.needsUpdate = false;
|
|
823
|
+
}
|
|
824
|
+
pipeline = null;
|
|
825
|
+
createPipeline(format) {
|
|
826
|
+
this.pipeline = this.material.createPipeline(this.geometry.gpuAttributes, format);
|
|
827
|
+
}
|
|
828
|
+
update(format) {
|
|
829
|
+
this.geometry.update();
|
|
830
|
+
this.material.update();
|
|
831
|
+
if (!this.pipeline) this.createPipeline(format);
|
|
832
|
+
if (this.needsUpdate) this._updateBuffer();
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
const layoutCache$1 = /* @__PURE__ */ new WeakMap();
|
|
836
|
+
const CAMERA_UNIFORM_SIZE = 224;
|
|
837
|
+
class PerspectiveCamera extends THREE.PerspectiveCamera {
|
|
838
|
+
static get bindGroupLayout() {
|
|
839
|
+
const device2 = getDevice();
|
|
840
|
+
let layout = layoutCache$1.get(device2);
|
|
841
|
+
if (layout) return layout;
|
|
842
|
+
layout = device2.createBindGroupLayout({
|
|
843
|
+
entries: [
|
|
844
|
+
{
|
|
845
|
+
binding: 0,
|
|
846
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
847
|
+
buffer: {
|
|
848
|
+
type: "uniform"
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
]
|
|
852
|
+
});
|
|
853
|
+
layoutCache$1.set(device2, layout);
|
|
854
|
+
return layout;
|
|
855
|
+
}
|
|
856
|
+
buffer;
|
|
857
|
+
bindGroup;
|
|
858
|
+
_needsUpdate = true;
|
|
859
|
+
get needsUpdate() {
|
|
860
|
+
return this._needsUpdate;
|
|
861
|
+
}
|
|
862
|
+
set needsUpdate(v) {
|
|
863
|
+
this._needsUpdate = v;
|
|
864
|
+
}
|
|
865
|
+
uniformArray = new Float32Array(CAMERA_UNIFORM_SIZE / 4);
|
|
866
|
+
constructor(fov, aspect, near, far) {
|
|
867
|
+
super(fov, aspect, near, far);
|
|
868
|
+
const device2 = getDevice();
|
|
869
|
+
this.buffer = device2.createBuffer({
|
|
870
|
+
size: 256,
|
|
871
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
872
|
+
});
|
|
873
|
+
this.bindGroup = device2.createBindGroup({
|
|
874
|
+
layout: PerspectiveCamera.bindGroupLayout,
|
|
875
|
+
entries: [
|
|
876
|
+
{
|
|
877
|
+
binding: 0,
|
|
878
|
+
resource: {
|
|
879
|
+
buffer: this.buffer
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
]
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
_updateBuffer() {
|
|
886
|
+
if (!this.needsUpdate) return;
|
|
887
|
+
const device2 = getDevice();
|
|
888
|
+
this.updateMatrixWorld();
|
|
889
|
+
this.updateProjectionMatrix();
|
|
890
|
+
const view = this.matrixWorldInverse;
|
|
891
|
+
const proj = this.projectionMatrix.clone();
|
|
892
|
+
const vp = new THREE.Matrix4().multiplyMatrices(proj, view);
|
|
893
|
+
const pos = this.position;
|
|
894
|
+
let offset = 0;
|
|
895
|
+
const a = this.uniformArray;
|
|
896
|
+
a.set(view.elements, offset);
|
|
897
|
+
offset += 16;
|
|
898
|
+
a.set(proj.elements, offset);
|
|
899
|
+
offset += 16;
|
|
900
|
+
a.set(vp.elements, offset);
|
|
901
|
+
offset += 16;
|
|
902
|
+
a[offset++] = pos.x;
|
|
903
|
+
a[offset++] = pos.y;
|
|
904
|
+
a[offset++] = pos.z;
|
|
905
|
+
a[offset++] = 0;
|
|
906
|
+
a[offset++] = this.near;
|
|
907
|
+
a[offset++] = this.far;
|
|
908
|
+
a[offset++] = 0;
|
|
909
|
+
a[offset++] = 0;
|
|
910
|
+
device2.queue.writeBuffer(
|
|
911
|
+
this.buffer,
|
|
912
|
+
0,
|
|
913
|
+
a
|
|
914
|
+
);
|
|
915
|
+
this.needsUpdate = false;
|
|
916
|
+
}
|
|
917
|
+
updateMatrix() {
|
|
918
|
+
super.updateMatrix();
|
|
919
|
+
this.needsUpdate = true;
|
|
920
|
+
}
|
|
921
|
+
updateMatrixWorld() {
|
|
922
|
+
super.updateMatrixWorld();
|
|
923
|
+
this.needsUpdate = true;
|
|
924
|
+
}
|
|
925
|
+
updateProjectionMatrix() {
|
|
926
|
+
super.updateProjectionMatrix();
|
|
927
|
+
this.needsUpdate = true;
|
|
928
|
+
}
|
|
929
|
+
update() {
|
|
930
|
+
if (this.needsUpdate) this._updateBuffer();
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
const samplerCache = /* @__PURE__ */ new Map();
|
|
934
|
+
function samplerKey(desc) {
|
|
935
|
+
return [
|
|
936
|
+
desc.magFilter,
|
|
937
|
+
desc.minFilter,
|
|
938
|
+
desc.mipmapFilter,
|
|
939
|
+
desc.addressModeU,
|
|
940
|
+
desc.addressModeV,
|
|
941
|
+
desc.addressModeW,
|
|
942
|
+
desc.compare
|
|
943
|
+
].join("_");
|
|
944
|
+
}
|
|
945
|
+
function getSampler(desc) {
|
|
946
|
+
const device2 = getDevice();
|
|
947
|
+
const key = samplerKey(desc);
|
|
948
|
+
let sampler = samplerCache.get(key);
|
|
949
|
+
if (!sampler) {
|
|
950
|
+
sampler = device2.createSampler(desc);
|
|
951
|
+
samplerCache.set(key, sampler);
|
|
952
|
+
}
|
|
953
|
+
return sampler;
|
|
954
|
+
}
|
|
955
|
+
class Texture {
|
|
956
|
+
depthTexture;
|
|
957
|
+
depthView;
|
|
958
|
+
texture;
|
|
959
|
+
view;
|
|
960
|
+
width;
|
|
961
|
+
height;
|
|
962
|
+
format;
|
|
963
|
+
sampler;
|
|
964
|
+
needDepthTexture;
|
|
965
|
+
depthOrArrayLayers;
|
|
966
|
+
dimension;
|
|
967
|
+
viewDimension;
|
|
968
|
+
constructor(width, height = width, des) {
|
|
969
|
+
const {
|
|
970
|
+
format = navigator.gpu.getPreferredCanvasFormat(),
|
|
971
|
+
sampler,
|
|
972
|
+
depthOrArrayLayers = 1,
|
|
973
|
+
dimension = "2d",
|
|
974
|
+
viewDimension = "2d",
|
|
975
|
+
init = true,
|
|
976
|
+
needDepthTexture = false
|
|
977
|
+
} = des ?? {};
|
|
978
|
+
this.format = format;
|
|
979
|
+
this.depthOrArrayLayers = depthOrArrayLayers;
|
|
980
|
+
this.dimension = dimension;
|
|
981
|
+
this.viewDimension = viewDimension;
|
|
982
|
+
this.needDepthTexture = needDepthTexture;
|
|
983
|
+
this.sampler = getSampler({
|
|
984
|
+
magFilter: "linear",
|
|
985
|
+
minFilter: "linear",
|
|
986
|
+
mipmapFilter: "linear",
|
|
987
|
+
...sampler
|
|
988
|
+
});
|
|
989
|
+
init && this.init(width, height);
|
|
990
|
+
}
|
|
991
|
+
init(width, height = width) {
|
|
992
|
+
if (width === this.width && height === this.height) return;
|
|
993
|
+
this.destroy();
|
|
994
|
+
const device2 = getDevice();
|
|
995
|
+
this.width = width;
|
|
996
|
+
this.height = height;
|
|
997
|
+
if (this.needDepthTexture) {
|
|
998
|
+
this.depthTexture = device2.createTexture({
|
|
999
|
+
size: [width, height],
|
|
1000
|
+
format: "depth24plus",
|
|
1001
|
+
label: "depthTexture",
|
|
1002
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
1003
|
+
});
|
|
1004
|
+
this.depthView = this.depthTexture.createView();
|
|
1005
|
+
}
|
|
1006
|
+
this.texture = device2.createTexture({
|
|
1007
|
+
size: {
|
|
1008
|
+
width,
|
|
1009
|
+
height,
|
|
1010
|
+
depthOrArrayLayers: this.depthOrArrayLayers
|
|
1011
|
+
},
|
|
1012
|
+
format: this.format,
|
|
1013
|
+
dimension: this.dimension,
|
|
1014
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
|
|
1015
|
+
});
|
|
1016
|
+
this.view = this.texture.createView({
|
|
1017
|
+
dimension: this.viewDimension
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
destroy() {
|
|
1021
|
+
this.texture?.destroy();
|
|
1022
|
+
this.depthTexture?.destroy();
|
|
1023
|
+
}
|
|
1024
|
+
static async fromByImage(bitmap) {
|
|
1025
|
+
const device2 = getDevice();
|
|
1026
|
+
const texture = new Texture(bitmap.width, bitmap.height);
|
|
1027
|
+
device2.queue.copyExternalImageToTexture(
|
|
1028
|
+
{ source: bitmap },
|
|
1029
|
+
{ texture: texture.texture },
|
|
1030
|
+
{ width: bitmap.width, height: bitmap.height }
|
|
1031
|
+
);
|
|
1032
|
+
return texture;
|
|
1033
|
+
}
|
|
1034
|
+
static async fromURL(url) {
|
|
1035
|
+
const img = await fetch(url).then((r) => r.blob());
|
|
1036
|
+
const bitmap = await createImageBitmap(img);
|
|
1037
|
+
return await this.fromByImage(bitmap);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
class CubeTexture extends Texture {
|
|
1041
|
+
get cubeView() {
|
|
1042
|
+
return this.view;
|
|
1043
|
+
}
|
|
1044
|
+
faceViews = new Array(6);
|
|
1045
|
+
constructor(width, height = width, des) {
|
|
1046
|
+
super(width, height, {
|
|
1047
|
+
viewDimension: "cube",
|
|
1048
|
+
dimension: "2d",
|
|
1049
|
+
depthOrArrayLayers: 6,
|
|
1050
|
+
...des
|
|
1051
|
+
});
|
|
1052
|
+
this.buildFaceViews();
|
|
1053
|
+
}
|
|
1054
|
+
getFaceView(face) {
|
|
1055
|
+
return this.faceViews[face];
|
|
1056
|
+
}
|
|
1057
|
+
buildFaceViews() {
|
|
1058
|
+
for (let i = 0; i < 6; i++) {
|
|
1059
|
+
this.faceViews[i] = this.texture.createView({
|
|
1060
|
+
dimension: "2d",
|
|
1061
|
+
baseArrayLayer: i,
|
|
1062
|
+
arrayLayerCount: 1
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
function preprocessShaderFSM(source, defs) {
|
|
1068
|
+
let State;
|
|
1069
|
+
((State2) => {
|
|
1070
|
+
State2[State2["TEXT"] = 0] = "TEXT";
|
|
1071
|
+
State2[State2["DIRECTIVE"] = 1] = "DIRECTIVE";
|
|
1072
|
+
})(State || (State = {}));
|
|
1073
|
+
let state = 0;
|
|
1074
|
+
let i = 0;
|
|
1075
|
+
const len = source.length;
|
|
1076
|
+
let output = "";
|
|
1077
|
+
const condStack = [];
|
|
1078
|
+
let enabled = true;
|
|
1079
|
+
function readUntilLineEnd() {
|
|
1080
|
+
let start = i;
|
|
1081
|
+
while (i < len && source[i] !== "\n") i++;
|
|
1082
|
+
return source.slice(start, i);
|
|
1083
|
+
}
|
|
1084
|
+
while (i < len) {
|
|
1085
|
+
const ch = source[i];
|
|
1086
|
+
switch (state) {
|
|
1087
|
+
case 0:
|
|
1088
|
+
if (ch === "#") {
|
|
1089
|
+
state = 1;
|
|
1090
|
+
i++;
|
|
1091
|
+
} else {
|
|
1092
|
+
if (enabled) output += ch;
|
|
1093
|
+
i++;
|
|
1094
|
+
}
|
|
1095
|
+
break;
|
|
1096
|
+
case 1:
|
|
1097
|
+
const line = "#" + readUntilLineEnd();
|
|
1098
|
+
i++;
|
|
1099
|
+
state = 0;
|
|
1100
|
+
let m = line.match(/^#if\s*\(\s*([A-Za-z0-9_]+)\s*\)/);
|
|
1101
|
+
if (m) {
|
|
1102
|
+
const key = m[1];
|
|
1103
|
+
const cond = !!defs[key];
|
|
1104
|
+
condStack.push(enabled);
|
|
1105
|
+
enabled = enabled && cond;
|
|
1106
|
+
break;
|
|
1107
|
+
}
|
|
1108
|
+
if (/^#else/.test(line)) {
|
|
1109
|
+
const parent = condStack[condStack.length - 1] ?? true;
|
|
1110
|
+
enabled = parent && !enabled;
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
if (/^#endif/.test(line)) {
|
|
1114
|
+
enabled = condStack.pop() ?? true;
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
if (enabled) {
|
|
1118
|
+
output += line + "\n";
|
|
1119
|
+
}
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return output;
|
|
1124
|
+
}
|
|
1125
|
+
const MATERIAL_UNIFORM_SIZE = 256;
|
|
1126
|
+
const pipelineCache = /* @__PURE__ */ new Map();
|
|
1127
|
+
const layoutCache = /* @__PURE__ */ new Map();
|
|
1128
|
+
function getUniformType(v, atomic = false) {
|
|
1129
|
+
if (v instanceof Matrix4) return "mat4";
|
|
1130
|
+
if (v instanceof Matrix3) return "mat3";
|
|
1131
|
+
if (v instanceof Color) return "vec3";
|
|
1132
|
+
if (v instanceof Vector2) return "vec2";
|
|
1133
|
+
if (v instanceof Vector3) return "vec3";
|
|
1134
|
+
if (v instanceof Vector4) return "vec4";
|
|
1135
|
+
if (typeof v === "number") return "f32";
|
|
1136
|
+
if (v instanceof CubeTexture) return "texture_cube";
|
|
1137
|
+
if (v instanceof Texture) return "texture_2d";
|
|
1138
|
+
if (Array.isArray(v) || v instanceof Float32Array) return `array<${atomic ? "atomic<f32>" : "f32"}>`;
|
|
1139
|
+
if (v instanceof Uint32Array || v instanceof Uint8Array) return `array<${atomic ? "atomic<u32>" : "u32"}>`;
|
|
1140
|
+
return "f32";
|
|
1141
|
+
}
|
|
1142
|
+
function getArray(v) {
|
|
1143
|
+
if (Array.isArray(v)) return new Float32Array(v);
|
|
1144
|
+
if (v instanceof Float32Array || v instanceof Uint32Array || v instanceof Uint8Array) return v;
|
|
1145
|
+
return new Float32Array();
|
|
1146
|
+
}
|
|
1147
|
+
const vertexFormatToWGSL = {
|
|
1148
|
+
uint8x2: "vec2<u32>",
|
|
1149
|
+
uint8x4: "vec4<u32>",
|
|
1150
|
+
sint8x2: "vec2<i32>",
|
|
1151
|
+
sint8x4: "vec4<i32>",
|
|
1152
|
+
unorm8x2: "vec2<f32>",
|
|
1153
|
+
unorm8x4: "vec4<f32>",
|
|
1154
|
+
snorm8x2: "vec2<f32>",
|
|
1155
|
+
snorm8x4: "vec4<f32>",
|
|
1156
|
+
uint16x2: "vec2<u32>",
|
|
1157
|
+
uint16x4: "vec4<u32>",
|
|
1158
|
+
sint16x2: "vec2<i32>",
|
|
1159
|
+
sint16x4: "vec4<i32>",
|
|
1160
|
+
unorm16x2: "vec2<f32>",
|
|
1161
|
+
unorm16x4: "vec4<f32>",
|
|
1162
|
+
snorm16x2: "vec2<f32>",
|
|
1163
|
+
snorm16x4: "vec4<f32>",
|
|
1164
|
+
float16x2: "vec2<f16>",
|
|
1165
|
+
float16x4: "vec4<f16>",
|
|
1166
|
+
float32: "f32",
|
|
1167
|
+
float32x2: "vec2<f32>",
|
|
1168
|
+
float32x3: "vec3<f32>",
|
|
1169
|
+
float32x4: "vec4<f32>",
|
|
1170
|
+
uint32: "u32",
|
|
1171
|
+
uint32x2: "vec2<u32>",
|
|
1172
|
+
uint32x3: "vec3<u32>",
|
|
1173
|
+
uint32x4: "vec4<u32>",
|
|
1174
|
+
sint32: "i32",
|
|
1175
|
+
sint32x2: "vec2<i32>",
|
|
1176
|
+
sint32x3: "vec3<i32>",
|
|
1177
|
+
sint32x4: "vec4<i32>"
|
|
1178
|
+
};
|
|
1179
|
+
const vertexCommon = (
|
|
1180
|
+
/* wgsl */
|
|
1181
|
+
`
|
|
1182
|
+
struct Camera {
|
|
1183
|
+
view : mat4x4<f32>,
|
|
1184
|
+
projection : mat4x4<f32>,
|
|
1185
|
+
viewProjection : mat4x4<f32>,
|
|
1186
|
+
position : vec3<f32>,
|
|
1187
|
+
_pad0 : f32,
|
|
1188
|
+
near : f32,
|
|
1189
|
+
far : f32,
|
|
1190
|
+
_pad1 : vec2<f32>,
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
@group(0) @binding(0)
|
|
1194
|
+
var<uniform> camera : Camera;
|
|
1195
|
+
|
|
1196
|
+
struct Object {
|
|
1197
|
+
modelMatrix : mat4x4<f32>,
|
|
1198
|
+
normalMatrix : mat3x3<f32>,
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
@group(1) @binding(0)
|
|
1202
|
+
var<uniform> object : Object;
|
|
1203
|
+
`
|
|
1204
|
+
);
|
|
1205
|
+
class ShaderMaterial {
|
|
1206
|
+
vertex;
|
|
1207
|
+
fragment;
|
|
1208
|
+
pipeline;
|
|
1209
|
+
bindGroup;
|
|
1210
|
+
buffer;
|
|
1211
|
+
uniforms;
|
|
1212
|
+
uniformArray = new Float32Array(MATERIAL_UNIFORM_SIZE / 4);
|
|
1213
|
+
_bufferUniforms = {};
|
|
1214
|
+
_textureUniforms = {};
|
|
1215
|
+
_storageUniforms = {};
|
|
1216
|
+
topology;
|
|
1217
|
+
side;
|
|
1218
|
+
blend;
|
|
1219
|
+
writeMask;
|
|
1220
|
+
depthStencil;
|
|
1221
|
+
env;
|
|
1222
|
+
_needsUniformUpdate = true;
|
|
1223
|
+
get needsUniformUpdate() {
|
|
1224
|
+
return this._needsUniformUpdate;
|
|
1225
|
+
}
|
|
1226
|
+
set needsUniformUpdate(v) {
|
|
1227
|
+
this._needsUniformUpdate = v;
|
|
1228
|
+
}
|
|
1229
|
+
_needsBindGroupUpdate = true;
|
|
1230
|
+
get needsBindGroupUpdate() {
|
|
1231
|
+
return this._needsBindGroupUpdate;
|
|
1232
|
+
}
|
|
1233
|
+
set needsBindGroupUpdate(v) {
|
|
1234
|
+
this._needsBindGroupUpdate = v;
|
|
1235
|
+
}
|
|
1236
|
+
constructor(options) {
|
|
1237
|
+
const device2 = getDevice();
|
|
1238
|
+
this.vertex = vertexCommon + options.vertex;
|
|
1239
|
+
this.fragment = options.fragment;
|
|
1240
|
+
this.topology = options.topology;
|
|
1241
|
+
this.side = options.side ?? "front";
|
|
1242
|
+
this.blend = options.blend;
|
|
1243
|
+
this.writeMask = options.writeMask;
|
|
1244
|
+
this.depthStencil = options.depthStencil;
|
|
1245
|
+
this.env = options.env;
|
|
1246
|
+
this.buffer = device2.createBuffer({
|
|
1247
|
+
size: MATERIAL_UNIFORM_SIZE,
|
|
1248
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
1249
|
+
});
|
|
1250
|
+
this._initUniform(options.uniforms || {});
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* 初始化uniform
|
|
1254
|
+
* @param uniforms
|
|
1255
|
+
*/
|
|
1256
|
+
_initUniform(uniforms) {
|
|
1257
|
+
const device2 = getDevice();
|
|
1258
|
+
this.uniforms = {};
|
|
1259
|
+
let binding = 1;
|
|
1260
|
+
const createObjectListener = (key, value, type, set) => {
|
|
1261
|
+
const uniform = {
|
|
1262
|
+
get value() {
|
|
1263
|
+
return value;
|
|
1264
|
+
},
|
|
1265
|
+
set value(v) {
|
|
1266
|
+
if (getUniformType(v) !== type) return;
|
|
1267
|
+
value = set(v, value);
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
this.uniforms[key] = uniform;
|
|
1271
|
+
};
|
|
1272
|
+
const defaultListener = (key, value, type) => {
|
|
1273
|
+
createObjectListener(key, value, type, (newValue) => {
|
|
1274
|
+
this._bufferUniforms[key].value = newValue;
|
|
1275
|
+
this.needsUniformUpdate = true;
|
|
1276
|
+
return newValue;
|
|
1277
|
+
});
|
|
1278
|
+
this._bufferUniforms[key] = { type, value };
|
|
1279
|
+
};
|
|
1280
|
+
const arrayListener = (key, value, type) => {
|
|
1281
|
+
createObjectListener(key, value, type, (newValue, oldValue) => {
|
|
1282
|
+
const data = getArray(newValue);
|
|
1283
|
+
if (newValue.length !== oldValue.length) {
|
|
1284
|
+
this._storageUniforms[key].buffer = createBuffer(device2, {
|
|
1285
|
+
data,
|
|
1286
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
|
|
1287
|
+
});
|
|
1288
|
+
this.needsBindGroupUpdate = true;
|
|
1289
|
+
} else {
|
|
1290
|
+
device2.queue.writeBuffer(this._storageUniforms[key].buffer, 0, data);
|
|
1291
|
+
}
|
|
1292
|
+
this._storageUniforms[key].value = newValue;
|
|
1293
|
+
return newValue;
|
|
1294
|
+
});
|
|
1295
|
+
this._storageUniforms[key] = {
|
|
1296
|
+
type,
|
|
1297
|
+
value,
|
|
1298
|
+
binding,
|
|
1299
|
+
buffer: createBuffer(device2, {
|
|
1300
|
+
data: getArray(value),
|
|
1301
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
|
|
1302
|
+
})
|
|
1303
|
+
};
|
|
1304
|
+
binding += 1;
|
|
1305
|
+
};
|
|
1306
|
+
const textureListener = (key, value, type) => {
|
|
1307
|
+
createObjectListener(key, value, type, (newValue) => {
|
|
1308
|
+
this._textureUniforms[key].value = newValue;
|
|
1309
|
+
this._createBindGroup();
|
|
1310
|
+
this.needsUniformUpdate = true;
|
|
1311
|
+
return newValue;
|
|
1312
|
+
});
|
|
1313
|
+
this._textureUniforms[key] = { type, value, binding };
|
|
1314
|
+
binding += 2;
|
|
1315
|
+
};
|
|
1316
|
+
for (const key in uniforms) {
|
|
1317
|
+
let value = uniforms[key].value;
|
|
1318
|
+
const type = getUniformType(value, uniforms[key]?.atomic);
|
|
1319
|
+
if (Array.isArray(value) || value instanceof Float32Array || value instanceof Uint32Array || value instanceof Uint8Array) {
|
|
1320
|
+
arrayListener(key, value, type);
|
|
1321
|
+
this._storageUniforms[key].write = uniforms[key].write ?? false;
|
|
1322
|
+
} else if (type === "texture_cube" || type === "texture_2d") textureListener(key, value, type);
|
|
1323
|
+
else defaultListener(key, value, type);
|
|
1324
|
+
}
|
|
1325
|
+
this.needsBindGroupUpdate = true;
|
|
1326
|
+
this.needsUniformUpdate = true;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* 获取绑定组布局
|
|
1330
|
+
* @returns
|
|
1331
|
+
*/
|
|
1332
|
+
_getBindGroupLayout() {
|
|
1333
|
+
const device2 = getDevice(), textureKeys = Object.keys(this._textureUniforms), storageUniformsKey = Object.keys(this._storageUniforms), key = "layout_" + JSON.stringify({
|
|
1334
|
+
textures: textureKeys.map((k) => ({
|
|
1335
|
+
name: k,
|
|
1336
|
+
binding: this._textureUniforms[k].binding,
|
|
1337
|
+
type: this._textureUniforms[k].type
|
|
1338
|
+
})),
|
|
1339
|
+
storages: storageUniformsKey.map((k) => ({
|
|
1340
|
+
name: k,
|
|
1341
|
+
binding: this._storageUniforms[k].binding,
|
|
1342
|
+
write: this._storageUniforms[k].write
|
|
1343
|
+
}))
|
|
1344
|
+
});
|
|
1345
|
+
if (layoutCache.has(key)) return layoutCache.get(key);
|
|
1346
|
+
const entries = [{
|
|
1347
|
+
binding: 0,
|
|
1348
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
1349
|
+
buffer: {}
|
|
1350
|
+
}];
|
|
1351
|
+
textureKeys.forEach((key2) => {
|
|
1352
|
+
const item = this._textureUniforms[key2];
|
|
1353
|
+
entries.push({
|
|
1354
|
+
binding: item.binding,
|
|
1355
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
1356
|
+
texture: item.type === "texture_cube" ? {
|
|
1357
|
+
"viewDimension": "cube"
|
|
1358
|
+
} : {}
|
|
1359
|
+
});
|
|
1360
|
+
entries.push({
|
|
1361
|
+
binding: item.binding + 1,
|
|
1362
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
1363
|
+
sampler: {}
|
|
1364
|
+
});
|
|
1365
|
+
});
|
|
1366
|
+
storageUniformsKey.forEach((key2) => {
|
|
1367
|
+
const item = this._storageUniforms[key2];
|
|
1368
|
+
entries.push({
|
|
1369
|
+
binding: item.binding,
|
|
1370
|
+
visibility: item.write ? GPUShaderStage.FRAGMENT : GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX,
|
|
1371
|
+
buffer: {
|
|
1372
|
+
type: item.write ? "storage" : "read-only-storage"
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
});
|
|
1376
|
+
const layout = device2.createBindGroupLayout({ entries });
|
|
1377
|
+
layoutCache.set(key, layout);
|
|
1378
|
+
return layout;
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* 创建绑定组
|
|
1382
|
+
*/
|
|
1383
|
+
_createBindGroup() {
|
|
1384
|
+
const device2 = getDevice();
|
|
1385
|
+
const entries = [{
|
|
1386
|
+
binding: 0,
|
|
1387
|
+
resource: { buffer: this.buffer }
|
|
1388
|
+
}];
|
|
1389
|
+
for (const key in this._textureUniforms) {
|
|
1390
|
+
const meta = this._textureUniforms[key];
|
|
1391
|
+
const tex = meta.value;
|
|
1392
|
+
entries.push({
|
|
1393
|
+
binding: meta.binding,
|
|
1394
|
+
resource: tex.view
|
|
1395
|
+
});
|
|
1396
|
+
entries.push({
|
|
1397
|
+
binding: meta.binding + 1,
|
|
1398
|
+
resource: tex.sampler
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
for (const key in this._storageUniforms) {
|
|
1402
|
+
const meta = this._storageUniforms[key];
|
|
1403
|
+
entries.push({
|
|
1404
|
+
binding: meta.binding,
|
|
1405
|
+
resource: meta.buffer
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
this.bindGroup = device2.createBindGroup({
|
|
1409
|
+
layout: this._getBindGroupLayout(),
|
|
1410
|
+
entries
|
|
1411
|
+
});
|
|
1412
|
+
this.needsBindGroupUpdate = false;
|
|
1413
|
+
}
|
|
1414
|
+
/** 生成uniform 代码
|
|
1415
|
+
* @param type
|
|
1416
|
+
* @returns
|
|
1417
|
+
*/
|
|
1418
|
+
_generateUniformWGSL(type) {
|
|
1419
|
+
let code = "";
|
|
1420
|
+
if (Object.keys(this._bufferUniforms).length) {
|
|
1421
|
+
code += "struct MaterialUniforms {\n";
|
|
1422
|
+
for (const key in this._bufferUniforms) {
|
|
1423
|
+
const type2 = this._bufferUniforms[key].type;
|
|
1424
|
+
if (type2 === "mat4")
|
|
1425
|
+
code += ` ${key} : mat4x4<f32>,
|
|
1426
|
+
`;
|
|
1427
|
+
else if (type2 === "mat3")
|
|
1428
|
+
code += ` ${key} : mat3x3<f32>,
|
|
1429
|
+
`;
|
|
1430
|
+
else if (type2 === "vec2")
|
|
1431
|
+
code += ` ${key} : vec2<f32>,
|
|
1432
|
+
`;
|
|
1433
|
+
else if (type2 === "vec3")
|
|
1434
|
+
code += ` ${key} : vec3<f32>,
|
|
1435
|
+
`;
|
|
1436
|
+
else if (type2 === "vec4")
|
|
1437
|
+
code += ` ${key} : vec4<f32>,
|
|
1438
|
+
`;
|
|
1439
|
+
else if (type2 === "f32")
|
|
1440
|
+
code += ` ${key} : f32,
|
|
1441
|
+
`;
|
|
1442
|
+
}
|
|
1443
|
+
code += "};\n";
|
|
1444
|
+
code += ` @group(2) @binding(0) var<uniform> uniforms : MaterialUniforms; `;
|
|
1445
|
+
}
|
|
1446
|
+
if (type === "fragment") {
|
|
1447
|
+
for (const key in this._textureUniforms) {
|
|
1448
|
+
const meta = this._textureUniforms[key];
|
|
1449
|
+
const b = meta.binding;
|
|
1450
|
+
if (meta.type === "texture_2d") {
|
|
1451
|
+
code += `
|
|
1452
|
+
@group(2) @binding(${b})
|
|
1453
|
+
var ${key} : texture_2d<f32>;
|
|
1454
|
+
@group(2) @binding(${b + 1})
|
|
1455
|
+
var ${key}Sampler : sampler;
|
|
1456
|
+
`;
|
|
1457
|
+
}
|
|
1458
|
+
if (meta.type === "texture_cube") {
|
|
1459
|
+
code += `
|
|
1460
|
+
@group(2) @binding(${b})
|
|
1461
|
+
var ${key} : texture_cube<f32>;
|
|
1462
|
+
@group(2) @binding(${b + 1})
|
|
1463
|
+
var ${key}Sampler : sampler;
|
|
1464
|
+
`;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return code;
|
|
1469
|
+
}
|
|
1470
|
+
/** 生成 顶点着色器入参类型代码
|
|
1471
|
+
* @param attrMap
|
|
1472
|
+
* @returns
|
|
1473
|
+
*/
|
|
1474
|
+
_generateVertexInputWGSL(attrMap) {
|
|
1475
|
+
if (!attrMap.size) return "";
|
|
1476
|
+
let code = `struct VertexInput { `;
|
|
1477
|
+
attrMap.forEach((v, name) => code += `@location(${v.shaderLocation}) ${name}: ${vertexFormatToWGSL[v.format]}, `);
|
|
1478
|
+
code += `};`;
|
|
1479
|
+
return code;
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* 生成 Storage 类型代码
|
|
1483
|
+
*/
|
|
1484
|
+
_generateStorageWGSL(type) {
|
|
1485
|
+
return Object.keys(this._storageUniforms).map((name) => {
|
|
1486
|
+
const item = this._storageUniforms[name];
|
|
1487
|
+
if (item.write && type === "vertex") return "";
|
|
1488
|
+
return `@group(2) @binding(${item.binding}) var<storage, ${item.write ? "read_write" : "read"}> ${name}: ${item.type}; `;
|
|
1489
|
+
}).join("");
|
|
1490
|
+
}
|
|
1491
|
+
/** 创建渲染管线
|
|
1492
|
+
* @param attrMap
|
|
1493
|
+
* @param colorFormat
|
|
1494
|
+
* @param depthFormat
|
|
1495
|
+
* @returns
|
|
1496
|
+
*/
|
|
1497
|
+
createPipeline(attrMap, colorFormat = "rgba8unorm", depthFormat = "depth24plus") {
|
|
1498
|
+
const vertexBuffers = [];
|
|
1499
|
+
attrMap.forEach((v) => {
|
|
1500
|
+
vertexBuffers.push({
|
|
1501
|
+
arrayStride: v.stride,
|
|
1502
|
+
attributes: [
|
|
1503
|
+
{
|
|
1504
|
+
shaderLocation: v.shaderLocation,
|
|
1505
|
+
offset: 0,
|
|
1506
|
+
format: v.format
|
|
1507
|
+
}
|
|
1508
|
+
]
|
|
1509
|
+
});
|
|
1510
|
+
});
|
|
1511
|
+
const device2 = getDevice();
|
|
1512
|
+
const key = this.vertex + this.fragment + colorFormat + depthFormat + this.topology + this.side + Object.keys(this._textureUniforms).length + JSON.stringify(vertexBuffers);
|
|
1513
|
+
if (pipelineCache.has(key)) {
|
|
1514
|
+
this.pipeline = pipelineCache.get(key);
|
|
1515
|
+
return this.pipeline;
|
|
1516
|
+
}
|
|
1517
|
+
const env = { ...this.env };
|
|
1518
|
+
for (const key2 of attrMap.keys()) env[key2] = true;
|
|
1519
|
+
const moduleVertex = device2.createShaderModule({
|
|
1520
|
+
code: `${this._generateUniformWGSL("vertex")} ${this._generateStorageWGSL("vertex")} ${this._generateVertexInputWGSL(attrMap)} ${preprocessShaderFSM(this.vertex, env)}`
|
|
1521
|
+
});
|
|
1522
|
+
const moduleFragment = device2.createShaderModule({
|
|
1523
|
+
code: `${this._generateUniformWGSL("fragment")} ${this._generateStorageWGSL("fragment")} ${preprocessShaderFSM(this.fragment, env)}`
|
|
1524
|
+
});
|
|
1525
|
+
const pipeline = device2.createRenderPipeline({
|
|
1526
|
+
layout: device2.createPipelineLayout({
|
|
1527
|
+
bindGroupLayouts: [
|
|
1528
|
+
PerspectiveCamera.bindGroupLayout,
|
|
1529
|
+
Object3D.bindGroupLayout,
|
|
1530
|
+
this._getBindGroupLayout()
|
|
1531
|
+
]
|
|
1532
|
+
}),
|
|
1533
|
+
vertex: {
|
|
1534
|
+
module: moduleVertex,
|
|
1535
|
+
entryPoint: "main",
|
|
1536
|
+
buffers: vertexBuffers
|
|
1537
|
+
},
|
|
1538
|
+
fragment: {
|
|
1539
|
+
module: moduleFragment,
|
|
1540
|
+
entryPoint: "main",
|
|
1541
|
+
targets: [{
|
|
1542
|
+
format: colorFormat,
|
|
1543
|
+
blend: this.blend,
|
|
1544
|
+
writeMask: this.writeMask
|
|
1545
|
+
}]
|
|
1546
|
+
},
|
|
1547
|
+
primitive: {
|
|
1548
|
+
topology: this.topology,
|
|
1549
|
+
cullMode: this.side
|
|
1550
|
+
},
|
|
1551
|
+
depthStencil: {
|
|
1552
|
+
depthWriteEnabled: true,
|
|
1553
|
+
format: depthFormat,
|
|
1554
|
+
depthCompare: "less",
|
|
1555
|
+
...this.depthStencil
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
pipelineCache.set(key, pipeline);
|
|
1559
|
+
this.pipeline = pipeline;
|
|
1560
|
+
return pipeline;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* 更新uniform 数据
|
|
1564
|
+
*/
|
|
1565
|
+
_updateUniform() {
|
|
1566
|
+
const device2 = getDevice();
|
|
1567
|
+
let offset = 0;
|
|
1568
|
+
const align4 = (n) => n + 3 & -4;
|
|
1569
|
+
for (const key in this._bufferUniforms) {
|
|
1570
|
+
const value = this._bufferUniforms[key].value;
|
|
1571
|
+
offset = align4(offset);
|
|
1572
|
+
if (typeof value === "number") {
|
|
1573
|
+
this.uniformArray[offset++] = value;
|
|
1574
|
+
continue;
|
|
1575
|
+
}
|
|
1576
|
+
if (value instanceof Vector2) {
|
|
1577
|
+
this.uniformArray.set([value.x, value.y], offset);
|
|
1578
|
+
offset += 2;
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
if (value instanceof Vector3) {
|
|
1582
|
+
this.uniformArray.set([value.x, value.y, value.z], offset);
|
|
1583
|
+
offset += 3;
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
if (value instanceof Vector4) {
|
|
1587
|
+
this.uniformArray.set([value.x, value.y, value.z, value.w], offset);
|
|
1588
|
+
offset += 4;
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
if (value.isColor) {
|
|
1592
|
+
const c = value;
|
|
1593
|
+
this.uniformArray[offset++] = c.r;
|
|
1594
|
+
this.uniformArray[offset++] = c.g;
|
|
1595
|
+
this.uniformArray[offset++] = c.b;
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
if (value.isMatrix4) {
|
|
1599
|
+
this.uniformArray.set(value.elements, offset);
|
|
1600
|
+
offset += 16;
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
if (value.isMatrix3) {
|
|
1604
|
+
const m = value.elements;
|
|
1605
|
+
this.uniformArray[offset + 0] = m[0];
|
|
1606
|
+
this.uniformArray[offset + 1] = m[1];
|
|
1607
|
+
this.uniformArray[offset + 2] = m[2];
|
|
1608
|
+
this.uniformArray[offset + 4] = m[3];
|
|
1609
|
+
this.uniformArray[offset + 5] = m[4];
|
|
1610
|
+
this.uniformArray[offset + 6] = m[5];
|
|
1611
|
+
this.uniformArray[offset + 8] = m[6];
|
|
1612
|
+
this.uniformArray[offset + 9] = m[7];
|
|
1613
|
+
this.uniformArray[offset + 10] = m[8];
|
|
1614
|
+
offset += 12;
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
device2.queue.writeBuffer(this.buffer, 0, this.uniformArray);
|
|
1619
|
+
this.needsUniformUpdate = false;
|
|
1620
|
+
}
|
|
1621
|
+
update() {
|
|
1622
|
+
if (this.needsBindGroupUpdate) this._createBindGroup();
|
|
1623
|
+
if (this.needsUniformUpdate) this._updateUniform();
|
|
1624
|
+
}
|
|
1625
|
+
/** 获取buffer
|
|
1626
|
+
* @param name
|
|
1627
|
+
* @returns
|
|
1628
|
+
*/
|
|
1629
|
+
getStorageBuffer(name) {
|
|
1630
|
+
return this._storageUniforms[name]?.buffer;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
const transparentOption = {
|
|
1634
|
+
blend: {
|
|
1635
|
+
color: {
|
|
1636
|
+
srcFactor: "src-alpha",
|
|
1637
|
+
dstFactor: "one-minus-src-alpha",
|
|
1638
|
+
operation: "add"
|
|
1639
|
+
},
|
|
1640
|
+
alpha: {
|
|
1641
|
+
srcFactor: "one",
|
|
1642
|
+
dstFactor: "one-minus-src-alpha",
|
|
1643
|
+
operation: "add"
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
};
|
|
1647
|
+
const vertex = (
|
|
1648
|
+
/* wgsl */
|
|
1649
|
+
`
|
|
1650
|
+
struct VertexOutput {
|
|
1651
|
+
@builtin(position) Position : vec4<f32>,
|
|
1652
|
+
#if(uv)
|
|
1653
|
+
@location(0) uv: vec2<f32>
|
|
1654
|
+
#endif
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1657
|
+
@vertex
|
|
1658
|
+
fn main(input: VertexInput) -> VertexOutput {
|
|
1659
|
+
var out: VertexOutput;
|
|
1660
|
+
let worldPos = object.modelMatrix * vec4<f32>(input.position, 1.0);
|
|
1661
|
+
out.Position = camera.viewProjection * worldPos;
|
|
1662
|
+
|
|
1663
|
+
#if(uv)
|
|
1664
|
+
out.uv = input.uv; #endif
|
|
1665
|
+
|
|
1666
|
+
return out;
|
|
1667
|
+
}
|
|
1668
|
+
`
|
|
1669
|
+
);
|
|
1670
|
+
const fragment = (
|
|
1671
|
+
/* wgsl */
|
|
1672
|
+
`
|
|
1673
|
+
@fragment
|
|
1674
|
+
fn main(
|
|
1675
|
+
#if(uv)
|
|
1676
|
+
@location(0) uv: vec2<f32>
|
|
1677
|
+
#endif
|
|
1678
|
+
) -> @location(0) vec4<f32> {
|
|
1679
|
+
#if(uv)
|
|
1680
|
+
let color = textureSample(map, mapSampler, uv);
|
|
1681
|
+
return vec4<f32>(uniforms.color, uniforms.opacity) * color;
|
|
1682
|
+
#else
|
|
1683
|
+
return vec4<f32>(uniforms.color, uniforms.opacity);
|
|
1684
|
+
#endif
|
|
1685
|
+
}
|
|
1686
|
+
`
|
|
1687
|
+
);
|
|
1688
|
+
class BaseMaterial extends ShaderMaterial {
|
|
1689
|
+
constructor(opt = {}) {
|
|
1690
|
+
super({
|
|
1691
|
+
vertex,
|
|
1692
|
+
fragment,
|
|
1693
|
+
side: opt.side ?? "front",
|
|
1694
|
+
topology: opt.topology ?? "triangle-list",
|
|
1695
|
+
uniforms: {
|
|
1696
|
+
opacity: { value: opt.opacity ?? 1 },
|
|
1697
|
+
color: { value: new Color(opt.color ?? 16777215) },
|
|
1698
|
+
map: { value: opt.map ?? new Texture(128, 128) }
|
|
1699
|
+
},
|
|
1700
|
+
...opt.transparent && {
|
|
1701
|
+
...transparentOption,
|
|
1702
|
+
writeMask: GPUColorWrite.ALL,
|
|
1703
|
+
depthStencil: {
|
|
1704
|
+
depthWriteEnabled: false,
|
|
1705
|
+
depthCompare: "less"
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
class DepthMaterial extends ShaderMaterial {
|
|
1712
|
+
constructor(opt = {}) {
|
|
1713
|
+
super({
|
|
1714
|
+
vertex: (
|
|
1715
|
+
/* wgsl */
|
|
1716
|
+
`
|
|
1717
|
+
struct VertexOutput {
|
|
1718
|
+
@builtin(position) Position : vec4<f32>,
|
|
1719
|
+
@location(0) worldDir: vec3<f32>,
|
|
1720
|
+
@location(1) near: f32,
|
|
1721
|
+
@location(2) far: f32,
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
@vertex
|
|
1725
|
+
fn main(input: VertexInput) -> VertexOutput {
|
|
1726
|
+
var out: VertexOutput;
|
|
1727
|
+
let worldPos = object.modelMatrix * vec4<f32>(input.position, 1.0);
|
|
1728
|
+
|
|
1729
|
+
out.Position = camera.viewProjection * worldPos;
|
|
1730
|
+
out.worldDir = worldPos.xyz - camera.position;
|
|
1731
|
+
out.near = camera.near;
|
|
1732
|
+
out.far = camera.far;
|
|
1733
|
+
|
|
1734
|
+
return out;
|
|
1735
|
+
}
|
|
1736
|
+
`
|
|
1737
|
+
),
|
|
1738
|
+
fragment: (
|
|
1739
|
+
/* wgsl */
|
|
1740
|
+
`
|
|
1741
|
+
fn packDepth(v: f32) -> vec4<f32> {
|
|
1742
|
+
var bitShift: vec4<f32> = vec4<f32>( 256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0 );
|
|
1743
|
+
var bitMask: vec4<f32> = vec4<f32>( 0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0 );
|
|
1744
|
+
var res: vec4<f32> = fract(v * bitShift);
|
|
1745
|
+
res -= res.xxyz * bitMask;
|
|
1746
|
+
return res;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
@fragment
|
|
1750
|
+
fn main(
|
|
1751
|
+
@location(0) worldDir: vec3<f32>,
|
|
1752
|
+
@location(1) near: f32,
|
|
1753
|
+
@location(2) far: f32
|
|
1754
|
+
) -> @location(0) vec4<f32> {
|
|
1755
|
+
var depth = length(worldDir);
|
|
1756
|
+
depth = (depth - near) / (far - near);
|
|
1757
|
+
depth = clamp(depth, 0.0, 1.0);
|
|
1758
|
+
return packDepth(depth);
|
|
1759
|
+
}
|
|
1760
|
+
`
|
|
1761
|
+
),
|
|
1762
|
+
side: opt.side ?? "none",
|
|
1763
|
+
topology: opt.topology ?? "triangle-list",
|
|
1764
|
+
depthStencil: {
|
|
1765
|
+
depthWriteEnabled: true,
|
|
1766
|
+
depthCompare: "greater"
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
class OcclusionMaterial extends ShaderMaterial {
|
|
1772
|
+
constructor(opt) {
|
|
1773
|
+
super({
|
|
1774
|
+
vertex: (
|
|
1775
|
+
/* wgsl */
|
|
1776
|
+
`
|
|
1777
|
+
struct VertexOutput {
|
|
1778
|
+
@builtin(position) Position : vec4<f32>,
|
|
1779
|
+
@location(0) worldDir: vec3<f32>,
|
|
1780
|
+
@location(1) near: f32,
|
|
1781
|
+
@location(2) far: f32,
|
|
1782
|
+
@location(3) @interpolate(flat) vId: vec4<f32>,
|
|
1783
|
+
@location(4) @interpolate(flat) id: u32,
|
|
1784
|
+
};
|
|
1785
|
+
|
|
1786
|
+
fn packUint(v: u32) -> vec4<f32>{
|
|
1787
|
+
return vec4<f32>(
|
|
1788
|
+
f32(v & 255u),
|
|
1789
|
+
f32((v >> 8) & 255u),
|
|
1790
|
+
f32((v >> 16) & 255u),
|
|
1791
|
+
f32((v >> 24) & 255u)
|
|
1792
|
+
) / 255.0;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
@vertex
|
|
1796
|
+
fn main(input: VertexInput) -> VertexOutput {
|
|
1797
|
+
var out: VertexOutput;
|
|
1798
|
+
let worldPos = object.modelMatrix * vec4<f32>(input.position, 1.0);
|
|
1799
|
+
|
|
1800
|
+
out.Position = camera.viewProjection * worldPos;
|
|
1801
|
+
out.worldDir = worldPos.xyz - camera.position;
|
|
1802
|
+
out.near = camera.near;
|
|
1803
|
+
out.far = camera.far;
|
|
1804
|
+
out.vId = packUint( input.id );
|
|
1805
|
+
out.id = input.id;
|
|
1806
|
+
|
|
1807
|
+
return out;
|
|
1808
|
+
}
|
|
1809
|
+
`
|
|
1810
|
+
),
|
|
1811
|
+
fragment: (
|
|
1812
|
+
/* wgsl */
|
|
1813
|
+
`
|
|
1814
|
+
fn unpackDepth(v: vec4<f32>) -> f32 {
|
|
1815
|
+
var bitShift: vec4<f32> = vec4<f32>( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );
|
|
1816
|
+
return dot(v, 1.0 / bitShift);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
@fragment
|
|
1820
|
+
fn main(
|
|
1821
|
+
@location(0) worldDir: vec3<f32>,
|
|
1822
|
+
@location(1) near: f32,
|
|
1823
|
+
@location(2) far: f32,
|
|
1824
|
+
@location(3) @interpolate(flat) vId: vec4<f32>,
|
|
1825
|
+
@location(4) @interpolate(flat) id: u32
|
|
1826
|
+
) -> @location(0) vec4<f32> {
|
|
1827
|
+
var dist = length(worldDir);
|
|
1828
|
+
|
|
1829
|
+
// 偏移
|
|
1830
|
+
var diff = 0.1;
|
|
1831
|
+
|
|
1832
|
+
var dir = normalize(worldDir);
|
|
1833
|
+
dir.x = -dir.x;
|
|
1834
|
+
|
|
1835
|
+
var depthColor = textureSample(cubeTexture, cubeTextureSampler, dir);
|
|
1836
|
+
var maxDepth = unpackDepth( depthColor );
|
|
1837
|
+
var maxDist = maxDepth * (far - near) + near;
|
|
1838
|
+
|
|
1839
|
+
if(depthColor.a != 1.0 && maxDist - dist > diff) {
|
|
1840
|
+
atomicOr(&results[id], 1);
|
|
1841
|
+
return vId;
|
|
1842
|
+
}
|
|
1843
|
+
return vec4<f32>(0);
|
|
1844
|
+
}
|
|
1845
|
+
`
|
|
1846
|
+
),
|
|
1847
|
+
side: opt.side ?? "none",
|
|
1848
|
+
topology: opt.topology ?? "triangle-list",
|
|
1849
|
+
uniforms: {
|
|
1850
|
+
cubeTexture: { value: opt.cubeTexture },
|
|
1851
|
+
results: { value: opt.results ?? new Uint32Array([0, 0, 0]), write: true, atomic: true }
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
static packUint(v) {
|
|
1856
|
+
const vec4 = new Vector4();
|
|
1857
|
+
vec4.set(
|
|
1858
|
+
v & 255,
|
|
1859
|
+
v >> 8 & 255,
|
|
1860
|
+
v >> 16 & 255,
|
|
1861
|
+
v >> 24 & 255
|
|
1862
|
+
);
|
|
1863
|
+
return vec4.divideScalar(255);
|
|
1864
|
+
}
|
|
1865
|
+
static unpackUint(r, g, b, a) {
|
|
1866
|
+
return (r | g << 8 | b << 16 | a << 24) >>> 0;
|
|
1867
|
+
}
|
|
1868
|
+
static unpackUintByUint8(buff, i) {
|
|
1869
|
+
const r = buff[i * 4], g = buff[i * 4 + 1], b = buff[i * 4 + 2], a = buff[i * 4 + 3];
|
|
1870
|
+
if ((r | g | b) === 0 && a === 255) return null;
|
|
1871
|
+
return this.unpackUint(r, g, b, a);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
const directions = [
|
|
1875
|
+
new THREE.Vector3(-1, 0, 0),
|
|
1876
|
+
// -X
|
|
1877
|
+
new THREE.Vector3(1, 0, 0),
|
|
1878
|
+
// +X
|
|
1879
|
+
new THREE.Vector3(0, 1, 0),
|
|
1880
|
+
// +Y
|
|
1881
|
+
new THREE.Vector3(0, -1, 0),
|
|
1882
|
+
// -Y
|
|
1883
|
+
new THREE.Vector3(0, 0, 1),
|
|
1884
|
+
// +Z
|
|
1885
|
+
new THREE.Vector3(0, 0, -1)
|
|
1886
|
+
// -Z
|
|
1887
|
+
];
|
|
1888
|
+
const ups = [
|
|
1889
|
+
new THREE.Vector3(0, -1, 0),
|
|
1890
|
+
// -X
|
|
1891
|
+
new THREE.Vector3(0, -1, 0),
|
|
1892
|
+
// +X
|
|
1893
|
+
new THREE.Vector3(0, 0, 1),
|
|
1894
|
+
// +Y
|
|
1895
|
+
new THREE.Vector3(0, 0, -1),
|
|
1896
|
+
// -Y
|
|
1897
|
+
new THREE.Vector3(0, -1, 0),
|
|
1898
|
+
// +Z
|
|
1899
|
+
new THREE.Vector3(0, -1, 0)
|
|
1900
|
+
// -Z
|
|
1901
|
+
];
|
|
1902
|
+
class CubeCamera {
|
|
1903
|
+
cameras = [];
|
|
1904
|
+
depthClearValue = 1;
|
|
1905
|
+
constructor(near = 0.1, far = 1e3, depthClearValue = 1) {
|
|
1906
|
+
this.depthClearValue = depthClearValue;
|
|
1907
|
+
for (let i = 0; i < 6; i++) {
|
|
1908
|
+
const cam = new PerspectiveCamera(-90, 1, near, far);
|
|
1909
|
+
cam.up.copy(ups[i]);
|
|
1910
|
+
cam.lookAt(directions[i]);
|
|
1911
|
+
this.cameras.push(cam);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
setPosition(x, y, z) {
|
|
1915
|
+
for (let i = 0; i < 6; i++) {
|
|
1916
|
+
const cam = this.cameras[i];
|
|
1917
|
+
cam.position.set(x, y, z);
|
|
1918
|
+
cam.lookAt(
|
|
1919
|
+
x + directions[i].x,
|
|
1920
|
+
y + directions[i].y,
|
|
1921
|
+
z + directions[i].z
|
|
1922
|
+
);
|
|
1923
|
+
cam.updateMatrixWorld();
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
updateDirections() {
|
|
1927
|
+
for (let i = 0; i < 6; i++) {
|
|
1928
|
+
const cam = this.cameras[i];
|
|
1929
|
+
cam.up.copy(ups[i]);
|
|
1930
|
+
cam.lookAt(
|
|
1931
|
+
cam.position.x + directions[i].x,
|
|
1932
|
+
cam.position.y + directions[i].y,
|
|
1933
|
+
cam.position.z + directions[i].z
|
|
1934
|
+
);
|
|
1935
|
+
cam.updateMatrixWorld();
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
render(meshList, renderer, cubeTexture) {
|
|
1939
|
+
for (let i = 0; i < 6; i++) {
|
|
1940
|
+
const camera = this.cameras[i];
|
|
1941
|
+
const view = cubeTexture.getFaceView(i);
|
|
1942
|
+
renderer.renderTarget = view;
|
|
1943
|
+
renderer.depthTarget = cubeTexture.depthView;
|
|
1944
|
+
renderer.render(meshList, camera, this.depthClearValue);
|
|
1945
|
+
}
|
|
1946
|
+
renderer.renderTarget = null;
|
|
1947
|
+
renderer.depthTarget = null;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
class Renderer {
|
|
1951
|
+
canvas;
|
|
1952
|
+
context;
|
|
1953
|
+
depthTexture;
|
|
1954
|
+
depthTextureView;
|
|
1955
|
+
format;
|
|
1956
|
+
renderTarget = null;
|
|
1957
|
+
depthTarget = null;
|
|
1958
|
+
constructor(width = 800, height = 800, format = navigator.gpu.getPreferredCanvasFormat()) {
|
|
1959
|
+
const device2 = getDevice();
|
|
1960
|
+
this.format = format;
|
|
1961
|
+
if (typeof window !== "undefined") {
|
|
1962
|
+
this.canvas = document.createElement("canvas");
|
|
1963
|
+
this.canvas.width = width;
|
|
1964
|
+
this.canvas.height = height;
|
|
1965
|
+
this.context = this.canvas.getContext("webgpu");
|
|
1966
|
+
this.context.configure({
|
|
1967
|
+
device: device2,
|
|
1968
|
+
format,
|
|
1969
|
+
alphaMode: "opaque"
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
this.depthTexture = device2.createTexture({
|
|
1973
|
+
size: [width, height],
|
|
1974
|
+
format: "depth24plus",
|
|
1975
|
+
label: "cubeDepthDepthTexture",
|
|
1976
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
1977
|
+
});
|
|
1978
|
+
this.depthTextureView = this.depthTexture.createView();
|
|
1979
|
+
}
|
|
1980
|
+
render(meshList, camera, depthClearValue = 1) {
|
|
1981
|
+
const device2 = getDevice();
|
|
1982
|
+
camera.update();
|
|
1983
|
+
const encoder = device2.createCommandEncoder();
|
|
1984
|
+
const view = this.renderTarget ?? this.context?.getCurrentTexture().createView();
|
|
1985
|
+
if (!view) return;
|
|
1986
|
+
const pass = encoder.beginRenderPass({
|
|
1987
|
+
colorAttachments: [{
|
|
1988
|
+
view,
|
|
1989
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
1990
|
+
loadOp: "clear",
|
|
1991
|
+
storeOp: "store"
|
|
1992
|
+
}],
|
|
1993
|
+
depthStencilAttachment: {
|
|
1994
|
+
view: this.depthTarget ?? this.depthTextureView,
|
|
1995
|
+
depthClearValue,
|
|
1996
|
+
depthLoadOp: "clear",
|
|
1997
|
+
depthStoreOp: "store"
|
|
1998
|
+
}
|
|
1999
|
+
});
|
|
2000
|
+
pass.setBindGroup(0, camera.bindGroup);
|
|
2001
|
+
meshList.forEach((mesh) => {
|
|
2002
|
+
mesh.update(this.format);
|
|
2003
|
+
pass.setPipeline(mesh.pipeline);
|
|
2004
|
+
pass.setBindGroup(1, mesh.bindGroup);
|
|
2005
|
+
pass.setBindGroup(2, mesh.material.bindGroup);
|
|
2006
|
+
let index = 0;
|
|
2007
|
+
mesh.geometry.gpuAttributes.forEach((v) => {
|
|
2008
|
+
pass.setVertexBuffer(index, v.buffer);
|
|
2009
|
+
index++;
|
|
2010
|
+
});
|
|
2011
|
+
if (mesh.geometry.indexBuffer) {
|
|
2012
|
+
pass.setIndexBuffer(mesh.geometry.indexBuffer, mesh.geometry.indexFormat);
|
|
2013
|
+
pass.drawIndexed(mesh.geometry.index.count);
|
|
2014
|
+
} else {
|
|
2015
|
+
pass.draw(mesh.geometry.count);
|
|
2016
|
+
}
|
|
2017
|
+
});
|
|
2018
|
+
pass.end();
|
|
2019
|
+
device2.queue.submit([encoder.finish()]);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
class PCD {
|
|
2023
|
+
header;
|
|
2024
|
+
positions;
|
|
2025
|
+
intensity;
|
|
2026
|
+
rgb;
|
|
2027
|
+
static async load(url) {
|
|
2028
|
+
const buffer = await readFile(url);
|
|
2029
|
+
const pcd = new PCD();
|
|
2030
|
+
pcd.parse(buffer);
|
|
2031
|
+
return pcd;
|
|
2032
|
+
}
|
|
2033
|
+
parse(buffer) {
|
|
2034
|
+
this.header = this.parseHeader(buffer);
|
|
2035
|
+
if (this.header.data === "binary") {
|
|
2036
|
+
this.parseBinary(buffer);
|
|
2037
|
+
} else if (this.header.data === "ascii") {
|
|
2038
|
+
this.parseASCII(buffer);
|
|
2039
|
+
} else {
|
|
2040
|
+
throw new Error("binary_compressed 暂未支持");
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
parseHeader(buffer) {
|
|
2044
|
+
const text = new TextDecoder().decode(buffer.slice(0, 5e3));
|
|
2045
|
+
const lines = text.split("\n");
|
|
2046
|
+
const header = {
|
|
2047
|
+
fields: [],
|
|
2048
|
+
size: [],
|
|
2049
|
+
type: [],
|
|
2050
|
+
count: [],
|
|
2051
|
+
width: 0,
|
|
2052
|
+
height: 1,
|
|
2053
|
+
points: 0,
|
|
2054
|
+
data: "ascii",
|
|
2055
|
+
headerLen: 0
|
|
2056
|
+
};
|
|
2057
|
+
let offset = 0;
|
|
2058
|
+
for (const line of lines) {
|
|
2059
|
+
offset += line.length + 1;
|
|
2060
|
+
const parts = line.trim().split(/\s+/);
|
|
2061
|
+
const key = parts[0];
|
|
2062
|
+
if (key === "FIELDS") header.fields = parts.slice(1);
|
|
2063
|
+
else if (key === "SIZE") header.size = parts.slice(1).map(Number);
|
|
2064
|
+
else if (key === "TYPE") header.type = parts.slice(1);
|
|
2065
|
+
else if (key === "COUNT") header.count = parts.slice(1).map(Number);
|
|
2066
|
+
else if (key === "WIDTH") header.width = Number(parts[1]);
|
|
2067
|
+
else if (key === "HEIGHT") header.height = Number(parts[1]);
|
|
2068
|
+
else if (key === "POINTS") header.points = Number(parts[1]);
|
|
2069
|
+
else if (key === "DATA") {
|
|
2070
|
+
header.data = parts[1];
|
|
2071
|
+
header.headerLen = offset;
|
|
2072
|
+
break;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
if (!header.points) {
|
|
2076
|
+
header.points = header.width * header.height;
|
|
2077
|
+
}
|
|
2078
|
+
return header;
|
|
2079
|
+
}
|
|
2080
|
+
parseBinary(buffer) {
|
|
2081
|
+
const { header } = this;
|
|
2082
|
+
const body = buffer.slice(header.headerLen);
|
|
2083
|
+
const dv = new DataView(body);
|
|
2084
|
+
const stride = header.size.reduce((a, b) => a + b, 0);
|
|
2085
|
+
const points = header.points;
|
|
2086
|
+
const fieldOffset = {};
|
|
2087
|
+
let offset = 0;
|
|
2088
|
+
for (let i = 0; i < header.fields.length; i++) {
|
|
2089
|
+
fieldOffset[header.fields[i]] = offset;
|
|
2090
|
+
offset += header.size[i];
|
|
2091
|
+
}
|
|
2092
|
+
this.positions = new Float32Array(points * 3);
|
|
2093
|
+
if (fieldOffset.intensity !== void 0) {
|
|
2094
|
+
this.intensity = new Float32Array(points);
|
|
2095
|
+
}
|
|
2096
|
+
if (fieldOffset.rgb !== void 0) {
|
|
2097
|
+
this.rgb = new Uint8Array(points * 3);
|
|
2098
|
+
}
|
|
2099
|
+
let pIndex = 0;
|
|
2100
|
+
let iIndex = 0;
|
|
2101
|
+
let cIndex = 0;
|
|
2102
|
+
for (let i = 0; i < points; i++) {
|
|
2103
|
+
const base = i * stride;
|
|
2104
|
+
const x = dv.getFloat32(base + fieldOffset.x, true);
|
|
2105
|
+
const y = dv.getFloat32(base + fieldOffset.y, true);
|
|
2106
|
+
const z = dv.getFloat32(base + fieldOffset.z, true);
|
|
2107
|
+
this.positions[pIndex++] = x;
|
|
2108
|
+
this.positions[pIndex++] = y;
|
|
2109
|
+
this.positions[pIndex++] = z;
|
|
2110
|
+
if (this.intensity) {
|
|
2111
|
+
this.intensity[iIndex++] = dv.getFloat32(
|
|
2112
|
+
base + fieldOffset.intensity,
|
|
2113
|
+
true
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
if (this.rgb) {
|
|
2117
|
+
const rgb = dv.getUint32(base + fieldOffset.rgb, true);
|
|
2118
|
+
this.rgb[cIndex++] = rgb >> 16 & 255;
|
|
2119
|
+
this.rgb[cIndex++] = rgb >> 8 & 255;
|
|
2120
|
+
this.rgb[cIndex++] = rgb & 255;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
parseASCII(buffer) {
|
|
2125
|
+
const { header } = this;
|
|
2126
|
+
const text = new TextDecoder().decode(buffer.slice(header.headerLen));
|
|
2127
|
+
const lines = text.trim().split("\n");
|
|
2128
|
+
const points = lines.length;
|
|
2129
|
+
this.positions = new Float32Array(points * 3);
|
|
2130
|
+
let pIndex = 0;
|
|
2131
|
+
for (let i = 0; i < points; i++) {
|
|
2132
|
+
const parts = lines[i].trim().split(/\s+/);
|
|
2133
|
+
this.positions[pIndex++] = parseFloat(parts[0]);
|
|
2134
|
+
this.positions[pIndex++] = parseFloat(parts[1]);
|
|
2135
|
+
this.positions[pIndex++] = parseFloat(parts[2]);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
const IS_BROWSER = typeof window !== "undefined";
|
|
2140
|
+
async function readFile(path) {
|
|
2141
|
+
if (IS_BROWSER) {
|
|
2142
|
+
const data = await fetch(path).then((res) => res.arrayBuffer());
|
|
2143
|
+
return data;
|
|
2144
|
+
}
|
|
2145
|
+
const fs = await include("fs");
|
|
2146
|
+
const buf = fs.readFileSync(path);
|
|
2147
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
2148
|
+
}
|
|
2149
|
+
async function loadJson(url) {
|
|
2150
|
+
const arrayBuffer = await readFile(url);
|
|
2151
|
+
const decoder = new TextDecoder("utf-8");
|
|
2152
|
+
const str = decoder.decode(arrayBuffer);
|
|
2153
|
+
return JSON.parse(str);
|
|
2154
|
+
}
|
|
2155
|
+
async function loadPCDCustom(path) {
|
|
2156
|
+
const data = await PCD.load(path);
|
|
2157
|
+
return data.positions;
|
|
2158
|
+
}
|
|
2159
|
+
async function loadTrajectory(base) {
|
|
2160
|
+
return await loadJson(base + "/data/trajectory.json");
|
|
2161
|
+
}
|
|
2162
|
+
async function loadLines(base) {
|
|
2163
|
+
return await loadJson(base + "/data/originSplitLines.json");
|
|
2164
|
+
}
|
|
2165
|
+
function linesToBuffer(lines) {
|
|
2166
|
+
const map = /* @__PURE__ */ new Map(), rectArray = [], indexArray = [];
|
|
2167
|
+
let count = 0;
|
|
2168
|
+
const rectIndex = [0, 1, 2, 2, 3, 0];
|
|
2169
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2170
|
+
let num = 0;
|
|
2171
|
+
lines[i].checkResults?.forEach((checkResult, j) => {
|
|
2172
|
+
checkResult?.originalVertices.forEach((rect, k) => {
|
|
2173
|
+
const index = count++;
|
|
2174
|
+
map.set(index, [i, j, k, num++]);
|
|
2175
|
+
rectIndex.forEach((j2) => {
|
|
2176
|
+
const item = rect[j2];
|
|
2177
|
+
rectArray.push(item.x, item.y, item.z);
|
|
2178
|
+
indexArray.push(index);
|
|
2179
|
+
});
|
|
2180
|
+
});
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
return {
|
|
2184
|
+
map,
|
|
2185
|
+
rectArray,
|
|
2186
|
+
indexArray
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
async function trajectoryHandle(baseUrl, trajectoryData) {
|
|
2190
|
+
const keys = Object.keys(trajectoryData);
|
|
2191
|
+
const trajectoryList = new Array(keys.length);
|
|
2192
|
+
const pointClouds = new Array(keys.length);
|
|
2193
|
+
const pointCloudArray = [];
|
|
2194
|
+
await Promise.all(
|
|
2195
|
+
keys.map(async (key, i) => {
|
|
2196
|
+
const item = trajectoryData[key];
|
|
2197
|
+
const float32Array = await loadPCDCustom(baseUrl + "/pcd/" + key + ".pcd");
|
|
2198
|
+
trajectoryList[i] = { x: item.x, y: item.y, z: item.z };
|
|
2199
|
+
pointClouds[i] = float32Array;
|
|
2200
|
+
pointCloudArray.push(...float32Array);
|
|
2201
|
+
})
|
|
2202
|
+
);
|
|
2203
|
+
return {
|
|
2204
|
+
pointClouds,
|
|
2205
|
+
trajectoryList,
|
|
2206
|
+
pointCloudArray
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
async function loadData(base) {
|
|
2210
|
+
const t = performance.now();
|
|
2211
|
+
const trajectory = await loadTrajectory(base), { pointClouds, pointCloudArray, trajectoryList } = await trajectoryHandle(base, trajectory), linesJson = await loadLines(base), { map, rectArray, indexArray } = linesToBuffer(linesJson);
|
|
2212
|
+
console.log("加载耗时", performance.now() - t);
|
|
2213
|
+
return {
|
|
2214
|
+
pointClouds,
|
|
2215
|
+
pointCloudArray,
|
|
2216
|
+
trajectoryList,
|
|
2217
|
+
map,
|
|
2218
|
+
rectArray,
|
|
2219
|
+
indexArray,
|
|
2220
|
+
linesJson
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
async function obstacleDetection_(opt) {
|
|
2224
|
+
const {
|
|
2225
|
+
base,
|
|
2226
|
+
size = 512,
|
|
2227
|
+
near = 0.01,
|
|
2228
|
+
far = 100,
|
|
2229
|
+
canvasWidth = 1400,
|
|
2230
|
+
canvasHeight = 800,
|
|
2231
|
+
fov = 75
|
|
2232
|
+
} = opt;
|
|
2233
|
+
let t = performance.now();
|
|
2234
|
+
const data = await loadData(base), { indexArray, rectArray, trajectoryList, pointClouds, map } = data, renderer = new Renderer(canvasWidth, canvasHeight, "rgba8unorm"), camera = new PerspectiveCamera(fov, canvasWidth / canvasHeight, 1e-3, 100);
|
|
2235
|
+
console.log("数据加载耗时", performance.now() - t);
|
|
2236
|
+
t = performance.now();
|
|
2237
|
+
camera.up = new THREE.Vector3(0, 0, 1);
|
|
2238
|
+
camera.position.set(0, 0, 5);
|
|
2239
|
+
camera.lookAt(new THREE.Vector3(0, 0, 0));
|
|
2240
|
+
const depthCubeTexture = new CubeTexture(size, size, { needDepthTexture: true, format: "rgba8unorm", sampler: { minFilter: "nearest", magFilter: "nearest", mipmapFilter: "nearest" } }), resultCubeTexture = new CubeTexture(size, size, { needDepthTexture: true, format: "rgba8unorm", sampler: { minFilter: "nearest", magFilter: "nearest", mipmapFilter: "nearest" } }), cubeCamera = new CubeCamera(near, far), indexCount = indexArray.length / 6, quadObject3D = new Object3D(
|
|
2241
|
+
new BufferGeometry().setAttribute("position", new THREE.BufferAttribute(new Float32Array(rectArray), 3)).setAttribute("id", new THREE.Uint32BufferAttribute(new Uint32Array(indexArray), 1)),
|
|
2242
|
+
new OcclusionMaterial({
|
|
2243
|
+
cubeTexture: depthCubeTexture,
|
|
2244
|
+
results: new Uint32Array(indexCount)
|
|
2245
|
+
// indexCount 个 uint8
|
|
2246
|
+
})
|
|
2247
|
+
), pointsObject3D = new Object3D(
|
|
2248
|
+
new BufferGeometry().setAttribute("position", new THREE.BufferAttribute(new Float32Array(), 3)),
|
|
2249
|
+
new DepthMaterial({ topology: "point-list" })
|
|
2250
|
+
);
|
|
2251
|
+
for (let i = 0; i < trajectoryList.length; i++) {
|
|
2252
|
+
const trajectory = trajectoryList[i];
|
|
2253
|
+
pointsObject3D.geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(pointClouds[i]), 3));
|
|
2254
|
+
cubeCamera.setPosition(trajectory.x, trajectory.y, trajectory.z);
|
|
2255
|
+
cubeCamera.depthClearValue = 0;
|
|
2256
|
+
cubeCamera.render([pointsObject3D], renderer, depthCubeTexture);
|
|
2257
|
+
cubeCamera.depthClearValue = 1;
|
|
2258
|
+
cubeCamera.render([quadObject3D], renderer, resultCubeTexture);
|
|
2259
|
+
}
|
|
2260
|
+
const buffer = quadObject3D.material.getStorageBuffer("results");
|
|
2261
|
+
const resultTextureBuff = await readGPUBuffer(buffer, indexCount * 4, Uint32Array);
|
|
2262
|
+
const result = [];
|
|
2263
|
+
resultTextureBuff.forEach((v, index) => {
|
|
2264
|
+
if (v === 0) {
|
|
2265
|
+
const [i, j, k, l] = map.get(index);
|
|
2266
|
+
result.push(i, j, k, l);
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2269
|
+
console.log("渲染耗时", performance.now() - t);
|
|
2270
|
+
console.log("轨迹点数量", trajectoryList.length, "长方形数量", indexArray.length / 6);
|
|
2271
|
+
return {
|
|
2272
|
+
renderer,
|
|
2273
|
+
camera,
|
|
2274
|
+
data,
|
|
2275
|
+
depthCubeTexture,
|
|
2276
|
+
resultCubeTexture,
|
|
2277
|
+
cubeCamera,
|
|
2278
|
+
quadObject3D,
|
|
2279
|
+
pointsObject3D,
|
|
2280
|
+
indexCount,
|
|
2281
|
+
resultTextureBuff,
|
|
2282
|
+
result
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
const obstacleDetection = Object.assign(obstacleDetection_, {
|
|
2286
|
+
async getResult(base) {
|
|
2287
|
+
await initWebgpu();
|
|
2288
|
+
const { result } = await obstacleDetection_({ base });
|
|
2289
|
+
await destroyWebgpu();
|
|
2290
|
+
return result;
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
export {
|
|
2294
|
+
AtomicUint32Array,
|
|
2295
|
+
BaseMaterial,
|
|
2296
|
+
BufferGeometry,
|
|
2297
|
+
CubeCamera,
|
|
2298
|
+
CubeTexture,
|
|
2299
|
+
DepthMaterial,
|
|
2300
|
+
GpuComputed,
|
|
2301
|
+
Object3D,
|
|
2302
|
+
OcclusionMaterial,
|
|
2303
|
+
PerspectiveCamera,
|
|
2304
|
+
Renderer,
|
|
2305
|
+
ShaderMaterial,
|
|
2306
|
+
Texture,
|
|
2307
|
+
createBuffer,
|
|
2308
|
+
createComputePipeline,
|
|
2309
|
+
createRenderPipeline,
|
|
2310
|
+
createTexture,
|
|
2311
|
+
destroyWebgpu,
|
|
2312
|
+
getAdapter,
|
|
2313
|
+
getDevice,
|
|
2314
|
+
getSampler,
|
|
2315
|
+
initWebgpu,
|
|
2316
|
+
loadData,
|
|
2317
|
+
obstacleDetection,
|
|
2318
|
+
readCubeTexture,
|
|
2319
|
+
readGPUBuffer,
|
|
2320
|
+
readTextureBuffer,
|
|
2321
|
+
shaderLocationMap,
|
|
2322
|
+
transparentOption
|
|
2323
|
+
};
|