@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/dist/render/fog.d.ts
CHANGED
|
@@ -42,6 +42,25 @@ export interface FogOptions {
|
|
|
42
42
|
*/
|
|
43
43
|
readonly near: number;
|
|
44
44
|
readonly far: number;
|
|
45
|
+
/** The medium's second layer: its extinction per metre at the camera's height. Absent, none. */
|
|
46
|
+
readonly density2?: number;
|
|
47
|
+
/** And its reciprocal scale height. */
|
|
48
|
+
readonly heightFalloff2?: number;
|
|
49
|
+
/**
|
|
50
|
+
* The least of a surface the haze must leave, `Atmosphere.fogMaxOpacity`'s complement. Absent or 0,
|
|
51
|
+
* no floor.
|
|
52
|
+
*/
|
|
53
|
+
readonly floor?: number;
|
|
54
|
+
/** Metres past which the haze hides nothing. Absent or 0, none. */
|
|
55
|
+
readonly cutoff?: number;
|
|
56
|
+
/** The sun's glow in the haze: its colour. Absent or black, none. */
|
|
57
|
+
readonly glowColour?: Rgb;
|
|
58
|
+
/** The glow's lobe: the cosine of the angle from its light, to this power. */
|
|
59
|
+
readonly glowExponent?: number;
|
|
60
|
+
/** Surface → the glow's light, a unit vector. */
|
|
61
|
+
readonly glowDirection?: Rgb;
|
|
62
|
+
/** Metres past the haze's start before the glow begins. */
|
|
63
|
+
readonly glowStart?: number;
|
|
45
64
|
}
|
|
46
65
|
/** What distance converges toward: the day's haze, or the water column. */
|
|
47
66
|
export declare function mediumColour(fog: FogOptions): [number, number, number];
|
|
@@ -50,6 +69,19 @@ export declare function mediumColour(fog: FogOptions): [number, number, number];
|
|
|
50
69
|
* 0 is perfectly clear, 1 is the medium's own colour.
|
|
51
70
|
*/
|
|
52
71
|
export declare function mediumFog(fog: FogOptions, dist: number, pointY: number): number;
|
|
72
|
+
/**
|
|
73
|
+
* The haze's optical depth from where it begins along the ray to a point at `pointY`, `dist` metres
|
|
74
|
+
* away: each layer's density at the eye carried over what is left of the ray, the second only where
|
|
75
|
+
* there is one. `mediumFog` and `mediumGlow` both integrate it.
|
|
76
|
+
*/
|
|
77
|
+
export declare function hazeDepth(fog: FogOptions, dist: number, pointY: number): number;
|
|
78
|
+
/**
|
|
79
|
+
* The sun's light the haze scatters toward the eye along a ray to a point at `pointY`, `dist`
|
|
80
|
+
* metres away in the unit direction `toPoint`, into `out`: added after the fog, never mixed. A lobe
|
|
81
|
+
* of `cos^exponent` about the light, by as much of the ray past the glow's start as the haze hides.
|
|
82
|
+
* None in a linear ramp, past the cutoff, or under water.
|
|
83
|
+
*/
|
|
84
|
+
export declare function mediumGlow(fog: FogOptions, dist: number, pointY: number, toPoint: ArrayLike<number>, out: [number, number, number]): [number, number, number];
|
|
53
85
|
/** `FogOptions` with its fields writable, which is what a caller holds and refills a frame at a time. */
|
|
54
86
|
export interface FogTarget {
|
|
55
87
|
colour: [number, number, number];
|
|
@@ -62,6 +94,14 @@ export interface FogTarget {
|
|
|
62
94
|
mode: number;
|
|
63
95
|
near: number;
|
|
64
96
|
far: number;
|
|
97
|
+
density2: number;
|
|
98
|
+
heightFalloff2: number;
|
|
99
|
+
floor: number;
|
|
100
|
+
cutoff: number;
|
|
101
|
+
glowColour: [number, number, number];
|
|
102
|
+
glowExponent: number;
|
|
103
|
+
glowDirection: [number, number, number];
|
|
104
|
+
glowStart: number;
|
|
65
105
|
}
|
|
66
106
|
/** A target for `atmosphereFog`, allocated once, outside the frame loop. */
|
|
67
107
|
export declare function createFogTarget(): FogTarget;
|
|
@@ -79,4 +119,6 @@ export declare function createFogTarget(): FogTarget;
|
|
|
79
119
|
* @param underwater The renderer's `underwaterAtmosphere` quality, which gates the second medium
|
|
80
120
|
* on the forward path; pass the same.
|
|
81
121
|
*/
|
|
82
|
-
export declare function atmosphereFog(atmosphere: Atmosphere
|
|
122
|
+
export declare function atmosphereFog(atmosphere: Atmosphere & {
|
|
123
|
+
readonly directionalDir?: ArrayLike<number>;
|
|
124
|
+
}, eyeY: number, underwater: boolean, out: FogTarget): FogTarget;
|
package/dist/render/fog.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* block that declares everything it uses can be pasted into any program; a function takes them as
|
|
17
17
|
* one argument instead, and the backends keep their own packing.
|
|
18
18
|
*/
|
|
19
|
-
import { resolveAtmosphere } from './atmosphere.js';
|
|
19
|
+
import { createResolvedAtmosphere, resolveAtmosphere, } from './atmosphere.js';
|
|
20
20
|
/** Beer-Lambert through a haze layer that thins with height. `uFogMode` 0. */
|
|
21
21
|
export const MEDIUM_FOG = 0;
|
|
22
22
|
/** A ramp between two distances, which is a look rather than a medium. `uFogMode` 1. */
|
|
@@ -53,15 +53,37 @@ export function mediumFog(fog, dist, pointY) {
|
|
|
53
53
|
* is perfectly clear, which Beer-Lambert can never be, and height falloff has no meaning here
|
|
54
54
|
* because a ramp is defined on distance alone. `shaders/fog.ts` argues the whole of it.
|
|
55
55
|
*/
|
|
56
|
+
/* Past the cutoff the air hides nothing, in either mode; the water column keeps its own law. */
|
|
57
|
+
const cutoff = fog.cutoff ?? 0;
|
|
58
|
+
const cut = cutoff > 0 && dist > cutoff;
|
|
59
|
+
const floor = fog.floor ?? 0;
|
|
56
60
|
if (fog.mode === LINEAR_FOG) {
|
|
57
61
|
/* A consumer asking for a hard cut writes `far === near`; the guard is why that is a cut. */
|
|
58
62
|
const span = Math.max(fog.far - fog.near, 1e-4);
|
|
59
|
-
const ramp = Math.min(1, Math.max(0, (dist - fog.near) / span));
|
|
63
|
+
const ramp = cut ? 0 : Math.min(Math.min(1, Math.max(0, (dist - fog.near) / span)), 1 - floor);
|
|
60
64
|
if (fog.underwaterFactor <= 0)
|
|
61
65
|
return ramp;
|
|
62
66
|
const wetLinear = fog.underwaterDensity * dist;
|
|
63
67
|
return mix(ramp, 1 - Math.pow(2, -wetLinear * wetLinear * 1.442695), fog.underwaterFactor);
|
|
64
68
|
}
|
|
69
|
+
/*
|
|
70
|
+
* Beer-Lambert through both layers, leaving at least `floor` of the surface: a floor on what
|
|
71
|
+
* survives rather than a ceiling on the depth, so the curve keeps its shape above it.
|
|
72
|
+
*/
|
|
73
|
+
const air = cut ? 0 : 1 - Math.max(Math.exp(-hazeDepth(fog, dist, pointY)), floor);
|
|
74
|
+
if (fog.underwaterFactor <= 0)
|
|
75
|
+
return air;
|
|
76
|
+
/* Water is its own medium and keeps its own curve, so crossing the surface changes nothing
|
|
77
|
+
about how the water column reads. */
|
|
78
|
+
const wet = fog.underwaterDensity * dist;
|
|
79
|
+
return mix(air, 1 - Math.pow(2, -wet * wet * 1.442695), fog.underwaterFactor);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The haze's optical depth from where it begins along the ray to a point at `pointY`, `dist` metres
|
|
83
|
+
* away: each layer's density at the eye carried over what is left of the ray, the second only where
|
|
84
|
+
* there is one. `mediumFog` and `mediumGlow` both integrate it.
|
|
85
|
+
*/
|
|
86
|
+
export function hazeDepth(fog, dist, pointY) {
|
|
65
87
|
/*
|
|
66
88
|
* **Where the medium begins along the ray**, `near` — the linear mode's slot, which this mode
|
|
67
89
|
* never read before `Atmosphere.fogStart`. The ray is integrated from that point on: the density
|
|
@@ -77,14 +99,50 @@ export function mediumFog(fog, dist, pointY) {
|
|
|
77
99
|
* `t = 0` the quotient is 0/0, and 1e-4 of a scale height is 8 mm.
|
|
78
100
|
*/
|
|
79
101
|
const denom = Math.abs(rest) < 1e-4 ? 1e-4 : rest;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
let depth = (fog.density * Math.exp(-t * before) * (dist - start) * (1 - Math.exp(-denom))) / denom;
|
|
103
|
+
const density2 = fog.density2 ?? 0;
|
|
104
|
+
if (density2 > 0) {
|
|
105
|
+
const t2 = (pointY - fog.eyeY) * (fog.heightFalloff2 ?? 0);
|
|
106
|
+
const rest2 = t2 * (1 - before);
|
|
107
|
+
const denom2 = Math.abs(rest2) < 1e-4 ? 1e-4 : rest2;
|
|
108
|
+
depth +=
|
|
109
|
+
(density2 * Math.exp(-t2 * before) * (dist - start) * (1 - Math.exp(-denom2))) / denom2;
|
|
110
|
+
}
|
|
111
|
+
return depth;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The sun's light the haze scatters toward the eye along a ray to a point at `pointY`, `dist`
|
|
115
|
+
* metres away in the unit direction `toPoint`, into `out`: added after the fog, never mixed. A lobe
|
|
116
|
+
* of `cos^exponent` about the light, by as much of the ray past the glow's start as the haze hides.
|
|
117
|
+
* None in a linear ramp, past the cutoff, or under water.
|
|
118
|
+
*/
|
|
119
|
+
export function mediumGlow(fog, dist, pointY, toPoint, out) {
|
|
120
|
+
out[0] = 0;
|
|
121
|
+
out[1] = 0;
|
|
122
|
+
out[2] = 0;
|
|
123
|
+
const colour = fog.glowColour;
|
|
124
|
+
if (fog.mode === LINEAR_FOG || colour === undefined)
|
|
125
|
+
return out;
|
|
126
|
+
if (Math.max(colour[0], colour[1], colour[2]) <= 0)
|
|
127
|
+
return out;
|
|
128
|
+
const cutoff = fog.cutoff ?? 0;
|
|
129
|
+
if (cutoff > 0 && dist > cutoff)
|
|
130
|
+
return out;
|
|
131
|
+
const span = dist - Math.min(fog.near, dist);
|
|
132
|
+
if (span <= 0)
|
|
133
|
+
return out;
|
|
134
|
+
const perMetre = hazeDepth(fog, dist, pointY) / span;
|
|
135
|
+
const hidden = 1 - Math.exp(-perMetre * Math.max(span - (fog.glowStart ?? 0), 0));
|
|
136
|
+
const toward = fog.glowDirection ?? [0, 1, 0];
|
|
137
|
+
const cosine = toPoint[0] * toward[0] +
|
|
138
|
+
toPoint[1] * toward[1] +
|
|
139
|
+
toPoint[2] * toward[2];
|
|
140
|
+
const lobe = Math.pow(Math.min(1, Math.max(0, cosine)), fog.glowExponent ?? 1);
|
|
141
|
+
const share = lobe * hidden * (1 - fog.underwaterFactor);
|
|
142
|
+
out[0] = colour[0] * share;
|
|
143
|
+
out[1] = colour[1] * share;
|
|
144
|
+
out[2] = colour[2] * share;
|
|
145
|
+
return out;
|
|
88
146
|
}
|
|
89
147
|
/** A target for `atmosphereFog`, allocated once, outside the frame loop. */
|
|
90
148
|
export function createFogTarget() {
|
|
@@ -99,21 +157,18 @@ export function createFogTarget() {
|
|
|
99
157
|
mode: MEDIUM_FOG,
|
|
100
158
|
near: 0,
|
|
101
159
|
far: 1,
|
|
160
|
+
density2: 0,
|
|
161
|
+
heightFalloff2: 0,
|
|
162
|
+
floor: 0,
|
|
163
|
+
cutoff: 0,
|
|
164
|
+
glowColour: [0, 0, 0],
|
|
165
|
+
glowExponent: 1,
|
|
166
|
+
glowDirection: [0, 1, 0],
|
|
167
|
+
glowStart: 0,
|
|
102
168
|
};
|
|
103
169
|
}
|
|
104
170
|
/** `resolveAtmosphere`'s own target, reused, because this runs a frame at a time. */
|
|
105
|
-
const RESOLVED =
|
|
106
|
-
fogColor: new Float32Array(3),
|
|
107
|
-
underwaterColor: new Float32Array(3),
|
|
108
|
-
fogDensity: 0,
|
|
109
|
-
fogHeightFalloff: 0,
|
|
110
|
-
fogEyeY: 0,
|
|
111
|
-
fogMode: 0,
|
|
112
|
-
fogNear: 0,
|
|
113
|
-
fogFar: 1,
|
|
114
|
-
underwaterFogDensity: 0,
|
|
115
|
-
underwaterFactor: 0,
|
|
116
|
-
};
|
|
171
|
+
const RESOLVED = createResolvedAtmosphere();
|
|
117
172
|
/**
|
|
118
173
|
* The haze an `Environment` makes at a camera's height, as `GpuDrivenView.fog` takes it.
|
|
119
174
|
*
|
|
@@ -144,5 +199,15 @@ export function atmosphereFog(atmosphere, eyeY, underwater, out) {
|
|
|
144
199
|
out.mode = medium.fogMode;
|
|
145
200
|
out.near = medium.fogNear;
|
|
146
201
|
out.far = medium.fogFar;
|
|
202
|
+
out.density2 = medium.fogLayer2[0];
|
|
203
|
+
out.heightFalloff2 = medium.fogLayer2[1];
|
|
204
|
+
out.floor = medium.fogLayer2[2];
|
|
205
|
+
out.cutoff = medium.fogLayer2[3];
|
|
206
|
+
for (let c = 0; c < 3; c++) {
|
|
207
|
+
out.glowColour[c] = medium.fogGlow[c];
|
|
208
|
+
out.glowDirection[c] = medium.fogGlowDir[c];
|
|
209
|
+
}
|
|
210
|
+
out.glowExponent = medium.fogGlow[3];
|
|
211
|
+
out.glowStart = medium.fogGlowDir[3];
|
|
147
212
|
return out;
|
|
148
213
|
}
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
* and this asks what the journey adds — which is every point along it the sun can see, and is the
|
|
12
12
|
* only one of the two that can put the shape of a doorway on a floor.
|
|
13
13
|
*/
|
|
14
|
+
import type { Vec3 } from '../math/color.ts';
|
|
15
|
+
import type { FogLayer } from './atmosphere.ts';
|
|
16
|
+
import type { ResolvedPointLights } from './lightBudget.ts';
|
|
17
|
+
import type { ResolvedPointShadows } from './pointShadowImage.ts';
|
|
14
18
|
/**
|
|
15
19
|
* The most steps a march may take, which is the shader's own loop bound.
|
|
16
20
|
*
|
|
@@ -38,6 +42,11 @@ export declare const MAX_GLOBAL_MEDIUM_STEPS = 64;
|
|
|
38
42
|
* entitled to it and to the frame rate that comes with it.
|
|
39
43
|
*/
|
|
40
44
|
export declare const WEAK_GPU_MEDIUM_STEPS = 16;
|
|
45
|
+
/**
|
|
46
|
+
* The most lamps the march lights the medium with, which is the lit stage's own per-pixel set:
|
|
47
|
+
* the lamps a frame shades with uniforms, each with the shadow it already has.
|
|
48
|
+
*/
|
|
49
|
+
export declare const MAX_MEDIUM_LAMPS = 16;
|
|
41
50
|
/**
|
|
42
51
|
* How thick the air is, this frame.
|
|
43
52
|
*
|
|
@@ -55,13 +64,15 @@ export interface GlobalMediumOptions {
|
|
|
55
64
|
*/
|
|
56
65
|
readonly density: number;
|
|
57
66
|
/**
|
|
58
|
-
* How much of what the medium takes out of a ray comes back as light rather than as heat, 0 to 1
|
|
67
|
+
* How much of what the medium takes out of a ray comes back as light rather than as heat, 0 to 1,
|
|
68
|
+
* or a colour, a share a channel.
|
|
59
69
|
*
|
|
60
70
|
* Near 1 for water droplets, which is fog, cloud and steam; lower for smoke, which is soot and
|
|
61
71
|
* absorbs most of what it intercepts. It is the difference between a white fog that a torch
|
|
62
|
-
* lights up and a black smoke that swallows the same torch.
|
|
72
|
+
* lights up and a black smoke that swallows the same torch. A colour is a medium that keeps more
|
|
73
|
+
* of one light than another: a faintly blue haze returns the sky's blue a little more readily.
|
|
63
74
|
*/
|
|
64
|
-
readonly albedo: number;
|
|
75
|
+
readonly albedo: number | Vec3;
|
|
65
76
|
/**
|
|
66
77
|
* Henyey-Greenstein anisotropy, -1 back to 1 forward. 0 is isotropic.
|
|
67
78
|
*
|
|
@@ -72,14 +83,58 @@ export interface GlobalMediumOptions {
|
|
|
72
83
|
*/
|
|
73
84
|
readonly anisotropy: number;
|
|
74
85
|
/**
|
|
75
|
-
* Where the
|
|
86
|
+
* Where the medium ends, in metres: past it the air neither scatters nor absorbs.
|
|
76
87
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
88
|
+
* **This said extinction went on past it, and the march never did that**: it integrates from
|
|
89
|
+
* the eye to here or to the surface, whichever is nearer, and nothing after. Which is what a
|
|
90
|
+
* medium standing for the near part of the environment's haze wants: the haze takes the rest of
|
|
91
|
+
* the ray when its `fogStart` is this distance, and the two never lay one stretch of air twice.
|
|
92
|
+
* **What it gives up** is a medium of its own beyond the reach, which the haze has to be.
|
|
93
|
+
*/
|
|
94
|
+
readonly maxDistance: number;
|
|
95
|
+
/**
|
|
96
|
+
* Reciprocal scale height, per metre: `density` is the extinction at `baseY`, thinning above it
|
|
97
|
+
* as the haze does (`Atmosphere.fogHeightFalloff`). Absent or 0, the medium is as thick
|
|
98
|
+
* everywhere, as it always was.
|
|
81
99
|
*/
|
|
100
|
+
readonly heightFalloff?: number;
|
|
101
|
+
/** The height `density` is measured at. Default 0. */
|
|
102
|
+
readonly baseY?: number;
|
|
103
|
+
/**
|
|
104
|
+
* A second layer, summed with the first, its own density at its own height thinning by its own
|
|
105
|
+
* falloff: `Atmosphere.fogLayer2`'s shape, so a medium standing for the near part of a two-layer
|
|
106
|
+
* haze can be that haze.
|
|
107
|
+
*/
|
|
108
|
+
readonly layer2?: FogLayer | null;
|
|
109
|
+
/** How strongly the sun scatters in it, as a share of how strongly it lights a surface. Default 1. */
|
|
110
|
+
readonly sunScatter?: number;
|
|
111
|
+
/** The same for the sky's fill, which lights a shadowed medium from every side. Default 1. */
|
|
112
|
+
readonly skyScatter?: number;
|
|
113
|
+
/**
|
|
114
|
+
* The same for the lamps, each through its own shadow and times its own `lightMediumScatter`.
|
|
115
|
+
* **Default 0**: a medium was the sun's and the sky's until there was this, and a scene that
|
|
116
|
+
* never asks keeps that picture and that cost. The lamps are the lit stage's per-pixel set,
|
|
117
|
+
* shaped by their cones; a photometric profile and a cookie shape the pool on a surface and not
|
|
118
|
+
* the shaft in the air.
|
|
119
|
+
*/
|
|
120
|
+
readonly lampScatter?: number;
|
|
121
|
+
}
|
|
122
|
+
/** A medium with every field filled in and clamped: what both backends' passes are handed. */
|
|
123
|
+
export interface ResolvedGlobalMedium {
|
|
124
|
+
readonly density: number;
|
|
125
|
+
/** A colour, three shares. */
|
|
126
|
+
readonly albedo: readonly [number, number, number];
|
|
127
|
+
readonly anisotropy: number;
|
|
82
128
|
readonly maxDistance: number;
|
|
129
|
+
readonly heightFalloff: number;
|
|
130
|
+
readonly baseY: number;
|
|
131
|
+
/** The second layer's density at its own height, 0 for none, its falloff and that height. */
|
|
132
|
+
readonly density2: number;
|
|
133
|
+
readonly heightFalloff2: number;
|
|
134
|
+
readonly baseY2: number;
|
|
135
|
+
readonly sunScatter: number;
|
|
136
|
+
readonly skyScatter: number;
|
|
137
|
+
readonly lampScatter: number;
|
|
83
138
|
}
|
|
84
139
|
/**
|
|
85
140
|
* What a renderer starts with: off, and everything else at a value that means "ordinary air".
|
|
@@ -97,7 +152,16 @@ export declare const DEFAULT_GLOBAL_MEDIUM: GlobalMediumOptions;
|
|
|
97
152
|
* function by zero, which is a NaN that a half-float target spreads over the whole screen rather
|
|
98
153
|
* than reporting; a `maxDistance` of zero is a march with no length that still costs its steps.
|
|
99
154
|
*/
|
|
100
|
-
export declare function resolveGlobalMedium(options: Partial<GlobalMediumOptions>):
|
|
155
|
+
export declare function resolveGlobalMedium(options: Partial<GlobalMediumOptions>): ResolvedGlobalMedium;
|
|
156
|
+
/** `setGlobalMedium`'s object form: a density, and any of the rest. */
|
|
157
|
+
export type GlobalMediumSettings = Partial<GlobalMediumOptions> & {
|
|
158
|
+
readonly density: number;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* `setGlobalMedium`'s two forms, its four numbers or the object, read one way for both backends:
|
|
162
|
+
* a density that is not a number is none.
|
|
163
|
+
*/
|
|
164
|
+
export declare function resolveGlobalMediumSettings(density: number | GlobalMediumSettings, albedo?: number | Vec3, anisotropy?: number, maxDistance?: number): ResolvedGlobalMedium;
|
|
101
165
|
/**
|
|
102
166
|
* Whether this frame draws the medium at all.
|
|
103
167
|
*
|
|
@@ -105,7 +169,7 @@ export declare function resolveGlobalMedium(options: Partial<GlobalMediumOptions
|
|
|
105
169
|
* `density: 0` is pixel-identical to the pass being absent is only true if neither backend runs a
|
|
106
170
|
* composite at zero, and a second copy of this test is where that stops being true.
|
|
107
171
|
*/
|
|
108
|
-
export declare function mediumActive(options: GlobalMediumOptions, steps: number): boolean;
|
|
172
|
+
export declare function mediumActive(options: Pick<GlobalMediumOptions, 'density'>, steps: number): boolean;
|
|
109
173
|
/**
|
|
110
174
|
* The march's target size.
|
|
111
175
|
*
|
|
@@ -122,3 +186,41 @@ export declare function mediumTargetSize(width: number, height: number, halfReso
|
|
|
122
186
|
width: number;
|
|
123
187
|
height: number;
|
|
124
188
|
};
|
|
189
|
+
/**
|
|
190
|
+
* The lamps as the march reads them: eight vectors a lamp, each array `MAX_MEDIUM_LAMPS` of them.
|
|
191
|
+
*
|
|
192
|
+
* **Decided here and bound twice**, on the split `resolvePointLights` draws: which lamps light the
|
|
193
|
+
* medium, how bright and through which maps is arithmetic both backends run, and a backend only
|
|
194
|
+
* uploads the arrays. A lamp's shadows are the lit stage's own, the static map and the live one,
|
|
195
|
+
* read where the frame left them, so the shaft in the air and the pool on the floor are cut by the
|
|
196
|
+
* same casters.
|
|
197
|
+
*/
|
|
198
|
+
export interface ResolvedMediumLamps {
|
|
199
|
+
/** How many lamps light the medium: none while `lampScatter` is 0. */
|
|
200
|
+
count: number;
|
|
201
|
+
/** 1 for inverse-square, 0 for the smooth falloff: the frame's own rule. */
|
|
202
|
+
falloff: number;
|
|
203
|
+
/** Where each lamp is, and how far it reaches. */
|
|
204
|
+
readonly position: Float32Array;
|
|
205
|
+
/** Its colour, times its presence and its scatter, and its own falloff exponent (0 for none). */
|
|
206
|
+
readonly color: Float32Array;
|
|
207
|
+
/** The direction it shines, a spot's axis: `lightDirections`. */
|
|
208
|
+
readonly aim: Float32Array;
|
|
209
|
+
/** Its cone's two cosines, inner and outer, as the lit stage takes them. */
|
|
210
|
+
readonly cone: Float32Array;
|
|
211
|
+
/** Its static shadow: the layer (−1 for none), the near plane, and how present the map is. */
|
|
212
|
+
readonly shadow: Float32Array;
|
|
213
|
+
/** The static map's projection: its origin and its far plane. */
|
|
214
|
+
readonly shadowAt: Float32Array;
|
|
215
|
+
/** Its live shadow, by the same layout, and the live map's projection. */
|
|
216
|
+
readonly live: Float32Array;
|
|
217
|
+
readonly liveAt: Float32Array;
|
|
218
|
+
}
|
|
219
|
+
/** A target for `resolveMediumLamps`, made once outside the frame loop. */
|
|
220
|
+
export declare function createResolvedMediumLamps(): ResolvedMediumLamps;
|
|
221
|
+
/**
|
|
222
|
+
* The frame's lamps as the medium is lit by them, into `out`: the lit stage's resolved lamps and
|
|
223
|
+
* their resolved shadows, a lamp's scatter `lampScatter` times its own `lightMediumScatter` (1 where
|
|
224
|
+
* absent). Allocates nothing; a lamp past the count keeps whatever it held, which nothing reads.
|
|
225
|
+
*/
|
|
226
|
+
export declare function resolveMediumLamps(medium: ResolvedGlobalMedium, lights: ResolvedPointLights, shadows: ResolvedPointShadows | null, scatter: Float32Array | undefined, out: ResolvedMediumLamps): ResolvedMediumLamps;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* and this asks what the journey adds — which is every point along it the sun can see, and is the
|
|
12
12
|
* only one of the two that can put the shape of a doorway on a floor.
|
|
13
13
|
*/
|
|
14
|
+
import { MAX_POINT_LIGHTS } from './lightBudget.js';
|
|
14
15
|
/**
|
|
15
16
|
* The most steps a march may take, which is the shader's own loop bound.
|
|
16
17
|
*
|
|
@@ -38,6 +39,11 @@ export const MAX_GLOBAL_MEDIUM_STEPS = 64;
|
|
|
38
39
|
* entitled to it and to the frame rate that comes with it.
|
|
39
40
|
*/
|
|
40
41
|
export const WEAK_GPU_MEDIUM_STEPS = 16;
|
|
42
|
+
/**
|
|
43
|
+
* The most lamps the march lights the medium with, which is the lit stage's own per-pixel set:
|
|
44
|
+
* the lamps a frame shades with uniforms, each with the shadow it already has.
|
|
45
|
+
*/
|
|
46
|
+
export const MAX_MEDIUM_LAMPS = MAX_POINT_LIGHTS;
|
|
41
47
|
/**
|
|
42
48
|
* What a renderer starts with: off, and everything else at a value that means "ordinary air".
|
|
43
49
|
*
|
|
@@ -61,14 +67,47 @@ export const DEFAULT_GLOBAL_MEDIUM = {
|
|
|
61
67
|
*/
|
|
62
68
|
export function resolveGlobalMedium(options) {
|
|
63
69
|
const d = DEFAULT_GLOBAL_MEDIUM;
|
|
70
|
+
const albedo = options.albedo ?? d.albedo;
|
|
71
|
+
const share = (value) => Math.min(1, Math.max(0, Number.isFinite(value) ? value : 0));
|
|
72
|
+
const layer2 = options.layer2 ?? null;
|
|
73
|
+
const density2 = layer2 === null ? 0 : Math.max(0, layer2.density);
|
|
74
|
+
const finite = (value, fallback) => value !== undefined && Number.isFinite(value) ? value : fallback;
|
|
64
75
|
return {
|
|
65
76
|
density: Math.max(0, options.density ?? d.density),
|
|
66
|
-
albedo:
|
|
77
|
+
albedo: typeof albedo === 'number'
|
|
78
|
+
? [share(albedo), share(albedo), share(albedo)]
|
|
79
|
+
: [share(albedo[0]), share(albedo[1]), share(albedo[2])],
|
|
67
80
|
/* Short of the poles, where `(1 - g^2)` over `(1 + g^2 - 2g)^1.5` is 0/0. */
|
|
68
81
|
anisotropy: Math.min(0.95, Math.max(-0.95, options.anisotropy ?? d.anisotropy)),
|
|
69
82
|
maxDistance: Math.max(1e-3, options.maxDistance ?? d.maxDistance),
|
|
83
|
+
heightFalloff: finite(options.heightFalloff, 0),
|
|
84
|
+
baseY: finite(options.baseY, 0),
|
|
85
|
+
density2,
|
|
86
|
+
heightFalloff2: layer2 === null ? 0 : finite(layer2.heightFalloff, 0),
|
|
87
|
+
baseY2: layer2 === null ? 0 : finite(layer2.baseY, 0),
|
|
88
|
+
sunScatter: Math.max(0, finite(options.sunScatter, 1)),
|
|
89
|
+
skyScatter: Math.max(0, finite(options.skyScatter, 1)),
|
|
90
|
+
lampScatter: Math.max(0, finite(options.lampScatter, 0)),
|
|
70
91
|
};
|
|
71
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* `setGlobalMedium`'s two forms, its four numbers or the object, read one way for both backends:
|
|
95
|
+
* a density that is not a number is none.
|
|
96
|
+
*/
|
|
97
|
+
export function resolveGlobalMediumSettings(density, albedo, anisotropy, maxDistance) {
|
|
98
|
+
if (typeof density === 'object') {
|
|
99
|
+
return resolveGlobalMedium({
|
|
100
|
+
...density,
|
|
101
|
+
density: Number.isFinite(density.density) ? density.density : 0,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return resolveGlobalMedium({
|
|
105
|
+
density: Number.isFinite(density) ? density : 0,
|
|
106
|
+
albedo,
|
|
107
|
+
anisotropy,
|
|
108
|
+
maxDistance,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
72
111
|
/**
|
|
73
112
|
* Whether this frame draws the medium at all.
|
|
74
113
|
*
|
|
@@ -98,3 +137,59 @@ export function mediumTargetSize(width, height, halfResolution) {
|
|
|
98
137
|
height: Math.max(1, Math.ceil(Math.max(1, height) / divisor)),
|
|
99
138
|
};
|
|
100
139
|
}
|
|
140
|
+
/** A target for `resolveMediumLamps`, made once outside the frame loop. */
|
|
141
|
+
export function createResolvedMediumLamps() {
|
|
142
|
+
const vectors = () => new Float32Array(MAX_MEDIUM_LAMPS * 4);
|
|
143
|
+
return {
|
|
144
|
+
count: 0,
|
|
145
|
+
falloff: 0,
|
|
146
|
+
position: vectors(),
|
|
147
|
+
color: vectors(),
|
|
148
|
+
aim: vectors(),
|
|
149
|
+
cone: vectors(),
|
|
150
|
+
shadow: vectors(),
|
|
151
|
+
shadowAt: vectors(),
|
|
152
|
+
live: vectors(),
|
|
153
|
+
liveAt: vectors(),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The frame's lamps as the medium is lit by them, into `out`: the lit stage's resolved lamps and
|
|
158
|
+
* their resolved shadows, a lamp's scatter `lampScatter` times its own `lightMediumScatter` (1 where
|
|
159
|
+
* absent). Allocates nothing; a lamp past the count keeps whatever it held, which nothing reads.
|
|
160
|
+
*/
|
|
161
|
+
export function resolveMediumLamps(medium, lights, shadows, scatter, out) {
|
|
162
|
+
const count = medium.lampScatter > 0 ? Math.min(lights.count, MAX_MEDIUM_LAMPS) : 0;
|
|
163
|
+
out.count = count;
|
|
164
|
+
out.falloff = lights.falloff;
|
|
165
|
+
const own = scatter !== undefined && scatter.length >= count ? scatter : undefined;
|
|
166
|
+
for (let i = 0; i < count; i++) {
|
|
167
|
+
const at = i * 4;
|
|
168
|
+
const strength = medium.lampScatter * Math.max(0, own?.[i] ?? 1) * lights.weights[i];
|
|
169
|
+
for (let c = 0; c < 3; c++) {
|
|
170
|
+
out.position[at + c] = lights.positions[i * 3 + c];
|
|
171
|
+
out.color[at + c] = lights.colors[i * 3 + c] * strength;
|
|
172
|
+
out.aim[at + c] = lights.directions[i * 3 + c];
|
|
173
|
+
}
|
|
174
|
+
out.position[at + 3] = lights.radii[i];
|
|
175
|
+
out.color[at + 3] = lights.conesAndFalloffs[at + 2];
|
|
176
|
+
out.aim[at + 3] = 0;
|
|
177
|
+
out.cone[at] = lights.conesAndFalloffs[at];
|
|
178
|
+
out.cone[at + 1] = lights.conesAndFalloffs[at + 1];
|
|
179
|
+
out.cone[at + 2] = 0;
|
|
180
|
+
out.cone[at + 3] = 0;
|
|
181
|
+
out.shadow[at] = shadows === null ? -1 : shadows.layers[i];
|
|
182
|
+
out.shadow[at + 1] = shadows === null ? 0 : shadows.near[i];
|
|
183
|
+
out.shadow[at + 2] = shadows === null ? 0 : shadows.presence[i];
|
|
184
|
+
out.shadow[at + 3] = 0;
|
|
185
|
+
out.live[at] = shadows === null ? -1 : shadows.liveLayers[i];
|
|
186
|
+
out.live[at + 1] = shadows === null ? 0 : shadows.liveNear[i];
|
|
187
|
+
out.live[at + 2] = shadows === null ? 0 : shadows.liveWeights[i];
|
|
188
|
+
out.live[at + 3] = 0;
|
|
189
|
+
for (let k = 0; k < 4; k++) {
|
|
190
|
+
out.shadowAt[at + k] = shadows === null ? 1 : shadows.projections[at + k];
|
|
191
|
+
out.liveAt[at + k] = shadows === null ? 1 : shadows.liveProjections[at + k];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
2
|
-
import type {
|
|
3
|
-
/** What the march needs about the light
|
|
2
|
+
import type { ResolvedGlobalMedium, ResolvedMediumLamps } from './globalMedium.ts';
|
|
3
|
+
/** What the march needs about the light: the sun, the sky, and the lamps that scatter. */
|
|
4
4
|
export interface MediumLight {
|
|
5
5
|
/** Surface → sun, normalised. `Environment.directionalDir` unchanged. */
|
|
6
6
|
readonly sunDir: ArrayLike<number>;
|
|
@@ -16,6 +16,10 @@ export interface MediumLight {
|
|
|
16
16
|
/** The sun's static, moving and peeled maps, as one array. See `shadowMap.ts`. */
|
|
17
17
|
readonly sunShadows: WebGLTexture;
|
|
18
18
|
readonly peeledEnabled: boolean;
|
|
19
|
+
/** The lamps that scatter, as `resolveMediumLamps` lays them out: none while `lampScatter` is 0. */
|
|
20
|
+
readonly lamps: ResolvedMediumLamps;
|
|
21
|
+
/** The lamps' shadow array, the lit stage's; the placeholder where there is none. */
|
|
22
|
+
readonly pointShadows: WebGLTexture;
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
21
25
|
* The global participating medium on WebGL2: a half-res march, then a depth-aware composite.
|
|
@@ -90,7 +94,7 @@ export declare class GlobalMediumPass {
|
|
|
90
94
|
* `DEPTH_01_TO_CLIP` carries a stored depth back into the clip space the first was built in,
|
|
91
95
|
* and pairing either with a clip-corrected matrix applies the remap twice.
|
|
92
96
|
*/
|
|
93
|
-
marchMedium(depth: WebGLTexture, frameWidth: number, frameHeight: number, half: boolean, steps: number, options:
|
|
97
|
+
marchMedium(depth: WebGLTexture, frameWidth: number, frameHeight: number, half: boolean, steps: number, options: ResolvedGlobalMedium, viewProjection: ReadonlyMat4, projection: ReadonlyMat4, eye: ArrayLike<number>, light: MediumLight): boolean;
|
|
94
98
|
/**
|
|
95
99
|
* Composite the march over whatever framebuffer is bound, upsampled depth-aware.
|
|
96
100
|
*
|
|
@@ -161,13 +161,19 @@ export class GlobalMediumPass {
|
|
|
161
161
|
gl.activeTexture(gl.TEXTURE1);
|
|
162
162
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, light.sunShadows);
|
|
163
163
|
gl.uniform1i(u['uSunShadows'] ?? null, 1);
|
|
164
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
165
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, light.pointShadows);
|
|
166
|
+
gl.uniform1i(u['uPointShadows'] ?? null, 2);
|
|
164
167
|
gl.uniformMatrix4fv(u['uDepthToWorld'] ?? null, false, this.depthToWorld);
|
|
165
168
|
gl.uniform3f(u['uCameraPos'] ?? null, eye[0] ?? 0, eye[1] ?? 0, eye[2] ?? 0);
|
|
166
169
|
gl.uniform3f(u['uSunDir'] ?? null, light.sunDir[0] ?? 0, light.sunDir[1] ?? 1, light.sunDir[2] ?? 0);
|
|
167
170
|
gl.uniform3f(u['uSunColor'] ?? null, light.sunColor[0] ?? 0, light.sunColor[1] ?? 0, light.sunColor[2] ?? 0);
|
|
168
171
|
gl.uniform3f(u['uAmbient'] ?? null, light.ambient[0] ?? 0, light.ambient[1] ?? 0, light.ambient[2] ?? 0);
|
|
169
172
|
gl.uniform1f(u['uDensity'] ?? null, options.density);
|
|
170
|
-
gl.
|
|
173
|
+
gl.uniform2f(u['uHeight'] ?? null, options.heightFalloff, options.baseY);
|
|
174
|
+
gl.uniform3f(u['uLayer2'] ?? null, options.density2, options.heightFalloff2, options.baseY2);
|
|
175
|
+
gl.uniform3f(u['uAlbedo'] ?? null, options.albedo[0], options.albedo[1], options.albedo[2]);
|
|
176
|
+
gl.uniform2f(u['uScatter'] ?? null, options.sunScatter, options.skyScatter);
|
|
171
177
|
gl.uniform1f(u['uAnisotropy'] ?? null, options.anisotropy);
|
|
172
178
|
gl.uniform1f(u['uMaxDistance'] ?? null, options.maxDistance);
|
|
173
179
|
gl.uniform1i(u['uSteps'] ?? null, steps);
|
|
@@ -175,6 +181,20 @@ export class GlobalMediumPass {
|
|
|
175
181
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, light.lightViewProj);
|
|
176
182
|
gl.uniformMatrix4fv(u['uMovingLightViewProj'] ?? null, false, light.movingLightViewProj);
|
|
177
183
|
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, light.peeledEnabled ? 1 : 0);
|
|
184
|
+
const lamps = light.lamps;
|
|
185
|
+
gl.uniform1i(u['uLampCount'] ?? null, lamps.count);
|
|
186
|
+
gl.uniform1i(u['uLampFalloff'] ?? null, lamps.falloff);
|
|
187
|
+
/* As `name[0]`, the only spelling an array uniform answers to: see `bindPointShadows`. */
|
|
188
|
+
if (lamps.count > 0) {
|
|
189
|
+
gl.uniform4fv(u['uLampPosition[0]'] ?? null, lamps.position);
|
|
190
|
+
gl.uniform4fv(u['uLampColor[0]'] ?? null, lamps.color);
|
|
191
|
+
gl.uniform4fv(u['uLampAim[0]'] ?? null, lamps.aim);
|
|
192
|
+
gl.uniform4fv(u['uLampCone[0]'] ?? null, lamps.cone);
|
|
193
|
+
gl.uniform4fv(u['uLampShadow[0]'] ?? null, lamps.shadow);
|
|
194
|
+
gl.uniform4fv(u['uLampShadowAt[0]'] ?? null, lamps.shadowAt);
|
|
195
|
+
gl.uniform4fv(u['uLampLive[0]'] ?? null, lamps.live);
|
|
196
|
+
gl.uniform4fv(u['uLampLiveAt[0]'] ?? null, lamps.liveAt);
|
|
197
|
+
}
|
|
178
198
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
179
199
|
gl.bindVertexArray(null);
|
|
180
200
|
/* Released down to unit 0, for the reason `SsrPass` records: a binding outlives the frame that
|
|
@@ -182,6 +202,7 @@ export class GlobalMediumPass {
|
|
|
182
202
|
for (const unit of [gl.TEXTURE3, gl.TEXTURE2, gl.TEXTURE1, gl.TEXTURE0]) {
|
|
183
203
|
gl.activeTexture(unit);
|
|
184
204
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
205
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
185
206
|
}
|
|
186
207
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
187
208
|
return true;
|
|
@@ -46,6 +46,16 @@ export declare const HAIR_MIN_BETA = 0.05;
|
|
|
46
46
|
* and its integral 4π times that. Over π: **1.33**.
|
|
47
47
|
*/
|
|
48
48
|
export declare const HAIR_SCATTER_SPHERE = 1.33;
|
|
49
|
+
/**
|
|
50
|
+
* How far every lobe is widened for light from every side, in radians: the angle a source would
|
|
51
|
+
* subtend to widen them as much, `sourceRadius / 2·dist` in `hairLight`'s terms.
|
|
52
|
+
*
|
|
53
|
+
* **0.2, the real-time form's own value**, which stands one direction in for the sphere: a lobe as
|
|
54
|
+
* narrow as a lamp's would put the whole sky's light into one highlight wherever the eye's
|
|
55
|
+
* direction happens to meet it. What it gives up is the sky's shape: a sun low in a clear sky and
|
|
56
|
+
* an even overcast light hair through the same widened lobes.
|
|
57
|
+
*/
|
|
58
|
+
export declare const HAIR_AREA = 0.2;
|
|
49
59
|
/** A longitudinal lobe: a normalised Gaussian of width `beta` at `x` from its centre. */
|
|
50
60
|
export declare function hairLongitudinal(beta: number, x: number): number;
|
|
51
61
|
/** Schlick's Fresnel for the strand at a cosine, clamped to the unit interval. */
|
|
@@ -61,10 +71,25 @@ export declare const HAIR_OUT: {
|
|
|
61
71
|
/**
|
|
62
72
|
* One light on a strand, in lamp units and unshadowed, into `out` (`HAIR_OUT`'s layout: R is grey,
|
|
63
73
|
* the rest colours). `tangent`, `toLight` and `toEye` are unit vectors but for a tangent that could
|
|
64
|
-
* not be made one, which comes in as zeros; `fibre` is the base colour
|
|
74
|
+
* not be made one, which comes in as zeros; `fibre` is the base colour; `specular` is the strand's,
|
|
75
|
+
* the model map's red, which scales the reflected lobe by twice it, so 0.5 draws the lobe as the
|
|
76
|
+
* fit gives it and 0 is a strand that reflects nothing off its surface.
|
|
65
77
|
*
|
|
66
78
|
* **A lamp with a size widens every lobe by the angle it subtends**, `sourceRadius / 2·dist` as
|
|
67
79
|
* `sphereLobe` takes it, and the lobes are normalised Gaussians, so a wider one carries the same
|
|
68
80
|
* light rather than more. The sun passes zeros.
|
|
69
81
|
*/
|
|
70
|
-
export declare function hairLight(tangent: ArrayLike<number>, toLight: ArrayLike<number>, toEye: ArrayLike<number>, fibre: Vec3, roughness: number, shift: number, scatter: number, backlit: number, sourceRadius: number, dist: number, out: Float64Array): void;
|
|
82
|
+
export declare function hairLight(tangent: ArrayLike<number>, toLight: ArrayLike<number>, toEye: ArrayLike<number>, fibre: Vec3, roughness: number, shift: number, specular: number, scatter: number, backlit: number, sourceRadius: number, dist: number, out: Float64Array): void;
|
|
83
|
+
/**
|
|
84
|
+
* The share of light from every side a strand returns, a colour into `out`, with the one
|
|
85
|
+
* direction's lobes it is made of into `lobes` (`HAIR_OUT`'s layout). `normal` is the surface's,
|
|
86
|
+
* which stands in for the flattened eye where the eye runs along the strand and there is none.
|
|
87
|
+
*
|
|
88
|
+
* **One direction for the sphere**: the eye flattened across the strand, `V − T(V·T)`, unshadowed,
|
|
89
|
+
* with no light from behind and so no transmitted lobe, every lobe widened by `HAIR_AREA`. Twice
|
|
90
|
+
* the sum, and at most all of what arrives. **Why twice**: the scatter's half is then
|
|
91
|
+
* `HAIR_SCATTER_SPHERE`, its own answer to an even sky — along the flattened eye its shape is
|
|
92
|
+
* `mix(1/2, 1, 0.33)`, 0.665, which doubled is that constant's 1.33 — so the lobes ride on a
|
|
93
|
+
* scatter the sphere's quadrature already holds to, and R and TRT are what the direction adds.
|
|
94
|
+
*/
|
|
95
|
+
export declare function hairIndirect(tangent: ArrayLike<number>, toEye: ArrayLike<number>, normal: ArrayLike<number>, fibre: Vec3, roughness: number, shift: number, specular: number, scatter: number, lobes: Float64Array, out: Float64Array): void;
|