@driftengine/core 4.3.0 → 4.4.1
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 +282 -0
- package/README.md +1 -1
- package/dist/cinematic/cameraPath.d.ts +53 -0
- package/dist/cinematic/cameraPath.js +118 -0
- package/dist/environment/daylight.d.ts +77 -0
- package/dist/environment/daylight.js +77 -0
- package/dist/geometry/meshConcat.d.ts +9 -0
- package/dist/geometry/meshConcat.js +101 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +14 -1
- package/dist/render/ambientOcclusionPass.js +5 -1
- package/dist/render/backend/createRenderer.d.ts +13 -1
- package/dist/render/backend/createRenderer.js +6 -5
- package/dist/render/backend/webgl2/renderer.d.ts +147 -1
- package/dist/render/backend/webgl2/renderer.js +402 -23
- package/dist/render/backend/webgpu/buffers.js +46 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +81 -3
- package/dist/render/backend/webgpu/depthPass.js +75 -14
- package/dist/render/backend/webgpu/exposurePass.d.ts +39 -0
- package/dist/render/backend/webgpu/exposurePass.js +190 -0
- package/dist/render/backend/webgpu/fieldCompose.d.ts +11 -0
- package/dist/render/backend/webgpu/fieldCompose.js +13 -0
- package/dist/render/backend/webgpu/flatPass.d.ts +3 -1
- package/dist/render/backend/webgpu/flatPass.js +12 -5
- package/dist/render/backend/webgpu/particlePass.d.ts +8 -0
- package/dist/render/backend/webgpu/particlePass.js +1 -0
- package/dist/render/backend/webgpu/postPass.d.ts +13 -3
- package/dist/render/backend/webgpu/probeBake.d.ts +59 -3
- package/dist/render/backend/webgpu/probeBake.js +97 -7
- package/dist/render/backend/webgpu/probeTargets.d.ts +65 -0
- package/dist/render/backend/webgpu/probeTargets.js +180 -0
- package/dist/render/backend/webgpu/renderer.d.ts +174 -11
- package/dist/render/backend/webgpu/renderer.js +682 -252
- package/dist/render/backend/webgpu/scatterPass.js +7 -7
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +4 -0
- package/dist/render/backend/webgpu/skinPaletteRing.js +9 -0
- package/dist/render/backend/webgpu/skyPass.js +5 -1
- package/dist/render/backend/webgpu/uniformRing.d.ts +31 -1
- package/dist/render/backend/webgpu/uniformRing.js +53 -3
- package/dist/render/bloomChain.d.ts +6 -0
- package/dist/render/bloomChain.js +8 -0
- package/dist/render/clusteredLights.d.ts +19 -11
- package/dist/render/clusteredLights.js +96 -45
- package/dist/render/cutoutCaster.d.ts +23 -0
- package/dist/render/cutoutCaster.js +20 -0
- package/dist/render/driftLight/bake.d.ts +34 -0
- package/dist/render/driftLight/bake.js +95 -0
- package/dist/render/driftLight/falloff.d.ts +16 -0
- package/dist/render/driftLight/falloff.js +23 -0
- package/dist/render/driftLight/layout.d.ts +59 -0
- package/dist/render/driftLight/layout.js +138 -0
- package/dist/render/driftLight/lightField.d.ts +108 -0
- package/dist/render/driftLight/lightField.js +179 -0
- package/dist/render/driftLight/uniforms.d.ts +28 -0
- package/dist/render/driftLight/uniforms.js +41 -0
- package/dist/render/driftLight/visibility.d.ts +18 -0
- package/dist/render/driftLight/visibility.js +47 -0
- package/dist/render/emptyTexture.d.ts +6 -0
- package/dist/render/emptyTexture.js +19 -0
- package/dist/render/exposurePass.d.ts +48 -0
- package/dist/render/exposurePass.js +172 -0
- package/dist/render/flameLight.d.ts +71 -0
- package/dist/render/flameLight.js +63 -0
- package/dist/render/gi/bounceLights.d.ts +34 -0
- package/dist/render/gi/bounceLights.js +66 -0
- package/dist/render/gi/fieldReach.d.ts +12 -0
- package/dist/render/gi/fieldReach.js +62 -0
- package/dist/render/gi/globalField.d.ts +8 -0
- package/dist/render/gi/globalField.js +31 -0
- package/dist/render/gi/probeTrace.d.ts +3 -1
- package/dist/render/gi/probeTrace.js +3 -1
- package/dist/render/gpuCapability.js +19 -4
- package/dist/render/lightBudget.d.ts +11 -0
- package/dist/render/lightBudget.js +11 -0
- package/dist/render/pointLightSelection.d.ts +16 -0
- package/dist/render/pointLightSelection.js +178 -83
- package/dist/render/pointShadowBudget.d.ts +12 -2
- package/dist/render/pointShadowBudget.js +16 -10
- package/dist/render/probeGrid.d.ts +11 -1
- package/dist/render/probeGrid.js +5 -2
- package/dist/render/probeSweeps.d.ts +45 -0
- package/dist/render/probeSweeps.js +77 -0
- package/dist/render/reflectionProbe.d.ts +43 -1
- package/dist/render/reflectionProbe.js +17 -3
- package/dist/render/renderQuality.d.ts +3 -1
- package/dist/render/rendererHarness.d.ts +9 -0
- package/dist/render/rendererHarness.js +18 -3
- package/dist/render/sceneTarget.d.ts +20 -0
- package/dist/render/sceneTarget.js +62 -1
- package/dist/render/shaders/ambientOcclusion.d.ts +63 -6
- package/dist/render/shaders/ambientOcclusion.js +188 -71
- package/dist/render/shaders/cutoutCoverage.d.ts +31 -0
- package/dist/render/shaders/cutoutCoverage.js +40 -0
- package/dist/render/shaders/depth.d.ts +25 -0
- package/dist/render/shaders/depth.js +69 -3
- package/dist/render/shaders/exposure.d.ts +52 -0
- package/dist/render/shaders/exposure.js +124 -0
- package/dist/render/shaders/filmLook.d.ts +47 -0
- package/dist/render/shaders/filmLook.js +98 -0
- package/dist/render/shaders/flat/directionalShadow.js +35 -4
- package/dist/render/shaders/flat/driftLight.d.ts +29 -0
- package/dist/render/shaders/flat/driftLight.js +75 -0
- package/dist/render/shaders/flat/index.js +5 -0
- package/dist/render/shaders/flat/lobes.js +13 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +68 -9
- package/dist/render/shaders/flat/preamble.js +23 -5
- package/dist/render/shaders/flat/probeGrid.js +46 -3
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +8 -3
- package/dist/render/shaders/generated/depth.wgsl.d.ts +100 -0
- package/dist/render/shaders/generated/depth.wgsl.js +100 -0
- package/dist/render/shaders/generated/exposure.wgsl.d.ts +42 -0
- package/dist/render/shaders/generated/exposure.wgsl.js +48 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1800 -1344
- package/dist/render/shaders/generated/flat.wgsl.js +1816 -1360
- package/dist/render/shaders/generated/localExposure.wgsl.d.ts +18 -0
- package/dist/render/shaders/generated/localExposure.wgsl.js +24 -0
- package/dist/render/shaders/generated/rush.wgsl.d.ts +40 -10
- package/dist/render/shaders/generated/rush.wgsl.js +40 -10
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/gi/driftLight.wgsl.d.ts +16 -0
- package/dist/render/shaders/gi/driftLight.wgsl.js +49 -0
- package/dist/render/shaders/gi/probeBake.wgsl.d.ts +29 -4
- package/dist/render/shaders/gi/probeBake.wgsl.js +190 -20
- package/dist/render/shaders/globalMedium.js +4 -0
- package/dist/render/shaders/localExposure.d.ts +35 -0
- package/dist/render/shaders/localExposure.js +138 -0
- package/dist/render/shaders/prefilter.d.ts +1 -1
- package/dist/render/shaders/prefilter.js +6 -0
- package/dist/render/shaders/rush.js +33 -5
- package/dist/render/shaders/tangentFrame.d.ts +1 -1
- package/dist/render/shaders/tangentFrame.js +15 -3
- package/dist/render/shaders/temporalResolve.js +39 -1
- package/dist/render/shadowCasters.d.ts +5 -3
- package/dist/render/shadowMap.js +13 -0
- package/dist/render/surfaceTexture.d.ts +5 -0
- package/package.json +6 -6
- package/scripts/gpuCompute.mjs +24 -3
- package/scripts/png.mjs +23 -12
- package/src/cinematic/cameraPath.ts +178 -0
- package/src/environment/daylight.ts +152 -0
- package/src/geometry/meshConcat.ts +100 -0
- package/src/index.ts +32 -0
- package/src/render/ambientOcclusionPass.ts +5 -1
- package/src/render/backend/createRenderer.ts +22 -6
- package/src/render/backend/webgl2/renderer.ts +439 -20
- package/src/render/backend/webgpu/buffers.ts +51 -8
- package/src/render/backend/webgpu/depthPass.ts +78 -10
- package/src/render/backend/webgpu/exposurePass.ts +234 -0
- package/src/render/backend/webgpu/fieldCompose.ts +15 -0
- package/src/render/backend/webgpu/flatPass.ts +10 -2
- package/src/render/backend/webgpu/particlePass.ts +9 -0
- package/src/render/backend/webgpu/probeBake.ts +143 -9
- package/src/render/backend/webgpu/probeTargets.ts +211 -0
- package/src/render/backend/webgpu/renderer.ts +808 -273
- package/src/render/backend/webgpu/scatterPass.ts +7 -7
- package/src/render/backend/webgpu/skinPaletteRing.ts +10 -0
- package/src/render/backend/webgpu/skyPass.ts +5 -1
- package/src/render/backend/webgpu/uniformRing.ts +49 -3
- package/src/render/bloomChain.ts +9 -0
- package/src/render/clusteredLights.ts +108 -63
- package/src/render/cutoutCaster.ts +44 -0
- package/src/render/driftLight/bake.ts +125 -0
- package/src/render/driftLight/falloff.ts +26 -0
- package/src/render/driftLight/layout.ts +177 -0
- package/src/render/driftLight/lightField.ts +227 -0
- package/src/render/driftLight/uniforms.ts +61 -0
- package/src/render/driftLight/visibility.ts +58 -0
- package/src/render/emptyTexture.ts +41 -0
- package/src/render/exposurePass.ts +186 -0
- package/src/render/flameLight.ts +135 -0
- package/src/render/gi/bounceLights.ts +68 -0
- package/src/render/gi/fieldReach.ts +74 -0
- package/src/render/gi/globalField.ts +39 -0
- package/src/render/gi/probeTrace.ts +3 -1
- package/src/render/gpuCapability.ts +18 -5
- package/src/render/lightBudget.ts +12 -0
- package/src/render/pointLightSelection.ts +191 -79
- package/src/render/pointShadowBudget.ts +16 -8
- package/src/render/probeGrid.ts +13 -2
- package/src/render/probeSweeps.ts +80 -0
- package/src/render/reflectionProbe.ts +54 -2
- package/src/render/renderQuality.ts +3 -1
- package/src/render/rendererHarness.ts +25 -3
- package/src/render/sceneTarget.ts +75 -1
- package/src/render/shaders/ambientOcclusion.ts +205 -71
- package/src/render/shaders/cutoutCoverage.ts +43 -0
- package/src/render/shaders/depth.ts +80 -3
- package/src/render/shaders/exposure.ts +128 -0
- package/src/render/shaders/filmLook.ts +111 -0
- package/src/render/shaders/flat/directionalShadow.ts +35 -4
- package/src/render/shaders/flat/driftLight.ts +75 -0
- package/src/render/shaders/flat/index.ts +5 -0
- package/src/render/shaders/flat/lobes.ts +13 -0
- package/src/render/shaders/flat/main.ts +68 -9
- package/src/render/shaders/flat/preamble.ts +23 -5
- package/src/render/shaders/flat/probeGrid.ts +46 -3
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +8 -3
- package/src/render/shaders/generated/depth.wgsl.ts +103 -0
- package/src/render/shaders/generated/exposure.wgsl.ts +51 -0
- package/src/render/shaders/generated/flat.wgsl.ts +1816 -1360
- package/src/render/shaders/generated/localExposure.wgsl.ts +26 -0
- package/src/render/shaders/generated/rush.wgsl.ts +40 -10
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/gi/driftLight.wgsl.ts +49 -0
- package/src/render/shaders/gi/probeBake.wgsl.ts +191 -20
- package/src/render/shaders/globalMedium.ts +4 -0
- package/src/render/shaders/localExposure.ts +154 -0
- package/src/render/shaders/prefilter.ts +6 -0
- package/src/render/shaders/rush.ts +33 -5
- package/src/render/shaders/tangentFrame.ts +15 -3
- package/src/render/shaders/temporalResolve.ts +39 -1
- package/src/render/shadowCasters.ts +5 -3
- package/src/render/shadowMap.ts +14 -0
- package/src/render/surfaceTexture.ts +5 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveConditionals } from './conditionals.ts';
|
|
2
|
+
import { CUTOUT_COVERAGE_GLSL } from './cutoutCoverage.ts';
|
|
2
3
|
import { SKINNING_GLSL } from './skinning.ts';
|
|
3
4
|
import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from './vertexChannel.ts';
|
|
4
5
|
|
|
@@ -56,7 +57,22 @@ ${CHANNEL_ATTRIBUTE}
|
|
|
56
57
|
${CHANNEL_BEND}
|
|
57
58
|
#endif
|
|
58
59
|
|
|
60
|
+
#if CUTOUT
|
|
61
|
+
/*
|
|
62
|
+
* **What a cutout caster adds, and where.** The UV at location 5, where every mesh carries it; two
|
|
63
|
+
* uniforms declared after every other, so no field of the plain variant moves and a binder written
|
|
64
|
+
* against it stays right for this one; and two outputs after vLightPosition, which keeps location 0.
|
|
65
|
+
*/
|
|
66
|
+
layout(location = 5) in vec2 aUv;
|
|
67
|
+
uniform vec2 uUvScale;
|
|
68
|
+
uniform float uAlphaCutout;
|
|
69
|
+
#endif
|
|
70
|
+
|
|
59
71
|
out vec4 vLightPosition;
|
|
72
|
+
#if CUTOUT
|
|
73
|
+
out vec2 vUv;
|
|
74
|
+
flat out float vAlphaCutout;
|
|
75
|
+
#endif
|
|
60
76
|
|
|
61
77
|
void main() {
|
|
62
78
|
#if SKINNED
|
|
@@ -80,20 +96,24 @@ void main() {
|
|
|
80
96
|
#endif
|
|
81
97
|
vLightPosition = uLightViewProj * vec4(bent, world.w);
|
|
82
98
|
gl_Position = vLightPosition;
|
|
99
|
+
#if CUTOUT
|
|
100
|
+
vUv = aUv * uUvScale;
|
|
101
|
+
vAlphaCutout = uAlphaCutout;
|
|
102
|
+
#endif
|
|
83
103
|
}
|
|
84
104
|
`;
|
|
85
105
|
|
|
86
106
|
/** The rigid variant: the world, a prop, a mesh with no rig behind it. */
|
|
87
107
|
export const DEPTH_VERT = resolveConditionals(
|
|
88
108
|
DEPTH_VERT_SOURCE,
|
|
89
|
-
{ SKINNED: false, INSTANCED: false },
|
|
109
|
+
{ SKINNED: false, INSTANCED: false, CUTOUT: false },
|
|
90
110
|
'depth',
|
|
91
111
|
);
|
|
92
112
|
|
|
93
113
|
/** The skinned variant, which reads a joint palette and moves the vertex by it. */
|
|
94
114
|
export const DEPTH_SKINNED_VERT = resolveConditionals(
|
|
95
115
|
DEPTH_VERT_SOURCE,
|
|
96
|
-
{ SKINNED: true, INSTANCED: false },
|
|
116
|
+
{ SKINNED: true, INSTANCED: false, CUTOUT: false },
|
|
97
117
|
'depth-skinned',
|
|
98
118
|
);
|
|
99
119
|
|
|
@@ -107,10 +127,32 @@ export const DEPTH_SKINNED_VERT = resolveConditionals(
|
|
|
107
127
|
*/
|
|
108
128
|
export const DEPTH_INSTANCED_VERT = resolveConditionals(
|
|
109
129
|
DEPTH_VERT_SOURCE,
|
|
110
|
-
{ SKINNED: false, INSTANCED: true },
|
|
130
|
+
{ SKINNED: false, INSTANCED: true, CUTOUT: false },
|
|
111
131
|
'depth-instanced',
|
|
112
132
|
);
|
|
113
133
|
|
|
134
|
+
/**
|
|
135
|
+
* The variants a cutout caster casts through: a leaf card, a chain link, a fence, whose shape is in
|
|
136
|
+
* its texture's alpha rather than in its geometry.
|
|
137
|
+
*
|
|
138
|
+
* **Variants, not a uniform gate on the plain program**, for this file's own reason: a gate would
|
|
139
|
+
* need the UV, a varying and a bound texture on every opaque caster, and a branch on a varying is
|
|
140
|
+
* not provably uniform, which forbids the implicit derivative the sample below wants. A variant
|
|
141
|
+
* leaves every opaque caster byte for byte as it was. What it costs is one more program each, and
|
|
142
|
+
* a skinned cutout, which nothing has asked for, is not among them.
|
|
143
|
+
*/
|
|
144
|
+
export const DEPTH_CUTOUT_VERT = resolveConditionals(
|
|
145
|
+
DEPTH_VERT_SOURCE,
|
|
146
|
+
{ SKINNED: false, INSTANCED: false, CUTOUT: true },
|
|
147
|
+
'depth-cutout',
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
export const DEPTH_INSTANCED_CUTOUT_VERT = resolveConditionals(
|
|
151
|
+
DEPTH_VERT_SOURCE,
|
|
152
|
+
{ SKINNED: false, INSTANCED: true, CUTOUT: true },
|
|
153
|
+
'depth-instanced-cutout',
|
|
154
|
+
);
|
|
155
|
+
|
|
114
156
|
export const DEPTH_FRAG = `#version 300 es
|
|
115
157
|
precision highp float;
|
|
116
158
|
in vec4 vLightPosition;
|
|
@@ -129,3 +171,38 @@ void main() {
|
|
|
129
171
|
}
|
|
130
172
|
}
|
|
131
173
|
`;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The cutout caster's depth: the plain pass, and a texel of alpha under the cutoff writes nothing.
|
|
177
|
+
*
|
|
178
|
+
* **The sample is the first statement, in uniform control flow**, so its implicit derivative picks
|
|
179
|
+
* the mip the shadow map's texel footprint wants; inside a branch it would be undefined (AGENTS.md,
|
|
180
|
+
* 2026-08-07). The peel's own sample becomes an explicit-level fetch for the same reason, and is
|
|
181
|
+
* bit for bit what it was: that map has one level and a nearest sampler. The discard is last, so
|
|
182
|
+
* nothing samples after it.
|
|
183
|
+
*
|
|
184
|
+
* `uCutoutMap` is declared after `uPreviousShadowMap`, so the peel keeps its bindings and the cutout
|
|
185
|
+
* map takes the next.
|
|
186
|
+
*/
|
|
187
|
+
export const DEPTH_CUTOUT_FRAG = `#version 300 es
|
|
188
|
+
precision highp float;
|
|
189
|
+
in vec4 vLightPosition;
|
|
190
|
+
in vec2 vUv;
|
|
191
|
+
flat in float vAlphaCutout;
|
|
192
|
+
|
|
193
|
+
uniform highp sampler2D uPreviousShadowMap;
|
|
194
|
+
uniform int uPeelShadowLayer;
|
|
195
|
+
uniform highp sampler2D uCutoutMap;
|
|
196
|
+
${CUTOUT_COVERAGE_GLSL}
|
|
197
|
+
void main() {
|
|
198
|
+
/* Credited for its mip level as the surface's own test is, so a leaf casts the shape it draws. */
|
|
199
|
+
float alpha = cutoutAlpha(texture(uCutoutMap, vUv).a, vUv * vec2(textureSize(uCutoutMap, 0)));
|
|
200
|
+
if (uPeelShadowLayer != 0) {
|
|
201
|
+
vec3 p = vLightPosition.xyz / vLightPosition.w;
|
|
202
|
+
vec2 uv = p.xy * 0.5 + 0.5;
|
|
203
|
+
float previousDepth = textureLod(uPreviousShadowMap, uv, 0.0).r;
|
|
204
|
+
if (gl_FragCoord.z <= previousDepth + 0.00001) discard;
|
|
205
|
+
}
|
|
206
|
+
if (alpha < vAlphaCutout) discard;
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eye adaptation: the frame's own brightness, measured on the GPU and followed over time.
|
|
3
|
+
*
|
|
4
|
+
* **A fixed exposure is right for one view and wrong for the next.** A courtyard with the sun on
|
|
5
|
+
* its upper walls and its floor in the shade of a 19 m wall is several stops darker at ground level
|
|
6
|
+
* than from the gallery, and an exposure set for one draws the other either clipped or black. A
|
|
7
|
+
* camera meters and an eye adapts; this does both, from the finished scene, with no readback.
|
|
8
|
+
*
|
|
9
|
+
* **Two passes, both tiny.** The meter writes a 32×32 grid, each texel the mean of 8×8 taps of log
|
|
10
|
+
* luminance over its share of the frame. The adaptation reads all 1,024 of them in one texel,
|
|
11
|
+
* averages them into the frame's log-mean luminance, and moves the held value toward it by a blend
|
|
12
|
+
* the caller's frame time decides. The composite reads the held value and scales the scene toward
|
|
13
|
+
* middle grey. Log luminance, because an eye's response is to ratios: one sunlit pixel beside a
|
|
14
|
+
* dark frame should not set the exposure the way an arithmetic mean lets it.
|
|
15
|
+
*
|
|
16
|
+
* What it gives up is metering by region: every pixel counts the same, so a small bright window in
|
|
17
|
+
* a dark room pulls less than a photographer's spot meter would. What would change it is a scene
|
|
18
|
+
* that needs a subject held at a brightness, and the answer then is a centre weight in the meter.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Texels a side in the meter's grid, and taps a side within each. */
|
|
22
|
+
export const METER_GRID = 32;
|
|
23
|
+
export const METER_TAPS = 8;
|
|
24
|
+
/** The luminance the meter clamps to, in stops: a black pixel is not minus infinity. */
|
|
25
|
+
export const METER_MIN_LOG2 = -12;
|
|
26
|
+
export const METER_MAX_LOG2 = 8;
|
|
27
|
+
/** What full adaptation brings the frame's log-mean luminance to: middle grey. */
|
|
28
|
+
export const ADAPTED_KEY = 0.18;
|
|
29
|
+
/** The furthest adaptation moves the exposure either way, in stops. A black loading screen is not a
|
|
30
|
+
* reason to multiply the next frame by a thousand. */
|
|
31
|
+
export const ADAPT_RANGE_STOPS = 6;
|
|
32
|
+
/**
|
|
33
|
+
* How fast the held brightness follows the measured one, per second: 1.5 closes about 78% of the
|
|
34
|
+
* way in a second. An eye takes longer to open than to close, and a camera's meter is faster than
|
|
35
|
+
* either; one rate is what a scene that cuts between shots can predict.
|
|
36
|
+
*/
|
|
37
|
+
export const ADAPT_RATE = 1.5;
|
|
38
|
+
|
|
39
|
+
export const EXPOSURE_METER_FRAG = `#version 300 es
|
|
40
|
+
precision highp float;
|
|
41
|
+
|
|
42
|
+
in vec2 vUv;
|
|
43
|
+
|
|
44
|
+
/** The finished scene, in scene light, before any exposure or curve. */
|
|
45
|
+
uniform highp sampler2D uScene;
|
|
46
|
+
|
|
47
|
+
out float fragColor;
|
|
48
|
+
|
|
49
|
+
void main() {
|
|
50
|
+
/* This texel's corner, and taps spread evenly over its share of the frame. */
|
|
51
|
+
vec2 corner = vUv - vec2(0.5 / ${METER_GRID.toFixed(1)});
|
|
52
|
+
float sum = 0.0;
|
|
53
|
+
for (int y = 0; y < ${METER_TAPS}; y++) {
|
|
54
|
+
for (int x = 0; x < ${METER_TAPS}; x++) {
|
|
55
|
+
vec2 uv = corner + (vec2(float(x), float(y)) + 0.5) / ${(METER_GRID * METER_TAPS).toFixed(1)};
|
|
56
|
+
vec3 c = textureLod(uScene, uv, 0.0).rgb;
|
|
57
|
+
float luma = dot(c, vec3(0.2126, 0.7152, 0.0722));
|
|
58
|
+
sum += clamp(log2(max(luma, 1e-8)), ${METER_MIN_LOG2.toFixed(1)}, ${METER_MAX_LOG2.toFixed(1)});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
fragColor = sum / ${(METER_TAPS * METER_TAPS).toFixed(1)};
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
export const EXPOSURE_ADAPT_FRAG = `#version 300 es
|
|
66
|
+
precision highp float;
|
|
67
|
+
|
|
68
|
+
/** The meter's grid of log luminances. */
|
|
69
|
+
uniform highp sampler2D uMeter;
|
|
70
|
+
/** The brightness held from the frames before, in stops, one texel. */
|
|
71
|
+
uniform highp sampler2D uHeld;
|
|
72
|
+
/** How far toward this frame's measurement to move: 1 snaps, 0 holds. */
|
|
73
|
+
uniform float uBlend;
|
|
74
|
+
|
|
75
|
+
out float fragColor;
|
|
76
|
+
|
|
77
|
+
void main() {
|
|
78
|
+
float sum = 0.0;
|
|
79
|
+
for (int y = 0; y < ${METER_GRID}; y++) {
|
|
80
|
+
for (int x = 0; x < ${METER_GRID}; x++) {
|
|
81
|
+
sum += texelFetch(uMeter, ivec2(x, y), 0).r;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
float measured = sum / ${(METER_GRID * METER_GRID).toFixed(1)};
|
|
85
|
+
float held = texelFetch(uHeld, ivec2(0, 0), 0).r;
|
|
86
|
+
fragColor = mix(held, measured, uBlend);
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* `uAutoExposure`, `uExposureHeld` and `autoExposureGain`, for the composite. The gain scales scene
|
|
92
|
+
* light before the tone curve, toward the key by the fraction the caller asked for.
|
|
93
|
+
*/
|
|
94
|
+
export const AUTO_EXPOSURE_GLSL = `
|
|
95
|
+
/** How far the frame adapts toward middle grey, 0 to 1. 0 is off and the whole of the off path. */
|
|
96
|
+
uniform float uAutoExposure;
|
|
97
|
+
/** The held log-mean luminance, in stops, one texel. */
|
|
98
|
+
uniform highp sampler2D uExposureHeld;
|
|
99
|
+
|
|
100
|
+
float autoExposureGain() {
|
|
101
|
+
if (uAutoExposure <= 0.0) return 1.0;
|
|
102
|
+
float held = texelFetch(uExposureHeld, ivec2(0, 0), 0).r;
|
|
103
|
+
float stops = uAutoExposure * (${Math.log2(ADAPTED_KEY).toFixed(6)} - held);
|
|
104
|
+
return exp2(clamp(stops, -${ADAPT_RANGE_STOPS.toFixed(1)}, ${ADAPT_RANGE_STOPS.toFixed(1)}));
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The fraction of the way toward this frame's measurement to move, after `dtSec` seconds. A cut,
|
|
110
|
+
* or a first frame with nothing held, snaps.
|
|
111
|
+
*/
|
|
112
|
+
export function adaptBlend(dtSec: number, snap: boolean): number {
|
|
113
|
+
if (snap) return 1;
|
|
114
|
+
if (!(dtSec > 0)) return 0;
|
|
115
|
+
return 1 - Math.exp(-ADAPT_RATE * dtSec);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The scene's gain for a held log-mean `heldLog2` at `strength`, as the composite computes it. */
|
|
119
|
+
export function autoExposureGain(heldLog2: number, strength: number): number {
|
|
120
|
+
if (!(strength > 0)) return 1;
|
|
121
|
+
const stops = strength * (Math.log2(ADAPTED_KEY) - heldLog2);
|
|
122
|
+
return 2 ** Math.min(Math.max(stops, -ADAPT_RANGE_STOPS), ADAPT_RANGE_STOPS);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** An adaptation strength as the shader takes it: 0 to 1, anything else off. */
|
|
126
|
+
export function clampAutoExposure(strength: number): number {
|
|
127
|
+
return Number.isFinite(strength) ? Math.min(Math.max(strength, 0), 1) : 0;
|
|
128
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lens and the print: a vignette in scene light, and grain in display values.
|
|
3
|
+
*
|
|
4
|
+
* **The vignette is the lens's, so it acts on light rather than on the picture.** A lens loses light
|
|
5
|
+
* toward its corners by roughly the fourth power of the cosine of the angle off axis, which for a
|
|
6
|
+
* point `r` from the centre is `1 / (1 + k r^2)^2`. Applied to scene light before the tone curve,
|
|
7
|
+
* a bright corner rolls off through the curve's shoulder the way a photographed one does, where a
|
|
8
|
+
* vignette painted over the finished picture greys it. `r` is measured to the frame's corner on a
|
|
9
|
+
* circle, not an ellipse, so a wide window is not darker at its sides than at its top.
|
|
10
|
+
*
|
|
11
|
+
* **The grain is the print's, so it acts on display values after the grade and the veil.** It is
|
|
12
|
+
* one value a pixel, the same on all three channels, weighted toward the midtones where film shows
|
|
13
|
+
* it and kept faint in deep shadow and highlight, where adding it would lift black and grey white.
|
|
14
|
+
* It also dithers: a dark gradient quantised to eight bits bands, and a noise of a few levels breaks
|
|
15
|
+
* the bands up. **The seed is the caller's**, because the engine takes time from its caller: a held
|
|
16
|
+
* capture passes the same seed and is identical run to run, and a playing scene passes a new one
|
|
17
|
+
* each frame and the grain moves as film does.
|
|
18
|
+
*
|
|
19
|
+
* Zero strength for each is the whole of its off path: a branch on a uniform, and the frame is what
|
|
20
|
+
* it was byte for byte.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `uVignette`, `uGrain`, `withVignette` and `withGrain`. The rush composite includes it once.
|
|
25
|
+
*
|
|
26
|
+
* `withVignette` takes the pixel's coordinate and the scene's size, since a distance to the corner
|
|
27
|
+
* has to know the aspect. `withGrain` takes the fragment's integer position, so the grain is one
|
|
28
|
+
* value a pixel whatever the resolution.
|
|
29
|
+
*/
|
|
30
|
+
export const FILM_LOOK_GLSL = `
|
|
31
|
+
/** How strongly the lens darkens its corners: 0 is none, 0.5 about 1.2 stops at the corner. */
|
|
32
|
+
uniform float uVignette;
|
|
33
|
+
/** x: the grain's amplitude in display values, 0 for none. y: this frame's seed, the caller's. */
|
|
34
|
+
uniform vec2 uGrain;
|
|
35
|
+
|
|
36
|
+
vec3 withVignette(vec3 c, vec2 uv, vec2 size) {
|
|
37
|
+
if (uVignette <= 0.0) return c;
|
|
38
|
+
vec2 d = (uv - 0.5) * size;
|
|
39
|
+
vec2 corner = 0.5 * size;
|
|
40
|
+
float r2 = dot(d, d) / dot(corner, corner);
|
|
41
|
+
float falloff = 1.0 + uVignette * r2;
|
|
42
|
+
return c / (falloff * falloff);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
float grainNoise(uvec2 p, uint seed) {
|
|
46
|
+
uint h = p.x * 1664525u + p.y * 1013904223u + seed * 2654435769u;
|
|
47
|
+
h ^= h >> 16u;
|
|
48
|
+
h *= 2246822519u;
|
|
49
|
+
h ^= h >> 13u;
|
|
50
|
+
h *= 3266489917u;
|
|
51
|
+
h ^= h >> 16u;
|
|
52
|
+
return float(h) * (1.0 / 4294967295.0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
vec3 withGrain(vec3 c, vec2 fragCoord) {
|
|
56
|
+
if (uGrain.x <= 0.0) return c;
|
|
57
|
+
float luma = dot(clamp(c, 0.0, 1.0), vec3(0.2126, 0.7152, 0.0722));
|
|
58
|
+
float weight = 0.25 + 3.0 * luma * (1.0 - luma);
|
|
59
|
+
float n = grainNoise(uvec2(fragCoord), uint(uGrain.y)) - 0.5;
|
|
60
|
+
return c + vec3(2.0 * n * uGrain.x * weight);
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The vignette's factor at `uv` on a `width` by `height` frame, as the shader computes it.
|
|
66
|
+
* The shader's arithmetic written out once more, so a test can pin it to hand-derived values.
|
|
67
|
+
*/
|
|
68
|
+
export function vignetteFactor(
|
|
69
|
+
u: number,
|
|
70
|
+
v: number,
|
|
71
|
+
width: number,
|
|
72
|
+
height: number,
|
|
73
|
+
strength: number,
|
|
74
|
+
): number {
|
|
75
|
+
if (strength <= 0) return 1;
|
|
76
|
+
const dx = (u - 0.5) * width;
|
|
77
|
+
const dy = (v - 0.5) * height;
|
|
78
|
+
const r2 = (dx * dx + dy * dy) / (0.25 * (width * width + height * height));
|
|
79
|
+
const falloff = 1 + strength * r2;
|
|
80
|
+
return 1 / (falloff * falloff);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** How much of the grain a display luminance takes: full in the midtones, a quarter at the ends. */
|
|
84
|
+
export function grainWeight(luma: number): number {
|
|
85
|
+
const l = Math.min(Math.max(luma, 0), 1);
|
|
86
|
+
return 0.25 + 3 * l * (1 - l);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Both backends' words when there is no composite to apply the look in. */
|
|
90
|
+
export const FILM_LOOK_WITHOUT_COMPOSITE =
|
|
91
|
+
'[driftengine] setVignette and setFilmGrain need `screenEffects`: without a composite every ' +
|
|
92
|
+
'pass is the last thing to touch the frame, so there is no one place to apply a lens or a ' +
|
|
93
|
+
'print. The look was not applied.';
|
|
94
|
+
|
|
95
|
+
/** A vignette strength as the shader takes it: 0 to 4, anything else off. */
|
|
96
|
+
export function clampVignette(strength: number): number {
|
|
97
|
+
return Number.isFinite(strength) ? Math.min(Math.max(strength, 0), 4) : 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A grain amplitude in display values: 0 to 0.5, anything else off. */
|
|
101
|
+
export function clampGrain(strength: number): number {
|
|
102
|
+
return Number.isFinite(strength) ? Math.min(Math.max(strength, 0), 0.5) : 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A seed as the shader reads it: a whole number below 2^24, which a 32-bit float carries exactly,
|
|
107
|
+
* so the same seed is the same grain on both backends. Any number a caller has is accepted.
|
|
108
|
+
*/
|
|
109
|
+
export function grainSeed(seed: number): number {
|
|
110
|
+
return Number.isFinite(seed) ? Math.floor(Math.abs(seed)) % 16777216 : 0;
|
|
111
|
+
}
|
|
@@ -68,10 +68,13 @@ float shadowFactor(float directionalNdl) {
|
|
|
68
68
|
// Near a surface's own light terminator its direct-light term is already
|
|
69
69
|
// approaching zero, while the orthographic projection of that surface
|
|
70
70
|
// collapses toward a line. No finite contact-safe bias can represent that
|
|
71
|
-
// slope reliably
|
|
72
|
-
//
|
|
71
|
+
// slope reliably, so through this band the fine filter below hands over to the
|
|
72
|
+
// coarse test after it. \`directionalNdl\` is the surface's own, before any normal
|
|
73
|
+
// map: the band is about the receiving plane, and a map's tilt says nothing about it.
|
|
73
74
|
float receiverFade = smoothstep(0.08, 0.20, directionalNdl);
|
|
74
|
-
|
|
75
|
+
/* A surface turned from the light takes none of it, so there is nothing to shadow; what reads
|
|
76
|
+
this there is the emissive share, which has always seen such a surface as unshadowed. */
|
|
77
|
+
if (directionalNdl <= 0.0) return 1.0;
|
|
75
78
|
|
|
76
79
|
// The receiver stays at its real position, computed above. A normal offset changes
|
|
77
80
|
// direction at a hard mesh edge and tears one continuous shadow where it crosses from
|
|
@@ -104,6 +107,34 @@ float shadowFactor(float directionalNdl) {
|
|
|
104
107
|
);
|
|
105
108
|
if (lowElevationFade <= 0.0) return 1.0;
|
|
106
109
|
|
|
110
|
+
/*
|
|
111
|
+
* **The coarse test: is there anything a metre or more between this point and the sun.**
|
|
112
|
+
*
|
|
113
|
+
* One tap at the receiver's own texel, with a metre of tolerance where the filter below allows
|
|
114
|
+
* fourteen centimetres. It cannot see a contact shadow, and it does not need the plane's slope:
|
|
115
|
+
* a grazing plane's own depth moves about a centimetre times the tangent of its angle to the
|
|
116
|
+
* light across a texel, which stays under a metre down to a hundredth of n·l on a map with
|
|
117
|
+
* centimetre texels. What it does see is a roof.
|
|
118
|
+
*
|
|
119
|
+
* **This band used to return fully lit**, on the argument that the Lambert term was already
|
|
120
|
+
* near zero there. Under a high sun that is every vertical wall: at 70° of elevation a wall's
|
|
121
|
+
* n·l is at most 0.34, and one turned from the sun's bearing sits inside the band. A gallery's
|
|
122
|
+
* back wall under its vault took the sun through the vault, and so did the faces of its
|
|
123
|
+
* columns, which read as lit stone in shade; with a normal map carrying that n·l per texel it
|
|
124
|
+
* was a field of white specks. What would make this wrong is a map whose texels are tens of
|
|
125
|
+
* centimetres, where a grazing plane's own depth moves more than the tolerance and the band
|
|
126
|
+
* draws the acne it exists to avoid.
|
|
127
|
+
*/
|
|
128
|
+
float coarseCompare = p.z - 1.0 / uShadowDepthSpan;
|
|
129
|
+
float coarse =
|
|
130
|
+
directionalVisibility(p.z, coarseCompare, textureLod(uStaticShadowMap, p.xy, 0.0).r) *
|
|
131
|
+
directionalVisibility(p.z, coarseCompare, textureLod(uDynamicShadowMap, p.xy, 0.0).r);
|
|
132
|
+
if (uPeeledShadowEnabled != 0) {
|
|
133
|
+
coarse *= directionalVisibility(p.z, coarseCompare, textureLod(uPeeledShadowMap, p.xy, 0.0).r);
|
|
134
|
+
}
|
|
135
|
+
float strength = uShadowStrength * edgeFade * lowElevationFade;
|
|
136
|
+
if (receiverFade <= 0.0) return mix(1.0, coarse, strength);
|
|
137
|
+
|
|
107
138
|
float texel = 1.35 / uShadowMapSize;
|
|
108
139
|
/*
|
|
109
140
|
* How far the plane compensation below may carry a tap's depth, in the map's own units.
|
|
@@ -182,7 +213,7 @@ float shadowFactor(float directionalNdl) {
|
|
|
182
213
|
// conventional one-depth map would discard; movers remain independent too.
|
|
183
214
|
float lit = staticLit * peeledLit * dynamicLit;
|
|
184
215
|
|
|
185
|
-
return mix(1.0, lit,
|
|
216
|
+
return mix(1.0, mix(coarse, lit, receiverFade), strength);
|
|
186
217
|
}
|
|
187
218
|
#endif
|
|
188
219
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DriftLight in the lit shader: how much of a pixel the summed light stands in for, and the summed
|
|
3
|
+
* light itself, read from the field's sparse volume.
|
|
4
|
+
*
|
|
5
|
+
* **The split is per pixel.** Inside the frame's completeness radius (`uDriftLight.y`, measured from
|
|
6
|
+
* the eye) every light that reaches the pixel was chosen, so it is shaded exactly; past it the
|
|
7
|
+
* volume stands in, and across the band before the radius the two crossfade. The choice was made
|
|
8
|
+
* about some centre, which need not be the eye; `uniforms.ts` takes the distance between the two
|
|
9
|
+
* off the radius, so a pixel inside it is inside the choice's radius too. A field light's
|
|
10
|
+
* exact contribution is scaled by the share left over (`main.ts`, where its weight's sign is read),
|
|
11
|
+
* so a light is counted once however the pixel is split.
|
|
12
|
+
*
|
|
13
|
+
* **One index fetch and two filtered ones.** The index finds the brick; the brick's four samples a
|
|
14
|
+
* side share their faces with the next brick's, so the hardware's trilinear lookup inside one is
|
|
15
|
+
* seamless across the join. The light texel carries in its fourth channel whether each sample is a
|
|
16
|
+
* sample at all, and dividing by the filtered value drops the samples buried in walls rather than
|
|
17
|
+
* blending their darkness into the surface. The lookup is nudged half a spacing along the normal,
|
|
18
|
+
* which keeps a surface reading the air in front of it rather than the wall it is part of.
|
|
19
|
+
*
|
|
20
|
+
* **Two vectors of uniforms, and the sizes read off the textures.** The lit stage sits two vectors
|
|
21
|
+
* under the 256 a reported part offers at eight lights, and the first cut of this, at five, took that
|
|
22
|
+
* part down a rung to four. So the grid's extent and the atlas's are what `textureSize` answers, and
|
|
23
|
+
* the radius is measured from `uCameraPos`, which the stage already has, rather than from a centre
|
|
24
|
+
* of its own. What it gives up is two integer queries a pixel past the radius.
|
|
25
|
+
*
|
|
26
|
+
* `textureLod` throughout, by the 2026-08-07 rule: this is reached through branches no compiler can
|
|
27
|
+
* prove uniform. Both samplers are declared `highp` because GLSL ES gives 3D samplers no default.
|
|
28
|
+
*/
|
|
29
|
+
export const DRIFT_LIGHT_GLSL = /* glsl */ `
|
|
30
|
+
/** x how much of the summed light is in the frame (0 off), y the radius, z the band, w the scale. */
|
|
31
|
+
uniform vec4 uDriftLight;
|
|
32
|
+
/** xyz the first brick's first sample; w metres between samples, a third of a brick. */
|
|
33
|
+
uniform vec4 uDriftLightOrigin;
|
|
34
|
+
uniform highp usampler3D uDriftLightIndex; // wgsl:share shadow
|
|
35
|
+
uniform highp sampler3D uDriftLightAtlas;
|
|
36
|
+
|
|
37
|
+
/** The share of this pixel's field light the volume stands in for: 0 inside the radius, 1 past it. */
|
|
38
|
+
float driftLightShare(vec3 world) {
|
|
39
|
+
if (uDriftLight.x <= 0.0) return 0.0;
|
|
40
|
+
float away = distance(world, uCameraPos);
|
|
41
|
+
return smoothstep(uDriftLight.y - uDriftLight.z, uDriftLight.y, away) * uDriftLight.x;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The summed light arriving on a surface at \`world\` facing \`n\`. */
|
|
45
|
+
vec3 driftLightIrradiance(vec3 world, vec3 n) {
|
|
46
|
+
float spacing = uDriftLightOrigin.w;
|
|
47
|
+
vec3 at = (world + n * (0.5 * spacing) - uDriftLightOrigin.xyz) / (3.0 * spacing);
|
|
48
|
+
ivec3 cell = ivec3(floor(at));
|
|
49
|
+
if (any(lessThan(cell, ivec3(0))) || any(greaterThanEqual(cell, textureSize(uDriftLightIndex, 0)))) {
|
|
50
|
+
return vec3(0.0);
|
|
51
|
+
}
|
|
52
|
+
uint id = texelFetch(uDriftLightIndex, cell, 0).r;
|
|
53
|
+
if (id == 0u) return vec3(0.0);
|
|
54
|
+
/* A brick is eight texels across (its light block, then its direction block) and four up and deep. */
|
|
55
|
+
ivec3 size = textureSize(uDriftLightAtlas, 0);
|
|
56
|
+
int brick = int(id) - 1;
|
|
57
|
+
int across = size.x / 8;
|
|
58
|
+
int down = size.y / 4;
|
|
59
|
+
vec3 corner = vec3(
|
|
60
|
+
float((brick % across) * 8),
|
|
61
|
+
float(((brick / across) % down) * 4),
|
|
62
|
+
float((brick / (across * down)) * 4)
|
|
63
|
+
);
|
|
64
|
+
vec3 texel = 1.0 / vec3(size);
|
|
65
|
+
vec3 inside = (at - vec3(cell)) * 3.0 + 0.5;
|
|
66
|
+
vec4 light = textureLod(uDriftLightAtlas, (corner + inside) * texel, 0.0);
|
|
67
|
+
if (light.a < 1e-3) return vec3(0.0);
|
|
68
|
+
vec4 toward = textureLod(uDriftLightAtlas, (corner + vec3(4.0, 0.0, 0.0) + inside) * texel, 0.0);
|
|
69
|
+
vec3 arriving = light.rgb / light.a;
|
|
70
|
+
vec3 from = toward.xyz / light.a;
|
|
71
|
+
/* Lambert for a single light, a quarter for light arriving from everywhere: see \`bake.ts\`. */
|
|
72
|
+
float facing = max(dot(n, from), 0.0) + (1.0 - min(length(from), 1.0)) * 0.25;
|
|
73
|
+
return arriving * facing;
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
@@ -2,6 +2,7 @@ import { resolveConditionals } from '../conditionals.ts';
|
|
|
2
2
|
import { OCTAHEDRAL_GLSL } from '../octahedral.ts';
|
|
3
3
|
import { PROBEGRID_GLSL } from './probeGrid.ts';
|
|
4
4
|
import { TANGENT_FRAME_GLSL } from '../tangentFrame.ts';
|
|
5
|
+
import { CUTOUT_COVERAGE_GLSL } from '../cutoutCoverage.ts';
|
|
5
6
|
import { preambleGlsl } from './preamble.ts';
|
|
6
7
|
import { LOBES_GLSL } from './lobes.ts';
|
|
7
8
|
import { POINTSHADOW_GLSL } from './pointShadow.ts';
|
|
@@ -10,6 +11,7 @@ import { MORPH_GLSL } from '../morph.ts';
|
|
|
10
11
|
import { SKINNING_GLSL } from '../skinning.ts';
|
|
11
12
|
import { SURFACE_GLSL } from './surface.ts';
|
|
12
13
|
import { MAIN_GLSL } from './main.ts';
|
|
14
|
+
import { DRIFT_LIGHT_GLSL } from './driftLight.ts';
|
|
13
15
|
import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from '../vertexChannel.ts';
|
|
14
16
|
import { FULL_LIGHT_BUDGET, type LightBudget } from '../../uniformVectorBudget.ts';
|
|
15
17
|
|
|
@@ -489,6 +491,9 @@ export function flatFrag(options: FlatShaderOptions): string {
|
|
|
489
491
|
* and `main` is unconditional by construction.
|
|
490
492
|
*/
|
|
491
493
|
TANGENT_FRAME_GLSL,
|
|
494
|
+
/* Unconditional for the same reason, and called only where a cutoff was asked for. */
|
|
495
|
+
CUTOUT_COVERAGE_GLSL,
|
|
496
|
+
DRIFT_LIGHT_GLSL,
|
|
492
497
|
MAIN_GLSL,
|
|
493
498
|
].join('\n'),
|
|
494
499
|
{
|
|
@@ -74,6 +74,19 @@ const float PENUMBRA_FADE = 1.6;
|
|
|
74
74
|
*/
|
|
75
75
|
const float MAX_FILTER_RADIUS = 0.25;
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* The weight below which a point light's shadow is not looked up at all.
|
|
79
|
+
*
|
|
80
|
+
* A light's shadow is blended toward none as the light falls off, \`mix(1, occl, weight)\` with the
|
|
81
|
+
* weight twice the falloff, so a shadow looked up under this floor changes at most three per cent
|
|
82
|
+
* of a light that has already all but faded: with the inverse-square falloff, past about eight
|
|
83
|
+
* metres from it. Measured on a lantern-lit courtyard, the lookups the floor skips were most of a
|
|
84
|
+
* night's frame: 14 ms of 26 at 1440p, a blocker tap and twelve filter taps for every light in every
|
|
85
|
+
* froxel. What it gives up is the last three per cent of a distant light's shadow, which the blend
|
|
86
|
+
* was already taking away; what would make it wrong is a falloff that stays strong far out.
|
|
87
|
+
*/
|
|
88
|
+
const float POINT_SHADOW_MIN_WEIGHT = 0.03;
|
|
89
|
+
|
|
77
90
|
/**
|
|
78
91
|
* How much of a self-lit surface's glow a lamp's shadow may take.
|
|
79
92
|
*
|