wgsl-renderer 0.0.2 → 0.0.3
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.md +236 -228
- package/README.zh-CN.md +417 -0
- package/dist/cjs/index.js +106 -113
- package/dist/esm/index.js +106 -113
- package/dist/types/index.d.ts +79 -15
- package/dist/types/index.js +106 -113
- package/package.json +4 -2
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
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
|
+
```ts
|
|
205
|
+
interface WGSLRendererOptions {
|
|
206
|
+
config?: GPUCanvasConfiguration;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### renderer.addPass(passOptions)
|
|
211
|
+
|
|
212
|
+
添加渲染通道。
|
|
213
|
+
|
|
214
|
+
```ts
|
|
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
|
+
view?: GPUTextureView; // 可选的自定义View
|
|
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
|
+
### renderer.createSampler(options?)
|
|
316
|
+
|
|
317
|
+
创建采样器,默认参数:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
const options = {
|
|
321
|
+
magFilter: 'linear',
|
|
322
|
+
minFilter: 'linear',
|
|
323
|
+
addressModeU: 'clamp-to-edge',
|
|
324
|
+
addressModeV: 'clamp-to-edge',
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const sampler = renderer.createSampler(options)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## 🎯 Pass流程
|
|
331
|
+
|
|
332
|
+
渲染器提供以下管理功能:
|
|
333
|
+
|
|
334
|
+
1. **用户定义所有Pass**
|
|
335
|
+
- 用户完全控制所有资源的绑定
|
|
336
|
+
- 可以通过`getPassTexture(passName)`获取任意pass的输出纹理
|
|
337
|
+
- 可以通过`getPassByName(passName)`获取pass对象
|
|
338
|
+
|
|
339
|
+
2. **纹理管理**
|
|
340
|
+
- 每个pass自动创建输出纹理(格式:`{passName}_output`)
|
|
341
|
+
- 用户可以手动将这些纹理绑定到其他pass
|
|
342
|
+
- 最后一个pass自动渲染到canvas
|
|
343
|
+
|
|
344
|
+
3. **完全灵活性**
|
|
345
|
+
- 用户决定绑定顺序和方式
|
|
346
|
+
- 支持任意复杂的pass连接关系
|
|
347
|
+
- 可以创建循环依赖(如果需要的话)
|
|
348
|
+
|
|
349
|
+
**示例用法:**
|
|
350
|
+
```typescript
|
|
351
|
+
// 方法1: 简单的链式引用
|
|
352
|
+
renderer.addPass({
|
|
353
|
+
name: 'background',
|
|
354
|
+
resources: [bgTexture, sampler1]
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
renderer.addPass({
|
|
358
|
+
name: 'main_effect',
|
|
359
|
+
resources: [renderer.getPassTexture('background'), sampler2] // 引用background的输出
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
renderer.addPass({
|
|
363
|
+
name: 'post_process',
|
|
364
|
+
resources: [renderer.getPassTexture('main_effect'), sampler3] // 引用main_effect的输出
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
// 方法2: 复杂的多pass混合
|
|
368
|
+
renderer.addPass({ name: 'layer1', resources: [textureA, sampler] })
|
|
369
|
+
renderer.addPass({ name: 'layer2', resources: [textureB, sampler] })
|
|
370
|
+
renderer.addPass({ name: 'layer3', resources: [textureC, sampler] })
|
|
371
|
+
|
|
372
|
+
// 创建混合pass,同时引用多个不同的pass
|
|
373
|
+
const layer1Output = renderer.getPassTexture('layer1')
|
|
374
|
+
const layer2Output = renderer.getPassTexture('layer2')
|
|
375
|
+
const layer3Output = renderer.getPassTexture('layer3')
|
|
376
|
+
|
|
377
|
+
renderer.addPass({
|
|
378
|
+
name: 'composite',
|
|
379
|
+
resources: [layer1Output, layer2Output, layer3Output, finalSampler]
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
// 方法3: 动态更新绑定
|
|
383
|
+
const mainPass = renderer.getPassByName('main_effect')
|
|
384
|
+
if (mainPass) {
|
|
385
|
+
// 运行时动态改变引用关系
|
|
386
|
+
mainPass.updateBindGroup([renderer.getPassTexture('layer1'), newSampler])
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**错误处理示例:**
|
|
391
|
+
```typescript
|
|
392
|
+
// 如果引用不存在的pass,会在渲染时抛出详细错误
|
|
393
|
+
const invalidTexture = renderer.getPassTexture('nonexistent_pass') // 这个pass不存在
|
|
394
|
+
renderer.addPass({
|
|
395
|
+
name: 'test',
|
|
396
|
+
resources: [invalidTexture, sampler] // 渲染时会抛出错误
|
|
397
|
+
})
|
|
398
|
+
// 错误信息: Cannot find pass named 'nonexistent_pass'. Available passes: [background, main_effect, ...]
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## 🛠️ 开发
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
# 开发模式
|
|
405
|
+
pnpm dev
|
|
406
|
+
|
|
407
|
+
# 构建
|
|
408
|
+
pnpm build
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## 📝 许可证
|
|
412
|
+
|
|
413
|
+
MIT License
|
|
414
|
+
|
|
415
|
+
## 🤝 贡献
|
|
416
|
+
|
|
417
|
+
欢迎提交Issue和Pull Request!
|