@driftengine/core 4.3.0 → 4.4.0
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 +261 -0
- package/README.md +1 -1
- package/dist/cinematic/cameraPath.d.ts +53 -0
- package/dist/cinematic/cameraPath.js +118 -0
- package/dist/environment/daylight.d.ts +77 -0
- package/dist/environment/daylight.js +77 -0
- package/dist/geometry/meshConcat.d.ts +9 -0
- package/dist/geometry/meshConcat.js +101 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +14 -1
- package/dist/render/backend/createRenderer.d.ts +13 -1
- package/dist/render/backend/createRenderer.js +6 -5
- package/dist/render/backend/webgl2/renderer.d.ts +147 -1
- package/dist/render/backend/webgl2/renderer.js +402 -23
- package/dist/render/backend/webgpu/buffers.js +46 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +81 -3
- package/dist/render/backend/webgpu/depthPass.js +75 -14
- package/dist/render/backend/webgpu/exposurePass.d.ts +39 -0
- package/dist/render/backend/webgpu/exposurePass.js +190 -0
- package/dist/render/backend/webgpu/fieldCompose.d.ts +11 -0
- package/dist/render/backend/webgpu/fieldCompose.js +13 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +3 -1
- package/dist/render/backend/webgpu/flatPass.js +12 -5
- package/dist/render/backend/webgpu/particlePass.d.ts +8 -0
- package/dist/render/backend/webgpu/particlePass.js +1 -0
- package/dist/render/backend/webgpu/postPass.d.ts +12 -2
- package/dist/render/backend/webgpu/probeBake.d.ts +59 -3
- package/dist/render/backend/webgpu/probeBake.js +97 -7
- package/dist/render/backend/webgpu/probeTargets.d.ts +65 -0
- package/dist/render/backend/webgpu/probeTargets.js +180 -0
- package/dist/render/backend/webgpu/renderer.d.ts +174 -11
- package/dist/render/backend/webgpu/renderer.js +675 -249
- package/dist/render/backend/webgpu/scatterPass.js +7 -7
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +9 -0
- package/dist/render/backend/webgpu/skyPass.js +5 -1
- package/dist/render/backend/webgpu/uniformRing.d.ts +31 -1
- package/dist/render/backend/webgpu/uniformRing.js +53 -3
- package/dist/render/bloomChain.d.ts +6 -0
- package/dist/render/bloomChain.js +8 -0
- package/dist/render/clusteredLights.d.ts +19 -11
- package/dist/render/clusteredLights.js +96 -45
- package/dist/render/cutoutCaster.d.ts +23 -0
- package/dist/render/cutoutCaster.js +20 -0
- package/dist/render/driftLight/bake.d.ts +34 -0
- package/dist/render/driftLight/bake.js +95 -0
- package/dist/render/driftLight/falloff.d.ts +16 -0
- package/dist/render/driftLight/falloff.js +23 -0
- package/dist/render/driftLight/layout.d.ts +59 -0
- package/dist/render/driftLight/layout.js +138 -0
- package/dist/render/driftLight/lightField.d.ts +108 -0
- package/dist/render/driftLight/lightField.js +179 -0
- package/dist/render/driftLight/uniforms.d.ts +28 -0
- package/dist/render/driftLight/uniforms.js +41 -0
- package/dist/render/driftLight/visibility.d.ts +18 -0
- package/dist/render/driftLight/visibility.js +47 -0
- package/dist/render/emptyTexture.d.ts +6 -0
- package/dist/render/emptyTexture.js +19 -0
- package/dist/render/exposurePass.d.ts +48 -0
- package/dist/render/exposurePass.js +172 -0
- package/dist/render/flameLight.d.ts +71 -0
- package/dist/render/flameLight.js +63 -0
- package/dist/render/gi/bounceLights.d.ts +34 -0
- package/dist/render/gi/bounceLights.js +66 -0
- package/dist/render/gi/fieldReach.d.ts +12 -0
- package/dist/render/gi/fieldReach.js +62 -0
- package/dist/render/gi/globalField.d.ts +8 -0
- package/dist/render/gi/globalField.js +31 -0
- package/dist/render/gi/probeTrace.d.ts +3 -1
- package/dist/render/gi/probeTrace.js +3 -1
- package/dist/render/gpuCapability.js +19 -4
- package/dist/render/lightBudget.d.ts +11 -0
- package/dist/render/lightBudget.js +11 -0
- package/dist/render/pointLightSelection.d.ts +16 -0
- package/dist/render/pointLightSelection.js +178 -83
- package/dist/render/probeGrid.d.ts +11 -1
- package/dist/render/probeGrid.js +5 -2
- package/dist/render/probeSweeps.d.ts +45 -0
- package/dist/render/probeSweeps.js +77 -0
- package/dist/render/reflectionProbe.d.ts +43 -1
- package/dist/render/reflectionProbe.js +17 -3
- package/dist/render/renderQuality.d.ts +3 -1
- package/dist/render/rendererHarness.d.ts +9 -0
- package/dist/render/rendererHarness.js +18 -3
- package/dist/render/sceneTarget.d.ts +20 -0
- package/dist/render/sceneTarget.js +62 -1
- package/dist/render/shaders/agx.d.ts +31 -0
- package/dist/render/shaders/agx.js +81 -0
- package/dist/render/shaders/ambientOcclusion.d.ts +24 -5
- package/dist/render/shaders/ambientOcclusion.js +120 -63
- package/dist/render/shaders/cutoutCoverage.d.ts +31 -0
- package/dist/render/shaders/cutoutCoverage.js +40 -0
- package/dist/render/shaders/depth.d.ts +25 -0
- package/dist/render/shaders/depth.js +69 -3
- package/dist/render/shaders/exposure.d.ts +52 -0
- package/dist/render/shaders/exposure.js +124 -0
- package/dist/render/shaders/filmLook.d.ts +47 -0
- package/dist/render/shaders/filmLook.js +98 -0
- package/dist/render/shaders/flat/directionalShadow.js +35 -4
- package/dist/render/shaders/flat/driftLight.d.ts +29 -0
- package/dist/render/shaders/flat/driftLight.js +75 -0
- package/dist/render/shaders/flat/index.js +5 -0
- package/dist/render/shaders/flat/lobes.js +13 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +68 -9
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/probeGrid.js +46 -3
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +100 -0
- package/dist/render/shaders/generated/depth.wgsl.js +100 -0
- package/dist/render/shaders/generated/exposure.wgsl.d.ts +42 -0
- package/dist/render/shaders/generated/exposure.wgsl.js +48 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1800 -1344
- package/dist/render/shaders/generated/flat.wgsl.js +1816 -1360
- package/dist/render/shaders/generated/localExposure.wgsl.d.ts +18 -0
- package/dist/render/shaders/generated/localExposure.wgsl.js +24 -0
- package/dist/render/shaders/generated/rush.wgsl.d.ts +40 -10
- package/dist/render/shaders/generated/rush.wgsl.js +40 -10
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +16 -0
- package/dist/render/shaders/gi/driftLight.wgsl.js +49 -0
- package/dist/render/shaders/gi/probeBake.wgsl.d.ts +29 -4
- package/dist/render/shaders/gi/probeBake.wgsl.js +190 -20
- package/dist/render/shaders/globalMedium.js +4 -0
- package/dist/render/shaders/localExposure.d.ts +35 -0
- package/dist/render/shaders/localExposure.js +138 -0
- package/dist/render/shaders/prefilter.d.ts +1 -1
- package/dist/render/shaders/prefilter.js +6 -0
- package/dist/render/shaders/rush.js +33 -5
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +15 -3
- package/dist/render/shaders/temporalResolve.js +39 -1
- package/dist/render/shadowCasters.d.ts +5 -3
- package/dist/render/shadowMap.js +13 -0
- package/dist/render/surfaceTexture.d.ts +5 -0
- package/package.json +6 -6
- package/scripts/gpuCompute.mjs +24 -3
- package/scripts/png.mjs +23 -12
- package/src/cinematic/cameraPath.ts +178 -0
- package/src/environment/daylight.ts +152 -0
- package/src/geometry/meshConcat.ts +100 -0
- package/src/index.ts +32 -0
- package/src/render/backend/createRenderer.ts +22 -6
- package/src/render/backend/webgl2/renderer.ts +439 -20
- package/src/render/backend/webgpu/buffers.ts +51 -8
- package/src/render/backend/webgpu/depthPass.ts +78 -10
- package/src/render/backend/webgpu/exposurePass.ts +234 -0
- package/src/render/backend/webgpu/fieldCompose.ts +15 -0
- package/src/render/backend/webgpu/flatPass.ts +10 -2
- package/src/render/backend/webgpu/particlePass.ts +9 -0
- package/src/render/backend/webgpu/probeBake.ts +143 -9
- package/src/render/backend/webgpu/probeTargets.ts +211 -0
- package/src/render/backend/webgpu/renderer.ts +801 -270
- package/src/render/backend/webgpu/scatterPass.ts +7 -7
- package/src/render/backend/webgpu/skinPaletteRing.ts +10 -0
- package/src/render/backend/webgpu/skyPass.ts +5 -1
- package/src/render/backend/webgpu/uniformRing.ts +49 -3
- package/src/render/bloomChain.ts +9 -0
- package/src/render/clusteredLights.ts +108 -63
- package/src/render/cutoutCaster.ts +44 -0
- package/src/render/driftLight/bake.ts +125 -0
- package/src/render/driftLight/falloff.ts +26 -0
- package/src/render/driftLight/layout.ts +177 -0
- package/src/render/driftLight/lightField.ts +227 -0
- package/src/render/driftLight/uniforms.ts +61 -0
- package/src/render/driftLight/visibility.ts +58 -0
- package/src/render/emptyTexture.ts +41 -0
- package/src/render/exposurePass.ts +186 -0
- package/src/render/flameLight.ts +135 -0
- package/src/render/gi/bounceLights.ts +68 -0
- package/src/render/gi/fieldReach.ts +74 -0
- package/src/render/gi/globalField.ts +39 -0
- package/src/render/gi/probeTrace.ts +3 -1
- package/src/render/gpuCapability.ts +18 -5
- package/src/render/lightBudget.ts +12 -0
- package/src/render/pointLightSelection.ts +191 -79
- package/src/render/probeGrid.ts +13 -2
- package/src/render/probeSweeps.ts +80 -0
- package/src/render/reflectionProbe.ts +54 -2
- package/src/render/renderQuality.ts +3 -1
- package/src/render/rendererHarness.ts +25 -3
- package/src/render/sceneTarget.ts +75 -1
- package/src/render/shaders/ambientOcclusion.ts +125 -63
- package/src/render/shaders/cutoutCoverage.ts +43 -0
- package/src/render/shaders/depth.ts +80 -3
- package/src/render/shaders/exposure.ts +128 -0
- package/src/render/shaders/filmLook.ts +111 -0
- package/src/render/shaders/flat/directionalShadow.ts +35 -4
- package/src/render/shaders/flat/driftLight.ts +75 -0
- package/src/render/shaders/flat/index.ts +5 -0
- package/src/render/shaders/flat/lobes.ts +13 -0
- package/src/render/shaders/flat/main.ts +68 -9
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/probeGrid.ts +46 -3
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +103 -0
- package/src/render/shaders/generated/exposure.wgsl.ts +51 -0
- package/src/render/shaders/generated/flat.wgsl.ts +1816 -1360
- package/src/render/shaders/generated/localExposure.wgsl.ts +26 -0
- package/src/render/shaders/generated/rush.wgsl.ts +40 -10
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +49 -0
- package/src/render/shaders/gi/probeBake.wgsl.ts +191 -20
- package/src/render/shaders/globalMedium.ts +4 -0
- package/src/render/shaders/localExposure.ts +154 -0
- package/src/render/shaders/prefilter.ts +6 -0
- package/src/render/shaders/rush.ts +33 -5
- package/src/render/shaders/tangentFrame.ts +15 -3
- package/src/render/shaders/temporalResolve.ts +39 -1
- package/src/render/shadowCasters.ts +5 -3
- package/src/render/shadowMap.ts +14 -0
- package/src/render/surfaceTexture.ts +5 -0
|
@@ -2,7 +2,6 @@ import { FrameBudget } from '../budget.ts';
|
|
|
2
2
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.ts';
|
|
3
3
|
import { mat4 } from 'gl-matrix';
|
|
4
4
|
import {
|
|
5
|
-
DEPTH_OFFSET_SIGN,
|
|
6
5
|
GL_DEPTH_REMAP,
|
|
7
6
|
MAX_DEPTH_LAYER,
|
|
8
7
|
depthClearFor,
|
|
@@ -81,12 +80,17 @@ import {
|
|
|
81
80
|
import { FILM_FRAG, FILM_VERT } from '../../shaders/film.ts';
|
|
82
81
|
import { SceneTarget } from '../../sceneTarget.ts';
|
|
83
82
|
import type { ColourGradeLut } from '../../colourGrade.ts';
|
|
84
|
-
import {
|
|
83
|
+
import { FILM_LOOK_WITHOUT_COMPOSITE } from '../../shaders/filmLook.ts';
|
|
84
|
+
import { bloomProfileWarning, bloomThresholdOf } from '../../bloomChain.ts';
|
|
85
85
|
import {
|
|
86
86
|
createEmptyTexture2D,
|
|
87
87
|
createEmptyTexture2DArray,
|
|
88
|
+
createEmptyTexture3D,
|
|
88
89
|
createEmptyTextureCube,
|
|
89
90
|
} from '../../emptyTexture.ts';
|
|
91
|
+
import { LightField } from '../../driftLight/lightField.ts';
|
|
92
|
+
import type { LightFieldOptions, LightFieldSource } from '../../driftLight/lightField.ts';
|
|
93
|
+
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.ts';
|
|
90
94
|
|
|
91
95
|
/**
|
|
92
96
|
* How far the rush's outermost tap reaches at full strength, in UV.
|
|
@@ -113,11 +117,12 @@ import { SkinPaletteTexture } from './skinPaletteTexture.ts';
|
|
|
113
117
|
/* The codes the shader reads, stated once in `vertexDefaults.ts` for both backends. */
|
|
114
118
|
import { LIGHT_VOLUME_VERT, lightVolumeFrag } from '../../shaders/lightVolume.ts';
|
|
115
119
|
import { PANEL_FRAG, PANEL_VERT } from '../../shaders/panel.ts';
|
|
116
|
-
import { ReflectionProbe, cubeFaceProjection } from '../../reflectionProbe.ts';
|
|
120
|
+
import { ReflectionProbe, cubeFaceProjection, probeFaceRange } from '../../reflectionProbe.ts';
|
|
117
121
|
import type { ProbeBakeOptions } from '../../reflectionProbe.ts';
|
|
118
122
|
import { EnvProbeArray } from '../../envProbeArray.ts';
|
|
119
123
|
import { ProbeGrid, SINGLE_PROBE, UNIT_STEP, WORLD_ORIGIN, sameGrid } from '../../probeGrid.ts';
|
|
120
124
|
import type { ProbeGridOptions } from '../../probeGrid.ts';
|
|
125
|
+
import { ProbeSweeps } from '../../probeSweeps.ts';
|
|
121
126
|
import { octahedralEdgeFor } from '../../prefilterEnvMap.ts';
|
|
122
127
|
import { EnvironmentPrefilterPass } from '../../prefilterPass.ts';
|
|
123
128
|
import { packIesAtlas } from '../../iesProfile.ts';
|
|
@@ -135,7 +140,10 @@ import { equirectToCubeFaces } from '../../equirectToCube.ts';
|
|
|
135
140
|
|
|
136
141
|
import { SKY_FRAG, skyVertFor } from '../../shaders/sky.ts';
|
|
137
142
|
import {
|
|
143
|
+
DEPTH_CUTOUT_FRAG,
|
|
144
|
+
DEPTH_CUTOUT_VERT,
|
|
138
145
|
DEPTH_FRAG,
|
|
146
|
+
DEPTH_INSTANCED_CUTOUT_VERT,
|
|
139
147
|
DEPTH_INSTANCED_VERT,
|
|
140
148
|
DEPTH_SKINNED_VERT,
|
|
141
149
|
DEPTH_VERT,
|
|
@@ -167,6 +175,8 @@ import {
|
|
|
167
175
|
bindPointLights,
|
|
168
176
|
COOKIE_ATLAS_TEXTURE_UNIT,
|
|
169
177
|
REFRACT_SCENE_TEXTURE_UNIT,
|
|
178
|
+
DRIFT_LIGHT_ATLAS_TEXTURE_UNIT,
|
|
179
|
+
DRIFT_LIGHT_INDEX_TEXTURE_UNIT,
|
|
170
180
|
COOKIE_TILE,
|
|
171
181
|
IES_ATLAS_TEXTURE_UNIT,
|
|
172
182
|
bindAreaLights,
|
|
@@ -226,6 +236,9 @@ import type { WindField } from '../../windField.ts';
|
|
|
226
236
|
import type { FlockParams } from '../../flockRenderer.ts';
|
|
227
237
|
import { InstancedBatch } from './instanced.ts';
|
|
228
238
|
import type { MeshInstances } from '../../instances.ts';
|
|
239
|
+
import { cutoutOf } from '../../cutoutCaster.ts';
|
|
240
|
+
import type { CutoutCaster } from '../../cutoutCaster.ts';
|
|
241
|
+
import type { SurfaceTextureHandle } from '../api.ts';
|
|
229
242
|
|
|
230
243
|
/**
|
|
231
244
|
* How many frames `firstFrameSettled` will ask before it stops asking.
|
|
@@ -789,6 +802,14 @@ export interface TranslucentMeshOptions {
|
|
|
789
802
|
* that two surfaces overlapping in view blend in submission order. And a surface that writes no
|
|
790
803
|
* depth cannot occlude the sky. A caller wanting both wants the pieces sorted and this left
|
|
791
804
|
* alone.
|
|
805
|
+
*
|
|
806
|
+
* **And depth a blended surface writes is where the frame's depth-reading passes stop.** The
|
|
807
|
+
* global medium marches to it on both backends, and on WebGPU ambient occlusion, the temporal
|
|
808
|
+
* resolve and motion blur take it for the surface too. Right for a pane that is most of a pixel's
|
|
809
|
+
* colour; wrong for a faint sheet. A bought courtyard's dirt decals stand a centimetre off their
|
|
810
|
+
* stone and overhang every corner they wrap, and the haze stopped at each overhang, a clear band
|
|
811
|
+
* down a pier's whole height. A decal lying on an opaque surface needs no depth to hold the sky
|
|
812
|
+
* off, since the surface under it already does, so `false` costs it only an order among decals.
|
|
792
813
|
*/
|
|
793
814
|
readonly depthWrite?: boolean;
|
|
794
815
|
/**
|
|
@@ -1060,6 +1081,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1060
1081
|
private readonly panelProgram: WebGLProgram;
|
|
1061
1082
|
private readonly panelUniforms: Record<string, WebGLUniformLocation>;
|
|
1062
1083
|
private readonly panelVao: WebGLVertexArrayObject;
|
|
1084
|
+
/** The unit quad behind `panelVao`, held so `dispose` can release it. */
|
|
1085
|
+
private readonly panelBuffer: WebGLBuffer | null;
|
|
1063
1086
|
private readonly filmProgram: WebGLProgram;
|
|
1064
1087
|
/**
|
|
1065
1088
|
* The off-screen colour target, when screen effects are on. Null is not a failure —
|
|
@@ -1091,6 +1114,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1091
1114
|
private skinPaletteSet = false;
|
|
1092
1115
|
|
|
1093
1116
|
private readonly emptyTexture2D: WebGLTexture;
|
|
1117
|
+
/** DriftLight's stand-ins while no field is whole: no brick anywhere, and no light. */
|
|
1118
|
+
private readonly emptyDriftIndex: WebGLTexture;
|
|
1119
|
+
private readonly emptyDriftAtlas: WebGLTexture;
|
|
1120
|
+
/** The scene's DriftLight field, and its two volumes once it is whole. See `createLightField`. */
|
|
1121
|
+
private lightField: LightField | null = null;
|
|
1122
|
+
private driftIndex: WebGLTexture | null = null;
|
|
1123
|
+
private driftAtlas: WebGLTexture | null = null;
|
|
1124
|
+
private readonly driftUniforms = createDriftLightUniforms();
|
|
1094
1125
|
private readonly emptyTexture2DArray: WebGLTexture;
|
|
1095
1126
|
private readonly emptyTextureCube: WebGLTexture;
|
|
1096
1127
|
/** How hard the rush blurs this frame, and how far its outermost tap reaches. */
|
|
@@ -1170,6 +1201,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1170
1201
|
private motionBlurScale = 1;
|
|
1171
1202
|
/** How much of the bloom ceiling this frame takes. See `setBloom`. */
|
|
1172
1203
|
private bloomScale = 1;
|
|
1204
|
+
/** In scene units, once `setBloom` has moved it; null reads the profile's. */
|
|
1205
|
+
private bloomThresholdSet: number | null = null;
|
|
1173
1206
|
/**
|
|
1174
1207
|
* This frame's exposure into the tone curve. See `setOutputExposure`.
|
|
1175
1208
|
*
|
|
@@ -1277,6 +1310,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1277
1310
|
private readonly depthUniforms: Record<string, WebGLUniformLocation>;
|
|
1278
1311
|
private readonly depthInstancedProgram: WebGLProgram;
|
|
1279
1312
|
private readonly depthInstancedUniforms: Record<string, WebGLUniformLocation>;
|
|
1313
|
+
/**
|
|
1314
|
+
* The cutout casters' depth programs, rigid and instanced: a leaf or a chain link casts the shape
|
|
1315
|
+
* in its texture's alpha, not its whole card. See `DEPTH_CUTOUT_FRAG`. Chosen per draw by
|
|
1316
|
+
* `cutoutOf`, so an opaque caster never touches them.
|
|
1317
|
+
*/
|
|
1318
|
+
private readonly depthCutoutProgram: WebGLProgram;
|
|
1319
|
+
private readonly depthCutoutUniforms: Record<string, WebGLUniformLocation>;
|
|
1320
|
+
private readonly depthInstancedCutoutProgram: WebGLProgram;
|
|
1321
|
+
private readonly depthInstancedCutoutUniforms: Record<string, WebGLUniformLocation>;
|
|
1280
1322
|
private readonly scatterDepthProgram: WebGLProgram;
|
|
1281
1323
|
private readonly scatterDepthUniforms: Record<string, WebGLUniformLocation>;
|
|
1282
1324
|
private readonly shadowMap: ShadowMap | null;
|
|
@@ -1335,6 +1377,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1335
1377
|
private readonly probeCapture: ReflectionProbe | null;
|
|
1336
1378
|
/** Every probe of the grid, as one array texture. Allocated when a grid is declared. */
|
|
1337
1379
|
private probeArray: EnvProbeArray | null = null;
|
|
1380
|
+
/** Which layers a bake writes and the shading reads. See `probeSweeps.ts`. */
|
|
1381
|
+
private probeSweeps: ProbeSweeps | null = null;
|
|
1382
|
+
/** `uProbeGridSets`, rewritten in place. */
|
|
1383
|
+
private readonly probeSets = new Float32Array(3);
|
|
1338
1384
|
/**
|
|
1339
1385
|
* Where those probes stand. A single baked probe is a grid of one at that probe's own origin.
|
|
1340
1386
|
*
|
|
@@ -1360,6 +1406,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1360
1406
|
private warnedEnvironmentAmbient = false;
|
|
1361
1407
|
/** Whether a probe is being baked, so the passes that would recurse into one can tell. */
|
|
1362
1408
|
private probePassActive = false;
|
|
1409
|
+
/** Whether the bake under way lights its faces by the grid. `ProbeBakeOptions.bounce`. */
|
|
1410
|
+
private probeBounce = false;
|
|
1363
1411
|
/** The clip-control extension, or null where this context does not offer it. */
|
|
1364
1412
|
private clipControl: ClipControlExtension | null = null;
|
|
1365
1413
|
/**
|
|
@@ -1518,6 +1566,17 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1518
1566
|
*/
|
|
1519
1567
|
private activeDepthViewProj: ReadonlyMat4 = IDENTITY_MAT4;
|
|
1520
1568
|
private activeDepthPeel = false;
|
|
1569
|
+
/**
|
|
1570
|
+
* Whether a peel pass has filled the second layer since the first one last opened, which is
|
|
1571
|
+
* what the frame samples it on: the rule the other backend's `peelFilled` states, held here too.
|
|
1572
|
+
*
|
|
1573
|
+
* **Not whether the map exists.** That was the test here, on the belief that a GL texture is
|
|
1574
|
+
* born reading as the far plane; it is born zeroed, and a scene that built the layer and never
|
|
1575
|
+
* peeled it sampled an occluder at the light. `ShadowMap` now clears itself at birth, so an
|
|
1576
|
+
* unfilled layer would read as lit either way — this is what keeps the two backends one
|
|
1577
|
+
* decision, and a frame that does not peel from paying a fetch per tap for nothing.
|
|
1578
|
+
*/
|
|
1579
|
+
private peelFilled = false;
|
|
1521
1580
|
/**
|
|
1522
1581
|
* Whether the depth pass this scatter draw interrupts wants back-face culling.
|
|
1523
1582
|
*
|
|
@@ -1528,6 +1587,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1528
1587
|
* cubemap — so the pass states what it wants and this restores that.
|
|
1529
1588
|
*/
|
|
1530
1589
|
private depthPassCullsFaces = true;
|
|
1590
|
+
/**
|
|
1591
|
+
* Whether the bound material is seen from both faces, so its draws cull nothing. Toggled around
|
|
1592
|
+
* each such draw and handed back, where the other backend bakes it into a pipeline.
|
|
1593
|
+
*/
|
|
1594
|
+
private materialDoubleSided = false;
|
|
1531
1595
|
|
|
1532
1596
|
/**
|
|
1533
1597
|
* The one sink handed to every caster enumeration.
|
|
@@ -1536,16 +1600,34 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1536
1600
|
* per pass would allocate six objects per light per frame.
|
|
1537
1601
|
*/
|
|
1538
1602
|
private readonly casterSink: ShadowCasterSink = {
|
|
1539
|
-
mesh: (mesh, model) => {
|
|
1603
|
+
mesh: (mesh, model, material) => {
|
|
1540
1604
|
this.shadowDrawBudget.ask();
|
|
1541
1605
|
const { gl } = this;
|
|
1606
|
+
/* A two-sided caster casts from whichever face the light sees: nothing culled. */
|
|
1607
|
+
const twoSided = material?.doubleSided === true;
|
|
1608
|
+
const cutout = cutoutOf(material);
|
|
1609
|
+
if (cutout !== null) {
|
|
1610
|
+
const u = this.depthCutoutUniforms;
|
|
1611
|
+
gl.useProgram(this.depthCutoutProgram);
|
|
1612
|
+
this.bindCutoutDepth(u, cutout);
|
|
1613
|
+
gl.uniformMatrix4fv(u['uModel'] ?? null, false, model);
|
|
1614
|
+
if (twoSided) gl.disable(gl.CULL_FACE);
|
|
1615
|
+
(mesh as Mesh).draw(gl);
|
|
1616
|
+
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1617
|
+
/* Hand the pass back as it was found: the next caster may be rigid and opaque. */
|
|
1618
|
+
gl.useProgram(this.depthProgram);
|
|
1619
|
+
this.restoreSurfaceTextureUnit();
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1542
1622
|
gl.uniformMatrix4fv(this.depthUniforms['uModel'] ?? null, false, model);
|
|
1543
1623
|
/*
|
|
1544
1624
|
* The sink takes the opaque handle both backends share, and this renderer only ever
|
|
1545
1625
|
* handed out its own `Mesh`. Narrowing is safe here for that reason and nowhere else:
|
|
1546
1626
|
* a handle from the other backend would have to have crossed renderers to arrive.
|
|
1547
1627
|
*/
|
|
1628
|
+
if (twoSided) gl.disable(gl.CULL_FACE);
|
|
1548
1629
|
(mesh as Mesh).draw(gl);
|
|
1630
|
+
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1549
1631
|
},
|
|
1550
1632
|
/**
|
|
1551
1633
|
* A batch into the depth map, placed by the same matrices the visible draw uses.
|
|
@@ -1553,18 +1635,31 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1553
1635
|
* No `uModel`: the instanced depth program has none, its placement being the four attribute
|
|
1554
1636
|
* columns the batch's own buffer supplies.
|
|
1555
1637
|
*/
|
|
1556
|
-
instanced: (batch, data) => {
|
|
1638
|
+
instanced: (batch, data, material) => {
|
|
1557
1639
|
const { gl } = this;
|
|
1558
1640
|
const gpuBatch = batch as InstancedBatch;
|
|
1559
1641
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
1560
1642
|
if (count === 0) return;
|
|
1561
1643
|
this.shadowDrawBudget.ask();
|
|
1644
|
+
const twoSided = material?.doubleSided === true;
|
|
1645
|
+
if (twoSided) gl.disable(gl.CULL_FACE);
|
|
1646
|
+
const cutout = cutoutOf(material);
|
|
1647
|
+
if (cutout !== null) {
|
|
1648
|
+
gl.useProgram(this.depthInstancedCutoutProgram);
|
|
1649
|
+
this.bindCutoutDepth(this.depthInstancedCutoutUniforms, cutout);
|
|
1650
|
+
gpuBatch.mesh.drawInstances(gl, count);
|
|
1651
|
+
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1652
|
+
gl.useProgram(this.depthProgram);
|
|
1653
|
+
this.restoreSurfaceTextureUnit();
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1562
1656
|
const u = this.depthInstancedUniforms;
|
|
1563
1657
|
gl.useProgram(this.depthInstancedProgram);
|
|
1564
1658
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
1565
1659
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1566
1660
|
if (this.activeDepthPeel) gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1567
1661
|
gpuBatch.mesh.drawInstances(gl, count);
|
|
1662
|
+
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1568
1663
|
gl.useProgram(this.depthProgram);
|
|
1569
1664
|
},
|
|
1570
1665
|
skinnedMesh: (mesh, model, palette) => {
|
|
@@ -1612,6 +1707,24 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1612
1707
|
},
|
|
1613
1708
|
};
|
|
1614
1709
|
|
|
1710
|
+
/**
|
|
1711
|
+
* What a cutout depth draw needs beyond the plain one: the pass's matrix and peel, the material's
|
|
1712
|
+
* cutoff and UV scale, and its albedo on the surface unit, which the caller gives back after.
|
|
1713
|
+
*/
|
|
1714
|
+
private bindCutoutDepth(
|
|
1715
|
+
u: Record<string, WebGLUniformLocation>,
|
|
1716
|
+
cutout: CutoutCaster<SurfaceTextureHandle>,
|
|
1717
|
+
): void {
|
|
1718
|
+
const { gl } = this;
|
|
1719
|
+
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
1720
|
+
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1721
|
+
if (this.activeDepthPeel) gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1722
|
+
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
1723
|
+
gl.uniform1f(u['uAlphaCutout'] ?? null, cutout.cutoff);
|
|
1724
|
+
/* Narrowed for the reason the sink's own `mesh` gives: this renderer only hands out its own. */
|
|
1725
|
+
(cutout.albedo as SurfaceTexture).bind(gl, SURFACE_TEXTURE_UNIT);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1615
1728
|
private readonly drawPointShadowFace = (viewProj: ReadonlyMat4): void => {
|
|
1616
1729
|
const casters = this.pointShadowCasters;
|
|
1617
1730
|
if (casters === null) return;
|
|
@@ -2262,6 +2375,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2262
2375
|
* "unbound". Two 1x1 uploads for the life of the renderer.
|
|
2263
2376
|
*/
|
|
2264
2377
|
this.emptyTexture2D = createEmptyTexture2D(gl);
|
|
2378
|
+
this.emptyDriftIndex = createEmptyTexture3D(gl, true);
|
|
2379
|
+
this.emptyDriftAtlas = createEmptyTexture3D(gl, false);
|
|
2265
2380
|
this.emptyTexture2DArray = createEmptyTexture2DArray(gl);
|
|
2266
2381
|
this.emptyTextureCube = createEmptyTextureCube(gl);
|
|
2267
2382
|
this.gpuTimer = new GpuTimer(gl);
|
|
@@ -2483,6 +2598,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2483
2598
|
this.panelVao = panelVao;
|
|
2484
2599
|
gl.bindVertexArray(panelVao);
|
|
2485
2600
|
const panelBuffer = gl.createBuffer();
|
|
2601
|
+
this.panelBuffer = panelBuffer;
|
|
2486
2602
|
gl.bindBuffer(gl.ARRAY_BUFFER, panelBuffer);
|
|
2487
2603
|
// A unit quad. The rectangle it becomes is a uniform, so this is uploaded once
|
|
2488
2604
|
// for the life of the renderer however many panels are drawn.
|
|
@@ -2570,6 +2686,33 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2570
2686
|
*/
|
|
2571
2687
|
this.scatterDepthProgram = compileProgram(gl, SCATTER_DEPTH_VERT, DEPTH_FRAG, 'scatterDepth');
|
|
2572
2688
|
this.scatterDepthUniforms = uniformLocations(gl, this.scatterDepthProgram, 'scatterDepth');
|
|
2689
|
+
this.depthCutoutProgram = compileProgram(
|
|
2690
|
+
gl,
|
|
2691
|
+
DEPTH_CUTOUT_VERT,
|
|
2692
|
+
DEPTH_CUTOUT_FRAG,
|
|
2693
|
+
'depth-cutout',
|
|
2694
|
+
);
|
|
2695
|
+
this.depthCutoutUniforms = uniformLocations(gl, this.depthCutoutProgram, 'depth-cutout');
|
|
2696
|
+
this.depthInstancedCutoutProgram = compileProgram(
|
|
2697
|
+
gl,
|
|
2698
|
+
DEPTH_INSTANCED_CUTOUT_VERT,
|
|
2699
|
+
DEPTH_CUTOUT_FRAG,
|
|
2700
|
+
'depth-instanced-cutout',
|
|
2701
|
+
);
|
|
2702
|
+
this.depthInstancedCutoutUniforms = uniformLocations(
|
|
2703
|
+
gl,
|
|
2704
|
+
this.depthInstancedCutoutProgram,
|
|
2705
|
+
'depth-instanced-cutout',
|
|
2706
|
+
);
|
|
2707
|
+
/* The cutout map is always read from the surface unit, which the draw borrows and gives back. */
|
|
2708
|
+
for (const [program, uniforms] of [
|
|
2709
|
+
[this.depthCutoutProgram, this.depthCutoutUniforms],
|
|
2710
|
+
[this.depthInstancedCutoutProgram, this.depthInstancedCutoutUniforms],
|
|
2711
|
+
] as const) {
|
|
2712
|
+
gl.useProgram(program);
|
|
2713
|
+
gl.uniform1i(uniforms['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
2714
|
+
}
|
|
2715
|
+
gl.useProgram(null);
|
|
2573
2716
|
this.shadowMap = this.quality.directionalShadows
|
|
2574
2717
|
? new ShadowMap(gl, this.quality.directionalShadowMapSize)
|
|
2575
2718
|
: null;
|
|
@@ -2736,6 +2879,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2736
2879
|
this.flatSkinnedProgram,
|
|
2737
2880
|
this.flatMorphedProgram,
|
|
2738
2881
|
this.flatBothProgram,
|
|
2882
|
+
this.flatInstancedProgram,
|
|
2739
2883
|
]) {
|
|
2740
2884
|
if (extra !== null) gl.deleteProgram(extra);
|
|
2741
2885
|
}
|
|
@@ -2746,12 +2890,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2746
2890
|
this.skyProgram,
|
|
2747
2891
|
this.scatterProgram,
|
|
2748
2892
|
this.depthProgram,
|
|
2893
|
+
this.depthSkinnedProgram,
|
|
2894
|
+
this.depthInstancedProgram,
|
|
2749
2895
|
this.scatterDepthProgram,
|
|
2896
|
+
this.lightVolumeProgram,
|
|
2897
|
+
this.depthCutoutProgram,
|
|
2898
|
+
this.depthInstancedCutoutProgram,
|
|
2750
2899
|
]) {
|
|
2751
2900
|
gl.deleteProgram(program);
|
|
2752
2901
|
}
|
|
2753
2902
|
this.skinPalette.dispose(gl);
|
|
2754
2903
|
gl.deleteTexture(this.emptyTexture2D);
|
|
2904
|
+
gl.deleteTexture(this.emptyDriftIndex);
|
|
2905
|
+
gl.deleteTexture(this.emptyDriftAtlas);
|
|
2906
|
+
this.forgetLightFieldVolumes();
|
|
2755
2907
|
gl.deleteTexture(this.emptyTexture2DArray);
|
|
2756
2908
|
gl.deleteTexture(this.emptyTextureCube);
|
|
2757
2909
|
/* A cubemap and its whole mip chain, which is the largest single thing this
|
|
@@ -2759,6 +2911,24 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2759
2911
|
this.probeCapture?.dispose(gl);
|
|
2760
2912
|
this.probeArray?.dispose(gl);
|
|
2761
2913
|
this.environmentPrefilter?.dispose(gl);
|
|
2914
|
+
/*
|
|
2915
|
+
* **Everything else the constructor made, which this method never released.** The screen
|
|
2916
|
+
* chain and its bloom and occlusion, three sun maps, the mirror, the point-shadow array, two
|
|
2917
|
+
* vertex arrays, a quad and three textures made on first use. A page swapping renderers kept
|
|
2918
|
+
* every one of them, per renderer, for as long as it lived; `renderer.test.ts` counts them.
|
|
2919
|
+
*/
|
|
2920
|
+
this.sceneTarget?.dispose();
|
|
2921
|
+
this.shadowMap?.dispose(gl);
|
|
2922
|
+
this.peeledShadowMap?.dispose(gl);
|
|
2923
|
+
this.dynamicShadowMap?.dispose(gl);
|
|
2924
|
+
this.planarReflection?.dispose(gl);
|
|
2925
|
+
this.pointShadows?.dispose();
|
|
2926
|
+
gl.deleteVertexArray(this.panelVao);
|
|
2927
|
+
gl.deleteVertexArray(this.skyVao);
|
|
2928
|
+
if (this.panelBuffer !== null) gl.deleteBuffer(this.panelBuffer);
|
|
2929
|
+
if (this.cookieTexture !== null) gl.deleteTexture(this.cookieTexture);
|
|
2930
|
+
if (this.iesTexture !== null) gl.deleteTexture(this.iesTexture);
|
|
2931
|
+
if (this.clusterTexture !== null) gl.deleteTexture(this.clusterTexture);
|
|
2762
2932
|
}
|
|
2763
2933
|
|
|
2764
2934
|
if (options.releaseContext === true) {
|
|
@@ -2987,6 +3157,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2987
3157
|
if (this.contextLost) return;
|
|
2988
3158
|
this.materials.dirty();
|
|
2989
3159
|
this.currentSurfaceTexture = material?.albedo ?? null;
|
|
3160
|
+
this.materialDoubleSided = material?.doubleSided === true;
|
|
2990
3161
|
/*
|
|
2991
3162
|
* Written to every flat program, because material state persists across draws and a skinned
|
|
2992
3163
|
* draw is entitled to the material the caller set before it. A second program holding none of
|
|
@@ -3066,10 +3237,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3066
3237
|
* The emissive map, on exactly the terms the two maps above it are bound: before the albedo
|
|
3067
3238
|
* early return, and the stand-in rather than `null`.
|
|
3068
3239
|
*
|
|
3069
|
-
* **
|
|
3070
|
-
* map,
|
|
3071
|
-
*
|
|
3072
|
-
*
|
|
3240
|
+
* **The stand-in is black, so the gate is what keeps a glow on.** The shader multiplies by
|
|
3241
|
+
* this map, and `emptyTexture2D` is opaque black rather than the multiplicative identity, so an
|
|
3242
|
+
* unbound material keeps the arithmetic it had before only because `uEmissiveMapEnabled` is 0
|
|
3243
|
+
* and the read never happens. What would make that wrong is a read taken outside that branch:
|
|
3244
|
+
* every glow on a material without a map would go out.
|
|
3073
3245
|
*/
|
|
3074
3246
|
const emissiveMap = material?.emissive ?? null;
|
|
3075
3247
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
@@ -3093,6 +3265,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3093
3265
|
* how big a dome was — WebGPU writes it before its own early return and WebGL2 did not.
|
|
3094
3266
|
*/
|
|
3095
3267
|
gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
|
|
3268
|
+
gl.uniform1i(u['uDoubleSided'] ?? null, material?.doubleSided === true ? 1 : 0);
|
|
3096
3269
|
|
|
3097
3270
|
if (albedo === null) {
|
|
3098
3271
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
@@ -3889,7 +4062,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3889
4062
|
this.reflectionPassActive ? this.reflectionAtmosphereY : (camera.position[1] ?? 0),
|
|
3890
4063
|
this.quality.underwaterAtmosphere,
|
|
3891
4064
|
);
|
|
4065
|
+
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
3892
4066
|
batch.drawTo(gl, data.count);
|
|
4067
|
+
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
3893
4068
|
}
|
|
3894
4069
|
|
|
3895
4070
|
/** Build a batch for electrical arcs. `capacity` is in segments, not arcs. */
|
|
@@ -4563,6 +4738,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4563
4738
|
this.gpuTimer.begin('shadows');
|
|
4564
4739
|
this.shadowPassActive = true;
|
|
4565
4740
|
this.activeShadowMap = shadowMap;
|
|
4741
|
+
/* The static layer opens first, so a frame's peel counts from here. See `peelFilled`. */
|
|
4742
|
+
if (layer === 'static') this.peelFilled = false;
|
|
4566
4743
|
/*
|
|
4567
4744
|
* **Range-corrected but not reversed.** `EXT_clip_control` is context state, so a light matrix
|
|
4568
4745
|
* that still emits OpenGL's `[-1, 1]` loses everything below zero the moment it is on. See
|
|
@@ -4620,6 +4797,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4620
4797
|
this.uploadWind(this.depthUniforms, this.frameWind);
|
|
4621
4798
|
gl.useProgram(this.depthSkinnedProgram);
|
|
4622
4799
|
this.uploadWind(this.depthSkinnedUniforms, this.frameWind);
|
|
4800
|
+
gl.useProgram(this.depthCutoutProgram);
|
|
4801
|
+
this.uploadWind(this.depthCutoutUniforms, this.frameWind);
|
|
4623
4802
|
gl.useProgram(this.depthProgram);
|
|
4624
4803
|
}
|
|
4625
4804
|
|
|
@@ -4711,6 +4890,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4711
4890
|
const shadowMap = this.activeShadowMap;
|
|
4712
4891
|
if (!this.shadowPassActive || shadowMap === null) return;
|
|
4713
4892
|
shadowMap.end(this.gl);
|
|
4893
|
+
if (shadowMap === this.peeledShadowMap) this.peelFilled = true;
|
|
4714
4894
|
/* Back to the frame's sense; see `beginShadowPass` for why it left it. */
|
|
4715
4895
|
this.gl.depthFunc(glDepthFuncEqual(this.gl));
|
|
4716
4896
|
this.gl.clearDepth(this.depthClear);
|
|
@@ -5441,6 +5621,73 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5441
5621
|
this.motionBlurScale = Math.min(Math.max(scale, 0), 1);
|
|
5442
5622
|
}
|
|
5443
5623
|
|
|
5624
|
+
/**
|
|
5625
|
+
* The camera has cut: this frame is a new shot, not the next moment of the last one.
|
|
5626
|
+
*
|
|
5627
|
+
* **Everything temporal reprojects through the previous frame's view**: the motion blur measures
|
|
5628
|
+
* its smear against it, and the temporal resolve reads its history through it. A cut is a jump
|
|
5629
|
+
* the renderer cannot tell from a fast camera, so without this a transport that seeks, a respawn
|
|
5630
|
+
* or an edit smears its first frame along the whole jump and blends in a picture of somewhere
|
|
5631
|
+
* else. The caller knows it cut; this is how it says so, before the frame's `beginFrame`.
|
|
5632
|
+
*
|
|
5633
|
+
* What it gives up is one frame of history: the frame after a cut has no blur and no temporal
|
|
5634
|
+
* blend, as the first frame of a session has none. Nothing else is reset.
|
|
5635
|
+
*/
|
|
5636
|
+
cameraCut(): void {
|
|
5637
|
+
this.hasPreviousView = false;
|
|
5638
|
+
this.temporalHistory.invalidate();
|
|
5639
|
+
/* The eye was adapted to the last shot. */
|
|
5640
|
+
this.sceneTarget?.cutExposure();
|
|
5641
|
+
}
|
|
5642
|
+
|
|
5643
|
+
/**
|
|
5644
|
+
* **Eye adaptation**: how far the frame's exposure follows its own brightness, 0 to 1, and the
|
|
5645
|
+
* seconds since the last frame. 0 is off and the default, and at 0 nothing is metered or allocated.
|
|
5646
|
+
*
|
|
5647
|
+
* The finished scene is metered on the GPU each frame — the log-mean of its luminance — and a
|
|
5648
|
+
* held brightness moves toward it at a fixed rate, about 78% of the way in a second; the composite
|
|
5649
|
+
* then scales scene light toward middle grey by \`strength\` of the stops between them, at most six
|
|
5650
|
+
* either way. So a shaded courtyard floor opens up the way a camera or an eye would, and
|
|
5651
|
+
* \`setOutputExposure\` becomes a bias on top: a night kept darker than a day is a bias under one.
|
|
5652
|
+
* **The time is the caller's**, because the engine takes time from its caller; a held capture
|
|
5653
|
+
* passes 0 and holds. \`cameraCut\` snaps it, so a new shot is metered rather than eased into.
|
|
5654
|
+
*
|
|
5655
|
+
* What it gives up is regional metering: every pixel counts the same. Needs \`screenEffects\` and
|
|
5656
|
+
* \`hdrScene\`, and says so once rather than doing nothing. See \`shaders/exposure.ts\`.
|
|
5657
|
+
*/
|
|
5658
|
+
setAutoExposure(strength: number, dtSec: number): void {
|
|
5659
|
+
if (this.sceneTarget === null || !this.quality.hdrScene) {
|
|
5660
|
+
this.warnLookWithoutComposite(strength);
|
|
5661
|
+
return;
|
|
5662
|
+
}
|
|
5663
|
+
this.sceneTarget.setAutoExposure(strength, dtSec);
|
|
5664
|
+
}
|
|
5665
|
+
|
|
5666
|
+
/**
|
|
5667
|
+
* **Local exposure**: how far each region of the frame is brought toward the frame's own
|
|
5668
|
+
* brightness, 0 to 1. 0 is off and the default, and at 0 nothing more is measured or allocated.
|
|
5669
|
+
*
|
|
5670
|
+
* A tone curve maps one range, and a sunlit courtyard is two: its shaded arcades hold a fiftieth
|
|
5671
|
+
* of the light on its sunlit paving, so exposed for either the other is black or white. This moves
|
|
5672
|
+
* each region `strength` of the stops between it and the frame's held brightness, at most three,
|
|
5673
|
+
* read from a bilateral grid over the exposure meter's tiles, so shade beside sun is lifted as
|
|
5674
|
+
* shade rather than as the average of the two. The eye's held brightness is what regions move
|
|
5675
|
+
* toward, so it is measured whether or not `setAutoExposure` is on; with adaptation off the frame
|
|
5676
|
+
* as a whole keeps the exposure it was given. About 0.5 is a photograph's latitude, and 1 is a
|
|
5677
|
+
* frame with no light and shade left in it.
|
|
5678
|
+
*
|
|
5679
|
+
* What it gives up is contrast between regions, which is the point, and a halo a tile wide where
|
|
5680
|
+
* two regions of one brightness band differ. Needs `screenEffects` and `hdrScene`, and says so
|
|
5681
|
+
* once rather than doing nothing. See `shaders/localExposure.ts`.
|
|
5682
|
+
*/
|
|
5683
|
+
setLocalExposure(strength: number): void {
|
|
5684
|
+
if (this.sceneTarget === null || !this.quality.hdrScene) {
|
|
5685
|
+
this.warnLookWithoutComposite(strength);
|
|
5686
|
+
return;
|
|
5687
|
+
}
|
|
5688
|
+
this.sceneTarget.setLocalExposure(strength);
|
|
5689
|
+
}
|
|
5690
|
+
|
|
5444
5691
|
/**
|
|
5445
5692
|
* Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
|
|
5446
5693
|
* to take.
|
|
@@ -5495,9 +5742,17 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5495
5742
|
*
|
|
5496
5743
|
* Set before `beginFrame` and held until changed. Ignored entirely when `bloom` is 0, since
|
|
5497
5744
|
* the chain is never built.
|
|
5745
|
+
*
|
|
5746
|
+
* **`threshold` moves `bloomThreshold` for this frame and every one after**, in the same scene
|
|
5747
|
+
* units. A threshold is compared before exposure is applied, so one fixed at construction means a
|
|
5748
|
+
* different brightness on screen at every exposure. That is harmless while exposure stands still
|
|
5749
|
+
* and wrong for a day whose exposure spans 2.5 to 14: a courtyard blooming at noon, or candles
|
|
5750
|
+
* never blooming at night. A caller whose exposure moves passes what it wants on screen over the
|
|
5751
|
+
* exposure. Omitted leaves it where it was, so a caller that never passes one keeps the profile's.
|
|
5498
5752
|
*/
|
|
5499
|
-
setBloom(scale: number): void {
|
|
5753
|
+
setBloom(scale: number, threshold?: number): void {
|
|
5500
5754
|
this.bloomScale = Math.min(Math.max(scale, 0), 1);
|
|
5755
|
+
if (threshold !== undefined) this.bloomThresholdSet = bloomThresholdOf(threshold);
|
|
5501
5756
|
}
|
|
5502
5757
|
|
|
5503
5758
|
/**
|
|
@@ -5644,6 +5899,41 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5644
5899
|
this.sceneTarget.setColourGrade(lut, strength);
|
|
5645
5900
|
}
|
|
5646
5901
|
|
|
5902
|
+
/**
|
|
5903
|
+
* How strongly the lens darkens the frame's corners, held until changed: 0 is none, 0.5 is about
|
|
5904
|
+
* 1.2 stops at the corner. Applied to scene light before the tone curve, as a lens loses light;
|
|
5905
|
+
* see `filmLook.ts`. Needs `screenEffects`, and says so once rather than doing nothing.
|
|
5906
|
+
*/
|
|
5907
|
+
setVignette(strength: number): void {
|
|
5908
|
+
if (this.sceneTarget === null) {
|
|
5909
|
+
this.warnLookWithoutComposite(strength);
|
|
5910
|
+
return;
|
|
5911
|
+
}
|
|
5912
|
+
this.sceneTarget.setVignette(strength);
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5915
|
+
/**
|
|
5916
|
+
* Film grain, held until changed: an amplitude in display values (0 is none, 0.03 is a fine
|
|
5917
|
+
* grain) and this frame's seed. **The seed is the caller's**, because the engine takes time from
|
|
5918
|
+
* its caller: pass a new one each frame for grain that moves, and the same one for a still that
|
|
5919
|
+
* is identical run to run. Applied last, after the grade and the veil; see `filmLook.ts`.
|
|
5920
|
+
*/
|
|
5921
|
+
setFilmGrain(strength: number, seed: number): void {
|
|
5922
|
+
if (this.sceneTarget === null) {
|
|
5923
|
+
this.warnLookWithoutComposite(strength);
|
|
5924
|
+
return;
|
|
5925
|
+
}
|
|
5926
|
+
this.sceneTarget.setFilmGrain(strength, seed);
|
|
5927
|
+
}
|
|
5928
|
+
|
|
5929
|
+
private warnedLookWithoutComposite = false;
|
|
5930
|
+
private warnLookWithoutComposite(strength: number): void {
|
|
5931
|
+
if (strength > 0 && !this.warnedLookWithoutComposite) {
|
|
5932
|
+
this.warnedLookWithoutComposite = true;
|
|
5933
|
+
console.warn(FILM_LOOK_WITHOUT_COMPOSITE);
|
|
5934
|
+
}
|
|
5935
|
+
}
|
|
5936
|
+
|
|
5647
5937
|
/**
|
|
5648
5938
|
* What this frame asked for. **Every ceiling here is `null`, and that is the finding.**
|
|
5649
5939
|
*
|
|
@@ -5982,7 +6272,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5982
6272
|
const bloomStrength = this.quality.bloom * this.bloomScale;
|
|
5983
6273
|
const bloom =
|
|
5984
6274
|
bloomStrength > 0
|
|
5985
|
-
? {
|
|
6275
|
+
? {
|
|
6276
|
+
strength: bloomStrength,
|
|
6277
|
+
threshold: this.bloomThresholdSet ?? this.quality.bloomThreshold,
|
|
6278
|
+
}
|
|
5986
6279
|
: undefined;
|
|
5987
6280
|
|
|
5988
6281
|
/* The grade travels to the resolve only where the mesh pass gave it up. See `bindMeshPass`. */
|
|
@@ -6189,11 +6482,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6189
6482
|
sunShadow,
|
|
6190
6483
|
lightViewProj: env?.lightViewProj ?? IDENTITY_MAT4,
|
|
6191
6484
|
/* The placeholder rather than null where a map is merely absent, for the reason
|
|
6192
|
-
`emptyTexture.ts` gives
|
|
6485
|
+
`emptyTexture.ts` gives. It is black, which as a depth is an occluder at the light,
|
|
6486
|
+
so it is safe only because `sunShadow` is 0 whenever these maps do not exist. */
|
|
6193
6487
|
staticShadowMap: this.shadowMap?.texture ?? this.emptyTexture2D,
|
|
6194
6488
|
peeledShadowMap: this.peeledShadowMap?.texture ?? this.emptyTexture2D,
|
|
6195
6489
|
dynamicShadowMap: this.dynamicShadowMap?.texture ?? this.emptyTexture2D,
|
|
6196
|
-
peeledEnabled: this.
|
|
6490
|
+
peeledEnabled: this.peelFilled,
|
|
6197
6491
|
},
|
|
6198
6492
|
);
|
|
6199
6493
|
if (marched) {
|
|
@@ -6415,6 +6709,100 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6415
6709
|
this.useFlatProgram();
|
|
6416
6710
|
}
|
|
6417
6711
|
|
|
6712
|
+
/**
|
|
6713
|
+
* Sum a scene's many fixed lights into a DriftLight field, which stands in for each of them
|
|
6714
|
+
* wherever the frame's choice of exact lights does not reach. See `driftLight/lightField.ts`.
|
|
6715
|
+
*
|
|
6716
|
+
* Replaces any field made before. The scene then paces `field.bake(bricks)` and calls
|
|
6717
|
+
* `field.follow(buffer.complete, ...)` each frame with the selection that chose its exact lights;
|
|
6718
|
+
* the volume is summed into the frame once every brick has landed.
|
|
6719
|
+
*/
|
|
6720
|
+
createLightField(
|
|
6721
|
+
lights: readonly LightFieldSource[],
|
|
6722
|
+
options: Omit<LightFieldOptions, 'falloff'> = {},
|
|
6723
|
+
): LightField {
|
|
6724
|
+
this.forgetLightFieldVolumes();
|
|
6725
|
+
const field = new LightField(lights, { ...options, falloff: this.quality.pointLightFalloff });
|
|
6726
|
+
this.lightField = field;
|
|
6727
|
+
return field;
|
|
6728
|
+
}
|
|
6729
|
+
|
|
6730
|
+
/** Let go of the field and its volumes; the scene's lights are all exact again. */
|
|
6731
|
+
disposeLightField(): void {
|
|
6732
|
+
this.forgetLightFieldVolumes();
|
|
6733
|
+
this.lightField = null;
|
|
6734
|
+
}
|
|
6735
|
+
|
|
6736
|
+
private forgetLightFieldVolumes(): void {
|
|
6737
|
+
if (this.driftIndex !== null) this.gl.deleteTexture(this.driftIndex);
|
|
6738
|
+
if (this.driftAtlas !== null) this.gl.deleteTexture(this.driftAtlas);
|
|
6739
|
+
this.driftIndex = null;
|
|
6740
|
+
this.driftAtlas = null;
|
|
6741
|
+
}
|
|
6742
|
+
|
|
6743
|
+
/**
|
|
6744
|
+
* Upload the field's two volumes, once, when its last brick has baked: nothing samples them
|
|
6745
|
+
* before that, so uploading as bricks land would be work for no picture.
|
|
6746
|
+
*/
|
|
6747
|
+
private uploadLightField(): void {
|
|
6748
|
+
const field = this.lightField;
|
|
6749
|
+
if (field === null || this.driftAtlas !== null || !field.ready || field.layout.count === 0) {
|
|
6750
|
+
return;
|
|
6751
|
+
}
|
|
6752
|
+
const { gl } = this;
|
|
6753
|
+
const [ix, iy, iz] = field.layout.dims;
|
|
6754
|
+
const index = gl.createTexture();
|
|
6755
|
+
gl.bindTexture(gl.TEXTURE_3D, index);
|
|
6756
|
+
gl.texImage3D(
|
|
6757
|
+
gl.TEXTURE_3D,
|
|
6758
|
+
0,
|
|
6759
|
+
gl.R32UI,
|
|
6760
|
+
ix,
|
|
6761
|
+
iy,
|
|
6762
|
+
iz,
|
|
6763
|
+
0,
|
|
6764
|
+
gl.RED_INTEGER,
|
|
6765
|
+
gl.UNSIGNED_INT,
|
|
6766
|
+
field.layout.index,
|
|
6767
|
+
);
|
|
6768
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
6769
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
6770
|
+
const [aw, ah, ad] = field.atlasSize;
|
|
6771
|
+
const atlas = gl.createTexture();
|
|
6772
|
+
gl.bindTexture(gl.TEXTURE_3D, atlas);
|
|
6773
|
+
gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA16F, aw, ah, ad, 0, gl.RGBA, gl.HALF_FLOAT, field.atlas);
|
|
6774
|
+
/* Filtered, because a brick's samples share their faces: the lookup is seamless inside one. */
|
|
6775
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
6776
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
6777
|
+
for (const wrap of [gl.TEXTURE_WRAP_S, gl.TEXTURE_WRAP_T, gl.TEXTURE_WRAP_R]) {
|
|
6778
|
+
gl.texParameteri(gl.TEXTURE_3D, wrap, gl.CLAMP_TO_EDGE);
|
|
6779
|
+
}
|
|
6780
|
+
gl.bindTexture(gl.TEXTURE_3D, null);
|
|
6781
|
+
this.driftIndex = index;
|
|
6782
|
+
this.driftAtlas = atlas;
|
|
6783
|
+
}
|
|
6784
|
+
|
|
6785
|
+
/** The field's uniforms and volumes for this pass, or the stand-ins that switch it off. */
|
|
6786
|
+
private bindDriftLight(u: Record<string, WebGLUniformLocation>, eye: ArrayLike<number>): void {
|
|
6787
|
+
const { gl } = this;
|
|
6788
|
+
this.uploadLightField();
|
|
6789
|
+
const d = this.driftUniforms;
|
|
6790
|
+
resolveDriftLight(
|
|
6791
|
+
this.driftAtlas === null ? null : this.lightField,
|
|
6792
|
+
this.probePassActive,
|
|
6793
|
+
eye,
|
|
6794
|
+
d,
|
|
6795
|
+
);
|
|
6796
|
+
gl.uniform4fv(u['uDriftLight'] ?? null, d.light);
|
|
6797
|
+
gl.uniform4fv(u['uDriftLightOrigin'] ?? null, d.origin);
|
|
6798
|
+
gl.activeTexture(gl.TEXTURE0 + DRIFT_LIGHT_INDEX_TEXTURE_UNIT);
|
|
6799
|
+
gl.bindTexture(gl.TEXTURE_3D, this.driftIndex ?? this.emptyDriftIndex);
|
|
6800
|
+
gl.uniform1i(u['uDriftLightIndex'] ?? null, DRIFT_LIGHT_INDEX_TEXTURE_UNIT);
|
|
6801
|
+
gl.activeTexture(gl.TEXTURE0 + DRIFT_LIGHT_ATLAS_TEXTURE_UNIT);
|
|
6802
|
+
gl.bindTexture(gl.TEXTURE_3D, this.driftAtlas ?? this.emptyDriftAtlas);
|
|
6803
|
+
gl.uniform1i(u['uDriftLightAtlas'] ?? null, DRIFT_LIGHT_ATLAS_TEXTURE_UNIT);
|
|
6804
|
+
}
|
|
6805
|
+
|
|
6418
6806
|
/**
|
|
6419
6807
|
* Everything one flat program needs to know about this frame.
|
|
6420
6808
|
*
|
|
@@ -6553,7 +6941,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6553
6941
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
6554
6942
|
gl.uniform1f(u['uShadowDepthSpan'] ?? null, env.shadowDepthSpan);
|
|
6555
6943
|
gl.uniform1f(u['uShadowMaxDistance'] ?? null, this.quality.directionalShadowMaxDistance);
|
|
6556
|
-
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.
|
|
6944
|
+
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
6557
6945
|
gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
|
|
6558
6946
|
gl.uniform1i(u['uShadowFilterTaps'] ?? null, this.quality.shadowFilterTaps);
|
|
6559
6947
|
gl.uniform3fv(u['uHighlightMin'] ?? null, env.highlightMin);
|
|
@@ -6633,11 +7021,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6633
7021
|
* zero would not be enough on its own — a driver may fetch a sampler's descriptor before it
|
|
6634
7022
|
* evaluates the arithmetic that discards the result, which is the same reason
|
|
6635
7023
|
* `emptyTexture.ts` exists.
|
|
7024
|
+
*
|
|
7025
|
+
* **Except a bounce bake, which reads it on purpose.** Its faces are drawn into the capture
|
|
7026
|
+
* cube, and the array is attached only by the convolution after them, so no pass both samples
|
|
7027
|
+
* and attaches it.
|
|
6636
7028
|
*/
|
|
6637
7029
|
if (this.probeCapture !== null) {
|
|
6638
7030
|
const array = this.probeArray;
|
|
6639
7031
|
const grid = this.probes;
|
|
6640
|
-
const usable =
|
|
7032
|
+
const usable =
|
|
7033
|
+
array !== null &&
|
|
7034
|
+
grid !== null &&
|
|
7035
|
+
array.usable &&
|
|
7036
|
+
this.probeSweeps?.ready === true &&
|
|
7037
|
+
(!this.probePassActive || this.probeBounce);
|
|
6641
7038
|
gl.activeTexture(gl.TEXTURE0 + ENVIRONMENT_TEXTURE_UNIT);
|
|
6642
7039
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, usable ? array.texture : this.emptyTexture2DArray);
|
|
6643
7040
|
gl.uniform1i(u['uEnvironment'] ?? null, ENVIRONMENT_TEXTURE_UNIT);
|
|
@@ -6678,14 +7075,21 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6678
7075
|
* times over two days as four different bugs before it could be asked for.
|
|
6679
7076
|
*/
|
|
6680
7077
|
gl.uniform1f(u['uProbeGridAmbient'] ?? null, usable && this.probeAmbient ? 1 : 0);
|
|
7078
|
+
/* Which sets the shading reads, and how far between them. See `probeSweeps.ts`. */
|
|
7079
|
+
const sets = this.probeSweeps?.uniforms(this.probeSets) ?? this.probeSets.fill(0);
|
|
7080
|
+
gl.uniform3f(u['uProbeGridSets'] ?? null, sets[0] ?? 0, sets[1] ?? 0, sets[2] ?? 0);
|
|
6681
7081
|
}
|
|
6682
7082
|
|
|
7083
|
+
this.bindDriftLight(u, camera.position);
|
|
7084
|
+
|
|
6683
7085
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
6684
7086
|
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
6685
7087
|
gl.uniform1i(u['uAlbedo'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
6686
7088
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
6687
7089
|
gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
|
|
6688
7090
|
gl.uniform1f(u['uAlbedoCutout'] ?? null, 0);
|
|
7091
|
+
gl.uniform1i(u['uDoubleSided'] ?? null, 0);
|
|
7092
|
+
this.materialDoubleSided = false;
|
|
6689
7093
|
this.currentSurfaceTexture = null;
|
|
6690
7094
|
|
|
6691
7095
|
/*
|
|
@@ -6918,7 +7322,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6918
7322
|
*/
|
|
6919
7323
|
gl.depthFunc(this.reversedDepth ? gl.GREATER : gl.LESS);
|
|
6920
7324
|
if (layer === 0) {
|
|
7325
|
+
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
6921
7326
|
mesh.draw(gl);
|
|
7327
|
+
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
6922
7328
|
gl.depthFunc(this.reversedDepth ? gl.GEQUAL : gl.LEQUAL);
|
|
6923
7329
|
if (tint !== null) gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
6924
7330
|
if (program !== null) this.useFlatProgram();
|
|
@@ -6937,7 +7343,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6937
7343
|
*/
|
|
6938
7344
|
const offset = depthOffsetForLayer(layer, this.reversedDepth);
|
|
6939
7345
|
gl.polygonOffset(offset.slope, offset.units);
|
|
7346
|
+
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
6940
7347
|
mesh.draw(gl);
|
|
7348
|
+
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
6941
7349
|
gl.polygonOffset(0, 0);
|
|
6942
7350
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
6943
7351
|
if (program !== null) this.useFlatProgram();
|
|
@@ -7090,7 +7498,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7090
7498
|
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
7091
7499
|
gl.polygonOffset(offset.slope, offset.units);
|
|
7092
7500
|
}
|
|
7501
|
+
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
7093
7502
|
mesh.draw(gl);
|
|
7503
|
+
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
7094
7504
|
if (offset.units !== 0) {
|
|
7095
7505
|
gl.polygonOffset(0, 0);
|
|
7096
7506
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
@@ -7163,10 +7573,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7163
7573
|
|
|
7164
7574
|
this.probeArray?.dispose(this.gl);
|
|
7165
7575
|
this.probes = grid;
|
|
7576
|
+
this.probeSweeps = new ProbeSweeps(grid.layers, grid.crossfade);
|
|
7166
7577
|
this.probeArray = new EnvProbeArray(
|
|
7167
7578
|
this.gl,
|
|
7168
7579
|
octahedralEdgeFor(this.quality.reflectionProbeSize),
|
|
7169
|
-
grid.layers,
|
|
7580
|
+
grid.layers * this.probeSweeps.sets,
|
|
7170
7581
|
this.quality.hdrScene,
|
|
7171
7582
|
);
|
|
7172
7583
|
return this.probeArray.usable;
|
|
@@ -7193,12 +7604,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7193
7604
|
const probe = this.probeCapture;
|
|
7194
7605
|
const array = this.probeArray;
|
|
7195
7606
|
const grid = this.probes;
|
|
7196
|
-
|
|
7607
|
+
const sweeps = this.probeSweeps;
|
|
7608
|
+
if (probe === null || array === null || grid === null || sweeps === null) return false;
|
|
7197
7609
|
if (layer < 0 || layer >= grid.layers) return false;
|
|
7198
7610
|
|
|
7199
7611
|
this.probeAmbient = options?.irradiance ?? true;
|
|
7200
7612
|
grid.positionOf(layer, this.probeOrigin);
|
|
7201
7613
|
|
|
7614
|
+
this.probeBounce = options?.bounce ?? false;
|
|
7202
7615
|
this.probePassActive = true;
|
|
7203
7616
|
/*
|
|
7204
7617
|
* **A probe stores radiance, not display pixels.**
|
|
@@ -7236,7 +7649,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7236
7649
|
this.gl.frontFace(this.gl.CW);
|
|
7237
7650
|
let baked = false;
|
|
7238
7651
|
try {
|
|
7239
|
-
|
|
7652
|
+
const [first, end] = probeFaceRange(options);
|
|
7653
|
+
baked = probe.bake(this.gl, this.probeOrigin, clearColor, drawFace, first, end);
|
|
7654
|
+
/* A bake that has not reached its sixth face has nothing whole to convolve yet. */
|
|
7655
|
+
if (end < 6) return baked;
|
|
7240
7656
|
/*
|
|
7241
7657
|
* The convolution, immediately after the faces and inside the same guard, because it reads
|
|
7242
7658
|
* the chain `bake` has just generated. A bake that failed leaves the layer untouched rather
|
|
@@ -7247,11 +7663,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7247
7663
|
this.gl,
|
|
7248
7664
|
probe,
|
|
7249
7665
|
array,
|
|
7250
|
-
layer,
|
|
7666
|
+
sweeps.writeLayer(layer),
|
|
7251
7667
|
ENVIRONMENT_TEXTURE_UNIT,
|
|
7252
7668
|
this.quality.environmentPrefilterSamples,
|
|
7253
7669
|
this.quality.environmentPrefilter,
|
|
7254
7670
|
);
|
|
7671
|
+
sweeps.baked(layer);
|
|
7255
7672
|
}
|
|
7256
7673
|
} finally {
|
|
7257
7674
|
this.gl.frontFace(this.gl.CCW);
|
|
@@ -7264,6 +7681,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7264
7681
|
* later frame reading the placeholder and quietly reflecting nothing.
|
|
7265
7682
|
*/
|
|
7266
7683
|
this.probePassActive = false;
|
|
7684
|
+
this.probeBounce = false;
|
|
7267
7685
|
/* Back to the canvas, or to the frame's own target if one is bound. See `SceneTarget`. */
|
|
7268
7686
|
if (this.sceneTarget !== null && !this.framePresented) this.sceneTarget.bind();
|
|
7269
7687
|
else {
|
|
@@ -7337,6 +7755,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7337
7755
|
this.quality.environmentPrefilterSamples,
|
|
7338
7756
|
this.quality.environmentPrefilter,
|
|
7339
7757
|
);
|
|
7758
|
+
this.probeSweeps?.baked(0);
|
|
7340
7759
|
return true;
|
|
7341
7760
|
}
|
|
7342
7761
|
|
|
@@ -7474,7 +7893,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7474
7893
|
lookup does its own `* 0.5 + 0.5` and correcting the matrix as well doubles it. */
|
|
7475
7894
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, env.lightViewProj);
|
|
7476
7895
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
7477
|
-
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.
|
|
7896
|
+
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
7478
7897
|
|
|
7479
7898
|
gl.activeTexture(gl.TEXTURE0);
|
|
7480
7899
|
gl.bindTexture(gl.TEXTURE_2D, this.shadowMap?.texture ?? this.emptyTexture2D);
|