@driftengine/core 4.8.6 → 4.9.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 +132 -0
- package/README.md +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +8 -1
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +35 -0
- package/dist/render/areaLights.js +35 -0
- package/dist/render/atmosphere.d.ts +13 -0
- package/dist/render/atmosphere.js +9 -1
- package/dist/render/backend/api.d.ts +41 -3
- package/dist/render/backend/createRenderer.d.ts +6 -0
- package/dist/render/backend/createRenderer.js +4 -1
- package/dist/render/backend/webgl2/renderer.d.ts +140 -16
- package/dist/render/backend/webgl2/renderer.js +436 -44
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- package/dist/render/backend/webgpu/device.d.ts +7 -1
- package/dist/render/backend/webgpu/device.js +31 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +16 -1
- package/dist/render/backend/webgpu/flatPass.js +58 -26
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +51 -9
- package/dist/render/backend/webgpu/postPass.js +16 -2
- package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
- package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
- package/dist/render/backend/webgpu/renderer.d.ts +166 -3
- package/dist/render/backend/webgpu/renderer.js +738 -83
- package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
- package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +34 -8
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/clusteredLights.d.ts +23 -5
- package/dist/render/clusteredLights.js +35 -8
- package/dist/render/displayRange.d.ts +31 -0
- package/dist/render/displayRange.js +47 -0
- package/dist/render/driftLight/bake.d.ts +2 -0
- package/dist/render/driftLight/bake.js +1 -1
- package/dist/render/driftLight/falloff.d.ts +4 -2
- package/dist/render/driftLight/falloff.js +8 -2
- package/dist/render/filmicCurve.d.ts +74 -0
- package/dist/render/filmicCurve.js +203 -0
- package/dist/render/fog.d.ts +4 -1
- package/dist/render/fog.js +12 -2
- package/dist/render/frame/resources.d.ts +2 -2
- package/dist/render/frame/resources.js +6 -0
- package/dist/render/frameReflections.d.ts +40 -0
- package/dist/render/frameReflections.js +48 -0
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +5 -0
- package/dist/render/instances.d.ts +19 -5
- package/dist/render/instances.js +9 -3
- package/dist/render/lightBudget.d.ts +12 -1
- package/dist/render/lightBudget.js +23 -3
- package/dist/render/lightmap.d.ts +23 -9
- package/dist/render/lightmap.js +40 -18
- package/dist/render/particlePool.d.ts +21 -0
- package/dist/render/particlePool.js +7 -0
- package/dist/render/pass.d.ts +49 -0
- package/dist/render/pointLightSelection.d.ts +13 -0
- package/dist/render/pointLightSelection.js +3 -1
- package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
- package/dist/render/reflectionSurfaceTargets.js +99 -0
- package/dist/render/renderQuality.d.ts +47 -15
- package/dist/render/renderQuality.js +4 -0
- package/dist/render/rendererHarness.js +3 -0
- package/dist/render/rgb9e5.d.ts +22 -0
- package/dist/render/rgb9e5.js +47 -0
- package/dist/render/sceneCapture.d.ts +29 -0
- package/dist/render/sceneCapture.js +36 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +24 -1
- package/dist/render/screenSpaceReflection.d.ts +32 -1
- package/dist/render/screenSpaceReflection.js +14 -0
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +7 -1
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/depth.js +11 -0
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/filmLook.js +2 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.js +37 -3
- package/dist/render/shaders/flat/areaLight.d.ts +1 -1
- package/dist/render/shaders/flat/areaLight.js +46 -0
- package/dist/render/shaders/flat/glassTint.d.ts +3 -2
- package/dist/render/shaders/flat/glassTint.js +5 -1
- package/dist/render/shaders/flat/index.d.ts +15 -0
- package/dist/render/shaders/flat/index.js +22 -2
- package/dist/render/shaders/flat/lobes.d.ts +6 -0
- package/dist/render/shaders/flat/lobes.js +49 -43
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +85 -22
- package/dist/render/shaders/flat/models.d.ts +5 -2
- package/dist/render/shaders/flat/models.js +100 -16
- package/dist/render/shaders/flat/overlay.d.ts +16 -0
- package/dist/render/shaders/flat/overlay.js +117 -0
- package/dist/render/shaders/flat/pointShadow.js +1 -1
- package/dist/render/shaders/flat/preamble.js +10 -3
- package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
- package/dist/render/shaders/flat/reflectionSurface.js +61 -0
- package/dist/render/shaders/fog.d.ts +1 -1
- package/dist/render/shaders/fog.js +9 -2
- package/dist/render/shaders/fringe.d.ts +29 -0
- package/dist/render/shaders/fringe.js +55 -0
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/depth.wgsl.js +4 -4
- package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/film.wgsl.js +1 -1
- package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/fire.wgsl.js +1 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +192 -16
- package/dist/render/shaders/generated/flat.wgsl.js +230 -54
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/particle.wgsl.js +4 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
- package/dist/render/shaders/generated/rush.wgsl.js +56 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
- package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +32 -7
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +32 -7
- package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
- package/dist/render/shaders/generated/water.wgsl.js +23 -16
- package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
- package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +13 -2
- package/dist/render/shaders/rush.js +54 -11
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/ssrResolve.d.ts +17 -0
- package/dist/render/shaders/ssrResolve.js +51 -0
- package/dist/render/shaders/ssrTrace.js +46 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -3
- package/dist/render/skinScatter.d.ts +3 -1
- package/dist/render/skinScatter.js +3 -1
- package/dist/render/ssrPass.d.ts +16 -0
- package/dist/render/ssrPass.js +107 -1
- package/dist/render/surfaceModel.d.ts +29 -8
- package/dist/render/surfaceModel.js +33 -9
- package/dist/render/surfaceOverlay.d.ts +132 -0
- package/dist/render/surfaceOverlay.js +85 -0
- package/dist/render/surfaceTexture.d.ts +44 -1
- package/dist/render/surfaceTexture.js +96 -5
- package/dist/render/temporalAa.d.ts +14 -0
- package/dist/render/temporalAa.js +20 -0
- package/dist/render/vertexDefaults.d.ts +17 -0
- package/dist/render/vertexDefaults.js +24 -0
- package/package.json +6 -6
- package/src/index.ts +22 -2
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +58 -0
- package/src/render/atmosphere.ts +22 -1
- package/src/render/backend/api.ts +58 -2
- package/src/render/backend/createRenderer.ts +18 -1
- package/src/render/backend/webgl2/renderer.ts +548 -50
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- package/src/render/backend/webgpu/device.ts +42 -3
- package/src/render/backend/webgpu/flatPass.ts +63 -26
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/postPass.ts +18 -0
- package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
- package/src/render/backend/webgpu/renderer.ts +927 -100
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +44 -14
- package/src/render/clusteredLights.ts +41 -8
- package/src/render/displayRange.ts +61 -0
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/filmicCurve.ts +229 -0
- package/src/render/fog.ts +19 -3
- package/src/render/frame/resources.ts +6 -0
- package/src/render/frameReflections.ts +72 -0
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +4 -0
- package/src/render/instances.ts +26 -6
- package/src/render/lightBudget.ts +40 -2
- package/src/render/lightmap.ts +64 -23
- package/src/render/particlePool.ts +23 -0
- package/src/render/pass.ts +53 -1
- package/src/render/pointLightSelection.ts +20 -1
- package/src/render/reflectionSurfaceTargets.ts +105 -0
- package/src/render/renderQuality.ts +52 -15
- package/src/render/rendererHarness.ts +3 -0
- package/src/render/rgb9e5.ts +52 -0
- package/src/render/sceneCapture.ts +56 -0
- package/src/render/sceneTarget.ts +30 -2
- package/src/render/screenSpaceReflection.ts +38 -1
- package/src/render/shaders/bolt.ts +7 -1
- package/src/render/shaders/depth.ts +11 -0
- package/src/render/shaders/filmLook.ts +2 -1
- package/src/render/shaders/flat/anisotropic.ts +37 -3
- package/src/render/shaders/flat/areaLight.ts +46 -0
- package/src/render/shaders/flat/glassTint.ts +13 -2
- package/src/render/shaders/flat/index.ts +37 -2
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +85 -22
- package/src/render/shaders/flat/models.ts +100 -16
- package/src/render/shaders/flat/overlay.ts +117 -0
- package/src/render/shaders/flat/pointShadow.ts +1 -1
- package/src/render/shaders/flat/preamble.ts +10 -3
- package/src/render/shaders/flat/reflectionSurface.ts +61 -0
- package/src/render/shaders/fog.ts +9 -2
- package/src/render/shaders/fringe.ts +59 -0
- package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +4 -4
- package/src/render/shaders/generated/film.wgsl.ts +1 -1
- package/src/render/shaders/generated/fire.wgsl.ts +1 -1
- package/src/render/shaders/generated/flat.wgsl.ts +230 -54
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +4 -4
- package/src/render/shaders/generated/rush.wgsl.ts +56 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
- package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
- package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +32 -7
- package/src/render/shaders/generated/water.wgsl.ts +23 -16
- package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
- package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/rush.ts +54 -11
- package/src/render/shaders/ssrResolve.ts +52 -0
- package/src/render/shaders/ssrTrace.ts +46 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +139 -1
- package/src/render/surfaceModel.ts +47 -10
- package/src/render/surfaceOverlay.ts +195 -0
- package/src/render/surfaceTexture.ts +136 -9
- package/src/render/temporalAa.ts +20 -0
- package/src/render/vertexDefaults.ts +28 -0
|
@@ -25,6 +25,10 @@ export const MAIN_GLSL = `void main() {
|
|
|
25
25
|
vec2 fxDx = dFdx(surfaceAt.xy);
|
|
26
26
|
vec2 fxDy = dFdy(surfaceAt.xy);
|
|
27
27
|
if (uWriteMode.y != 0.0 && !ditherKeeps(uWriteMode.y)) discard;
|
|
28
|
+
/* A draw's dissolve cuts the surface away before anything is shaded: overlay.ts. */
|
|
29
|
+
if (SURFACE_OVERLAY) {
|
|
30
|
+
if (overlayCuts(surfaceAt.xy)) discard;
|
|
31
|
+
}
|
|
28
32
|
|
|
29
33
|
/*
|
|
30
34
|
* Surface colour and its coverage, resolved before anything else — including before it is
|
|
@@ -139,6 +143,11 @@ export const MAIN_GLSL = `void main() {
|
|
|
139
143
|
}
|
|
140
144
|
|
|
141
145
|
vec3 lit = albedo;
|
|
146
|
+
/* How much of the environment the frame shows here, and how a found reflection would land:
|
|
147
|
+
reflectionSurface.ts. None where nothing is reflected. */
|
|
148
|
+
vec3 reflectedProbe = vec3(0.0);
|
|
149
|
+
vec3 reflectedTint = vec3(0.0);
|
|
150
|
+
float reflectedRoughness = 1.0;
|
|
142
151
|
/* Skin's diffuse, held before emission for the screen-space blur's own pass: models.ts. */
|
|
143
152
|
vec3 diffuseAlone = vec3(0.0);
|
|
144
153
|
/* The light arriving at a glass pane from behind it, gathered in each light loop below and added
|
|
@@ -186,7 +195,10 @@ export const MAIN_GLSL = `void main() {
|
|
|
186
195
|
* right: a mipped colour image, sampled under uniform control flow, exactly as uAlbedo is
|
|
187
196
|
* below. The rule is about non-uniform branches, and this branch is on a uniform.
|
|
188
197
|
*/
|
|
198
|
+
vec3 unmapped = n;
|
|
189
199
|
if (uNormalStrength > 0.0) n = normalMapped(n, surfaceAt);
|
|
200
|
+
/* A draw's wrinkles, by region, over whatever the map made: overlay.ts. */
|
|
201
|
+
if (SURFACE_OVERLAY) n = overlayWrinkle(n, unmapped, surfaceAt.xy);
|
|
190
202
|
|
|
191
203
|
/*
|
|
192
204
|
* Microscopic relief: the surface's own texture, as a turn of the normal rather than a change
|
|
@@ -542,7 +554,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
542
554
|
* specularLobe's header argues for at length.
|
|
543
555
|
*/
|
|
544
556
|
float sunVoH = max(dot(normalize(uCameraPos - vWorldPos), halfway), 0.0);
|
|
545
|
-
vec3 sunSpec =
|
|
557
|
+
vec3 sunSpec = highlightTint(specColor, sunVoH, metal);
|
|
546
558
|
/*
|
|
547
559
|
* **Held back until after the environment blend, because that blend is a mix and would
|
|
548
560
|
* otherwise erase this.**
|
|
@@ -561,8 +573,12 @@ export const MAIN_GLSL = `void main() {
|
|
|
561
573
|
* Zero for the whole world that carries no specular attribute and no metalness, exactly as
|
|
562
574
|
* before: \`sunSpec\` is \`vec3(vSpecular)\` at metal 0 and \`vSpecular\` defaults to 0.
|
|
563
575
|
*/
|
|
564
|
-
|
|
565
|
-
|
|
576
|
+
/* GGX's own where the surface asks (\`physicalSpecular\`): the sun is a point, so nothing widens it. */
|
|
577
|
+
float sunNdh = max(dot(n, halfway), 0.0);
|
|
578
|
+
float sunLobe = physicalSpecular()
|
|
579
|
+
? physicalLobe(sunNdh, ndl, dot(n, normalize(uCameraPos - vWorldPos)), surfaceRoughness, 0.0, 1.0)
|
|
580
|
+
: specularLobe(sunNdh, surfaceRoughness);
|
|
581
|
+
vec3 sunHighlight = sunColor * sunLobe * sunSpec * sunShade * sunFacing;
|
|
566
582
|
if (modelled) sunHighlight = sunModelSpecular;
|
|
567
583
|
/*
|
|
568
584
|
* The dielectric's share goes in here, where it has always gone, so the environment blend
|
|
@@ -911,6 +927,15 @@ export const MAIN_GLSL = `void main() {
|
|
|
911
927
|
environment * mix(vec3(1.0), albedo, metal),
|
|
912
928
|
weight
|
|
913
929
|
);
|
|
930
|
+
reflectedTint = mix(vec3(1.0), albedo, metal) * weight;
|
|
931
|
+
reflectedProbe = environment * reflectedTint;
|
|
932
|
+
reflectedRoughness = surfaceRoughness;
|
|
933
|
+
}
|
|
934
|
+
/* The reflection pass's surface half leaves here, before the lamps: reflectionSurface.ts. */
|
|
935
|
+
if (REFLECTION_SURFACE) {
|
|
936
|
+
reflectionSurfaceOut(reflectedProbe, reflectedTint, reflectedRoughness, ormOcclusion,
|
|
937
|
+
reflectionAir());
|
|
938
|
+
return;
|
|
914
939
|
}
|
|
915
940
|
/*
|
|
916
941
|
* The sun's highlight, on top of whatever the surface reflects rather than underneath it —
|
|
@@ -1066,6 +1091,10 @@ export const MAIN_GLSL = `void main() {
|
|
|
1066
1091
|
float cookie;
|
|
1067
1092
|
/* The light's lighting channels, a mask: 1 unless it names others. See \`uLightIesAxis\`. */
|
|
1068
1093
|
float lightChannels = 1.0;
|
|
1094
|
+
/* The light's own falloff exponent, or 0 for the frame's falloff. See \`uLightCone\`. */
|
|
1095
|
+
float lightExponent = 0.0;
|
|
1096
|
+
/* A clustered rectangle's barn doors: their cosine and length, none at 0. See \`areaBarnDoors\`. */
|
|
1097
|
+
vec2 areaDoors = vec2(0.0);
|
|
1069
1098
|
/*
|
|
1070
1099
|
* **A rectangle in the froxel table** — one past the fixed arm's \`maxAreaLights\` — which
|
|
1071
1100
|
* its record says with a negative radius, its reach negated. Its fields come out of the
|
|
@@ -1116,8 +1145,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
1116
1145
|
lightWeight = sizeAndWeight.y;
|
|
1117
1146
|
/*
|
|
1118
1147
|
* **The fixture, read only for a light that has one.** What a plain light's record holds in
|
|
1119
|
-
* the last
|
|
1120
|
-
* profile or
|
|
1148
|
+
* the last four texels is exactly these values, so a light carrying no shadow slot, cone,
|
|
1149
|
+
* profile, cookie, channels or falloff of its own shades the same whether they are read or assumed, and at heavy overlap
|
|
1121
1150
|
* reading them was a third of the pass. A branch on a value from a texture around
|
|
1122
1151
|
* \`texelFetch\` is not the 2026-08-07 case: an integer fetch takes no derivative.
|
|
1123
1152
|
*
|
|
@@ -1137,6 +1166,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
1137
1166
|
uvec4 rec2 = clusterTexel(record + 2);
|
|
1138
1167
|
uvec4 rec3 = clusterTexel(record + 3);
|
|
1139
1168
|
uvec4 rec4 = clusterTexel(record + 4);
|
|
1169
|
+
uvec4 rec5 = clusterTexel(record + 5);
|
|
1140
1170
|
shadowSlot = clusteredArea ? -1 : int(uintBitsToFloat(rec2.x));
|
|
1141
1171
|
areaTwoSided = uintBitsToFloat(rec2.x);
|
|
1142
1172
|
lightDir = vec3(
|
|
@@ -1149,6 +1179,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
1149
1179
|
iesAxis = vec3(
|
|
1150
1180
|
uintBitsToFloat(rec4.x), uintBitsToFloat(rec4.y), uintBitsToFloat(rec4.z));
|
|
1151
1181
|
lightChannels = uintBitsToFloat(rec4.w);
|
|
1182
|
+
lightExponent = uintBitsToFloat(rec5.x);
|
|
1183
|
+
areaDoors = vec2(uintBitsToFloat(rec5.y), uintBitsToFloat(rec5.z));
|
|
1152
1184
|
}
|
|
1153
1185
|
}
|
|
1154
1186
|
} else {
|
|
@@ -1161,7 +1193,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
1161
1193
|
lightWeight = uLightWeight[i];
|
|
1162
1194
|
shadowSlot = i;
|
|
1163
1195
|
lightDir = uLightDir[i];
|
|
1164
|
-
lightCone = uLightCone[i];
|
|
1196
|
+
lightCone = uLightCone[i].xy;
|
|
1197
|
+
lightExponent = uLightCone[i].z;
|
|
1165
1198
|
iesProfile = uLightIesProfile[i];
|
|
1166
1199
|
iesAxis = uLightIesAxis[i].xyz;
|
|
1167
1200
|
lightChannels = uLightIesAxis[i].w;
|
|
@@ -1187,12 +1220,15 @@ export const MAIN_GLSL = `void main() {
|
|
|
1187
1220
|
* froxel it is binned into has no edge in the picture.
|
|
1188
1221
|
*/
|
|
1189
1222
|
if (clusteredArea) {
|
|
1190
|
-
|
|
1223
|
+
vec3 seenCentre = lightPos;
|
|
1224
|
+
vec2 seenHalf = lightCone;
|
|
1225
|
+
if (!areaBarnDoors(seenCentre, lightDir, iesAxis, seenHalf, areaDoors)) continue;
|
|
1226
|
+
float areaForm = areaSignedForm(n, seenCentre, lightDir, iesAxis, seenHalf);
|
|
1191
1227
|
float areaWindow = clamp(1.0 - pow(length(lightPos - vWorldPos) / max(lightRadius, 1e-4), 4.0), 0.0, 1.0);
|
|
1192
1228
|
areaForm = mix(max(0.0, areaForm), abs(areaForm), areaTwoSided);
|
|
1193
1229
|
if (areaForm > 0.0) {
|
|
1194
1230
|
areaLightAdd(
|
|
1195
|
-
|
|
1231
|
+
seenCentre, lightDir, iesAxis, seenHalf, lightColor * (areaWindow * areaWindow), areaForm,
|
|
1196
1232
|
1.0, vec3(1.0), n, albedo, metal, specColor, surfaceRoughness, modelled,
|
|
1197
1233
|
lampOpen, lampShadowed
|
|
1198
1234
|
);
|
|
@@ -1243,7 +1279,16 @@ export const MAIN_GLSL = `void main() {
|
|
|
1243
1279
|
* same way three.js windows its own, so the light still ends where it says it does.
|
|
1244
1280
|
*/
|
|
1245
1281
|
float falloff;
|
|
1246
|
-
|
|
1282
|
+
/*
|
|
1283
|
+
* **A light may name its own falloff, and then it is Unreal's for a light without
|
|
1284
|
+
* inverse-square falloff**: \`(1 - (d/R)^2)^n\` with no distance term, so a fill authored to
|
|
1285
|
+
* light a whole hall across a hundred metres does, where the inverse square would have it
|
|
1286
|
+
* gone a few metres out. Zero is the frame's own rule below, which every light was before.
|
|
1287
|
+
*/
|
|
1288
|
+
if (lightExponent > 0.0) {
|
|
1289
|
+
float reach = dist / max(lightRadius, 1e-4);
|
|
1290
|
+
falloff = pow(clamp(1.0 - reach * reach, 0.0, 1.0), lightExponent);
|
|
1291
|
+
} else if (uLightFalloff == 1) {
|
|
1247
1292
|
float window = clamp(1.0 - pow(dist / max(lightRadius, 1e-4), 4.0), 0.0, 1.0);
|
|
1248
1293
|
falloff = window * window / max(dist * dist, 0.01);
|
|
1249
1294
|
} else {
|
|
@@ -1506,7 +1551,11 @@ export const MAIN_GLSL = `void main() {
|
|
|
1506
1551
|
photometric = mix(1.0, iesGain, step(0.0, iesProfile));
|
|
1507
1552
|
}
|
|
1508
1553
|
|
|
1509
|
-
|
|
1554
|
+
/* A light's own exponent already is its whole curve; the frame's smooth rule squares its own. */
|
|
1555
|
+
float shape =
|
|
1556
|
+
(lightExponent > 0.0 || uLightFalloff == 1 ? falloff : falloff * falloff) *
|
|
1557
|
+
coneFalloff *
|
|
1558
|
+
photometric;
|
|
1510
1559
|
glassGlow += lightColor * backNdl * shape * lightWeight * shaded * lampGlass;
|
|
1511
1560
|
if (ndl <= 0.0 && !modelledBehind) continue;
|
|
1512
1561
|
/*
|
|
@@ -1583,16 +1632,15 @@ export const MAIN_GLSL = `void main() {
|
|
|
1583
1632
|
if (vSpecular > 0.0 || metal > 0.0) {
|
|
1584
1633
|
vec3 toEyeLamp = normalize(uCameraPos - vWorldPos);
|
|
1585
1634
|
vec3 lampHalfway = normalize(toLight / max(dist, 1e-4) + toEyeLamp);
|
|
1586
|
-
/* The same grazing whitening the sun's highlight takes, and zero at metal 0. */
|
|
1587
|
-
vec3 lampSpec =
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1635
|
+
/* The same grazing whitening the sun's highlight takes, and zero at metal 0 for the look. */
|
|
1636
|
+
vec3 lampSpec = highlightTint(specColor, max(dot(toEyeLamp, lampHalfway), 0.0), metal);
|
|
1637
|
+
/* The look's lobe, or GGX's own where the surface asks for it: see \`physicalLobe\`. */
|
|
1638
|
+
float lampNdh = max(dot(n, lampHalfway), 0.0);
|
|
1639
|
+
float lampLobe = physicalSpecular()
|
|
1640
|
+
? physicalLobe(lampNdh, ndl, dot(n, toEyeLamp), surfaceRoughness, lightSourceRadius, dist)
|
|
1641
|
+
: sphereLobe(lampNdh, surfaceRoughness, lightSourceRadius, dist);
|
|
1592
1642
|
/* Held back with the diffuse half, and shadowed by the same term it always was. */
|
|
1593
|
-
vec3 lampHighlight =
|
|
1594
|
-
lightColor * sphereLobe(max(dot(n, lampHalfway), 0.0), surfaceRoughness, lightSourceRadius, dist)
|
|
1595
|
-
* lampSpec * shape * lightWeight;
|
|
1643
|
+
vec3 lampHighlight = lightColor * lampLobe * lampSpec * shape * lightWeight;
|
|
1596
1644
|
lampOpen += lampHighlight;
|
|
1597
1645
|
lampShadowed += lampHighlight * shaded * lampGlass;
|
|
1598
1646
|
}
|
|
@@ -1636,10 +1684,18 @@ export const MAIN_GLSL = `void main() {
|
|
|
1636
1684
|
vec3 centre = uAreaLightPos[a];
|
|
1637
1685
|
vec3 right = uAreaLightRight[a];
|
|
1638
1686
|
vec3 up = uAreaLightUp[a];
|
|
1639
|
-
vec2 halfSize = uAreaLightSize[a];
|
|
1687
|
+
vec2 halfSize = uAreaLightSize[a].xy;
|
|
1688
|
+
/*
|
|
1689
|
+
* **The part of the rectangle this fragment sees past its barn doors** (\`areaBarnDoors\`),
|
|
1690
|
+
* which its light and highlight come from. Its shadow keeps the whole rectangle's centre and
|
|
1691
|
+
* size below, because that is what its map was baked from.
|
|
1692
|
+
*/
|
|
1693
|
+
vec3 seenCentre = centre;
|
|
1694
|
+
vec2 seenHalf = halfSize;
|
|
1695
|
+
if (!areaBarnDoors(seenCentre, right, up, seenHalf, uAreaLightSize[a].zw)) continue;
|
|
1640
1696
|
|
|
1641
1697
|
/* The form factor and its sign, which is the sidedness: see \`areaSignedForm\`. */
|
|
1642
|
-
float signedForm = areaSignedForm(n,
|
|
1698
|
+
float signedForm = areaSignedForm(n, seenCentre, right, up, seenHalf);
|
|
1643
1699
|
float form = mix(max(0.0, signedForm), abs(signedForm), uAreaLightTwoSided[a]);
|
|
1644
1700
|
/* The form factor is linear in the normal, so the one seen from behind is its negation. */
|
|
1645
1701
|
float backForm = glassTransmission > 0.0
|
|
@@ -1756,7 +1812,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
1756
1812
|
if (form <= 0.0) continue;
|
|
1757
1813
|
/* The diffuse, the model's answer or the lobe integrated over the rectangle: see areaLight.ts. */
|
|
1758
1814
|
areaLightAdd(
|
|
1759
|
-
|
|
1815
|
+
seenCentre, right, up, seenHalf, uAreaLightColor[a], form, areaOccl, areaGlass,
|
|
1760
1816
|
n, albedo, metal, specColor, surfaceRoughness, modelled, lampOpen, lampShadowed
|
|
1761
1817
|
);
|
|
1762
1818
|
}
|
|
@@ -1904,6 +1960,9 @@ export const MAIN_GLSL = `void main() {
|
|
|
1904
1960
|
* mix(1.0, min(lightShade, sunShade), EMISSIVE_SHADOW_SHARE) * fxGlow;
|
|
1905
1961
|
/* And a lit window's glow, which the scene's lit share already times: not gated on night. */
|
|
1906
1962
|
lit += windowGlow * uEmission.x;
|
|
1963
|
+
/* And a draw's overlay: its rim, its dissolve's band and the colour laid over. Not gated on the
|
|
1964
|
+
night, as emission is: a glow the surface wears rather than light it gives off. overlay.ts. */
|
|
1965
|
+
if (SURFACE_OVERLAY) lit += overlayGlow(surfaceAt.xy, n);
|
|
1907
1966
|
|
|
1908
1967
|
#if NIGHT_EMISSIVE
|
|
1909
1968
|
/*
|
|
@@ -2102,6 +2161,10 @@ export const MAIN_GLSL = `void main() {
|
|
|
2102
2161
|
return;
|
|
2103
2162
|
}
|
|
2104
2163
|
|
|
2164
|
+
/* WebGL2's frame writes the reflection maps itself, with the fog already reckoned above. */
|
|
2165
|
+
if (REFLECTION_MAPS) {
|
|
2166
|
+
reflectionSurfaceOut(reflectedProbe, reflectedTint, reflectedRoughness, ormOcclusion, fog);
|
|
2167
|
+
}
|
|
2105
2168
|
outColor = vec4(shaded, alpha);
|
|
2106
2169
|
}
|
|
2107
2170
|
`;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { SurfaceModelKind } from '../../surfaceModel.ts';
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The models' whole chunk, at depth zero before `main`: see `flatFrag`. `physicalSpecular` is the
|
|
4
|
+
* \`PHYSICAL_SPECULAR\` lit switch's value, written in as \`litSwitchesGlsl\` writes the others.
|
|
5
|
+
*/
|
|
6
|
+
export declare function modelsGlsl(physicalSpecular: boolean): string;
|
|
4
7
|
/**
|
|
5
8
|
* Which half of a skin a lit pipeline draws under `skinScattering: 'screen-space'`: the whole
|
|
6
9
|
* surface (every other pipeline), the frame's half without its diffuse, the diffuse's light alone
|
|
@@ -46,6 +46,12 @@ const bool SKIN_ALBEDO = false; // wgsl:override
|
|
|
46
46
|
* Declared after skin's halves so that every switch before it keeps the id it shipped with.
|
|
47
47
|
*/
|
|
48
48
|
const bool MODEL_LIGHTMAP = false; // wgsl:override
|
|
49
|
+
/*
|
|
50
|
+
* Not a model either: whether this program carries GGX's own highlight for a material that asks
|
|
51
|
+
* (\`SurfaceMaterial.physicalSpecular\`). A lit switch, off until a material first asks and then
|
|
52
|
+
* on for good, as \`litSwitchesGlsl\`'s are; declared last so every switch before it keeps its id.
|
|
53
|
+
*/
|
|
54
|
+
const bool PHYSICAL_SPECULAR = PHYSICAL_SPECULAR_ON; // wgsl:override
|
|
49
55
|
|
|
50
56
|
/*
|
|
51
57
|
* A model's numbers, two vectors a material — the lit stage's uniform budget has no more room
|
|
@@ -124,15 +130,57 @@ float smithMasking(float ndl, float ndv, float alpha) {
|
|
|
124
130
|
);
|
|
125
131
|
}
|
|
126
132
|
|
|
133
|
+
/*
|
|
134
|
+
* Whether this surface's highlights are GGX's own (\`SurfaceMaterial.physicalSpecular\`), from the
|
|
135
|
+
* lane the standard, lightmap and anisotropic models leave free (\`packModel\`). Skin, hair and the
|
|
136
|
+
* eye shade their own highlights, and the eye's axis holds that lane, so they are never asked. The
|
|
137
|
+
* lit switch first, so a program built before any material asked carries none of it.
|
|
138
|
+
*/
|
|
139
|
+
bool physicalSpecular() {
|
|
140
|
+
if (PHYSICAL_SPECULAR) {
|
|
141
|
+
if (MODEL_HAIR) return false;
|
|
142
|
+
if (MODEL_SKIN) return false;
|
|
143
|
+
if (MODEL_EYE) return false;
|
|
144
|
+
return uModelParams[1].z > 0.5;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* GGX as a BRDF in this engine's light units, \`π · D · Vis · N·L\` with Fresnel left to the caller:
|
|
151
|
+
* the term skin's and the eye's highlights are, for a surface that asks for it. Its peak is
|
|
152
|
+
* \`1 / (4 α²)\` of the light head-on, against the look's 1. A lamp's size widens α as \`sphereLobe\`
|
|
153
|
+
* widens it, and a normalised distribution keeps its energy as it widens.
|
|
154
|
+
*/
|
|
155
|
+
float physicalLobe(float ndh, float ndl, float ndv, float roughness, float sourceRadius, float dist) {
|
|
156
|
+
float grow = sourceRadius / max(2.0 * dist, 1e-3);
|
|
157
|
+
float alpha = min(max(roughness * roughness, MIN_LOBE_ALPHA) + grow, 1.0);
|
|
158
|
+
return 3.14159265 * ggxLobe(ndh, alpha) * smithMasking(ndl, max(ndv, 1e-4), alpha) * ndl;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* A highlight's colour: Schlick's Fresnel from the surface's reflectance toward white at a grazing
|
|
163
|
+
* \`voh\`. On a metal alone for the look, whose dielectric attribute is a strength rather than a
|
|
164
|
+
* reflectance; on every surface where the highlight is physical, whose attribute is its F0 — but
|
|
165
|
+
* one reflecting nothing head-on keeps nothing, as the lamps' gate on that attribute has it.
|
|
166
|
+
*/
|
|
167
|
+
vec3 highlightTint(vec3 specColor, float voh, float metal) {
|
|
168
|
+
float edge = pow(1.0 - voh, 5.0);
|
|
169
|
+
bool reflects = max(max(specColor.r, specColor.g), specColor.b) > 0.0;
|
|
170
|
+
return mix(specColor, vec3(1.0), physicalSpecular() && reflects ? edge : edge * metal);
|
|
171
|
+
}
|
|
172
|
+
|
|
127
173
|
/** The standard model's answer to one light, which a model falls back to for what it does not change. */
|
|
128
174
|
void standardLight(vec3 l, float sourceRadius, float dist) {
|
|
129
175
|
float ndl = max(dot(mNormal, l), 0.0);
|
|
130
176
|
vec3 h = normalize(l + mToEye);
|
|
131
177
|
float ndh = max(dot(mNormal, h), 0.0);
|
|
132
|
-
float lobe =
|
|
133
|
-
?
|
|
134
|
-
:
|
|
135
|
-
|
|
178
|
+
float lobe = physicalSpecular()
|
|
179
|
+
? physicalLobe(ndh, ndl, dot(mNormal, mToEye), mRoughness, sourceRadius, dist)
|
|
180
|
+
: sourceRadius > 0.0
|
|
181
|
+
? sphereLobe(ndh, mRoughness, sourceRadius, dist)
|
|
182
|
+
: specularLobe(ndh, mRoughness);
|
|
183
|
+
vec3 tint = highlightTint(mSpecColor, max(dot(mToEye, h), 0.0), mMetal);
|
|
136
184
|
mDiffuse = mAlbedo * ndl * (1.0 - mMetal);
|
|
137
185
|
mSpecular = ndl > 0.0 ? tint * lobe : vec3(0.0);
|
|
138
186
|
}
|
|
@@ -250,20 +298,51 @@ vec3 modelEnvironment(vec3 environment) {
|
|
|
250
298
|
return environment;
|
|
251
299
|
}
|
|
252
300
|
|
|
301
|
+
/**
|
|
302
|
+
* One texel of a page's irradiance layer: its four bytes put back into the rgb9e5 word they are,
|
|
303
|
+
* lowest first, and decoded — three 9-bit mantissas over the exponent in the top five bits.
|
|
304
|
+
*/
|
|
305
|
+
vec3 bakedIrradianceAt(ivec3 texel) {
|
|
306
|
+
uvec4 bytes = uvec4(texelFetch(uModelMap, texel, 0) * 255.0 + 0.5);
|
|
307
|
+
uint word = bytes.x | (bytes.y << 8u) | (bytes.z << 16u) | (bytes.w << 24u);
|
|
308
|
+
float scale = exp2(float(word >> 27u) - 24.0);
|
|
309
|
+
return vec3(float(word & 511u), float((word >> 9u) & 511u), float((word >> 18u) & 511u)) * scale;
|
|
310
|
+
}
|
|
311
|
+
|
|
253
312
|
/**
|
|
254
313
|
* What a baked page adds to this surface's diffuse: nothing in any pipeline but a lightmapped one,
|
|
255
314
|
* nor in one whose page is missing. The page's two layers at the surface's second coordinates, which
|
|
256
315
|
* ride the grain and relief lanes as \`-1 - uv\` (lightmap.ts) and are placed by the material's
|
|
257
316
|
* region: the irradiance, and the first-order harmonic its direction is, answered by the shading
|
|
258
317
|
* normal.
|
|
259
|
-
*
|
|
318
|
+
*
|
|
319
|
+
* **Filtered here, because the page cannot be filtered as it is stored**: the irradiance is a
|
|
320
|
+
* shared-exponent word in four bytes, and a blend of two words is not the blend of their colours.
|
|
321
|
+
* So the four texels around the sample are fetched, decoded and blended, as a bilinear sampler
|
|
322
|
+
* clamped at the page's edge would. Fetches need no derivative, so the branch costs nothing there.
|
|
260
323
|
*/
|
|
261
324
|
vec3 modelBaked(vec3 n, vec3 albedo, float metal) {
|
|
262
325
|
if (MODEL_LIGHTMAP) {
|
|
263
326
|
if (uModelParams[1].w <= 0.0) return vec3(0.0);
|
|
264
327
|
vec2 at = (vec2(-1.0) - vec2(vGrain, vRelief)) * uModelParams[0].xy + uModelParams[0].zw;
|
|
265
|
-
|
|
266
|
-
|
|
328
|
+
ivec2 size = textureSize(uModelMap, 0).xy;
|
|
329
|
+
vec2 texel = at * vec2(size) - 0.5;
|
|
330
|
+
vec2 corner = floor(texel);
|
|
331
|
+
vec2 f = texel - corner;
|
|
332
|
+
ivec2 last = size - 1;
|
|
333
|
+
ivec2 lo = clamp(ivec2(corner), ivec2(0), last);
|
|
334
|
+
ivec2 hi = clamp(ivec2(corner) + 1, ivec2(0), last);
|
|
335
|
+
vec3 irradiance = mix(
|
|
336
|
+
mix(bakedIrradianceAt(ivec3(lo.x, lo.y, 0)), bakedIrradianceAt(ivec3(hi.x, lo.y, 0)), f.x),
|
|
337
|
+
mix(bakedIrradianceAt(ivec3(lo.x, hi.y, 0)), bakedIrradianceAt(ivec3(hi.x, hi.y, 0)), f.x),
|
|
338
|
+
f.y
|
|
339
|
+
);
|
|
340
|
+
vec4 direction = mix(
|
|
341
|
+
mix(texelFetch(uModelMap, ivec3(lo.x, lo.y, 1), 0), texelFetch(uModelMap, ivec3(hi.x, lo.y, 1), 0), f.x),
|
|
342
|
+
mix(texelFetch(uModelMap, ivec3(lo.x, hi.y, 1), 0), texelFetch(uModelMap, ivec3(hi.x, hi.y, 1), 0), f.x),
|
|
343
|
+
f.y
|
|
344
|
+
);
|
|
345
|
+
vec4 d = direction * 2.0 - 1.0;
|
|
267
346
|
return albedo * (1.0 - metal) * irradiance * max(dot(d.xyz, n) + d.w, 0.0);
|
|
268
347
|
}
|
|
269
348
|
return vec3(0.0);
|
|
@@ -285,15 +364,20 @@ float modelReflectance(float standard) {
|
|
|
285
364
|
return standard;
|
|
286
365
|
}
|
|
287
366
|
`;
|
|
288
|
-
/**
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
367
|
+
/**
|
|
368
|
+
* The models' whole chunk, at depth zero before `main`: see `flatFrag`. `physicalSpecular` is the
|
|
369
|
+
* \`PHYSICAL_SPECULAR\` lit switch's value, written in as \`litSwitchesGlsl\` writes the others.
|
|
370
|
+
*/
|
|
371
|
+
export function modelsGlsl(physicalSpecular) {
|
|
372
|
+
return [
|
|
373
|
+
MODELS_HEAD.replace('PHYSICAL_SPECULAR_ON', physicalSpecular ? 'true' : 'false'),
|
|
374
|
+
ANISOTROPIC_GLSL,
|
|
375
|
+
HAIR_GLSL,
|
|
376
|
+
SKIN_GLSL,
|
|
377
|
+
EYE_GLSL,
|
|
378
|
+
MODELS_DISPATCH,
|
|
379
|
+
].join('\n');
|
|
380
|
+
}
|
|
297
381
|
/** The constant each half of a skin turns on. See `SkinHalf`. */
|
|
298
382
|
export const SKIN_HALF_SWITCH = {
|
|
299
383
|
scene: 'SKIN_SCREEN',
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A draw's surface overlay in the lit stage: the rim, the dissolve and the wrinkles
|
|
3
|
+
* `surfaceOverlay.ts` packs into `uOverlay`. Called from `main` at three places — the dissolve's cut
|
|
4
|
+
* before anything is shaded, the wrinkles after the normal map, and the glow beside emission.
|
|
5
|
+
*
|
|
6
|
+
* **A lit switch, `SURFACE_OVERLAY`**, off until a draw first sets an overlay and then on for good,
|
|
7
|
+
* as `litSwitchesGlsl`'s are; declared after the models so every switch before it keeps its id.
|
|
8
|
+
* Compiled out, the three calls are stubs and the fifteen vectors are not declared at all, which is
|
|
9
|
+
* what lets WebGL2 build a lit program without them on a device that has no room.
|
|
10
|
+
*
|
|
11
|
+
* **Images come from the atlas bound where the frame's refraction copy goes**, which an opaque draw
|
|
12
|
+
* never reads; a draw that refracts or is glass reads that copy there, so its overlay reads no image
|
|
13
|
+
* (`overlayImagesBound`). Each image is read by `textureLod` at level 0: several are reached under a
|
|
14
|
+
* branch on a per-pixel value, and the atlas carries no chain anyway.
|
|
15
|
+
*/
|
|
16
|
+
export declare function overlayGlsl(on: boolean): string;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A draw's surface overlay in the lit stage: the rim, the dissolve and the wrinkles
|
|
3
|
+
* `surfaceOverlay.ts` packs into `uOverlay`. Called from `main` at three places — the dissolve's cut
|
|
4
|
+
* before anything is shaded, the wrinkles after the normal map, and the glow beside emission.
|
|
5
|
+
*
|
|
6
|
+
* **A lit switch, `SURFACE_OVERLAY`**, off until a draw first sets an overlay and then on for good,
|
|
7
|
+
* as `litSwitchesGlsl`'s are; declared after the models so every switch before it keeps its id.
|
|
8
|
+
* Compiled out, the three calls are stubs and the fifteen vectors are not declared at all, which is
|
|
9
|
+
* what lets WebGL2 build a lit program without them on a device that has no room.
|
|
10
|
+
*
|
|
11
|
+
* **Images come from the atlas bound where the frame's refraction copy goes**, which an opaque draw
|
|
12
|
+
* never reads; a draw that refracts or is glass reads that copy there, so its overlay reads no image
|
|
13
|
+
* (`overlayImagesBound`). Each image is read by `textureLod` at level 0: several are reached under a
|
|
14
|
+
* branch on a per-pixel value, and the atlas carries no chain anyway.
|
|
15
|
+
*/
|
|
16
|
+
export function overlayGlsl(on) {
|
|
17
|
+
if (!on) {
|
|
18
|
+
return /* glsl */ `
|
|
19
|
+
const bool SURFACE_OVERLAY = false; // wgsl:override
|
|
20
|
+
bool overlayCuts(vec2 uv) { return false; }
|
|
21
|
+
vec3 overlayWrinkle(vec3 mapped, vec3 unmapped, vec2 uv) { return mapped; }
|
|
22
|
+
vec3 overlayGlow(vec2 uv, vec3 n) { return vec3(0.0); }
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
return /* glsl */ `
|
|
26
|
+
/* Whether this program carries a draw's overlay. Off but where one has been set: overlay.ts. */
|
|
27
|
+
const bool SURFACE_OVERLAY = true; // wgsl:override
|
|
28
|
+
/* The overlay's fifteen vectors; packSurfaceOverlay in surfaceOverlay.ts says what each holds. */
|
|
29
|
+
uniform vec4 uOverlay[15];
|
|
30
|
+
|
|
31
|
+
/* Whether the atlas is what the refraction slot holds: not where this draw refracts or is glass. */
|
|
32
|
+
bool overlayImagesBound() {
|
|
33
|
+
return uSeeThrough.x <= 0.0 && uSeeThrough.z <= 0.0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* One image of the atlas at uv, repeated within its region; zero where the region has no size. */
|
|
37
|
+
vec4 overlayImage(vec4 region, vec2 uv) {
|
|
38
|
+
if (region.x <= 0.0) return vec4(0.0);
|
|
39
|
+
return textureLod(uRefractScene, region.zw + fract(uv) * region.xy, 0.0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* The dissolve's noise at uv, and whether the draw dissolves at all: -1 where it does not. */
|
|
43
|
+
float overlayDissolveNoise(vec2 uv) {
|
|
44
|
+
if (uOverlay[6].x <= 0.0 || uOverlay[7].z <= 0.0) return -1.0;
|
|
45
|
+
if (!overlayImagesBound()) return -1.0;
|
|
46
|
+
return overlayImage(uOverlay[6], uv * uOverlay[7].xy).r;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Whether the dissolve cuts this fragment away: where its noise is under the threshold, or all of
|
|
50
|
+
it at a threshold of 1. */
|
|
51
|
+
bool overlayCuts(vec2 uv) {
|
|
52
|
+
float noise = overlayDissolveNoise(uv);
|
|
53
|
+
if (noise < 0.0) return false;
|
|
54
|
+
return noise < uOverlay[7].z || uOverlay[7].z >= 1.0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/*
|
|
58
|
+
* The wrinkle normal blended into the mapped one by the two masks' channels times the six weights,
|
|
59
|
+
* in the frame the normal map is read in. **Under uniform control flow up to the frame**, which
|
|
60
|
+
* takes derivatives: every branch before it is on the overlay's numbers alone, and the weight a
|
|
61
|
+
* pixel takes is a mix rather than a branch.
|
|
62
|
+
*/
|
|
63
|
+
vec3 overlayWrinkle(vec3 mapped, vec3 unmapped, vec2 uv) {
|
|
64
|
+
if (uOverlay[10].x <= 0.0 || !overlayImagesBound()) return mapped;
|
|
65
|
+
mat3 tbn = tangentFrame(unmapped, vWorldPos, vUv.xy, vTangent, vHasTangents);
|
|
66
|
+
vec3 a = overlayImage(uOverlay[11], uv).rgb;
|
|
67
|
+
vec3 b = overlayImage(uOverlay[12], uv).rgb;
|
|
68
|
+
float weight = clamp(
|
|
69
|
+
dot(a, uOverlay[13].xyz) + dot(b, vec3(uOverlay[13].w, uOverlay[14].xy)), 0.0, 1.0);
|
|
70
|
+
vec3 wrinkle = overlayImage(uOverlay[10], uv).xyz * 2.0 - 1.0;
|
|
71
|
+
/* z rebuilt where none is stored, as normalMapped does: see normalMap.ts. */
|
|
72
|
+
wrinkle.z = wrinkle.z > 0.0 ? wrinkle.z : sqrt(max(0.0, 1.0 - dot(wrinkle.xy, wrinkle.xy)));
|
|
73
|
+
return normalize(mix(mapped, normalize(tbn * wrinkle), weight));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* The rim's place on the screen, 0 to 1 up its height and its width in the same units: from view
|
|
78
|
+
* space and the projection's tangent, not from gl_FragCoord, whose y runs up on one backend and
|
|
79
|
+
* down on the other (the 2026-08-17 rule), and whose scale is a render target's, not the screen's.
|
|
80
|
+
*/
|
|
81
|
+
vec2 overlayScreen() {
|
|
82
|
+
vec4 eye = uView * vec4(vWorldPos, 1.0);
|
|
83
|
+
float depth = max(-eye.z, 1e-4) * uClusterFrustum.z;
|
|
84
|
+
return vec2(eye.x / depth + uClusterFrustum.w, eye.y / depth + 1.0) * 0.5;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* What the overlay adds to the light the surface gives off: the dissolve's band, the colour laid
|
|
88
|
+
over through its noise, and the rim. Zero where the draw sets none of them. */
|
|
89
|
+
vec3 overlayGlow(vec2 uv, vec3 n) {
|
|
90
|
+
vec3 glow = vec3(0.0);
|
|
91
|
+
float noise = overlayDissolveNoise(uv);
|
|
92
|
+
if (noise >= 0.0) {
|
|
93
|
+
float band = 1.0 - clamp((noise - uOverlay[7].z) / uOverlay[7].w, 0.0, 1.0);
|
|
94
|
+
glow += uOverlay[8].rgb * uOverlay[8].w * band + uOverlay[9].rgb * uOverlay[9].w * noise;
|
|
95
|
+
}
|
|
96
|
+
float strength = uOverlay[0].w;
|
|
97
|
+
if (strength > 0.0) {
|
|
98
|
+
vec3 toEye = normalize(uCameraPos - vWorldPos);
|
|
99
|
+
float edge = pow(1.0 - clamp(dot(n, toEye), 0.0, 1.0), max(uOverlay[1].x, 1e-4));
|
|
100
|
+
float up = pow(clamp(n.y * 0.5 + 0.5, 0.0, 1.0), max(uOverlay[1].y, 1e-4));
|
|
101
|
+
float rim = pow(edge * up, max(uOverlay[1].z, 1e-4));
|
|
102
|
+
float time = uSurfaceScene.x;
|
|
103
|
+
if (uOverlay[2].x > 0.0 && overlayImagesBound()) {
|
|
104
|
+
vec2 at = overlayScreen() * uOverlay[3].z + uOverlay[3].xy * time;
|
|
105
|
+
rim *= overlayImage(uOverlay[2], at).r;
|
|
106
|
+
}
|
|
107
|
+
rim = min(rim, 1.0);
|
|
108
|
+
rim *= mix(uOverlay[4].x, uOverlay[4].y, 0.5 + 0.5 * sin(uOverlay[3].w * time));
|
|
109
|
+
if (uOverlay[5].x > 0.0 && overlayImagesBound()) {
|
|
110
|
+
rim *= 1.0 - uOverlay[1].w * overlayImage(uOverlay[5], uv).r;
|
|
111
|
+
}
|
|
112
|
+
glow += uOverlay[0].rgb * strength * rim;
|
|
113
|
+
}
|
|
114
|
+
return glow;
|
|
115
|
+
}
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
@@ -771,13 +771,15 @@ uniform float uLightWeight[MAX_LIGHTS];
|
|
|
771
771
|
*/
|
|
772
772
|
uniform vec3 uLightDir[MAX_LIGHTS];
|
|
773
773
|
/**
|
|
774
|
-
* The cosine of each light's inner cone angle, then of its outer
|
|
774
|
+
* The cosine of each light's inner cone angle, then of its outer, and in z the light's own falloff
|
|
775
|
+
* exponent, 0 for the frame's falloff (\`PointLightSource.falloffExponent\`): a \`vec2\` array cost
|
|
776
|
+
* a whole row a light anyway, so the exponent spends no row of its own.
|
|
775
777
|
*
|
|
776
778
|
* A light with no cone carries -1 and -2, which makes \`smoothstep(outer, inner, dot)\` exactly 1
|
|
777
779
|
* for every direction on the sphere. See \`POINT_LIGHT_COS_OUTER\`: the pair is chosen so a point
|
|
778
780
|
* light collapses to the arithmetic it had rather than to something very close to it.
|
|
779
781
|
*/
|
|
780
|
-
uniform
|
|
782
|
+
uniform vec4 uLightCone[MAX_LIGHTS];
|
|
781
783
|
/**
|
|
782
784
|
* Which row of the photometric atlas each light uses, or a negative index for none.
|
|
783
785
|
*
|
|
@@ -882,7 +884,12 @@ uniform vec3 uAreaLightColor[MAX_AREA_LIGHTS];
|
|
|
882
884
|
/** The rectangle's in-plane axes, unit length, and its half extents along each. */
|
|
883
885
|
uniform vec3 uAreaLightRight[MAX_AREA_LIGHTS];
|
|
884
886
|
uniform vec3 uAreaLightUp[MAX_AREA_LIGHTS];
|
|
885
|
-
|
|
887
|
+
/*
|
|
888
|
+
* Each rectangle's half extents, and in zw its barn doors: the cosine of their angle from its normal
|
|
889
|
+
* and their length, 0 for none (\`areaBarnDoors\`). A \`vec2\` array costs a whole row a rectangle
|
|
890
|
+
* anyway, so the doors spend no row of their own.
|
|
891
|
+
*/
|
|
892
|
+
uniform vec4 uAreaLightSize[MAX_AREA_LIGHTS];
|
|
886
893
|
/**
|
|
887
894
|
* Whether the rectangle emits from both faces, 1 or 0.
|
|
888
895
|
*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the lit stage leaves for a reflection traced in the frame: how much of the environment each
|
|
3
|
+
* pixel shows, and how a reflection found in the frame would land there instead. Read by the
|
|
4
|
+
* reflection pass (`screenSpaceReflection.ts`), which swaps the one for the other where its ray
|
|
5
|
+
* finds something.
|
|
6
|
+
*
|
|
7
|
+
* **The swap is exact because the lit stage's own blend is a mix**: `lit = mix(lit, environment ·
|
|
8
|
+
* tint, weight)` leaves `environment · tint · weight` of the probe in the frame, and a reflection
|
|
9
|
+
* found in the frame belongs there with the same tint and the same weight. So the frame gains
|
|
10
|
+
* `(found · tint − environment · tint) · weight` and nothing else moves: the diffuse the blend
|
|
11
|
+
* already took its share from keeps it. Both are carried the rest of the way the shaded colour
|
|
12
|
+
* goes — the occlusion, the water and the medium in front of the surface — so the swap lands in the
|
|
13
|
+
* frame's own units.
|
|
14
|
+
*
|
|
15
|
+
* **Two outputs beside the colour, written by every lit draw and kept only where a target is
|
|
16
|
+
* bound.** WebGL2 binds them during opaque lit draws alone; WebGPU's frame pass binds none, and a
|
|
17
|
+
* second pass replays the frame's opaque draws into them with `REFLECTION_SURFACE` on, which leaves
|
|
18
|
+
* before the lamps (`main`). What it gives up is a vector of arithmetic on every lit pixel whether
|
|
19
|
+
* or not anything reads it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function reflectionSurfaceGlsl(maps: boolean): string;
|