@driftengine/core 4.4.2 → 4.5.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 +46 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +79 -8
- package/dist/render/backend/webgl2/renderer.js +360 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +2 -12
- package/dist/render/backend/webgpu/renderer.d.ts +171 -54
- package/dist/render/backend/webgpu/renderer.js +944 -390
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/lateRouting.d.ts +14 -0
- package/dist/render/recon/lateRouting.js +18 -0
- package/dist/render/recon/mover.d.ts +57 -0
- package/dist/render/recon/mover.js +86 -0
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +7 -1
- package/dist/render/sceneTarget.js +58 -12
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +4 -0
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +115 -15
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shadowCasters.d.ts +20 -1
- package/dist/render/shadowCasters.js +15 -1
- package/dist/render/shadowMap.d.ts +54 -5
- package/dist/render/shadowMap.js +190 -36
- package/dist/render/sunGlassTint.d.ts +25 -0
- package/dist/render/sunGlassTint.js +79 -0
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +401 -108
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/renderer.ts +1108 -421
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +58 -11
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/translucentQueue.ts +20 -1
|
@@ -2,6 +2,7 @@ import { FrameBudget } from '../budget.js';
|
|
|
2
2
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.js';
|
|
3
3
|
import { mat4 } from 'gl-matrix';
|
|
4
4
|
import { GL_DEPTH_REMAP, MAX_DEPTH_LAYER, depthClearFor, depthOffsetForLayer, GL_SHADOW_REMAP, REVERSED_DEPTH, SHADOW_DEPTH_CLEAR, glDepthFuncEqual, glShadowDepthFunc, } from '../../depthConvention.js';
|
|
5
|
+
import { resolveGlass } from '../../glass.js';
|
|
5
6
|
import { PickableSet } from '../../pickable.js';
|
|
6
7
|
import { TEMPORAL_HISTORY_BLEND, TemporalHistory, jitterOffset, jitterProjection, } from '../../temporalAa.js';
|
|
7
8
|
import { DecalPass } from '../../decalPass.js';
|
|
@@ -51,13 +52,17 @@ import { areaShadowLayerCount, castingRange, createResolvedAreaShadows, firstAre
|
|
|
51
52
|
import { equirectToCubeFaces } from '../../equirectToCube.js';
|
|
52
53
|
import { SKY_FRAG, skyVertFor } from '../../shaders/sky.js';
|
|
53
54
|
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';
|
|
54
|
-
import { PointShadowArray } from '../../pointShadowArray.js';
|
|
55
|
-
import {
|
|
55
|
+
import { OCTAHEDRAL_EDGE, PointShadowArray, strideLayers } from '../../pointShadowArray.js';
|
|
56
|
+
import { SunShadowArray } from '../../shadowMap.js';
|
|
56
57
|
import { POINT_SHADOW_SAMPLER, PointShadowSystem } from '../../pointShadowSystem.js';
|
|
58
|
+
import { castsDepth, glassOf } from '../../shadowCasters.js';
|
|
59
|
+
import { GlassCasterList } from '../../glassCasters.js';
|
|
60
|
+
import { SunGlassTint } from '../../sunGlassTint.js';
|
|
61
|
+
import { GlassTintPrograms } from './glassTintPrograms.js';
|
|
57
62
|
import { FACE_COUNT, PointShadowMap } from '../../pointShadowMap.js';
|
|
58
63
|
import { createResolvedPointShadows, DEFAULT_SOURCE_RADIUS } from '../../pointShadowImage.js';
|
|
59
64
|
import { createBakeScratch, runPointShadowBakes, selectCastingLights, } from '../../pointShadowBudget.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';
|
|
65
|
+
import { DIRECTIONAL_SHADOW_UNITS, SUN_GLASS_TINT_TEXTURE_UNIT, POINT_GLASS_TINT_TEXTURE_UNIT, 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';
|
|
61
66
|
import { resolveRenderQuality } from '../../renderQuality.js';
|
|
62
67
|
import { drawingBufferSize } from '../../drawingBuffer.js';
|
|
63
68
|
import { Mesh, createMeshIncremental } from '../../mesh.js';
|
|
@@ -488,6 +493,8 @@ export class WebGL2Renderer {
|
|
|
488
493
|
/** Whether a palette is currently chosen. Distinct from whether one was ever uploaded. */
|
|
489
494
|
skinPaletteSet = false;
|
|
490
495
|
emptyTexture2D;
|
|
496
|
+
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
497
|
+
glassScratch = { transmission: 0, frost: 0, tint: [1, 1, 1] };
|
|
491
498
|
/** DriftLight's stand-ins while no field is whole: no brick anywhere, and no light. */
|
|
492
499
|
emptyDriftIndex;
|
|
493
500
|
emptyDriftAtlas;
|
|
@@ -560,6 +567,7 @@ export class WebGL2Renderer {
|
|
|
560
567
|
refraction: 0,
|
|
561
568
|
refractTint: undefined,
|
|
562
569
|
thicknessM: 0,
|
|
570
|
+
glass: undefined,
|
|
563
571
|
};
|
|
564
572
|
motionBlurScale = 1;
|
|
565
573
|
/** How much of the bloom ceiling this frame takes. See `setBloom`. */
|
|
@@ -687,9 +695,8 @@ export class WebGL2Renderer {
|
|
|
687
695
|
depthInstancedCutoutUniforms;
|
|
688
696
|
scatterDepthProgram;
|
|
689
697
|
scatterDepthUniforms;
|
|
690
|
-
shadowMap
|
|
691
|
-
|
|
692
|
-
dynamicShadowMap;
|
|
698
|
+
/** The sun's static, moving and peeled maps, as layers of one array. See `shadowMap.ts`. */
|
|
699
|
+
sunShadows;
|
|
693
700
|
pointShadows;
|
|
694
701
|
/**
|
|
695
702
|
* The layers a world's casting rectangles hold, above the point pool in the same array.
|
|
@@ -895,7 +902,7 @@ export class WebGL2Renderer {
|
|
|
895
902
|
/** Non-zero while the drawing buffer is pinned; see `lockDrawingBuffer`. */
|
|
896
903
|
lockedWidth = 0;
|
|
897
904
|
lockedHeight = 0;
|
|
898
|
-
|
|
905
|
+
activeSunLayer = null;
|
|
899
906
|
reflectionPassActive = false;
|
|
900
907
|
reflectionReadyThisFrame = false;
|
|
901
908
|
reflectionAtmosphereY = 0;
|
|
@@ -917,7 +924,7 @@ export class WebGL2Renderer {
|
|
|
917
924
|
*
|
|
918
925
|
* **Not whether the map exists.** That was the test here, on the belief that a GL texture is
|
|
919
926
|
* born reading as the far plane; it is born zeroed, and a scene that built the layer and never
|
|
920
|
-
* peeled it sampled an occluder at the light. `
|
|
927
|
+
* peeled it sampled an occluder at the light. `SunShadowArray` clears itself at birth, so an
|
|
921
928
|
* unfilled layer would read as lit either way — this is what keeps the two backends one
|
|
922
929
|
* decision, and a frame that does not peel from paying a fetch per tap for nothing.
|
|
923
930
|
*/
|
|
@@ -943,8 +950,34 @@ export class WebGL2Renderer {
|
|
|
943
950
|
* Built once at construction, arrow properties closing over `this`: a sink created
|
|
944
951
|
* per pass would allocate six objects per light per frame.
|
|
945
952
|
*/
|
|
953
|
+
/** What the sun's light keeps through glass, made the first time glass casts: `sunGlassTint.ts`. */
|
|
954
|
+
sunGlassTint = null;
|
|
955
|
+
/** The five programs a pane casts its colour through, compiled the first time glass casts. */
|
|
956
|
+
glassTintPrograms = null;
|
|
957
|
+
/** True while the glass list is replayed into the glass depth layer through `casterSink`. */
|
|
958
|
+
drawingGlassDepth = false;
|
|
959
|
+
/** How many times a shadow array grew its glass layers: once per family, and never again. */
|
|
960
|
+
sunGlassRebuilds = 0;
|
|
961
|
+
/** The light a tint pass is drawn for, and one pane's colour and clarity: uploaded per entry. */
|
|
962
|
+
glassLight = new Float32Array(4);
|
|
963
|
+
glassPane = new Float32Array(4);
|
|
964
|
+
/** The glass the open shadow pass was offered, kept for its own passes: `glassCasters.ts`. */
|
|
965
|
+
glassCasters = new GlassCasterList();
|
|
966
|
+
/** One resolution every glass caster is read through, so recording one allocates nothing. */
|
|
967
|
+
glassCasterScratch = {
|
|
968
|
+
transmission: 0,
|
|
969
|
+
frost: 0,
|
|
970
|
+
tint: [1, 1, 1],
|
|
971
|
+
};
|
|
946
972
|
casterSink = {
|
|
947
973
|
mesh: (mesh, model, material) => {
|
|
974
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
975
|
+
/* Glass: kept aside for its own two passes rather than dropped. See `glassCasters.ts`. */
|
|
976
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
977
|
+
this.glassCasters.recordMesh(mesh, model, material, this.glassCasterScratch);
|
|
978
|
+
}
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
948
981
|
this.shadowDrawBudget.ask();
|
|
949
982
|
const { gl } = this;
|
|
950
983
|
/* A two-sided caster casts from whichever face the light sees: nothing culled. */
|
|
@@ -984,6 +1017,12 @@ export class WebGL2Renderer {
|
|
|
984
1017
|
* columns the batch's own buffer supplies.
|
|
985
1018
|
*/
|
|
986
1019
|
instanced: (batch, data, material) => {
|
|
1020
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1021
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1022
|
+
this.glassCasters.recordInstanced(batch, data, material, this.glassCasterScratch);
|
|
1023
|
+
}
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
987
1026
|
const { gl } = this;
|
|
988
1027
|
const gpuBatch = batch;
|
|
989
1028
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
@@ -1015,7 +1054,13 @@ export class WebGL2Renderer {
|
|
|
1015
1054
|
gl.enable(gl.CULL_FACE);
|
|
1016
1055
|
gl.useProgram(this.depthProgram);
|
|
1017
1056
|
},
|
|
1018
|
-
skinnedMesh: (mesh, model, palette) => {
|
|
1057
|
+
skinnedMesh: (mesh, model, palette, material) => {
|
|
1058
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1059
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1060
|
+
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
1061
|
+
}
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1019
1064
|
this.shadowDrawBudget.ask();
|
|
1020
1065
|
const { gl } = this;
|
|
1021
1066
|
const u = this.depthSkinnedUniforms;
|
|
@@ -1098,8 +1143,52 @@ export class WebGL2Renderer {
|
|
|
1098
1143
|
gl.useProgram(this.depthProgram);
|
|
1099
1144
|
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, 0);
|
|
1100
1145
|
gl.uniformMatrix4fv(this.depthUniforms['uLightViewProj'] ?? null, false, faceViewProj);
|
|
1146
|
+
/* A face is an enumeration of its own: its glass is its own. */
|
|
1147
|
+
this.glassCasters.clear();
|
|
1101
1148
|
casters(this.casterSink);
|
|
1149
|
+
/*
|
|
1150
|
+
* The first face to offer glass interleaves the array, before its opaque depth is resolved:
|
|
1151
|
+
* `PointShadowArray.growForGlass`. Nothing baked before this saw glass, so nothing re-bakes.
|
|
1152
|
+
*/
|
|
1153
|
+
if (this.glassCasters.count > 0 && this.quality.glassShadows !== 'off') {
|
|
1154
|
+
const tintEdge = this.quality.glassShadows === 'half' ? OCTAHEDRAL_EDGE >> 1 : OCTAHEDRAL_EDGE;
|
|
1155
|
+
this.pointShadowArray?.growForGlass(gl, tintEdge);
|
|
1156
|
+
}
|
|
1102
1157
|
};
|
|
1158
|
+
/**
|
|
1159
|
+
* A lamp's glass, after each face's opaque depth: `PointShadowMap.bake` asks and binds, this
|
|
1160
|
+
* draws, through the same sinks the sun's glass takes and in the face's own matrix.
|
|
1161
|
+
*/
|
|
1162
|
+
pointGlassDraw = {
|
|
1163
|
+
offered: () => this.glassCasters.count > 0,
|
|
1164
|
+
draw: (pass, x, y, z) => {
|
|
1165
|
+
const { gl } = this;
|
|
1166
|
+
if (pass === 'depth') {
|
|
1167
|
+
/* Nothing on the unit the depth programs declare a previous map on: see `flushSunGlass`. */
|
|
1168
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1169
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
1170
|
+
this.activeDepthPeel = false;
|
|
1171
|
+
gl.useProgram(this.depthProgram);
|
|
1172
|
+
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, 0);
|
|
1173
|
+
gl.uniformMatrix4fv(this.depthUniforms['uLightViewProj'] ?? null, false, this.activeDepthViewProj ?? IDENTITY_MAT4);
|
|
1174
|
+
this.drawingGlassDepth = true;
|
|
1175
|
+
this.glassCasters.replay(this.glassDepthSink);
|
|
1176
|
+
this.drawingGlassDepth = false;
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
this.glassTintPrograms ??= new GlassTintPrograms(gl);
|
|
1180
|
+
/* A place, w 1: the tint takes each pane's angle to the lamp from its own position. */
|
|
1181
|
+
this.glassLight[0] = x;
|
|
1182
|
+
this.glassLight[1] = y;
|
|
1183
|
+
this.glassLight[2] = z;
|
|
1184
|
+
this.glassLight[3] = 1;
|
|
1185
|
+
this.glassCasters.replay(this.glassTintSink);
|
|
1186
|
+
},
|
|
1187
|
+
};
|
|
1188
|
+
/** The glass hook a bake is handed: none at all when glass shadows are off. */
|
|
1189
|
+
get pointGlass() {
|
|
1190
|
+
return this.quality.glassShadows === 'off' ? null : this.pointGlassDraw;
|
|
1191
|
+
}
|
|
1103
1192
|
/**
|
|
1104
1193
|
* Viewport in CSS pixels rather than device pixels.
|
|
1105
1194
|
*
|
|
@@ -1906,20 +1995,14 @@ export class WebGL2Renderer {
|
|
|
1906
1995
|
gl.uniform1i(uniforms['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
1907
1996
|
}
|
|
1908
1997
|
gl.useProgram(null);
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
: null;
|
|
1918
|
-
// Movers get an independent layer. It preserves distance fading when a
|
|
1919
|
-
// moving and a static caster overlap: their transmissions multiply rather
|
|
1920
|
-
// than one depth replacing the other.
|
|
1921
|
-
this.dynamicShadowMap = this.quality.directionalShadows
|
|
1922
|
-
? new ShadowMap(gl, this.quality.directionalShadowMapSize)
|
|
1998
|
+
/*
|
|
1999
|
+
* The static world, its movers and — where more than one static depth layer is asked for —
|
|
2000
|
+
* the peel, as layers of one array. Movers get a layer of their own so a moving and a static
|
|
2001
|
+
* caster that overlap keep both distance fades: their transmissions multiply rather than one
|
|
2002
|
+
* depth replacing the other. The peel keeps the second static occluder the same way.
|
|
2003
|
+
*/
|
|
2004
|
+
this.sunShadows = this.quality.directionalShadows
|
|
2005
|
+
? new SunShadowArray(gl, this.quality.directionalShadowMapSize, this.quality.directionalShadowDepthLayers > 1)
|
|
1923
2006
|
: null;
|
|
1924
2007
|
// Static maps are allocated once the world reports its light count. No
|
|
1925
2008
|
// cubemap resources exist at all when the quality profile disables them.
|
|
@@ -2088,9 +2171,9 @@ export class WebGL2Renderer {
|
|
|
2088
2171
|
* every one of them, per renderer, for as long as it lived; `renderer.test.ts` counts them.
|
|
2089
2172
|
*/
|
|
2090
2173
|
this.sceneTarget?.dispose();
|
|
2091
|
-
this.
|
|
2092
|
-
this.
|
|
2093
|
-
this.
|
|
2174
|
+
this.sunShadows?.dispose(gl);
|
|
2175
|
+
this.sunGlassTint?.dispose(gl);
|
|
2176
|
+
this.glassTintPrograms?.dispose(gl);
|
|
2094
2177
|
this.planarReflection?.dispose(gl);
|
|
2095
2178
|
this.pointShadows?.dispose();
|
|
2096
2179
|
gl.deleteVertexArray(this.panelVao);
|
|
@@ -2719,8 +2802,10 @@ export class WebGL2Renderer {
|
|
|
2719
2802
|
const lit = options.lit ?? true;
|
|
2720
2803
|
const fog = options.fog ?? true;
|
|
2721
2804
|
const toneMapped = options.toneMapped ?? true;
|
|
2722
|
-
/*
|
|
2723
|
-
|
|
2805
|
+
/* A blended batch shows what is behind it — refraction or glass — as a single draw does, and
|
|
2806
|
+
counts a material of its own for it by the rule both backends share (`materialChanges.ts`). */
|
|
2807
|
+
const refracting = blend && this.bindSeeThrough(u, options);
|
|
2808
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
2724
2809
|
if (own)
|
|
2725
2810
|
this.materials.dirty();
|
|
2726
2811
|
this.takeMaterial();
|
|
@@ -2741,20 +2826,9 @@ export class WebGL2Renderer {
|
|
|
2741
2826
|
* **A null snapshot leaves the strength at zero**, so the draw shades as an ordinary
|
|
2742
2827
|
* translucent one rather than sampling a black texture and painting the pane the colour of a
|
|
2743
2828
|
* hole. That is the defined state the two-backends rule asks for instead of a silent no-op.
|
|
2829
|
+
* The copy is bound above, before the material is counted, because whether it was taken is
|
|
2830
|
+
* part of what the count asks.
|
|
2744
2831
|
*/
|
|
2745
|
-
const refraction = options.refraction ?? 0;
|
|
2746
|
-
let refracting = false;
|
|
2747
|
-
if (refraction > 0) {
|
|
2748
|
-
const snapshot = this.sceneTarget?.snapshotColor() ?? null;
|
|
2749
|
-
if (snapshot !== null) {
|
|
2750
|
-
refracting = true;
|
|
2751
|
-
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
2752
|
-
gl.bindTexture(gl.TEXTURE_2D, snapshot);
|
|
2753
|
-
gl.uniform1f(u['uRefractStrength'] ?? null, refraction);
|
|
2754
|
-
gl.uniform3fv(u['uRefractTint'] ?? null, options.refractTint ?? WHITE_TINT);
|
|
2755
|
-
gl.uniform1f(u['uRefractThickness'] ?? null, options.thicknessM ?? 0);
|
|
2756
|
-
}
|
|
2757
|
-
}
|
|
2758
2832
|
if (!lit)
|
|
2759
2833
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
2760
2834
|
if (!fog)
|
|
@@ -2774,7 +2848,13 @@ export class WebGL2Renderer {
|
|
|
2774
2848
|
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
2775
2849
|
gl.polygonOffset(offset.slope, offset.units);
|
|
2776
2850
|
}
|
|
2851
|
+
/* Both sides of a double-sided material, as every mesh path does and WebGPU's pipeline does
|
|
2852
|
+
for both: without it a batch's far faces were culled here alone. */
|
|
2853
|
+
if (this.materialDoubleSided)
|
|
2854
|
+
gl.disable(gl.CULL_FACE);
|
|
2777
2855
|
mesh.drawInstances(gl, count);
|
|
2856
|
+
if (this.materialDoubleSided)
|
|
2857
|
+
gl.enable(gl.CULL_FACE);
|
|
2778
2858
|
if (layer > 0) {
|
|
2779
2859
|
gl.polygonOffset(0, 0);
|
|
2780
2860
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
@@ -2790,11 +2870,8 @@ export class WebGL2Renderer {
|
|
|
2790
2870
|
gl.uniform1f(u['uOpacity'] ?? null, 1);
|
|
2791
2871
|
/* The binding is released with the uniform, which is the 2026-08-27 rule: a WebGL2 binding
|
|
2792
2872
|
outlives its frame, so a snapshot left on unit 14 is held against the next frame's copy. */
|
|
2793
|
-
if (refracting)
|
|
2794
|
-
|
|
2795
|
-
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
2796
|
-
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
2797
|
-
}
|
|
2873
|
+
if (refracting)
|
|
2874
|
+
this.unbindSeeThrough(u);
|
|
2798
2875
|
if (!lit)
|
|
2799
2876
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
2800
2877
|
if (!fog)
|
|
@@ -2805,6 +2882,45 @@ export class WebGL2Renderer {
|
|
|
2805
2882
|
this.materials.dirty();
|
|
2806
2883
|
this.useFlatProgram();
|
|
2807
2884
|
}
|
|
2885
|
+
/**
|
|
2886
|
+
* Bind what a translucent draw shows through it: the frame's copy, refraction's bend and absorption,
|
|
2887
|
+
* and glass. Returns whether the copy is bound, and so whether `unbindSeeThrough` owes it back.
|
|
2888
|
+
*
|
|
2889
|
+
* **A glass draw takes the copy even when it does not bend**, since a pane shows what is behind it
|
|
2890
|
+
* either way, and asks for the copy's mip chain, which a frosted pane reads blurred. A null copy
|
|
2891
|
+
* leaves both off, so the pane shades as ordinary translucency rather than sampling black.
|
|
2892
|
+
*
|
|
2893
|
+
* **Refused inside a probe's face or a mirror**, as WebGPU's `takeRefractSnapshot` refuses: the
|
|
2894
|
+
* copy is of the frame drawn to the screen, so a pane there would show the wrong picture, and
|
|
2895
|
+
* taking it mid-bake is how a probe's next face came to be attached to the scene's framebuffer.
|
|
2896
|
+
*/
|
|
2897
|
+
bindSeeThrough(u, options) {
|
|
2898
|
+
const { gl } = this;
|
|
2899
|
+
const refraction = options.refraction ?? 0;
|
|
2900
|
+
const glassy = resolveGlass(options.glass, this.glassScratch);
|
|
2901
|
+
if (refraction <= 0 && !glassy)
|
|
2902
|
+
return false;
|
|
2903
|
+
if (this.probePassActive || this.reflectionPassActive)
|
|
2904
|
+
return false;
|
|
2905
|
+
const snapshot = this.sceneTarget?.snapshotColor(false, glassy) ?? null;
|
|
2906
|
+
if (snapshot === null)
|
|
2907
|
+
return false;
|
|
2908
|
+
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
2909
|
+
gl.bindTexture(gl.TEXTURE_2D, snapshot);
|
|
2910
|
+
const glass = this.glassScratch;
|
|
2911
|
+
/* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
|
|
2912
|
+
gl.uniform4f(u['uSeeThrough'] ?? null, Math.max(refraction, 0), options.thicknessM ?? 0, glass.transmission, glass.frost);
|
|
2913
|
+
gl.uniform3fv(u['uRefractTint'] ?? null, options.refractTint ?? WHITE_TINT);
|
|
2914
|
+
gl.uniform3fv(u['uGlassTint'] ?? null, glass.tint);
|
|
2915
|
+
return true;
|
|
2916
|
+
}
|
|
2917
|
+
/** Put back what `bindSeeThrough` bound: the binding outlives its frame (the 2026-08-27 rule). */
|
|
2918
|
+
unbindSeeThrough(u) {
|
|
2919
|
+
const { gl } = this;
|
|
2920
|
+
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
2921
|
+
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
2922
|
+
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
2923
|
+
}
|
|
2808
2924
|
disposeMesh(mesh) {
|
|
2809
2925
|
/*
|
|
2810
2926
|
* Nothing to release when the context is gone: it took every buffer and VAO with it,
|
|
@@ -3338,6 +3454,10 @@ export class WebGL2Renderer {
|
|
|
3338
3454
|
*/
|
|
3339
3455
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.pointShadowArray?.texture ?? this.emptyTexture2DArray);
|
|
3340
3456
|
gl.uniform1i(uniforms[POINT_SHADOW_SAMPLER] ?? null, firstUnit);
|
|
3457
|
+
/* What the lamps keep through glass: the one-texel stand-in, read as none, until glass casts. */
|
|
3458
|
+
gl.activeTexture(gl.TEXTURE0 + POINT_GLASS_TINT_TEXTURE_UNIT);
|
|
3459
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.pointShadowArray?.tintTexture ?? this.emptyTexture2DArray);
|
|
3460
|
+
gl.uniform1i(uniforms['uPointGlassTints'] ?? null, POINT_GLASS_TINT_TEXTURE_UNIT);
|
|
3341
3461
|
gl.uniform1iv(uniforms['uPointShadowLayer[0]'] ?? null, resolved.layers);
|
|
3342
3462
|
/* Origin and far plane in one row; see `uPointShadowProjection` in the preamble. */
|
|
3343
3463
|
gl.uniform4fv(uniforms['uPointShadowProjection[0]'] ?? null, resolved.projections);
|
|
@@ -3614,20 +3734,17 @@ export class WebGL2Renderer {
|
|
|
3614
3734
|
* emitting, because a stale map must never be sampled.
|
|
3615
3735
|
*/
|
|
3616
3736
|
beginShadowPass(lightViewProj, layer = 'static') {
|
|
3617
|
-
const
|
|
3618
|
-
|
|
3619
|
-
: layer === 'static-peel'
|
|
3620
|
-
? this.peeledShadowMap
|
|
3621
|
-
: this.dynamicShadowMap;
|
|
3622
|
-
if (shadowMap === null) {
|
|
3737
|
+
const sun = this.sunShadows;
|
|
3738
|
+
if (sun === null || (layer === 'static-peel' && sun.layers < 3)) {
|
|
3623
3739
|
this.shadowPassActive = false;
|
|
3624
|
-
this.
|
|
3740
|
+
this.activeSunLayer = null;
|
|
3625
3741
|
return;
|
|
3626
3742
|
}
|
|
3627
3743
|
const { gl } = this;
|
|
3628
3744
|
this.gpuTimer.begin('shadows');
|
|
3629
3745
|
this.shadowPassActive = true;
|
|
3630
|
-
this.
|
|
3746
|
+
this.activeSunLayer = layer;
|
|
3747
|
+
this.glassCasters.clear();
|
|
3631
3748
|
/* The static layer opens first, so a frame's peel counts from here. See `peelFilled`. */
|
|
3632
3749
|
if (layer === 'static')
|
|
3633
3750
|
this.peelFilled = false;
|
|
@@ -3655,16 +3772,16 @@ export class WebGL2Renderer {
|
|
|
3655
3772
|
// The previous mesh pass left all directional maps bound for sampling.
|
|
3656
3773
|
// A texture cannot be sampled while it is attached to the active depth
|
|
3657
3774
|
// target, even when the shader branch would skip that sample.
|
|
3658
|
-
|
|
3659
|
-
gl.
|
|
3660
|
-
|
|
3661
|
-
shadowMap.begin(gl);
|
|
3775
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
3776
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3777
|
+
sun.begin(gl, layer);
|
|
3662
3778
|
gl.useProgram(this.depthProgram);
|
|
3663
3779
|
const peel = layer === 'static-peel';
|
|
3664
3780
|
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, peel ? 1 : 0);
|
|
3665
3781
|
if (peel) {
|
|
3782
|
+
/* The static layer, read while the peel is drawn aside: see `SunShadowArray`. */
|
|
3666
3783
|
gl.activeTexture(gl.TEXTURE0);
|
|
3667
|
-
gl.bindTexture(gl.
|
|
3784
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, sun.texture);
|
|
3668
3785
|
gl.uniform1i(this.depthUniforms['uPreviousShadowMap'] ?? null, 0);
|
|
3669
3786
|
}
|
|
3670
3787
|
/* The corrected one, matching `activeDepthViewProj` above: a pass must render its map with the
|
|
@@ -3770,21 +3887,158 @@ export class WebGL2Renderer {
|
|
|
3770
3887
|
return;
|
|
3771
3888
|
casters(this.casterSink);
|
|
3772
3889
|
}
|
|
3890
|
+
/**
|
|
3891
|
+
* Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
|
|
3892
|
+
*
|
|
3893
|
+
* After the opaque casters, in the pass's own light matrix. A layer that has held glass is
|
|
3894
|
+
* redrawn even when this pass offered none, so glass that has gone takes its colour with it. The
|
|
3895
|
+
* first glass grows the array (`SunShadowArray.growForGlass`) and makes the tint target.
|
|
3896
|
+
*/
|
|
3897
|
+
flushSunGlass(which) {
|
|
3898
|
+
const sun = this.sunShadows;
|
|
3899
|
+
const list = this.glassCasters;
|
|
3900
|
+
if (sun === null || this.quality.glassShadows === 'off')
|
|
3901
|
+
return;
|
|
3902
|
+
if (list.count === 0 && !sun.hasGlass)
|
|
3903
|
+
return;
|
|
3904
|
+
const { gl } = this;
|
|
3905
|
+
if (list.count > 0 && sun.growForGlass(gl))
|
|
3906
|
+
this.sunGlassRebuilds++;
|
|
3907
|
+
/* Where the nearest pane is: the depth programs, through the sink every opaque caster took. */
|
|
3908
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
3909
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3910
|
+
sun.beginGlass(gl, which);
|
|
3911
|
+
this.activeDepthPeel = false;
|
|
3912
|
+
gl.useProgram(this.depthProgram);
|
|
3913
|
+
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, 0);
|
|
3914
|
+
gl.uniformMatrix4fv(this.depthUniforms['uLightViewProj'] ?? null, false, this.activeDepthViewProj ?? IDENTITY_MAT4);
|
|
3915
|
+
this.drawingGlassDepth = true;
|
|
3916
|
+
list.replay(this.glassDepthSink);
|
|
3917
|
+
this.drawingGlassDepth = false;
|
|
3918
|
+
sun.end(gl);
|
|
3919
|
+
/* What the panes let through, every one on a ray, multiplied. */
|
|
3920
|
+
this.glassTintPrograms ??= new GlassTintPrograms(gl);
|
|
3921
|
+
const size = this.quality.glassShadows === 'half' ? Math.max(1, sun.size >> 1) : sun.size;
|
|
3922
|
+
const tint = (this.sunGlassTint ??= new SunGlassTint(gl, size));
|
|
3923
|
+
/*
|
|
3924
|
+
* The direction the light travels, off the light matrix's depth row: an orthographic
|
|
3925
|
+
* projection's depth grows along it. The sign is immaterial — the tint takes the cosine
|
|
3926
|
+
* absolutely — and w 0 says it is a direction rather than a place.
|
|
3927
|
+
*/
|
|
3928
|
+
const m = this.activeDepthViewProj ?? IDENTITY_MAT4;
|
|
3929
|
+
const length = Math.hypot(m[2], m[6], m[10]) || 1;
|
|
3930
|
+
this.glassLight[0] = m[2] / length;
|
|
3931
|
+
this.glassLight[1] = m[6] / length;
|
|
3932
|
+
this.glassLight[2] = m[10] / length;
|
|
3933
|
+
this.glassLight[3] = 0;
|
|
3934
|
+
tint.begin(gl, which === 'static' ? 0 : 1);
|
|
3935
|
+
list.replay(this.glassTintSink);
|
|
3936
|
+
tint.end(gl);
|
|
3937
|
+
gl.useProgram(this.depthProgram);
|
|
3938
|
+
}
|
|
3939
|
+
/** The glass list into the glass depth layer: `casterSink` itself, with glass let through. */
|
|
3940
|
+
glassDepthSink = {
|
|
3941
|
+
mesh: (mesh, model, material) => this.casterSink.mesh(mesh, model, material),
|
|
3942
|
+
instanced: (batch, data, material) => this.casterSink.instanced?.(batch, data, material),
|
|
3943
|
+
skinnedMesh: (mesh, model, palette, material) => this.casterSink.skinnedMesh(mesh, model, palette, material),
|
|
3944
|
+
};
|
|
3945
|
+
/** One pane's tint uniforms, and its cutout where it has one. */
|
|
3946
|
+
bindGlassTint(program, glass, cutout) {
|
|
3947
|
+
const { gl } = this;
|
|
3948
|
+
const u = program.uniforms;
|
|
3949
|
+
gl.useProgram(program.program);
|
|
3950
|
+
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj ?? IDENTITY_MAT4);
|
|
3951
|
+
this.uploadWind(u, this.frameWind);
|
|
3952
|
+
this.glassPane[0] = glass.transmission * glass.tint[0];
|
|
3953
|
+
this.glassPane[1] = glass.transmission * glass.tint[1];
|
|
3954
|
+
this.glassPane[2] = glass.transmission * glass.tint[2];
|
|
3955
|
+
this.glassPane[3] = 1 - glass.frost;
|
|
3956
|
+
gl.uniform4fv(u['uGlassPane'] ?? null, this.glassPane);
|
|
3957
|
+
gl.uniform4fv(u['uGlassLight'] ?? null, this.glassLight);
|
|
3958
|
+
if (cutout !== null) {
|
|
3959
|
+
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
3960
|
+
gl.uniform1f(u['uAlphaCutout'] ?? null, cutout.cutoff);
|
|
3961
|
+
cutout.albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
3962
|
+
gl.uniform1i(u['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
/**
|
|
3966
|
+
* The glass list into the tint layer, through the tint programs, **culling nothing**: a light
|
|
3967
|
+
* crosses a pane whichever way the pane faces, and a closed glass object has two surfaces on a
|
|
3968
|
+
* ray, so every one counts. What that asks of a model is that a pane be one surface — two-sided
|
|
3969
|
+
* glass says so with `doubleSided`, and a second copy of its triangles is a second pane.
|
|
3970
|
+
*/
|
|
3971
|
+
glassTintSink = {
|
|
3972
|
+
mesh: (mesh, model, material, glass) => {
|
|
3973
|
+
const programs = this.glassTintPrograms;
|
|
3974
|
+
if (programs === null)
|
|
3975
|
+
return;
|
|
3976
|
+
const { gl } = this;
|
|
3977
|
+
const cutout = cutoutOf(material);
|
|
3978
|
+
const program = cutout !== null ? programs.cutout : programs.rigid;
|
|
3979
|
+
this.bindGlassTint(program, glass, cutout);
|
|
3980
|
+
gl.uniformMatrix4fv(program.uniforms['uModel'] ?? null, false, model);
|
|
3981
|
+
gl.disable(gl.CULL_FACE);
|
|
3982
|
+
mesh.draw(gl);
|
|
3983
|
+
if (this.depthPassCullsFaces)
|
|
3984
|
+
gl.enable(gl.CULL_FACE);
|
|
3985
|
+
if (cutout !== null)
|
|
3986
|
+
this.restoreSurfaceTextureUnit();
|
|
3987
|
+
},
|
|
3988
|
+
instanced: (batch, data, material, glass) => {
|
|
3989
|
+
const programs = this.glassTintPrograms;
|
|
3990
|
+
if (programs === null)
|
|
3991
|
+
return;
|
|
3992
|
+
const { gl } = this;
|
|
3993
|
+
const gpuBatch = batch;
|
|
3994
|
+
const count = Math.min(data.count, gpuBatch.capacity);
|
|
3995
|
+
if (count === 0)
|
|
3996
|
+
return;
|
|
3997
|
+
const cutout = cutoutOf(material);
|
|
3998
|
+
this.bindGlassTint(cutout !== null ? programs.instancedCutout : programs.instanced, glass, cutout);
|
|
3999
|
+
gl.disable(gl.CULL_FACE);
|
|
4000
|
+
gpuBatch.mesh.drawInstances(gl, count);
|
|
4001
|
+
if (this.depthPassCullsFaces)
|
|
4002
|
+
gl.enable(gl.CULL_FACE);
|
|
4003
|
+
if (cutout !== null)
|
|
4004
|
+
this.restoreSurfaceTextureUnit();
|
|
4005
|
+
},
|
|
4006
|
+
skinnedMesh: (mesh, model, palette, _material, glass) => {
|
|
4007
|
+
const programs = this.glassTintPrograms;
|
|
4008
|
+
if (programs === null)
|
|
4009
|
+
return;
|
|
4010
|
+
const { gl } = this;
|
|
4011
|
+
const program = programs.skinned;
|
|
4012
|
+
this.bindGlassTint(program, glass, null);
|
|
4013
|
+
gl.uniformMatrix4fv(program.uniforms['uModel'] ?? null, false, model);
|
|
4014
|
+
this.skinPalette.update(gl, palette);
|
|
4015
|
+
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
4016
|
+
gl.uniform1i(program.uniforms['uJointPalette'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
4017
|
+
gl.disable(gl.CULL_FACE);
|
|
4018
|
+
mesh.draw(gl);
|
|
4019
|
+
if (this.depthPassCullsFaces)
|
|
4020
|
+
gl.enable(gl.CULL_FACE);
|
|
4021
|
+
},
|
|
4022
|
+
};
|
|
3773
4023
|
endShadowPass() {
|
|
3774
4024
|
if (this.contextLost)
|
|
3775
4025
|
return;
|
|
3776
|
-
const
|
|
3777
|
-
|
|
4026
|
+
const sun = this.sunShadows;
|
|
4027
|
+
const layer = this.activeSunLayer;
|
|
4028
|
+
if (!this.shadowPassActive || sun === null || layer === null)
|
|
3778
4029
|
return;
|
|
3779
|
-
|
|
3780
|
-
if (
|
|
4030
|
+
sun.end(this.gl);
|
|
4031
|
+
if (layer === 'static-peel')
|
|
3781
4032
|
this.peelFilled = true;
|
|
4033
|
+
/* The peel keeps no glass: its layer is the second opaque occluder and nothing else. */
|
|
4034
|
+
else
|
|
4035
|
+
this.flushSunGlass(layer === 'dynamic' ? 'dynamic' : 'static');
|
|
3782
4036
|
/* Back to the frame's sense; see `beginShadowPass` for why it left it. */
|
|
3783
4037
|
this.gl.depthFunc(glDepthFuncEqual(this.gl));
|
|
3784
4038
|
this.gl.clearDepth(this.depthClear);
|
|
3785
4039
|
this.gpuTimer.end();
|
|
3786
4040
|
this.shadowPassActive = false;
|
|
3787
|
-
this.
|
|
4041
|
+
this.activeSunLayer = null;
|
|
3788
4042
|
// Restore the drawing-buffer viewport the shadow pass overrode.
|
|
3789
4043
|
this.restoreViewport();
|
|
3790
4044
|
}
|
|
@@ -3818,6 +4072,8 @@ export class WebGL2Renderer {
|
|
|
3818
4072
|
directionalShadows: this.quality.directionalShadows,
|
|
3819
4073
|
environmentProbe: this.probeCapture !== null,
|
|
3820
4074
|
nightEmissive: this.quality.nightEmissive,
|
|
4075
|
+
/* Off compiles none of the glass lookup: see `FlatShaderOptions.glassShadows`. */
|
|
4076
|
+
glassShadows: this.quality.glassShadows !== 'off',
|
|
3821
4077
|
};
|
|
3822
4078
|
const build = (budget) => flatFrag({ ...variant, maxLights: budget.maxLights, maxAreaLights: budget.maxAreaLights });
|
|
3823
4079
|
const vert = flatVert({ skinned: false, morphed: false, instanced: false });
|
|
@@ -4081,6 +4337,8 @@ export class WebGL2Renderer {
|
|
|
4081
4337
|
*/
|
|
4082
4338
|
runPointShadowBakes(pointShadows, lights, this.quality.pointShadowFacesPerFrame, this.quality.liveShadowFacesPerFrame, this.quality.pointShadowRebakeDistance, FACE_COUNT, this.bakeScratch, staticCasters, dynamicCasters, (map, light, casters, maxFaces) => this.bakeOne(map, light, casters, maxFaces));
|
|
4083
4339
|
this.updateAreaShadows(areaLights, frameDt, staticCasters, dynamicCasters);
|
|
4340
|
+
/* The chain frost reads, once for the round, for the lights whose glass it drew or cleared. */
|
|
4341
|
+
this.pointShadowArray.finishTints(this.gl);
|
|
4084
4342
|
this.restoreViewport();
|
|
4085
4343
|
}
|
|
4086
4344
|
/**
|
|
@@ -4127,6 +4385,10 @@ export class WebGL2Renderer {
|
|
|
4127
4385
|
* reason: it already holds everything it needs.
|
|
4128
4386
|
*/
|
|
4129
4387
|
set.resolve(areaLights, this.resolvedAreaShadows);
|
|
4388
|
+
/* As the array stores them: 2L once glass has interleaved it. See `strideLayers`. */
|
|
4389
|
+
const stride = this.pointShadowArray?.stride ?? 1;
|
|
4390
|
+
strideLayers(this.resolvedAreaShadows.layer, stride);
|
|
4391
|
+
strideLayers(this.resolvedAreaShadows.liveLayer, stride);
|
|
4130
4392
|
}
|
|
4131
4393
|
/**
|
|
4132
4394
|
* Every point-shadow bake, under the one set of conventions all of them need.
|
|
@@ -4172,12 +4434,12 @@ export class WebGL2Renderer {
|
|
|
4172
4434
|
* map is stale on every frame it is checked — `pointShadowImage.ts` records that exact bug
|
|
4173
4435
|
* from two readers disagreeing about this argument's default.
|
|
4174
4436
|
*/
|
|
4175
|
-
return this.bakePointShadow(drawCasters, () => map.bake(gl, light.x, light.y, light.z, range, this.drawPointShadowFace, near, DEFAULT_SOURCE_RADIUS, maxFaces));
|
|
4437
|
+
return this.bakePointShadow(drawCasters, () => map.bake(gl, light.x, light.y, light.z, range, this.drawPointShadowFace, near, DEFAULT_SOURCE_RADIUS, maxFaces, this.pointGlass));
|
|
4176
4438
|
}
|
|
4177
4439
|
/** Spend up to `maxFaces` on this map, returning how many it used. */
|
|
4178
4440
|
bakeOne(map, light, drawCasters, maxFaces) {
|
|
4179
4441
|
const { gl } = this;
|
|
4180
|
-
return this.bakePointShadow(drawCasters, () => map.bake(gl, light.x, light.y, light.z, light.radius, this.drawPointShadowFace, light.shadowNear, light.sourceRadius, maxFaces));
|
|
4442
|
+
return this.bakePointShadow(drawCasters, () => map.bake(gl, light.x, light.y, light.z, light.radius, this.drawPointShadowFace, light.shadowNear, light.sourceRadius, maxFaces, this.pointGlass));
|
|
4181
4443
|
}
|
|
4182
4444
|
/**
|
|
4183
4445
|
* Draw into a rectangle of the canvas, on its own terms.
|
|
@@ -4388,6 +4650,10 @@ export class WebGL2Renderer {
|
|
|
4388
4650
|
*
|
|
4389
4651
|
* What it gives up is one frame of history: the frame after a cut has no blur and no temporal
|
|
4390
4652
|
* blend, as the first frame of a session has none. Nothing else is reset.
|
|
4653
|
+
*
|
|
4654
|
+
* **A reconstructing renderer depends on it**: its history is the last shot's, and without this
|
|
4655
|
+
* call a cut blends that shot into the first frames of the next. Nothing detects a cut on the
|
|
4656
|
+
* caller's behalf, deliberately — a fast pan and a cut look alike from here.
|
|
4391
4657
|
*/
|
|
4392
4658
|
cameraCut() {
|
|
4393
4659
|
this.hasPreviousView = false;
|
|
@@ -5103,8 +5369,9 @@ export class WebGL2Renderer {
|
|
|
5103
5369
|
* flat paint with the effect on. The opaque frame is finished here, which is what a pane
|
|
5104
5370
|
* should show; the latch then serves every pane in the replay. Only when something refracts.
|
|
5105
5371
|
*/
|
|
5106
|
-
if (this.translucentQueue.refracts)
|
|
5107
|
-
this.sceneTarget?.snapshotColor();
|
|
5372
|
+
if (this.translucentQueue.refracts) {
|
|
5373
|
+
this.sceneTarget?.snapshotColor(false, this.translucentQueue.glassy);
|
|
5374
|
+
}
|
|
5108
5375
|
this.oit.accumulateOit(this.canvas.width, this.canvas.height, oitDepth, (weighted) => {
|
|
5109
5376
|
this.oitReplaying = true;
|
|
5110
5377
|
this.useFlatProgram();
|
|
@@ -5121,6 +5388,7 @@ export class WebGL2Renderer {
|
|
|
5121
5388
|
options.refraction = draw.refraction;
|
|
5122
5389
|
options.refractTint = draw.refractTint;
|
|
5123
5390
|
options.thicknessM = draw.thicknessM;
|
|
5391
|
+
options.glass = draw.glass ?? undefined;
|
|
5124
5392
|
this.drawTranslucentMesh(draw.mesh, draw.model, draw.opacity, options);
|
|
5125
5393
|
});
|
|
5126
5394
|
/* Back to zero for every other draw in the frame, exactly as `uOpacity` and `uTint` are. */
|
|
@@ -5170,9 +5438,7 @@ export class WebGL2Renderer {
|
|
|
5170
5438
|
/* The placeholder rather than null where a map is merely absent, for the reason
|
|
5171
5439
|
`emptyTexture.ts` gives. It is black, which as a depth is an occluder at the light,
|
|
5172
5440
|
so it is safe only because `sunShadow` is 0 whenever these maps do not exist. */
|
|
5173
|
-
|
|
5174
|
-
peeledShadowMap: this.peeledShadowMap?.texture ?? this.emptyTexture2D,
|
|
5175
|
-
dynamicShadowMap: this.dynamicShadowMap?.texture ?? this.emptyTexture2D,
|
|
5441
|
+
sunShadows: this.sunShadows?.texture ?? this.emptyTexture2DArray,
|
|
5176
5442
|
peeledEnabled: this.peelFilled,
|
|
5177
5443
|
});
|
|
5178
5444
|
if (marched) {
|
|
@@ -5475,9 +5741,11 @@ export class WebGL2Renderer {
|
|
|
5475
5741
|
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
5476
5742
|
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
5477
5743
|
gl.uniform1i(u['uRefractScene'] ?? null, REFRACT_SCENE_TEXTURE_UNIT);
|
|
5478
|
-
|
|
5744
|
+
/* Neither refracting nor glass, which is every draw that does not say otherwise. See
|
|
5745
|
+
`bindSeeThrough` for the four numbers in the one vector. */
|
|
5746
|
+
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
5479
5747
|
gl.uniform3fv(u['uRefractTint'] ?? null, WHITE_TINT);
|
|
5480
|
-
gl.
|
|
5748
|
+
gl.uniform3fv(u['uGlassTint'] ?? null, WHITE_TINT);
|
|
5481
5749
|
gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
|
|
5482
5750
|
gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
|
|
5483
5751
|
gl.uniform3fv(u['uAmbient'] ?? null, env.ambient);
|
|
@@ -5573,7 +5841,7 @@ export class WebGL2Renderer {
|
|
|
5573
5841
|
* its own copy of this lookup; the two backends have to project the same way.
|
|
5574
5842
|
*/
|
|
5575
5843
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, env.lightViewProj);
|
|
5576
|
-
gl.uniform1f(u['uShadowStrength'] ?? null, this.
|
|
5844
|
+
gl.uniform1f(u['uShadowStrength'] ?? null, this.sunShadows === null ? 0 : env.shadowStrength);
|
|
5577
5845
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
5578
5846
|
gl.uniform1f(u['uShadowDepthSpan'] ?? null, env.shadowDepthSpan);
|
|
5579
5847
|
gl.uniform1f(u['uShadowMaxDistance'] ?? null, this.quality.directionalShadowMaxDistance);
|
|
@@ -5598,14 +5866,12 @@ export class WebGL2Renderer {
|
|
|
5598
5866
|
*/
|
|
5599
5867
|
if (this.quality.directionalShadows) {
|
|
5600
5868
|
gl.activeTexture(gl.TEXTURE0);
|
|
5601
|
-
gl.bindTexture(gl.
|
|
5602
|
-
gl.uniform1i(u['
|
|
5603
|
-
|
|
5604
|
-
gl.
|
|
5605
|
-
gl.
|
|
5606
|
-
gl.
|
|
5607
|
-
gl.bindTexture(gl.TEXTURE_2D, this.dynamicShadowMap?.texture ?? this.emptyTexture2D);
|
|
5608
|
-
gl.uniform1i(u['uDynamicShadowMap'] ?? null, 2);
|
|
5869
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.sunShadows?.texture ?? this.emptyTexture2DArray);
|
|
5870
|
+
gl.uniform1i(u['uSunShadows'] ?? null, 0);
|
|
5871
|
+
/* What the sun keeps through glass, or the one-texel stand-in the lookup reads as none. */
|
|
5872
|
+
gl.activeTexture(gl.TEXTURE0 + SUN_GLASS_TINT_TEXTURE_UNIT);
|
|
5873
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.sunGlassTint?.texture ?? this.emptyTexture2DArray);
|
|
5874
|
+
gl.uniform1i(u['uSunGlassTints'] ?? null, SUN_GLASS_TINT_TEXTURE_UNIT);
|
|
5609
5875
|
}
|
|
5610
5876
|
if (this.pointShadows === null) {
|
|
5611
5877
|
/*
|
|
@@ -5618,6 +5884,10 @@ export class WebGL2Renderer {
|
|
|
5618
5884
|
else {
|
|
5619
5885
|
/* Chosen in `pointShadowSystem.ts` and bound here; see `resolve` for the split. */
|
|
5620
5886
|
this.pointShadows.resolve(this.resolvedPointShadows);
|
|
5887
|
+
/* As the array stores them: 2L once glass has interleaved it. See `strideLayers`. */
|
|
5888
|
+
const stride = this.pointShadowArray?.stride ?? 1;
|
|
5889
|
+
strideLayers(this.resolvedPointShadows.layers, stride);
|
|
5890
|
+
strideLayers(this.resolvedPointShadows.liveLayers, stride);
|
|
5621
5891
|
this.bindPointShadows(u, DIRECTIONAL_SHADOW_UNITS);
|
|
5622
5892
|
/*
|
|
5623
5893
|
* The rectangles' layers, into the array `bindPointShadows` just put on its unit. Resolved
|
|
@@ -5823,11 +6093,12 @@ export class WebGL2Renderer {
|
|
|
5823
6093
|
*/
|
|
5824
6094
|
tint = null,
|
|
5825
6095
|
/**
|
|
5826
|
-
* Where the mesh was last frame
|
|
6096
|
+
* Where the mesh was last frame — a `Mover` or a matrix — which this backend takes and ignores.
|
|
5827
6097
|
*
|
|
5828
6098
|
* Accepted rather than absent so that one scene draws through both renderers unchanged — the
|
|
5829
6099
|
* parity rule this repository is built on. Reconstruction is WebGPU's, having no compute stage
|
|
5830
|
-
* here, so there is no motion target for this to be written into
|
|
6100
|
+
* here, so there is no motion target for this to be written into, and a mover passed here is
|
|
6101
|
+
* neither read nor advanced.
|
|
5831
6102
|
*/
|
|
5832
6103
|
_previousModel = null) {
|
|
5833
6104
|
if (this.contextLost)
|
|
@@ -6081,19 +6352,7 @@ export class WebGL2Renderer {
|
|
|
6081
6352
|
* translucent one rather than sampling a black texture and painting the pane the colour of a
|
|
6082
6353
|
* hole. That is the defined state the two-backends rule asks for instead of a silent no-op.
|
|
6083
6354
|
*/
|
|
6084
|
-
const
|
|
6085
|
-
let refracting = false;
|
|
6086
|
-
if (refraction > 0) {
|
|
6087
|
-
const snapshot = this.sceneTarget?.snapshotColor() ?? null;
|
|
6088
|
-
if (snapshot !== null) {
|
|
6089
|
-
refracting = true;
|
|
6090
|
-
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
6091
|
-
gl.bindTexture(gl.TEXTURE_2D, snapshot);
|
|
6092
|
-
gl.uniform1f(u['uRefractStrength'] ?? null, refraction);
|
|
6093
|
-
gl.uniform3fv(u['uRefractTint'] ?? null, options.refractTint ?? WHITE_TINT);
|
|
6094
|
-
gl.uniform1f(u['uRefractThickness'] ?? null, options.thicknessM ?? 0);
|
|
6095
|
-
}
|
|
6096
|
-
}
|
|
6355
|
+
const refracting = this.bindSeeThrough(u, options);
|
|
6097
6356
|
/*
|
|
6098
6357
|
* **The blend and the depth mask belong to whoever owns the pass.** In the ordinary case that
|
|
6099
6358
|
* is this draw; during an order-independent replay it is `OitPass`, which has set an additive
|
|
@@ -6143,6 +6402,8 @@ export class WebGL2Renderer {
|
|
|
6143
6402
|
gl.uniform1f(u['uOpacity'] ?? null, 1);
|
|
6144
6403
|
if (tint !== null)
|
|
6145
6404
|
gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
6405
|
+
if (refracting)
|
|
6406
|
+
this.unbindSeeThrough(u);
|
|
6146
6407
|
/* Put back for whatever is drawn next, exactly as `uOpacity` and `uTint` are — a pass
|
|
6147
6408
|
cannot inherit a material from the draw before it. */
|
|
6148
6409
|
if (!lit)
|
|
@@ -6479,14 +6740,8 @@ export class WebGL2Renderer {
|
|
|
6479
6740
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
6480
6741
|
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
6481
6742
|
gl.activeTexture(gl.TEXTURE0);
|
|
6482
|
-
gl.bindTexture(gl.
|
|
6483
|
-
gl.uniform1i(u['
|
|
6484
|
-
gl.activeTexture(gl.TEXTURE1);
|
|
6485
|
-
gl.bindTexture(gl.TEXTURE_2D, this.peeledShadowMap?.texture ?? this.emptyTexture2D);
|
|
6486
|
-
gl.uniform1i(u['uPeeledShadowMap'] ?? null, 1);
|
|
6487
|
-
gl.activeTexture(gl.TEXTURE2);
|
|
6488
|
-
gl.bindTexture(gl.TEXTURE_2D, this.dynamicShadowMap?.texture ?? this.emptyTexture2D);
|
|
6489
|
-
gl.uniform1i(u['uDynamicShadowMap'] ?? null, 2);
|
|
6743
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.sunShadows?.texture ?? this.emptyTexture2DArray);
|
|
6744
|
+
gl.uniform1i(u['uSunShadows'] ?? null, 0);
|
|
6490
6745
|
}
|
|
6491
6746
|
}
|
|
6492
6747
|
gl.uniform3fv(u['uCameraLocal'] ?? null, cameraLocal);
|