@driftengine/core 4.13.4 → 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 +36 -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/filmPass.d.ts +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/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 +5 -3
- package/dist/render/hairLobes.js +11 -7
- 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 +31 -7
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +20 -1
- package/dist/render/shaders/flat/models.js +1 -1
- 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/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/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 +35 -4
- 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 +31 -7
- package/src/render/shaders/flat/main.ts +20 -1
- package/src/render/shaders/flat/models.ts +1 -1
- 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,40 @@
|
|
|
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
|
+
},
|
|
2
38
|
{
|
|
3
39
|
"version": "4.13.4",
|
|
4
40
|
"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[];
|
|
@@ -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;
|
|
@@ -123,11 +123,11 @@ export const VERTEX_FLOATS = GPU_DRIVEN_VERTEX_FLOATS;
|
|
|
123
123
|
/**
|
|
124
124
|
* Floats the shading pass's frame block holds. Matches `FRAME_BLOCK_WGSL`'s `Frame`.
|
|
125
125
|
*
|
|
126
|
-
* Two matrices and
|
|
126
|
+
* Two matrices and eighteen vectors: the camera's, the light's, then the eye, the light's direction
|
|
127
127
|
* and colour, the sky, the ground, the target's size, the shadow's settings in two, the probe's in
|
|
128
|
-
* two, the medium's four,
|
|
128
|
+
* two, the medium's four, the visibility buffer's row, and the medium's last three after it.
|
|
129
129
|
*/
|
|
130
|
-
const FRAME_FLOATS =
|
|
130
|
+
const FRAME_FLOATS = 104;
|
|
131
131
|
/** Where each block starts in it, so the writer and the shader cannot drift apart. */
|
|
132
132
|
const FRAME_LIGHT_VIEW_PROJ = 16;
|
|
133
133
|
const FRAME_EYE = 32;
|
|
@@ -145,6 +145,9 @@ const FRAME_FOG_UNDERWATER = 76;
|
|
|
145
145
|
const FRAME_FOG_HEIGHT = 80;
|
|
146
146
|
const FRAME_FOG_RANGE = 84;
|
|
147
147
|
const FRAME_PITCH = 88;
|
|
148
|
+
const FRAME_FOG_LIMITS = 92;
|
|
149
|
+
const FRAME_FOG_GLOW = 96;
|
|
150
|
+
const FRAME_FOG_GLOW_DIR = 100;
|
|
148
151
|
/**
|
|
149
152
|
* Pixels the internal width is rounded up to.
|
|
150
153
|
*
|
|
@@ -2168,6 +2171,20 @@ export class GpuDrivenPass {
|
|
|
2168
2171
|
params[FRAME_FOG_RANGE + 1] = fog?.far ?? 0;
|
|
2169
2172
|
params[FRAME_FOG_RANGE + 2] = fog?.mode ?? MEDIUM_FOG;
|
|
2170
2173
|
params[FRAME_FOG_RANGE + 3] = fog?.underwaterDensity ?? 0;
|
|
2174
|
+
/* The second layer, the floor, the cutoff and the glow: every one none at zero, as a view
|
|
2175
|
+
written before they existed asks. */
|
|
2176
|
+
params[FRAME_FOG_HEIGHT + 2] = fog?.density2 ?? 0;
|
|
2177
|
+
params[FRAME_FOG_HEIGHT + 3] = fog?.heightFalloff2 ?? 0;
|
|
2178
|
+
params[FRAME_FOG_LIMITS] = fog?.floor ?? 0;
|
|
2179
|
+
params[FRAME_FOG_LIMITS + 1] = fog?.cutoff ?? 0;
|
|
2180
|
+
params[FRAME_FOG_GLOW] = fog?.glowColour?.[0] ?? 0;
|
|
2181
|
+
params[FRAME_FOG_GLOW + 1] = fog?.glowColour?.[1] ?? 0;
|
|
2182
|
+
params[FRAME_FOG_GLOW + 2] = fog?.glowColour?.[2] ?? 0;
|
|
2183
|
+
params[FRAME_FOG_GLOW + 3] = fog?.glowExponent ?? 1;
|
|
2184
|
+
params[FRAME_FOG_GLOW_DIR] = fog?.glowDirection?.[0] ?? 0;
|
|
2185
|
+
params[FRAME_FOG_GLOW_DIR + 1] = fog?.glowDirection?.[1] ?? 1;
|
|
2186
|
+
params[FRAME_FOG_GLOW_DIR + 2] = fog?.glowDirection?.[2] ?? 0;
|
|
2187
|
+
params[FRAME_FOG_GLOW_DIR + 3] = fog?.glowStart ?? 0;
|
|
2171
2188
|
/* The scene's own decision about whether its grid supplies the ambient, as it is on the
|
|
2172
2189
|
forward path: `ProbeBakeOptions.irradiance` reaches here as `PassEnvironment.irradiance`. */
|
|
2173
2190
|
params[FRAME_ENVIRONMENT_MIX] = room !== null && room.irradiance ? 1 : 0;
|
|
@@ -29,7 +29,7 @@ import type { PipelineCache } from './pipelineCache.ts';
|
|
|
29
29
|
export declare const LINE_VERT_FIELDS: UniformFields;
|
|
30
30
|
export declare const LINE_VERT_SIZE: 160;
|
|
31
31
|
export declare const LINE_FRAG_FIELDS: UniformFields;
|
|
32
|
-
export declare const LINE_FRAG_SIZE:
|
|
32
|
+
export declare const LINE_FRAG_SIZE: 160;
|
|
33
33
|
/** A polyline batch on the device, behind the surface's opaque handle. */
|
|
34
34
|
export interface GpuLines {
|
|
35
35
|
readonly fromBuffer: GPUBuffer;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The medium's fields, every uniform `shaders/fog.ts` declares, written into a pass's block by name
|
|
3
|
+
* from one resolved atmosphere.
|
|
4
|
+
*
|
|
5
|
+
* **One writer for every pass that fogs.** Ten passes wrote the same ten fields by hand, so a field
|
|
6
|
+
* the medium grows was ten places to remember and a pass that missed one read zero: the fourth
|
|
7
|
+
* point of the rule `AGENTS.md` dates 2026-08-13. A field a pass's shader never reads is compiled
|
|
8
|
+
* out of its block, so each is written only where the block holds it. An absent field is not an
|
|
9
|
+
* offset of zero, which is the first field of the block.
|
|
10
|
+
*/
|
|
11
|
+
import type { ResolvedAtmosphere } from '../../atmosphere.ts';
|
|
12
|
+
import type { UniformFields } from './scatterPass.ts';
|
|
13
|
+
import type { UniformRing } from './uniformRing.ts';
|
|
14
|
+
/** Into a block staged as floats, indexed in four-byte words. */
|
|
15
|
+
export declare function writeMediumFields(fields: UniformFields, f: Float32Array, m: ResolvedAtmosphere): void;
|
|
16
|
+
/** Into one slot of a ring, at the fields' byte offsets. */
|
|
17
|
+
export declare function writeMediumSlot(ring: UniformRing, slot: number, fields: UniformFields, m: ResolvedAtmosphere): void;
|