@driftengine/core 4.4.1 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +72 -0
- package/README.md +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/render/backend/api.d.ts +17 -11
- package/dist/render/backend/webgl2/glassTintPrograms.d.ts +13 -0
- package/dist/render/backend/webgl2/glassTintPrograms.js +38 -0
- package/dist/render/backend/webgl2/renderer.d.ts +82 -8
- package/dist/render/backend/webgl2/renderer.js +373 -105
- package/dist/render/backend/webgpu/buffers.d.ts +19 -1
- package/dist/render/backend/webgpu/buffers.js +27 -1
- package/dist/render/backend/webgpu/depthPass.d.ts +1 -1
- package/dist/render/backend/webgpu/depthPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +2 -0
- package/dist/render/backend/webgpu/flatPass.js +20 -1
- package/dist/render/backend/webgpu/glassTintPass.d.ts +28 -0
- package/dist/render/backend/webgpu/glassTintPass.js +205 -0
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +60 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +305 -5
- package/dist/render/backend/webgpu/instanced.d.ts +12 -1
- package/dist/render/backend/webgpu/instanced.js +20 -1
- package/dist/render/backend/webgpu/latePass.d.ts +26 -0
- package/dist/render/backend/webgpu/latePass.js +133 -0
- package/dist/render/backend/webgpu/lightVolumePass.js +5 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +95 -0
- package/dist/render/backend/webgpu/motionPass.js +491 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +10 -1
- package/dist/render/backend/webgpu/pipelineCache.js +11 -1
- package/dist/render/backend/webgpu/pointGlassTintPass.d.ts +32 -0
- package/dist/render/backend/webgpu/pointGlassTintPass.js +193 -0
- package/dist/render/backend/webgpu/pointShadowPass.d.ts +35 -6
- package/dist/render/backend/webgpu/pointShadowPass.js +105 -20
- package/dist/render/backend/webgpu/postPass.d.ts +21 -15
- package/dist/render/backend/webgpu/postPass.js +17 -4
- package/dist/render/backend/webgpu/renderer.d.ts +181 -54
- package/dist/render/backend/webgpu/renderer.js +1011 -394
- package/dist/render/backend/webgpu/scatterPass.js +2 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +4 -0
- package/dist/render/backend/webgpu/snapshotMips.d.ts +29 -0
- package/dist/render/backend/webgpu/snapshotMips.js +52 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.js +1 -1
- package/dist/render/flameLight.js +15 -2
- package/dist/render/glass.d.ts +45 -0
- package/dist/render/glass.js +39 -0
- package/dist/render/glassCasters.d.ts +40 -0
- package/dist/render/glassCasters.js +84 -0
- package/dist/render/glassShadow.d.ts +78 -0
- package/dist/render/glassShadow.js +125 -0
- package/dist/render/globalMediumPass.d.ts +2 -3
- package/dist/render/globalMediumPass.js +2 -8
- package/dist/render/gpudriven/lit.d.ts +12 -0
- package/dist/render/gpudriven/lit.js +23 -0
- package/dist/render/gpudriven/materialTable.d.ts +15 -5
- package/dist/render/gpudriven/materialTable.js +26 -6
- package/dist/render/gpudriven/pipeline.d.ts +1 -1
- package/dist/render/gpudriven/pipeline.js +4 -3
- package/dist/render/gpudriven/sunGlass.d.ts +35 -0
- package/dist/render/gpudriven/sunGlass.js +89 -0
- package/dist/render/lightBudget.d.ts +17 -5
- package/dist/render/lightBudget.js +17 -5
- package/dist/render/pointGlassTint.d.ts +59 -0
- package/dist/render/pointGlassTint.js +198 -0
- package/dist/render/pointShadowArray.d.ts +56 -3
- package/dist/render/pointShadowArray.js +127 -16
- package/dist/render/pointShadowBudget.js +51 -41
- package/dist/render/pointShadowMap.d.ts +23 -1
- package/dist/render/pointShadowMap.js +27 -1
- package/dist/render/recon/changeFrames.d.ts +15 -0
- package/dist/render/recon/changeFrames.js +21 -0
- package/dist/render/recon/disocclusion.d.ts +36 -3
- package/dist/render/recon/disocclusion.js +56 -4
- 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.js +95 -12
- package/dist/render/renderQuality.d.ts +35 -0
- package/dist/render/renderQuality.js +7 -0
- package/dist/render/rendererHarness.js +12 -0
- package/dist/render/sceneTarget.d.ts +9 -1
- package/dist/render/sceneTarget.js +59 -13
- package/dist/render/shaders/depth.d.ts +16 -1
- package/dist/render/shaders/depth.js +85 -9
- package/dist/render/shaders/flat/directionalShadow.js +91 -6
- package/dist/render/shaders/flat/glassTint.d.ts +6 -0
- package/dist/render/shaders/flat/glassTint.js +25 -0
- package/dist/render/shaders/flat/index.d.ts +13 -0
- package/dist/render/shaders/flat/index.js +4 -0
- package/dist/render/shaders/flat/lampGlass.d.ts +1 -0
- package/dist/render/shaders/flat/lampGlass.js +183 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +115 -15
- package/dist/render/shaders/flat/pointShadow.d.ts +1 -1
- package/dist/render/shaders/flat/pointShadow.js +2 -0
- package/dist/render/shaders/flat/preamble.js +21 -6
- package/dist/render/shaders/generated/depth.wgsl.d.ts +227 -4
- package/dist/render/shaders/generated/depth.wgsl.js +227 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +360 -312
- package/dist/render/shaders/generated/flat.wgsl.js +376 -328
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/globalMedium.wgsl.js +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +3 -13
- package/dist/render/shaders/generated/lightVolume.wgsl.js +4 -14
- package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/octahedralResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
- package/dist/render/shaders/globalMedium.js +6 -6
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +7 -0
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +86 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/cull.wgsl.js +11 -2
- package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/materialTable.wgsl.js +3 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +50 -1
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.d.ts +9 -0
- package/dist/render/shaders/gpudriven/sunGlass.wgsl.js +133 -0
- package/dist/render/shaders/lightVolume.js +6 -6
- package/dist/render/shaders/octahedralResolve.d.ts +8 -0
- package/dist/render/shaders/octahedralResolve.js +48 -17
- package/dist/render/shaders/recon/lateDepth.wgsl.d.ts +1 -0
- package/dist/render/shaders/recon/lateDepth.wgsl.js +44 -0
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +84 -0
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
- package/dist/render/shaders/temporalResolve.js +133 -5
- 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/temporalAa.d.ts +145 -0
- package/dist/render/temporalAa.js +190 -0
- package/dist/render/temporalPass.d.ts +16 -14
- package/dist/render/temporalPass.js +59 -14
- package/dist/render/translucentQueue.d.ts +6 -0
- package/dist/render/translucentQueue.js +15 -1
- package/package.json +6 -6
- package/src/index.ts +10 -0
- package/src/render/backend/api.ts +15 -9
- package/src/render/backend/webgl2/glassTintPrograms.ts +58 -0
- package/src/render/backend/webgl2/renderer.ts +422 -109
- package/src/render/backend/webgpu/buffers.ts +50 -2
- package/src/render/backend/webgpu/depthPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +22 -1
- package/src/render/backend/webgpu/glassTintPass.ts +244 -0
- package/src/render/backend/webgpu/gpuDrivenPass.ts +349 -5
- package/src/render/backend/webgpu/instanced.ts +21 -1
- package/src/render/backend/webgpu/latePass.ts +152 -0
- package/src/render/backend/webgpu/lightVolumePass.ts +5 -1
- package/src/render/backend/webgpu/motionPass.ts +535 -0
- package/src/render/backend/webgpu/pipelineCache.ts +12 -1
- package/src/render/backend/webgpu/pointGlassTintPass.ts +233 -0
- package/src/render/backend/webgpu/pointShadowPass.ts +158 -26
- package/src/render/backend/webgpu/postPass.ts +34 -4
- package/src/render/backend/webgpu/renderer.ts +1178 -425
- package/src/render/backend/webgpu/scatterPass.ts +2 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +4 -0
- package/src/render/backend/webgpu/snapshotMips.ts +80 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +1 -1
- package/src/render/flameLight.ts +15 -2
- package/src/render/glass.ts +74 -0
- package/src/render/glassCasters.ts +164 -0
- package/src/render/glassShadow.ts +153 -0
- package/src/render/globalMediumPass.ts +4 -11
- package/src/render/gpudriven/lit.ts +31 -0
- package/src/render/gpudriven/materialTable.ts +28 -6
- package/src/render/gpudriven/pipeline.ts +4 -3
- package/src/render/gpudriven/sunGlass.ts +117 -0
- package/src/render/lightBudget.ts +19 -5
- package/src/render/pointGlassTint.ts +250 -0
- package/src/render/pointShadowArray.ts +188 -22
- package/src/render/pointShadowBudget.ts +49 -39
- package/src/render/pointShadowMap.ts +41 -0
- package/src/render/recon/changeFrames.ts +24 -0
- package/src/render/recon/disocclusion.ts +54 -3
- package/src/render/recon/lateRouting.ts +29 -0
- package/src/render/recon/mover.ts +118 -0
- package/src/render/recon/resolve.ts +137 -10
- package/src/render/renderQuality.ts +47 -0
- package/src/render/rendererHarness.ts +12 -0
- package/src/render/sceneTarget.ts +66 -12
- package/src/render/shaders/depth.ts +109 -9
- package/src/render/shaders/flat/directionalShadow.ts +97 -6
- package/src/render/shaders/flat/glassTint.ts +25 -0
- package/src/render/shaders/flat/index.ts +17 -0
- package/src/render/shaders/flat/lampGlass.ts +185 -0
- package/src/render/shaders/flat/main.ts +115 -15
- package/src/render/shaders/flat/pointShadow.ts +2 -0
- package/src/render/shaders/flat/preamble.ts +21 -6
- package/src/render/shaders/generated/depth.wgsl.ts +234 -4
- package/src/render/shaders/generated/flat.wgsl.ts +376 -328
- package/src/render/shaders/generated/globalMedium.wgsl.ts +3 -13
- package/src/render/shaders/generated/lightVolume.wgsl.ts +4 -14
- package/src/render/shaders/generated/octahedralResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
- package/src/render/shaders/globalMedium.ts +6 -6
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +88 -1
- package/src/render/shaders/gpudriven/cull.wgsl.ts +11 -2
- package/src/render/shaders/gpudriven/materialTable.wgsl.ts +3 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +50 -1
- package/src/render/shaders/gpudriven/sunGlass.wgsl.ts +139 -0
- package/src/render/shaders/lightVolume.ts +6 -6
- package/src/render/shaders/octahedralResolve.ts +50 -17
- package/src/render/shaders/recon/lateDepth.wgsl.ts +47 -0
- package/src/render/shaders/recon/motion.wgsl.ts +84 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
- package/src/render/shaders/temporalResolve.ts +141 -5
- package/src/render/shadowCasters.ts +28 -1
- package/src/render/shadowMap.ts +231 -37
- package/src/render/sunGlassTint.ts +86 -0
- package/src/render/temporalAa.ts +227 -0
- package/src/render/temporalPass.ts +88 -12
- package/src/render/translucentQueue.ts +20 -1
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
* The vertex stage is `FULLSCREEN_VERT`, shared with the occlusion pass, its blur and the rush.
|
|
21
21
|
*/
|
|
22
22
|
import { glslSceneDepthToNdc } from '../depthConvention.ts';
|
|
23
|
+
import {
|
|
24
|
+
FLICKER_BLEND,
|
|
25
|
+
FLICKER_MEMORY,
|
|
26
|
+
FLICKER_REPEAT,
|
|
27
|
+
FLICKER_SURFACE,
|
|
28
|
+
FLICKER_WIDTH,
|
|
29
|
+
STILL_FROM,
|
|
30
|
+
STILL_TO,
|
|
31
|
+
} from '../temporalAa.ts';
|
|
23
32
|
|
|
24
33
|
export const TEMPORAL_RESOLVE_FRAG = `#version 300 es
|
|
25
34
|
precision highp float;
|
|
@@ -30,6 +39,22 @@ in vec2 vUv;
|
|
|
30
39
|
uniform sampler2D uScene;
|
|
31
40
|
/** What this pass wrote last frame: resolved, and conceptually unjittered. */
|
|
32
41
|
uniform sampler2D uHistory;
|
|
42
|
+
/**
|
|
43
|
+
* Each pixel's flicker record from last frame, the anti-flicker in temporalAa.ts: its spread about
|
|
44
|
+
* its period's mean, the sum this period has gathered, the last period's mean, and how many periods
|
|
45
|
+
* running have repeated. highp for the reason the depth's is: a luma in a half-float scene is not a
|
|
46
|
+
* value from 0 to 1.
|
|
47
|
+
*/
|
|
48
|
+
uniform highp sampler2D uFlicker;
|
|
49
|
+
/**
|
|
50
|
+
* The rest of the record: the view depth the pixel's sample stood at, so the next frame can tell
|
|
51
|
+
* whether it follows the same surface, and the pixel's own depth swing, which that test allows for.
|
|
52
|
+
*/
|
|
53
|
+
uniform highp sampler2D uFlickerMotion;
|
|
54
|
+
/** The inverse projection's depth row and w row, as the other passes read a depth into metres. */
|
|
55
|
+
uniform vec4 uDepthToViewZ;
|
|
56
|
+
/** One where the records can hold what they hold, zero where they cannot: then 4.4.1's resolve. */
|
|
57
|
+
uniform float uAntiFlicker;
|
|
33
58
|
/**
|
|
34
59
|
* The frame's depth.
|
|
35
60
|
*
|
|
@@ -53,6 +78,8 @@ uniform highp sampler2D uDepth;
|
|
|
53
78
|
uniform mat4 uReprojection;
|
|
54
79
|
/** One texel of the target, for walking the neighbourhood. */
|
|
55
80
|
uniform vec2 uTexel;
|
|
81
|
+
/** One on the first frame of the jitter's period, where the anti-flicker compares two periods. */
|
|
82
|
+
uniform float uPeriodStart;
|
|
56
83
|
/**
|
|
57
84
|
* How much of the clipped history to keep, 0 to 1.
|
|
58
85
|
*
|
|
@@ -63,7 +90,41 @@ uniform vec2 uTexel;
|
|
|
63
90
|
*/
|
|
64
91
|
uniform float uHistoryBlend;
|
|
65
92
|
|
|
66
|
-
out vec4 fragColor;
|
|
93
|
+
layout(location = 0) out vec4 fragColor;
|
|
94
|
+
/** The record carried out, in the texture uFlicker reads next frame. */
|
|
95
|
+
layout(location = 1) out vec4 flickerOut;
|
|
96
|
+
/** And the rest of it, in the texture uFlickerMotion reads. */
|
|
97
|
+
layout(location = 2) out vec4 flickerMotionOut;
|
|
98
|
+
|
|
99
|
+
const float FLICKER_MEMORY = ${FLICKER_MEMORY.toFixed(3)};
|
|
100
|
+
const float FLICKER_WIDTH = ${FLICKER_WIDTH.toFixed(1)};
|
|
101
|
+
const float FLICKER_REPEAT = ${FLICKER_REPEAT.toFixed(2)};
|
|
102
|
+
const float FLICKER_BLEND = ${FLICKER_BLEND.toFixed(2)};
|
|
103
|
+
const float FLICKER_SURFACE = ${FLICKER_SURFACE.toFixed(2)};
|
|
104
|
+
const float STILL_FROM = ${STILL_FROM.toFixed(2)};
|
|
105
|
+
const float STILL_TO = ${STILL_TO.toFixed(2)};
|
|
106
|
+
|
|
107
|
+
float luma(vec3 c) {
|
|
108
|
+
return dot(c, vec3(0.2126, 0.7152, 0.0722));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** A record starting over: no spread, and nothing repeated yet. */
|
|
112
|
+
vec4 freshRecord(vec3 current) {
|
|
113
|
+
float seen = luma(current);
|
|
114
|
+
return vec4(0.0, seen * FLICKER_MEMORY, seen, 0.0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Metres in front of the camera, from a depth sample, as depthOfField.ts reads one. */
|
|
118
|
+
float viewDepthOf(float depth) {
|
|
119
|
+
float z = ${glslSceneDepthToNdc('depth')};
|
|
120
|
+
return -(uDepthToViewZ.x * z + uDepthToViewZ.y) / (uDepthToViewZ.z * z + uDepthToViewZ.w);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Whether two periods' means are the same, one to zero. */
|
|
124
|
+
float repeats(float mean, float previous) {
|
|
125
|
+
float apart = abs(mean - previous) / max(max(mean, previous), 1.0 / 255.0);
|
|
126
|
+
return 1.0 - smoothstep(FLICKER_REPEAT, 2.0 * FLICKER_REPEAT, apart);
|
|
127
|
+
}
|
|
67
128
|
|
|
68
129
|
/**
|
|
69
130
|
* The colours actually present around this pixel this frame, as a box.
|
|
@@ -155,19 +216,45 @@ void main() {
|
|
|
155
216
|
/* Nothing to blend towards: the first frame, a resize, a cut, or the effect switched off. */
|
|
156
217
|
if (uHistoryBlend <= 0.0) {
|
|
157
218
|
fragColor = vec4(current, 1.0);
|
|
219
|
+
flickerOut = freshRecord(current);
|
|
220
|
+
flickerMotionOut = vec4(0.0);
|
|
158
221
|
return;
|
|
159
222
|
}
|
|
160
223
|
|
|
224
|
+
/*
|
|
225
|
+
* **The motion is the nearest surface's among the nine**, the dilation every temporal resolve
|
|
226
|
+
* uses: a pixel at a thin thing samples it in some frames and what is behind it in others, and the
|
|
227
|
+
* two move apart as the camera does, so reprojecting each frame by whichever it caught sends its
|
|
228
|
+
* history and its record somewhere different every frame. A crack of light round a door moves with
|
|
229
|
+
* the door that frames it, and so does its pixel. The depth kept is that surface's too, so the record
|
|
230
|
+
* keeps and next frame tests the depth the reprojection followed.
|
|
231
|
+
*/
|
|
232
|
+
vec2 nearestAt = vUv;
|
|
161
233
|
float depth = textureLod(uDepth, vUv, 0.0).r;
|
|
162
|
-
|
|
234
|
+
float metres = viewDepthOf(depth);
|
|
235
|
+
for (int y = -1; y <= 1; y++) {
|
|
236
|
+
for (int x = -1; x <= 1; x++) {
|
|
237
|
+
vec2 at = vUv + vec2(float(x), float(y)) * uTexel;
|
|
238
|
+
float tap = textureLod(uDepth, at, 0.0).r;
|
|
239
|
+
float tapMetres = viewDepthOf(tap);
|
|
240
|
+
if (tapMetres < metres) {
|
|
241
|
+
metres = tapMetres;
|
|
242
|
+
depth = tap;
|
|
243
|
+
nearestAt = at;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
vec4 clip = vec4(nearestAt * 2.0 - 1.0, ${glslSceneDepthToNdc('depth')}, 1.0);
|
|
163
248
|
vec4 previous = uReprojection * clip;
|
|
164
249
|
/* Behind the previous eye: there is no last-frame position to sample. */
|
|
165
250
|
if (previous.w <= 0.0) {
|
|
166
251
|
fragColor = vec4(current, 1.0);
|
|
252
|
+
flickerOut = freshRecord(current);
|
|
253
|
+
flickerMotionOut = vec4(metres, 0.0, 0.0, 0.0);
|
|
167
254
|
return;
|
|
168
255
|
}
|
|
169
256
|
|
|
170
|
-
vec2 wasUv = (previous.xy / previous.w) * 0.5 + 0.5;
|
|
257
|
+
vec2 wasUv = vUv + ((previous.xy / previous.w) * 0.5 + 0.5 - nearestAt);
|
|
171
258
|
/*
|
|
172
259
|
* **Off the edge of the last frame is a disocclusion**, and the honest answer is this frame's
|
|
173
260
|
* colour rather than a clamped sample of the border, which would drag one row of pixels inward
|
|
@@ -175,6 +262,8 @@ void main() {
|
|
|
175
262
|
*/
|
|
176
263
|
if (wasUv.x < 0.0 || wasUv.x > 1.0 || wasUv.y < 0.0 || wasUv.y > 1.0) {
|
|
177
264
|
fragColor = vec4(current, 1.0);
|
|
265
|
+
flickerOut = freshRecord(current);
|
|
266
|
+
flickerMotionOut = vec4(metres, 0.0, 0.0, 0.0);
|
|
178
267
|
return;
|
|
179
268
|
}
|
|
180
269
|
|
|
@@ -182,8 +271,55 @@ void main() {
|
|
|
182
271
|
vec3 hi;
|
|
183
272
|
neighbourhood(lo, hi);
|
|
184
273
|
vec3 history = historyCatmullRom(wasUv);
|
|
185
|
-
vec3 bounded = clipToNeighbourhood(history, lo, hi);
|
|
186
274
|
|
|
187
|
-
|
|
275
|
+
/*
|
|
276
|
+
* The anti-flicker, operation for operation the one in temporalAa.ts. The record is read at the
|
|
277
|
+
* texel where the surface was rather than filtered, so two surfaces' records never mix at an
|
|
278
|
+
* edge; it is the surface's own if last frame's depth there, or at one of the texels beside it,
|
|
279
|
+
* is the depth this surface should have had — the nearest of five, because this frame's sample
|
|
280
|
+
* and last frame's can straddle an edge with nothing moved. Otherwise it is no record.
|
|
281
|
+
*/
|
|
282
|
+
ivec2 size = textureSize(uFlicker, 0);
|
|
283
|
+
ivec2 was = clamp(ivec2(wasUv * vec2(size)), ivec2(0), size - 1);
|
|
284
|
+
float nearest = 1e9;
|
|
285
|
+
for (int n = 0; n < 5; n++) {
|
|
286
|
+
ivec2 at = was + (n == 1 ? ivec2(1, 0) : n == 2 ? ivec2(-1, 0) : n == 3 ? ivec2(0, 1) : n == 4 ? ivec2(0, -1) : ivec2(0));
|
|
287
|
+
float stood = texelFetch(uFlickerMotion, clamp(at, ivec2(0), size - 1), 0).r;
|
|
288
|
+
if (stood > 0.0) nearest = min(nearest, abs(stood - previous.w));
|
|
289
|
+
}
|
|
290
|
+
/* The swing is the pixel's whatever the test says: resetting it would stop it learning one. */
|
|
291
|
+
vec4 motionThere = texelFetch(uFlickerMotion, was, 0);
|
|
292
|
+
bool missedAll = nearest > 1e8;
|
|
293
|
+
bool same = uAntiFlicker > 0.5 && !missedAll &&
|
|
294
|
+
nearest <= FLICKER_WIDTH * motionThere.g + FLICKER_SURFACE * previous.w;
|
|
295
|
+
float swingOut = missedAll ? 0.0 : motionThere.g + (nearest - motionThere.g) * FLICKER_MEMORY;
|
|
296
|
+
float seen = luma(current);
|
|
297
|
+
vec4 held = same ? texelFetch(uFlicker, was, 0) : freshRecord(current);
|
|
298
|
+
|
|
299
|
+
float still = 1.0 - smoothstep(STILL_FROM, STILL_TO, length((wasUv - vUv) / uTexel));
|
|
300
|
+
float apart = abs(seen - held.b);
|
|
301
|
+
float reach = FLICKER_WIDTH * held.r + 1.0 / 255.0;
|
|
302
|
+
bool within = apart <= reach;
|
|
303
|
+
bool proven = held.a > 0.75 && within;
|
|
304
|
+
float widen = proven ? FLICKER_WIDTH * held.r * still : 0.0;
|
|
305
|
+
float blend = proven ? mix(uHistoryBlend, FLICKER_BLEND, still) : uHistoryBlend;
|
|
306
|
+
vec3 bounded = clipToNeighbourhood(history, lo - widen, hi + widen);
|
|
307
|
+
|
|
308
|
+
float sum = held.g;
|
|
309
|
+
float mean = held.b;
|
|
310
|
+
float repeated = within ? held.a : 0.0;
|
|
311
|
+
if (uPeriodStart > 0.5) {
|
|
312
|
+
repeated = repeats(sum, mean) > 0.5 ? min(1.0, repeated + 0.5) : 0.0;
|
|
313
|
+
mean = sum;
|
|
314
|
+
sum = 0.0;
|
|
315
|
+
}
|
|
316
|
+
flickerOut = vec4(
|
|
317
|
+
held.r + (apart - held.r) * FLICKER_MEMORY,
|
|
318
|
+
sum + seen * FLICKER_MEMORY,
|
|
319
|
+
mean,
|
|
320
|
+
repeated * still
|
|
321
|
+
);
|
|
322
|
+
flickerMotionOut = vec4(metres, swingOut, 0.0, 0.0);
|
|
323
|
+
fragColor = vec4(mix(current, bounded, blend), 1.0);
|
|
188
324
|
}
|
|
189
325
|
`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveGlass, type GlassOptions, type ResolvedGlass } from './glass.ts';
|
|
1
2
|
import type { ReadonlyMat4 } from 'gl-matrix';
|
|
2
3
|
import type { InstanceData } from './instancedMesh.ts';
|
|
3
4
|
import type { InstancedHandle, MeshHandle, ScatterHandle } from './backend/api.ts';
|
|
@@ -41,7 +42,33 @@ import type { SurfaceTextureHandle } from './backend/api.ts';
|
|
|
41
42
|
* outwards, so a required parameter added in a minor stops every consumer's sink and every test
|
|
42
43
|
* double compiling. A sink that ignores it behaves exactly as it did.
|
|
43
44
|
*/
|
|
44
|
-
export type SceneCasterMaterial =
|
|
45
|
+
export type SceneCasterMaterial =
|
|
46
|
+
| (SurfaceMaterial<SurfaceTextureHandle> & {
|
|
47
|
+
/**
|
|
48
|
+
* The caster is glass: it lets light through, so it writes no depth into any shadow map and a
|
|
49
|
+
* lamp behind it lights what is beyond. The colour replay into a mirror or a probe still draws
|
|
50
|
+
* it. With `RenderQuality.glassShadows` on it casts a coloured shadow instead: see
|
|
51
|
+
* `glassShadow.ts`.
|
|
52
|
+
*/
|
|
53
|
+
readonly glass?: GlassOptions;
|
|
54
|
+
})
|
|
55
|
+
| null;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether a caster writes depth into a shadow map: everything but glass. One line in each depth
|
|
59
|
+
* sink, so the answer is the same in every shadow a backend draws.
|
|
60
|
+
*/
|
|
61
|
+
export function castsDepth(material: SceneCasterMaterial | undefined): boolean {
|
|
62
|
+
return (material?.glass?.transmission ?? 0) <= 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The caster's glass, resolved into `out`, and whether it is glass at all — the rule `castsDepth`
|
|
67
|
+
* states from the other side, so a caster is exactly one of the two.
|
|
68
|
+
*/
|
|
69
|
+
export function glassOf(material: SceneCasterMaterial | undefined, out: ResolvedGlass): boolean {
|
|
70
|
+
return resolveGlass(material?.glass, out);
|
|
71
|
+
}
|
|
45
72
|
|
|
46
73
|
export interface ShadowCasterSink {
|
|
47
74
|
/** A rigid mesh at a model transform: the world, a prop, a static piece of scenery. */
|
package/src/render/shadowMap.ts
CHANGED
|
@@ -1,49 +1,106 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* the
|
|
2
|
+
* The sun's shadow: one depth array whose layers are the static world, its movers, and the static
|
|
3
|
+
* world's second occluder (the peel), drawn one at a time and sampled together.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* **One array rather than three maps since 4.5.0**, for the reason the lamps' twelve cubemaps
|
|
6
|
+
* became one: a texture unit. The lit pass binds all three through one `sampler2DArray`, which is
|
|
7
|
+
* what paid for the two tints glass casts with. The layers share one size and one format, so
|
|
8
|
+
* nothing about how each is drawn or read moved — only where it lives.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* payload
|
|
10
|
+
* Filtering belongs to the receiving material shader, so the depth target can stay a
|
|
11
|
+
* single-sample texture shared by every mesh pass. This is a depth *render target*, not an art
|
|
12
|
+
* texture: AGENTS.md's "zero textures" rule is about the payload, and nothing here adds to it.
|
|
11
13
|
*/
|
|
12
14
|
import { SHADOW_DEPTH_CLEAR } from './depthConvention.ts';
|
|
13
15
|
import { DEFAULT_RENDER_QUALITY } from './renderQuality.ts';
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
/** Where each of the sun's maps lives in the array. The shaders read these same numbers. */
|
|
18
|
+
export const SUN_STATIC_LAYER = 0;
|
|
19
|
+
export const SUN_DYNAMIC_LAYER = 1;
|
|
20
|
+
/** Present only when more than one static depth layer is asked for. */
|
|
21
|
+
export const SUN_PEELED_LAYER = 2;
|
|
22
|
+
/**
|
|
23
|
+
* Where the nearest pane's depth lives once glass has cast: after the three maps, at fixed places,
|
|
24
|
+
* because a shader cannot ask an array how many layers it has here — naga refuses the third
|
|
25
|
+
* component of an arrayed size query. A profile with no peel grows past an unused third layer to
|
|
26
|
+
* reach them, which is one map's memory in a profile that already declined one.
|
|
27
|
+
*/
|
|
28
|
+
export const SUN_GLASS_STATIC_LAYER = 3;
|
|
29
|
+
export const SUN_GLASS_MOVING_LAYER = 4;
|
|
30
|
+
|
|
31
|
+
/** The three passes a scene opens, by the names `beginShadowPass` takes. */
|
|
32
|
+
export type SunShadowLayer = 'static' | 'static-peel' | 'dynamic';
|
|
33
|
+
|
|
34
|
+
/** The array layer a pass draws into. */
|
|
35
|
+
export function sunLayerIndex(layer: SunShadowLayer): number {
|
|
36
|
+
return layer === 'static'
|
|
37
|
+
? SUN_STATIC_LAYER
|
|
38
|
+
: layer === 'dynamic'
|
|
39
|
+
? SUN_DYNAMIC_LAYER
|
|
40
|
+
: SUN_PEELED_LAYER;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class SunShadowArray {
|
|
16
44
|
readonly size: number;
|
|
17
|
-
|
|
45
|
+
/** Replaced once, when glass first casts: see `growForGlass`. */
|
|
46
|
+
private current: WebGLTexture;
|
|
47
|
+
private layerCount: number;
|
|
48
|
+
private glass = false;
|
|
18
49
|
|
|
19
50
|
private readonly framebuffer: WebGLFramebuffer;
|
|
51
|
+
/**
|
|
52
|
+
* **The peel is drawn here and copied into its layer when the pass ends.** It reads the static
|
|
53
|
+
* layer while it is drawn, and WebGL2 calls a texture both sampled and attached a feedback loop
|
|
54
|
+
* whatever layer each names — so drawing the peel into the array would draw nothing at all.
|
|
55
|
+
* What it costs is one more map's memory where a peel is asked for, and one blit per peel.
|
|
56
|
+
*/
|
|
57
|
+
private readonly peelTexture: WebGLTexture | null;
|
|
58
|
+
private readonly peelFramebuffer: WebGLFramebuffer | null;
|
|
59
|
+
private peeling = false;
|
|
20
60
|
|
|
21
|
-
constructor(
|
|
61
|
+
constructor(
|
|
62
|
+
gl: WebGL2RenderingContext,
|
|
63
|
+
size = DEFAULT_RENDER_QUALITY.directionalShadowMapSize,
|
|
64
|
+
peeled = false,
|
|
65
|
+
) {
|
|
22
66
|
this.size = size;
|
|
67
|
+
this.layerCount = peeled ? 3 : 2;
|
|
23
68
|
|
|
24
|
-
const texture = gl.
|
|
25
|
-
|
|
26
|
-
this.texture = texture;
|
|
27
|
-
|
|
28
|
-
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
29
|
-
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.DEPTH_COMPONENT24, size, size);
|
|
30
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
31
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
32
|
-
// Clamp so anything outside the light frustum samples the border and is
|
|
33
|
-
// treated as lit, rather than wrapping into a spurious shadow.
|
|
34
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
35
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
69
|
+
const texture = createSunTexture(gl, size, this.layerCount);
|
|
70
|
+
this.current = texture;
|
|
36
71
|
|
|
37
72
|
const framebuffer = gl.createFramebuffer();
|
|
38
|
-
if (framebuffer === null) throw new Error('
|
|
73
|
+
if (framebuffer === null) throw new Error('SunShadowArray: createFramebuffer failed');
|
|
39
74
|
this.framebuffer = framebuffer;
|
|
40
|
-
|
|
41
75
|
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
42
|
-
gl.
|
|
76
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, SUN_STATIC_LAYER);
|
|
43
77
|
// Depth-only: without this WebGL expects a colour attachment.
|
|
44
78
|
gl.drawBuffers([gl.NONE]);
|
|
45
79
|
gl.readBuffer(gl.NONE);
|
|
46
80
|
|
|
81
|
+
if (peeled) {
|
|
82
|
+
const peelTexture = gl.createTexture();
|
|
83
|
+
const peelFramebuffer = gl.createFramebuffer();
|
|
84
|
+
if (peelTexture === null || peelFramebuffer === null) {
|
|
85
|
+
throw new Error('SunShadowArray: the peel target could not be created');
|
|
86
|
+
}
|
|
87
|
+
gl.bindTexture(gl.TEXTURE_2D, peelTexture);
|
|
88
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.DEPTH_COMPONENT24, size, size);
|
|
89
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
90
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
91
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, peelFramebuffer);
|
|
92
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, peelTexture, 0);
|
|
93
|
+
gl.drawBuffers([gl.NONE]);
|
|
94
|
+
gl.readBuffer(gl.NONE);
|
|
95
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
96
|
+
this.peelTexture = peelTexture;
|
|
97
|
+
this.peelFramebuffer = peelFramebuffer;
|
|
98
|
+
} else {
|
|
99
|
+
this.peelTexture = null;
|
|
100
|
+
this.peelFramebuffer = null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
47
104
|
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
48
105
|
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
|
49
106
|
/*
|
|
@@ -52,29 +109,121 @@ export class ShadowMap {
|
|
|
52
109
|
* because the context cannot be lost before it exists.
|
|
53
110
|
*/
|
|
54
111
|
if (gl.isContextLost()) return;
|
|
55
|
-
throw new Error(`
|
|
112
|
+
throw new Error(`SunShadowArray: framebuffer incomplete (0x${status.toString(16)})`);
|
|
56
113
|
}
|
|
57
114
|
|
|
58
115
|
/*
|
|
59
|
-
* **
|
|
60
|
-
* WebGL zeroes
|
|
61
|
-
* a layer no pass has drawn put the whole world in shade. ANGLE on Vulkan
|
|
62
|
-
* texture back reading 1, which is lit, and that is what every capture here
|
|
63
|
-
* with a second depth layer it never peeled lost its sun only under ANGLE's
|
|
64
|
-
* one Chrome on Linux runs by default. The frame's clear value is
|
|
116
|
+
* **Every layer cleared once, here, to the far plane, because nothing else says what it
|
|
117
|
+
* holds.** WebGL zeroes the texture, and a zero compared with `LEQUAL` is an occluder at the
|
|
118
|
+
* light itself, so a layer no pass has drawn put the whole world in shade. ANGLE on Vulkan
|
|
119
|
+
* hands the same texture back reading 1, which is lit, and that is what every capture here
|
|
120
|
+
* ran on: a scene with a second depth layer it never peeled lost its sun only under ANGLE's
|
|
121
|
+
* GL backend, the one Chrome on Linux runs by default. The frame's clear value is put back.
|
|
65
122
|
*/
|
|
66
123
|
const frameClear = gl.getParameter(gl.DEPTH_CLEAR_VALUE) as number;
|
|
67
124
|
gl.clearDepth(SHADOW_DEPTH_CLEAR);
|
|
68
|
-
gl.
|
|
125
|
+
gl.viewport(0, 0, size, size);
|
|
126
|
+
for (let layer = 0; layer < this.layers; layer++) {
|
|
127
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, layer);
|
|
128
|
+
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
129
|
+
}
|
|
69
130
|
gl.clearDepth(frameClear);
|
|
70
131
|
|
|
71
132
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
72
|
-
gl.bindTexture(gl.
|
|
133
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get texture(): WebGLTexture {
|
|
137
|
+
return this.current;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get layers(): number {
|
|
141
|
+
return this.layerCount;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Whether the two glass layers exist: from the first glass a sun pass is offered, for good. */
|
|
145
|
+
get hasGlass(): boolean {
|
|
146
|
+
return this.glass;
|
|
73
147
|
}
|
|
74
148
|
|
|
75
|
-
/**
|
|
76
|
-
|
|
149
|
+
/** The glass depth layer beside a map: fixed places after the three maps. */
|
|
150
|
+
glassLayer(which: 'static' | 'dynamic'): number {
|
|
151
|
+
return which === 'static' ? SUN_GLASS_STATIC_LAYER : SUN_GLASS_MOVING_LAYER;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Add the two glass layers, once, carrying every layer already held across.
|
|
156
|
+
*
|
|
157
|
+
* **Carried rather than re-baked**, because the sun's static map is the consumer's to bake and
|
|
158
|
+
* it may bake it once: the glass that grows the array arrives in the very pass that drew that
|
|
159
|
+
* map, so a copy is what keeps it. A depth blit per layer, same size and format, and the glass
|
|
160
|
+
* layers cleared to the far plane — nothing nearer than the sun until a pane is drawn there.
|
|
161
|
+
* Returns whether it grew, so the caller knows every bind of the old texture is stale.
|
|
162
|
+
*/
|
|
163
|
+
growForGlass(gl: WebGL2RenderingContext): boolean {
|
|
164
|
+
if (this.glass) return false;
|
|
165
|
+
const old = this.current;
|
|
166
|
+
const oldLayers = this.layerCount;
|
|
167
|
+
const layers = SUN_GLASS_MOVING_LAYER + 1;
|
|
168
|
+
const texture = createSunTexture(gl, this.size, layers);
|
|
169
|
+
const read = gl.createFramebuffer();
|
|
170
|
+
if (read === null) throw new Error('SunShadowArray: createFramebuffer failed');
|
|
171
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read);
|
|
172
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
|
|
173
|
+
for (let layer = 0; layer < oldLayers; layer++) {
|
|
174
|
+
gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, old, 0, layer);
|
|
175
|
+
gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, layer);
|
|
176
|
+
gl.blitFramebuffer(
|
|
177
|
+
0,
|
|
178
|
+
0,
|
|
179
|
+
this.size,
|
|
180
|
+
this.size,
|
|
181
|
+
0,
|
|
182
|
+
0,
|
|
183
|
+
this.size,
|
|
184
|
+
this.size,
|
|
185
|
+
gl.DEPTH_BUFFER_BIT,
|
|
186
|
+
gl.NEAREST,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
for (let layer = oldLayers; layer < layers; layer++) {
|
|
190
|
+
gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texture, 0, layer);
|
|
191
|
+
gl.clearBufferfv(gl.DEPTH, 0, FAR_PLANE);
|
|
192
|
+
}
|
|
193
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
|
|
194
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
195
|
+
gl.deleteFramebuffer(read);
|
|
196
|
+
gl.deleteTexture(old);
|
|
197
|
+
this.current = texture;
|
|
198
|
+
this.layerCount = layers;
|
|
199
|
+
this.glass = true;
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Bind `layer` as the render target and clear it. Caller then draws the casters. */
|
|
204
|
+
begin(gl: WebGL2RenderingContext, layer: SunShadowLayer): void {
|
|
205
|
+
this.peeling = layer === 'static-peel' && this.peelFramebuffer !== null;
|
|
206
|
+
if (this.peeling) {
|
|
207
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.peelFramebuffer);
|
|
208
|
+
} else {
|
|
209
|
+
this.attach(gl, sunLayerIndex(layer));
|
|
210
|
+
}
|
|
211
|
+
this.clearAndRaster(gl);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Bind a glass depth layer as the render target and clear it: where the nearest pane is. */
|
|
215
|
+
beginGlass(gl: WebGL2RenderingContext, which: 'static' | 'dynamic'): void {
|
|
216
|
+
this.peeling = false;
|
|
217
|
+
this.attach(gl, this.glassLayer(which));
|
|
218
|
+
this.clearAndRaster(gl);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private attach(gl: WebGL2RenderingContext, layer: number): void {
|
|
77
222
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
223
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, this.current, 0, layer);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private clearAndRaster(gl: WebGL2RenderingContext): void {
|
|
78
227
|
gl.viewport(0, 0, this.size, this.size);
|
|
79
228
|
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
80
229
|
/*
|
|
@@ -93,11 +242,56 @@ export class ShadowMap {
|
|
|
93
242
|
end(gl: WebGL2RenderingContext): void {
|
|
94
243
|
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
95
244
|
gl.cullFace(gl.BACK);
|
|
245
|
+
if (this.peeling) {
|
|
246
|
+
/* The peel into its layer: same size, same format, so a nearest depth blit is a copy. */
|
|
247
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.peelFramebuffer);
|
|
248
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.framebuffer);
|
|
249
|
+
gl.framebufferTextureLayer(
|
|
250
|
+
gl.DRAW_FRAMEBUFFER,
|
|
251
|
+
gl.DEPTH_ATTACHMENT,
|
|
252
|
+
this.current,
|
|
253
|
+
0,
|
|
254
|
+
SUN_PEELED_LAYER,
|
|
255
|
+
);
|
|
256
|
+
gl.blitFramebuffer(
|
|
257
|
+
0,
|
|
258
|
+
0,
|
|
259
|
+
this.size,
|
|
260
|
+
this.size,
|
|
261
|
+
0,
|
|
262
|
+
0,
|
|
263
|
+
this.size,
|
|
264
|
+
this.size,
|
|
265
|
+
gl.DEPTH_BUFFER_BIT,
|
|
266
|
+
gl.NEAREST,
|
|
267
|
+
);
|
|
268
|
+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
|
|
269
|
+
this.peeling = false;
|
|
270
|
+
}
|
|
96
271
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
97
272
|
}
|
|
98
273
|
|
|
99
274
|
dispose(gl: WebGL2RenderingContext): void {
|
|
100
275
|
gl.deleteFramebuffer(this.framebuffer);
|
|
101
|
-
gl.deleteTexture(this.
|
|
276
|
+
gl.deleteTexture(this.current);
|
|
277
|
+
if (this.peelFramebuffer !== null) gl.deleteFramebuffer(this.peelFramebuffer);
|
|
278
|
+
if (this.peelTexture !== null) gl.deleteTexture(this.peelTexture);
|
|
102
279
|
}
|
|
103
280
|
}
|
|
281
|
+
|
|
282
|
+
/** The far plane, as `clearBufferfv` takes it: a layer no pane has reached is clear of glass. */
|
|
283
|
+
const FAR_PLANE = new Float32Array([SHADOW_DEPTH_CLEAR]);
|
|
284
|
+
|
|
285
|
+
function createSunTexture(gl: WebGL2RenderingContext, size: number, layers: number): WebGLTexture {
|
|
286
|
+
const texture = gl.createTexture();
|
|
287
|
+
if (texture === null) throw new Error('SunShadowArray: createTexture failed');
|
|
288
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
289
|
+
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.DEPTH_COMPONENT24, size, size, layers);
|
|
290
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
291
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
292
|
+
// Clamp so anything outside the light frustum samples the border and is
|
|
293
|
+
// treated as lit, rather than wrapping into a spurious shadow.
|
|
294
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
295
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
296
|
+
return texture;
|
|
297
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the sun's light keeps of itself through glass: an RGBA8 array of two layers, static and
|
|
3
|
+
* moving, beside the sun's depth array. RGB is the product of every pane's
|
|
4
|
+
* `transmission × (1 − F) × tint`, A the product of their clarities (`glassShadow.ts`).
|
|
5
|
+
*
|
|
6
|
+
* **Cleared to white and drawn with a multiplying blend and no depth test**, so every pane on a ray
|
|
7
|
+
* contributes and the order they come in cannot matter. **Mipmapped and filtered**, unlike the
|
|
8
|
+
* depths beside it: a frosted pane's colour is read at the level its spread asks for, so a wide
|
|
9
|
+
* spread is smooth rather than a scatter of taps — the chain is rebuilt after every draw of a layer.
|
|
10
|
+
* `'half'` makes it half the depth map's size and nothing else changes: the light matrix is the
|
|
11
|
+
* same, so a texel is a texel of the same place, only larger.
|
|
12
|
+
*/
|
|
13
|
+
export class SunGlassTint {
|
|
14
|
+
readonly size: number;
|
|
15
|
+
readonly texture: WebGLTexture;
|
|
16
|
+
private readonly framebuffer: WebGLFramebuffer;
|
|
17
|
+
|
|
18
|
+
constructor(gl: WebGL2RenderingContext, size: number) {
|
|
19
|
+
this.size = size;
|
|
20
|
+
const texture = gl.createTexture();
|
|
21
|
+
const framebuffer = gl.createFramebuffer();
|
|
22
|
+
if (texture === null || framebuffer === null) {
|
|
23
|
+
throw new Error('SunGlassTint: the tint target could not be created');
|
|
24
|
+
}
|
|
25
|
+
this.texture = texture;
|
|
26
|
+
this.framebuffer = framebuffer;
|
|
27
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
|
|
28
|
+
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, mipLevels(size), gl.RGBA8, size, size, 2);
|
|
29
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
30
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
31
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
32
|
+
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
33
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
|
|
34
|
+
/* Both layers born clear: a layer no pane has reached lets everything through. */
|
|
35
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
36
|
+
for (let layer = 0; layer < 2; layer++) {
|
|
37
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture, 0, layer);
|
|
38
|
+
gl.clearBufferfv(gl.COLOR, 0, CLEAR);
|
|
39
|
+
}
|
|
40
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
41
|
+
this.fillMips(gl);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Bind `layer` (0 static, 1 moving), clear it to white, and set the multiplying state. */
|
|
45
|
+
begin(gl: WebGL2RenderingContext, layer: number): void {
|
|
46
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
|
47
|
+
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, this.texture, 0, layer);
|
|
48
|
+
gl.viewport(0, 0, this.size, this.size);
|
|
49
|
+
gl.clearBufferfv(gl.COLOR, 0, CLEAR);
|
|
50
|
+
gl.disable(gl.DEPTH_TEST);
|
|
51
|
+
gl.depthMask(false);
|
|
52
|
+
gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
53
|
+
gl.enable(gl.BLEND);
|
|
54
|
+
gl.blendEquation(gl.FUNC_ADD);
|
|
55
|
+
gl.blendFuncSeparate(gl.DST_COLOR, gl.ZERO, gl.DST_ALPHA, gl.ZERO);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Put back what `begin` changed, and rebuild the mip chain the frost reads. */
|
|
59
|
+
end(gl: WebGL2RenderingContext): void {
|
|
60
|
+
gl.disable(gl.BLEND);
|
|
61
|
+
gl.depthMask(true);
|
|
62
|
+
gl.enable(gl.DEPTH_TEST);
|
|
63
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
64
|
+
this.fillMips(gl);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Every level from level 0, both layers: bound on the active unit and handed back. */
|
|
68
|
+
private fillMips(gl: WebGL2RenderingContext): void {
|
|
69
|
+
const previous = gl.getParameter(gl.TEXTURE_BINDING_2D_ARRAY) as WebGLTexture | null;
|
|
70
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture);
|
|
71
|
+
gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
|
|
72
|
+
gl.bindTexture(gl.TEXTURE_2D_ARRAY, previous);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
dispose(gl: WebGL2RenderingContext): void {
|
|
76
|
+
gl.deleteFramebuffer(this.framebuffer);
|
|
77
|
+
gl.deleteTexture(this.texture);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const CLEAR = new Float32Array([1, 1, 1, 1]);
|
|
82
|
+
|
|
83
|
+
/** A full chain down to one texel. */
|
|
84
|
+
function mipLevels(size: number): number {
|
|
85
|
+
return Math.floor(Math.log2(Math.max(1, size))) + 1;
|
|
86
|
+
}
|