@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
@@ -36,6 +36,8 @@ export class SkinScatterPass {
36
36
  layout;
37
37
  uniforms;
38
38
  staging = new Float32Array((SLOT * 2) / 4);
39
+ /** The same blocks' ints: the frame's grade, which the second axis applies. */
40
+ stagingInts = new Int32Array(this.staging.buffer);
39
41
  linear;
40
42
  nearest;
41
43
  commands = createCommandPool(16);
@@ -182,7 +184,9 @@ export class SkinScatterPass {
182
184
  * attachment its pass draws into, `resolve` where a multisampled one resolves, `samples` its
183
185
  * count. `projection` is the frame's, `height` the frame's height in pixels.
184
186
  */
185
- spread(encoder, pipelines, depth, frame, resolve, samples, projection, inverseProjection, timestampWrites) {
187
+ spread(encoder, pipelines, depth, frame, resolve, samples, projection, inverseProjection, timestampWrites,
188
+ /** The frame's grade, which the second axis applies to the light it adds. */
189
+ outputTransform = 0, outputExposure = 1) {
186
190
  const diffuse = this.diffuse;
187
191
  if (diffuse === null || this.diffuseView === null || this.acrossView === null)
188
192
  return;
@@ -193,7 +197,7 @@ export class SkinScatterPass {
193
197
  this.groupDown = this.group('skin.blurDown', this.acrossView, depth);
194
198
  this.groupsDepth = depth;
195
199
  }
196
- this.writeUniforms(diffuse.width, diffuse.height, projection, inverseProjection);
200
+ this.writeUniforms(diffuse.width, diffuse.height, projection, inverseProjection, outputTransform, outputExposure);
197
201
  const across = postPipeline(pipelines, this.device, this.layout, 'skin.blurAcross', SKIN_BLUR_FRAG_WGSL, SKIN_TARGET_FORMAT);
198
202
  const down = postPipelineTargets(pipelines, this.device, this.layout, `skin.blurDown:${pipelines.format}:${samples}`, SKIN_BLUR_FRAG_WGSL, [
199
203
  {
@@ -261,7 +265,7 @@ export class SkinScatterPass {
261
265
  });
262
266
  }
263
267
  /** Both axes' blocks: the step along each, and what they share. Column-major matrices. */
264
- writeUniforms(width, height, projection, inverse) {
268
+ writeUniforms(width, height, projection, inverse, outputTransform, outputExposure) {
265
269
  const f = this.staging;
266
270
  const fields = BINDINGS.fields;
267
271
  for (let axis = 0; axis < 2; axis += 1) {
@@ -280,6 +284,8 @@ export class SkinScatterPass {
280
284
  f.set(this.profiles, base + fields.uProfiles.offset / 4);
281
285
  /* The colour is applied once, on the axis that adds into the frame. */
282
286
  f[base + fields.uApplyAlbedo.offset / 4] = axis;
287
+ this.stagingInts[base + fields.uOutputTransform.offset / 4] = outputTransform;
288
+ f[base + fields.uOutputExposure.offset / 4] = outputExposure;
283
289
  }
284
290
  this.device.queue.writeBuffer(this.uniforms, 0, f);
285
291
  }
@@ -88,8 +88,18 @@ export declare const SKY_FIELDS: {
88
88
  readonly size: 4;
89
89
  readonly type: "float";
90
90
  };
91
+ readonly uOutputTransform: {
92
+ readonly offset: 224;
93
+ readonly size: 4;
94
+ readonly type: "int";
95
+ };
96
+ readonly uOutputExposure: {
97
+ readonly offset: 228;
98
+ readonly size: 4;
99
+ readonly type: "float";
100
+ };
91
101
  };
92
- export declare const SKY_UNIFORM_SIZE: 224;
102
+ export declare const SKY_UNIFORM_SIZE: 240;
93
103
  export declare function createSkyBindGroupLayout(device: GPUDevice): GPUBindGroupLayout;
94
104
  export declare function createSkyBindGroup(device: GPUDevice, layout: GPUBindGroupLayout, uniforms: GPUBuffer): GPUBindGroup;
95
105
  /**
@@ -11,7 +11,7 @@ import type { UniformFields } from './scatterPass.ts';
11
11
  export declare const WATER_VERT_FIELDS: UniformFields;
12
12
  export declare const WATER_VERT_SIZE: 208;
13
13
  export declare const WATER_FRAG_FIELDS: UniformFields;
14
- export declare const WATER_FRAG_SIZE: 1504;
14
+ export declare const WATER_FRAG_SIZE: 1520;
15
15
  /** The two meshes and the numbers the grid decided, behind the surface's opaque handle. */
16
16
  export interface GpuWater {
17
17
  /** The ocean: near sheet and far skirt in one buffer. */
@@ -14,7 +14,7 @@ import type { PipelineCache } from './pipelineCache.ts';
14
14
  export declare const WIND_STREAK_VERT_FIELDS: UniformFields;
15
15
  export declare const WIND_STREAK_VERT_SIZE: 128;
16
16
  export declare const WIND_STREAK_FRAG_FIELDS: UniformFields;
17
- export declare const WIND_STREAK_FRAG_SIZE: 16;
17
+ export declare const WIND_STREAK_FRAG_SIZE: 32;
18
18
  /** A lattice on the device, behind the surface's opaque handle. */
19
19
  export interface GpuWindStreaks {
20
20
  readonly corners: GPUBuffer;
@@ -60,6 +60,8 @@ export declare class CausticsRenderer {
60
60
  *
61
61
  * Answers whether it drew, which the renderer counts on its frame budget.
62
62
  */
63
- draw(gl: WebGL2RenderingContext, camera: FrameView, timeSeconds: number, directionalDir: Vec3, directionalColor: Vec3, ambient: Vec3, atmosphere: Atmosphere, underwaterEnabled: boolean, windX?: number, windZ?: number, strength?: number): boolean;
63
+ draw(gl: WebGL2RenderingContext, camera: FrameView, timeSeconds: number, directionalDir: Vec3, directionalColor: Vec3, ambient: Vec3, atmosphere: Atmosphere, underwaterEnabled: boolean, windX?: number, windZ?: number, strength?: number,
64
+ /** The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
65
+ outputTransform?: number, outputExposure?: number): boolean;
64
66
  dispose(gl: WebGL2RenderingContext): void;
65
67
  }
@@ -66,11 +66,15 @@ export class CausticsRenderer {
66
66
  *
67
67
  * Answers whether it drew, which the renderer counts on its frame budget.
68
68
  */
69
- draw(gl, camera, timeSeconds, directionalDir, directionalColor, ambient, atmosphere, underwaterEnabled, windX = 0, windZ = 0, strength = 1) {
69
+ draw(gl, camera, timeSeconds, directionalDir, directionalColor, ambient, atmosphere, underwaterEnabled, windX = 0, windZ = 0, strength = 1,
70
+ /** The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
71
+ outputTransform = 0, outputExposure = 1) {
70
72
  if (this.vertexCount === 0)
71
73
  return false;
72
74
  const u = this.uniforms;
73
75
  gl.useProgram(this.program);
76
+ gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
77
+ gl.uniform1f(u['uOutputExposure'] ?? null, outputExposure);
74
78
  gl.uniformMatrix4fv(u['uViewProj'] ?? null, false, camera.viewProjection);
75
79
  gl.uniform3fv(u['uCameraPos'] ?? null, camera.position);
76
80
  gl.uniform1f(u['uTime'] ?? null, timeSeconds);
@@ -29,6 +29,8 @@ export declare class DecalPass {
29
29
  * turns a stored depth back into the clip space that matrix was built in, and pairing it with
30
30
  * the corrected one applies the remap twice. `lightVolumeDraw.ts` carries that derivation.
31
31
  */
32
- drawDecals(queue: DecalQueue, depth: WebGLTexture, viewProjection: ReadonlyMat4, eye: ArrayLike<number>, width: number, height: number): void;
32
+ drawDecals(queue: DecalQueue, depth: WebGLTexture, viewProjection: ReadonlyMat4, eye: ArrayLike<number>, width: number, height: number,
33
+ /** The renderer's `passGrade`: how the frame under the marks was graded, 0 for not yet. */
34
+ outputTransform?: number): void;
33
35
  dispose(): void;
34
36
  }
@@ -42,7 +42,9 @@ export class DecalPass {
42
42
  * turns a stored depth back into the clip space that matrix was built in, and pairing it with
43
43
  * the corrected one applies the remap twice. `lightVolumeDraw.ts` carries that derivation.
44
44
  */
45
- drawDecals(queue, depth, viewProjection, eye, width, height) {
45
+ drawDecals(queue, depth, viewProjection, eye, width, height,
46
+ /** The renderer's `passGrade`: how the frame under the marks was graded, 0 for not yet. */
47
+ outputTransform = 0) {
46
48
  if (queue.length === 0)
47
49
  return;
48
50
  const { gl } = this;
@@ -50,6 +52,7 @@ export class DecalPass {
50
52
  mat4.invert(this.depthToWorld, viewProjection);
51
53
  mat4.multiply(this.depthToWorld, this.depthToWorld, DEPTH_01_TO_CLIP);
52
54
  gl.useProgram(this.program);
55
+ gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
53
56
  gl.bindVertexArray(this.vao);
54
57
  gl.disable(gl.DEPTH_TEST);
55
58
  gl.depthMask(false);
@@ -32,6 +32,6 @@ export declare class FlockRenderer {
32
32
  private readonly buffers;
33
33
  private readonly vertexCount;
34
34
  constructor(gl: WebGL2RenderingContext, count: number);
35
- draw(gl: WebGL2RenderingContext, camera: FrameView, timeSeconds: number, params: FlockParams, tint: Vec3, windX?: number, windZ?: number): void;
35
+ draw(gl: WebGL2RenderingContext, camera: FrameView, timeSeconds: number, params: FlockParams, tint: Vec3, windX?: number, windZ?: number, outputTransform?: number, outputExposure?: number): void;
36
36
  dispose(gl: WebGL2RenderingContext): void;
37
37
  }
@@ -34,9 +34,12 @@ export class FlockRenderer {
34
34
  attach(indices, 2, 1);
35
35
  gl.bindVertexArray(null);
36
36
  }
37
- draw(gl, camera, timeSeconds, params, tint, windX = 0, windZ = 0) {
37
+ draw(gl, camera, timeSeconds, params, tint, windX = 0, windZ = 0, outputTransform = 0, outputExposure = 1) {
38
38
  const u = this.uniforms;
39
39
  gl.useProgram(this.program);
40
+ /* The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
41
+ gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
42
+ gl.uniform1f(u['uOutputExposure'] ?? null, outputExposure);
40
43
  gl.uniformMatrix4fv(u['uViewProj'] ?? null, false, camera.viewProjection);
41
44
  /*
42
45
  * No `uCameraPos` here, and the shader no longer declares one. It fed the banking that
@@ -97,6 +97,8 @@ export declare class GlobalMediumPass {
97
97
  * `(ONE, SRC_ALPHA)` on colour is `dst * transmittance + inscatter`; alpha is left alone with
98
98
  * `(ZERO, ONE)`, because the frame's own alpha is not the medium's to spend.
99
99
  */
100
- compositeMedium(depth: WebGLTexture, frameWidth: number, frameHeight: number): void;
100
+ compositeMedium(depth: WebGLTexture, frameWidth: number, frameHeight: number,
101
+ /** The renderer's `passGrade`: how the frame under the medium was graded. */
102
+ outputTransform?: number, outputExposure?: number): void;
101
103
  dispose(): void;
102
104
  }
@@ -192,7 +192,9 @@ export class GlobalMediumPass {
192
192
  * `(ONE, SRC_ALPHA)` on colour is `dst * transmittance + inscatter`; alpha is left alone with
193
193
  * `(ZERO, ONE)`, because the frame's own alpha is not the medium's to spend.
194
194
  */
195
- compositeMedium(depth, frameWidth, frameHeight) {
195
+ compositeMedium(depth, frameWidth, frameHeight,
196
+ /** The renderer's `passGrade`: how the frame under the medium was graded. */
197
+ outputTransform = 0, outputExposure = 1) {
196
198
  if (this.target === null || this.unavailable)
197
199
  return;
198
200
  const { gl } = this;
@@ -215,6 +217,8 @@ export class GlobalMediumPass {
215
217
  this.mediumTexel[1] = 1 / Math.max(1, this.height);
216
218
  gl.uniform2fv(u['uMediumTexel'] ?? null, this.mediumTexel);
217
219
  gl.uniform4fv(u['uDepthToViewZ'] ?? null, this.depthToViewZ);
220
+ gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
221
+ gl.uniform1f(u['uOutputExposure'] ?? null, outputExposure);
218
222
  gl.drawArrays(gl.TRIANGLES, 0, 3);
219
223
  gl.bindVertexArray(null);
220
224
  gl.activeTexture(gl.TEXTURE1);
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The forward grade on the CPU: what `applyOutputTransform` in `shaders/outputTransform.ts` does to
3
+ * a colour, for a colour no shader draws — a clear colour, written to a target before any pass runs.
4
+ *
5
+ * **Why a clear colour is graded at all.** Every pass grades what it draws when nothing after it
6
+ * will, so a colour handed to the renderer means one thing wherever it lands: linear light. A clear
7
+ * left as it was given is the one colour in the frame read as a display value, so a scene whose fog
8
+ * fades to its background colour fades to a different colour than the background it meets; with
9
+ * `hdrScene` the resolve grades the clear with everything else, and a frame without it has to agree.
10
+ *
11
+ * **A second copy of the curve, and what holds the two together** is the hand-derived values in
12
+ * `gradeColor.test.ts`, the same ones the shader's own constants give: the sRGB encode's two
13
+ * segments, the ACES fit through its two matrices, and the highlight shoulder. Code 4, the filmic
14
+ * curve, is the composite's alone, so a forward code never carries it (`forwardTransformCode`).
15
+ */
16
+ import type { Vec3 } from '../math/color.ts';
17
+ /**
18
+ * `color` graded by forward code `code` at `exposure` into `out`, which may be `color`: 0 leaves it
19
+ * as it is, 1 encodes it as sRGB, 2 runs ACES first and 3 the highlight shoulder. Allocates nothing.
20
+ */
21
+ export declare function gradeColorInto(code: number, exposure: number, color: Readonly<Vec3>, out: Vec3): Vec3;
@@ -0,0 +1,64 @@
1
+ /* Columns as the shader's `mat3` reads them, written here as rows of the product. */
2
+ const IN_R = [0.59719, 0.35458, 0.04823];
3
+ const IN_G = [0.076, 0.90834, 0.01566];
4
+ const IN_B = [0.0284, 0.13383, 0.83777];
5
+ const OUT_R = [1.60475, -0.53108, -0.07367];
6
+ const OUT_G = [-0.10208, 1.10813, -0.00605];
7
+ const OUT_B = [-0.00327, -0.07276, 1.07602];
8
+ /**
9
+ * `color` graded by forward code `code` at `exposure` into `out`, which may be `color`: 0 leaves it
10
+ * as it is, 1 encodes it as sRGB, 2 runs ACES first and 3 the highlight shoulder. Allocates nothing.
11
+ */
12
+ export function gradeColorInto(code, exposure, color, out) {
13
+ let r = color[0];
14
+ let g = color[1];
15
+ let b = color[2];
16
+ if (code === 0) {
17
+ out[0] = r;
18
+ out[1] = g;
19
+ out[2] = b;
20
+ return out;
21
+ }
22
+ if (code === 2) {
23
+ const x = r * exposure;
24
+ const y = g * exposure;
25
+ const z = b * exposure;
26
+ const ir = fit(dot(IN_R, x, y, z));
27
+ const ig = fit(dot(IN_G, x, y, z));
28
+ const ib = fit(dot(IN_B, x, y, z));
29
+ r = clamp01(dot(OUT_R, ir, ig, ib));
30
+ g = clamp01(dot(OUT_G, ir, ig, ib));
31
+ b = clamp01(dot(OUT_B, ir, ig, ib));
32
+ }
33
+ else if (code === 3) {
34
+ r *= exposure;
35
+ g *= exposure;
36
+ b *= exposure;
37
+ const m = Math.max(r, g, b);
38
+ if (m > 0.8) {
39
+ const e = m - 0.8;
40
+ const k = (0.8 + (0.2 * e) / (e + 0.2)) / m;
41
+ r *= k;
42
+ g *= k;
43
+ b *= k;
44
+ }
45
+ }
46
+ out[0] = encode(r);
47
+ out[1] = encode(g);
48
+ out[2] = encode(b);
49
+ return out;
50
+ }
51
+ function dot(row, x, y, z) {
52
+ return row[0] * x + row[1] * y + row[2] * z;
53
+ }
54
+ /** The RRT and ODT fit, `rrtAndOdtFit` in the shader. */
55
+ function fit(v) {
56
+ return (v * (v + 0.0245786) - 0.000090537) / (v * (0.983729 * v + 0.432951) + 0.238081);
57
+ }
58
+ /** Linear to sRGB, the piecewise form the shader uses. */
59
+ function encode(c) {
60
+ return c <= 0.0031308 ? c * 12.92 : 1.055 * Math.max(c, 0) ** (1 / 2.4) - 0.055;
61
+ }
62
+ function clamp01(v) {
63
+ return v < 0 ? 0 : v > 1 ? 1 : v;
64
+ }
@@ -1,6 +1,19 @@
1
1
  import { mat4 } from 'gl-matrix';
2
2
  import type { Vec3 } from '../math/color.ts';
3
3
  export declare function computeLightMatrix(lightDir: Vec3, focusX: number, focusY: number, focusZ: number, radius: number, shadowMapSize: number, out: mat4): number;
4
+ /**
5
+ * A directional light's matrix covering a box of known size whole: a model, a level, a room. The
6
+ * sphere around the box, at its centre, through `computeLightMatrix`, widened by the half texel the
7
+ * centre's snap may move it so a corner on the sphere stays inside. Returns the depth span, as that
8
+ * does.
9
+ *
10
+ * **For a scene whose extent is known rather than one that follows a viewer.** A radius chosen by
11
+ * eye around a guessed centre leaves part of the model outside the map, where it casts nothing; the
12
+ * bounds are the answer, and a loader has them. **What it gives up** is resolution where the box is
13
+ * large: the map's texels are spread over all of it, so a building's shadows are coarser than a
14
+ * focus on the viewer would draw them, which is what `computeLightMatrix` around the viewer is for.
15
+ */
16
+ export declare function computeLightMatrixForBounds(lightDir: Vec3, min: Readonly<Vec3>, max: Readonly<Vec3>, shadowMapSize: number, out: mat4): number;
4
17
  /**
5
18
  * The depth an orthographic light matrix spans, in metres: what `computeLightMatrix` returns, read
6
19
  * back off any such matrix. Its depth row is the light's axis scaled by two over the span, so the
@@ -43,6 +43,23 @@ export function computeLightMatrix(lightDir, focusX, focusY, focusZ, radius, sha
43
43
  // to express its residual depth tolerance in stable world-space metres.
44
44
  return radius * 6;
45
45
  }
46
+ /**
47
+ * A directional light's matrix covering a box of known size whole: a model, a level, a room. The
48
+ * sphere around the box, at its centre, through `computeLightMatrix`, widened by the half texel the
49
+ * centre's snap may move it so a corner on the sphere stays inside. Returns the depth span, as that
50
+ * does.
51
+ *
52
+ * **For a scene whose extent is known rather than one that follows a viewer.** A radius chosen by
53
+ * eye around a guessed centre leaves part of the model outside the map, where it casts nothing; the
54
+ * bounds are the answer, and a loader has them. **What it gives up** is resolution where the box is
55
+ * large: the map's texels are spread over all of it, so a building's shadows are coarser than a
56
+ * focus on the viewer would draw them, which is what `computeLightMatrix` around the viewer is for.
57
+ */
58
+ export function computeLightMatrixForBounds(lightDir, min, max, shadowMapSize, out) {
59
+ const half = Math.hypot(max[0] - min[0], max[1] - min[1], max[2] - min[2]) / 2;
60
+ const radius = Math.max(half, 1e-3) * (1 + 2 / Math.max(shadowMapSize, 1));
61
+ return computeLightMatrix(lightDir, (min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2, radius, shadowMapSize, out);
62
+ }
46
63
  /**
47
64
  * The depth an orthographic light matrix spans, in metres: what `computeLightMatrix` returns, read
48
65
  * back off any such matrix. Its depth row is the light's axis scaled by two over the span, so the
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The `uOutputTransform` a pass uploads: `storing` inside a probe's face or a capture, `presented`
3
+ * once the frame's resolve has run, `resolveGrades` where that resolve applies the transform.
4
+ */
5
+ export declare function passGradeCode(storing: boolean, presented: boolean, resolveGrades: boolean, outputTransform: string): number;
6
+ /** The `uOutputExposure` beside it: the frame's exposure wherever the pass grades, else one. */
7
+ export declare function passGradeExposure(storing: boolean, presented: boolean, resolveGrades: boolean, exposure: number): number;
8
+ /**
9
+ * The code an interface draws with, given the pass's: the screen encode wherever the pass grades at
10
+ * all, and nothing where it does not. Never a curve, so never an exposure either: a screen-space
11
+ * caption, a panel or a 2D layer keeps the colour it was picked as whatever the scene is graded by.
12
+ */
13
+ export declare function interfaceGrade(passCode: number): number;
@@ -0,0 +1,47 @@
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.js';
25
+ /**
26
+ * The `uOutputTransform` a pass uploads: `storing` inside a probe's face or a capture, `presented`
27
+ * once the frame's resolve has run, `resolveGrades` where that resolve applies the transform.
28
+ */
29
+ export function passGradeCode(storing, presented, resolveGrades, outputTransform) {
30
+ if (storing)
31
+ return 0;
32
+ return presented || !resolveGrades ? forwardTransformCode(outputTransform) : 0;
33
+ }
34
+ /** The `uOutputExposure` beside it: the frame's exposure wherever the pass grades, else one. */
35
+ export function passGradeExposure(storing, presented, resolveGrades, exposure) {
36
+ if (storing)
37
+ return 1;
38
+ return presented || !resolveGrades ? exposure : 1;
39
+ }
40
+ /**
41
+ * The code an interface draws with, given the pass's: the screen encode wherever the pass grades at
42
+ * all, and nothing where it does not. Never a curve, so never an exposure either: a screen-space
43
+ * caption, a panel or a 2D layer keeps the colour it was picked as whatever the scene is graded by.
44
+ */
45
+ export function interfaceGrade(passCode) {
46
+ return passCode === 0 ? 0 : 1;
47
+ }
@@ -89,7 +89,7 @@ export declare class PlumeRenderer {
89
89
  * the sky at depth 1.0 under LEQUAL would otherwise overwrite anything drawn
90
90
  * against open background.
91
91
  */
92
- draw(gl: WebGL2RenderingContext, camera: FrameView, timeSeconds: number, atmosphere: Atmosphere, underwaterEnabled: boolean, atmosphereCameraY: number, clipPlane: Float32Array | null, windX?: number, windZ?: number, originX?: number, originY?: number, originZ?: number): void;
92
+ draw(gl: WebGL2RenderingContext, camera: FrameView, timeSeconds: number, atmosphere: Atmosphere, underwaterEnabled: boolean, atmosphereCameraY: number, clipPlane: Float32Array | null, windX?: number, windZ?: number, originX?: number, originY?: number, originZ?: number, outputTransform?: number, outputExposure?: number): void;
93
93
  dispose(gl: WebGL2RenderingContext): void;
94
94
  /** Uploads one attribute and returns its buffer, for the rare one a caller can change. */
95
95
  private attach;
@@ -93,7 +93,7 @@ export class PlumeRenderer {
93
93
  * the sky at depth 1.0 under LEQUAL would otherwise overwrite anything drawn
94
94
  * against open background.
95
95
  */
96
- draw(gl, camera, timeSeconds, atmosphere, underwaterEnabled, atmosphereCameraY, clipPlane, windX = 0, windZ = 0, originX = 0, originY = 0, originZ = 0) {
96
+ draw(gl, camera, timeSeconds, atmosphere, underwaterEnabled, atmosphereCameraY, clipPlane, windX = 0, windZ = 0, originX = 0, originY = 0, originZ = 0, outputTransform = 0, outputExposure = 1) {
97
97
  if (this.indexCount === 0)
98
98
  return;
99
99
  const u = this.uniforms;
@@ -121,6 +121,9 @@ export class PlumeRenderer {
121
121
  gl.uniform1i(u['uClipEnabled'] ?? null, clipPlane === null ? 0 : 1);
122
122
  if (clipPlane !== null)
123
123
  gl.uniform4fv(u['uClipPlane'] ?? null, clipPlane);
124
+ /* The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
125
+ gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
126
+ gl.uniform1f(u['uOutputExposure'] ?? null, outputExposure);
124
127
  bindAtmosphere(gl, u, atmosphere, atmosphereCameraY, underwaterEnabled);
125
128
  gl.enable(gl.BLEND);
126
129
  if (this.blend === 'additive')
@@ -50,22 +50,35 @@ export type GlassShadows = 'full' | 'half' | 'off';
50
50
  export type SkinScattering = 'pre-integrated' | 'screen-space';
51
51
  export interface RenderQuality {
52
52
  /**
53
- * The transfer curve applied at the end of every shaded pass.
54
- *
55
- * Construction-time, like every other quality control here, and `none` by default so
56
- * no existing world changes by a single bit.
57
- *
58
- * **The symptom, written down because nobody searches for a transfer curve they do not know
59
- * exists.** With `none`, values above 1 clip flat rather than rolling off, so a scene with
60
- * bright sources against dark surroundings reads as crushed and desaturated: the highlights
61
- * go to a hard white with no colour left in them and the midtones look muddy. That is
62
- * reported by eye as "dark", "flat" or "not vivid", which sounds like a lighting problem, so
63
- * that is where people look. A consumer built, tuned and shipped six worlds before anybody
64
- * said the word. If a scene looks like that, try `outputTransform: 'aces'` with an
65
- * `outputExposure` before touching a single light.
66
- *
67
- * The default stays `none`, and that is not the fault: a curve that changed every existing
68
- * consumer's output would be far worse than one nobody found.
53
+ * The transfer curve applied at the end of every shaded pass. Construction-time, like every
54
+ * other quality control here.
55
+ *
56
+ * **`srgb` by default since 4.13.0, the encoding a display expects**: the light the renderer
57
+ * computes is linear, and a texture's colours are decoded to linear when they are read, so a
58
+ * frame written without encoding them back is a frame of linear values read as display values —
59
+ * every midtone far too dark. It is the encoding most renderers apply by default, so a model
60
+ * drawn here with no options reads as it does elsewhere. **What it gives up** is a highlight's
61
+ * colour: above 1 each channel clips on its own, so a bright saturated source goes to white
62
+ * rather than rolling off, which a curve with a shoulder (`aces`, `shoulder`) is for.
63
+ *
64
+ * **`none` was the default until 4.13.0, and it is the frame that reads as dark**: values
65
+ * written as they are computed, which is right only for a world whose colours were chosen by
66
+ * eye as display values and lit to match. It was reported by eye as "dark", "flat" or "not
67
+ * vivid", which sounds like a lighting problem, so that is where people looked; a consumer
68
+ * built, tuned and shipped six worlds under it before anybody said the word, and a model drawn
69
+ * from the manual came out darker than in any other engine. A world graded under it says
70
+ * `outputTransform: 'none'` and draws exactly as it did. **What would make `srgb` the wrong
71
+ * default** is a renderer whose colours are not linear, which this one's are not.
72
+ *
73
+ * **Every pass applies it where nothing after it will**, and the clear colour with them, so a
74
+ * colour handed to the renderer is linear wherever it lands: a frame with `hdrScene` is graded
75
+ * once at the resolve, and one without it is graded pass by pass to the same curve. A colour
76
+ * picked by eye is a display value, so it is stated through `srgbColor` (or a mesh built with
77
+ * `colorSpace: 'srgb'`), which gives back exactly the colour picked where a surface is lit at 1
78
+ * under `srgb`, and through the curve under a tone curve. **An interface** (screen text, a panel,
79
+ * the 2D layer) takes the screen encode and never the curve or the exposure, so its colours come
80
+ * out as picked under every transform; inside an `hdrScene` frame it is part of the picture the
81
+ * resolve grades, and is drawn after `endFrame` to stay out of the curve.
69
82
  */
70
83
  readonly outputTransform: OutputTransform;
71
84
  /**
@@ -259,9 +272,35 @@ export interface RenderQuality {
259
272
  * at a shadow's edge — that is what the blur is for.
260
273
  */
261
274
  readonly skinScattering: SkinScattering;
262
- /** Horizontal world-space reach over which a directional shadow dissolves. */
275
+ /**
276
+ * How far a sun shadow may reach along the ground from its caster, in metres, before it
277
+ * dissolves. **Not a distance from the camera**: a shadow's own length, from where it leaves its
278
+ * caster to where it lands, fading from 35% of this to all of it.
279
+ *
280
+ * **1,000 since 4.13.0, so no building's shadow dissolves**: the fade starts at 350 m, past any
281
+ * wall, tower or canyon a shadow map covers at a useful resolution, so what bounds a shadow is
282
+ * its map, whose edge already fades. **What it gives up** is a way to hide a long shadow's
283
+ * stair-stepping at a low sun, which is what a short reach was for. It was 6 until 4.13.0: a
284
+ * tuning for short casters, which left a building's walls and arcades casting nothing on its
285
+ * floor under a high sun — the shadows vanished silently and read as random lighting. **What
286
+ * would make it wrong** is a scene whose shadows are too coarse to show at their full length;
287
+ * there a reach of the length that looks right is the setting, and the map around the viewer
288
+ * (`computeLightMatrix`) the better fix.
289
+ */
263
290
  readonly directionalShadowMaxDistance: number;
264
- /** Maximum horizontal projection per vertical metre before low-angle shadows vanish. */
291
+ /**
292
+ * How low the sun may stand before its shadows fade, as the ground a metre of height may shadow:
293
+ * the sun's horizontal reach per vertical metre, so 1 is 45° of elevation and 60 about 1°. Every
294
+ * sun shadow fades from 65% of this to all of it.
295
+ *
296
+ * **60 since 4.13.0: shadows hold until the sun is about a degree above the horizon**, the
297
+ * stretch of a day when they are longest and most worth seeing. **What it gives up**: a sun on
298
+ * the horizon throws shadows kilometres long that a map can hold only as stripes, and the
299
+ * last degree is where they fade rather than show. It was 3 until 4.13.0, which dropped every
300
+ * sun shadow below about 18° — an evening without shadows. **What would make it wrong** is a
301
+ * scene whose low sun streaks; a smaller number here is the setting, at the cost of the
302
+ * shadows going earlier in the evening.
303
+ */
265
304
  readonly directionalShadowMaxSlope: number;
266
305
  /**
267
306
  * Metres a point light may drift from where its shadow was baked before re-baking.
@@ -17,7 +17,7 @@ export const DIRECTIONAL_SHADOW_FADE_START = 0.65;
17
17
  * six times, while the directional map spends all of it on one focused area.
18
18
  */
19
19
  export const DEFAULT_RENDER_QUALITY = Object.freeze({
20
- outputTransform: 'none',
20
+ outputTransform: 'srgb',
21
21
  outputExposure: 1,
22
22
  pointLightFalloff: 'smooth',
23
23
  /**
@@ -65,8 +65,8 @@ export const DEFAULT_RENDER_QUALITY = Object.freeze({
65
65
  glassShadows: 'full',
66
66
  pipelineCompile: 'wait',
67
67
  skinScattering: 'pre-integrated',
68
- directionalShadowMaxDistance: 6,
69
- directionalShadowMaxSlope: 3,
68
+ directionalShadowMaxDistance: 1000,
69
+ directionalShadowMaxSlope: 60,
70
70
  pointShadowRebakeDistance: 0.001,
71
71
  pointShadowFacesPerFrame: 2,
72
72
  /*
@@ -303,6 +303,8 @@ export declare class SceneTarget {
303
303
  readonly transform: number;
304
304
  readonly exposure: number;
305
305
  readonly film?: Float32Array;
306
+ /** Whether the forward passes graded the scene because this resolve does not. */
307
+ readonly sceneGraded?: boolean;
306
308
  }, motion?: {
307
309
  readonly reprojection: Float32Array;
308
310
  readonly strength: number;
@@ -819,6 +819,7 @@ export class SceneTarget {
819
819
  gl.uniform1f(this.uniforms['uReach'] ?? null, reachUv);
820
820
  gl.uniform1i(this.uniforms['uOutputTransform'] ?? null, grade.transform);
821
821
  gl.uniform1f(this.uniforms['uOutputExposure'] ?? null, grade.exposure);
822
+ gl.uniform1i(this.uniforms['uSceneGraded'] ?? null, grade.sceneGraded === true ? 1 : 0);
822
823
  if (grade.film !== undefined) {
823
824
  gl.uniform4fv(this.uniforms['uFilmA'] ?? null, grade.film, 0, 4);
824
825
  gl.uniform4fv(this.uniforms['uFilmB'] ?? null, grade.film, 4, 4);
@@ -65,7 +65,7 @@ export declare class SdfTextRenderer {
65
65
  */
66
66
  /**
67
67
  * `outputTransform` and `outputExposure` come from the renderer rather than from here, because
68
- * whether this pass grades depends on whether anything after it will. See `Renderer.gradeCode`.
68
+ * whether this pass grades depends on whether anything after it will. See `passGrade.ts`.
69
69
  */
70
70
  draw(viewProj: ReadonlyMat4, model: Float32Array, color: Vec3, opacity: number, outputTransform: number, outputExposure: number): boolean;
71
71
  dispose(): void;
@@ -103,7 +103,7 @@ export class SdfTextRenderer {
103
103
  */
104
104
  /**
105
105
  * `outputTransform` and `outputExposure` come from the renderer rather than from here, because
106
- * whether this pass grades depends on whether anything after it will. See `Renderer.gradeCode`.
106
+ * whether this pass grades depends on whether anything after it will. See `passGrade.ts`.
107
107
  */
108
108
  draw(viewProj, model, color, opacity, outputTransform, outputExposure) {
109
109
  if (this.layout.quadCount === 0 || opacity <= 0 || this.font === null || this.atlas === null) {