@multiplekex/shallot 0.1.12 → 0.2.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.
Files changed (62) hide show
  1. package/package.json +3 -4
  2. package/src/core/builder.ts +71 -32
  3. package/src/core/component.ts +25 -11
  4. package/src/core/index.ts +14 -13
  5. package/src/core/math.ts +135 -0
  6. package/src/core/runtime.ts +0 -1
  7. package/src/core/state.ts +9 -68
  8. package/src/core/xml.ts +381 -265
  9. package/src/editor/format.ts +5 -0
  10. package/src/editor/index.ts +101 -0
  11. package/src/extras/arrows/index.ts +28 -69
  12. package/src/extras/gradient/index.ts +36 -52
  13. package/src/extras/lines/index.ts +51 -122
  14. package/src/extras/orbit/index.ts +40 -15
  15. package/src/extras/text/font.ts +546 -0
  16. package/src/extras/text/index.ts +158 -204
  17. package/src/extras/text/sdf.ts +429 -0
  18. package/src/standard/activity/index.ts +172 -0
  19. package/src/standard/compute/graph.ts +23 -23
  20. package/src/standard/compute/index.ts +76 -61
  21. package/src/standard/defaults.ts +8 -5
  22. package/src/standard/index.ts +1 -0
  23. package/src/standard/input/index.ts +30 -19
  24. package/src/standard/loading/index.ts +18 -13
  25. package/src/standard/render/bvh/blas.ts +752 -0
  26. package/src/standard/render/bvh/radix.ts +476 -0
  27. package/src/standard/render/bvh/structs.ts +167 -0
  28. package/src/standard/render/bvh/tlas.ts +886 -0
  29. package/src/standard/render/bvh/traverse.ts +467 -0
  30. package/src/standard/render/camera.ts +302 -27
  31. package/src/standard/render/data.ts +93 -0
  32. package/src/standard/render/depth.ts +117 -0
  33. package/src/standard/render/forward/index.ts +259 -0
  34. package/src/standard/render/forward/raster.ts +228 -0
  35. package/src/standard/render/index.ts +443 -70
  36. package/src/standard/render/indirect.ts +40 -0
  37. package/src/standard/render/instance.ts +214 -0
  38. package/src/standard/render/intersection.ts +72 -0
  39. package/src/standard/render/light.ts +16 -16
  40. package/src/standard/render/mesh/index.ts +67 -75
  41. package/src/standard/render/mesh/unified.ts +96 -0
  42. package/src/standard/render/{transparent.ts → overlay.ts} +14 -15
  43. package/src/standard/render/pass.ts +10 -4
  44. package/src/standard/render/postprocess.ts +142 -64
  45. package/src/standard/render/ray.ts +61 -0
  46. package/src/standard/render/scene.ts +38 -164
  47. package/src/standard/render/shaders.ts +484 -0
  48. package/src/standard/render/surface/compile.ts +3 -10
  49. package/src/standard/render/surface/index.ts +60 -30
  50. package/src/standard/render/surface/noise.ts +45 -0
  51. package/src/standard/render/surface/structs.ts +60 -19
  52. package/src/standard/render/surface/wgsl.ts +573 -0
  53. package/src/standard/render/triangle.ts +84 -0
  54. package/src/standard/transforms/index.ts +4 -6
  55. package/src/standard/tween/index.ts +10 -1
  56. package/src/standard/tween/sequence.ts +24 -16
  57. package/src/standard/tween/tween.ts +67 -16
  58. package/src/core/types.ts +0 -37
  59. package/src/standard/compute/inspect.ts +0 -201
  60. package/src/standard/compute/pass.ts +0 -23
  61. package/src/standard/compute/timing.ts +0 -139
  62. package/src/standard/render/forward.ts +0 -273
@@ -0,0 +1,484 @@
1
+ import type { SurfaceData } from "./surface";
2
+ import {
3
+ compileVertexBody,
4
+ compileApplyLighting,
5
+ reflectionWgsl,
6
+ SHADOW_WGSL,
7
+ SPECULAR_WGSL,
8
+ REFRACTION_WGSL,
9
+ SKY_DIR_WGSL,
10
+ SKY_WGSL,
11
+ HAZE_WGSL,
12
+ } from "./surface/wgsl";
13
+ import {
14
+ BVH_STRUCTS,
15
+ TLAS_BLAS_STRUCTS,
16
+ TLAS_BLAS_BINDINGS,
17
+ TLAS_BLAS_TRAVERSAL,
18
+ TLAS_BLAS_ANY_HIT,
19
+ } from "./bvh/traverse";
20
+ import { RAY_STRUCT_WGSL, HIT_RESULT_STRUCT_WGSL } from "./bvh/structs";
21
+ import { SURFACE_DATA_STRUCT_WGSL, SCENE_STRUCT_WGSL, DATA_STRUCT_WGSL } from "./surface/structs";
22
+
23
+ export { SCENE_STRUCT_WGSL };
24
+
25
+ const EPSILON = 1e-7;
26
+
27
+ export const RT_STRUCTS = /* wgsl */ `
28
+ ${RAY_STRUCT_WGSL}
29
+ ${HIT_RESULT_STRUCT_WGSL}
30
+ ${SURFACE_DATA_STRUCT_WGSL}
31
+ ${SCENE_STRUCT_WGSL}
32
+ ${DATA_STRUCT_WGSL}
33
+ `;
34
+
35
+ export const RT_BINDINGS = /* wgsl */ `
36
+ @group(0) @binding(0) var<uniform> scene: Scene;
37
+ @group(0) @binding(1) var<storage, read> data: array<Data>;
38
+ @group(0) @binding(2) var output_scene: texture_storage_2d<rgba8unorm, write>;
39
+ @group(0) @binding(3) var output_depth: texture_storage_2d<r32float, write>;
40
+ @group(0) @binding(4) var output_entityId: texture_storage_2d<r32uint, write>;
41
+ `;
42
+
43
+ export const RT_UTILS = /* wgsl */ `
44
+ fn getData(eid: u32) -> Data {
45
+ return data[eid];
46
+ }
47
+
48
+ fn getSurfaceType(eid: u32) -> u32 {
49
+ return data[eid].flags & 0xFFu;
50
+ }
51
+
52
+ fn getVolume(eid: u32) -> u32 {
53
+ return (data[eid].flags >> 8u) & 0xFu;
54
+ }
55
+
56
+ fn getShapeId(eid: u32) -> u32 {
57
+ return data[eid].flags >> 16u;
58
+ }
59
+
60
+ fn getInstanceCount() -> u32 {
61
+ return scene.instanceCount;
62
+ }
63
+ `;
64
+
65
+ export const RT_RAY_GEN = /* wgsl */ `
66
+ ${SKY_DIR_WGSL}
67
+
68
+ struct PrimaryRay {
69
+ origin: vec3<f32>,
70
+ direction: vec3<f32>,
71
+ skyDir: vec3<f32>,
72
+ }
73
+
74
+ fn generateRay(screenX: f32, screenY: f32) -> PrimaryRay {
75
+ var result: PrimaryRay;
76
+ result.skyDir = computeSkyDir(screenX, screenY);
77
+
78
+ let width = scene.viewport.x;
79
+ let height = scene.viewport.y;
80
+ let ndcX = screenX * 2.0 - 1.0;
81
+ let ndcY = 1.0 - screenY * 2.0;
82
+ let aspect = width / height;
83
+
84
+ let cameraWorld = scene.cameraWorld;
85
+ let camPosX = cameraWorld[3][0];
86
+ let camPosY = cameraWorld[3][1];
87
+ let camPosZ = cameraWorld[3][2];
88
+
89
+ if (scene.cameraMode > 0.5) {
90
+ let r00 = cameraWorld[0][0]; let r10 = cameraWorld[0][1]; let r20 = cameraWorld[0][2];
91
+ let r01 = cameraWorld[1][0]; let r11 = cameraWorld[1][1]; let r21 = cameraWorld[1][2];
92
+ let r02 = cameraWorld[2][0]; let r12 = cameraWorld[2][1]; let r22 = cameraWorld[2][2];
93
+
94
+ let halfHeight = scene.cameraSize;
95
+ let halfWidth = halfHeight * aspect;
96
+ let offsetX = ndcX * halfWidth;
97
+ let offsetY = ndcY * halfHeight;
98
+ let fwdX = -r02; let fwdY = -r12; let fwdZ = -r22;
99
+
100
+ result.origin = vec3(
101
+ camPosX + r00 * offsetX + r01 * offsetY + fwdX * scene.near,
102
+ camPosY + r10 * offsetX + r11 * offsetY + fwdY * scene.near,
103
+ camPosZ + r20 * offsetX + r21 * offsetY + fwdZ * scene.near
104
+ );
105
+ result.direction = vec3(fwdX, fwdY, fwdZ);
106
+ } else {
107
+ let dir = result.skyDir;
108
+ result.origin = vec3(camPosX + dir.x * scene.near, camPosY + dir.y * scene.near, camPosZ + dir.z * scene.near);
109
+ result.direction = dir;
110
+ }
111
+
112
+ return result;
113
+ }
114
+ `;
115
+
116
+ export const RT_INTERSECTION = /* wgsl */ `
117
+ const EPSILON: f32 = ${EPSILON};
118
+ `;
119
+
120
+ export function compileRTSurface(data: SurfaceData): string {
121
+ return compileUberShader([data]);
122
+ }
123
+
124
+ function compileSurfaceVariant(id: number, data: SurfaceData): string {
125
+ const vertexBody = compileVertexBody(data.vertex);
126
+ const fragmentBody = data.fragment ?? "";
127
+ const lightingCode = compileApplyLighting(data.lit, true, true);
128
+
129
+ return `
130
+ fn userVertexTransform_${id}(worldPos: vec3<f32>, normal: vec3<f32>, eid: u32) -> vec3<f32> {
131
+ ${vertexBody}
132
+ }
133
+
134
+ fn userFragment_${id}(surface: ptr<function, SurfaceData>, position: vec4<f32>) {
135
+ ${fragmentBody}
136
+ }
137
+
138
+ fn applyLighting_${id}(surface: SurfaceData, rayDir: vec3<f32>) -> vec3<f32> {
139
+ ${lightingCode}
140
+ }
141
+ `;
142
+ }
143
+
144
+ function compileDispatchFunctions(surfaceCount: number): string {
145
+ const vertexCases = Array.from(
146
+ { length: surfaceCount },
147
+ (_, i) => ` case ${i}u: { return userVertexTransform_${i}(worldPos, normal, eid); }`
148
+ ).join("\n");
149
+
150
+ const fragmentCases = Array.from(
151
+ { length: surfaceCount },
152
+ (_, i) => ` case ${i}u: { userFragment_${i}(surface, position); }`
153
+ ).join("\n");
154
+
155
+ const lightingCases = Array.from(
156
+ { length: surfaceCount },
157
+ (_, i) => ` case ${i}u: { return applyLighting_${i}(surface, rayDir); }`
158
+ ).join("\n");
159
+
160
+ return `
161
+ fn dispatchVertexTransform(surfaceId: u32, worldPos: vec3<f32>, normal: vec3<f32>, eid: u32) -> vec3<f32> {
162
+ switch surfaceId {
163
+ ${vertexCases}
164
+ default: { return userVertexTransform_0(worldPos, normal, eid); }
165
+ }
166
+ }
167
+
168
+ fn dispatchFragment(surfaceId: u32, surface: ptr<function, SurfaceData>, position: vec4<f32>) {
169
+ switch surfaceId {
170
+ ${fragmentCases}
171
+ default: { userFragment_0(surface, position); }
172
+ }
173
+ }
174
+
175
+ fn dispatchLighting(surfaceId: u32, surface: SurfaceData, rayDir: vec3<f32>) -> vec3<f32> {
176
+ switch surfaceId {
177
+ ${lightingCases}
178
+ default: { return applyLighting_0(surface, rayDir); }
179
+ }
180
+ }
181
+ `;
182
+ }
183
+
184
+ export function compileUberShader(
185
+ surfaces: SurfaceData[],
186
+ _shadows: boolean = true,
187
+ _reflections: boolean = true,
188
+ _refractions: boolean = true
189
+ ): string {
190
+ const surfaceVariants = surfaces.map((s, i) => compileSurfaceVariant(i, s)).join("\n");
191
+ const dispatchFunctions = compileDispatchFunctions(surfaces.length);
192
+
193
+ return /* wgsl */ `
194
+ ${RT_STRUCTS}
195
+ ${BVH_STRUCTS}
196
+ ${TLAS_BLAS_STRUCTS}
197
+ ${RT_BINDINGS}
198
+ ${RT_UTILS}
199
+ ${TLAS_BLAS_BINDINGS}
200
+ ${RT_RAY_GEN}
201
+ ${RT_INTERSECTION}
202
+ ${TLAS_BLAS_TRAVERSAL}
203
+ ${TLAS_BLAS_ANY_HIT}
204
+ ${SHADOW_WGSL}
205
+ ${SPECULAR_WGSL}
206
+ ${REFRACTION_WGSL}
207
+ ${SKY_WGSL}
208
+ ${HAZE_WGSL}
209
+ ${reflectionWgsl(true)}
210
+
211
+ ${surfaceVariants}
212
+ ${dispatchFunctions}
213
+
214
+ const MAX_TRANSPARENT_DEPTH: u32 = 4u;
215
+ const TRANSPARENCY_EPSILON: f32 = 0.001;
216
+ const MAX_REFRACTION_DEPTH: u32 = 2u;
217
+ const REFRACTION_EPSILON: f32 = 0.001;
218
+
219
+ ${compileRefractionTracer(true)}
220
+
221
+ @compute @workgroup_size(8, 8)
222
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
223
+ let width = u32(scene.viewport.x);
224
+ let height = u32(scene.viewport.y);
225
+
226
+ if (gid.x >= width || gid.y >= height) {
227
+ return;
228
+ }
229
+
230
+ let screenX = (f32(gid.x) + 0.5) / f32(width);
231
+ let screenY = (f32(gid.y) + 0.5) / f32(height);
232
+
233
+ let primary = generateRay(screenX, screenY);
234
+ var ray: Ray;
235
+ ray.origin = primary.origin;
236
+ ray.direction = primary.direction;
237
+
238
+ var finalColor = vec3(0.0);
239
+ var remainingOpacity = 1.0;
240
+ var depth = 1e30;
241
+ var entityId = 0u;
242
+ var firstHit = true;
243
+
244
+ for (var i = 0u; i < MAX_TRANSPARENT_DEPTH; i++) {
245
+ let hit = trace(ray);
246
+
247
+ if (!hit.hit) {
248
+ finalColor += sampleSky(primary.skyDir) * remainingOpacity;
249
+ break;
250
+ }
251
+
252
+ let eid = hit.entityId;
253
+ let d = getData(eid);
254
+ let opacity = d.baseColor.a;
255
+ let surfaceId = getSurfaceType(eid);
256
+
257
+ if (firstHit) {
258
+ depth = hit.t;
259
+ entityId = eid;
260
+ firstHit = false;
261
+ }
262
+
263
+ let finalWorldPos = dispatchVertexTransform(surfaceId, hit.worldPos, hit.normal, eid);
264
+
265
+ var surface: SurfaceData;
266
+ surface.baseColor = d.baseColor.rgb;
267
+ surface.roughness = d.pbr.x;
268
+ surface.metallic = d.pbr.y;
269
+ surface.opacity = opacity;
270
+ surface.emission = d.emission.rgb * d.emission.a;
271
+ surface.normal = hit.normal;
272
+ surface.worldPos = finalWorldPos;
273
+
274
+ dispatchFragment(surfaceId, &surface, vec4(f32(gid.x), f32(gid.y), hit.t, 1.0));
275
+
276
+ ${compileRefractionBranch()}
277
+ let litColor = dispatchLighting(surfaceId, surface, primary.direction);
278
+ let hazeColor = applyHaze(litColor, hit.t);
279
+
280
+ let contribution = opacity * remainingOpacity;
281
+ finalColor += hazeColor * contribution;
282
+
283
+ if (opacity >= 1.0) {
284
+ break;
285
+ }
286
+
287
+ remainingOpacity *= (1.0 - opacity);
288
+ if (remainingOpacity < 0.02) {
289
+ break;
290
+ }
291
+
292
+ ray.origin = hit.worldPos + ray.direction * TRANSPARENCY_EPSILON;
293
+ }
294
+
295
+ textureStore(output_scene, vec2<i32>(gid.xy), vec4(finalColor, 1.0));
296
+ textureStore(output_depth, vec2<i32>(gid.xy), vec4(depth, 0.0, 0.0, 0.0));
297
+ textureStore(output_entityId, vec2<i32>(gid.xy), vec4(entityId, 0u, 0u, 0u));
298
+ }
299
+ `;
300
+ }
301
+
302
+ function compileRefractionTracer(_shadows: boolean = true): string {
303
+ const shadowCheck = `var hitShadow = 1.0;
304
+ if (hitNdotL > 0.0 && scene.shadowSamples > 0u) {
305
+ var shadowRay: Ray;
306
+ shadowRay.origin = refHit.worldPos + refHit.normal * REFRACTION_EPSILON;
307
+ shadowRay.direction = hitL;
308
+ if (traceAnyHit(shadowRay, 1000.0)) { hitShadow = 0.0; }
309
+ }`;
310
+
311
+ return /* wgsl */ `
312
+ const VOLUME_SOLID: u32 = 0u;
313
+ const VOLUME_HALF_SPACE: u32 = 1u;
314
+
315
+ fn traceRefraction(
316
+ startPos: vec3<f32>,
317
+ startNormal: vec3<f32>,
318
+ rayDir: vec3<f32>,
319
+ ior: f32,
320
+ mediumColor: vec3<f32>,
321
+ volumeType: u32,
322
+ maxBounces: u32
323
+ ) -> vec3<f32> {
324
+ var currentPos = startPos;
325
+ var currentNormal = startNormal;
326
+ var currentDir = rayDir;
327
+ var currentIOR = ior;
328
+ var currentMediumColor = mediumColor;
329
+ var totalDistance = 0.0;
330
+ var inMedium = true;
331
+ let isHalfSpace = volumeType == VOLUME_HALF_SPACE;
332
+
333
+ for (var bounce = 0u; bounce < maxBounces; bounce++) {
334
+ let entering = dot(currentDir, currentNormal) < 0.0;
335
+ let n = select(-currentNormal, currentNormal, entering);
336
+ let n1 = select(currentIOR, 1.0, entering);
337
+ let n2 = select(1.0, currentIOR, entering);
338
+ let eta = n1 / n2;
339
+
340
+ let refractResult = refractRay(currentDir, n, eta);
341
+ let isTIR = refractResult.w > 0.5;
342
+
343
+ var refractRay_: Ray;
344
+ refractRay_.direction = refractResult.xyz;
345
+ refractRay_.origin = select(
346
+ currentPos - n * REFRACTION_EPSILON,
347
+ currentPos + n * REFRACTION_EPSILON,
348
+ isTIR
349
+ );
350
+
351
+ let refHit = trace(refractRay_);
352
+ if (!refHit.hit) {
353
+ if (isHalfSpace && inMedium) {
354
+ let depthFade = exp(-totalDistance * 0.1);
355
+ return currentMediumColor * depthFade * scene.ambientColor.rgb * scene.ambientColor.a;
356
+ }
357
+ var skyColor = sampleSky(refractRay_.direction);
358
+ if (inMedium && totalDistance > 0.0) {
359
+ let absorption = exp(-totalDistance * 0.5);
360
+ skyColor = mix(skyColor * currentMediumColor, skyColor, absorption);
361
+ }
362
+ return skyColor;
363
+ }
364
+
365
+ if (inMedium) {
366
+ totalDistance += refHit.t;
367
+ }
368
+
369
+ let refEid = refHit.entityId;
370
+ let refracted = getData(refEid);
371
+ let refOpacity = refracted.baseColor.a;
372
+ let refIOR = refracted.pbr.z;
373
+
374
+ if (refIOR <= 1.0) {
375
+ let hitBaseColor = refracted.baseColor.rgb;
376
+ let hitMetallic = refracted.pbr.y;
377
+ let hitEmission = refracted.emission.rgb * refracted.emission.a;
378
+
379
+ let hitL = -scene.sunDirection.xyz;
380
+ let hitNdotL = max(dot(refHit.normal, hitL), 0.0);
381
+ ${shadowCheck}
382
+
383
+ let hitAmbient = scene.ambientColor.rgb * scene.ambientColor.a;
384
+ let hitSun = scene.sunColor.rgb * hitNdotL * hitShadow;
385
+ var hitDiffuse = hitBaseColor * (hitAmbient + hitSun) * (1.0 - hitMetallic);
386
+
387
+ if (totalDistance > 0.0) {
388
+ let absorption = exp(-totalDistance * 0.5);
389
+ hitDiffuse = mix(hitDiffuse * currentMediumColor, hitDiffuse, absorption);
390
+ }
391
+
392
+ var surfaceColor = applyHaze(hitDiffuse + hitEmission, refHit.t);
393
+
394
+ if (refOpacity >= 1.0) {
395
+ return surfaceColor;
396
+ }
397
+
398
+ var behindRay: Ray;
399
+ behindRay.origin = refHit.worldPos + refractRay_.direction * REFRACTION_EPSILON;
400
+ behindRay.direction = refractRay_.direction;
401
+ let behindHit = trace(behindRay);
402
+ var behindColor = sampleSky(refractRay_.direction);
403
+ if (behindHit.hit) {
404
+ let behind = getData(behindHit.entityId);
405
+ let behindBaseColor = behind.baseColor.rgb;
406
+ let behindMetallic = behind.pbr.y;
407
+ let behindEmission = behind.emission.rgb * behind.emission.a;
408
+ let behindNdotL = max(dot(behindHit.normal, hitL), 0.0);
409
+ let behindAmbient = scene.ambientColor.rgb * scene.ambientColor.a;
410
+ let behindSun = scene.sunColor.rgb * behindNdotL;
411
+ behindColor = applyHaze(behindBaseColor * (behindAmbient + behindSun) * (1.0 - behindMetallic) + behindEmission, behindHit.t);
412
+ }
413
+
414
+ return surfaceColor * refOpacity + behindColor * (1.0 - refOpacity);
415
+ }
416
+
417
+ if (!entering) {
418
+ inMedium = false;
419
+ } else {
420
+ inMedium = true;
421
+ currentMediumColor = refracted.baseColor.rgb;
422
+ totalDistance = 0.0;
423
+ }
424
+
425
+ currentPos = refHit.worldPos;
426
+ currentNormal = refHit.normal;
427
+ currentDir = refractRay_.direction;
428
+ currentIOR = refIOR;
429
+ }
430
+
431
+ if (isHalfSpace && inMedium) {
432
+ let depthFade = exp(-totalDistance * 0.1);
433
+ return currentMediumColor * depthFade * scene.ambientColor.rgb * scene.ambientColor.a;
434
+ }
435
+ var skyColor = sampleSky(currentDir);
436
+ if (inMedium && totalDistance > 0.0) {
437
+ let absorption = exp(-totalDistance * 0.5);
438
+ skyColor = mix(skyColor * currentMediumColor, skyColor, absorption);
439
+ }
440
+ return skyColor;
441
+ }
442
+ `;
443
+ }
444
+
445
+ function compileRefractionBranch(): string {
446
+ return /* wgsl */ `
447
+ let ior = d.pbr.z;
448
+ if (ior > 1.0 && scene.refractionDepth > 0u) {
449
+ let entering = dot(ray.direction, surface.normal) < 0.0;
450
+ let n = select(-surface.normal, surface.normal, entering);
451
+ let n1 = select(ior, 1.0, entering);
452
+ let n2 = select(1.0, ior, entering);
453
+ let cosI = abs(dot(ray.direction, n));
454
+ let fresnel = fresnelSchlickIOR(cosI, n1, n2);
455
+
456
+ let volumeType = getVolume(eid);
457
+ let refractedColor = traceRefraction(
458
+ surface.worldPos, surface.normal, ray.direction, ior,
459
+ surface.baseColor, volumeType, scene.refractionDepth
460
+ );
461
+
462
+ let contribution = opacity * remainingOpacity;
463
+ let refractContrib = (1.0 - fresnel) * contribution;
464
+ finalColor += applyHaze(refractedColor, hit.t) * refractContrib;
465
+
466
+ let litColor = dispatchLighting(surfaceId, surface, primary.direction);
467
+ finalColor += applyHaze(litColor, hit.t) * fresnel * contribution;
468
+
469
+ if (opacity >= 1.0) {
470
+ break;
471
+ }
472
+ remainingOpacity *= (1.0 - opacity);
473
+ if (remainingOpacity < 0.02) {
474
+ break;
475
+ }
476
+ ray.origin = hit.worldPos + ray.direction * TRANSPARENCY_EPSILON;
477
+ continue;
478
+ }
479
+ `;
480
+ }
481
+
482
+ export function compileForwardShader(surface: SurfaceData): string {
483
+ return compileUberShader([surface]);
484
+ }
@@ -1,12 +1,9 @@
1
1
  import { WGSL_STRUCTS } from "./structs";
2
2
  import type { SurfaceData } from "./index";
3
+ import { compileVertexBody, WGSL_LIGHTING_CALC } from "./wgsl";
3
4
 
4
5
  export function compileSurface(data: SurfaceData): string {
5
- const vertexTransform = data.vertex
6
- ? `var pos = worldPos;
7
- ${data.vertex}
8
- return pos;`
9
- : "return worldPos;";
6
+ const vertexTransform = compileVertexBody(data.vertex);
10
7
 
11
8
  const fragmentBody = data.fragment ?? "";
12
9
 
@@ -60,11 +57,7 @@ fn fs(input: VertexOutput) -> FragmentOutput {
60
57
  ${
61
58
  data.lit === false
62
59
  ? ` output.color = vec4<f32>(surface.baseColor, input.color.a);`
63
- : ` let NdotL = max(dot(surface.normal, -scene.sunDirection.xyz), 0.0);
64
- let ambient = scene.ambientColor.rgb * scene.ambientColor.a;
65
- let diffuse = scene.sunColor.rgb * NdotL;
66
- let diffuseFactor = 1.0 - surface.metallic * 0.9;
67
- let lighting = ambient + diffuse * diffuseFactor;
60
+ : ` ${WGSL_LIGHTING_CALC}
68
61
  output.color = vec4<f32>(surface.baseColor * lighting + surface.emission, input.color.a);`
69
62
  }
70
63
  output.entityId = input.entityId;
@@ -11,31 +11,35 @@ interface ComposedSurface extends SurfaceData {
11
11
  composed: number[];
12
12
  }
13
13
 
14
- const surfaces: (SurfaceData | ComposedSurface)[] = [];
14
+ export interface SurfaceRegistry {
15
+ surfaces: (SurfaceData | ComposedSurface)[];
16
+ }
17
+
18
+ export function createSurfaceRegistry(): SurfaceRegistry {
19
+ const registry: SurfaceRegistry = { surfaces: [] };
20
+ initBuiltIns(registry);
21
+ return registry;
22
+ }
15
23
 
16
- function initBuiltIns(): void {
17
- if (surfaces.length === 0) {
18
- surfaces.push({ lit: true });
24
+ function initBuiltIns(registry: SurfaceRegistry): void {
25
+ registry.surfaces.push({ lit: true });
19
26
 
20
- surfaces.push({
21
- lit: false,
22
- fragment: `(*surface).baseColor = (*surface).normal * 0.5 + 0.5;`,
23
- });
27
+ registry.surfaces.push({
28
+ lit: false,
29
+ fragment: `(*surface).baseColor = (*surface).normal * 0.5 + 0.5;`,
30
+ });
24
31
 
25
- surfaces.push({
26
- lit: false,
27
- fragment: `
32
+ registry.surfaces.push({
33
+ lit: false,
34
+ fragment: `
28
35
  let depth = position.z;
29
36
  let remapped = pow(1.0 - depth, 0.1);
30
37
  (*surface).baseColor = vec3(remapped);`,
31
- });
38
+ });
32
39
 
33
- surfaces.push({ lit: false });
34
- }
40
+ registry.surfaces.push({ lit: false });
35
41
  }
36
42
 
37
- initBuiltIns();
38
-
39
43
  export const SurfaceType = {
40
44
  Default: 0,
41
45
  Normals: 1,
@@ -43,23 +47,27 @@ export const SurfaceType = {
43
47
  Albedo: 3,
44
48
  } as const;
45
49
 
46
- export function surface(data: SurfaceData): number {
47
- const id = surfaces.length;
48
- surfaces.push(data);
50
+ export function registerSurface(registry: SurfaceRegistry, data: SurfaceData): number {
51
+ const id = registry.surfaces.length;
52
+ registry.surfaces.push(data);
49
53
  return id;
50
54
  }
51
55
 
52
- export function getSurface(id: number): SurfaceData | undefined {
53
- return surfaces[id];
56
+ export function getSurface(registry: SurfaceRegistry, id: number): SurfaceData | undefined {
57
+ return registry.surfaces[id];
54
58
  }
55
59
 
56
- export function clearSurfaces(): void {
57
- surfaces.length = 0;
58
- initBuiltIns();
60
+ export function getAllSurfaces(registry: SurfaceRegistry): SurfaceData[] {
61
+ return [...registry.surfaces];
59
62
  }
60
63
 
61
- export function compose(...ids: number[]): number {
62
- const validIds = ids.filter((id) => id > 0 && id < surfaces.length);
64
+ export function clearSurfaces(registry: SurfaceRegistry): void {
65
+ registry.surfaces.length = 0;
66
+ initBuiltIns(registry);
67
+ }
68
+
69
+ export function composeSurfaces(registry: SurfaceRegistry, ...ids: number[]): number {
70
+ const validIds = ids.filter((id) => id > 0 && id < registry.surfaces.length);
63
71
  if (validIds.length === 0) return SurfaceType.Default;
64
72
  if (validIds.length === 1) return validIds[0];
65
73
 
@@ -68,14 +76,14 @@ export function compose(...ids: number[]): number {
68
76
  let lit = true;
69
77
 
70
78
  for (const id of validIds) {
71
- const s = surfaces[id];
79
+ const s = registry.surfaces[id];
72
80
  if (!s) continue;
73
81
 
74
82
  if (s.lit === false) lit = false;
75
83
 
76
84
  if ("composed" in s) {
77
85
  for (const composedId of s.composed) {
78
- const inner = surfaces[composedId];
86
+ const inner = registry.surfaces[composedId];
79
87
  if (inner?.vertex) vertexParts.push(inner.vertex);
80
88
  if (inner?.fragment) fragmentParts.push(inner.fragment);
81
89
  }
@@ -92,11 +100,33 @@ export function compose(...ids: number[]): number {
92
100
  composed: validIds,
93
101
  };
94
102
 
95
- const id = surfaces.length;
96
- surfaces.push(composedData);
103
+ const id = registry.surfaces.length;
104
+ registry.surfaces.push(composedData);
97
105
  return id;
98
106
  }
99
107
 
108
+ const defaultRegistry = createSurfaceRegistry();
109
+
110
+ export function surface(data: SurfaceData): number {
111
+ return registerSurface(defaultRegistry, data);
112
+ }
113
+
114
+ export function getDefaultSurface(id: number): SurfaceData | undefined {
115
+ return getSurface(defaultRegistry, id);
116
+ }
117
+
118
+ export function getDefaultAllSurfaces(): SurfaceData[] {
119
+ return getAllSurfaces(defaultRegistry);
120
+ }
121
+
122
+ export function clearDefaultSurfaces(): void {
123
+ clearSurfaces(defaultRegistry);
124
+ }
125
+
126
+ export function compose(...ids: number[]): number {
127
+ return composeSurfaces(defaultRegistry, ...ids);
128
+ }
129
+
100
130
  export const SurfaceIds = {
101
131
  data: new Uint32Array(MAX_ENTITIES),
102
132
  };
@@ -0,0 +1,45 @@
1
+ export const NOISE_WGSL = /* wgsl */ `
2
+ fn hash2(p: vec2<f32>) -> f32 {
3
+ var p3 = fract(vec3(p.x, p.y, p.x) * 0.1031);
4
+ p3 += dot(p3, p3.yzx + 33.33);
5
+ return fract((p3.x + p3.y) * p3.z);
6
+ }
7
+
8
+ fn simplex2(p: vec2<f32>) -> f32 {
9
+ let K1 = 0.366025404;
10
+ let K2 = 0.211324865;
11
+
12
+ let i = floor(p + (p.x + p.y) * K1);
13
+ let a = p - i + (i.x + i.y) * K2;
14
+
15
+ let o = select(vec2(0.0, 1.0), vec2(1.0, 0.0), a.x > a.y);
16
+ let b = a - o + K2;
17
+ let c = a - 1.0 + 2.0 * K2;
18
+
19
+ let h = max(0.5 - vec3(dot(a, a), dot(b, b), dot(c, c)), vec3(0.0));
20
+ let h4 = h * h * h * h;
21
+
22
+ let n = vec3(
23
+ dot(a, vec2(hash2(i) * 2.0 - 1.0, hash2(i + vec2(0.0, 1.0)) * 2.0 - 1.0)),
24
+ dot(b, vec2(hash2(i + o) * 2.0 - 1.0, hash2(i + o + vec2(0.0, 1.0)) * 2.0 - 1.0)),
25
+ dot(c, vec2(hash2(i + 1.0) * 2.0 - 1.0, hash2(i + vec2(1.0, 2.0)) * 2.0 - 1.0))
26
+ );
27
+
28
+ return dot(h4, n) * 70.0;
29
+ }
30
+
31
+ fn fbm2(p: vec2<f32>, octaves: i32) -> f32 {
32
+ var value = 0.0;
33
+ var amplitude = 0.5;
34
+ var frequency = 1.0;
35
+ var pos = p;
36
+
37
+ for (var i = 0; i < octaves; i++) {
38
+ value += amplitude * simplex2(pos * frequency);
39
+ amplitude *= 0.5;
40
+ frequency *= 2.0;
41
+ }
42
+
43
+ return value;
44
+ }
45
+ `;