@driftengine/core 4.11.1 → 4.13.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 (231) hide show
  1. package/CHANGELOG.json +102 -0
  2. package/README.md +18 -9
  3. package/dist/geometry/meshBuilder.d.ts +10 -0
  4. package/dist/geometry/meshBuilder.js +11 -2
  5. package/dist/index.d.ts +5 -2
  6. package/dist/index.js +3 -2
  7. package/dist/math/color.d.ts +25 -0
  8. package/dist/math/color.js +37 -0
  9. package/dist/render/backend/webgl2/renderer.d.ts +33 -5
  10. package/dist/render/backend/webgl2/renderer.js +123 -49
  11. package/dist/render/backend/webgl2/skinScatter.d.ts +3 -1
  12. package/dist/render/backend/webgl2/skinScatter.js +5 -1
  13. package/dist/render/backend/webgpu/boltPass.d.ts +1 -1
  14. package/dist/render/backend/webgpu/causticsPass.d.ts +1 -1
  15. package/dist/render/backend/webgpu/filmPass.d.ts +1 -1
  16. package/dist/render/backend/webgpu/flockPass.d.ts +1 -1
  17. package/dist/render/backend/webgpu/giFieldPass.d.ts +3 -1
  18. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +10 -1
  19. package/dist/render/backend/webgpu/gpuDrivenPass.js +28 -11
  20. package/dist/render/backend/webgpu/panelPass.d.ts +1 -1
  21. package/dist/render/backend/webgpu/postPass.d.ts +2 -2
  22. package/dist/render/backend/webgpu/renderer.d.ts +21 -8
  23. package/dist/render/backend/webgpu/renderer.js +103 -38
  24. package/dist/render/backend/webgpu/scatterPass.d.ts +1 -1
  25. package/dist/render/backend/webgpu/skinScatterPass.d.ts +5 -1
  26. package/dist/render/backend/webgpu/skinScatterPass.js +9 -3
  27. package/dist/render/backend/webgpu/skyPass.d.ts +11 -1
  28. package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
  29. package/dist/render/backend/webgpu/windStreakPass.d.ts +1 -1
  30. package/dist/render/causticsRenderer.d.ts +3 -1
  31. package/dist/render/causticsRenderer.js +5 -1
  32. package/dist/render/decalPass.d.ts +3 -1
  33. package/dist/render/decalPass.js +4 -1
  34. package/dist/render/flockRenderer.d.ts +1 -1
  35. package/dist/render/flockRenderer.js +4 -1
  36. package/dist/render/globalMediumPass.d.ts +3 -1
  37. package/dist/render/globalMediumPass.js +5 -1
  38. package/dist/render/gradeColor.d.ts +21 -0
  39. package/dist/render/gradeColor.js +64 -0
  40. package/dist/render/lightMatrix.d.ts +13 -0
  41. package/dist/render/lightMatrix.js +17 -0
  42. package/dist/render/passGrade.d.ts +13 -0
  43. package/dist/render/passGrade.js +47 -0
  44. package/dist/render/plumeRenderer.d.ts +1 -1
  45. package/dist/render/plumeRenderer.js +4 -1
  46. package/dist/render/renderQuality.d.ts +57 -18
  47. package/dist/render/renderQuality.js +3 -3
  48. package/dist/render/sceneTarget.d.ts +2 -0
  49. package/dist/render/sceneTarget.js +1 -0
  50. package/dist/render/sdfTextRenderer.d.ts +1 -1
  51. package/dist/render/sdfTextRenderer.js +1 -1
  52. package/dist/render/shaders/arcane.d.ts +1 -1
  53. package/dist/render/shaders/arcane.js +5 -1
  54. package/dist/render/shaders/bolt.d.ts +1 -1
  55. package/dist/render/shaders/bolt.js +4 -1
  56. package/dist/render/shaders/caustics.d.ts +1 -1
  57. package/dist/render/shaders/caustics.js +4 -1
  58. package/dist/render/shaders/decalProject.js +12 -2
  59. package/dist/render/shaders/film.d.ts +1 -1
  60. package/dist/render/shaders/film.js +22 -9
  61. package/dist/render/shaders/fire.d.ts +1 -1
  62. package/dist/render/shaders/fire.js +4 -1
  63. package/dist/render/shaders/flat/areaLight.d.ts +1 -1
  64. package/dist/render/shaders/flat/areaLight.js +1 -1
  65. package/dist/render/shaders/flat/glassTint.d.ts +1 -1
  66. package/dist/render/shaders/flat/glassTint.js +1 -0
  67. package/dist/render/shaders/flat/index.d.ts +5 -0
  68. package/dist/render/shaders/flat/index.js +1 -1
  69. package/dist/render/shaders/flat/layerLooks.d.ts +16 -0
  70. package/dist/render/shaders/flat/layerLooks.js +108 -0
  71. package/dist/render/shaders/flat/layered.d.ts +6 -4
  72. package/dist/render/shaders/flat/layered.js +77 -13
  73. package/dist/render/shaders/flat/main.d.ts +1 -1
  74. package/dist/render/shaders/flat/main.js +7 -2
  75. package/dist/render/shaders/flat/models.js +1 -1
  76. package/dist/render/shaders/flat/preamble.js +6 -0
  77. package/dist/render/shaders/flock.d.ts +1 -1
  78. package/dist/render/shaders/flock.js +4 -1
  79. package/dist/render/shaders/generated/arcane.wgsl.d.ts +11 -1
  80. package/dist/render/shaders/generated/arcane.wgsl.js +11 -1
  81. package/dist/render/shaders/generated/bolt.wgsl.d.ts +12 -2
  82. package/dist/render/shaders/generated/bolt.wgsl.js +12 -2
  83. package/dist/render/shaders/generated/caustics.wgsl.d.ts +12 -2
  84. package/dist/render/shaders/generated/caustics.wgsl.js +12 -2
  85. package/dist/render/shaders/generated/decalProject.wgsl.d.ts +6 -1
  86. package/dist/render/shaders/generated/decalProject.wgsl.js +6 -1
  87. package/dist/render/shaders/generated/film.wgsl.d.ts +12 -2
  88. package/dist/render/shaders/generated/film.wgsl.js +12 -2
  89. package/dist/render/shaders/generated/fire.wgsl.d.ts +11 -1
  90. package/dist/render/shaders/generated/fire.wgsl.js +11 -1
  91. package/dist/render/shaders/generated/flat.wgsl.d.ts +144 -16
  92. package/dist/render/shaders/generated/flat.wgsl.js +183 -55
  93. package/dist/render/shaders/generated/flock.wgsl.d.ts +12 -2
  94. package/dist/render/shaders/generated/flock.wgsl.js +12 -2
  95. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +12 -2
  96. package/dist/render/shaders/generated/globalMedium.wgsl.js +12 -2
  97. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +36 -16
  98. package/dist/render/shaders/generated/lightVolume.wgsl.js +39 -19
  99. package/dist/render/shaders/generated/panel.wgsl.d.ts +12 -2
  100. package/dist/render/shaders/generated/panel.wgsl.js +12 -2
  101. package/dist/render/shaders/generated/rush.wgsl.d.ts +31 -26
  102. package/dist/render/shaders/generated/rush.wgsl.js +31 -26
  103. package/dist/render/shaders/generated/scatter.wgsl.d.ts +12 -2
  104. package/dist/render/shaders/generated/scatter.wgsl.js +12 -2
  105. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +11 -1
  106. package/dist/render/shaders/generated/skinBlur.wgsl.js +11 -1
  107. package/dist/render/shaders/generated/sky.wgsl.d.ts +12 -2
  108. package/dist/render/shaders/generated/sky.wgsl.js +12 -2
  109. package/dist/render/shaders/generated/smoke.wgsl.d.ts +11 -1
  110. package/dist/render/shaders/generated/smoke.wgsl.js +11 -1
  111. package/dist/render/shaders/generated/text.wgsl.d.ts +11 -1
  112. package/dist/render/shaders/generated/text.wgsl.js +11 -1
  113. package/dist/render/shaders/generated/water.wgsl.d.ts +12 -2
  114. package/dist/render/shaders/generated/water.wgsl.js +12 -2
  115. package/dist/render/shaders/generated/windStreaks.wgsl.d.ts +12 -2
  116. package/dist/render/shaders/generated/windStreaks.wgsl.js +12 -2
  117. package/dist/render/shaders/globalMedium.js +14 -1
  118. package/dist/render/shaders/gpudriven/present.wgsl.d.ts +15 -6
  119. package/dist/render/shaders/gpudriven/present.wgsl.js +71 -8
  120. package/dist/render/shaders/lightVolume.js +4 -1
  121. package/dist/render/shaders/line.d.ts +1 -1
  122. package/dist/render/shaders/outputTransform.d.ts +8 -2
  123. package/dist/render/shaders/outputTransform.js +18 -11
  124. package/dist/render/shaders/panel.d.ts +1 -1
  125. package/dist/render/shaders/panel.js +5 -1
  126. package/dist/render/shaders/particle.d.ts +3 -3
  127. package/dist/render/shaders/rush.js +8 -0
  128. package/dist/render/shaders/scatter.d.ts +1 -1
  129. package/dist/render/shaders/scatter.js +4 -1
  130. package/dist/render/shaders/sdfText.d.ts +1 -1
  131. package/dist/render/shaders/skinBlur.js +5 -1
  132. package/dist/render/shaders/sky.d.ts +1 -1
  133. package/dist/render/shaders/sky.js +5 -1
  134. package/dist/render/shaders/smoke.d.ts +1 -1
  135. package/dist/render/shaders/smoke.js +4 -1
  136. package/dist/render/shaders/text.d.ts +1 -1
  137. package/dist/render/shaders/text.js +4 -1
  138. package/dist/render/shaders/water.d.ts +1 -1
  139. package/dist/render/shaders/water.js +12 -2
  140. package/dist/render/shaders/windStreaks.d.ts +1 -1
  141. package/dist/render/shaders/windStreaks.js +4 -1
  142. package/dist/render/surfaceLayerLooks.d.ts +92 -0
  143. package/dist/render/surfaceLayerLooks.js +102 -0
  144. package/dist/render/surfaceLayers.d.ts +47 -6
  145. package/dist/render/surfaceLayers.js +5 -1
  146. package/dist/render/textRenderer.d.ts +3 -1
  147. package/dist/render/textRenderer.js +7 -1
  148. package/dist/render/vertexDefaults.d.ts +1 -1
  149. package/dist/render/vertexDefaults.js +1 -1
  150. package/dist/render/waterRenderer.d.ts +1 -1
  151. package/dist/render/waterRenderer.js +4 -1
  152. package/dist/render/windStreakRenderer.d.ts +3 -1
  153. package/dist/render/windStreakRenderer.js +5 -1
  154. package/package.json +6 -6
  155. package/scripts/browser.d.mts +5 -0
  156. package/scripts/browser.mjs +68 -14
  157. package/scripts/look.d.mts +81 -0
  158. package/scripts/look.mjs +380 -0
  159. package/src/geometry/meshBuilder.ts +21 -2
  160. package/src/index.ts +13 -2
  161. package/src/math/color.ts +39 -0
  162. package/src/render/backend/webgl2/renderer.ts +174 -46
  163. package/src/render/backend/webgl2/skinScatter.ts +5 -0
  164. package/src/render/backend/webgpu/giFieldPass.ts +3 -1
  165. package/src/render/backend/webgpu/gpuDrivenPass.ts +29 -13
  166. package/src/render/backend/webgpu/renderer.ts +112 -37
  167. package/src/render/backend/webgpu/skinScatterPass.ts +17 -1
  168. package/src/render/causticsRenderer.ts +5 -0
  169. package/src/render/decalPass.ts +3 -0
  170. package/src/render/flockRenderer.ts +5 -0
  171. package/src/render/globalMediumPass.ts +10 -1
  172. package/src/render/gradeColor.ts +90 -0
  173. package/src/render/lightMatrix.ts +32 -0
  174. package/src/render/passGrade.ts +58 -0
  175. package/src/render/plumeRenderer.ts +5 -0
  176. package/src/render/renderQuality.ts +60 -21
  177. package/src/render/sceneTarget.ts +3 -0
  178. package/src/render/sdfTextRenderer.ts +1 -1
  179. package/src/render/shaders/arcane.ts +5 -1
  180. package/src/render/shaders/bolt.ts +4 -1
  181. package/src/render/shaders/caustics.ts +4 -1
  182. package/src/render/shaders/decalProject.ts +12 -2
  183. package/src/render/shaders/film.ts +22 -9
  184. package/src/render/shaders/fire.ts +4 -1
  185. package/src/render/shaders/flat/areaLight.ts +1 -1
  186. package/src/render/shaders/flat/glassTint.ts +3 -1
  187. package/src/render/shaders/flat/index.ts +6 -1
  188. package/src/render/shaders/flat/layerLooks.ts +108 -0
  189. package/src/render/shaders/flat/layered.ts +78 -13
  190. package/src/render/shaders/flat/main.ts +7 -2
  191. package/src/render/shaders/flat/models.ts +1 -1
  192. package/src/render/shaders/flat/preamble.ts +6 -0
  193. package/src/render/shaders/flock.ts +4 -1
  194. package/src/render/shaders/generated/arcane.wgsl.ts +11 -1
  195. package/src/render/shaders/generated/bolt.wgsl.ts +12 -2
  196. package/src/render/shaders/generated/caustics.wgsl.ts +12 -2
  197. package/src/render/shaders/generated/decalProject.wgsl.ts +6 -1
  198. package/src/render/shaders/generated/film.wgsl.ts +12 -2
  199. package/src/render/shaders/generated/fire.wgsl.ts +11 -1
  200. package/src/render/shaders/generated/flat.wgsl.ts +183 -55
  201. package/src/render/shaders/generated/flock.wgsl.ts +12 -2
  202. package/src/render/shaders/generated/globalMedium.wgsl.ts +12 -2
  203. package/src/render/shaders/generated/lightVolume.wgsl.ts +39 -19
  204. package/src/render/shaders/generated/panel.wgsl.ts +12 -2
  205. package/src/render/shaders/generated/rush.wgsl.ts +31 -26
  206. package/src/render/shaders/generated/scatter.wgsl.ts +12 -2
  207. package/src/render/shaders/generated/skinBlur.wgsl.ts +11 -1
  208. package/src/render/shaders/generated/sky.wgsl.ts +12 -2
  209. package/src/render/shaders/generated/smoke.wgsl.ts +11 -1
  210. package/src/render/shaders/generated/text.wgsl.ts +11 -1
  211. package/src/render/shaders/generated/water.wgsl.ts +12 -2
  212. package/src/render/shaders/generated/windStreaks.wgsl.ts +12 -2
  213. package/src/render/shaders/globalMedium.ts +14 -1
  214. package/src/render/shaders/gpudriven/present.wgsl.ts +72 -8
  215. package/src/render/shaders/lightVolume.ts +4 -1
  216. package/src/render/shaders/outputTransform.ts +19 -11
  217. package/src/render/shaders/panel.ts +5 -1
  218. package/src/render/shaders/rush.ts +8 -0
  219. package/src/render/shaders/scatter.ts +4 -1
  220. package/src/render/shaders/skinBlur.ts +5 -1
  221. package/src/render/shaders/sky.ts +5 -1
  222. package/src/render/shaders/smoke.ts +4 -1
  223. package/src/render/shaders/text.ts +4 -1
  224. package/src/render/shaders/water.ts +12 -2
  225. package/src/render/shaders/windStreaks.ts +4 -1
  226. package/src/render/surfaceLayerLooks.ts +186 -0
  227. package/src/render/surfaceLayers.ts +52 -7
  228. package/src/render/textRenderer.ts +6 -0
  229. package/src/render/vertexDefaults.ts +1 -1
  230. package/src/render/waterRenderer.ts +5 -0
  231. package/src/render/windStreakRenderer.ts +5 -0
@@ -68,6 +68,38 @@ export function computeLightMatrix(
68
68
  return radius * 6;
69
69
  }
70
70
 
71
+ /**
72
+ * A directional light's matrix covering a box of known size whole: a model, a level, a room. The
73
+ * sphere around the box, at its centre, through `computeLightMatrix`, widened by the half texel the
74
+ * centre's snap may move it so a corner on the sphere stays inside. Returns the depth span, as that
75
+ * does.
76
+ *
77
+ * **For a scene whose extent is known rather than one that follows a viewer.** A radius chosen by
78
+ * eye around a guessed centre leaves part of the model outside the map, where it casts nothing; the
79
+ * bounds are the answer, and a loader has them. **What it gives up** is resolution where the box is
80
+ * large: the map's texels are spread over all of it, so a building's shadows are coarser than a
81
+ * focus on the viewer would draw them, which is what `computeLightMatrix` around the viewer is for.
82
+ */
83
+ export function computeLightMatrixForBounds(
84
+ lightDir: Vec3,
85
+ min: Readonly<Vec3>,
86
+ max: Readonly<Vec3>,
87
+ shadowMapSize: number,
88
+ out: mat4,
89
+ ): number {
90
+ const half = Math.hypot(max[0] - min[0], max[1] - min[1], max[2] - min[2]) / 2;
91
+ const radius = Math.max(half, 1e-3) * (1 + 2 / Math.max(shadowMapSize, 1));
92
+ return computeLightMatrix(
93
+ lightDir,
94
+ (min[0] + max[0]) / 2,
95
+ (min[1] + max[1]) / 2,
96
+ (min[2] + max[2]) / 2,
97
+ radius,
98
+ shadowMapSize,
99
+ out,
100
+ );
101
+ }
102
+
71
103
  /**
72
104
  * The depth an orthographic light matrix spans, in metres: what `computeLightMatrix` returns, read
73
105
  * back off any such matrix. Its depth row is the light's axis scaled by two over the span, so the
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Which grade a pass applies to what it draws, decided once for both backends.
3
+ *
4
+ * **Every pass grades where nothing after it will**, so a colour handed to the renderer means linear
5
+ * light wherever it lands. Three facts decide where that is:
6
+ *
7
+ * - **A probe's face or a capture stores radiance** for something else to light with or to show, so
8
+ * it is never graded. The frame grades what it reads of one; graded at the bake as well, the curve
9
+ * would be applied twice.
10
+ * - **A frame whose resolve grades** (`hdrScene` over a scene target) leaves every pass before the
11
+ * resolve linear, because grading early throws away the range the float target exists to keep.
12
+ * - **Past the present, a pass lands on the canvas the resolve has already written**, so nothing
13
+ * after it will grade it and it grades itself with the frame's forward code, composite or not. An
14
+ * interface drawn after `endFrame` escapes the screen-space chain; it does not escape the grade.
15
+ *
16
+ * **An interface takes the screen encode and not the curve** (`interfaceGrade`). A tone curve and
17
+ * an exposure are how a scene's light is brought to the screen, and a caption or a panel is not
18
+ * light: its colour was picked for the screen, so it is encoded wherever the frame is and comes out
19
+ * as picked under every transform, at every exposure. What it gives up is an interface drawn inside
20
+ * a frame whose resolve grades (`hdrScene`): that one is part of the picture the resolve tone maps,
21
+ * and is drawn after `endFrame` to stay out of it. What would make any of this wrong is a composite
22
+ * step running after the present, which none does.
23
+ */
24
+ import { forwardTransformCode } from './vertexDefaults.ts';
25
+
26
+ /**
27
+ * The `uOutputTransform` a pass uploads: `storing` inside a probe's face or a capture, `presented`
28
+ * once the frame's resolve has run, `resolveGrades` where that resolve applies the transform.
29
+ */
30
+ export function passGradeCode(
31
+ storing: boolean,
32
+ presented: boolean,
33
+ resolveGrades: boolean,
34
+ outputTransform: string,
35
+ ): number {
36
+ if (storing) return 0;
37
+ return presented || !resolveGrades ? forwardTransformCode(outputTransform) : 0;
38
+ }
39
+
40
+ /** The `uOutputExposure` beside it: the frame's exposure wherever the pass grades, else one. */
41
+ export function passGradeExposure(
42
+ storing: boolean,
43
+ presented: boolean,
44
+ resolveGrades: boolean,
45
+ exposure: number,
46
+ ): number {
47
+ if (storing) return 1;
48
+ return presented || !resolveGrades ? exposure : 1;
49
+ }
50
+
51
+ /**
52
+ * The code an interface draws with, given the pass's: the screen encode wherever the pass grades at
53
+ * all, and nothing where it does not. Never a curve, so never an exposure either: a screen-space
54
+ * caption, a panel or a 2D layer keeps the colour it was picked as whatever the scene is graded by.
55
+ */
56
+ export function interfaceGrade(passCode: number): number {
57
+ return passCode === 0 ? 0 : 1;
58
+ }
@@ -173,6 +173,8 @@ export class PlumeRenderer {
173
173
  originX = 0,
174
174
  originY = 0,
175
175
  originZ = 0,
176
+ outputTransform = 0,
177
+ outputExposure = 1,
176
178
  ): void {
177
179
  if (this.indexCount === 0) return;
178
180
  const u = this.uniforms;
@@ -200,6 +202,9 @@ export class PlumeRenderer {
200
202
  gl.uniform3f(u['uTint'] ?? null, this.tint[0], this.tint[1], this.tint[2]);
201
203
  gl.uniform1i(u['uClipEnabled'] ?? null, clipPlane === null ? 0 : 1);
202
204
  if (clipPlane !== null) gl.uniform4fv(u['uClipPlane'] ?? null, clipPlane);
205
+ /* The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
206
+ gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
207
+ gl.uniform1f(u['uOutputExposure'] ?? null, outputExposure);
203
208
  bindAtmosphere(gl, u, atmosphere, atmosphereCameraY, underwaterEnabled);
204
209
 
205
210
  gl.enable(gl.BLEND);
@@ -61,22 +61,35 @@ export type SkinScattering = 'pre-integrated' | 'screen-space';
61
61
 
62
62
  export interface RenderQuality {
63
63
  /**
64
- * The transfer curve applied at the end of every shaded pass.
65
- *
66
- * Construction-time, like every other quality control here, and `none` by default so
67
- * no existing world changes by a single bit.
68
- *
69
- * **The symptom, written down because nobody searches for a transfer curve they do not know
70
- * exists.** With `none`, values above 1 clip flat rather than rolling off, so a scene with
71
- * bright sources against dark surroundings reads as crushed and desaturated: the highlights
72
- * go to a hard white with no colour left in them and the midtones look muddy. That is
73
- * reported by eye as "dark", "flat" or "not vivid", which sounds like a lighting problem, so
74
- * that is where people look. A consumer built, tuned and shipped six worlds before anybody
75
- * said the word. If a scene looks like that, try `outputTransform: 'aces'` with an
76
- * `outputExposure` before touching a single light.
77
- *
78
- * The default stays `none`, and that is not the fault: a curve that changed every existing
79
- * consumer's output would be far worse than one nobody found.
64
+ * The transfer curve applied at the end of every shaded pass. Construction-time, like every
65
+ * other quality control here.
66
+ *
67
+ * **`srgb` by default since 4.13.0, the encoding a display expects**: the light the renderer
68
+ * computes is linear, and a texture's colours are decoded to linear when they are read, so a
69
+ * frame written without encoding them back is a frame of linear values read as display values —
70
+ * every midtone far too dark. It is the encoding most renderers apply by default, so a model
71
+ * drawn here with no options reads as it does elsewhere. **What it gives up** is a highlight's
72
+ * colour: above 1 each channel clips on its own, so a bright saturated source goes to white
73
+ * rather than rolling off, which a curve with a shoulder (`aces`, `shoulder`) is for.
74
+ *
75
+ * **`none` was the default until 4.13.0, and it is the frame that reads as dark**: values
76
+ * written as they are computed, which is right only for a world whose colours were chosen by
77
+ * eye as display values and lit to match. It was reported by eye as "dark", "flat" or "not
78
+ * vivid", which sounds like a lighting problem, so that is where people looked; a consumer
79
+ * built, tuned and shipped six worlds under it before anybody said the word, and a model drawn
80
+ * from the manual came out darker than in any other engine. A world graded under it says
81
+ * `outputTransform: 'none'` and draws exactly as it did. **What would make `srgb` the wrong
82
+ * default** is a renderer whose colours are not linear, which this one's are not.
83
+ *
84
+ * **Every pass applies it where nothing after it will**, and the clear colour with them, so a
85
+ * colour handed to the renderer is linear wherever it lands: a frame with `hdrScene` is graded
86
+ * once at the resolve, and one without it is graded pass by pass to the same curve. A colour
87
+ * picked by eye is a display value, so it is stated through `srgbColor` (or a mesh built with
88
+ * `colorSpace: 'srgb'`), which gives back exactly the colour picked where a surface is lit at 1
89
+ * under `srgb`, and through the curve under a tone curve. **An interface** (screen text, a panel,
90
+ * the 2D layer) takes the screen encode and never the curve or the exposure, so its colours come
91
+ * out as picked under every transform; inside an `hdrScene` frame it is part of the picture the
92
+ * resolve grades, and is drawn after `endFrame` to stay out of the curve.
80
93
  */
81
94
  readonly outputTransform: OutputTransform;
82
95
  /**
@@ -270,9 +283,35 @@ export interface RenderQuality {
270
283
  * at a shadow's edge — that is what the blur is for.
271
284
  */
272
285
  readonly skinScattering: SkinScattering;
273
- /** Horizontal world-space reach over which a directional shadow dissolves. */
286
+ /**
287
+ * How far a sun shadow may reach along the ground from its caster, in metres, before it
288
+ * dissolves. **Not a distance from the camera**: a shadow's own length, from where it leaves its
289
+ * caster to where it lands, fading from 35% of this to all of it.
290
+ *
291
+ * **1,000 since 4.13.0, so no building's shadow dissolves**: the fade starts at 350 m, past any
292
+ * wall, tower or canyon a shadow map covers at a useful resolution, so what bounds a shadow is
293
+ * its map, whose edge already fades. **What it gives up** is a way to hide a long shadow's
294
+ * stair-stepping at a low sun, which is what a short reach was for. It was 6 until 4.13.0: a
295
+ * tuning for short casters, which left a building's walls and arcades casting nothing on its
296
+ * floor under a high sun — the shadows vanished silently and read as random lighting. **What
297
+ * would make it wrong** is a scene whose shadows are too coarse to show at their full length;
298
+ * there a reach of the length that looks right is the setting, and the map around the viewer
299
+ * (`computeLightMatrix`) the better fix.
300
+ */
274
301
  readonly directionalShadowMaxDistance: number;
275
- /** Maximum horizontal projection per vertical metre before low-angle shadows vanish. */
302
+ /**
303
+ * How low the sun may stand before its shadows fade, as the ground a metre of height may shadow:
304
+ * the sun's horizontal reach per vertical metre, so 1 is 45° of elevation and 60 about 1°. Every
305
+ * sun shadow fades from 65% of this to all of it.
306
+ *
307
+ * **60 since 4.13.0: shadows hold until the sun is about a degree above the horizon**, the
308
+ * stretch of a day when they are longest and most worth seeing. **What it gives up**: a sun on
309
+ * the horizon throws shadows kilometres long that a map can hold only as stripes, and the
310
+ * last degree is where they fade rather than show. It was 3 until 4.13.0, which dropped every
311
+ * sun shadow below about 18° — an evening without shadows. **What would make it wrong** is a
312
+ * scene whose low sun streaks; a smaller number here is the setting, at the cost of the
313
+ * shadows going earlier in the evening.
314
+ */
276
315
  readonly directionalShadowMaxSlope: number;
277
316
  /**
278
317
  * Metres a point light may drift from where its shadow was baked before re-baking.
@@ -958,7 +997,7 @@ export const DIRECTIONAL_SHADOW_FADE_START = 0.65;
958
997
  * six times, while the directional map spends all of it on one focused area.
959
998
  */
960
999
  export const DEFAULT_RENDER_QUALITY: Readonly<RenderQuality> = Object.freeze({
961
- outputTransform: 'none',
1000
+ outputTransform: 'srgb',
962
1001
  outputExposure: 1,
963
1002
  pointLightFalloff: 'smooth',
964
1003
  /**
@@ -1006,8 +1045,8 @@ export const DEFAULT_RENDER_QUALITY: Readonly<RenderQuality> = Object.freeze({
1006
1045
  glassShadows: 'full',
1007
1046
  pipelineCompile: 'wait',
1008
1047
  skinScattering: 'pre-integrated',
1009
- directionalShadowMaxDistance: 6,
1010
- directionalShadowMaxSlope: 3,
1048
+ directionalShadowMaxDistance: 1000,
1049
+ directionalShadowMaxSlope: 60,
1011
1050
  pointShadowRebakeDistance: 0.001,
1012
1051
  pointShadowFacesPerFrame: 2,
1013
1052
  /*
@@ -785,6 +785,8 @@ export class SceneTarget {
785
785
  readonly transform: number;
786
786
  readonly exposure: number;
787
787
  readonly film?: Float32Array;
788
+ /** Whether the forward passes graded the scene because this resolve does not. */
789
+ readonly sceneGraded?: boolean;
788
790
  } = {
789
791
  transform: 0,
790
792
  exposure: 1,
@@ -1030,6 +1032,7 @@ export class SceneTarget {
1030
1032
  gl.uniform1f(this.uniforms['uReach'] ?? null, reachUv);
1031
1033
  gl.uniform1i(this.uniforms['uOutputTransform'] ?? null, grade.transform);
1032
1034
  gl.uniform1f(this.uniforms['uOutputExposure'] ?? null, grade.exposure);
1035
+ gl.uniform1i(this.uniforms['uSceneGraded'] ?? null, grade.sceneGraded === true ? 1 : 0);
1033
1036
  if (grade.film !== undefined) {
1034
1037
  gl.uniform4fv(this.uniforms['uFilmA'] ?? null, grade.film, 0, 4);
1035
1038
  gl.uniform4fv(this.uniforms['uFilmB'] ?? null, grade.film, 4, 4);
@@ -112,7 +112,7 @@ export class SdfTextRenderer {
112
112
  */
113
113
  /**
114
114
  * `outputTransform` and `outputExposure` come from the renderer rather than from here, because
115
- * whether this pass grades depends on whether anything after it will. See `Renderer.gradeCode`.
115
+ * whether this pass grades depends on whether anything after it will. See `passGrade.ts`.
116
116
  */
117
117
  draw(
118
118
  viewProj: ReadonlyMat4,
@@ -1,4 +1,5 @@
1
1
  import { FOG_GLSL } from './fog.ts';
2
+ import { OUTPUT_TRANSFORM_GLSL } from './outputTransform.ts';
2
3
 
3
4
  /**
4
5
  * An arcane aura: the volume a floating object sits inside.
@@ -31,6 +32,8 @@ ${FOG_GLSL}
31
32
  uniform int uNoiseOctaves;
32
33
  uniform vec4 uClipPlane;
33
34
  uniform int uClipEnabled;
35
+ /* Before \`uTint\`, where fire and smoke declare it, so one field table serves all three. */
36
+ ${OUTPUT_TRANSFORM_GLSL}
34
37
  /** The caller's colour, so an aura shifts with the world's own palette. */
35
38
  uniform vec3 uTint;
36
39
 
@@ -98,6 +101,7 @@ void main() {
98
101
  col = mix(col, mediumColor(), fog);
99
102
  alpha *= 1.0 - fog;
100
103
 
101
- outColor = vec4(col, alpha);
104
+ /* Graded where nothing after this will grade it, as every pass is. The alpha is coverage. */
105
+ outColor = vec4(applyOutputTransform(col), alpha);
102
106
  }
103
107
  `;
@@ -1,4 +1,5 @@
1
1
  import { FOG_GLSL } from './fog.ts';
2
+ import { OUTPUT_TRANSFORM_GLSL } from './outputTransform.ts';
2
3
 
3
4
  /**
4
5
  * Electrical arcs: a filament with a saturating core and a wide soft glow.
@@ -95,6 +96,7 @@ ${FOG_GLSL}
95
96
  uniform vec3 uCameraPos;
96
97
 
97
98
  out vec4 outColor;
99
+ ${OUTPUT_TRANSFORM_GLSL}
98
100
 
99
101
  float hash11(float p) {
100
102
  return fract(sin(p * 12.9898) * 43758.5453123);
@@ -165,6 +167,7 @@ void main() {
165
167
  * faint: at a typical energy of 0.5 it delivered a quarter of what it asked for.
166
168
  * Every emissive material here has to pick one channel and mean it.
167
169
  */
168
- outColor = vec4(lit * energy * (1.0 - fog), 1.0);
170
+ /* Graded where nothing after this will grade it, as every pass is, light added included. */
171
+ outColor = vec4(applyOutputTransform(lit * energy * (1.0 - fog)), 1.0);
169
172
  }
170
173
  `;
@@ -1,5 +1,6 @@
1
1
  import { GERSTNER_GLSL } from './gerstner.ts';
2
2
  import { FOG_GLSL } from './fog.ts';
3
+ import { OUTPUT_TRANSFORM_GLSL } from './outputTransform.ts';
3
4
 
4
5
  /**
5
6
  * Light off water, projected onto what covers it.
@@ -78,6 +79,7 @@ uniform float uMaxDrop;
78
79
  ${FOG_GLSL}
79
80
 
80
81
  out vec4 outColor;
82
+ ${OUTPUT_TRANSFORM_GLSL}
81
83
 
82
84
  ${GERSTNER_GLSL}
83
85
 
@@ -164,6 +166,7 @@ void main() {
164
166
  float fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);
165
167
 
166
168
  // Additive: this is light arriving at a surface that is already shaded.
167
- outColor = vec4(uTint * (caustic * uStrength * reach * (1.0 - fog)), 1.0);
169
+ /* Graded where nothing after this will grade it, as every pass is, light added included. */
170
+ outColor = vec4(applyOutputTransform(uTint * (caustic * uStrength * reach * (1.0 - fog))), 1.0);
168
171
  }
169
172
  `;
@@ -24,6 +24,7 @@
24
24
  */
25
25
 
26
26
  import { glslIsFarDepth } from '../depthConvention.ts';
27
+ import { LINEAR_TO_SRGB_GLSL } from './outputTransform.ts';
27
28
 
28
29
  export const DECAL_PROJECT_FRAG = `#version 300 es
29
30
  precision highp float;
@@ -63,7 +64,9 @@ uniform float uDecalOpacity;
63
64
  uniform float uDecalFacingCos;
64
65
  /** How much of the mark's radius is edge. */
65
66
  uniform float uDecalSoftness;
66
-
67
+ /** How the frame under the marks was graded, 0 where the resolve grades it. See renderQuality.ts. */
68
+ uniform int uOutputTransform;
69
+ ${LINEAR_TO_SRGB_GLSL}
67
70
  out vec4 fragColor;
68
71
 
69
72
  void main() {
@@ -122,6 +125,13 @@ void main() {
122
125
  mark *= ${glslIsFarDepth('stored')} ? 0.0 : 1.0;
123
126
 
124
127
  /* White is the identity of a multiply, so a pixel the projector missed changes nothing. */
125
- fragColor = vec4(mix(vec3(1.0), uDecalColor, mark * uDecalOpacity), 1.0);
128
+ vec3 multiply = mix(vec3(1.0), uDecalColor, mark * uDecalOpacity);
129
+ /*
130
+ * **The multiply as an encoded pixel takes it**, where the frame was graded before this: the
131
+ * receiver is a display value, and darkening its light by a factor is darkening the display value
132
+ * by that factor encoded — exact through the sRGB encode, whose power carries a product through,
133
+ * and close through a curve, which bends it. A frame graded at the resolve multiplies linear light.
134
+ */
135
+ fragColor = vec4(mix(multiply, linearToSrgb(multiply), float(uOutputTransform != 0)), 1.0);
126
136
  }
127
137
  `;
@@ -1,4 +1,5 @@
1
1
  import { FOG_GLSL } from './fog.ts';
2
+ import { OUTPUT_TRANSFORM_GLSL } from './outputTransform.ts';
2
3
 
3
4
  /**
4
5
  * A thin, wet, iridescent film lying on a surface.
@@ -122,6 +123,7 @@ uniform float uFilmRoughness;
122
123
  uniform float uFilmRoughnessCycles;
123
124
 
124
125
  out vec4 fragColor;
126
+ ${OUTPUT_TRANSFORM_GLSL}
125
127
 
126
128
  /** A hue wheel, without a texture. Standard 6-segment ramp. */
127
129
  vec3 hue(float h) {
@@ -199,6 +201,19 @@ void main() {
199
201
  vec3 base = vTint * 0.9;
200
202
  vec3 color = base + sheen * uSheen * (0.22 + grazing * 0.75);
201
203
 
204
+ /*
205
+ * **Graded before the mirror is mixed in**, as every pass grades what it draws when nothing after
206
+ * it will, because the mirror's own passes graded what it holds. The fog that used to follow the
207
+ * mirror distributes over the mix, so the film's own colour is fogged and graded here, and what
208
+ * the mirror shows is fogged toward the same air, graded, where it is mixed in below.
209
+ */
210
+ // The same fog every other pass applies — and now literally the same code, which
211
+ // it was not: this ran its own curve while claiming otherwise, so a slick receded
212
+ // at a different rate than the deck it was lying on.
213
+ float fog = mediumFog(length(uCameraPos - vWorld), vWorld.y);
214
+ vec3 gradedAir = applyOutputTransform(mediumColor());
215
+ vec3 graded = applyOutputTransform(mix(color, mediumColor(), fog));
216
+
202
217
  /*
203
218
  * The mirrored scene, under the sheen rather than instead of it.
204
219
  *
@@ -208,7 +223,7 @@ void main() {
208
223
  *
209
224
  * Weighted by the grazing term as well as by strength, because that is what a reflection does:
210
225
  * a wet surface underfoot shows almost nothing and the same surface down the street shows the
211
- * whole scene. Added before the fog so the image recedes with everything else in the frame.
226
+ * whole scene. Fogged as the film is, so the image recedes with everything else in the frame.
212
227
  */
213
228
  if (uReflectionEnabled != 0 && uReflectionStrength > 0.0 && vReflectionClip.w > 0.0) {
214
229
  vec2 reflectionUv = vReflectionClip.xy / vReflectionClip.w * 0.5 + 0.5;
@@ -254,15 +269,13 @@ void main() {
254
269
  valid = min(valid, smoothstep(0.0, 0.025, min(movedBorder.x, movedBorder.y)));
255
270
  }
256
271
  vec3 mirrored = textureLod(uReflectionMap, clamp(reflectionUv, 0.0, 1.0), 0.0).rgb;
257
- color = mix(color, mirrored, valid * uReflectionStrength * (0.25 + grazing * 0.75));
272
+ graded = mix(
273
+ graded,
274
+ mix(mirrored, gradedAir, fog),
275
+ valid * uReflectionStrength * (0.25 + grazing * 0.75)
276
+ );
258
277
  }
259
278
 
260
- // The same fog every other pass applies — and now literally the same code, which
261
- // it was not: this ran its own curve while claiming otherwise, so a slick receded
262
- // at a different rate than the deck it was lying on.
263
- float fog = mediumFog(length(uCameraPos - vWorld), vWorld.y);
264
- color = mix(color, mediumColor(), fog);
265
-
266
279
  /*
267
280
  * Alpha from coverage, squared.
268
281
  *
@@ -280,6 +293,6 @@ void main() {
280
293
  */
281
294
  float coverage = clamp(vCoverage, 0.0, 1.0);
282
295
  float alpha = mix(coverage * coverage, coverage, 0.65) * 0.92;
283
- fragColor = vec4(color, alpha);
296
+ fragColor = vec4(graded, alpha);
284
297
  }
285
298
  `;
@@ -1,4 +1,5 @@
1
1
  import { FOG_GLSL } from './fog.ts';
2
+ import { OUTPUT_TRANSFORM_GLSL } from './outputTransform.ts';
2
3
 
3
4
  /**
4
5
  * Procedural flames: camera-facing quads whose shape and colour come entirely
@@ -116,6 +117,7 @@ uniform vec4 uClipPlane;
116
117
  uniform int uClipEnabled;
117
118
 
118
119
  out vec4 outColor;
120
+ ${OUTPUT_TRANSFORM_GLSL}
119
121
 
120
122
  float hash21(vec2 p) {
121
123
  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
@@ -175,6 +177,7 @@ void main() {
175
177
  col = mix(col, mediumColor(), fog);
176
178
  alpha *= 1.0 - fog;
177
179
 
178
- outColor = vec4(col, alpha);
180
+ /* Graded where nothing after this will grade it, as every pass is. The alpha is coverage. */
181
+ outColor = vec4(applyOutputTransform(col), alpha);
179
182
  }
180
183
  `;
@@ -123,7 +123,7 @@ void areaLightAdd(
123
123
  lampOpen += areaDiffuse;
124
124
  lampShadowed += areaDiffuse * occl * glass;
125
125
 
126
- if (vSpecular > 0.0 || metal > 0.0) {
126
+ if (surfaceSpecular > 0.0 || metal > 0.0) {
127
127
  vec3 c0 = centre - right * halfSize.x + up * halfSize.y;
128
128
  vec3 c1 = centre + right * halfSize.x + up * halfSize.y;
129
129
  vec3 c2 = centre + right * halfSize.x - up * halfSize.y;
@@ -39,7 +39,8 @@ export type LitSwitch =
39
39
  | 'REFLECTION_MAPS'
40
40
  | 'WORLD_UVS'
41
41
  | 'MOVING_SUN'
42
- | 'LAYERED';
42
+ | 'LAYERED'
43
+ | 'LAYER_LOOKS';
43
44
 
44
45
  /** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
45
46
  export function noLitSwitches(): Record<LitSwitch, boolean> {
@@ -55,6 +56,7 @@ export function noLitSwitches(): Record<LitSwitch, boolean> {
55
56
  WORLD_UVS: false,
56
57
  MOVING_SUN: false,
57
58
  LAYERED: false,
59
+ LAYER_LOOKS: false,
58
60
  };
59
61
  }
60
62
 
@@ -534,6 +534,11 @@ export interface FlatShaderOptions {
534
534
  * True when absent. See \`layeredGlsl\`.
535
535
  */
536
536
  readonly layered?: boolean;
537
+ /**
538
+ * Whether a layered material's layers are picked from shared arrays and given looks of their own,
539
+ * \`uLayerLooks\` and all. True when absent; nothing without \`layered\`. See \`layerLooksGlsl\`.
540
+ */
541
+ readonly layerLooks?: boolean;
537
542
  /**
538
543
  * How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
539
544
  *
@@ -686,7 +691,7 @@ export function flatFrag(options: FlatShaderOptions): string {
686
691
  /* After the reflection maps, so its switch takes the next id; main calls it. worldUv.ts. */
687
692
  worldUvGlsl(options.worldUvs ?? true),
688
693
  /* After the projection, so its switch takes the next id; the normal map below calls it. */
689
- layeredGlsl(options.layered ?? true),
694
+ layeredGlsl(options.layered ?? true, options.layerLooks ?? true),
690
695
  /* Where each map is read, after both of the ways it can be read otherwise. See mapReads.ts. */
691
696
  MAP_READS_GLSL,
692
697
  /* After the frame and the projection it calls, unconditional for the same reason as the
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Which layer of the shared arrays each of a layered material's layers reads, and how each looks:
3
+ * `uLayerLooks`, packed by `surfaceLayerLooks.ts`, read by `layered.ts`.
4
+ *
5
+ * **Every helper is the identity where the switch is off**, written as a branch on `LAYER_LOOKS`
6
+ * itself because the generated WGSL carries one string for both values of an override: layer `i`
7
+ * reads layer `i`, the maps beyond the layers start at the layer count, a tint is white, a range is
8
+ * 0 to 1, a normal is as stored, a specular is the surface's own, a repeat down is the repeat
9
+ * across, and nothing is occluded — exactly what `layered.ts` did before this existed, which is what
10
+ * a layered material that asks for none of it has to keep drawing.
11
+ *
12
+ * **A lit switch, `LAYER_LOOKS`**, off until a material first asks and declared after `LAYERED`, so
13
+ * every switch before it keeps its id. It is only ever compiled beside `LAYERED`, whose `uLayers`
14
+ * it reads.
15
+ */
16
+ export function layerLooksGlsl(on: boolean): string {
17
+ if (!on) {
18
+ return /* glsl */ `
19
+ const bool LAYER_LOOKS = false; // wgsl:override
20
+ float layerArray(int i) { return float(i); }
21
+ float layerExtras() { return uLayers[1].y; }
22
+ vec3 layerTint(int i) { return vec3(1.0); }
23
+ vec2 layerRanges(int i, vec2 roughMetal) { return roughMetal; }
24
+ vec3 layerStrengthen(int i, vec3 texel) { return texel; }
25
+ float layerNamedSpecular(int i) { return -1.0; }
26
+ float layerRepeatDown(int i, float across) { return across; }
27
+ vec2 layerOcclusionStrengths() { return vec2(0.0); }
28
+ vec2 layerOcclusionRange() { return vec2(0.0, 1.0); }
29
+ `;
30
+ }
31
+ return /* glsl */ `
32
+ /* Whether layers are picked from shared arrays and given looks of their own: layerLooks.ts. */
33
+ const bool LAYER_LOOKS = true; // wgsl:override
34
+ /*
35
+ * Fifteen vectors, as surfaceLayerLooks.ts packs them: the array layer each of the five reads, base
36
+ * first (floats 0 to 4); where the maps beyond the layers start (5), the mesh occlusion's strength
37
+ * on the colour, from 0 up (6), and on the ambient light (7); then two vectors a
38
+ * layer, base first: its tint (x y z) and normal strength (w), then its roughness range (x to y)
39
+ * and metalness range (z to w); then each layer's specular, -1 for the surface's own (48 to 52), its
40
+ * repeat down (53 to 57), and the occlusion's range (58 to 59).
41
+ */
42
+ uniform vec4 uLayerLooks[15]; // wgsl:material
43
+
44
+ /* Float k of the fifteen vectors. */
45
+ float layerLook(int k) {
46
+ return uLayerLooks[k / 4][k % 4];
47
+ }
48
+
49
+ /* The layer of the albedo, normal and ORM arrays a material's layer i reads. */
50
+ float layerArray(int i) {
51
+ if (LAYER_LOOKS) return layerLook(i);
52
+ return float(i);
53
+ }
54
+
55
+ /* Where the maps beyond the layers start in each array. */
56
+ float layerExtras() {
57
+ if (LAYER_LOOKS) return uLayerLooks[1].y;
58
+ return uLayers[1].y;
59
+ }
60
+
61
+ vec3 layerTint(int i) {
62
+ if (LAYER_LOOKS) return uLayerLooks[2 + i * 2].xyz;
63
+ return vec3(1.0);
64
+ }
65
+
66
+ /* The ORM's roughness and metalness, each spread over the layer's own range. */
67
+ vec2 layerRanges(int i, vec2 roughMetal) {
68
+ if (LAYER_LOOKS) {
69
+ vec4 range = uLayerLooks[3 + i * 2];
70
+ return vec2(mix(range.x, range.y, roughMetal.x), mix(range.z, range.w, roughMetal.y));
71
+ }
72
+ return roughMetal;
73
+ }
74
+
75
+ /*
76
+ * A tangent-space texel bent further or less by the layer's strength: its x and y scaled, its z
77
+ * kept, so 0 lies flat and a strength above 1 steepens every slope the map holds. z is held above
78
+ * zero so a texel lying exactly flat on its surface cannot normalise to nothing at strength 0.
79
+ */
80
+ vec3 layerStrengthen(int i, vec3 texel) {
81
+ if (LAYER_LOOKS) return normalize(vec3(texel.xy * uLayerLooks[2 + i * 2].w, max(texel.z, 1e-4)));
82
+ return texel;
83
+ }
84
+
85
+ /* The specular the layer names, or -1 where it names none and the surface's own stands. */
86
+ float layerNamedSpecular(int i) {
87
+ if (LAYER_LOOKS) return layerLook(48 + i);
88
+ return -1.0;
89
+ }
90
+
91
+ /* The layer's repeat down, which is its repeat across unless it gave two. */
92
+ float layerRepeatDown(int i, float across) {
93
+ if (LAYER_LOOKS) return layerLook(53 + i);
94
+ return across;
95
+ }
96
+
97
+ /* The mesh occlusion's strength on the colour (x), from 0 up, and on the ambient light (y). */
98
+ vec2 layerOcclusionStrengths() {
99
+ if (LAYER_LOOKS) return uLayerLooks[1].zw;
100
+ return vec2(0.0);
101
+ }
102
+
103
+ vec2 layerOcclusionRange() {
104
+ if (LAYER_LOOKS) return uLayerLooks[14].zw;
105
+ return vec2(0.0, 1.0);
106
+ }
107
+ `;
108
+ }