@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,5 +1,6 @@
|
|
|
1
1
|
import { FrameBudget } from '../budget.ts';
|
|
2
2
|
import { MaterialChanges, ownsMaterial } from '../materialChanges.ts';
|
|
3
|
+
import { FOG_RECEDE, fogModeOf } from '../drawFog.ts';
|
|
3
4
|
import { mat4 } from 'gl-matrix';
|
|
4
5
|
import {
|
|
5
6
|
GL_DEPTH_REMAP,
|
|
@@ -13,6 +14,9 @@ import {
|
|
|
13
14
|
glShadowDepthFunc,
|
|
14
15
|
} from '../../depthConvention.ts';
|
|
15
16
|
import type { FrameView } from '../../frameView.ts';
|
|
17
|
+
import type { Mover } from '../../recon/mover.ts';
|
|
18
|
+
import { resolveGlass } from '../../glass.ts';
|
|
19
|
+
import type { GlassOptions, ResolvedGlass } from '../../glass.ts';
|
|
16
20
|
import type { ClipControlExtension } from '../../depthConvention.ts';
|
|
17
21
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
18
22
|
import type { Camera } from '../../camera.ts';
|
|
@@ -46,6 +50,7 @@ import { TranslucentQueue } from '../../translucentQueue.ts';
|
|
|
46
50
|
import { createFrustum, frustumFromViewProjection } from '../../../math/frustum.ts';
|
|
47
51
|
import type { Frustum } from '../../../math/frustum.ts';
|
|
48
52
|
import type { Bounds } from '../../../math/bounds.ts';
|
|
53
|
+
import { batchBoxVisible, cullsInstances } from '../../instanceCull.ts';
|
|
49
54
|
import { OcclusionBuffer } from '../../occlusion.ts';
|
|
50
55
|
import { boundsVisible } from '../../visibility.ts';
|
|
51
56
|
import { createPassRegistry, drainRegistry, passAt, registerIn, unregisterIn } from '../../pass.ts';
|
|
@@ -90,6 +95,10 @@ import {
|
|
|
90
95
|
} from '../../emptyTexture.ts';
|
|
91
96
|
import { LightField } from '../../driftLight/lightField.ts';
|
|
92
97
|
import type { LightFieldOptions, LightFieldSource } from '../../driftLight/lightField.ts';
|
|
98
|
+
import type { DriftLightVolumes } from '../../driftLight/presence.ts';
|
|
99
|
+
import type { DenseLightVolume } from '../../driftLight/denseField.ts';
|
|
100
|
+
import { WorldLightField } from '../../driftLight/worldLightField.ts';
|
|
101
|
+
import type { WorldLightFieldOptions } from '../../driftLight/worldLightField.ts';
|
|
93
102
|
import { createDriftLightUniforms, resolveDriftLight } from '../../driftLight/uniforms.ts';
|
|
94
103
|
|
|
95
104
|
/**
|
|
@@ -126,6 +135,8 @@ import { ProbeSweeps } from '../../probeSweeps.ts';
|
|
|
126
135
|
import { octahedralEdgeFor } from '../../prefilterEnvMap.ts';
|
|
127
136
|
import { EnvironmentPrefilterPass } from '../../prefilterPass.ts';
|
|
128
137
|
import { packIesAtlas } from '../../iesProfile.ts';
|
|
138
|
+
import { fixtureAtlasLayout, fixtureAtlasTexels } from '../../fixtureAtlas.ts';
|
|
139
|
+
import { resolveSurfaceScene } from '../../surfaceScene.ts';
|
|
129
140
|
import type { AreaLightBuffer, AreaLightSource } from '../../areaLights.ts';
|
|
130
141
|
import { MAX_AREA_LIGHTS } from '../../areaLights.ts';
|
|
131
142
|
import {
|
|
@@ -148,12 +159,16 @@ import {
|
|
|
148
159
|
DEPTH_SKINNED_VERT,
|
|
149
160
|
DEPTH_VERT,
|
|
150
161
|
} from '../../shaders/depth.ts';
|
|
151
|
-
import { PointShadowArray } from '../../pointShadowArray.ts';
|
|
152
|
-
import {
|
|
162
|
+
import { OCTAHEDRAL_EDGE, PointShadowArray, strideLayers } from '../../pointShadowArray.ts';
|
|
163
|
+
import { SunShadowArray, type SunShadowLayer } from '../../shadowMap.ts';
|
|
153
164
|
import { POINT_SHADOW_SAMPLER, PointShadowSystem } from '../../pointShadowSystem.ts';
|
|
154
165
|
import type { ShadowLight } from '../../pointShadowSystem.ts';
|
|
166
|
+
import { castsDepth, glassOf } from '../../shadowCasters.ts';
|
|
167
|
+
import { GlassCasterList, type GlassReplaySink } from '../../glassCasters.ts';
|
|
168
|
+
import { SunGlassTint } from '../../sunGlassTint.ts';
|
|
169
|
+
import { GlassTintPrograms, type GlassTintProgram } from './glassTintPrograms.ts';
|
|
155
170
|
import type { SceneCasterMaterial, ShadowCasterSink, ShadowCasters } from '../../shadowCasters.ts';
|
|
156
|
-
import { FACE_COUNT, PointShadowMap } from '../../pointShadowMap.ts';
|
|
171
|
+
import { FACE_COUNT, PointShadowMap, type PointGlassDraw } from '../../pointShadowMap.ts';
|
|
157
172
|
import { createResolvedPointShadows, DEFAULT_SOURCE_RADIUS } from '../../pointShadowImage.ts';
|
|
158
173
|
import {
|
|
159
174
|
createBakeScratch,
|
|
@@ -162,6 +177,8 @@ import {
|
|
|
162
177
|
} from '../../pointShadowBudget.ts';
|
|
163
178
|
import {
|
|
164
179
|
DIRECTIONAL_SHADOW_UNITS,
|
|
180
|
+
SUN_GLASS_TINT_TEXTURE_UNIT,
|
|
181
|
+
POINT_GLASS_TINT_TEXTURE_UNIT,
|
|
165
182
|
LIVE_POINT_SHADOW_MAPS,
|
|
166
183
|
MAX_POINT_LIGHTS,
|
|
167
184
|
NORMAL_TEXTURE_UNIT,
|
|
@@ -173,12 +190,12 @@ import {
|
|
|
173
190
|
ENVIRONMENT_TEXTURE_UNIT,
|
|
174
191
|
SURFACE_TEXTURE_UNIT,
|
|
175
192
|
bindPointLights,
|
|
176
|
-
COOKIE_ATLAS_TEXTURE_UNIT,
|
|
177
193
|
REFRACT_SCENE_TEXTURE_UNIT,
|
|
178
194
|
DRIFT_LIGHT_ATLAS_TEXTURE_UNIT,
|
|
179
195
|
DRIFT_LIGHT_INDEX_TEXTURE_UNIT,
|
|
180
196
|
COOKIE_TILE,
|
|
181
|
-
|
|
197
|
+
FIXTURE_ATLAS_TEXTURE_UNIT,
|
|
198
|
+
SURFACE_EFFECTS_TEXTURE_UNIT,
|
|
182
199
|
bindAreaLights,
|
|
183
200
|
bindAreaShadows,
|
|
184
201
|
openCones,
|
|
@@ -235,7 +252,7 @@ import type { WindStreakOptions } from '../../windStreakRenderer.ts';
|
|
|
235
252
|
import type { WindField } from '../../windField.ts';
|
|
236
253
|
import type { FlockParams } from '../../flockRenderer.ts';
|
|
237
254
|
import { InstancedBatch } from './instanced.ts';
|
|
238
|
-
import type { MeshInstances } from '../../instances.ts';
|
|
255
|
+
import type { InstancedOptions, MeshInstances } from '../../instances.ts';
|
|
239
256
|
import { cutoutOf } from '../../cutoutCaster.ts';
|
|
240
257
|
import type { CutoutCaster } from '../../cutoutCaster.ts';
|
|
241
258
|
import type { SurfaceTextureHandle } from '../api.ts';
|
|
@@ -287,6 +304,29 @@ export interface Environment extends Atmosphere {
|
|
|
287
304
|
* how strongly.
|
|
288
305
|
*/
|
|
289
306
|
nightFactor: number;
|
|
307
|
+
/**
|
|
308
|
+
* Seconds on the caller's clock, for the surfaces that animate — a sign's pulse, a ticker's
|
|
309
|
+
* scroll, a flipbook. The caller's rather than a clock read here, so a held clock holds every
|
|
310
|
+
* surface still and a replay replays them. 0 unless stated.
|
|
311
|
+
*/
|
|
312
|
+
surfaceTime?: number;
|
|
313
|
+
/**
|
|
314
|
+
* How wet the world is, 0 to 1: upward surfaces darken and polish, except materials that say
|
|
315
|
+
* they stay dry. The scene-wide counterpart of `drawFilm`, which stays for a puddle. 0 unless
|
|
316
|
+
* stated.
|
|
317
|
+
*/
|
|
318
|
+
wetness?: number;
|
|
319
|
+
/**
|
|
320
|
+
* The share of facade windows lit, 0 to 1, which a scene sets from its time of day. Windows switch
|
|
321
|
+
* on in steps by a per-window hash, so a rising share lights more windows rather than brightening
|
|
322
|
+
* the ones already lit. 0 unless stated.
|
|
323
|
+
*/
|
|
324
|
+
litWindows?: number;
|
|
325
|
+
/**
|
|
326
|
+
* The share of windows that stay lit however late it gets, 0 to 1, so the small hours are not a
|
|
327
|
+
* dark city. 0 unless stated.
|
|
328
|
+
*/
|
|
329
|
+
lateWindows?: number;
|
|
290
330
|
/**
|
|
291
331
|
* Emission that only shows where the dominant light does not reach, 0 and off by default.
|
|
292
332
|
*
|
|
@@ -547,6 +587,8 @@ export { MAX_DEPTH_LAYER } from '../../depthConvention.ts';
|
|
|
547
587
|
|
|
548
588
|
/** The identity tint, handed back after any draw that asked for one. See `drawMesh`. */
|
|
549
589
|
const WHITE_TINT = new Float32Array([1, 1, 1]);
|
|
590
|
+
/** The lit stage's clip plane when there is no mirror: zero, which clips nothing. */
|
|
591
|
+
const NO_CLIP_PLANE = new Float32Array(4);
|
|
550
592
|
|
|
551
593
|
/**
|
|
552
594
|
* What a film should do beyond shimmering, which until now was nothing.
|
|
@@ -698,6 +740,31 @@ export interface LightVolumeDrawOptions {
|
|
|
698
740
|
* boolean is refused everywhere else in this file.
|
|
699
741
|
*/
|
|
700
742
|
export interface TranslucentMeshOptions {
|
|
743
|
+
/**
|
|
744
|
+
* Add this surface's light to what is behind it instead of covering it: a glow, a beam, a pool
|
|
745
|
+
* of light under a lamp. Its colour times its opacity is added and nothing is darkened, so black
|
|
746
|
+
* is invisible and overlapping glows brighten — which is why such surfaces are authored nearly
|
|
747
|
+
* black where they fade. **Order does not matter** to a sum, so an additive draw is never held
|
|
748
|
+
* for the order-independent pass; it is drawn where it is asked for. Write depth off with it
|
|
749
|
+
* (`depthWrite: false`) or a glow hides the glows behind it.
|
|
750
|
+
*/
|
|
751
|
+
/**
|
|
752
|
+
* Drawn into the picture a reconstruction resolves, rather than after it. For a surface that
|
|
753
|
+
* moves with what it lies on — a glow on a wall, a pane in a window — whose one motion is that
|
|
754
|
+
* surface's, so the resolve can carry it with the surface.
|
|
755
|
+
*
|
|
756
|
+
* **Late is the default, and it is right for anything else.** A translucent surface writes no
|
|
757
|
+
* depth the motion pass could test and may move apart from what shows through it — a caption
|
|
758
|
+
* over a moving car — so a history can only smear it; after the upscale it is sharp and has none.
|
|
759
|
+
* **What late gives up** is the edge: drawn unjittered against a depth the jittered render
|
|
760
|
+
* covered, it meets a silhouette the render drew differently every frame, and a glow along an
|
|
761
|
+
* edge is let through on one frame and not the next. Reconstructed, it is resolved with the edge.
|
|
762
|
+
*
|
|
763
|
+
* Only a reconstructing frame draws anything late, so this changes nothing on WebGL2 or with
|
|
764
|
+
* `reconstruction` off. An order-independent blended set replays where it replays.
|
|
765
|
+
*/
|
|
766
|
+
readonly reconstructed?: boolean;
|
|
767
|
+
readonly additive?: boolean;
|
|
701
768
|
/**
|
|
702
769
|
* `false` draws the mesh exactly its own vertex colour times its own texture, with no
|
|
703
770
|
* ambient, sun, point lights, specular, reflectivity, grain or emissive touching it —
|
|
@@ -780,6 +847,13 @@ export interface TranslucentMeshOptions {
|
|
|
780
847
|
* clear, and it is the whole difference between this and a flat tint.
|
|
781
848
|
*/
|
|
782
849
|
readonly thicknessM?: number;
|
|
850
|
+
/**
|
|
851
|
+
* Glass: the pane shows what is behind it, by how much it lets through and how little it reflects
|
|
852
|
+
* at the view angle, blurred by how frosted it is, tinted, and glowing with the lights behind it.
|
|
853
|
+
* It keeps its own lighting and highlight, which `refraction` alone replaces. Absent, or letting
|
|
854
|
+
* nothing through, is not glass. See `glass.ts`; `refraction` still bends what a pane shows.
|
|
855
|
+
*/
|
|
856
|
+
readonly glass?: GlassOptions;
|
|
783
857
|
/**
|
|
784
858
|
* Whether this draw writes depth. `true`, which is what it has always done, unless a caller
|
|
785
859
|
* says otherwise.
|
|
@@ -1114,11 +1188,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1114
1188
|
private skinPaletteSet = false;
|
|
1115
1189
|
|
|
1116
1190
|
private readonly emptyTexture2D: WebGLTexture;
|
|
1191
|
+
/** One glass resolution reused by every draw, so a pane allocates nothing. See `glass.ts`. */
|
|
1192
|
+
private readonly glassScratch: ResolvedGlass = { transmission: 0, frost: 0, tint: [1, 1, 1] };
|
|
1117
1193
|
/** DriftLight's stand-ins while no field is whole: no brick anywhere, and no light. */
|
|
1118
1194
|
private readonly emptyDriftIndex: WebGLTexture;
|
|
1119
1195
|
private readonly emptyDriftAtlas: WebGLTexture;
|
|
1120
1196
|
/** The scene's DriftLight field, and its two volumes once it is whole. See `createLightField`. */
|
|
1121
|
-
private lightField:
|
|
1197
|
+
private lightField: DriftLightVolumes | null = null;
|
|
1122
1198
|
private driftIndex: WebGLTexture | null = null;
|
|
1123
1199
|
private driftAtlas: WebGLTexture | null = null;
|
|
1124
1200
|
private readonly driftUniforms = createDriftLightUniforms();
|
|
@@ -1165,6 +1241,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1165
1241
|
private oitActive = false;
|
|
1166
1242
|
/** True while the queue is being replayed, so a replayed draw does not record itself again. */
|
|
1167
1243
|
private oitReplaying = false;
|
|
1244
|
+
/** `uSurfaceScene`, resolved once a pass. See `surfaceScene.ts`. */
|
|
1245
|
+
private readonly surfaceScene = new Float32Array(4);
|
|
1246
|
+
/** The two halves of `uWriteMode`, kept here because a `vec2` is written whole. */
|
|
1247
|
+
private oitWeight = 0;
|
|
1248
|
+
private ditherFade = 0;
|
|
1168
1249
|
/** Said once, not per frame, where the context cannot give the float target the sum needs. */
|
|
1169
1250
|
private oitRefused = false;
|
|
1170
1251
|
/** Said once for the whole renderer. See `refuseOitMultisampled`. */
|
|
@@ -1186,6 +1267,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1186
1267
|
refraction: number;
|
|
1187
1268
|
refractTint: Vec3 | undefined;
|
|
1188
1269
|
thicknessM: number;
|
|
1270
|
+
glass: GlassOptions | undefined;
|
|
1189
1271
|
} = {
|
|
1190
1272
|
lit: true,
|
|
1191
1273
|
fog: true,
|
|
@@ -1196,6 +1278,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1196
1278
|
refraction: 0,
|
|
1197
1279
|
refractTint: undefined,
|
|
1198
1280
|
thicknessM: 0,
|
|
1281
|
+
glass: undefined,
|
|
1199
1282
|
};
|
|
1200
1283
|
|
|
1201
1284
|
private motionBlurScale = 1;
|
|
@@ -1268,6 +1351,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1268
1351
|
private readonly taaDepthToViewZ = Float32Array.of(0, 0, 0, 1);
|
|
1269
1352
|
private readonly aoInvProjection = mat4.create();
|
|
1270
1353
|
private readonly aoProjScale = new Float32Array(2);
|
|
1354
|
+
/** Where this frame's jitter put the occlusion, in the composite's uv. See `uAoOffset`. */
|
|
1355
|
+
private readonly aoOffset = new Float32Array(2);
|
|
1271
1356
|
/**
|
|
1272
1357
|
* Whether this frame has already been resolved to the canvas.
|
|
1273
1358
|
*
|
|
@@ -1324,9 +1409,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1324
1409
|
private readonly depthInstancedCutoutUniforms: Record<string, WebGLUniformLocation>;
|
|
1325
1410
|
private readonly scatterDepthProgram: WebGLProgram;
|
|
1326
1411
|
private readonly scatterDepthUniforms: Record<string, WebGLUniformLocation>;
|
|
1327
|
-
|
|
1328
|
-
private readonly
|
|
1329
|
-
private readonly dynamicShadowMap: ShadowMap | null;
|
|
1412
|
+
/** The sun's static, moving and peeled maps, as layers of one array. See `shadowMap.ts`. */
|
|
1413
|
+
private readonly sunShadows: SunShadowArray | null;
|
|
1330
1414
|
private readonly pointShadows: PointShadowSystem<PointShadowMap> | null;
|
|
1331
1415
|
/**
|
|
1332
1416
|
* The layers a world's casting rectangles hold, above the point pool in the same array.
|
|
@@ -1360,12 +1444,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1360
1444
|
* world. See `prepareStaticPointShadows`.
|
|
1361
1445
|
*/
|
|
1362
1446
|
private pointShadowArray: PointShadowArray | null = null;
|
|
1363
|
-
/** The
|
|
1364
|
-
private
|
|
1365
|
-
/**
|
|
1447
|
+
/** The fixture atlas — cookies over profiles — allocated on first use. See `fixtureAtlas.ts`. */
|
|
1448
|
+
private fixtureTexture: WebGLTexture | null = null;
|
|
1449
|
+
/** The profiles as packed, kept so a new set of cookies can rebuild the atlas around them. */
|
|
1450
|
+
private iesAtlas = packIesAtlas([]);
|
|
1451
|
+
/** The cookie images, kept so a new set of profiles can rebuild the atlas around them. */
|
|
1452
|
+
private cookieImages: readonly TexImageSource[] = [];
|
|
1453
|
+
/** Rows of profile in the atlas. One until a consumer loads a profile, which is the row of ones. */
|
|
1366
1454
|
private iesRows = 1;
|
|
1367
|
-
|
|
1368
|
-
/** Tiles in the cookie atlas. 0 is the white placeholder and the whole off path. */
|
|
1455
|
+
/** Cookie tiles in the atlas. 0 is none, and the whole off path. */
|
|
1369
1456
|
private cookieTiles = 0;
|
|
1370
1457
|
/** Horizontal planes each profile occupies. 1 unless something asymmetric was loaded. */
|
|
1371
1458
|
private iesPlanes = 1;
|
|
@@ -1551,7 +1638,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1551
1638
|
/** Non-zero while the drawing buffer is pinned; see `lockDrawingBuffer`. */
|
|
1552
1639
|
private lockedWidth = 0;
|
|
1553
1640
|
private lockedHeight = 0;
|
|
1554
|
-
private
|
|
1641
|
+
private activeSunLayer: SunShadowLayer | null = null;
|
|
1555
1642
|
private reflectionPassActive = false;
|
|
1556
1643
|
private reflectionReadyThisFrame = false;
|
|
1557
1644
|
private reflectionAtmosphereY = 0;
|
|
@@ -1575,7 +1662,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1575
1662
|
*
|
|
1576
1663
|
* **Not whether the map exists.** That was the test here, on the belief that a GL texture is
|
|
1577
1664
|
* born reading as the far plane; it is born zeroed, and a scene that built the layer and never
|
|
1578
|
-
* peeled it sampled an occluder at the light. `
|
|
1665
|
+
* peeled it sampled an occluder at the light. `SunShadowArray` clears itself at birth, so an
|
|
1579
1666
|
* unfilled layer would read as lit either way — this is what keeps the two backends one
|
|
1580
1667
|
* decision, and a frame that does not peel from paying a fetch per tap for nothing.
|
|
1581
1668
|
*/
|
|
@@ -1602,8 +1689,34 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1602
1689
|
* Built once at construction, arrow properties closing over `this`: a sink created
|
|
1603
1690
|
* per pass would allocate six objects per light per frame.
|
|
1604
1691
|
*/
|
|
1692
|
+
/** What the sun's light keeps through glass, made the first time glass casts: `sunGlassTint.ts`. */
|
|
1693
|
+
private sunGlassTint: SunGlassTint | null = null;
|
|
1694
|
+
/** The five programs a pane casts its colour through, compiled the first time glass casts. */
|
|
1695
|
+
private glassTintPrograms: GlassTintPrograms | null = null;
|
|
1696
|
+
/** True while the glass list is replayed into the glass depth layer through `casterSink`. */
|
|
1697
|
+
private drawingGlassDepth = false;
|
|
1698
|
+
/** How many times a shadow array grew its glass layers: once per family, and never again. */
|
|
1699
|
+
private sunGlassRebuilds = 0;
|
|
1700
|
+
/** The light a tint pass is drawn for, and one pane's colour and clarity: uploaded per entry. */
|
|
1701
|
+
private readonly glassLight = new Float32Array(4);
|
|
1702
|
+
private readonly glassPane = new Float32Array(4);
|
|
1703
|
+
/** The glass the open shadow pass was offered, kept for its own passes: `glassCasters.ts`. */
|
|
1704
|
+
private readonly glassCasters = new GlassCasterList();
|
|
1705
|
+
/** One resolution every glass caster is read through, so recording one allocates nothing. */
|
|
1706
|
+
private readonly glassCasterScratch: ResolvedGlass = {
|
|
1707
|
+
transmission: 0,
|
|
1708
|
+
frost: 0,
|
|
1709
|
+
tint: [1, 1, 1],
|
|
1710
|
+
};
|
|
1605
1711
|
private readonly casterSink: ShadowCasterSink = {
|
|
1606
1712
|
mesh: (mesh, model, material) => {
|
|
1713
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1714
|
+
/* Glass: kept aside for its own two passes rather than dropped. See `glassCasters.ts`. */
|
|
1715
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1716
|
+
this.glassCasters.recordMesh(mesh, model, material, this.glassCasterScratch);
|
|
1717
|
+
}
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1607
1720
|
this.shadowDrawBudget.ask();
|
|
1608
1721
|
const { gl } = this;
|
|
1609
1722
|
/* A two-sided caster casts from whichever face the light sees: nothing culled. */
|
|
@@ -1639,6 +1752,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1639
1752
|
* columns the batch's own buffer supplies.
|
|
1640
1753
|
*/
|
|
1641
1754
|
instanced: (batch, data, material) => {
|
|
1755
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1756
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1757
|
+
this.glassCasters.recordInstanced(batch, data, material, this.glassCasterScratch);
|
|
1758
|
+
}
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1642
1761
|
const { gl } = this;
|
|
1643
1762
|
const gpuBatch = batch as InstancedBatch;
|
|
1644
1763
|
const count = Math.min(data.count, gpuBatch.capacity);
|
|
@@ -1650,7 +1769,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1650
1769
|
if (cutout !== null) {
|
|
1651
1770
|
gl.useProgram(this.depthInstancedCutoutProgram);
|
|
1652
1771
|
this.bindCutoutDepth(this.depthInstancedCutoutUniforms, cutout);
|
|
1653
|
-
gpuBatch.
|
|
1772
|
+
gpuBatch.draw(gl, count);
|
|
1654
1773
|
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1655
1774
|
gl.useProgram(this.depthProgram);
|
|
1656
1775
|
this.restoreSurfaceTextureUnit();
|
|
@@ -1661,11 +1780,17 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1661
1780
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, this.activeDepthViewProj);
|
|
1662
1781
|
gl.uniform1i(u['uPeelShadowLayer'] ?? null, this.activeDepthPeel ? 1 : 0);
|
|
1663
1782
|
if (this.activeDepthPeel) gl.uniform1i(u['uPreviousShadowMap'] ?? null, 0);
|
|
1664
|
-
gpuBatch.
|
|
1783
|
+
gpuBatch.draw(gl, count);
|
|
1665
1784
|
if (twoSided && this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
1666
1785
|
gl.useProgram(this.depthProgram);
|
|
1667
1786
|
},
|
|
1668
|
-
skinnedMesh: (mesh, model, palette) => {
|
|
1787
|
+
skinnedMesh: (mesh, model, palette, material) => {
|
|
1788
|
+
if (!this.drawingGlassDepth && !castsDepth(material)) {
|
|
1789
|
+
if (this.quality.glassShadows !== 'off' && glassOf(material, this.glassCasterScratch)) {
|
|
1790
|
+
this.glassCasters.recordSkinned(mesh, model, palette, material, this.glassCasterScratch);
|
|
1791
|
+
}
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1669
1794
|
this.shadowDrawBudget.ask();
|
|
1670
1795
|
const { gl } = this;
|
|
1671
1796
|
const u = this.depthSkinnedUniforms;
|
|
@@ -1748,9 +1873,60 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1748
1873
|
gl.useProgram(this.depthProgram);
|
|
1749
1874
|
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, 0);
|
|
1750
1875
|
gl.uniformMatrix4fv(this.depthUniforms['uLightViewProj'] ?? null, false, faceViewProj);
|
|
1876
|
+
/* A face is an enumeration of its own: its glass is its own. */
|
|
1877
|
+
this.glassCasters.clear();
|
|
1751
1878
|
casters(this.casterSink);
|
|
1879
|
+
/*
|
|
1880
|
+
* The first face to offer glass interleaves the array, before its opaque depth is resolved:
|
|
1881
|
+
* `PointShadowArray.growForGlass`. Nothing baked before this saw glass, so nothing re-bakes.
|
|
1882
|
+
*/
|
|
1883
|
+
if (this.glassCasters.count > 0 && this.quality.glassShadows !== 'off') {
|
|
1884
|
+
const tintEdge =
|
|
1885
|
+
this.quality.glassShadows === 'half' ? OCTAHEDRAL_EDGE >> 1 : OCTAHEDRAL_EDGE;
|
|
1886
|
+
this.pointShadowArray?.growForGlass(gl, tintEdge);
|
|
1887
|
+
}
|
|
1752
1888
|
};
|
|
1753
1889
|
|
|
1890
|
+
/**
|
|
1891
|
+
* A lamp's glass, after each face's opaque depth: `PointShadowMap.bake` asks and binds, this
|
|
1892
|
+
* draws, through the same sinks the sun's glass takes and in the face's own matrix.
|
|
1893
|
+
*/
|
|
1894
|
+
private readonly pointGlassDraw: PointGlassDraw = {
|
|
1895
|
+
offered: () => this.glassCasters.count > 0,
|
|
1896
|
+
draw: (pass, x, y, z) => {
|
|
1897
|
+
const { gl } = this;
|
|
1898
|
+
if (pass === 'depth') {
|
|
1899
|
+
/* Nothing on the unit the depth programs declare a previous map on: see `flushSunGlass`. */
|
|
1900
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
1901
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
1902
|
+
this.activeDepthPeel = false;
|
|
1903
|
+
gl.useProgram(this.depthProgram);
|
|
1904
|
+
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, 0);
|
|
1905
|
+
gl.uniformMatrix4fv(
|
|
1906
|
+
this.depthUniforms['uLightViewProj'] ?? null,
|
|
1907
|
+
false,
|
|
1908
|
+
this.activeDepthViewProj ?? IDENTITY_MAT4,
|
|
1909
|
+
);
|
|
1910
|
+
this.drawingGlassDepth = true;
|
|
1911
|
+
this.glassCasters.replay(this.glassDepthSink);
|
|
1912
|
+
this.drawingGlassDepth = false;
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
this.glassTintPrograms ??= new GlassTintPrograms(gl);
|
|
1916
|
+
/* A place, w 1: the tint takes each pane's angle to the lamp from its own position. */
|
|
1917
|
+
this.glassLight[0] = x;
|
|
1918
|
+
this.glassLight[1] = y;
|
|
1919
|
+
this.glassLight[2] = z;
|
|
1920
|
+
this.glassLight[3] = 1;
|
|
1921
|
+
this.glassCasters.replay(this.glassTintSink);
|
|
1922
|
+
},
|
|
1923
|
+
};
|
|
1924
|
+
|
|
1925
|
+
/** The glass hook a bake is handed: none at all when glass shadows are off. */
|
|
1926
|
+
private get pointGlass(): PointGlassDraw | null {
|
|
1927
|
+
return this.quality.glassShadows === 'off' ? null : this.pointGlassDraw;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1754
1930
|
/**
|
|
1755
1931
|
* Viewport in CSS pixels rather than device pixels.
|
|
1756
1932
|
*
|
|
@@ -1883,6 +2059,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1883
2059
|
return this.occlusion?.occluded(bounds, model) ?? false;
|
|
1884
2060
|
}
|
|
1885
2061
|
|
|
2062
|
+
/**
|
|
2063
|
+
* Whether a world-space box is wholly behind the declared occluders — the tighter question for
|
|
2064
|
+
* something that is a box, such as a region. False with no occlusion buffer. See `occluded`.
|
|
2065
|
+
*/
|
|
2066
|
+
occludedBox(min: ArrayLike<number>, max: ArrayLike<number>): boolean {
|
|
2067
|
+
return this.occlusion?.occludedBox(min, max) ?? false;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
1886
2070
|
/* -- Registered passes ---------------------------------------------------------------- */
|
|
1887
2071
|
|
|
1888
2072
|
private readonly passes: PassRegistry = createPassRegistry();
|
|
@@ -2073,106 +2257,67 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2073
2257
|
* `createSurfaceTexture` and the material maps take.
|
|
2074
2258
|
*/
|
|
2075
2259
|
setSpotCookies(images: readonly TexImageSource[]): void {
|
|
2076
|
-
|
|
2077
|
-
this.
|
|
2078
|
-
if (this.cookieTexture === null) this.cookieTexture = gl.createTexture();
|
|
2079
|
-
gl.activeTexture(gl.TEXTURE0 + COOKIE_ATLAS_TEXTURE_UNIT);
|
|
2080
|
-
gl.bindTexture(gl.TEXTURE_2D, this.cookieTexture);
|
|
2081
|
-
if (images.length === 0) {
|
|
2082
|
-
/* White, not black: this multiplies a light's colour. */
|
|
2083
|
-
gl.texImage2D(
|
|
2084
|
-
gl.TEXTURE_2D,
|
|
2085
|
-
0,
|
|
2086
|
-
gl.RGBA8,
|
|
2087
|
-
1,
|
|
2088
|
-
1,
|
|
2089
|
-
0,
|
|
2090
|
-
gl.RGBA,
|
|
2091
|
-
gl.UNSIGNED_BYTE,
|
|
2092
|
-
new Uint8Array([255, 255, 255, 255]),
|
|
2093
|
-
);
|
|
2094
|
-
} else {
|
|
2095
|
-
gl.texImage2D(
|
|
2096
|
-
gl.TEXTURE_2D,
|
|
2097
|
-
0,
|
|
2098
|
-
gl.RGBA8,
|
|
2099
|
-
COOKIE_TILE * images.length,
|
|
2100
|
-
COOKIE_TILE,
|
|
2101
|
-
0,
|
|
2102
|
-
gl.RGBA,
|
|
2103
|
-
gl.UNSIGNED_BYTE,
|
|
2104
|
-
null,
|
|
2105
|
-
);
|
|
2106
|
-
for (let tile = 0; tile < images.length; tile++) {
|
|
2107
|
-
const image = images[tile];
|
|
2108
|
-
if (image === undefined) continue;
|
|
2109
|
-
gl.texSubImage2D(gl.TEXTURE_2D, 0, tile * COOKIE_TILE, 0, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
/* Linear and clamped: a cookie is an image, and the shader's own inset is what keeps a tile
|
|
2113
|
-
from reaching its neighbour — clamping alone cannot, the seam being interior to the row. */
|
|
2114
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2115
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2116
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2117
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2118
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
2260
|
+
this.cookieImages = images.slice();
|
|
2261
|
+
this.buildFixtureAtlas();
|
|
2119
2262
|
}
|
|
2120
2263
|
|
|
2121
2264
|
setIesProfiles(profiles: readonly PhotometricProfile[]): void {
|
|
2265
|
+
this.iesAtlas = packIesAtlas(profiles);
|
|
2266
|
+
this.buildFixtureAtlas();
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
/**
|
|
2270
|
+
* The fixture atlas from the profiles and cookies in hand: the profiles' rows at the bottom, the
|
|
2271
|
+
* cookies' tiles on top, one `RGBA16F` texture. See `fixtureAtlas.ts` for the layout.
|
|
2272
|
+
*
|
|
2273
|
+
* **Half float, matching what the other backend must use**: a 32-bit float texture is not
|
|
2274
|
+
* filterable in core WebGPU, and both tables are curves and images that want linear filtering.
|
|
2275
|
+
* Linear and clamped: a profile is sampled between two angles, and a cookie's own inset is what
|
|
2276
|
+
* keeps a tile from its neighbour — clamping alone cannot, the seam being interior to the row.
|
|
2277
|
+
*/
|
|
2278
|
+
private buildFixtureAtlas(): void {
|
|
2122
2279
|
const { gl } = this;
|
|
2123
|
-
const
|
|
2124
|
-
if (this.
|
|
2125
|
-
gl.activeTexture(gl.TEXTURE0 +
|
|
2126
|
-
gl.bindTexture(gl.TEXTURE_2D, this.
|
|
2127
|
-
/*
|
|
2128
|
-
* `LINEAR` across a row and clamped, unlike the froxel table's `NEAREST`. This one *is* a
|
|
2129
|
-
* curve rather than data addressed by index: interpolating between two columns is sampling
|
|
2130
|
-
* the distribution between two angles, which is exactly what is wanted, and clamping stops a
|
|
2131
|
-
* row bleeding into its neighbour at the edges.
|
|
2132
|
-
*/
|
|
2133
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2134
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2135
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2136
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2137
|
-
/*
|
|
2138
|
-
* One channel, because a row holds one number per angle and the other three would be three
|
|
2139
|
-
* quarters of the upload spent on nothing — and **half float rather than full**, matching what
|
|
2140
|
-
* the other backend must use: a 32-bit float texture is not filterable in core WebGPU, and this
|
|
2141
|
-
* row is a curve that wants linear filtering. Keeping the two formats the same is the
|
|
2142
|
-
* 2026-08-13 rule rather than tidiness; the driver converts the float data on the way in.
|
|
2143
|
-
*/
|
|
2280
|
+
const layout = fixtureAtlasLayout(this.iesAtlas.height, this.cookieImages.length);
|
|
2281
|
+
if (this.fixtureTexture === null) this.fixtureTexture = gl.createTexture();
|
|
2282
|
+
gl.activeTexture(gl.TEXTURE0 + FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
2283
|
+
gl.bindTexture(gl.TEXTURE_2D, this.fixtureTexture);
|
|
2144
2284
|
gl.texImage2D(
|
|
2145
2285
|
gl.TEXTURE_2D,
|
|
2146
2286
|
0,
|
|
2147
|
-
gl.
|
|
2148
|
-
|
|
2149
|
-
|
|
2287
|
+
gl.RGBA16F,
|
|
2288
|
+
layout.width,
|
|
2289
|
+
layout.height,
|
|
2150
2290
|
0,
|
|
2151
|
-
gl.
|
|
2291
|
+
gl.RGBA,
|
|
2152
2292
|
gl.FLOAT,
|
|
2153
|
-
|
|
2293
|
+
fixtureAtlasTexels(layout, this.iesAtlas),
|
|
2154
2294
|
);
|
|
2155
|
-
|
|
2156
|
-
|
|
2295
|
+
for (let tile = 0; tile < this.cookieImages.length; tile++) {
|
|
2296
|
+
const image = this.cookieImages[tile];
|
|
2297
|
+
if (image === undefined) continue;
|
|
2298
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, tile * COOKIE_TILE, 0, gl.RGBA, gl.HALF_FLOAT, image);
|
|
2299
|
+
}
|
|
2300
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
2301
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
2302
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
2303
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
2304
|
+
this.iesRows = this.iesAtlas.height;
|
|
2305
|
+
this.iesPlanes = this.iesAtlas.planes;
|
|
2306
|
+
this.cookieTiles = this.cookieImages.length;
|
|
2157
2307
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
2308
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
2158
2309
|
}
|
|
2159
2310
|
|
|
2160
|
-
/** Bind the atlas and say
|
|
2311
|
+
/** Bind the fixture atlas and say what is in it. Always, for the reason `bindClusters` gives. */
|
|
2161
2312
|
private bindIesAtlas(u: Record<string, WebGLUniformLocation>): void {
|
|
2162
2313
|
const { gl } = this;
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
gl.
|
|
2166
|
-
gl.
|
|
2314
|
+
/* Built on demand rather than at construction, for the reason the grade's placeholder is. */
|
|
2315
|
+
if (this.fixtureTexture === null) this.buildFixtureAtlas();
|
|
2316
|
+
gl.activeTexture(gl.TEXTURE0 + FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
2317
|
+
gl.bindTexture(gl.TEXTURE_2D, this.fixtureTexture);
|
|
2318
|
+
gl.uniform1i(u['uFixtureAtlas'] ?? null, FIXTURE_ATLAS_TEXTURE_UNIT);
|
|
2167
2319
|
gl.uniform1f(u['uIesAtlasRows'] ?? null, this.iesRows);
|
|
2168
2320
|
gl.uniform1f(u['uIesPlaneCount'] ?? null, this.iesPlanes);
|
|
2169
|
-
/* Built on demand rather than at construction, for the reason the grade's placeholder is: a
|
|
2170
|
-
scene that never projects a cookie should not carry a texture, and a declared sampler still
|
|
2171
|
-
needs a complete one. */
|
|
2172
|
-
if (this.cookieTexture === null) this.setSpotCookies([]);
|
|
2173
|
-
gl.activeTexture(gl.TEXTURE0 + COOKIE_ATLAS_TEXTURE_UNIT);
|
|
2174
|
-
gl.bindTexture(gl.TEXTURE_2D, this.cookieTexture);
|
|
2175
|
-
gl.uniform1i(u['uCookieAtlas'] ?? null, COOKIE_ATLAS_TEXTURE_UNIT);
|
|
2176
2321
|
gl.uniform1f(u['uCookieTiles'] ?? null, this.cookieTiles);
|
|
2177
2322
|
gl.activeTexture(gl.TEXTURE0);
|
|
2178
2323
|
}
|
|
@@ -2716,20 +2861,18 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2716
2861
|
gl.uniform1i(uniforms['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
2717
2862
|
}
|
|
2718
2863
|
gl.useProgram(null);
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
this.dynamicShadowMap = this.quality.directionalShadows
|
|
2732
|
-
? new ShadowMap(gl, this.quality.directionalShadowMapSize)
|
|
2864
|
+
/*
|
|
2865
|
+
* The static world, its movers and — where more than one static depth layer is asked for —
|
|
2866
|
+
* the peel, as layers of one array. Movers get a layer of their own so a moving and a static
|
|
2867
|
+
* caster that overlap keep both distance fades: their transmissions multiply rather than one
|
|
2868
|
+
* depth replacing the other. The peel keeps the second static occluder the same way.
|
|
2869
|
+
*/
|
|
2870
|
+
this.sunShadows = this.quality.directionalShadows
|
|
2871
|
+
? new SunShadowArray(
|
|
2872
|
+
gl,
|
|
2873
|
+
this.quality.directionalShadowMapSize,
|
|
2874
|
+
this.quality.directionalShadowDepthLayers > 1,
|
|
2875
|
+
)
|
|
2733
2876
|
: null;
|
|
2734
2877
|
// Static maps are allocated once the world reports its light count. No
|
|
2735
2878
|
// cubemap resources exist at all when the quality profile disables them.
|
|
@@ -2921,16 +3064,15 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
2921
3064
|
* every one of them, per renderer, for as long as it lived; `renderer.test.ts` counts them.
|
|
2922
3065
|
*/
|
|
2923
3066
|
this.sceneTarget?.dispose();
|
|
2924
|
-
this.
|
|
2925
|
-
this.
|
|
2926
|
-
this.
|
|
3067
|
+
this.sunShadows?.dispose(gl);
|
|
3068
|
+
this.sunGlassTint?.dispose(gl);
|
|
3069
|
+
this.glassTintPrograms?.dispose(gl);
|
|
2927
3070
|
this.planarReflection?.dispose(gl);
|
|
2928
3071
|
this.pointShadows?.dispose();
|
|
2929
3072
|
gl.deleteVertexArray(this.panelVao);
|
|
2930
3073
|
gl.deleteVertexArray(this.skyVao);
|
|
2931
3074
|
if (this.panelBuffer !== null) gl.deleteBuffer(this.panelBuffer);
|
|
2932
|
-
if (this.
|
|
2933
|
-
if (this.iesTexture !== null) gl.deleteTexture(this.iesTexture);
|
|
3075
|
+
if (this.fixtureTexture !== null) gl.deleteTexture(this.fixtureTexture);
|
|
2934
3076
|
if (this.clusterTexture !== null) gl.deleteTexture(this.clusterTexture);
|
|
2935
3077
|
}
|
|
2936
3078
|
|
|
@@ -3087,6 +3229,19 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3087
3229
|
return new SurfaceTexture(this.gl, source, options);
|
|
3088
3230
|
}
|
|
3089
3231
|
|
|
3232
|
+
/**
|
|
3233
|
+
* Upload several images of one size as the layers of one array, for a merged mesh that names a
|
|
3234
|
+
* layer per vertex (`MeshData.layers`). One material binding then covers every image, which is
|
|
3235
|
+
* what makes a block wearing forty facades one draw. Every layer must be the same size, and the
|
|
3236
|
+
* refusal names the first that is not.
|
|
3237
|
+
*/
|
|
3238
|
+
createSurfaceTextureArray(
|
|
3239
|
+
sources: readonly TexImageSource[],
|
|
3240
|
+
options: SurfaceTextureOptions = {},
|
|
3241
|
+
): SurfaceTexture {
|
|
3242
|
+
return new SurfaceTexture(this.gl, sources, options);
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3090
3245
|
/**
|
|
3091
3246
|
* Replace a surface texture's pixels, keeping the GPU object and its sampler state.
|
|
3092
3247
|
*
|
|
@@ -3204,7 +3359,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3204
3359
|
* page-faulted an RDNA4 card and wedged the device. See `emptyTexture.ts`.
|
|
3205
3360
|
*/
|
|
3206
3361
|
gl.activeTexture(gl.TEXTURE0 + NORMAL_TEXTURE_UNIT);
|
|
3207
|
-
gl.bindTexture(gl.
|
|
3362
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3208
3363
|
if (normal !== null) normal.bind(gl, NORMAL_TEXTURE_UNIT);
|
|
3209
3364
|
gl.uniform1i(u['uNormalMap'] ?? null, NORMAL_TEXTURE_UNIT);
|
|
3210
3365
|
gl.uniform1f(
|
|
@@ -3225,7 +3380,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3225
3380
|
*/
|
|
3226
3381
|
const orm = material?.orm ?? null;
|
|
3227
3382
|
gl.activeTexture(gl.TEXTURE0 + ORM_TEXTURE_UNIT);
|
|
3228
|
-
gl.bindTexture(gl.
|
|
3383
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3229
3384
|
if (orm !== null) orm.bind(gl, ORM_TEXTURE_UNIT);
|
|
3230
3385
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
3231
3386
|
gl.uniform1i(u['uOrmEnabled'] ?? null, orm === null ? 0 : 1);
|
|
@@ -3248,7 +3403,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3248
3403
|
*/
|
|
3249
3404
|
const emissiveMap = material?.emissive ?? null;
|
|
3250
3405
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
3251
|
-
gl.bindTexture(gl.
|
|
3406
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3252
3407
|
if (emissiveMap !== null) emissiveMap.bind(gl, EMISSIVE_TEXTURE_UNIT);
|
|
3253
3408
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
3254
3409
|
gl.uniform1i(u['uEmissiveMapEnabled'] ?? null, emissiveMap === null ? 0 : 1);
|
|
@@ -3270,7 +3425,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3270
3425
|
gl.uniform2f(u['uUvScale'] ?? null, material?.uScale ?? 1, material?.vScale ?? 1);
|
|
3271
3426
|
gl.uniform1i(u['uDoubleSided'] ?? null, material?.doubleSided === true ? 1 : 0);
|
|
3272
3427
|
|
|
3428
|
+
/* The albedo's effects table, or the stand-in the shader reads as none. Before the early
|
|
3429
|
+
return, so a material with no albedo still leaves a complete texture on the unit. */
|
|
3430
|
+
if (albedo === null) {
|
|
3431
|
+
gl.activeTexture(gl.TEXTURE0 + SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
3432
|
+
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
3433
|
+
} else {
|
|
3434
|
+
albedo.bindEffects(gl, SURFACE_EFFECTS_TEXTURE_UNIT, this.emptyTexture2D);
|
|
3435
|
+
}
|
|
3436
|
+
gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
3273
3437
|
if (albedo === null) {
|
|
3438
|
+
/* The array stand-in, for the reason the maps above take one: a sampler reads its unit's
|
|
3439
|
+
array target, and with nothing there it is incomplete. */
|
|
3440
|
+
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
3441
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3274
3442
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
3275
3443
|
return;
|
|
3276
3444
|
}
|
|
@@ -3307,7 +3475,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3307
3475
|
return;
|
|
3308
3476
|
}
|
|
3309
3477
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
3310
|
-
gl.bindTexture(gl.
|
|
3478
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3311
3479
|
}
|
|
3312
3480
|
|
|
3313
3481
|
/**
|
|
@@ -3335,8 +3503,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3335
3503
|
setSurfaceReflectivity(amount: number): void {
|
|
3336
3504
|
if (this.contextLost) return;
|
|
3337
3505
|
this.materials.dirty();
|
|
3338
|
-
this.
|
|
3339
|
-
this.gl.uniform1f(this.flatUniforms['uReflectivity'] ?? null, Math.min(1, Math.max(0, amount)));
|
|
3506
|
+
this.writeFlatFloat('uReflectivity', Math.min(1, Math.max(0, amount)));
|
|
3340
3507
|
}
|
|
3341
3508
|
|
|
3342
3509
|
/**
|
|
@@ -3354,8 +3521,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3354
3521
|
setEnvironmentGain(gain: number): void {
|
|
3355
3522
|
if (this.contextLost) return;
|
|
3356
3523
|
this.materials.dirty();
|
|
3357
|
-
this.
|
|
3358
|
-
this.gl.uniform1f(this.flatUniforms['uEnvironmentGain'] ?? null, Math.max(0, gain));
|
|
3524
|
+
this.writeFlatFloat('uEnvironmentGain', Math.max(0, gain));
|
|
3359
3525
|
}
|
|
3360
3526
|
|
|
3361
3527
|
/**
|
|
@@ -3382,10 +3548,55 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3382
3548
|
}
|
|
3383
3549
|
|
|
3384
3550
|
setSurfaceGrain(amount: number): void {
|
|
3551
|
+
if (this.contextLost) return;
|
|
3552
|
+
this.materials.dirty();
|
|
3553
|
+
this.writeFlatFloat('uGrain', Math.min(1, Math.max(0, amount)));
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
/**
|
|
3557
|
+
* One float of material state, into every flat program.
|
|
3558
|
+
*
|
|
3559
|
+
* **Every one, for `setMaterial`'s reason**: an instanced or skinned draw runs a program of its
|
|
3560
|
+
* own and is entitled to the material its caller set before it. These setters wrote the plain
|
|
3561
|
+
* program alone until 2026-09-30, so on this backend a batch drew with the pass defaults — grain
|
|
3562
|
+
* full, no reflection, no relief — while the other backend, whose material block every variant
|
|
3563
|
+
* reads, drew what was asked.
|
|
3564
|
+
*/
|
|
3565
|
+
private writeFlatFloat(name: string, value: number): void {
|
|
3566
|
+
for (const target of this.flatTargets) {
|
|
3567
|
+
this.gl.useProgram(target.program);
|
|
3568
|
+
this.gl.uniform1f(target.uniforms[name] ?? null, value);
|
|
3569
|
+
}
|
|
3570
|
+
this.useFlatProgram();
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3573
|
+
/**
|
|
3574
|
+
* How much of the surfaces drawn next survives a screen-door dither, for crossfading two levels
|
|
3575
|
+
* of detail of one thing.
|
|
3576
|
+
*
|
|
3577
|
+
* 0 is off and is what `bindMeshPass` restores. A positive amount keeps that share of the
|
|
3578
|
+
* pattern's cells, a negative one keeps the complement, so the level fading in drawn at `t` and
|
|
3579
|
+
* the one fading out at `-t` cover every pixel exactly once — no hole, no pixel drawn twice.
|
|
3580
|
+
* Material state like `setSurfaceGrain`; instanced draws read it too, and shadow casters, which
|
|
3581
|
+
* are a pass of their own, do not. See `uDither` in `flat/preamble.ts` for what it costs.
|
|
3582
|
+
*/
|
|
3583
|
+
setDitherFade(amount: number): void {
|
|
3385
3584
|
if (this.contextLost) return;
|
|
3386
3585
|
this.materials.dirty();
|
|
3387
3586
|
this.useFlatProgram();
|
|
3388
|
-
this.
|
|
3587
|
+
this.ditherFade = Math.min(1, Math.max(-1, amount));
|
|
3588
|
+
/* Every flat program, as `setMaterial` writes them: an instanced or skinned draw is entitled
|
|
3589
|
+
to the fade the caller set before it, and a second program holding none draws whole. */
|
|
3590
|
+
for (const target of this.flatTargets) {
|
|
3591
|
+
this.gl.useProgram(target.program);
|
|
3592
|
+
this.writeMode(target.uniforms);
|
|
3593
|
+
}
|
|
3594
|
+
this.useFlatProgram();
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
/** `uWriteMode`: the order-independent weight and the dither, which share one uniform row. */
|
|
3598
|
+
private writeMode(u: Record<string, WebGLUniformLocation>): void {
|
|
3599
|
+
this.gl.uniform2f(u['uWriteMode'] ?? null, this.oitWeight, this.ditherFade);
|
|
3389
3600
|
}
|
|
3390
3601
|
|
|
3391
3602
|
/**
|
|
@@ -3414,10 +3625,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3414
3625
|
setSurfaceRelief(amount: number, cyclesPerMetre = 60): void {
|
|
3415
3626
|
if (this.contextLost) return;
|
|
3416
3627
|
this.materials.dirty();
|
|
3417
|
-
this.
|
|
3418
|
-
|
|
3419
|
-
this.gl.uniform1f(u['uRelief'] ?? null, Math.min(1, Math.max(0, amount)));
|
|
3420
|
-
this.gl.uniform1f(u['uReliefCycles'] ?? null, Math.max(0.01, cyclesPerMetre));
|
|
3628
|
+
this.writeFlatFloat('uRelief', Math.min(1, Math.max(0, amount)));
|
|
3629
|
+
this.writeFlatFloat('uReliefCycles', Math.max(0.01, cyclesPerMetre));
|
|
3421
3630
|
}
|
|
3422
3631
|
|
|
3423
3632
|
/**
|
|
@@ -3448,11 +3657,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3448
3657
|
setSurfaceTextureRelief(scale: number): void {
|
|
3449
3658
|
if (this.contextLost) return;
|
|
3450
3659
|
this.materials.dirty();
|
|
3451
|
-
this.
|
|
3452
|
-
this.gl.uniform1f(
|
|
3453
|
-
this.flatUniforms['uTextureRelief'] ?? null,
|
|
3454
|
-
Number.isFinite(scale) ? scale : 0,
|
|
3455
|
-
);
|
|
3660
|
+
this.writeFlatFloat('uTextureRelief', Number.isFinite(scale) ? scale : 0);
|
|
3456
3661
|
}
|
|
3457
3662
|
|
|
3458
3663
|
/**
|
|
@@ -3479,8 +3684,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3479
3684
|
setEmissiveGain(gain: number): void {
|
|
3480
3685
|
if (this.contextLost) return;
|
|
3481
3686
|
this.materials.dirty();
|
|
3482
|
-
this.
|
|
3483
|
-
this.gl.uniform1f(this.flatUniforms['uEmissiveGain'] ?? null, gain);
|
|
3687
|
+
this.writeFlatFloat('uEmissiveGain', gain);
|
|
3484
3688
|
}
|
|
3485
3689
|
|
|
3486
3690
|
/* -- Instanced meshes --------------------------------------------------------------- */
|
|
@@ -3528,7 +3732,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3528
3732
|
* One batch per mesh: the attributes bind to the mesh's own vertex array, of which a mesh has
|
|
3529
3733
|
* one. `Mesh.attachInstances` is what refuses a second, and says why.
|
|
3530
3734
|
*/
|
|
3531
|
-
createInstanced(mesh: Mesh, capacity: number): InstancedBatch {
|
|
3735
|
+
createInstanced(mesh: Mesh, capacity: number, options: InstancedOptions = {}): InstancedBatch {
|
|
3532
3736
|
/* The same refusal as the other backend, for the same reason: the instanced attributes take
|
|
3533
3737
|
the two locations a skinned mesh's joints and weights already occupy. */
|
|
3534
3738
|
if (mesh.isSkinned) {
|
|
@@ -3538,7 +3742,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3538
3742
|
);
|
|
3539
3743
|
}
|
|
3540
3744
|
this.ensureInstancedProgram();
|
|
3541
|
-
return new InstancedBatch(this.gl, mesh, capacity);
|
|
3745
|
+
return new InstancedBatch(this.gl, mesh, capacity, options.cull ?? false);
|
|
3542
3746
|
}
|
|
3543
3747
|
|
|
3544
3748
|
/** Push placement and colour. Only the live prefix. */
|
|
@@ -3593,6 +3797,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3593
3797
|
if (!mesh.complete) return;
|
|
3594
3798
|
const count = Math.min(data.count, batch.capacity);
|
|
3595
3799
|
if (count === 0) return;
|
|
3800
|
+
/* A culling batch's camera draw: the whole batch against the view and the occluders first,
|
|
3801
|
+
then instance by instance where it survives. Blended draws keep every instance and their
|
|
3802
|
+
order. See `InstancedOptions.cull`. */
|
|
3803
|
+
const culling = !blend && batch.cull;
|
|
3804
|
+
if (culling && !batchBoxVisible(batch.box, this.frustum, this.occlusion)) return;
|
|
3596
3805
|
this.drawBudget.ask();
|
|
3597
3806
|
this.ensureInstancedProgram();
|
|
3598
3807
|
const program = this.flatInstancedProgram;
|
|
@@ -3604,10 +3813,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3604
3813
|
gl.uniform1i(u['uHasTangents'] ?? null, mesh.hasTangents ? 1 : 0);
|
|
3605
3814
|
|
|
3606
3815
|
const lit = options.lit ?? true;
|
|
3607
|
-
|
|
3816
|
+
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
3817
|
+
const fog = fogModeOf(options.fog ?? true, blend && options.additive === true);
|
|
3608
3818
|
const toneMapped = options.toneMapped ?? true;
|
|
3609
|
-
/*
|
|
3610
|
-
|
|
3819
|
+
/* A blended batch shows what is behind it — refraction or glass — as a single draw does, and
|
|
3820
|
+
counts a material of its own for it by the rule both backends share (`materialChanges.ts`). */
|
|
3821
|
+
const refracting = blend && this.bindSeeThrough(u, options);
|
|
3822
|
+
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting });
|
|
3611
3823
|
if (own) this.materials.dirty();
|
|
3612
3824
|
this.takeMaterial();
|
|
3613
3825
|
const depthWrite = options.depthWrite ?? true;
|
|
@@ -3627,27 +3839,18 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3627
3839
|
* **A null snapshot leaves the strength at zero**, so the draw shades as an ordinary
|
|
3628
3840
|
* translucent one rather than sampling a black texture and painting the pane the colour of a
|
|
3629
3841
|
* hole. That is the defined state the two-backends rule asks for instead of a silent no-op.
|
|
3842
|
+
* The copy is bound above, before the material is counted, because whether it was taken is
|
|
3843
|
+
* part of what the count asks.
|
|
3630
3844
|
*/
|
|
3631
|
-
const refraction = options.refraction ?? 0;
|
|
3632
|
-
let refracting = false;
|
|
3633
|
-
if (refraction > 0) {
|
|
3634
|
-
const snapshot = this.sceneTarget?.snapshotColor() ?? null;
|
|
3635
|
-
if (snapshot !== null) {
|
|
3636
|
-
refracting = true;
|
|
3637
|
-
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
3638
|
-
gl.bindTexture(gl.TEXTURE_2D, snapshot);
|
|
3639
|
-
gl.uniform1f(u['uRefractStrength'] ?? null, refraction);
|
|
3640
|
-
gl.uniform3fv(u['uRefractTint'] ?? null, options.refractTint ?? WHITE_TINT);
|
|
3641
|
-
gl.uniform1f(u['uRefractThickness'] ?? null, options.thicknessM ?? 0);
|
|
3642
|
-
}
|
|
3643
|
-
}
|
|
3644
3845
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
3645
|
-
if (
|
|
3846
|
+
if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, fog);
|
|
3646
3847
|
if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, Math.min(this.gradeCode(), 1));
|
|
3647
3848
|
|
|
3648
3849
|
if (blend) {
|
|
3649
3850
|
gl.enable(gl.BLEND);
|
|
3650
|
-
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.
|
|
3851
|
+
if (options.additive === true) gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ZERO, gl.ONE);
|
|
3852
|
+
else
|
|
3853
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
3651
3854
|
if (!depthWrite) gl.depthMask(false);
|
|
3652
3855
|
}
|
|
3653
3856
|
/* The sign follows the compare, which `depthOffsetForLayer` owns — see `drawMesh`, where the
|
|
@@ -3658,7 +3861,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3658
3861
|
gl.polygonOffset(offset.slope, offset.units);
|
|
3659
3862
|
}
|
|
3660
3863
|
|
|
3661
|
-
|
|
3864
|
+
/* Both sides of a double-sided material, as every mesh path does and WebGPU's pipeline does
|
|
3865
|
+
for both: without it a batch's far faces were culled here alone. */
|
|
3866
|
+
if (this.materialDoubleSided) gl.disable(gl.CULL_FACE);
|
|
3867
|
+
if (culling && cullsInstances(count, mesh.indexCount)) batch.drawCulled(gl, data, this.frustum);
|
|
3868
|
+
else batch.draw(gl, count);
|
|
3869
|
+
if (this.materialDoubleSided) gl.enable(gl.CULL_FACE);
|
|
3662
3870
|
|
|
3663
3871
|
if (layer > 0) {
|
|
3664
3872
|
gl.polygonOffset(0, 0);
|
|
@@ -3673,18 +3881,61 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
3673
3881
|
if (opacity < 1) gl.uniform1f(u['uOpacity'] ?? null, 1);
|
|
3674
3882
|
/* The binding is released with the uniform, which is the 2026-08-27 rule: a WebGL2 binding
|
|
3675
3883
|
outlives its frame, so a snapshot left on unit 14 is held against the next frame's copy. */
|
|
3676
|
-
if (refracting)
|
|
3677
|
-
gl.uniform1f(u['uRefractStrength'] ?? null, 0);
|
|
3678
|
-
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
3679
|
-
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
3680
|
-
}
|
|
3884
|
+
if (refracting) this.unbindSeeThrough(u);
|
|
3681
3885
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
3682
|
-
if (
|
|
3886
|
+
if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
|
|
3683
3887
|
if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
|
|
3684
3888
|
if (own) this.materials.dirty();
|
|
3685
3889
|
this.useFlatProgram();
|
|
3686
3890
|
}
|
|
3687
3891
|
|
|
3892
|
+
/**
|
|
3893
|
+
* Bind what a translucent draw shows through it: the frame's copy, refraction's bend and absorption,
|
|
3894
|
+
* and glass. Returns whether the copy is bound, and so whether `unbindSeeThrough` owes it back.
|
|
3895
|
+
*
|
|
3896
|
+
* **A glass draw takes the copy even when it does not bend**, since a pane shows what is behind it
|
|
3897
|
+
* either way, and asks for the copy's mip chain, which a frosted pane reads blurred. A null copy
|
|
3898
|
+
* leaves both off, so the pane shades as ordinary translucency rather than sampling black.
|
|
3899
|
+
*
|
|
3900
|
+
* **Refused inside a probe's face or a mirror**, as WebGPU's `takeRefractSnapshot` refuses: the
|
|
3901
|
+
* copy is of the frame drawn to the screen, so a pane there would show the wrong picture, and
|
|
3902
|
+
* taking it mid-bake is how a probe's next face came to be attached to the scene's framebuffer.
|
|
3903
|
+
*/
|
|
3904
|
+
private bindSeeThrough(
|
|
3905
|
+
u: Record<string, WebGLUniformLocation>,
|
|
3906
|
+
options: TranslucentMeshOptions,
|
|
3907
|
+
): boolean {
|
|
3908
|
+
const { gl } = this;
|
|
3909
|
+
const refraction = options.refraction ?? 0;
|
|
3910
|
+
const glassy = resolveGlass(options.glass, this.glassScratch);
|
|
3911
|
+
if (refraction <= 0 && !glassy) return false;
|
|
3912
|
+
if (this.probePassActive || this.reflectionPassActive) return false;
|
|
3913
|
+
const snapshot = this.sceneTarget?.snapshotColor(false, glassy) ?? null;
|
|
3914
|
+
if (snapshot === null) return false;
|
|
3915
|
+
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
3916
|
+
gl.bindTexture(gl.TEXTURE_2D, snapshot);
|
|
3917
|
+
const glass = this.glassScratch;
|
|
3918
|
+
/* Strength, path thickness, transmission, frost: one vector, for the budget the shader states. */
|
|
3919
|
+
gl.uniform4f(
|
|
3920
|
+
u['uSeeThrough'] ?? null,
|
|
3921
|
+
Math.max(refraction, 0),
|
|
3922
|
+
options.thicknessM ?? 0,
|
|
3923
|
+
glass.transmission,
|
|
3924
|
+
glass.frost,
|
|
3925
|
+
);
|
|
3926
|
+
gl.uniform3fv(u['uRefractTint'] ?? null, options.refractTint ?? WHITE_TINT);
|
|
3927
|
+
gl.uniform3fv(u['uGlassTint'] ?? null, glass.tint);
|
|
3928
|
+
return true;
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
/** Put back what `bindSeeThrough` bound: the binding outlives its frame (the 2026-08-27 rule). */
|
|
3932
|
+
private unbindSeeThrough(u: Record<string, WebGLUniformLocation>): void {
|
|
3933
|
+
const { gl } = this;
|
|
3934
|
+
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
3935
|
+
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
3936
|
+
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3688
3939
|
disposeMesh(mesh: Mesh): void {
|
|
3689
3940
|
/*
|
|
3690
3941
|
* Nothing to release when the context is gone: it took every buffer and VAO with it,
|
|
@@ -4353,6 +4604,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4353
4604
|
*/
|
|
4354
4605
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.pointShadowArray?.texture ?? this.emptyTexture2DArray);
|
|
4355
4606
|
gl.uniform1i(uniforms[POINT_SHADOW_SAMPLER] ?? null, firstUnit);
|
|
4607
|
+
/* What the lamps keep through glass: the one-texel stand-in, read as none, until glass casts. */
|
|
4608
|
+
gl.activeTexture(gl.TEXTURE0 + POINT_GLASS_TINT_TEXTURE_UNIT);
|
|
4609
|
+
gl.bindTexture(
|
|
4610
|
+
gl.TEXTURE_2D_ARRAY,
|
|
4611
|
+
this.pointShadowArray?.tintTexture ?? this.emptyTexture2DArray,
|
|
4612
|
+
);
|
|
4613
|
+
gl.uniform1i(uniforms['uPointGlassTints'] ?? null, POINT_GLASS_TINT_TEXTURE_UNIT);
|
|
4356
4614
|
|
|
4357
4615
|
gl.uniform1iv(uniforms['uPointShadowLayer[0]'] ?? null, resolved.layers);
|
|
4358
4616
|
/* Origin and far plane in one row; see `uPointShadowProjection` in the preamble. */
|
|
@@ -4726,21 +4984,17 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4726
4984
|
lightViewProj: ReadonlyMat4,
|
|
4727
4985
|
layer: 'static' | 'static-peel' | 'dynamic' = 'static',
|
|
4728
4986
|
): void {
|
|
4729
|
-
const
|
|
4730
|
-
|
|
4731
|
-
? this.shadowMap
|
|
4732
|
-
: layer === 'static-peel'
|
|
4733
|
-
? this.peeledShadowMap
|
|
4734
|
-
: this.dynamicShadowMap;
|
|
4735
|
-
if (shadowMap === null) {
|
|
4987
|
+
const sun = this.sunShadows;
|
|
4988
|
+
if (sun === null || (layer === 'static-peel' && sun.layers < 3)) {
|
|
4736
4989
|
this.shadowPassActive = false;
|
|
4737
|
-
this.
|
|
4990
|
+
this.activeSunLayer = null;
|
|
4738
4991
|
return;
|
|
4739
4992
|
}
|
|
4740
4993
|
const { gl } = this;
|
|
4741
4994
|
this.gpuTimer.begin('shadows');
|
|
4742
4995
|
this.shadowPassActive = true;
|
|
4743
|
-
this.
|
|
4996
|
+
this.activeSunLayer = layer;
|
|
4997
|
+
this.glassCasters.clear();
|
|
4744
4998
|
/* The static layer opens first, so a frame's peel counts from here. See `peelFilled`. */
|
|
4745
4999
|
if (layer === 'static') this.peelFilled = false;
|
|
4746
5000
|
/*
|
|
@@ -4767,16 +5021,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4767
5021
|
// The previous mesh pass left all directional maps bound for sampling.
|
|
4768
5022
|
// A texture cannot be sampled while it is attached to the active depth
|
|
4769
5023
|
// target, even when the shader branch would skip that sample.
|
|
4770
|
-
|
|
4771
|
-
gl.
|
|
4772
|
-
|
|
4773
|
-
shadowMap.begin(gl);
|
|
5024
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
5025
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
5026
|
+
sun.begin(gl, layer);
|
|
4774
5027
|
gl.useProgram(this.depthProgram);
|
|
4775
5028
|
const peel = layer === 'static-peel';
|
|
4776
5029
|
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, peel ? 1 : 0);
|
|
4777
5030
|
if (peel) {
|
|
5031
|
+
/* The static layer, read while the peel is drawn aside: see `SunShadowArray`. */
|
|
4778
5032
|
gl.activeTexture(gl.TEXTURE0);
|
|
4779
|
-
gl.bindTexture(gl.
|
|
5033
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, sun.texture);
|
|
4780
5034
|
gl.uniform1i(this.depthUniforms['uPreviousShadowMap'] ?? null, 0);
|
|
4781
5035
|
}
|
|
4782
5036
|
/* The corrected one, matching `activeDepthViewProj` above: a pass must render its map with the
|
|
@@ -4888,18 +5142,165 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4888
5142
|
casters(this.casterSink);
|
|
4889
5143
|
}
|
|
4890
5144
|
|
|
5145
|
+
/**
|
|
5146
|
+
* Draw the pass's glass twice: where the nearest pane is, and what the panes let through.
|
|
5147
|
+
*
|
|
5148
|
+
* After the opaque casters, in the pass's own light matrix. A layer that has held glass is
|
|
5149
|
+
* redrawn even when this pass offered none, so glass that has gone takes its colour with it. The
|
|
5150
|
+
* first glass grows the array (`SunShadowArray.growForGlass`) and makes the tint target.
|
|
5151
|
+
*/
|
|
5152
|
+
private flushSunGlass(which: 'static' | 'dynamic'): void {
|
|
5153
|
+
const sun = this.sunShadows;
|
|
5154
|
+
const list = this.glassCasters;
|
|
5155
|
+
if (sun === null || this.quality.glassShadows === 'off') return;
|
|
5156
|
+
if (list.count === 0 && !sun.hasGlass) return;
|
|
5157
|
+
const { gl } = this;
|
|
5158
|
+
if (list.count > 0 && sun.growForGlass(gl)) this.sunGlassRebuilds++;
|
|
5159
|
+
|
|
5160
|
+
/* Where the nearest pane is: the depth programs, through the sink every opaque caster took. */
|
|
5161
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
5162
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
5163
|
+
sun.beginGlass(gl, which);
|
|
5164
|
+
this.activeDepthPeel = false;
|
|
5165
|
+
gl.useProgram(this.depthProgram);
|
|
5166
|
+
gl.uniform1i(this.depthUniforms['uPeelShadowLayer'] ?? null, 0);
|
|
5167
|
+
gl.uniformMatrix4fv(
|
|
5168
|
+
this.depthUniforms['uLightViewProj'] ?? null,
|
|
5169
|
+
false,
|
|
5170
|
+
this.activeDepthViewProj ?? IDENTITY_MAT4,
|
|
5171
|
+
);
|
|
5172
|
+
this.drawingGlassDepth = true;
|
|
5173
|
+
list.replay(this.glassDepthSink);
|
|
5174
|
+
this.drawingGlassDepth = false;
|
|
5175
|
+
sun.end(gl);
|
|
5176
|
+
|
|
5177
|
+
/* What the panes let through, every one on a ray, multiplied. */
|
|
5178
|
+
this.glassTintPrograms ??= new GlassTintPrograms(gl);
|
|
5179
|
+
const size = this.quality.glassShadows === 'half' ? Math.max(1, sun.size >> 1) : sun.size;
|
|
5180
|
+
const tint = (this.sunGlassTint ??= new SunGlassTint(gl, size));
|
|
5181
|
+
/*
|
|
5182
|
+
* The direction the light travels, off the light matrix's depth row: an orthographic
|
|
5183
|
+
* projection's depth grows along it. The sign is immaterial — the tint takes the cosine
|
|
5184
|
+
* absolutely — and w 0 says it is a direction rather than a place.
|
|
5185
|
+
*/
|
|
5186
|
+
const m = this.activeDepthViewProj ?? IDENTITY_MAT4;
|
|
5187
|
+
const length = Math.hypot(m[2] as number, m[6] as number, m[10] as number) || 1;
|
|
5188
|
+
this.glassLight[0] = (m[2] as number) / length;
|
|
5189
|
+
this.glassLight[1] = (m[6] as number) / length;
|
|
5190
|
+
this.glassLight[2] = (m[10] as number) / length;
|
|
5191
|
+
this.glassLight[3] = 0;
|
|
5192
|
+
tint.begin(gl, which === 'static' ? 0 : 1);
|
|
5193
|
+
list.replay(this.glassTintSink);
|
|
5194
|
+
tint.end(gl);
|
|
5195
|
+
gl.useProgram(this.depthProgram);
|
|
5196
|
+
}
|
|
5197
|
+
|
|
5198
|
+
/** The glass list into the glass depth layer: `casterSink` itself, with glass let through. */
|
|
5199
|
+
private readonly glassDepthSink: GlassReplaySink = {
|
|
5200
|
+
mesh: (mesh, model, material) => this.casterSink.mesh(mesh, model, material),
|
|
5201
|
+
instanced: (batch, data, material) => this.casterSink.instanced?.(batch, data, material),
|
|
5202
|
+
skinnedMesh: (mesh, model, palette, material) =>
|
|
5203
|
+
this.casterSink.skinnedMesh(mesh, model, palette, material),
|
|
5204
|
+
};
|
|
5205
|
+
|
|
5206
|
+
/** One pane's tint uniforms, and its cutout where it has one. */
|
|
5207
|
+
private bindGlassTint(
|
|
5208
|
+
program: GlassTintProgram,
|
|
5209
|
+
glass: ResolvedGlass,
|
|
5210
|
+
cutout: CutoutCaster<SurfaceTextureHandle> | null,
|
|
5211
|
+
): void {
|
|
5212
|
+
const { gl } = this;
|
|
5213
|
+
const u = program.uniforms;
|
|
5214
|
+
gl.useProgram(program.program);
|
|
5215
|
+
gl.uniformMatrix4fv(
|
|
5216
|
+
u['uLightViewProj'] ?? null,
|
|
5217
|
+
false,
|
|
5218
|
+
this.activeDepthViewProj ?? IDENTITY_MAT4,
|
|
5219
|
+
);
|
|
5220
|
+
this.uploadWind(u, this.frameWind);
|
|
5221
|
+
this.glassPane[0] = glass.transmission * glass.tint[0];
|
|
5222
|
+
this.glassPane[1] = glass.transmission * glass.tint[1];
|
|
5223
|
+
this.glassPane[2] = glass.transmission * glass.tint[2];
|
|
5224
|
+
this.glassPane[3] = 1 - glass.frost;
|
|
5225
|
+
gl.uniform4fv(u['uGlassPane'] ?? null, this.glassPane);
|
|
5226
|
+
gl.uniform4fv(u['uGlassLight'] ?? null, this.glassLight);
|
|
5227
|
+
if (cutout !== null) {
|
|
5228
|
+
gl.uniform2f(u['uUvScale'] ?? null, cutout.u, cutout.v);
|
|
5229
|
+
gl.uniform1f(u['uAlphaCutout'] ?? null, cutout.cutoff);
|
|
5230
|
+
(cutout.albedo as SurfaceTexture).bind(gl, SURFACE_TEXTURE_UNIT);
|
|
5231
|
+
gl.uniform1i(u['uCutoutMap'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
|
|
5235
|
+
/**
|
|
5236
|
+
* The glass list into the tint layer, through the tint programs, **culling nothing**: a light
|
|
5237
|
+
* crosses a pane whichever way the pane faces, and a closed glass object has two surfaces on a
|
|
5238
|
+
* ray, so every one counts. What that asks of a model is that a pane be one surface — two-sided
|
|
5239
|
+
* glass says so with `doubleSided`, and a second copy of its triangles is a second pane.
|
|
5240
|
+
*/
|
|
5241
|
+
private readonly glassTintSink: GlassReplaySink = {
|
|
5242
|
+
mesh: (mesh, model, material, glass) => {
|
|
5243
|
+
const programs = this.glassTintPrograms;
|
|
5244
|
+
if (programs === null) return;
|
|
5245
|
+
const { gl } = this;
|
|
5246
|
+
const cutout = cutoutOf(material);
|
|
5247
|
+
const program = cutout !== null ? programs.cutout : programs.rigid;
|
|
5248
|
+
this.bindGlassTint(program, glass, cutout);
|
|
5249
|
+
gl.uniformMatrix4fv(program.uniforms['uModel'] ?? null, false, model);
|
|
5250
|
+
gl.disable(gl.CULL_FACE);
|
|
5251
|
+
(mesh as Mesh).draw(gl);
|
|
5252
|
+
if (this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
5253
|
+
if (cutout !== null) this.restoreSurfaceTextureUnit();
|
|
5254
|
+
},
|
|
5255
|
+
instanced: (batch, data, material, glass) => {
|
|
5256
|
+
const programs = this.glassTintPrograms;
|
|
5257
|
+
if (programs === null) return;
|
|
5258
|
+
const { gl } = this;
|
|
5259
|
+
const gpuBatch = batch as InstancedBatch;
|
|
5260
|
+
const count = Math.min(data.count, gpuBatch.capacity);
|
|
5261
|
+
if (count === 0) return;
|
|
5262
|
+
const cutout = cutoutOf(material);
|
|
5263
|
+
this.bindGlassTint(
|
|
5264
|
+
cutout !== null ? programs.instancedCutout : programs.instanced,
|
|
5265
|
+
glass,
|
|
5266
|
+
cutout,
|
|
5267
|
+
);
|
|
5268
|
+
gl.disable(gl.CULL_FACE);
|
|
5269
|
+
gpuBatch.draw(gl, count);
|
|
5270
|
+
if (this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
5271
|
+
if (cutout !== null) this.restoreSurfaceTextureUnit();
|
|
5272
|
+
},
|
|
5273
|
+
skinnedMesh: (mesh, model, palette, _material, glass) => {
|
|
5274
|
+
const programs = this.glassTintPrograms;
|
|
5275
|
+
if (programs === null) return;
|
|
5276
|
+
const { gl } = this;
|
|
5277
|
+
const program = programs.skinned;
|
|
5278
|
+
this.bindGlassTint(program, glass, null);
|
|
5279
|
+
gl.uniformMatrix4fv(program.uniforms['uModel'] ?? null, false, model);
|
|
5280
|
+
this.skinPalette.update(gl, palette);
|
|
5281
|
+
this.skinPalette.bind(gl, SKIN_PALETTE_TEXTURE_UNIT);
|
|
5282
|
+
gl.uniform1i(program.uniforms['uJointPalette'] ?? null, SKIN_PALETTE_TEXTURE_UNIT);
|
|
5283
|
+
gl.disable(gl.CULL_FACE);
|
|
5284
|
+
(mesh as Mesh).draw(gl);
|
|
5285
|
+
if (this.depthPassCullsFaces) gl.enable(gl.CULL_FACE);
|
|
5286
|
+
},
|
|
5287
|
+
};
|
|
5288
|
+
|
|
4891
5289
|
endShadowPass(): void {
|
|
4892
5290
|
if (this.contextLost) return;
|
|
4893
|
-
const
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
5291
|
+
const sun = this.sunShadows;
|
|
5292
|
+
const layer = this.activeSunLayer;
|
|
5293
|
+
if (!this.shadowPassActive || sun === null || layer === null) return;
|
|
5294
|
+
sun.end(this.gl);
|
|
5295
|
+
if (layer === 'static-peel') this.peelFilled = true;
|
|
5296
|
+
/* The peel keeps no glass: its layer is the second opaque occluder and nothing else. */
|
|
5297
|
+
else this.flushSunGlass(layer === 'dynamic' ? 'dynamic' : 'static');
|
|
4897
5298
|
/* Back to the frame's sense; see `beginShadowPass` for why it left it. */
|
|
4898
5299
|
this.gl.depthFunc(glDepthFuncEqual(this.gl));
|
|
4899
5300
|
this.gl.clearDepth(this.depthClear);
|
|
4900
5301
|
this.gpuTimer.end();
|
|
4901
5302
|
this.shadowPassActive = false;
|
|
4902
|
-
this.
|
|
5303
|
+
this.activeSunLayer = null;
|
|
4903
5304
|
// Restore the drawing-buffer viewport the shadow pass overrode.
|
|
4904
5305
|
this.restoreViewport();
|
|
4905
5306
|
}
|
|
@@ -4938,6 +5339,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4938
5339
|
directionalShadows: this.quality.directionalShadows,
|
|
4939
5340
|
environmentProbe: this.probeCapture !== null,
|
|
4940
5341
|
nightEmissive: this.quality.nightEmissive,
|
|
5342
|
+
/* Off compiles none of the glass lookup: see `FlatShaderOptions.glassShadows`. */
|
|
5343
|
+
glassShadows: this.quality.glassShadows !== 'off',
|
|
4941
5344
|
};
|
|
4942
5345
|
const build = (budget: LightBudget): string =>
|
|
4943
5346
|
flatFrag({ ...variant, maxLights: budget.maxLights, maxAreaLights: budget.maxAreaLights });
|
|
@@ -5254,6 +5657,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5254
5657
|
this.bakeOne(map, light, casters as ShadowCasters, maxFaces),
|
|
5255
5658
|
);
|
|
5256
5659
|
this.updateAreaShadows(areaLights, frameDt, staticCasters, dynamicCasters);
|
|
5660
|
+
/* The chain frost reads, once for the round, for the lights whose glass it drew or cleared. */
|
|
5661
|
+
this.pointShadowArray.finishTints(this.gl);
|
|
5257
5662
|
this.restoreViewport();
|
|
5258
5663
|
}
|
|
5259
5664
|
|
|
@@ -5314,6 +5719,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5314
5719
|
* reason: it already holds everything it needs.
|
|
5315
5720
|
*/
|
|
5316
5721
|
set.resolve(areaLights, this.resolvedAreaShadows);
|
|
5722
|
+
/* As the array stores them: 2L once glass has interleaved it. See `strideLayers`. */
|
|
5723
|
+
const stride = this.pointShadowArray?.stride ?? 1;
|
|
5724
|
+
strideLayers(this.resolvedAreaShadows.layer, stride);
|
|
5725
|
+
strideLayers(this.resolvedAreaShadows.liveLayer, stride);
|
|
5317
5726
|
}
|
|
5318
5727
|
|
|
5319
5728
|
/**
|
|
@@ -5378,6 +5787,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5378
5787
|
near,
|
|
5379
5788
|
DEFAULT_SOURCE_RADIUS,
|
|
5380
5789
|
maxFaces,
|
|
5790
|
+
this.pointGlass,
|
|
5381
5791
|
),
|
|
5382
5792
|
);
|
|
5383
5793
|
}
|
|
@@ -5401,6 +5811,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5401
5811
|
light.shadowNear,
|
|
5402
5812
|
light.sourceRadius,
|
|
5403
5813
|
maxFaces,
|
|
5814
|
+
this.pointGlass,
|
|
5404
5815
|
),
|
|
5405
5816
|
);
|
|
5406
5817
|
}
|
|
@@ -5635,6 +6046,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
5635
6046
|
*
|
|
5636
6047
|
* What it gives up is one frame of history: the frame after a cut has no blur and no temporal
|
|
5637
6048
|
* blend, as the first frame of a session has none. Nothing else is reset.
|
|
6049
|
+
*
|
|
6050
|
+
* **A reconstructing renderer depends on it**: its history is the last shot's, and without this
|
|
6051
|
+
* call a cut blends that shot into the first frames of the next. Nothing detects a cut on the
|
|
6052
|
+
* caller's behalf, deliberately — a fast pan and a cut look alike from here.
|
|
5638
6053
|
*/
|
|
5639
6054
|
cameraCut(): void {
|
|
5640
6055
|
this.hasPreviousView = false;
|
|
@@ -6266,6 +6681,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6266
6681
|
radius: number;
|
|
6267
6682
|
projScale: Float32Array;
|
|
6268
6683
|
invProjection: Float32Array;
|
|
6684
|
+
offset: Float32Array;
|
|
6269
6685
|
}
|
|
6270
6686
|
| undefined;
|
|
6271
6687
|
if (aoStrength > 0 && projection !== null) {
|
|
@@ -6275,11 +6691,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6275
6691
|
metres into a radius in UV. */
|
|
6276
6692
|
this.aoProjScale[0] = projection[0] ?? 1;
|
|
6277
6693
|
this.aoProjScale[1] = projection[5] ?? 1;
|
|
6694
|
+
/* The occlusion read where this frame's jitter put it (`uAoOffset`): a texel stands at its
|
|
6695
|
+
index plus a half less the jitter, in the drawing buffer the jitter was spread over. */
|
|
6696
|
+
this.aoOffset[0] = this.temporalJittering
|
|
6697
|
+
? this.temporalJitterX / Math.max(1, this.gl.drawingBufferWidth)
|
|
6698
|
+
: 0;
|
|
6699
|
+
this.aoOffset[1] = this.temporalJittering
|
|
6700
|
+
? this.temporalJitterY / Math.max(1, this.gl.drawingBufferHeight)
|
|
6701
|
+
: 0;
|
|
6278
6702
|
ao = {
|
|
6279
6703
|
strength: aoStrength,
|
|
6280
6704
|
radius: this.quality.ambientOcclusionRadius,
|
|
6281
6705
|
projScale: this.aoProjScale,
|
|
6282
6706
|
invProjection: this.aoInvProjection as Float32Array,
|
|
6707
|
+
offset: this.aoOffset,
|
|
6283
6708
|
};
|
|
6284
6709
|
}
|
|
6285
6710
|
|
|
@@ -6425,11 +6850,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6425
6850
|
* flat paint with the effect on. The opaque frame is finished here, which is what a pane
|
|
6426
6851
|
* should show; the latch then serves every pane in the replay. Only when something refracts.
|
|
6427
6852
|
*/
|
|
6428
|
-
if (this.translucentQueue.refracts)
|
|
6853
|
+
if (this.translucentQueue.refracts) {
|
|
6854
|
+
this.sceneTarget?.snapshotColor(false, this.translucentQueue.glassy);
|
|
6855
|
+
}
|
|
6429
6856
|
this.oit.accumulateOit(this.canvas.width, this.canvas.height, oitDepth, (weighted) => {
|
|
6430
6857
|
this.oitReplaying = true;
|
|
6431
6858
|
this.useFlatProgram();
|
|
6432
|
-
this.
|
|
6859
|
+
this.oitWeight = weighted ? 1 : 0;
|
|
6860
|
+
this.writeMode(flat);
|
|
6433
6861
|
this.translucentQueue.replay((draw) => {
|
|
6434
6862
|
options.lit = draw.lit;
|
|
6435
6863
|
options.fog = draw.fog;
|
|
@@ -6442,10 +6870,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6442
6870
|
options.refraction = draw.refraction;
|
|
6443
6871
|
options.refractTint = draw.refractTint as unknown as Vec3 | undefined;
|
|
6444
6872
|
options.thicknessM = draw.thicknessM;
|
|
6873
|
+
options.glass = draw.glass ?? undefined;
|
|
6445
6874
|
this.drawTranslucentMesh(draw.mesh as Mesh, draw.model, draw.opacity, options);
|
|
6446
6875
|
});
|
|
6447
6876
|
/* Back to zero for every other draw in the frame, exactly as `uOpacity` and `uTint` are. */
|
|
6448
|
-
this.
|
|
6877
|
+
this.oitWeight = 0;
|
|
6878
|
+
this.writeMode(flat);
|
|
6449
6879
|
this.oitReplaying = false;
|
|
6450
6880
|
});
|
|
6451
6881
|
this.sceneTarget?.bind();
|
|
@@ -6504,9 +6934,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6504
6934
|
/* The placeholder rather than null where a map is merely absent, for the reason
|
|
6505
6935
|
`emptyTexture.ts` gives. It is black, which as a depth is an occluder at the light,
|
|
6506
6936
|
so it is safe only because `sunShadow` is 0 whenever these maps do not exist. */
|
|
6507
|
-
|
|
6508
|
-
peeledShadowMap: this.peeledShadowMap?.texture ?? this.emptyTexture2D,
|
|
6509
|
-
dynamicShadowMap: this.dynamicShadowMap?.texture ?? this.emptyTexture2D,
|
|
6937
|
+
sunShadows: this.sunShadows?.texture ?? this.emptyTexture2DArray,
|
|
6510
6938
|
peeledEnabled: this.peelFilled,
|
|
6511
6939
|
},
|
|
6512
6940
|
);
|
|
@@ -6633,7 +7061,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6633
7061
|
* A reflection pass binds its own camera and is skipped: blurring toward a mirror's
|
|
6634
7062
|
* previous view is not what a viewer moved through.
|
|
6635
7063
|
*/
|
|
6636
|
-
|
|
7064
|
+
/*
|
|
7065
|
+
* **Not after the present**: a mesh pass bound then is the interface's, over a picture already
|
|
7066
|
+
* resolved. Deciding the frame again drew the interface jittered with nothing to resolve it,
|
|
7067
|
+
* and opened a second temporal frame, so the sequence stepped twice a frame, the world saw every
|
|
7068
|
+
* other position, and the anti-flicker's period never started.
|
|
7069
|
+
*/
|
|
7070
|
+
if (this.framePresented) this.temporalJittering = false;
|
|
7071
|
+
if (!this.reflectionPassActive && !this.probePassActive && !this.framePresented) {
|
|
6637
7072
|
this.frameViewProj = camera.viewProjection;
|
|
6638
7073
|
/* Copied rather than referenced: a caller moves its camera in place, and the decal pass
|
|
6639
7074
|
reads this at the end of the frame. */
|
|
@@ -6747,6 +7182,23 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6747
7182
|
return field;
|
|
6748
7183
|
}
|
|
6749
7184
|
|
|
7185
|
+
/**
|
|
7186
|
+
* Hand a world's DriftLight volume to the renderer: every fixed light of a city, summed offline by
|
|
7187
|
+
* `bakeDenseField`, standing in past the frame's exact choice. Replaces any field made before, of
|
|
7188
|
+
* either kind; the scene calls `field.follow(buffer.complete, ...)` each frame as it would for
|
|
7189
|
+
* `createLightField`, and marks the lights the volume summed `inLightField` so none is counted
|
|
7190
|
+
* twice. See `driftLight/worldLightField.ts`.
|
|
7191
|
+
*/
|
|
7192
|
+
createWorldLightField(
|
|
7193
|
+
volume: DenseLightVolume,
|
|
7194
|
+
options: WorldLightFieldOptions = {},
|
|
7195
|
+
): WorldLightField {
|
|
7196
|
+
this.forgetLightFieldVolumes();
|
|
7197
|
+
const field = new WorldLightField(volume, options);
|
|
7198
|
+
this.lightField = field;
|
|
7199
|
+
return field;
|
|
7200
|
+
}
|
|
7201
|
+
|
|
6750
7202
|
/** Let go of the field and its volumes; the scene's lights are all exact again. */
|
|
6751
7203
|
disposeLightField(): void {
|
|
6752
7204
|
this.forgetLightFieldVolumes();
|
|
@@ -6766,11 +7218,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6766
7218
|
*/
|
|
6767
7219
|
private uploadLightField(): void {
|
|
6768
7220
|
const field = this.lightField;
|
|
6769
|
-
if (field === null || this.driftAtlas !== null || !field.ready || field.
|
|
7221
|
+
if (field === null || this.driftAtlas !== null || !field.ready || field.empty) {
|
|
6770
7222
|
return;
|
|
6771
7223
|
}
|
|
6772
7224
|
const { gl } = this;
|
|
6773
|
-
const [ix, iy, iz] = field.
|
|
7225
|
+
const [ix, iy, iz] = field.indexDims;
|
|
6774
7226
|
const index = gl.createTexture();
|
|
6775
7227
|
gl.bindTexture(gl.TEXTURE_3D, index);
|
|
6776
7228
|
gl.texImage3D(
|
|
@@ -6783,7 +7235,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6783
7235
|
0,
|
|
6784
7236
|
gl.RED_INTEGER,
|
|
6785
7237
|
gl.UNSIGNED_INT,
|
|
6786
|
-
field.
|
|
7238
|
+
field.index,
|
|
6787
7239
|
);
|
|
6788
7240
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
6789
7241
|
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
@@ -6853,9 +7305,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6853
7305
|
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
6854
7306
|
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
6855
7307
|
gl.uniform1i(u['uRefractScene'] ?? null, REFRACT_SCENE_TEXTURE_UNIT);
|
|
6856
|
-
|
|
7308
|
+
/* Neither refracting nor glass, which is every draw that does not say otherwise. See
|
|
7309
|
+
`bindSeeThrough` for the four numbers in the one vector. */
|
|
7310
|
+
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
6857
7311
|
gl.uniform3fv(u['uRefractTint'] ?? null, WHITE_TINT);
|
|
6858
|
-
gl.
|
|
7312
|
+
gl.uniform3fv(u['uGlassTint'] ?? null, WHITE_TINT);
|
|
6859
7313
|
gl.uniform3fv(u['uDirectionalDir'] ?? null, env.directionalDir);
|
|
6860
7314
|
gl.uniform3fv(u['uDirectionalColor'] ?? null, env.directionalColor);
|
|
6861
7315
|
gl.uniform3fv(u['uAmbient'] ?? null, env.ambient);
|
|
@@ -6921,6 +7375,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6921
7375
|
gl.uniform1i(u['uFogEnabled'] ?? null, 1);
|
|
6922
7376
|
/* Grain on by default, so every scene written before it was a choice looks unchanged. */
|
|
6923
7377
|
gl.uniform1f(u['uGrain'] ?? null, 1);
|
|
7378
|
+
/* No dither and no weighting: every draw whole, until a crossfade asks. See `setDitherFade`. */
|
|
7379
|
+
this.oitWeight = 0;
|
|
7380
|
+
this.ditherFade = 0;
|
|
7381
|
+
gl.uniform2f(u['uWriteMode'] ?? null, 0, 0);
|
|
6924
7382
|
/* No environment reflection unless a caller asks, so existing scenes are unchanged. */
|
|
6925
7383
|
gl.uniform1f(u['uReflectivity'] ?? null, 0);
|
|
6926
7384
|
/* 1, not 0: this one is a multiplier and its identity is one. See `setEnvironmentGain`. */
|
|
@@ -6930,11 +7388,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6930
7388
|
gl.uniform1f(u['uReliefCycles'] ?? null, 60);
|
|
6931
7389
|
/* And no relief read off a surface texture either, for the same reason. */
|
|
6932
7390
|
gl.uniform1f(u['uTextureRelief'] ?? null, 0);
|
|
6933
|
-
|
|
7391
|
+
/* The mirror's plane in a reflection pass, and zero — which clips nothing — in every other. */
|
|
6934
7392
|
const reflection = this.planarReflection;
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
7393
|
+
gl.uniform4fv(
|
|
7394
|
+
u['uClipPlane'] ?? null,
|
|
7395
|
+
this.reflectionPassActive && reflection !== null ? reflection.clipPlane : NO_CLIP_PLANE,
|
|
7396
|
+
);
|
|
7397
|
+
resolveSurfaceScene(env, this.surfaceScene);
|
|
7398
|
+
gl.uniform4fv(u['uSurfaceScene'] ?? null, this.surfaceScene);
|
|
6938
7399
|
bindAtmosphere(
|
|
6939
7400
|
gl,
|
|
6940
7401
|
u,
|
|
@@ -6957,7 +7418,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6957
7418
|
* its own copy of this lookup; the two backends have to project the same way.
|
|
6958
7419
|
*/
|
|
6959
7420
|
gl.uniformMatrix4fv(u['uLightViewProj'] ?? null, false, env.lightViewProj);
|
|
6960
|
-
gl.uniform1f(u['uShadowStrength'] ?? null, this.
|
|
7421
|
+
gl.uniform1f(u['uShadowStrength'] ?? null, this.sunShadows === null ? 0 : env.shadowStrength);
|
|
6961
7422
|
gl.uniform1f(u['uShadowMapSize'] ?? null, this.quality.directionalShadowMapSize);
|
|
6962
7423
|
gl.uniform1f(u['uShadowDepthSpan'] ?? null, env.shadowDepthSpan);
|
|
6963
7424
|
gl.uniform1f(u['uShadowMaxDistance'] ?? null, this.quality.directionalShadowMaxDistance);
|
|
@@ -6984,16 +7445,12 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
6984
7445
|
*/
|
|
6985
7446
|
if (this.quality.directionalShadows) {
|
|
6986
7447
|
gl.activeTexture(gl.TEXTURE0);
|
|
6987
|
-
gl.bindTexture(gl.
|
|
6988
|
-
gl.uniform1i(u['
|
|
6989
|
-
|
|
6990
|
-
gl.activeTexture(gl.
|
|
6991
|
-
gl.bindTexture(gl.
|
|
6992
|
-
gl.uniform1i(u['
|
|
6993
|
-
|
|
6994
|
-
gl.activeTexture(gl.TEXTURE2);
|
|
6995
|
-
gl.bindTexture(gl.TEXTURE_2D, this.dynamicShadowMap?.texture ?? this.emptyTexture2D);
|
|
6996
|
-
gl.uniform1i(u['uDynamicShadowMap'] ?? null, 2);
|
|
7448
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.sunShadows?.texture ?? this.emptyTexture2DArray);
|
|
7449
|
+
gl.uniform1i(u['uSunShadows'] ?? null, 0);
|
|
7450
|
+
/* What the sun keeps through glass, or the one-texel stand-in the lookup reads as none. */
|
|
7451
|
+
gl.activeTexture(gl.TEXTURE0 + SUN_GLASS_TINT_TEXTURE_UNIT);
|
|
7452
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.sunGlassTint?.texture ?? this.emptyTexture2DArray);
|
|
7453
|
+
gl.uniform1i(u['uSunGlassTints'] ?? null, SUN_GLASS_TINT_TEXTURE_UNIT);
|
|
6997
7454
|
}
|
|
6998
7455
|
|
|
6999
7456
|
if (this.pointShadows === null) {
|
|
@@ -7006,6 +7463,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7006
7463
|
} else {
|
|
7007
7464
|
/* Chosen in `pointShadowSystem.ts` and bound here; see `resolve` for the split. */
|
|
7008
7465
|
this.pointShadows.resolve(this.resolvedPointShadows);
|
|
7466
|
+
/* As the array stores them: 2L once glass has interleaved it. See `strideLayers`. */
|
|
7467
|
+
const stride = this.pointShadowArray?.stride ?? 1;
|
|
7468
|
+
strideLayers(this.resolvedPointShadows.layers, stride);
|
|
7469
|
+
strideLayers(this.resolvedPointShadows.liveLayers, stride);
|
|
7009
7470
|
this.bindPointShadows(u, DIRECTIONAL_SHADOW_UNITS);
|
|
7010
7471
|
/*
|
|
7011
7472
|
* The rectangles' layers, into the array `bindPointShadows` just put on its unit. Resolved
|
|
@@ -7103,9 +7564,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7103
7564
|
this.bindDriftLight(u, camera.position);
|
|
7104
7565
|
|
|
7105
7566
|
gl.activeTexture(gl.TEXTURE0 + SURFACE_TEXTURE_UNIT);
|
|
7106
|
-
gl.bindTexture(gl.
|
|
7567
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7107
7568
|
gl.uniform1i(u['uAlbedo'] ?? null, SURFACE_TEXTURE_UNIT);
|
|
7108
7569
|
gl.uniform1i(u['uAlbedoEnabled'] ?? null, 0);
|
|
7570
|
+
/* No albedo, so no effects table: the stand-in, which the shader reads as none. */
|
|
7571
|
+
gl.activeTexture(gl.TEXTURE0 + SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
7572
|
+
gl.bindTexture(gl.TEXTURE_2D, this.emptyTexture2D);
|
|
7573
|
+
gl.uniform1i(u['uSurfaceEffects'] ?? null, SURFACE_EFFECTS_TEXTURE_UNIT);
|
|
7109
7574
|
gl.uniform2f(u['uUvScale'] ?? null, 1, 1);
|
|
7110
7575
|
gl.uniform1f(u['uAlbedoCutout'] ?? null, 0);
|
|
7111
7576
|
gl.uniform1i(u['uDoubleSided'] ?? null, 0);
|
|
@@ -7130,20 +7595,20 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7130
7595
|
* fetch a sampler's descriptor before it evaluates the branch that would have skipped the read.
|
|
7131
7596
|
*/
|
|
7132
7597
|
gl.activeTexture(gl.TEXTURE0 + NORMAL_TEXTURE_UNIT);
|
|
7133
|
-
gl.bindTexture(gl.
|
|
7598
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7134
7599
|
gl.uniform1i(u['uNormalMap'] ?? null, NORMAL_TEXTURE_UNIT);
|
|
7135
7600
|
gl.uniform1f(u['uNormalStrength'] ?? null, 0);
|
|
7136
7601
|
|
|
7137
7602
|
/* And the ORM map, for the reason directly above and the one above that. */
|
|
7138
7603
|
gl.activeTexture(gl.TEXTURE0 + ORM_TEXTURE_UNIT);
|
|
7139
|
-
gl.bindTexture(gl.
|
|
7604
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7140
7605
|
gl.uniform1i(u['uOrmMap'] ?? null, ORM_TEXTURE_UNIT);
|
|
7141
7606
|
gl.uniform1i(u['uOrmEnabled'] ?? null, 0);
|
|
7142
7607
|
gl.uniform3f(u['uOrmScale'] ?? null, 1, 1, 1);
|
|
7143
7608
|
|
|
7144
7609
|
/* And the emissive map. The scale goes back to one, which is the identity it multiplies by. */
|
|
7145
7610
|
gl.activeTexture(gl.TEXTURE0 + EMISSIVE_TEXTURE_UNIT);
|
|
7146
|
-
gl.bindTexture(gl.
|
|
7611
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
7147
7612
|
gl.uniform1i(u['uEmissiveMap'] ?? null, EMISSIVE_TEXTURE_UNIT);
|
|
7148
7613
|
gl.uniform1i(u['uEmissiveMapEnabled'] ?? null, 0);
|
|
7149
7614
|
gl.uniform3f(u['uEmissiveScale'] ?? null, 1, 1, 1);
|
|
@@ -7223,13 +7688,14 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7223
7688
|
*/
|
|
7224
7689
|
tint: Vec3 | null = null,
|
|
7225
7690
|
/**
|
|
7226
|
-
* Where the mesh was last frame
|
|
7691
|
+
* Where the mesh was last frame — a `Mover` or a matrix — which this backend takes and ignores.
|
|
7227
7692
|
*
|
|
7228
7693
|
* Accepted rather than absent so that one scene draws through both renderers unchanged — the
|
|
7229
7694
|
* parity rule this repository is built on. Reconstruction is WebGPU's, having no compute stage
|
|
7230
|
-
* here, so there is no motion target for this to be written into
|
|
7695
|
+
* here, so there is no motion target for this to be written into, and a mover passed here is
|
|
7696
|
+
* neither read nor advanced.
|
|
7231
7697
|
*/
|
|
7232
|
-
_previousModel: ReadonlyMat4 | null = null,
|
|
7698
|
+
_previousModel: ReadonlyMat4 | Mover | null = null,
|
|
7233
7699
|
): void {
|
|
7234
7700
|
if (this.contextLost) return;
|
|
7235
7701
|
/* Geometry that has not all arrived is not drawn. See `Mesh.complete`. */
|
|
@@ -7429,7 +7895,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7429
7895
|
* The two buffers are two blend states over the same geometry, so the set has to survive until
|
|
7430
7896
|
* the end of the pass. Everything is copied — see `TranslucentQueue`.
|
|
7431
7897
|
*/
|
|
7432
|
-
if (this.oitActive && !this.oitReplaying) {
|
|
7898
|
+
if (this.oitActive && !this.oitReplaying && options.additive !== true) {
|
|
7433
7899
|
this.translucentQueue.record(mesh, model, opacity, options);
|
|
7434
7900
|
return;
|
|
7435
7901
|
}
|
|
@@ -7448,10 +7914,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7448
7914
|
* drew with, and only a caller that asks pays for the branch that skips them.
|
|
7449
7915
|
*/
|
|
7450
7916
|
const lit = options.lit ?? true;
|
|
7451
|
-
|
|
7917
|
+
/* Added light fades in the medium rather than receding into it. See `drawFog.ts`. */
|
|
7918
|
+
const fog = fogModeOf(options.fog ?? true, options.additive === true);
|
|
7452
7919
|
const toneMapped = options.toneMapped ?? true;
|
|
7453
7920
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 0);
|
|
7454
|
-
if (
|
|
7921
|
+
if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, fog);
|
|
7455
7922
|
/* `1` is sRGB alone: the conversion without the curve, which is what dropping the tone map
|
|
7456
7923
|
means rather than dropping the whole transform. `Math.min` rather than a literal, so a
|
|
7457
7924
|
renderer asked for `none` stays at none and one asked for `srgb` is already there. */
|
|
@@ -7474,19 +7941,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7474
7941
|
* translucent one rather than sampling a black texture and painting the pane the colour of a
|
|
7475
7942
|
* hole. That is the defined state the two-backends rule asks for instead of a silent no-op.
|
|
7476
7943
|
*/
|
|
7477
|
-
const
|
|
7478
|
-
let refracting = false;
|
|
7479
|
-
if (refraction > 0) {
|
|
7480
|
-
const snapshot = this.sceneTarget?.snapshotColor() ?? null;
|
|
7481
|
-
if (snapshot !== null) {
|
|
7482
|
-
refracting = true;
|
|
7483
|
-
gl.activeTexture(gl.TEXTURE0 + REFRACT_SCENE_TEXTURE_UNIT);
|
|
7484
|
-
gl.bindTexture(gl.TEXTURE_2D, snapshot);
|
|
7485
|
-
gl.uniform1f(u['uRefractStrength'] ?? null, refraction);
|
|
7486
|
-
gl.uniform3fv(u['uRefractTint'] ?? null, options.refractTint ?? WHITE_TINT);
|
|
7487
|
-
gl.uniform1f(u['uRefractThickness'] ?? null, options.thicknessM ?? 0);
|
|
7488
|
-
}
|
|
7489
|
-
}
|
|
7944
|
+
const refracting = this.bindSeeThrough(u, options);
|
|
7490
7945
|
/*
|
|
7491
7946
|
* **The blend and the depth mask belong to whoever owns the pass.** In the ordinary case that
|
|
7492
7947
|
* is this draw; during an order-independent replay it is `OitPass`, which has set an additive
|
|
@@ -7501,7 +7956,9 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7501
7956
|
const ownsState = !this.oitReplaying;
|
|
7502
7957
|
if (ownsState) {
|
|
7503
7958
|
gl.enable(gl.BLEND);
|
|
7504
|
-
|
|
7959
|
+
/* Added, where asked: colour times alpha onto what is there, and the target's alpha kept. */
|
|
7960
|
+
if (options.additive === true) gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ZERO, gl.ONE);
|
|
7961
|
+
else gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
7505
7962
|
}
|
|
7506
7963
|
/*
|
|
7507
7964
|
* The two depth options, both scoped to this draw and handed straight back — the same
|
|
@@ -7529,10 +7986,11 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7529
7986
|
if (ownsState) gl.disable(gl.BLEND);
|
|
7530
7987
|
gl.uniform1f(u['uOpacity'] ?? null, 1);
|
|
7531
7988
|
if (tint !== null) gl.uniform3fv(u['uTint'] ?? null, WHITE_TINT);
|
|
7989
|
+
if (refracting) this.unbindSeeThrough(u);
|
|
7532
7990
|
/* Put back for whatever is drawn next, exactly as `uOpacity` and `uTint` are — a pass
|
|
7533
7991
|
cannot inherit a material from the draw before it. */
|
|
7534
7992
|
if (!lit) gl.uniform1i(u['uLightingEnabled'] ?? null, 1);
|
|
7535
|
-
if (
|
|
7993
|
+
if (fog !== FOG_RECEDE) gl.uniform1i(u['uFogEnabled'] ?? null, FOG_RECEDE);
|
|
7536
7994
|
if (!toneMapped) gl.uniform1i(u['uOutputTransform'] ?? null, this.gradeCode());
|
|
7537
7995
|
if (own) this.materials.dirty();
|
|
7538
7996
|
}
|
|
@@ -7916,16 +8374,8 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7916
8374
|
gl.uniform1i(u['uPeeledShadowEnabled'] ?? null, this.peelFilled ? 1 : 0);
|
|
7917
8375
|
|
|
7918
8376
|
gl.activeTexture(gl.TEXTURE0);
|
|
7919
|
-
gl.bindTexture(gl.
|
|
7920
|
-
gl.uniform1i(u['
|
|
7921
|
-
|
|
7922
|
-
gl.activeTexture(gl.TEXTURE1);
|
|
7923
|
-
gl.bindTexture(gl.TEXTURE_2D, this.peeledShadowMap?.texture ?? this.emptyTexture2D);
|
|
7924
|
-
gl.uniform1i(u['uPeeledShadowMap'] ?? null, 1);
|
|
7925
|
-
|
|
7926
|
-
gl.activeTexture(gl.TEXTURE2);
|
|
7927
|
-
gl.bindTexture(gl.TEXTURE_2D, this.dynamicShadowMap?.texture ?? this.emptyTexture2D);
|
|
7928
|
-
gl.uniform1i(u['uDynamicShadowMap'] ?? null, 2);
|
|
8377
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.sunShadows?.texture ?? this.emptyTexture2DArray);
|
|
8378
|
+
gl.uniform1i(u['uSunShadows'] ?? null, 0);
|
|
7929
8379
|
}
|
|
7930
8380
|
}
|
|
7931
8381
|
|