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/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, // @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 {
206
- config?: GPUCanvasConfiguration;
207
- }
208
- ```
209
-
210
- ### renderer.addPass(passOptions)
211
-
212
- 添加渲染通道。
213
-
214
- ```typescript
215
- interface RenderPassOptions {
216
- name: string;
217
- shaderCode: string;
218
- entryPoints?: {
219
- vertex?: string; // 默认是 'vs_main' 函数
220
- fragment?: string; // 默认 'fs_main' 函数
221
- };
222
- clearColor?: { r: number; g: number; b: number; a: number };
223
- blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
224
- resources?: GPUBindingResource[];
225
- bindGroupSets?: { [setName: string]: GPUBindingResource[] }; // 可选的设置多个绑定组,用于动态切换
226
- renderToCanvas?: boolean; // 可选的将当前通道输出到canvas,默认是false,最后一个通道始终是true
227
- view?: GPUTextureView; // 可选的自定义View,renderToCanvas为true时无效。
228
- format?: GPUTextureFormat; // 可选的自定义格式(使用自定义View时需要指定格式一致)
229
- }
230
- ```
231
-
232
- ### renderer.getPassTexture(passName)
233
-
234
- 获取指定通道的输出纹理,返回值并不是真正的纹理,而是一个占位符,只在实际渲染时自动将输出纹理绑定到着色器。
235
-
236
- ```typescript
237
- // 获取my_pass通道的输出纹理
238
- const passOutputTexture = renderer.getPassTexture('my_pass')
239
- const sampler = renderer.createSampler()
240
- renderer.addPass({
241
- name: 'my_pass2',
242
- shaderCode: wgslShaderCode,
243
- resources: [
244
- passOutputTexture,
245
- sampler,
246
- ],
247
- })
248
- ```
249
-
250
- **对应的WGSL绑定:**
251
-
252
- ```wgsl
253
- @group(0) @binding(0) var myTexture: texture_2d<f32>; // resources[0]
254
- @group(0) @binding(1) var mySampler: sampler; // resources[1]
255
- ```
256
-
257
-
258
-
259
-
260
-
261
- ### renderer.createUniforms(length)
262
-
263
- 创建uniform变量,使用Float32Array,length单位是float的数量。
264
-
265
- ```typescript
266
- const myUniforms = renderer.createUniforms(8) // 8个float
267
-
268
- // 绑定到着色器
269
- renderer.addPass({
270
- name: 'my_pass',
271
- shaderCode: wgslShaderCode,
272
- resources: [
273
- myUniforms.getBuffer(), // group(0) binding(0) var<uniform>
274
- ],
275
- })
276
-
277
- myUniforms.values[0] = 1.0 // 设置值
278
- myUniforms.apply() // 应用到GPU
279
- ```
280
-
281
- ### renderer.getContext()
282
-
283
- 获取WebGPU画布上下文。
284
-
285
- ```typescript
286
- const context = renderer.getContext()
287
- ```
288
-
289
- ### renderer.getDevice()
290
-
291
- 获取WebGPU设备对象。
292
-
293
- ```typescript
294
- const device = renderer.getDevice()
295
- ```
296
-
297
- ### 渲染控制
298
-
299
- #### renderer.renderFrame()
300
- 单帧渲染。
301
-
302
- #### renderer.loopRender(callback?)
303
- 内置的循环渲染,支持每帧回调,可用于实时更新uniforms。
304
-
305
- ```typescript
306
- renderer.loopRender((time) => {
307
-
308
- // 每帧更新uniforms
309
- myUniforms.values[2] = time * 0.001
310
- myUniforms.apply()
311
- })
312
- ```
313
-
314
- #### renderer.stopLoop()
315
- 停止循环渲染。
316
-
317
- ### 切换绑定组
318
-
319
- 渲染器支持在运行时在不同的绑定组之间切换,用于:
320
-
321
- - 切换不同的纹理
322
- - 动态修改着色器参数
323
- - 实现多材质渲染
324
-
325
- #### renderer.switchBindGroupSet(passName, setName)
326
-
327
- 给指定的通道切换绑定组
328
-
329
- ```typescript
330
- // 给渲染通道添加多个绑定组
331
- renderer.addPass({
332
- name: 'main',
333
- shaderCode: myShader,
334
- resources: [uniforms, sampler, texture1], // Default resources
335
- bindGroupSets: {
336
- 'material1': [uniforms, sampler, texture1],
337
- 'material2': [uniforms, sampler, texture2],
338
- 'material3': [uniforms, sampler, texture3],
339
- }
340
- });
341
-
342
- // Switch between materials
343
- renderer.switchBindGroupSet('main', 'material1');
344
- renderer.switchBindGroupSet('main', 'material2');
345
- renderer.switchBindGroupSet('main', 'material3');
346
- ```
347
-
348
- **示例:动态切换纹理**
349
-
350
-
351
-
352
-
353
-
354
- ```typescript
355
- // 创建多个纹理
356
- const textures = [
357
- await renderer.loadImageTexture('texture1.png'),
358
- await renderer.loadImageTexture('texture2.png'),
359
- await renderer.loadImageTexture('texture3.png'),
360
- ];
361
-
362
- // 给渲染通道设置多个绑定
363
- renderer.addPass({
364
- name: 'renderer',
365
- shaderCode: textureShader,
366
- resources: [uniforms, sampler, textures[0]], // Default
367
- bindGroupSets: {
368
- 'texture0': [uniforms, sampler, textures[0]],
369
- 'texture1': [uniforms, sampler, textures[1]],
370
- 'texture2': [uniforms, sampler, textures[2]],
371
- }
372
- });
373
-
374
- // 用户控制
375
- document.getElementById('btn1').onclick = () => {
376
- renderer.switchBindGroupSet('renderer', 'texture0');
377
- };
378
- document.getElementById('btn2').onclick = () => {
379
- renderer.switchBindGroupSet('renderer', 'texture1');
380
- };
381
- document.getElementById('btn3').onclick = () => {
382
- renderer.switchBindGroupSet('renderer', 'texture2');
383
- };
384
- ```
385
-
386
- #### renderer.updateBindGroupSetResources(passName, setName, resources)
387
-
388
- 动态增、改设置的绑定组。这可以让你在运行时修改绑定组。
389
-
390
- ```typescript
391
- // 添加新纹理到已有绑定组(假设textureSet绑定组已经添加到了main通道)
392
- const newTexture = renderer.createTexture({ /* options */ });
393
- renderer.updateBindGroupSetResources('main', 'textureSet', [
394
- uniforms,
395
- sampler,
396
- newTexture,
397
- ]);
398
-
399
- // 即时创建一个新的绑定组
400
- renderer.updateBindGroupSetResources('main', 'newSet', [
401
- newUniforms,
402
- newSampler,
403
- anotherTexture,
404
- ]);
405
- renderer.switchBindGroupSet('main', 'newSet');
406
- ```
407
-
408
- 可用于:
409
-
410
- - 实时流式传输纹理
411
- - 动态更新着色器参数
412
- - 运行时创建编程式内容
413
- - 高效内存资源管理
414
-
415
- ### 渲染通道管理
416
-
417
- 渲染器提供了灵活的渲染通道管理功能,你可以动态的开启、关闭、移除渲染通道。
418
-
419
- #### renderer.enablePass(passName)
420
-
421
- 开启渲染通道。
422
-
423
- ```typescript
424
- renderer.enablePass('background-effect');
425
- ```
426
-
427
- #### renderer.disablePass(passName)
428
-
429
- 禁用渲染通道(在渲染过程中将跳过该通道的渲染)。
430
-
431
- ```typescript
432
- renderer.disablePass('post-process');
433
- ```
434
-
435
- #### renderer.isPassEnabled(passName)
436
-
437
- 检查通道当前是否开启。
438
-
439
- ```typescript
440
- if (renderer.isPassEnabled('main-effect')) {
441
- console.log('Main effect is active');
442
- }
443
- ```
444
-
445
-
446
-
447
- #### renderer.removePass(passName)
448
-
449
- 从渲染管线中永久删除渲染通道。
450
-
451
- ```typescript
452
- const removed = renderer.removePass('debug-pass');
453
- if (removed) {
454
- console.log('Pass successfully removed');
455
- }
456
- ```
457
-
458
- #### renderer.getEnabledPasses()
459
-
460
- 获取所有开启的渲染通道。
461
-
462
- ```typescript
463
- const activePasses = renderer.getEnabledPasses();
464
- console.log(`Active passes: ${activePasses.length}`);
465
- ```
466
-
467
-
468
-
469
- #### renderer.getAllPasses()
470
-
471
- 获取所有渲染通道(包括开启和禁用的)。
472
-
473
- ```typescript
474
- const allPasses = renderer.getAllPasses();
475
- allPasses.forEach(pass => {
476
- console.log(`Pass: ${pass.name}, Enabled: ${pass.enabled}`);
477
- });
478
- ```
479
-
480
- #### 通道管理示例
481
-
482
- **开发调试**
483
-
484
- ```typescript
485
- // Isolate a specific pass for debugging
486
- renderer.disablePass('post-process');
487
- renderer.disablePass('effects');
488
- // Only background will render
489
-
490
- // Re-enable all passes
491
- const allPasses = renderer.getAllPasses();
492
- allPasses.forEach(pass => renderer.enablePass(pass.name));
493
- ```
494
-
495
- **性能优化**
496
-
497
- ```typescript
498
- // Disable expensive effects on low-end devices
499
- if (isLowEndDevice) {
500
- renderer.disablePass('bloom');
501
- renderer.disablePass('ssao');
502
- }
503
- ```
504
-
505
- **动态功能切换**
506
-
507
- ```typescript
508
- // UI controls for enabling/disabling effects
509
- document.getElementById('toggle-bloom').onclick = () => {
510
- if (renderer.isPassEnabled('bloom')) {
511
- renderer.disablePass('bloom');
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
- mainPass.updateBindGroup([renderer.getPassTexture('layer1'), newSampler])
592
- }
593
- ```
594
-
595
- **错误处理示例:**
596
- ```typescript
597
- // 如果引用不存在的pass,会在渲染时抛出详细错误
598
- const invalidTexture = renderer.getPassTexture('nonexistent_pass') // 这个pass不存在
599
- renderer.addPass({
600
- name: 'test',
601
- resources: [invalidTexture, sampler] // 渲染时会抛出错误
602
- })
603
- // 错误信息: Cannot find pass named 'nonexistent_pass'. Available passes: [background, main_effect, ...]
604
- ```
605
-
606
- ## 🛠️ 开发
607
-
608
- ```bash
609
- # 开发模式
610
- pnpm dev
611
-
612
- # 构建
613
- pnpm build
614
- ```
615
-
616
- ## 📝 许可证
617
-
618
- MIT License
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!