@driftengine/core 4.4.2 → 4.6.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 +97 -0
- package/README.md +1 -1
- package/dist/geometry/boxCsg.d.ts +28 -0
- package/dist/geometry/boxCsg.js +139 -0
- package/dist/geometry/csg.d.ts +36 -0
- package/dist/geometry/csg.js +329 -0
- package/dist/geometry/meshBuilder.js +13 -0
- package/dist/geometry/meshConcat.js +1 -0
- package/dist/geometry/pixelFont.js +2 -0
- package/dist/geometry/solid.d.ts +61 -0
- package/dist/geometry/solid.js +152 -0
- package/dist/geometry/solidFlat.d.ts +35 -0
- package/dist/geometry/solidFlat.js +428 -0
- package/dist/geometry/solidHarness.d.ts +19 -0
- package/dist/geometry/solidHarness.js +66 -0
- package/dist/geometry/solidNormals.d.ts +17 -0
- package/dist/geometry/solidNormals.js +86 -0
- package/dist/geometry/solidRound.d.ts +41 -0
- package/dist/geometry/solidRound.js +323 -0
- package/dist/geometry/solidWriter.d.ts +16 -0
- package/dist/geometry/solidWriter.js +42 -0
- package/dist/index.d.ts +28 -3
- package/dist/index.js +20 -1
- package/dist/render/backend/api.d.ts +34 -14
- package/dist/render/backend/drawFog.d.ts +26 -0
- package/dist/render/backend/drawFog.js +30 -0
- package/dist/render/backend/materialChanges.d.ts +2 -1
- package/dist/render/backend/materialChanges.js +6 -1
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/instanced.d.ts +24 -5
- package/dist/render/backend/webgl2/instanced.js +72 -15
- package/dist/render/backend/webgl2/renderer.d.ts +201 -16
- package/dist/render/backend/webgl2/renderer.js +596 -224
- package/dist/render/backend/webgpu/buffers.d.ts +29 -1
- package/dist/render/backend/webgpu/buffers.js +61 -4
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +7 -2
- package/dist/render/backend/webgpu/drawCommand.d.ts +5 -0
- package/dist/render/backend/webgpu/drawCommand.js +1 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +6 -1
- package/dist/render/backend/webgpu/flatPass.js +57 -9
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +206 -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/instanceCullPass.d.ts +34 -0
- package/dist/render/backend/webgpu/instanceCullPass.js +144 -0
- package/dist/render/backend/webgpu/instanced.d.ts +23 -2
- package/dist/render/backend/webgpu/instanced.js +41 -4
- package/dist/render/backend/webgpu/latePass.d.ts +30 -0
- package/dist/render/backend/webgpu/latePass.js +140 -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 +3 -13
- package/dist/render/backend/webgpu/renderer.d.ts +271 -64
- package/dist/render/backend/webgpu/renderer.js +1395 -580
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/sdfTextPass.js +6 -1
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.d.ts +18 -0
- package/dist/render/backend/webgpu/shaders/instanceCull.wgsl.js +59 -0
- 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 +22 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +78 -39
- package/dist/render/driftLight/bake.d.ts +7 -0
- package/dist/render/driftLight/bake.js +61 -56
- package/dist/render/driftLight/denseField.d.ts +46 -0
- package/dist/render/driftLight/denseField.js +109 -0
- package/dist/render/driftLight/lightField.d.ts +10 -5
- package/dist/render/driftLight/lightField.js +25 -19
- package/dist/render/driftLight/presence.d.ts +50 -0
- package/dist/render/driftLight/presence.js +43 -0
- package/dist/render/driftLight/uniforms.d.ts +3 -3
- package/dist/render/driftLight/uniforms.js +5 -6
- package/dist/render/driftLight/worldLightField.d.ts +41 -0
- package/dist/render/driftLight/worldLightField.js +66 -0
- package/dist/render/fixtureAtlas.d.ts +20 -0
- package/dist/render/fixtureAtlas.js +49 -0
- 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/instanceCull.d.ts +63 -0
- package/dist/render/instanceCull.js +160 -0
- package/dist/render/instances.d.ts +15 -0
- package/dist/render/lightBudget.d.ts +36 -35
- package/dist/render/lightBudget.js +38 -37
- package/dist/render/lightGrid.d.ts +42 -0
- package/dist/render/lightGrid.js +87 -0
- package/dist/render/mesh.d.ts +18 -24
- package/dist/render/mesh.js +51 -68
- package/dist/render/occlusion.d.ts +10 -0
- package/dist/render/occlusion.js +36 -7
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointLightSelection.d.ts +7 -0
- package/dist/render/pointLightSelection.js +29 -4
- 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/rainField.d.ts +13 -0
- package/dist/render/rainField.js +27 -5
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/lateDepth.d.ts +11 -0
- package/dist/render/recon/lateDepth.js +70 -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/recon/resolve.d.ts +34 -4
- package/dist/render/recon/resolve.js +161 -20
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +16 -0
- package/dist/render/sceneTarget.d.ts +9 -1
- package/dist/render/sceneTarget.js +60 -12
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +94 -15
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/driftLight.d.ts +1 -1
- package/dist/render/shaders/flat/driftLight.js +32 -7
- 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 +9 -3
- 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 +190 -38
- 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 +96 -26
- package/dist/render/shaders/flat/surfaceEffects.d.ts +29 -0
- package/dist/render/shaders/flat/surfaceEffects.js +152 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +230 -7
- package/dist/render/shaders/generated/depth.wgsl.js +230 -7
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1644 -1596
- package/dist/render/shaders/generated/flat.wgsl.js +1683 -1621
- 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 +21 -17
- 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/generated/rush.wgsl.d.ts +21 -16
- package/dist/render/shaders/generated/rush.wgsl.js +21 -16
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +2 -2
- package/dist/render/shaders/generated/sdfText.wgsl.js +2 -2
- package/dist/render/shaders/generated/text.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/text.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.js +27 -4
- 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 +69 -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/shaders/recon/resolve.wgsl.d.ts +8 -7
- package/dist/render/shaders/recon/resolve.wgsl.js +161 -27
- package/dist/render/shaders/rush.js +11 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/sdfText.js +3 -2
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +18 -9
- 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/surfaceEffects.d.ts +83 -0
- package/dist/render/surfaceEffects.js +75 -0
- package/dist/render/surfaceScene.d.ts +8 -0
- package/dist/render/surfaceScene.js +8 -0
- package/dist/render/surfaceTexture.d.ts +18 -2
- package/dist/render/surfaceTexture.js +68 -15
- package/dist/render/temporalAa.d.ts +25 -6
- package/dist/render/temporalAa.js +45 -15
- package/dist/render/textureSource.d.ts +30 -0
- package/dist/render/textureSource.js +53 -0
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/dist/render/vertexDefaults.js +2 -0
- package/dist/world/hlod.d.ts +104 -0
- package/dist/world/hlod.js +214 -0
- package/package.json +6 -6
- package/src/geometry/boxCsg.ts +186 -0
- package/src/geometry/csg.ts +384 -0
- package/src/geometry/meshBuilder.ts +15 -0
- package/src/geometry/meshConcat.ts +1 -0
- package/src/geometry/pixelFont.ts +2 -0
- package/src/geometry/solid.ts +189 -0
- package/src/geometry/solidFlat.ts +522 -0
- package/src/geometry/solidHarness.ts +83 -0
- package/src/geometry/solidNormals.ts +103 -0
- package/src/geometry/solidRound.ts +443 -0
- package/src/geometry/solidWriter.ts +58 -0
- package/src/index.ts +61 -1
- package/src/render/backend/api.ts +36 -11
- package/src/render/backend/drawFog.ts +31 -0
- package/src/render/backend/materialChanges.ts +9 -2
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/instanced.ts +80 -14
- package/src/render/backend/webgl2/renderer.ts +700 -250
- package/src/render/backend/webgpu/buffers.ts +92 -5
- package/src/render/backend/webgpu/depthPass.ts +7 -2
- package/src/render/backend/webgpu/drawCommand.ts +6 -0
- package/src/render/backend/webgpu/flatPass.ts +62 -11
- package/src/render/backend/webgpu/glassTintPass.ts +245 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanceCullPass.ts +165 -0
- package/src/render/backend/webgpu/instanced.ts +42 -4
- package/src/render/backend/webgpu/latePass.ts +161 -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 +1671 -670
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/sdfTextPass.ts +6 -1
- package/src/render/backend/webgpu/shaders/instanceCull.wgsl.ts +61 -0
- 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 +91 -51
- package/src/render/driftLight/bake.ts +85 -62
- package/src/render/driftLight/denseField.ts +149 -0
- package/src/render/driftLight/lightField.ts +33 -18
- package/src/render/driftLight/presence.ts +69 -0
- package/src/render/driftLight/uniforms.ts +8 -9
- package/src/render/driftLight/worldLightField.ts +83 -0
- package/src/render/fixtureAtlas.ts +63 -0
- 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/instanceCull.ts +205 -0
- package/src/render/instances.ts +16 -0
- package/src/render/lightBudget.ts +41 -38
- package/src/render/lightGrid.ts +130 -0
- package/src/render/mesh.ts +59 -72
- package/src/render/occlusion.ts +51 -7
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointLightSelection.ts +72 -4
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/rainField.ts +39 -5
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/lateDepth.ts +83 -0
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/recon/resolve.ts +206 -20
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +16 -0
- package/src/render/sceneTarget.ts +62 -11
- package/src/render/shaders/depth.ts +118 -15
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/driftLight.ts +32 -7
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +22 -3
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +190 -38
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +96 -26
- package/src/render/shaders/flat/surfaceEffects.ts +152 -0
- package/src/render/shaders/generated/depth.wgsl.ts +237 -7
- package/src/render/shaders/generated/flat.wgsl.ts +1685 -1621
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +23 -17
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/rush.wgsl.ts +21 -16
- package/src/render/shaders/generated/sdfText.wgsl.ts +2 -2
- package/src/render/shaders/generated/text.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +27 -4
- 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 +72 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +161 -27
- package/src/render/shaders/rush.ts +11 -1
- package/src/render/shaders/sdfText.ts +3 -2
- package/src/render/shaders/text.ts +18 -9
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/surfaceEffects.ts +132 -0
- package/src/render/surfaceScene.ts +16 -0
- package/src/render/surfaceTexture.ts +110 -19
- package/src/render/temporalAa.ts +78 -14
- package/src/render/textureSource.ts +72 -0
- package/src/render/translucentQueue.ts +20 -1
- package/src/render/vertexDefaults.ts +2 -0
- package/src/world/hlod.ts +290 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { FrameBudget } from '../budget.js';
|
|
2
2
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.js';
|
|
3
|
+
import { FOG_RECEDE, fogModeOf } from '../drawFog.js';
|
|
3
4
|
import { mat4 } from 'gl-matrix';
|
|
4
5
|
import { GL_DEPTH_REMAP, MAX_DEPTH_LAYER, depthClearFor, depthOffsetForLayer, GL_SHADOW_REMAP, REVERSED_DEPTH, SHADOW_DEPTH_CLEAR, glDepthFuncEqual, glShadowDepthFunc, } from '../../depthConvention.js';
|
|
6
|
+
import { resolveGlass } from '../../glass.js';
|
|
5
7
|
import { PickableSet } from '../../pickable.js';
|
|
6
8
|
import { TEMPORAL_HISTORY_BLEND, TemporalHistory, jitterOffset, jitterProjection, } from '../../temporalAa.js';
|
|
7
9
|
import { DecalPass } from '../../decalPass.js';
|
|
@@ -15,6 +17,7 @@ import { OIT_MULTISAMPLE_REFUSAL } from '../../orderIndependent.js';
|
|
|
15
17
|
import { INDIRECT_LIGHT_WEBGL2_REFUSAL } from '../../gi/probeTrace.js';
|
|
16
18
|
import { TranslucentQueue } from '../../translucentQueue.js';
|
|
17
19
|
import { createFrustum, frustumFromViewProjection } from '../../../math/frustum.js';
|
|
20
|
+
import { batchBoxVisible, cullsInstances } from '../../instanceCull.js';
|
|
18
21
|
import { OcclusionBuffer } from '../../occlusion.js';
|
|
19
22
|
import { boundsVisible } from '../../visibility.js';
|
|
20
23
|
import { createPassRegistry, drainRegistry, passAt, registerIn, unregisterIn } from '../../pass.js';
|
|
@@ -31,6 +34,7 @@ import { FILM_LOOK_WITHOUT_COMPOSITE } from '../../shaders/filmLook.js';
|
|
|
31
34
|
import { bloomProfileWarning, bloomThresholdOf } from '../../bloomChain.js';
|
|
32
35
|
import { createEmptyTexture2D, createEmptyTexture2DArray, createEmptyTexture3D, createEmptyTextureCube, } from '../../emptyTexture.js';
|
|
33
36
|
import { LightField } from '../../driftLight/lightField.js';
|
|
37
|
+
import { WorldLightField } from '../../driftLight/worldLightField.js';
|
|
34
38
|
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.js';
|
|
35
39
|
import { flatFrag, flatVert } from '../../shaders/flat/index.js';
|
|
36
40
|
import { MORPH_DELTA_TEXTURE_UNIT, SKIN_PALETTE_TEXTURE_UNIT } from '../../lightBudget.js';
|
|
@@ -46,18 +50,24 @@ import { ProbeSweeps } from '../../probeSweeps.js';
|
|
|
46
50
|
import { octahedralEdgeFor } from '../../prefilterEnvMap.js';
|
|
47
51
|
import { EnvironmentPrefilterPass } from '../../prefilterPass.js';
|
|
48
52
|
import { packIesAtlas } from '../../iesProfile.js';
|
|
53
|
+
import { fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.js';
|
|
54
|
+
import { resolveSurfaceScene } from '../../surfaceScene.js';
|
|
49
55
|
import { MAX_AREA_LIGHTS } from '../../areaLights.js';
|
|
50
56
|
import { areaShadowLayerCount, castingRange, createResolvedAreaShadows, firstAreaShadowLayer, AreaShadowSet, } from '../../areaShadowSet.js';
|
|
51
57
|
import { equirectToCubeFaces } from '../../equirectToCube.js';
|
|
52
58
|
import { SKY_FRAG, skyVertFor } from '../../shaders/sky.js';
|
|
53
59
|
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 {
|
|
60
|
+
import { OCTAHEDRAL_EDGE, PointShadowArray, strideLayers } from '../../pointShadowArray.js';
|
|
61
|
+
import { SunShadowArray } from '../../shadowMap.js';
|
|
56
62
|
import { POINT_SHADOW_SAMPLER, PointShadowSystem } from '../../pointShadowSystem.js';
|
|
63
|
+
import { castsDepth, glassOf } from '../../shadowCasters.js';
|
|
64
|
+
import { GlassCasterList } from '../../glassCasters.js';
|
|
65
|
+
import { SunGlassTint } from '../../sunGlassTint.js';
|
|
66
|
+
import { GlassTintPrograms } from './glassTintPrograms.js';
|
|
57
67
|
import { FACE_COUNT, PointShadowMap } from '../../pointShadowMap.js';
|
|
58
68
|
import { createResolvedPointShadows, DEFAULT_SOURCE_RADIUS } from '../../pointShadowImage.js';
|
|
59
69
|
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,
|
|
70
|
+
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, REFRACT_SCENE_TEXTURE_UNIT, DRIFT_LIGHT_ATLAS_TEXTURE_UNIT, DRIFT_LIGHT_INDEX_TEXTURE_UNIT, COOKIE_TILE, FIXTURE_ATLAS_TEXTURE_UNIT, SURFACE_EFFECTS_TEXTURE_UNIT, bindAreaLights, bindAreaShadows, openCones, } from '../../lightBudget.js';
|
|
61
71
|
import { resolveRenderQuality } from '../../renderQuality.js';
|
|
62
72
|
import { drawingBufferSize } from '../../drawingBuffer.js';
|
|
63
73
|
import { Mesh, createMeshIncremental } from '../../mesh.js';
|
|
@@ -231,6 +241,8 @@ const IDENTITY_MAT4 = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
|
231
241
|
export { MAX_DEPTH_LAYER } from '../../depthConvention.js';
|
|
232
242
|
/** The identity tint, handed back after any draw that asked for one. See `drawMesh`. */
|
|
233
243
|
const WHITE_TINT = new Float32Array([1, 1, 1]);
|
|
244
|
+
/** The lit stage's clip plane when there is no mirror: zero, which clips nothing. */
|
|
245
|
+
const NO_CLIP_PLANE = new Float32Array(4);
|
|
234
246
|
/** Handed to the shader when a batch has no press field. See `drawScatter`. */
|
|
235
247
|
/**
|
|
236
248
|
* What a contributed pass pre-multiplies its projection by on this backend: nothing.
|
|
@@ -488,6 +500,8 @@ export class WebGL2Renderer {
|
|
|
488
500
|
/** Whether a palette is currently chosen. Distinct from whether one was ever uploaded. */
|
|
489
501
|
skinPaletteSet = false;
|
|
490
502
|
emptyTexture2D;
|
|
503
|
+
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
504
|
+
glassScratch = { transmission: 0, frost: 0, tint: [1, 1, 1] };
|
|
491
505
|
/** DriftLight's stand-ins while no field is whole: no brick anywhere, and no light. */
|
|
492
506
|
emptyDriftIndex;
|
|
493
507
|
emptyDriftAtlas;
|
|
@@ -539,6 +553,11 @@ export class WebGL2Renderer {
|
|
|
539
553
|
oitActive = false;
|
|
540
554
|
/** True while the queue is being replayed, so a replayed draw does not record itself again. */
|
|
541
555
|
oitReplaying = false;
|
|
556
|
+
/** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
|
|
557
|
+
surfaceScene = new Float32Array(4);
|
|
558
|
+
/** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
|
|
559
|
+
oitWeight = 0;
|
|
560
|
+
ditherFade = 0;
|
|
542
561
|
/** Said once, not per frame, where the context cannot give the float target the sum needs. */
|
|
543
562
|
oitRefused = false;
|
|
544
563
|
/** Said once for the whole renderer. See `refuseOitMultisampled`. */
|
|
@@ -560,6 +579,7 @@ export class WebGL2Renderer {
|
|
|
560
579
|
refraction: 0,
|
|
561
580
|
refractTint: undefined,
|
|
562
581
|
thicknessM: 0,
|
|
582
|
+
glass: undefined,
|
|
563
583
|
};
|
|
564
584
|
motionBlurScale = 1;
|
|
565
585
|
/** How much of the bloom ceiling this frame takes. See `setBloom`. */
|
|
@@ -631,6 +651,8 @@ export class WebGL2Renderer {
|
|
|
631
651
|
taaDepthToViewZ = Float32Array.of(0, 0, 0, 1);
|
|
632
652
|
aoInvProjection = mat4.create();
|
|
633
653
|
aoProjScale = new Float32Array(2);
|
|
654
|
+
/** Where this frame's jitter put the occlusion, in the composite's uv. See `uAoOffset`. */
|
|
655
|
+
aoOffset = new Float32Array(2);
|
|
634
656
|
/**
|
|
635
657
|
* Whether this frame has already been resolved to the canvas.
|
|
636
658
|
*
|
|
@@ -687,9 +709,8 @@ export class WebGL2Renderer {
|
|
|
687
709
|
depthInstancedCutoutUniforms;
|
|
688
710
|
scatterDepthProgram;
|
|
689
711
|
scatterDepthUniforms;
|
|
690
|
-
shadowMap
|
|
691
|
-
|
|
692
|
-
dynamicShadowMap;
|
|
712
|
+
/** The sun's static, moving and peeled maps, as layers of one array. See `shadowMap.ts`. */
|
|
713
|
+
sunShadows;
|
|
693
714
|
pointShadows;
|
|
694
715
|
/**
|
|
695
716
|
* The layers a world's casting rectangles hold, above the point pool in the same array.
|
|
@@ -723,12 +744,15 @@ export class WebGL2Renderer {
|
|
|
723
744
|
* world. See `prepareStaticPointShadows`.
|
|
724
745
|
*/
|
|
725
746
|
pointShadowArray = null;
|
|
726
|
-
/** The
|
|
727
|
-
|
|
728
|
-
/**
|
|
747
|
+
/** The fixture atlas — cookies over profiles — allocated on first use. See `fixtureAtlas.ts`. */
|
|
748
|
+
fixtureTexture = null;
|
|
749
|
+
/** The profiles as packed, kept so a new set of cookies can rebuild the atlas around them. */
|
|
750
|
+
iesAtlas = packIesAtlas([]);
|
|
751
|
+
/** The cookie images, kept so a new set of profiles can rebuild the atlas around them. */
|
|
752
|
+
cookieImages = [];
|
|
753
|
+
/** Rows of profile in the atlas. One until a consumer loads a profile, which is the row of ones. */
|
|
729
754
|
iesRows = 1;
|
|
730
|
-
|
|
731
|
-
/** Tiles in the cookie atlas. 0 is the white placeholder and the whole off path. */
|
|
755
|
+
/** Cookie tiles in the atlas. 0 is none, and the whole off path. */
|
|
732
756
|
cookieTiles = 0;
|
|
733
757
|
/** Horizontal planes each profile occupies. 1 unless something asymmetric was loaded. */
|
|
734
758
|
iesPlanes = 1;
|
|
@@ -895,7 +919,7 @@ export class WebGL2Renderer {
|
|
|
895
919
|
/** Non-zero while the drawing buffer is pinned; see `lockDrawingBuffer`. */
|
|
896
920
|
lockedWidth = 0;
|
|
897
921
|
lockedHeight = 0;
|
|
898
|
-
|
|
922
|
+
activeSunLayer = null;
|
|
899
923
|
reflectionPassActive = false;
|
|
900
924
|
reflectionReadyThisFrame = false;
|
|
901
925
|
reflectionAtmosphereY = 0;
|
|
@@ -917,7 +941,7 @@ export class WebGL2Renderer {
|
|
|
917
941
|
*
|
|
918
942
|
* **Not whether the map exists.** That was the test here, on the belief that a GL texture is
|
|
919
943
|
* 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. `
|
|
944
|
+
* peeled it sampled an occluder at the light. `SunShadowArray` clears itself at birth, so an
|
|
921
945
|
* unfilled layer would read as lit either way — this is what keeps the two backends one
|
|
922
946
|
* decision, and a frame that does not peel from paying a fetch per tap for nothing.
|
|
923
947
|
*/
|
|
@@ -943,8 +967,34 @@ export class WebGL2Renderer {
|
|
|
943
967
|
* Built once at construction, arrow properties closing over `this`: a sink created
|
|
944
968
|
* per pass would allocate six objects per light per frame.
|
|
945
969
|
*/
|
|
970
|
+
/** What the sun's light keeps through glass, made the first time glass casts: `sunGlassTint.ts`. */
|
|
971
|
+
sunGlassTint = null;
|
|
972
|
+
/** The five programs a pane casts its colour through, compiled the first time glass casts. */
|
|
973
|
+
glassTintPrograms = null;
|
|
974
|
+
/** True while the glass list is replayed into the glass depth layer through `casterSink`. */
|
|
975
|
+
drawingGlassDepth = false;
|
|
976
|
+
/** How many times a shadow array grew its glass layers: once per family, and never again. */
|
|
977
|
+
sunGlassRebuilds = 0;
|
|
978
|
+
/** The light a tint pass is drawn for, and one pane's colour and clarity: uploaded per entry. */
|
|
979
|
+
glassLight = new Float32Array(4);
|
|
980
|
+
glassPane = new Float32Array(4);
|
|
981
|
+
/** The glass the open shadow pass was offered, kept for its own passes: `glassCasters.ts`. */
|
|
982
|
+
glassCasters = new GlassCasterList();
|
|
983
|
+
/** One resolution every glass caster is read through, so recording one allocates nothing. */
|
|
984
|
+
glassCasterScratch = {
|
|
985
|
+
transmission: 0,
|
|
986
|
+
frost: 0,
|
|
987
|
+
tint: [1, 1, 1],
|
|
988
|
+
};
|
|
946
989
|
casterSink = {
|
|
947
990
|
mesh: (mesh, model, material) => {
|
|
991
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
992
|
+
/* Glass: kept aside for its own two passes rather than dropped. See `glassCasters.ts`. */
|
|
993
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
994
|
+
this.glassCasters.recordMesh(mesh, model, material, this.glassCasterScratch);
|
|
995
|
+
}
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
948
998
|
this.shadowDrawBudget.ask();
|
|
949
999
|
const { gl } = this;
|
|
950
1000
|
/* A two-sided caster casts from whichever face the light sees: nothing culled. */
|
|
@@ -984,6 +1034,12 @@ export class WebGL2Renderer {
|
|
|
984
1034
|
* columns the batch's own buffer supplies.
|
|
985
1035
|
*/
|
|
986
1036
|
instanced: (batch, data, material) => {
|
|
1037
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1038
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1039
|
+
this.glassCasters.recordInstanced(batch, data, material, this.glassCasterScratch);
|
|
1040
|
+
}
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
987
1043
|
const { gl } = this;
|
|
988
1044
|
const gpuBatch = batch;
|
|
989
1045
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
@@ -997,7 +1053,7 @@ export class WebGL2Renderer {
|
|
|
997
1053
|
if (cutout !== null) {
|
|
998
1054
|
gl.useProgram(this.depthInstancedCutoutProgram);
|
|
999
1055
|
this.bindCutoutDepth(this.depthInstancedCutoutUniforms, cutout);
|
|
1000
|
-
gpuBatch.
|
|
1056
|
+
gpuBatch.draw(gl, count);
|
|
1001
1057
|
if (twoSided && this.depthPassCullsFaces)
|
|
1002
1058
|
gl.enable(gl.CULL_FACE);
|
|
1003
1059
|
gl.useProgram(this.depthProgram);
|
|
@@ -1010,12 +1066,18 @@ export class WebGL2Renderer {
|
|
|
1010
1066
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1011
1067
|
if (this.activeDepthPeel)
|
|
1012
1068
|
gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1013
|
-
gpuBatch.
|
|
1069
|
+
gpuBatch.draw(gl, count);
|
|
1014
1070
|
if (twoSided && this.depthPassCullsFaces)
|
|
1015
1071
|
gl.enable(gl.CULL_FACE);
|
|
1016
1072
|
gl.useProgram(this.depthProgram);
|
|
1017
1073
|
},
|
|
1018
|
-
skinnedMesh: (mesh, model, palette) => {
|
|
1074
|
+
skinnedMesh: (mesh, model, palette, material) => {
|
|
1075
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1076
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1077
|
+
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
1078
|
+
}
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1019
1081
|
this.shadowDrawBudget.ask();
|
|
1020
1082
|
const { gl } = this;
|
|
1021
1083
|
const u = this.depthSkinnedUniforms;
|
|
@@ -1098,8 +1160,52 @@ export class WebGL2Renderer {
|
|
|
1098
1160
|
gl.useProgram(this.depthProgram);
|
|
1099
1161
|
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, 0);
|
|
1100
1162
|
gl.uniformMatrix4fv(this.depthUniforms['uLightViewProj'] ?? null, false, faceViewProj);
|
|
1163
|
+
/* A face is an enumeration of its own: its glass is its own. */
|
|
1164
|
+
this.glassCasters.clear();
|
|
1101
1165
|
casters(this.casterSink);
|
|
1166
|
+
/*
|
|
1167
|
+
* The first face to offer glass interleaves the array, before its opaque depth is resolved:
|
|
1168
|
+
* `PointShadowArray.growForGlass`. Nothing baked before this saw glass, so nothing re-bakes.
|
|
1169
|
+
*/
|
|
1170
|
+
if (this.glassCasters.count > 0 && this.quality.glassShadows !== 'off') {
|
|
1171
|
+
const tintEdge = this.quality.glassShadows === 'half' ? OCTAHEDRAL_EDGE >> 1 : OCTAHEDRAL_EDGE;
|
|
1172
|
+
this.pointShadowArray?.growForGlass(gl, tintEdge);
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
/**
|
|
1176
|
+
* A lamp's glass, after each face's opaque depth: `PointShadowMap.bake` asks and binds, this
|
|
1177
|
+
* draws, through the same sinks the sun's glass takes and in the face's own matrix.
|
|
1178
|
+
*/
|
|
1179
|
+
pointGlassDraw = {
|
|
1180
|
+
offered: () => this.glassCasters.count > 0,
|
|
1181
|
+
draw: (pass, x, y, z) => {
|
|
1182
|
+
const { gl } = this;
|
|
1183
|
+
if (pass === 'depth') {
|
|
1184
|
+
/* Nothing on the unit the depth programs declare a previous map on: see `flushSunGlass`. */
|
|
1185
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1186
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
1187
|
+
this.activeDepthPeel = false;
|
|
1188
|
+
gl.useProgram(this.depthProgram);
|
|
1189
|
+
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, 0);
|
|
1190
|
+
gl.uniformMatrix4fv(this.depthUniforms['uLightViewProj'] ?? null, false, this.activeDepthViewProj ?? IDENTITY_MAT4);
|
|
1191
|
+
this.drawingGlassDepth = true;
|
|
1192
|
+
this.glassCasters.replay(this.glassDepthSink);
|
|
1193
|
+
this.drawingGlassDepth = false;
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
this.glassTintPrograms ??= new GlassTintPrograms(gl);
|
|
1197
|
+
/* A place, w 1: the tint takes each pane's angle to the lamp from its own position. */
|
|
1198
|
+
this.glassLight[0] = x;
|
|
1199
|
+
this.glassLight[1] = y;
|
|
1200
|
+
this.glassLight[2] = z;
|
|
1201
|
+
this.glassLight[3] = 1;
|
|
1202
|
+
this.glassCasters.replay(this.glassTintSink);
|
|
1203
|
+
},
|
|
1102
1204
|
};
|
|
1205
|
+
/** The glass hook a bake is handed: none at all when glass shadows are off. */
|
|
1206
|
+
get pointGlass() {
|
|
1207
|
+
return this.quality.glassShadows === 'off' ? null : this.pointGlassDraw;
|
|
1208
|
+
}
|
|
1103
1209
|
/**
|
|
1104
1210
|
* Viewport in CSS pixels rather than device pixels.
|
|
1105
1211
|
*
|
|
@@ -1216,6 +1322,13 @@ export class WebGL2Renderer {
|
|
|
1216
1322
|
occluded(bounds, model) {
|
|
1217
1323
|
return this.occlusion?.occluded(bounds, model) ?? false;
|
|
1218
1324
|
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Whether a world-space box is wholly behind the declared occluders — the tighter question for
|
|
1327
|
+
* something that is a box, such as a region. False with no occlusion buffer. See `occluded`.
|
|
1328
|
+
*/
|
|
1329
|
+
occludedBox(min, max) {
|
|
1330
|
+
return this.occlusion?.occludedBox(min, max) ?? false;
|
|
1331
|
+
}
|
|
1219
1332
|
/* -- Registered passes ---------------------------------------------------------------- */
|
|
1220
1333
|
passes = createPassRegistry();
|
|
1221
1334
|
/** Built once so invoking somebody else's pass allocates nothing. See `PassDefinition.draw`. */
|
|
@@ -1389,80 +1502,57 @@ export class WebGL2Renderer {
|
|
|
1389
1502
|
* `createSurfaceTexture` and the material maps take.
|
|
1390
1503
|
*/
|
|
1391
1504
|
setSpotCookies(images) {
|
|
1392
|
-
|
|
1393
|
-
this.
|
|
1394
|
-
if (this.cookieTexture === null)
|
|
1395
|
-
this.cookieTexture = gl.createTexture();
|
|
1396
|
-
gl.activeTexture(gl.TEXTURE0 + COOKIE_ATLAS_TEXTURE_UNIT);
|
|
1397
|
-
gl.bindTexture(gl.TEXTURE_2D, this.cookieTexture);
|
|
1398
|
-
if (images.length === 0) {
|
|
1399
|
-
/* White, not black: this multiplies a light's colour. */
|
|
1400
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255, 255]));
|
|
1401
|
-
}
|
|
1402
|
-
else {
|
|
1403
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, COOKIE_TILE * images.length, COOKIE_TILE, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
1404
|
-
for (let tile = 0; tile < images.length; tile++) {
|
|
1405
|
-
const image = images[tile];
|
|
1406
|
-
if (image === undefined)
|
|
1407
|
-
continue;
|
|
1408
|
-
gl.texSubImage2D(gl.TEXTURE_2D, 0, tile * COOKIE_TILE, 0, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
/* Linear and clamped: a cookie is an image, and the shader's own inset is what keeps a tile
|
|
1412
|
-
from reaching its neighbour — clamping alone cannot, the seam being interior to the row. */
|
|
1413
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
1414
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
1415
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1416
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1417
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
1505
|
+
this.cookieImages = images.slice();
|
|
1506
|
+
this.buildFixtureAtlas();
|
|
1418
1507
|
}
|
|
1419
1508
|
setIesProfiles(profiles) {
|
|
1509
|
+
this.iesAtlas = packIesAtlas(profiles);
|
|
1510
|
+
this.buildFixtureAtlas();
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* The fixture atlas from the profiles and cookies in hand: the profiles' rows at the bottom, the
|
|
1514
|
+
* cookies' tiles on top, one `RGBA16F` texture. See `fixtureAtlas.ts` for the layout.
|
|
1515
|
+
*
|
|
1516
|
+
* **Half float, matching what the other backend must use**: a 32-bit float texture is not
|
|
1517
|
+
* filterable in core WebGPU, and both tables are curves and images that want linear filtering.
|
|
1518
|
+
* Linear and clamped: a profile is sampled between two angles, and a cookie's own inset is what
|
|
1519
|
+
* keeps a tile from its neighbour — clamping alone cannot, the seam being interior to the row.
|
|
1520
|
+
*/
|
|
1521
|
+
buildFixtureAtlas() {
|
|
1420
1522
|
const { gl } = this;
|
|
1421
|
-
const
|
|
1422
|
-
if (this.
|
|
1423
|
-
this.
|
|
1424
|
-
gl.activeTexture(gl.TEXTURE0 +
|
|
1425
|
-
gl.bindTexture(gl.TEXTURE_2D, this.
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1523
|
+
const layout = fixtureAtlasLayout(this.iesAtlas.height, this.cookieImages.length);
|
|
1524
|
+
if (this.fixtureTexture === null)
|
|
1525
|
+
this.fixtureTexture = gl.createTexture();
|
|
1526
|
+
gl.activeTexture(gl.TEXTURE0 + FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
1527
|
+
gl.bindTexture(gl.TEXTURE_2D, this.fixtureTexture);
|
|
1528
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, layout.width, layout.height, 0, gl.RGBA, gl.FLOAT, fixtureAtlasTexels(layout, this.iesAtlas));
|
|
1529
|
+
for (let tile = 0; tile < this.cookieImages.length; tile++) {
|
|
1530
|
+
const image = this.cookieImages[tile];
|
|
1531
|
+
if (image === undefined)
|
|
1532
|
+
continue;
|
|
1533
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, tile * COOKIE_TILE, 0, gl.RGBA, gl.HALF_FLOAT, image);
|
|
1534
|
+
}
|
|
1432
1535
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
1433
1536
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
1434
1537
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1435
1538
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
* the other backend must use: a 32-bit float texture is not filterable in core WebGPU, and this
|
|
1440
|
-
* row is a curve that wants linear filtering. Keeping the two formats the same is the
|
|
1441
|
-
* 2026-08-13 rule rather than tidiness; the driver converts the float data on the way in.
|
|
1442
|
-
*/
|
|
1443
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R16F, atlas.width, atlas.height, 0, gl.RED, gl.FLOAT, atlas.data);
|
|
1444
|
-
this.iesRows = atlas.height;
|
|
1445
|
-
this.iesPlanes = atlas.planes;
|
|
1539
|
+
this.iesRows = this.iesAtlas.height;
|
|
1540
|
+
this.iesPlanes = this.iesAtlas.planes;
|
|
1541
|
+
this.cookieTiles = this.cookieImages.length;
|
|
1446
1542
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
1543
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1447
1544
|
}
|
|
1448
|
-
/** Bind the atlas and say
|
|
1545
|
+
/** Bind the fixture atlas and say what is in it. Always, for the reason `bindClusters` gives. */
|
|
1449
1546
|
bindIesAtlas(u) {
|
|
1450
1547
|
const { gl } = this;
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
gl.
|
|
1455
|
-
gl.
|
|
1548
|
+
/* Built on demand rather than at construction, for the reason the grade's placeholder is. */
|
|
1549
|
+
if (this.fixtureTexture === null)
|
|
1550
|
+
this.buildFixtureAtlas();
|
|
1551
|
+
gl.activeTexture(gl.TEXTURE0 + FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
1552
|
+
gl.bindTexture(gl.TEXTURE_2D, this.fixtureTexture);
|
|
1553
|
+
gl.uniform1i(u['uFixtureAtlas'] ?? null, FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
1456
1554
|
gl.uniform1f(u['uIesAtlasRows'] ?? null, this.iesRows);
|
|
1457
1555
|
gl.uniform1f(u['uIesPlaneCount'] ?? null, this.iesPlanes);
|
|
1458
|
-
/* Built on demand rather than at construction, for the reason the grade's placeholder is: a
|
|
1459
|
-
scene that never projects a cookie should not carry a texture, and a declared sampler still
|
|
1460
|
-
needs a complete one. */
|
|
1461
|
-
if (this.cookieTexture === null)
|
|
1462
|
-
this.setSpotCookies([]);
|
|
1463
|
-
gl.activeTexture(gl.TEXTURE0 + COOKIE_ATLAS_TEXTURE_UNIT);
|
|
1464
|
-
gl.bindTexture(gl.TEXTURE_2D, this.cookieTexture);
|
|
1465
|
-
gl.uniform1i(u['uCookieAtlas'] ?? null, COOKIE_ATLAS_TEXTURE_UNIT);
|
|
1466
1556
|
gl.uniform1f(u['uCookieTiles'] ?? null, this.cookieTiles);
|
|
1467
1557
|
gl.activeTexture(gl.TEXTURE0);
|
|
1468
1558
|
}
|
|
@@ -1906,20 +1996,14 @@ export class WebGL2Renderer {
|
|
|
1906
1996
|
gl.uniform1i(uniforms['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
1907
1997
|
}
|
|
1908
1998
|
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)
|
|
1999
|
+
/*
|
|
2000
|
+
* The static world, its movers and — where more than one static depth layer is asked for —
|
|
2001
|
+
* the peel, as layers of one array. Movers get a layer of their own so a moving and a static
|
|
2002
|
+
* caster that overlap keep both distance fades: their transmissions multiply rather than one
|
|
2003
|
+
* depth replacing the other. The peel keeps the second static occluder the same way.
|
|
2004
|
+
*/
|
|
2005
|
+
this.sunShadows = this.quality.directionalShadows
|
|
2006
|
+
? new SunShadowArray(gl, this.quality.directionalShadowMapSize, this.quality.directionalShadowDepthLayers > 1)
|
|
1923
2007
|
: null;
|
|
1924
2008
|
// Static maps are allocated once the world reports its light count. No
|
|
1925
2009
|
// cubemap resources exist at all when the quality profile disables them.
|
|
@@ -2088,19 +2172,17 @@ export class WebGL2Renderer {
|
|
|
2088
2172
|
* every one of them, per renderer, for as long as it lived; `renderer.test.ts` counts them.
|
|
2089
2173
|
*/
|
|
2090
2174
|
this.sceneTarget?.dispose();
|
|
2091
|
-
this.
|
|
2092
|
-
this.
|
|
2093
|
-
this.
|
|
2175
|
+
this.sunShadows?.dispose(gl);
|
|
2176
|
+
this.sunGlassTint?.dispose(gl);
|
|
2177
|
+
this.glassTintPrograms?.dispose(gl);
|
|
2094
2178
|
this.planarReflection?.dispose(gl);
|
|
2095
2179
|
this.pointShadows?.dispose();
|
|
2096
2180
|
gl.deleteVertexArray(this.panelVao);
|
|
2097
2181
|
gl.deleteVertexArray(this.skyVao);
|
|
2098
2182
|
if (this.panelBuffer !== null)
|
|
2099
2183
|
gl.deleteBuffer(this.panelBuffer);
|
|
2100
|
-
if (this.
|
|
2101
|
-
gl.deleteTexture(this.
|
|
2102
|
-
if (this.iesTexture !== null)
|
|
2103
|
-
gl.deleteTexture(this.iesTexture);
|
|
2184
|
+
if (this.fixtureTexture !== null)
|
|
2185
|
+
gl.deleteTexture(this.fixtureTexture);
|
|
2104
2186
|
if (this.clusterTexture !== null)
|
|
2105
2187
|
gl.deleteTexture(this.clusterTexture);
|
|
2106
2188
|
}
|
|
@@ -2248,6 +2330,15 @@ export class WebGL2Renderer {
|
|
|
2248
2330
|
createSurfaceTexture(source, options = {}) {
|
|
2249
2331
|
return new SurfaceTexture(this.gl, source, options);
|
|
2250
2332
|
}
|
|
2333
|
+
/**
|
|
2334
|
+
* Upload several images of one size as the layers of one array, for a merged mesh that names a
|
|
2335
|
+
* layer per vertex (`MeshData.layers`). One material binding then covers every image, which is
|
|
2336
|
+
* what makes a block wearing forty facades one draw. Every layer must be the same size, and the
|
|
2337
|
+
* refusal names the first that is not.
|
|
2338
|
+
*/
|
|
2339
|
+
createSurfaceTextureArray(sources, options = {}) {
|
|
2340
|
+
return new SurfaceTexture(this.gl, sources, options);
|
|
2341
|
+
}
|
|
2251
2342
|
/**
|
|
2252
2343
|
* Replace a surface texture's pixels, keeping the GPU object and its sampler state.
|
|
2253
2344
|
*
|
|
@@ -2361,7 +2452,7 @@ export class WebGL2Renderer {
|
|
|
2361
2452
|
* page-faulted an RDNA4 card and wedged the device. See `emptyTexture.ts`.
|
|
2362
2453
|
*/
|
|
2363
2454
|
gl.activeTexture(gl.TEXTURE0 + NORMAL_TEXTURE_UNIT);
|
|
2364
|
-
gl.bindTexture(gl.
|
|
2455
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
2365
2456
|
if (normal !== null)
|
|
2366
2457
|
normal.bind(gl, NORMAL_TEXTURE_UNIT);
|
|
2367
2458
|
gl.uniform1i(u['uNormalMap'] ?? null, NORMAL_TEXTURE_UNIT);
|
|
@@ -2379,7 +2470,7 @@ export class WebGL2Renderer {
|
|
|
2379
2470
|
*/
|
|
2380
2471
|
const orm = material?.orm ?? null;
|
|
2381
2472
|
gl.activeTexture(gl.TEXTURE0 + ORM_TEXTURE_UNIT);
|
|
2382
|
-
gl.bindTexture(gl.
|
|
2473
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
2383
2474
|
if (orm !== null)
|
|
2384
2475
|
orm.bind(gl, ORM_TEXTURE_UNIT);
|
|
2385
2476
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
@@ -2397,7 +2488,7 @@ export class WebGL2Renderer {
|
|
|
2397
2488
|
*/
|
|
2398
2489
|
const emissiveMap = material?.emissive ?? null;
|
|
2399
2490
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
2400
|
-
gl.bindTexture(gl.
|
|
2491
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
2401
2492
|
if (emissiveMap !== null)
|
|
2402
2493
|
emissiveMap.bind(gl, EMISSIVE_TEXTURE_UNIT);
|
|
2403
2494
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
@@ -2413,7 +2504,21 @@ export class WebGL2Renderer {
|
|
|
2413
2504
|
*/
|
|
2414
2505
|
gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
|
|
2415
2506
|
gl.uniform1i(u['uDoubleSided'] ?? null, material?.doubleSided === true ? 1 : 0);
|
|
2507
|
+
/* The albedo's effects table, or the stand-in the shader reads as none. Before the early
|
|
2508
|
+
return, so a material with no albedo still leaves a complete texture on the unit. */
|
|
2509
|
+
if (albedo === null) {
|
|
2510
|
+
gl.activeTexture(gl.TEXTURE0 + SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
2511
|
+
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
2512
|
+
}
|
|
2513
|
+
else {
|
|
2514
|
+
albedo.bindEffects(gl, SURFACE_EFFECTS_TEXTURE_UNIT, this.emptyTexture2D);
|
|
2515
|
+
}
|
|
2516
|
+
gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
2416
2517
|
if (albedo === null) {
|
|
2518
|
+
/* The array stand-in, for the reason the maps above take one: a sampler reads its unit's
|
|
2519
|
+
array target, and with nothing there it is incomplete. */
|
|
2520
|
+
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
2521
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
2417
2522
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
2418
2523
|
return;
|
|
2419
2524
|
}
|
|
@@ -2448,7 +2553,7 @@ export class WebGL2Renderer {
|
|
|
2448
2553
|
return;
|
|
2449
2554
|
}
|
|
2450
2555
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
2451
|
-
gl.bindTexture(gl.
|
|
2556
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
2452
2557
|
}
|
|
2453
2558
|
/**
|
|
2454
2559
|
* A pass-level scale over the grain the geometry itself declared, 0 to 1.
|
|
@@ -2476,8 +2581,7 @@ export class WebGL2Renderer {
|
|
|
2476
2581
|
if (this.contextLost)
|
|
2477
2582
|
return;
|
|
2478
2583
|
this.materials.dirty();
|
|
2479
|
-
this.
|
|
2480
|
-
this.gl.uniform1f(this.flatUniforms['uReflectivity'] ?? null, Math.min(1, Math.max(0, amount)));
|
|
2584
|
+
this.writeFlatFloat('uReflectivity', Math.min(1, Math.max(0, amount)));
|
|
2481
2585
|
}
|
|
2482
2586
|
/**
|
|
2483
2587
|
* How bright the environment the surfaces drawn next reflect is. 1 is the default and identity.
|
|
@@ -2495,8 +2599,7 @@ export class WebGL2Renderer {
|
|
|
2495
2599
|
if (this.contextLost)
|
|
2496
2600
|
return;
|
|
2497
2601
|
this.materials.dirty();
|
|
2498
|
-
this.
|
|
2499
|
-
this.gl.uniform1f(this.flatUniforms['uEnvironmentGain'] ?? null, Math.max(0, gain));
|
|
2602
|
+
this.writeFlatFloat('uEnvironmentGain', Math.max(0, gain));
|
|
2500
2603
|
}
|
|
2501
2604
|
/**
|
|
2502
2605
|
* Kept as a no-op that says so once, because it was a stand-in for a term that now exists.
|
|
@@ -2523,8 +2626,51 @@ export class WebGL2Renderer {
|
|
|
2523
2626
|
if (this.contextLost)
|
|
2524
2627
|
return;
|
|
2525
2628
|
this.materials.dirty();
|
|
2629
|
+
this.writeFlatFloat('uGrain', Math.min(1, Math.max(0, amount)));
|
|
2630
|
+
}
|
|
2631
|
+
/**
|
|
2632
|
+
* One float of material state, into every flat program.
|
|
2633
|
+
*
|
|
2634
|
+
* **Every one, for `setMaterial`'s reason**: an instanced or skinned draw runs a program of its
|
|
2635
|
+
* own and is entitled to the material its caller set before it. These setters wrote the plain
|
|
2636
|
+
* program alone until 2026-09-30, so on this backend a batch drew with the pass defaults — grain
|
|
2637
|
+
* full, no reflection, no relief — while the other backend, whose material block every variant
|
|
2638
|
+
* reads, drew what was asked.
|
|
2639
|
+
*/
|
|
2640
|
+
writeFlatFloat(name, value) {
|
|
2641
|
+
for (const target of this.flatTargets) {
|
|
2642
|
+
this.gl.useProgram(target.program);
|
|
2643
|
+
this.gl.uniform1f(target.uniforms[name] ?? null, value);
|
|
2644
|
+
}
|
|
2645
|
+
this.useFlatProgram();
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* How much of the surfaces drawn next survives a screen-door dither, for crossfading two levels
|
|
2649
|
+
* of detail of one thing.
|
|
2650
|
+
*
|
|
2651
|
+
* 0 is off and is what `bindMeshPass` restores. A positive amount keeps that share of the
|
|
2652
|
+
* pattern's cells, a negative one keeps the complement, so the level fading in drawn at `t` and
|
|
2653
|
+
* the one fading out at `-t` cover every pixel exactly once — no hole, no pixel drawn twice.
|
|
2654
|
+
* Material state like `setSurfaceGrain`; instanced draws read it too, and shadow casters, which
|
|
2655
|
+
* are a pass of their own, do not. See `uDither` in `flat/preamble.ts` for what it costs.
|
|
2656
|
+
*/
|
|
2657
|
+
setDitherFade(amount) {
|
|
2658
|
+
if (this.contextLost)
|
|
2659
|
+
return;
|
|
2660
|
+
this.materials.dirty();
|
|
2661
|
+
this.useFlatProgram();
|
|
2662
|
+
this.ditherFade = Math.min(1, Math.max(-1, amount));
|
|
2663
|
+
/* Every flat program, as `setMaterial` writes them: an instanced or skinned draw is entitled
|
|
2664
|
+
to the fade the caller set before it, and a second program holding none draws whole. */
|
|
2665
|
+
for (const target of this.flatTargets) {
|
|
2666
|
+
this.gl.useProgram(target.program);
|
|
2667
|
+
this.writeMode(target.uniforms);
|
|
2668
|
+
}
|
|
2526
2669
|
this.useFlatProgram();
|
|
2527
|
-
|
|
2670
|
+
}
|
|
2671
|
+
/** `uWriteMode`: the order-independent weight and the dither, which share one uniform row. */
|
|
2672
|
+
writeMode(u) {
|
|
2673
|
+
this.gl.uniform2f(u['uWriteMode'] ?? null, this.oitWeight, this.ditherFade);
|
|
2528
2674
|
}
|
|
2529
2675
|
/**
|
|
2530
2676
|
* How strong the microscopic relief on the surfaces drawn next is, and how coarse.
|
|
@@ -2553,10 +2699,8 @@ export class WebGL2Renderer {
|
|
|
2553
2699
|
if (this.contextLost)
|
|
2554
2700
|
return;
|
|
2555
2701
|
this.materials.dirty();
|
|
2556
|
-
this.
|
|
2557
|
-
|
|
2558
|
-
this.gl.uniform1f(u['uRelief'] ?? null, Math.min(1, Math.max(0, amount)));
|
|
2559
|
-
this.gl.uniform1f(u['uReliefCycles'] ?? null, Math.max(0.01, cyclesPerMetre));
|
|
2702
|
+
this.writeFlatFloat('uRelief', Math.min(1, Math.max(0, amount)));
|
|
2703
|
+
this.writeFlatFloat('uReliefCycles', Math.max(0.01, cyclesPerMetre));
|
|
2560
2704
|
}
|
|
2561
2705
|
/**
|
|
2562
2706
|
* How hard the bound surface texture's own luminance turns the shading normal, 0 for not at all.
|
|
@@ -2587,8 +2731,7 @@ export class WebGL2Renderer {
|
|
|
2587
2731
|
if (this.contextLost)
|
|
2588
2732
|
return;
|
|
2589
2733
|
this.materials.dirty();
|
|
2590
|
-
this.
|
|
2591
|
-
this.gl.uniform1f(this.flatUniforms['uTextureRelief'] ?? null, Number.isFinite(scale) ? scale : 0);
|
|
2734
|
+
this.writeFlatFloat('uTextureRelief', Number.isFinite(scale) ? scale : 0);
|
|
2592
2735
|
}
|
|
2593
2736
|
/**
|
|
2594
2737
|
* Release a mesh's GPU buffers.
|
|
@@ -2615,8 +2758,7 @@ export class WebGL2Renderer {
|
|
|
2615
2758
|
if (this.contextLost)
|
|
2616
2759
|
return;
|
|
2617
2760
|
this.materials.dirty();
|
|
2618
|
-
this.
|
|
2619
|
-
this.gl.uniform1f(this.flatUniforms['uEmissiveGain'] ?? null, gain);
|
|
2761
|
+
this.writeFlatFloat('uEmissiveGain', gain);
|
|
2620
2762
|
}
|
|
2621
2763
|
/* -- Instanced meshes --------------------------------------------------------------- */
|
|
2622
2764
|
/** The instanced program, compiled on first use. See `ensureInstancedProgram`. */
|
|
@@ -2655,7 +2797,7 @@ export class WebGL2Renderer {
|
|
|
2655
2797
|
* One batch per mesh: the attributes bind to the mesh's own vertex array, of which a mesh has
|
|
2656
2798
|
* one. `Mesh.attachInstances` is what refuses a second, and says why.
|
|
2657
2799
|
*/
|
|
2658
|
-
createInstanced(mesh, capacity) {
|
|
2800
|
+
createInstanced(mesh, capacity, options = {}) {
|
|
2659
2801
|
/* The same refusal as the other backend, for the same reason: the instanced attributes take
|
|
2660
2802
|
the two locations a skinned mesh's joints and weights already occupy. */
|
|
2661
2803
|
if (mesh.isSkinned) {
|
|
@@ -2663,7 +2805,7 @@ export class WebGL2Renderer {
|
|
|
2663
2805
|
'locations the joint indices and weights occupy, and this mesh supplies them.');
|
|
2664
2806
|
}
|
|
2665
2807
|
this.ensureInstancedProgram();
|
|
2666
|
-
return new InstancedBatch(this.gl, mesh, capacity);
|
|
2808
|
+
return new InstancedBatch(this.gl, mesh, capacity, options.cull ?? false);
|
|
2667
2809
|
}
|
|
2668
2810
|
/** Push placement and colour. Only the live prefix. */
|
|
2669
2811
|
uploadInstanced(batch, data) {
|
|
@@ -2707,6 +2849,12 @@ export class WebGL2Renderer {
|
|
|
2707
2849
|
const count = Math.min(data.count, batch.capacity);
|
|
2708
2850
|
if (count === 0)
|
|
2709
2851
|
return;
|
|
2852
|
+
/* A culling batch's camera draw: the whole batch against the view and the occluders first,
|
|
2853
|
+
then instance by instance where it survives. Blended draws keep every instance and their
|
|
2854
|
+
order. See `InstancedOptions.cull`. */
|
|
2855
|
+
const culling = !blend && batch.cull;
|
|
2856
|
+
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion))
|
|
2857
|
+
return;
|
|
2710
2858
|
this.drawBudget.ask();
|
|
2711
2859
|
this.ensureInstancedProgram();
|
|
2712
2860
|
const program = this.flatInstancedProgram;
|
|
@@ -2717,10 +2865,13 @@ export class WebGL2Renderer {
|
|
|
2717
2865
|
gl.useProgram(program);
|
|
2718
2866
|
gl.uniform1i(u['uHasTangents'] ?? null, mesh.hasTangents ? 1 : 0);
|
|
2719
2867
|
const lit = options.lit ?? true;
|
|
2720
|
-
|
|
2868
|
+
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
2869
|
+
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
2721
2870
|
const toneMapped = options.toneMapped ?? true;
|
|
2722
|
-
/*
|
|
2723
|
-
|
|
2871
|
+
/* A blended batch shows what is behind it — refraction or glass — as a single draw does, and
|
|
2872
|
+
counts a material of its own for it by the rule both backends share (`materialChanges.ts`). */
|
|
2873
|
+
const refracting = blend && this.bindSeeThrough(u, options);
|
|
2874
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
2724
2875
|
if (own)
|
|
2725
2876
|
this.materials.dirty();
|
|
2726
2877
|
this.takeMaterial();
|
|
@@ -2741,29 +2892,21 @@ export class WebGL2Renderer {
|
|
|
2741
2892
|
* **A null snapshot leaves the strength at zero**, so the draw shades as an ordinary
|
|
2742
2893
|
* translucent one rather than sampling a black texture and painting the pane the colour of a
|
|
2743
2894
|
* hole. That is the defined state the two-backends rule asks for instead of a silent no-op.
|
|
2895
|
+
* The copy is bound above, before the material is counted, because whether it was taken is
|
|
2896
|
+
* part of what the count asks.
|
|
2744
2897
|
*/
|
|
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
2898
|
if (!lit)
|
|
2759
2899
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
2760
|
-
if (
|
|
2761
|
-
gl.uniform1i(u['uFogEnabled'] ?? null,
|
|
2900
|
+
if (fog !== FOG_RECEDE)
|
|
2901
|
+
gl.uniform1i(u['uFogEnabled'] ?? null, fog);
|
|
2762
2902
|
if (!toneMapped)
|
|
2763
2903
|
gl.uniform1i(u['uOutputTransform'] ?? null, Math.min(this.gradeCode(), 1));
|
|
2764
2904
|
if (blend) {
|
|
2765
2905
|
gl.enable(gl.BLEND);
|
|
2766
|
-
|
|
2906
|
+
if (options.additive === true)
|
|
2907
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ZERO, gl.ONE);
|
|
2908
|
+
else
|
|
2909
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
2767
2910
|
if (!depthWrite)
|
|
2768
2911
|
gl.depthMask(false);
|
|
2769
2912
|
}
|
|
@@ -2774,7 +2917,16 @@ export class WebGL2Renderer {
|
|
|
2774
2917
|
gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
2775
2918
|
gl.polygonOffset(offset.slope, offset.units);
|
|
2776
2919
|
}
|
|
2777
|
-
|
|
2920
|
+
/* Both sides of a double-sided material, as every mesh path does and WebGPU's pipeline does
|
|
2921
|
+
for both: without it a batch's far faces were culled here alone. */
|
|
2922
|
+
if (this.materialDoubleSided)
|
|
2923
|
+
gl.disable(gl.CULL_FACE);
|
|
2924
|
+
if (culling && cullsInstances(count, mesh.indexCount))
|
|
2925
|
+
batch.drawCulled(gl, data, this.frustum);
|
|
2926
|
+
else
|
|
2927
|
+
batch.draw(gl, count);
|
|
2928
|
+
if (this.materialDoubleSided)
|
|
2929
|
+
gl.enable(gl.CULL_FACE);
|
|
2778
2930
|
if (layer > 0) {
|
|
2779
2931
|
gl.polygonOffset(0, 0);
|
|
2780
2932
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
@@ -2790,21 +2942,57 @@ export class WebGL2Renderer {
|
|
|
2790
2942
|
gl.uniform1f(u['uOpacity'] ?? null, 1);
|
|
2791
2943
|
/* The binding is released with the uniform, which is the 2026-08-27 rule: a WebGL2 binding
|
|
2792
2944
|
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
|
-
}
|
|
2945
|
+
if (refracting)
|
|
2946
|
+
this.unbindSeeThrough(u);
|
|
2798
2947
|
if (!lit)
|
|
2799
2948
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
2800
|
-
if (
|
|
2801
|
-
gl.uniform1i(u['uFogEnabled'] ?? null,
|
|
2949
|
+
if (fog !== FOG_RECEDE)
|
|
2950
|
+
gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
|
|
2802
2951
|
if (!toneMapped)
|
|
2803
2952
|
gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
|
|
2804
2953
|
if (own)
|
|
2805
2954
|
this.materials.dirty();
|
|
2806
2955
|
this.useFlatProgram();
|
|
2807
2956
|
}
|
|
2957
|
+
/**
|
|
2958
|
+
* Bind what a translucent draw shows through it: the frame's copy, refraction's bend and absorption,
|
|
2959
|
+
* and glass. Returns whether the copy is bound, and so whether `unbindSeeThrough` owes it back.
|
|
2960
|
+
*
|
|
2961
|
+
* **A glass draw takes the copy even when it does not bend**, since a pane shows what is behind it
|
|
2962
|
+
* either way, and asks for the copy's mip chain, which a frosted pane reads blurred. A null copy
|
|
2963
|
+
* leaves both off, so the pane shades as ordinary translucency rather than sampling black.
|
|
2964
|
+
*
|
|
2965
|
+
* **Refused inside a probe's face or a mirror**, as WebGPU's `takeRefractSnapshot` refuses: the
|
|
2966
|
+
* copy is of the frame drawn to the screen, so a pane there would show the wrong picture, and
|
|
2967
|
+
* taking it mid-bake is how a probe's next face came to be attached to the scene's framebuffer.
|
|
2968
|
+
*/
|
|
2969
|
+
bindSeeThrough(u, options) {
|
|
2970
|
+
const { gl } = this;
|
|
2971
|
+
const refraction = options.refraction ?? 0;
|
|
2972
|
+
const glassy = resolveGlass(options.glass, this.glassScratch);
|
|
2973
|
+
if (refraction <= 0 && !glassy)
|
|
2974
|
+
return false;
|
|
2975
|
+
if (this.probePassActive || this.reflectionPassActive)
|
|
2976
|
+
return false;
|
|
2977
|
+
const snapshot = this.sceneTarget?.snapshotColor(false, glassy) ?? null;
|
|
2978
|
+
if (snapshot === null)
|
|
2979
|
+
return false;
|
|
2980
|
+
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
2981
|
+
gl.bindTexture(gl.TEXTURE_2D, snapshot);
|
|
2982
|
+
const glass = this.glassScratch;
|
|
2983
|
+
/* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
|
|
2984
|
+
gl.uniform4f(u['uSeeThrough'] ?? null, Math.max(refraction, 0), options.thicknessM ?? 0, glass.transmission, glass.frost);
|
|
2985
|
+
gl.uniform3fv(u['uRefractTint'] ?? null, options.refractTint ?? WHITE_TINT);
|
|
2986
|
+
gl.uniform3fv(u['uGlassTint'] ?? null, glass.tint);
|
|
2987
|
+
return true;
|
|
2988
|
+
}
|
|
2989
|
+
/** Put back what `bindSeeThrough` bound: the binding outlives its frame (the 2026-08-27 rule). */
|
|
2990
|
+
unbindSeeThrough(u) {
|
|
2991
|
+
const { gl } = this;
|
|
2992
|
+
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
2993
|
+
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
2994
|
+
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
2995
|
+
}
|
|
2808
2996
|
disposeMesh(mesh) {
|
|
2809
2997
|
/*
|
|
2810
2998
|
* Nothing to release when the context is gone: it took every buffer and VAO with it,
|
|
@@ -3338,6 +3526,10 @@ export class WebGL2Renderer {
|
|
|
3338
3526
|
*/
|
|
3339
3527
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.pointShadowArray?.texture ?? this.emptyTexture2DArray);
|
|
3340
3528
|
gl.uniform1i(uniforms[POINT_SHADOW_SAMPLER] ?? null, firstUnit);
|
|
3529
|
+
/* What the lamps keep through glass: the one-texel stand-in, read as none, until glass casts. */
|
|
3530
|
+
gl.activeTexture(gl.TEXTURE0 + POINT_GLASS_TINT_TEXTURE_UNIT);
|
|
3531
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.pointShadowArray?.tintTexture ?? this.emptyTexture2DArray);
|
|
3532
|
+
gl.uniform1i(uniforms['uPointGlassTints'] ?? null, POINT_GLASS_TINT_TEXTURE_UNIT);
|
|
3341
3533
|
gl.uniform1iv(uniforms['uPointShadowLayer[0]'] ?? null, resolved.layers);
|
|
3342
3534
|
/* Origin and far plane in one row; see `uPointShadowProjection` in the preamble. */
|
|
3343
3535
|
gl.uniform4fv(uniforms['uPointShadowProjection[0]'] ?? null, resolved.projections);
|
|
@@ -3614,20 +3806,17 @@ export class WebGL2Renderer {
|
|
|
3614
3806
|
* emitting, because a stale map must never be sampled.
|
|
3615
3807
|
*/
|
|
3616
3808
|
beginShadowPass(lightViewProj, layer = 'static') {
|
|
3617
|
-
const
|
|
3618
|
-
|
|
3619
|
-
: layer === 'static-peel'
|
|
3620
|
-
? this.peeledShadowMap
|
|
3621
|
-
: this.dynamicShadowMap;
|
|
3622
|
-
if (shadowMap === null) {
|
|
3809
|
+
const sun = this.sunShadows;
|
|
3810
|
+
if (sun === null || (layer === 'static-peel' && sun.layers < 3)) {
|
|
3623
3811
|
this.shadowPassActive = false;
|
|
3624
|
-
this.
|
|
3812
|
+
this.activeSunLayer = null;
|
|
3625
3813
|
return;
|
|
3626
3814
|
}
|
|
3627
3815
|
const { gl } = this;
|
|
3628
3816
|
this.gpuTimer.begin('shadows');
|
|
3629
3817
|
this.shadowPassActive = true;
|
|
3630
|
-
this.
|
|
3818
|
+
this.activeSunLayer = layer;
|
|
3819
|
+
this.glassCasters.clear();
|
|
3631
3820
|
/* The static layer opens first, so a frame's peel counts from here. See `peelFilled`. */
|
|
3632
3821
|
if (layer === 'static')
|
|
3633
3822
|
this.peelFilled = false;
|
|
@@ -3655,16 +3844,16 @@ export class WebGL2Renderer {
|
|
|
3655
3844
|
// The previous mesh pass left all directional maps bound for sampling.
|
|
3656
3845
|
// A texture cannot be sampled while it is attached to the active depth
|
|
3657
3846
|
// target, even when the shader branch would skip that sample.
|
|
3658
|
-
|
|
3659
|
-
gl.
|
|
3660
|
-
|
|
3661
|
-
shadowMap.begin(gl);
|
|
3847
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
3848
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3849
|
+
sun.begin(gl, layer);
|
|
3662
3850
|
gl.useProgram(this.depthProgram);
|
|
3663
3851
|
const peel = layer === 'static-peel';
|
|
3664
3852
|
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, peel ? 1 : 0);
|
|
3665
3853
|
if (peel) {
|
|
3854
|
+
/* The static layer, read while the peel is drawn aside: see `SunShadowArray`. */
|
|
3666
3855
|
gl.activeTexture(gl.TEXTURE0);
|
|
3667
|
-
gl.bindTexture(gl.
|
|
3856
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, sun.texture);
|
|
3668
3857
|
gl.uniform1i(this.depthUniforms['uPreviousShadowMap'] ?? null, 0);
|
|
3669
3858
|
}
|
|
3670
3859
|
/* The corrected one, matching `activeDepthViewProj` above: a pass must render its map with the
|
|
@@ -3770,21 +3959,158 @@ export class WebGL2Renderer {
|
|
|
3770
3959
|
return;
|
|
3771
3960
|
casters(this.casterSink);
|
|
3772
3961
|
}
|
|
3962
|
+
/**
|
|
3963
|
+
* Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
|
|
3964
|
+
*
|
|
3965
|
+
* After the opaque casters, in the pass's own light matrix. A layer that has held glass is
|
|
3966
|
+
* redrawn even when this pass offered none, so glass that has gone takes its colour with it. The
|
|
3967
|
+
* first glass grows the array (`SunShadowArray.growForGlass`) and makes the tint target.
|
|
3968
|
+
*/
|
|
3969
|
+
flushSunGlass(which) {
|
|
3970
|
+
const sun = this.sunShadows;
|
|
3971
|
+
const list = this.glassCasters;
|
|
3972
|
+
if (sun === null || this.quality.glassShadows === 'off')
|
|
3973
|
+
return;
|
|
3974
|
+
if (list.count === 0 && !sun.hasGlass)
|
|
3975
|
+
return;
|
|
3976
|
+
const { gl } = this;
|
|
3977
|
+
if (list.count > 0 && sun.growForGlass(gl))
|
|
3978
|
+
this.sunGlassRebuilds++;
|
|
3979
|
+
/* Where the nearest pane is: the depth programs, through the sink every opaque caster took. */
|
|
3980
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
3981
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3982
|
+
sun.beginGlass(gl, which);
|
|
3983
|
+
this.activeDepthPeel = false;
|
|
3984
|
+
gl.useProgram(this.depthProgram);
|
|
3985
|
+
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, 0);
|
|
3986
|
+
gl.uniformMatrix4fv(this.depthUniforms['uLightViewProj'] ?? null, false, this.activeDepthViewProj ?? IDENTITY_MAT4);
|
|
3987
|
+
this.drawingGlassDepth = true;
|
|
3988
|
+
list.replay(this.glassDepthSink);
|
|
3989
|
+
this.drawingGlassDepth = false;
|
|
3990
|
+
sun.end(gl);
|
|
3991
|
+
/* What the panes let through, every one on a ray, multiplied. */
|
|
3992
|
+
this.glassTintPrograms ??= new GlassTintPrograms(gl);
|
|
3993
|
+
const size = this.quality.glassShadows === 'half' ? Math.max(1, sun.size >> 1) : sun.size;
|
|
3994
|
+
const tint = (this.sunGlassTint ??= new SunGlassTint(gl, size));
|
|
3995
|
+
/*
|
|
3996
|
+
* The direction the light travels, off the light matrix's depth row: an orthographic
|
|
3997
|
+
* projection's depth grows along it. The sign is immaterial — the tint takes the cosine
|
|
3998
|
+
* absolutely — and w 0 says it is a direction rather than a place.
|
|
3999
|
+
*/
|
|
4000
|
+
const m = this.activeDepthViewProj ?? IDENTITY_MAT4;
|
|
4001
|
+
const length = Math.hypot(m[2], m[6], m[10]) || 1;
|
|
4002
|
+
this.glassLight[0] = m[2] / length;
|
|
4003
|
+
this.glassLight[1] = m[6] / length;
|
|
4004
|
+
this.glassLight[2] = m[10] / length;
|
|
4005
|
+
this.glassLight[3] = 0;
|
|
4006
|
+
tint.begin(gl, which === 'static' ? 0 : 1);
|
|
4007
|
+
list.replay(this.glassTintSink);
|
|
4008
|
+
tint.end(gl);
|
|
4009
|
+
gl.useProgram(this.depthProgram);
|
|
4010
|
+
}
|
|
4011
|
+
/** The glass list into the glass depth layer: `casterSink` itself, with glass let through. */
|
|
4012
|
+
glassDepthSink = {
|
|
4013
|
+
mesh: (mesh, model, material) => this.casterSink.mesh(mesh, model, material),
|
|
4014
|
+
instanced: (batch, data, material) => this.casterSink.instanced?.(batch, data, material),
|
|
4015
|
+
skinnedMesh: (mesh, model, palette, material) => this.casterSink.skinnedMesh(mesh, model, palette, material),
|
|
4016
|
+
};
|
|
4017
|
+
/** One pane's tint uniforms, and its cutout where it has one. */
|
|
4018
|
+
bindGlassTint(program, glass, cutout) {
|
|
4019
|
+
const { gl } = this;
|
|
4020
|
+
const u = program.uniforms;
|
|
4021
|
+
gl.useProgram(program.program);
|
|
4022
|
+
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj ?? IDENTITY_MAT4);
|
|
4023
|
+
this.uploadWind(u, this.frameWind);
|
|
4024
|
+
this.glassPane[0] = glass.transmission * glass.tint[0];
|
|
4025
|
+
this.glassPane[1] = glass.transmission * glass.tint[1];
|
|
4026
|
+
this.glassPane[2] = glass.transmission * glass.tint[2];
|
|
4027
|
+
this.glassPane[3] = 1 - glass.frost;
|
|
4028
|
+
gl.uniform4fv(u['uGlassPane'] ?? null, this.glassPane);
|
|
4029
|
+
gl.uniform4fv(u['uGlassLight'] ?? null, this.glassLight);
|
|
4030
|
+
if (cutout !== null) {
|
|
4031
|
+
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
4032
|
+
gl.uniform1f(u['uAlphaCutout'] ?? null, cutout.cutoff);
|
|
4033
|
+
cutout.albedo.bind(gl, SURFACE_TEXTURE_UNIT);
|
|
4034
|
+
gl.uniform1i(u['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
/**
|
|
4038
|
+
* The glass list into the tint layer, through the tint programs, **culling nothing**: a light
|
|
4039
|
+
* crosses a pane whichever way the pane faces, and a closed glass object has two surfaces on a
|
|
4040
|
+
* ray, so every one counts. What that asks of a model is that a pane be one surface — two-sided
|
|
4041
|
+
* glass says so with `doubleSided`, and a second copy of its triangles is a second pane.
|
|
4042
|
+
*/
|
|
4043
|
+
glassTintSink = {
|
|
4044
|
+
mesh: (mesh, model, material, glass) => {
|
|
4045
|
+
const programs = this.glassTintPrograms;
|
|
4046
|
+
if (programs === null)
|
|
4047
|
+
return;
|
|
4048
|
+
const { gl } = this;
|
|
4049
|
+
const cutout = cutoutOf(material);
|
|
4050
|
+
const program = cutout !== null ? programs.cutout : programs.rigid;
|
|
4051
|
+
this.bindGlassTint(program, glass, cutout);
|
|
4052
|
+
gl.uniformMatrix4fv(program.uniforms['uModel'] ?? null, false, model);
|
|
4053
|
+
gl.disable(gl.CULL_FACE);
|
|
4054
|
+
mesh.draw(gl);
|
|
4055
|
+
if (this.depthPassCullsFaces)
|
|
4056
|
+
gl.enable(gl.CULL_FACE);
|
|
4057
|
+
if (cutout !== null)
|
|
4058
|
+
this.restoreSurfaceTextureUnit();
|
|
4059
|
+
},
|
|
4060
|
+
instanced: (batch, data, material, glass) => {
|
|
4061
|
+
const programs = this.glassTintPrograms;
|
|
4062
|
+
if (programs === null)
|
|
4063
|
+
return;
|
|
4064
|
+
const { gl } = this;
|
|
4065
|
+
const gpuBatch = batch;
|
|
4066
|
+
const count = Math.min(data.count, gpuBatch.capacity);
|
|
4067
|
+
if (count === 0)
|
|
4068
|
+
return;
|
|
4069
|
+
const cutout = cutoutOf(material);
|
|
4070
|
+
this.bindGlassTint(cutout !== null ? programs.instancedCutout : programs.instanced, glass, cutout);
|
|
4071
|
+
gl.disable(gl.CULL_FACE);
|
|
4072
|
+
gpuBatch.draw(gl, count);
|
|
4073
|
+
if (this.depthPassCullsFaces)
|
|
4074
|
+
gl.enable(gl.CULL_FACE);
|
|
4075
|
+
if (cutout !== null)
|
|
4076
|
+
this.restoreSurfaceTextureUnit();
|
|
4077
|
+
},
|
|
4078
|
+
skinnedMesh: (mesh, model, palette, _material, glass) => {
|
|
4079
|
+
const programs = this.glassTintPrograms;
|
|
4080
|
+
if (programs === null)
|
|
4081
|
+
return;
|
|
4082
|
+
const { gl } = this;
|
|
4083
|
+
const program = programs.skinned;
|
|
4084
|
+
this.bindGlassTint(program, glass, null);
|
|
4085
|
+
gl.uniformMatrix4fv(program.uniforms['uModel'] ?? null, false, model);
|
|
4086
|
+
this.skinPalette.update(gl, palette);
|
|
4087
|
+
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
4088
|
+
gl.uniform1i(program.uniforms['uJointPalette'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
4089
|
+
gl.disable(gl.CULL_FACE);
|
|
4090
|
+
mesh.draw(gl);
|
|
4091
|
+
if (this.depthPassCullsFaces)
|
|
4092
|
+
gl.enable(gl.CULL_FACE);
|
|
4093
|
+
},
|
|
4094
|
+
};
|
|
3773
4095
|
endShadowPass() {
|
|
3774
4096
|
if (this.contextLost)
|
|
3775
4097
|
return;
|
|
3776
|
-
const
|
|
3777
|
-
|
|
4098
|
+
const sun = this.sunShadows;
|
|
4099
|
+
const layer = this.activeSunLayer;
|
|
4100
|
+
if (!this.shadowPassActive || sun === null || layer === null)
|
|
3778
4101
|
return;
|
|
3779
|
-
|
|
3780
|
-
if (
|
|
4102
|
+
sun.end(this.gl);
|
|
4103
|
+
if (layer === 'static-peel')
|
|
3781
4104
|
this.peelFilled = true;
|
|
4105
|
+
/* The peel keeps no glass: its layer is the second opaque occluder and nothing else. */
|
|
4106
|
+
else
|
|
4107
|
+
this.flushSunGlass(layer === 'dynamic' ? 'dynamic' : 'static');
|
|
3782
4108
|
/* Back to the frame's sense; see `beginShadowPass` for why it left it. */
|
|
3783
4109
|
this.gl.depthFunc(glDepthFuncEqual(this.gl));
|
|
3784
4110
|
this.gl.clearDepth(this.depthClear);
|
|
3785
4111
|
this.gpuTimer.end();
|
|
3786
4112
|
this.shadowPassActive = false;
|
|
3787
|
-
this.
|
|
4113
|
+
this.activeSunLayer = null;
|
|
3788
4114
|
// Restore the drawing-buffer viewport the shadow pass overrode.
|
|
3789
4115
|
this.restoreViewport();
|
|
3790
4116
|
}
|
|
@@ -3818,6 +4144,8 @@ export class WebGL2Renderer {
|
|
|
3818
4144
|
directionalShadows: this.quality.directionalShadows,
|
|
3819
4145
|
environmentProbe: this.probeCapture !== null,
|
|
3820
4146
|
nightEmissive: this.quality.nightEmissive,
|
|
4147
|
+
/* Off compiles none of the glass lookup: see `FlatShaderOptions.glassShadows`. */
|
|
4148
|
+
glassShadows: this.quality.glassShadows !== 'off',
|
|
3821
4149
|
};
|
|
3822
4150
|
const build = (budget) => flatFrag({ ...variant, maxLights: budget.maxLights, maxAreaLights: budget.maxAreaLights });
|
|
3823
4151
|
const vert = flatVert({ skinned: false, morphed: false, instanced: false });
|
|
@@ -4081,6 +4409,8 @@ export class WebGL2Renderer {
|
|
|
4081
4409
|
*/
|
|
4082
4410
|
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
4411
|
this.updateAreaShadows(areaLights, frameDt, staticCasters, dynamicCasters);
|
|
4412
|
+
/* The chain frost reads, once for the round, for the lights whose glass it drew or cleared. */
|
|
4413
|
+
this.pointShadowArray.finishTints(this.gl);
|
|
4084
4414
|
this.restoreViewport();
|
|
4085
4415
|
}
|
|
4086
4416
|
/**
|
|
@@ -4127,6 +4457,10 @@ export class WebGL2Renderer {
|
|
|
4127
4457
|
* reason: it already holds everything it needs.
|
|
4128
4458
|
*/
|
|
4129
4459
|
set.resolve(areaLights, this.resolvedAreaShadows);
|
|
4460
|
+
/* As the array stores them: 2L once glass has interleaved it. See `strideLayers`. */
|
|
4461
|
+
const stride = this.pointShadowArray?.stride ?? 1;
|
|
4462
|
+
strideLayers(this.resolvedAreaShadows.layer, stride);
|
|
4463
|
+
strideLayers(this.resolvedAreaShadows.liveLayer, stride);
|
|
4130
4464
|
}
|
|
4131
4465
|
/**
|
|
4132
4466
|
* Every point-shadow bake, under the one set of conventions all of them need.
|
|
@@ -4172,12 +4506,12 @@ export class WebGL2Renderer {
|
|
|
4172
4506
|
* map is stale on every frame it is checked — `pointShadowImage.ts` records that exact bug
|
|
4173
4507
|
* from two readers disagreeing about this argument's default.
|
|
4174
4508
|
*/
|
|
4175
|
-
return this.bakePointShadow(drawCasters, () => map.bake(gl, light.x, light.y, light.z, range, this.drawPointShadowFace, near, DEFAULT_SOURCE_RADIUS, maxFaces));
|
|
4509
|
+
return this.bakePointShadow(drawCasters, () => map.bake(gl, light.x, light.y, light.z, range, this.drawPointShadowFace, near, DEFAULT_SOURCE_RADIUS, maxFaces, this.pointGlass));
|
|
4176
4510
|
}
|
|
4177
4511
|
/** Spend up to `maxFaces` on this map, returning how many it used. */
|
|
4178
4512
|
bakeOne(map, light, drawCasters, maxFaces) {
|
|
4179
4513
|
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));
|
|
4514
|
+
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
4515
|
}
|
|
4182
4516
|
/**
|
|
4183
4517
|
* Draw into a rectangle of the canvas, on its own terms.
|
|
@@ -4388,6 +4722,10 @@ export class WebGL2Renderer {
|
|
|
4388
4722
|
*
|
|
4389
4723
|
* What it gives up is one frame of history: the frame after a cut has no blur and no temporal
|
|
4390
4724
|
* blend, as the first frame of a session has none. Nothing else is reset.
|
|
4725
|
+
*
|
|
4726
|
+
* **A reconstructing renderer depends on it**: its history is the last shot's, and without this
|
|
4727
|
+
* call a cut blends that shot into the first frames of the next. Nothing detects a cut on the
|
|
4728
|
+
* caller's behalf, deliberately — a fast pan and a cut look alike from here.
|
|
4391
4729
|
*/
|
|
4392
4730
|
cameraCut() {
|
|
4393
4731
|
this.hasPreviousView = false;
|
|
@@ -4981,11 +5319,20 @@ export class WebGL2Renderer {
|
|
|
4981
5319
|
metres into a radius in UV. */
|
|
4982
5320
|
this.aoProjScale[0] = projection[0] ?? 1;
|
|
4983
5321
|
this.aoProjScale[1] = projection[5] ?? 1;
|
|
5322
|
+
/* The occlusion read where this frame's jitter put it (`uAoOffset`): a texel stands at its
|
|
5323
|
+
index plus a half less the jitter, in the drawing buffer the jitter was spread over. */
|
|
5324
|
+
this.aoOffset[0] = this.temporalJittering
|
|
5325
|
+
? this.temporalJitterX / Math.max(1, this.gl.drawingBufferWidth)
|
|
5326
|
+
: 0;
|
|
5327
|
+
this.aoOffset[1] = this.temporalJittering
|
|
5328
|
+
? this.temporalJitterY / Math.max(1, this.gl.drawingBufferHeight)
|
|
5329
|
+
: 0;
|
|
4984
5330
|
ao = {
|
|
4985
5331
|
strength: aoStrength,
|
|
4986
5332
|
radius: this.quality.ambientOcclusionRadius,
|
|
4987
5333
|
projScale: this.aoProjScale,
|
|
4988
5334
|
invProjection: this.aoInvProjection,
|
|
5335
|
+
offset: this.aoOffset,
|
|
4989
5336
|
};
|
|
4990
5337
|
}
|
|
4991
5338
|
/*
|
|
@@ -5103,12 +5450,14 @@ export class WebGL2Renderer {
|
|
|
5103
5450
|
* flat paint with the effect on. The opaque frame is finished here, which is what a pane
|
|
5104
5451
|
* should show; the latch then serves every pane in the replay. Only when something refracts.
|
|
5105
5452
|
*/
|
|
5106
|
-
if (this.translucentQueue.refracts)
|
|
5107
|
-
this.sceneTarget?.snapshotColor();
|
|
5453
|
+
if (this.translucentQueue.refracts) {
|
|
5454
|
+
this.sceneTarget?.snapshotColor(false, this.translucentQueue.glassy);
|
|
5455
|
+
}
|
|
5108
5456
|
this.oit.accumulateOit(this.canvas.width, this.canvas.height, oitDepth, (weighted) => {
|
|
5109
5457
|
this.oitReplaying = true;
|
|
5110
5458
|
this.useFlatProgram();
|
|
5111
|
-
this.
|
|
5459
|
+
this.oitWeight = weighted ? 1 : 0;
|
|
5460
|
+
this.writeMode(flat);
|
|
5112
5461
|
this.translucentQueue.replay((draw) => {
|
|
5113
5462
|
options.lit = draw.lit;
|
|
5114
5463
|
options.fog = draw.fog;
|
|
@@ -5121,10 +5470,12 @@ export class WebGL2Renderer {
|
|
|
5121
5470
|
options.refraction = draw.refraction;
|
|
5122
5471
|
options.refractTint = draw.refractTint;
|
|
5123
5472
|
options.thicknessM = draw.thicknessM;
|
|
5473
|
+
options.glass = draw.glass ?? undefined;
|
|
5124
5474
|
this.drawTranslucentMesh(draw.mesh, draw.model, draw.opacity, options);
|
|
5125
5475
|
});
|
|
5126
5476
|
/* Back to zero for every other draw in the frame, exactly as `uOpacity` and `uTint` are. */
|
|
5127
|
-
this.
|
|
5477
|
+
this.oitWeight = 0;
|
|
5478
|
+
this.writeMode(flat);
|
|
5128
5479
|
this.oitReplaying = false;
|
|
5129
5480
|
});
|
|
5130
5481
|
this.sceneTarget?.bind();
|
|
@@ -5170,9 +5521,7 @@ export class WebGL2Renderer {
|
|
|
5170
5521
|
/* The placeholder rather than null where a map is merely absent, for the reason
|
|
5171
5522
|
`emptyTexture.ts` gives. It is black, which as a depth is an occluder at the light,
|
|
5172
5523
|
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,
|
|
5524
|
+
sunShadows: this.sunShadows?.texture ?? this.emptyTexture2DArray,
|
|
5176
5525
|
peeledEnabled: this.peelFilled,
|
|
5177
5526
|
});
|
|
5178
5527
|
if (marched) {
|
|
@@ -5285,7 +5634,15 @@ export class WebGL2Renderer {
|
|
|
5285
5634
|
* A reflection pass binds its own camera and is skipped: blurring toward a mirror's
|
|
5286
5635
|
* previous view is not what a viewer moved through.
|
|
5287
5636
|
*/
|
|
5288
|
-
|
|
5637
|
+
/*
|
|
5638
|
+
* **Not after the present**: a mesh pass bound then is the interface's, over a picture already
|
|
5639
|
+
* resolved. Deciding the frame again drew the interface jittered with nothing to resolve it,
|
|
5640
|
+
* and opened a second temporal frame, so the sequence stepped twice a frame, the world saw every
|
|
5641
|
+
* other position, and the anti-flicker's period never started.
|
|
5642
|
+
*/
|
|
5643
|
+
if (this.framePresented)
|
|
5644
|
+
this.temporalJittering = false;
|
|
5645
|
+
if (!this.reflectionPassActive && !this.probePassActive && !this.framePresented) {
|
|
5289
5646
|
this.frameViewProj = camera.viewProjection;
|
|
5290
5647
|
/* Copied rather than referenced: a caller moves its camera in place, and the decal pass
|
|
5291
5648
|
reads this at the end of the frame. */
|
|
@@ -5391,6 +5748,19 @@ export class WebGL2Renderer {
|
|
|
5391
5748
|
this.lightField = field;
|
|
5392
5749
|
return field;
|
|
5393
5750
|
}
|
|
5751
|
+
/**
|
|
5752
|
+
* Hand a world's DriftLight volume to the renderer: every fixed light of a city, summed offline by
|
|
5753
|
+
* `bakeDenseField`, standing in past the frame's exact choice. Replaces any field made before, of
|
|
5754
|
+
* either kind; the scene calls `field.follow(buffer.complete, ...)` each frame as it would for
|
|
5755
|
+
* `createLightField`, and marks the lights the volume summed `inLightField` so none is counted
|
|
5756
|
+
* twice. See `driftLight/worldLightField.ts`.
|
|
5757
|
+
*/
|
|
5758
|
+
createWorldLightField(volume, options = {}) {
|
|
5759
|
+
this.forgetLightFieldVolumes();
|
|
5760
|
+
const field = new WorldLightField(volume, options);
|
|
5761
|
+
this.lightField = field;
|
|
5762
|
+
return field;
|
|
5763
|
+
}
|
|
5394
5764
|
/** Let go of the field and its volumes; the scene's lights are all exact again. */
|
|
5395
5765
|
disposeLightField() {
|
|
5396
5766
|
this.forgetLightFieldVolumes();
|
|
@@ -5410,14 +5780,14 @@ export class WebGL2Renderer {
|
|
|
5410
5780
|
*/
|
|
5411
5781
|
uploadLightField() {
|
|
5412
5782
|
const field = this.lightField;
|
|
5413
|
-
if (field === null || this.driftAtlas !== null || !field.ready || field.
|
|
5783
|
+
if (field === null || this.driftAtlas !== null || !field.ready || field.empty) {
|
|
5414
5784
|
return;
|
|
5415
5785
|
}
|
|
5416
5786
|
const { gl } = this;
|
|
5417
|
-
const [ix, iy, iz] = field.
|
|
5787
|
+
const [ix, iy, iz] = field.indexDims;
|
|
5418
5788
|
const index = gl.createTexture();
|
|
5419
5789
|
gl.bindTexture(gl.TEXTURE_3D, index);
|
|
5420
|
-
gl.texImage3D(gl.TEXTURE_3D, 0, gl.R32UI, ix, iy, iz, 0, gl.RED_INTEGER, gl.UNSIGNED_INT, field.
|
|
5790
|
+
gl.texImage3D(gl.TEXTURE_3D, 0, gl.R32UI, ix, iy, iz, 0, gl.RED_INTEGER, gl.UNSIGNED_INT, field.index);
|
|
5421
5791
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
5422
5792
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
5423
5793
|
const [aw, ah, ad] = field.atlasSize;
|
|
@@ -5475,9 +5845,11 @@ export class WebGL2Renderer {
|
|
|
5475
5845
|
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
5476
5846
|
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
5477
5847
|
gl.uniform1i(u['uRefractScene'] ?? null, REFRACT_SCENE_TEXTURE_UNIT);
|
|
5478
|
-
|
|
5848
|
+
/* Neither refracting nor glass, which is every draw that does not say otherwise. See
|
|
5849
|
+
`bindSeeThrough` for the four numbers in the one vector. */
|
|
5850
|
+
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
5479
5851
|
gl.uniform3fv(u['uRefractTint'] ?? null, WHITE_TINT);
|
|
5480
|
-
gl.
|
|
5852
|
+
gl.uniform3fv(u['uGlassTint'] ?? null, WHITE_TINT);
|
|
5481
5853
|
gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
|
|
5482
5854
|
gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
|
|
5483
5855
|
gl.uniform3fv(u['uAmbient'] ?? null, env.ambient);
|
|
@@ -5543,6 +5915,10 @@ export class WebGL2Renderer {
|
|
|
5543
5915
|
gl.uniform1i(u['uFogEnabled'] ?? null, 1);
|
|
5544
5916
|
/* Grain on by default, so every scene written before it was a choice looks unchanged. */
|
|
5545
5917
|
gl.uniform1f(u['uGrain'] ?? null, 1);
|
|
5918
|
+
/* No dither and no weighting: every draw whole, until a crossfade asks. See `setDitherFade`. */
|
|
5919
|
+
this.oitWeight = 0;
|
|
5920
|
+
this.ditherFade = 0;
|
|
5921
|
+
gl.uniform2f(u['uWriteMode'] ?? null, 0, 0);
|
|
5546
5922
|
/* No environment reflection unless a caller asks, so existing scenes are unchanged. */
|
|
5547
5923
|
gl.uniform1f(u['uReflectivity'] ?? null, 0);
|
|
5548
5924
|
/* 1, not 0: this one is a multiplier and its identity is one. See `setEnvironmentGain`. */
|
|
@@ -5552,11 +5928,11 @@ export class WebGL2Renderer {
|
|
|
5552
5928
|
gl.uniform1f(u['uReliefCycles'] ?? null, 60);
|
|
5553
5929
|
/* And no relief read off a surface texture either, for the same reason. */
|
|
5554
5930
|
gl.uniform1f(u['uTextureRelief'] ?? null, 0);
|
|
5555
|
-
|
|
5931
|
+
/* The mirror's plane in a reflection pass, and zero — which clips nothing — in every other. */
|
|
5556
5932
|
const reflection = this.planarReflection;
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5933
|
+
gl.uniform4fv(u['uClipPlane'] ?? null, this.reflectionPassActive && reflection !== null ? reflection.clipPlane : NO_CLIP_PLANE);
|
|
5934
|
+
resolveSurfaceScene(env, this.surfaceScene);
|
|
5935
|
+
gl.uniform4fv(u['uSurfaceScene'] ?? null, this.surfaceScene);
|
|
5560
5936
|
bindAtmosphere(gl, u, env, this.reflectionPassActive ? this.reflectionAtmosphereY : (camera.position[1] ?? 0), this.quality.underwaterAtmosphere);
|
|
5561
5937
|
gl.uniform1f(u['uEmissiveGain'] ?? null, env.emissiveGain);
|
|
5562
5938
|
gl.uniform1f(u['uNightFactor'] ?? null, env.nightFactor);
|
|
@@ -5573,7 +5949,7 @@ export class WebGL2Renderer {
|
|
|
5573
5949
|
* its own copy of this lookup; the two backends have to project the same way.
|
|
5574
5950
|
*/
|
|
5575
5951
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, env.lightViewProj);
|
|
5576
|
-
gl.uniform1f(u['uShadowStrength'] ?? null, this.
|
|
5952
|
+
gl.uniform1f(u['uShadowStrength'] ?? null, this.sunShadows === null ? 0 : env.shadowStrength);
|
|
5577
5953
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
5578
5954
|
gl.uniform1f(u['uShadowDepthSpan'] ?? null, env.shadowDepthSpan);
|
|
5579
5955
|
gl.uniform1f(u['uShadowMaxDistance'] ?? null, this.quality.directionalShadowMaxDistance);
|
|
@@ -5598,14 +5974,12 @@ export class WebGL2Renderer {
|
|
|
5598
5974
|
*/
|
|
5599
5975
|
if (this.quality.directionalShadows) {
|
|
5600
5976
|
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);
|
|
5977
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.sunShadows?.texture ?? this.emptyTexture2DArray);
|
|
5978
|
+
gl.uniform1i(u['uSunShadows'] ?? null, 0);
|
|
5979
|
+
/* What the sun keeps through glass, or the one-texel stand-in the lookup reads as none. */
|
|
5980
|
+
gl.activeTexture(gl.TEXTURE0 + SUN_GLASS_TINT_TEXTURE_UNIT);
|
|
5981
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.sunGlassTint?.texture ?? this.emptyTexture2DArray);
|
|
5982
|
+
gl.uniform1i(u['uSunGlassTints'] ?? null, SUN_GLASS_TINT_TEXTURE_UNIT);
|
|
5609
5983
|
}
|
|
5610
5984
|
if (this.pointShadows === null) {
|
|
5611
5985
|
/*
|
|
@@ -5618,6 +5992,10 @@ export class WebGL2Renderer {
|
|
|
5618
5992
|
else {
|
|
5619
5993
|
/* Chosen in `pointShadowSystem.ts` and bound here; see `resolve` for the split. */
|
|
5620
5994
|
this.pointShadows.resolve(this.resolvedPointShadows);
|
|
5995
|
+
/* As the array stores them: 2L once glass has interleaved it. See `strideLayers`. */
|
|
5996
|
+
const stride = this.pointShadowArray?.stride ?? 1;
|
|
5997
|
+
strideLayers(this.resolvedPointShadows.layers, stride);
|
|
5998
|
+
strideLayers(this.resolvedPointShadows.liveLayers, stride);
|
|
5621
5999
|
this.bindPointShadows(u, DIRECTIONAL_SHADOW_UNITS);
|
|
5622
6000
|
/*
|
|
5623
6001
|
* The rectangles' layers, into the array `bindPointShadows` just put on its unit. Resolved
|
|
@@ -5711,9 +6089,13 @@ export class WebGL2Renderer {
|
|
|
5711
6089
|
}
|
|
5712
6090
|
this.bindDriftLight(u, camera.position);
|
|
5713
6091
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
5714
|
-
gl.bindTexture(gl.
|
|
6092
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
5715
6093
|
gl.uniform1i(u['uAlbedo'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
5716
6094
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
6095
|
+
/* No albedo, so no effects table: the stand-in, which the shader reads as none. */
|
|
6096
|
+
gl.activeTexture(gl.TEXTURE0 + SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
6097
|
+
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
6098
|
+
gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
5717
6099
|
gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
|
|
5718
6100
|
gl.uniform1f(u['uAlbedoCutout'] ?? null, 0);
|
|
5719
6101
|
gl.uniform1i(u['uDoubleSided'] ?? null, 0);
|
|
@@ -5737,18 +6119,18 @@ export class WebGL2Renderer {
|
|
|
5737
6119
|
* fetch a sampler's descriptor before it evaluates the branch that would have skipped the read.
|
|
5738
6120
|
*/
|
|
5739
6121
|
gl.activeTexture(gl.TEXTURE0 + NORMAL_TEXTURE_UNIT);
|
|
5740
|
-
gl.bindTexture(gl.
|
|
6122
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
5741
6123
|
gl.uniform1i(u['uNormalMap'] ?? null, NORMAL_TEXTURE_UNIT);
|
|
5742
6124
|
gl.uniform1f(u['uNormalStrength'] ?? null, 0);
|
|
5743
6125
|
/* And the ORM map, for the reason directly above and the one above that. */
|
|
5744
6126
|
gl.activeTexture(gl.TEXTURE0 + ORM_TEXTURE_UNIT);
|
|
5745
|
-
gl.bindTexture(gl.
|
|
6127
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
5746
6128
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
5747
6129
|
gl.uniform1i(u['uOrmEnabled'] ?? null, 0);
|
|
5748
6130
|
gl.uniform3f(u['uOrmScale'] ?? null, 1, 1, 1);
|
|
5749
6131
|
/* And the emissive map. The scale goes back to one, which is the identity it multiplies by. */
|
|
5750
6132
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
5751
|
-
gl.bindTexture(gl.
|
|
6133
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
5752
6134
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
5753
6135
|
gl.uniform1i(u['uEmissiveMapEnabled'] ?? null, 0);
|
|
5754
6136
|
gl.uniform3f(u['uEmissiveScale'] ?? null, 1, 1, 1);
|
|
@@ -5823,11 +6205,12 @@ export class WebGL2Renderer {
|
|
|
5823
6205
|
*/
|
|
5824
6206
|
tint = null,
|
|
5825
6207
|
/**
|
|
5826
|
-
* Where the mesh was last frame
|
|
6208
|
+
* Where the mesh was last frame — a `Mover` or a matrix — which this backend takes and ignores.
|
|
5827
6209
|
*
|
|
5828
6210
|
* Accepted rather than absent so that one scene draws through both renderers unchanged — the
|
|
5829
6211
|
* 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
|
|
6212
|
+
* here, so there is no motion target for this to be written into, and a mover passed here is
|
|
6213
|
+
* neither read nor advanced.
|
|
5831
6214
|
*/
|
|
5832
6215
|
_previousModel = null) {
|
|
5833
6216
|
if (this.contextLost)
|
|
@@ -6033,7 +6416,7 @@ export class WebGL2Renderer {
|
|
|
6033
6416
|
* The two buffers are two blend states over the same geometry, so the set has to survive until
|
|
6034
6417
|
* the end of the pass. Everything is copied — see `TranslucentQueue`.
|
|
6035
6418
|
*/
|
|
6036
|
-
if (this.oitActive && !this.oitReplaying) {
|
|
6419
|
+
if (this.oitActive && !this.oitReplaying && options.additive !== true) {
|
|
6037
6420
|
this.translucentQueue.record(mesh, model, opacity, options);
|
|
6038
6421
|
return;
|
|
6039
6422
|
}
|
|
@@ -6051,12 +6434,13 @@ export class WebGL2Renderer {
|
|
|
6051
6434
|
* drew with, and only a caller that asks pays for the branch that skips them.
|
|
6052
6435
|
*/
|
|
6053
6436
|
const lit = options.lit ?? true;
|
|
6054
|
-
|
|
6437
|
+
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
6438
|
+
const fog = fogModeOf(options.fog ?? true, options.additive === true);
|
|
6055
6439
|
const toneMapped = options.toneMapped ?? true;
|
|
6056
6440
|
if (!lit)
|
|
6057
6441
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
6058
|
-
if (
|
|
6059
|
-
gl.uniform1i(u['uFogEnabled'] ?? null,
|
|
6442
|
+
if (fog !== FOG_RECEDE)
|
|
6443
|
+
gl.uniform1i(u['uFogEnabled'] ?? null, fog);
|
|
6060
6444
|
/* `1` is sRGB alone: the conversion without the curve, which is what dropping the tone map
|
|
6061
6445
|
means rather than dropping the whole transform. `Math.min` rather than a literal, so a
|
|
6062
6446
|
renderer asked for `none` stays at none and one asked for `srgb` is already there. */
|
|
@@ -6081,19 +6465,7 @@ export class WebGL2Renderer {
|
|
|
6081
6465
|
* translucent one rather than sampling a black texture and painting the pane the colour of a
|
|
6082
6466
|
* hole. That is the defined state the two-backends rule asks for instead of a silent no-op.
|
|
6083
6467
|
*/
|
|
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
|
-
}
|
|
6468
|
+
const refracting = this.bindSeeThrough(u, options);
|
|
6097
6469
|
/*
|
|
6098
6470
|
* **The blend and the depth mask belong to whoever owns the pass.** In the ordinary case that
|
|
6099
6471
|
* is this draw; during an order-independent replay it is `OitPass`, which has set an additive
|
|
@@ -6109,7 +6481,11 @@ export class WebGL2Renderer {
|
|
|
6109
6481
|
const ownsState = !this.oitReplaying;
|
|
6110
6482
|
if (ownsState) {
|
|
6111
6483
|
gl.enable(gl.BLEND);
|
|
6112
|
-
|
|
6484
|
+
/* Added, where asked: colour times alpha onto what is there, and the target's alpha kept. */
|
|
6485
|
+
if (options.additive === true)
|
|
6486
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ZERO, gl.ONE);
|
|
6487
|
+
else
|
|
6488
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
6113
6489
|
}
|
|
6114
6490
|
/*
|
|
6115
6491
|
* The two depth options, both scoped to this draw and handed straight back — the same
|
|
@@ -6143,12 +6519,14 @@ export class WebGL2Renderer {
|
|
|
6143
6519
|
gl.uniform1f(u['uOpacity'] ?? null, 1);
|
|
6144
6520
|
if (tint !== null)
|
|
6145
6521
|
gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
6522
|
+
if (refracting)
|
|
6523
|
+
this.unbindSeeThrough(u);
|
|
6146
6524
|
/* Put back for whatever is drawn next, exactly as `uOpacity` and `uTint` are — a pass
|
|
6147
6525
|
cannot inherit a material from the draw before it. */
|
|
6148
6526
|
if (!lit)
|
|
6149
6527
|
gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
6150
|
-
if (
|
|
6151
|
-
gl.uniform1i(u['uFogEnabled'] ?? null,
|
|
6528
|
+
if (fog !== FOG_RECEDE)
|
|
6529
|
+
gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
|
|
6152
6530
|
if (!toneMapped)
|
|
6153
6531
|
gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
|
|
6154
6532
|
if (own)
|
|
@@ -6479,14 +6857,8 @@ export class WebGL2Renderer {
|
|
|
6479
6857
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
6480
6858
|
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
6481
6859
|
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);
|
|
6860
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.sunShadows?.texture ?? this.emptyTexture2DArray);
|
|
6861
|
+
gl.uniform1i(u['uSunShadows'] ?? null, 0);
|
|
6490
6862
|
}
|
|
6491
6863
|
}
|
|
6492
6864
|
gl.uniform3fv(u['uCameraLocal'] ?? null, cameraLocal);
|