@driftengine/core 4.12.0 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +66 -0
- package/README.md +18 -9
- package/dist/geometry/meshBuilder.d.ts +10 -0
- package/dist/geometry/meshBuilder.js +11 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/math/color.d.ts +25 -0
- package/dist/math/color.js +37 -0
- package/dist/render/backend/webgl2/renderer.d.ts +17 -4
- package/dist/render/backend/webgl2/renderer.js +82 -47
- package/dist/render/backend/webgl2/skinScatter.d.ts +3 -1
- package/dist/render/backend/webgl2/skinScatter.js +5 -1
- package/dist/render/backend/webgpu/boltPass.d.ts +1 -1
- package/dist/render/backend/webgpu/causticsPass.d.ts +1 -1
- package/dist/render/backend/webgpu/filmPass.d.ts +1 -1
- package/dist/render/backend/webgpu/flockPass.d.ts +1 -1
- package/dist/render/backend/webgpu/giFieldPass.d.ts +3 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +10 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +28 -11
- package/dist/render/backend/webgpu/panelPass.d.ts +1 -1
- package/dist/render/backend/webgpu/postPass.d.ts +2 -2
- package/dist/render/backend/webgpu/renderer.d.ts +19 -8
- package/dist/render/backend/webgpu/renderer.js +94 -38
- package/dist/render/backend/webgpu/scatterPass.d.ts +1 -1
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +5 -1
- package/dist/render/backend/webgpu/skinScatterPass.js +9 -3
- package/dist/render/backend/webgpu/skyPass.d.ts +11 -1
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/backend/webgpu/windStreakPass.d.ts +1 -1
- package/dist/render/causticsRenderer.d.ts +3 -1
- package/dist/render/causticsRenderer.js +5 -1
- package/dist/render/decalPass.d.ts +3 -1
- package/dist/render/decalPass.js +4 -1
- package/dist/render/flockRenderer.d.ts +1 -1
- package/dist/render/flockRenderer.js +4 -1
- package/dist/render/globalMediumPass.d.ts +3 -1
- package/dist/render/globalMediumPass.js +5 -1
- package/dist/render/gradeColor.d.ts +21 -0
- package/dist/render/gradeColor.js +64 -0
- package/dist/render/lightMatrix.d.ts +13 -0
- package/dist/render/lightMatrix.js +17 -0
- package/dist/render/passGrade.d.ts +13 -0
- package/dist/render/passGrade.js +47 -0
- package/dist/render/plumeRenderer.d.ts +1 -1
- package/dist/render/plumeRenderer.js +4 -1
- package/dist/render/renderQuality.d.ts +57 -18
- package/dist/render/renderQuality.js +3 -3
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -0
- package/dist/render/sdfTextRenderer.d.ts +1 -1
- package/dist/render/sdfTextRenderer.js +1 -1
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/arcane.js +5 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +4 -1
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/caustics.js +4 -1
- package/dist/render/shaders/decalProject.js +12 -2
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/film.js +22 -9
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/fire.js +4 -1
- package/dist/render/shaders/flat/layerLooks.js +11 -19
- package/dist/render/shaders/flat/layered.js +33 -18
- package/dist/render/shaders/flock.d.ts +1 -1
- package/dist/render/shaders/flock.js +4 -1
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +11 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +11 -1
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/bolt.wgsl.js +12 -2
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/caustics.wgsl.js +12 -2
- package/dist/render/shaders/generated/decalProject.wgsl.d.ts +6 -1
- package/dist/render/shaders/generated/decalProject.wgsl.js +6 -1
- package/dist/render/shaders/generated/film.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/film.wgsl.js +12 -2
- package/dist/render/shaders/generated/fire.wgsl.d.ts +11 -1
- package/dist/render/shaders/generated/fire.wgsl.js +11 -1
- package/dist/render/shaders/generated/flat.wgsl.js +23 -23
- package/dist/render/shaders/generated/flock.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/flock.wgsl.js +12 -2
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/globalMedium.wgsl.js +12 -2
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +36 -16
- package/dist/render/shaders/generated/lightVolume.wgsl.js +39 -19
- package/dist/render/shaders/generated/panel.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/panel.wgsl.js +12 -2
- package/dist/render/shaders/generated/rush.wgsl.d.ts +31 -26
- package/dist/render/shaders/generated/rush.wgsl.js +31 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/scatter.wgsl.js +12 -2
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +11 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.js +11 -1
- package/dist/render/shaders/generated/sky.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/sky.wgsl.js +12 -2
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +11 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +11 -1
- package/dist/render/shaders/generated/text.wgsl.d.ts +11 -1
- package/dist/render/shaders/generated/text.wgsl.js +11 -1
- package/dist/render/shaders/generated/water.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/water.wgsl.js +12 -2
- package/dist/render/shaders/generated/windStreaks.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/windStreaks.wgsl.js +12 -2
- package/dist/render/shaders/globalMedium.js +14 -1
- package/dist/render/shaders/gpudriven/present.wgsl.d.ts +15 -6
- package/dist/render/shaders/gpudriven/present.wgsl.js +71 -8
- package/dist/render/shaders/lightVolume.js +4 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +8 -2
- package/dist/render/shaders/outputTransform.js +18 -11
- package/dist/render/shaders/panel.d.ts +1 -1
- package/dist/render/shaders/panel.js +5 -1
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/rush.js +8 -0
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/scatter.js +4 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/skinBlur.js +5 -1
- package/dist/render/shaders/sky.d.ts +1 -1
- package/dist/render/shaders/sky.js +5 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/smoke.js +4 -1
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +4 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -2
- package/dist/render/shaders/windStreaks.d.ts +1 -1
- package/dist/render/shaders/windStreaks.js +4 -1
- package/dist/render/surfaceLayerLooks.d.ts +16 -4
- package/dist/render/surfaceLayerLooks.js +33 -14
- package/dist/render/surfaceLayers.d.ts +2 -2
- package/dist/render/textRenderer.d.ts +3 -1
- package/dist/render/textRenderer.js +7 -1
- package/dist/render/vertexDefaults.d.ts +1 -1
- package/dist/render/vertexDefaults.js +1 -1
- package/dist/render/waterRenderer.d.ts +1 -1
- package/dist/render/waterRenderer.js +4 -1
- package/dist/render/windStreakRenderer.d.ts +3 -1
- package/dist/render/windStreakRenderer.js +5 -1
- package/package.json +6 -6
- package/src/geometry/meshBuilder.ts +21 -2
- package/src/index.ts +11 -2
- package/src/math/color.ts +39 -0
- package/src/render/backend/webgl2/renderer.ts +134 -44
- package/src/render/backend/webgl2/skinScatter.ts +5 -0
- package/src/render/backend/webgpu/giFieldPass.ts +3 -1
- package/src/render/backend/webgpu/gpuDrivenPass.ts +29 -13
- package/src/render/backend/webgpu/renderer.ts +104 -37
- package/src/render/backend/webgpu/skinScatterPass.ts +17 -1
- package/src/render/causticsRenderer.ts +5 -0
- package/src/render/decalPass.ts +3 -0
- package/src/render/flockRenderer.ts +5 -0
- package/src/render/globalMediumPass.ts +10 -1
- package/src/render/gradeColor.ts +90 -0
- package/src/render/lightMatrix.ts +32 -0
- package/src/render/passGrade.ts +58 -0
- package/src/render/plumeRenderer.ts +5 -0
- package/src/render/renderQuality.ts +60 -21
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/sdfTextRenderer.ts +1 -1
- package/src/render/shaders/arcane.ts +5 -1
- package/src/render/shaders/bolt.ts +4 -1
- package/src/render/shaders/caustics.ts +4 -1
- package/src/render/shaders/decalProject.ts +12 -2
- package/src/render/shaders/film.ts +22 -9
- package/src/render/shaders/fire.ts +4 -1
- package/src/render/shaders/flat/layerLooks.ts +11 -19
- package/src/render/shaders/flat/layered.ts +33 -18
- package/src/render/shaders/flock.ts +4 -1
- package/src/render/shaders/generated/arcane.wgsl.ts +11 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +12 -2
- package/src/render/shaders/generated/caustics.wgsl.ts +12 -2
- package/src/render/shaders/generated/decalProject.wgsl.ts +6 -1
- package/src/render/shaders/generated/film.wgsl.ts +12 -2
- package/src/render/shaders/generated/fire.wgsl.ts +11 -1
- package/src/render/shaders/generated/flat.wgsl.ts +23 -23
- package/src/render/shaders/generated/flock.wgsl.ts +12 -2
- package/src/render/shaders/generated/globalMedium.wgsl.ts +12 -2
- package/src/render/shaders/generated/lightVolume.wgsl.ts +39 -19
- package/src/render/shaders/generated/panel.wgsl.ts +12 -2
- package/src/render/shaders/generated/rush.wgsl.ts +31 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +12 -2
- package/src/render/shaders/generated/skinBlur.wgsl.ts +11 -1
- package/src/render/shaders/generated/sky.wgsl.ts +12 -2
- package/src/render/shaders/generated/smoke.wgsl.ts +11 -1
- package/src/render/shaders/generated/text.wgsl.ts +11 -1
- package/src/render/shaders/generated/water.wgsl.ts +12 -2
- package/src/render/shaders/generated/windStreaks.wgsl.ts +12 -2
- package/src/render/shaders/globalMedium.ts +14 -1
- package/src/render/shaders/gpudriven/present.wgsl.ts +72 -8
- package/src/render/shaders/lightVolume.ts +4 -1
- package/src/render/shaders/outputTransform.ts +19 -11
- package/src/render/shaders/panel.ts +5 -1
- package/src/render/shaders/rush.ts +8 -0
- package/src/render/shaders/scatter.ts +4 -1
- package/src/render/shaders/skinBlur.ts +5 -1
- package/src/render/shaders/sky.ts +5 -1
- package/src/render/shaders/smoke.ts +4 -1
- package/src/render/shaders/text.ts +4 -1
- package/src/render/shaders/water.ts +12 -2
- package/src/render/shaders/windStreaks.ts +4 -1
- package/src/render/surfaceLayerLooks.ts +49 -18
- package/src/render/surfaceLayers.ts +2 -2
- package/src/render/textRenderer.ts +6 -0
- package/src/render/vertexDefaults.ts +1 -1
- package/src/render/waterRenderer.ts +5 -0
- package/src/render/windStreakRenderer.ts +5 -0
package/CHANGELOG.json
CHANGED
|
@@ -1,4 +1,70 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "4.13.0",
|
|
4
|
+
"date": "2026-10-10",
|
|
5
|
+
"entries": [
|
|
6
|
+
{
|
|
7
|
+
"kind": "changed",
|
|
8
|
+
"short": "The frame is encoded for the screen by default, every pass and the clear colour alike: `outputTransform` is `'srgb'`, so a model reads as it does elsewhere.",
|
|
9
|
+
"text": "`outputTransform` defaults to `'srgb'`. The light the renderer computes is linear, and a texture's colours are decoded to linear when they are read, so a frame written without encoding them back, which `'none'`, the default until now, did, reads every midtone far too dark: a textured model drawn from the manual with no quality options came out darker than the same file in any other engine, and the cause looks like lighting, so that is where people looked. **Every pass now grades where nothing after it will, and the clear colour with them**, so a colour handed to the renderer means one thing wherever it lands: the sky, water, scatter, the plumes, flocks, wind streaks, bolts, caustics, light volumes and film grade themselves, as the lit pass, particles, lines and world text already did; the decal multiply, the global medium, the skin blur's add and the resolve's occlusion work in the terms of a pixel already graded; and the gpu-driven pipeline's blit grades instead of warning. Until now, without `hdrScene`, those passes drew their colours as given beside a graded world, so a sky never met the fog fading into it. `'srgb'` is the display encoding with no tone curve; `'aces'` with an `outputExposure` still rolls highlights off. A world graded under `'none'` says so and draws exactly as it did, bit for bit, and a frame with `hdrScene` is graded once at the resolve exactly as before. What changes for a world on another curve without `hdrScene` is that its sky and water are graded with it, and its interface is encoded for the screen, as the next entry says."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"kind": "added",
|
|
13
|
+
"short": "Colours picked by eye are stated as such: `srgbColor(r, g, b)`, `MeshBuilder.build({ colorSpace: 'srgb' })` and `decodeSrgbInPlace`, each decoding to the linear values the renderer draws.",
|
|
14
|
+
"text": "A colour picked by eye, on a screen or in a paint program, is a display value, and the renderer draws linear ones, so typed in as it looks it comes out too light. Three ways say which it is. `srgbColor(r, g, b)` decodes one colour through the sRGB curve, so a surface lit at 1, a sky or a clear colour comes out as the colour picked. `MeshBuildOptions.colorSpace: 'srgb'` decodes every vertex colour and emissive colour of a built mesh once, at build, as a texture uploaded as sRGB is decoded at upload, so a world built from hand-picked colours changes one line. `decodeSrgbInPlace` decodes an array of them, leaving a negative sentinel alone, for a mesh written by hand. `srgbToLinear` decodes one channel, for a colour that arrives a number at a time, as a packed hex token does. A light's intensity is an amount rather than a look and stays as it is. The examples state their colours this way, and the starter, which a new project begins from, shows the default."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"kind": "changed",
|
|
18
|
+
"short": "Text, panels and the 2D layer are encoded for the screen and never tone mapped, so an interface comes out as picked under any transform and at any exposure.",
|
|
19
|
+
"text": "Screen text, `fillPanel` and `@driftengine/ui2d`'s 2D layer take the screen encode and never the curve or the exposure: `interfaceGrade` says which code that is, for a contributed pass that draws an interface. A caption or a panel is not light, and its colour was picked for the screen, so under `'aces'` a menu's white text came out grey, its dark panel crushed to black, and the whole interface brightened and darkened as the scene's exposure eased. Now every interface colour stated through `srgbColor`, or as a theme's hex token, comes out exactly as picked under every transform. Inside an `hdrScene` frame an interface is part of the picture the resolve tone maps, as it was; drawn after `endFrame`, it is not. Text drawn in the world at a model matrix is part of the world and keeps the curve."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"kind": "fixed",
|
|
23
|
+
"short": "A pass drawn after `endFrame` grades itself under an `hdrScene` composite too, where it drew its colours as display values beside a graded frame.",
|
|
24
|
+
"text": "With `hdrScene` the resolve grades the frame and every pass before it draws linear light. A pass drawn after `endFrame` lands on the canvas the resolve has already written, so nothing after it grades it, and it used to draw ungraded there: an interface drawn after the present, which is where the manual puts one to keep it out of the screen-space chain, was the one surface on screen reading its colours as display values. The decision is now one function both backends read, and past the present it answers the frame's own grade, composite or not."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"kind": "changed",
|
|
28
|
+
"short": "A colour picked for the screen is decoded where an interface takes one: ui2d's hex tokens, and a script's `ui.tint` and `sprites.tinted`.",
|
|
29
|
+
"text": "`unpackRgba` and `themeRgba` decode a packed `0xRRGGBBAA` token from the sRGB a hex colour is written in to the linear light a node holds, keeping its alpha as coverage. Read as linear, `0x336699` came out as `0x7caacb` under the new default, every panel a washed-out version of the one picked. DriftScript's `ui.tint` and `sprites.tinted` take a colour picked for the screen in the same terms and decode it, since a script has no `srgbColor` to say so, so a white sprite draws exactly the tint named. A renderer asked for `outputTransform: 'none'` draws these darker than picked, as it does every colour."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"kind": "changed",
|
|
33
|
+
"short": "A splat capture's colour is a display value, decoded after its view-dependent band, so a trained capture draws as it does in the tool that made it.",
|
|
34
|
+
"text": "A Gaussian capture is trained against photographs, so its colour terms reproduce their pixels drawn as they are. `SplatSource.colors` was documented as linear and drawn so, which under the new `'srgb'` default is a washed-out cloud. The splat shader now decodes a splat's colour after adding its l=1 band, which training added in the same space, and the eight bits a colour is packed into are spent where the eye sees them. What it gives up is the blend: splats compose in linear light rather than in the display values training composed them in, which moves a translucent edge a little. `@driftengine/capture` fits in linear light against decoded frames and now hands its colours over encoded, its l=1 band carried through the encode's slope at each colour, so a fitted capture draws as fitted."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"kind": "changed",
|
|
38
|
+
"short": "Sun shadows no longer dissolve past six metres or below 18° of sun: `directionalShadowMaxDistance` defaults to 1,000 and `directionalShadowMaxSlope` to 60.",
|
|
39
|
+
"text": "`directionalShadowMaxDistance` defaults to 1,000 metres and `directionalShadowMaxSlope` to 60. The old 6 and 3 were a tuning for short casters: any sun shadow longer than six metres along the ground dissolved, so a building's walls and arcades cast nothing on its floor under a high sun and the light read as random, and every sun shadow faded below about 18° of elevation, which is an evening without shadows. A shadow now reaches as far as its map holds, whose edge already fades it, and holds until the sun is about a degree above the horizon. The reach is a shadow's own length along the ground and not a distance from the camera, which its name suggests, and both options now say what their default gives up and what would make it wrong. A scene whose low sun streaks lowers the slope; one tuned around the old values sets them. The medium's shafts and a surface's shadows, which disagreed past six metres, now agree."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"kind": "added",
|
|
43
|
+
"short": "`DrftLoader` draws, casts and prepares its model: `draw()`, `casters` and `prepare()` apply every rule a container needs, and `PartDraws` does it for a chosen list.",
|
|
44
|
+
"text": "`DrftLoader.draw()` draws this frame's parts into the open mesh pass by every rule a baked container needs, each of which fails as a picture rather than an error when it is missed: a part's whole material, so foliage keeps its alpha test and a one-sided card its faces; its copies through one instanced draw, where a part drawn as a plain mesh shows one arch of a repeated row; a blended part translucent whatever its opacity and writing no depth, since its alpha is in its texture, where drawn opaque a grime decal is a black patch; a blended batch through the translucent instanced path; glass as glass, its options kept per pane so a frame allocates nothing; and each part's own reflectivity, with nothing left set for the caller's draws after it. `loader.casters` is a ready `ShadowCasters`: every part but a decal, which would shadow its own surface, each with its material so a cutout casts its shape, glass as glass and copies as copies. `await loader.prepare()` compiles every pipeline the parts draw with, off the frame, translucent where they will be drawn so. `PartDraws` is the same for any list of parts, several loaders' or a filtered one. The manual's chapter on importing models and the models example draw through them."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"kind": "added",
|
|
48
|
+
"short": "`computeLightMatrixForBounds` covers a box with the sun's map whole, and `DrftLoader.shadowFit` fits the map to a model as placed, from the bounds its file declares.",
|
|
49
|
+
"text": "`computeLightMatrixForBounds(lightDir, min, max, mapSize, out)` places a directional light's map around a box of known size: the sphere around the box, at its centre, widened by the half texel the centre's snap may move it, so a corner on the sphere stays inside. A focus and a radius chosen by eye around a guessed centre left part of a model outside the map, where it cast nothing. `DrftLoader.shadowFit(lightDir, mapSize, out)` does it for the model as fitted and returns the depth span for `shadowDepthSpan`, or null until `HEAD` has said how large the model is. What both give up is resolution on a large box, whose texels are spread over all of it; a world that follows a player keeps `computeLightMatrix` around the viewer."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"kind": "added",
|
|
53
|
+
"short": "`meshOcclusion` darkens the colour and the ambient light at once, each at its own strength: `colorStrength` beside `into: 'ambient'`, past 1 where a material asks.",
|
|
54
|
+
"text": "`SurfaceMeshOcclusion.colorStrength` is the colour's own strength: the blended colour times `max(0, 1 − colorStrength × (1 − occlusion))`, which up to 1 is the `mix` a strength always gave and past it darkens faster than the occlusion does. Named beside `into: 'ambient'`, one occlusion darkens the ambient light at `strength` and the colour at `colorStrength`, which is how a material that takes its ambient occlusion from a map and darkens its colour by the same map, at up to twice that, is drawn; with a single target it drew one of the two. Absent, it is `strength` where the colour is the target and 0 where the ambient light is, so every material written for 4.12.0 draws as it did. The two floats that carried a strength and a target now carry the two strengths."
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"kind": "fixed",
|
|
58
|
+
"short": "A summed layer blend reflects the surface's own specular once where its layers name none, rather than its own times the sum of the layers' shares.",
|
|
59
|
+
"text": "Under `blend: 'sum'` a layer's share is its weight, and the shares add past 1 where the weights do, which is what a summed blend is for in colour. The specular took the same sum: a layer that named none gave the surface's own times its share, so a surface painted to weights adding to 2.5 or 3 reflected two and a half to three times its own specular, measured as a sandy floor 2.5% brighter across a frame than with its own. The layers that name no specular now give the surface's own at most once, whatever their shares add to, and named speculars still add as their colours do. A blend laid over, whose shares never pass 1, draws exactly as before, as does any layered material while no material has asked for looks."
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"kind": "fixed",
|
|
63
|
+
"short": "A reflection probe stays ungraded when its bake's callback binds the mesh pass, so the frame no longer grades what it reads from the probe a second time.",
|
|
64
|
+
"text": "A probe stores radiance, so its bake holds the output transform at 0 while it draws the faces. `bindMeshPass`, which every bake's callback calls first, set the frame's grade again, so under any forward grade each face was graded as it was drawn and the frame graded what it read back from the cube a second time: reflections and probe ambient lifted and flattened. Every pass, the mesh pass among them, now asks one gate, which answers the frame's grade except in a probe's face or a capture. The same gate keeps particles, lines, text and contributed passes ungraded inside a bake."
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
},
|
|
2
68
|
{
|
|
3
69
|
"version": "4.12.0",
|
|
4
70
|
"date": "2026-10-10",
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ WebGL2 everywhere else, behind one API, and carries the fixed-step loop, the ren
|
|
|
6
6
|
input, the scene graph, geometry, text and the whole of `@driftengine/physics`, which it re-exports.
|
|
7
7
|
Everything else is a separate package a game adds when it reaches for it.
|
|
8
8
|
|
|
9
|
-
**Cost:
|
|
9
|
+
**Cost: 920.9 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
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
SceneNode,
|
|
36
36
|
createEnvironment,
|
|
37
37
|
createRenderer,
|
|
38
|
+
srgbColor,
|
|
38
39
|
startLoop,
|
|
39
40
|
} from '@driftengine/core';
|
|
40
41
|
import { bindModule } from '@driftengine/script';
|
|
@@ -60,15 +61,23 @@ if (readout !== null) readout.textContent = `${backend}: ${reason}`;
|
|
|
60
61
|
renderer.resize();
|
|
61
62
|
addEventListener('resize', () => renderer.resize());
|
|
62
63
|
|
|
63
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Light, air and ground bounce. Chosen once, because these are allocation-sized decisions.
|
|
66
|
+
*
|
|
67
|
+
* Every colour here and below was picked by eye, so it goes through `srgbColor`: the renderer
|
|
68
|
+
* lights in linear values and encodes them for the screen at the end, and a colour typed as it
|
|
69
|
+
* looks would come out too light. The clear colour in the loop is one of them.
|
|
70
|
+
*/
|
|
71
|
+
const CLEAR = srgbColor(0.05, 0.06, 0.09);
|
|
72
|
+
|
|
64
73
|
const ENV = createEnvironment({
|
|
65
74
|
directionalDir: [0.4, 0.7, 0.35],
|
|
66
|
-
directionalColor:
|
|
67
|
-
ambient:
|
|
68
|
-
ambientGround:
|
|
75
|
+
directionalColor: srgbColor(1, 0.96, 0.88),
|
|
76
|
+
ambient: srgbColor(0.2, 0.22, 0.28),
|
|
77
|
+
ambientGround: srgbColor(0.08, 0.08, 0.1),
|
|
69
78
|
emissiveGain: 0,
|
|
70
79
|
nightFactor: 0,
|
|
71
|
-
fogColor:
|
|
80
|
+
fogColor: srgbColor(0.16, 0.18, 0.24),
|
|
72
81
|
fogDensity: 0.004,
|
|
73
82
|
fogHeightFalloff: 0.03,
|
|
74
83
|
fogBaseY: 0,
|
|
@@ -80,11 +89,11 @@ const ENV = createEnvironment({
|
|
|
80
89
|
* is two units on a side.
|
|
81
90
|
*/
|
|
82
91
|
const shapes = new MeshBuilder();
|
|
83
|
-
shapes.addBox([0, 1, 0], [1, 1, 1],
|
|
92
|
+
shapes.addBox([0, 1, 0], [1, 1, 1], srgbColor(0.85, 0.45, 0.25));
|
|
84
93
|
const cube = renderer.createMesh(shapes.build());
|
|
85
94
|
|
|
86
95
|
const slab = new MeshBuilder();
|
|
87
|
-
slab.addBox([0, -0.25, 0], [12, 0.25, 12],
|
|
96
|
+
slab.addBox([0, -0.25, 0], [12, 0.25, 12], srgbColor(0.3, 0.32, 0.36));
|
|
88
97
|
const ground = renderer.createMesh(slab.build());
|
|
89
98
|
|
|
90
99
|
const spinner = new SceneNode();
|
|
@@ -136,7 +145,7 @@ startLoop({
|
|
|
136
145
|
|
|
137
146
|
camera.updateMatrices(canvas.height > 0 ? canvas.width / canvas.height : 1);
|
|
138
147
|
|
|
139
|
-
renderer.beginFrame(
|
|
148
|
+
renderer.beginFrame(CLEAR);
|
|
140
149
|
renderer.bindMeshPass(camera, ENV);
|
|
141
150
|
renderer.drawMesh(ground, stillness.worldMatrix);
|
|
142
151
|
renderer.drawMesh(cube, spinner.worldMatrix);
|
|
@@ -10,6 +10,16 @@ export interface MeshBuildOptions {
|
|
|
10
10
|
* sample them. On, for a mesh that will be drawn with a `SurfaceTexture`.
|
|
11
11
|
*/
|
|
12
12
|
planarUvs?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The space the builder's colours were given in. `'linear'`, the default, is the renderer's own:
|
|
15
|
+
* light as a surface reflects it. `'srgb'` is a colour picked by eye, on a screen or in a paint
|
|
16
|
+
* program, and decodes every vertex's colour and emissive colour to linear here, once — as a
|
|
17
|
+
* texture of colours uploaded as sRGB is decoded — so a world built from hand-picked colours draws
|
|
18
|
+
* them as picked under the default `outputTransform`, where the same numbers taken as linear come
|
|
19
|
+
* out pale. What it gives up: nothing at the colour picked; a colour meant as light rather than as
|
|
20
|
+
* a look wants a linear number of its own.
|
|
21
|
+
*/
|
|
22
|
+
colorSpace?: 'linear' | 'srgb';
|
|
13
23
|
}
|
|
14
24
|
/**
|
|
15
25
|
* CPU-side accumulator that merges axis-aligned boxes into one static mesh —
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GrowableF32, GrowableU32 } from './growable.js';
|
|
2
|
+
import { decodeSrgbInPlace } from '../math/color.js';
|
|
2
3
|
/**
|
|
3
4
|
* The emissive colour of a vertex that has not been given one: negative, which no real
|
|
4
5
|
* colour can be, so "inherit the surface's albedo" is representable without a second
|
|
@@ -914,14 +915,22 @@ export class MeshBuilder {
|
|
|
914
915
|
/* Same promise as `specular`: a mesh nothing bound uploads no skinning buffers, and
|
|
915
916
|
`MeshData` requires both arrays or neither. */
|
|
916
917
|
const skinned = this.anyJoint;
|
|
918
|
+
/* Colours picked by eye, decoded once here: see `MeshBuildOptions.colorSpace`. */
|
|
919
|
+
const picked = options.colorSpace === 'srgb';
|
|
917
920
|
return {
|
|
918
921
|
positions: this.positions.toTyped(),
|
|
919
922
|
normals: this.normals.toTyped(),
|
|
920
|
-
colors: this.colors.toTyped(),
|
|
923
|
+
colors: picked ? decodeSrgbInPlace(this.colors.toTyped()) : this.colors.toTyped(),
|
|
921
924
|
emissive: this.emissive.toTyped(),
|
|
922
925
|
...(shines ? { specular: this.specular.toTyped() } : {}),
|
|
923
926
|
...(textured ? { relief: this.relief.toTyped() } : {}),
|
|
924
|
-
...(tinted
|
|
927
|
+
...(tinted
|
|
928
|
+
? {
|
|
929
|
+
emissiveColor: picked
|
|
930
|
+
? decodeSrgbInPlace(this.emissiveColor.toTyped())
|
|
931
|
+
: this.emissiveColor.toTyped(),
|
|
932
|
+
}
|
|
933
|
+
: {}),
|
|
925
934
|
...(rough ? { roughness: this.roughness.toTyped() } : {}),
|
|
926
935
|
...(mineral ? { grain: this.grain.toTyped() } : {}),
|
|
927
936
|
...(skinned ? { joints: this.expandedJoints(), weights: this.expandedWeights() } : {}),
|
package/dist/index.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export type { CelestialSite, CelestialState } from './environment/celestialClock
|
|
|
38
38
|
export { createDaylightPalette, createDaylightState, easeExposure, resolveDaylight, } from './environment/daylight.ts';
|
|
39
39
|
export type { DaylightKey, DaylightPalette, DaylightState } from './environment/daylight.ts';
|
|
40
40
|
export { TAU, DEG_TO_RAD, clamp, lerp, smoothstep, damp, dampTracking, angleDelta, dampAngle, wrapAngle, lerpAngle, } from './math/scalar.ts';
|
|
41
|
-
export { hslToRgb, mixColor, mixColorInto, scaleColor } from './math/color.ts';
|
|
41
|
+
export { decodeSrgbInPlace, hslToRgb, mixColor, mixColorInto, scaleColor, srgbColor, srgbToLinear, } from './math/color.ts';
|
|
42
42
|
export type { Vec3 } from './math/color.ts';
|
|
43
43
|
export { Camera } from './render/camera.ts';
|
|
44
44
|
export { ThirdPersonCamera } from './render/thirdPersonCamera.ts';
|
|
@@ -250,7 +250,7 @@ export type { LightGrid } from './render/lightGrid.ts';
|
|
|
250
250
|
export type { PointLightBuffer, PointLightSource } from './render/pointLightSelection.ts';
|
|
251
251
|
export { createFlame, flameFrequencyHz, updateFlame } from './render/flameLight.ts';
|
|
252
252
|
export type { Flame, FlameOptions } from './render/flameLight.ts';
|
|
253
|
-
export { computeLightMatrix } from './render/lightMatrix.ts';
|
|
253
|
+
export { computeLightMatrix, computeLightMatrixForBounds } from './render/lightMatrix.ts';
|
|
254
254
|
export { PlumeRenderer } from './render/plumeRenderer.ts';
|
|
255
255
|
export { createEnvironment } from './render/backend/webgl2/renderer.ts';
|
|
256
256
|
export type { InsetRect } from './render/backend/webgl2/renderer.ts';
|
|
@@ -264,6 +264,7 @@ export type { LoadTask, LoadSummary } from './core/loadTracker.ts';
|
|
|
264
264
|
export { buildShellFill, shellSkin, shellFillMatrix, DEFAULT_SHELL_PROFILE, } from './geometry/shellFill.ts';
|
|
265
265
|
export type { ShellStation, ShellFillOptions } from './geometry/shellFill.ts';
|
|
266
266
|
export { OUTPUT_TRANSFORM_GLSL } from './render/shaders/outputTransform.ts';
|
|
267
|
+
export { interfaceGrade } from './render/passGrade.ts';
|
|
267
268
|
export { PLUME_VERT, FIRE_FRAG } from './render/shaders/fire.ts';
|
|
268
269
|
export { ARCANE_FRAG } from './render/shaders/arcane.ts';
|
|
269
270
|
export { SMOKE_FRAG } from './render/shaders/smoke.ts';
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ export { MessageQueue } from './core/messageQueue.js';
|
|
|
35
35
|
export { celestialStateAt, createCelestialState, moonIllumination, } from './environment/celestialClock.js';
|
|
36
36
|
export { createDaylightPalette, createDaylightState, easeExposure, resolveDaylight, } from './environment/daylight.js';
|
|
37
37
|
export { TAU, DEG_TO_RAD, clamp, lerp, smoothstep, damp, dampTracking, angleDelta, dampAngle, wrapAngle, lerpAngle, } from './math/scalar.js';
|
|
38
|
-
export { hslToRgb, mixColor, mixColorInto, scaleColor } from './math/color.js';
|
|
38
|
+
export { decodeSrgbInPlace, hslToRgb, mixColor, mixColorInto, scaleColor, srgbColor, srgbToLinear, } from './math/color.js';
|
|
39
39
|
export { Camera } from './render/camera.js';
|
|
40
40
|
export { ThirdPersonCamera } from './render/thirdPersonCamera.js';
|
|
41
41
|
export { CinematicCamera } from './render/cinematicCamera.js';
|
|
@@ -305,7 +305,7 @@ export { describeGpu } from './render/gpuCapability.js';
|
|
|
305
305
|
export { DEFAULT_POINT_LIGHT_VIEW_RANGE, createPointLightBuffer, selectGridLights, selectPointLights, } from './render/pointLightSelection.js';
|
|
306
306
|
export { LIGHT_GRID_MAX_CELLS, createLightGrid, gatherLights } from './render/lightGrid.js';
|
|
307
307
|
export { createFlame, flameFrequencyHz, updateFlame } from './render/flameLight.js';
|
|
308
|
-
export { computeLightMatrix } from './render/lightMatrix.js';
|
|
308
|
+
export { computeLightMatrix, computeLightMatrixForBounds } from './render/lightMatrix.js';
|
|
309
309
|
export { PlumeRenderer } from './render/plumeRenderer.js';
|
|
310
310
|
export { createEnvironment } from './render/backend/webgl2/renderer.js';
|
|
311
311
|
export { TrampleField, TRAMPLE_SLOTS } from './render/trample.js';
|
|
@@ -337,6 +337,7 @@ export { buildShellFill, shellSkin, shellFillMatrix, DEFAULT_SHELL_PROFILE, } fr
|
|
|
337
337
|
* this line the only way for it to comply was to copy, which is what the rule forbids.
|
|
338
338
|
*/
|
|
339
339
|
export { OUTPUT_TRANSFORM_GLSL } from './render/shaders/outputTransform.js';
|
|
340
|
+
export { interfaceGrade } from './render/passGrade.js';
|
|
340
341
|
export { PLUME_VERT, FIRE_FRAG } from './render/shaders/fire.js';
|
|
341
342
|
export { ARCANE_FRAG } from './render/shaders/arcane.js';
|
|
342
343
|
export { SMOKE_FRAG } from './render/shaders/smoke.js';
|
package/dist/math/color.d.ts
CHANGED
|
@@ -6,3 +6,28 @@ export declare function mixColor(a: Vec3, b: Vec3, t: number): Vec3;
|
|
|
6
6
|
/** In-place `mixColor` for per-frame use — allocating here would be a hot-path bug. */
|
|
7
7
|
export declare function mixColorInto(out: Vec3, a: Vec3, b: Vec3, t: number): void;
|
|
8
8
|
export declare function scaleColor(color: Vec3, factor: number): Vec3;
|
|
9
|
+
/**
|
|
10
|
+
* A colour picked by eye — on a screen, in a paint program, as a CSS colour — as the linear value
|
|
11
|
+
* the renderer lights, blends, fogs and grades: each channel through the sRGB curve, which the
|
|
12
|
+
* default `outputTransform`, `'srgb'`, applies the other way at the end, so a colour drawn as it is,
|
|
13
|
+
* a sky, a clear or a surface lit at exactly 1, comes out as the colour picked.
|
|
14
|
+
*
|
|
15
|
+
* **Why a colour needs it**: everything the renderer draws is linear and encoded at the end, so a
|
|
16
|
+
* colour typed as it looks comes out too light — a brown picked as `(0.55, 0.3, 0.15)` draws as
|
|
17
|
+
* tan. **What it gives up**: nothing at the colour picked. **What would make it wrong** is a number
|
|
18
|
+
* that is an amount rather than a look: a light's intensity, a multiplier, a strength, which are
|
|
19
|
+
* linear already. Allocates, so it is for building a world rather than for a frame.
|
|
20
|
+
*/
|
|
21
|
+
export declare function srgbColor(r: number, g: number, b: number): Vec3;
|
|
22
|
+
/**
|
|
23
|
+
* Every value of `values` decoded from sRGB in place, but a negative one, which is a sentinel and
|
|
24
|
+
* not a colour (`MeshBuilder`'s emissive colour left to the albedo). For whole arrays of colours
|
|
25
|
+
* picked by eye; see `MeshBuildOptions.colorSpace`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function decodeSrgbInPlace(values: Float32Array): Float32Array;
|
|
28
|
+
/**
|
|
29
|
+
* One channel, 0 to 1, through the sRGB decode: the straight segment below 0.04045. For a colour
|
|
30
|
+
* that arrives as numbers per channel rather than as a triple, a packed hex token being the usual
|
|
31
|
+
* one; allocates nothing.
|
|
32
|
+
*/
|
|
33
|
+
export declare function srgbToLinear(value: number): number;
|
package/dist/math/color.js
CHANGED
|
@@ -33,3 +33,40 @@ export function mixColorInto(out, a, b, t) {
|
|
|
33
33
|
export function scaleColor(color, factor) {
|
|
34
34
|
return [color[0] * factor, color[1] * factor, color[2] * factor];
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* A colour picked by eye — on a screen, in a paint program, as a CSS colour — as the linear value
|
|
38
|
+
* the renderer lights, blends, fogs and grades: each channel through the sRGB curve, which the
|
|
39
|
+
* default `outputTransform`, `'srgb'`, applies the other way at the end, so a colour drawn as it is,
|
|
40
|
+
* a sky, a clear or a surface lit at exactly 1, comes out as the colour picked.
|
|
41
|
+
*
|
|
42
|
+
* **Why a colour needs it**: everything the renderer draws is linear and encoded at the end, so a
|
|
43
|
+
* colour typed as it looks comes out too light — a brown picked as `(0.55, 0.3, 0.15)` draws as
|
|
44
|
+
* tan. **What it gives up**: nothing at the colour picked. **What would make it wrong** is a number
|
|
45
|
+
* that is an amount rather than a look: a light's intensity, a multiplier, a strength, which are
|
|
46
|
+
* linear already. Allocates, so it is for building a world rather than for a frame.
|
|
47
|
+
*/
|
|
48
|
+
export function srgbColor(r, g, b) {
|
|
49
|
+
return [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Every value of `values` decoded from sRGB in place, but a negative one, which is a sentinel and
|
|
53
|
+
* not a colour (`MeshBuilder`'s emissive colour left to the albedo). For whole arrays of colours
|
|
54
|
+
* picked by eye; see `MeshBuildOptions.colorSpace`.
|
|
55
|
+
*/
|
|
56
|
+
export function decodeSrgbInPlace(values) {
|
|
57
|
+
for (let i = 0; i < values.length; i++) {
|
|
58
|
+
const value = values[i];
|
|
59
|
+
if (value >= 0)
|
|
60
|
+
values[i] = srgbToLinear(value);
|
|
61
|
+
}
|
|
62
|
+
return values;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* One channel, 0 to 1, through the sRGB decode: the straight segment below 0.04045. For a colour
|
|
66
|
+
* that arrives as numbers per channel rather than as a triple, a packed hex token being the usual
|
|
67
|
+
* one; allocates nothing.
|
|
68
|
+
*/
|
|
69
|
+
export function srgbToLinear(value) {
|
|
70
|
+
// determinism: build-time — a colour for drawing, which no tick reads
|
|
71
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
72
|
+
}
|
|
@@ -1304,6 +1304,8 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
1304
1304
|
private warnedOverlayBlocks;
|
|
1305
1305
|
/** Whether a probe is being baked, so the passes that would recurse into one can tell. */
|
|
1306
1306
|
private probePassActive;
|
|
1307
|
+
/** `clearOf`'s scratch: a clear colour as the open target takes it. */
|
|
1308
|
+
private readonly gradedClear;
|
|
1307
1309
|
/**
|
|
1308
1310
|
* Whether a scene capture is being drawn (`captureScene`): an off-screen pass like a bake, so the
|
|
1309
1311
|
* frame's own view, its refraction copy and skin's spread are left alone, but lit as the frame is.
|
|
@@ -2561,7 +2563,9 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
2561
2563
|
* real draw call touches the unit in the first place.
|
|
2562
2564
|
*/
|
|
2563
2565
|
/**
|
|
2564
|
-
* The
|
|
2566
|
+
* The grade a pass applies to what it draws into the target open now, and the exposure it
|
|
2567
|
+
* applies it at. The decision is `passGrade.ts`'s, for both backends; this only gathers the
|
|
2568
|
+
* state it is taken from.
|
|
2565
2569
|
*
|
|
2566
2570
|
* **Only where that pass is genuinely the last one to touch the frame.** With a scene target
|
|
2567
2571
|
* the resolve grades, so a pass that graded as well would be applying a curve to its own
|
|
@@ -2569,15 +2573,24 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
2569
2573
|
* `screenEffects` off there is no resolve at all and every pass writes straight to the canvas,
|
|
2570
2574
|
* so each one has to grade itself or the frame comes out half converted: linear values in an
|
|
2571
2575
|
* eight-bit buffer read as display values, which is a saturated, clipped version of the colour
|
|
2572
|
-
* the caller asked for rather than a slightly different one.
|
|
2576
|
+
* the caller asked for rather than a slightly different one. A probe's face or a capture is
|
|
2577
|
+
* never graded, and past the present every pass grades itself, composite or not.
|
|
2573
2578
|
*
|
|
2574
2579
|
* One pair of accessors rather than the expression written out at each site, because that is
|
|
2575
2580
|
* how the mesh pass and the particle pass came to disagree in the first place: the mesh pass
|
|
2576
2581
|
* gated, and the particle pass uploaded the transform unconditionally to a stage that did not
|
|
2577
2582
|
* yet declare it.
|
|
2578
2583
|
*/
|
|
2579
|
-
private
|
|
2580
|
-
private
|
|
2584
|
+
private passGrade;
|
|
2585
|
+
private passGradeExposure;
|
|
2586
|
+
/** Whether the frame's resolve applies the transform, so the passes before it draw linear. */
|
|
2587
|
+
private resolveGrades;
|
|
2588
|
+
/**
|
|
2589
|
+
* A clear colour as the target open now takes it: graded as a pass would grade it, so a colour
|
|
2590
|
+
* handed to the renderer is linear wherever it lands — the clear too, which the resolve grades
|
|
2591
|
+
* with everything else under `hdrScene`. See `gradeColor.ts`. Into one scratch, read at once.
|
|
2592
|
+
*/
|
|
2593
|
+
private clearOf;
|
|
2581
2594
|
drawSdfText(handle: SdfTextRenderer, model: Float32Array, color: Vec3, opacity: number): void;
|
|
2582
2595
|
/** Release an SDF text label. Takes no context; see `disposeText`. */
|
|
2583
2596
|
disposeSdfText(handle: SdfTextRenderer): void;
|