wgsl-renderer 0.4.2 → 0.5.0
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/LICENSE.md +8 -8
- package/README.md +590 -589
- package/README.zh-CN.md +623 -621
- package/dist/cjs/index.js +19 -19
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +14 -14
- package/package.json +9 -10
package/README.zh-CN.md
CHANGED
|
@@ -1,622 +1,624 @@
|
|
|
1
|
-
# WGSL Multi-Pass Renderer
|
|
2
|
-
|
|
3
|
-
一个基于WebGPU和WGSL的多通道渲染器。
|
|
4
|
-
|
|
5
|
-
## ✨ 特性
|
|
6
|
-
|
|
7
|
-
- 🖼️ **多Pass渲染** - 支持纹理渲染、后处理效果等自定义多通道渲染
|
|
8
|
-
- ⚡ **高性能渲染循环** - 支持单帧渲染和循环渲染模式
|
|
9
|
-
- 🛠️ **TypeScript支持** - 完整的类型定义和清晰的API分离
|
|
10
|
-
- 🎮 **Uniform系统** - 内置uniform buffer管理,支持动态参数
|
|
11
|
-
|
|
12
|
-
## 🚀 快速开始
|
|
13
|
-
|
|
14
|
-
### 安装
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npm i wgls-renderer
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
### 添加渲染通道
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import { createWGSLRenderer } from 'wgls-renderer'
|
|
24
|
-
|
|
25
|
-
const canvas = document.querySelector('canvas')
|
|
26
|
-
const renderer = await createWGSLRenderer(canvas)
|
|
27
|
-
|
|
28
|
-
renderer.addPass({
|
|
29
|
-
name: 'my-pass',
|
|
30
|
-
shaderCode: `
|
|
31
|
-
struct VSOut {
|
|
32
|
-
@builtin(position) pos: vec4<f32>,
|
|
33
|
-
@location(0) uv: vec2<f32>,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
@vertex
|
|
37
|
-
fn vs_main(@location(0) p: vec3<f32>) -> VSOut {
|
|
38
|
-
var o: VSOut;
|
|
39
|
-
o.pos = vec4<f32>(p, 1.0);
|
|
40
|
-
o.uv = p.xy * 0.5 + vec2<f32>(0.5, 0.5);
|
|
41
|
-
o.uv.y = 1.0 - o.uv.y;
|
|
42
|
-
return o;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
@fragment
|
|
46
|
-
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
|
47
|
-
return vec4(1.0, 1.0, 0.0, 1.0);
|
|
48
|
-
}`,
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
renderer.renderFrame()
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
### 基础多通道使用
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import { createWGSLRenderer } from 'wgls-renderer'
|
|
62
|
-
|
|
63
|
-
const canvas = document.getElementById('canvas')
|
|
64
|
-
const renderer = await createWGSLRenderer(canvas)
|
|
65
|
-
|
|
66
|
-
// 创建采样器
|
|
67
|
-
const sampler = renderer.createSampler()
|
|
68
|
-
|
|
69
|
-
// 加载图片纹理
|
|
70
|
-
const { texture, width, height } = await renderer.loadImageTexture('image.jpg')
|
|
71
|
-
|
|
72
|
-
// 添加Pass 1: 渲染纹理
|
|
73
|
-
renderer.addPass({
|
|
74
|
-
name: 'texture_pass',
|
|
75
|
-
shaderCode: textureShader,
|
|
76
|
-
resources: [texture, sampler], // binding 0, 1
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
// 添加Pass 2: 后处理效果
|
|
80
|
-
const uniforms = renderer.createUniforms(8) // 创建uniform变量的绑定
|
|
81
|
-
|
|
82
|
-
// 获取Pass 1的输出纹理并绑定到Pass 2
|
|
83
|
-
const texturePassOutput = renderer.getPassTexture('texture_pass')
|
|
84
|
-
renderer.addPass({
|
|
85
|
-
name: 'post_process',
|
|
86
|
-
shaderCode: postProcessShader,
|
|
87
|
-
resources: [
|
|
88
|
-
texturePassOutput,
|
|
89
|
-
sampler,
|
|
90
|
-
uniforms.getBuffer(),
|
|
91
|
-
],
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
// 启动循环渲染,可以在回调函数中更新uniforms
|
|
95
|
-
renderer.loopRender(
|
|
96
|
-
|
|
97
|
-
// 更新uniforms (注意WebGPU的内存对齐规则)
|
|
98
|
-
uniforms.values[0] = canvas.width
|
|
99
|
-
uniforms.values[1] = canvas.height
|
|
100
|
-
uniforms.values[2] = t / 1000
|
|
101
|
-
uniforms.values[3] = 0
|
|
102
|
-
uniforms.values[4] = width
|
|
103
|
-
uniforms.values[5] = height
|
|
104
|
-
uniforms.apply()
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
// 或者手动执行单帧渲染
|
|
108
|
-
renderer.renderFrame()
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
## 🎨 着色器示例
|
|
113
|
-
|
|
114
|
-
### Pass 1: 纹理渲染
|
|
115
|
-
|
|
116
|
-
```wgsl
|
|
117
|
-
// textureShader
|
|
118
|
-
struct VSOut {
|
|
119
|
-
@builtin(position) pos: vec4<f32>,
|
|
120
|
-
@location(0) uv: vec2<f32>,
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
@vertex
|
|
124
|
-
fn vs_main(@location(0) p: vec3<f32>) -> VSOut {
|
|
125
|
-
var o: VSOut;
|
|
126
|
-
o.pos = vec4<f32>(p, 1.0);
|
|
127
|
-
o.uv = p.xy * 0.5 + vec2<f32>(0.5, 0.5);
|
|
128
|
-
o.uv.y = 1.0 - o.uv.y;
|
|
129
|
-
return o;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
@group(0) @binding(0) var myTexture: texture_2d<f32>;
|
|
133
|
-
@group(0) @binding(1) var mySampler: sampler;
|
|
134
|
-
|
|
135
|
-
@fragment
|
|
136
|
-
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
|
137
|
-
return textureSample(myTexture, mySampler, uv);
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Pass 2: 动态后处理效果
|
|
142
|
-
|
|
143
|
-
```wgsl
|
|
144
|
-
// postProcessShader
|
|
145
|
-
struct Uniforms {
|
|
146
|
-
resolution: vec2<f32>, // offset 0-7
|
|
147
|
-
time: f32, // offset 8
|
|
148
|
-
// 4 bytes padding for vec3 alignment
|
|
149
|
-
texResolution: vec2<f32>, // offset 16-23
|
|
150
|
-
speed: f32, // offset 24
|
|
151
|
-
// 8 bytes padding for next vec3
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
struct VSOut {
|
|
155
|
-
@builtin(position) pos: vec4<f32>,
|
|
156
|
-
@location(0) uv: vec2<f32>,
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
@vertex
|
|
160
|
-
fn vs_main(@location(0) p: vec3<f32>) -> VSOut {
|
|
161
|
-
var o: VSOut;
|
|
162
|
-
o.pos = vec4<f32>(p, 1.0);
|
|
163
|
-
o.uv = p.xy * 0.5 + vec2<f32>(0.5, 0.5);
|
|
164
|
-
o.uv.y = 1.0 - o.uv.y;
|
|
165
|
-
return o;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
@group(0) @binding(0) var prevTexture: texture_2d<f32>; // Pass 1输出纹理
|
|
169
|
-
@group(0) @binding(1) var mySampler: sampler;
|
|
170
|
-
@group(0) @binding(2) var<uniform> uniforms: Uniforms;
|
|
171
|
-
|
|
172
|
-
@fragment
|
|
173
|
-
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
|
174
|
-
var color = textureSample(prevTexture, mySampler, uv);
|
|
175
|
-
|
|
176
|
-
// 动态扫描线效果
|
|
177
|
-
let scanline = 0.8 + 0.2 * sin(uv.y * 600.0 + uniforms.time * 5.0);
|
|
178
|
-
color = vec4<f32>(color.r * scanline, color.g * scanline, color.b * scanline, color.a);
|
|
179
|
-
|
|
180
|
-
// 动态波纹效果
|
|
181
|
-
let waveAmplitude = 0.05 + 0.02 * sin(uniforms.time * 2.0);
|
|
182
|
-
let waveX = sin(uv.x * 10.0 + uniforms.time * 3.0) * cos(uv.y * 8.0 + uniforms.time * 2.0) * waveAmplitude;
|
|
183
|
-
|
|
184
|
-
let finalR = clamp(color.r + waveX, 0.0, 1.0);
|
|
185
|
-
let finalG = clamp(color.g - waveX * 0.5, 0.0, 1.0);
|
|
186
|
-
let finalB = clamp(color.b + waveX * 0.3, 0.0, 1.0);
|
|
187
|
-
|
|
188
|
-
return vec4<f32>(finalR, finalG, finalB, color.a);
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## 📋 API
|
|
193
|
-
|
|
194
|
-
### createWGSLRenderer(canvas, options?)
|
|
195
|
-
|
|
196
|
-
创建WGSL渲染器实例。
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
const renderer = await createWGSLRenderer(canvas)
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
options:
|
|
203
|
-
|
|
204
|
-
```typescript
|
|
205
|
-
interface WGSLRendererOptions {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
renderer.switchBindGroupSet('main', '
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
await renderer.loadImageTexture('
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
-
|
|
411
|
-
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
renderer.disablePass('
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
renderer.enablePass('bloom')
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
### renderer.createSampler(options?)
|
|
521
|
-
|
|
522
|
-
创建采样器,默认参数:
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
const options = {
|
|
526
|
-
magFilter: 'linear',
|
|
527
|
-
minFilter: 'linear',
|
|
528
|
-
addressModeU: 'clamp-to-edge',
|
|
529
|
-
addressModeV: 'clamp-to-edge',
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const sampler = renderer.createSampler(options)
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
## 🎯 Pass流程
|
|
536
|
-
|
|
537
|
-
渲染器提供以下管理功能:
|
|
538
|
-
|
|
539
|
-
1. **用户定义所有Pass**
|
|
540
|
-
- 用户完全控制所有资源的绑定
|
|
541
|
-
- 可以通过`getPassTexture(passName)`获取任意pass的输出纹理
|
|
542
|
-
- 可以通过`getPassByName(passName)`获取pass对象
|
|
543
|
-
|
|
544
|
-
2. **纹理管理**
|
|
545
|
-
- 每个pass自动创建输出纹理(格式:`{passName}_output`)
|
|
546
|
-
- 用户可以手动将这些纹理绑定到其他pass
|
|
547
|
-
- 最后一个pass自动渲染到canvas
|
|
548
|
-
|
|
549
|
-
3. **完全灵活性**
|
|
550
|
-
- 用户决定绑定顺序和方式
|
|
551
|
-
- 支持任意复杂的pass连接关系
|
|
552
|
-
- 可以创建循环依赖(如果需要的话)
|
|
553
|
-
|
|
554
|
-
**示例用法:**
|
|
555
|
-
```typescript
|
|
556
|
-
// 方法1: 简单的链式引用
|
|
557
|
-
renderer.addPass({
|
|
558
|
-
name: 'background',
|
|
559
|
-
resources: [bgTexture, sampler1]
|
|
560
|
-
})
|
|
561
|
-
|
|
562
|
-
renderer.addPass({
|
|
563
|
-
name: 'main_effect',
|
|
564
|
-
resources: [renderer.getPassTexture('background'), sampler2]
|
|
565
|
-
})
|
|
566
|
-
|
|
567
|
-
renderer.addPass({
|
|
568
|
-
name: 'post_process',
|
|
569
|
-
resources: [renderer.getPassTexture('main_effect'), sampler3]
|
|
570
|
-
})
|
|
571
|
-
|
|
572
|
-
// 方法2: 复杂的多pass混合
|
|
573
|
-
renderer.addPass({ name: 'layer1', resources: [textureA, sampler] })
|
|
574
|
-
renderer.addPass({ name: 'layer2', resources: [textureB, sampler] })
|
|
575
|
-
renderer.addPass({ name: 'layer3', resources: [textureC, sampler] })
|
|
576
|
-
|
|
577
|
-
// 创建混合pass,同时引用多个不同的pass
|
|
578
|
-
const layer1Output = renderer.getPassTexture('layer1')
|
|
579
|
-
const layer2Output = renderer.getPassTexture('layer2')
|
|
580
|
-
const layer3Output = renderer.getPassTexture('layer3')
|
|
581
|
-
|
|
582
|
-
renderer.addPass({
|
|
583
|
-
name: 'composite',
|
|
584
|
-
resources: [layer1Output, layer2Output, layer3Output, finalSampler]
|
|
585
|
-
})
|
|
586
|
-
|
|
587
|
-
// 方法3: 动态更新绑定
|
|
588
|
-
const mainPass = renderer.getPassByName('main_effect')
|
|
589
|
-
if (mainPass) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
renderer.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
1
|
+
# WGSL Multi-Pass Renderer
|
|
2
|
+
|
|
3
|
+
一个基于WebGPU和WGSL的多通道渲染器。
|
|
4
|
+
|
|
5
|
+
## ✨ 特性
|
|
6
|
+
|
|
7
|
+
- 🖼️ **多Pass渲染** - 支持纹理渲染、后处理效果等自定义多通道渲染
|
|
8
|
+
- ⚡ **高性能渲染循环** - 支持单帧渲染和循环渲染模式
|
|
9
|
+
- 🛠️ **TypeScript支持** - 完整的类型定义和清晰的API分离
|
|
10
|
+
- 🎮 **Uniform系统** - 内置uniform buffer管理,支持动态参数
|
|
11
|
+
|
|
12
|
+
## 🚀 快速开始
|
|
13
|
+
|
|
14
|
+
### 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm i wgls-renderer
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 添加渲染通道
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { createWGSLRenderer } from 'wgls-renderer'
|
|
24
|
+
|
|
25
|
+
const canvas = document.querySelector('canvas')
|
|
26
|
+
const renderer = await createWGSLRenderer(canvas)
|
|
27
|
+
|
|
28
|
+
renderer.addPass({
|
|
29
|
+
name: 'my-pass',
|
|
30
|
+
shaderCode: `
|
|
31
|
+
struct VSOut {
|
|
32
|
+
@builtin(position) pos: vec4<f32>,
|
|
33
|
+
@location(0) uv: vec2<f32>,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
@vertex
|
|
37
|
+
fn vs_main(@location(0) p: vec3<f32>) -> VSOut {
|
|
38
|
+
var o: VSOut;
|
|
39
|
+
o.pos = vec4<f32>(p, 1.0);
|
|
40
|
+
o.uv = p.xy * 0.5 + vec2<f32>(0.5, 0.5);
|
|
41
|
+
o.uv.y = 1.0 - o.uv.y;
|
|
42
|
+
return o;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@fragment
|
|
46
|
+
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
|
47
|
+
return vec4(1.0, 1.0, 0.0, 1.0);
|
|
48
|
+
}`,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
renderer.renderFrame()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### 基础多通道使用
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { createWGSLRenderer } from 'wgls-renderer'
|
|
62
|
+
|
|
63
|
+
const canvas = document.getElementById('canvas')
|
|
64
|
+
const renderer = await createWGSLRenderer(canvas)
|
|
65
|
+
|
|
66
|
+
// 创建采样器
|
|
67
|
+
const sampler = renderer.createSampler()
|
|
68
|
+
|
|
69
|
+
// 加载图片纹理
|
|
70
|
+
const { texture, width, height } = await renderer.loadImageTexture('image.jpg')
|
|
71
|
+
|
|
72
|
+
// 添加Pass 1: 渲染纹理
|
|
73
|
+
renderer.addPass({
|
|
74
|
+
name: 'texture_pass',
|
|
75
|
+
shaderCode: textureShader,
|
|
76
|
+
resources: [texture, sampler], // binding 0, 1
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// 添加Pass 2: 后处理效果
|
|
80
|
+
const uniforms = renderer.createUniforms(8) // 创建uniform变量的绑定
|
|
81
|
+
|
|
82
|
+
// 获取Pass 1的输出纹理并绑定到Pass 2
|
|
83
|
+
const texturePassOutput = renderer.getPassTexture('texture_pass')
|
|
84
|
+
renderer.addPass({
|
|
85
|
+
name: 'post_process',
|
|
86
|
+
shaderCode: postProcessShader,
|
|
87
|
+
resources: [
|
|
88
|
+
texturePassOutput, // @group(0) @binding(0)
|
|
89
|
+
sampler, // @group(0) @binding(1)
|
|
90
|
+
uniforms.getBuffer(), // @group(0) @binding(2)
|
|
91
|
+
],
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// 启动循环渲染,可以在回调函数中更新uniforms
|
|
95
|
+
renderer.loopRender(t => {
|
|
96
|
+
|
|
97
|
+
// 更新uniforms (注意WebGPU的内存对齐规则)
|
|
98
|
+
uniforms.values[0] = canvas.width // resolution.x
|
|
99
|
+
uniforms.values[1] = canvas.height // resolution.y
|
|
100
|
+
uniforms.values[2] = t / 1000 // time
|
|
101
|
+
uniforms.values[3] = 0 // padding (留空)
|
|
102
|
+
uniforms.values[4] = width // textureResolution.x
|
|
103
|
+
uniforms.values[5] = height // textureResolution.y
|
|
104
|
+
uniforms.apply() // 应用到GPU
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// 或者手动执行单帧渲染
|
|
108
|
+
renderer.renderFrame()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## 🎨 着色器示例
|
|
113
|
+
|
|
114
|
+
### Pass 1: 纹理渲染
|
|
115
|
+
|
|
116
|
+
```wgsl
|
|
117
|
+
// textureShader
|
|
118
|
+
struct VSOut {
|
|
119
|
+
@builtin(position) pos: vec4<f32>,
|
|
120
|
+
@location(0) uv: vec2<f32>,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
@vertex
|
|
124
|
+
fn vs_main(@location(0) p: vec3<f32>) -> VSOut {
|
|
125
|
+
var o: VSOut;
|
|
126
|
+
o.pos = vec4<f32>(p, 1.0);
|
|
127
|
+
o.uv = p.xy * 0.5 + vec2<f32>(0.5, 0.5);
|
|
128
|
+
o.uv.y = 1.0 - o.uv.y;
|
|
129
|
+
return o;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@group(0) @binding(0) var myTexture: texture_2d<f32>;
|
|
133
|
+
@group(0) @binding(1) var mySampler: sampler;
|
|
134
|
+
|
|
135
|
+
@fragment
|
|
136
|
+
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
|
137
|
+
return textureSample(myTexture, mySampler, uv);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Pass 2: 动态后处理效果
|
|
142
|
+
|
|
143
|
+
```wgsl
|
|
144
|
+
// postProcessShader
|
|
145
|
+
struct Uniforms {
|
|
146
|
+
resolution: vec2<f32>, // offset 0-7
|
|
147
|
+
time: f32, // offset 8
|
|
148
|
+
// 4 bytes padding for vec3 alignment
|
|
149
|
+
texResolution: vec2<f32>, // offset 16-23
|
|
150
|
+
speed: f32, // offset 24
|
|
151
|
+
// 8 bytes padding for next vec3
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
struct VSOut {
|
|
155
|
+
@builtin(position) pos: vec4<f32>,
|
|
156
|
+
@location(0) uv: vec2<f32>,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
@vertex
|
|
160
|
+
fn vs_main(@location(0) p: vec3<f32>) -> VSOut {
|
|
161
|
+
var o: VSOut;
|
|
162
|
+
o.pos = vec4<f32>(p, 1.0);
|
|
163
|
+
o.uv = p.xy * 0.5 + vec2<f32>(0.5, 0.5);
|
|
164
|
+
o.uv.y = 1.0 - o.uv.y;
|
|
165
|
+
return o;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@group(0) @binding(0) var prevTexture: texture_2d<f32>; // Pass 1输出纹理
|
|
169
|
+
@group(0) @binding(1) var mySampler: sampler;
|
|
170
|
+
@group(0) @binding(2) var<uniform> uniforms: Uniforms;
|
|
171
|
+
|
|
172
|
+
@fragment
|
|
173
|
+
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
|
174
|
+
var color = textureSample(prevTexture, mySampler, uv);
|
|
175
|
+
|
|
176
|
+
// 动态扫描线效果
|
|
177
|
+
let scanline = 0.8 + 0.2 * sin(uv.y * 600.0 + uniforms.time * 5.0);
|
|
178
|
+
color = vec4<f32>(color.r * scanline, color.g * scanline, color.b * scanline, color.a);
|
|
179
|
+
|
|
180
|
+
// 动态波纹效果
|
|
181
|
+
let waveAmplitude = 0.05 + 0.02 * sin(uniforms.time * 2.0);
|
|
182
|
+
let waveX = sin(uv.x * 10.0 + uniforms.time * 3.0) * cos(uv.y * 8.0 + uniforms.time * 2.0) * waveAmplitude;
|
|
183
|
+
|
|
184
|
+
let finalR = clamp(color.r + waveX, 0.0, 1.0);
|
|
185
|
+
let finalG = clamp(color.g - waveX * 0.5, 0.0, 1.0);
|
|
186
|
+
let finalB = clamp(color.b + waveX * 0.3, 0.0, 1.0);
|
|
187
|
+
|
|
188
|
+
return vec4<f32>(finalR, finalG, finalB, color.a);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## 📋 API
|
|
193
|
+
|
|
194
|
+
### createWGSLRenderer(canvas, options?)
|
|
195
|
+
|
|
196
|
+
创建WGSL渲染器实例。
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const renderer = await createWGSLRenderer(canvas)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
options:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
interface WGSLRendererOptions { config?: GPUCanvasConfiguration; }
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### renderer.addPass(passOptions)
|
|
209
|
+
|
|
210
|
+
添加渲染通道。
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
interface RenderPassOptions {
|
|
214
|
+
name: string;
|
|
215
|
+
shaderCode: string;
|
|
216
|
+
entryPoints?: {
|
|
217
|
+
vertex?: string; // 默认是 'vs_main' 函数
|
|
218
|
+
fragment?: string; // 默认 'fs_main' 函数
|
|
219
|
+
};
|
|
220
|
+
clearColor?: { r: number; g: number; b: number; a: number };
|
|
221
|
+
blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
222
|
+
resources?: GPUBindingResource[];
|
|
223
|
+
bindGroupSets?: { [setName: string]: GPUBindingResource[] }; // 可选的设置多个绑定组,用于动态切换
|
|
224
|
+
renderToCanvas?: boolean; // 可选的将当前通道输出到canvas,默认是false,最后一个通道始终是true
|
|
225
|
+
view?: GPUTextureView; // 可选的自定义View,renderToCanvas为true时无效。
|
|
226
|
+
format?: GPUTextureFormat; // 可选的自定义格式(使用自定义View时需要指定格式一致)
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### renderer.getPassTexture(passName)
|
|
231
|
+
|
|
232
|
+
获取指定通道的输出纹理,返回值并不是真正的纹理,而是一个占位符,只在实际渲染时自动将输出纹理绑定到着色器。
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// 获取my_pass通道的输出纹理
|
|
236
|
+
const passOutputTexture = renderer.getPassTexture('my_pass')
|
|
237
|
+
const sampler = renderer.createSampler()
|
|
238
|
+
renderer.addPass({
|
|
239
|
+
name: 'my_pass2',
|
|
240
|
+
shaderCode: wgslShaderCode,
|
|
241
|
+
resources: [
|
|
242
|
+
passOutputTexture,
|
|
243
|
+
sampler,
|
|
244
|
+
],
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**对应的WGSL绑定:**
|
|
249
|
+
|
|
250
|
+
```wgsl
|
|
251
|
+
@group(0) @binding(0) var myTexture: texture_2d<f32>; // resources[0]
|
|
252
|
+
@group(0) @binding(1) var mySampler: sampler; // resources[1]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
### renderer.createUniforms(length)
|
|
260
|
+
|
|
261
|
+
创建uniform变量,使用Float32Array,length单位是float的数量。
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
const myUniforms = renderer.createUniforms(8) // 8个float
|
|
265
|
+
|
|
266
|
+
// 绑定到着色器
|
|
267
|
+
renderer.addPass({
|
|
268
|
+
name: 'my_pass',
|
|
269
|
+
shaderCode: wgslShaderCode,
|
|
270
|
+
resources: [
|
|
271
|
+
myUniforms.getBuffer(), // group(0) binding(0) var<uniform>
|
|
272
|
+
],
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
myUniforms.values[0] = 1.0 // 设置值
|
|
276
|
+
myUniforms.apply() // 应用到GPU
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### renderer.getContext()
|
|
280
|
+
|
|
281
|
+
获取WebGPU画布上下文。
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const context = renderer.getContext()
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### renderer.getDevice()
|
|
288
|
+
|
|
289
|
+
获取WebGPU设备对象。
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
const device = renderer.getDevice()
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### 渲染控制
|
|
296
|
+
|
|
297
|
+
#### renderer.renderFrame()
|
|
298
|
+
单帧渲染。
|
|
299
|
+
|
|
300
|
+
#### renderer.loopRender(callback?)
|
|
301
|
+
内置的循环渲染,支持每帧回调,可用于实时更新uniforms。
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
renderer.loopRender(time => {
|
|
305
|
+
|
|
306
|
+
// 每帧更新uniforms
|
|
307
|
+
myUniforms.values[2] = time * 0.001
|
|
308
|
+
myUniforms.apply()
|
|
309
|
+
})
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
#### renderer.stopLoop()
|
|
313
|
+
停止循环渲染。
|
|
314
|
+
|
|
315
|
+
### 切换绑定组
|
|
316
|
+
|
|
317
|
+
渲染器支持在运行时在不同的绑定组之间切换,用于:
|
|
318
|
+
|
|
319
|
+
- 切换不同的纹理
|
|
320
|
+
- 动态修改着色器参数
|
|
321
|
+
- 实现多材质渲染
|
|
322
|
+
|
|
323
|
+
#### renderer.switchBindGroupSet(passName, setName)
|
|
324
|
+
|
|
325
|
+
给指定的通道切换绑定组
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// 给渲染通道添加多个绑定组
|
|
329
|
+
renderer.addPass({
|
|
330
|
+
name: 'main',
|
|
331
|
+
shaderCode: myShader,
|
|
332
|
+
resources: [uniforms, sampler, texture1], // Default resources
|
|
333
|
+
bindGroupSets: {
|
|
334
|
+
material1: [uniforms, sampler, texture1],
|
|
335
|
+
material2: [uniforms, sampler, texture2],
|
|
336
|
+
material3: [uniforms, sampler, texture3],
|
|
337
|
+
},
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
// Switch between materials
|
|
341
|
+
renderer.switchBindGroupSet('main', 'material1')
|
|
342
|
+
renderer.switchBindGroupSet('main', 'material2')
|
|
343
|
+
renderer.switchBindGroupSet('main', 'material3')
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**示例:动态切换纹理**
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// 创建多个纹理
|
|
354
|
+
const textures = [
|
|
355
|
+
await renderer.loadImageTexture('texture1.png'),
|
|
356
|
+
await renderer.loadImageTexture('texture2.png'),
|
|
357
|
+
await renderer.loadImageTexture('texture3.png'),
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
// 给渲染通道设置多个绑定
|
|
361
|
+
renderer.addPass({
|
|
362
|
+
name: 'renderer',
|
|
363
|
+
shaderCode: textureShader,
|
|
364
|
+
resources: [uniforms, sampler, textures[0]], // Default
|
|
365
|
+
bindGroupSets: {
|
|
366
|
+
texture0: [uniforms, sampler, textures[0]],
|
|
367
|
+
texture1: [uniforms, sampler, textures[1]],
|
|
368
|
+
texture2: [uniforms, sampler, textures[2]],
|
|
369
|
+
},
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
// 用户控制
|
|
373
|
+
document.getElementById('btn1').onclick = () => {
|
|
374
|
+
renderer.switchBindGroupSet('renderer', 'texture0')
|
|
375
|
+
}
|
|
376
|
+
document.getElementById('btn2').onclick = () => {
|
|
377
|
+
renderer.switchBindGroupSet('renderer', 'texture1')
|
|
378
|
+
}
|
|
379
|
+
document.getElementById('btn3').onclick = () => {
|
|
380
|
+
renderer.switchBindGroupSet('renderer', 'texture2')
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### renderer.updateBindGroupSetResources(passName, setName, resources)
|
|
385
|
+
|
|
386
|
+
动态增、改设置的绑定组。这可以让你在运行时修改绑定组。
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// 添加新纹理到已有绑定组(假设textureSet绑定组已经添加到了main通道)
|
|
390
|
+
const newTexture = renderer.createTexture({ /* options */ })
|
|
391
|
+
renderer.updateBindGroupSetResources('main', 'textureSet', [
|
|
392
|
+
uniforms,
|
|
393
|
+
sampler,
|
|
394
|
+
newTexture,
|
|
395
|
+
])
|
|
396
|
+
|
|
397
|
+
// 即时创建一个新的绑定组
|
|
398
|
+
renderer.updateBindGroupSetResources('main', 'newSet', [
|
|
399
|
+
newUniforms,
|
|
400
|
+
newSampler,
|
|
401
|
+
anotherTexture,
|
|
402
|
+
])
|
|
403
|
+
renderer.switchBindGroupSet('main', 'newSet')
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
可用于:
|
|
407
|
+
|
|
408
|
+
- 实时流式传输纹理
|
|
409
|
+
- 动态更新着色器参数
|
|
410
|
+
- 运行时创建编程式内容
|
|
411
|
+
- 高效内存资源管理
|
|
412
|
+
|
|
413
|
+
### 渲染通道管理
|
|
414
|
+
|
|
415
|
+
渲染器提供了灵活的渲染通道管理功能,你可以动态的开启、关闭、移除渲染通道。
|
|
416
|
+
|
|
417
|
+
#### renderer.enablePass(passName)
|
|
418
|
+
|
|
419
|
+
开启渲染通道。
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
renderer.enablePass('background-effect')
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### renderer.disablePass(passName)
|
|
426
|
+
|
|
427
|
+
禁用渲染通道(在渲染过程中将跳过该通道的渲染)。
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
renderer.disablePass('post-process')
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
#### renderer.isPassEnabled(passName)
|
|
434
|
+
|
|
435
|
+
检查通道当前是否开启。
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
if (renderer.isPassEnabled('main-effect')) {
|
|
439
|
+
console.log('Main effect is active')
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
#### renderer.removePass(passName)
|
|
446
|
+
|
|
447
|
+
从渲染管线中永久删除渲染通道。
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
const removed = renderer.removePass('debug-pass')
|
|
451
|
+
if (removed) {
|
|
452
|
+
console.log('Pass successfully removed')
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### renderer.getEnabledPasses()
|
|
457
|
+
|
|
458
|
+
获取所有开启的渲染通道。
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
const activePasses = renderer.getEnabledPasses()
|
|
462
|
+
console.log(`Active passes: ${activePasses.length}`)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
#### renderer.getAllPasses()
|
|
468
|
+
|
|
469
|
+
获取所有渲染通道(包括开启和禁用的)。
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
const allPasses = renderer.getAllPasses()
|
|
473
|
+
allPasses.forEach(pass => {
|
|
474
|
+
console.log(`Pass: ${pass.name}, Enabled: ${pass.enabled}`)
|
|
475
|
+
})
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
#### 通道管理示例
|
|
479
|
+
|
|
480
|
+
**开发调试**
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
// Isolate a specific pass for debugging
|
|
484
|
+
renderer.disablePass('post-process')
|
|
485
|
+
renderer.disablePass('effects')
|
|
486
|
+
|
|
487
|
+
// Only background will render
|
|
488
|
+
|
|
489
|
+
// Re-enable all passes
|
|
490
|
+
const allPasses = renderer.getAllPasses()
|
|
491
|
+
allPasses.forEach(pass => renderer.enablePass(pass.name))
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**性能优化**
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
// Disable expensive effects on low-end devices
|
|
498
|
+
if (isLowEndDevice) {
|
|
499
|
+
renderer.disablePass('bloom')
|
|
500
|
+
renderer.disablePass('ssao')
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**动态功能切换**
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
// UI controls for enabling/disabling effects
|
|
508
|
+
document.getElementById('toggle-bloom').onclick = () => {
|
|
509
|
+
if (renderer.isPassEnabled('bloom')) {
|
|
510
|
+
renderer.disablePass('bloom')
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
renderer.enablePass('bloom')
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
### renderer.createSampler(options?)
|
|
521
|
+
|
|
522
|
+
创建采样器,默认参数:
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
const options = {
|
|
526
|
+
magFilter: 'linear',
|
|
527
|
+
minFilter: 'linear',
|
|
528
|
+
addressModeU: 'clamp-to-edge',
|
|
529
|
+
addressModeV: 'clamp-to-edge',
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const sampler = renderer.createSampler(options)
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## 🎯 Pass流程
|
|
536
|
+
|
|
537
|
+
渲染器提供以下管理功能:
|
|
538
|
+
|
|
539
|
+
1. **用户定义所有Pass**
|
|
540
|
+
- 用户完全控制所有资源的绑定
|
|
541
|
+
- 可以通过`getPassTexture(passName)`获取任意pass的输出纹理
|
|
542
|
+
- 可以通过`getPassByName(passName)`获取pass对象
|
|
543
|
+
|
|
544
|
+
2. **纹理管理**
|
|
545
|
+
- 每个pass自动创建输出纹理(格式:`{passName}_output`)
|
|
546
|
+
- 用户可以手动将这些纹理绑定到其他pass
|
|
547
|
+
- 最后一个pass自动渲染到canvas
|
|
548
|
+
|
|
549
|
+
3. **完全灵活性**
|
|
550
|
+
- 用户决定绑定顺序和方式
|
|
551
|
+
- 支持任意复杂的pass连接关系
|
|
552
|
+
- 可以创建循环依赖(如果需要的话)
|
|
553
|
+
|
|
554
|
+
**示例用法:**
|
|
555
|
+
```typescript
|
|
556
|
+
// 方法1: 简单的链式引用
|
|
557
|
+
renderer.addPass({
|
|
558
|
+
name: 'background',
|
|
559
|
+
resources: [bgTexture, sampler1],
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
renderer.addPass({
|
|
563
|
+
name: 'main_effect',
|
|
564
|
+
resources: [renderer.getPassTexture('background'), sampler2], // 引用background的输出
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
renderer.addPass({
|
|
568
|
+
name: 'post_process',
|
|
569
|
+
resources: [renderer.getPassTexture('main_effect'), sampler3], // 引用main_effect的输出
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
// 方法2: 复杂的多pass混合
|
|
573
|
+
renderer.addPass({ name: 'layer1', resources: [textureA, sampler] })
|
|
574
|
+
renderer.addPass({ name: 'layer2', resources: [textureB, sampler] })
|
|
575
|
+
renderer.addPass({ name: 'layer3', resources: [textureC, sampler] })
|
|
576
|
+
|
|
577
|
+
// 创建混合pass,同时引用多个不同的pass
|
|
578
|
+
const layer1Output = renderer.getPassTexture('layer1')
|
|
579
|
+
const layer2Output = renderer.getPassTexture('layer2')
|
|
580
|
+
const layer3Output = renderer.getPassTexture('layer3')
|
|
581
|
+
|
|
582
|
+
renderer.addPass({
|
|
583
|
+
name: 'composite',
|
|
584
|
+
resources: [layer1Output, layer2Output, layer3Output, finalSampler],
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
// 方法3: 动态更新绑定
|
|
588
|
+
const mainPass = renderer.getPassByName('main_effect')
|
|
589
|
+
if (mainPass) {
|
|
590
|
+
|
|
591
|
+
// 运行时动态改变引用关系
|
|
592
|
+
mainPass.updateBindGroup([renderer.getPassTexture('layer1'), newSampler])
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
**错误处理示例:**
|
|
597
|
+
```typescript
|
|
598
|
+
// 如果引用不存在的pass,会在渲染时抛出详细错误
|
|
599
|
+
const invalidTexture = renderer.getPassTexture('nonexistent_pass') // 这个pass不存在
|
|
600
|
+
renderer.addPass({
|
|
601
|
+
name: 'test',
|
|
602
|
+
resources: [invalidTexture, sampler], // 渲染时会抛出错误
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
// 错误信息: Cannot find pass named 'nonexistent_pass'. Available passes: [background, main_effect, ...]
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## 🛠️ 开发
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
# 开发模式
|
|
612
|
+
pnpm dev
|
|
613
|
+
|
|
614
|
+
# 构建
|
|
615
|
+
pnpm build
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
## 📝 许可证
|
|
619
|
+
|
|
620
|
+
MIT License
|
|
621
|
+
|
|
622
|
+
## 🤝 贡献
|
|
623
|
+
|
|
622
624
|
欢迎提交Issue和Pull Request!
|