@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/src/render/fog.ts
CHANGED
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
* one argument instead, and the backends keep their own packing.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
createResolvedAtmosphere,
|
|
22
|
+
resolveAtmosphere,
|
|
23
|
+
type Atmosphere,
|
|
24
|
+
type ResolvedAtmosphere,
|
|
25
|
+
} from './atmosphere.ts';
|
|
21
26
|
|
|
22
27
|
/** A colour as three numbers, matching `render/orderIndependent.ts`'s own `Rgb`. */
|
|
23
28
|
export type Rgb = readonly [number, number, number];
|
|
@@ -46,6 +51,25 @@ export interface FogOptions {
|
|
|
46
51
|
*/
|
|
47
52
|
readonly near: number;
|
|
48
53
|
readonly far: number;
|
|
54
|
+
/** The medium's second layer: its extinction per metre at the camera's height. Absent, none. */
|
|
55
|
+
readonly density2?: number;
|
|
56
|
+
/** And its reciprocal scale height. */
|
|
57
|
+
readonly heightFalloff2?: number;
|
|
58
|
+
/**
|
|
59
|
+
* The least of a surface the haze must leave, `Atmosphere.fogMaxOpacity`'s complement. Absent or 0,
|
|
60
|
+
* no floor.
|
|
61
|
+
*/
|
|
62
|
+
readonly floor?: number;
|
|
63
|
+
/** Metres past which the haze hides nothing. Absent or 0, none. */
|
|
64
|
+
readonly cutoff?: number;
|
|
65
|
+
/** The sun's glow in the haze: its colour. Absent or black, none. */
|
|
66
|
+
readonly glowColour?: Rgb;
|
|
67
|
+
/** The glow's lobe: the cosine of the angle from its light, to this power. */
|
|
68
|
+
readonly glowExponent?: number;
|
|
69
|
+
/** Surface → the glow's light, a unit vector. */
|
|
70
|
+
readonly glowDirection?: Rgb;
|
|
71
|
+
/** Metres past the haze's start before the glow begins. */
|
|
72
|
+
readonly glowStart?: number;
|
|
49
73
|
}
|
|
50
74
|
|
|
51
75
|
/**
|
|
@@ -82,15 +106,37 @@ export function mediumFog(fog: FogOptions, dist: number, pointY: number): number
|
|
|
82
106
|
* is perfectly clear, which Beer-Lambert can never be, and height falloff has no meaning here
|
|
83
107
|
* because a ramp is defined on distance alone. `shaders/fog.ts` argues the whole of it.
|
|
84
108
|
*/
|
|
109
|
+
/* Past the cutoff the air hides nothing, in either mode; the water column keeps its own law. */
|
|
110
|
+
const cutoff = fog.cutoff ?? 0;
|
|
111
|
+
const cut = cutoff > 0 && dist > cutoff;
|
|
112
|
+
const floor = fog.floor ?? 0;
|
|
85
113
|
if (fog.mode === LINEAR_FOG) {
|
|
86
114
|
/* A consumer asking for a hard cut writes `far === near`; the guard is why that is a cut. */
|
|
87
115
|
const span = Math.max(fog.far - fog.near, 1e-4);
|
|
88
|
-
const ramp = Math.min(1, Math.max(0, (dist - fog.near) / span));
|
|
116
|
+
const ramp = cut ? 0 : Math.min(Math.min(1, Math.max(0, (dist - fog.near) / span)), 1 - floor);
|
|
89
117
|
if (fog.underwaterFactor <= 0) return ramp;
|
|
90
118
|
const wetLinear = fog.underwaterDensity * dist;
|
|
91
119
|
return mix(ramp, 1 - Math.pow(2, -wetLinear * wetLinear * 1.442695), fog.underwaterFactor);
|
|
92
120
|
}
|
|
93
121
|
|
|
122
|
+
/*
|
|
123
|
+
* Beer-Lambert through both layers, leaving at least `floor` of the surface: a floor on what
|
|
124
|
+
* survives rather than a ceiling on the depth, so the curve keeps its shape above it.
|
|
125
|
+
*/
|
|
126
|
+
const air = cut ? 0 : 1 - Math.max(Math.exp(-hazeDepth(fog, dist, pointY)), floor);
|
|
127
|
+
if (fog.underwaterFactor <= 0) return air;
|
|
128
|
+
/* Water is its own medium and keeps its own curve, so crossing the surface changes nothing
|
|
129
|
+
about how the water column reads. */
|
|
130
|
+
const wet = fog.underwaterDensity * dist;
|
|
131
|
+
return mix(air, 1 - Math.pow(2, -wet * wet * 1.442695), fog.underwaterFactor);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The haze's optical depth from where it begins along the ray to a point at `pointY`, `dist` metres
|
|
136
|
+
* away: each layer's density at the eye carried over what is left of the ray, the second only where
|
|
137
|
+
* there is one. `mediumFog` and `mediumGlow` both integrate it.
|
|
138
|
+
*/
|
|
139
|
+
export function hazeDepth(fog: FogOptions, dist: number, pointY: number): number {
|
|
94
140
|
/*
|
|
95
141
|
* **Where the medium begins along the ray**, `near` — the linear mode's slot, which this mode
|
|
96
142
|
* never read before `Atmosphere.fogStart`. The ray is integrated from that point on: the density
|
|
@@ -106,16 +152,55 @@ export function mediumFog(fog: FogOptions, dist: number, pointY: number): number
|
|
|
106
152
|
* `t = 0` the quotient is 0/0, and 1e-4 of a scale height is 8 mm.
|
|
107
153
|
*/
|
|
108
154
|
const denom = Math.abs(rest) < 1e-4 ? 1e-4 : rest;
|
|
109
|
-
|
|
110
|
-
1 -
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
155
|
+
let depth =
|
|
156
|
+
(fog.density * Math.exp(-t * before) * (dist - start) * (1 - Math.exp(-denom))) / denom;
|
|
157
|
+
const density2 = fog.density2 ?? 0;
|
|
158
|
+
if (density2 > 0) {
|
|
159
|
+
const t2 = (pointY - fog.eyeY) * (fog.heightFalloff2 ?? 0);
|
|
160
|
+
const rest2 = t2 * (1 - before);
|
|
161
|
+
const denom2 = Math.abs(rest2) < 1e-4 ? 1e-4 : rest2;
|
|
162
|
+
depth +=
|
|
163
|
+
(density2 * Math.exp(-t2 * before) * (dist - start) * (1 - Math.exp(-denom2))) / denom2;
|
|
164
|
+
}
|
|
165
|
+
return depth;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The sun's light the haze scatters toward the eye along a ray to a point at `pointY`, `dist`
|
|
170
|
+
* metres away in the unit direction `toPoint`, into `out`: added after the fog, never mixed. A lobe
|
|
171
|
+
* of `cos^exponent` about the light, by as much of the ray past the glow's start as the haze hides.
|
|
172
|
+
* None in a linear ramp, past the cutoff, or under water.
|
|
173
|
+
*/
|
|
174
|
+
export function mediumGlow(
|
|
175
|
+
fog: FogOptions,
|
|
176
|
+
dist: number,
|
|
177
|
+
pointY: number,
|
|
178
|
+
toPoint: ArrayLike<number>,
|
|
179
|
+
out: [number, number, number],
|
|
180
|
+
): [number, number, number] {
|
|
181
|
+
out[0] = 0;
|
|
182
|
+
out[1] = 0;
|
|
183
|
+
out[2] = 0;
|
|
184
|
+
const colour = fog.glowColour;
|
|
185
|
+
if (fog.mode === LINEAR_FOG || colour === undefined) return out;
|
|
186
|
+
if (Math.max(colour[0], colour[1], colour[2]) <= 0) return out;
|
|
187
|
+
const cutoff = fog.cutoff ?? 0;
|
|
188
|
+
if (cutoff > 0 && dist > cutoff) return out;
|
|
189
|
+
const span = dist - Math.min(fog.near, dist);
|
|
190
|
+
if (span <= 0) return out;
|
|
191
|
+
const perMetre = hazeDepth(fog, dist, pointY) / span;
|
|
192
|
+
const hidden = 1 - Math.exp(-perMetre * Math.max(span - (fog.glowStart ?? 0), 0));
|
|
193
|
+
const toward = fog.glowDirection ?? [0, 1, 0];
|
|
194
|
+
const cosine =
|
|
195
|
+
(toPoint[0] as number) * toward[0] +
|
|
196
|
+
(toPoint[1] as number) * toward[1] +
|
|
197
|
+
(toPoint[2] as number) * toward[2];
|
|
198
|
+
const lobe = Math.pow(Math.min(1, Math.max(0, cosine)), fog.glowExponent ?? 1);
|
|
199
|
+
const share = lobe * hidden * (1 - fog.underwaterFactor);
|
|
200
|
+
out[0] = colour[0] * share;
|
|
201
|
+
out[1] = colour[1] * share;
|
|
202
|
+
out[2] = colour[2] * share;
|
|
203
|
+
return out;
|
|
119
204
|
}
|
|
120
205
|
|
|
121
206
|
/** `FogOptions` with its fields writable, which is what a caller holds and refills a frame at a time. */
|
|
@@ -130,6 +215,14 @@ export interface FogTarget {
|
|
|
130
215
|
mode: number;
|
|
131
216
|
near: number;
|
|
132
217
|
far: number;
|
|
218
|
+
density2: number;
|
|
219
|
+
heightFalloff2: number;
|
|
220
|
+
floor: number;
|
|
221
|
+
cutoff: number;
|
|
222
|
+
glowColour: [number, number, number];
|
|
223
|
+
glowExponent: number;
|
|
224
|
+
glowDirection: [number, number, number];
|
|
225
|
+
glowStart: number;
|
|
133
226
|
}
|
|
134
227
|
|
|
135
228
|
/** A target for `atmosphereFog`, allocated once, outside the frame loop. */
|
|
@@ -145,22 +238,19 @@ export function createFogTarget(): FogTarget {
|
|
|
145
238
|
mode: MEDIUM_FOG,
|
|
146
239
|
near: 0,
|
|
147
240
|
far: 1,
|
|
241
|
+
density2: 0,
|
|
242
|
+
heightFalloff2: 0,
|
|
243
|
+
floor: 0,
|
|
244
|
+
cutoff: 0,
|
|
245
|
+
glowColour: [0, 0, 0],
|
|
246
|
+
glowExponent: 1,
|
|
247
|
+
glowDirection: [0, 1, 0],
|
|
248
|
+
glowStart: 0,
|
|
148
249
|
};
|
|
149
250
|
}
|
|
150
251
|
|
|
151
252
|
/** `resolveAtmosphere`'s own target, reused, because this runs a frame at a time. */
|
|
152
|
-
const RESOLVED: ResolvedAtmosphere =
|
|
153
|
-
fogColor: new Float32Array(3),
|
|
154
|
-
underwaterColor: new Float32Array(3),
|
|
155
|
-
fogDensity: 0,
|
|
156
|
-
fogHeightFalloff: 0,
|
|
157
|
-
fogEyeY: 0,
|
|
158
|
-
fogMode: 0,
|
|
159
|
-
fogNear: 0,
|
|
160
|
-
fogFar: 1,
|
|
161
|
-
underwaterFogDensity: 0,
|
|
162
|
-
underwaterFactor: 0,
|
|
163
|
-
};
|
|
253
|
+
const RESOLVED: ResolvedAtmosphere = createResolvedAtmosphere();
|
|
164
254
|
|
|
165
255
|
/**
|
|
166
256
|
* The haze an `Environment` makes at a camera's height, as `GpuDrivenView.fog` takes it.
|
|
@@ -177,7 +267,7 @@ const RESOLVED: ResolvedAtmosphere = {
|
|
|
177
267
|
* on the forward path; pass the same.
|
|
178
268
|
*/
|
|
179
269
|
export function atmosphereFog(
|
|
180
|
-
atmosphere: Atmosphere,
|
|
270
|
+
atmosphere: Atmosphere & { readonly directionalDir?: ArrayLike<number> },
|
|
181
271
|
eyeY: number,
|
|
182
272
|
underwater: boolean,
|
|
183
273
|
out: FogTarget,
|
|
@@ -197,5 +287,15 @@ export function atmosphereFog(
|
|
|
197
287
|
out.mode = medium.fogMode;
|
|
198
288
|
out.near = medium.fogNear;
|
|
199
289
|
out.far = medium.fogFar;
|
|
290
|
+
out.density2 = medium.fogLayer2[0] as number;
|
|
291
|
+
out.heightFalloff2 = medium.fogLayer2[1] as number;
|
|
292
|
+
out.floor = medium.fogLayer2[2] as number;
|
|
293
|
+
out.cutoff = medium.fogLayer2[3] as number;
|
|
294
|
+
for (let c = 0; c < 3; c++) {
|
|
295
|
+
out.glowColour[c] = medium.fogGlow[c] as number;
|
|
296
|
+
out.glowDirection[c] = medium.fogGlowDir[c] as number;
|
|
297
|
+
}
|
|
298
|
+
out.glowExponent = medium.fogGlow[3] as number;
|
|
299
|
+
out.glowStart = medium.fogGlowDir[3] as number;
|
|
200
300
|
return out;
|
|
201
301
|
}
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
* only one of the two that can put the shape of a doorway on a floor.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import type { Vec3 } from '../math/color.ts';
|
|
16
|
+
import type { FogLayer } from './atmosphere.ts';
|
|
17
|
+
import { MAX_POINT_LIGHTS } from './lightBudget.ts';
|
|
18
|
+
import type { ResolvedPointLights } from './lightBudget.ts';
|
|
19
|
+
import type { ResolvedPointShadows } from './pointShadowImage.ts';
|
|
20
|
+
|
|
15
21
|
/**
|
|
16
22
|
* The most steps a march may take, which is the shader's own loop bound.
|
|
17
23
|
*
|
|
@@ -41,6 +47,12 @@ export const MAX_GLOBAL_MEDIUM_STEPS = 64;
|
|
|
41
47
|
*/
|
|
42
48
|
export const WEAK_GPU_MEDIUM_STEPS = 16;
|
|
43
49
|
|
|
50
|
+
/**
|
|
51
|
+
* The most lamps the march lights the medium with, which is the lit stage's own per-pixel set:
|
|
52
|
+
* the lamps a frame shades with uniforms, each with the shadow it already has.
|
|
53
|
+
*/
|
|
54
|
+
export const MAX_MEDIUM_LAMPS = MAX_POINT_LIGHTS;
|
|
55
|
+
|
|
44
56
|
/**
|
|
45
57
|
* How thick the air is, this frame.
|
|
46
58
|
*
|
|
@@ -58,13 +70,15 @@ export interface GlobalMediumOptions {
|
|
|
58
70
|
*/
|
|
59
71
|
readonly density: number;
|
|
60
72
|
/**
|
|
61
|
-
* How much of what the medium takes out of a ray comes back as light rather than as heat, 0 to 1
|
|
73
|
+
* How much of what the medium takes out of a ray comes back as light rather than as heat, 0 to 1,
|
|
74
|
+
* or a colour, a share a channel.
|
|
62
75
|
*
|
|
63
76
|
* Near 1 for water droplets, which is fog, cloud and steam; lower for smoke, which is soot and
|
|
64
77
|
* absorbs most of what it intercepts. It is the difference between a white fog that a torch
|
|
65
|
-
* lights up and a black smoke that swallows the same torch.
|
|
78
|
+
* lights up and a black smoke that swallows the same torch. A colour is a medium that keeps more
|
|
79
|
+
* of one light than another: a faintly blue haze returns the sky's blue a little more readily.
|
|
66
80
|
*/
|
|
67
|
-
readonly albedo: number;
|
|
81
|
+
readonly albedo: number | Vec3;
|
|
68
82
|
/**
|
|
69
83
|
* Henyey-Greenstein anisotropy, -1 back to 1 forward. 0 is isotropic.
|
|
70
84
|
*
|
|
@@ -75,14 +89,59 @@ export interface GlobalMediumOptions {
|
|
|
75
89
|
*/
|
|
76
90
|
readonly anisotropy: number;
|
|
77
91
|
/**
|
|
78
|
-
* Where the
|
|
92
|
+
* Where the medium ends, in metres: past it the air neither scatters nor absorbs.
|
|
79
93
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
94
|
+
* **This said extinction went on past it, and the march never did that**: it integrates from
|
|
95
|
+
* the eye to here or to the surface, whichever is nearer, and nothing after. Which is what a
|
|
96
|
+
* medium standing for the near part of the environment's haze wants: the haze takes the rest of
|
|
97
|
+
* the ray when its `fogStart` is this distance, and the two never lay one stretch of air twice.
|
|
98
|
+
* **What it gives up** is a medium of its own beyond the reach, which the haze has to be.
|
|
84
99
|
*/
|
|
85
100
|
readonly maxDistance: number;
|
|
101
|
+
/**
|
|
102
|
+
* Reciprocal scale height, per metre: `density` is the extinction at `baseY`, thinning above it
|
|
103
|
+
* as the haze does (`Atmosphere.fogHeightFalloff`). Absent or 0, the medium is as thick
|
|
104
|
+
* everywhere, as it always was.
|
|
105
|
+
*/
|
|
106
|
+
readonly heightFalloff?: number;
|
|
107
|
+
/** The height `density` is measured at. Default 0. */
|
|
108
|
+
readonly baseY?: number;
|
|
109
|
+
/**
|
|
110
|
+
* A second layer, summed with the first, its own density at its own height thinning by its own
|
|
111
|
+
* falloff: `Atmosphere.fogLayer2`'s shape, so a medium standing for the near part of a two-layer
|
|
112
|
+
* haze can be that haze.
|
|
113
|
+
*/
|
|
114
|
+
readonly layer2?: FogLayer | null;
|
|
115
|
+
/** How strongly the sun scatters in it, as a share of how strongly it lights a surface. Default 1. */
|
|
116
|
+
readonly sunScatter?: number;
|
|
117
|
+
/** The same for the sky's fill, which lights a shadowed medium from every side. Default 1. */
|
|
118
|
+
readonly skyScatter?: number;
|
|
119
|
+
/**
|
|
120
|
+
* The same for the lamps, each through its own shadow and times its own `lightMediumScatter`.
|
|
121
|
+
* **Default 0**: a medium was the sun's and the sky's until there was this, and a scene that
|
|
122
|
+
* never asks keeps that picture and that cost. The lamps are the lit stage's per-pixel set,
|
|
123
|
+
* shaped by their cones; a photometric profile and a cookie shape the pool on a surface and not
|
|
124
|
+
* the shaft in the air.
|
|
125
|
+
*/
|
|
126
|
+
readonly lampScatter?: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** A medium with every field filled in and clamped: what both backends' passes are handed. */
|
|
130
|
+
export interface ResolvedGlobalMedium {
|
|
131
|
+
readonly density: number;
|
|
132
|
+
/** A colour, three shares. */
|
|
133
|
+
readonly albedo: readonly [number, number, number];
|
|
134
|
+
readonly anisotropy: number;
|
|
135
|
+
readonly maxDistance: number;
|
|
136
|
+
readonly heightFalloff: number;
|
|
137
|
+
readonly baseY: number;
|
|
138
|
+
/** The second layer's density at its own height, 0 for none, its falloff and that height. */
|
|
139
|
+
readonly density2: number;
|
|
140
|
+
readonly heightFalloff2: number;
|
|
141
|
+
readonly baseY2: number;
|
|
142
|
+
readonly sunScatter: number;
|
|
143
|
+
readonly skyScatter: number;
|
|
144
|
+
readonly lampScatter: number;
|
|
86
145
|
}
|
|
87
146
|
|
|
88
147
|
/**
|
|
@@ -107,17 +166,62 @@ export const DEFAULT_GLOBAL_MEDIUM: GlobalMediumOptions = {
|
|
|
107
166
|
* function by zero, which is a NaN that a half-float target spreads over the whole screen rather
|
|
108
167
|
* than reporting; a `maxDistance` of zero is a march with no length that still costs its steps.
|
|
109
168
|
*/
|
|
110
|
-
export function resolveGlobalMedium(options: Partial<GlobalMediumOptions>):
|
|
169
|
+
export function resolveGlobalMedium(options: Partial<GlobalMediumOptions>): ResolvedGlobalMedium {
|
|
111
170
|
const d = DEFAULT_GLOBAL_MEDIUM;
|
|
171
|
+
const albedo = options.albedo ?? d.albedo;
|
|
172
|
+
const share = (value: number | undefined): number =>
|
|
173
|
+
Math.min(1, Math.max(0, Number.isFinite(value) ? (value as number) : 0));
|
|
174
|
+
const layer2 = options.layer2 ?? null;
|
|
175
|
+
const density2 = layer2 === null ? 0 : Math.max(0, layer2.density);
|
|
176
|
+
const finite = (value: number | undefined, fallback: number): number =>
|
|
177
|
+
value !== undefined && Number.isFinite(value) ? value : fallback;
|
|
112
178
|
return {
|
|
113
179
|
density: Math.max(0, options.density ?? d.density),
|
|
114
|
-
albedo:
|
|
180
|
+
albedo:
|
|
181
|
+
typeof albedo === 'number'
|
|
182
|
+
? [share(albedo), share(albedo), share(albedo)]
|
|
183
|
+
: [share(albedo[0]), share(albedo[1]), share(albedo[2])],
|
|
115
184
|
/* Short of the poles, where `(1 - g^2)` over `(1 + g^2 - 2g)^1.5` is 0/0. */
|
|
116
185
|
anisotropy: Math.min(0.95, Math.max(-0.95, options.anisotropy ?? d.anisotropy)),
|
|
117
186
|
maxDistance: Math.max(1e-3, options.maxDistance ?? d.maxDistance),
|
|
187
|
+
heightFalloff: finite(options.heightFalloff, 0),
|
|
188
|
+
baseY: finite(options.baseY, 0),
|
|
189
|
+
density2,
|
|
190
|
+
heightFalloff2: layer2 === null ? 0 : finite(layer2.heightFalloff, 0),
|
|
191
|
+
baseY2: layer2 === null ? 0 : finite(layer2.baseY, 0),
|
|
192
|
+
sunScatter: Math.max(0, finite(options.sunScatter, 1)),
|
|
193
|
+
skyScatter: Math.max(0, finite(options.skyScatter, 1)),
|
|
194
|
+
lampScatter: Math.max(0, finite(options.lampScatter, 0)),
|
|
118
195
|
};
|
|
119
196
|
}
|
|
120
197
|
|
|
198
|
+
/** `setGlobalMedium`'s object form: a density, and any of the rest. */
|
|
199
|
+
export type GlobalMediumSettings = Partial<GlobalMediumOptions> & { readonly density: number };
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* `setGlobalMedium`'s two forms, its four numbers or the object, read one way for both backends:
|
|
203
|
+
* a density that is not a number is none.
|
|
204
|
+
*/
|
|
205
|
+
export function resolveGlobalMediumSettings(
|
|
206
|
+
density: number | GlobalMediumSettings,
|
|
207
|
+
albedo?: number | Vec3,
|
|
208
|
+
anisotropy?: number,
|
|
209
|
+
maxDistance?: number,
|
|
210
|
+
): ResolvedGlobalMedium {
|
|
211
|
+
if (typeof density === 'object') {
|
|
212
|
+
return resolveGlobalMedium({
|
|
213
|
+
...density,
|
|
214
|
+
density: Number.isFinite(density.density) ? density.density : 0,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return resolveGlobalMedium({
|
|
218
|
+
density: Number.isFinite(density) ? density : 0,
|
|
219
|
+
albedo,
|
|
220
|
+
anisotropy,
|
|
221
|
+
maxDistance,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
121
225
|
/**
|
|
122
226
|
* Whether this frame draws the medium at all.
|
|
123
227
|
*
|
|
@@ -125,7 +229,10 @@ export function resolveGlobalMedium(options: Partial<GlobalMediumOptions>): Glob
|
|
|
125
229
|
* `density: 0` is pixel-identical to the pass being absent is only true if neither backend runs a
|
|
126
230
|
* composite at zero, and a second copy of this test is where that stops being true.
|
|
127
231
|
*/
|
|
128
|
-
export function mediumActive(
|
|
232
|
+
export function mediumActive(
|
|
233
|
+
options: Pick<GlobalMediumOptions, 'density'>,
|
|
234
|
+
steps: number,
|
|
235
|
+
): boolean {
|
|
129
236
|
return options.density > 0 && steps > 0;
|
|
130
237
|
}
|
|
131
238
|
|
|
@@ -152,3 +259,99 @@ export function mediumTargetSize(
|
|
|
152
259
|
height: Math.max(1, Math.ceil(Math.max(1, height) / divisor)),
|
|
153
260
|
};
|
|
154
261
|
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* The lamps as the march reads them: eight vectors a lamp, each array `MAX_MEDIUM_LAMPS` of them.
|
|
265
|
+
*
|
|
266
|
+
* **Decided here and bound twice**, on the split `resolvePointLights` draws: which lamps light the
|
|
267
|
+
* medium, how bright and through which maps is arithmetic both backends run, and a backend only
|
|
268
|
+
* uploads the arrays. A lamp's shadows are the lit stage's own, the static map and the live one,
|
|
269
|
+
* read where the frame left them, so the shaft in the air and the pool on the floor are cut by the
|
|
270
|
+
* same casters.
|
|
271
|
+
*/
|
|
272
|
+
export interface ResolvedMediumLamps {
|
|
273
|
+
/** How many lamps light the medium: none while `lampScatter` is 0. */
|
|
274
|
+
count: number;
|
|
275
|
+
/** 1 for inverse-square, 0 for the smooth falloff: the frame's own rule. */
|
|
276
|
+
falloff: number;
|
|
277
|
+
/** Where each lamp is, and how far it reaches. */
|
|
278
|
+
readonly position: Float32Array;
|
|
279
|
+
/** Its colour, times its presence and its scatter, and its own falloff exponent (0 for none). */
|
|
280
|
+
readonly color: Float32Array;
|
|
281
|
+
/** The direction it shines, a spot's axis: `lightDirections`. */
|
|
282
|
+
readonly aim: Float32Array;
|
|
283
|
+
/** Its cone's two cosines, inner and outer, as the lit stage takes them. */
|
|
284
|
+
readonly cone: Float32Array;
|
|
285
|
+
/** Its static shadow: the layer (−1 for none), the near plane, and how present the map is. */
|
|
286
|
+
readonly shadow: Float32Array;
|
|
287
|
+
/** The static map's projection: its origin and its far plane. */
|
|
288
|
+
readonly shadowAt: Float32Array;
|
|
289
|
+
/** Its live shadow, by the same layout, and the live map's projection. */
|
|
290
|
+
readonly live: Float32Array;
|
|
291
|
+
readonly liveAt: Float32Array;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** A target for `resolveMediumLamps`, made once outside the frame loop. */
|
|
295
|
+
export function createResolvedMediumLamps(): ResolvedMediumLamps {
|
|
296
|
+
const vectors = (): Float32Array => new Float32Array(MAX_MEDIUM_LAMPS * 4);
|
|
297
|
+
return {
|
|
298
|
+
count: 0,
|
|
299
|
+
falloff: 0,
|
|
300
|
+
position: vectors(),
|
|
301
|
+
color: vectors(),
|
|
302
|
+
aim: vectors(),
|
|
303
|
+
cone: vectors(),
|
|
304
|
+
shadow: vectors(),
|
|
305
|
+
shadowAt: vectors(),
|
|
306
|
+
live: vectors(),
|
|
307
|
+
liveAt: vectors(),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* The frame's lamps as the medium is lit by them, into `out`: the lit stage's resolved lamps and
|
|
313
|
+
* their resolved shadows, a lamp's scatter `lampScatter` times its own `lightMediumScatter` (1 where
|
|
314
|
+
* absent). Allocates nothing; a lamp past the count keeps whatever it held, which nothing reads.
|
|
315
|
+
*/
|
|
316
|
+
export function resolveMediumLamps(
|
|
317
|
+
medium: ResolvedGlobalMedium,
|
|
318
|
+
lights: ResolvedPointLights,
|
|
319
|
+
shadows: ResolvedPointShadows | null,
|
|
320
|
+
scatter: Float32Array | undefined,
|
|
321
|
+
out: ResolvedMediumLamps,
|
|
322
|
+
): ResolvedMediumLamps {
|
|
323
|
+
const count = medium.lampScatter > 0 ? Math.min(lights.count, MAX_MEDIUM_LAMPS) : 0;
|
|
324
|
+
out.count = count;
|
|
325
|
+
out.falloff = lights.falloff;
|
|
326
|
+
const own = scatter !== undefined && scatter.length >= count ? scatter : undefined;
|
|
327
|
+
for (let i = 0; i < count; i++) {
|
|
328
|
+
const at = i * 4;
|
|
329
|
+
const strength =
|
|
330
|
+
medium.lampScatter * Math.max(0, own?.[i] ?? 1) * (lights.weights[i] as number);
|
|
331
|
+
for (let c = 0; c < 3; c++) {
|
|
332
|
+
out.position[at + c] = lights.positions[i * 3 + c] as number;
|
|
333
|
+
out.color[at + c] = (lights.colors[i * 3 + c] as number) * strength;
|
|
334
|
+
out.aim[at + c] = lights.directions[i * 3 + c] as number;
|
|
335
|
+
}
|
|
336
|
+
out.position[at + 3] = lights.radii[i] as number;
|
|
337
|
+
out.color[at + 3] = lights.conesAndFalloffs[at + 2] as number;
|
|
338
|
+
out.aim[at + 3] = 0;
|
|
339
|
+
out.cone[at] = lights.conesAndFalloffs[at] as number;
|
|
340
|
+
out.cone[at + 1] = lights.conesAndFalloffs[at + 1] as number;
|
|
341
|
+
out.cone[at + 2] = 0;
|
|
342
|
+
out.cone[at + 3] = 0;
|
|
343
|
+
out.shadow[at] = shadows === null ? -1 : (shadows.layers[i] as number);
|
|
344
|
+
out.shadow[at + 1] = shadows === null ? 0 : (shadows.near[i] as number);
|
|
345
|
+
out.shadow[at + 2] = shadows === null ? 0 : (shadows.presence[i] as number);
|
|
346
|
+
out.shadow[at + 3] = 0;
|
|
347
|
+
out.live[at] = shadows === null ? -1 : (shadows.liveLayers[i] as number);
|
|
348
|
+
out.live[at + 1] = shadows === null ? 0 : (shadows.liveNear[i] as number);
|
|
349
|
+
out.live[at + 2] = shadows === null ? 0 : (shadows.liveWeights[i] as number);
|
|
350
|
+
out.live[at + 3] = 0;
|
|
351
|
+
for (let k = 0; k < 4; k++) {
|
|
352
|
+
out.shadowAt[at + k] = shadows === null ? 1 : (shadows.projections[at + k] as number);
|
|
353
|
+
out.liveAt[at + k] = shadows === null ? 1 : (shadows.liveProjections[at + k] as number);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return out;
|
|
357
|
+
}
|
|
@@ -2,14 +2,14 @@ import { mat4 } from 'gl-matrix';
|
|
|
2
2
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
3
3
|
|
|
4
4
|
import { mediumTargetSize } from './globalMedium.ts';
|
|
5
|
-
import type {
|
|
5
|
+
import type { ResolvedGlobalMedium, ResolvedMediumLamps } from './globalMedium.ts';
|
|
6
6
|
import { DEPTH_01_TO_CLIP } from './lightVolumeDraw.ts';
|
|
7
7
|
import { compileProgram, uniformLocations } from './shader.ts';
|
|
8
8
|
import { FULLSCREEN_VERT } from './shaders/fullscreen.ts';
|
|
9
9
|
import { MEDIUM_FRAG, MEDIUM_UPSAMPLE_FRAG } from './shaders/globalMedium.ts';
|
|
10
10
|
import { blendCovering, blendTransmitting } from './sceneCoverage.ts';
|
|
11
11
|
|
|
12
|
-
/** What the march needs about the light
|
|
12
|
+
/** What the march needs about the light: the sun, the sky, and the lamps that scatter. */
|
|
13
13
|
export interface MediumLight {
|
|
14
14
|
/** Surface → sun, normalised. `Environment.directionalDir` unchanged. */
|
|
15
15
|
readonly sunDir: ArrayLike<number>;
|
|
@@ -25,6 +25,10 @@ export interface MediumLight {
|
|
|
25
25
|
/** The sun's static, moving and peeled maps, as one array. See `shadowMap.ts`. */
|
|
26
26
|
readonly sunShadows: WebGLTexture;
|
|
27
27
|
readonly peeledEnabled: boolean;
|
|
28
|
+
/** The lamps that scatter, as `resolveMediumLamps` lays them out: none while `lampScatter` is 0. */
|
|
29
|
+
readonly lamps: ResolvedMediumLamps;
|
|
30
|
+
/** The lamps' shadow array, the lit stage's; the placeholder where there is none. */
|
|
31
|
+
readonly pointShadows: WebGLTexture;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
/**
|
|
@@ -172,7 +176,7 @@ export class GlobalMediumPass {
|
|
|
172
176
|
frameHeight: number,
|
|
173
177
|
half: boolean,
|
|
174
178
|
steps: number,
|
|
175
|
-
options:
|
|
179
|
+
options: ResolvedGlobalMedium,
|
|
176
180
|
viewProjection: ReadonlyMat4,
|
|
177
181
|
projection: ReadonlyMat4,
|
|
178
182
|
eye: ArrayLike<number>,
|
|
@@ -214,6 +218,9 @@ export class GlobalMediumPass {
|
|
|
214
218
|
gl.activeTexture(gl.TEXTURE1);
|
|
215
219
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, light.sunShadows);
|
|
216
220
|
gl.uniform1i(u['uSunShadows'] ?? null, 1);
|
|
221
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
222
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, light.pointShadows);
|
|
223
|
+
gl.uniform1i(u['uPointShadows'] ?? null, 2);
|
|
217
224
|
|
|
218
225
|
gl.uniformMatrix4fv(u['uDepthToWorld'] ?? null, false, this.depthToWorld);
|
|
219
226
|
gl.uniform3f(u['uCameraPos'] ?? null, eye[0] ?? 0, eye[1] ?? 0, eye[2] ?? 0);
|
|
@@ -236,7 +243,10 @@ export class GlobalMediumPass {
|
|
|
236
243
|
light.ambient[2] ?? 0,
|
|
237
244
|
);
|
|
238
245
|
gl.uniform1f(u['uDensity'] ?? null, options.density);
|
|
239
|
-
gl.
|
|
246
|
+
gl.uniform2f(u['uHeight'] ?? null, options.heightFalloff, options.baseY);
|
|
247
|
+
gl.uniform3f(u['uLayer2'] ?? null, options.density2, options.heightFalloff2, options.baseY2);
|
|
248
|
+
gl.uniform3f(u['uAlbedo'] ?? null, options.albedo[0], options.albedo[1], options.albedo[2]);
|
|
249
|
+
gl.uniform2f(u['uScatter'] ?? null, options.sunScatter, options.skyScatter);
|
|
240
250
|
gl.uniform1f(u['uAnisotropy'] ?? null, options.anisotropy);
|
|
241
251
|
gl.uniform1f(u['uMaxDistance'] ?? null, options.maxDistance);
|
|
242
252
|
gl.uniform1i(u['uSteps'] ?? null, steps);
|
|
@@ -248,6 +258,20 @@ export class GlobalMediumPass {
|
|
|
248
258
|
light.movingLightViewProj as Float32Array,
|
|
249
259
|
);
|
|
250
260
|
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, light.peeledEnabled ? 1 : 0);
|
|
261
|
+
const lamps = light.lamps;
|
|
262
|
+
gl.uniform1i(u['uLampCount'] ?? null, lamps.count);
|
|
263
|
+
gl.uniform1i(u['uLampFalloff'] ?? null, lamps.falloff);
|
|
264
|
+
/* As `name[0]`, the only spelling an array uniform answers to: see `bindPointShadows`. */
|
|
265
|
+
if (lamps.count > 0) {
|
|
266
|
+
gl.uniform4fv(u['uLampPosition[0]'] ?? null, lamps.position);
|
|
267
|
+
gl.uniform4fv(u['uLampColor[0]'] ?? null, lamps.color);
|
|
268
|
+
gl.uniform4fv(u['uLampAim[0]'] ?? null, lamps.aim);
|
|
269
|
+
gl.uniform4fv(u['uLampCone[0]'] ?? null, lamps.cone);
|
|
270
|
+
gl.uniform4fv(u['uLampShadow[0]'] ?? null, lamps.shadow);
|
|
271
|
+
gl.uniform4fv(u['uLampShadowAt[0]'] ?? null, lamps.shadowAt);
|
|
272
|
+
gl.uniform4fv(u['uLampLive[0]'] ?? null, lamps.live);
|
|
273
|
+
gl.uniform4fv(u['uLampLiveAt[0]'] ?? null, lamps.liveAt);
|
|
274
|
+
}
|
|
251
275
|
|
|
252
276
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
253
277
|
|
|
@@ -257,6 +281,7 @@ export class GlobalMediumPass {
|
|
|
257
281
|
for (const unit of [gl.TEXTURE3, gl.TEXTURE2, gl.TEXTURE1, gl.TEXTURE0]) {
|
|
258
282
|
gl.activeTexture(unit);
|
|
259
283
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
284
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
260
285
|
}
|
|
261
286
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
262
287
|
return true;
|