wgsl-renderer 0.0.2 → 0.0.4

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 CHANGED
@@ -1,25 +1,61 @@
1
1
  # WGSL Multi-Pass Renderer
2
2
 
3
- 一个基于WebGPU和WGSL的多通道渲染器。
3
+ English | [中文](./README.zh-CN.md)
4
4
 
5
- ## 特性
5
+ A multi-pass renderer based on WebGPU and WGSL.
6
6
 
7
- - 🔗 **灵活的Pass链** - Pass 2开始自动绑定上一个pass的输出
8
- - 🖼️ **多Pass渲染** - 支持纹理渲染、后处理效果等多通道
9
- - ⚡ **高性能渲染循环** - 支持单帧渲染和循环渲染模式
10
- - 🛠️ **TypeScript支持** - 完整的类型定义和清晰的API分离
11
- - 🎮 **Uniform系统** - 内置uniform buffer管理,支持动态参数
12
- - 🔄 **自动Resize** - 内置ResizeObserver自动处理canvas大小变化
7
+ ## Features
13
8
 
14
- ## 🚀 快速开始
9
+ - 🖼️ **Multi-Pass Rendering** - Support for texture rendering, post-processing effects, and other multi-pass rendering
10
+ - ⚡ **High-Performance Rendering Loop** - Support for single-frame rendering and loop rendering modes
11
+ - 🛠️ **TypeScript Support** - Complete type definitions and clear API separation
12
+ - 🎮 **Uniform System** - Built-in uniform buffer management with dynamic parameter support
15
13
 
16
- ### 安装
14
+ ## 🚀 Quick Start
15
+
16
+ ### Installation
17
17
 
18
18
  ```bash
19
19
  npm i wgls-renderer
20
20
  ```
21
21
 
22
- ### 基础使用
22
+ ### Add Pass
23
+
24
+ ```typescript
25
+ import { createWGSLRenderer } from 'wgls-renderer'
26
+
27
+ const canvas = document.querySelector('canvas');
28
+ const renderer = await createWGSLRenderer(canvas)
29
+
30
+ renderer.addPass({
31
+ name: 'my-pass',
32
+ shaderCode: `
33
+ struct VSOut {
34
+ @builtin(position) pos: vec4<f32>,
35
+ @location(0) uv: vec2<f32>,
36
+ };
37
+
38
+ @vertex
39
+ fn vs_main(@location(0) p: vec3<f32>) -> VSOut {
40
+ var o: VSOut;
41
+ o.pos = vec4<f32>(p, 1.0);
42
+ o.uv = p.xy * 0.5 + vec2<f32>(0.5, 0.5);
43
+ o.uv.y = 1.0 - o.uv.y;
44
+ return o;
45
+ }
46
+
47
+ @fragment
48
+ fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
49
+ return vec4(1.0, 1.0, 0.0, 1.0);
50
+ }`,
51
+ })
52
+
53
+ renderer.renderFrame()
54
+ ```
55
+
56
+
57
+
58
+ ### Basic Multi-Pass Usage
23
59
 
24
60
  ```typescript
25
61
  import { createWGSLRenderer } from 'wgls-renderer'
@@ -27,50 +63,54 @@ import { createWGSLRenderer } from 'wgls-renderer'
27
63
  const canvas = document.getElementById('canvas')
28
64
  const renderer = await createWGSLRenderer(canvas)
29
65
 
30
- // 创建采样器
66
+ // Create sampler
31
67
  const sampler = renderer.createSampler()
32
68
 
33
- // 加载图片纹理
34
- const { texture } = await renderer.loadImageTexture('image.jpg')
69
+ // Load image texture
70
+ const { texture, width, height } = await renderer.loadImageTexture('image.jpg')
35
71
 
36
- // 添加Pass 1: 渲染纹理
72
+ // Add Pass 1: Render texture
37
73
  renderer.addPass({
38
74
  name: 'texture_pass',
39
75
  shaderCode: textureShader,
40
- blendMode: 'alpha',
41
76
  resources: [texture, sampler], // binding 0, 1
42
77
  })
43
78
 
44
- // 添加Pass 2: 后处理效果 (自动绑定Pass 1的输出到binding 0)
45
- const uniforms = renderer.createUniforms(16) // 支持复杂的uniform结构
79
+ // Add Pass 2: Post-processing effect
80
+ const uniforms = renderer.createUniforms(8) // Create uniform variable binding
81
+
82
+ // Get Pass 1 output texture and bind to Pass 2
83
+ const texturePassOutput = renderer.getPassTexture('texture_pass')
46
84
  renderer.addPass({
47
85
  name: 'post_process',
48
86
  shaderCode: postProcessShader,
49
- blendMode: 'alpha',
50
- resources: [sampler, uniforms.getBuffer()], // 对应binding 1, 2 (binding 0自动绑定到Pass 1的输出)
87
+ resources: [
88
+ texturePassOutput, // @group(0) @binding(0)
89
+ sampler, // @group(0) @binding(1)
90
+ uniforms.getBuffer(), // @group(0) @binding(2)
91
+ ],
51
92
  })
52
93
 
53
- // 启动循环渲染,可以在回调函数中更新uniforms
54
- renderer.loopRender(() => {
55
-
56
- // 更新uniforms (注意WebGPU的内存对齐规则)
57
- uniforms.values[0] = canvas.width // resolution.x
58
- uniforms.values[1] = canvas.height // resolution.y
59
- uniforms.values[2] = performance.now() // time
60
- uniforms.values[3] = 0 // padding (vec3对齐)
61
- uniforms.values[4] = 1024 // textureResolution.x
62
- uniforms.values[5] = 1024 // textureResolution.y
63
- uniforms.apply()
94
+ // Start loop rendering, can update uniforms in callback
95
+ renderer.loopRender((t) => {
96
+
97
+ // Update uniforms (Note WebGPU memory alignment rules)
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 (leave empty)
102
+ uniforms.values[4] = width // textureResolution.x
103
+ uniforms.values[5] = height // textureResolution.y
104
+ uniforms.apply() // Apply to GPU
64
105
  })
65
106
 
66
- // 或者单帧渲染
107
+ // Or manually execute single frame render
67
108
  renderer.renderFrame()
68
109
  ```
69
110
 
111
+ ## 🎨 Shader Examples
70
112
 
71
- ## 🎨 着色器示例
72
-
73
- ### Pass 1: 纹理渲染
113
+ ### Pass 1: Texture Rendering
74
114
 
75
115
  ```wgsl
76
116
  // textureShader
@@ -97,20 +137,18 @@ fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
97
137
  }
98
138
  ```
99
139
 
100
- ### Pass 2: 动态后处理效果
140
+ ### Pass 2: Brightness & Contrast Adjustment
101
141
 
102
142
  ```wgsl
103
143
  // postProcessShader
104
144
  struct Uniforms {
105
- resolution: vec2<f32>, // offset 0-7
106
- time: f32, // offset 8
145
+ brightness: f32, // offset 0
146
+ contrast: f32, // offset 4
147
+ saturation: f32, // offset 8
107
148
  // 4 bytes padding for vec3 alignment
108
- texResolution: vec2<f32>, // offset 16-23
109
- speed: f32, // offset 24
110
- // 8 bytes padding for next vec3
111
149
  }
112
150
 
113
- @group(0) @binding(0) var prevTexture: texture_2d<f32>; // 自动绑定到Pass 1的输出纹理
151
+ @group(0) @binding(0) var prevTexture: texture_2d<f32>; // Pass 1 output texture
114
152
  @group(0) @binding(1) var mySampler: sampler;
115
153
  @group(0) @binding(2) var<uniform> uniforms: Uniforms;
116
154
 
@@ -118,277 +156,247 @@ struct Uniforms {
118
156
  fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
119
157
  var color = textureSample(prevTexture, mySampler, uv);
120
158
 
121
- // 动态扫描线效果
122
- let scanline = 0.8 + 0.2 * sin(uv.y * 600.0 + uniforms.time * 5.0);
123
- color = vec4<f32>(color.r * scanline, color.g * scanline, color.b * scanline, color.a);
159
+ // Apply brightness
160
+ color.rgb += uniforms.brightness;
124
161
 
125
- // 动态波纹效果
126
- let waveAmplitude = 0.05 + 0.02 * sin(uniforms.time * 2.0);
127
- let waveX = sin(uv.x * 10.0 + uniforms.time * 3.0) * cos(uv.y * 8.0 + uniforms.time * 2.0) * waveAmplitude;
162
+ // Apply contrast
163
+ color.rgb = (color.rgb - 0.5) * uniforms.contrast + 0.5;
128
164
 
129
- let finalR = clamp(color.r + waveX, 0.0, 1.0);
130
- let finalG = clamp(color.g - waveX * 0.5, 0.0, 1.0);
131
- let finalB = clamp(color.b + waveX * 0.3, 0.0, 1.0);
165
+ // Apply saturation
166
+ let gray = dot(color.rgb, vec3<f32>(0.299, 0.587, 0.114));
167
+ color.rgb = mix(vec3<f32>(gray), color.rgb, uniforms.saturation);
132
168
 
133
- return vec4<f32>(finalR, finalG, finalB, color.a);
169
+ return clamp(color, vec4<f32>(0.0), vec4<f32>(1.0));
134
170
  }
135
171
  ```
136
172
 
173
+
137
174
  ## 📋 API
138
175
 
139
176
  ### createWGSLRenderer(canvas, options?)
140
177
 
141
- 创建WGSL渲染器实例。
178
+ Create WGSL renderer instance.
142
179
 
143
180
  ```typescript
181
+ import { createWGSLRenderer } from 'wgsl-renderer'
144
182
  const renderer = await createWGSLRenderer(canvas)
145
183
  ```
146
184
 
147
- ### createUniforms(length)
185
+ options:
148
186
 
149
- 创建uniform变量,length单位为float数量。
187
+ ```ts
188
+ interface WGSLRendererOptions {
189
+ config?: GPUCanvasConfiguration;
190
+ }
191
+ ```
150
192
 
151
- ```typescript
152
- const myUniforms = renderer.createUniforms(8) // 8个float
193
+ ### renderer.addPass(passOptions)
194
+
195
+ Add a render pass.
196
+
197
+ ```ts
198
+ interface RenderPassOptions {
199
+ name: string;
200
+ shaderCode: string;
201
+ entryPoints?: {
202
+ vertex?: string; // Default is 'vs_main' function
203
+ fragment?: string; // Default is 'fs_main' function
204
+ };
205
+ clearColor?: { r: number; g: number; b: number; a: number };
206
+ blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
207
+ resources?: GPUBindingResource[];
208
+ view?: GPUTextureView; // Optional custom view for this pass
209
+ format?: GPUTextureFormat; // Optional format for the view (required when using custom view with different format)
210
+ }
153
211
  ```
154
212
 
155
- ### getContext()
213
+ ### renderer.getPassTexture(passName)
156
214
 
157
- 获取WebGPU画布上下文。
215
+ Get the output texture of the specified pass. The return value is not a real texture but a placeholder that automatically binds the output texture to the shader during actual rendering.
158
216
 
159
217
  ```typescript
160
- const context = renderer.getContext()
218
+ // Get output texture of my_pass
219
+ const passOutputTexture = renderer.getPassTexture('my_pass')
220
+ const sampler = renderer.createSampler()
221
+ renderer.addPass({
222
+ name: 'my_pass2',
223
+ shaderCode: wgslShaderCode,
224
+ resources: [
225
+ passOutputTexture,
226
+ sampler,
227
+ ],
228
+ })
161
229
  ```
162
230
 
163
- ### getDevice()
164
-
165
- 获取WebGPU设备对象。
231
+ **Corresponding WGSL binding:**
166
232
 
167
- ```typescript
168
- const device = renderer.getDevice()
233
+ ```wgsl
234
+ @group(0) @binding(0) var myTexture: texture_2d<f32>; // resources[0]
235
+ @group(0) @binding(1) var mySampler: sampler; // resources[1]
169
236
  ```
170
237
 
171
- ### 渲染控制
172
238
 
173
- #### renderFrame()
174
- 单帧渲染,不循环。
175
239
 
176
- ```typescript
177
- renderer.renderFrame()
178
- ```
240
+ ### renderer.createUniforms(length)
179
241
 
180
- #### loopRender(callback?)
181
- 循环渲染,支持每帧回调,可用于时时更新uniforms。
242
+ Create uniform variables using Float32Array, length unit is the number of floats.
182
243
 
183
244
  ```typescript
184
- renderer.loopRender(() => {
245
+ const myUniforms = renderer.createUniforms(8) // 8 floats
185
246
 
186
- // 每帧更新uniforms
187
- myUniforms.values[0] = performance.now() / 1000.0
188
- myUniforms.apply()
247
+ // Bind to shader
248
+ renderer.addPass({
249
+ name: 'my_pass',
250
+ shaderCode: wgslShaderCode,
251
+ resources: [
252
+ myUniforms.getBuffer(), // group(0) binding(0) var<uniform>
253
+ ],
189
254
  })
255
+
256
+ myUniforms.values[0] = 1.0 // Set value
257
+ myUniforms.apply() // Apply to GPU
190
258
  ```
191
259
 
192
- #### stopLoop()
193
- 停止循环渲染。
260
+ ### renderer.getContext()
261
+
262
+ Get WebGPU canvas context.
194
263
 
195
264
  ```typescript
196
- renderer.stopLoop()
265
+ const context = renderer.getContext()
197
266
  ```
198
267
 
199
- ### addPass(descriptor)
268
+ ### renderer.getDevice()
200
269
 
201
- 添加一个渲染通道。
270
+ Get WebGPU device object.
202
271
 
203
272
  ```typescript
204
- renderer.addPass({
205
- name: 'my_pass',
206
- shaderCode: wgslShaderCode,
207
- blendMode: 'alpha',
208
- resources: [textureView, sampler], // 资源数组
209
- })
273
+ const device = renderer.getDevice()
210
274
  ```
211
275
 
212
- **资源数组绑定规则:**
276
+ ### Render Control
213
277
 
214
- - **Pass 1**: 无自动绑定,完全自由
215
- - **Binding 0**: `resources[0]`
216
- - **Binding 1**: `resources[1]`
217
- - 以此类推...
278
+ #### renderer.renderFrame()
279
+ Single frame rendering.
218
280
 
219
- - **Pass 2及以上**: 自动绑定上一个pass的输出
220
- - **Binding 0**: 上一个pass的输出纹理(自动)
221
- - **Binding 1**: `resources[0]`
222
- - **Binding 2**: `resources[1]`
223
- - 以此类推...
281
+ #### renderer.loopRender(callback?)
282
+ Built-in loop rendering with per-frame callback for real-time uniform updates.
224
283
 
225
- **对应的WGSL绑定:**
226
-
227
- ```wgsl
228
- // Pass 1:
229
- @group(0) @binding(0) var myTexture: texture_2d<f32>; // resources[0]
230
- @group(0) @binding(1) var mySampler: sampler; // resources[1]
284
+ ```typescript
285
+ renderer.loopRender(time => {
231
286
 
232
- // Pass 2+:
233
- @group(0) @binding(0) var prevTexture: texture_2d<f32>; // 自动绑定
234
- @group(0) @binding(1) var myTexture: texture_2d<f32>; // resources[0]
235
- @group(0) @binding(2) var mySampler: sampler; // resources[1]
287
+ // Update uniforms every frame
288
+ myUniforms.values[2] = time * 0.001
289
+ myUniforms.apply()
290
+ })
236
291
  ```
237
292
 
238
- ### Uniform
293
+ #### renderer.stopLoop()
294
+ Stop loop rendering.
239
295
 
240
- #### createUniforms(length)
241
- 创建uniform buffer管理对象。
242
296
 
243
- ```typescript
244
- const uniforms = renderer.createUniforms(4) // 4个float
245
- uniforms.values[0] = 1.0 // 设置值
246
- uniforms.apply() // 应用到GPU
247
- const buffer = uniforms.getBuffer() // 获取GPUBuffer
248
- ```
249
297
 
250
- **JavaScript Uniforms设置 (注意内存对齐):**
251
-
252
- ```javascript
253
- const uniforms = renderer.createUniforms(16) // 64字节
254
- uniforms.values[0] = canvas.width // resolution.x
255
- uniforms.values[1] = canvas.height // resolution.y
256
- uniforms.values[2] = performance.now() // time
257
- uniforms.values[3] = 0 // padding (vec3对齐)
258
- uniforms.values[4] = 1024 // texResolution.x
259
- uniforms.values[5] = 1024 // texResolution.y
260
- uniforms.values[6] = 1.0 // speed
261
- uniforms.values[7] = 0 // padding
262
- uniforms.values[8] = 0 // padding
263
- uniforms.apply()
264
- ```
298
+ ### renderer.createSampler(options?)
265
299
 
266
- ## 🔧 内置方法
300
+ Create sampler with default parameters:
267
301
 
268
- ### 纹理相关
302
+ ```ts
303
+ const options = {
304
+ magFilter: 'linear',
305
+ minFilter: 'linear',
306
+ addressModeU: 'clamp-to-edge',
307
+ addressModeV: 'clamp-to-edge',
308
+ }
269
309
 
270
- ```typescript
271
- // 从url加载图片纹理
272
- const { texture, width, height } = await renderer.loadImageTexture('image.png')
310
+ const sampler = renderer.createSampler(options)
311
+ ```
273
312
 
274
- // 创建采样器
275
- const sampler = renderer.createSampler()
313
+ ## 🎯 Pass Flow
276
314
 
277
- // 绑定到Pass
278
- const textureView = texture.createView()
279
- renderer.addPass({
280
- name: 'texture-pass',
281
- shaderCode: shaderCode,
282
- resources: [
283
- textureView,
284
- sampler,
285
- ],
286
- })
287
- ```
315
+ The renderer provides the following management features:
288
316
 
289
- ```wgsl
290
- // 如果是Pass 1:
291
- @group(0) @binding(0) var myTexture: texture_2d<f32>;
292
- @group(0) @binding(1) var mySampler: sampler;
317
+ 1. **User-defined all Passes**
318
+ - Users have complete control over all resource binding
319
+ - Can get output texture of any pass through `getPassTexture(passName)`
320
+ - Can get pass object through `getPassByName(passName)`
293
321
 
294
- // 如果是Pass 2及以后:
295
- @group(0) @binding(0) var prevTexture: texture_2d<f32>; // 自动绑定,上一个Pass的输出纹理
296
- @group(0) @binding(1) var myTexture: texture_2d<f32>;
297
- @group(0) @binding(2) var mySampler: sampler;
298
- ```
322
+ 2. **Texture Management**
323
+ - Each pass automatically creates output texture (format: `{passName}_output`)
324
+ - Users can manually bind these textures to other passes
325
+ - The last pass automatically renders to canvas
299
326
 
300
- ### Uniform变量
327
+ 3. **Complete Flexibility**
328
+ - Users decide binding order and method
329
+ - Support arbitrarily complex pass connections
330
+ - Can create circular dependencies (if needed)
301
331
 
332
+ **Example Usage:**
302
333
  ```typescript
303
- // 创建uniform buffer,length单位为float数量
304
- const uniforms = renderer.createUniforms(8) // 8个float (32字节)
305
- uniforms.values[0] = 1.0 // 设置第一个float值
306
- uniforms.values[1] = 0.5 // 设置第二个float值
307
- uniforms.values[2] = 0.25 // 设置第三个float值
308
- // 向量值需要内存对齐,这里的offset必须是4的倍数,因此跳过uniforms.values[3]
309
- uniforms.values[4] = 1.0 // texResolution.x
310
- uniforms.values[5] = 1024.0 // texResolution.y
311
-
312
- uniforms.apply() // 应用到GPU
313
- const uniformBuffer = uniforms.getBuffer() // 获取GPUBuffer
314
-
315
- // 绑定到Pass
334
+ // Method 1: Simple chain reference
316
335
  renderer.addPass({
317
- name: 'uniform-pass',
318
- shaderCode: shaderCode,
319
- resources: [
336
+ name: 'background',
337
+ resources: [bgTexture, sampler1],
338
+ })
320
339
 
321
- // 数组第0项,Pass 1着色器中对应@group(0) @binding(0),Pass2及以后的着色器中是@group(0) @binding(1)
322
- uniformBuffer,
323
- ],
340
+ renderer.addPass({
341
+ name: 'main_effect',
342
+ resources: [renderer.getPassTexture('background'), sampler2], // Reference background output
324
343
  })
325
- ```
326
344
 
327
- ```wgsl
328
- struct Uniforms {
329
- value1: f32, // 对应 uniforms.values[0]
330
- value2: f32, // 对应 uniforms.values[1]
331
- value3: f32, // 对应 uniforms.values[2]
332
- textureResolution: vec2<f32>, // x, y分别对应 uniforms.values[4], uniforms.values[5]
333
- // ...
334
- }
345
+ renderer.addPass({
346
+ name: 'post_process',
347
+ resources: [renderer.getPassTexture('main_effect'), sampler3], // Reference main_effect output
348
+ })
335
349
 
336
- // 如果是Pass 1:
337
- @group(0) @binding(0) var<uniform> uniforms: Uniforms;
350
+ // Method 2: Complex multi-pass blending
351
+ renderer.addPass({ name: 'layer1', resources: [textureA, sampler] })
352
+ renderer.addPass({ name: 'layer2', resources: [textureB, sampler] })
353
+ renderer.addPass({ name: 'layer3', resources: [textureC, sampler] })
338
354
 
339
- // 如果是Pass 2及以后:
340
- @group(0) @binding(0) var prevTexture: texture_2d<f32>; // 自动绑定,上一个Pass的输出纹理
341
- @group(0) @binding(1) var<uniform> uniforms: Uniforms;
342
- ```
355
+ // Create blend pass, referencing multiple different passes simultaneously
356
+ const layer1Output = renderer.getPassTexture('layer1')
357
+ const layer2Output = renderer.getPassTexture('layer2')
358
+ const layer3Output = renderer.getPassTexture('layer3')
343
359
 
344
- ### 控制相关
360
+ renderer.addPass({
361
+ name: 'composite',
362
+ resources: [layer1Output, layer2Output, layer3Output, finalSampler],
363
+ })
345
364
 
346
- ```typescript
347
- // 调整画布大小
348
- renderer.resize(800, 600)
365
+ // Method 3: Dynamic update binding
366
+ const mainPass = renderer.getPassByName('main_effect')
367
+ if (mainPass) {
349
368
 
350
- // 停止渲染
351
- renderer.stopLoop()
369
+ // Dynamically change reference relationship at runtime
370
+ mainPass.updateBindGroup([renderer.getPassTexture('layer1'), newSampler])
371
+ }
352
372
  ```
353
373
 
354
- ## 🎯 Pass流程
355
-
356
- 渲染器自动管理以下pass流程:
357
-
358
- 1. **User Pass 1**
359
- - 无自动绑定,完全自由
360
- - Binding 0+: 用户资源
361
- - 输出到 `pass_0_output`
362
-
363
- 2. **User Pass 2**
364
- - Binding 0: Pass 1输出纹理(自动)
365
- - Binding 1+: 用户资源
366
- - 输出到 `pass_1_output`
367
-
368
- 3. **User Pass 3+**
369
- - Binding 0: 上一个pass输出纹理(自动)
370
- - Binding 1+: 用户资源
371
- - 输出到 `pass_N-1_output`
374
+ **Error Handling Example:**
375
+ ```typescript
376
+ // If referencing non-existent pass, will throw detailed error during rendering
377
+ const invalidTexture = renderer.getPassTexture('nonexistent_pass') // This pass doesn't exist
378
+ renderer.addPass({
379
+ name: 'test',
380
+ resources: [invalidTexture, sampler], // Will throw error during rendering
381
+ })
372
382
 
373
- 4. **Final Pass**
374
- - Binding 0: 上一个pass输出纹理(自动)
375
- - Binding 1+: 用户资源
376
- - 渲染到canvas
383
+ // Error message: Cannot find pass named 'nonexistent_pass'. Available passes: [background, main_effect, ...]
384
+ ```
377
385
 
378
- ## 🛠️ 开发
386
+ ## 🛠️ Development
379
387
 
380
388
  ```bash
381
- # 开发模式
389
+ # Development mode
382
390
  pnpm dev
383
391
 
384
- # 构建
392
+ # Build
385
393
  pnpm build
386
394
  ```
387
395
 
388
- ## 📝 许可证
396
+ ## 📝 License
389
397
 
390
398
  MIT License
391
399
 
392
- ## 🤝 贡献
400
+ ## 🤝 Contributing
393
401
 
394
- 欢迎提交Issue和Pull Request!
402
+ Issues and Pull Requests are welcome!