@driftengine/core 4.9.0 → 4.10.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.
- package/CHANGELOG.json +62 -11
- package/README.md +43 -38
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -3
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/backend/api.d.ts +10 -3
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +90 -2
- package/dist/render/backend/webgl2/renderer.js +253 -24
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +19 -4
- package/dist/render/backend/webgpu/buffers.js +18 -11
- package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
- package/dist/render/backend/webgpu/drawCommand.js +10 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +85 -42
- package/dist/render/backend/webgpu/flatPass.js +136 -20
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
- package/dist/render/backend/webgpu/pipelineCache.js +7 -0
- package/dist/render/backend/webgpu/renderer.d.ts +152 -35
- package/dist/render/backend/webgpu/renderer.js +787 -142
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +241 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/glCompressed.js +83 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +5 -0
- package/dist/render/mesh.js +6 -0
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/screenSpaceReflection.d.ts +2 -1
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/flat/index.d.ts +6 -0
- package/dist/render/shaders/flat/index.js +27 -5
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +15 -5
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/overlay.js +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -2
- package/dist/render/shaders/flat/preamble.js +38 -24
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3077 -2903
- package/dist/render/shaders/generated/flat.wgsl.js +3100 -2925
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceTexture.d.ts +38 -7
- package/dist/render/surfaceTexture.js +56 -17
- package/package.json +6 -6
- package/src/index.ts +8 -2
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/backend/api.ts +13 -3
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +315 -24
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +42 -16
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/drawCommand.ts +22 -4
- package/src/render/backend/webgpu/flatPass.ts +187 -14
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/pipelineCache.ts +7 -0
- package/src/render/backend/webgpu/renderer.ts +928 -155
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +363 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/glCompressed.ts +83 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +6 -0
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/screenSpaceReflection.ts +2 -1
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/flat/index.ts +33 -5
- package/src/render/shaders/flat/main.ts +15 -5
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/overlay.ts +1 -1
- package/src/render/shaders/flat/pointShadow.ts +2 -2
- package/src/render/shaders/flat/preamble.ts +38 -24
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/flat.wgsl.ts +3100 -2925
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceTexture.ts +94 -27
package/CHANGELOG.json
CHANGED
|
@@ -1,4 +1,55 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "4.10.0",
|
|
4
|
+
"date": "2026-10-07",
|
|
5
|
+
"entries": [
|
|
6
|
+
{
|
|
7
|
+
"kind": "added",
|
|
8
|
+
"short": "`createStaticDraws` keeps a world's still draws and `drawStaticDraws` replays them: on WebGPU one render bundle a view, recorded once, where a frame encoded every draw.",
|
|
9
|
+
"text": "`renderer.createStaticDraws(casters)` takes the enumeration `drawSceneCasters` takes — rigid meshes and instanced batches, each with its material — once, and keeps it; `drawStaticDraws(list)` draws it into the open mesh pass under whatever camera, lights and fog `bindMeshPass` set, the frame's, a mirror's or a capture's, and `disposeStaticDraws` releases it. On WebGPU a list is recorded into a render bundle for each view and replayed with one call, and recorded again only when something it was recorded against changes; WebGL2 has no bundles and replays the entries every frame, drawing the same picture. A skinned mesh or a scatter batch is refused by name, since both change every frame, and a list draws whole in every view, so a world split into lists by region culls by list. On `demo/dev/staticDraws.html`, 412 draws cost the main thread 0.650 ms a frame drawn one by one and 0.127 ms as a list — 1.646 and 0.202 with a scene capture beside the frame — and the two pictures are the same to the pixel on both backends."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"kind": "added",
|
|
13
|
+
"short": "`createBoneAnimation` plays a crowd in the instanced vertex stage: each vertex follows the bone its second coordinates name, each instance on its own clock, shadows included.",
|
|
14
|
+
"text": "`renderer.createBoneAnimation(clip)` takes a clip as a turn and a place for each bone at each of its frames, and `InstancedOptions.animation` plays it on every instance of a batch: each vertex follows the one bone its second coordinates' u names, at the instance's own moment of the clip. `MeshInstances.clocks` holds a phase and a rate an instance, and `setAnimationTime(seconds)` the clock they share, set once a frame before the shadows. Every instance moves at the frame's rate and out of step with the next, in the colour pass and in every shadow, with nothing done on the processor once the clip is made; `disposeBoneAnimation` releases it. Both backends run the same arithmetic: on `demo/dev/boneCrowd.html` twenty-five instances played by the vertex stage and posed on the processor by that arithmetic differ by 0 pixels on WebGPU and 2 on WebGL2, shadows included. What it gives up: a vertex follows one bone; an animated batch is culled whole, never instance by instance; and under reconstruction or the temporal resolve a crowd's own movement is not in the frame's motion."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"kind": "added",
|
|
18
|
+
"short": "Surface textures take ETC2, EAC and ASTC blocks beside BC, uploaded as they are wherever `compressedFormats` says the device samples them, on both backends.",
|
|
19
|
+
"text": "`createSurfaceTexture` takes ETC2 (`etc2-rgb8`, `etc2-rgb8a1`, `etc2-rgba8`), EAC (`eac-r11`, `eac-rg11`) and every LDR ASTC block size from 4x4 to 12x12, each with its sRGB twin where it has one, beside the BC formats it already took. WebGPU asks for `texture-compression-etc2` and `texture-compression-astc` wherever the adapter offers them, WebGL2 reads `WEBGL_compressed_texture_etc` and `WEBGL_compressed_texture_astc`, and `compressedFormats` lists what the device samples — so a phone's textures go up at a byte a texel or less, where RGBA takes four. `updateSurfaceTexture` takes blocks too: they replace an image behind the handle every draw already holds, read in the colour space the texture was made in and refused where the device does not take them, while an image in place of blocks is still refused. `levelBytes` gives the bytes a level of any block format holds. On `demo/dev/phoneTextures.html` an ETC2 chain, an EAC normal map and an ASTC block draw exactly the texels they decode to, on both backends."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"kind": "added",
|
|
23
|
+
"short": "On a device that samples ETC2 and not BC, `DrftLoader` re-encodes BC textures as ETC2 in a worker and swaps them in; `readKtx2` takes a KTX2 file as its blocks.",
|
|
24
|
+
"text": "A `DrftLoader` given `bcWorker` re-encodes each BC texture as ETC2 or EAC where the device samples those and not BC, which is a phone. The texture is shown decoded first, exactly as before, and its chain then replaces it behind the handle it was drawn with, from a second worker of the same factory, one texture at a time: `etc2-rgb8`, or `etc2-rgba8` where the decoded alpha is less than opaque, `eac-r11` for BC4 and `eac-rg11` for BC5, every level averaged in light where the slot reads colour. Nothing arrives later for it, and the texture ends at half a byte a texel, or a byte with alpha, where it held four; the encode costs about 0.7 s for a 2048² image on a desktop processor, on a core of its own. Without a worker nothing is encoded. `readKtx2` reads an uncompressed KTX2 file of ASTC, ETC2, EAC or BC as the blocks it carries, refusing a supercompressed file, a cube or an array by name, and `encodeEtc2` and `encodeEtc2Chain` encode pixels a caller holds. On `demo/dev/phoneLoad.html` the loader's swapped texture and the same chain uploaded directly draw the same picture on both backends."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"kind": "added",
|
|
28
|
+
"short": "`SurfaceMaterial.reflectivity` and `environmentGain` state a material's own reflection for its draws, so a static list keeps each batch's reflectivity and gain.",
|
|
29
|
+
"text": "A `SurfaceMaterial` may state `reflectivity`, 0 to 1, and `environmentGain`, held non-negative: the numbers `setSurfaceReflectivity` and `setEnvironmentGain` set for every draw, stated by the material for its own. A draw under a material that states one wears it whatever the setters say, and a material stating neither leaves its draws to the setters, as every material did before; each falls back on its own. They are what lets a static list carry a stage whose batches each mirror their own share of a baked reflection at their own brightness, since a list keeps each entry's material and none of the renderer's state between draws. On both backends."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"kind": "changed",
|
|
33
|
+
"short": "WebGPU keeps a draw's material across frames: the lit block is a pass's and a material's, and a material seen before is bound again rather than uploaded again.",
|
|
34
|
+
"text": "The lit stage's uniforms on WebGPU are three blocks where they were one: the pass's, written once a pass; the material's, kept in a store by its contents and bound by offset, so a material a frame has drawn before costs no upload; and, for the vertex stage, the camera, light matrix and wind, so a draw's own slot no longer changes when the camera does. A frame that draws what the one before it drew uploads no material bytes, where the dev pages measured 14 to 29 KB a frame, and every published scene draws the same to the pixel on both backends. The cutout dither's frame moved into the pass's block with it, since a number that changes every frame made every cutout material new every frame. The frame's statistics still count material changes, as WebGL2's do."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"kind": "changed",
|
|
38
|
+
"short": "A skinned draw keeps its bind group per material, and a skin palette slot keeps its largest texture, so mixed rigs no longer make textures and bind groups every frame.",
|
|
39
|
+
"text": "On WebGPU a skin palette slot keeps the largest texture it has needed rather than replacing it whenever a rig of another joint count takes the slot, and a skinned or morphed draw's bind group is kept per slot and per material rather than built again; WebGL2's palette only grows as well. A frame whose skinned meshes and characters are culled differently from the frame before no longer creates and destroys textures and bind groups to draw them."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"kind": "changed",
|
|
43
|
+
"short": "`updateMesh` under reconstruction records the previous positions' copy into the frame's own work rather than submitting a command buffer of its own each time.",
|
|
44
|
+
"text": "On WebGPU, rewriting a mesh's positions while DriftTR keeps its previous positions for motion copied the old ones in a command buffer submitted on the spot, one submit a rewrite. The copy is recorded into the frame's own work now, so a mesh rewritten every frame costs its upload and nothing more, and every submit the renderer makes goes through one place."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"kind": "fixed",
|
|
48
|
+
"short": "Refraction bends by the surface's normal as the eye sees it, so a card facing the camera shimmers by its normal map instead of bending as one lens.",
|
|
49
|
+
"text": "A refracting draw bent the frame behind it by the world x and y of its normal, which on a quad facing the camera is the quad's lean against the world's axes: one offset across the whole quad, a lens, with a normal map's tilt a small change on top. It bends by the normal in view space now, the map's tilt included, so a distortion card shimmers by its map and a pane bends by how it leans toward the eye wherever the eye stands, the same on both backends."
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
2
53
|
{
|
|
3
54
|
"version": "4.9.0",
|
|
4
55
|
"date": "2026-10-07",
|
|
@@ -76,18 +127,18 @@
|
|
|
76
127
|
},
|
|
77
128
|
{
|
|
78
129
|
"kind": "added",
|
|
79
|
-
"short": "`PointLightSource.falloffExponent` lights by `(1 - (d/R)^2)^n` with no distance term,
|
|
80
|
-
"text": "A point or spot light may name its own falloff: `(1 - (d/R)^2)^n` with `R` its radius and `n` the exponent, and no inverse square, which is how
|
|
130
|
+
"short": "`PointLightSource.falloffExponent` lights by `(1 - (d/R)^2)^n` with no distance term, the usual falloff for a light without inverse square, in both arms and on water, DriftLight and DriftRay.",
|
|
131
|
+
"text": "A point or spot light may name its own falloff: `(1 - (d/R)^2)^n` with `R` its radius and `n` the exponent, and no inverse square, which is how lighting tools commonly light a light that does not use inverse-square falloff. A rig authored that way keeps its shape: a fill meant to light a whole hall across a hundred metres does, where the frame's falloff had it gone a few metres out. 0, or absent, is the frame's falloff as before. The selection carries it in `PointLightBuffer.falloffExponents` and the environment in `lightFalloffExponents`; the fixed arm reads it in the third lane of the cone uniform, which a two-float array already paid a whole row for, so the eight-light build spends no uniform row on it, and the froxel table's record grows to six texels. Water, DriftLight's volume and DriftRay's bounce fall off by it too, so a light lands the same way on every surface it reaches. On `demo/dev/clustered.html` one light drawn by the fixed arm and by the table is 0 pixels apart on both backends, and an exponent of 8 moves 42,123 pixels against none."
|
|
81
132
|
},
|
|
82
133
|
{
|
|
83
134
|
"kind": "added",
|
|
84
135
|
"short": "`AreaLightSource.barnDoorAngle` and `barnDoorLength` hang barn doors on a rectangle: each fragment is lit by the part the doors leave in view, its highlight included, in both arms.",
|
|
85
|
-
"text": "A rectangle may carry barn doors, a flap hinged at each edge standing `barnDoorAngle` degrees from its normal and `barnDoorLength` metres long, 20 cm when absent,
|
|
136
|
+
"text": "A rectangle may carry barn doors, a flap hinged at each edge standing `barnDoorAngle` degrees from its normal and `barnDoorLength` metres long, 20 cm when absent, the usual default for a rectangle light. At 90 they fold flat and hide nothing; smaller narrows the light to the opening they leave. Each fragment shades the part of the rectangle no door's tip hides, one axis at a time, so the form factor and the integrated highlight both come from that smaller rectangle, in the fixed arm, which reads the doors in the size uniform's two spare lanes, and in the froxel table's record. Each door is taken as endless along its hinge, so where two meet the corner they hide is square rather than mitred, and a shadowed rectangle's map is still baked from its whole face. Measured from straight above a 1.2 by 0.5 m panel 1.2 m over a floor with 20 degree, 30 cm doors: nothing changes within the 1.04 by 0.69 m box the door tips leave whole, nothing is lit past 4.94 and 2.32 m where they hide all of it, the fixed and clustered arms are 0 pixels apart, and doors at 90 degrees are 0 pixels from none, on both backends."
|
|
86
137
|
},
|
|
87
138
|
{
|
|
88
139
|
"kind": "added",
|
|
89
140
|
"short": "`SurfaceMaterial.physicalSpecular` shades a lamp's and the sun's highlight as GGX's own `pi D Vis F N.L` with the specular attribute as F0, on the standard, lightmap and anisotropic models.",
|
|
90
|
-
"text": "The engine's highlight is a look control: it peaks at the specular attribute times one however rough the surface, so strength and width are set apart. A material with `physicalSpecular: true` takes GGX's own term instead, `pi * D * Vis * F * N.L` with Smith's height-correlated masking, as
|
|
141
|
+
"text": "The engine's highlight is a look control: it peaks at the specular attribute times one however rough the surface, so strength and width are set apart. A material with `physicalSpecular: true` takes GGX's own term instead, `pi * D * Vis * F * N.L` with Smith's height-correlated masking, as this engine's skin and eye already did. Its peak is `1 / (4 alpha^2)` of the light head-on, so a polished surface's highlight is several times the look's and a rough one's lower and wider; the specular attribute is read as the reflectance at normal incidence, `0.08 * Specular`, and Fresnel brightens every surface toward grazing rather than a metal alone. Lamps, widened by their size as before, and the sun take it, on the standard, lightmap and anisotropic models; a rectangle's highlight was already the lobe integrated over it with that F0. The code is a lit switch, compiled into the lit programs the first time a material asks, so a scene that never does carries none of it. Measured with a lamp and an eye mirrored about a floor at roughness 0.5, where the term works out by hand to 4.4376: the physical highlight at 1/4.4377 of the light reads the look's middle pixel to within a level on both backends, from a lamp and from the sun, and the anisotropic model's physical lobe at strength 0 is the standard one to within a level everywhere."
|
|
91
142
|
},
|
|
92
143
|
{
|
|
93
144
|
"kind": "added",
|
|
@@ -138,7 +189,7 @@
|
|
|
138
189
|
{
|
|
139
190
|
"kind": "added",
|
|
140
191
|
"short": "Baked lightmaps: `createLightmap` uploads a page of irradiance and direction, and a `lightmapModel` material reads its region of it at a mesh's `lightmapUvs`, or an instance's region in a batch.",
|
|
141
|
-
"text": "`createLightmap(page)` uploads a baked page, linear irradiance as three floats a texel and the direction it arrives from as a first-order spherical harmonic in four bytes, as two half-float layers, and returns a texture that a material made with `lightmapModel({ region })` takes as its `modelMap`. A surface adds `albedo * (1 - metal) * irradiance * max(0, dot(d.xyz, n) + d.w)` to its diffuse,
|
|
192
|
+
"text": "`createLightmap(page)` uploads a baked page, linear irradiance as three floats a texel and the direction it arrives from as a first-order spherical harmonic in four bytes, as two half-float layers, and returns a texture that a material made with `lightmapModel({ region })` takes as its `modelMap`. A surface adds `albedo * (1 - metal) * irradiance * max(0, dot(d.xyz, n) + d.w)` to its diffuse, a directional lightmap read, with `n` the shading normal and the page read at the mesh's second coordinates, `MeshData.lightmapUvs`, scaled and offset by the region. An instanced batch carries a region an instance in `MeshInstances.lightmapRegions`, applied before the material's, so instances of one mesh sit in different parts of one page. The lightmap is a shading model, a pipeline constant of its own, so nothing that does not use one pays for it, and every published scene moves 0 pixels on WebGPU. What it takes is spent elsewhere: the coordinates ride the grain and relief attributes, stored below zero so that the same mesh drawn without its page has neither, and an instance's region rides its tint and opacity. A mesh naming lightmap coordinates beside grain or relief that is not zero is refused, and a batch and a material that disagree about regions are said once on the console. Checked on a room baked on the CPU with a box's shadow in it (`demo/dev/lightmap.html`): a page of one value adds exactly what the same ambient raised by `setAmbientSH` adds, 0 pixels apart on both backends, sixteen instanced tiles are within one level of the same tiles drawn one at a time, and the two backends are within one level of each other. The GPU-driven pipeline reads no page."
|
|
142
193
|
},
|
|
143
194
|
{
|
|
144
195
|
"kind": "added",
|
|
@@ -148,17 +199,17 @@
|
|
|
148
199
|
{
|
|
149
200
|
"kind": "added",
|
|
150
201
|
"short": "`setAmbientSH(coefficients)` gives the draws that follow their own ambient, nine RGB spherical-harmonic coefficients in place of the frame's and the probes', and `null` gives the frame's back.",
|
|
151
|
-
"text": "`setAmbientSH(coefficients)` takes the 27 numbers of a second-order spherical harmonic, nine coefficients of radiance in three channels, and lights the draws that follow by its cosine-convolved irradiance in place of the frame's ambient and the probe grid's, until `null` or the next `bindMeshPass` gives the frame's back. A character moving through a baked irradiance volume samples it where it stands each frame and sets it around its own draws,
|
|
202
|
+
"text": "`setAmbientSH(coefficients)` takes the 27 numbers of a second-order spherical harmonic, nine coefficients of radiance in three channels, and lights the draws that follow by its cosine-convolved irradiance in place of the frame's ambient and the probe grid's, until `null` or the next `bindMeshPass` gives the frame's back. A character moving through a baked irradiance volume samples it where it stands each frame and sets it around its own draws, a per-object volumetric lightmap. It is material state, so on WebGPU a change takes a material slot. The lit shader had no uniform row to spare on a 256-row phone, so scalar uniforms were packed into vectors they now share, and the eight-light build fits the 256 rows it fitted before. Checked on both backends: the frame's own sky-and-ground gradient handed back as coefficients draws 0 pixels from the frame without them."
|
|
152
203
|
},
|
|
153
204
|
{
|
|
154
205
|
"kind": "added",
|
|
155
206
|
"short": "Lighting channels: a point or spot light shades only surfaces whose `SurfaceMaterial.lightChannels` mask shares a bit with its own, so a character's key and rim lights leave the stage alone.",
|
|
156
|
-
"text": "Each point or spot light carries a channel mask, `Environment.lightChannels` beside `lightColors` (and `PointLightSet.lightChannels` where a consumer fills the set itself), and each material one, `SurfaceMaterial.lightChannels`, both whole masks from 1 to 255 and 1 by default, so a scene that names none is lit exactly as it was. A light shades a surface only where the two share a bit
|
|
207
|
+
"text": "Each point or spot light carries a channel mask, `Environment.lightChannels` beside `lightColors` (and `PointLightSet.lightChannels` where a consumer fills the set itself), and each material one, `SurfaceMaterial.lightChannels`, both whole masks from 1 to 255 and 1 by default, so a scene that names none is lit exactly as it was. A light shades a surface only where the two share a bit: a game lights its characters with key and rim lights that the stage never sees. The fixed loop and the froxel table both honour it on both backends, the mask riding a spare lane of a light's profile axis in the one and the light's record in the other. The sun, the sky, the probes, area lights, DriftLight and the GPU-driven pipeline light every surface whatever its mask."
|
|
157
208
|
},
|
|
158
209
|
{
|
|
159
210
|
"kind": "added",
|
|
160
|
-
"short": "`setBloom` takes a response: a ramp in place of subtracting the threshold, and a tint for each of the six levels, so a stage authored against
|
|
161
|
-
"text": "`setBloom(scale, threshold, response)` takes a `BloomResponse`: `ramp`, the scene units over which a colour comes in from none of it at the threshold to all of it,
|
|
211
|
+
"short": "`setBloom` takes a response: a ramp in place of subtracting the threshold, and a tint for each of the six levels, so a stage authored against a ramped bloom blooms as it was lit.",
|
|
212
|
+
"text": "`setBloom(scale, threshold, response)` takes a `BloomResponse`: `ramp`, the scene units over which a colour comes in from none of it at the threshold to all of it, `saturate((L - threshold) / 2)` where the old subtraction stays the default; and `tints`, a colour for each of the six levels from the finest halo to a twentieth of the frame, white by default and not clamped, so a tint carries a strength. One band over the colour less a low threshold washed a stage authored with a threshold of 0.1 white. Both backends weigh each level by a blend constant as it is added back, so the response costs no pass and no texture, and a frame that names none draws what it drew: the published scenes move 0 pixels on WebGPU. `BLOOM_LEVELS` is exported."
|
|
162
213
|
},
|
|
163
214
|
{
|
|
164
215
|
"kind": "added",
|
|
@@ -173,7 +224,7 @@
|
|
|
173
224
|
{
|
|
174
225
|
"kind": "added",
|
|
175
226
|
"short": "With `clusteredLights` on, rectangle lights past `maxAreaLights` are shaded through the froxel table, unshadowed and windowed by a `range`, so a stage's strips and panels all light it.",
|
|
176
|
-
"text": "Rectangles past the fixed `maxAreaLights` are binned into the froxel table beside the point lights and shaded by the same function the fixed loop uses, integrated specular and all, so one rectangle drawn either way is 0 pixels apart on both backends. They take no shadow, and are windowed to zero at `AreaLightSource.range`,
|
|
227
|
+
"text": "Rectangles past the fixed `maxAreaLights` are binned into the froxel table beside the point lights and shaded by the same function the fixed loop uses, integrated specular and all, so one rectangle drawn either way is 0 pixels apart on both backends. They take no shadow, and are windowed to zero at `AreaLightSource.range`, an attenuation radius in metres, which a rectangle that names none derives from where its light on its own axis falls to a thousandth of a scene unit. A stage with 163 rectangle lights had been drawing them as spots. Without the table, rectangles past the fixed ones are said once on the console rather than dropped quietly."
|
|
177
228
|
},
|
|
178
229
|
{
|
|
179
230
|
"kind": "changed",
|
|
@@ -188,7 +239,7 @@
|
|
|
188
239
|
{
|
|
189
240
|
"kind": "fixed",
|
|
190
241
|
"short": "On WebGPU a crowd of skinned characters no longer stands in its bind pose past the 128th palette of a frame: the ring holds one a draw, and a rig drawn by five passes spends one slot.",
|
|
191
|
-
"text": "The WebGPU joint-palette ring was capped at 128 palettes a frame and declined every skinned draw past it, and a rig drawn in five passes (the main pass, a reflection and three shadow layers) uploaded the same matrices five times, so three dozen idling extras beside two
|
|
242
|
+
"text": "The WebGPU joint-palette ring was capped at 128 palettes a frame and declined every skinned draw past it, and a rig drawn in five passes (the main pass, a reflection and three shadow layers) uploaded the same matrices five times, so three dozen idling extras beside two characters filled it in one frame and the consumer stood its crowd in the bind pose. The ring holds as many palettes as a frame holds draws, `RenderQuality.drawsPerFrame`, and a palette set again with the same numbers is the slot it already has, compared by value, since a caller may pose every rig into one scratch array. A slot still allocates its texture only when a frame first reaches it. WebGL2 never had the limit."
|
|
192
243
|
},
|
|
193
244
|
{
|
|
194
245
|
"kind": "fixed",
|
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# @driftengine/core
|
|
2
2
|
|
|
3
|
-
The runtime of DriftEngine, a
|
|
4
|
-
WebGPU where the browser has a device and through
|
|
5
|
-
|
|
6
|
-
and the whole of
|
|
7
|
-
|
|
3
|
+
The runtime of [DriftEngine](https://github.com/drftrun/driftengine), a 3D game engine for the web
|
|
4
|
+
and for installed applications. It draws through WebGPU where the browser has a device and through
|
|
5
|
+
WebGL2 everywhere else, behind one API, and carries the fixed-step loop, the renderer, cameras,
|
|
6
|
+
input, the scene graph, geometry, text and the whole of `@driftengine/physics`, which it re-exports.
|
|
7
|
+
Everything else is a separate package a game adds when it reaches for it.
|
|
8
8
|
|
|
9
|
-
**Cost:
|
|
9
|
+
**Cost: 873.3 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`,
|
|
10
10
|
which fails the build if the figure drifts by more than 3%.
|
|
11
11
|
|
|
12
12
|
The manual is at **[driftengine.dev/docs](https://driftengine.dev/docs)**, with an example running
|
|
13
|
-
beside every chapter, and
|
|
14
|
-
[
|
|
13
|
+
beside every chapter, and every export is in the
|
|
14
|
+
[reference](https://driftengine.dev/docs/api).
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
@@ -19,7 +19,8 @@ beside every chapter, and the reference for every export at
|
|
|
19
19
|
npm install @driftengine/core
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
It
|
|
22
|
+
It resolves to compiled JavaScript, so any ES module bundler takes it as it is; Vite is what the
|
|
23
|
+
examples use. A browser needs WebGL2 at least, which every current one has.
|
|
23
24
|
[Installation](https://driftengine.dev/docs/start/installation) covers the page, TypeScript and the
|
|
24
25
|
backends.
|
|
25
26
|
|
|
@@ -170,42 +171,46 @@ This is the engine's `examples/starter`, and a test keeps the two the same.
|
|
|
170
171
|
|
|
171
172
|
## What is in it
|
|
172
173
|
|
|
173
|
-
|
|
|
174
|
-
|
|
|
175
|
-
| The frame
|
|
176
|
-
| Drawing
|
|
177
|
-
| Light at scale
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
174
|
+
| | |
|
|
175
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
176
|
+
| The frame | A fixed-step [loop](https://driftengine.dev/docs/start/the-loop) with interpolation, [two backends](https://driftengine.dev/docs/concepts/backends) behind one API, construction-time [quality profiles](https://driftengine.dev/docs/concepts/quality), and a [frame](https://driftengine.dev/docs/concepts/the-frame) whose order you can read. |
|
|
177
|
+
| Drawing | [Meshes](https://driftengine.dev/docs/rendering/meshes), [materials](https://driftengine.dev/docs/rendering/materials), [instancing](https://driftengine.dev/docs/rendering/instancing), [lights](https://driftengine.dev/docs/rendering/lights) and [shadows](https://driftengine.dev/docs/rendering/shadows), [the sky](https://driftengine.dev/docs/rendering/sky-and-atmosphere), [fog and weather](https://driftengine.dev/docs/rendering/fog-and-weather), [water](https://driftengine.dev/docs/rendering/water), [reflections](https://driftengine.dev/docs/rendering/reflections), [post-processing](https://driftengine.dev/docs/rendering/post-processing), [particles](https://driftengine.dev/docs/rendering/particles), [text](https://driftengine.dev/docs/rendering/text-and-overlays) and [passes of your own](https://driftengine.dev/docs/rendering/custom-passes). |
|
|
178
|
+
| Light at scale | [DriftLight](https://driftengine.dev/docs/rendering/driftlight) for thousands of fixed lights, [DriftRay](https://driftengine.dev/docs/rendering/driftray) for bounced light traced every frame, [DriftTR](https://driftengine.dev/docs/rendering/drifttr) for temporal reconstruction, and the [GPU-driven pipeline](https://driftengine.dev/docs/rendering/gpu-driven). |
|
|
179
|
+
| Many things | Instanced batches that cull, crowds animated from bone textures in the vertex stage (`createBoneAnimation`), and static draws recorded once and replayed every frame (`createStaticDraws`). |
|
|
180
|
+
| Textures | Images and compressed blocks through one call: BC for desktops, ETC2, EAC and ASTC for phones, each uploaded as it is where `compressedFormats` says the device samples it. `updateSurfaceTexture` can replace an image with blocks behind the handle a draw already holds. |
|
|
181
|
+
| Worlds | [Worlds bigger than a float](https://driftengine.dev/docs/worlds/large-worlds), streamed in cells, with [hierarchical detail](https://driftengine.dev/docs/worlds/hierarchical-detail) and [worlds built from a kit](https://driftengine.dev/docs/worlds/worlds-from-a-kit). |
|
|
182
|
+
| Physics | Deterministic [rigid bodies](https://driftengine.dev/docs/simulation/rigid-bodies), [joints](https://driftengine.dev/docs/simulation/joints), [queries](https://driftengine.dev/docs/simulation/queries), [characters](https://driftengine.dev/docs/simulation/characters), [vehicles](https://driftengine.dev/docs/simulation/vehicles), and [ragdolls and cloth](https://driftengine.dev/docs/simulation/ragdolls-and-cloth). |
|
|
183
|
+
| Play | [Input](https://driftengine.dev/docs/interface/input) from every device through one action map, [cameras that cut](https://driftengine.dev/docs/interface/cameras), [picking](https://driftengine.dev/docs/rendering/picking) and the [scene graph](https://driftengine.dev/docs/concepts/scene-graph). |
|
|
184
|
+
| Around the game | Saves and preferences through a store the game supplies, a host seam for windows, displays and files, and the visual-gate scripts — `@driftengine/core/scripts/browser.mjs` and its siblings — that photograph a page on the hardware GPU. |
|
|
181
185
|
|
|
182
186
|
## The other packages
|
|
183
187
|
|
|
184
188
|
Each is added when a game reaches for it, and a game that never imports one carries none of it.
|
|
185
189
|
|
|
186
|
-
| Package | What it adds
|
|
187
|
-
| ----------------------------------------------------- |
|
|
188
|
-
| `@driftengine/animation` | Skeletons, clips, blend trees and IK. [Guide](https://driftengine.dev/docs/simulation/animation)
|
|
189
|
-
| `@driftengine/nav` | Navigation meshes and routes. [Guide](https://driftengine.dev/docs/simulation/navigation)
|
|
190
|
-
| `@driftengine/entities` |
|
|
191
|
-
| `@driftengine/chemistry` | Heat, burning, phase changes and smoke. [Guide](https://driftengine.dev/docs/simulation/chemistry)
|
|
192
|
-
| `@driftengine/ai` | Behaviour trees and agents. [Guide](https://driftengine.dev/docs/simulation/agents)
|
|
193
|
-
| `@driftengine/audio` | A mix of buses, placed sounds, rooms and rhythm. [Guide](https://driftengine.dev/docs/content/audio)
|
|
194
|
-
| `@driftengine/assets` and `@driftengine/drft` | Model importers and the streaming `.drft` container. [Guide](https://driftengine.dev/docs/content/importing-models)
|
|
195
|
-
| `@driftengine/terrain` | Terrain drawn as a clipmap. [Guide](https://driftengine.dev/docs/worlds/terrain)
|
|
196
|
-
| `@driftengine/splats` | Gaussian splat captures among meshes. [Guide](https://driftengine.dev/docs/worlds/splats)
|
|
197
|
-
| `@driftengine/texture` | Materials as programs decoded on the device. [Guide](https://driftengine.dev/docs/worlds/drifttexture)
|
|
198
|
-
| `@driftengine/capture` | A video clip turned into a surface, a collider and entities. [Guide](https://driftengine.dev/docs/worlds/driftcapture)
|
|
199
|
-
| `@driftengine/ui2d` | Sprites, tilemaps and interfaces. [Guide](https://driftengine.dev/docs/interface/sprites)
|
|
200
|
-
| `@driftengine/script` | DriftScript, the engine's scripting language, with hot reload. [Reference](https://driftengine.dev/docs/api/script)
|
|
201
|
-
| `@driftengine/network` |
|
|
202
|
-
| `@driftengine/xr` | WebXR sessions. [Reference](https://driftengine.dev/docs/api/xr)
|
|
203
|
-
| `@driftengine/media` | Recording and exporting clips. [Reference](https://driftengine.dev/docs/api/media)
|
|
204
|
-
| `@driftengine/editor` and `@driftengine/tools` | The editor and in-game tools. [Reference](https://driftengine.dev/docs/api/editor)
|
|
205
|
-
| `@driftengine/package` and `@driftengine/native-host` | Installable apps built from the same game. [Reference](https://driftengine.dev/docs/api/package)
|
|
190
|
+
| Package | What it adds |
|
|
191
|
+
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
192
|
+
| `@driftengine/animation` | Skeletons, clips, blend trees and IK. [Guide](https://driftengine.dev/docs/simulation/animation) |
|
|
193
|
+
| `@driftengine/nav` | Navigation meshes and routes. [Guide](https://driftengine.dev/docs/simulation/navigation) |
|
|
194
|
+
| `@driftengine/entities` | Entities, components, systems and prefabs, with rewind. [Guide](https://driftengine.dev/docs/simulation/entities) |
|
|
195
|
+
| `@driftengine/chemistry` | Heat, burning, phase changes and smoke. [Guide](https://driftengine.dev/docs/simulation/chemistry) |
|
|
196
|
+
| `@driftengine/ai` | Behaviour trees and agents. [Guide](https://driftengine.dev/docs/simulation/agents) |
|
|
197
|
+
| `@driftengine/audio` | A mix of buses, placed sounds, rooms and rhythm. [Guide](https://driftengine.dev/docs/content/audio) |
|
|
198
|
+
| `@driftengine/assets` and `@driftengine/drft` | Model importers, KTX2, and the streaming `.drft` container. [Guide](https://driftengine.dev/docs/content/importing-models) |
|
|
199
|
+
| `@driftengine/terrain` | Terrain drawn as a clipmap. [Guide](https://driftengine.dev/docs/worlds/terrain) |
|
|
200
|
+
| `@driftengine/splats` | Gaussian splat captures among meshes. [Guide](https://driftengine.dev/docs/worlds/splats) |
|
|
201
|
+
| `@driftengine/texture` | Materials as programs decoded on the device. [Guide](https://driftengine.dev/docs/worlds/drifttexture) |
|
|
202
|
+
| `@driftengine/capture` | A video clip turned into a surface, a collider and entities. [Guide](https://driftengine.dev/docs/worlds/driftcapture) |
|
|
203
|
+
| `@driftengine/ui2d` | Sprites, tilemaps and interfaces. [Guide](https://driftengine.dev/docs/interface/sprites) |
|
|
204
|
+
| `@driftengine/script` | DriftScript, the engine's scripting language, with hot reload. [Reference](https://driftengine.dev/docs/api/script) |
|
|
205
|
+
| `@driftengine/network` | Lockstep and rollback netcode. [Reference](https://driftengine.dev/docs/api/network) |
|
|
206
|
+
| `@driftengine/xr` | WebXR sessions. [Reference](https://driftengine.dev/docs/api/xr) |
|
|
207
|
+
| `@driftengine/media` | Recording and exporting clips. [Reference](https://driftengine.dev/docs/api/media) |
|
|
208
|
+
| `@driftengine/editor` and `@driftengine/tools` | The editor and in-game tools. [Reference](https://driftengine.dev/docs/api/editor) |
|
|
209
|
+
| `@driftengine/package` and `@driftengine/native-host` | Installable apps built from the same game. [Reference](https://driftengine.dev/docs/api/package) |
|
|
206
210
|
|
|
207
211
|
## Licence
|
|
208
212
|
|
|
209
|
-
Apache-2.0.
|
|
213
|
+
Apache-2.0. Distributing it means reproducing its `NOTICE`, which every bundle of it already
|
|
214
|
+
carries. Part of [DriftEngine](https://github.com/drftrun/driftengine); see
|
|
210
215
|
[`docs/ARCHITECTURE.md`](https://github.com/drftrun/driftengine/blob/main/docs/ARCHITECTURE.md) for
|
|
211
216
|
how the packages divide.
|
package/dist/index.d.ts
CHANGED
|
@@ -175,6 +175,8 @@ export type { SkinnedClothSolver } from './render/skinnedClothRun.ts';
|
|
|
175
175
|
export type { BudgetLine, FrameBudget } from './render/backend/budget.ts';
|
|
176
176
|
export type { FrameTimer } from './render/backend/timer.ts';
|
|
177
177
|
export type { SceneCasterMaterial, SceneCasters, ShadowCasterCloth, ShadowCasterSink, ShadowCasters, } from './render/shadowCasters.ts';
|
|
178
|
+
export type { StaticDrawsHandle } from './render/staticDraws.ts';
|
|
179
|
+
export type { BoneAnimationClip, BoneAnimationHandle } from './render/boneAnimation.ts';
|
|
178
180
|
export type { Atmosphere, UnderwaterAtmosphere } from './render/atmosphere.ts';
|
|
179
181
|
export { createImageTexelDecoder } from './render/imageTexels.ts';
|
|
180
182
|
export type { ImageTexelDecoder, ImageTexels } from './render/imageTexels.ts';
|
|
@@ -226,7 +228,7 @@ export { MAX_POINT_LIGHTS, SURFACE_TEXTURE_UNIT } from './render/lightBudget.ts'
|
|
|
226
228
|
export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.ts';
|
|
227
229
|
export { SurfaceTexture } from './render/surfaceTexture.ts';
|
|
228
230
|
export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
|
|
229
|
-
export { compressedFormatName, isCompressedSource, uploadsCompressed, } from './render/compressedSource.ts';
|
|
231
|
+
export { compressedFormatName, isCompressedSource, levelBytes, uploadsCompressed, } from './render/compressedSource.ts';
|
|
230
232
|
export type { BlockFormat, CompressedTextureFormat, CompressedTextureSource, SurfaceSource, } from './render/compressedSource.ts';
|
|
231
233
|
export { DEFAULT_GOVERNOR_LIMITS, ResolutionGovernor } from './render/resolutionGovernor.ts';
|
|
232
234
|
export type { GovernorLimits } from './render/resolutionGovernor.ts';
|
package/dist/index.js
CHANGED
|
@@ -288,10 +288,11 @@ export { MAX_POINT_LIGHTS, SURFACE_TEXTURE_UNIT } from './render/lightBudget.js'
|
|
|
288
288
|
export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.js';
|
|
289
289
|
export { SurfaceTexture } from './render/surfaceTexture.js';
|
|
290
290
|
/*
|
|
291
|
-
* BC
|
|
292
|
-
* ask before handing one over — whether this device takes its format
|
|
291
|
+
* Compressed textures — BC, ETC2 and EAC, ASTC: the source `createSurfaceTexture` takes as blocks,
|
|
292
|
+
* the one question to ask before handing one over — whether this device takes its format
|
|
293
|
+
* (`renderer.compressedFormats`) — and the bytes a level of it holds, for a reader checking a file.
|
|
293
294
|
*/
|
|
294
|
-
export { compressedFormatName, isCompressedSource, uploadsCompressed, } from './render/compressedSource.js';
|
|
295
|
+
export { compressedFormatName, isCompressedSource, levelBytes, uploadsCompressed, } from './render/compressedSource.js';
|
|
295
296
|
export { DEFAULT_GOVERNOR_LIMITS, ResolutionGovernor } from './render/resolutionGovernor.js';
|
|
296
297
|
export { isWeakGpuFamily } from './render/gpuCapability.js';
|
|
297
298
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* **Why a draw needs its own.** The engine's diffuse ambient is one answer for a whole frame — the
|
|
6
6
|
* sky-and-ground gradient, or the probe grid's irradiance — so everything standing in one place is
|
|
7
|
-
* lit alike. A stage with a baked volume of indirect light (
|
|
7
|
+
* lit alike. A stage with a baked volume of indirect light (a volumetric lightmap) lights a
|
|
8
8
|
* character by sampling that volume where the character stands; a consumer that samples it each
|
|
9
9
|
* frame hands the result here, around that character's draws, and the character takes the light of
|
|
10
10
|
* the spot it is in rather than the frame's average.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* **Why a draw needs its own.** The engine's diffuse ambient is one answer for a whole frame — the
|
|
6
6
|
* sky-and-ground gradient, or the probe grid's irradiance — so everything standing in one place is
|
|
7
|
-
* lit alike. A stage with a baked volume of indirect light (
|
|
7
|
+
* lit alike. A stage with a baked volume of indirect light (a volumetric lightmap) lights a
|
|
8
8
|
* character by sampling that volume where the character stands; a consumer that samples it each
|
|
9
9
|
* frame hands the result here, around that character's draws, and the character takes the light of
|
|
10
10
|
* the spot it is in rather than the frame's average.
|
|
@@ -92,14 +92,14 @@ export interface AreaLightSource {
|
|
|
92
92
|
* others. **Read only where the rectangle is shaded through the froxel table** — the ones past
|
|
93
93
|
* `maxAreaLights` with `clusteredLights` on; the fixed four light everything they face, as they
|
|
94
94
|
* always have. Absent, it is where the rectangle's irradiance on its axis falls to a thousandth
|
|
95
|
-
* of a scene unit, and never inside the rectangle itself.
|
|
95
|
+
* of a scene unit, and never inside the rectangle itself. An attenuation radius, in metres.
|
|
96
96
|
*/
|
|
97
97
|
range?: number;
|
|
98
98
|
/**
|
|
99
99
|
* Barn doors: a flap hinged at each of the rectangle's four edges, standing this many degrees from
|
|
100
100
|
* its normal. 90, or absent, folds them flat and they hide nothing; smaller closes them, so the
|
|
101
101
|
* light narrows to the opening they leave — a rectangle at 50° throws a beam rather than filling
|
|
102
|
-
* its hemisphere.
|
|
102
|
+
* its hemisphere. 88° is the usual default, nearly flat.
|
|
103
103
|
*
|
|
104
104
|
* What a fragment sees past the doors is the part of the rectangle no door's tip hides, one axis
|
|
105
105
|
* at a time, and the rectangle then shades as that smaller rectangle, its highlight included.
|
|
@@ -109,7 +109,7 @@ export interface AreaLightSource {
|
|
|
109
109
|
* rectangle's back face has none.
|
|
110
110
|
*/
|
|
111
111
|
barnDoorAngle?: number;
|
|
112
|
-
/** The barn doors' length in metres. Absent, 0.2:
|
|
112
|
+
/** The barn doors' length in metres. Absent, 0.2: the usual default of 20 cm. */
|
|
113
113
|
barnDoorLength?: number;
|
|
114
114
|
}
|
|
115
115
|
/** The irradiance a derived `range` ends at, in scene units. See `AreaLightSource.range`. */
|
|
@@ -132,7 +132,7 @@ export interface AreaLightBuffer {
|
|
|
132
132
|
readonly barnDoors?: Float32Array;
|
|
133
133
|
}
|
|
134
134
|
export declare function createAreaLightBuffer(capacity?: number): AreaLightBuffer;
|
|
135
|
-
/**
|
|
135
|
+
/** The usual default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
|
|
136
136
|
export declare const DEFAULT_BARN_DOOR_LENGTH = 0.2;
|
|
137
137
|
/**
|
|
138
138
|
* A rectangle's barn doors as the shader reads them, into `out[at]` and `out[at + 1]`: the cosine of
|
|
@@ -23,7 +23,7 @@ export function createAreaLightBuffer(capacity = MAX_AREA_LIGHTS) {
|
|
|
23
23
|
barnDoors: new Float32Array(capacity * 2),
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
26
|
+
/** The usual default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
|
|
27
27
|
export const DEFAULT_BARN_DOOR_LENGTH = 0.2;
|
|
28
28
|
/**
|
|
29
29
|
* A rectangle's barn doors as the shader reads them, into `out[at]` and `out[at + 1]`: the cosine of
|
|
@@ -8,7 +8,7 @@ import type { Renderer, TranslucentMeshOptions } from './webgl2/renderer.ts';
|
|
|
8
8
|
import type { InstancedOptions, MeshInstances } from '../instances.ts';
|
|
9
9
|
import type { SurfaceMaterial } from '../surfaceTexture.ts';
|
|
10
10
|
import type { SurfaceOverlay } from '../surfaceOverlay.ts';
|
|
11
|
-
import type { SurfaceSource } from '../compressedSource.ts';
|
|
11
|
+
import type { CompressedTextureSource, SurfaceSource } from '../compressedSource.ts';
|
|
12
12
|
import type { LightmapPage } from '../lightmap.ts';
|
|
13
13
|
import type { DeviceParticles } from '../particlePool.ts';
|
|
14
14
|
import type { ClothBindingData } from '../clothBindingData.ts';
|
|
@@ -270,12 +270,19 @@ export type RendererApi = Omit<{
|
|
|
270
270
|
*/
|
|
271
271
|
captureScene(capture: SurfaceTextureHandle, camera: Camera, clearColor: Vec3, draw: (camera: Camera) => void): boolean;
|
|
272
272
|
/**
|
|
273
|
-
* Replace a texture's pixels, keeping the
|
|
273
|
+
* Replace a texture's pixels, keeping the handle and its sampler state.
|
|
274
274
|
*
|
|
275
275
|
* The binding a draw loop already holds stays valid, so the swap is a swap rather than a
|
|
276
276
|
* rebuild. Not a hot path: it re-uploads the whole image and rebuilds the mip chain.
|
|
277
|
+
*
|
|
278
|
+
* **Blocks may replace an image**, at the format's own size and with the chain they carry: the
|
|
279
|
+
* same picture at a fraction of the memory, which is how a loader that first showed a decoded
|
|
280
|
+
* image hands the device the blocks it encoded afterwards. Read in the colour space the texture
|
|
281
|
+
* was made with, and refused as `createSurfaceTexture` refuses them where the device does not take
|
|
282
|
+
* that format (`compressedFormats`). **The way back is refused**: a texture holding blocks takes
|
|
283
|
+
* no image, since nothing could say what its chain should become.
|
|
277
284
|
*/
|
|
278
|
-
updateSurfaceTexture(texture: SurfaceTextureHandle, source: TexImageSource): void;
|
|
285
|
+
updateSurfaceTexture(texture: SurfaceTextureHandle, source: TexImageSource | CompressedTextureSource): void;
|
|
279
286
|
disposeSurfaceTexture(texture: SurfaceTextureHandle): void;
|
|
280
287
|
/**
|
|
281
288
|
* Choose the texture the following `drawMesh` calls sample, or null for none.
|
|
@@ -207,12 +207,18 @@ async function acquireWebGpu(gpu, wantsGpuTiming, shaders, stage) {
|
|
|
207
207
|
features.push('timestamp-query');
|
|
208
208
|
}
|
|
209
209
|
/*
|
|
210
|
-
*
|
|
211
|
-
* a device without
|
|
212
|
-
* texture at a byte a texel instead of four.
|
|
210
|
+
* The three block families, on the same terms: each asked for wherever the adapter offers it,
|
|
211
|
+
* because a device without one still draws — the loader decodes, or encodes ETC2, for it — and
|
|
212
|
+
* one with it samples a texture at a byte a texel or half of one instead of four. Desktops
|
|
213
|
+
* offer BC; phones ETC2 and ASTC. See `compressedUpload.ts`.
|
|
213
214
|
*/
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
for (const family of [
|
|
216
|
+
'texture-compression-bc',
|
|
217
|
+
'texture-compression-etc2',
|
|
218
|
+
'texture-compression-astc',
|
|
219
|
+
]) {
|
|
220
|
+
if (adapter.features?.has(family) === true)
|
|
221
|
+
features.push(family);
|
|
216
222
|
}
|
|
217
223
|
const descriptor = {};
|
|
218
224
|
if (Object.keys(required).length > 0)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bone animation's textures on this backend, and a batch's clocks: what
|
|
3
|
+
* `shaders/boneAnimation.ts` reads with `texelFetch`, made once and never filtered. The twin of
|
|
4
|
+
* `webgpu/boneAnimations.ts`, at the same formats for the same reason: a turn in half floats jitters
|
|
5
|
+
* a crowd's hands at the frame's rate.
|
|
6
|
+
*/
|
|
7
|
+
import { type BoneAnimationHandle, type BoneAnimationTexels } from '../../boneAnimation.ts';
|
|
8
|
+
/** A clip on the device, from `createBoneAnimation`. */
|
|
9
|
+
export declare class GlBoneAnimation implements BoneAnimationHandle {
|
|
10
|
+
readonly bones: number;
|
|
11
|
+
readonly frames: number;
|
|
12
|
+
readonly framesPerSecond: number;
|
|
13
|
+
readonly boneScale: number;
|
|
14
|
+
readonly places: WebGLTexture;
|
|
15
|
+
readonly turns: WebGLTexture;
|
|
16
|
+
disposed: boolean;
|
|
17
|
+
constructor(gl: WebGL2RenderingContext, texels: BoneAnimationTexels);
|
|
18
|
+
dispose(gl: WebGL2RenderingContext): void;
|
|
19
|
+
}
|
|
20
|
+
/** A batch's clocks: two floats an instance, wrapped onto rows `CLOCK_TEXTURE_WIDTH` wide. */
|
|
21
|
+
export declare class GlInstanceClocks {
|
|
22
|
+
readonly texture: WebGLTexture;
|
|
23
|
+
readonly staging: Float32Array;
|
|
24
|
+
private readonly width;
|
|
25
|
+
constructor(gl: WebGL2RenderingContext, capacity: number);
|
|
26
|
+
/** The rows that hold the first `count` instances, from `staging`. */
|
|
27
|
+
upload(gl: WebGL2RenderingContext, count: number): void;
|
|
28
|
+
dispose(gl: WebGL2RenderingContext): void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bone animation's textures on this backend, and a batch's clocks: what
|
|
3
|
+
* `shaders/boneAnimation.ts` reads with `texelFetch`, made once and never filtered. The twin of
|
|
4
|
+
* `webgpu/boneAnimations.ts`, at the same formats for the same reason: a turn in half floats jitters
|
|
5
|
+
* a crowd's hands at the frame's rate.
|
|
6
|
+
*/
|
|
7
|
+
import { CLOCK_TEXTURE_WIDTH, } from '../../boneAnimation.js';
|
|
8
|
+
/**
|
|
9
|
+
* A float texture read by index: one level, nearest, clamped. Bound on unit 0, chosen rather than
|
|
10
|
+
* inherited, for the reason `oitPass.ts` gives: `bindTexture` binds to whatever unit is active, and
|
|
11
|
+
* a frame leaves that wherever its last pass finished.
|
|
12
|
+
*/
|
|
13
|
+
function floatTexture(gl, internal, width, height) {
|
|
14
|
+
const texture = gl.createTexture();
|
|
15
|
+
if (texture === null)
|
|
16
|
+
throw new Error('boneAnimations: createTexture failed');
|
|
17
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
18
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
19
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, internal, width, height);
|
|
20
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
21
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
22
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
23
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
24
|
+
return texture;
|
|
25
|
+
}
|
|
26
|
+
/** A clip on the device, from `createBoneAnimation`. */
|
|
27
|
+
export class GlBoneAnimation {
|
|
28
|
+
bones;
|
|
29
|
+
frames;
|
|
30
|
+
framesPerSecond;
|
|
31
|
+
boneScale;
|
|
32
|
+
places;
|
|
33
|
+
turns;
|
|
34
|
+
disposed = false;
|
|
35
|
+
constructor(gl, texels) {
|
|
36
|
+
this.bones = texels.bones;
|
|
37
|
+
this.frames = texels.frames;
|
|
38
|
+
this.framesPerSecond = texels.framesPerSecond;
|
|
39
|
+
this.boneScale = texels.boneScale;
|
|
40
|
+
const make = (data) => {
|
|
41
|
+
const texture = floatTexture(gl, gl.RGBA32F, texels.bones, texels.frames);
|
|
42
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, texels.bones, texels.frames, gl.RGBA, gl.FLOAT, data);
|
|
43
|
+
return texture;
|
|
44
|
+
};
|
|
45
|
+
this.places = make(texels.places);
|
|
46
|
+
this.turns = make(texels.turns);
|
|
47
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
48
|
+
}
|
|
49
|
+
dispose(gl) {
|
|
50
|
+
this.disposed = true;
|
|
51
|
+
gl.deleteTexture(this.places);
|
|
52
|
+
gl.deleteTexture(this.turns);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** A batch's clocks: two floats an instance, wrapped onto rows `CLOCK_TEXTURE_WIDTH` wide. */
|
|
56
|
+
export class GlInstanceClocks {
|
|
57
|
+
texture;
|
|
58
|
+
staging;
|
|
59
|
+
width;
|
|
60
|
+
constructor(gl, capacity) {
|
|
61
|
+
this.width = Math.max(1, Math.min(capacity, CLOCK_TEXTURE_WIDTH));
|
|
62
|
+
const rows = Math.max(1, Math.ceil(capacity / this.width));
|
|
63
|
+
this.staging = new Float32Array(this.width * rows * 2);
|
|
64
|
+
this.texture = floatTexture(gl, gl.RG32F, this.width, rows);
|
|
65
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
66
|
+
}
|
|
67
|
+
/** The rows that hold the first `count` instances, from `staging`. */
|
|
68
|
+
upload(gl, count) {
|
|
69
|
+
if (count <= 0)
|
|
70
|
+
return;
|
|
71
|
+
const rows = Math.ceil(count / this.width);
|
|
72
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
73
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
74
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, rows, gl.RG, gl.FLOAT, this.staging);
|
|
75
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
76
|
+
}
|
|
77
|
+
dispose(gl) {
|
|
78
|
+
gl.deleteTexture(this.texture);
|
|
79
|
+
}
|
|
80
|
+
}
|