@driftengine/core 4.2.1 → 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 +272 -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 +166 -1
- package/dist/render/backend/webgl2/renderer.js +424 -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 +176 -11
- package/dist/render/backend/webgpu/renderer.js +680 -250
- 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 +462 -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 +807 -271
- 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,
|
|
@@ -218,6 +228,7 @@ import type { SdfTextStyle } from '../../sdfTextLayout.ts';
|
|
|
218
228
|
import { MOTION_BLUR_MAX_UV, RUSH_REACH_UV } from '../../vertexDefaults.ts';
|
|
219
229
|
import { OUTPUT_TRANSFORM_CODE } from '../../vertexDefaults.ts';
|
|
220
230
|
import type { RenderBackend } from '../api.ts';
|
|
231
|
+
import { deviceSnappedCellSize } from '../../textLayout.ts';
|
|
221
232
|
import type { TextStyle } from '../../textLayout.ts';
|
|
222
233
|
import { WindStreakRenderer } from '../../windStreakRenderer.ts';
|
|
223
234
|
import type { WindStreakOptions } from '../../windStreakRenderer.ts';
|
|
@@ -225,6 +236,9 @@ import type { WindField } from '../../windField.ts';
|
|
|
225
236
|
import type { FlockParams } from '../../flockRenderer.ts';
|
|
226
237
|
import { InstancedBatch } from './instanced.ts';
|
|
227
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';
|
|
228
242
|
|
|
229
243
|
/**
|
|
230
244
|
* How many frames `firstFrameSettled` will ask before it stops asking.
|
|
@@ -788,6 +802,14 @@ export interface TranslucentMeshOptions {
|
|
|
788
802
|
* that two surfaces overlapping in view blend in submission order. And a surface that writes no
|
|
789
803
|
* depth cannot occlude the sky. A caller wanting both wants the pieces sorted and this left
|
|
790
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.
|
|
791
813
|
*/
|
|
792
814
|
readonly depthWrite?: boolean;
|
|
793
815
|
/**
|
|
@@ -1059,6 +1081,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1059
1081
|
private readonly panelProgram: WebGLProgram;
|
|
1060
1082
|
private readonly panelUniforms: Record<string, WebGLUniformLocation>;
|
|
1061
1083
|
private readonly panelVao: WebGLVertexArrayObject;
|
|
1084
|
+
/** The unit quad behind `panelVao`, held so `dispose` can release it. */
|
|
1085
|
+
private readonly panelBuffer: WebGLBuffer | null;
|
|
1062
1086
|
private readonly filmProgram: WebGLProgram;
|
|
1063
1087
|
/**
|
|
1064
1088
|
* The off-screen colour target, when screen effects are on. Null is not a failure —
|
|
@@ -1090,6 +1114,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1090
1114
|
private skinPaletteSet = false;
|
|
1091
1115
|
|
|
1092
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();
|
|
1093
1125
|
private readonly emptyTexture2DArray: WebGLTexture;
|
|
1094
1126
|
private readonly emptyTextureCube: WebGLTexture;
|
|
1095
1127
|
/** How hard the rush blurs this frame, and how far its outermost tap reaches. */
|
|
@@ -1169,6 +1201,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1169
1201
|
private motionBlurScale = 1;
|
|
1170
1202
|
/** How much of the bloom ceiling this frame takes. See `setBloom`. */
|
|
1171
1203
|
private bloomScale = 1;
|
|
1204
|
+
/** In scene units, once `setBloom` has moved it; null reads the profile's. */
|
|
1205
|
+
private bloomThresholdSet: number | null = null;
|
|
1172
1206
|
/**
|
|
1173
1207
|
* This frame's exposure into the tone curve. See `setOutputExposure`.
|
|
1174
1208
|
*
|
|
@@ -1276,6 +1310,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1276
1310
|
private readonly depthUniforms: Record<string, WebGLUniformLocation>;
|
|
1277
1311
|
private readonly depthInstancedProgram: WebGLProgram;
|
|
1278
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>;
|
|
1279
1322
|
private readonly scatterDepthProgram: WebGLProgram;
|
|
1280
1323
|
private readonly scatterDepthUniforms: Record<string, WebGLUniformLocation>;
|
|
1281
1324
|
private readonly shadowMap: ShadowMap | null;
|
|
@@ -1334,6 +1377,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1334
1377
|
private readonly probeCapture: ReflectionProbe | null;
|
|
1335
1378
|
/** Every probe of the grid, as one array texture. Allocated when a grid is declared. */
|
|
1336
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);
|
|
1337
1384
|
/**
|
|
1338
1385
|
* Where those probes stand. A single baked probe is a grid of one at that probe's own origin.
|
|
1339
1386
|
*
|
|
@@ -1359,6 +1406,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1359
1406
|
private warnedEnvironmentAmbient = false;
|
|
1360
1407
|
/** Whether a probe is being baked, so the passes that would recurse into one can tell. */
|
|
1361
1408
|
private probePassActive = false;
|
|
1409
|
+
/** Whether the bake under way lights its faces by the grid. `ProbeBakeOptions.bounce`. */
|
|
1410
|
+
private probeBounce = false;
|
|
1362
1411
|
/** The clip-control extension, or null where this context does not offer it. */
|
|
1363
1412
|
private clipControl: ClipControlExtension | null = null;
|
|
1364
1413
|
/**
|
|
@@ -1517,6 +1566,17 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1517
1566
|
*/
|
|
1518
1567
|
private activeDepthViewProj: ReadonlyMat4 = IDENTITY_MAT4;
|
|
1519
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;
|
|
1520
1580
|
/**
|
|
1521
1581
|
* Whether the depth pass this scatter draw interrupts wants back-face culling.
|
|
1522
1582
|
*
|
|
@@ -1527,6 +1587,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1527
1587
|
* cubemap — so the pass states what it wants and this restores that.
|
|
1528
1588
|
*/
|
|
1529
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;
|
|
1530
1595
|
|
|
1531
1596
|
/**
|
|
1532
1597
|
* The one sink handed to every caster enumeration.
|
|
@@ -1535,16 +1600,34 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1535
1600
|
* per pass would allocate six objects per light per frame.
|
|
1536
1601
|
*/
|
|
1537
1602
|
private readonly casterSink: ShadowCasterSink = {
|
|
1538
|
-
mesh: (mesh, model) => {
|
|
1603
|
+
mesh: (mesh, model, material) => {
|
|
1539
1604
|
this.shadowDrawBudget.ask();
|
|
1540
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
|
+
}
|
|
1541
1622
|
gl.uniformMatrix4fv(this.depthUniforms['uModel'] ?? null, false, model);
|
|
1542
1623
|
/*
|
|
1543
1624
|
* The sink takes the opaque handle both backends share, and this renderer only ever
|
|
1544
1625
|
* handed out its own `Mesh`. Narrowing is safe here for that reason and nowhere else:
|
|
1545
1626
|
* a handle from the other backend would have to have crossed renderers to arrive.
|
|
1546
1627
|
*/
|
|
1628
|
+
if (twoSided) gl.disable(gl.CULL_FACE);
|
|
1547
1629
|
(mesh as Mesh).draw(gl);
|
|
1630
|
+
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1548
1631
|
},
|
|
1549
1632
|
/**
|
|
1550
1633
|
* A batch into the depth map, placed by the same matrices the visible draw uses.
|
|
@@ -1552,18 +1635,31 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1552
1635
|
* No `uModel`: the instanced depth program has none, its placement being the four attribute
|
|
1553
1636
|
* columns the batch's own buffer supplies.
|
|
1554
1637
|
*/
|
|
1555
|
-
instanced: (batch, data) => {
|
|
1638
|
+
instanced: (batch, data, material) => {
|
|
1556
1639
|
const { gl } = this;
|
|
1557
1640
|
const gpuBatch = batch as InstancedBatch;
|
|
1558
1641
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
1559
1642
|
if (count === 0) return;
|
|
1560
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
|
+
}
|
|
1561
1656
|
const u = this.depthInstancedUniforms;
|
|
1562
1657
|
gl.useProgram(this.depthInstancedProgram);
|
|
1563
1658
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
1564
1659
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1565
1660
|
if (this.activeDepthPeel) gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1566
1661
|
gpuBatch.mesh.drawInstances(gl, count);
|
|
1662
|
+
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1567
1663
|
gl.useProgram(this.depthProgram);
|
|
1568
1664
|
},
|
|
1569
1665
|
skinnedMesh: (mesh, model, palette) => {
|
|
@@ -1611,6 +1707,24 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1611
1707
|
},
|
|
1612
1708
|
};
|
|
1613
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
|
+
|
|
1614
1728
|
private readonly drawPointShadowFace = (viewProj: ReadonlyMat4): void => {
|
|
1615
1729
|
const casters = this.pointShadowCasters;
|
|
1616
1730
|
if (casters === null) return;
|
|
@@ -2261,6 +2375,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2261
2375
|
* "unbound". Two 1x1 uploads for the life of the renderer.
|
|
2262
2376
|
*/
|
|
2263
2377
|
this.emptyTexture2D = createEmptyTexture2D(gl);
|
|
2378
|
+
this.emptyDriftIndex = createEmptyTexture3D(gl, true);
|
|
2379
|
+
this.emptyDriftAtlas = createEmptyTexture3D(gl, false);
|
|
2264
2380
|
this.emptyTexture2DArray = createEmptyTexture2DArray(gl);
|
|
2265
2381
|
this.emptyTextureCube = createEmptyTextureCube(gl);
|
|
2266
2382
|
this.gpuTimer = new GpuTimer(gl);
|
|
@@ -2482,6 +2598,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2482
2598
|
this.panelVao = panelVao;
|
|
2483
2599
|
gl.bindVertexArray(panelVao);
|
|
2484
2600
|
const panelBuffer = gl.createBuffer();
|
|
2601
|
+
this.panelBuffer = panelBuffer;
|
|
2485
2602
|
gl.bindBuffer(gl.ARRAY_BUFFER, panelBuffer);
|
|
2486
2603
|
// A unit quad. The rectangle it becomes is a uniform, so this is uploaded once
|
|
2487
2604
|
// for the life of the renderer however many panels are drawn.
|
|
@@ -2569,6 +2686,33 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2569
2686
|
*/
|
|
2570
2687
|
this.scatterDepthProgram = compileProgram(gl, SCATTER_DEPTH_VERT, DEPTH_FRAG, 'scatterDepth');
|
|
2571
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);
|
|
2572
2716
|
this.shadowMap = this.quality.directionalShadows
|
|
2573
2717
|
? new ShadowMap(gl, this.quality.directionalShadowMapSize)
|
|
2574
2718
|
: null;
|
|
@@ -2735,6 +2879,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2735
2879
|
this.flatSkinnedProgram,
|
|
2736
2880
|
this.flatMorphedProgram,
|
|
2737
2881
|
this.flatBothProgram,
|
|
2882
|
+
this.flatInstancedProgram,
|
|
2738
2883
|
]) {
|
|
2739
2884
|
if (extra !== null) gl.deleteProgram(extra);
|
|
2740
2885
|
}
|
|
@@ -2745,12 +2890,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2745
2890
|
this.skyProgram,
|
|
2746
2891
|
this.scatterProgram,
|
|
2747
2892
|
this.depthProgram,
|
|
2893
|
+
this.depthSkinnedProgram,
|
|
2894
|
+
this.depthInstancedProgram,
|
|
2748
2895
|
this.scatterDepthProgram,
|
|
2896
|
+
this.lightVolumeProgram,
|
|
2897
|
+
this.depthCutoutProgram,
|
|
2898
|
+
this.depthInstancedCutoutProgram,
|
|
2749
2899
|
]) {
|
|
2750
2900
|
gl.deleteProgram(program);
|
|
2751
2901
|
}
|
|
2752
2902
|
this.skinPalette.dispose(gl);
|
|
2753
2903
|
gl.deleteTexture(this.emptyTexture2D);
|
|
2904
|
+
gl.deleteTexture(this.emptyDriftIndex);
|
|
2905
|
+
gl.deleteTexture(this.emptyDriftAtlas);
|
|
2906
|
+
this.forgetLightFieldVolumes();
|
|
2754
2907
|
gl.deleteTexture(this.emptyTexture2DArray);
|
|
2755
2908
|
gl.deleteTexture(this.emptyTextureCube);
|
|
2756
2909
|
/* A cubemap and its whole mip chain, which is the largest single thing this
|
|
@@ -2758,6 +2911,24 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2758
2911
|
this.probeCapture?.dispose(gl);
|
|
2759
2912
|
this.probeArray?.dispose(gl);
|
|
2760
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);
|
|
2761
2932
|
}
|
|
2762
2933
|
|
|
2763
2934
|
if (options.releaseContext === true) {
|
|
@@ -2986,6 +3157,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2986
3157
|
if (this.contextLost) return;
|
|
2987
3158
|
this.materials.dirty();
|
|
2988
3159
|
this.currentSurfaceTexture = material?.albedo ?? null;
|
|
3160
|
+
this.materialDoubleSided = material?.doubleSided === true;
|
|
2989
3161
|
/*
|
|
2990
3162
|
* Written to every flat program, because material state persists across draws and a skinned
|
|
2991
3163
|
* draw is entitled to the material the caller set before it. A second program holding none of
|
|
@@ -3065,10 +3237,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3065
3237
|
* The emissive map, on exactly the terms the two maps above it are bound: before the albedo
|
|
3066
3238
|
* early return, and the stand-in rather than `null`.
|
|
3067
3239
|
*
|
|
3068
|
-
* **
|
|
3069
|
-
* map,
|
|
3070
|
-
*
|
|
3071
|
-
*
|
|
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.
|
|
3072
3245
|
*/
|
|
3073
3246
|
const emissiveMap = material?.emissive ?? null;
|
|
3074
3247
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
@@ -3092,6 +3265,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3092
3265
|
* how big a dome was — WebGPU writes it before its own early return and WebGL2 did not.
|
|
3093
3266
|
*/
|
|
3094
3267
|
gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
|
|
3268
|
+
gl.uniform1i(u['uDoubleSided'] ?? null, material?.doubleSided === true ? 1 : 0);
|
|
3095
3269
|
|
|
3096
3270
|
if (albedo === null) {
|
|
3097
3271
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
@@ -3657,6 +3831,28 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3657
3831
|
}
|
|
3658
3832
|
}
|
|
3659
3833
|
|
|
3834
|
+
/**
|
|
3835
|
+
* The cell size to draw a bitmap glyph at so every cell covers whole device pixels.
|
|
3836
|
+
*
|
|
3837
|
+
* **The caller snaps, and then measures and draws with the one number.** 4.1.4 applied this
|
|
3838
|
+
* inside `drawText` instead, where `textWidthPx` could not see it — it takes no viewport and so
|
|
3839
|
+
* cannot know the ratio — and every consumer went on laying out against the cell it asked for
|
|
3840
|
+
* while the engine drew a smaller one. A centred line, a right-aligned column and a line fitted
|
|
3841
|
+
* to a box broke together. Snapped here, the arithmetic and the picture cannot disagree.
|
|
3842
|
+
*
|
|
3843
|
+
* **It is on the renderer because only the renderer knows the second number.** `viewportWidth`
|
|
3844
|
+
* is the caller's, and it already passes it to `drawText`; the drawing buffer is not on the
|
|
3845
|
+
* shared surface and a game's text layout is a pure function over CSS pixels, so reaching it
|
|
3846
|
+
* meant threading a canvas through an interface layer to a value the renderer was holding.
|
|
3847
|
+
*
|
|
3848
|
+
* The decision itself stays in `textLayout.ts` and only this binding is per backend, which is
|
|
3849
|
+
* the 2026-08-13 rule: a cell already whole comes back untouched, and the snap is downward so a
|
|
3850
|
+
* line fitted to a box can only leave a gap rather than overflow it.
|
|
3851
|
+
*/
|
|
3852
|
+
snapTextCellSize(cellSize: number, viewportWidth: number): number {
|
|
3853
|
+
return deviceSnappedCellSize(cellSize, viewportWidth, this.gl.drawingBufferWidth);
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3660
3856
|
/** Width of the string this handle currently holds, in pixels at a given cell size. */
|
|
3661
3857
|
textWidth(text: TextRenderer, cellSize: number): number {
|
|
3662
3858
|
return text.widthPx(cellSize);
|
|
@@ -3866,7 +4062,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3866
4062
|
this.reflectionPassActive ? this.reflectionAtmosphereY : (camera.position[1] ?? 0),
|
|
3867
4063
|
this.quality.underwaterAtmosphere,
|
|
3868
4064
|
);
|
|
4065
|
+
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
3869
4066
|
batch.drawTo(gl, data.count);
|
|
4067
|
+
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
3870
4068
|
}
|
|
3871
4069
|
|
|
3872
4070
|
/** Build a batch for electrical arcs. `capacity` is in segments, not arcs. */
|
|
@@ -4540,6 +4738,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4540
4738
|
this.gpuTimer.begin('shadows');
|
|
4541
4739
|
this.shadowPassActive = true;
|
|
4542
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;
|
|
4543
4743
|
/*
|
|
4544
4744
|
* **Range-corrected but not reversed.** `EXT_clip_control` is context state, so a light matrix
|
|
4545
4745
|
* that still emits OpenGL's `[-1, 1]` loses everything below zero the moment it is on. See
|
|
@@ -4597,6 +4797,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4597
4797
|
this.uploadWind(this.depthUniforms, this.frameWind);
|
|
4598
4798
|
gl.useProgram(this.depthSkinnedProgram);
|
|
4599
4799
|
this.uploadWind(this.depthSkinnedUniforms, this.frameWind);
|
|
4800
|
+
gl.useProgram(this.depthCutoutProgram);
|
|
4801
|
+
this.uploadWind(this.depthCutoutUniforms, this.frameWind);
|
|
4600
4802
|
gl.useProgram(this.depthProgram);
|
|
4601
4803
|
}
|
|
4602
4804
|
|
|
@@ -4688,6 +4890,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4688
4890
|
const shadowMap = this.activeShadowMap;
|
|
4689
4891
|
if (!this.shadowPassActive || shadowMap === null) return;
|
|
4690
4892
|
shadowMap.end(this.gl);
|
|
4893
|
+
if (shadowMap === this.peeledShadowMap) this.peelFilled = true;
|
|
4691
4894
|
/* Back to the frame's sense; see `beginShadowPass` for why it left it. */
|
|
4692
4895
|
this.gl.depthFunc(glDepthFuncEqual(this.gl));
|
|
4693
4896
|
this.gl.clearDepth(this.depthClear);
|
|
@@ -5418,6 +5621,73 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5418
5621
|
this.motionBlurScale = Math.min(Math.max(scale, 0), 1);
|
|
5419
5622
|
}
|
|
5420
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
|
+
|
|
5421
5691
|
/**
|
|
5422
5692
|
* Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
|
|
5423
5693
|
* to take.
|
|
@@ -5472,9 +5742,17 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5472
5742
|
*
|
|
5473
5743
|
* Set before `beginFrame` and held until changed. Ignored entirely when `bloom` is 0, since
|
|
5474
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.
|
|
5475
5752
|
*/
|
|
5476
|
-
setBloom(scale: number): void {
|
|
5753
|
+
setBloom(scale: number, threshold?: number): void {
|
|
5477
5754
|
this.bloomScale = Math.min(Math.max(scale, 0), 1);
|
|
5755
|
+
if (threshold !== undefined) this.bloomThresholdSet = bloomThresholdOf(threshold);
|
|
5478
5756
|
}
|
|
5479
5757
|
|
|
5480
5758
|
/**
|
|
@@ -5621,6 +5899,41 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5621
5899
|
this.sceneTarget.setColourGrade(lut, strength);
|
|
5622
5900
|
}
|
|
5623
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
|
+
|
|
5624
5937
|
/**
|
|
5625
5938
|
* What this frame asked for. **Every ceiling here is `null`, and that is the finding.**
|
|
5626
5939
|
*
|
|
@@ -5959,7 +6272,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5959
6272
|
const bloomStrength = this.quality.bloom * this.bloomScale;
|
|
5960
6273
|
const bloom =
|
|
5961
6274
|
bloomStrength > 0
|
|
5962
|
-
? {
|
|
6275
|
+
? {
|
|
6276
|
+
strength: bloomStrength,
|
|
6277
|
+
threshold: this.bloomThresholdSet ?? this.quality.bloomThreshold,
|
|
6278
|
+
}
|
|
5963
6279
|
: undefined;
|
|
5964
6280
|
|
|
5965
6281
|
/* The grade travels to the resolve only where the mesh pass gave it up. See `bindMeshPass`. */
|
|
@@ -6166,11 +6482,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6166
6482
|
sunShadow,
|
|
6167
6483
|
lightViewProj: env?.lightViewProj ?? IDENTITY_MAT4,
|
|
6168
6484
|
/* The placeholder rather than null where a map is merely absent, for the reason
|
|
6169
|
-
`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. */
|
|
6170
6487
|
staticShadowMap: this.shadowMap?.texture ?? this.emptyTexture2D,
|
|
6171
6488
|
peeledShadowMap: this.peeledShadowMap?.texture ?? this.emptyTexture2D,
|
|
6172
6489
|
dynamicShadowMap: this.dynamicShadowMap?.texture ?? this.emptyTexture2D,
|
|
6173
|
-
peeledEnabled: this.
|
|
6490
|
+
peeledEnabled: this.peelFilled,
|
|
6174
6491
|
},
|
|
6175
6492
|
);
|
|
6176
6493
|
if (marched) {
|
|
@@ -6392,6 +6709,100 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6392
6709
|
this.useFlatProgram();
|
|
6393
6710
|
}
|
|
6394
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
|
+
|
|
6395
6806
|
/**
|
|
6396
6807
|
* Everything one flat program needs to know about this frame.
|
|
6397
6808
|
*
|
|
@@ -6530,7 +6941,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6530
6941
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
6531
6942
|
gl.uniform1f(u['uShadowDepthSpan'] ?? null, env.shadowDepthSpan);
|
|
6532
6943
|
gl.uniform1f(u['uShadowMaxDistance'] ?? null, this.quality.directionalShadowMaxDistance);
|
|
6533
|
-
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.
|
|
6944
|
+
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
6534
6945
|
gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
|
|
6535
6946
|
gl.uniform1i(u['uShadowFilterTaps'] ?? null, this.quality.shadowFilterTaps);
|
|
6536
6947
|
gl.uniform3fv(u['uHighlightMin'] ?? null, env.highlightMin);
|
|
@@ -6610,11 +7021,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6610
7021
|
* zero would not be enough on its own — a driver may fetch a sampler's descriptor before it
|
|
6611
7022
|
* evaluates the arithmetic that discards the result, which is the same reason
|
|
6612
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.
|
|
6613
7028
|
*/
|
|
6614
7029
|
if (this.probeCapture !== null) {
|
|
6615
7030
|
const array = this.probeArray;
|
|
6616
7031
|
const grid = this.probes;
|
|
6617
|
-
const usable =
|
|
7032
|
+
const usable =
|
|
7033
|
+
array !== null &&
|
|
7034
|
+
grid !== null &&
|
|
7035
|
+
array.usable &&
|
|
7036
|
+
this.probeSweeps?.ready === true &&
|
|
7037
|
+
(!this.probePassActive || this.probeBounce);
|
|
6618
7038
|
gl.activeTexture(gl.TEXTURE0 + ENVIRONMENT_TEXTURE_UNIT);
|
|
6619
7039
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, usable ? array.texture : this.emptyTexture2DArray);
|
|
6620
7040
|
gl.uniform1i(u['uEnvironment'] ?? null, ENVIRONMENT_TEXTURE_UNIT);
|
|
@@ -6655,14 +7075,21 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6655
7075
|
* times over two days as four different bugs before it could be asked for.
|
|
6656
7076
|
*/
|
|
6657
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);
|
|
6658
7081
|
}
|
|
6659
7082
|
|
|
7083
|
+
this.bindDriftLight(u, camera.position);
|
|
7084
|
+
|
|
6660
7085
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
6661
7086
|
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
6662
7087
|
gl.uniform1i(u['uAlbedo'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
6663
7088
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
6664
7089
|
gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
|
|
6665
7090
|
gl.uniform1f(u['uAlbedoCutout'] ?? null, 0);
|
|
7091
|
+
gl.uniform1i(u['uDoubleSided'] ?? null, 0);
|
|
7092
|
+
this.materialDoubleSided = false;
|
|
6666
7093
|
this.currentSurfaceTexture = null;
|
|
6667
7094
|
|
|
6668
7095
|
/*
|
|
@@ -6895,7 +7322,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6895
7322
|
*/
|
|
6896
7323
|
gl.depthFunc(this.reversedDepth ? gl.GREATER : gl.LESS);
|
|
6897
7324
|
if (layer === 0) {
|
|
7325
|
+
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
6898
7326
|
mesh.draw(gl);
|
|
7327
|
+
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
6899
7328
|
gl.depthFunc(this.reversedDepth ? gl.GEQUAL : gl.LEQUAL);
|
|
6900
7329
|
if (tint !== null) gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
6901
7330
|
if (program !== null) this.useFlatProgram();
|
|
@@ -6914,7 +7343,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6914
7343
|
*/
|
|
6915
7344
|
const offset = depthOffsetForLayer(layer, this.reversedDepth);
|
|
6916
7345
|
gl.polygonOffset(offset.slope, offset.units);
|
|
7346
|
+
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
6917
7347
|
mesh.draw(gl);
|
|
7348
|
+
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
6918
7349
|
gl.polygonOffset(0, 0);
|
|
6919
7350
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
6920
7351
|
if (program !== null) this.useFlatProgram();
|
|
@@ -7067,7 +7498,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7067
7498
|
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
7068
7499
|
gl.polygonOffset(offset.slope, offset.units);
|
|
7069
7500
|
}
|
|
7501
|
+
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
7070
7502
|
mesh.draw(gl);
|
|
7503
|
+
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
7071
7504
|
if (offset.units !== 0) {
|
|
7072
7505
|
gl.polygonOffset(0, 0);
|
|
7073
7506
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
@@ -7140,10 +7573,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7140
7573
|
|
|
7141
7574
|
this.probeArray?.dispose(this.gl);
|
|
7142
7575
|
this.probes = grid;
|
|
7576
|
+
this.probeSweeps = new ProbeSweeps(grid.layers, grid.crossfade);
|
|
7143
7577
|
this.probeArray = new EnvProbeArray(
|
|
7144
7578
|
this.gl,
|
|
7145
7579
|
octahedralEdgeFor(this.quality.reflectionProbeSize),
|
|
7146
|
-
grid.layers,
|
|
7580
|
+
grid.layers * this.probeSweeps.sets,
|
|
7147
7581
|
this.quality.hdrScene,
|
|
7148
7582
|
);
|
|
7149
7583
|
return this.probeArray.usable;
|
|
@@ -7170,12 +7604,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7170
7604
|
const probe = this.probeCapture;
|
|
7171
7605
|
const array = this.probeArray;
|
|
7172
7606
|
const grid = this.probes;
|
|
7173
|
-
|
|
7607
|
+
const sweeps = this.probeSweeps;
|
|
7608
|
+
if (probe === null || array === null || grid === null || sweeps === null) return false;
|
|
7174
7609
|
if (layer < 0 || layer >= grid.layers) return false;
|
|
7175
7610
|
|
|
7176
7611
|
this.probeAmbient = options?.irradiance ?? true;
|
|
7177
7612
|
grid.positionOf(layer, this.probeOrigin);
|
|
7178
7613
|
|
|
7614
|
+
this.probeBounce = options?.bounce ?? false;
|
|
7179
7615
|
this.probePassActive = true;
|
|
7180
7616
|
/*
|
|
7181
7617
|
* **A probe stores radiance, not display pixels.**
|
|
@@ -7213,7 +7649,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7213
7649
|
this.gl.frontFace(this.gl.CW);
|
|
7214
7650
|
let baked = false;
|
|
7215
7651
|
try {
|
|
7216
|
-
|
|
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;
|
|
7217
7656
|
/*
|
|
7218
7657
|
* The convolution, immediately after the faces and inside the same guard, because it reads
|
|
7219
7658
|
* the chain `bake` has just generated. A bake that failed leaves the layer untouched rather
|
|
@@ -7224,11 +7663,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7224
7663
|
this.gl,
|
|
7225
7664
|
probe,
|
|
7226
7665
|
array,
|
|
7227
|
-
layer,
|
|
7666
|
+
sweeps.writeLayer(layer),
|
|
7228
7667
|
ENVIRONMENT_TEXTURE_UNIT,
|
|
7229
7668
|
this.quality.environmentPrefilterSamples,
|
|
7230
7669
|
this.quality.environmentPrefilter,
|
|
7231
7670
|
);
|
|
7671
|
+
sweeps.baked(layer);
|
|
7232
7672
|
}
|
|
7233
7673
|
} finally {
|
|
7234
7674
|
this.gl.frontFace(this.gl.CCW);
|
|
@@ -7241,6 +7681,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7241
7681
|
* later frame reading the placeholder and quietly reflecting nothing.
|
|
7242
7682
|
*/
|
|
7243
7683
|
this.probePassActive = false;
|
|
7684
|
+
this.probeBounce = false;
|
|
7244
7685
|
/* Back to the canvas, or to the frame's own target if one is bound. See `SceneTarget`. */
|
|
7245
7686
|
if (this.sceneTarget !== null && !this.framePresented) this.sceneTarget.bind();
|
|
7246
7687
|
else {
|
|
@@ -7314,6 +7755,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7314
7755
|
this.quality.environmentPrefilterSamples,
|
|
7315
7756
|
this.quality.environmentPrefilter,
|
|
7316
7757
|
);
|
|
7758
|
+
this.probeSweeps?.baked(0);
|
|
7317
7759
|
return true;
|
|
7318
7760
|
}
|
|
7319
7761
|
|
|
@@ -7451,7 +7893,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7451
7893
|
lookup does its own `* 0.5 + 0.5` and correcting the matrix as well doubles it. */
|
|
7452
7894
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, env.lightViewProj);
|
|
7453
7895
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
7454
|
-
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.
|
|
7896
|
+
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
7455
7897
|
|
|
7456
7898
|
gl.activeTexture(gl.TEXTURE0);
|
|
7457
7899
|
gl.bindTexture(gl.TEXTURE_2D, this.shadowMap?.texture ?? this.emptyTexture2D);
|