@driftengine/core 4.8.6 → 4.8.7
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 +71 -0
- package/README.md +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +35 -0
- package/dist/render/areaLights.js +35 -0
- package/dist/render/backend/api.d.ts +33 -3
- package/dist/render/backend/webgl2/renderer.d.ts +50 -14
- package/dist/render/backend/webgl2/renderer.js +141 -30
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/renderer.d.ts +35 -1
- package/dist/render/backend/webgpu/renderer.js +155 -16
- package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
- package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +34 -8
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/clusteredLights.d.ts +23 -5
- package/dist/render/clusteredLights.js +35 -8
- package/dist/render/driftLight/bake.d.ts +2 -0
- package/dist/render/driftLight/bake.js +1 -1
- package/dist/render/driftLight/falloff.d.ts +4 -2
- package/dist/render/driftLight/falloff.js +8 -2
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +5 -0
- package/dist/render/instances.d.ts +19 -5
- package/dist/render/instances.js +9 -3
- package/dist/render/lightBudget.d.ts +12 -1
- package/dist/render/lightBudget.js +23 -3
- package/dist/render/lightmap.d.ts +23 -9
- package/dist/render/lightmap.js +40 -18
- package/dist/render/particlePool.d.ts +21 -0
- package/dist/render/particlePool.js +7 -0
- package/dist/render/pointLightSelection.d.ts +13 -0
- package/dist/render/pointLightSelection.js +3 -1
- package/dist/render/renderQuality.d.ts +8 -7
- package/dist/render/rendererHarness.js +3 -0
- package/dist/render/rgb9e5.d.ts +22 -0
- package/dist/render/rgb9e5.js +47 -0
- package/dist/render/sceneCapture.d.ts +29 -0
- package/dist/render/sceneCapture.js +36 -0
- package/dist/render/screenSpaceReflection.d.ts +27 -1
- package/dist/render/screenSpaceReflection.js +9 -0
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +7 -1
- package/dist/render/shaders/depth.js +11 -0
- package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.js +37 -3
- package/dist/render/shaders/flat/areaLight.d.ts +1 -1
- package/dist/render/shaders/flat/areaLight.js +46 -0
- package/dist/render/shaders/flat/glassTint.d.ts +3 -2
- package/dist/render/shaders/flat/glassTint.js +3 -1
- package/dist/render/shaders/flat/index.d.ts +5 -0
- package/dist/render/shaders/flat/index.js +16 -2
- package/dist/render/shaders/flat/lobes.d.ts +6 -0
- package/dist/render/shaders/flat/lobes.js +49 -43
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +57 -22
- package/dist/render/shaders/flat/models.d.ts +5 -2
- package/dist/render/shaders/flat/models.js +100 -16
- package/dist/render/shaders/flat/preamble.js +10 -3
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/depth.wgsl.js +4 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +16 -0
- package/dist/render/shaders/generated/flat.wgsl.js +54 -38
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +11 -6
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +11 -6
- package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
- package/dist/render/shaders/generated/water.wgsl.js +23 -16
- package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +13 -2
- package/dist/render/shaders/ssrTrace.js +20 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -3
- package/dist/render/skinScatter.d.ts +3 -1
- package/dist/render/skinScatter.js +3 -1
- package/dist/render/ssrPass.js +1 -0
- package/dist/render/surfaceModel.d.ts +29 -8
- package/dist/render/surfaceModel.js +33 -9
- package/dist/render/surfaceTexture.d.ts +25 -1
- package/dist/render/surfaceTexture.js +27 -5
- package/package.json +6 -6
- package/src/index.ts +6 -2
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +58 -0
- package/src/render/backend/api.ts +49 -2
- package/src/render/backend/webgl2/renderer.ts +192 -38
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/renderer.ts +194 -19
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +44 -14
- package/src/render/clusteredLights.ts +41 -8
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +4 -0
- package/src/render/instances.ts +26 -6
- package/src/render/lightBudget.ts +40 -2
- package/src/render/lightmap.ts +64 -23
- package/src/render/particlePool.ts +23 -0
- package/src/render/pointLightSelection.ts +20 -1
- package/src/render/renderQuality.ts +8 -7
- package/src/render/rendererHarness.ts +3 -0
- package/src/render/rgb9e5.ts +52 -0
- package/src/render/sceneCapture.ts +56 -0
- package/src/render/screenSpaceReflection.ts +33 -1
- package/src/render/shaders/bolt.ts +7 -1
- package/src/render/shaders/depth.ts +11 -0
- package/src/render/shaders/flat/anisotropic.ts +37 -3
- package/src/render/shaders/flat/areaLight.ts +46 -0
- package/src/render/shaders/flat/glassTint.ts +9 -2
- package/src/render/shaders/flat/index.ts +21 -2
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +57 -22
- package/src/render/shaders/flat/models.ts +100 -16
- package/src/render/shaders/flat/preamble.ts +10 -3
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +4 -4
- package/src/render/shaders/generated/flat.wgsl.ts +54 -38
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +11 -6
- package/src/render/shaders/generated/water.wgsl.ts +23 -16
- package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/ssrTrace.ts +20 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +1 -0
- package/src/render/surfaceModel.ts +47 -10
- package/src/render/surfaceTexture.ts +69 -9
package/src/index.ts
CHANGED
|
@@ -1065,8 +1065,12 @@ export {
|
|
|
1065
1065
|
INSTANCE_FLOATS,
|
|
1066
1066
|
INSTANCE_STRIDE,
|
|
1067
1067
|
} from './render/instances.ts';
|
|
1068
|
-
export { ParticlePool } from './render/particlePool.ts';
|
|
1069
|
-
export type {
|
|
1068
|
+
export { ParticlePool, DEVICE_PARTICLE_FLOATS } from './render/particlePool.ts';
|
|
1069
|
+
export type {
|
|
1070
|
+
DeviceParticles,
|
|
1071
|
+
ParticlePoolOptions,
|
|
1072
|
+
ParticleInstances,
|
|
1073
|
+
} from './render/particlePool.ts';
|
|
1070
1074
|
export { ParticleBatch } from './render/particleBatch.ts';
|
|
1071
1075
|
export type {
|
|
1072
1076
|
ParticleBatchOptions,
|
|
@@ -39,3 +39,32 @@ export function anisotropicLobe(
|
|
|
39
39
|
const d = x * x + y * y + nh * nh;
|
|
40
40
|
return 1 / Math.max(d * d, 1e-8);
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
/** Three components in the lobe's frame: along the direction, across it, along the normal. */
|
|
44
|
+
export type FrameVector = readonly [number, number, number];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The physical form of the same lobe, `π · D · Vis · N·L`, for a surface whose highlight is GGX's
|
|
48
|
+
* own (`SurfaceMaterial.physicalSpecular`): the stretched distribution normalised as a BRDF's, and
|
|
49
|
+
* Smith's height-correlated masking stretched with it (Heitz 2014, as Filament writes it), Fresnel
|
|
50
|
+
* left to the caller. The half vector, the light and the view are each in the lobe's frame. At
|
|
51
|
+
* strength 0 it is isotropic GGX, the term skin's and the eye's highlights are.
|
|
52
|
+
*/
|
|
53
|
+
export function anisotropicPhysicalLobe(
|
|
54
|
+
half: FrameVector,
|
|
55
|
+
light: FrameVector,
|
|
56
|
+
view: FrameVector,
|
|
57
|
+
alphaT: number,
|
|
58
|
+
alphaB: number,
|
|
59
|
+
): number {
|
|
60
|
+
const x = half[0] / alphaT;
|
|
61
|
+
const y = half[1] / alphaB;
|
|
62
|
+
const d = x * x + y * y + half[2] * half[2];
|
|
63
|
+
const distribution = 1 / (Math.PI * alphaT * alphaB * d * d);
|
|
64
|
+
const ndl = light[2];
|
|
65
|
+
const ndv = view[2];
|
|
66
|
+
const lambdaView = ndl * Math.hypot(alphaT * view[0], alphaB * view[1], ndv);
|
|
67
|
+
const lambdaLight = ndv * Math.hypot(alphaT * light[0], alphaB * light[1], ndl);
|
|
68
|
+
const visibility = 0.5 / Math.max(lambdaView + lambdaLight, 1e-5);
|
|
69
|
+
return Math.PI * distribution * visibility * ndl;
|
|
70
|
+
}
|
package/src/render/areaLights.ts
CHANGED
|
@@ -97,6 +97,22 @@ export interface AreaLightSource {
|
|
|
97
97
|
* of a scene unit, and never inside the rectangle itself. Unreal's `AttenuationRadius`, in metres.
|
|
98
98
|
*/
|
|
99
99
|
range?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Barn doors: a flap hinged at each of the rectangle's four edges, standing this many degrees from
|
|
102
|
+
* its normal. 90, or absent, folds them flat and they hide nothing; smaller closes them, so the
|
|
103
|
+
* light narrows to the opening they leave — a rectangle at 50° throws a beam rather than filling
|
|
104
|
+
* its hemisphere. Unreal's `BarnDoorAngle`, whose default of 88° is nearly flat.
|
|
105
|
+
*
|
|
106
|
+
* What a fragment sees past the doors is the part of the rectangle no door's tip hides, one axis
|
|
107
|
+
* at a time, and the rectangle then shades as that smaller rectangle, its highlight included.
|
|
108
|
+
* Its shadow map is still baked from the whole rectangle's centre. **What it gives up**: each
|
|
109
|
+
* door is taken as endless along its hinge, so the two axes clip apart and the corner where two
|
|
110
|
+
* doors meet hides a square rather than the mitre two real flaps make. One-sided: a two-sided
|
|
111
|
+
* rectangle's back face has none.
|
|
112
|
+
*/
|
|
113
|
+
barnDoorAngle?: number;
|
|
114
|
+
/** The barn doors' length in metres. Absent, 0.2: Unreal's default of 20 cm. */
|
|
115
|
+
barnDoorLength?: number;
|
|
100
116
|
}
|
|
101
117
|
|
|
102
118
|
/** The irradiance a derived `range` ends at, in scene units. See `AreaLightSource.range`. */
|
|
@@ -113,6 +129,11 @@ export interface AreaLightBuffer {
|
|
|
113
129
|
readonly twoSided: Float32Array;
|
|
114
130
|
/** One a rectangle: how far its light reaches. See `AreaLightSource.range`. */
|
|
115
131
|
readonly ranges?: Float32Array;
|
|
132
|
+
/**
|
|
133
|
+
* Two a rectangle: the cosine of its barn doors' angle from its normal, then their length in
|
|
134
|
+
* metres — 0 and 0 for none. See `AreaLightSource.barnDoorAngle`.
|
|
135
|
+
*/
|
|
136
|
+
readonly barnDoors?: Float32Array;
|
|
116
137
|
}
|
|
117
138
|
|
|
118
139
|
export function createAreaLightBuffer(capacity: number = MAX_AREA_LIGHTS): AreaLightBuffer {
|
|
@@ -125,9 +146,45 @@ export function createAreaLightBuffer(capacity: number = MAX_AREA_LIGHTS): AreaL
|
|
|
125
146
|
sizes: new Float32Array(capacity * 2),
|
|
126
147
|
twoSided: new Float32Array(capacity),
|
|
127
148
|
ranges: new Float32Array(capacity),
|
|
149
|
+
barnDoors: new Float32Array(capacity * 2),
|
|
128
150
|
};
|
|
129
151
|
}
|
|
130
152
|
|
|
153
|
+
/** Unreal's default barn door length, in metres. See `AreaLightSource.barnDoorLength`. */
|
|
154
|
+
export const DEFAULT_BARN_DOOR_LENGTH = 0.2;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* A rectangle's barn doors as the shader reads them, into `out[at]` and `out[at + 1]`: the cosine of
|
|
158
|
+
* their angle and their length. An angle not between 0 and 90 degrees, or a length not above zero,
|
|
159
|
+
* is no doors at all, written as 0 and 0 — which the shader reads as "nothing hidden".
|
|
160
|
+
*/
|
|
161
|
+
export function barnDoorsOf(light: AreaLightSource, out: Float32Array, at: number): void {
|
|
162
|
+
const angle = light.barnDoorAngle;
|
|
163
|
+
const length = light.barnDoorLength ?? DEFAULT_BARN_DOOR_LENGTH;
|
|
164
|
+
if (angle === undefined || !(angle >= 0 && angle < 90) || !(length > 0)) {
|
|
165
|
+
out[at] = 0;
|
|
166
|
+
out[at + 1] = 0;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
out[at] = Math.cos((angle * Math.PI) / 180);
|
|
170
|
+
out[at + 1] = length;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The fixed arm's half extents and barn doors, four a rectangle as `uAreaLightSize` holds them: the
|
|
175
|
+
* two half extents, then the doors' cosine and length. Into a target the binder owns, for the first
|
|
176
|
+
* `MAX_AREA_LIGHTS` alone, which are all the fixed arm reads.
|
|
177
|
+
*/
|
|
178
|
+
export function packAreaSizesAndDoors(lights: AreaLightBuffer, out: Float32Array): void {
|
|
179
|
+
const doors = lights.barnDoors;
|
|
180
|
+
for (let a = 0; a < MAX_AREA_LIGHTS; a++) {
|
|
181
|
+
out[a * 4] = lights.sizes[a * 2] ?? 0;
|
|
182
|
+
out[a * 4 + 1] = lights.sizes[a * 2 + 1] ?? 0;
|
|
183
|
+
out[a * 4 + 2] = doors?.[a * 2] ?? 0;
|
|
184
|
+
out[a * 4 + 3] = doors?.[a * 2 + 1] ?? 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
131
188
|
/**
|
|
132
189
|
* How far a rectangle's light reaches when it names no range: where its irradiance on its own axis,
|
|
133
190
|
* `L · A / d²` for a rectangle of radiance `L` and area `A` seen from far, falls to
|
|
@@ -227,6 +284,7 @@ export function selectAreaLights(
|
|
|
227
284
|
? light.range
|
|
228
285
|
: areaRange(light.r, light.g, light.b, light.halfWidth, light.halfHeight);
|
|
229
286
|
}
|
|
287
|
+
if (out.barnDoors !== undefined) barnDoorsOf(light, out.barnDoors, slot * 2);
|
|
230
288
|
}
|
|
231
289
|
|
|
232
290
|
return out;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Bounds } from '../../math/bounds.ts';
|
|
2
2
|
import type { Vec3 } from '../../math/color.ts';
|
|
3
|
+
import type { Camera } from '../camera.ts';
|
|
3
4
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
4
5
|
import type { MeshData } from '../mesh.ts';
|
|
5
6
|
import type { ProbeBakeOptions } from '../reflectionProbe.ts';
|
|
@@ -8,6 +9,7 @@ import type { InstancedOptions, MeshInstances } from '../instances.ts';
|
|
|
8
9
|
import type { SurfaceMaterial } from '../surfaceTexture.ts';
|
|
9
10
|
import type { SurfaceSource } from '../compressedSource.ts';
|
|
10
11
|
import type { LightmapPage } from '../lightmap.ts';
|
|
12
|
+
import type { DeviceParticles } from '../particlePool.ts';
|
|
11
13
|
import type { ClothBindingData } from '../clothBindingData.ts';
|
|
12
14
|
import type { TextStyle } from '../textLayout.ts';
|
|
13
15
|
import type { SdfFont } from '../sdfFont.ts';
|
|
@@ -276,6 +278,7 @@ export type RendererApi = Omit<
|
|
|
276
278
|
| 'disposeCaustics'
|
|
277
279
|
| 'createParticles'
|
|
278
280
|
| 'drawParticles'
|
|
281
|
+
| 'drawDeviceParticles'
|
|
279
282
|
| 'disposeParticles'
|
|
280
283
|
| 'createWindStreaks'
|
|
281
284
|
| 'drawWindStreaks'
|
|
@@ -302,6 +305,8 @@ export type RendererApi = Omit<
|
|
|
302
305
|
| 'createSurfaceTexture'
|
|
303
306
|
| 'createSurfaceTextureArray'
|
|
304
307
|
| 'createLightmap'
|
|
308
|
+
| 'createSceneCapture'
|
|
309
|
+
| 'captureScene'
|
|
305
310
|
| 'updateSurfaceTexture'
|
|
306
311
|
| 'disposeSurfaceTexture'
|
|
307
312
|
| 'setSurfaceTexture'
|
|
@@ -365,13 +370,41 @@ export type RendererApi = Omit<
|
|
|
365
370
|
|
|
366
371
|
/**
|
|
367
372
|
* A baked lightmap page on the device, as the texture a `lightmapModel` material takes for its
|
|
368
|
-
* `modelMap`: two layers of
|
|
369
|
-
*
|
|
373
|
+
* `modelMap`: two layers of four bytes a texel, the irradiance as an rgb9e5 word and the
|
|
374
|
+
* direction, which the lit stage decodes and filters itself, clamped at the page's edges, with no
|
|
375
|
+
* mip chain. A page whose irradiance arrives packed uploads where it lies. Disposed as any texture is, with
|
|
370
376
|
* `disposeSurfaceTexture`; `updateSurfaceTexture` refuses it, as it refuses any array. See
|
|
371
377
|
* `lightmap.ts` for what a surface does with it.
|
|
372
378
|
*/
|
|
373
379
|
createLightmap(page: LightmapPage): SurfaceTextureHandle;
|
|
374
380
|
|
|
381
|
+
/**
|
|
382
|
+
* A texture the scene can be drawn into from a camera of the caller's (`captureScene`), which a
|
|
383
|
+
* material then shows like any image — most often as its `emissive`, for a screen in the world
|
|
384
|
+
* showing what a camera elsewhere sees. `width` and `height` in pixels, 1 to 4096. It holds
|
|
385
|
+
* radiance, at the colour format a probe takes: half floats under `hdrScene`, bytes otherwise.
|
|
386
|
+
* Disposed as any texture is; `updateSurfaceTexture` refuses it. See `sceneCapture.ts`.
|
|
387
|
+
*/
|
|
388
|
+
createSceneCapture(width: number, height: number): SurfaceTextureHandle;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Draw the scene into a capture from `camera`, at the moment a caller says, usually once a frame
|
|
392
|
+
* before the frame's own world. `draw` is handed the camera, its matrices updated for the
|
|
393
|
+
* capture's shape, and submits the scene exactly as it would to the screen — `bindMeshPass`,
|
|
394
|
+
* `drawMesh` and the rest; `bindMeshPass` the frame's own camera again afterwards. The output
|
|
395
|
+
* transform is held off, so a screen showing it is graded once, with the frame.
|
|
396
|
+
*
|
|
397
|
+
* **The mesh pass alone**, as a probe bake is: what a scene draws after its world is in the
|
|
398
|
+
* capture only if `draw` draws it, and no temporal resolve or effect after the world runs on it.
|
|
399
|
+
* Returns whether it drew: false for a texture that is not a capture.
|
|
400
|
+
*/
|
|
401
|
+
captureScene(
|
|
402
|
+
capture: SurfaceTextureHandle,
|
|
403
|
+
camera: Camera,
|
|
404
|
+
clearColor: Vec3,
|
|
405
|
+
draw: (camera: Camera) => void,
|
|
406
|
+
): boolean;
|
|
407
|
+
|
|
375
408
|
/**
|
|
376
409
|
* Replace a texture's pixels, keeping the GPU object and its sampler state.
|
|
377
410
|
*
|
|
@@ -735,6 +768,20 @@ export type RendererApi = Omit<
|
|
|
735
768
|
timeSeconds: number,
|
|
736
769
|
): void;
|
|
737
770
|
|
|
771
|
+
/**
|
|
772
|
+
* Draw a pool's particles from a buffer a caller's own compute wrote (`registerCompute`), where
|
|
773
|
+
* they lie: the pool's material, blend, flipbook and fades, its instance stream the caller's
|
|
774
|
+
* buffer. WebGPU only; WebGL2 says so once and draws nothing. Not sorted. See `DeviceParticles`
|
|
775
|
+
* for the layout.
|
|
776
|
+
*/
|
|
777
|
+
drawDeviceParticles(
|
|
778
|
+
batch: ParticleHandle,
|
|
779
|
+
particles: DeviceParticles,
|
|
780
|
+
camera: Parameters<Renderer['drawParticles']>[2],
|
|
781
|
+
env: Parameters<Renderer['drawParticles']>[3],
|
|
782
|
+
timeSeconds: number,
|
|
783
|
+
): void;
|
|
784
|
+
|
|
738
785
|
/** Release a particle pool. */
|
|
739
786
|
disposeParticles(batch: ParticleHandle): void;
|
|
740
787
|
|
|
@@ -136,7 +136,12 @@ import {
|
|
|
136
136
|
MORPH_DELTA_TEXTURE_UNIT,
|
|
137
137
|
SKIN_PALETTE_TEXTURE_UNIT,
|
|
138
138
|
} from '../../lightBudget.ts';
|
|
139
|
-
import {
|
|
139
|
+
import {
|
|
140
|
+
MODEL_PARAM_FLOATS,
|
|
141
|
+
eyeAxisInWorld,
|
|
142
|
+
packModel,
|
|
143
|
+
skinProfileDistance,
|
|
144
|
+
} from '../../surfaceModel.ts';
|
|
140
145
|
import type { SurfaceModelKind } from '../../surfaceModel.ts';
|
|
141
146
|
import { lightmapRegionsBound, modelBound, skinHalfBound } from '../../shaders/flat/models.ts';
|
|
142
147
|
import {
|
|
@@ -251,6 +256,8 @@ import type { IncrementalMesh } from '../../mesh.ts';
|
|
|
251
256
|
import type { MeshData } from '../../mesh.ts';
|
|
252
257
|
import type { MeshOptions } from '../api.ts';
|
|
253
258
|
import { SurfaceTexture } from '../../surfaceTexture.ts';
|
|
259
|
+
import { sceneCaptureTexels } from '../../sceneCapture.ts';
|
|
260
|
+
import { GlSceneCaptureTarget } from './sceneCaptureTarget.ts';
|
|
254
261
|
import { glCompressedFormats } from '../../glCompressed.ts';
|
|
255
262
|
import type { CompressedTextureFormat, SurfaceSource } from '../../compressedSource.ts';
|
|
256
263
|
import type { SurfaceMaterial, SurfaceTextureOptions } from '../../surfaceTexture.ts';
|
|
@@ -274,7 +281,7 @@ import { PlanarReflection } from '../../planarReflection.ts';
|
|
|
274
281
|
import { InstancedMesh } from '../../instancedMesh.ts';
|
|
275
282
|
import { ParticleBatch } from '../../particleBatch.ts';
|
|
276
283
|
import type { ParticleBatchOptions } from '../../particleBatch.ts';
|
|
277
|
-
import type { ParticleInstances } from '../../particlePool.ts';
|
|
284
|
+
import type { DeviceParticles, ParticleInstances } from '../../particlePool.ts';
|
|
278
285
|
import { BoltBatch } from '../../boltBatch.ts';
|
|
279
286
|
import type { BoltSegments } from '../../boltPool.ts';
|
|
280
287
|
import { LineBatch } from '../../lineBatch.ts';
|
|
@@ -435,6 +442,11 @@ export interface Environment extends Atmosphere {
|
|
|
435
442
|
lightDirections: Float32Array;
|
|
436
443
|
/** Two per active light: the cosine of the inner cone angle, then of the outer. */
|
|
437
444
|
lightConeCos: Float32Array;
|
|
445
|
+
/**
|
|
446
|
+
* One per active light: its own falloff exponent, or 0 for the frame's. Optional: absent is 0 for
|
|
447
|
+
* every light. See `PointLightSource.falloffExponent`.
|
|
448
|
+
*/
|
|
449
|
+
lightFalloffExponents?: Float32Array;
|
|
438
450
|
/** One per active light: a row of the photometric atlas, or −1 for none. */
|
|
439
451
|
lightIesProfiles: Float32Array;
|
|
440
452
|
/**
|
|
@@ -1621,6 +1633,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1621
1633
|
private warnedEnvironmentAmbient = false;
|
|
1622
1634
|
/** Whether a probe is being baked, so the passes that would recurse into one can tell. */
|
|
1623
1635
|
private probePassActive = false;
|
|
1636
|
+
/**
|
|
1637
|
+
* Whether a scene capture is being drawn (`captureScene`): an off-screen pass like a bake, so the
|
|
1638
|
+
* frame's own view, its refraction copy and skin's spread are left alone, but lit as the frame is.
|
|
1639
|
+
*/
|
|
1640
|
+
private capturePassActive = false;
|
|
1641
|
+
/** Each capture's framebuffer, beside the texture a material binds. See `createSceneCapture`. */
|
|
1642
|
+
private readonly captureTargets = new WeakMap<SurfaceTexture, GlSceneCaptureTarget>();
|
|
1643
|
+
/** Said once, where a driver refuses to draw into a capture. */
|
|
1644
|
+
private warnedCapture = false;
|
|
1624
1645
|
/** Whether the bake under way lights its faces by the grid. `ProbeBakeOptions.bounce`. */
|
|
1625
1646
|
private probeBounce = false;
|
|
1626
1647
|
/** The clip-control extension, or null where this context does not offer it. */
|
|
@@ -1692,12 +1713,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1692
1713
|
* the camera's matrix unchanged and multiplies nothing.
|
|
1693
1714
|
*/
|
|
1694
1715
|
private viewProjFor(camera: { readonly viewProjection: ReadonlyMat4 }): ReadonlyMat4 {
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1716
|
+
/* A capture's rows are flipped as a probe face's are, so it reads as an image does. */
|
|
1717
|
+
const scene =
|
|
1718
|
+
this.probePassActive || this.capturePassActive
|
|
1719
|
+
? (cubeFaceProjection(
|
|
1720
|
+
this.cubeFaceViewProj,
|
|
1721
|
+
this.sceneMatrix(camera.viewProjection) as unknown as ArrayLike<number>,
|
|
1722
|
+
) as unknown as ReadonlyMat4)
|
|
1723
|
+
: this.sceneMatrix(camera.viewProjection);
|
|
1701
1724
|
/*
|
|
1702
1725
|
* **The jitter goes on here and nowhere else.** This is the one funnel every geometry upload
|
|
1703
1726
|
* passes through, so one line covers the mesh passes, the sky, the water and the effects
|
|
@@ -3499,6 +3522,92 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3499
3522
|
return new SurfaceTexture(this.gl, lightmapTexels(page), { wrap: 'clamp', mipmap: false });
|
|
3500
3523
|
}
|
|
3501
3524
|
|
|
3525
|
+
/**
|
|
3526
|
+
* A texture the scene can be drawn into, for a material to show. See `RendererApi` and
|
|
3527
|
+
* `sceneCapture.ts`. Half floats where the frame keeps range, as a probe does.
|
|
3528
|
+
*/
|
|
3529
|
+
createSceneCapture(width: number, height: number): SurfaceTexture {
|
|
3530
|
+
const float = this.quality.hdrScene && this.gl.getExtension('EXT_color_buffer_float') !== null;
|
|
3531
|
+
const texture = new SurfaceTexture(this.gl, sceneCaptureTexels(width, height, float), {
|
|
3532
|
+
wrap: 'clamp',
|
|
3533
|
+
mipmap: false,
|
|
3534
|
+
});
|
|
3535
|
+
this.captureTargets.set(
|
|
3536
|
+
texture,
|
|
3537
|
+
new GlSceneCaptureTarget(
|
|
3538
|
+
this.gl,
|
|
3539
|
+
texture,
|
|
3540
|
+
width,
|
|
3541
|
+
height,
|
|
3542
|
+
this.sceneTarget?.sampleCount ?? 1,
|
|
3543
|
+
float,
|
|
3544
|
+
),
|
|
3545
|
+
);
|
|
3546
|
+
return texture;
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
/**
|
|
3550
|
+
* Draw the scene into a capture from `camera`: `draw` is handed the camera, its matrices updated
|
|
3551
|
+
* for the capture's shape, and submits the scene as it would to the screen. See `RendererApi`.
|
|
3552
|
+
*
|
|
3553
|
+
* **Radiance, as a probe bake stores it**: the output transform and exposure are held off for the
|
|
3554
|
+
* pass, so the frame grades a screen showing it once. No temporal jitter, which a capture has no
|
|
3555
|
+
* resolve to undo. Returns whether it drew: false for a texture that is not a capture, or one the
|
|
3556
|
+
* driver will not draw into.
|
|
3557
|
+
*/
|
|
3558
|
+
captureScene(
|
|
3559
|
+
capture: SurfaceTexture,
|
|
3560
|
+
camera: Camera,
|
|
3561
|
+
clearColor: Vec3,
|
|
3562
|
+
draw: (camera: Camera) => void,
|
|
3563
|
+
): boolean {
|
|
3564
|
+
if (this.contextLost) return false;
|
|
3565
|
+
const target = this.captureTargets.get(capture);
|
|
3566
|
+
if (target === undefined) return false;
|
|
3567
|
+
if (!target.usable) {
|
|
3568
|
+
if (!this.warnedCapture) {
|
|
3569
|
+
this.warnedCapture = true;
|
|
3570
|
+
console.warn(
|
|
3571
|
+
'Renderer: this driver will not draw into a scene capture, so it keeps its clear. ' +
|
|
3572
|
+
'Nothing else is affected.',
|
|
3573
|
+
);
|
|
3574
|
+
}
|
|
3575
|
+
return false;
|
|
3576
|
+
}
|
|
3577
|
+
camera.updateMatrices(target.width / target.height);
|
|
3578
|
+
const { gl } = this;
|
|
3579
|
+
const jittering = this.temporalJittering;
|
|
3580
|
+
this.temporalJittering = false;
|
|
3581
|
+
this.capturePassActive = true;
|
|
3582
|
+
this.useFlatProgram();
|
|
3583
|
+
gl.uniform1i(this.flatUniforms['uOutputTransform'] ?? null, 0);
|
|
3584
|
+
gl.uniform1f(this.flatUniforms['uOutputExposure'] ?? null, 1);
|
|
3585
|
+
this.materials.dirty();
|
|
3586
|
+
target.begin(gl, clearColor, this.depthClear);
|
|
3587
|
+
/*
|
|
3588
|
+
* **Rows flipped, as a probe face's are.** A GL framebuffer's first row is its bottom and an
|
|
3589
|
+
* image's is its top, so a capture drawn upright would be shown upside down by every material
|
|
3590
|
+
* reading it as the image it is; `viewProjFor` negates clip y for the pass, and negating one axis
|
|
3591
|
+
* reverses every triangle's winding, which the front face follows. WebGPU's rows already run
|
|
3592
|
+
* the image's way.
|
|
3593
|
+
*/
|
|
3594
|
+
gl.frontFace(gl.CW);
|
|
3595
|
+
try {
|
|
3596
|
+
draw(camera);
|
|
3597
|
+
} finally {
|
|
3598
|
+
target.end(gl);
|
|
3599
|
+
gl.frontFace(gl.CCW);
|
|
3600
|
+
this.useFlatProgram();
|
|
3601
|
+
gl.uniform1i(this.flatUniforms['uOutputTransform'] ?? null, this.gradeCode());
|
|
3602
|
+
gl.uniform1f(this.flatUniforms['uOutputExposure'] ?? null, this.gradeExposure());
|
|
3603
|
+
this.materials.dirty();
|
|
3604
|
+
this.capturePassActive = false;
|
|
3605
|
+
this.temporalJittering = jittering;
|
|
3606
|
+
this.restoreViewport();
|
|
3607
|
+
}
|
|
3608
|
+
return true;
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3502
3611
|
/**
|
|
3503
3612
|
* Replace a surface texture's pixels, keeping the GPU object and its sampler state.
|
|
3504
3613
|
*
|
|
@@ -3519,6 +3628,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3519
3628
|
disposeSurfaceTexture(texture: SurfaceTexture): void {
|
|
3520
3629
|
if (this.contextLost) return;
|
|
3521
3630
|
texture.dispose(this.gl);
|
|
3631
|
+
this.captureTargets.get(texture)?.dispose(this.gl);
|
|
3632
|
+
this.captureTargets.delete(texture);
|
|
3522
3633
|
}
|
|
3523
3634
|
|
|
3524
3635
|
/**
|
|
@@ -3751,9 +3862,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3751
3862
|
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
3752
3863
|
this.skinScatter.setProfile(
|
|
3753
3864
|
model.profile,
|
|
3754
|
-
model
|
|
3755
|
-
model
|
|
3756
|
-
model
|
|
3865
|
+
skinProfileDistance(model, 0),
|
|
3866
|
+
skinProfileDistance(model, 1),
|
|
3867
|
+
skinProfileDistance(model, 2),
|
|
3757
3868
|
);
|
|
3758
3869
|
}
|
|
3759
3870
|
/*
|
|
@@ -3901,7 +4012,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3901
4012
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3902
4013
|
if (modelMap !== null) modelMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
|
|
3903
4014
|
gl.uniform1i(u['uModelMap'] ?? null, MODEL_MAP_TEXTURE_UNIT);
|
|
3904
|
-
packModel(
|
|
4015
|
+
packModel(
|
|
4016
|
+
material?.model ?? null,
|
|
4017
|
+
modelMap !== null,
|
|
4018
|
+
this.modelParams,
|
|
4019
|
+
material?.physicalSpecular === true,
|
|
4020
|
+
);
|
|
4021
|
+
/* The first physical highlight asked for is when the lit programs start carrying one. */
|
|
4022
|
+
if (material?.physicalSpecular === true) this.askLit('PHYSICAL_SPECULAR');
|
|
3905
4023
|
gl.uniform4fv(u['uModelParams'] ?? null, this.modelParams);
|
|
3906
4024
|
|
|
3907
4025
|
/* The albedo's effects table, or the stand-in the shader reads as none. Before the early
|
|
@@ -3963,18 +4081,6 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3963
4081
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3964
4082
|
}
|
|
3965
4083
|
|
|
3966
|
-
/**
|
|
3967
|
-
* A pass-level scale over the grain the geometry itself declared, 0 to 1.
|
|
3968
|
-
*
|
|
3969
|
-
* Which surfaces are mineral is now `MeshData.grain`, stated per vertex through
|
|
3970
|
-
* `MeshBuilder.setGrain` — because it is a property of a material, and absent means none.
|
|
3971
|
-
* This stays as the override for the case that has no answer to state: an imported model
|
|
3972
|
-
* arrives with no grain information at all, and a caller drawing one decides for it.
|
|
3973
|
-
*
|
|
3974
|
-
* Pass state, like `setSurfaceTexture`, because a material covers many draws. The default
|
|
3975
|
-
* is 1 and `bindMeshPass` restores it, so it scales geometry that declared grain and
|
|
3976
|
-
* leaves geometry that did not exactly as it is.
|
|
3977
|
-
*/
|
|
3978
4084
|
/**
|
|
3979
4085
|
* The ambient the following draws take, as nine spherical-harmonic coefficients of incoming
|
|
3980
4086
|
* radiance — red, green and blue of each, twenty-seven numbers — or `null` for the frame's own:
|
|
@@ -3986,16 +4092,6 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3986
4092
|
* only — a glossy surface still reflects the probes — and it is pass state like the material,
|
|
3987
4093
|
* which `bindMeshPass` clears, so a mirror or a probe bake drawn after does not inherit it.
|
|
3988
4094
|
*/
|
|
3989
|
-
/**
|
|
3990
|
-
* How much of the environment the following draws mirror, 0 to 1.
|
|
3991
|
-
*
|
|
3992
|
-
* Pass state beside the grain, and the counterpart to it: grain is a surface being uneven,
|
|
3993
|
-
* this is a surface being smooth enough to carry an image. Polished paint, glass, chrome
|
|
3994
|
-
* and still water want it; plaster and stone want none.
|
|
3995
|
-
*
|
|
3996
|
-
* Defaults to zero and bindMeshPass restores it, so a scene that never calls this looks
|
|
3997
|
-
* exactly as it did.
|
|
3998
|
-
*/
|
|
3999
4095
|
setAmbientSH(coefficients: ArrayLike<number> | null): void {
|
|
4000
4096
|
if (this.contextLost) return;
|
|
4001
4097
|
packAmbientSH(coefficients, this.ambientSH);
|
|
@@ -4007,6 +4103,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4007
4103
|
this.useFlatProgram();
|
|
4008
4104
|
}
|
|
4009
4105
|
|
|
4106
|
+
/**
|
|
4107
|
+
* How much of the environment the following draws mirror, 0 to 1.
|
|
4108
|
+
*
|
|
4109
|
+
* Pass state beside the grain, and the counterpart to it: grain is a surface being uneven,
|
|
4110
|
+
* this is a surface being smooth enough to carry an image. Polished paint, glass, chrome
|
|
4111
|
+
* and still water want it; plaster and stone want none.
|
|
4112
|
+
*
|
|
4113
|
+
* Defaults to zero and bindMeshPass restores it, so a scene that never calls this looks
|
|
4114
|
+
* exactly as it did.
|
|
4115
|
+
*/
|
|
4010
4116
|
setSurfaceReflectivity(amount: number): void {
|
|
4011
4117
|
if (this.contextLost) return;
|
|
4012
4118
|
this.materials.dirty();
|
|
@@ -4056,6 +4162,18 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4056
4162
|
);
|
|
4057
4163
|
}
|
|
4058
4164
|
|
|
4165
|
+
/**
|
|
4166
|
+
* A pass-level scale over the grain the geometry itself declared, 0 to 1.
|
|
4167
|
+
*
|
|
4168
|
+
* Which surfaces are mineral is now `MeshData.grain`, stated per vertex through
|
|
4169
|
+
* `MeshBuilder.setGrain` — because it is a property of a material, and absent means none.
|
|
4170
|
+
* This stays as the override for the case that has no answer to state: an imported model
|
|
4171
|
+
* arrives with no grain information at all, and a caller drawing one decides for it.
|
|
4172
|
+
*
|
|
4173
|
+
* Pass state, like `setSurfaceTexture`, because a material covers many draws. The default
|
|
4174
|
+
* is 1 and `bindMeshPass` restores it, so it scales geometry that declared grain and
|
|
4175
|
+
* leaves geometry that did not exactly as it is.
|
|
4176
|
+
*/
|
|
4059
4177
|
setSurfaceGrain(amount: number): void {
|
|
4060
4178
|
if (this.contextLost) return;
|
|
4061
4179
|
this.materials.dirty();
|
|
@@ -4485,7 +4603,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4485
4603
|
const refraction = options.refraction ?? 0;
|
|
4486
4604
|
const glassy = resolveGlass(options.glass, this.glassScratch);
|
|
4487
4605
|
if (refraction <= 0 && !glassy) return false;
|
|
4488
|
-
if (this.probePassActive || this.reflectionPassActive) return false;
|
|
4606
|
+
if (this.probePassActive || this.reflectionPassActive || this.capturePassActive) return false;
|
|
4489
4607
|
const snapshot = this.sceneTarget?.snapshotColor(false, glassy) ?? null;
|
|
4490
4608
|
if (snapshot === null) return false;
|
|
4491
4609
|
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
@@ -4847,6 +4965,29 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4847
4965
|
return new ParticleBatch(this.gl, capacity, options);
|
|
4848
4966
|
}
|
|
4849
4967
|
|
|
4968
|
+
/**
|
|
4969
|
+
* Particles a caller's own compute wrote: WebGPU's alone, since this backend has no compute stage
|
|
4970
|
+
* to have written them (`computeSupported`). Said once, and nothing is drawn.
|
|
4971
|
+
*/
|
|
4972
|
+
drawDeviceParticles(
|
|
4973
|
+
_batch: ParticleBatch,
|
|
4974
|
+
_particles: DeviceParticles,
|
|
4975
|
+
_camera: Camera,
|
|
4976
|
+
_env: Environment,
|
|
4977
|
+
_timeSeconds: number,
|
|
4978
|
+
): void {
|
|
4979
|
+
if (this.warnedDeviceParticles) return;
|
|
4980
|
+
this.warnedDeviceParticles = true;
|
|
4981
|
+
console.warn(
|
|
4982
|
+
'Renderer: drawDeviceParticles draws particles a compute shader wrote, and WebGL2 has no ' +
|
|
4983
|
+
'compute stage, so nothing is drawn. Check `computeSupported`, and draw a pool with ' +
|
|
4984
|
+
'`drawParticles` where it is false.',
|
|
4985
|
+
);
|
|
4986
|
+
}
|
|
4987
|
+
|
|
4988
|
+
/** Said once: see `drawDeviceParticles`. */
|
|
4989
|
+
private warnedDeviceParticles = false;
|
|
4990
|
+
|
|
4850
4991
|
/**
|
|
4851
4992
|
* Draw a live particle pool.
|
|
4852
4993
|
*
|
|
@@ -4913,7 +5054,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4913
5054
|
const u = batch.uniforms;
|
|
4914
5055
|
const scene = this.sceneTarget;
|
|
4915
5056
|
let depth: WebGLTexture | null = null;
|
|
4916
|
-
if (
|
|
5057
|
+
if (
|
|
5058
|
+
batch.softDepth > 0 &&
|
|
5059
|
+
!this.reflectionPassActive &&
|
|
5060
|
+
!this.probePassActive &&
|
|
5061
|
+
!this.capturePassActive
|
|
5062
|
+
) {
|
|
4917
5063
|
depth = scene?.snapshotDepth(true) ?? null;
|
|
4918
5064
|
if (depth === null && !this.warnedSpriteDepth) {
|
|
4919
5065
|
this.warnedSpriteDepth = true;
|
|
@@ -5982,6 +6128,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5982
6128
|
lightFixtures: this.litOn.LIGHT_FIXTURES,
|
|
5983
6129
|
surfaceEffects: this.litOn.SURFACE_EFFECTS,
|
|
5984
6130
|
driftLight: this.litOn.DRIFT_LIGHT,
|
|
6131
|
+
physicalSpecular: this.litOn.PHYSICAL_SPECULAR,
|
|
5985
6132
|
maxLights: budget.maxLights,
|
|
5986
6133
|
maxAreaLights: budget.maxAreaLights,
|
|
5987
6134
|
});
|
|
@@ -7877,7 +8024,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7877
8024
|
* other position, and the anti-flicker's period never started.
|
|
7878
8025
|
*/
|
|
7879
8026
|
if (this.framePresented) this.temporalJittering = false;
|
|
7880
|
-
if (
|
|
8027
|
+
if (
|
|
8028
|
+
!this.reflectionPassActive &&
|
|
8029
|
+
!this.probePassActive &&
|
|
8030
|
+
!this.capturePassActive &&
|
|
8031
|
+
!this.framePresented
|
|
8032
|
+
) {
|
|
7881
8033
|
this.frameViewProj = camera.viewProjection;
|
|
7882
8034
|
/* Copied rather than referenced: a caller moves its camera in place, and the decal pass
|
|
7883
8035
|
reads this at the end of the frame. */
|
|
@@ -8599,6 +8751,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8599
8751
|
!this.skinScattered &&
|
|
8600
8752
|
!this.reflectionPassActive &&
|
|
8601
8753
|
!this.probePassActive &&
|
|
8754
|
+
!this.capturePassActive &&
|
|
8602
8755
|
!this.framePresented &&
|
|
8603
8756
|
skin.ready(scene)
|
|
8604
8757
|
);
|
|
@@ -8619,7 +8772,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
8619
8772
|
/* Latched whether or not skin was drawn: a skin drawn after this point is the whole surface. */
|
|
8620
8773
|
this.skinScattered = true;
|
|
8621
8774
|
if (!skin.pending) return;
|
|
8622
|
-
if (this.reflectionPassActive || this.probePassActive ||
|
|
8775
|
+
if (this.reflectionPassActive || this.probePassActive || this.capturePassActive) return;
|
|
8776
|
+
if (projection === null) return;
|
|
8623
8777
|
const depth = scene.snapshotDepth(true);
|
|
8624
8778
|
mat4.invert(this.skinInverseProjection, projection);
|
|
8625
8779
|
skin.spread(scene, depth, this.emptyTexture2D, projection, this.skinInverseProjection);
|