@driftengine/core 4.8.4 → 4.8.6
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 +4332 -4250
- package/dist/build/shaderComments.js +0 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +11 -1
- package/dist/render/ambientHarmonics.d.ts +46 -0
- package/dist/render/ambientHarmonics.js +80 -0
- package/dist/render/areaLights.d.ts +19 -0
- package/dist/render/areaLights.js +20 -0
- package/dist/render/backend/api.d.ts +13 -2
- package/dist/render/backend/webgl2/instanced.d.ts +2 -0
- package/dist/render/backend/webgl2/instanced.js +11 -2
- package/dist/render/backend/webgl2/renderer.d.ts +74 -1
- package/dist/render/backend/webgl2/renderer.js +229 -37
- package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
- package/dist/render/backend/webgl2/skinScatter.js +88 -26
- package/dist/render/backend/webgpu/buffers.js +2 -1
- package/dist/render/backend/webgpu/clusterBinner.js +3 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
- package/dist/render/backend/webgpu/flatPass.js +15 -13
- package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
- package/dist/render/backend/webgpu/particlePass.js +80 -12
- package/dist/render/backend/webgpu/postPass.d.ts +6 -1
- package/dist/render/backend/webgpu/postPass.js +7 -2
- package/dist/render/backend/webgpu/renderer.d.ts +58 -7
- package/dist/render/backend/webgpu/renderer.js +329 -78
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +41 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
- package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
- package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +2 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +18 -4
- package/dist/render/bloomChain.d.ts +44 -0
- package/dist/render/bloomChain.js +28 -0
- package/dist/render/bloomPass.d.ts +3 -1
- package/dist/render/bloomPass.js +16 -2
- package/dist/render/clusteredLights.d.ts +55 -1
- package/dist/render/clusteredLights.js +182 -8
- package/dist/render/fixtureAtlas.js +1 -1
- package/dist/render/instanceCull.js +7 -0
- package/dist/render/instances.d.ts +32 -10
- package/dist/render/instances.js +17 -8
- package/dist/render/lightBudget.d.ts +26 -1
- package/dist/render/lightBudget.js +26 -3
- package/dist/render/lightmap.d.ts +102 -0
- package/dist/render/lightmap.js +118 -0
- package/dist/render/mesh.js +2 -1
- package/dist/render/particleBatch.d.ts +59 -3
- package/dist/render/particleBatch.js +53 -2
- package/dist/render/particleMaterial.d.ts +6 -1
- package/dist/render/particleMaterial.js +10 -2
- package/dist/render/particlePool.d.ts +11 -0
- package/dist/render/particlePool.js +2 -0
- package/dist/render/particleSort.d.ts +34 -0
- package/dist/render/particleSort.js +96 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +8 -1
- package/dist/render/shaders/bloom.d.ts +8 -4
- package/dist/render/shaders/bloom.js +20 -3
- package/dist/render/shaders/flat/areaLight.d.ts +10 -0
- package/dist/render/shaders/flat/areaLight.js +96 -0
- package/dist/render/shaders/flat/index.js +30 -4
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +107 -105
- package/dist/render/shaders/flat/models.d.ts +16 -4
- package/dist/render/shaders/flat/models.js +64 -4
- package/dist/render/shaders/flat/normalMap.d.ts +10 -0
- package/dist/render/shaders/flat/normalMap.js +25 -0
- package/dist/render/shaders/flat/preamble.js +74 -37
- package/dist/render/shaders/flat/skin.js +6 -5
- package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
- package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1631 -2044
- package/dist/render/shaders/generated/flat.wgsl.js +1669 -2082
- package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
- package/dist/render/shaders/generated/particle.wgsl.js +115 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
- package/dist/render/shaders/particle.d.ts +17 -1
- package/dist/render/shaders/particle.js +133 -1
- package/dist/render/shaders/skinBlur.js +25 -3
- package/dist/render/skinBlur.d.ts +8 -5
- package/dist/render/skinBlur.js +8 -5
- package/dist/render/surfaceModel.d.ts +21 -1
- package/dist/render/surfaceModel.js +22 -0
- package/dist/render/surfaceTexture.d.ts +14 -1
- package/dist/render/surfaceTexture.js +15 -2
- package/package.json +6 -6
- package/src/build/shaderComments.ts +0 -0
- package/src/index.ts +20 -1
- package/src/render/ambientHarmonics.ts +92 -0
- package/src/render/areaLights.ts +39 -0
- package/src/render/backend/api.ts +14 -1
- package/src/render/backend/webgl2/instanced.ts +12 -2
- package/src/render/backend/webgl2/renderer.ts +292 -35
- package/src/render/backend/webgl2/skinScatter.ts +120 -43
- package/src/render/backend/webgpu/buffers.ts +2 -1
- package/src/render/backend/webgpu/clusterBinner.ts +3 -1
- package/src/render/backend/webgpu/flatPass.ts +18 -13
- package/src/render/backend/webgpu/particlePass.ts +118 -12
- package/src/render/backend/webgpu/postPass.ts +7 -2
- package/src/render/backend/webgpu/renderer.ts +396 -95
- package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
- package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
- package/src/render/backend/webgpu/surfaceTexturePass.ts +26 -5
- package/src/render/bloomChain.ts +69 -0
- package/src/render/bloomPass.ts +26 -2
- package/src/render/clusteredLights.ts +221 -7
- package/src/render/fixtureAtlas.ts +1 -1
- package/src/render/instanceCull.ts +5 -0
- package/src/render/instances.ts +40 -11
- package/src/render/lightBudget.ts +48 -2
- package/src/render/lightmap.ts +202 -0
- package/src/render/mesh.ts +2 -1
- package/src/render/particleBatch.ts +95 -4
- package/src/render/particleMaterial.ts +11 -1
- package/src/render/particlePool.ts +13 -0
- package/src/render/particleSort.ts +133 -0
- package/src/render/sceneTarget.ts +18 -1
- package/src/render/shaders/bloom.ts +20 -3
- package/src/render/shaders/flat/areaLight.ts +96 -0
- package/src/render/shaders/flat/index.ts +30 -4
- package/src/render/shaders/flat/main.ts +107 -105
- package/src/render/shaders/flat/models.ts +73 -8
- package/src/render/shaders/flat/normalMap.ts +25 -0
- package/src/render/shaders/flat/preamble.ts +74 -37
- package/src/render/shaders/flat/skin.ts +6 -5
- package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
- package/src/render/shaders/generated/flat.wgsl.ts +1669 -2082
- package/src/render/shaders/generated/particle.wgsl.ts +116 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
- package/src/render/shaders/particle.ts +134 -1
- package/src/render/shaders/skinBlur.ts +25 -3
- package/src/render/skinBlur.ts +8 -5
- package/src/render/surfaceModel.ts +36 -1
- package/src/render/surfaceTexture.ts +41 -3
|
@@ -147,6 +147,12 @@ export const MAIN_GLSL = `void main() {
|
|
|
147
147
|
float glassTransmission = uSeeThrough.z;
|
|
148
148
|
float glassFrost = uSeeThrough.w;
|
|
149
149
|
|
|
150
|
+
/*
|
|
151
|
+
* The normal a refracting draw bends by: the shading normal, a normal map's tilt and every relief
|
|
152
|
+
* in it, as the lit block leaves it before turning a back face — the geometric normal where
|
|
153
|
+
* nothing tilts it, which is what refraction bent by before 4.8.6. See the refraction below.
|
|
154
|
+
*/
|
|
155
|
+
vec3 shadingNormal = normalize(vNormal);
|
|
150
156
|
if (uLightingEnabled != 0) {
|
|
151
157
|
vec3 n = normalize(vNormal);
|
|
152
158
|
/*
|
|
@@ -180,20 +186,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
180
186
|
* right: a mipped colour image, sampled under uniform control flow, exactly as uAlbedo is
|
|
181
187
|
* below. The rule is about non-uniform branches, and this branch is on a uniform.
|
|
182
188
|
*/
|
|
183
|
-
if (uNormalStrength > 0.0)
|
|
184
|
-
vec3 mapped = texture(uNormalMap, surfaceAt).xyz * 2.0 - 1.0;
|
|
185
|
-
/*
|
|
186
|
-
* z rebuilt where none is stored. A tangent-space normal points out of its surface, so a
|
|
187
|
-
* stored z at or below zero is never a real one: it is a two-channel map, BC5, whose blue a
|
|
188
|
-
* device samples as zero and which arrives here as -1. Every map baked from BC5 to PNG before
|
|
189
|
-
* 4.8.4 carried the same zero. A select rather than a flag: no uniform to bind, no material
|
|
190
|
-
* that has to say what format it holds, and continuous at the boundary, since a unit texel
|
|
191
|
-
* lying flat has its z near zero either way. A texel with z above zero is left as it was.
|
|
192
|
-
*/
|
|
193
|
-
mapped.z = mapped.z > 0.0 ? mapped.z : sqrt(max(0.0, 1.0 - dot(mapped.xy, mapped.xy)));
|
|
194
|
-
mat3 tbn = tangentFrame(n, vWorldPos, vUv.xy, vTangent, vHasTangents);
|
|
195
|
-
n = normalize(mix(n, normalize(tbn * mapped), uNormalStrength));
|
|
196
|
-
}
|
|
189
|
+
if (uNormalStrength > 0.0) n = normalMapped(n, surfaceAt);
|
|
197
190
|
|
|
198
191
|
/*
|
|
199
192
|
* Microscopic relief: the surface's own texture, as a turn of the normal rather than a change
|
|
@@ -217,7 +210,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
217
210
|
* widening driven by fwidth was measured making that artefact three times worse, because that
|
|
218
211
|
* derivative spikes at every hard crease. See docs/IMPROVEMENTS.md.
|
|
219
212
|
*/
|
|
220
|
-
|
|
213
|
+
/* Never below zero: a lightmapped mesh's coordinates ride this lane below it. See lightmap.ts. */
|
|
214
|
+
float reliefAmount = uRelief.x * max(vRelief, 0.0);
|
|
221
215
|
/* Grown with the relief, before anything reads it. See the comment above: a normal that
|
|
222
216
|
wanders cannot hold a highlight narrower than the wander. */
|
|
223
217
|
/*
|
|
@@ -258,11 +252,11 @@ export const MAIN_GLSL = `void main() {
|
|
|
258
252
|
* variance is still roughness — a road that faded to smooth at distance would go glossy
|
|
259
253
|
* instead of noisy, which is a worse artefact wearing better clothes.
|
|
260
254
|
*/
|
|
261
|
-
vec3 reliefPixel = fwidth(vWorldPos) *
|
|
255
|
+
vec3 reliefPixel = fwidth(vWorldPos) * uRelief.y;
|
|
262
256
|
float reliefFootprint = max(max(reliefPixel.x, reliefPixel.y), reliefPixel.z);
|
|
263
257
|
float reliefResolved = reliefAmount * (1.0 - smoothstep(0.5, 1.5, reliefFootprint));
|
|
264
258
|
if (reliefResolved > 0.0) {
|
|
265
|
-
vec3 at = vWorldPos *
|
|
259
|
+
vec3 at = vWorldPos * uRelief.y;
|
|
266
260
|
/* One cell of the noise, which is one bump: the gradient over any smaller step is the
|
|
267
261
|
interpolant's own slope rather than the shape's. */
|
|
268
262
|
float here = grain(at);
|
|
@@ -359,6 +353,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
359
353
|
* Turning only the interpolated normal would leave a normal map's tilt pointing the wrong way
|
|
360
354
|
* across the fold of every curtain.
|
|
361
355
|
*/
|
|
356
|
+
shadingNormal = n;
|
|
362
357
|
if (backFace) n = -n;
|
|
363
358
|
/*
|
|
364
359
|
* A model's surface, gathered once, on a branch on the switches alone (models.ts) — and whether
|
|
@@ -368,6 +363,14 @@ export const MAIN_GLSL = `void main() {
|
|
|
368
363
|
bool modelled = modelOn();
|
|
369
364
|
bool modelledBehind = modelSeesBehind();
|
|
370
365
|
if (modelled) modelSurface(n, albedo, surfaceRoughness, metal, surfaceAt);
|
|
366
|
+
/*
|
|
367
|
+
* Skin's colour half leaves here, with the colour its spread light is multiplied by, and a whole
|
|
368
|
+
* alpha a multisampled edge resolves to its coverage. A branch on the switches alone, so uniform.
|
|
369
|
+
*/
|
|
370
|
+
if (modelAlbedoAlone()) {
|
|
371
|
+
outColor = vec4(albedo, 1.0);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
371
374
|
|
|
372
375
|
/*
|
|
373
376
|
* One dot product, used twice: ndl is the lit side and its negative is the night side below.
|
|
@@ -467,6 +470,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
467
470
|
irradiance = mix(irradiance, ambient, probeGridOutside(surfaceN, vWorldPos));
|
|
468
471
|
ambient = mix(ambient, irradiance, uProbeGridAmbient * uEnvironmentEnabled);
|
|
469
472
|
#endif
|
|
473
|
+
/* A draw's own ambient, where one is set, in place of the frame's: \`setAmbientSH\`. */
|
|
474
|
+
if (uAmbientSH[6].w > 0.5) ambient = ambientHarmonics(n);
|
|
470
475
|
/*
|
|
471
476
|
* A metal keeps its ambient and loses its direct diffuse.
|
|
472
477
|
*
|
|
@@ -494,6 +499,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
494
499
|
} else {
|
|
495
500
|
lit = albedo * (ambient + sunColor * direct * (1.0 - metal));
|
|
496
501
|
}
|
|
502
|
+
/* A baked page's light, beside the sky's and the sun's: nothing but where a material has one. */
|
|
503
|
+
lit += modelBaked(n, albedo, metal);
|
|
497
504
|
|
|
498
505
|
/*
|
|
499
506
|
* A sun highlight, for the few surfaces that ask for one.
|
|
@@ -639,7 +646,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
639
646
|
*/
|
|
640
647
|
/* A model reflects its environment whatever the pass asked of the standard model: see
|
|
641
648
|
modelReflectance. On the switches alone, so the derivatives below stay in uniform flow. */
|
|
642
|
-
if (
|
|
649
|
+
if (uEnvironmentDials.y > 0.0 || uMaterialFlags.y != 0 || modelled) {
|
|
643
650
|
vec3 toEye = normalize(uCameraPos - vWorldPos);
|
|
644
651
|
vec3 mirrored = reflect(-toEye, modelled ? modelReflectNormal(n) : n);
|
|
645
652
|
vec3 environment = mix(uAmbientGround, uAmbient, mirrored.y * 0.5 + 0.5);
|
|
@@ -740,7 +747,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
740
747
|
float footprintRough = 0.5 * sqrt(min(swingRad, 1.0));
|
|
741
748
|
float lobeRough = min(1.0, sqrt(surfaceRoughness * surfaceRoughness + footprintRough * footprintRough));
|
|
742
749
|
float envLod = clamp(
|
|
743
|
-
mix(boxLod, lobeRough * uEnvironmentMaxLod,
|
|
750
|
+
mix(boxLod, lobeRough * uEnvironmentMaxLod, uEnvironmentDials.x),
|
|
744
751
|
0.0,
|
|
745
752
|
uEnvironmentMaxLod
|
|
746
753
|
);
|
|
@@ -776,7 +783,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
776
783
|
* \`mix(1, gain, 0)\` is exactly 1, so a dielectric is arithmetically untouched however the
|
|
777
784
|
* caller sets this.
|
|
778
785
|
*/
|
|
779
|
-
environment *= mix(1.0,
|
|
786
|
+
environment *= mix(1.0, uEnvironmentDials.z, metal);
|
|
780
787
|
float facing = 1.0 - max(dot(n, toEye), 0.0);
|
|
781
788
|
/*
|
|
782
789
|
* **A metal's Fresnel base is close to its albedo, not a dielectric's four percent.** Schlick
|
|
@@ -810,7 +817,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
810
817
|
* which is what the old \`max(uReflectivity, metal) * (1 - r)\` came to, and both renderers
|
|
811
818
|
* clamp that uniform non-negative on upload.
|
|
812
819
|
*/
|
|
813
|
-
float reflectAmount = max(
|
|
820
|
+
float reflectAmount = max(uEnvironmentDials.y * (1.0 - surfaceRoughness), metal);
|
|
814
821
|
/*
|
|
815
822
|
* **With a prefiltered environment, how much a surface returns is an integral rather than a
|
|
816
823
|
* curve somebody chose**, and this is where the comment above said the thinning should go
|
|
@@ -880,7 +887,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
880
887
|
*/
|
|
881
888
|
#if ENVIRONMENT_PROBE
|
|
882
889
|
vec2 dfg = envBrdfApprox(ndv, surfaceRoughness);
|
|
883
|
-
float integrated = clamp(envSpecularEnergy(f0, dfg) * max(
|
|
890
|
+
float integrated = clamp(envSpecularEnergy(f0, dfg) * max(uEnvironmentDials.y, metal), 0.0, 1.0);
|
|
884
891
|
/*
|
|
885
892
|
* **The split sum only where a prefiltered chain is what is being sampled.** It is the
|
|
886
893
|
* correct share of a *prefiltered* environment; over a box chain it asserts an integral that
|
|
@@ -892,7 +899,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
892
899
|
float weight = mix(
|
|
893
900
|
clamp(fresnel * reflectAmount, 0.0, 1.0),
|
|
894
901
|
integrated,
|
|
895
|
-
uEnvironmentEnabled *
|
|
902
|
+
uEnvironmentEnabled * uEnvironmentDials.x
|
|
896
903
|
);
|
|
897
904
|
#else
|
|
898
905
|
float weight = clamp(fresnel * reflectAmount, 0.0, 1.0);
|
|
@@ -1029,8 +1036,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
1029
1036
|
bool fixtureProfiles = false;
|
|
1030
1037
|
bool fixtureShapes = false;
|
|
1031
1038
|
if (LIGHT_FIXTURES) {
|
|
1032
|
-
fixtureProfiles =
|
|
1033
|
-
fixtureShapes =
|
|
1039
|
+
fixtureProfiles = uFixtureShape.x > 1.0;
|
|
1040
|
+
fixtureShapes = uFixtureShape.y > 1.0 || uFixtureShape.z > 0.0;
|
|
1034
1041
|
}
|
|
1035
1042
|
|
|
1036
1043
|
// Point lights. Inverse-square-ish falloff clipped to a finite radius, so a
|
|
@@ -1057,6 +1064,15 @@ export const MAIN_GLSL = `void main() {
|
|
|
1057
1064
|
float iesProfile;
|
|
1058
1065
|
vec3 iesAxis;
|
|
1059
1066
|
float cookie;
|
|
1067
|
+
/* The light's lighting channels, a mask: 1 unless it names others. See \`uLightIesAxis\`. */
|
|
1068
|
+
float lightChannels = 1.0;
|
|
1069
|
+
/*
|
|
1070
|
+
* **A rectangle in the froxel table** — one past the fixed arm's \`maxAreaLights\` — which
|
|
1071
|
+
* its record says with a negative radius, its reach negated. Its fields come out of the
|
|
1072
|
+
* fixture texels: sidedness, the right axis, the half extents and the up axis.
|
|
1073
|
+
*/
|
|
1074
|
+
bool clusteredArea = false;
|
|
1075
|
+
float areaTwoSided = 0.0;
|
|
1060
1076
|
if (clusteredArm) {
|
|
1061
1077
|
if (slot >= clusterLights) break;
|
|
1062
1078
|
/*
|
|
@@ -1075,6 +1091,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
1075
1091
|
lightPos = vec3(
|
|
1076
1092
|
uintBitsToFloat(rec0.x), uintBitsToFloat(rec0.y), uintBitsToFloat(rec0.z));
|
|
1077
1093
|
lightRadius = uintBitsToFloat(rec0.w);
|
|
1094
|
+
clusteredArea = lightRadius < 0.0;
|
|
1095
|
+
lightRadius = abs(lightRadius);
|
|
1078
1096
|
/*
|
|
1079
1097
|
* **Out of reach is decided on the first texel, before the rest of the record is read.**
|
|
1080
1098
|
* A froxel holds every light whose sphere touches its box, so most of the lights a fragment
|
|
@@ -1119,7 +1137,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
1119
1137
|
uvec4 rec2 = clusterTexel(record + 2);
|
|
1120
1138
|
uvec4 rec3 = clusterTexel(record + 3);
|
|
1121
1139
|
uvec4 rec4 = clusterTexel(record + 4);
|
|
1122
|
-
shadowSlot = int(uintBitsToFloat(rec2.x));
|
|
1140
|
+
shadowSlot = clusteredArea ? -1 : int(uintBitsToFloat(rec2.x));
|
|
1141
|
+
areaTwoSided = uintBitsToFloat(rec2.x);
|
|
1123
1142
|
lightDir = vec3(
|
|
1124
1143
|
uintBitsToFloat(rec2.y), uintBitsToFloat(rec2.z), uintBitsToFloat(rec2.w));
|
|
1125
1144
|
lightCone = vec2(uintBitsToFloat(rec3.x), uintBitsToFloat(rec3.y));
|
|
@@ -1129,6 +1148,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
1129
1148
|
texel rather than being derived from the direction. */
|
|
1130
1149
|
iesAxis = vec3(
|
|
1131
1150
|
uintBitsToFloat(rec4.x), uintBitsToFloat(rec4.y), uintBitsToFloat(rec4.z));
|
|
1151
|
+
lightChannels = uintBitsToFloat(rec4.w);
|
|
1132
1152
|
}
|
|
1133
1153
|
}
|
|
1134
1154
|
} else {
|
|
@@ -1143,9 +1163,42 @@ export const MAIN_GLSL = `void main() {
|
|
|
1143
1163
|
lightDir = uLightDir[i];
|
|
1144
1164
|
lightCone = uLightCone[i];
|
|
1145
1165
|
iesProfile = uLightIesProfile[i];
|
|
1146
|
-
iesAxis = uLightIesAxis[i];
|
|
1166
|
+
iesAxis = uLightIesAxis[i].xyz;
|
|
1167
|
+
lightChannels = uLightIesAxis[i].w;
|
|
1147
1168
|
cookie = uLightCookie[i];
|
|
1148
1169
|
}
|
|
1170
|
+
/*
|
|
1171
|
+
* **A light shades a surface only where their channels meet**: the light's mask against the
|
|
1172
|
+
* material's. Both default to channel 1, so a scene naming none takes every light as before
|
|
1173
|
+
* — and **zero reads as that default on both sides**, so a pass that never writes the lane
|
|
1174
|
+
* loses no light: an unwritten uniform is zero, and zero here would be every lamp switched
|
|
1175
|
+
* off. A branch on a value read from a texture in the clustered arm, as the reach test above
|
|
1176
|
+
* is, and it skips only what the light would add; no derivative is taken past it.
|
|
1177
|
+
*/
|
|
1178
|
+
int litChannels = int(lightChannels);
|
|
1179
|
+
int surfaceChannels = int(uCutout.w);
|
|
1180
|
+
if (((litChannels == 0 ? 1 : litChannels) & (surfaceChannels == 0 ? 1 : surfaceChannels)) == 0) {
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
/*
|
|
1184
|
+
* **A clustered rectangle is shaded here and goes no further**: by the fixed loop's own body
|
|
1185
|
+
* (\`areaLight.ts\`), unshadowed — its occlusion is the fixed arm's, which is why the first
|
|
1186
|
+
* \`maxAreaLights\` stay there — and windowed to nothing at its reach, as a lamp is, so the
|
|
1187
|
+
* froxel it is binned into has no edge in the picture.
|
|
1188
|
+
*/
|
|
1189
|
+
if (clusteredArea) {
|
|
1190
|
+
float areaForm = areaSignedForm(n, lightPos, lightDir, iesAxis, lightCone);
|
|
1191
|
+
float areaWindow = clamp(1.0 - pow(length(lightPos - vWorldPos) / max(lightRadius, 1e-4), 4.0), 0.0, 1.0);
|
|
1192
|
+
areaForm = mix(max(0.0, areaForm), abs(areaForm), areaTwoSided);
|
|
1193
|
+
if (areaForm > 0.0) {
|
|
1194
|
+
areaLightAdd(
|
|
1195
|
+
lightPos, lightDir, iesAxis, lightCone, lightColor * (areaWindow * areaWindow), areaForm,
|
|
1196
|
+
1.0, vec3(1.0), n, albedo, metal, specColor, surfaceRoughness, modelled,
|
|
1197
|
+
lampOpen, lampShadowed
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1149
1202
|
/* A DriftLight field's own light carries a negative weight, and is shaded exactly only for the
|
|
1150
1203
|
share of this pixel the field does not stand in for. See \`driftLight.ts\`. */
|
|
1151
1204
|
if (lightWeight < 0.0) lightWeight = -lightWeight * (1.0 - driftShare);
|
|
@@ -1350,7 +1403,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
1350
1403
|
* and the result is selected afterwards with a \`mix\` on a compare — which is arithmetic
|
|
1351
1404
|
* rather than control flow.
|
|
1352
1405
|
*
|
|
1353
|
-
* **And skipped outright where nothing loaded a profile**, behind \`
|
|
1406
|
+
* **And skipped outright where nothing loaded a profile**, behind \`uFixtureShape.x\`, which is
|
|
1354
1407
|
* one while the atlas holds only its row of ones. A uniform is provably uniform control
|
|
1355
1408
|
* flow, so this branch is not the one the rule forbids. It was paid on every scene until a
|
|
1356
1409
|
* 4K frame of 320 lamps and no shadows measured it at 0.7 ms of the main pass, rising to
|
|
@@ -1393,12 +1446,12 @@ export const MAIN_GLSL = `void main() {
|
|
|
1393
1446
|
if (iesRefLen > 1e-5) {
|
|
1394
1447
|
iesRef /= iesRefLen;
|
|
1395
1448
|
vec3 iesBinormal = cross(lightDir, iesRef);
|
|
1396
|
-
if (
|
|
1449
|
+
if (uFixtureShape.y > 1.0) {
|
|
1397
1450
|
/* \`atan\` answers -PI..PI and \`fract\` of a negative wraps forward, so this is 0..1
|
|
1398
1451
|
across the whole turn without a branch on the sign. */
|
|
1399
1452
|
iesTurns = fract(atan(dot(iesRay, iesBinormal), dot(iesRay, iesRef)) / TAU_IES);
|
|
1400
1453
|
}
|
|
1401
|
-
if (
|
|
1454
|
+
if (uFixtureShape.z > 0.0) {
|
|
1402
1455
|
/*
|
|
1403
1456
|
* **A perspective projection onto the plane one unit down the aim**, which is what a
|
|
1404
1457
|
* fixture actually throws: the mask grows with distance rather than being pasted on
|
|
@@ -1445,10 +1498,10 @@ export const MAIN_GLSL = `void main() {
|
|
|
1445
1498
|
*/
|
|
1446
1499
|
float photometric = 1.0;
|
|
1447
1500
|
if (fixtureProfiles) {
|
|
1448
|
-
float iesBase = iesRow * (
|
|
1501
|
+
float iesBase = iesRow * (uFixtureShape.y + 1.0);
|
|
1449
1502
|
/* The profiles are the atlas's bottom rows, below any cookies. */
|
|
1450
|
-
float iesTop = fixtureSize.y -
|
|
1451
|
-
float iesV = (iesTop + iesBase + iesTurns *
|
|
1503
|
+
float iesTop = fixtureSize.y - uFixtureShape.x;
|
|
1504
|
+
float iesV = (iesTop + iesBase + iesTurns * uFixtureShape.y + 0.5) / fixtureSize.y;
|
|
1452
1505
|
float iesGain = textureLod(uFixtureAtlas, vec2(iesU, iesV), 0.0).r;
|
|
1453
1506
|
photometric = mix(1.0, iesGain, step(0.0, iesProfile));
|
|
1454
1507
|
}
|
|
@@ -1585,22 +1638,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
1585
1638
|
vec3 up = uAreaLightUp[a];
|
|
1586
1639
|
vec2 halfSize = uAreaLightSize[a];
|
|
1587
1640
|
|
|
1588
|
-
/*
|
|
1589
|
-
|
|
1590
|
-
* makes the form factor positive there. The natural order — minus, plus, plus, minus — winds
|
|
1591
|
-
* the other way, and every one-sided light then clamped to zero and the frame was black.
|
|
1592
|
-
*/
|
|
1593
|
-
vec3 c0 = centre - right * halfSize.x + up * halfSize.y;
|
|
1594
|
-
vec3 c1 = centre + right * halfSize.x + up * halfSize.y;
|
|
1595
|
-
vec3 c2 = centre + right * halfSize.x - up * halfSize.y;
|
|
1596
|
-
vec3 c3 = centre - right * halfSize.x - up * halfSize.y;
|
|
1597
|
-
|
|
1598
|
-
/*
|
|
1599
|
-
* **The sign is the sidedness.** A one-sided rectangle takes the positive part and a
|
|
1600
|
-
* two-sided one the magnitude — no facing test, because the sign already answers it exactly
|
|
1601
|
-
* per fragment rather than once for the rectangle's centre.
|
|
1602
|
-
*/
|
|
1603
|
-
float signedForm = quadFormFactor(n, vWorldPos, c0, c1, c2, c3);
|
|
1641
|
+
/* The form factor and its sign, which is the sidedness: see \`areaSignedForm\`. */
|
|
1642
|
+
float signedForm = areaSignedForm(n, centre, right, up, halfSize);
|
|
1604
1643
|
float form = mix(max(0.0, signedForm), abs(signedForm), uAreaLightTwoSided[a]);
|
|
1605
1644
|
/* The form factor is linear in the normal, so the one seen from behind is its negation. */
|
|
1606
1645
|
float backForm = glassTransmission > 0.0
|
|
@@ -1715,55 +1754,11 @@ export const MAIN_GLSL = `void main() {
|
|
|
1715
1754
|
|
|
1716
1755
|
glassGlow += uAreaLightColor[a] * backForm * areaOccl * areaGlass;
|
|
1717
1756
|
if (form <= 0.0) continue;
|
|
1718
|
-
/*
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
* from its centre rather than its shape.
|
|
1724
|
-
*/
|
|
1725
|
-
if (modelled) {
|
|
1726
|
-
vec3 areaL = normalize(centre - vWorldPos);
|
|
1727
|
-
modelLight(areaL, 0.0, 0.0);
|
|
1728
|
-
vec3 areaModel = (mDiffuse * (form / max(dot(n, areaL), 1e-3)) + mSpecular * form)
|
|
1729
|
-
* uAreaLightColor[a];
|
|
1730
|
-
lampOpen += areaModel;
|
|
1731
|
-
lampShadowed += areaModel * areaOccl * areaGlass;
|
|
1732
|
-
continue;
|
|
1733
|
-
}
|
|
1734
|
-
/* Held back with the point lamps', and shadowed by the same term it always was. */
|
|
1735
|
-
vec3 areaDiffuse = albedo * uAreaLightColor[a] * form * (1.0 - metal);
|
|
1736
|
-
lampOpen += areaDiffuse;
|
|
1737
|
-
lampShadowed += areaDiffuse * areaOccl * areaGlass;
|
|
1738
|
-
|
|
1739
|
-
if (vSpecular > 0.0 || metal > 0.0) {
|
|
1740
|
-
/*
|
|
1741
|
-
* **The lobe integrated over the rectangle, rather than sampled at one point on it.**
|
|
1742
|
-
*
|
|
1743
|
-
* What stood here handed the closest point on the rectangle to \`sphereLobe\` and scaled it
|
|
1744
|
-
* by \`form\`, the *diffuse* form factor. Measured against a brute-force integral by
|
|
1745
|
-
* \`scripts/areaSpecular.mjs\`, that lost the reflection rather than blurring it: on
|
|
1746
|
-
* polished metal facing a softbox it returned 0.000233 where the integral is 0.9207, and
|
|
1747
|
-
* head-on on a rough surface it was 3.7 times too bright. Worst 287% either way; this is
|
|
1748
|
-
* 40.1% where the light is what the surface reflects and 79.0% for one overhead.
|
|
1749
|
-
*
|
|
1750
|
-
* \`quadCoverage\` says what fraction of the specular lobe the rectangle covers and the
|
|
1751
|
-
* environment BRDF says how much the surface returns, which is the same split the probe
|
|
1752
|
-
* path uses. **\`dfg.x + dfg.y\` and not \`envSpecularEnergy\`**, deliberately and against
|
|
1753
|
-
* the obvious argument: the compensation returns the multiply-scattered share, and every
|
|
1754
|
-
* direct lobe in this shader is single-scattering, so adding it here returns energy the
|
|
1755
|
-
* light never had. Measured at 211% against 40.1%.
|
|
1756
|
-
*/
|
|
1757
|
-
vec3 toEyeArea = normalize(uCameraPos - vWorldPos);
|
|
1758
|
-
float coverage = quadCoverage(n, vWorldPos, toEyeArea, surfaceRoughness, c0, c1, c2, c3);
|
|
1759
|
-
if (coverage > 0.0) {
|
|
1760
|
-
vec2 areaDfg = envBrdfApprox(max(dot(n, toEyeArea), 0.0), surfaceRoughness);
|
|
1761
|
-
vec3 areaSpec = specColor * areaDfg.x + vec3(areaDfg.y);
|
|
1762
|
-
vec3 areaHighlight = areaSpec * uAreaLightColor[a] * coverage;
|
|
1763
|
-
lampOpen += areaHighlight;
|
|
1764
|
-
lampShadowed += areaHighlight * areaOccl * areaGlass;
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1757
|
+
/* The diffuse, the model's answer or the lobe integrated over the rectangle: see areaLight.ts. */
|
|
1758
|
+
areaLightAdd(
|
|
1759
|
+
centre, right, up, halfSize, uAreaLightColor[a], form, areaOccl, areaGlass,
|
|
1760
|
+
n, albedo, metal, specColor, surfaceRoughness, modelled, lampOpen, lampShadowed
|
|
1761
|
+
);
|
|
1767
1762
|
}
|
|
1768
1763
|
|
|
1769
1764
|
/*
|
|
@@ -1905,10 +1900,10 @@ export const MAIN_GLSL = `void main() {
|
|
|
1905
1900
|
}
|
|
1906
1901
|
/* Pulse, flicker and fade from the layer's effects; exactly 1 where it names none. */
|
|
1907
1902
|
float fxGlow = fx4.z + fx5.x + fx5.w > 0.0 ? fxEmission(surfaceAt.z) : 1.0;
|
|
1908
|
-
lit += emissiveTint * emissiveMapped *
|
|
1903
|
+
lit += emissiveTint * emissiveMapped * uEmission.x * vEmissive * uEmission.y
|
|
1909
1904
|
* mix(1.0, min(lightShade, sunShade), EMISSIVE_SHADOW_SHARE) * fxGlow;
|
|
1910
1905
|
/* And a lit window's glow, which the scene's lit share already times: not gated on night. */
|
|
1911
|
-
lit += windowGlow *
|
|
1906
|
+
lit += windowGlow * uEmission.x;
|
|
1912
1907
|
|
|
1913
1908
|
#if NIGHT_EMISSIVE
|
|
1914
1909
|
/*
|
|
@@ -1941,9 +1936,9 @@ export const MAIN_GLSL = `void main() {
|
|
|
1941
1936
|
// Arrival glow: a world-space box lit independently of the clock, so
|
|
1942
1937
|
// finishing a run reads the same at noon and at midnight. Costs one compare
|
|
1943
1938
|
// per fragment and no extra draw call — the box is a uniform, not geometry.
|
|
1944
|
-
if (
|
|
1945
|
-
vec3 inside = step(uHighlightMin, vWorldPos) * step(vWorldPos, uHighlightMax);
|
|
1946
|
-
lit += albedo *
|
|
1939
|
+
if (uHighlightMax.w > 0.0) {
|
|
1940
|
+
vec3 inside = step(uHighlightMin, vWorldPos) * step(vWorldPos, uHighlightMax.xyz);
|
|
1941
|
+
lit += albedo * uHighlightMax.w * inside.x * inside.y * inside.z;
|
|
1947
1942
|
}
|
|
1948
1943
|
}
|
|
1949
1944
|
|
|
@@ -2000,7 +1995,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
2000
1995
|
/*
|
|
2001
1996
|
* **Refraction: the scene behind this surface, bent and absorbed.**
|
|
2002
1997
|
*
|
|
2003
|
-
* The offset is along the
|
|
1998
|
+
* The offset is along the shading normal in screen space, which is the cheap approximation every
|
|
2004
1999
|
* real-time renderer ships rather than a ray traced through two interfaces of a solid. What it
|
|
2005
2000
|
* cannot do is show anything the frame had not already drawn, so a pane at the edge of the frame
|
|
2006
2001
|
* refracts what is beside it -- and the clamp below is what keeps that from being worse than
|
|
@@ -2035,7 +2030,14 @@ export const MAIN_GLSL = `void main() {
|
|
|
2035
2030
|
* draw uses.
|
|
2036
2031
|
*/
|
|
2037
2032
|
if (uSeeThrough.x > 0.0 || glassTransmission > 0.0) {
|
|
2038
|
-
|
|
2033
|
+
/*
|
|
2034
|
+
* **Bent by the shading normal, so a normal map shimmers what is seen through it** — a heat
|
|
2035
|
+
* haze, a distortion sphere, a rippled pane. Until 4.8.6 this was the geometric normal and a
|
|
2036
|
+
* mapped surface refracted as a smooth lens. An unlit draw shaded no normal, so it reads the
|
|
2037
|
+
* map here, on a branch on uniforms alone, which keeps its derivatives legal.
|
|
2038
|
+
*/
|
|
2039
|
+
vec3 refractN = shadingNormal;
|
|
2040
|
+
if (uLightingEnabled == 0 && uNormalStrength > 0.0) refractN = normalMapped(refractN, surfaceAt);
|
|
2039
2041
|
vec3 refractV = normalize(uCameraPos - vWorldPos);
|
|
2040
2042
|
ivec2 snapSize = textureSize(uRefractScene, 0);
|
|
2041
2043
|
vec2 screenUv = gl_FragCoord.xy / vec2(snapSize);
|
|
@@ -3,12 +3,24 @@ import type { SurfaceModelKind } from '../../surfaceModel.ts';
|
|
|
3
3
|
export declare const MODELS_GLSL: string;
|
|
4
4
|
/**
|
|
5
5
|
* Which half of a skin a lit pipeline draws under `skinScattering: 'screen-space'`: the whole
|
|
6
|
-
* surface (every other pipeline), the frame's half without its diffuse,
|
|
7
|
-
* the blur's target
|
|
8
|
-
*
|
|
6
|
+
* surface (every other pipeline), the frame's half without its diffuse, the diffuse's light alone
|
|
7
|
+
* into the blur's target, or the colour that light is multiplied by after the blur into a target
|
|
8
|
+
* of its own — those last two depth-tested for equality against what the frame's half wrote and
|
|
9
|
+
* writing none, since they are the same surface drawn again. See `skinBlur.ts`.
|
|
9
10
|
*/
|
|
10
|
-
export type SkinHalf = 'whole' | 'scene' | 'diffuse';
|
|
11
|
+
export type SkinHalf = 'whole' | 'scene' | 'diffuse' | 'albedo';
|
|
12
|
+
/** The constant each half of a skin turns on. See `SkinHalf`. */
|
|
13
|
+
export declare const SKIN_HALF_SWITCH: {
|
|
14
|
+
readonly scene: 'SKIN_SCREEN';
|
|
15
|
+
readonly diffuse: 'SKIN_DIFFUSE';
|
|
16
|
+
readonly albedo: 'SKIN_ALBEDO';
|
|
17
|
+
};
|
|
11
18
|
/** A skin lit stage with one half's switch on, WebGL2's way: see `SkinHalf`. */
|
|
12
19
|
export declare function skinHalfBound(source: string, half: SkinHalf): string;
|
|
20
|
+
/**
|
|
21
|
+
* An instanced vertex stage whose tint lane is a lightmap region: the same source, the constant
|
|
22
|
+
* flipped — WebGL2's way. Only the instanced stage declares it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function lightmapRegionsBound(source: string): string;
|
|
13
25
|
/** A lit stage with one model's switch on: the same source, the constant flipped — WebGL2's way. */
|
|
14
26
|
export declare function modelBound(source: string, kind: SurfaceModelKind): string;
|
|
@@ -33,12 +33,19 @@ const bool MODEL_HAIR = false; // wgsl:override
|
|
|
33
33
|
const bool MODEL_SKIN = false; // wgsl:override
|
|
34
34
|
const bool MODEL_EYE = false; // wgsl:override
|
|
35
35
|
/*
|
|
36
|
-
* Skin's screen-space scattering, as
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* Skin's screen-space scattering, as halves of one surface: the scene pass without its diffuse; a
|
|
37
|
+
* pass that is nothing but the light the diffuse is made of, which the blur spreads; and a pass that
|
|
38
|
+
* is nothing but the colour that light is multiplied by once it has spread. All off but where
|
|
39
|
+
* \`skinScattering\` is \`'screen-space'\`; see \`skinBlur.ts\`.
|
|
39
40
|
*/
|
|
40
41
|
const bool SKIN_SCREEN = false; // wgsl:override
|
|
41
42
|
const bool SKIN_DIFFUSE = false; // wgsl:override
|
|
43
|
+
const bool SKIN_ALBEDO = false; // wgsl:override
|
|
44
|
+
/*
|
|
45
|
+
* Not a model of light but a page of it: the standard model, plus what a bake left (lightmap.ts).
|
|
46
|
+
* Declared after skin's halves so that every switch before it keeps the id it shipped with.
|
|
47
|
+
*/
|
|
48
|
+
const bool MODEL_LIGHTMAP = false; // wgsl:override
|
|
42
49
|
|
|
43
50
|
/*
|
|
44
51
|
* A model's numbers, two vectors a material — the lit stage's uniform budget has no more room
|
|
@@ -65,6 +72,14 @@ bool modelDiffuseAlone() {
|
|
|
65
72
|
return false;
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
/** Whether this pipeline draws skin's colour alone, which the spread light is multiplied by. */
|
|
76
|
+
bool modelAlbedoAlone() {
|
|
77
|
+
if (MODEL_SKIN) {
|
|
78
|
+
if (SKIN_ALBEDO) return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
68
83
|
/** Whether light from behind the surface reaches the eye through it: hair, and skin's thin parts. */
|
|
69
84
|
bool modelSeesBehind() {
|
|
70
85
|
if (MODEL_HAIR) return true;
|
|
@@ -146,6 +161,15 @@ void modelSurface(vec3 n, vec3 albedo, float roughness, float metal, vec3 at) {
|
|
|
146
161
|
mTangent = frame[0];
|
|
147
162
|
mBitangent = frame[1];
|
|
148
163
|
mAlbedo = albedo;
|
|
164
|
+
/*
|
|
165
|
+
* **The diffuse half is light, not colour.** The blur spreads what this half writes, and what
|
|
166
|
+
* spreads beneath skin is the light: a brow, a lip line and a freckle stay where they are drawn.
|
|
167
|
+
* So this half shades a white surface and the colour is applied after the spread, from a half of
|
|
168
|
+
* its own. Spreading the coloured diffuse smeared every mark on a face into the skin around it.
|
|
169
|
+
*/
|
|
170
|
+
if (MODEL_SKIN) {
|
|
171
|
+
if (SKIN_DIFFUSE) mAlbedo = vec3(1.0);
|
|
172
|
+
}
|
|
149
173
|
mSpecColor = mix(vec3(vSpecular), albedo, metal);
|
|
150
174
|
mRoughness = roughness;
|
|
151
175
|
mMetal = metal;
|
|
@@ -226,6 +250,25 @@ vec3 modelEnvironment(vec3 environment) {
|
|
|
226
250
|
return environment;
|
|
227
251
|
}
|
|
228
252
|
|
|
253
|
+
/**
|
|
254
|
+
* What a baked page adds to this surface's diffuse: nothing in any pipeline but a lightmapped one,
|
|
255
|
+
* nor in one whose page is missing. The page's two layers at the surface's second coordinates, which
|
|
256
|
+
* ride the grain and relief lanes as \`-1 - uv\` (lightmap.ts) and are placed by the material's
|
|
257
|
+
* region: the irradiance, and the first-order harmonic its direction is, answered by the shading
|
|
258
|
+
* normal.
|
|
259
|
+
* **An explicit level**, because the page has one and nothing here needs a derivative.
|
|
260
|
+
*/
|
|
261
|
+
vec3 modelBaked(vec3 n, vec3 albedo, float metal) {
|
|
262
|
+
if (MODEL_LIGHTMAP) {
|
|
263
|
+
if (uModelParams[1].w <= 0.0) return vec3(0.0);
|
|
264
|
+
vec2 at = (vec2(-1.0) - vec2(vGrain, vRelief)) * uModelParams[0].xy + uModelParams[0].zw;
|
|
265
|
+
vec3 irradiance = textureLod(uModelMap, vec3(at, 0.0), 0.0).rgb;
|
|
266
|
+
vec4 d = textureLod(uModelMap, vec3(at, 1.0), 0.0) * 2.0 - 1.0;
|
|
267
|
+
return albedo * (1.0 - metal) * irradiance * max(dot(d.xyz, n) + d.w, 0.0);
|
|
268
|
+
}
|
|
269
|
+
return vec3(0.0);
|
|
270
|
+
}
|
|
271
|
+
|
|
229
272
|
/** Skin's diffuse as the diffuse pass writes it, from the lit total before emission, fogged. */
|
|
230
273
|
vec3 modelDiffuseOut(vec3 diffuse, float fog) {
|
|
231
274
|
return diffuse * (1.0 - fog);
|
|
@@ -251,17 +294,34 @@ export const MODELS_GLSL = [
|
|
|
251
294
|
EYE_GLSL,
|
|
252
295
|
MODELS_DISPATCH,
|
|
253
296
|
].join('\n');
|
|
297
|
+
/** The constant each half of a skin turns on. See `SkinHalf`. */
|
|
298
|
+
export const SKIN_HALF_SWITCH = {
|
|
299
|
+
scene: 'SKIN_SCREEN',
|
|
300
|
+
diffuse: 'SKIN_DIFFUSE',
|
|
301
|
+
albedo: 'SKIN_ALBEDO',
|
|
302
|
+
};
|
|
254
303
|
/** A skin lit stage with one half's switch on, WebGL2's way: see `SkinHalf`. */
|
|
255
304
|
export function skinHalfBound(source, half) {
|
|
256
305
|
if (half === 'whole')
|
|
257
306
|
return source;
|
|
258
|
-
const name = half
|
|
307
|
+
const name = SKIN_HALF_SWITCH[half];
|
|
259
308
|
const off = `const bool ${name} = false;`;
|
|
260
309
|
if (!source.includes(off)) {
|
|
261
310
|
throw new Error(`skinHalfBound: this lit stage declares no ${name} constant`);
|
|
262
311
|
}
|
|
263
312
|
return source.replace(off, `const bool ${name} = true;`);
|
|
264
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* An instanced vertex stage whose tint lane is a lightmap region: the same source, the constant
|
|
316
|
+
* flipped — WebGL2's way. Only the instanced stage declares it.
|
|
317
|
+
*/
|
|
318
|
+
export function lightmapRegionsBound(source) {
|
|
319
|
+
const off = 'const bool LIGHTMAP_REGIONS = false;';
|
|
320
|
+
if (!source.includes(off)) {
|
|
321
|
+
throw new Error('lightmapRegionsBound: this vertex stage declares no LIGHTMAP_REGIONS constant');
|
|
322
|
+
}
|
|
323
|
+
return source.replace(off, 'const bool LIGHTMAP_REGIONS = true;');
|
|
324
|
+
}
|
|
265
325
|
/** A lit stage with one model's switch on: the same source, the constant flipped — WebGL2's way. */
|
|
266
326
|
export function modelBound(source, kind) {
|
|
267
327
|
const name = SURFACE_MODEL_SWITCH[kind];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The material's normal map applied to a surface normal. **The one statement of it**, read by the
|
|
3
|
+
* lit block for every light and by an unlit refracting draw for the direction it bends, so a lit
|
|
4
|
+
* pane and an unlit distortion bend by one tilt rather than two transcriptions of it.
|
|
5
|
+
*
|
|
6
|
+
* Depth zero, after the tangent frame it calls; see `flatFrag`. **Only ever called under a branch
|
|
7
|
+
* on a uniform** — `uNormalStrength` and whatever gates its caller — which is what makes the
|
|
8
|
+
* derivatives inside `tangentFrame` and the implicit level of the read legal: AGENTS.md 2026-08-07.
|
|
9
|
+
*/
|
|
10
|
+
export declare const NORMAL_MAP_GLSL = "\nvec3 normalMapped(vec3 n, vec3 at) {\n vec3 mapped = texture(uNormalMap, at).xyz * 2.0 - 1.0;\n /*\n * z rebuilt where none is stored. A tangent-space normal points out of its surface, so a\n * stored z at or below zero is never a real one: it is a two-channel map, BC5, whose blue a\n * device samples as zero and which arrives here as -1. Every map baked from BC5 to PNG before\n * 4.8.4 carried the same zero. A select rather than a flag: no uniform to bind, no material\n * that has to say what format it holds, and continuous at the boundary, since a unit texel\n * lying flat has its z near zero either way. A texel with z above zero is left as it was.\n */\n mapped.z = mapped.z > 0.0 ? mapped.z : sqrt(max(0.0, 1.0 - dot(mapped.xy, mapped.xy)));\n mat3 tbn = tangentFrame(n, vWorldPos, vUv.xy, vTangent, vHasTangents);\n return normalize(mix(n, normalize(tbn * mapped), uNormalStrength));\n}\n";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The material's normal map applied to a surface normal. **The one statement of it**, read by the
|
|
3
|
+
* lit block for every light and by an unlit refracting draw for the direction it bends, so a lit
|
|
4
|
+
* pane and an unlit distortion bend by one tilt rather than two transcriptions of it.
|
|
5
|
+
*
|
|
6
|
+
* Depth zero, after the tangent frame it calls; see `flatFrag`. **Only ever called under a branch
|
|
7
|
+
* on a uniform** — `uNormalStrength` and whatever gates its caller — which is what makes the
|
|
8
|
+
* derivatives inside `tangentFrame` and the implicit level of the read legal: AGENTS.md 2026-08-07.
|
|
9
|
+
*/
|
|
10
|
+
export const NORMAL_MAP_GLSL = /* glsl */ `
|
|
11
|
+
vec3 normalMapped(vec3 n, vec3 at) {
|
|
12
|
+
vec3 mapped = texture(uNormalMap, at).xyz * 2.0 - 1.0;
|
|
13
|
+
/*
|
|
14
|
+
* z rebuilt where none is stored. A tangent-space normal points out of its surface, so a
|
|
15
|
+
* stored z at or below zero is never a real one: it is a two-channel map, BC5, whose blue a
|
|
16
|
+
* device samples as zero and which arrives here as -1. Every map baked from BC5 to PNG before
|
|
17
|
+
* 4.8.4 carried the same zero. A select rather than a flag: no uniform to bind, no material
|
|
18
|
+
* that has to say what format it holds, and continuous at the boundary, since a unit texel
|
|
19
|
+
* lying flat has its z near zero either way. A texel with z above zero is left as it was.
|
|
20
|
+
*/
|
|
21
|
+
mapped.z = mapped.z > 0.0 ? mapped.z : sqrt(max(0.0, 1.0 - dot(mapped.xy, mapped.xy)));
|
|
22
|
+
mat3 tbn = tangentFrame(n, vWorldPos, vUv.xy, vTangent, vHasTangents);
|
|
23
|
+
return normalize(mix(n, normalize(tbn * mapped), uNormalStrength));
|
|
24
|
+
}
|
|
25
|
+
`;
|