@driftengine/core 4.13.3 → 4.13.5
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 +52 -0
- package/README.md +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/render/anisotropicLobe.d.ts +5 -0
- package/dist/render/anisotropicLobe.js +13 -0
- package/dist/render/atmosphere.d.ts +79 -1
- package/dist/render/atmosphere.js +66 -19
- package/dist/render/backend/webgl2/renderer.d.ts +34 -2
- package/dist/render/backend/webgl2/renderer.js +44 -12
- 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/clothSetPack.js +1 -0
- package/dist/render/backend/webgpu/clothSolvePack.d.ts +5 -2
- package/dist/render/backend/webgpu/clothSolvePack.js +2 -1
- package/dist/render/backend/webgpu/filmPass.d.ts +1 -1
- package/dist/render/backend/webgpu/gpuClothSet.js +1 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +20 -3
- package/dist/render/backend/webgpu/linePass.d.ts +1 -1
- package/dist/render/backend/webgpu/mediumFields.d.ts +17 -0
- package/dist/render/backend/webgpu/mediumFields.js +74 -0
- package/dist/render/backend/webgpu/plumePass.d.ts +3 -3
- package/dist/render/backend/webgpu/postPass.d.ts +7 -2
- package/dist/render/backend/webgpu/renderer.d.ts +13 -3
- package/dist/render/backend/webgpu/renderer.js +86 -150
- package/dist/render/backend/webgpu/scatterPass.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +7 -5
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/environmentMixing.d.ts +18 -0
- package/dist/render/environmentMixing.js +40 -0
- package/dist/render/fog.d.ts +43 -1
- package/dist/render/fog.js +87 -22
- package/dist/render/globalMedium.d.ts +112 -10
- package/dist/render/globalMedium.js +96 -1
- package/dist/render/globalMediumPass.d.ts +7 -3
- package/dist/render/globalMediumPass.js +22 -1
- package/dist/render/hairLobes.d.ts +27 -2
- package/dist/render/hairLobes.js +57 -4
- package/dist/render/lightBudget.d.ts +12 -0
- package/dist/render/lightBudget.js +24 -0
- package/dist/render/sceneTarget.d.ts +5 -3
- package/dist/render/sceneTarget.js +14 -8
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/film.js +4 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.d.ts +8 -1
- package/dist/render/shaders/flat/anisotropic.js +25 -3
- package/dist/render/shaders/flat/hair.js +77 -27
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +23 -3
- package/dist/render/shaders/flat/models.js +12 -5
- package/dist/render/shaders/flat/preamble.js +7 -0
- package/dist/render/shaders/fog.d.ts +4 -4
- package/dist/render/shaders/fog.js +81 -32
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +29 -39
- package/dist/render/shaders/generated/arcane.wgsl.js +29 -39
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +26 -36
- package/dist/render/shaders/generated/bolt.wgsl.js +26 -36
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +25 -35
- package/dist/render/shaders/generated/caustics.wgsl.js +25 -35
- package/dist/render/shaders/generated/film.wgsl.d.ts +28 -38
- package/dist/render/shaders/generated/film.wgsl.js +28 -38
- package/dist/render/shaders/generated/fire.wgsl.d.ts +28 -38
- package/dist/render/shaders/generated/fire.wgsl.js +28 -38
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1466 -1546
- package/dist/render/shaders/generated/flat.wgsl.js +1489 -1569
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +98 -12
- package/dist/render/shaders/generated/globalMedium.wgsl.js +98 -12
- package/dist/render/shaders/generated/line.wgsl.d.ts +25 -35
- package/dist/render/shaders/generated/line.wgsl.js +25 -35
- package/dist/render/shaders/generated/particle.wgsl.d.ts +100 -140
- package/dist/render/shaders/generated/particle.wgsl.js +100 -140
- package/dist/render/shaders/generated/rush.wgsl.d.ts +20 -10
- package/dist/render/shaders/generated/rush.wgsl.js +20 -10
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +25 -35
- package/dist/render/shaders/generated/scatter.wgsl.js +25 -35
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +28 -38
- package/dist/render/shaders/generated/smoke.wgsl.js +28 -38
- package/dist/render/shaders/generated/water.wgsl.d.ts +29 -39
- package/dist/render/shaders/generated/water.wgsl.js +29 -39
- package/dist/render/shaders/globalMedium.js +114 -10
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +2 -1
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +10 -4
- package/dist/render/shaders/gpudriven/fog.wgsl.js +77 -19
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +12 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +3 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/line.js +4 -2
- package/dist/render/shaders/localExposure.d.ts +42 -1
- package/dist/render/shaders/localExposure.js +73 -6
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/particle.js +10 -6
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/scatter.js +4 -2
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +2 -1
- package/dist/render/surfaceModel.d.ts +23 -4
- package/dist/render/surfaceModel.js +12 -2
- package/package.json +6 -6
- package/src/index.ts +12 -2
- package/src/render/anisotropicLobe.ts +20 -0
- package/src/render/atmosphere.ts +153 -20
- package/src/render/backend/webgl2/renderer.ts +63 -13
- package/src/render/backend/webgpu/clothSetPack.ts +1 -0
- package/src/render/backend/webgpu/clothSolvePack.ts +6 -2
- package/src/render/backend/webgpu/gpuClothSet.ts +1 -1
- package/src/render/backend/webgpu/gpuDrivenPass.ts +20 -3
- package/src/render/backend/webgpu/mediumFields.ts +87 -0
- package/src/render/backend/webgpu/renderer.ts +109 -157
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +7 -5
- package/src/render/environmentMixing.ts +48 -0
- package/src/render/fog.ts +125 -25
- package/src/render/globalMedium.ts +214 -11
- package/src/render/globalMediumPass.ts +29 -4
- package/src/render/hairLobes.ts +109 -3
- package/src/render/lightBudget.ts +32 -0
- package/src/render/sceneTarget.ts +15 -11
- package/src/render/shaders/film.ts +4 -1
- package/src/render/shaders/flat/anisotropic.ts +25 -3
- package/src/render/shaders/flat/hair.ts +77 -27
- package/src/render/shaders/flat/main.ts +23 -3
- package/src/render/shaders/flat/models.ts +12 -5
- package/src/render/shaders/flat/preamble.ts +7 -0
- package/src/render/shaders/fog.ts +81 -32
- package/src/render/shaders/generated/arcane.wgsl.ts +29 -39
- package/src/render/shaders/generated/bolt.wgsl.ts +26 -36
- package/src/render/shaders/generated/caustics.wgsl.ts +25 -35
- package/src/render/shaders/generated/film.wgsl.ts +28 -38
- package/src/render/shaders/generated/fire.wgsl.ts +28 -38
- package/src/render/shaders/generated/flat.wgsl.ts +1489 -1569
- package/src/render/shaders/generated/globalMedium.wgsl.ts +98 -12
- package/src/render/shaders/generated/line.wgsl.ts +25 -35
- package/src/render/shaders/generated/particle.wgsl.ts +100 -140
- package/src/render/shaders/generated/rush.wgsl.ts +20 -10
- package/src/render/shaders/generated/scatter.wgsl.ts +25 -35
- package/src/render/shaders/generated/smoke.wgsl.ts +28 -38
- package/src/render/shaders/generated/water.wgsl.ts +29 -39
- package/src/render/shaders/globalMedium.ts +114 -10
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +2 -1
- package/src/render/shaders/gpudriven/fog.wgsl.ts +78 -19
- package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +12 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +3 -1
- package/src/render/shaders/line.ts +4 -2
- package/src/render/shaders/localExposure.ts +115 -6
- package/src/render/shaders/particle.ts +10 -6
- package/src/render/shaders/scatter.ts +4 -2
- package/src/render/shaders/water.ts +2 -1
- package/src/render/surfaceModel.ts +41 -6
package/CHANGELOG.json
CHANGED
|
@@ -1,4 +1,56 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "4.13.5",
|
|
4
|
+
"date": "2026-10-12",
|
|
5
|
+
"entries": [
|
|
6
|
+
{
|
|
7
|
+
"kind": "added",
|
|
8
|
+
"short": "The haze takes a second layer, a most it may hide, a distance past which it hides nothing, and the sun glowing in it: `fogLayer2`, `fogMaxOpacity`, `fogCutoff` and `fogSunGlow`.",
|
|
9
|
+
"text": "The environment's haze takes four more fields, in every pass that fogs and in the GPU-driven pipeline. `fogLayer2` is a second layer, its own density at its own height thinning by its own falloff, summed with the first along every ray: a ground mist under a high summer haze, where one layer had to pick one of the two scale heights. `fogMaxOpacity` is the most of a surface the haze may hide, so a skyline keeps that much of itself however much air lies in front of it. `fogCutoff` is a distance past which the haze hides nothing, the sky included, for a backdrop that brings its own haze painted in. `fogSunGlow` is the sun's light the haze scatters toward the eye: a colour looking straight at the sun through haze that hides everything, falling off as the cosine of the angle from it to an exponent, beginning a distance past where the haze begins, and added after the fog rather than mixed in, so a far ridge seen into a low sun burns gold. Every new lane reads as none at zero, so a pass a binder never wrote them into draws the haze it drew before. A haze authored in base two converts to the engine's natural units as density `ρ · (ln 2)²` and falloff `f · ln 2`, which the manual states. The haze's eight scalar uniforms are packed into two vectors, which paid for the three new ones and freed two more of the lit stage's rows on a phone's 256. Measured on `demo/dev/fogStart.html`, eight white patches at known distances: each option reads its hand-derived value to a level on WebGPU and on WebGL2 over Vulkan and over OpenGL, and the GPU-driven pipeline's fog agrees with the reference in `fog.ts` to 7.8e-5 over 102 glowing channels. Every published scene draws what 4.13.4 drew, to the pixel on WebGPU."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"kind": "added",
|
|
13
|
+
"short": "The global medium thins with height in two layers, takes a coloured albedo, and scatters the sun, the sky and the lamps at strengths of their own, each lamp through its own shadows.",
|
|
14
|
+
"text": "`setGlobalMedium` takes an object beside its four numbers, with what the numbers cannot say: `heightFalloff` and `baseY`, so the medium thins with height as the environment's haze does, and `layer2`, a second layer, integrated over each step of the march rather than sampled; `albedo` as a colour, a share a channel; `sunScatter` and `skyScatter`, how strongly the sun and the sky's fill scatter, 1 as each lights a surface; and `lampScatter`, the lamps, 0 by default so a scene that never asks keeps its picture and its cost. A lamp is the lit stage's own, shaped by its falloff and its cone and cut by its own static and live shadow maps, so a lamp behind a post throws the post's shadow into the air, and `lightMediumScatter` in the environment scales each lamp's own. A medium standing for the near part of the environment's haze hands the rest of the ray to that haze by setting `fogStart` to its `maxDistance`: the reach is where the medium ends, which the documentation had wrongly said extinction went on past and the march never did. Measured: with the sun and the sky at 0 no pixel rises, a red albedo raises no green or blue, a falloff that empties the room draws the frame without the pass to the pixel, and turning a lamp's maps off brightens 12,722 pixels of air behind the post on `demo/dev/pointshadow.html`, where with the lamp out of the medium it moves 2,928, on WebGPU and WebGL2 alike. `scripts/medium-check.mjs` had gone red on three claims when 4.13.0 made the screen encoding the default, because its unshadowed runs clipped to white; it reads the linear output now, and all six claims hold on both backends with the numbers the build before this one gives."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"kind": "added",
|
|
18
|
+
"short": "Local exposure takes a shape: each region turned about middle grey at the frame’s exposure, a share kept above it and another below, and a pixel’s own detail kept apart.",
|
|
19
|
+
"text": "`setLocalExposure` takes a shape beside its one number: `{ highlightContrast, shadowContrast, detail, blurredBlend, middleGreyBias, range }`. Each region is turned about middle grey at the frame's exposure, the auto exposure's gain and the output exposure together, rather than about the frame's metered brightness: it keeps `highlightContrast` of its stops from grey where it is brighter and `shadowContrast` where it is darker, and each pixel keeps `detail` of its own stops from its region, so above 1 sharpens the texture a curve would flatten. `blurredBlend` leans a region's level toward its tile's mean, 0.4 as the engine has held it, `middleGreyBias` adds stops to grey, and `range` is the furthest a region moves. The one number is the shape it always was, both shares one less the strength about the held level, and it draws what it drew to the bit, measured on both backends. On `demo/dev/localExposure.html`, a sunlit wall half pale and half dark: keeping half the stops each side brings the clipped pale half down 0.79 of a stop, enough for its stripes to show again, and the dark half up 0.82, and a detail of 1.5 widens the stripes by half while the halves hold."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"kind": "added",
|
|
23
|
+
"short": "Every surface's reflection can be brought to the light where it stands, by a weight mixed in by roughness: a mirror keeps the capture, a rough surface takes its place's light.",
|
|
24
|
+
"text": "`setEnvironmentMixing(weight, roughnessLow, roughnessHigh)` scales what every surface reflects of the environment, metal or not, by `lerp(1, weight, smoothstep(roughnessLow, roughnessHigh, roughness))`, held until changed and put back at `bindMeshPass`. A probe is captured at one place and seen from many, so a figure standing where the indirect light is twice what the probe saw reflected the probe too dimly against everything lit around it, and `setEnvironmentGain` reached metals only while `setSurfaceReflectivity` stopped at 1. The weight is the place's indirect light over the probe's own average brightness, above 1 or below, and the roughnesses are where it is mixed in, 0.1 and 0.3 by default, so a mirror keeps its image. Measured on the models page: a weight of 1 is the frame without it to the pixel, a weight of 2 brightens 60,314 pixels of the row's reflections, and the backends agree to 7 pixels. It costs the lit stage one uniform row."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"kind": "added",
|
|
28
|
+
"short": "Hair reads each strand's specular and direction texel by texel: the model map's red scales the white highlight, and a layer of its own can lean strands out of the card.",
|
|
29
|
+
"text": "The hair model reads two things a texel that it took once a material or from the mesh. The model map's red is the strand's specular: the reflected lobe is scaled by twice it, in the lamps' lobes and in the light from every side alike, so 0.5 is the highlight as drawn and 0 a root that reflects nothing; a hair model map whose red was left at 0 now draws no white highlight, and 128 keeps the old look. And `hairModel({ directionOffset })` names a layer of the model map, that many past the texel's own, holding each strand's direction in the surface's tangent frame, packed as a normal map packs a normal: the strand runs along it rather than along the tangent, so a card's strands lean out of its plane each its own way. Measured on the models page's strands: no map draws what it drew, a red of 0 and of 1 move 83,451 and 82,138 pixels, strands leaned 30 degrees move 118,342, and the backends agree to 31 pixels."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"kind": "added",
|
|
33
|
+
"short": "A brushed surface's highlight can be lifted where it is bright, its strength texel by texel: `anisotropicModel({ lift })`, scaled by the model map's alpha.",
|
|
34
|
+
"text": "`anisotropicModel({ lift })` raises the anisotropic distribution where it is bright, `D + min(3, D^1.5) · lift`, the shape a brushed finish that reads as polished along its streak is authored with, applied as a factor of the distribution at the half vector so the peak-normalised lobe and the physical one lift alike. Where a model map is bound its alpha scales the lift texel by texel, so a map storing a tenth of each region's lift takes `lift: 10`. 0, the default, draws what it drew; on the models page's brushed discs a lift of 3 moves 55,287 pixels of their streaks and the backends agree to 11."
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"version": "4.13.4",
|
|
40
|
+
"date": "2026-10-12",
|
|
41
|
+
"entries": [
|
|
42
|
+
{
|
|
43
|
+
"kind": "changed",
|
|
44
|
+
"short": "Hair takes the sky, a probe's light and DriftLight through its own highlights, for the eye's side of each strand, and reflects none of the environment, so a fibre keeps its colour in shade.",
|
|
45
|
+
"text": "Hair is lit from every side through its own lobes. The sky, a probe's irradiance and DriftLight reached a hair surface through its scatter alone, and the environment's reflection was laid over it, white, at the strand's Fresnel: a consumer's golden hair, base colour (1, 0.43, 0.115) and scatter 0.2, was reported drawing as platinum where its source draws it gold. Now the lobes are evaluated once for a light along the eye flattened across the strand, unshadowed, with no transmitted lobe because that direction stands for the eye's side, and every lobe widened by 0.2 radians as a source that large would widen it; twice their sum, at most 1, multiplies the light from every side, under the model map's occlusion as before. The scatter's half of that is exactly what the scatter alone gave, its own average over the sphere, so what is new is the reflected and the internally reflected lobes, which carry the fibre's colour into the shade. Hair reflects none of the environment, the probe, the sky's gradient and screen-space reflections alike, because the reflected lobe already holds that light. Measured on `demo/dev/models.html?view=hair&sky=only`, strands under an even white sky: a strand's middle drew (149, 127, 102) and draws (235, 189, 142), where the reference in `hairLobes.ts` gives (234, 189, 142) for the same geometry; the row of five models moves 8,340 pixels, every one of them the hair sphere's; and WebGPU and WebGL2, on Vulkan and on OpenGL, agree as closely as they did. **What it gives up**: hair lit from behind by an open sky shows no rim from the sky, which the lamps still give it, and the share moves with the eye as the lobes do rather than holding still as an average would."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"kind": "added",
|
|
49
|
+
"short": "A skinned cloth's pull is weighed particle by particle, so a strap is held at its root and hangs free at its tip: `SkinnedClothSetup.pullWeights`.",
|
|
50
|
+
"text": "`SkinnedClothSetup.pullWeights` weighs a skinned cloth's pull particle by particle, one a particle from 0 to 1: each keeps `1 / (1 + w · pull · h²)` of its offset from its skinned place a substep, and hangs `9.81 / (w · pull)` metres below it at rest. One pull for a whole garment had to choose, as a consumer reported: held at its mean, a belt's tips stood out from the waist they hang against; held at none, a styled fringe fell past the jaw it is swept up from. Now a strap is held at its root and hangs free at its tip, a fringe is held nearly whole and a coat's body swings free, in one garment. Absent, every particle takes the whole pull, as before; a weight of 0 is no pull at all, a free particle rather than a pinned one, and a weight outside 0 to 1 is refused by its index. The device carries the weights as a section of its statics and the pull as a substep's stiffness, `pull · h²`, and divides as the CPU does. Measured on `demo/dev/skinnedCloth.html?pull=400&weights=ramp`, a cape weighed 1 at its shoulders to 0 at its hem: it moves 39,267 pixels from the same cape under the whole pull; weights of 1 everywhere draw the unweighted cape to the pixel; WebGPU and WebGL2 agree to 159 pixels; and the device parts from the CPU by 1.8 mm at most and 0.03 mm by the 150th frame, inside what an unweighted pull of 100 parted by on the build before (1.8 mm, 0.07 mm), a hem that hangs free being as sensitive as a weak pull. An unweighted pull draws within 11 pixels of what it drew, the device now dividing in single precision where it was handed the share."
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
2
54
|
{
|
|
3
55
|
"version": "4.13.3",
|
|
4
56
|
"date": "2026-10-12",
|
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: 951.0 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
|
package/dist/index.d.ts
CHANGED
|
@@ -178,7 +178,7 @@ export type { FrameTimer } from './render/backend/timer.ts';
|
|
|
178
178
|
export type { SceneCasterMaterial, SceneCasters, ShadowCasterCloth, ShadowCasterSink, ShadowCasters, } from './render/shadowCasters.ts';
|
|
179
179
|
export type { StaticDrawsHandle } from './render/staticDraws.ts';
|
|
180
180
|
export type { BoneAnimationClip, BoneAnimationHandle } from './render/boneAnimation.ts';
|
|
181
|
-
export type { Atmosphere, UnderwaterAtmosphere } from './render/atmosphere.ts';
|
|
181
|
+
export type { Atmosphere, FogLayer, FogSunGlow, UnderwaterAtmosphere, } from './render/atmosphere.ts';
|
|
182
182
|
export { createImageTexelDecoder } from './render/imageTexels.ts';
|
|
183
183
|
export type { ImageTexelDecoder, ImageTexels } from './render/imageTexels.ts';
|
|
184
184
|
export { DEFAULT_RENDER_QUALITY, resolveRenderQuality } from './render/renderQuality.ts';
|
|
@@ -190,7 +190,8 @@ export { DEFAULT_RENDER_QUALITY, resolveRenderQuality } from './render/renderQua
|
|
|
190
190
|
* is the type the setter takes and the two bounds a settings screen has to know about.
|
|
191
191
|
*/
|
|
192
192
|
export { DEFAULT_GLOBAL_MEDIUM, MAX_GLOBAL_MEDIUM_STEPS, WEAK_GPU_MEDIUM_STEPS, resolveGlobalMedium, } from './render/globalMedium.ts';
|
|
193
|
-
export type { GlobalMediumOptions } from './render/globalMedium.ts';
|
|
193
|
+
export type { GlobalMediumOptions, GlobalMediumSettings, ResolvedGlobalMedium, } from './render/globalMedium.ts';
|
|
194
|
+
export type { LocalExposureOptions } from './render/shaders/localExposure.ts';
|
|
194
195
|
export { equirectToCubeFaces } from './render/equirectToCube.ts';
|
|
195
196
|
export type { EquirectSource } from './render/equirectToCube.ts';
|
|
196
197
|
export { IRRADIANCE_EDGE, PREFILTER_SAMPLE_COUNTS, ggxMaxLevelFor, irradianceLevelFor, octahedralEdgeFor, roughnessForLevel, } from './render/prefilterEnvMap.ts';
|
|
@@ -4,6 +4,11 @@ export declare function anisotropicAlphas(roughness: number, strength: number, o
|
|
|
4
4
|
* The lobe at a half vector whose components along the direction, across it and along the normal
|
|
5
5
|
* are `th`, `bh` and `nh`. 1 at the mirror direction.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* How much the lift brightens the stretched lobe at a half vector: `(D + min(3, D^1.5) · k) / D`,
|
|
9
|
+
* with D the distribution there, `1 / (π αT αB d²)`, as the shader's `anisotropicLift` has it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function anisotropicLiftFactor(th: number, bh: number, nh: number, alphaT: number, alphaB: number, lift: number): number;
|
|
7
12
|
export declare function anisotropicLobe(th: number, bh: number, nh: number, alphaT: number, alphaB: number): number;
|
|
8
13
|
/** Three components in the lobe's frame: along the direction, across it, along the normal. */
|
|
9
14
|
export type FrameVector = readonly [number, number, number];
|
|
@@ -25,6 +25,19 @@ export function anisotropicAlphas(roughness, strength, out) {
|
|
|
25
25
|
* The lobe at a half vector whose components along the direction, across it and along the normal
|
|
26
26
|
* are `th`, `bh` and `nh`. 1 at the mirror direction.
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* How much the lift brightens the stretched lobe at a half vector: `(D + min(3, D^1.5) · k) / D`,
|
|
30
|
+
* with D the distribution there, `1 / (π αT αB d²)`, as the shader's `anisotropicLift` has it.
|
|
31
|
+
*/
|
|
32
|
+
export function anisotropicLiftFactor(th, bh, nh, alphaT, alphaB, lift) {
|
|
33
|
+
if (lift <= 0)
|
|
34
|
+
return 1;
|
|
35
|
+
const x = th / alphaT;
|
|
36
|
+
const y = bh / alphaB;
|
|
37
|
+
const d = x * x + y * y + nh * nh;
|
|
38
|
+
const distribution = 1 / Math.max(Math.PI * alphaT * alphaB * d * d, 1e-8);
|
|
39
|
+
return 1 + lift * Math.min(3 / distribution, Math.sqrt(distribution));
|
|
40
|
+
}
|
|
28
41
|
export function anisotropicLobe(th, bh, nh, alphaT, alphaB) {
|
|
29
42
|
const x = th / alphaT;
|
|
30
43
|
const y = bh / alphaB;
|
|
@@ -10,6 +10,42 @@ export interface UnderwaterAtmosphere {
|
|
|
10
10
|
/** Half-width of the smooth air↔water transition around the surface. */
|
|
11
11
|
transitionDepth: number;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* A layer of haze: how thick it is where it is thickest, and how fast it thins above that.
|
|
15
|
+
*
|
|
16
|
+
* In natural units, as `fogDensity` is: a haze authored in base two, its surviving light
|
|
17
|
+
* `2^(−∫ρ)` and its density falling as `2^(−f·Δy)`, is the layer `density: ρ · (ln 2)²`,
|
|
18
|
+
* `heightFalloff: f · ln 2` here — the integral of base two's exponential carries one `ln 2` of its
|
|
19
|
+
* own, and the transmittance's base the other.
|
|
20
|
+
*/
|
|
21
|
+
export interface FogLayer {
|
|
22
|
+
/** Extinction per metre at `baseY`. */
|
|
23
|
+
density: number;
|
|
24
|
+
/** Reciprocal scale height, per metre: 0 is a layer of even density. */
|
|
25
|
+
heightFalloff: number;
|
|
26
|
+
/** The height it is thickest at. */
|
|
27
|
+
baseY: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The sun's light scattered toward the eye by the haze: a glow in the sun's direction, as dust and
|
|
31
|
+
* droplets throw forward what they catch.
|
|
32
|
+
*
|
|
33
|
+
* Added to what the haze returns rather than mixed with it, by as much of the ray past `start` as
|
|
34
|
+
* the haze hides: a mountain range seen into a low sun burns gold at its distance, and the
|
|
35
|
+
* figures a few metres off do not. **What it gives up** is the light's own shape: the glow is a
|
|
36
|
+
* lobe about one direction, `cos^exponent` of the angle from it, not light scattered along the
|
|
37
|
+
* ray through the shadows the sun's map knows of — the global medium is that one.
|
|
38
|
+
*/
|
|
39
|
+
export interface FogSunGlow {
|
|
40
|
+
/** The glow looking straight along `direction` through haze that hides everything. */
|
|
41
|
+
color: Vec3;
|
|
42
|
+
/** How tight the glow is about the sun: the cosine of the angle from it, to this power. */
|
|
43
|
+
exponent: number;
|
|
44
|
+
/** Metres past the haze's own start before the glow begins. Default 0. */
|
|
45
|
+
start?: number;
|
|
46
|
+
/** Surface → the light the glow surrounds. Absent, the environment's `directionalDir`. */
|
|
47
|
+
direction?: Vec3;
|
|
48
|
+
}
|
|
13
49
|
/** Atmosphere fields shared by every visible render pass. */
|
|
14
50
|
export interface Atmosphere {
|
|
15
51
|
fogColor: Vec3;
|
|
@@ -58,6 +94,31 @@ export interface Atmosphere {
|
|
|
58
94
|
* between the camera and the start, which a real medium has; that is the point of it.
|
|
59
95
|
*/
|
|
60
96
|
fogStart?: number;
|
|
97
|
+
/**
|
|
98
|
+
* The medium only: a second layer of haze, summed with the first along every ray, each its own
|
|
99
|
+
* density, height and falloff. Absent, one layer.
|
|
100
|
+
*
|
|
101
|
+
* Two layers are what a valley with a ground mist under a high summer haze is: one thin and
|
|
102
|
+
* tall, one dense and hugging the floor. One layer must pick one of the two scale heights, and
|
|
103
|
+
* either gets the other's half of the picture wrong.
|
|
104
|
+
*/
|
|
105
|
+
fogLayer2?: FogLayer | null;
|
|
106
|
+
/**
|
|
107
|
+
* The most of a surface the haze may hide, 0 to 1. Absent or 1, all of it.
|
|
108
|
+
*
|
|
109
|
+
* Below 1 a far mountain keeps that much of itself however much air lies between, which is how a
|
|
110
|
+
* scene keeps a skyline readable behind a haze that should hide everything nearer the ground.
|
|
111
|
+
* **What it gives up** is physics: no medium has a floor on what survives it.
|
|
112
|
+
*/
|
|
113
|
+
fogMaxOpacity?: number;
|
|
114
|
+
/**
|
|
115
|
+
* Metres from the camera past which the haze hides nothing: a surface further away, the sky
|
|
116
|
+
* included, is drawn clear. Absent or 0, none. For a sky or a backdrop that brings its own
|
|
117
|
+
* haze painted in, which the medium would otherwise lay a second time.
|
|
118
|
+
*/
|
|
119
|
+
fogCutoff?: number;
|
|
120
|
+
/** The medium only: the sun's light scattered toward the eye by the haze. Absent, none. */
|
|
121
|
+
fogSunGlow?: FogSunGlow | null;
|
|
61
122
|
underwater: UnderwaterAtmosphere | null;
|
|
62
123
|
}
|
|
63
124
|
/**
|
|
@@ -80,6 +141,16 @@ export declare function fogDensityAtEye(atmosphere: Atmosphere, cameraY: number)
|
|
|
80
141
|
*/
|
|
81
142
|
export interface ResolvedAtmosphere {
|
|
82
143
|
readonly fogColor: Float32Array;
|
|
144
|
+
/**
|
|
145
|
+
* Four floats: the second layer's extinction per metre at the camera's height and its falloff,
|
|
146
|
+
* then the least of a surface the haze must leave, `fogMaxOpacity`'s complement, and the cutoff
|
|
147
|
+
* distance. **Every lane is none at zero**, so a pass that never wrote them draws as before.
|
|
148
|
+
*/
|
|
149
|
+
readonly fogLayer2: Float32Array;
|
|
150
|
+
/** Four floats: the sun glow's colour, black for none, and its exponent. */
|
|
151
|
+
readonly fogGlow: Float32Array;
|
|
152
|
+
/** Four floats: surface → the glow's light, and how far past the haze's start the glow begins. */
|
|
153
|
+
readonly fogGlowDir: Float32Array;
|
|
83
154
|
fogDensity: number;
|
|
84
155
|
fogHeightFalloff: number;
|
|
85
156
|
fogEyeY: number;
|
|
@@ -98,7 +169,14 @@ export interface ResolvedAtmosphere {
|
|
|
98
169
|
* allows no allocation in one. Each backend keeps a single `ResolvedAtmosphere` and refills
|
|
99
170
|
* it; nothing here allocates and nothing is shared between them.
|
|
100
171
|
*/
|
|
101
|
-
export declare function resolveAtmosphere(atmosphere: Atmosphere
|
|
172
|
+
export declare function resolveAtmosphere(atmosphere: Atmosphere & {
|
|
173
|
+
readonly directionalDir?: ArrayLike<number>;
|
|
174
|
+
}, cameraY: number, underwaterEnabled: boolean, out: ResolvedAtmosphere): ResolvedAtmosphere;
|
|
175
|
+
/**
|
|
176
|
+
* A target for `resolveAtmosphere`, made once outside the frame loop: one factory, so a field added
|
|
177
|
+
* to the resolved medium is a field every holder of one gets rather than one each must remember.
|
|
178
|
+
*/
|
|
179
|
+
export declare function createResolvedAtmosphere(): ResolvedAtmosphere;
|
|
102
180
|
/**
|
|
103
181
|
* Bind the same camera medium to any shader that declares atmosphere uniforms.
|
|
104
182
|
* Missing optional uniforms are harmless WebGL null locations, so mesh, sky,
|
|
@@ -64,21 +64,69 @@ export function resolveAtmosphere(atmosphere, cameraY, underwaterEnabled, out) {
|
|
|
64
64
|
out.underwaterColor.set(underwater === null ? atmosphere.fogColor : underwater.color);
|
|
65
65
|
out.underwaterFogDensity = underwater === null ? atmosphere.fogDensity : underwater.fogDensity;
|
|
66
66
|
out.underwaterFactor = factor;
|
|
67
|
+
resolveHazeExtras(atmosphere, cameraY, out);
|
|
67
68
|
return out;
|
|
68
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The second layer, the haze's limits and the sun's glow, into `out`: the layer and the glow only
|
|
72
|
+
* in the medium, since a linear ramp has neither a density nor a ray to integrate, and every slot
|
|
73
|
+
* written whatever is asked, so nothing a shader reads keeps what an earlier frame left in it.
|
|
74
|
+
*/
|
|
75
|
+
function resolveHazeExtras(atmosphere, cameraY, out) {
|
|
76
|
+
const medium = atmosphere.fogMode !== 'linear';
|
|
77
|
+
const layer = medium ? (atmosphere.fogLayer2 ?? null) : null;
|
|
78
|
+
const density = layer === null ? 0 : Math.max(0, layer.density);
|
|
79
|
+
/* The second layer at the camera's height, as `fogDensityAtEye` puts the first. */
|
|
80
|
+
out.fogLayer2[0] =
|
|
81
|
+
density > 0 && layer !== null
|
|
82
|
+
? density * Math.exp(-(cameraY - layer.baseY) * layer.heightFalloff)
|
|
83
|
+
: 0;
|
|
84
|
+
out.fogLayer2[1] = density > 0 && layer !== null ? layer.heightFalloff : 0;
|
|
85
|
+
const most = atmosphere.fogMaxOpacity;
|
|
86
|
+
out.fogLayer2[2] =
|
|
87
|
+
most !== undefined && Number.isFinite(most) ? 1 - Math.min(1, Math.max(0, most)) : 0;
|
|
88
|
+
const cutoff = atmosphere.fogCutoff;
|
|
89
|
+
out.fogLayer2[3] = cutoff !== undefined && Number.isFinite(cutoff) && cutoff > 0 ? cutoff : 0;
|
|
90
|
+
const glow = medium ? (atmosphere.fogSunGlow ?? null) : null;
|
|
91
|
+
const toward = glow?.direction ?? atmosphere.directionalDir;
|
|
92
|
+
const x = toward?.[0] ?? 0;
|
|
93
|
+
const y = toward?.[1] ?? 1;
|
|
94
|
+
const z = toward?.[2] ?? 0;
|
|
95
|
+
const length = Math.hypot(x, y, z);
|
|
96
|
+
const lit = glow !== null && length > 1e-6;
|
|
97
|
+
for (let c = 0; c < 3; c++) {
|
|
98
|
+
out.fogGlow[c] = lit && glow !== null ? Math.max(0, glow.color[c]) : 0;
|
|
99
|
+
}
|
|
100
|
+
/* Above zero, so the shader's pow never meets 0 to the 0: a lobe that flat is the whole sky. */
|
|
101
|
+
out.fogGlow[3] = lit && glow !== null ? Math.max(1e-3, glow.exponent) : 1;
|
|
102
|
+
out.fogGlowDir[0] = lit ? x / length : 0;
|
|
103
|
+
out.fogGlowDir[1] = lit ? y / length : 1;
|
|
104
|
+
out.fogGlowDir[2] = lit ? z / length : 0;
|
|
105
|
+
out.fogGlowDir[3] = lit && glow !== null ? Math.max(0, glow.start ?? 0) : 0;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A target for `resolveAtmosphere`, made once outside the frame loop: one factory, so a field added
|
|
109
|
+
* to the resolved medium is a field every holder of one gets rather than one each must remember.
|
|
110
|
+
*/
|
|
111
|
+
export function createResolvedAtmosphere() {
|
|
112
|
+
return {
|
|
113
|
+
fogColor: new Float32Array(3),
|
|
114
|
+
fogLayer2: new Float32Array(4),
|
|
115
|
+
fogGlow: new Float32Array(4),
|
|
116
|
+
fogGlowDir: new Float32Array(4),
|
|
117
|
+
underwaterColor: new Float32Array(3),
|
|
118
|
+
fogDensity: 0,
|
|
119
|
+
fogHeightFalloff: 0,
|
|
120
|
+
fogEyeY: 0,
|
|
121
|
+
fogMode: 0,
|
|
122
|
+
fogNear: 0,
|
|
123
|
+
fogFar: 1,
|
|
124
|
+
underwaterFogDensity: 0,
|
|
125
|
+
underwaterFactor: 0,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
69
128
|
/** One per GL binder, refilled per call. See `resolveAtmosphere` on why it is not allocated. */
|
|
70
|
-
const glResolved =
|
|
71
|
-
fogColor: new Float32Array(3),
|
|
72
|
-
underwaterColor: new Float32Array(3),
|
|
73
|
-
fogDensity: 0,
|
|
74
|
-
fogHeightFalloff: 0,
|
|
75
|
-
fogEyeY: 0,
|
|
76
|
-
fogMode: 0,
|
|
77
|
-
fogNear: 0,
|
|
78
|
-
fogFar: 1,
|
|
79
|
-
underwaterFogDensity: 0,
|
|
80
|
-
underwaterFactor: 0,
|
|
81
|
-
};
|
|
129
|
+
const glResolved = createResolvedAtmosphere();
|
|
82
130
|
/**
|
|
83
131
|
* Bind the same camera medium to any shader that declares atmosphere uniforms.
|
|
84
132
|
* Missing optional uniforms are harmless WebGL null locations, so mesh, sky,
|
|
@@ -87,13 +135,12 @@ const glResolved = {
|
|
|
87
135
|
export function bindAtmosphere(gl, uniforms, atmosphere, cameraY, underwaterEnabled) {
|
|
88
136
|
const m = resolveAtmosphere(atmosphere, cameraY, underwaterEnabled, glResolved);
|
|
89
137
|
gl.uniform3fv(uniforms['uFogColor'] ?? null, m.fogColor);
|
|
90
|
-
|
|
91
|
-
gl.
|
|
92
|
-
gl.
|
|
93
|
-
gl.uniform1i(uniforms['uFogMode'] ?? null, m.fogMode);
|
|
94
|
-
gl.uniform1f(uniforms['uFogNear'] ?? null, m.fogNear);
|
|
95
|
-
gl.uniform1f(uniforms['uFogFar'] ?? null, m.fogFar);
|
|
138
|
+
/* Packed as `shaders/fog.ts` declares them, four to a row: see `uFogMedium` there for why. */
|
|
139
|
+
gl.uniform4f(uniforms['uFogMedium'] ?? null, m.fogDensity, m.fogHeightFalloff, m.fogEyeY, m.underwaterFogDensity);
|
|
140
|
+
gl.uniform4f(uniforms['uFogRange'] ?? null, m.fogNear, m.fogFar, m.fogMode, 0);
|
|
96
141
|
gl.uniform3fv(uniforms['uUnderwaterColor'] ?? null, m.underwaterColor);
|
|
97
|
-
gl.uniform1f(uniforms['uUnderwaterFogDensity'] ?? null, m.underwaterFogDensity);
|
|
98
142
|
gl.uniform1f(uniforms['uUnderwaterFactor'] ?? null, m.underwaterFactor);
|
|
143
|
+
gl.uniform4fv(uniforms['uFogLayer2'] ?? null, m.fogLayer2);
|
|
144
|
+
gl.uniform4fv(uniforms['uFogGlow'] ?? null, m.fogGlow);
|
|
145
|
+
gl.uniform4fv(uniforms['uFogGlowDir'] ?? null, m.fogGlowDir);
|
|
99
146
|
}
|
|
@@ -4,6 +4,7 @@ import type { GlassOptions } from '../../glass.ts';
|
|
|
4
4
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
5
5
|
import type { Camera } from '../../camera.ts';
|
|
6
6
|
import type { PickHit, PickableSource } from '../../pickable.ts';
|
|
7
|
+
import type { GlobalMediumSettings } from '../../globalMedium.ts';
|
|
7
8
|
import type { ReflectiveSurface } from '../../screenSpaceReflection.ts';
|
|
8
9
|
import type { DecalProjector } from '../../decalProjector.ts';
|
|
9
10
|
import type { FieldSource } from '../../gi/globalField.ts';
|
|
@@ -11,6 +12,7 @@ import type { Bounds } from '../../../math/bounds.ts';
|
|
|
11
12
|
import type { ComputeDefinition, ComputeHandle } from '../../compute.ts';
|
|
12
13
|
import type { PassDefinition, PassHandle } from '../../pass.ts';
|
|
13
14
|
import type { Vec3 } from '../../../math/color.ts';
|
|
15
|
+
import type { LocalExposureOptions } from '../../shaders/localExposure.ts';
|
|
14
16
|
import { GpuTimer } from '../../gpuTimer.ts';
|
|
15
17
|
import { type SurfaceOverlay } from '../../surfaceOverlay.ts';
|
|
16
18
|
import type { ColourGradeLut } from '../../colourGrade.ts';
|
|
@@ -210,6 +212,12 @@ export interface Environment extends Atmosphere {
|
|
|
210
212
|
* every light. See `PointLightSource.falloffExponent`.
|
|
211
213
|
*/
|
|
212
214
|
lightFalloffExponents?: Float32Array;
|
|
215
|
+
/**
|
|
216
|
+
* One per active light: how strongly it scatters in the global medium, a share of how strongly it
|
|
217
|
+
* lights a surface, beside `setGlobalMedium`'s `lampScatter`. Optional: absent is 1 for every
|
|
218
|
+
* light. See `PointLightSet.lightMediumScatter`.
|
|
219
|
+
*/
|
|
220
|
+
lightMediumScatter?: Float32Array;
|
|
213
221
|
/** One per active light: a row of the photometric atlas, or −1 for none. */
|
|
214
222
|
lightIesProfiles: Float32Array;
|
|
215
223
|
/**
|
|
@@ -1041,6 +1049,8 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
1041
1049
|
* vector on the device, so written whole, and kept here so a setter moving one keeps the others.
|
|
1042
1050
|
*/
|
|
1043
1051
|
private readonly environmentDials;
|
|
1052
|
+
/** `uEnvironmentMixing` as it stands: see `setEnvironmentMixing`. None until a draw asks. */
|
|
1053
|
+
private readonly environmentMixing;
|
|
1044
1054
|
/** `uEmission` as it stands: the emissive gain and the night factor. */
|
|
1045
1055
|
private readonly emission;
|
|
1046
1056
|
/** The ramp and each level's tint, as `resolveBloomResponse` writes them; the subtraction and white. */
|
|
@@ -2232,6 +2242,25 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
2232
2242
|
* legitimate as one above. Held non-negative, because a negative environment is not a darker one.
|
|
2233
2243
|
*/
|
|
2234
2244
|
setEnvironmentGain(gain: number): void;
|
|
2245
|
+
/**
|
|
2246
|
+
* How much of the light where the surfaces drawn next stand their reflection is brought to, for
|
|
2247
|
+
* every surface, metal or not: what each reflects of the environment times
|
|
2248
|
+
* `lerp(1, weight, smoothstep(roughnessLow, roughnessHigh, roughness))`. 1 is the default and
|
|
2249
|
+
* identity; held until changed, and put back at `bindMeshPass`.
|
|
2250
|
+
*
|
|
2251
|
+
* **Why there is a weight at all**: a probe is captured at one place and seen from many, so a
|
|
2252
|
+
* figure standing where the indirect light is twice what the probe saw reflects the probe at the
|
|
2253
|
+
* probe's brightness and reads as dull against everything lit around it. `weight` is that
|
|
2254
|
+
* ratio, the place's indirect light over the probe's own average, and a caller that knows both
|
|
2255
|
+
* passes it; above 1 brightens, below dims. A mirror keeps the capture as it is, because its
|
|
2256
|
+
* image is the point, and a rough surface, whose reflection is an average anyway, takes its
|
|
2257
|
+
* place's light: the roughnesses say where between, 0.1 and 0.3 by default.
|
|
2258
|
+
*
|
|
2259
|
+
* Unlike `setEnvironmentGain`, which reaches metals only, and `setSurfaceReflectivity`, which
|
|
2260
|
+
* stops at 1. **What it gives up** is a reflection that stays the probe's image on a rough
|
|
2261
|
+
* surface: past the upper roughness it is the probe scaled to the light, not the place relit.
|
|
2262
|
+
*/
|
|
2263
|
+
setEnvironmentMixing(weight: number, roughnessLow?: number, roughnessHigh?: number): void;
|
|
2235
2264
|
/**
|
|
2236
2265
|
* Kept as a no-op that says so once, because it was a stand-in for a term that now exists.
|
|
2237
2266
|
*
|
|
@@ -3383,7 +3412,7 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
3383
3412
|
* two regions of one brightness band differ. Needs `screenEffects` and `hdrScene`, and says so
|
|
3384
3413
|
* once rather than doing nothing. See `shaders/localExposure.ts`.
|
|
3385
3414
|
*/
|
|
3386
|
-
setLocalExposure(
|
|
3415
|
+
setLocalExposure(setting: number | LocalExposureOptions): void;
|
|
3387
3416
|
/**
|
|
3388
3417
|
* Where ambient occlusion fades out with distance: whole up to `distance` metres from the eye and
|
|
3389
3418
|
* gone `radius` metres past it, so the far depth buffer's steps — a sky dome kilometres off, a
|
|
@@ -3477,9 +3506,12 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
3477
3506
|
* this is everything between the camera and whatever the frame already drew, and it is the one
|
|
3478
3507
|
* of the two that can put the shape of a doorway on a floor.
|
|
3479
3508
|
*/
|
|
3480
|
-
setGlobalMedium(density: number, albedo?: number, anisotropy?: number, maxDistance?: number): void;
|
|
3509
|
+
setGlobalMedium(density: number | GlobalMediumSettings, albedo?: number | Vec3, anisotropy?: number, maxDistance?: number): void;
|
|
3481
3510
|
/** This frame's medium. Off until a caller says otherwise. See `setGlobalMedium`. */
|
|
3482
3511
|
private mediumOptions;
|
|
3512
|
+
/** The lamps the medium is lit by, and the lit stage's lamps they come from, refilled a frame. */
|
|
3513
|
+
private readonly mediumLamps;
|
|
3514
|
+
private readonly mediumLights;
|
|
3483
3515
|
/** Built on the first frame that asks for a medium, and never on one that does not. */
|
|
3484
3516
|
private medium;
|
|
3485
3517
|
/**
|
|
@@ -15,8 +15,9 @@ import { DecalPass } from '../../decalPass.js';
|
|
|
15
15
|
import { SsrPass } from '../../ssrPass.js';
|
|
16
16
|
import { ReflectionSurfaceTargets } from '../../reflectionSurfaceTargets.js';
|
|
17
17
|
import { frameReflectionsRefused, resolveFrameReflections, } from '../../frameReflections.js';
|
|
18
|
+
import { noEnvironmentMixing, resolveEnvironmentMixing } from '../../environmentMixing.js';
|
|
18
19
|
import { GlobalMediumPass } from '../../globalMediumPass.js';
|
|
19
|
-
import { DEFAULT_GLOBAL_MEDIUM, WEAK_GPU_MEDIUM_STEPS, mediumActive, resolveGlobalMedium, } from '../../globalMedium.js';
|
|
20
|
+
import { DEFAULT_GLOBAL_MEDIUM, WEAK_GPU_MEDIUM_STEPS, createResolvedMediumLamps, mediumActive, resolveGlobalMedium, resolveGlobalMediumSettings, resolveMediumLamps, } from '../../globalMedium.js';
|
|
20
21
|
import { REFLECTION_EDGE_FADE, ReflectionQueue } from '../../screenSpaceReflection.js';
|
|
21
22
|
import { DecalQueue } from '../../decalQueue.js';
|
|
22
23
|
import { OitPass } from '../../oitPass.js';
|
|
@@ -54,7 +55,7 @@ import { WorldLightField } from '../../driftLight/worldLightField.js';
|
|
|
54
55
|
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.js';
|
|
55
56
|
import { flatFrag, flatVert } from '../../shaders/flat/index.js';
|
|
56
57
|
import { skinEight } from '../../shaders/skinning.js';
|
|
57
|
-
import { MODEL_MAP_TEXTURE_UNIT, MORPH_DELTA_TEXTURE_UNIT, SKIN_PALETTE_TEXTURE_UNIT, } from '../../lightBudget.js';
|
|
58
|
+
import { MODEL_MAP_TEXTURE_UNIT, MORPH_DELTA_TEXTURE_UNIT, SKIN_PALETTE_TEXTURE_UNIT, createResolvedPointLights, resolvePointLights, } from '../../lightBudget.js';
|
|
58
59
|
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel, skinProfileDistance, } from '../../surfaceModel.js';
|
|
59
60
|
import { lightmapRegionsBound, modelBound, skinHalfBound } from '../../shaders/flat/models.js';
|
|
60
61
|
import { lightmapTexels, warnRegionsWithoutPage, warnUnpagedLightmap, withLightmapUvs, } from '../../lightmap.js';
|
|
@@ -718,6 +719,8 @@ export class WebGL2Renderer {
|
|
|
718
719
|
* vector on the device, so written whole, and kept here so a setter moving one keeps the others.
|
|
719
720
|
*/
|
|
720
721
|
environmentDials = new Float32Array(3);
|
|
722
|
+
/** `uEnvironmentMixing` as it stands: see `setEnvironmentMixing`. None until a draw asks. */
|
|
723
|
+
environmentMixing = noEnvironmentMixing(new Float32Array(4));
|
|
721
724
|
/** `uEmission` as it stands: the emissive gain and the night factor. */
|
|
722
725
|
emission = new Float32Array([1, 0]);
|
|
723
726
|
/** The ramp and each level's tint, as `resolveBloomResponse` writes them; the subtraction and white. */
|
|
@@ -3691,6 +3694,31 @@ export class WebGL2Renderer {
|
|
|
3691
3694
|
this.passEnvironmentGain = Math.max(0, gain);
|
|
3692
3695
|
this.writeEnvironmentDials();
|
|
3693
3696
|
}
|
|
3697
|
+
/**
|
|
3698
|
+
* How much of the light where the surfaces drawn next stand their reflection is brought to, for
|
|
3699
|
+
* every surface, metal or not: what each reflects of the environment times
|
|
3700
|
+
* `lerp(1, weight, smoothstep(roughnessLow, roughnessHigh, roughness))`. 1 is the default and
|
|
3701
|
+
* identity; held until changed, and put back at `bindMeshPass`.
|
|
3702
|
+
*
|
|
3703
|
+
* **Why there is a weight at all**: a probe is captured at one place and seen from many, so a
|
|
3704
|
+
* figure standing where the indirect light is twice what the probe saw reflects the probe at the
|
|
3705
|
+
* probe's brightness and reads as dull against everything lit around it. `weight` is that
|
|
3706
|
+
* ratio, the place's indirect light over the probe's own average, and a caller that knows both
|
|
3707
|
+
* passes it; above 1 brightens, below dims. A mirror keeps the capture as it is, because its
|
|
3708
|
+
* image is the point, and a rough surface, whose reflection is an average anyway, takes its
|
|
3709
|
+
* place's light: the roughnesses say where between, 0.1 and 0.3 by default.
|
|
3710
|
+
*
|
|
3711
|
+
* Unlike `setEnvironmentGain`, which reaches metals only, and `setSurfaceReflectivity`, which
|
|
3712
|
+
* stops at 1. **What it gives up** is a reflection that stays the probe's image on a rough
|
|
3713
|
+
* surface: past the upper roughness it is the probe scaled to the light, not the place relit.
|
|
3714
|
+
*/
|
|
3715
|
+
setEnvironmentMixing(weight, roughnessLow = 0.1, roughnessHigh = 0.3) {
|
|
3716
|
+
if (this.contextLost)
|
|
3717
|
+
return;
|
|
3718
|
+
resolveEnvironmentMixing(weight, roughnessLow, roughnessHigh, this.environmentMixing);
|
|
3719
|
+
this.materials.dirty();
|
|
3720
|
+
this.writeFlatVector('uEnvironmentMixing', this.environmentMixing);
|
|
3721
|
+
}
|
|
3694
3722
|
/**
|
|
3695
3723
|
* Kept as a no-op that says so once, because it was a stand-in for a term that now exists.
|
|
3696
3724
|
*
|
|
@@ -6395,12 +6423,12 @@ export class WebGL2Renderer {
|
|
|
6395
6423
|
* two regions of one brightness band differ. Needs `screenEffects` and `hdrScene`, and says so
|
|
6396
6424
|
* once rather than doing nothing. See `shaders/localExposure.ts`.
|
|
6397
6425
|
*/
|
|
6398
|
-
setLocalExposure(
|
|
6426
|
+
setLocalExposure(setting) {
|
|
6399
6427
|
if (this.sceneTarget === null || !this.quality.hdrScene) {
|
|
6400
|
-
this.warnLookWithoutComposite(
|
|
6428
|
+
this.warnLookWithoutComposite(typeof setting === 'number' ? setting : 1);
|
|
6401
6429
|
return;
|
|
6402
6430
|
}
|
|
6403
|
-
this.sceneTarget.setLocalExposure(
|
|
6431
|
+
this.sceneTarget.setLocalExposure(setting);
|
|
6404
6432
|
}
|
|
6405
6433
|
/**
|
|
6406
6434
|
* Where ambient occlusion fades out with distance: whole up to `distance` metres from the eye and
|
|
@@ -6508,15 +6536,13 @@ export class WebGL2Renderer {
|
|
|
6508
6536
|
* of the two that can put the shape of a doorway on a floor.
|
|
6509
6537
|
*/
|
|
6510
6538
|
setGlobalMedium(density, albedo, anisotropy, maxDistance) {
|
|
6511
|
-
this.mediumOptions =
|
|
6512
|
-
density: Number.isFinite(density) ? density : 0,
|
|
6513
|
-
albedo,
|
|
6514
|
-
anisotropy,
|
|
6515
|
-
maxDistance,
|
|
6516
|
-
});
|
|
6539
|
+
this.mediumOptions = resolveGlobalMediumSettings(density, albedo, anisotropy, maxDistance);
|
|
6517
6540
|
}
|
|
6518
6541
|
/** This frame's medium. Off until a caller says otherwise. See `setGlobalMedium`. */
|
|
6519
|
-
mediumOptions = DEFAULT_GLOBAL_MEDIUM;
|
|
6542
|
+
mediumOptions = resolveGlobalMedium(DEFAULT_GLOBAL_MEDIUM);
|
|
6543
|
+
/** The lamps the medium is lit by, and the lit stage's lamps they come from, refilled a frame. */
|
|
6544
|
+
mediumLamps = createResolvedMediumLamps();
|
|
6545
|
+
mediumLights = createResolvedPointLights();
|
|
6520
6546
|
/** Built on the first frame that asks for a medium, and never on one that does not. */
|
|
6521
6547
|
medium = null;
|
|
6522
6548
|
/**
|
|
@@ -7265,6 +7291,9 @@ export class WebGL2Renderer {
|
|
|
7265
7291
|
so it is safe only because `sunShadow` is 0 whenever these maps do not exist. */
|
|
7266
7292
|
sunShadows: this.sunShadows?.texture ?? this.emptyTexture2DArray,
|
|
7267
7293
|
peeledEnabled: this.peelFilled,
|
|
7294
|
+
/* The lit stage's lamps and the shadows it resolved for them, this frame's. */
|
|
7295
|
+
lamps: resolveMediumLamps(this.mediumOptions, resolvePointLights(env, this.quality.pointLightFalloff, this.mediumLights), this.pointShadows === null ? null : this.resolvedPointShadows, env?.lightMediumScatter, this.mediumLamps),
|
|
7296
|
+
pointShadows: this.pointShadowArray?.texture ?? this.emptyTexture2DArray,
|
|
7268
7297
|
});
|
|
7269
7298
|
if (marched) {
|
|
7270
7299
|
/* The march finished on the default framebuffer, which is not where the frame is being
|
|
@@ -7512,6 +7541,8 @@ export class WebGL2Renderer {
|
|
|
7512
7541
|
this.materialEnvironmentGain = null;
|
|
7513
7542
|
this.environmentDials[1] = 0;
|
|
7514
7543
|
this.environmentDials[2] = 1;
|
|
7544
|
+
/* And no mixing, for the same reason. See `setEnvironmentMixing`. */
|
|
7545
|
+
noEnvironmentMixing(this.environmentMixing);
|
|
7515
7546
|
this.emission[0] = env.emissiveGain;
|
|
7516
7547
|
this.emission[1] = env.nightFactor;
|
|
7517
7548
|
/* The moving layer's own matrix, the first time a frame draws that layer with one. */
|
|
@@ -7731,6 +7762,7 @@ export class WebGL2Renderer {
|
|
|
7731
7762
|
* See `setSurfaceReflectivity` and `setEnvironmentGain`.
|
|
7732
7763
|
*/
|
|
7733
7764
|
gl.uniform3fv(u['uEnvironmentDials'] ?? null, this.environmentDials);
|
|
7765
|
+
gl.uniform4fv(u['uEnvironmentMixing'] ?? null, this.environmentMixing);
|
|
7734
7766
|
/* And no relief unless a caller asks, for the same reason. */
|
|
7735
7767
|
gl.uniform2f(u['uRelief'] ?? null, 0, 60);
|
|
7736
7768
|
/* And no relief read off a surface texture either, for the same reason. */
|
|
@@ -11,7 +11,7 @@ import type { PipelineCache } from './pipelineCache.ts';
|
|
|
11
11
|
export declare const BOLT_VERT_FIELDS: UniformFields;
|
|
12
12
|
export declare const BOLT_VERT_SIZE: 96;
|
|
13
13
|
export declare const BOLT_FRAG_FIELDS: UniformFields;
|
|
14
|
-
export declare const BOLT_FRAG_SIZE:
|
|
14
|
+
export declare const BOLT_FRAG_SIZE: 192;
|
|
15
15
|
/** A batch of arcs on the device, behind the surface's opaque handle. */
|
|
16
16
|
export interface GpuBolts {
|
|
17
17
|
readonly fromBuffer: GPUBuffer;
|
|
@@ -10,7 +10,7 @@ import type { PipelineCache } from './pipelineCache.ts';
|
|
|
10
10
|
export declare const CAUSTICS_VERT_FIELDS: UniformFields;
|
|
11
11
|
export declare const CAUSTICS_VERT_SIZE: 64;
|
|
12
12
|
export declare const CAUSTICS_FRAG_FIELDS: UniformFields;
|
|
13
|
-
export declare const CAUSTICS_FRAG_SIZE:
|
|
13
|
+
export declare const CAUSTICS_FRAG_SIZE: 208;
|
|
14
14
|
/** A lit surface on the device, behind the API's opaque handle. */
|
|
15
15
|
export interface GpuCaustics {
|
|
16
16
|
readonly vertexBuffers: readonly GPUBuffer[];
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { SkinnedClothSetup } from '@driftengine/physics';
|
|
2
2
|
/** Floats of statics a particle, across its sections. */
|
|
3
|
-
export declare const CLOTH_STATIC_FLOATS =
|
|
3
|
+
export declare const CLOTH_STATIC_FLOATS = 30;
|
|
4
4
|
/** Bit flags of the limits a set-up has: a max distance, a backstop, a frontstop. */
|
|
5
5
|
export declare const CLOTH_LIMIT_MAX = 1;
|
|
6
6
|
export declare const CLOTH_LIMIT_BACK = 2;
|
|
7
7
|
export declare const CLOTH_LIMIT_FRONT = 4;
|
|
8
8
|
export interface PackedCloth {
|
|
9
9
|
readonly count: number;
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* inverse mass, max distance, backstop ×2, frontstop ×2, thickness, rest ×3, normal ×3,
|
|
12
|
+
* influences ×16, pull weight.
|
|
13
|
+
*/
|
|
11
14
|
readonly statics: Float32Array;
|
|
12
15
|
/** Distance records (a, b, rest, compliance), bending (four ids, rest, compliance), tethers (particle, anchor, length). */
|
|
13
16
|
readonly constraints: Uint32Array;
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { batchesOf } from '@driftengine/physics';
|
|
20
20
|
/** Floats of statics a particle, across its sections. */
|
|
21
|
-
export const CLOTH_STATIC_FLOATS =
|
|
21
|
+
export const CLOTH_STATIC_FLOATS = 30;
|
|
22
22
|
/** Bit flags of the limits a set-up has: a max distance, a backstop, a frontstop. */
|
|
23
23
|
export const CLOTH_LIMIT_MAX = 1;
|
|
24
24
|
export const CLOTH_LIMIT_BACK = 2;
|
|
@@ -50,6 +50,7 @@ export function packCloth(setup) {
|
|
|
50
50
|
statics[at + i * 2 + 1] = real ? radius : 0;
|
|
51
51
|
}
|
|
52
52
|
statics[6 * n + i] = limits.thickness?.[i] ?? 0;
|
|
53
|
+
statics[29 * n + i] = setup.pullWeights?.[i] ?? 1;
|
|
53
54
|
for (let k = 0; k < 3; k++) {
|
|
54
55
|
statics[7 * n + i * 3 + k] = setup.positions[i * 3 + k];
|
|
55
56
|
statics[10 * n + i * 3 + k] = setup.normals?.[i * 3 + k] ?? (k === 2 ? 1 : 0);
|
|
@@ -14,7 +14,7 @@ import type { PipelineCache } from './pipelineCache.ts';
|
|
|
14
14
|
export declare const FILM_VERT_FIELDS: UniformFields;
|
|
15
15
|
export declare const FILM_VERT_SIZE: 128;
|
|
16
16
|
export declare const FILM_FRAG_FIELDS: UniformFields;
|
|
17
|
-
export declare const FILM_FRAG_SIZE:
|
|
17
|
+
export declare const FILM_FRAG_SIZE: 208;
|
|
18
18
|
export declare function createFilmBindGroupLayout(device: GPUDevice): GPUBindGroupLayout;
|
|
19
19
|
/** Bound once against both rings; a dynamic offset per film. See `createPanelBindGroup`. */
|
|
20
20
|
export declare function createFilmBindGroup(device: GPUDevice, layout: GPUBindGroupLayout, vertices: GPUBuffer, fragments: GPUBuffer, reflection: GPUTextureView, sampler: GPUSampler): GPUBindGroup;
|