@driftengine/core 4.8.1 → 4.8.3

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 (159) hide show
  1. package/CHANGELOG.json +142 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +8 -0
  5. package/dist/input/actionMap.d.ts +6 -1
  6. package/dist/input/actionMap.js +20 -7
  7. package/dist/input/input.d.ts +28 -0
  8. package/dist/input/input.js +71 -9
  9. package/dist/input/inputHarness.d.ts +9 -0
  10. package/dist/input/inputHarness.js +34 -2
  11. package/dist/render/backend/materialChanges.d.ts +7 -2
  12. package/dist/render/backend/materialChanges.js +8 -3
  13. package/dist/render/backend/webgl2/renderer.d.ts +75 -3
  14. package/dist/render/backend/webgl2/renderer.js +209 -22
  15. package/dist/render/backend/webgpu/buffers.d.ts +2 -0
  16. package/dist/render/backend/webgpu/buffers.js +2 -0
  17. package/dist/render/backend/webgpu/causticsPass.js +2 -1
  18. package/dist/render/backend/webgpu/depthPass.d.ts +13 -3
  19. package/dist/render/backend/webgpu/depthPass.js +4 -3
  20. package/dist/render/backend/webgpu/filmPass.js +2 -1
  21. package/dist/render/backend/webgpu/flatPass.d.ts +5 -0
  22. package/dist/render/backend/webgpu/flatPass.js +18 -8
  23. package/dist/render/backend/webgpu/linePass.js +2 -1
  24. package/dist/render/backend/webgpu/panelPass.js +2 -1
  25. package/dist/render/backend/webgpu/particlePass.js +2 -1
  26. package/dist/render/backend/webgpu/pipelineCache.d.ts +40 -8
  27. package/dist/render/backend/webgpu/pipelineCache.js +76 -9
  28. package/dist/render/backend/webgpu/plumePass.js +2 -1
  29. package/dist/render/backend/webgpu/postPass.d.ts +12 -2
  30. package/dist/render/backend/webgpu/postPass.js +19 -5
  31. package/dist/render/backend/webgpu/renderer.d.ts +66 -2
  32. package/dist/render/backend/webgpu/renderer.js +189 -38
  33. package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
  34. package/dist/render/backend/webgpu/textPass.js +2 -1
  35. package/dist/render/backend/webgpu/waterPass.js +2 -1
  36. package/dist/render/backend/webgpu/windStreakPass.js +2 -1
  37. package/dist/render/boltBatch.js +2 -1
  38. package/dist/render/causticsRenderer.js +2 -1
  39. package/dist/render/cutoutCaster.d.ts +3 -1
  40. package/dist/render/cutoutCaster.js +4 -0
  41. package/dist/render/decalPass.js +2 -1
  42. package/dist/render/globalMediumPass.js +3 -2
  43. package/dist/render/lineBatch.js +5 -1
  44. package/dist/render/mapsWithoutUvs.d.ts +24 -0
  45. package/dist/render/mapsWithoutUvs.js +24 -0
  46. package/dist/render/mesh.d.ts +2 -0
  47. package/dist/render/mesh.js +3 -0
  48. package/dist/render/oitPass.js +2 -1
  49. package/dist/render/particleBatch.js +3 -2
  50. package/dist/render/particlePool.d.ts +21 -2
  51. package/dist/render/particlePool.js +72 -37
  52. package/dist/render/pass.d.ts +12 -0
  53. package/dist/render/plumeRenderer.js +3 -2
  54. package/dist/render/renderQuality.d.ts +16 -8
  55. package/dist/render/sceneCoverage.d.ts +54 -0
  56. package/dist/render/sceneCoverage.js +72 -0
  57. package/dist/render/sdfTextRenderer.js +2 -1
  58. package/dist/render/shaders/depth.js +3 -2
  59. package/dist/render/shaders/flat/glassTint.d.ts +28 -0
  60. package/dist/render/shaders/flat/glassTint.js +31 -0
  61. package/dist/render/shaders/flat/index.d.ts +12 -0
  62. package/dist/render/shaders/flat/index.js +13 -2
  63. package/dist/render/shaders/flat/main.d.ts +1 -1
  64. package/dist/render/shaders/flat/main.js +60 -25
  65. package/dist/render/shaders/flat/surfaceEffects.d.ts +1 -1
  66. package/dist/render/shaders/flat/surfaceEffects.js +6 -2
  67. package/dist/render/shaders/generated/depth.wgsl.d.ts +32 -12
  68. package/dist/render/shaders/generated/depth.wgsl.js +32 -12
  69. package/dist/render/shaders/generated/flat.wgsl.d.ts +89 -0
  70. package/dist/render/shaders/generated/flat.wgsl.js +127 -38
  71. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  72. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  73. package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
  74. package/dist/render/shaders/generated/particle.wgsl.js +3 -3
  75. package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
  76. package/dist/render/shaders/generated/rush.wgsl.js +1 -1
  77. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
  78. package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
  79. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
  80. package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
  81. package/dist/render/shaders/line.d.ts +1 -1
  82. package/dist/render/shaders/outputTransform.d.ts +14 -1
  83. package/dist/render/shaders/outputTransform.js +23 -1
  84. package/dist/render/shaders/particle.d.ts +3 -3
  85. package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
  86. package/dist/render/shaders/rush.js +13 -2
  87. package/dist/render/shaders/sdfText.d.ts +1 -1
  88. package/dist/render/shaders/temporalResolve.js +12 -5
  89. package/dist/render/ssrPass.js +2 -1
  90. package/dist/render/surfaceTexture.d.ts +13 -0
  91. package/dist/render/surfaceTexture.js +4 -0
  92. package/dist/render/textRenderer.js +2 -1
  93. package/dist/render/vertexDefaults.js +1 -0
  94. package/dist/render/viewModel.d.ts +29 -0
  95. package/dist/render/viewModel.js +35 -0
  96. package/dist/render/waterRenderer.js +2 -1
  97. package/dist/render/windStreakRenderer.js +2 -1
  98. package/package.json +6 -6
  99. package/src/index.ts +22 -1
  100. package/src/input/actionMap.ts +23 -8
  101. package/src/input/input.ts +79 -9
  102. package/src/input/inputHarness.ts +27 -2
  103. package/src/render/backend/materialChanges.ts +8 -3
  104. package/src/render/backend/webgl2/renderer.ts +223 -29
  105. package/src/render/backend/webgpu/buffers.ts +5 -0
  106. package/src/render/backend/webgpu/causticsPass.ts +2 -1
  107. package/src/render/backend/webgpu/depthPass.ts +4 -3
  108. package/src/render/backend/webgpu/filmPass.ts +2 -1
  109. package/src/render/backend/webgpu/flatPass.ts +43 -26
  110. package/src/render/backend/webgpu/linePass.ts +2 -1
  111. package/src/render/backend/webgpu/panelPass.ts +2 -1
  112. package/src/render/backend/webgpu/particlePass.ts +2 -1
  113. package/src/render/backend/webgpu/pipelineCache.ts +82 -8
  114. package/src/render/backend/webgpu/plumePass.ts +2 -1
  115. package/src/render/backend/webgpu/postPass.ts +20 -5
  116. package/src/render/backend/webgpu/renderer.ts +191 -37
  117. package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
  118. package/src/render/backend/webgpu/textPass.ts +2 -1
  119. package/src/render/backend/webgpu/waterPass.ts +2 -1
  120. package/src/render/backend/webgpu/windStreakPass.ts +2 -1
  121. package/src/render/boltBatch.ts +2 -1
  122. package/src/render/causticsRenderer.ts +2 -1
  123. package/src/render/cutoutCaster.ts +15 -2
  124. package/src/render/decalPass.ts +2 -1
  125. package/src/render/globalMediumPass.ts +3 -2
  126. package/src/render/lineBatch.ts +3 -1
  127. package/src/render/mapsWithoutUvs.ts +43 -0
  128. package/src/render/mesh.ts +3 -0
  129. package/src/render/oitPass.ts +2 -1
  130. package/src/render/particleBatch.ts +3 -2
  131. package/src/render/particlePool.ts +107 -56
  132. package/src/render/pass.ts +12 -0
  133. package/src/render/plumeRenderer.ts +3 -2
  134. package/src/render/renderQuality.ts +16 -8
  135. package/src/render/sceneCoverage.ts +78 -0
  136. package/src/render/sdfTextRenderer.ts +2 -1
  137. package/src/render/shaders/depth.ts +3 -2
  138. package/src/render/shaders/flat/glassTint.ts +48 -0
  139. package/src/render/shaders/flat/index.ts +25 -2
  140. package/src/render/shaders/flat/main.ts +60 -25
  141. package/src/render/shaders/flat/surfaceEffects.ts +6 -2
  142. package/src/render/shaders/generated/depth.wgsl.ts +32 -12
  143. package/src/render/shaders/generated/flat.wgsl.ts +127 -38
  144. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  145. package/src/render/shaders/generated/particle.wgsl.ts +3 -3
  146. package/src/render/shaders/generated/rush.wgsl.ts +1 -1
  147. package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
  148. package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
  149. package/src/render/shaders/outputTransform.ts +24 -1
  150. package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
  151. package/src/render/shaders/rush.ts +13 -2
  152. package/src/render/shaders/temporalResolve.ts +12 -5
  153. package/src/render/ssrPass.ts +2 -1
  154. package/src/render/surfaceTexture.ts +16 -0
  155. package/src/render/textRenderer.ts +2 -1
  156. package/src/render/vertexDefaults.ts +1 -0
  157. package/src/render/viewModel.ts +41 -0
  158. package/src/render/waterRenderer.ts +2 -1
  159. package/src/render/windStreakRenderer.ts +2 -1
@@ -25,6 +25,7 @@ import { DEPTH_OF_FIELD_GLSL } from './depthOfField.ts';
25
25
  import { AUTO_EXPOSURE_GLSL } from './exposure.ts';
26
26
  import { LOCAL_EXPOSURE_GLSL } from './localExposure.ts';
27
27
  import { FILM_LOOK_GLSL } from './filmLook.ts';
28
+ import { HIGHLIGHT_SHOULDER_GLSL } from './outputTransform.ts';
28
29
 
29
30
  export const RUSH_FRAG = `#version 300 es
30
31
  precision highp float;
@@ -242,9 +243,11 @@ vec3 acesFilmic(vec3 x) {
242
243
  return clamp(ACES_OUTPUT * (a / b), 0.0, 1.0);
243
244
  }
244
245
 
246
+ ${HIGHLIGHT_SHOULDER_GLSL}
245
247
  vec3 grade(vec3 c) {
246
248
  if (uOutputTransform == 0) return c;
247
249
  if (uOutputTransform == 2) c = acesFilmic(c);
250
+ if (uOutputTransform == 3) c = highlightShoulder(c * uOutputExposure);
248
251
  return linearToSrgb(c);
249
252
  }
250
253
 
@@ -335,7 +338,8 @@ vec3 finish(vec3 light) {
335
338
  }
336
339
 
337
340
  void main() {
338
- vec3 scene = textureLod(uScene, vUv, 0.0).rgb;
341
+ vec4 sampled = textureLod(uScene, vUv, 0.0);
342
+ vec3 scene = sampled.rgb;
339
343
  if (uMotionStrength > 0.0) scene = cameraBlur(scene);
340
344
  /*
341
345
  * Defocus after the camera smear and before everything else, because a lens is the last thing
@@ -350,8 +354,15 @@ void main() {
350
354
  * not then darkened again along its own trail. mix from 1 rather than a multiply by the
351
355
  * strength, so the parameter reads as "how much of this occlusion", and 0 is exactly the
352
356
  * frame that existed before the effect did.
357
+ *
358
+ * **Scaled by the surface's share of the pixel**, which the scene carries in its alpha
359
+ * (sceneCoverage.ts): the occlusion was measured from the depth the opaque world left, and a
360
+ * pane, a puff of smoke or a caption in front of a corner wrote no depth, so darkening all of the
361
+ * pixel darkened them by a corner they hide. Where nothing blended landed the share is 1 and
362
+ * this is the frame it was.
353
363
  */
354
- float ao = mix(1.0, textureLod(uAo, vUv + uAoOffset, 0.0).r, uAoStrength);
364
+ float share = clamp(sampled.a, 0.0, 1.0);
365
+ float ao = mix(1.0, textureLod(uAo, vUv + uAoOffset, 0.0).r, uAoStrength * share);
355
366
 
356
367
  /*
357
368
  * Distance from the centre, corrected so the falloff is a circle on screen rather than
@@ -211,11 +211,18 @@ vec3 historyCatmullRom(vec2 uv) {
211
211
  }
212
212
 
213
213
  void main() {
214
- vec3 current = textureLod(uScene, vUv, 0.0).rgb;
214
+ /*
215
+ * **The alpha is this frame's and takes no history.** It is how much of the pixel is still the
216
+ * opaque surface (sceneCoverage.ts), which the composite's occlusion reads after this has
217
+ * replaced the scene, so it passes through untouched. The history is read for its colour alone.
218
+ */
219
+ vec4 sampled = textureLod(uScene, vUv, 0.0);
220
+ vec3 current = sampled.rgb;
221
+ float share = sampled.a;
215
222
 
216
223
  /* Nothing to blend towards: the first frame, a resize, a cut, or the effect switched off. */
217
224
  if (uHistoryBlend <= 0.0) {
218
- fragColor = vec4(current, 1.0);
225
+ fragColor = vec4(current, share);
219
226
  flickerOut = freshRecord(current);
220
227
  flickerMotionOut = vec4(0.0);
221
228
  return;
@@ -248,7 +255,7 @@ void main() {
248
255
  vec4 previous = uReprojection * clip;
249
256
  /* Behind the previous eye: there is no last-frame position to sample. */
250
257
  if (previous.w <= 0.0) {
251
- fragColor = vec4(current, 1.0);
258
+ fragColor = vec4(current, share);
252
259
  flickerOut = freshRecord(current);
253
260
  flickerMotionOut = vec4(metres, 0.0, 0.0, 0.0);
254
261
  return;
@@ -261,7 +268,7 @@ void main() {
261
268
  * as a smear for as long as the camera kept turning.
262
269
  */
263
270
  if (wasUv.x < 0.0 || wasUv.x > 1.0 || wasUv.y < 0.0 || wasUv.y > 1.0) {
264
- fragColor = vec4(current, 1.0);
271
+ fragColor = vec4(current, share);
265
272
  flickerOut = freshRecord(current);
266
273
  flickerMotionOut = vec4(metres, 0.0, 0.0, 0.0);
267
274
  return;
@@ -320,6 +327,6 @@ void main() {
320
327
  repeated * still
321
328
  );
322
329
  flickerMotionOut = vec4(metres, swingOut, 0.0, 0.0);
323
- fragColor = vec4(mix(current, bounded, blend), 1.0);
330
+ fragColor = vec4(mix(current, bounded, blend), share);
324
331
  }
325
332
  `;
@@ -8,6 +8,7 @@ import { compileProgram, uniformLocations } from './shader.ts';
8
8
  import { FULLSCREEN_VERT } from './shaders/fullscreen.ts';
9
9
  import { SSR_RESOLVE_FRAG } from './shaders/ssrResolve.ts';
10
10
  import { SSR_TRACE_FRAG } from './shaders/ssrTrace.ts';
11
+ import { blendKeeping } from './sceneCoverage.ts';
11
12
 
12
13
  /**
13
14
  * Screen-space reflection on WebGL2: a scissored trace per surface, then one composite.
@@ -202,7 +203,7 @@ export class SsrPass {
202
203
  gl.disable(gl.CULL_FACE);
203
204
  gl.enable(gl.BLEND);
204
205
  /* Premultiplied `over`: the trace already multiplied its colour by the coverage it found. */
205
- gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
206
+ blendKeeping(gl, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
206
207
 
207
208
  gl.useProgram(this.resolve);
208
209
  gl.activeTexture(gl.TEXTURE0);
@@ -70,6 +70,17 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
70
70
  /** Repeats across the mesh's own UV range, per axis. Applies to every map the material holds. */
71
71
  uScale?: number;
72
72
  vScale?: number;
73
+ /**
74
+ * Where the texture starts, per axis, added after the scale: a surface samples
75
+ * `uv · scale + offset`. Applies to every map, and to a cutout's shadow and depth as well.
76
+ *
77
+ * **What picks a cell of a flipbook or an atlas through the material**, so one quad draws any
78
+ * cell: a strip of four frames is `uScale: 0.25` and `uOffset: frame * 0.25`. Without it a
79
+ * consumer built one quad mesh per cell — 106 meshes for four strips — because the only way to
80
+ * move a texture was to move the geometry's own coordinates.
81
+ */
82
+ uOffset?: number;
83
+ vOffset?: number;
73
84
  /** Alpha below which a fragment is discarded. See `uAlbedoCutout`. */
74
85
  cutout?: number;
75
86
  /**
@@ -335,6 +346,11 @@ export class SurfaceTexture {
335
346
  }
336
347
 
337
348
  /** Bind the effects table to `unit`, or `fallback` for a texture given none. */
349
+ /** Whether this texture carries an effects table, which the lit program reads only once one exists. */
350
+ get hasEffects(): boolean {
351
+ return this.effectsTable !== null;
352
+ }
353
+
338
354
  bindEffects(gl: WebGL2RenderingContext, unit: number, fallback: WebGLTexture): void {
339
355
  gl.activeTexture(gl.TEXTURE0 + unit);
340
356
  gl.bindTexture(gl.TEXTURE_2D, this.effectsTable ?? fallback);
@@ -2,6 +2,7 @@ import type { TextStyle } from './textLayout.ts';
2
2
  import { MAX_CELLS, TEXT_CUBE, TextLayout, deviceSnappedOrigin } from './textLayout.ts';
3
3
  import { compileProgram, uniformLocations } from './shader.ts';
4
4
  import { TEXT_FRAG, TEXT_VERT } from './shaders/text.ts';
5
+ import { blendCovering } from './sceneCoverage.ts';
5
6
 
6
7
  /**
7
8
  * Text as geometry, on WebGL2.
@@ -112,7 +113,7 @@ export class TextRenderer {
112
113
  const reversed = current === gl.GREATER || current === gl.GEQUAL;
113
114
  gl.depthFunc(reversed ? gl.GEQUAL : gl.LEQUAL);
114
115
  gl.enable(gl.BLEND);
115
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
116
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
116
117
 
117
118
  setVec2(gl, this.uniforms['uViewport'], viewportWidth, viewportHeight);
118
119
  /* Snapped with the cell below: a whole pitch on a fractional origin still splits a stroke.
@@ -105,6 +105,7 @@ export const OUTPUT_TRANSFORM_CODE: Readonly<Record<string, number>> = {
105
105
  none: 0,
106
106
  srgb: 1,
107
107
  aces: 2,
108
+ shoulder: 3,
108
109
  };
109
110
 
110
111
  /**
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The slice of depth a view model is drawn into: the nearest share of the range.
3
+ *
4
+ * **A first-person view model — arms and a held weapon — must never go into the wall it is pushed
5
+ * against**, and clearing depth to draw it on top costs every pass that reads depth afterwards:
6
+ * ambient occlusion, depth of field and the fog all see a hole where the world was. Squeezing the
7
+ * view model's depth into the nearest sliver of the range does neither. Anything past the near
8
+ * plane in the world lands behind it, the world's depth is kept, and the view model still sorts
9
+ * against itself, at a fraction of the precision.
10
+ *
11
+ * **The near end is where the convention says it is**: 1 under reversed depth and 0 under the
12
+ * conventional sense, which a WebGL2 context without `EXT_clip_control` runs. Taken from the
13
+ * renderer's own `reversedDepth`, never from the constant, for the reason `depthConvention.ts`
14
+ * gives at length.
15
+ *
16
+ * **What it gives up**: a view model is in front of everything, so it cannot be drawn reaching
17
+ * behind a world object in its own frame — which is what a view model is for. And under temporal
18
+ * reconstruction its pixels take no motion of their own, because the motion pass matches the frame's
19
+ * depth exactly and a squeezed draw matches nothing; draw a view model with multisampling.
20
+ */
21
+
22
+ /** The share of depth a view model is drawn into when the caller names none: the nearest 1%. */
23
+ export const VIEW_MODEL_DEPTH_SHARE = 0.01;
24
+
25
+ /**
26
+ * Fill `out` with the `[min, max]` window depth a view model's draws are squeezed into.
27
+ *
28
+ * `share` is clamped to `[1e-4, 1]`: zero would collapse every view-model fragment onto one depth
29
+ * and lose its own sorting, and more than the whole range is no squeeze.
30
+ */
31
+ export function viewModelDepthRange(
32
+ reversed: boolean,
33
+ share: number,
34
+ out: [number, number] | Float32Array,
35
+ ): void {
36
+ const width = Number.isFinite(share)
37
+ ? Math.min(1, Math.max(1e-4, share))
38
+ : VIEW_MODEL_DEPTH_SHARE;
39
+ out[0] = reversed ? 1 - width : 0;
40
+ out[1] = reversed ? 1 : width;
41
+ }
@@ -10,6 +10,7 @@ import { bindPointLights } from './lightBudget.ts';
10
10
  import type { PointLightSet } from './lightBudget.ts';
11
11
  import { DEFAULT_RENDER_QUALITY } from './renderQuality.ts';
12
12
  import { createResolvedWater, resolveWater, waterAppearance } from './waterDraw.ts';
13
+ import { blendCovering } from './sceneCoverage.ts';
13
14
 
14
15
  /**
15
16
  * An endless wave surface. Game-agnostic: it takes a height, colours and a
@@ -325,7 +326,7 @@ export class WaterRenderer {
325
326
  gl.uniform1i(u['uReflectionFilterTaps'] ?? null, this.reflectionFilterTaps);
326
327
 
327
328
  gl.enable(gl.BLEND);
328
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
329
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
329
330
  // Waves are displaced far enough that back faces show at grazing angles.
330
331
  gl.disable(gl.CULL_FACE);
331
332
 
@@ -11,6 +11,7 @@ import {
11
11
  type WindStreakSettings,
12
12
  } from './windStreakDraw.ts';
13
13
  import type { WindField } from './windField.ts';
14
+ import { blendCovering } from './sceneCoverage.ts';
14
15
 
15
16
  /**
16
17
  * Makes the wind visible once it is strong enough to be worth seeing.
@@ -112,7 +113,7 @@ export class WindStreakRenderer {
112
113
  gl.uniform3fv(u['uTint'] ?? null, tint);
113
114
 
114
115
  gl.enable(gl.BLEND);
115
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
116
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
116
117
  gl.depthMask(false);
117
118
  gl.disable(gl.CULL_FACE);
118
119
  gl.bindVertexArray(this.vao);