@driftengine/core 4.10.0 → 4.11.1

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 (165) hide show
  1. package/CHANGELOG.json +107 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +6 -5
  4. package/dist/index.js +2 -2
  5. package/dist/render/ambientOcclusionPass.d.ts +3 -2
  6. package/dist/render/ambientOcclusionPass.js +4 -2
  7. package/dist/render/backend/api.d.ts +21 -1
  8. package/dist/render/backend/webgl2/renderer.d.ts +141 -3
  9. package/dist/render/backend/webgl2/renderer.js +454 -42
  10. package/dist/render/backend/webgpu/buffers.d.ts +5 -0
  11. package/dist/render/backend/webgpu/buffers.js +98 -48
  12. package/dist/render/backend/webgpu/clothKernels.d.ts +8 -0
  13. package/dist/render/backend/webgpu/clothKernels.js +76 -0
  14. package/dist/render/backend/webgpu/clothOps.d.ts +39 -0
  15. package/dist/render/backend/webgpu/clothOps.js +78 -0
  16. package/dist/render/backend/webgpu/clothSetLayout.d.ts +25 -0
  17. package/dist/render/backend/webgpu/clothSetLayout.js +84 -0
  18. package/dist/render/backend/webgpu/clothSetPack.d.ts +33 -0
  19. package/dist/render/backend/webgpu/clothSetPack.js +144 -0
  20. package/dist/render/backend/webgpu/drawCommand.d.ts +2 -0
  21. package/dist/render/backend/webgpu/drawCommand.js +1 -0
  22. package/dist/render/backend/webgpu/flatPass.d.ts +1 -1
  23. package/dist/render/backend/webgpu/flatPass.js +2 -1
  24. package/dist/render/backend/webgpu/gpuClothSet.d.ts +80 -0
  25. package/dist/render/backend/webgpu/gpuClothSet.js +462 -0
  26. package/dist/render/backend/webgpu/latePass.js +1 -0
  27. package/dist/render/backend/webgpu/motionPass.js +15 -6
  28. package/dist/render/backend/webgpu/pipelineCache.d.ts +22 -1
  29. package/dist/render/backend/webgpu/pipelineCache.js +39 -1
  30. package/dist/render/backend/webgpu/postPass.d.ts +1 -1
  31. package/dist/render/backend/webgpu/renderer.d.ts +100 -1
  32. package/dist/render/backend/webgpu/renderer.js +423 -95
  33. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +9 -2
  34. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +121 -47
  35. package/dist/render/backend/webgpu/staticBundles.js +2 -1
  36. package/dist/render/backend/webgpu/vertexFormats.d.ts +22 -0
  37. package/dist/render/backend/webgpu/vertexFormats.js +97 -0
  38. package/dist/render/globalMediumPass.d.ts +2 -0
  39. package/dist/render/globalMediumPass.js +1 -0
  40. package/dist/render/indexWidth.d.ts +17 -0
  41. package/dist/render/indexWidth.js +23 -0
  42. package/dist/render/instances.d.ts +13 -1
  43. package/dist/render/instances.js +10 -0
  44. package/dist/render/lightMatrix.d.ts +11 -0
  45. package/dist/render/lightMatrix.js +14 -0
  46. package/dist/render/mesh.d.ts +9 -0
  47. package/dist/render/mesh.js +38 -14
  48. package/dist/render/occlusionFade.d.ts +21 -0
  49. package/dist/render/occlusionFade.js +27 -0
  50. package/dist/render/renderQuality.d.ts +30 -0
  51. package/dist/render/renderQuality.js +5 -0
  52. package/dist/render/rendererHarness.d.ts +7 -0
  53. package/dist/render/rendererHarness.js +12 -0
  54. package/dist/render/sceneTarget.d.ts +2 -0
  55. package/dist/render/sceneTarget.js +1 -1
  56. package/dist/render/shader.d.ts +15 -0
  57. package/dist/render/shader.js +41 -0
  58. package/dist/render/shaders/ambientOcclusion.js +12 -1
  59. package/dist/render/shaders/flat/directionalShadow.js +84 -10
  60. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  61. package/dist/render/shaders/flat/glassTint.js +3 -0
  62. package/dist/render/shaders/flat/index.d.ts +15 -0
  63. package/dist/render/shaders/flat/index.js +24 -4
  64. package/dist/render/shaders/flat/layered.d.ts +27 -0
  65. package/dist/render/shaders/flat/layered.js +198 -0
  66. package/dist/render/shaders/flat/main.d.ts +1 -1
  67. package/dist/render/shaders/flat/main.js +50 -16
  68. package/dist/render/shaders/flat/mapReads.d.ts +10 -0
  69. package/dist/render/shaders/flat/mapReads.js +38 -0
  70. package/dist/render/shaders/flat/movingSun.d.ts +21 -0
  71. package/dist/render/shaders/flat/movingSun.js +46 -0
  72. package/dist/render/shaders/flat/normalMap.d.ts +6 -2
  73. package/dist/render/shaders/flat/normalMap.js +23 -3
  74. package/dist/render/shaders/flat/overlay.d.ts +3 -2
  75. package/dist/render/shaders/flat/overlay.js +41 -19
  76. package/dist/render/shaders/flat/preamble.js +23 -5
  77. package/dist/render/shaders/flat/worldUv.d.ts +22 -0
  78. package/dist/render/shaders/flat/worldUv.js +113 -0
  79. package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
  80. package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
  81. package/dist/render/shaders/generated/flat.wgsl.d.ts +768 -368
  82. package/dist/render/shaders/generated/flat.wgsl.js +791 -391
  83. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +8 -3
  84. package/dist/render/shaders/generated/globalMedium.wgsl.js +8 -3
  85. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +15 -10
  86. package/dist/render/shaders/generated/lightVolume.wgsl.js +16 -11
  87. package/dist/render/shaders/globalMedium.js +9 -1
  88. package/dist/render/shaders/lightVolume.js +9 -1
  89. package/dist/render/shaders/tangentFrame.d.ts +1 -1
  90. package/dist/render/shaders/tangentFrame.js +16 -3
  91. package/dist/render/skinnedClothRun.d.ts +28 -0
  92. package/dist/render/skinnedClothRun.js +103 -35
  93. package/dist/render/surfaceLayers.d.ts +107 -0
  94. package/dist/render/surfaceLayers.js +82 -0
  95. package/dist/render/surfaceOverlay.d.ts +16 -3
  96. package/dist/render/surfaceOverlay.js +11 -6
  97. package/dist/render/surfaceProjection.d.ts +34 -0
  98. package/dist/render/surfaceProjection.js +30 -0
  99. package/dist/render/surfaceTexture.d.ts +39 -0
  100. package/dist/render/transmission.d.ts +21 -0
  101. package/dist/render/transmission.js +21 -0
  102. package/dist/render/vertexPacking.d.ts +48 -0
  103. package/dist/render/vertexPacking.js +68 -0
  104. package/dist/ui/splash.d.ts +42 -2
  105. package/dist/ui/splash.js +78 -13
  106. package/package.json +6 -6
  107. package/src/index.ts +6 -2
  108. package/src/render/ambientOcclusionPass.ts +4 -1
  109. package/src/render/backend/api.ts +31 -0
  110. package/src/render/backend/webgl2/renderer.ts +519 -56
  111. package/src/render/backend/webgpu/buffers.ts +153 -54
  112. package/src/render/backend/webgpu/clothKernels.ts +86 -0
  113. package/src/render/backend/webgpu/clothOps.ts +86 -0
  114. package/src/render/backend/webgpu/clothSetLayout.ts +102 -0
  115. package/src/render/backend/webgpu/clothSetPack.ts +197 -0
  116. package/src/render/backend/webgpu/drawCommand.ts +3 -0
  117. package/src/render/backend/webgpu/flatPass.ts +26 -28
  118. package/src/render/backend/webgpu/gpuClothSet.ts +564 -0
  119. package/src/render/backend/webgpu/latePass.ts +1 -0
  120. package/src/render/backend/webgpu/motionPass.ts +18 -7
  121. package/src/render/backend/webgpu/pipelineCache.ts +46 -7
  122. package/src/render/backend/webgpu/renderer.ts +622 -164
  123. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +121 -47
  124. package/src/render/backend/webgpu/staticBundles.ts +2 -1
  125. package/src/render/backend/webgpu/vertexFormats.ts +113 -0
  126. package/src/render/globalMediumPass.ts +7 -0
  127. package/src/render/indexWidth.ts +27 -0
  128. package/src/render/instances.ts +18 -1
  129. package/src/render/lightMatrix.ts +15 -0
  130. package/src/render/mesh.ts +66 -14
  131. package/src/render/occlusionFade.ts +29 -0
  132. package/src/render/renderQuality.ts +38 -0
  133. package/src/render/rendererHarness.ts +15 -0
  134. package/src/render/sceneTarget.ts +3 -0
  135. package/src/render/shader.ts +59 -0
  136. package/src/render/shaders/ambientOcclusion.ts +12 -1
  137. package/src/render/shaders/flat/directionalShadow.ts +84 -10
  138. package/src/render/shaders/flat/glassTint.ts +7 -1
  139. package/src/render/shaders/flat/index.ts +39 -4
  140. package/src/render/shaders/flat/layered.ts +198 -0
  141. package/src/render/shaders/flat/main.ts +50 -16
  142. package/src/render/shaders/flat/mapReads.ts +38 -0
  143. package/src/render/shaders/flat/movingSun.ts +46 -0
  144. package/src/render/shaders/flat/normalMap.ts +23 -3
  145. package/src/render/shaders/flat/overlay.ts +41 -19
  146. package/src/render/shaders/flat/preamble.ts +23 -5
  147. package/src/render/shaders/flat/worldUv.ts +113 -0
  148. package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
  149. package/src/render/shaders/generated/flat.wgsl.ts +791 -391
  150. package/src/render/shaders/generated/globalMedium.wgsl.ts +8 -3
  151. package/src/render/shaders/generated/lightVolume.wgsl.ts +16 -11
  152. package/src/render/shaders/globalMedium.ts +9 -1
  153. package/src/render/shaders/lightVolume.ts +9 -1
  154. package/src/render/shaders/tangentFrame.ts +16 -3
  155. package/src/render/skinnedClothRun.ts +138 -38
  156. package/src/render/surfaceLayers.ts +176 -0
  157. package/src/render/surfaceOverlay.ts +24 -7
  158. package/src/render/surfaceProjection.ts +57 -0
  159. package/src/render/surfaceTexture.ts +39 -0
  160. package/src/render/transmission.ts +27 -0
  161. package/src/render/vertexPacking.ts +109 -0
  162. package/src/ui/splash.ts +83 -14
  163. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +0 -74
  164. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +0 -452
  165. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +0 -502
@@ -3,6 +3,13 @@
3
3
  import { MorphTexture } from './backend/webgl2/morphTexture.ts';
4
4
  import { ABSENT_ATTRIBUTE } from './vertexDefaults.ts';
5
5
  import { UPLOAD_BYTES_PER_STEP } from './uploadStep.ts';
6
+ import { compactIndices } from './indexWidth.ts';
7
+ import {
8
+ fixedArray,
9
+ sharedValue,
10
+ vertexPackingOf,
11
+ type AttributePacking,
12
+ } from './vertexPacking.ts';
6
13
 
7
14
  /*
8
15
  * `MeshData` and its validator live in `@driftengine/drft`, because the container's whole
@@ -92,7 +99,14 @@ export class Mesh {
92
99
  private readonly vao: WebGLVertexArrayObject;
93
100
  private readonly buffers: WebGLBuffer[] = [];
94
101
  /** Every attribute bound from a buffer, so a batch can build a vertex array of its own. */
95
- private readonly layout: { location: number; buffer: WebGLBuffer; size: number }[] = [];
102
+ private readonly layout: {
103
+ location: number;
104
+ buffer: WebGLBuffer;
105
+ size: number;
106
+ /** `FLOAT`, or a sixteen-bit type read normalised: `vertexPacking.ts`. */
107
+ type: number;
108
+ normalized: boolean;
109
+ }[] = [];
96
110
  private indexBuffer: WebGLBuffer | null = null;
97
111
  /**
98
112
  * The position and normal buffers, kept so `update` can rewrite them.
@@ -110,6 +124,8 @@ export class Mesh {
110
124
  * same frame.
111
125
  */
112
126
  readonly indexCount: number;
127
+ /** `UNSIGNED_SHORT` or `UNSIGNED_INT`, as `compactIndices` stored them: every draw says which. */
128
+ private readonly indexType: number;
113
129
  /**
114
130
  * The constant values this mesh's *absent* attributes are shaded with, re-applied on
115
131
  * every draw.
@@ -226,11 +242,13 @@ export class Mesh {
226
242
  * that has been told a lie every frame is the shape of a stall nobody can find.
227
243
  */
228
244
  const usage = dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW;
245
+ /* How each attribute travels: sixteen-bit fixed point, or one value as a constant. */
246
+ const packing = vertexPackingOf(data, dynamic);
229
247
  const positions = this.attachAttribute(gl, ATTR_POSITION, data.positions, 3, usage);
230
- const normals = this.attachAttribute(gl, ATTR_NORMAL, data.normals, 3, usage);
248
+ const normals = this.attachAttribute(gl, ATTR_NORMAL, data.normals, 3, usage, packing.normals);
231
249
  if (dynamic) this.deformable = { positions, normals };
232
- this.attachAttribute(gl, ATTR_COLOR, data.colors, 3);
233
- this.attachAttribute(gl, ATTR_EMISSIVE, data.emissive, 1);
250
+ this.attachShared(gl, ATTR_COLOR, data.colors, 3, packing.colors);
251
+ this.attachShared(gl, ATTR_EMISSIVE, data.emissive, 1, packing.emissive);
234
252
  /*
235
253
  * Absent means "not shiny", supplied as a constant rather than as a buffer of
236
254
  * zeroes. `vertexAttrib1f` sets the value a *disabled* attribute reads, so this
@@ -304,7 +322,7 @@ export class Mesh {
304
322
  this.constants.push({ location: ATTR_WEIGHTS, value: ABSENT_ATTRIBUTE['weights'] });
305
323
  } else {
306
324
  this.attachAttribute(gl, ATTR_JOINTS, data.joints, 4);
307
- this.attachAttribute(gl, ATTR_WEIGHTS, data.weights, 4);
325
+ this.attachAttribute(gl, ATTR_WEIGHTS, data.weights, 4, gl.STATIC_DRAW, packing.weights);
308
326
  }
309
327
  /* The fifth to eighth influences, or zeros, which add nothing. See `isSkinnedEight`. */
310
328
  this.isSkinnedEight = this.isSkinned && data.joints2 !== undefined;
@@ -313,7 +331,7 @@ export class Mesh {
313
331
  this.constants.push({ location: ATTR_WEIGHTS2, value: ABSENT_ATTRIBUTE['weights2'] });
314
332
  } else {
315
333
  this.attachAttribute(gl, ATTR_JOINTS2, data.joints2, 4);
316
- this.attachAttribute(gl, ATTR_WEIGHTS2, data.weights2, 4);
334
+ this.attachAttribute(gl, ATTR_WEIGHTS2, data.weights2, 4, gl.STATIC_DRAW, packing.weights2);
317
335
  }
318
336
 
319
337
  this.hasTangents = data.tangents !== undefined;
@@ -321,7 +339,7 @@ export class Mesh {
321
339
  gl.disableVertexAttribArray(ATTR_TANGENT);
322
340
  this.constants.push({ location: ATTR_TANGENT, value: ABSENT_ATTRIBUTE['tangents'] });
323
341
  } else {
324
- this.attachAttribute(gl, ATTR_TANGENT, data.tangents, 4);
342
+ this.attachAttribute(gl, ATTR_TANGENT, data.tangents, 4, gl.STATIC_DRAW, packing.tangents);
325
343
  }
326
344
 
327
345
  this.hasGrain = data.grain !== undefined;
@@ -351,7 +369,9 @@ export class Mesh {
351
369
  this.buffers.push(indexBuffer);
352
370
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
353
371
  this.indexBuffer = indexBuffer;
354
- this.allocate(gl, gl.ELEMENT_ARRAY_BUFFER, indexBuffer, data.indices, gl.STATIC_DRAW);
372
+ const indices = compactIndices(data.indices, this.vertexCount);
373
+ this.indexType = indices instanceof Uint16Array ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT;
374
+ this.allocate(gl, gl.ELEMENT_ARRAY_BUFFER, indexBuffer, indices, gl.STATIC_DRAW);
355
375
 
356
376
  gl.bindVertexArray(null);
357
377
  }
@@ -370,7 +390,7 @@ export class Mesh {
370
390
  const value = constant.value;
371
391
  applyConstant(gl, constant.location, value);
372
392
  }
373
- gl.drawElements(gl.TRIANGLES, this.indexCount, gl.UNSIGNED_INT, 0);
393
+ gl.drawElements(gl.TRIANGLES, this.indexCount, this.indexType, 0);
374
394
  }
375
395
 
376
396
  /**
@@ -394,7 +414,14 @@ export class Mesh {
394
414
  for (const attribute of this.layout) {
395
415
  gl.bindBuffer(gl.ARRAY_BUFFER, attribute.buffer);
396
416
  gl.enableVertexAttribArray(attribute.location);
397
- gl.vertexAttribPointer(attribute.location, attribute.size, gl.FLOAT, false, 0, 0);
417
+ gl.vertexAttribPointer(
418
+ attribute.location,
419
+ attribute.size,
420
+ attribute.type,
421
+ attribute.normalized,
422
+ 0,
423
+ 0,
424
+ );
398
425
  }
399
426
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
400
427
  gl.bindBuffer(gl.ARRAY_BUFFER, instances);
@@ -424,7 +451,7 @@ export class Mesh {
424
451
  const value = constant.value;
425
452
  applyConstant(gl, constant.location, value);
426
453
  }
427
- gl.drawElementsInstanced(gl.TRIANGLES, this.indexCount, gl.UNSIGNED_INT, 0, count);
454
+ gl.drawElementsInstanced(gl.TRIANGLES, this.indexCount, this.indexType, 0, count);
428
455
  }
429
456
 
430
457
  dispose(gl: WebGL2RenderingContext): void {
@@ -468,24 +495,49 @@ export class Mesh {
468
495
  }
469
496
  }
470
497
 
498
+ /**
499
+ * One attribute's buffer, as floats or in its sixteen-bit fixed point (`vertexPacking.ts`), which
500
+ * WebGL2 reads back through a normalised `SHORT` or `UNSIGNED_SHORT`: three lanes wide where
501
+ * WebGPU needs four, so a normal costs six bytes here.
502
+ */
471
503
  private attachAttribute(
472
504
  gl: WebGL2RenderingContext,
473
505
  location: number,
474
506
  data: Float32Array,
475
507
  size: number,
476
508
  usage: number = gl.STATIC_DRAW,
509
+ packing: AttributePacking = 'float',
477
510
  ): WebGLBuffer {
478
511
  const buffer = gl.createBuffer();
479
512
  if (buffer === null) throw new Error('createBuffer failed');
480
513
  this.buffers.push(buffer);
481
514
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
482
- this.allocate(gl, gl.ARRAY_BUFFER, buffer, data, usage);
515
+ const fixed = packing === 'snorm16' || packing === 'unorm16';
516
+ this.allocate(gl, gl.ARRAY_BUFFER, buffer, fixed ? fixedArray(data, packing) : data, usage);
517
+ const type =
518
+ packing === 'snorm16' ? gl.SHORT : packing === 'unorm16' ? gl.UNSIGNED_SHORT : gl.FLOAT;
483
519
  gl.enableVertexAttribArray(location);
484
- gl.vertexAttribPointer(location, size, gl.FLOAT, false, 0, 0);
485
- this.layout.push({ location, buffer, size });
520
+ gl.vertexAttribPointer(location, size, type, fixed, 0, 0);
521
+ this.layout.push({ location, buffer, size, type, normalized: fixed });
486
522
  return buffer;
487
523
  }
488
524
 
525
+ /** A buffer, or the one value every vertex shares as a constant, as the packing says. */
526
+ private attachShared(
527
+ gl: WebGL2RenderingContext,
528
+ location: number,
529
+ data: Float32Array,
530
+ size: number,
531
+ packing: AttributePacking,
532
+ ): void {
533
+ if (packing !== 'constant') {
534
+ this.attachAttribute(gl, location, data, size, gl.STATIC_DRAW, packing);
535
+ return;
536
+ }
537
+ gl.disableVertexAttribArray(location);
538
+ this.constants.push({ location, value: sharedValue(data, size) });
539
+ }
540
+
489
541
  /**
490
542
  * Give a bound buffer its storage, and its contents now or later.
491
543
  *
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Where ambient occlusion fades out with distance: `setAmbientOcclusionFade`'s two numbers as the
3
+ * estimate reads them, `uFade` in `shaders/ambientOcclusion.ts`.
4
+ *
5
+ * **Occlusion is whole up to `distance` metres from the eye and gone `radius` metres past it.** A
6
+ * screen-space estimate reaches as far as the depth does, and far off the depth buffer's steps are
7
+ * metres apart: a sky dome or a mountain range kilometres away is shaded in rings that follow
8
+ * those steps, and nothing there is in contact with anything the estimate could be right about.
9
+ * A pixel past the fade skips the estimate's walk, as the sky does, so it costs less as well.
10
+ *
11
+ * **What it gives up**: distant contact shade that is real — a doorway a hundred metres off — fades
12
+ * with the rest, and the distance is the eye's and not the subject's, so a long lens fades what it
13
+ * frames close up. What would make it wrong is a scene whose far depth is precise enough to shade,
14
+ * which is a reason to fade further, not to leave the fade off.
15
+ */
16
+
17
+ /** `uFade` for no fade: a distance below zero, which the estimate reads as "never". */
18
+ const NO_FADE = -1;
19
+
20
+ /**
21
+ * `distance` and `radius` into `out`, `[distance, radius]`. A distance that is not a finite number
22
+ * at or above zero is no fade, `[−1, 0]`, so `Infinity` reads as "never fades". A radius below zero
23
+ * or not a number is 0, which is a cut at `distance` rather than a ramp.
24
+ */
25
+ export function resolveOcclusionFade(distance: number, radius: number, out: Float32Array): void {
26
+ const fades = Number.isFinite(distance) && distance >= 0;
27
+ out[0] = fades ? distance : NO_FADE;
28
+ out[1] = fades && Number.isFinite(radius) ? Math.max(radius, 0) : 0;
29
+ }
@@ -50,6 +50,9 @@ export type ShadowFilterTaps = 4 | 8 | 12;
50
50
  export type DirectionalShadowDepthLayers = 1 | 2;
51
51
  export type WaterReflectionFilterTaps = 1 | 5 | 9;
52
52
 
53
+ /** What a draw does when its pipeline has not compiled yet: see `RenderQuality.pipelineCompile`. */
54
+ export type PipelineCompile = 'wait' | 'skip';
55
+
53
56
  /** How light through glass is coloured: see `RenderQuality.glassShadows`. */
54
57
  export type GlassShadows = 'full' | 'half' | 'off';
55
58
 
@@ -220,6 +223,34 @@ export interface RenderQuality {
220
223
  * lot of glass; that is what `'half'` is for. See `glassShadow.ts`.
221
224
  */
222
225
  readonly glassShadows: GlassShadows;
226
+ /**
227
+ * What a lit draw does when the pipeline it needs has not compiled: one first needed inside a
228
+ * frame, by a material's own state — two-sided, cut out, a surface model — or a draw state a
229
+ * mesh's creation did not warm.
230
+ *
231
+ * **`'wait'`, the default**: the pipeline is built where the draw asks for it, and on WebGPU the
232
+ * browser compiles it on its GPU process's main thread, holding the frame — 130 to 400 ms a
233
+ * pipeline was reported on a phone, for a material first drawn mid-scene. **`'skip'`** starts the
234
+ * compile off that thread and leaves the draw out until it lands, a few frames to a few dozen,
235
+ * and the draw then appears; nothing waits. Static draws always wait, since a recorded list keeps
236
+ * what it recorded, and a compile that fails is built where it is next asked for, so the device
237
+ * says why rather than the draw vanishing for good.
238
+ *
239
+ * **The way to have neither** is to draw what a scene will use once, behind a loading screen, and
240
+ * await `ready()`, which waits for every compile started — whichever this says. What would make
241
+ * `'skip'` wrong is a frame that must hold everything it was given, such as a capture of the first
242
+ * frame of a scene not awaited.
243
+ *
244
+ * **Measured on a desktop GPU** (`demo/dev/pipelineCompile.html`, eight new variants at once): on
245
+ * WebGPU the frame that met them took 700 to 3,700 ms waiting and 17 ms skipping, the draws
246
+ * landing 21 to 26 frames later. WebGL2 compiles its lit programs when it is made, all but a
247
+ * surface model's, which it compiles at the first draw that wants one: there `'skip'` asks the
248
+ * driver through `KHR_parallel_shader_compile` and waits where a context does not offer it — which
249
+ * ANGLE on Vulkan does not. Where it is offered, ANGLE on OpenGL, the longest frame went from 820 to
250
+ * 1,120 ms to 630 to 700 ms: the driver still finishes its own compile at the program's first
251
+ * draw, which nothing above it can move.
252
+ */
253
+ readonly pipelineCompile: PipelineCompile;
223
254
  /**
224
255
  * How light that scatters beneath skin is drawn, for a material whose model is `skinModel`.
225
256
  *
@@ -973,6 +1004,7 @@ export const DEFAULT_RENDER_QUALITY: Readonly<RenderQuality> = Object.freeze({
973
1004
  lightVolumeSamples: 32,
974
1005
  directionalShadowDepthLayers: 2,
975
1006
  glassShadows: 'full',
1007
+ pipelineCompile: 'wait',
976
1008
  skinScattering: 'pre-integrated',
977
1009
  directionalShadowMaxDistance: 6,
978
1010
  directionalShadowMaxSlope: 3,
@@ -1140,6 +1172,7 @@ export function resolveRenderQuality(options: RenderQualityOptions = {}): Readon
1140
1172
  directionalShadowDepthLayers:
1141
1173
  options.directionalShadowDepthLayers ?? DEFAULT_RENDER_QUALITY.directionalShadowDepthLayers,
1142
1174
  glassShadows: options.glassShadows ?? DEFAULT_RENDER_QUALITY.glassShadows,
1175
+ pipelineCompile: options.pipelineCompile ?? DEFAULT_RENDER_QUALITY.pipelineCompile,
1143
1176
  skinScattering: options.skinScattering ?? DEFAULT_RENDER_QUALITY.skinScattering,
1144
1177
  directionalShadowMaxDistance:
1145
1178
  options.directionalShadowMaxDistance ?? DEFAULT_RENDER_QUALITY.directionalShadowMaxDistance,
@@ -1298,6 +1331,11 @@ export function resolveRenderQuality(options: RenderQualityOptions = {}): Readon
1298
1331
  `RenderQuality.glassShadows must be 'full', 'half' or 'off', got ${String(quality.glassShadows)}`,
1299
1332
  );
1300
1333
  }
1334
+ if (quality.pipelineCompile !== 'wait' && quality.pipelineCompile !== 'skip') {
1335
+ throw new Error(
1336
+ `RenderQuality.pipelineCompile must be 'wait' or 'skip', got ${String(quality.pipelineCompile)}`,
1337
+ );
1338
+ }
1301
1339
  if (quality.skinScattering !== 'pre-integrated' && quality.skinScattering !== 'screen-space') {
1302
1340
  throw new Error(
1303
1341
  `RenderQuality.skinScattering must be 'pre-integrated' or 'screen-space', got ${String(quality.skinScattering)}`,
@@ -58,8 +58,14 @@ export function recordingGl(
58
58
  * recording.
59
59
  */
60
60
  readonly uniforms?: readonly string[];
61
+ /**
62
+ * Whether a program started under `KHR_parallel_shader_compile` has finished, which the test
63
+ * flips: `COMPLETION_STATUS_KHR` answers `done`. Finished by default, as every program here is.
64
+ */
65
+ readonly compiling?: { done: boolean };
61
66
  } = {},
62
67
  ) {
68
+ const compiling = options.compiling ?? { done: true };
63
69
  const extensions = new Set(options.extensions ?? []);
64
70
  const uniforms = options.uniforms ?? [];
65
71
  /* What `clearDepth` last set, so a module that borrows the value can be seen to put it back. */
@@ -136,6 +142,13 @@ export function recordingGl(
136
142
  HALF_FLOAT: 0x140b,
137
143
  DEPTH_COMPONENT: 0x1902,
138
144
  UNSIGNED_INT: 0x1405,
145
+ /* Named so a mesh's index width and each attribute's type can be read back: the buffer its
146
+ indices fill, the type every draw of it names, and how each attribute is read. See
147
+ `mesh.test.ts`. */
148
+ UNSIGNED_SHORT: 0x1403,
149
+ SHORT: 0x1402,
150
+ FLOAT: 0x1406,
151
+ ELEMENT_ARRAY_BUFFER: 0x8893,
139
152
  LINEAR: 0x2601,
140
153
  COLOR_BUFFER_BIT: 0x00004000,
141
154
  /* Named so a test can tell a mesh that declared itself deforming from one that did not. */
@@ -173,6 +186,7 @@ export function recordingGl(
173
186
  if (name === 'EXT_clip_control') {
174
187
  return { LOWER_LEFT_EXT: 0x8ca1, ZERO_TO_ONE_EXT: 0x935f, clipControlEXT: () => undefined };
175
188
  }
189
+ if (name === 'KHR_parallel_shader_compile') return { COMPLETION_STATUS_KHR: 0x91b1 };
176
190
  return {};
177
191
  },
178
192
  getShaderParameter: () => true,
@@ -200,6 +214,7 @@ export function recordingGl(
200
214
  /* Linked yes; zero active uniforms and attributes, which ends every reflection loop at once
201
215
  and is why this harness does not have to know a single uniform name. */
202
216
  getProgramParameter: (program: unknown, pname: unknown) => {
217
+ if (pname === 0x91b1) return compiling.done;
203
218
  if (pname === constants.ACTIVE_UNIFORMS) return uniforms.length;
204
219
  if (pname === constants.ACTIVE_ATTRIBUTES) return 0;
205
220
  if (pname !== constants.LINK_STATUS) return true;
@@ -800,6 +800,8 @@ export class SceneTarget {
800
800
  /** The projection's x and y scales, which turn a world radius into a screen one. */
801
801
  readonly projScale: Float32Array;
802
802
  readonly invProjection: Float32Array;
803
+ /** Where it fades out with distance, `uFade`: see `occlusionFade.ts`. */
804
+ readonly fade: Float32Array;
803
805
  /** Where the frame's jitter put the occlusion, in uv: see the shader's `uAoOffset`. */
804
806
  readonly offset?: Float32Array;
805
807
  },
@@ -959,6 +961,7 @@ export class SceneTarget {
959
961
  ao.radius,
960
962
  ao.projScale,
961
963
  ao.invProjection,
964
+ ao.fade,
962
965
  );
963
966
  }
964
967
 
@@ -26,6 +26,65 @@ export function compileProgram(
26
26
  return program;
27
27
  }
28
28
 
29
+ /**
30
+ * A program compiled and linked without asking whether that worked, since asking waits for the
31
+ * driver to finish: for a context offering `KHR_parallel_shader_compile`, whose
32
+ * `COMPLETION_STATUS_KHR` says when asking no longer waits. `finishProgram` asks then, and fails
33
+ * as loudly as `compileProgram` does; the stages are kept until it does, for their logs.
34
+ */
35
+ export interface StartedProgram {
36
+ readonly program: WebGLProgram;
37
+ readonly vertex: WebGLShader;
38
+ readonly fragment: WebGLShader;
39
+ readonly label: string;
40
+ }
41
+
42
+ export function startProgram(
43
+ gl: WebGL2RenderingContext,
44
+ vertexSource: string,
45
+ fragmentSource: string,
46
+ label: string,
47
+ ): StartedProgram {
48
+ const stage = (type: number, source: string, name: string): WebGLShader => {
49
+ const shader = gl.createShader(type);
50
+ if (shader === null) throw new Error(`[${label}.${name}] createShader failed`);
51
+ gl.shaderSource(shader, source);
52
+ gl.compileShader(shader);
53
+ return shader;
54
+ };
55
+ const vertex = stage(gl.VERTEX_SHADER, vertexSource, 'vert');
56
+ const fragment = stage(gl.FRAGMENT_SHADER, fragmentSource, 'frag');
57
+ const program = gl.createProgram();
58
+ if (program === null) throw new Error(`[${label}] createProgram failed`);
59
+ gl.attachShader(program, vertex);
60
+ gl.attachShader(program, fragment);
61
+ gl.linkProgram(program);
62
+ return { program, vertex, fragment, label };
63
+ }
64
+
65
+ /** A started program, checked as `compileProgram` checks one: its stages, then its link. */
66
+ export function finishProgram(gl: WebGL2RenderingContext, started: StartedProgram): WebGLProgram {
67
+ const { program, vertex, fragment, label } = started;
68
+ const fail = (what: string, log: string | null): never => {
69
+ gl.deleteShader(vertex);
70
+ gl.deleteShader(fragment);
71
+ gl.deleteProgram(program);
72
+ throw new Error(`[${label}${what}] ${log ?? 'no log'}`);
73
+ };
74
+ if (!gl.getShaderParameter(vertex, gl.COMPILE_STATUS)) {
75
+ fail('.vert compile failed:', gl.getShaderInfoLog(vertex));
76
+ }
77
+ if (!gl.getShaderParameter(fragment, gl.COMPILE_STATUS)) {
78
+ fail('.frag compile failed:', gl.getShaderInfoLog(fragment));
79
+ }
80
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
81
+ fail(' link failed:', gl.getProgramInfoLog(program));
82
+ }
83
+ gl.deleteShader(vertex);
84
+ gl.deleteShader(fragment);
85
+ return program;
86
+ }
87
+
29
88
  /**
30
89
  * Whether a uniform lookup that cannot reach the GPU is reported.
31
90
  *
@@ -76,6 +76,11 @@ uniform vec2 uProjScale;
76
76
  uniform mat4 uInvProjection;
77
77
  /** How far the sampling reaches, in metres of the world. */
78
78
  uniform float uRadius;
79
+ /**
80
+ * Where occlusion fades out: whole up to x metres from the eye, gone y metres past it, and no fade
81
+ * where x is below zero. See occlusionFade.ts.
82
+ */
83
+ uniform vec2 uFade;
79
84
 
80
85
  out float fragColor;
81
86
 
@@ -168,6 +173,12 @@ void main() {
168
173
  fragColor = 1.0;
169
174
  return;
170
175
  }
176
+ /* How far past the caller's fade this pixel is, and open with no walk where it is all the way. */
177
+ float faded = uFade.x < 0.0 ? 0.0 : clamp((length(p) - uFade.x) / max(uFade.y, 1e-4), 0.0, 1.0);
178
+ if (faded >= 1.0) {
179
+ fragColor = AO_STORE;
180
+ return;
181
+ }
171
182
 
172
183
  /*
173
184
  * At a silhouette, the quad derivative above is replaced by the one-sided difference that
@@ -308,7 +319,7 @@ void main() {
308
319
  visible += projectedLength * gtaoArc(h0, h1, normalAngle);
309
320
  }
310
321
 
311
- fragColor = clamp(visible / float(AO_SLICES) * AO_STORE, 0.0, 1.0);
322
+ fragColor = clamp(mix(visible / float(AO_SLICES), 1.0, faded) * AO_STORE, 0.0, 1.0);
312
323
  }
313
324
  `;
314
325
 
@@ -36,13 +36,56 @@ export const DIRECTIONALSHADOW_GLSL = `vec2 receiverPlaneDepthGradient(vec3 p) {
36
36
  );
37
37
  }
38
38
 
39
- float directionalVisibility(float receiverDepth, float compareDepth, float storedDepth) {
39
+ /* One stored depth against a receiver, in a map spanning \`span\` metres of depth. */
40
+ float directionalVisibilityAt(float receiverDepth, float compareDepth, float storedDepth, float span) {
40
41
  if (compareDepth <= storedDepth) return 1.0;
41
- float rayDistance = max(receiverDepth - storedDepth, 0.0) * uShadowDepthSpan;
42
+ float rayDistance = max(receiverDepth - storedDepth, 0.0) * span;
42
43
  float groundDistance = rayDistance * length(uDirectionalDir.xz);
43
44
  return 1.0 - shadowReach(groundDistance, uShadowMaxDistance);
44
45
  }
45
46
 
47
+ float directionalVisibility(float receiverDepth, float compareDepth, float storedDepth) {
48
+ return directionalVisibilityAt(receiverDepth, compareDepth, storedDepth, uShadowDepthSpan);
49
+ }
50
+
51
+ /* How far inside its square a receiver stands in a layer's map: 1 well inside, 0 at its border. */
52
+ float sunMapFade(vec3 p) {
53
+ if (p.z > 1.0 || p.x < 0.0 || p.x > 1.0 || p.y < 0.0 || p.y > 1.0) return 0.0;
54
+ vec2 fromCentre = abs(p.xy - 0.5) * 2.0;
55
+ return (1.0 - smoothstep(0.72, 0.98, max(fromCentre.x, fromCentre.y))) *
56
+ (1.0 - smoothstep(0.90, 1.0, p.z));
57
+ }
58
+
59
+ /*
60
+ * The moving layer at its own place, where it has a matrix of its own (movingSun.ts): the coarse
61
+ * test of shadowFactor, and its filter, each against the moving map's own depth span, and lit by
62
+ * how far inside the moving map's square the receiver stands rather than cut at its border.
63
+ */
64
+ float movingSunCoarse(vec3 pm, float span) {
65
+ float inside = sunMapFade(pm);
66
+ if (inside <= 0.0) return 1.0;
67
+ float stored = textureLod(uSunShadows, vec3(pm.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
68
+ return mix(1.0, directionalVisibilityAt(pm.z, pm.z - 1.0 / span, stored, span), inside);
69
+ }
70
+
71
+ float movingSunFiltered(vec3 pm, vec2 gradient, float span) {
72
+ float inside = sunMapFade(pm);
73
+ if (inside <= 0.0) return 1.0;
74
+ float texel = 1.35 / uShadowMapSize;
75
+ float slopeLimit = 9.1 / uShadowMapSize;
76
+ float lit = 0.0;
77
+ for (int i = 0; i < ${MAX_SHADOW_FILTER_TAPS}; i++) {
78
+ if (i >= uShadowFilterTaps) break;
79
+ vec2 sampleUv = pm.xy + DIRECTIONAL_PCF_OFFSETS[i] * texel;
80
+ vec2 texelCentre = (floor(sampleUv * uShadowMapSize) + 0.5) / uShadowMapSize;
81
+ float slopeOffset = clamp(dot(gradient, texelCentre - pm.xy), -slopeLimit, slopeLimit);
82
+ float tapReceiverDepth = pm.z + slopeOffset;
83
+ float stored = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
84
+ lit += directionalVisibilityAt(tapReceiverDepth, tapReceiverDepth - 0.14 / span, stored, span);
85
+ }
86
+ return mix(1.0, lit / float(max(uShadowFilterTaps, 1)), inside);
87
+ }
88
+
46
89
  float shadowFactor(float directionalNdl) {
47
90
  if (uShadowStrength <= 0.0) return 1.0;
48
91
 
@@ -72,6 +115,14 @@ float shadowFactor(float directionalNdl) {
72
115
  vec3 p = vLightPos.xyz / vLightPos.w;
73
116
  p = p * 0.5 + 0.5;
74
117
  vec2 depthGradient = receiverPlaneDepthGradient(p);
118
+ /* The moving layer's own place and plane where it has a matrix of its own (movingSun.ts), taken
119
+ here beside the static layer's for the same reason: above every varying-dependent return. */
120
+ vec3 pm = p;
121
+ vec2 movingGradient = depthGradient;
122
+ if (MOVING_SUN) {
123
+ pm = movingSunAt();
124
+ movingGradient = receiverPlaneDepthGradient(pm);
125
+ }
75
126
 
76
127
  // Near a surface's own light terminator its direct-light term is already
77
128
  // approaching zero, while the orthographic projection of that surface
@@ -136,7 +187,8 @@ float shadowFactor(float directionalNdl) {
136
187
  float coarseCompare = p.z - 1.0 / uShadowDepthSpan;
137
188
  float coarse =
138
189
  directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_STATIC_LAYER}.0), 0.0).r) *
139
- directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r);
190
+ (MOVING_SUN ? movingSunCoarse(pm, movingSunSpan()) :
191
+ directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_DYNAMIC_LAYER}.0), 0.0).r));
140
192
  if (uPeeledShadowEnabled != 0) {
141
193
  coarse *= directionalVisibility(p.z, coarseCompare, textureLod(uSunShadows, vec3(p.xy, ${SUN_PEELED_LAYER}.0), 0.0).r);
142
194
  }
@@ -200,7 +252,6 @@ float shadowFactor(float directionalNdl) {
200
252
  * there is no mip to select and level zero is the only thing \`texture\` could have read.
201
253
  */
202
254
  float staticDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_STATIC_LAYER}.0), 0.0).r;
203
- float dynamicDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
204
255
  staticLit += directionalVisibility(tapReceiverDepth, compare, staticDepth);
205
256
  if (uPeeledShadowEnabled != 0) {
206
257
  float peeledDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_PEELED_LAYER}.0), 0.0).r;
@@ -208,12 +259,18 @@ float shadowFactor(float directionalNdl) {
208
259
  } else {
209
260
  peeledLit += 1.0;
210
261
  }
211
- dynamicLit += directionalVisibility(tapReceiverDepth, compare, dynamicDepth);
262
+ /* The moving layer here where it shares the static one's place; at its own below otherwise.
263
+ Branched on the switch itself, never its negation: see overridableConstants in transform.mjs. */
264
+ if (MOVING_SUN) {
265
+ } else {
266
+ float dynamicDepth = textureLod(uSunShadows, vec3(sampleUv, ${SUN_DYNAMIC_LAYER}.0), 0.0).r;
267
+ dynamicLit += directionalVisibility(tapReceiverDepth, compare, dynamicDepth);
268
+ }
212
269
  }
213
270
  float tapCount = float(max(uShadowFilterTaps, 1));
214
271
  staticLit /= tapCount;
215
272
  peeledLit /= tapCount;
216
- dynamicLit /= tapCount;
273
+ dynamicLit = MOVING_SUN ? movingSunFiltered(pm, movingGradient, movingSunSpan()) : dynamicLit / tapCount;
217
274
 
218
275
  // Independent layers preserve every recorded fact at an overlap: each
219
276
  // caster keeps its own distance fade, and their transmissions compose
@@ -234,11 +291,11 @@ float shadowFactor(float directionalNdl) {
234
291
  * behind the receiver lending it colour within the spread, which only a receiver between two panes
235
292
  * meets. One function for both layers, because two copies were most of the lit shaders' growth.
236
293
  */
237
- vec3 sunGlassLayer(vec3 p, float paneLayer, float tintLayer, float bias, float texel, float uvPerMetre) {
294
+ vec3 sunGlassLayer(vec3 p, float paneLayer, float tintLayer, float bias, float texel, float uvPerMetre, float span) {
238
295
  float tintSize = float(textureSize(uSunGlassTints, 0).x);
239
296
  float clarity = textureLod(uSunGlassTints, vec3(p.xy, tintLayer), 0.0).a;
240
297
  float behind =
241
- max(p.z - textureLod(uSunShadows, vec3(p.xy, paneLayer), 0.0).r, 0.0) * uShadowDepthSpan;
298
+ max(p.z - textureLod(uSunShadows, vec3(p.xy, paneLayer), 0.0).r, 0.0) * span;
242
299
  float radius = clamp(
243
300
  (1.0 - clarity) * ${FROST_SPREAD.toFixed(6)} * behind * uvPerMetre,
244
301
  texel,
@@ -278,6 +335,13 @@ vec3 sunGlassLookup(float directionalNdl) {
278
335
  /* Map units per metre along the receiver, from derivatives taken first in uniform control flow,
279
336
  so a frost spread stated in metres lands the same whatever size of light matrix a consumer built. */
280
337
  float uvPerMetre = length(dFdx(p.xy)) / max(length(dFdx(vWorldPos)), 1e-6);
338
+ /* The moving glass at its own place where its layer has a matrix of its own: movingSun.ts. */
339
+ vec3 pm = p;
340
+ float movingPerMetre = uvPerMetre;
341
+ if (MOVING_SUN) {
342
+ pm = movingSunAt();
343
+ movingPerMetre = length(dFdx(pm.xy)) / max(length(dFdx(vWorldPos)), 1e-6);
344
+ }
281
345
  if (uShadowStrength <= 0.0 || textureSize(uSunGlassTints, 0).x <= 1) return vec3(1.0);
282
346
  if (directionalNdl <= 0.0) return vec3(1.0);
283
347
  if (p.z > 1.0 || p.x < 0.0 || p.x > 1.0 || p.y < 0.0 || p.y > 1.0) return vec3(1.0);
@@ -295,8 +359,18 @@ vec3 sunGlassLookup(float directionalNdl) {
295
359
 
296
360
  float bias = 0.14 / uShadowDepthSpan;
297
361
  float texel = 1.35 / uShadowMapSize;
298
- vec3 staticTint = sunGlassLayer(p, ${SUN_GLASS_STATIC_LAYER}.0, 0.0, bias, texel, uvPerMetre);
299
- vec3 movingTint = sunGlassLayer(p, ${SUN_GLASS_MOVING_LAYER}.0, 1.0, bias, texel, uvPerMetre);
362
+ vec3 staticTint = sunGlassLayer(p, ${SUN_GLASS_STATIC_LAYER}.0, 0.0, bias, texel, uvPerMetre, uShadowDepthSpan);
363
+ vec3 movingTint = MOVING_SUN && sunMapFade(pm) <= 0.0
364
+ ? vec3(1.0)
365
+ : sunGlassLayer(
366
+ pm,
367
+ ${SUN_GLASS_MOVING_LAYER}.0,
368
+ 1.0,
369
+ MOVING_SUN ? 0.14 / movingSunSpan() : bias,
370
+ texel,
371
+ movingPerMetre,
372
+ MOVING_SUN ? movingSunSpan() : uShadowDepthSpan
373
+ );
300
374
  return mix(vec3(1.0), staticTint * movingTint, strength);
301
375
  }
302
376
 
@@ -36,7 +36,10 @@ export type LitSwitch =
36
36
  | 'DRIFT_LIGHT'
37
37
  | 'PHYSICAL_SPECULAR'
38
38
  | 'SURFACE_OVERLAY'
39
- | 'REFLECTION_MAPS';
39
+ | 'REFLECTION_MAPS'
40
+ | 'WORLD_UVS'
41
+ | 'MOVING_SUN'
42
+ | 'LAYERED';
40
43
 
41
44
  /** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
42
45
  export function noLitSwitches(): Record<LitSwitch, boolean> {
@@ -49,6 +52,9 @@ export function noLitSwitches(): Record<LitSwitch, boolean> {
49
52
  PHYSICAL_SPECULAR: false,
50
53
  SURFACE_OVERLAY: false,
51
54
  REFLECTION_MAPS: false,
55
+ WORLD_UVS: false,
56
+ MOVING_SUN: false,
57
+ LAYERED: false,
52
58
  };
53
59
  }
54
60