@xifu/shader-graph-glsl 0.1.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.md +86 -0
- package/dist/ShaderConfig-RGrO5cST.d.ts +110 -0
- package/dist/editor/index.d.ts +2703 -0
- package/dist/editor/index.js +14499 -0
- package/dist/runtime/index.d.ts +403 -0
- package/dist/runtime/index.js +638 -0
- package/package.json +71 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
// src/runtime/RuntimeProgram.ts
|
|
2
|
+
function compileShader(gl, source, type) {
|
|
3
|
+
const shader = gl.createShader(type);
|
|
4
|
+
gl.shaderSource(shader, source);
|
|
5
|
+
gl.compileShader(shader);
|
|
6
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
7
|
+
const log = gl.getShaderInfoLog(shader);
|
|
8
|
+
const stage = type === gl.VERTEX_SHADER ? "VERTEX" : "FRAGMENT";
|
|
9
|
+
gl.deleteShader(shader);
|
|
10
|
+
throw new Error(`GLSL ${stage} compile error:
|
|
11
|
+
${log}
|
|
12
|
+
|
|
13
|
+
--- Source ---
|
|
14
|
+
${source}`);
|
|
15
|
+
}
|
|
16
|
+
return shader;
|
|
17
|
+
}
|
|
18
|
+
function createProgram(gl, vertSrc, fragSrc) {
|
|
19
|
+
const vs = compileShader(gl, vertSrc, gl.VERTEX_SHADER);
|
|
20
|
+
const fs = compileShader(gl, fragSrc, gl.FRAGMENT_SHADER);
|
|
21
|
+
const program = gl.createProgram();
|
|
22
|
+
gl.attachShader(program, vs);
|
|
23
|
+
gl.attachShader(program, fs);
|
|
24
|
+
gl.linkProgram(program);
|
|
25
|
+
gl.deleteShader(vs);
|
|
26
|
+
gl.deleteShader(fs);
|
|
27
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
28
|
+
const log = gl.getProgramInfoLog(program);
|
|
29
|
+
gl.deleteProgram(program);
|
|
30
|
+
throw new Error(`GLSL program link error:
|
|
31
|
+
${log}`);
|
|
32
|
+
}
|
|
33
|
+
return program;
|
|
34
|
+
}
|
|
35
|
+
function createRuntimeProgram(gl, config) {
|
|
36
|
+
const program = createProgram(gl, config.vertCode, config.fragCode);
|
|
37
|
+
const uniforms = {};
|
|
38
|
+
const attribs = {};
|
|
39
|
+
for (const meta of config.uniforms) {
|
|
40
|
+
uniforms[meta.name] = gl.getUniformLocation(program, meta.name);
|
|
41
|
+
}
|
|
42
|
+
const numAttribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
|
|
43
|
+
for (let i = 0; i < numAttribs; i++) {
|
|
44
|
+
const info = gl.getActiveAttrib(program, i);
|
|
45
|
+
if (info) {
|
|
46
|
+
attribs[info.name] = gl.getAttribLocation(program, info.name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { program, uniforms, attribs, config, textureUnitMap: {} };
|
|
50
|
+
}
|
|
51
|
+
function applyUniforms(gl, program, values) {
|
|
52
|
+
const { uniforms, config } = program;
|
|
53
|
+
for (const meta of config.uniforms) {
|
|
54
|
+
const loc = uniforms[meta.name];
|
|
55
|
+
if (loc === void 0 || loc === null) continue;
|
|
56
|
+
const value = values[meta.name] ?? meta.default;
|
|
57
|
+
if (value === void 0 || value === null) continue;
|
|
58
|
+
applyUniformValue(gl, loc, meta.type, value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function applyUniformValue(gl, loc, type, value) {
|
|
62
|
+
switch (type) {
|
|
63
|
+
case "float":
|
|
64
|
+
gl.uniform1f(loc, value);
|
|
65
|
+
break;
|
|
66
|
+
case "int":
|
|
67
|
+
gl.uniform1i(loc, value);
|
|
68
|
+
break;
|
|
69
|
+
case "bool":
|
|
70
|
+
gl.uniform1i(loc, value ? 1 : 0);
|
|
71
|
+
break;
|
|
72
|
+
case "vec2":
|
|
73
|
+
gl.uniform2fv(loc, value);
|
|
74
|
+
break;
|
|
75
|
+
case "vec3":
|
|
76
|
+
gl.uniform3fv(loc, value);
|
|
77
|
+
break;
|
|
78
|
+
case "vec4":
|
|
79
|
+
gl.uniform4fv(loc, value);
|
|
80
|
+
break;
|
|
81
|
+
case "mat2":
|
|
82
|
+
gl.uniformMatrix2fv(loc, false, value);
|
|
83
|
+
break;
|
|
84
|
+
case "mat3":
|
|
85
|
+
gl.uniformMatrix3fv(loc, false, value);
|
|
86
|
+
break;
|
|
87
|
+
case "mat4":
|
|
88
|
+
gl.uniformMatrix4fv(loc, false, value);
|
|
89
|
+
break;
|
|
90
|
+
case "sampler2D":
|
|
91
|
+
gl.uniform1i(loc, value);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function destroyRuntimeProgram(gl, program) {
|
|
96
|
+
gl.deleteProgram(program.program);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/runtime/RuntimeRenderer.ts
|
|
100
|
+
var RuntimeRenderer = class {
|
|
101
|
+
gl;
|
|
102
|
+
programs = /* @__PURE__ */ new Map();
|
|
103
|
+
currentProgram = null;
|
|
104
|
+
defaultVAO = null;
|
|
105
|
+
textureUnitCounter = 0;
|
|
106
|
+
textureCache = /* @__PURE__ */ new Map();
|
|
107
|
+
canvas;
|
|
108
|
+
clearColor = [0, 0, 0, 0];
|
|
109
|
+
viewport = [0, 0, 0, 0];
|
|
110
|
+
scissor = [0, 0, 0, 0];
|
|
111
|
+
constructor(canvas, options) {
|
|
112
|
+
this.canvas = canvas;
|
|
113
|
+
const gl = canvas.getContext("webgl2", {
|
|
114
|
+
alpha: true,
|
|
115
|
+
antialias: true,
|
|
116
|
+
premultipliedAlpha: false,
|
|
117
|
+
...options
|
|
118
|
+
});
|
|
119
|
+
if (!gl) throw new Error("WebGL2 not supported");
|
|
120
|
+
this.gl = gl;
|
|
121
|
+
this.defaultVAO = gl.createVertexArray();
|
|
122
|
+
gl.enable(gl.DEPTH_TEST);
|
|
123
|
+
gl.depthFunc(gl.LEQUAL);
|
|
124
|
+
this.viewport = [0, 0, canvas.width, canvas.height];
|
|
125
|
+
this.scissor = [0, 0, canvas.width, canvas.height];
|
|
126
|
+
}
|
|
127
|
+
// ============================================================
|
|
128
|
+
// 着色器程序管理
|
|
129
|
+
// ============================================================
|
|
130
|
+
/** 从 ShaderConfig 加载程序 */
|
|
131
|
+
loadProgram(config, key) {
|
|
132
|
+
const cacheKey = key || config.id;
|
|
133
|
+
const existing = this.programs.get(cacheKey);
|
|
134
|
+
if (existing) return existing;
|
|
135
|
+
const program = createRuntimeProgram(this.gl, config);
|
|
136
|
+
this.programs.set(cacheKey, program);
|
|
137
|
+
return program;
|
|
138
|
+
}
|
|
139
|
+
/** 获取已缓存的程序 */
|
|
140
|
+
getProgram(key) {
|
|
141
|
+
return this.programs.get(key);
|
|
142
|
+
}
|
|
143
|
+
// ============================================================
|
|
144
|
+
// 纹理管理
|
|
145
|
+
// ============================================================
|
|
146
|
+
/** 加载纹理 (支持 URL 或 HTMLImageElement) */
|
|
147
|
+
loadTexture(url) {
|
|
148
|
+
const cached = this.textureCache.get(url);
|
|
149
|
+
if (cached) return Promise.resolve(cached);
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const img = new Image();
|
|
152
|
+
img.crossOrigin = "anonymous";
|
|
153
|
+
img.onload = () => {
|
|
154
|
+
const gl = this.gl;
|
|
155
|
+
const texture = gl.createTexture();
|
|
156
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
157
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
|
|
158
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
159
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
160
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
|
|
161
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
|
|
162
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
163
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
164
|
+
this.textureCache.set(url, texture);
|
|
165
|
+
resolve(texture);
|
|
166
|
+
};
|
|
167
|
+
img.onerror = () => reject(new Error(`Failed to load texture: ${url}`));
|
|
168
|
+
img.src = url;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/** 绑定纹理到 uniform */
|
|
172
|
+
bindTexture(uniformName, texture, unit) {
|
|
173
|
+
const gl = this.gl;
|
|
174
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
175
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
176
|
+
gl.uniform1i(this.gl.getUniformLocation(this.currentProgram.program, uniformName), unit);
|
|
177
|
+
}
|
|
178
|
+
// ============================================================
|
|
179
|
+
// 几何体管理
|
|
180
|
+
// ============================================================
|
|
181
|
+
/** 创建几何体 VAO */
|
|
182
|
+
createGeometry(geo) {
|
|
183
|
+
const gl = this.gl;
|
|
184
|
+
const vao = gl.createVertexArray();
|
|
185
|
+
gl.bindVertexArray(vao);
|
|
186
|
+
for (const [name, attr] of Object.entries(geo.attributes)) {
|
|
187
|
+
const buffer = gl.createBuffer();
|
|
188
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
189
|
+
gl.bufferData(gl.ARRAY_BUFFER, attr.data, gl.STATIC_DRAW);
|
|
190
|
+
const loc = parseInt(name.replace(/\D/g, ""), 10);
|
|
191
|
+
if (!isNaN(loc)) {
|
|
192
|
+
gl.enableVertexAttribArray(loc);
|
|
193
|
+
gl.vertexAttribPointer(loc, attr.size, gl.FLOAT, attr.normalized || false, attr.stride || 0, attr.offset || 0);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (geo.index) {
|
|
197
|
+
const buffer = gl.createBuffer();
|
|
198
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
|
|
199
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, geo.index, gl.STATIC_DRAW);
|
|
200
|
+
}
|
|
201
|
+
gl.bindVertexArray(null);
|
|
202
|
+
return vao;
|
|
203
|
+
}
|
|
204
|
+
// ============================================================
|
|
205
|
+
// 渲染
|
|
206
|
+
// ============================================================
|
|
207
|
+
/** 清屏 */
|
|
208
|
+
clear() {
|
|
209
|
+
const gl = this.gl;
|
|
210
|
+
const [r, g, b, a] = this.clearColor;
|
|
211
|
+
gl.clearColor(r, g, b, a);
|
|
212
|
+
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
213
|
+
}
|
|
214
|
+
/** 渲染单次绘制调用 */
|
|
215
|
+
drawMesh(mesh) {
|
|
216
|
+
const gl = this.gl;
|
|
217
|
+
const { program, geometry, uniforms, mode } = mesh;
|
|
218
|
+
if (this.currentProgram !== program) {
|
|
219
|
+
gl.useProgram(program.program);
|
|
220
|
+
this.currentProgram = program;
|
|
221
|
+
}
|
|
222
|
+
applyUniforms(gl, program, uniforms);
|
|
223
|
+
const vao = this.createGeometry(geometry);
|
|
224
|
+
gl.bindVertexArray(vao);
|
|
225
|
+
if (geometry.index) {
|
|
226
|
+
const indexType = geometry.index instanceof Uint32Array ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
227
|
+
gl.drawElements(mode || gl.TRIANGLES, geometry.index.length, indexType, 0);
|
|
228
|
+
} else {
|
|
229
|
+
gl.drawArrays(mode || gl.TRIANGLES, 0, geometry.vertexCount);
|
|
230
|
+
}
|
|
231
|
+
gl.bindVertexArray(null);
|
|
232
|
+
}
|
|
233
|
+
/** 调整大小 */
|
|
234
|
+
resize(width, height) {
|
|
235
|
+
this.canvas.width = width;
|
|
236
|
+
this.canvas.height = height;
|
|
237
|
+
this.viewport = [0, 0, width, height];
|
|
238
|
+
this.scissor = [0, 0, width, height];
|
|
239
|
+
this.gl.viewport(0, 0, width, height);
|
|
240
|
+
}
|
|
241
|
+
/** 销毁 */
|
|
242
|
+
dispose() {
|
|
243
|
+
for (const program of this.programs.values()) {
|
|
244
|
+
destroyRuntimeProgram(this.gl, program);
|
|
245
|
+
}
|
|
246
|
+
this.programs.clear();
|
|
247
|
+
for (const texture of this.textureCache.values()) {
|
|
248
|
+
this.gl.deleteTexture(texture);
|
|
249
|
+
}
|
|
250
|
+
this.textureCache.clear();
|
|
251
|
+
this.gl.bindVertexArray(null);
|
|
252
|
+
if (this.defaultVAO) {
|
|
253
|
+
this.gl.deleteVertexArray(this.defaultVAO);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// packages/runtime/src/ShaderGraphRuntime.ts
|
|
259
|
+
var ShaderGraphRuntime = class {
|
|
260
|
+
/** 底层渲染器 */
|
|
261
|
+
renderer;
|
|
262
|
+
/** WebGL 上下文 */
|
|
263
|
+
gl;
|
|
264
|
+
/** 画布 */
|
|
265
|
+
canvas;
|
|
266
|
+
/** 子图关联表: programKey → { subGraphId → ShaderConfig } */
|
|
267
|
+
subGraphLinks = /* @__PURE__ */ new Map();
|
|
268
|
+
animFrameId = null;
|
|
269
|
+
lastTime = 0;
|
|
270
|
+
frameCallback = null;
|
|
271
|
+
_isPlaying = false;
|
|
272
|
+
/**
|
|
273
|
+
* 构造运行时
|
|
274
|
+
*
|
|
275
|
+
* @param canvas - HTMLCanvasElement
|
|
276
|
+
* @param options - 运行时选项
|
|
277
|
+
*/
|
|
278
|
+
constructor(canvas, options) {
|
|
279
|
+
this.renderer = new RuntimeRenderer(canvas, options?.contextAttributes);
|
|
280
|
+
this.gl = this.renderer.gl;
|
|
281
|
+
this.canvas = canvas;
|
|
282
|
+
if (options?.clearColor) {
|
|
283
|
+
this.renderer.clearColor = options.clearColor;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// ============================================================
|
|
287
|
+
// 着色器管理
|
|
288
|
+
// ============================================================
|
|
289
|
+
/**
|
|
290
|
+
* 加载着色器配置
|
|
291
|
+
*
|
|
292
|
+
* @param config - 由编辑器编译产出的 ShaderConfig
|
|
293
|
+
* @param key - 缓存键 (默认使用 config.id)
|
|
294
|
+
* @returns 程序句柄
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* const program = runtime.load(shaderConfig);
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
load(config, key) {
|
|
302
|
+
return this.renderer.loadProgram(config, key);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* 获取已缓存的程序
|
|
306
|
+
*
|
|
307
|
+
* @param key - 缓存键
|
|
308
|
+
*/
|
|
309
|
+
get(key) {
|
|
310
|
+
return this.renderer.getProgram(key);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* 卸载着色器程序
|
|
314
|
+
*
|
|
315
|
+
* @param handle - 程序句柄
|
|
316
|
+
*/
|
|
317
|
+
unload(handle) {
|
|
318
|
+
this.gl.deleteProgram(handle.program);
|
|
319
|
+
this.subGraphLinks.delete(handle.config.id);
|
|
320
|
+
}
|
|
321
|
+
// ============================================================
|
|
322
|
+
// 子图关联
|
|
323
|
+
// ============================================================
|
|
324
|
+
/**
|
|
325
|
+
* 关联子图配置到主程序
|
|
326
|
+
*
|
|
327
|
+
* 子图在编译时已内联到主图的 vertCode/fragCode 中。
|
|
328
|
+
* 此方法将子图的 uniform 元数据注册到主程序,使运行时
|
|
329
|
+
* 能正确管理所有由子图引入的 uniform 变量。
|
|
330
|
+
*
|
|
331
|
+
* @param program - 主程序句柄 (由 load() 返回)
|
|
332
|
+
* @param subGraphId - 子图 ID (与主图 SubGraph 节点的 asset.id 对应)
|
|
333
|
+
* @param config - 子图的 ShaderConfig
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* const mainProg = runtime.load(mainConfig);
|
|
338
|
+
* runtime.linkSubGraph(mainProg, 'flowMap', flowMapSubConfig);
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
linkSubGraph(program, subGraphId, config) {
|
|
342
|
+
const key = program.config.id;
|
|
343
|
+
let links = this.subGraphLinks.get(key);
|
|
344
|
+
if (!links) {
|
|
345
|
+
links = /* @__PURE__ */ new Map();
|
|
346
|
+
this.subGraphLinks.set(key, links);
|
|
347
|
+
}
|
|
348
|
+
links.set(subGraphId, config);
|
|
349
|
+
if (config.uniforms && program.config.uniforms) {
|
|
350
|
+
const existingNames = new Set(program.config.uniforms.map((u) => u.name));
|
|
351
|
+
for (const u of config.uniforms) {
|
|
352
|
+
if (!existingNames.has(u.name)) {
|
|
353
|
+
program.config.uniforms.push(u);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* 批量关联子图
|
|
360
|
+
*
|
|
361
|
+
* @param program - 主程序句柄
|
|
362
|
+
* @param subGraphs - { [subGraphId]: ShaderConfig }
|
|
363
|
+
*/
|
|
364
|
+
linkSubGraphs(program, subGraphs) {
|
|
365
|
+
for (const [id, config] of Object.entries(subGraphs)) {
|
|
366
|
+
this.linkSubGraph(program, id, config);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* 加载主图并同时关联其子图
|
|
371
|
+
*
|
|
372
|
+
* 等价于依次调用 load() + linkSubGraphs()。
|
|
373
|
+
*
|
|
374
|
+
* @param config - 主图 ShaderConfig
|
|
375
|
+
* @param subGraphs - 子图配置表 { subGraphId: ShaderConfig }
|
|
376
|
+
* @param key - 缓存键 (可选)
|
|
377
|
+
* @returns 主程序句柄
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* ```ts
|
|
381
|
+
* const program = runtime.loadWithSubGraphs(mainConfig, {
|
|
382
|
+
* 'flowMap': flowMapSubConfig,
|
|
383
|
+
* 'noise': noiseSubConfig,
|
|
384
|
+
* });
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
387
|
+
loadWithSubGraphs(config, subGraphs, key) {
|
|
388
|
+
const program = this.load(config, key);
|
|
389
|
+
this.linkSubGraphs(program, subGraphs);
|
|
390
|
+
return program;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* 获取程序关联的子图列表
|
|
394
|
+
*
|
|
395
|
+
* @param program - 程序句柄
|
|
396
|
+
* @returns 子图 ID 列表
|
|
397
|
+
*/
|
|
398
|
+
getLinkedSubGraphs(program) {
|
|
399
|
+
const links = this.subGraphLinks.get(program.config.id);
|
|
400
|
+
return links ? Array.from(links.keys()) : [];
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* 获取程序关联的子图配置
|
|
404
|
+
*
|
|
405
|
+
* @param program - 程序句柄
|
|
406
|
+
* @param subGraphId - 子图 ID
|
|
407
|
+
* @returns ShaderConfig 或 undefined
|
|
408
|
+
*/
|
|
409
|
+
getSubGraphConfig(program, subGraphId) {
|
|
410
|
+
return this.subGraphLinks.get(program.config.id)?.get(subGraphId);
|
|
411
|
+
}
|
|
412
|
+
// ============================================================
|
|
413
|
+
// Uniform 控制
|
|
414
|
+
// ============================================================
|
|
415
|
+
/**
|
|
416
|
+
* 获取 Uniform 绑定器
|
|
417
|
+
*
|
|
418
|
+
* 提供链式 API 方便批量设置 uniform。
|
|
419
|
+
*
|
|
420
|
+
* @param handle - 程序句柄
|
|
421
|
+
* @returns UniformBinder
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* ```ts
|
|
425
|
+
* runtime.uniforms(program)
|
|
426
|
+
* .set('uColor', [1, 0, 0, 1])
|
|
427
|
+
* .set('uTime', 0.5)
|
|
428
|
+
* .texture('uMainTex', img)
|
|
429
|
+
* .commit();
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
uniforms(handle) {
|
|
433
|
+
const gl = this.gl;
|
|
434
|
+
const deferred = [];
|
|
435
|
+
const binder = {
|
|
436
|
+
set(name, value) {
|
|
437
|
+
deferred.push(() => {
|
|
438
|
+
const loc = handle.uniforms[name];
|
|
439
|
+
if (!loc) return;
|
|
440
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
441
|
+
switch (arr.length) {
|
|
442
|
+
case 1:
|
|
443
|
+
gl.uniform1f(loc, arr[0]);
|
|
444
|
+
break;
|
|
445
|
+
case 2:
|
|
446
|
+
gl.uniform2f(loc, arr[0], arr[1]);
|
|
447
|
+
break;
|
|
448
|
+
case 3:
|
|
449
|
+
gl.uniform3f(loc, arr[0], arr[1], arr[2]);
|
|
450
|
+
break;
|
|
451
|
+
case 4:
|
|
452
|
+
gl.uniform4f(loc, arr[0], arr[1], arr[2], arr[3]);
|
|
453
|
+
break;
|
|
454
|
+
default:
|
|
455
|
+
;
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
return binder;
|
|
459
|
+
},
|
|
460
|
+
setMany(values) {
|
|
461
|
+
for (const [k, v] of Object.entries(values)) {
|
|
462
|
+
if (v == null) continue;
|
|
463
|
+
deferred.push(() => {
|
|
464
|
+
const loc = handle.uniforms[k];
|
|
465
|
+
if (!loc) return;
|
|
466
|
+
const arr = Array.isArray(v) ? v : [v];
|
|
467
|
+
switch (arr.length) {
|
|
468
|
+
case 1:
|
|
469
|
+
gl.uniform1f(loc, arr[0]);
|
|
470
|
+
break;
|
|
471
|
+
case 2:
|
|
472
|
+
gl.uniform2f(loc, arr[0], arr[1]);
|
|
473
|
+
break;
|
|
474
|
+
case 3:
|
|
475
|
+
gl.uniform3f(loc, arr[0], arr[1], arr[2]);
|
|
476
|
+
break;
|
|
477
|
+
case 4:
|
|
478
|
+
gl.uniform4f(loc, arr[0], arr[1], arr[2], arr[3]);
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return binder;
|
|
484
|
+
},
|
|
485
|
+
texture(_name, _source, _unit) {
|
|
486
|
+
return binder;
|
|
487
|
+
},
|
|
488
|
+
commit() {
|
|
489
|
+
gl.useProgram(handle.program);
|
|
490
|
+
for (const fn of deferred) fn();
|
|
491
|
+
deferred.length = 0;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
return binder;
|
|
495
|
+
}
|
|
496
|
+
// ============================================================
|
|
497
|
+
// 渲染
|
|
498
|
+
// ============================================================
|
|
499
|
+
/**
|
|
500
|
+
* 清除缓冲区
|
|
501
|
+
*
|
|
502
|
+
* 先应用设置的清除颜色,再清除缓冲区。
|
|
503
|
+
*
|
|
504
|
+
* @param mask - 清除掩码 (默认 gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
|
505
|
+
*/
|
|
506
|
+
clear(mask) {
|
|
507
|
+
const gl = this.gl;
|
|
508
|
+
const cc = this.renderer.clearColor;
|
|
509
|
+
gl.clearColor(cc[0], cc[1], cc[2], cc[3]);
|
|
510
|
+
const m = mask ?? gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT;
|
|
511
|
+
gl.clear(m);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* 设置清除颜色
|
|
515
|
+
*/
|
|
516
|
+
setClearColor(r, g, b, a) {
|
|
517
|
+
this.renderer.clearColor = [r, g, b, a];
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* 绘制网格
|
|
521
|
+
*
|
|
522
|
+
* @param mesh - 网格渲染描述
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* ```ts
|
|
526
|
+
* runtime.draw({
|
|
527
|
+
* geometry: { attributes: { aPosition: { data, size: 3 } }, vertexCount: 3 },
|
|
528
|
+
* program: myProgram,
|
|
529
|
+
* uniforms: { uColor: [1, 0, 0, 1] },
|
|
530
|
+
* });
|
|
531
|
+
* ```
|
|
532
|
+
*/
|
|
533
|
+
draw(mesh) {
|
|
534
|
+
const { geometry, program, uniforms, mode } = mesh;
|
|
535
|
+
this.uniforms(program).setMany(uniforms || {}).commit();
|
|
536
|
+
const gl = this.gl;
|
|
537
|
+
gl.useProgram(program.program);
|
|
538
|
+
const vao = gl.createVertexArray();
|
|
539
|
+
gl.bindVertexArray(vao);
|
|
540
|
+
for (const [name, attrib] of Object.entries(geometry.attributes)) {
|
|
541
|
+
const loc = program.attribs[name];
|
|
542
|
+
if (loc === void 0) continue;
|
|
543
|
+
const buffer = gl.createBuffer();
|
|
544
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
545
|
+
gl.bufferData(gl.ARRAY_BUFFER, attrib.data, gl.STATIC_DRAW);
|
|
546
|
+
gl.enableVertexAttribArray(loc);
|
|
547
|
+
gl.vertexAttribPointer(loc, attrib.size, gl.FLOAT, attrib.normalized ?? false, attrib.stride ?? 0, attrib.offset ?? 0);
|
|
548
|
+
}
|
|
549
|
+
if (geometry.index) {
|
|
550
|
+
const idxBuffer = gl.createBuffer();
|
|
551
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, idxBuffer);
|
|
552
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, geometry.index, gl.STATIC_DRAW);
|
|
553
|
+
const idxType = geometry.index instanceof Uint32Array ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
554
|
+
gl.drawElements(mode ?? gl.TRIANGLES, geometry.index.length, idxType, 0);
|
|
555
|
+
} else {
|
|
556
|
+
gl.drawArrays(mode ?? gl.TRIANGLES, 0, geometry.vertexCount);
|
|
557
|
+
}
|
|
558
|
+
gl.bindVertexArray(null);
|
|
559
|
+
gl.deleteVertexArray(vao);
|
|
560
|
+
}
|
|
561
|
+
// ============================================================
|
|
562
|
+
// 动画循环
|
|
563
|
+
// ============================================================
|
|
564
|
+
/**
|
|
565
|
+
* 开始动画循环
|
|
566
|
+
*
|
|
567
|
+
* @param callback - 每帧回调 (time, delta)
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* ```ts
|
|
571
|
+
* runtime.play((time, delta) => {
|
|
572
|
+
* runtime.uniforms(program).set('uTime', time).commit();
|
|
573
|
+
* runtime.draw(mesh);
|
|
574
|
+
* });
|
|
575
|
+
* ```
|
|
576
|
+
*/
|
|
577
|
+
play(callback) {
|
|
578
|
+
if (this._isPlaying) return;
|
|
579
|
+
this._isPlaying = true;
|
|
580
|
+
this.lastTime = performance.now();
|
|
581
|
+
this.frameCallback = callback ?? null;
|
|
582
|
+
const tick = (now) => {
|
|
583
|
+
if (!this._isPlaying) return;
|
|
584
|
+
const delta = (now - this.lastTime) / 1e3;
|
|
585
|
+
this.lastTime = now;
|
|
586
|
+
this.clear();
|
|
587
|
+
this.frameCallback?.(now / 1e3, delta);
|
|
588
|
+
this.animFrameId = requestAnimationFrame(tick);
|
|
589
|
+
};
|
|
590
|
+
this.animFrameId = requestAnimationFrame(tick);
|
|
591
|
+
}
|
|
592
|
+
/** 是否正在播放动画 */
|
|
593
|
+
get isPlaying() {
|
|
594
|
+
return this._isPlaying;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* 停止动画循环
|
|
598
|
+
*/
|
|
599
|
+
stop() {
|
|
600
|
+
this._isPlaying = false;
|
|
601
|
+
if (this.animFrameId !== null) {
|
|
602
|
+
cancelAnimationFrame(this.animFrameId);
|
|
603
|
+
this.animFrameId = null;
|
|
604
|
+
}
|
|
605
|
+
this.frameCallback = null;
|
|
606
|
+
}
|
|
607
|
+
// ============================================================
|
|
608
|
+
// 尺寸调整
|
|
609
|
+
// ============================================================
|
|
610
|
+
/**
|
|
611
|
+
* 调整画布尺寸 (自动同步 viewport)
|
|
612
|
+
*
|
|
613
|
+
* @param width - 像素宽度
|
|
614
|
+
* @param height - 像素高度
|
|
615
|
+
*/
|
|
616
|
+
resize(width, height) {
|
|
617
|
+
this.canvas.width = width;
|
|
618
|
+
this.canvas.height = height;
|
|
619
|
+
this.renderer.viewport = [0, 0, width, height];
|
|
620
|
+
this.gl.viewport(0, 0, width, height);
|
|
621
|
+
}
|
|
622
|
+
// ============================================================
|
|
623
|
+
// 资源清理
|
|
624
|
+
// ============================================================
|
|
625
|
+
/**
|
|
626
|
+
* 释放所有资源
|
|
627
|
+
*
|
|
628
|
+
* 清理着色器程序、纹理、停止动画。
|
|
629
|
+
* 调用后不可再使用此实例。
|
|
630
|
+
*/
|
|
631
|
+
dispose() {
|
|
632
|
+
this.stop();
|
|
633
|
+
const gl = this.gl;
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
export {
|
|
637
|
+
ShaderGraphRuntime
|
|
638
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xifu/shader-graph-glsl",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shader Graph GLSL — visual node-based shader editor & runtime engine",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
"./runtime": {
|
|
13
|
+
"types": "./dist/runtime/index.d.ts",
|
|
14
|
+
"import": "./dist/runtime/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./editor": {
|
|
17
|
+
"types": "./dist/editor/index.d.ts",
|
|
18
|
+
"import": "./dist/editor/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "vite",
|
|
23
|
+
"dev:docs": "pnpm --filter @xifu/shader-graph-glsl-docs dev",
|
|
24
|
+
"build": "tsc && vite build",
|
|
25
|
+
"build:npm": "tsup",
|
|
26
|
+
"build:editor": "vite build --config vite.config.editor.ts && pnpm -s exec node -e \"require('fs').renameSync('apps/docs/public/editor/editor.html','apps/docs/public/editor/index.html')\"",
|
|
27
|
+
"build:docs": "pnpm build:editor && pnpm --filter @xifu/shader-graph-glsl-docs build",
|
|
28
|
+
"preview": "vite preview",
|
|
29
|
+
"preview:docs": "pnpm --filter @xifu/shader-graph-glsl-docs preview",
|
|
30
|
+
"prepublishOnly": "pnpm build:npm"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"node material",
|
|
34
|
+
"shader graph",
|
|
35
|
+
"glsl",
|
|
36
|
+
"webgl2",
|
|
37
|
+
"node-editor",
|
|
38
|
+
"visual-programming"
|
|
39
|
+
],
|
|
40
|
+
"author": "928200728@qq.com",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"copy-to-clipboard": "3.3.3",
|
|
44
|
+
"react": "^18.2.0",
|
|
45
|
+
"react-dom": "^18.2.0",
|
|
46
|
+
"rete-area-plugin": "^0.2.1",
|
|
47
|
+
"three": "^0.151.3",
|
|
48
|
+
"use-debounce": "^9.0.4"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/react": "^18",
|
|
52
|
+
"@types/react-dom": "^18",
|
|
53
|
+
"@types/three": "^0.150.2",
|
|
54
|
+
"@vitejs/plugin-react": "^3.1.0",
|
|
55
|
+
"less": "^4.1.3",
|
|
56
|
+
"tsup": "^8.5.1",
|
|
57
|
+
"typescript": "^4.9.3",
|
|
58
|
+
"vite": "^4.5.2"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"monaco-editor": "^0.45.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"monaco-editor": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
}
|
|
71
|
+
}
|