@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FrameBudget } from '../budget.js';
|
|
2
2
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.js';
|
|
3
3
|
import { mat4 } from 'gl-matrix';
|
|
4
|
-
import {
|
|
4
|
+
import { GL_DEPTH_REMAP, MAX_DEPTH_LAYER, depthClearFor, depthOffsetForLayer, GL_SHADOW_REMAP, REVERSED_DEPTH, SHADOW_DEPTH_CLEAR, glDepthFuncEqual, glShadowDepthFunc, } from '../../depthConvention.js';
|
|
5
5
|
import { PickableSet } from '../../pickable.js';
|
|
6
6
|
import { TEMPORAL_HISTORY_BLEND, TemporalHistory, jitterOffset, jitterProjection, } from '../../temporalAa.js';
|
|
7
7
|
import { DecalPass } from '../../decalPass.js';
|
|
@@ -27,8 +27,11 @@ import { compileProgram, uniformLocations } from '../../shader.js';
|
|
|
27
27
|
import { FULL_LIGHT_BUDGET, nextLightBudget, planLightBudget, } from '../../uniformVectorBudget.js';
|
|
28
28
|
import { FILM_FRAG, FILM_VERT } from '../../shaders/film.js';
|
|
29
29
|
import { SceneTarget } from '../../sceneTarget.js';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
30
|
+
import { FILM_LOOK_WITHOUT_COMPOSITE } from '../../shaders/filmLook.js';
|
|
31
|
+
import { bloomProfileWarning, bloomThresholdOf } from '../../bloomChain.js';
|
|
32
|
+
import { createEmptyTexture2D, createEmptyTexture2DArray, createEmptyTexture3D, createEmptyTextureCube, } from '../../emptyTexture.js';
|
|
33
|
+
import { LightField } from '../../driftLight/lightField.js';
|
|
34
|
+
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.js';
|
|
32
35
|
import { flatFrag, flatVert } from '../../shaders/flat/index.js';
|
|
33
36
|
import { MORPH_DELTA_TEXTURE_UNIT, SKIN_PALETTE_TEXTURE_UNIT } from '../../lightBudget.js';
|
|
34
37
|
import { SkinPaletteTexture } from './skinPaletteTexture.js';
|
|
@@ -36,9 +39,10 @@ import { SkinPaletteTexture } from './skinPaletteTexture.js';
|
|
|
36
39
|
/* The codes the shader reads, stated once in `vertexDefaults.ts` for both backends. */
|
|
37
40
|
import { LIGHT_VOLUME_VERT, lightVolumeFrag } from '../../shaders/lightVolume.js';
|
|
38
41
|
import { PANEL_FRAG, PANEL_VERT } from '../../shaders/panel.js';
|
|
39
|
-
import { ReflectionProbe, cubeFaceProjection } from '../../reflectionProbe.js';
|
|
42
|
+
import { ReflectionProbe, cubeFaceProjection, probeFaceRange } from '../../reflectionProbe.js';
|
|
40
43
|
import { EnvProbeArray } from '../../envProbeArray.js';
|
|
41
44
|
import { ProbeGrid, SINGLE_PROBE, UNIT_STEP, WORLD_ORIGIN, sameGrid } from '../../probeGrid.js';
|
|
45
|
+
import { ProbeSweeps } from '../../probeSweeps.js';
|
|
42
46
|
import { octahedralEdgeFor } from '../../prefilterEnvMap.js';
|
|
43
47
|
import { EnvironmentPrefilterPass } from '../../prefilterPass.js';
|
|
44
48
|
import { packIesAtlas } from '../../iesProfile.js';
|
|
@@ -46,14 +50,14 @@ import { MAX_AREA_LIGHTS } from '../../areaLights.js';
|
|
|
46
50
|
import { areaShadowLayerCount, castingRange, createResolvedAreaShadows, firstAreaShadowLayer, AreaShadowSet, } from '../../areaShadowSet.js';
|
|
47
51
|
import { equirectToCubeFaces } from '../../equirectToCube.js';
|
|
48
52
|
import { SKY_FRAG, skyVertFor } from '../../shaders/sky.js';
|
|
49
|
-
import { DEPTH_FRAG, DEPTH_INSTANCED_VERT, DEPTH_SKINNED_VERT, DEPTH_VERT, } from '../../shaders/depth.js';
|
|
53
|
+
import { DEPTH_CUTOUT_FRAG, DEPTH_CUTOUT_VERT, DEPTH_FRAG, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_INSTANCED_VERT, DEPTH_SKINNED_VERT, DEPTH_VERT, } from '../../shaders/depth.js';
|
|
50
54
|
import { PointShadowArray } from '../../pointShadowArray.js';
|
|
51
55
|
import { ShadowMap } from '../../shadowMap.js';
|
|
52
56
|
import { POINT_SHADOW_SAMPLER, PointShadowSystem } from '../../pointShadowSystem.js';
|
|
53
57
|
import { FACE_COUNT, PointShadowMap } from '../../pointShadowMap.js';
|
|
54
58
|
import { createResolvedPointShadows, DEFAULT_SOURCE_RADIUS } from '../../pointShadowImage.js';
|
|
55
59
|
import { createBakeScratch, runPointShadowBakes, selectCastingLights, } from '../../pointShadowBudget.js';
|
|
56
|
-
import { DIRECTIONAL_SHADOW_UNITS, LIVE_POINT_SHADOW_MAPS, MAX_POINT_LIGHTS, NORMAL_TEXTURE_UNIT, ORM_TEXTURE_UNIT, POINT_SHADOW_POOL, POINT_SHADOW_UNITS, CLUSTER_TABLE_TEXTURE_UNIT, EMISSIVE_TEXTURE_UNIT, ENVIRONMENT_TEXTURE_UNIT, SURFACE_TEXTURE_UNIT, bindPointLights, COOKIE_ATLAS_TEXTURE_UNIT, REFRACT_SCENE_TEXTURE_UNIT, COOKIE_TILE, IES_ATLAS_TEXTURE_UNIT, bindAreaLights, bindAreaShadows, openCones, } from '../../lightBudget.js';
|
|
60
|
+
import { DIRECTIONAL_SHADOW_UNITS, LIVE_POINT_SHADOW_MAPS, MAX_POINT_LIGHTS, NORMAL_TEXTURE_UNIT, ORM_TEXTURE_UNIT, POINT_SHADOW_POOL, POINT_SHADOW_UNITS, CLUSTER_TABLE_TEXTURE_UNIT, EMISSIVE_TEXTURE_UNIT, ENVIRONMENT_TEXTURE_UNIT, SURFACE_TEXTURE_UNIT, bindPointLights, COOKIE_ATLAS_TEXTURE_UNIT, REFRACT_SCENE_TEXTURE_UNIT, DRIFT_LIGHT_ATLAS_TEXTURE_UNIT, DRIFT_LIGHT_INDEX_TEXTURE_UNIT, COOKIE_TILE, IES_ATLAS_TEXTURE_UNIT, bindAreaLights, bindAreaShadows, openCones, } from '../../lightBudget.js';
|
|
57
61
|
import { resolveRenderQuality } from '../../renderQuality.js';
|
|
58
62
|
import { drawingBufferSize } from '../../drawingBuffer.js';
|
|
59
63
|
import { Mesh, createMeshIncremental } from '../../mesh.js';
|
|
@@ -75,8 +79,10 @@ import { TextRenderer } from '../../textRenderer.js';
|
|
|
75
79
|
import { SdfTextRenderer } from '../../sdfTextRenderer.js';
|
|
76
80
|
import { MOTION_BLUR_MAX_UV, RUSH_REACH_UV } from '../../vertexDefaults.js';
|
|
77
81
|
import { OUTPUT_TRANSFORM_CODE } from '../../vertexDefaults.js';
|
|
82
|
+
import { deviceSnappedCellSize } from '../../textLayout.js';
|
|
78
83
|
import { WindStreakRenderer } from '../../windStreakRenderer.js';
|
|
79
84
|
import { InstancedBatch } from './instanced.js';
|
|
85
|
+
import { cutoutOf } from '../../cutoutCaster.js';
|
|
80
86
|
/**
|
|
81
87
|
* How many frames `firstFrameSettled` will ask before it stops asking.
|
|
82
88
|
*
|
|
@@ -450,6 +456,8 @@ export class WebGL2Renderer {
|
|
|
450
456
|
panelProgram;
|
|
451
457
|
panelUniforms;
|
|
452
458
|
panelVao;
|
|
459
|
+
/** The unit quad behind `panelVao`, held so `dispose` can release it. */
|
|
460
|
+
panelBuffer;
|
|
453
461
|
filmProgram;
|
|
454
462
|
/**
|
|
455
463
|
* The off-screen colour target, when screen effects are on. Null is not a failure —
|
|
@@ -480,6 +488,14 @@ export class WebGL2Renderer {
|
|
|
480
488
|
/** Whether a palette is currently chosen. Distinct from whether one was ever uploaded. */
|
|
481
489
|
skinPaletteSet = false;
|
|
482
490
|
emptyTexture2D;
|
|
491
|
+
/** DriftLight's stand-ins while no field is whole: no brick anywhere, and no light. */
|
|
492
|
+
emptyDriftIndex;
|
|
493
|
+
emptyDriftAtlas;
|
|
494
|
+
/** The scene's DriftLight field, and its two volumes once it is whole. See `createLightField`. */
|
|
495
|
+
lightField = null;
|
|
496
|
+
driftIndex = null;
|
|
497
|
+
driftAtlas = null;
|
|
498
|
+
driftUniforms = createDriftLightUniforms();
|
|
483
499
|
emptyTexture2DArray;
|
|
484
500
|
emptyTextureCube;
|
|
485
501
|
/** How hard the rush blurs this frame, and how far its outermost tap reaches. */
|
|
@@ -548,6 +564,8 @@ export class WebGL2Renderer {
|
|
|
548
564
|
motionBlurScale = 1;
|
|
549
565
|
/** How much of the bloom ceiling this frame takes. See `setBloom`. */
|
|
550
566
|
bloomScale = 1;
|
|
567
|
+
/** In scene units, once `setBloom` has moved it; null reads the profile's. */
|
|
568
|
+
bloomThresholdSet = null;
|
|
551
569
|
/**
|
|
552
570
|
* This frame's exposure into the tone curve. See `setOutputExposure`.
|
|
553
571
|
*
|
|
@@ -655,6 +673,15 @@ export class WebGL2Renderer {
|
|
|
655
673
|
depthUniforms;
|
|
656
674
|
depthInstancedProgram;
|
|
657
675
|
depthInstancedUniforms;
|
|
676
|
+
/**
|
|
677
|
+
* The cutout casters' depth programs, rigid and instanced: a leaf or a chain link casts the shape
|
|
678
|
+
* in its texture's alpha, not its whole card. See `DEPTH_CUTOUT_FRAG`. Chosen per draw by
|
|
679
|
+
* `cutoutOf`, so an opaque caster never touches them.
|
|
680
|
+
*/
|
|
681
|
+
depthCutoutProgram;
|
|
682
|
+
depthCutoutUniforms;
|
|
683
|
+
depthInstancedCutoutProgram;
|
|
684
|
+
depthInstancedCutoutUniforms;
|
|
658
685
|
scatterDepthProgram;
|
|
659
686
|
scatterDepthUniforms;
|
|
660
687
|
shadowMap;
|
|
@@ -713,6 +740,10 @@ export class WebGL2Renderer {
|
|
|
713
740
|
probeCapture;
|
|
714
741
|
/** Every probe of the grid, as one array texture. Allocated when a grid is declared. */
|
|
715
742
|
probeArray = null;
|
|
743
|
+
/** Which layers a bake writes and the shading reads. See `probeSweeps.ts`. */
|
|
744
|
+
probeSweeps = null;
|
|
745
|
+
/** `uProbeGridSets`, rewritten in place. */
|
|
746
|
+
probeSets = new Float32Array(3);
|
|
716
747
|
/**
|
|
717
748
|
* Where those probes stand. A single baked probe is a grid of one at that probe's own origin.
|
|
718
749
|
*
|
|
@@ -738,6 +769,8 @@ export class WebGL2Renderer {
|
|
|
738
769
|
warnedEnvironmentAmbient = false;
|
|
739
770
|
/** Whether a probe is being baked, so the passes that would recurse into one can tell. */
|
|
740
771
|
probePassActive = false;
|
|
772
|
+
/** Whether the bake under way lights its faces by the grid. `ProbeBakeOptions.bounce`. */
|
|
773
|
+
probeBounce = false;
|
|
741
774
|
/** The clip-control extension, or null where this context does not offer it. */
|
|
742
775
|
clipControl = null;
|
|
743
776
|
/**
|
|
@@ -875,6 +908,17 @@ export class WebGL2Renderer {
|
|
|
875
908
|
*/
|
|
876
909
|
activeDepthViewProj = IDENTITY_MAT4;
|
|
877
910
|
activeDepthPeel = false;
|
|
911
|
+
/**
|
|
912
|
+
* Whether a peel pass has filled the second layer since the first one last opened, which is
|
|
913
|
+
* what the frame samples it on: the rule the other backend's `peelFilled` states, held here too.
|
|
914
|
+
*
|
|
915
|
+
* **Not whether the map exists.** That was the test here, on the belief that a GL texture is
|
|
916
|
+
* born reading as the far plane; it is born zeroed, and a scene that built the layer and never
|
|
917
|
+
* peeled it sampled an occluder at the light. `ShadowMap` now clears itself at birth, so an
|
|
918
|
+
* unfilled layer would read as lit either way — this is what keeps the two backends one
|
|
919
|
+
* decision, and a frame that does not peel from paying a fetch per tap for nothing.
|
|
920
|
+
*/
|
|
921
|
+
peelFilled = false;
|
|
878
922
|
/**
|
|
879
923
|
* Whether the depth pass this scatter draw interrupts wants back-face culling.
|
|
880
924
|
*
|
|
@@ -885,6 +929,11 @@ export class WebGL2Renderer {
|
|
|
885
929
|
* cubemap — so the pass states what it wants and this restores that.
|
|
886
930
|
*/
|
|
887
931
|
depthPassCullsFaces = true;
|
|
932
|
+
/**
|
|
933
|
+
* Whether the bound material is seen from both faces, so its draws cull nothing. Toggled around
|
|
934
|
+
* each such draw and handed back, where the other backend bakes it into a pipeline.
|
|
935
|
+
*/
|
|
936
|
+
materialDoubleSided = false;
|
|
888
937
|
/**
|
|
889
938
|
* The one sink handed to every caster enumeration.
|
|
890
939
|
*
|
|
@@ -892,16 +941,38 @@ export class WebGL2Renderer {
|
|
|
892
941
|
* per pass would allocate six objects per light per frame.
|
|
893
942
|
*/
|
|
894
943
|
casterSink = {
|
|
895
|
-
mesh: (mesh, model) => {
|
|
944
|
+
mesh: (mesh, model, material) => {
|
|
896
945
|
this.shadowDrawBudget.ask();
|
|
897
946
|
const { gl } = this;
|
|
947
|
+
/* A two-sided caster casts from whichever face the light sees: nothing culled. */
|
|
948
|
+
const twoSided = material?.doubleSided === true;
|
|
949
|
+
const cutout = cutoutOf(material);
|
|
950
|
+
if (cutout !== null) {
|
|
951
|
+
const u = this.depthCutoutUniforms;
|
|
952
|
+
gl.useProgram(this.depthCutoutProgram);
|
|
953
|
+
this.bindCutoutDepth(u, cutout);
|
|
954
|
+
gl.uniformMatrix4fv(u['uModel'] ?? null, false, model);
|
|
955
|
+
if (twoSided)
|
|
956
|
+
gl.disable(gl.CULL_FACE);
|
|
957
|
+
mesh.draw(gl);
|
|
958
|
+
if (twoSided && this.depthPassCullsFaces)
|
|
959
|
+
gl.enable(gl.CULL_FACE);
|
|
960
|
+
/* Hand the pass back as it was found: the next caster may be rigid and opaque. */
|
|
961
|
+
gl.useProgram(this.depthProgram);
|
|
962
|
+
this.restoreSurfaceTextureUnit();
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
898
965
|
gl.uniformMatrix4fv(this.depthUniforms['uModel'] ?? null, false, model);
|
|
899
966
|
/*
|
|
900
967
|
* The sink takes the opaque handle both backends share, and this renderer only ever
|
|
901
968
|
* handed out its own `Mesh`. Narrowing is safe here for that reason and nowhere else:
|
|
902
969
|
* a handle from the other backend would have to have crossed renderers to arrive.
|
|
903
970
|
*/
|
|
971
|
+
if (twoSided)
|
|
972
|
+
gl.disable(gl.CULL_FACE);
|
|
904
973
|
mesh.draw(gl);
|
|
974
|
+
if (twoSided && this.depthPassCullsFaces)
|
|
975
|
+
gl.enable(gl.CULL_FACE);
|
|
905
976
|
},
|
|
906
977
|
/**
|
|
907
978
|
* A batch into the depth map, placed by the same matrices the visible draw uses.
|
|
@@ -909,13 +980,27 @@ export class WebGL2Renderer {
|
|
|
909
980
|
* No `uModel`: the instanced depth program has none, its placement being the four attribute
|
|
910
981
|
* columns the batch's own buffer supplies.
|
|
911
982
|
*/
|
|
912
|
-
instanced: (batch, data) => {
|
|
983
|
+
instanced: (batch, data, material) => {
|
|
913
984
|
const { gl } = this;
|
|
914
985
|
const gpuBatch = batch;
|
|
915
986
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
916
987
|
if (count === 0)
|
|
917
988
|
return;
|
|
918
989
|
this.shadowDrawBudget.ask();
|
|
990
|
+
const twoSided = material?.doubleSided === true;
|
|
991
|
+
if (twoSided)
|
|
992
|
+
gl.disable(gl.CULL_FACE);
|
|
993
|
+
const cutout = cutoutOf(material);
|
|
994
|
+
if (cutout !== null) {
|
|
995
|
+
gl.useProgram(this.depthInstancedCutoutProgram);
|
|
996
|
+
this.bindCutoutDepth(this.depthInstancedCutoutUniforms, cutout);
|
|
997
|
+
gpuBatch.mesh.drawInstances(gl, count);
|
|
998
|
+
if (twoSided && this.depthPassCullsFaces)
|
|
999
|
+
gl.enable(gl.CULL_FACE);
|
|
1000
|
+
gl.useProgram(this.depthProgram);
|
|
1001
|
+
this.restoreSurfaceTextureUnit();
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
919
1004
|
const u = this.depthInstancedUniforms;
|
|
920
1005
|
gl.useProgram(this.depthInstancedProgram);
|
|
921
1006
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
@@ -923,6 +1008,8 @@ export class WebGL2Renderer {
|
|
|
923
1008
|
if (this.activeDepthPeel)
|
|
924
1009
|
gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
925
1010
|
gpuBatch.mesh.drawInstances(gl, count);
|
|
1011
|
+
if (twoSided && this.depthPassCullsFaces)
|
|
1012
|
+
gl.enable(gl.CULL_FACE);
|
|
926
1013
|
gl.useProgram(this.depthProgram);
|
|
927
1014
|
},
|
|
928
1015
|
skinnedMesh: (mesh, model, palette) => {
|
|
@@ -972,6 +1059,21 @@ export class WebGL2Renderer {
|
|
|
972
1059
|
gl.useProgram(this.depthProgram);
|
|
973
1060
|
},
|
|
974
1061
|
};
|
|
1062
|
+
/**
|
|
1063
|
+
* What a cutout depth draw needs beyond the plain one: the pass's matrix and peel, the material's
|
|
1064
|
+
* cutoff and UV scale, and its albedo on the surface unit, which the caller gives back after.
|
|
1065
|
+
*/
|
|
1066
|
+
bindCutoutDepth(u, cutout) {
|
|
1067
|
+
const { gl } = this;
|
|
1068
|
+
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
1069
|
+
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1070
|
+
if (this.activeDepthPeel)
|
|
1071
|
+
gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1072
|
+
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
1073
|
+
gl.uniform1f(u['uAlphaCutout'] ?? null, cutout.cutoff);
|
|
1074
|
+
/* Narrowed for the reason the sink's own `mesh` gives: this renderer only hands out its own. */
|
|
1075
|
+
cutout.albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
1076
|
+
}
|
|
975
1077
|
drawPointShadowFace = (viewProj) => {
|
|
976
1078
|
const casters = this.pointShadowCasters;
|
|
977
1079
|
if (casters === null)
|
|
@@ -1528,6 +1630,8 @@ export class WebGL2Renderer {
|
|
|
1528
1630
|
* "unbound". Two 1x1 uploads for the life of the renderer.
|
|
1529
1631
|
*/
|
|
1530
1632
|
this.emptyTexture2D = createEmptyTexture2D(gl);
|
|
1633
|
+
this.emptyDriftIndex = createEmptyTexture3D(gl, true);
|
|
1634
|
+
this.emptyDriftAtlas = createEmptyTexture3D(gl, false);
|
|
1531
1635
|
this.emptyTexture2DArray = createEmptyTexture2DArray(gl);
|
|
1532
1636
|
this.emptyTextureCube = createEmptyTextureCube(gl);
|
|
1533
1637
|
this.gpuTimer = new GpuTimer(gl);
|
|
@@ -1719,6 +1823,7 @@ export class WebGL2Renderer {
|
|
|
1719
1823
|
this.panelVao = panelVao;
|
|
1720
1824
|
gl.bindVertexArray(panelVao);
|
|
1721
1825
|
const panelBuffer = gl.createBuffer();
|
|
1826
|
+
this.panelBuffer = panelBuffer;
|
|
1722
1827
|
gl.bindBuffer(gl.ARRAY_BUFFER, panelBuffer);
|
|
1723
1828
|
// A unit quad. The rectangle it becomes is a uniform, so this is uploaded once
|
|
1724
1829
|
// for the life of the renderer however many panels are drawn.
|
|
@@ -1785,6 +1890,19 @@ export class WebGL2Renderer {
|
|
|
1785
1890
|
*/
|
|
1786
1891
|
this.scatterDepthProgram = compileProgram(gl, SCATTER_DEPTH_VERT, DEPTH_FRAG, 'scatterDepth');
|
|
1787
1892
|
this.scatterDepthUniforms = uniformLocations(gl, this.scatterDepthProgram, 'scatterDepth');
|
|
1893
|
+
this.depthCutoutProgram = compileProgram(gl, DEPTH_CUTOUT_VERT, DEPTH_CUTOUT_FRAG, 'depth-cutout');
|
|
1894
|
+
this.depthCutoutUniforms = uniformLocations(gl, this.depthCutoutProgram, 'depth-cutout');
|
|
1895
|
+
this.depthInstancedCutoutProgram = compileProgram(gl, DEPTH_INSTANCED_CUTOUT_VERT, DEPTH_CUTOUT_FRAG, 'depth-instanced-cutout');
|
|
1896
|
+
this.depthInstancedCutoutUniforms = uniformLocations(gl, this.depthInstancedCutoutProgram, 'depth-instanced-cutout');
|
|
1897
|
+
/* The cutout map is always read from the surface unit, which the draw borrows and gives back. */
|
|
1898
|
+
for (const [program, uniforms] of [
|
|
1899
|
+
[this.depthCutoutProgram, this.depthCutoutUniforms],
|
|
1900
|
+
[this.depthInstancedCutoutProgram, this.depthInstancedCutoutUniforms],
|
|
1901
|
+
]) {
|
|
1902
|
+
gl.useProgram(program);
|
|
1903
|
+
gl.uniform1i(uniforms['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
1904
|
+
}
|
|
1905
|
+
gl.useProgram(null);
|
|
1788
1906
|
this.shadowMap = this.quality.directionalShadows
|
|
1789
1907
|
? new ShadowMap(gl, this.quality.directionalShadowMapSize)
|
|
1790
1908
|
: null;
|
|
@@ -1927,6 +2045,7 @@ export class WebGL2Renderer {
|
|
|
1927
2045
|
this.flatSkinnedProgram,
|
|
1928
2046
|
this.flatMorphedProgram,
|
|
1929
2047
|
this.flatBothProgram,
|
|
2048
|
+
this.flatInstancedProgram,
|
|
1930
2049
|
]) {
|
|
1931
2050
|
if (extra !== null)
|
|
1932
2051
|
gl.deleteProgram(extra);
|
|
@@ -1938,12 +2057,20 @@ export class WebGL2Renderer {
|
|
|
1938
2057
|
this.skyProgram,
|
|
1939
2058
|
this.scatterProgram,
|
|
1940
2059
|
this.depthProgram,
|
|
2060
|
+
this.depthSkinnedProgram,
|
|
2061
|
+
this.depthInstancedProgram,
|
|
1941
2062
|
this.scatterDepthProgram,
|
|
2063
|
+
this.lightVolumeProgram,
|
|
2064
|
+
this.depthCutoutProgram,
|
|
2065
|
+
this.depthInstancedCutoutProgram,
|
|
1942
2066
|
]) {
|
|
1943
2067
|
gl.deleteProgram(program);
|
|
1944
2068
|
}
|
|
1945
2069
|
this.skinPalette.dispose(gl);
|
|
1946
2070
|
gl.deleteTexture(this.emptyTexture2D);
|
|
2071
|
+
gl.deleteTexture(this.emptyDriftIndex);
|
|
2072
|
+
gl.deleteTexture(this.emptyDriftAtlas);
|
|
2073
|
+
this.forgetLightFieldVolumes();
|
|
1947
2074
|
gl.deleteTexture(this.emptyTexture2DArray);
|
|
1948
2075
|
gl.deleteTexture(this.emptyTextureCube);
|
|
1949
2076
|
/* A cubemap and its whole mip chain, which is the largest single thing this
|
|
@@ -1951,6 +2078,28 @@ export class WebGL2Renderer {
|
|
|
1951
2078
|
this.probeCapture?.dispose(gl);
|
|
1952
2079
|
this.probeArray?.dispose(gl);
|
|
1953
2080
|
this.environmentPrefilter?.dispose(gl);
|
|
2081
|
+
/*
|
|
2082
|
+
* **Everything else the constructor made, which this method never released.** The screen
|
|
2083
|
+
* chain and its bloom and occlusion, three sun maps, the mirror, the point-shadow array, two
|
|
2084
|
+
* vertex arrays, a quad and three textures made on first use. A page swapping renderers kept
|
|
2085
|
+
* every one of them, per renderer, for as long as it lived; `renderer.test.ts` counts them.
|
|
2086
|
+
*/
|
|
2087
|
+
this.sceneTarget?.dispose();
|
|
2088
|
+
this.shadowMap?.dispose(gl);
|
|
2089
|
+
this.peeledShadowMap?.dispose(gl);
|
|
2090
|
+
this.dynamicShadowMap?.dispose(gl);
|
|
2091
|
+
this.planarReflection?.dispose(gl);
|
|
2092
|
+
this.pointShadows?.dispose();
|
|
2093
|
+
gl.deleteVertexArray(this.panelVao);
|
|
2094
|
+
gl.deleteVertexArray(this.skyVao);
|
|
2095
|
+
if (this.panelBuffer !== null)
|
|
2096
|
+
gl.deleteBuffer(this.panelBuffer);
|
|
2097
|
+
if (this.cookieTexture !== null)
|
|
2098
|
+
gl.deleteTexture(this.cookieTexture);
|
|
2099
|
+
if (this.iesTexture !== null)
|
|
2100
|
+
gl.deleteTexture(this.iesTexture);
|
|
2101
|
+
if (this.clusterTexture !== null)
|
|
2102
|
+
gl.deleteTexture(this.clusterTexture);
|
|
1954
2103
|
}
|
|
1955
2104
|
if (options.releaseContext === true) {
|
|
1956
2105
|
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
|
@@ -2170,6 +2319,7 @@ export class WebGL2Renderer {
|
|
|
2170
2319
|
return;
|
|
2171
2320
|
this.materials.dirty();
|
|
2172
2321
|
this.currentSurfaceTexture = material?.albedo ?? null;
|
|
2322
|
+
this.materialDoubleSided = material?.doubleSided === true;
|
|
2173
2323
|
/*
|
|
2174
2324
|
* Written to every flat program, because material state persists across draws and a skinned
|
|
2175
2325
|
* draw is entitled to the material the caller set before it. A second program holding none of
|
|
@@ -2236,10 +2386,11 @@ export class WebGL2Renderer {
|
|
|
2236
2386
|
* The emissive map, on exactly the terms the two maps above it are bound: before the albedo
|
|
2237
2387
|
* early return, and the stand-in rather than `null`.
|
|
2238
2388
|
*
|
|
2239
|
-
* **
|
|
2240
|
-
* map,
|
|
2241
|
-
*
|
|
2242
|
-
*
|
|
2389
|
+
* **The stand-in is black, so the gate is what keeps a glow on.** The shader multiplies by
|
|
2390
|
+
* this map, and `emptyTexture2D` is opaque black rather than the multiplicative identity, so an
|
|
2391
|
+
* unbound material keeps the arithmetic it had before only because `uEmissiveMapEnabled` is 0
|
|
2392
|
+
* and the read never happens. What would make that wrong is a read taken outside that branch:
|
|
2393
|
+
* every glow on a material without a map would go out.
|
|
2243
2394
|
*/
|
|
2244
2395
|
const emissiveMap = material?.emissive ?? null;
|
|
2245
2396
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
@@ -2258,6 +2409,7 @@ export class WebGL2Renderer {
|
|
|
2258
2409
|
* how big a dome was — WebGPU writes it before its own early return and WebGL2 did not.
|
|
2259
2410
|
*/
|
|
2260
2411
|
gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
|
|
2412
|
+
gl.uniform1i(u['uDoubleSided'] ?? null, material?.doubleSided === true ? 1 : 0);
|
|
2261
2413
|
if (albedo === null) {
|
|
2262
2414
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
2263
2415
|
return;
|
|
@@ -2777,6 +2929,27 @@ export class WebGL2Renderer {
|
|
|
2777
2929
|
this.textBudget.ask();
|
|
2778
2930
|
}
|
|
2779
2931
|
}
|
|
2932
|
+
/**
|
|
2933
|
+
* The cell size to draw a bitmap glyph at so every cell covers whole device pixels.
|
|
2934
|
+
*
|
|
2935
|
+
* **The caller snaps, and then measures and draws with the one number.** 4.1.4 applied this
|
|
2936
|
+
* inside `drawText` instead, where `textWidthPx` could not see it — it takes no viewport and so
|
|
2937
|
+
* cannot know the ratio — and every consumer went on laying out against the cell it asked for
|
|
2938
|
+
* while the engine drew a smaller one. A centred line, a right-aligned column and a line fitted
|
|
2939
|
+
* to a box broke together. Snapped here, the arithmetic and the picture cannot disagree.
|
|
2940
|
+
*
|
|
2941
|
+
* **It is on the renderer because only the renderer knows the second number.** `viewportWidth`
|
|
2942
|
+
* is the caller's, and it already passes it to `drawText`; the drawing buffer is not on the
|
|
2943
|
+
* shared surface and a game's text layout is a pure function over CSS pixels, so reaching it
|
|
2944
|
+
* meant threading a canvas through an interface layer to a value the renderer was holding.
|
|
2945
|
+
*
|
|
2946
|
+
* The decision itself stays in `textLayout.ts` and only this binding is per backend, which is
|
|
2947
|
+
* the 2026-08-13 rule: a cell already whole comes back untouched, and the snap is downward so a
|
|
2948
|
+
* line fitted to a box can only leave a gap rather than overflow it.
|
|
2949
|
+
*/
|
|
2950
|
+
snapTextCellSize(cellSize, viewportWidth) {
|
|
2951
|
+
return deviceSnappedCellSize(cellSize, viewportWidth, this.gl.drawingBufferWidth);
|
|
2952
|
+
}
|
|
2780
2953
|
/** Width of the string this handle currently holds, in pixels at a given cell size. */
|
|
2781
2954
|
textWidth(text, cellSize) {
|
|
2782
2955
|
return text.widthPx(cellSize);
|
|
@@ -2941,7 +3114,11 @@ export class WebGL2Renderer {
|
|
|
2941
3114
|
*/
|
|
2942
3115
|
bindPointLights(gl, u, env, this.quality.pointLightFalloff);
|
|
2943
3116
|
bindAtmosphere(gl, u, env, this.reflectionPassActive ? this.reflectionAtmosphereY : (camera.position[1] ?? 0), this.quality.underwaterAtmosphere);
|
|
3117
|
+
if (this.materialDoubleSided)
|
|
3118
|
+
gl.disable(gl.CULL_FACE);
|
|
2944
3119
|
batch.drawTo(gl, data.count);
|
|
3120
|
+
if (this.materialDoubleSided)
|
|
3121
|
+
gl.enable(gl.CULL_FACE);
|
|
2945
3122
|
}
|
|
2946
3123
|
/** Build a batch for electrical arcs. `capacity` is in segments, not arcs. */
|
|
2947
3124
|
createBolts(segmentCapacity, label = 'bolts') {
|
|
@@ -3448,6 +3625,9 @@ export class WebGL2Renderer {
|
|
|
3448
3625
|
this.gpuTimer.begin('shadows');
|
|
3449
3626
|
this.shadowPassActive = true;
|
|
3450
3627
|
this.activeShadowMap = shadowMap;
|
|
3628
|
+
/* The static layer opens first, so a frame's peel counts from here. See `peelFilled`. */
|
|
3629
|
+
if (layer === 'static')
|
|
3630
|
+
this.peelFilled = false;
|
|
3451
3631
|
/*
|
|
3452
3632
|
* **Range-corrected but not reversed.** `EXT_clip_control` is context state, so a light matrix
|
|
3453
3633
|
* that still emits OpenGL's `[-1, 1]` loses everything below zero the moment it is on. See
|
|
@@ -3501,6 +3681,8 @@ export class WebGL2Renderer {
|
|
|
3501
3681
|
this.uploadWind(this.depthUniforms, this.frameWind);
|
|
3502
3682
|
gl.useProgram(this.depthSkinnedProgram);
|
|
3503
3683
|
this.uploadWind(this.depthSkinnedUniforms, this.frameWind);
|
|
3684
|
+
gl.useProgram(this.depthCutoutProgram);
|
|
3685
|
+
this.uploadWind(this.depthCutoutUniforms, this.frameWind);
|
|
3504
3686
|
gl.useProgram(this.depthProgram);
|
|
3505
3687
|
}
|
|
3506
3688
|
/**
|
|
@@ -3592,6 +3774,8 @@ export class WebGL2Renderer {
|
|
|
3592
3774
|
if (!this.shadowPassActive || shadowMap === null)
|
|
3593
3775
|
return;
|
|
3594
3776
|
shadowMap.end(this.gl);
|
|
3777
|
+
if (shadowMap === this.peeledShadowMap)
|
|
3778
|
+
this.peelFilled = true;
|
|
3595
3779
|
/* Back to the frame's sense; see `beginShadowPass` for why it left it. */
|
|
3596
3780
|
this.gl.depthFunc(glDepthFuncEqual(this.gl));
|
|
3597
3781
|
this.gl.clearDepth(this.depthClear);
|
|
@@ -4190,6 +4374,70 @@ export class WebGL2Renderer {
|
|
|
4190
4374
|
setCameraMotionBlur(scale) {
|
|
4191
4375
|
this.motionBlurScale = Math.min(Math.max(scale, 0), 1);
|
|
4192
4376
|
}
|
|
4377
|
+
/**
|
|
4378
|
+
* The camera has cut: this frame is a new shot, not the next moment of the last one.
|
|
4379
|
+
*
|
|
4380
|
+
* **Everything temporal reprojects through the previous frame's view**: the motion blur measures
|
|
4381
|
+
* its smear against it, and the temporal resolve reads its history through it. A cut is a jump
|
|
4382
|
+
* the renderer cannot tell from a fast camera, so without this a transport that seeks, a respawn
|
|
4383
|
+
* or an edit smears its first frame along the whole jump and blends in a picture of somewhere
|
|
4384
|
+
* else. The caller knows it cut; this is how it says so, before the frame's `beginFrame`.
|
|
4385
|
+
*
|
|
4386
|
+
* What it gives up is one frame of history: the frame after a cut has no blur and no temporal
|
|
4387
|
+
* blend, as the first frame of a session has none. Nothing else is reset.
|
|
4388
|
+
*/
|
|
4389
|
+
cameraCut() {
|
|
4390
|
+
this.hasPreviousView = false;
|
|
4391
|
+
this.temporalHistory.invalidate();
|
|
4392
|
+
/* The eye was adapted to the last shot. */
|
|
4393
|
+
this.sceneTarget?.cutExposure();
|
|
4394
|
+
}
|
|
4395
|
+
/**
|
|
4396
|
+
* **Eye adaptation**: how far the frame's exposure follows its own brightness, 0 to 1, and the
|
|
4397
|
+
* seconds since the last frame. 0 is off and the default, and at 0 nothing is metered or allocated.
|
|
4398
|
+
*
|
|
4399
|
+
* The finished scene is metered on the GPU each frame — the log-mean of its luminance — and a
|
|
4400
|
+
* held brightness moves toward it at a fixed rate, about 78% of the way in a second; the composite
|
|
4401
|
+
* then scales scene light toward middle grey by \`strength\` of the stops between them, at most six
|
|
4402
|
+
* either way. So a shaded courtyard floor opens up the way a camera or an eye would, and
|
|
4403
|
+
* \`setOutputExposure\` becomes a bias on top: a night kept darker than a day is a bias under one.
|
|
4404
|
+
* **The time is the caller's**, because the engine takes time from its caller; a held capture
|
|
4405
|
+
* passes 0 and holds. \`cameraCut\` snaps it, so a new shot is metered rather than eased into.
|
|
4406
|
+
*
|
|
4407
|
+
* What it gives up is regional metering: every pixel counts the same. Needs \`screenEffects\` and
|
|
4408
|
+
* \`hdrScene\`, and says so once rather than doing nothing. See \`shaders/exposure.ts\`.
|
|
4409
|
+
*/
|
|
4410
|
+
setAutoExposure(strength, dtSec) {
|
|
4411
|
+
if (this.sceneTarget === null || !this.quality.hdrScene) {
|
|
4412
|
+
this.warnLookWithoutComposite(strength);
|
|
4413
|
+
return;
|
|
4414
|
+
}
|
|
4415
|
+
this.sceneTarget.setAutoExposure(strength, dtSec);
|
|
4416
|
+
}
|
|
4417
|
+
/**
|
|
4418
|
+
* **Local exposure**: how far each region of the frame is brought toward the frame's own
|
|
4419
|
+
* brightness, 0 to 1. 0 is off and the default, and at 0 nothing more is measured or allocated.
|
|
4420
|
+
*
|
|
4421
|
+
* A tone curve maps one range, and a sunlit courtyard is two: its shaded arcades hold a fiftieth
|
|
4422
|
+
* of the light on its sunlit paving, so exposed for either the other is black or white. This moves
|
|
4423
|
+
* each region `strength` of the stops between it and the frame's held brightness, at most three,
|
|
4424
|
+
* read from a bilateral grid over the exposure meter's tiles, so shade beside sun is lifted as
|
|
4425
|
+
* shade rather than as the average of the two. The eye's held brightness is what regions move
|
|
4426
|
+
* toward, so it is measured whether or not `setAutoExposure` is on; with adaptation off the frame
|
|
4427
|
+
* as a whole keeps the exposure it was given. About 0.5 is a photograph's latitude, and 1 is a
|
|
4428
|
+
* frame with no light and shade left in it.
|
|
4429
|
+
*
|
|
4430
|
+
* What it gives up is contrast between regions, which is the point, and a halo a tile wide where
|
|
4431
|
+
* two regions of one brightness band differ. Needs `screenEffects` and `hdrScene`, and says so
|
|
4432
|
+
* once rather than doing nothing. See `shaders/localExposure.ts`.
|
|
4433
|
+
*/
|
|
4434
|
+
setLocalExposure(strength) {
|
|
4435
|
+
if (this.sceneTarget === null || !this.quality.hdrScene) {
|
|
4436
|
+
this.warnLookWithoutComposite(strength);
|
|
4437
|
+
return;
|
|
4438
|
+
}
|
|
4439
|
+
this.sceneTarget.setLocalExposure(strength);
|
|
4440
|
+
}
|
|
4193
4441
|
/**
|
|
4194
4442
|
* Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
|
|
4195
4443
|
* to take.
|
|
@@ -4242,9 +4490,18 @@ export class WebGL2Renderer {
|
|
|
4242
4490
|
*
|
|
4243
4491
|
* Set before `beginFrame` and held until changed. Ignored entirely when `bloom` is 0, since
|
|
4244
4492
|
* the chain is never built.
|
|
4493
|
+
*
|
|
4494
|
+
* **`threshold` moves `bloomThreshold` for this frame and every one after**, in the same scene
|
|
4495
|
+
* units. A threshold is compared before exposure is applied, so one fixed at construction means a
|
|
4496
|
+
* different brightness on screen at every exposure. That is harmless while exposure stands still
|
|
4497
|
+
* and wrong for a day whose exposure spans 2.5 to 14: a courtyard blooming at noon, or candles
|
|
4498
|
+
* never blooming at night. A caller whose exposure moves passes what it wants on screen over the
|
|
4499
|
+
* exposure. Omitted leaves it where it was, so a caller that never passes one keeps the profile's.
|
|
4245
4500
|
*/
|
|
4246
|
-
setBloom(scale) {
|
|
4501
|
+
setBloom(scale, threshold) {
|
|
4247
4502
|
this.bloomScale = Math.min(Math.max(scale, 0), 1);
|
|
4503
|
+
if (threshold !== undefined)
|
|
4504
|
+
this.bloomThresholdSet = bloomThresholdOf(threshold);
|
|
4248
4505
|
}
|
|
4249
4506
|
/**
|
|
4250
4507
|
* How thick the air is, this frame: a global participating medium filling the whole frustum.
|
|
@@ -4378,6 +4635,38 @@ export class WebGL2Renderer {
|
|
|
4378
4635
|
}
|
|
4379
4636
|
this.sceneTarget.setColourGrade(lut, strength);
|
|
4380
4637
|
}
|
|
4638
|
+
/**
|
|
4639
|
+
* How strongly the lens darkens the frame's corners, held until changed: 0 is none, 0.5 is about
|
|
4640
|
+
* 1.2 stops at the corner. Applied to scene light before the tone curve, as a lens loses light;
|
|
4641
|
+
* see `filmLook.ts`. Needs `screenEffects`, and says so once rather than doing nothing.
|
|
4642
|
+
*/
|
|
4643
|
+
setVignette(strength) {
|
|
4644
|
+
if (this.sceneTarget === null) {
|
|
4645
|
+
this.warnLookWithoutComposite(strength);
|
|
4646
|
+
return;
|
|
4647
|
+
}
|
|
4648
|
+
this.sceneTarget.setVignette(strength);
|
|
4649
|
+
}
|
|
4650
|
+
/**
|
|
4651
|
+
* Film grain, held until changed: an amplitude in display values (0 is none, 0.03 is a fine
|
|
4652
|
+
* grain) and this frame's seed. **The seed is the caller's**, because the engine takes time from
|
|
4653
|
+
* its caller: pass a new one each frame for grain that moves, and the same one for a still that
|
|
4654
|
+
* is identical run to run. Applied last, after the grade and the veil; see `filmLook.ts`.
|
|
4655
|
+
*/
|
|
4656
|
+
setFilmGrain(strength, seed) {
|
|
4657
|
+
if (this.sceneTarget === null) {
|
|
4658
|
+
this.warnLookWithoutComposite(strength);
|
|
4659
|
+
return;
|
|
4660
|
+
}
|
|
4661
|
+
this.sceneTarget.setFilmGrain(strength, seed);
|
|
4662
|
+
}
|
|
4663
|
+
warnedLookWithoutComposite = false;
|
|
4664
|
+
warnLookWithoutComposite(strength) {
|
|
4665
|
+
if (strength > 0 && !this.warnedLookWithoutComposite) {
|
|
4666
|
+
this.warnedLookWithoutComposite = true;
|
|
4667
|
+
console.warn(FILM_LOOK_WITHOUT_COMPOSITE);
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4381
4670
|
/**
|
|
4382
4671
|
* What this frame asked for. **Every ceiling here is `null`, and that is the finding.**
|
|
4383
4672
|
*
|
|
@@ -4694,7 +4983,10 @@ export class WebGL2Renderer {
|
|
|
4694
4983
|
*/
|
|
4695
4984
|
const bloomStrength = this.quality.bloom * this.bloomScale;
|
|
4696
4985
|
const bloom = bloomStrength > 0
|
|
4697
|
-
? {
|
|
4986
|
+
? {
|
|
4987
|
+
strength: bloomStrength,
|
|
4988
|
+
threshold: this.bloomThresholdSet ?? this.quality.bloomThreshold,
|
|
4989
|
+
}
|
|
4698
4990
|
: undefined;
|
|
4699
4991
|
/* The grade travels to the resolve only where the mesh pass gave it up. See `bindMeshPass`. */
|
|
4700
4992
|
const lateGrade = this.quality.hdrScene
|
|
@@ -4863,11 +5155,12 @@ export class WebGL2Renderer {
|
|
|
4863
5155
|
sunShadow,
|
|
4864
5156
|
lightViewProj: env?.lightViewProj ?? IDENTITY_MAT4,
|
|
4865
5157
|
/* The placeholder rather than null where a map is merely absent, for the reason
|
|
4866
|
-
`emptyTexture.ts` gives
|
|
5158
|
+
`emptyTexture.ts` gives. It is black, which as a depth is an occluder at the light,
|
|
5159
|
+
so it is safe only because `sunShadow` is 0 whenever these maps do not exist. */
|
|
4867
5160
|
staticShadowMap: this.shadowMap?.texture ?? this.emptyTexture2D,
|
|
4868
5161
|
peeledShadowMap: this.peeledShadowMap?.texture ?? this.emptyTexture2D,
|
|
4869
5162
|
dynamicShadowMap: this.dynamicShadowMap?.texture ?? this.emptyTexture2D,
|
|
4870
|
-
peeledEnabled: this.
|
|
5163
|
+
peeledEnabled: this.peelFilled,
|
|
4871
5164
|
});
|
|
4872
5165
|
if (marched) {
|
|
4873
5166
|
/* The march finished on the default framebuffer, which is not where the frame is being
|
|
@@ -5071,6 +5364,78 @@ export class WebGL2Renderer {
|
|
|
5071
5364
|
/* Leave the plain program current, which is what every other pass expects to find. */
|
|
5072
5365
|
this.useFlatProgram();
|
|
5073
5366
|
}
|
|
5367
|
+
/**
|
|
5368
|
+
* Sum a scene's many fixed lights into a DriftLight field, which stands in for each of them
|
|
5369
|
+
* wherever the frame's choice of exact lights does not reach. See `driftLight/lightField.ts`.
|
|
5370
|
+
*
|
|
5371
|
+
* Replaces any field made before. The scene then paces `field.bake(bricks)` and calls
|
|
5372
|
+
* `field.follow(buffer.complete, ...)` each frame with the selection that chose its exact lights;
|
|
5373
|
+
* the volume is summed into the frame once every brick has landed.
|
|
5374
|
+
*/
|
|
5375
|
+
createLightField(lights, options = {}) {
|
|
5376
|
+
this.forgetLightFieldVolumes();
|
|
5377
|
+
const field = new LightField(lights, { ...options, falloff: this.quality.pointLightFalloff });
|
|
5378
|
+
this.lightField = field;
|
|
5379
|
+
return field;
|
|
5380
|
+
}
|
|
5381
|
+
/** Let go of the field and its volumes; the scene's lights are all exact again. */
|
|
5382
|
+
disposeLightField() {
|
|
5383
|
+
this.forgetLightFieldVolumes();
|
|
5384
|
+
this.lightField = null;
|
|
5385
|
+
}
|
|
5386
|
+
forgetLightFieldVolumes() {
|
|
5387
|
+
if (this.driftIndex !== null)
|
|
5388
|
+
this.gl.deleteTexture(this.driftIndex);
|
|
5389
|
+
if (this.driftAtlas !== null)
|
|
5390
|
+
this.gl.deleteTexture(this.driftAtlas);
|
|
5391
|
+
this.driftIndex = null;
|
|
5392
|
+
this.driftAtlas = null;
|
|
5393
|
+
}
|
|
5394
|
+
/**
|
|
5395
|
+
* Upload the field's two volumes, once, when its last brick has baked: nothing samples them
|
|
5396
|
+
* before that, so uploading as bricks land would be work for no picture.
|
|
5397
|
+
*/
|
|
5398
|
+
uploadLightField() {
|
|
5399
|
+
const field = this.lightField;
|
|
5400
|
+
if (field === null || this.driftAtlas !== null || !field.ready || field.layout.count === 0) {
|
|
5401
|
+
return;
|
|
5402
|
+
}
|
|
5403
|
+
const { gl } = this;
|
|
5404
|
+
const [ix, iy, iz] = field.layout.dims;
|
|
5405
|
+
const index = gl.createTexture();
|
|
5406
|
+
gl.bindTexture(gl.TEXTURE_3D, index);
|
|
5407
|
+
gl.texImage3D(gl.TEXTURE_3D, 0, gl.R32UI, ix, iy, iz, 0, gl.RED_INTEGER, gl.UNSIGNED_INT, field.layout.index);
|
|
5408
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
5409
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
5410
|
+
const [aw, ah, ad] = field.atlasSize;
|
|
5411
|
+
const atlas = gl.createTexture();
|
|
5412
|
+
gl.bindTexture(gl.TEXTURE_3D, atlas);
|
|
5413
|
+
gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA16F, aw, ah, ad, 0, gl.RGBA, gl.HALF_FLOAT, field.atlas);
|
|
5414
|
+
/* Filtered, because a brick's samples share their faces: the lookup is seamless inside one. */
|
|
5415
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
5416
|
+
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
5417
|
+
for (const wrap of [gl.TEXTURE_WRAP_S, gl.TEXTURE_WRAP_T, gl.TEXTURE_WRAP_R]) {
|
|
5418
|
+
gl.texParameteri(gl.TEXTURE_3D, wrap, gl.CLAMP_TO_EDGE);
|
|
5419
|
+
}
|
|
5420
|
+
gl.bindTexture(gl.TEXTURE_3D, null);
|
|
5421
|
+
this.driftIndex = index;
|
|
5422
|
+
this.driftAtlas = atlas;
|
|
5423
|
+
}
|
|
5424
|
+
/** The field's uniforms and volumes for this pass, or the stand-ins that switch it off. */
|
|
5425
|
+
bindDriftLight(u, eye) {
|
|
5426
|
+
const { gl } = this;
|
|
5427
|
+
this.uploadLightField();
|
|
5428
|
+
const d = this.driftUniforms;
|
|
5429
|
+
resolveDriftLight(this.driftAtlas === null ? null : this.lightField, this.probePassActive, eye, d);
|
|
5430
|
+
gl.uniform4fv(u['uDriftLight'] ?? null, d.light);
|
|
5431
|
+
gl.uniform4fv(u['uDriftLightOrigin'] ?? null, d.origin);
|
|
5432
|
+
gl.activeTexture(gl.TEXTURE0 + DRIFT_LIGHT_INDEX_TEXTURE_UNIT);
|
|
5433
|
+
gl.bindTexture(gl.TEXTURE_3D, this.driftIndex ?? this.emptyDriftIndex);
|
|
5434
|
+
gl.uniform1i(u['uDriftLightIndex'] ?? null, DRIFT_LIGHT_INDEX_TEXTURE_UNIT);
|
|
5435
|
+
gl.activeTexture(gl.TEXTURE0 + DRIFT_LIGHT_ATLAS_TEXTURE_UNIT);
|
|
5436
|
+
gl.bindTexture(gl.TEXTURE_3D, this.driftAtlas ?? this.emptyDriftAtlas);
|
|
5437
|
+
gl.uniform1i(u['uDriftLightAtlas'] ?? null, DRIFT_LIGHT_ATLAS_TEXTURE_UNIT);
|
|
5438
|
+
}
|
|
5074
5439
|
/**
|
|
5075
5440
|
* Everything one flat program needs to know about this frame.
|
|
5076
5441
|
*
|
|
@@ -5199,7 +5564,7 @@ export class WebGL2Renderer {
|
|
|
5199
5564
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
5200
5565
|
gl.uniform1f(u['uShadowDepthSpan'] ?? null, env.shadowDepthSpan);
|
|
5201
5566
|
gl.uniform1f(u['uShadowMaxDistance'] ?? null, this.quality.directionalShadowMaxDistance);
|
|
5202
|
-
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.
|
|
5567
|
+
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
5203
5568
|
gl.uniform1f(u['uShadowMaxSlope'] ?? null, this.quality.directionalShadowMaxSlope);
|
|
5204
5569
|
gl.uniform1i(u['uShadowFilterTaps'] ?? null, this.quality.shadowFilterTaps);
|
|
5205
5570
|
gl.uniform3fv(u['uHighlightMin'] ?? null, env.highlightMin);
|
|
@@ -5274,11 +5639,19 @@ export class WebGL2Renderer {
|
|
|
5274
5639
|
* zero would not be enough on its own — a driver may fetch a sampler's descriptor before it
|
|
5275
5640
|
* evaluates the arithmetic that discards the result, which is the same reason
|
|
5276
5641
|
* `emptyTexture.ts` exists.
|
|
5642
|
+
*
|
|
5643
|
+
* **Except a bounce bake, which reads it on purpose.** Its faces are drawn into the capture
|
|
5644
|
+
* cube, and the array is attached only by the convolution after them, so no pass both samples
|
|
5645
|
+
* and attaches it.
|
|
5277
5646
|
*/
|
|
5278
5647
|
if (this.probeCapture !== null) {
|
|
5279
5648
|
const array = this.probeArray;
|
|
5280
5649
|
const grid = this.probes;
|
|
5281
|
-
const usable = array !== null &&
|
|
5650
|
+
const usable = array !== null &&
|
|
5651
|
+
grid !== null &&
|
|
5652
|
+
array.usable &&
|
|
5653
|
+
this.probeSweeps?.ready === true &&
|
|
5654
|
+
(!this.probePassActive || this.probeBounce);
|
|
5282
5655
|
gl.activeTexture(gl.TEXTURE0 + ENVIRONMENT_TEXTURE_UNIT);
|
|
5283
5656
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, usable ? array.texture : this.emptyTexture2DArray);
|
|
5284
5657
|
gl.uniform1i(u['uEnvironment'] ?? null, ENVIRONMENT_TEXTURE_UNIT);
|
|
@@ -5319,13 +5692,19 @@ export class WebGL2Renderer {
|
|
|
5319
5692
|
* times over two days as four different bugs before it could be asked for.
|
|
5320
5693
|
*/
|
|
5321
5694
|
gl.uniform1f(u['uProbeGridAmbient'] ?? null, usable && this.probeAmbient ? 1 : 0);
|
|
5695
|
+
/* Which sets the shading reads, and how far between them. See `probeSweeps.ts`. */
|
|
5696
|
+
const sets = this.probeSweeps?.uniforms(this.probeSets) ?? this.probeSets.fill(0);
|
|
5697
|
+
gl.uniform3f(u['uProbeGridSets'] ?? null, sets[0] ?? 0, sets[1] ?? 0, sets[2] ?? 0);
|
|
5322
5698
|
}
|
|
5699
|
+
this.bindDriftLight(u, camera.position);
|
|
5323
5700
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
5324
5701
|
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
5325
5702
|
gl.uniform1i(u['uAlbedo'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
5326
5703
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
5327
5704
|
gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
|
|
5328
5705
|
gl.uniform1f(u['uAlbedoCutout'] ?? null, 0);
|
|
5706
|
+
gl.uniform1i(u['uDoubleSided'] ?? null, 0);
|
|
5707
|
+
this.materialDoubleSided = false;
|
|
5329
5708
|
this.currentSurfaceTexture = null;
|
|
5330
5709
|
/*
|
|
5331
5710
|
* **And the normal map, which this did not reset and had to.**
|
|
@@ -5551,7 +5930,11 @@ export class WebGL2Renderer {
|
|
|
5551
5930
|
*/
|
|
5552
5931
|
gl.depthFunc(this.reversedDepth ? gl.GREATER : gl.LESS);
|
|
5553
5932
|
if (layer === 0) {
|
|
5933
|
+
if (this.materialDoubleSided)
|
|
5934
|
+
gl.disable(gl.CULL_FACE);
|
|
5554
5935
|
mesh.draw(gl);
|
|
5936
|
+
if (this.materialDoubleSided)
|
|
5937
|
+
gl.enable(gl.CULL_FACE);
|
|
5555
5938
|
gl.depthFunc(this.reversedDepth ? gl.GEQUAL : gl.LEQUAL);
|
|
5556
5939
|
if (tint !== null)
|
|
5557
5940
|
gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
@@ -5572,7 +5955,11 @@ export class WebGL2Renderer {
|
|
|
5572
5955
|
*/
|
|
5573
5956
|
const offset = depthOffsetForLayer(layer, this.reversedDepth);
|
|
5574
5957
|
gl.polygonOffset(offset.slope, offset.units);
|
|
5958
|
+
if (this.materialDoubleSided)
|
|
5959
|
+
gl.disable(gl.CULL_FACE);
|
|
5575
5960
|
mesh.draw(gl);
|
|
5961
|
+
if (this.materialDoubleSided)
|
|
5962
|
+
gl.enable(gl.CULL_FACE);
|
|
5576
5963
|
gl.polygonOffset(0, 0);
|
|
5577
5964
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
5578
5965
|
if (program !== null)
|
|
@@ -5727,7 +6114,11 @@ export class WebGL2Renderer {
|
|
|
5727
6114
|
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
5728
6115
|
gl.polygonOffset(offset.slope, offset.units);
|
|
5729
6116
|
}
|
|
6117
|
+
if (this.materialDoubleSided)
|
|
6118
|
+
gl.disable(gl.CULL_FACE);
|
|
5730
6119
|
mesh.draw(gl);
|
|
6120
|
+
if (this.materialDoubleSided)
|
|
6121
|
+
gl.enable(gl.CULL_FACE);
|
|
5731
6122
|
if (offset.units !== 0) {
|
|
5732
6123
|
gl.polygonOffset(0, 0);
|
|
5733
6124
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
@@ -5802,7 +6193,8 @@ export class WebGL2Renderer {
|
|
|
5802
6193
|
return true;
|
|
5803
6194
|
this.probeArray?.dispose(this.gl);
|
|
5804
6195
|
this.probes = grid;
|
|
5805
|
-
this.
|
|
6196
|
+
this.probeSweeps = new ProbeSweeps(grid.layers, grid.crossfade);
|
|
6197
|
+
this.probeArray = new EnvProbeArray(this.gl, octahedralEdgeFor(this.quality.reflectionProbeSize), grid.layers * this.probeSweeps.sets, this.quality.hdrScene);
|
|
5806
6198
|
return this.probeArray.usable;
|
|
5807
6199
|
}
|
|
5808
6200
|
/**
|
|
@@ -5822,12 +6214,14 @@ export class WebGL2Renderer {
|
|
|
5822
6214
|
const probe = this.probeCapture;
|
|
5823
6215
|
const array = this.probeArray;
|
|
5824
6216
|
const grid = this.probes;
|
|
5825
|
-
|
|
6217
|
+
const sweeps = this.probeSweeps;
|
|
6218
|
+
if (probe === null || array === null || grid === null || sweeps === null)
|
|
5826
6219
|
return false;
|
|
5827
6220
|
if (layer < 0 || layer >= grid.layers)
|
|
5828
6221
|
return false;
|
|
5829
6222
|
this.probeAmbient = options?.irradiance ?? true;
|
|
5830
6223
|
grid.positionOf(layer, this.probeOrigin);
|
|
6224
|
+
this.probeBounce = options?.bounce ?? false;
|
|
5831
6225
|
this.probePassActive = true;
|
|
5832
6226
|
/*
|
|
5833
6227
|
* **A probe stores radiance, not display pixels.**
|
|
@@ -5865,14 +6259,19 @@ export class WebGL2Renderer {
|
|
|
5865
6259
|
this.gl.frontFace(this.gl.CW);
|
|
5866
6260
|
let baked = false;
|
|
5867
6261
|
try {
|
|
5868
|
-
|
|
6262
|
+
const [first, end] = probeFaceRange(options);
|
|
6263
|
+
baked = probe.bake(this.gl, this.probeOrigin, clearColor, drawFace, first, end);
|
|
6264
|
+
/* A bake that has not reached its sixth face has nothing whole to convolve yet. */
|
|
6265
|
+
if (end < 6)
|
|
6266
|
+
return baked;
|
|
5869
6267
|
/*
|
|
5870
6268
|
* The convolution, immediately after the faces and inside the same guard, because it reads
|
|
5871
6269
|
* the chain `bake` has just generated. A bake that failed leaves the layer untouched rather
|
|
5872
6270
|
* than convolving whatever the driver left in the capture.
|
|
5873
6271
|
*/
|
|
5874
6272
|
if (baked && this.environmentPrefilter !== null) {
|
|
5875
|
-
this.environmentPrefilter.run(this.gl, probe, array, layer, ENVIRONMENT_TEXTURE_UNIT, this.quality.environmentPrefilterSamples, this.quality.environmentPrefilter);
|
|
6273
|
+
this.environmentPrefilter.run(this.gl, probe, array, sweeps.writeLayer(layer), ENVIRONMENT_TEXTURE_UNIT, this.quality.environmentPrefilterSamples, this.quality.environmentPrefilter);
|
|
6274
|
+
sweeps.baked(layer);
|
|
5876
6275
|
}
|
|
5877
6276
|
}
|
|
5878
6277
|
finally {
|
|
@@ -5886,6 +6285,7 @@ export class WebGL2Renderer {
|
|
|
5886
6285
|
* later frame reading the placeholder and quietly reflecting nothing.
|
|
5887
6286
|
*/
|
|
5888
6287
|
this.probePassActive = false;
|
|
6288
|
+
this.probeBounce = false;
|
|
5889
6289
|
/* Back to the canvas, or to the frame's own target if one is bound. See `SceneTarget`. */
|
|
5890
6290
|
if (this.sceneTarget !== null && !this.framePresented)
|
|
5891
6291
|
this.sceneTarget.bind();
|
|
@@ -5941,6 +6341,7 @@ export class WebGL2Renderer {
|
|
|
5941
6341
|
return false;
|
|
5942
6342
|
this.probeAmbient = options?.irradiance ?? true;
|
|
5943
6343
|
this.environmentPrefilter?.run(this.gl, probe, array, 0, ENVIRONMENT_TEXTURE_UNIT, this.quality.environmentPrefilterSamples, this.quality.environmentPrefilter);
|
|
6344
|
+
this.probeSweeps?.baked(0);
|
|
5944
6345
|
return true;
|
|
5945
6346
|
}
|
|
5946
6347
|
/**
|
|
@@ -6063,7 +6464,7 @@ export class WebGL2Renderer {
|
|
|
6063
6464
|
lookup does its own `* 0.5 + 0.5` and correcting the matrix as well doubles it. */
|
|
6064
6465
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, env.lightViewProj);
|
|
6065
6466
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
6066
|
-
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.
|
|
6467
|
+
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
6067
6468
|
gl.activeTexture(gl.TEXTURE0);
|
|
6068
6469
|
gl.bindTexture(gl.TEXTURE_2D, this.shadowMap?.texture ?? this.emptyTexture2D);
|
|
6069
6470
|
gl.uniform1i(u['uStaticShadowMap'] ?? null, 0);
|