wgsl-renderer 0.0.1 → 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/LICENSE.md CHANGED
@@ -1,9 +1,9 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2025 taiyuuki
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sub license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
-
7
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
-
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 taiyuuki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sub license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
9
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,163 +1,119 @@
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链** - Binding 0自动绑定到上一个pass的输出
8
- - 🖼️ **多Pass渲染** - 支持背景、纹理渲染、后处理效果等多通道
9
- - ⚡ **高性能渲染循环** - 支持单帧渲染和循环渲染模式
10
- - 🛠️ **TypeScript支持** - 完整的类型定义和清晰的API分离
11
- - 🎮 **Uniform系统** - 内置uniform buffer管理,支持动态参数
7
+ ## Features
12
8
 
13
- ## 🚀 快速开始
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
14
13
 
15
- ### 安装
14
+ ## 🚀 Quick Start
15
+
16
+ ### Installation
16
17
 
17
18
  ```bash
18
19
  npm i wgls-renderer
19
20
  ```
20
21
 
21
- ### 基础使用
22
+ ### Add Pass
22
23
 
23
24
  ```typescript
24
- import { createWGSLRenderer } from 'wgls-renderer';
25
-
26
- const canvas = document.getElementById('canvas');
27
- const renderer = await createWGSLRenderer(canvas, {
28
- backgroundColor: 0x66CCFF // 支持多种格式:0xRRGGBB, "#RRGGBB", {r, g, b}
29
- });
30
-
31
- // 创建采样器
32
- const sampler = renderer.createSampler();
25
+ import { createWGSLRenderer } from 'wgls-renderer'
33
26
 
34
- // 加载纹理
35
- const { texture } = await renderer.loadTexture('image.jpg');
27
+ const canvas = document.querySelector('canvas');
28
+ const renderer = await createWGSLRenderer(canvas)
36
29
 
37
- // 添加Pass 1: 渲染纹理
38
30
  renderer.addPass({
39
- name: 'texture_pass',
40
- shaderCode: textureShader,
41
- blendMode: 'alpha',
42
- resources: [texture.createView(), sampler] // binding 1, 2
43
- });
44
-
45
- // 添加Pass 2: 后处理效果
46
- const uniforms = renderer.createUniforms(4); // time, resolution.x, resolution.y, padding
47
- renderer.addPass({
48
- name: 'post_process',
49
- shaderCode: postProcessShader,
50
- blendMode: 'alpha',
51
- resources: [sampler, uniforms.getBuffer()] // binding 1, 2
52
- });
53
-
54
- // 启动循环渲染,支持uniforms更新
55
- renderer.loopRender(() => {
56
- // 更新uniforms
57
- uniforms.values[0] = performance.now() / 1000.0; // 时间
58
- uniforms.values[1] = canvas.width; // 分辨率
59
- uniforms.values[2] = canvas.height;
60
- uniforms.apply();
61
- });
62
-
63
- // 或者单帧渲染
64
- renderer.renderFrame();
65
- ```
66
-
67
- ## 📋 API
31
+ name: 'my-pass',
32
+ shaderCode: `
33
+ struct VSOut {
34
+ @builtin(position) pos: vec4<f32>,
35
+ @location(0) uv: vec2<f32>,
36
+ };
68
37
 
69
- ### createWGSLRenderer(canvas, options?)
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
+ }
70
46
 
71
- 创建WGSL渲染器实例。
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
+ })
72
52
 
73
- ```typescript
74
- const renderer = await createWGSLRenderer(canvas, {
75
- backgroundColor: 0x66CCFF // 支持多种格式
76
- });
53
+ renderer.renderFrame()
77
54
  ```
78
55
 
79
56
 
80
- - `number`: 十六进制颜色 `0xRRGGBB`
81
- - `string`: 十六进制字符串 `"#RRGGBB"`
82
- - `object`: RGB对象 `{r: 0-1, g: 0-1, b: 0-1}`
83
-
84
- ### 渲染控制
85
-
86
- #### renderFrame()
87
- 单帧渲染,不循环。
88
-
89
- ```typescript
90
- renderer.renderFrame();
91
- ```
92
57
 
93
- #### loopRender(callback?)
94
- 循环渲染,支持每帧回调,可用于时时更新uniforms。
58
+ ### Basic Multi-Pass Usage
95
59
 
96
60
  ```typescript
97
- renderer.loopRender(() => {
98
- // 每帧更新uniforms
99
- myUniforms.values[0] = performance.now() / 1000.0;
100
- myUniforms.apply();
101
- });
102
- ```
61
+ import { createWGSLRenderer } from 'wgls-renderer'
103
62
 
104
- #### stopLoop()
105
- 停止循环渲染。
63
+ const canvas = document.getElementById('canvas')
64
+ const renderer = await createWGSLRenderer(canvas)
106
65
 
107
- ```typescript
108
- renderer.stopLoop();
109
- ```
66
+ // Create sampler
67
+ const sampler = renderer.createSampler()
110
68
 
111
- ### addPass(descriptor)
69
+ // Load image texture
70
+ const { texture, width, height } = await renderer.loadImageTexture('image.jpg')
112
71
 
113
- 添加一个渲染通道。
114
-
115
- ```typescript
72
+ // Add Pass 1: Render texture
116
73
  renderer.addPass({
117
- name: 'my_pass',
118
- shaderCode: wgslShaderCode,
119
- blendMode: 'alpha',
120
- resources: [textureView, sampler] // 资源数组
121
- });
122
- ```
123
-
124
- **资源数组绑定规则:**
125
- - **Binding 0**: 自动绑定到上一个pass的输出(无需在数组中指定)
126
- - **Binding 1**: `resources[0]`
127
- - **Binding 2**: `resources[1]`
128
- - 以此类推...
129
-
130
- **对应的WGSL绑定:**
131
- ```wgsl
132
- @group(0) @binding(0) var prevTexture: texture_2d<f32>; // 自动
133
- @group(0) @binding(1) var myTexture: texture_2d<f32>; // resources[0]
134
- @group(0) @binding(2) var mySampler: sampler; // resources[1]
135
- ```
136
-
137
- ### Uniform
138
-
139
- #### createUniforms(length)
140
- 创建uniform buffer管理对象。
141
-
142
- ```typescript
143
- const uniforms = renderer.createUniforms(4); // 4个float
144
- uniforms.values[0] = 1.0; // 设置值
145
- uniforms.apply(); // 应用到GPU
146
- const buffer = uniforms.getBuffer(); // 获取GPUBuffer
147
- ```
74
+ name: 'texture_pass',
75
+ shaderCode: textureShader,
76
+ resources: [texture, sampler], // binding 0, 1
77
+ })
148
78
 
149
- #### getUniformsByID(id)
150
- 通过symbol ID获取uniform对象。
79
+ // Add Pass 2: Post-processing effect
80
+ const uniforms = renderer.createUniforms(8) // Create uniform variable binding
151
81
 
152
- ```typescript
153
- const uniform = renderer.getUniformsByID(myUniformSymbol);
82
+ // Get Pass 1 output texture and bind to 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
+ // 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
105
+ })
106
+
107
+ // Or manually execute single frame render
108
+ renderer.renderFrame()
154
109
  ```
155
110
 
156
- ## 🎨 着色器示例
111
+ ## 🎨 Shader Examples
157
112
 
158
- ### Pass 1: 纹理渲染
113
+ ### Pass 1: Texture Rendering
159
114
 
160
115
  ```wgsl
116
+ // textureShader
161
117
  struct VSOut {
162
118
  @builtin(position) pos: vec4<f32>,
163
119
  @location(0) uv: vec2<f32>,
@@ -172,34 +128,27 @@ fn vs_main(@location(0) p: vec3<f32>) -> VSOut {
172
128
  return o;
173
129
  }
174
130
 
175
- @group(0) @binding(0) var prevTexture: texture_2d<f32>; // 内置的纯色背景纹理
176
- @group(0) @binding(1) var myTexture: texture_2d<f32>;
177
- @group(0) @binding(2) var mySampler: sampler;
131
+ @group(0) @binding(0) var myTexture: texture_2d<f32>;
132
+ @group(0) @binding(1) var mySampler: sampler;
178
133
 
179
134
  @fragment
180
135
  fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
181
- let bgColor = textureSample(prevTexture, mySampler, uv);
182
- let texColor = textureSample(myTexture, mySampler, uv);
183
-
184
- // 背景与纹理混合
185
- return vec4<f32>(
186
- bgColor.r * (1.0 - texColor.a) + texColor.r * texColor.a,
187
- bgColor.g * (1.0 - texColor.a) + texColor.g * texColor.a,
188
- bgColor.b * (1.0 - texColor.a) + texColor.b * texColor.a,
189
- 1.0
190
- );
136
+ return textureSample(myTexture, mySampler, uv);
191
137
  }
192
138
  ```
193
139
 
194
- ### Pass 2: 动态后处理效果
140
+ ### Pass 2: Brightness & Contrast Adjustment
195
141
 
196
142
  ```wgsl
143
+ // postProcessShader
197
144
  struct Uniforms {
198
- time: f32,
199
- resolution: vec2<f32>,
145
+ brightness: f32, // offset 0
146
+ contrast: f32, // offset 4
147
+ saturation: f32, // offset 8
148
+ // 4 bytes padding for vec3 alignment
200
149
  }
201
150
 
202
- @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
203
152
  @group(0) @binding(1) var mySampler: sampler;
204
153
  @group(0) @binding(2) var<uniform> uniforms: Uniforms;
205
154
 
@@ -207,102 +156,247 @@ struct Uniforms {
207
156
  fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
208
157
  var color = textureSample(prevTexture, mySampler, uv);
209
158
 
210
- // 动态扫描线效果
211
- let scanline = 0.8 + 0.2 * sin(uv.y * 600.0 + uniforms.time * 5.0);
212
- color = vec4<f32>(color.r * scanline, color.g * scanline, color.b * scanline, color.a);
159
+ // Apply brightness
160
+ color.rgb += uniforms.brightness;
213
161
 
214
- // 动态波纹效果
215
- let waveAmplitude = 0.05 + 0.02 * sin(uniforms.time * 2.0);
216
- 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;
217
164
 
218
- let finalR = clamp(color.r + waveX, 0.0, 1.0);
219
- let finalG = clamp(color.g - waveX * 0.5, 0.0, 1.0);
220
- 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);
221
168
 
222
- return vec4<f32>(finalR, finalG, finalB, color.a);
169
+ return clamp(color, vec4<f32>(0.0), vec4<f32>(1.0));
223
170
  }
224
171
  ```
225
172
 
226
- ## 🔧 内置方法
227
173
 
228
- ### 纹理相关
174
+ ## 📋 API
175
+
176
+ ### createWGSLRenderer(canvas, options?)
177
+
178
+ Create WGSL renderer instance.
229
179
 
230
180
  ```typescript
231
- // 加载纹理
232
- const { texture, width, height } = await renderer.loadTexture('image.png');
181
+ import { createWGSLRenderer } from 'wgsl-renderer'
182
+ const renderer = await createWGSLRenderer(canvas)
183
+ ```
184
+
185
+ options:
186
+
187
+ ```ts
188
+ interface WGSLRendererOptions {
189
+ config?: GPUCanvasConfiguration;
190
+ }
191
+ ```
233
192
 
234
- // 创建采样器
235
- const sampler = renderer.createSampler({
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
+ }
211
+ ```
212
+
213
+ ### renderer.getPassTexture(passName)
214
+
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.
216
+
217
+ ```typescript
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
+ })
229
+ ```
230
+
231
+ **Corresponding WGSL binding:**
232
+
233
+ ```wgsl
234
+ @group(0) @binding(0) var myTexture: texture_2d<f32>; // resources[0]
235
+ @group(0) @binding(1) var mySampler: sampler; // resources[1]
236
+ ```
237
+
238
+
239
+
240
+ ### renderer.createUniforms(length)
241
+
242
+ Create uniform variables using Float32Array, length unit is the number of floats.
243
+
244
+ ```typescript
245
+ const myUniforms = renderer.createUniforms(8) // 8 floats
246
+
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
+ ],
254
+ })
255
+
256
+ myUniforms.values[0] = 1.0 // Set value
257
+ myUniforms.apply() // Apply to GPU
258
+ ```
259
+
260
+ ### renderer.getContext()
261
+
262
+ Get WebGPU canvas context.
263
+
264
+ ```typescript
265
+ const context = renderer.getContext()
266
+ ```
267
+
268
+ ### renderer.getDevice()
269
+
270
+ Get WebGPU device object.
271
+
272
+ ```typescript
273
+ const device = renderer.getDevice()
274
+ ```
275
+
276
+ ### Render Control
277
+
278
+ #### renderer.renderFrame()
279
+ Single frame rendering.
280
+
281
+ #### renderer.loopRender(callback?)
282
+ Built-in loop rendering with per-frame callback for real-time uniform updates.
283
+
284
+ ```typescript
285
+ renderer.loopRender(time => {
286
+
287
+ // Update uniforms every frame
288
+ myUniforms.values[2] = time * 0.001
289
+ myUniforms.apply()
290
+ })
291
+ ```
292
+
293
+ #### renderer.stopLoop()
294
+ Stop loop rendering.
295
+
296
+
297
+
298
+ ### renderer.createSampler(options?)
299
+
300
+ Create sampler with default parameters:
301
+
302
+ ```ts
303
+ const options = {
236
304
  magFilter: 'linear',
237
305
  minFilter: 'linear',
238
306
  addressModeU: 'clamp-to-edge',
239
307
  addressModeV: 'clamp-to-edge',
240
- });
308
+ }
241
309
 
242
- // 创建纹理绑定
243
- const textureView = renderer.createTextureBinding(texture);
310
+ const sampler = renderer.createSampler(options)
244
311
  ```
245
312
 
246
- ### 控制相关
313
+ ## 🎯 Pass Flow
247
314
 
248
- ```typescript
249
- // 调整画布大小
250
- renderer.resize(800, 600);
315
+ The renderer provides the following management features:
251
316
 
252
- // 停止渲染
253
- renderer.stopLoop();
254
- ```
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)`
321
+
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
326
+
327
+ 3. **Complete Flexibility**
328
+ - Users decide binding order and method
329
+ - Support arbitrarily complex pass connections
330
+ - Can create circular dependencies (if needed)
255
331
 
256
- ## 🎯 Pass流程
332
+ **Example Usage:**
333
+ ```typescript
334
+ // Method 1: Simple chain reference
335
+ renderer.addPass({
336
+ name: 'background',
337
+ resources: [bgTexture, sampler1],
338
+ })
257
339
 
258
- 渲染器自动管理以下pass流程:
340
+ renderer.addPass({
341
+ name: 'main_effect',
342
+ resources: [renderer.getPassTexture('background'), sampler2], // Reference background output
343
+ })
259
344
 
260
- 1. **Background Pass** (内置)
261
- - 渲染纯色背景
262
- - 输出到 `pass_0_output`
345
+ renderer.addPass({
346
+ name: 'post_process',
347
+ resources: [renderer.getPassTexture('main_effect'), sampler3], // Reference main_effect output
348
+ })
263
349
 
264
- 2. **User Pass 1**
265
- - Binding 0: 背景输出
266
- - Binding 1+: 用户资源
267
- - 输出到 `pass_1_output`
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] })
268
354
 
269
- 3. **User Pass 2**
270
- - Binding 0: Pass 1输出
271
- - Binding 1+: 用户资源
272
- - 输出到 `pass_2_output`
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')
273
359
 
274
- 4. **Final Pass**
275
- - Binding 0: 上一个pass输出
276
- - 渲染到canvas
360
+ renderer.addPass({
361
+ name: 'composite',
362
+ resources: [layer1Output, layer2Output, layer3Output, finalSampler],
363
+ })
277
364
 
278
- ## 📁 项目结构
365
+ // Method 3: Dynamic update binding
366
+ const mainPass = renderer.getPassByName('main_effect')
367
+ if (mainPass) {
279
368
 
369
+ // Dynamically change reference relationship at runtime
370
+ mainPass.updateBindGroup([renderer.getPassTexture('layer1'), newSampler])
371
+ }
280
372
  ```
281
- src/
282
- ├── index.ts # 主渲染器类,包含完整的API
283
- ├── RenderPass.ts # Pass渲染逻辑和类型定义
284
- ├── TextureManager.ts # 纹理管理
285
- examples/
286
- └── multi-pass-demo.html # 完整示例,包含纹理、动态uniforms效果
373
+
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
+ })
382
+
383
+ // Error message: Cannot find pass named 'nonexistent_pass'. Available passes: [background, main_effect, ...]
287
384
  ```
288
385
 
289
- ## 🛠️ 开发
386
+ ## 🛠️ Development
290
387
 
291
388
  ```bash
292
- # 开发模式
293
- npm run dev
294
-
295
- # 构建
296
- npm run build
389
+ # Development mode
390
+ pnpm dev
297
391
 
298
- # 类型检查
299
- npm run type-check
392
+ # Build
393
+ pnpm build
300
394
  ```
301
395
 
302
- ## 📝 许可证
396
+ ## 📝 License
303
397
 
304
398
  MIT License
305
399
 
306
- ## 🤝 贡献
400
+ ## 🤝 Contributing
307
401
 
308
- 欢迎提交Issue和Pull Request!
402
+ Issues and Pull Requests are welcome!