@multiplekex/shallot 0.2.3 → 0.2.5

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 (42) hide show
  1. package/package.json +1 -1
  2. package/src/extras/arrows/index.ts +3 -3
  3. package/src/extras/caustic.ts +37 -0
  4. package/src/extras/gradient/index.ts +63 -69
  5. package/src/extras/index.ts +3 -0
  6. package/src/extras/lines/index.ts +3 -3
  7. package/src/extras/skylab/index.ts +314 -0
  8. package/src/extras/text/font.ts +69 -14
  9. package/src/extras/text/index.ts +15 -7
  10. package/src/extras/text/sdf.ts +13 -2
  11. package/src/extras/water.ts +64 -0
  12. package/src/standard/defaults.ts +2 -0
  13. package/src/standard/index.ts +2 -0
  14. package/src/standard/raster/index.ts +517 -0
  15. package/src/standard/{render → raytracing}/bvh/blas.ts +3 -3
  16. package/src/standard/{render → raytracing}/bvh/tlas.ts +3 -0
  17. package/src/standard/{render → raytracing}/depth.ts +9 -9
  18. package/src/standard/raytracing/index.ts +380 -0
  19. package/src/standard/{render → raytracing}/instance.ts +3 -0
  20. package/src/standard/raytracing/shaders.ts +815 -0
  21. package/src/standard/{render → raytracing}/triangle.ts +1 -1
  22. package/src/standard/render/camera.ts +88 -80
  23. package/src/standard/render/index.ts +68 -208
  24. package/src/standard/render/indirect.ts +9 -10
  25. package/src/standard/render/mesh/index.ts +35 -166
  26. package/src/standard/render/overlay.ts +4 -4
  27. package/src/standard/render/pass.ts +1 -1
  28. package/src/standard/render/postprocess.ts +75 -50
  29. package/src/standard/render/scene.ts +28 -16
  30. package/src/standard/render/surface/compile.ts +6 -8
  31. package/src/standard/render/surface/noise.ts +15 -2
  32. package/src/standard/render/surface/shaders.ts +257 -0
  33. package/src/standard/render/surface/structs.ts +13 -6
  34. package/src/standard/render/forward/index.ts +0 -259
  35. package/src/standard/render/forward/raster.ts +0 -228
  36. package/src/standard/render/shaders.ts +0 -484
  37. package/src/standard/render/surface/wgsl.ts +0 -573
  38. /package/src/standard/{render → raytracing}/bvh/radix.ts +0 -0
  39. /package/src/standard/{render → raytracing}/bvh/structs.ts +0 -0
  40. /package/src/standard/{render → raytracing}/bvh/traverse.ts +0 -0
  41. /package/src/standard/{render → raytracing}/intersection.ts +0 -0
  42. /package/src/standard/{render → raytracing}/ray.ts +0 -0
@@ -0,0 +1,815 @@
1
+ import type { SurfaceData } from "../render/surface";
2
+ import {
3
+ compileVertexBody,
4
+ WGSL_LIGHTING_CALC,
5
+ SKY_DIR_WGSL,
6
+ HAZE_WGSL,
7
+ SKY_WGSL,
8
+ NOISE_WGSL,
9
+ } from "../render/surface/shaders";
10
+ import {
11
+ BVH_STRUCTS,
12
+ TLAS_BLAS_STRUCTS,
13
+ TLAS_BLAS_BINDINGS,
14
+ TLAS_BLAS_TRAVERSAL,
15
+ TLAS_BLAS_ANY_HIT,
16
+ } from "./bvh/traverse";
17
+ import { RAY_STRUCT_WGSL, HIT_RESULT_STRUCT_WGSL } from "./bvh/structs";
18
+ import {
19
+ SURFACE_DATA_STRUCT_WGSL,
20
+ SCENE_STRUCT_WGSL,
21
+ SKY_STRUCT_WGSL,
22
+ DATA_STRUCT_WGSL,
23
+ } from "../render/surface/structs";
24
+
25
+ const SPECULAR_WGSL = /* wgsl */ `
26
+ const DIELECTRIC_F0: f32 = 0.04;
27
+
28
+ fn blinnPhongSpecular(N: vec3<f32>, L: vec3<f32>, V: vec3<f32>, roughness: f32) -> f32 {
29
+ let H = normalize(L + V);
30
+ let NdotH = max(dot(N, H), 0.0);
31
+ let shininess = pow(2.0, (1.0 - roughness) * 10.0);
32
+ let intensity = (1.0 - roughness) * (1.0 - roughness);
33
+ return pow(NdotH, shininess) * intensity;
34
+ }
35
+
36
+ fn schlickFresnel(cosTheta: f32, F0: vec3<f32>) -> vec3<f32> {
37
+ return F0 + (vec3(1.0) - F0) * pow(1.0 - cosTheta, 5.0);
38
+ }
39
+
40
+ fn computeF0Vec(baseColor: vec3<f32>, metallic: f32) -> vec3<f32> {
41
+ return mix(vec3(DIELECTRIC_F0), baseColor, metallic);
42
+ }
43
+ `;
44
+
45
+ const REFRACTION_WGSL = /* wgsl */ `
46
+ fn refractRay(I: vec3<f32>, N: vec3<f32>, eta: f32) -> vec4<f32> {
47
+ let cosI = -dot(I, N);
48
+ let sinT2 = eta * eta * (1.0 - cosI * cosI);
49
+ if (sinT2 > 1.0) {
50
+ return vec4(reflect(I, N), 1.0);
51
+ }
52
+ let cosT = sqrt(1.0 - sinT2);
53
+ return vec4(normalize(eta * I + (eta * cosI - cosT) * N), 0.0);
54
+ }
55
+
56
+ fn fresnelSchlickIOR(cosTheta: f32, n1: f32, n2: f32, metallic: f32) -> f32 {
57
+ let r0 = pow((n1 - n2) / (n1 + n2), 2.0);
58
+ let baseFresnel = r0 + (1.0 - r0) * pow(1.0 - cosTheta, 5.0);
59
+ let minReflection = metallic * 0.5;
60
+ return max(baseFresnel, minReflection);
61
+ }
62
+ `;
63
+
64
+ const SHADOW_HELPER_WGSL = /* wgsl */ `
65
+ fn getShadow(surface: SurfaceData) -> f32 {
66
+ let L = -scene.sunDirection.xyz;
67
+ let NdotL = max(dot(surface.normal, L), 0.0);
68
+ if (NdotL <= 0.0) { return 1.0; }
69
+ let origin = surface.worldPos + surface.normal * 0.001;
70
+ return sampleSoftShadow(origin, L, scene.shadowSoftness, scene.shadowSamples);
71
+ }
72
+ `;
73
+
74
+ const SHADOW_WGSL = /* wgsl */ `
75
+ const GOLDEN_ANGLE: f32 = 2.39996323;
76
+ const SHADOW_TRANSPARENCY_EPSILON: f32 = 0.001;
77
+ const MAX_SHADOW_TRANSPARENT_DEPTH: u32 = 2u;
78
+
79
+ fn buildTangentBasis(dir: vec3<f32>) -> mat3x3<f32> {
80
+ let up = select(vec3(0.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), abs(dir.y) > 0.99);
81
+ let tangent = normalize(cross(dir, up));
82
+ let bitangent = cross(dir, tangent);
83
+ return mat3x3(tangent, bitangent, dir);
84
+ }
85
+
86
+ fn traceShadowWithTransparency(origin: vec3<f32>, sunDir: vec3<f32>, tMax: f32) -> f32 {
87
+ var shadowFactor = 1.0;
88
+ var currentPos = origin;
89
+
90
+ for (var i = 0u; i < MAX_SHADOW_TRANSPARENT_DEPTH; i++) {
91
+ var shadowRay: Ray;
92
+ shadowRay.origin = currentPos;
93
+ shadowRay.direction = sunDir;
94
+
95
+ let hit = trace(shadowRay);
96
+ if (!hit.hit || hit.t > tMax) {
97
+ break;
98
+ }
99
+
100
+ let shadow = getData(hit.entityId);
101
+ let opacity = shadow.baseColor.a;
102
+ shadowFactor *= (1.0 - opacity);
103
+
104
+ if (opacity >= 1.0 || shadowFactor < 0.02) {
105
+ break;
106
+ }
107
+
108
+ currentPos = hit.worldPos + sunDir * SHADOW_TRANSPARENCY_EPSILON;
109
+ }
110
+
111
+ return shadowFactor;
112
+ }
113
+
114
+ fn sampleSoftShadow(origin: vec3<f32>, sunDir: vec3<f32>, softness: f32, samples: u32) -> f32 {
115
+ if (samples == 0u) {
116
+ return 1.0;
117
+ }
118
+ if (samples == 1u || softness <= 0.0) {
119
+ return traceShadowWithTransparency(origin, sunDir, 1000.0);
120
+ }
121
+
122
+ let basis = buildTangentBasis(sunDir);
123
+ var totalShadow = 0.0;
124
+
125
+ let noise = fract(sin(dot(origin.xy, vec2(12.9898, 78.233))) * 43758.5453);
126
+ let rotationOffset = noise * 6.28318530718;
127
+
128
+ for (var i = 0u; i < samples; i++) {
129
+ let angle = f32(i) * GOLDEN_ANGLE + rotationOffset;
130
+ let z = (f32(i) + 0.5) / f32(samples);
131
+ let r = sqrt(1.0 - z * z) * softness * 0.1;
132
+
133
+ let localOffset = vec3(cos(angle) * r, sin(angle) * r, 0.0);
134
+ let sampleDir = normalize(sunDir + basis * localOffset);
135
+
136
+ totalShadow += traceShadowWithTransparency(origin, sampleDir, 1000.0);
137
+ }
138
+
139
+ return totalShadow / f32(samples);
140
+ }
141
+ `;
142
+
143
+ function compileApplyLighting(lit?: boolean, shadows?: boolean, reflections?: boolean): string {
144
+ if (lit === false) {
145
+ return "return surface.baseColor;";
146
+ }
147
+
148
+ if (shadows && reflections) {
149
+ // shadowFactor is now passed as parameter, computed once in main kernel
150
+ return `
151
+ let V = -rayDir;
152
+ let L = -scene.sunDirection.xyz;
153
+ let NdotL = max(dot(surface.normal, L), 0.0);
154
+ let NdotV = max(dot(surface.normal, V), 0.0);
155
+
156
+ let F0 = computeF0Vec(surface.baseColor, surface.metallic);
157
+ let F = schlickFresnel(NdotV, F0);
158
+
159
+ let ambient = scene.ambientColor.rgb * scene.ambientColor.a;
160
+ let sunDiffuse = scene.sunColor.rgb * NdotL * shadowFactor;
161
+ let diffuseWeight = 1.0 - surface.metallic;
162
+ let diffuseColor = surface.baseColor * (ambient + sunDiffuse) * diffuseWeight + surface.emission;
163
+
164
+ let specTerm = blinnPhongSpecular(surface.normal, L, V, surface.roughness);
165
+ let specular = scene.sunColor.rgb * specTerm * F * NdotL * shadowFactor;
166
+
167
+ var finalColor = diffuseColor * (vec3(1.0) - F) + specular;
168
+
169
+ if (scene.reflectionDepth > 0u) {
170
+ finalColor = traceReflections(
171
+ surface.worldPos, surface.normal, rayDir,
172
+ diffuseColor + specular, surface.baseColor,
173
+ surface.roughness, surface.metallic,
174
+ scene.reflectionDepth
175
+ );
176
+ }
177
+ return finalColor;`;
178
+ }
179
+
180
+ if (shadows) {
181
+ return `
182
+ let V = -rayDir;
183
+ let L = -scene.sunDirection.xyz;
184
+ let NdotL = max(dot(surface.normal, L), 0.0);
185
+ let NdotV = max(dot(surface.normal, V), 0.0);
186
+
187
+ let F0 = computeF0Vec(surface.baseColor, surface.metallic);
188
+ let F = schlickFresnel(NdotV, F0);
189
+ let diffuseWeight = (1.0 - surface.metallic) * (vec3(1.0) - F);
190
+ let ambient = scene.ambientColor.rgb * scene.ambientColor.a;
191
+ let sunDiffuse = scene.sunColor.rgb * NdotL * shadowFactor;
192
+ let diffuseColor = surface.baseColor * (ambient + sunDiffuse) * diffuseWeight + surface.emission;
193
+ let specTerm = blinnPhongSpecular(surface.normal, L, V, surface.roughness);
194
+ let specular = scene.sunColor.rgb * specTerm * F * NdotL * shadowFactor;
195
+ return diffuseColor + specular;`;
196
+ }
197
+
198
+ if (reflections) {
199
+ return `
200
+ let V = -rayDir;
201
+ let L = -scene.sunDirection.xyz;
202
+ let NdotL = max(dot(surface.normal, L), 0.0);
203
+ let NdotV = max(dot(surface.normal, V), 0.0);
204
+
205
+ let F0 = computeF0Vec(surface.baseColor, surface.metallic);
206
+ let F = schlickFresnel(NdotV, F0);
207
+
208
+ let ambient = scene.ambientColor.rgb * scene.ambientColor.a;
209
+ let sunDiffuse = scene.sunColor.rgb * NdotL;
210
+ let diffuseWeight = 1.0 - surface.metallic;
211
+ let diffuseColor = surface.baseColor * (ambient + sunDiffuse) * diffuseWeight + surface.emission;
212
+
213
+ let specTerm = blinnPhongSpecular(surface.normal, L, V, surface.roughness);
214
+ let specular = scene.sunColor.rgb * specTerm * F * NdotL;
215
+
216
+ var finalColor = diffuseColor * (vec3(1.0) - F) + specular;
217
+
218
+ if (scene.reflectionDepth > 0u) {
219
+ finalColor = traceReflections(
220
+ surface.worldPos, surface.normal, rayDir,
221
+ diffuseColor + specular, surface.baseColor,
222
+ surface.roughness, surface.metallic,
223
+ scene.reflectionDepth
224
+ );
225
+ }
226
+ return finalColor;`;
227
+ }
228
+
229
+ return `${WGSL_LIGHTING_CALC}
230
+ return surface.baseColor * lighting + surface.emission;`;
231
+ }
232
+
233
+ const EPSILON = 1e-7;
234
+
235
+ export const RT_STRUCTS = /* wgsl */ `
236
+ ${RAY_STRUCT_WGSL}
237
+ ${HIT_RESULT_STRUCT_WGSL}
238
+ ${SURFACE_DATA_STRUCT_WGSL}
239
+ ${SCENE_STRUCT_WGSL}
240
+ ${SKY_STRUCT_WGSL}
241
+ ${DATA_STRUCT_WGSL}
242
+ `;
243
+
244
+ export const RT_BINDINGS = /* wgsl */ `
245
+ @group(0) @binding(0) var<uniform> scene: Scene;
246
+ @group(0) @binding(1) var<storage, read> data: array<Data>;
247
+ @group(0) @binding(2) var output_scene: texture_storage_2d<rgba8unorm, write>;
248
+ @group(0) @binding(3) var output_depth: texture_storage_2d<r32float, write>;
249
+ @group(0) @binding(4) var output_entityId: texture_storage_2d<r32uint, write>;
250
+ @group(0) @binding(5) var<uniform> sky: Sky;
251
+ `;
252
+
253
+ export const RT_UTILS = /* wgsl */ `
254
+ fn getData(eid: u32) -> Data {
255
+ return data[eid];
256
+ }
257
+
258
+ fn getSurfaceType(eid: u32) -> u32 {
259
+ return data[eid].flags & 0xFFu;
260
+ }
261
+
262
+ fn getVolume(eid: u32) -> u32 {
263
+ return (data[eid].flags >> 8u) & 0xFu;
264
+ }
265
+
266
+ fn getShapeId(eid: u32) -> u32 {
267
+ return data[eid].flags >> 16u;
268
+ }
269
+
270
+ fn getInstanceCount() -> u32 {
271
+ return scene.instanceCount;
272
+ }
273
+ `;
274
+
275
+ export const RT_RAY_GEN = /* wgsl */ `
276
+ ${SKY_DIR_WGSL}
277
+
278
+ struct PrimaryRay {
279
+ origin: vec3<f32>,
280
+ direction: vec3<f32>,
281
+ skyDir: vec3<f32>,
282
+ }
283
+
284
+ fn generateRay(screenX: f32, screenY: f32) -> PrimaryRay {
285
+ var result: PrimaryRay;
286
+ result.skyDir = computeSkyDir(screenX, screenY);
287
+
288
+ let width = scene.viewport.x;
289
+ let height = scene.viewport.y;
290
+ let ndcX = screenX * 2.0 - 1.0;
291
+ let ndcY = 1.0 - screenY * 2.0;
292
+ let aspect = width / height;
293
+
294
+ let cameraWorld = scene.cameraWorld;
295
+ let camPosX = cameraWorld[3][0];
296
+ let camPosY = cameraWorld[3][1];
297
+ let camPosZ = cameraWorld[3][2];
298
+
299
+ if (scene.cameraMode > 0.5) {
300
+ let r00 = cameraWorld[0][0]; let r10 = cameraWorld[0][1]; let r20 = cameraWorld[0][2];
301
+ let r01 = cameraWorld[1][0]; let r11 = cameraWorld[1][1]; let r21 = cameraWorld[1][2];
302
+ let r02 = cameraWorld[2][0]; let r12 = cameraWorld[2][1]; let r22 = cameraWorld[2][2];
303
+
304
+ let halfHeight = scene.cameraSize;
305
+ let halfWidth = halfHeight * aspect;
306
+ let offsetX = ndcX * halfWidth;
307
+ let offsetY = ndcY * halfHeight;
308
+ let fwdX = -r02; let fwdY = -r12; let fwdZ = -r22;
309
+
310
+ result.origin = vec3(
311
+ camPosX + r00 * offsetX + r01 * offsetY + fwdX * scene.near,
312
+ camPosY + r10 * offsetX + r11 * offsetY + fwdY * scene.near,
313
+ camPosZ + r20 * offsetX + r21 * offsetY + fwdZ * scene.near
314
+ );
315
+ result.direction = vec3(fwdX, fwdY, fwdZ);
316
+ } else {
317
+ let dir = result.skyDir;
318
+ result.origin = vec3(camPosX + dir.x * scene.near, camPosY + dir.y * scene.near, camPosZ + dir.z * scene.near);
319
+ result.direction = dir;
320
+ }
321
+
322
+ return result;
323
+ }
324
+ `;
325
+
326
+ export const RT_INTERSECTION = /* wgsl */ `
327
+ const EPSILON: f32 = ${EPSILON};
328
+ `;
329
+
330
+ export function compileRTSurface(data: SurfaceData): string {
331
+ return compileUberShader([data]);
332
+ }
333
+
334
+ function compileSurfaceVariant(id: number, data: SurfaceData): string {
335
+ const vertexBody = compileVertexBody(data.vertex);
336
+ const fragmentBody = data.fragment ?? "";
337
+ const lightingCode = compileApplyLighting(data.lit, true, false);
338
+
339
+ return `
340
+ fn userVertexTransform_${id}(worldPos: vec3<f32>, normal: vec3<f32>, eid: u32) -> vec3<f32> {
341
+ ${vertexBody}
342
+ }
343
+
344
+ fn userFragment_${id}(surface: ptr<function, SurfaceData>, position: vec4<f32>) {
345
+ ${fragmentBody}
346
+ }
347
+
348
+ fn applyLighting_${id}(surface: SurfaceData, rayDir: vec3<f32>, shadowFactor: f32) -> vec3<f32> {
349
+ ${lightingCode}
350
+ }
351
+ `;
352
+ }
353
+
354
+ function compileDispatchFunctions(surfaceCount: number): string {
355
+ const vertexCases = Array.from(
356
+ { length: surfaceCount },
357
+ (_, i) => ` case ${i}u: { return userVertexTransform_${i}(worldPos, normal, eid); }`
358
+ ).join("\n");
359
+
360
+ const fragmentCases = Array.from(
361
+ { length: surfaceCount },
362
+ (_, i) => ` case ${i}u: { userFragment_${i}(surface, position); }`
363
+ ).join("\n");
364
+
365
+ const lightingCases = Array.from(
366
+ { length: surfaceCount },
367
+ (_, i) =>
368
+ ` case ${i}u: { return applyLighting_${i}(surface, rayDir, shadowFactor); }`
369
+ ).join("\n");
370
+
371
+ return `
372
+ fn dispatchVertexTransform(surfaceId: u32, worldPos: vec3<f32>, normal: vec3<f32>, eid: u32) -> vec3<f32> {
373
+ switch surfaceId {
374
+ ${vertexCases}
375
+ default: { return userVertexTransform_0(worldPos, normal, eid); }
376
+ }
377
+ }
378
+
379
+ fn dispatchFragment(surfaceId: u32, surface: ptr<function, SurfaceData>, position: vec4<f32>) {
380
+ switch surfaceId {
381
+ ${fragmentCases}
382
+ default: { userFragment_0(surface, position); }
383
+ }
384
+ }
385
+
386
+ fn dispatchLighting(surfaceId: u32, surface: SurfaceData, rayDir: vec3<f32>, shadowFactor: f32) -> vec3<f32> {
387
+ switch surfaceId {
388
+ ${lightingCases}
389
+ default: { return applyLighting_0(surface, rayDir, shadowFactor); }
390
+ }
391
+ }
392
+ `;
393
+ }
394
+
395
+ export function compileUberShader(surfaces: SurfaceData[]): string {
396
+ const surfaceVariants = surfaces.map((s, i) => compileSurfaceVariant(i, s)).join("\n");
397
+ const dispatchFunctions = compileDispatchFunctions(surfaces.length);
398
+
399
+ return /* wgsl */ `
400
+ ${RT_STRUCTS}
401
+ ${BVH_STRUCTS}
402
+ ${TLAS_BLAS_STRUCTS}
403
+ ${RT_BINDINGS}
404
+ ${RT_UTILS}
405
+ ${TLAS_BLAS_BINDINGS}
406
+ ${RT_RAY_GEN}
407
+ ${RT_INTERSECTION}
408
+ ${TLAS_BLAS_TRAVERSAL}
409
+ ${TLAS_BLAS_ANY_HIT}
410
+ ${SHADOW_WGSL}
411
+ ${SHADOW_HELPER_WGSL}
412
+ ${SPECULAR_WGSL}
413
+ ${REFRACTION_WGSL}
414
+ const PI: f32 = 3.14159265359;
415
+ ${NOISE_WGSL}
416
+ ${SKY_WGSL}
417
+ ${HAZE_WGSL}
418
+
419
+ ${surfaceVariants}
420
+ ${dispatchFunctions}
421
+
422
+ const KIND_PRIMARY: u32 = 0u;
423
+ const KIND_REFRACT: u32 = 1u;
424
+ const KIND_REFLECT: u32 = 2u;
425
+ const KIND_BEHIND: u32 = 3u;
426
+ const MAX_TASKS: u32 = 16u;
427
+ const MIN_CONTRIBUTION: f32 = 0.02;
428
+ const REFLECTION_EPSILON: f32 = 0.001;
429
+ const TRANSPARENCY_EPSILON: f32 = 0.001;
430
+ const REFRACTION_EPSILON: f32 = 0.001;
431
+
432
+ const VOLUME_SOLID: u32 = 0u;
433
+ const VOLUME_HALF_SPACE: u32 = 1u;
434
+
435
+ var<private> tOrigin: array<vec3f, MAX_TASKS>;
436
+ var<private> tDirection: array<vec3f, MAX_TASKS>;
437
+ var<private> tKind: array<u32, MAX_TASKS>;
438
+ var<private> tWeight: array<vec3f, MAX_TASKS>;
439
+ var<private> tIOR: array<f32, MAX_TASKS>;
440
+ var<private> tMediumColor: array<vec3f, MAX_TASKS>;
441
+ var<private> tTotalDist: array<f32, MAX_TASKS>;
442
+ var<private> tInMedium: array<u32, MAX_TASKS>;
443
+ var<private> tBounces: array<u32, MAX_TASKS>;
444
+ var<private> tVolumeType: array<u32, MAX_TASKS>;
445
+ var<private> tRemaining: array<f32, MAX_TASKS>;
446
+
447
+ @compute @workgroup_size(8, 8)
448
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
449
+ let width = u32(scene.viewport.x);
450
+ let height = u32(scene.viewport.y);
451
+
452
+ if (gid.x >= width || gid.y >= height) {
453
+ return;
454
+ }
455
+
456
+ let screenX = (f32(gid.x) + 0.5) / f32(width);
457
+ let screenY = (f32(gid.y) + 0.5) / f32(height);
458
+
459
+ let primary = generateRay(screenX, screenY);
460
+
461
+ tOrigin[0] = primary.origin;
462
+ tDirection[0] = primary.direction;
463
+ tKind[0] = KIND_PRIMARY;
464
+ tWeight[0] = vec3(1.0);
465
+ tRemaining[0] = 1.0;
466
+ var taskCount = 1u;
467
+
468
+ var finalColor = vec3(0.0);
469
+ var depth = 1e30;
470
+ var entityId = 0u;
471
+ var firstHit = true;
472
+
473
+ for (var i = 0u; i < MAX_TASKS; i++) {
474
+ if (i >= taskCount) {
475
+ break;
476
+ }
477
+
478
+ let avgWeight = (tWeight[i].x + tWeight[i].y + tWeight[i].z) / 3.0;
479
+ if (avgWeight < MIN_CONTRIBUTION) {
480
+ continue;
481
+ }
482
+
483
+ var ray: Ray;
484
+ ray.origin = tOrigin[i];
485
+ ray.direction = tDirection[i];
486
+ let hit = trace(ray);
487
+ let kind = tKind[i];
488
+ let weight = tWeight[i];
489
+
490
+ if (kind == KIND_PRIMARY) {
491
+ if (!hit.hit) {
492
+ finalColor += sampleSky(primary.skyDir) * weight;
493
+ continue;
494
+ }
495
+
496
+ let eid = hit.entityId;
497
+ let d = getData(eid);
498
+ let opacity = d.baseColor.a;
499
+ let surfaceId = getSurfaceType(eid);
500
+
501
+ if (firstHit) {
502
+ depth = hit.t;
503
+ entityId = eid;
504
+ firstHit = false;
505
+ }
506
+
507
+ let finalWorldPos = dispatchVertexTransform(surfaceId, hit.worldPos, hit.normal, eid);
508
+
509
+ var surface: SurfaceData;
510
+ surface.baseColor = d.baseColor.rgb;
511
+ surface.roughness = d.pbr.x;
512
+ surface.metallic = d.pbr.y;
513
+ surface.opacity = opacity;
514
+ surface.emission = d.emission.rgb * d.emission.a;
515
+ surface.normal = hit.normal;
516
+ surface.worldPos = finalWorldPos;
517
+
518
+ dispatchFragment(surfaceId, &surface, vec4(f32(gid.x), f32(gid.y), hit.t, 1.0));
519
+
520
+ let ior = d.pbr.z;
521
+ if (ior > 1.0 && scene.refractionDepth > 0u) {
522
+ let entering = dot(ray.direction, surface.normal) < 0.0;
523
+ let volumeType = getVolume(eid);
524
+
525
+ if (entering || volumeType == VOLUME_SOLID) {
526
+ let n = select(-surface.normal, surface.normal, entering);
527
+ let n1 = select(ior, 1.0, entering);
528
+ let n2 = select(1.0, ior, entering);
529
+ let cosI = abs(dot(ray.direction, n));
530
+ let fresnel = fresnelSchlickIOR(cosI, n1, n2, surface.metallic);
531
+
532
+ if (taskCount < MAX_TASKS) {
533
+ let refractResult = refractRay(ray.direction, n, n1 / n2);
534
+ let isTIR = refractResult.w > 0.5;
535
+ tOrigin[taskCount] = select(
536
+ surface.worldPos - n * REFRACTION_EPSILON,
537
+ surface.worldPos + n * REFRACTION_EPSILON,
538
+ isTIR
539
+ );
540
+ tDirection[taskCount] = refractResult.xyz;
541
+ tKind[taskCount] = KIND_REFRACT;
542
+ tWeight[taskCount] = weight * (1.0 - fresnel);
543
+ tIOR[taskCount] = ior;
544
+ tMediumColor[taskCount] = surface.baseColor;
545
+ tTotalDist[taskCount] = 0.0;
546
+ tInMedium[taskCount] = select(0u, 1u, entering);
547
+ tBounces[taskCount] = scene.refractionDepth;
548
+ tVolumeType[taskCount] = volumeType;
549
+ taskCount++;
550
+ }
551
+
552
+ let litColor = dispatchLighting(surfaceId, surface, primary.direction, getShadow(surface));
553
+ let fresnelWeight = weight * fresnel;
554
+ let refractionWeight = weight * (1.0 - fresnel);
555
+
556
+ let rSmoothness = 1.0 - surface.roughness;
557
+ let rRoughnessAtten = rSmoothness * rSmoothness;
558
+
559
+ if (fresnel >= MIN_CONTRIBUTION && scene.reflectionDepth > 0u && taskCount < MAX_TASKS) {
560
+ finalColor += applyHaze(litColor, hit.t) * refractionWeight;
561
+
562
+ tOrigin[taskCount] = surface.worldPos + surface.normal * REFLECTION_EPSILON;
563
+ tDirection[taskCount] = reflect(ray.direction, surface.normal);
564
+ tKind[taskCount] = KIND_REFLECT;
565
+ tWeight[taskCount] = fresnelWeight * rRoughnessAtten;
566
+ tBounces[taskCount] = scene.reflectionDepth;
567
+ tRemaining[taskCount] = 1.0;
568
+ taskCount++;
569
+ } else {
570
+ finalColor += applyHaze(litColor, hit.t) * fresnelWeight;
571
+ }
572
+ continue;
573
+ }
574
+ }
575
+
576
+ let litColor = dispatchLighting(surfaceId, surface, primary.direction, getShadow(surface));
577
+
578
+ let V = -ray.direction;
579
+ let NdotV = max(dot(surface.normal, V), 0.0);
580
+ let F0 = computeF0Vec(surface.baseColor, surface.metallic);
581
+ let F = schlickFresnel(NdotV, F0);
582
+ let smoothness = 1.0 - surface.roughness;
583
+ let roughnessAtten = smoothness * smoothness;
584
+ let reflWeight = F * roughnessAtten;
585
+ let avgRefl = (reflWeight.x + reflWeight.y + reflWeight.z) / 3.0;
586
+
587
+ if (avgRefl >= MIN_CONTRIBUTION && scene.reflectionDepth > 0u && taskCount < MAX_TASKS) {
588
+ finalColor += applyHaze(litColor, hit.t) * weight * opacity * (vec3(1.0) - reflWeight);
589
+
590
+ tOrigin[taskCount] = surface.worldPos + surface.normal * REFLECTION_EPSILON;
591
+ tDirection[taskCount] = reflect(ray.direction, surface.normal);
592
+ tKind[taskCount] = KIND_REFLECT;
593
+ tWeight[taskCount] = weight * opacity * reflWeight;
594
+ tBounces[taskCount] = scene.reflectionDepth;
595
+ tRemaining[taskCount] = 1.0;
596
+ taskCount++;
597
+ } else {
598
+ finalColor += applyHaze(litColor, hit.t) * weight * opacity;
599
+ }
600
+
601
+ if (opacity < 1.0) {
602
+ let nextWeight = weight * (1.0 - opacity);
603
+ let avgNext = (nextWeight.x + nextWeight.y + nextWeight.z) / 3.0;
604
+ if (avgNext >= MIN_CONTRIBUTION && taskCount < MAX_TASKS) {
605
+ tOrigin[taskCount] = hit.worldPos + ray.direction * TRANSPARENCY_EPSILON;
606
+ tDirection[taskCount] = ray.direction;
607
+ tKind[taskCount] = KIND_PRIMARY;
608
+ tWeight[taskCount] = nextWeight;
609
+ tRemaining[taskCount] = 1.0;
610
+ taskCount++;
611
+ }
612
+ }
613
+ } else if (kind == KIND_REFRACT) {
614
+ let isHalfSpace = tVolumeType[i] == VOLUME_HALF_SPACE;
615
+ let inMedium = tInMedium[i] == 1u;
616
+ let totalDist = tTotalDist[i];
617
+
618
+ if (!hit.hit) {
619
+ if (isHalfSpace && inMedium) {
620
+ let depthFade = exp(-totalDist * 0.1);
621
+ finalColor += weight * tMediumColor[i] * depthFade * scene.ambientColor.rgb * scene.ambientColor.a;
622
+ } else {
623
+ var skyColor = sampleSky(ray.direction);
624
+ if (inMedium && totalDist > 0.0) {
625
+ let absorption = exp(-totalDist * 0.5);
626
+ skyColor = mix(skyColor * tMediumColor[i], skyColor, absorption);
627
+ }
628
+ finalColor += weight * skyColor;
629
+ }
630
+ continue;
631
+ }
632
+
633
+ var newTotalDist = totalDist;
634
+ if (inMedium) {
635
+ newTotalDist += hit.t;
636
+ }
637
+
638
+ let refEid = hit.entityId;
639
+ let refracted = getData(refEid);
640
+ let refOpacity = refracted.baseColor.a;
641
+ let refIOR = refracted.pbr.z;
642
+
643
+ if (refIOR <= 1.0) {
644
+ let refSurfaceId = getSurfaceType(refEid);
645
+ let refWorldPos = dispatchVertexTransform(refSurfaceId, hit.worldPos, hit.normal, refEid);
646
+
647
+ var refSurface: SurfaceData;
648
+ refSurface.baseColor = refracted.baseColor.rgb;
649
+ refSurface.roughness = refracted.pbr.x;
650
+ refSurface.metallic = refracted.pbr.y;
651
+ refSurface.opacity = refOpacity;
652
+ refSurface.emission = refracted.emission.rgb * refracted.emission.a;
653
+ refSurface.normal = hit.normal;
654
+ refSurface.worldPos = refWorldPos;
655
+
656
+ dispatchFragment(refSurfaceId, &refSurface, vec4(0.0, 0.0, hit.t, 1.0));
657
+
658
+ var surfaceColor = dispatchLighting(refSurfaceId, refSurface, ray.direction, getShadow(refSurface));
659
+
660
+ if (newTotalDist > 0.0) {
661
+ let absorption = exp(-newTotalDist * 0.5);
662
+ surfaceColor = mix(surfaceColor * tMediumColor[i], surfaceColor, absorption);
663
+ }
664
+
665
+ surfaceColor = applyHaze(surfaceColor, hit.t);
666
+
667
+ if (refOpacity >= 1.0) {
668
+ finalColor += weight * surfaceColor;
669
+ } else {
670
+ finalColor += weight * surfaceColor * refOpacity;
671
+
672
+ if (taskCount < MAX_TASKS) {
673
+ tOrigin[taskCount] = hit.worldPos + ray.direction * REFRACTION_EPSILON;
674
+ tDirection[taskCount] = ray.direction;
675
+ tKind[taskCount] = KIND_BEHIND;
676
+ tWeight[taskCount] = weight * (1.0 - refOpacity);
677
+ taskCount++;
678
+ }
679
+ }
680
+ continue;
681
+ }
682
+
683
+ let entering = dot(ray.direction, hit.normal) < 0.0;
684
+
685
+ if (tBounces[i] > 0u && taskCount < MAX_TASKS) {
686
+ let n = select(-hit.normal, hit.normal, entering);
687
+ let n1 = select(tIOR[i], 1.0, entering);
688
+ let n2 = select(1.0, tIOR[i], entering);
689
+ let eta = n1 / n2;
690
+
691
+ let refractResult = refractRay(ray.direction, n, eta);
692
+ let isTIR = refractResult.w > 0.5;
693
+
694
+ var nextInMedium = inMedium;
695
+ if (!isTIR) {
696
+ nextInMedium = entering;
697
+ }
698
+
699
+ tOrigin[taskCount] = select(
700
+ hit.worldPos - n * REFRACTION_EPSILON,
701
+ hit.worldPos + n * REFRACTION_EPSILON,
702
+ isTIR
703
+ );
704
+ tDirection[taskCount] = refractResult.xyz;
705
+ tKind[taskCount] = KIND_REFRACT;
706
+ tWeight[taskCount] = weight;
707
+ tIOR[taskCount] = refIOR;
708
+ tMediumColor[taskCount] = select(tMediumColor[i], refracted.baseColor.rgb, entering);
709
+ tTotalDist[taskCount] = select(newTotalDist, 0.0, entering);
710
+ tInMedium[taskCount] = select(0u, 1u, nextInMedium);
711
+ tBounces[taskCount] = tBounces[i] - 1u;
712
+ tVolumeType[taskCount] = tVolumeType[i];
713
+ taskCount++;
714
+ } else {
715
+ if (isHalfSpace && inMedium) {
716
+ let depthFade = exp(-newTotalDist * 0.1);
717
+ finalColor += weight * tMediumColor[i] * depthFade * scene.ambientColor.rgb * scene.ambientColor.a;
718
+ } else {
719
+ var skyColor = sampleSky(ray.direction);
720
+ if (inMedium && newTotalDist > 0.0) {
721
+ let absorption = exp(-newTotalDist * 0.5);
722
+ skyColor = mix(skyColor * tMediumColor[i], skyColor, absorption);
723
+ }
724
+ finalColor += weight * skyColor;
725
+ }
726
+ }
727
+ } else if (kind == KIND_REFLECT) {
728
+ if (!hit.hit) {
729
+ finalColor += weight * sampleSky(ray.direction);
730
+ continue;
731
+ }
732
+
733
+ let eid = hit.entityId;
734
+ let hitData = getData(eid);
735
+ let hitSurfaceId = getSurfaceType(eid);
736
+ let hitWorldPos = dispatchVertexTransform(hitSurfaceId, hit.worldPos, hit.normal, eid);
737
+
738
+ var hitSurface: SurfaceData;
739
+ hitSurface.baseColor = hitData.baseColor.rgb;
740
+ hitSurface.roughness = hitData.pbr.x;
741
+ hitSurface.metallic = hitData.pbr.y;
742
+ hitSurface.opacity = hitData.baseColor.a;
743
+ hitSurface.emission = hitData.emission.rgb * hitData.emission.a;
744
+ hitSurface.normal = hit.normal;
745
+ hitSurface.worldPos = hitWorldPos;
746
+
747
+ dispatchFragment(hitSurfaceId, &hitSurface, vec4(0.0, 0.0, hit.t, 1.0));
748
+
749
+ let hitOpacity = hitSurface.opacity;
750
+ let hitLit = dispatchLighting(hitSurfaceId, hitSurface, ray.direction, getShadow(hitSurface));
751
+
752
+ finalColor += applyHaze(hitLit, hit.t) * weight * hitOpacity * tRemaining[i];
753
+
754
+ if (hitOpacity < 1.0) {
755
+ tRemaining[i] *= (1.0 - hitOpacity);
756
+ if (tRemaining[i] >= MIN_CONTRIBUTION && taskCount < MAX_TASKS) {
757
+ tOrigin[taskCount] = hit.worldPos + ray.direction * REFLECTION_EPSILON;
758
+ tDirection[taskCount] = ray.direction;
759
+ tKind[taskCount] = KIND_REFLECT;
760
+ tWeight[taskCount] = weight;
761
+ tBounces[taskCount] = tBounces[i];
762
+ tRemaining[taskCount] = tRemaining[i];
763
+ taskCount++;
764
+ } else {
765
+ finalColor += sampleSky(ray.direction) * weight * tRemaining[i];
766
+ }
767
+ continue;
768
+ }
769
+
770
+ if (tBounces[i] > 1u && taskCount < MAX_TASKS) {
771
+ let hitSmoothness = 1.0 - hitSurface.roughness;
772
+ let hitRoughnessAtten = hitSmoothness * hitSmoothness;
773
+ let opaqueF0 = computeF0Vec(hitSurface.baseColor, hitSurface.metallic);
774
+ let opaqueNdotV = max(dot(hit.normal, -ray.direction), 0.0);
775
+ let opaqueF = schlickFresnel(opaqueNdotV, opaqueF0);
776
+ let nextThroughput = weight * opaqueF * hitRoughnessAtten;
777
+ let avgNext = (nextThroughput.x + nextThroughput.y + nextThroughput.z) / 3.0;
778
+
779
+ if (avgNext >= MIN_CONTRIBUTION) {
780
+ tOrigin[taskCount] = hit.worldPos + hit.normal * REFLECTION_EPSILON;
781
+ tDirection[taskCount] = reflect(ray.direction, hit.normal);
782
+ tKind[taskCount] = KIND_REFLECT;
783
+ tWeight[taskCount] = nextThroughput;
784
+ tBounces[taskCount] = tBounces[i] - 1u;
785
+ tRemaining[taskCount] = 1.0;
786
+ taskCount++;
787
+ }
788
+ }
789
+ } else if (kind == KIND_BEHIND) {
790
+ if (!hit.hit) {
791
+ finalColor += weight * sampleSky(ray.direction);
792
+ continue;
793
+ }
794
+
795
+ let behind = getData(hit.entityId);
796
+ let bL = -scene.sunDirection.xyz;
797
+ let bNdotL = max(dot(hit.normal, bL), 0.0);
798
+ let bAmbient = scene.ambientColor.rgb * scene.ambientColor.a;
799
+ let bSun = scene.sunColor.rgb * bNdotL;
800
+ let bDiffuse = behind.baseColor.rgb * (bAmbient + bSun) * (1.0 - behind.pbr.y);
801
+ let bEmission = behind.emission.rgb * behind.emission.a;
802
+ finalColor += weight * applyHaze(bDiffuse + bEmission, hit.t);
803
+ }
804
+ }
805
+
806
+ textureStore(output_scene, vec2<i32>(gid.xy), vec4(finalColor, 1.0));
807
+ textureStore(output_depth, vec2<i32>(gid.xy), vec4(depth, 0.0, 0.0, 0.0));
808
+ textureStore(output_entityId, vec2<i32>(gid.xy), vec4(entityId, 0u, 0u, 0u));
809
+ }
810
+ `;
811
+ }
812
+
813
+ export function compileForwardShader(surface: SurfaceData): string {
814
+ return compileUberShader([surface]);
815
+ }