@driftengine/core 4.11.0 → 4.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.json +36 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +1 -1
  5. package/dist/render/ambientOcclusionPass.d.ts +3 -2
  6. package/dist/render/ambientOcclusionPass.js +4 -2
  7. package/dist/render/backend/api.d.ts +21 -1
  8. package/dist/render/backend/webgl2/renderer.d.ts +45 -4
  9. package/dist/render/backend/webgl2/renderer.js +112 -19
  10. package/dist/render/backend/webgpu/renderer.d.ts +52 -1
  11. package/dist/render/backend/webgpu/renderer.js +237 -89
  12. package/dist/render/instances.d.ts +13 -1
  13. package/dist/render/instances.js +10 -0
  14. package/dist/render/occlusionFade.d.ts +21 -0
  15. package/dist/render/occlusionFade.js +27 -0
  16. package/dist/render/sceneTarget.d.ts +2 -0
  17. package/dist/render/sceneTarget.js +1 -1
  18. package/dist/render/shaders/ambientOcclusion.js +12 -1
  19. package/dist/render/shaders/flat/index.js +9 -2
  20. package/dist/render/shaders/flat/layered.d.ts +15 -7
  21. package/dist/render/shaders/flat/layered.js +114 -31
  22. package/dist/render/shaders/flat/main.d.ts +1 -1
  23. package/dist/render/shaders/flat/main.js +10 -3
  24. package/dist/render/shaders/flat/normalMap.d.ts +1 -1
  25. package/dist/render/shaders/flat/normalMap.js +11 -2
  26. package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
  27. package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
  28. package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -48
  29. package/dist/render/shaders/generated/flat.wgsl.js +71 -71
  30. package/dist/render/surfaceLayers.d.ts +83 -15
  31. package/dist/render/surfaceLayers.js +69 -13
  32. package/dist/render/surfaceTexture.d.ts +13 -4
  33. package/dist/render/transmission.d.ts +21 -0
  34. package/dist/render/transmission.js +21 -0
  35. package/dist/ui/splash.d.ts +42 -2
  36. package/dist/ui/splash.js +78 -13
  37. package/package.json +6 -6
  38. package/src/index.ts +2 -0
  39. package/src/render/ambientOcclusionPass.ts +4 -1
  40. package/src/render/backend/api.ts +31 -0
  41. package/src/render/backend/webgl2/renderer.ts +130 -20
  42. package/src/render/backend/webgpu/renderer.ts +371 -139
  43. package/src/render/instances.ts +18 -1
  44. package/src/render/occlusionFade.ts +29 -0
  45. package/src/render/sceneTarget.ts +3 -0
  46. package/src/render/shaders/ambientOcclusion.ts +12 -1
  47. package/src/render/shaders/flat/index.ts +9 -2
  48. package/src/render/shaders/flat/layered.ts +114 -31
  49. package/src/render/shaders/flat/main.ts +10 -3
  50. package/src/render/shaders/flat/normalMap.ts +11 -2
  51. package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
  52. package/src/render/shaders/generated/flat.wgsl.ts +71 -71
  53. package/src/render/surfaceLayers.ts +138 -19
  54. package/src/render/surfaceTexture.ts +13 -4
  55. package/src/render/transmission.ts +27 -0
  56. package/src/ui/splash.ts +83 -14
@@ -106,8 +106,16 @@ import {
106
106
  type SurfaceOverlay,
107
107
  } from '../../surfaceOverlay.ts';
108
108
  import { PROJECTION_FLOATS, packSurfaceProjection, projects } from '../../surfaceProjection.ts';
109
+ import { resolveTransmission } from '../../transmission.ts';
110
+ import { resolveOcclusionFade } from '../../occlusionFade.ts';
109
111
  import { orthographicDepthSpan } from '../../lightMatrix.ts';
110
- import { LAYER_FLOATS, packSurfaceLayers } from '../../surfaceLayers.ts';
112
+ import {
113
+ LAYER_FLOATS,
114
+ layerMaskMap,
115
+ layeredProjection,
116
+ layersFitBeside,
117
+ packSurfaceLayers,
118
+ } from '../../surfaceLayers.ts';
111
119
  import type { SurfaceLayers } from '../../surfaceLayers.ts';
112
120
  import type { SurfaceProjection } from '../../surfaceProjection.ts';
113
121
  import { FILM_FRAG, FILM_VERT } from '../../shaders/film.ts';
@@ -282,7 +290,7 @@ import type { BufferSize } from '../../drawingBuffer.ts';
282
290
  import { Mesh, createMeshIncremental } from '../../mesh.ts';
283
291
  import type { IncrementalMesh } from '../../mesh.ts';
284
292
  import type { MeshData } from '../../mesh.ts';
285
- import type { MeshOptions } from '../api.ts';
293
+ import type { MeshOptions, PrepareOptions } from '../api.ts';
286
294
  import { SurfaceTexture } from '../../surfaceTexture.ts';
287
295
  import { sceneCaptureTexels } from '../../sceneCapture.ts';
288
296
  import { GlSceneCaptureTarget } from './sceneCaptureTarget.ts';
@@ -1599,6 +1607,8 @@ export class WebGL2Renderer implements RendererApi {
1599
1607
  private readonly aoProjScale = new Float32Array(2);
1600
1608
  /** Where this frame's jitter put the occlusion, in the composite's uv. See `uAoOffset`. */
1601
1609
  private readonly aoOffset = new Float32Array(2);
1610
+ /** Where occlusion fades out with distance, `uFade`: none until a caller sets one. */
1611
+ private readonly aoFade = new Float32Array([-1, 0]);
1602
1612
  /**
1603
1613
  * Whether this frame has already been resolved to the canvas.
1604
1614
  *
@@ -1766,14 +1776,18 @@ export class WebGL2Renderer implements RendererApi {
1766
1776
  private warnedProjectionRoom = false;
1767
1777
  /** `uWorldUv`, the current material's projection packed. See `surfaceProjection.ts`. */
1768
1778
  private readonly projection = new Float32Array(PROJECTION_FLOATS);
1769
- /** The current material's `diffuseTransmission`, `uGlassTint.w`, 0 to 1. */
1770
- private diffuseTransmission = 0;
1779
+ /**
1780
+ * `uGlassTint` as the current material leaves it: its light from behind and that light's colour,
1781
+ * which a pane overwrites for its own draw and `unbindSeeThrough` puts back. See `transmission.ts`.
1782
+ */
1783
+ private readonly transmission = new Float32Array([-1, -1, -1, 0]);
1771
1784
  /** `uLayers`, the current material's layers packed. See `surfaceLayers.ts`. */
1772
1785
  private readonly layerFloats = new Float32Array(LAYER_FLOATS);
1773
1786
  /** Whether the lit stage has room for layers' two vectors, and which refusals were said. */
1774
1787
  private layersRoom: boolean | null = null;
1775
1788
  private warnedLayersRoom = false;
1776
1789
  private warnedLayersModelMap = false;
1790
+ private warnedLayersTriplanar = false;
1777
1791
  private warnedOverlayRoom = false;
1778
1792
  private warnedOverlayBlocks = false;
1779
1793
  /** Whether a probe is being baked, so the passes that would recurse into one can tell. */
@@ -3929,6 +3943,58 @@ export class WebGL2Renderer implements RendererApi {
3929
3943
  * `uScale`/`vScale` are repeats across the mesh's own UV range: geometry authored in
3930
3944
  * metres is textured at whatever density the material wants without rebuilding it.
3931
3945
  */
3946
+ /**
3947
+ * Compile, off the frame, every program a draw of `mesh` in `material` takes that making the mesh
3948
+ * did not, so the first frame that draws it waits on none: the program of the material's shading
3949
+ * model for each vertex variant the mesh can be drawn with (its rig, with a palette; its targets,
3950
+ * with weights and without), a skin's halves under the screen-space blur, and the lit switches
3951
+ * the material turns on. `ready()` resolves once they have.
3952
+ *
3953
+ * **For `pipelineCompile: 'skip'` above all**, where a draw whose program is new is left out until
3954
+ * it lands: a figure brought on screen whole rather than without its face and hair for the frames
3955
+ * those take. Where draws wait, the programs compile here, at the call, rather than in a frame.
3956
+ * Two-sided and cut-out surfaces are draw state on this backend and need nothing; on WebGPU they
3957
+ * are pipelines of their own, prepared the same way.
3958
+ *
3959
+ * **Call it between frames**: it sets `material` to read it and leaves no material set after, as
3960
+ * `setMaterial(null)` does. `options` is the other backend's; blended draws share a program here.
3961
+ */
3962
+ prepareMesh(mesh: Mesh, material: SurfaceMaterial | null, _options: PrepareOptions = {}): void {
3963
+ if (this.contextLost) return;
3964
+ this.setMaterial(material);
3965
+ const kind = material?.model?.kind ?? null;
3966
+ if (kind !== null) {
3967
+ const eight = mesh.isSkinnedEight;
3968
+ const morphs = mesh.morph !== null;
3969
+ const plain = mesh.isSkinned ? (eight ? 'skinned8' : 'skinned') : 'plain';
3970
+ const morphed = mesh.isSkinned ? (eight ? 'both8' : 'both') : 'morphed';
3971
+ for (const variant of morphs ? [plain, morphed] : [plain]) {
3972
+ this.modelProgram(variant, kind);
3973
+ /* A skin under the screen-space blur draws three halves, each a program of its own. */
3974
+ if (kind === 'skin' && this.skinScatter !== null && this.sceneTarget !== null) {
3975
+ for (const half of SPLIT_SKIN_HALVES) this.modelProgram(variant, kind, half);
3976
+ }
3977
+ }
3978
+ }
3979
+ this.setMaterial(null);
3980
+ }
3981
+
3982
+ /** `prepareMesh` for an instanced batch: its own programs, and its model's for its variant. */
3983
+ prepareInstanced(
3984
+ batch: InstancedBatch,
3985
+ material: SurfaceMaterial | null,
3986
+ _options: PrepareOptions = {},
3987
+ ): void {
3988
+ if (this.contextLost) return;
3989
+ this.setMaterial(material);
3990
+ const animated = batch.animation !== null;
3991
+ this.ensureInstancedProgram();
3992
+ if (animated) this.ensureAnimatedProgram();
3993
+ const kind = material?.model?.kind ?? null;
3994
+ if (kind !== null) this.modelProgram(animated ? 'animated' : 'instanced', kind);
3995
+ this.setMaterial(null);
3996
+ }
3997
+
3932
3998
  /**
3933
3999
  * Choose the joint palette the following `drawMesh` calls skin by, or null for none.
3934
4000
  *
@@ -4323,9 +4389,9 @@ export class WebGL2Renderer implements RendererApi {
4323
4389
  * and read neither — one decision for every program, as `setMaterial` says.
4324
4390
  */
4325
4391
  const modelMap = material?.modelMap ?? null;
4326
- /* A layered material's mask goes where a model's map would: see `SurfaceMaterial.layers`. */
4392
+ /* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
4327
4393
  const layers = this.layersOf(material);
4328
- const boundMap = modelMap ?? layers?.mask ?? null;
4394
+ const boundMap = modelMap ?? layerMaskMap(layers);
4329
4395
  gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
4330
4396
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
4331
4397
  if (boundMap !== null) boundMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
@@ -4345,11 +4411,12 @@ export class WebGL2Renderer implements RendererApi {
4345
4411
  /* The draw's overlay, which a program born mid-pass must wear too: see `setSurfaceOverlay`. */
4346
4412
  gl.uniform4fv(u['uOverlay'] ?? null, this.overlayFloats);
4347
4413
  gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
4348
- /* How much light from behind the surface lets through, in the glass tint's spare lane. */
4349
- this.diffuseTransmission = Math.min(Math.max(material?.diffuseTransmission ?? 0, 0), 1) || 0;
4350
- gl.uniform4f(u['uGlassTint'] ?? null, 1, 1, 1, this.diffuseTransmission);
4414
+ /* How much light from behind the surface lets through, and its colour: transmission.ts. */
4415
+ resolveTransmission(material, this.transmission);
4416
+ this.writeTransmission(u);
4351
4417
  /* Maps placed by the world, where the material asks and the lit stage has room: worldUv.ts. */
4352
- const projection = material?.projection ?? null;
4418
+ const projection = layeredProjection(material?.projection, layers);
4419
+ if (projection !== (material?.projection ?? null)) this.warnLayersTriplanar();
4353
4420
  packSurfaceProjection(this.projectionFits(projection) ? projection : null, this.projection);
4354
4421
  if ((this.projection[0] as number) > 0) this.askLit('WORLD_UVS');
4355
4422
  gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
@@ -4598,14 +4665,15 @@ export class WebGL2Renderer implements RendererApi {
4598
4665
  * no room for their two vectors each leave one layer, the last two said once each.
4599
4666
  */
4600
4667
  private layersOf(material: SurfaceMaterial | null): SurfaceLayers<SurfaceTexture> | null {
4601
- const layers = material?.layers ?? null;
4668
+ const layers = (material?.layers ?? null) as SurfaceLayers<SurfaceTexture> | null;
4602
4669
  if (layers === null || layers.repeats.length === 0) return null;
4603
- if ((material?.modelMap ?? null) !== null) {
4670
+ if (!layersFitBeside(layers, material?.modelMap)) {
4604
4671
  if (!this.warnedLayersModelMap) {
4605
4672
  this.warnedLayersModelMap = true;
4606
4673
  console.warn(
4607
- "Renderer: a material's layers are drawn as one: its mask is read where a model's map " +
4608
- 'goes, and this material carries a `modelMap` too.',
4674
+ "Renderer: a material's layers are drawn as one: its mask is a map, read where a " +
4675
+ "model's map goes, and this material carries a `modelMap` too. A mask in the ORM " +
4676
+ "array (`mask: 'orm'`) or the vertex colour (`mask: 'vertex'`) leaves the slot free.",
4609
4677
  );
4610
4678
  }
4611
4679
  return null;
@@ -4615,7 +4683,7 @@ export class WebGL2Renderer implements RendererApi {
4615
4683
  if (!this.warnedLayersRoom) {
4616
4684
  this.warnedLayersRoom = true;
4617
4685
  console.warn(
4618
- "Renderer: a material's layers are refused on this device. They take two fragment uniform " +
4686
+ "Renderer: a material's layers are refused on this device. They take four fragment uniform " +
4619
4687
  'vectors the lit shader does not have; the material is drawn as its base layer. A lower ' +
4620
4688
  '`maxLights` makes the room, or WebGPU has it.',
4621
4689
  );
@@ -4623,6 +4691,16 @@ export class WebGL2Renderer implements RendererApi {
4623
4691
  return null;
4624
4692
  }
4625
4693
 
4694
+ /** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
4695
+ private warnLayersTriplanar(): void {
4696
+ if (this.warnedLayersTriplanar) return;
4697
+ this.warnedLayersTriplanar = true;
4698
+ console.warn(
4699
+ "Renderer: a layered material's triplanar projection is laid on the horizontal plane: every " +
4700
+ "layer's maps on three planes would be three times the reads. See `SurfaceMaterial.layers`.",
4701
+ );
4702
+ }
4703
+
4626
4704
  /** Whether the lit shader with `feature` compiled in fits this GPU's fragment uniform vectors. */
4627
4705
  private switchFits(feature: LitSwitch): boolean {
4628
4706
  const asIf: Partial<Record<LitSwitch, boolean>> = { [feature]: true };
@@ -4786,7 +4864,8 @@ export class WebGL2Renderer implements RendererApi {
4786
4864
  *
4787
4865
  * What it gives up: a fade reads as grain while it lasts, which a temporal resolve smooths and a
4788
4866
  * plain frame does not; a blended draw that asks spends its opacity here and blends none of it;
4789
- * and a lightmapped batch carries regions where the opacities would be, so it has none to fade.
4867
+ * and a lightmapped batch's opacities ride its page regions, so they fade in steps of a
4868
+ * sixty-fourth, which is the pattern's own resolution (`MeshInstances.lightmapRegions`).
4790
4869
  */
4791
4870
  setDitherOpacity(on: boolean): void {
4792
4871
  if (this.contextLost) return;
@@ -5281,15 +5360,23 @@ export class WebGL2Renderer implements RendererApi {
5281
5360
  glass.tint[0] ?? 1,
5282
5361
  glass.tint[1] ?? 1,
5283
5362
  glass.tint[2] ?? 1,
5284
- this.diffuseTransmission,
5363
+ this.transmission[3] as number,
5285
5364
  );
5286
5365
  return true;
5287
5366
  }
5288
5367
 
5368
+ /** `uGlassTint` as the current material leaves it. See `transmission`. */
5369
+ private writeTransmission(u: Record<string, WebGLUniformLocation>): void {
5370
+ const t = this.transmission;
5371
+ this.gl.uniform4f(u['uGlassTint'] ?? null, t[0] ?? -1, t[1] ?? -1, t[2] ?? -1, t[3] ?? 0);
5372
+ }
5373
+
5289
5374
  /** Put back what `bindSeeThrough` bound: the binding outlives its frame (the 2026-08-27 rule). */
5290
5375
  private unbindSeeThrough(u: Record<string, WebGLUniformLocation>): void {
5291
5376
  const { gl } = this;
5292
5377
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
5378
+ /* The material's light through, over the pane's tint: transmission.ts. */
5379
+ this.writeTransmission(u);
5293
5380
  gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
5294
5381
  /* The overlay's atlas, where one is held: it shares the unit. See `setSurfaceOverlay`. */
5295
5382
  gl.bindTexture(gl.TEXTURE_2D, this.overlayTexture ?? this.emptyTexture2D);
@@ -7800,6 +7887,20 @@ export class WebGL2Renderer implements RendererApi {
7800
7887
  this.sceneTarget.setLocalExposure(strength);
7801
7888
  }
7802
7889
 
7890
+ /**
7891
+ * Where ambient occlusion fades out with distance: whole up to `distance` metres from the eye and
7892
+ * gone `radius` metres past it, so the far depth buffer's steps — a sky dome kilometres off, a
7893
+ * mountain range — are not shaded in rings, and those pixels skip the occlusion's sampling.
7894
+ *
7895
+ * A distance that is not a finite number at or above zero is no fade, which is the default, and
7896
+ * `Infinity` reads as "never fades"; a radius below zero is a cut at `distance`. Set before
7897
+ * `endFrame` and held until changed, like `setDepthOfField`. Nothing at all when
7898
+ * `ambientOcclusion` is 0. See `occlusionFade.ts` for what it gives up.
7899
+ */
7900
+ setAmbientOcclusionFade(distance: number, radius: number): void {
7901
+ resolveOcclusionFade(distance, radius, this.aoFade);
7902
+ }
7903
+
7803
7904
  /**
7804
7905
  * Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
7805
7906
  * to take.
@@ -8453,6 +8554,7 @@ export class WebGL2Renderer implements RendererApi {
8453
8554
  radius: number;
8454
8555
  projScale: Float32Array;
8455
8556
  invProjection: Float32Array;
8557
+ fade: Float32Array;
8456
8558
  offset: Float32Array;
8457
8559
  }
8458
8560
  | undefined;
@@ -8476,6 +8578,7 @@ export class WebGL2Renderer implements RendererApi {
8476
8578
  radius: this.quality.ambientOcclusionRadius,
8477
8579
  projScale: this.aoProjScale,
8478
8580
  invProjection: this.aoInvProjection as Float32Array,
8581
+ fade: this.aoFade,
8479
8582
  offset: this.aoOffset,
8480
8583
  };
8481
8584
  }
@@ -9010,7 +9113,7 @@ export class WebGL2Renderer implements RendererApi {
9010
9113
  /* Nor a projection: maps follow the mesh's coordinates until a material asks otherwise. */
9011
9114
  this.projection.fill(0);
9012
9115
  /* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
9013
- this.diffuseTransmission = 0;
9116
+ resolveTransmission(null, this.transmission);
9014
9117
  /* Nor layers: one layer until a material asks for more. */
9015
9118
  this.layerFloats.fill(0);
9016
9119
  this.overlayMaps = null;
@@ -9191,8 +9294,15 @@ export class WebGL2Renderer implements RendererApi {
9191
9294
  `bindSeeThrough` for the four numbers in the one vector. */
9192
9295
  gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
9193
9296
  gl.uniform4f(u['uRefractTint'] ?? null, 1, 1, 1, 1);
9194
- /* White, and no light through from behind: no material at a pass's start. */
9195
- gl.uniform4f(u['uGlassTint'] ?? null, 1, 1, 1, this.diffuseTransmission);
9297
+ /* No light through from behind: no material at a pass's start. See `writeTransmission`. */
9298
+ const through = this.transmission;
9299
+ gl.uniform4f(
9300
+ u['uGlassTint'] ?? null,
9301
+ through[0] ?? -1,
9302
+ through[1] ?? -1,
9303
+ through[2] ?? -1,
9304
+ through[3] ?? 0,
9305
+ );
9196
9306
  gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
9197
9307
  gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
9198
9308
  gl.uniform3fv(u['uAmbient'] ?? null, env.ambient);