@driftengine/core 4.8.2 → 4.8.4
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 +127 -0
- package/README.md +1 -1
- package/dist/geometry/meshConcat.js +2 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +22 -1
- package/dist/math/scalar.js +1 -1
- package/dist/render/anisotropicLobe.d.ts +7 -0
- package/dist/render/anisotropicLobe.js +33 -0
- package/dist/render/backend/api.d.ts +41 -4
- package/dist/render/backend/materialChanges.d.ts +7 -2
- package/dist/render/backend/materialChanges.js +8 -3
- package/dist/render/backend/select.js +8 -0
- package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
- package/dist/render/backend/webgl2/clothTextures.js +93 -0
- package/dist/render/backend/webgl2/renderer.d.ts +159 -3
- package/dist/render/backend/webgl2/renderer.js +662 -80
- package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
- package/dist/render/backend/webgl2/skinScatter.js +256 -0
- package/dist/render/backend/webgpu/buffers.d.ts +25 -11
- package/dist/render/backend/webgpu/buffers.js +98 -49
- package/dist/render/backend/webgpu/causticsPass.js +2 -1
- package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
- package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
- package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
- package/dist/render/backend/webgpu/clothTextures.js +145 -0
- package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
- package/dist/render/backend/webgpu/compressedUpload.js +38 -0
- package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
- package/dist/render/backend/webgpu/computeHost.js +5 -0
- package/dist/render/backend/webgpu/depthPass.d.ts +29 -9
- package/dist/render/backend/webgpu/depthPass.js +43 -12
- package/dist/render/backend/webgpu/drawCommand.js +6 -2
- package/dist/render/backend/webgpu/filmPass.js +2 -1
- package/dist/render/backend/webgpu/flatPass.d.ts +43 -4
- package/dist/render/backend/webgpu/flatPass.js +111 -13
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
- package/dist/render/backend/webgpu/linePass.js +2 -1
- package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
- package/dist/render/backend/webgpu/motionPass.js +106 -9
- package/dist/render/backend/webgpu/panelPass.js +2 -1
- package/dist/render/backend/webgpu/particlePass.js +2 -1
- package/dist/render/backend/webgpu/plumePass.js +2 -1
- package/dist/render/backend/webgpu/postPass.d.ts +15 -3
- package/dist/render/backend/webgpu/postPass.js +25 -8
- package/dist/render/backend/webgpu/renderer.d.ts +123 -6
- package/dist/render/backend/webgpu/renderer.js +534 -58
- package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
- package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
- package/dist/render/backend/webgpu/textPass.js +2 -1
- package/dist/render/backend/webgpu/waterPass.js +2 -1
- package/dist/render/backend/webgpu/windStreakPass.js +2 -1
- package/dist/render/boltBatch.js +2 -1
- package/dist/render/causticsRenderer.js +2 -1
- package/dist/render/clothBindingData.d.ts +41 -0
- package/dist/render/clothBindingData.js +72 -0
- package/dist/render/clothPlacement.d.ts +25 -0
- package/dist/render/clothPlacement.js +85 -0
- package/dist/render/compressedSource.d.ts +66 -0
- package/dist/render/compressedSource.js +138 -0
- package/dist/render/cutoutCaster.d.ts +5 -1
- package/dist/render/cutoutCaster.js +6 -0
- package/dist/render/cutoutDither.d.ts +51 -0
- package/dist/render/cutoutDither.js +90 -0
- package/dist/render/decalPass.js +2 -1
- package/dist/render/eyeRefraction.d.ts +35 -0
- package/dist/render/eyeRefraction.js +40 -0
- package/dist/render/glCompressed.d.ts +27 -0
- package/dist/render/glCompressed.js +53 -0
- package/dist/render/globalMediumPass.js +3 -2
- package/dist/render/hairLobes.d.ts +70 -0
- package/dist/render/hairLobes.js +120 -0
- package/dist/render/lightBudget.d.ts +22 -0
- package/dist/render/lightBudget.js +22 -0
- package/dist/render/lineBatch.js +5 -1
- package/dist/render/mesh.d.ts +7 -0
- package/dist/render/mesh.js +24 -0
- package/dist/render/oitPass.js +2 -1
- package/dist/render/particleBatch.js +3 -2
- package/dist/render/particlePool.d.ts +6 -2
- package/dist/render/plumeRenderer.js +3 -2
- package/dist/render/recon/motionVectors.d.ts +4 -2
- package/dist/render/recon/motionVectors.js +6 -3
- package/dist/render/renderQuality.d.ts +29 -2
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneCoverage.d.ts +54 -0
- package/dist/render/sceneCoverage.js +72 -0
- package/dist/render/sceneTarget.d.ts +12 -0
- package/dist/render/sceneTarget.js +14 -0
- package/dist/render/sdfTextRenderer.js +2 -1
- package/dist/render/shaders/clothBinding.d.ts +22 -0
- package/dist/render/shaders/clothBinding.js +80 -0
- package/dist/render/shaders/depth.js +45 -8
- package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
- package/dist/render/shaders/flat/anisotropic.js +68 -0
- package/dist/render/shaders/flat/eye.d.ts +1 -0
- package/dist/render/shaders/flat/eye.js +81 -0
- package/dist/render/shaders/flat/hair.d.ts +1 -0
- package/dist/render/shaders/flat/hair.js +101 -0
- package/dist/render/shaders/flat/index.js +42 -3
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +113 -17
- package/dist/render/shaders/flat/models.d.ts +14 -0
- package/dist/render/shaders/flat/models.js +273 -0
- package/dist/render/shaders/flat/preamble.js +26 -24
- package/dist/render/shaders/flat/skin.d.ts +1 -0
- package/dist/render/shaders/flat/skin.js +114 -0
- package/dist/render/shaders/generated/depth.wgsl.d.ts +66 -24
- package/dist/render/shaders/generated/depth.wgsl.js +66 -24
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1815 -1704
- package/dist/render/shaders/generated/flat.wgsl.js +1853 -1742
- 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 +3 -3
- package/dist/render/shaders/generated/particle.wgsl.js +3 -3
- package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/rush.wgsl.js +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
- package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +14 -1
- package/dist/render/shaders/outputTransform.js +23 -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 +102 -0
- package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
- package/dist/render/shaders/rush.js +13 -2
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/skinBlur.d.ts +1 -0
- package/dist/render/shaders/skinBlur.js +103 -0
- package/dist/render/shaders/skinning.d.ts +3 -1
- package/dist/render/shaders/skinning.js +31 -4
- package/dist/render/shaders/temporalResolve.js +12 -5
- package/dist/render/shadowCasters.d.ts +13 -1
- package/dist/render/skinBlur.d.ts +41 -0
- package/dist/render/skinBlur.js +49 -0
- package/dist/render/skinScatter.d.ts +67 -0
- package/dist/render/skinScatter.js +80 -0
- package/dist/render/skinnedClothRun.d.ts +26 -0
- package/dist/render/skinnedClothRun.js +84 -0
- package/dist/render/ssrPass.js +2 -1
- package/dist/render/surfaceModel.d.ts +129 -0
- package/dist/render/surfaceModel.js +152 -0
- package/dist/render/surfaceTexture.d.ts +55 -4
- package/dist/render/surfaceTexture.js +60 -46
- package/dist/render/textRenderer.js +2 -1
- package/dist/render/vertexDefaults.js +8 -0
- package/dist/render/waterRenderer.js +2 -1
- package/dist/render/windStreakRenderer.js +2 -1
- package/package.json +6 -6
- package/src/geometry/meshConcat.ts +2 -0
- package/src/index.ts +53 -1
- package/src/math/scalar.ts +1 -1
- package/src/render/anisotropicLobe.ts +41 -0
- package/src/render/backend/api.ts +52 -3
- package/src/render/backend/materialChanges.ts +8 -3
- package/src/render/backend/select.ts +8 -0
- package/src/render/backend/webgl2/clothTextures.ts +120 -0
- package/src/render/backend/webgl2/renderer.ts +750 -80
- package/src/render/backend/webgl2/skinScatter.ts +309 -0
- package/src/render/backend/webgpu/buffers.ts +137 -63
- package/src/render/backend/webgpu/causticsPass.ts +2 -1
- package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
- package/src/render/backend/webgpu/clothTextures.ts +194 -0
- package/src/render/backend/webgpu/compressedUpload.ts +65 -0
- package/src/render/backend/webgpu/computeHost.ts +25 -0
- package/src/render/backend/webgpu/depthPass.ts +43 -10
- package/src/render/backend/webgpu/drawCommand.ts +6 -2
- package/src/render/backend/webgpu/filmPass.ts +2 -1
- package/src/render/backend/webgpu/flatPass.ts +147 -8
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
- package/src/render/backend/webgpu/linePass.ts +2 -1
- package/src/render/backend/webgpu/motionPass.ts +128 -9
- package/src/render/backend/webgpu/panelPass.ts +2 -1
- package/src/render/backend/webgpu/particlePass.ts +2 -1
- package/src/render/backend/webgpu/plumePass.ts +2 -1
- package/src/render/backend/webgpu/postPass.ts +25 -6
- package/src/render/backend/webgpu/renderer.ts +701 -52
- package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
- package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
- package/src/render/backend/webgpu/textPass.ts +2 -1
- package/src/render/backend/webgpu/waterPass.ts +2 -1
- package/src/render/backend/webgpu/windStreakPass.ts +2 -1
- package/src/render/boltBatch.ts +2 -1
- package/src/render/causticsRenderer.ts +2 -1
- package/src/render/clothBindingData.ts +89 -0
- package/src/render/clothPlacement.ts +114 -0
- package/src/render/compressedSource.ts +198 -0
- package/src/render/cutoutCaster.ts +20 -2
- package/src/render/cutoutDither.ts +99 -0
- package/src/render/decalPass.ts +2 -1
- package/src/render/eyeRefraction.ts +72 -0
- package/src/render/glCompressed.ts +86 -0
- package/src/render/globalMediumPass.ts +3 -2
- package/src/render/hairLobes.ts +188 -0
- package/src/render/lightBudget.ts +25 -0
- package/src/render/lineBatch.ts +3 -1
- package/src/render/mesh.ts +23 -0
- package/src/render/oitPass.ts +2 -1
- package/src/render/particleBatch.ts +3 -2
- package/src/render/particlePool.ts +6 -2
- package/src/render/plumeRenderer.ts +3 -2
- package/src/render/recon/motionVectors.ts +6 -3
- package/src/render/renderQuality.ts +37 -2
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneCoverage.ts +78 -0
- package/src/render/sceneTarget.ts +21 -0
- package/src/render/sdfTextRenderer.ts +2 -1
- package/src/render/shaders/clothBinding.ts +81 -0
- package/src/render/shaders/depth.ts +45 -8
- package/src/render/shaders/flat/anisotropic.ts +68 -0
- package/src/render/shaders/flat/eye.ts +82 -0
- package/src/render/shaders/flat/hair.ts +102 -0
- package/src/render/shaders/flat/index.ts +42 -3
- package/src/render/shaders/flat/main.ts +113 -17
- package/src/render/shaders/flat/models.ts +286 -0
- package/src/render/shaders/flat/preamble.ts +26 -24
- package/src/render/shaders/flat/skin.ts +116 -0
- package/src/render/shaders/generated/depth.wgsl.ts +66 -24
- package/src/render/shaders/generated/flat.wgsl.ts +1853 -1742
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +3 -3
- package/src/render/shaders/generated/rush.wgsl.ts +1 -1
- package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
- package/src/render/shaders/outputTransform.ts +24 -1
- package/src/render/shaders/recon/motion.wgsl.ts +102 -0
- package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
- package/src/render/shaders/rush.ts +13 -2
- package/src/render/shaders/skinBlur.ts +105 -0
- package/src/render/shaders/skinning.ts +31 -4
- package/src/render/shaders/temporalResolve.ts +12 -5
- package/src/render/shadowCasters.ts +13 -0
- package/src/render/skinBlur.ts +53 -0
- package/src/render/skinScatter.ts +92 -0
- package/src/render/skinnedClothRun.ts +128 -0
- package/src/render/ssrPass.ts +2 -1
- package/src/render/surfaceModel.ts +274 -0
- package/src/render/surfaceTexture.ts +134 -63
- package/src/render/textRenderer.ts +2 -1
- package/src/render/vertexDefaults.ts +8 -0
- package/src/render/waterRenderer.ts +2 -1
- package/src/render/windStreakRenderer.ts +2 -1
- package/dist/math/exact.d.ts +0 -76
- package/dist/math/exact.js +0 -327
- package/src/math/exact.ts +0 -340
|
@@ -21,6 +21,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
21
21
|
fxLoad(int(surfaceAt.z));
|
|
22
22
|
surfaceAt = fxAnimate(surfaceAt);
|
|
23
23
|
}
|
|
24
|
+
/* An eye's iris, moved by its cornea before any map reads it: models.ts. */
|
|
25
|
+
surfaceAt = modelSurfaceAt(surfaceAt);
|
|
24
26
|
vec2 fxDx = dFdx(surfaceAt.xy);
|
|
25
27
|
vec2 fxDy = dFdy(surfaceAt.xy);
|
|
26
28
|
if (uWriteMode.y != 0.0 && !ditherKeeps(uWriteMode.y)) discard;
|
|
@@ -56,18 +58,35 @@ export const MAIN_GLSL = `void main() {
|
|
|
56
58
|
* rectangle of haze with a picture in it, rather than a picture.
|
|
57
59
|
*/
|
|
58
60
|
float coverage = 1.0;
|
|
59
|
-
if (
|
|
61
|
+
if (uMaterialFlags.x != 0) {
|
|
60
62
|
vec4 texel = texture(uAlbedo, surfaceAt);
|
|
61
63
|
// Discarded before anything else is computed: a thrown-away fragment should not pay
|
|
62
64
|
// for the lighting it will never contribute to. The test credits alpha for the mip level
|
|
63
65
|
// it samples, so a needle averaged thin down the chain is not lost; see cutoutCoverage.ts.
|
|
64
66
|
// Behind a branch on a uniform, so a material with no cutoff pays nothing for it.
|
|
65
|
-
|
|
67
|
+
float kept = texel.a;
|
|
68
|
+
if (uCutout.x > 0.0) {
|
|
66
69
|
float tested = cutoutAlpha(texel.a, vUv.xy * vec2(textureSize(uAlbedo, 0).xy));
|
|
67
|
-
if (
|
|
70
|
+
if (uCutout.y == 0.0) {
|
|
71
|
+
if (tested < uCutout.x) discard;
|
|
72
|
+
} else {
|
|
73
|
+
/*
|
|
74
|
+
* A dithered edge: the pixel is kept by the share of it the texture covers. Under a
|
|
75
|
+
* temporal resolve the test is against noise that moves every frame, and a kept pixel is
|
|
76
|
+
* whole; multisampled, the share is the alpha the GPU turns into coverage. See
|
|
77
|
+
* cutoutDither.ts.
|
|
78
|
+
*/
|
|
79
|
+
float share = cutoutShare(tested, uCutout.x);
|
|
80
|
+
if (uCutout.y == 1.0) {
|
|
81
|
+
if (!cutoutKeeps(share, gl_FragCoord.xy, uCutout.z)) discard;
|
|
82
|
+
kept = 1.0;
|
|
83
|
+
} else {
|
|
84
|
+
kept = share;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
68
87
|
}
|
|
69
88
|
albedo *= texel.rgb;
|
|
70
|
-
coverage =
|
|
89
|
+
coverage = kept;
|
|
71
90
|
}
|
|
72
91
|
|
|
73
92
|
/*
|
|
@@ -91,7 +110,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
91
110
|
float ormOcclusion = 1.0;
|
|
92
111
|
float ormRoughness = vRoughness;
|
|
93
112
|
float metal = 0.0;
|
|
94
|
-
if (
|
|
113
|
+
if (uMaterialFlags.y != 0) {
|
|
95
114
|
vec3 t = texture(uOrmMap, surfaceAt).rgb;
|
|
96
115
|
ormOcclusion = mix(1.0, t.r, uOrmScale.r);
|
|
97
116
|
ormRoughness = clamp(t.g * uOrmScale.g, 0.0, 1.0);
|
|
@@ -121,6 +140,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
121
140
|
}
|
|
122
141
|
|
|
123
142
|
vec3 lit = albedo;
|
|
143
|
+
/* Skin's diffuse, held before emission for the screen-space blur's own pass: models.ts. */
|
|
144
|
+
vec3 diffuseAlone = vec3(0.0);
|
|
124
145
|
/* The light arriving at a glass pane from behind it, gathered in each light loop below and added
|
|
125
146
|
over the see-through at the end. Stays zero for anything that is not glass. See glass.ts. */
|
|
126
147
|
vec3 glassGlow = vec3(0.0);
|
|
@@ -143,7 +164,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
143
164
|
* Taken from the stored normal alone, a pane seen from behind glowed with the light on the
|
|
144
165
|
* eye's side and not with the sun shining through it.
|
|
145
166
|
*/
|
|
146
|
-
bool backFace = (
|
|
167
|
+
bool backFace = (uMaterialFlags.w != 0 || glassTransmission > 0.0) && dot(n, uCameraPos - vWorldPos) < 0.0;
|
|
147
168
|
/*
|
|
148
169
|
* The authored normal, before either relief perturbs it.
|
|
149
170
|
*
|
|
@@ -162,6 +183,15 @@ export const MAIN_GLSL = `void main() {
|
|
|
162
183
|
*/
|
|
163
184
|
if (uNormalStrength > 0.0) {
|
|
164
185
|
vec3 mapped = texture(uNormalMap, surfaceAt).xyz * 2.0 - 1.0;
|
|
186
|
+
/*
|
|
187
|
+
* z rebuilt where none is stored. A tangent-space normal points out of its surface, so a
|
|
188
|
+
* stored z at or below zero is never a real one: it is a two-channel map, BC5, whose blue a
|
|
189
|
+
* device samples as zero and which arrives here as -1. Every map baked from BC5 to PNG before
|
|
190
|
+
* 4.8.4 carried the same zero. A select rather than a flag: no uniform to bind, no material
|
|
191
|
+
* that has to say what format it holds, and continuous at the boundary, since a unit texel
|
|
192
|
+
* lying flat has its z near zero either way. A texel with z above zero is left as it was.
|
|
193
|
+
*/
|
|
194
|
+
mapped.z = mapped.z > 0.0 ? mapped.z : sqrt(max(0.0, 1.0 - dot(mapped.xy, mapped.xy)));
|
|
165
195
|
mat3 tbn = tangentFrame(n, vWorldPos, vUv.xy, vTangent, vHasTangents);
|
|
166
196
|
n = normalize(mix(n, normalize(tbn * mapped), uNormalStrength));
|
|
167
197
|
}
|
|
@@ -268,7 +298,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
268
298
|
* and normalized: dividing the gradient by \`det\` instead would blow up on a fragment where
|
|
269
299
|
* the surface is edge-on and the quad's footprint collapses.
|
|
270
300
|
*/
|
|
271
|
-
if (
|
|
301
|
+
if (uMaterialFlags.x != 0 && uTextureRelief != 0.0) {
|
|
272
302
|
/*
|
|
273
303
|
* **A centred difference, not three.js's forward one, and that is what makes the two
|
|
274
304
|
* backends agree.** WebGL2's framebuffer counts y upward and WebGPU's counts it downward,
|
|
@@ -331,6 +361,14 @@ export const MAIN_GLSL = `void main() {
|
|
|
331
361
|
* across the fold of every curtain.
|
|
332
362
|
*/
|
|
333
363
|
if (backFace) n = -n;
|
|
364
|
+
/*
|
|
365
|
+
* A model's surface, gathered once, on a branch on the switches alone (models.ts) — and whether
|
|
366
|
+
* there is one, asked once: every site below is a branch on these two, which a pipeline with no
|
|
367
|
+
* model folds away, and each extra call was a copy of its plumbing in all sixteen permutations.
|
|
368
|
+
*/
|
|
369
|
+
bool modelled = modelOn();
|
|
370
|
+
bool modelledBehind = modelSeesBehind();
|
|
371
|
+
if (modelled) modelSurface(n, albedo, surfaceRoughness, metal, surfaceAt);
|
|
334
372
|
|
|
335
373
|
/*
|
|
336
374
|
* One dot product, used twice: ndl is the lit side and its negative is the night side below.
|
|
@@ -444,7 +482,19 @@ export const MAIN_GLSL = `void main() {
|
|
|
444
482
|
* smallest change available in a shader with a documented history of shared subexpressions
|
|
445
483
|
* rounding differently when their neighbours move. See the night-emissive term.
|
|
446
484
|
*/
|
|
447
|
-
|
|
485
|
+
/*
|
|
486
|
+
* A model answers the sun itself, and is shadowed as it says (models.ts). The standard line
|
|
487
|
+
* stands as it was, so a pipeline with no model compiles to exactly the arithmetic before.
|
|
488
|
+
*/
|
|
489
|
+
vec3 sunModelSpecular = vec3(0.0);
|
|
490
|
+
if (modelled) {
|
|
491
|
+
modelLight(uDirectionalDir, 0.0, 0.0);
|
|
492
|
+
float sunModelFacing = modelledBehind ? 1.0 : sunFacing;
|
|
493
|
+
lit = modelAmbient(ambient) + sunColor * mDiffuse * modelShade(sunShade) * sunModelFacing;
|
|
494
|
+
sunModelSpecular = mSpecular * sunColor * sunShade * sunModelFacing;
|
|
495
|
+
} else {
|
|
496
|
+
lit = albedo * (ambient + sunColor * direct * (1.0 - metal));
|
|
497
|
+
}
|
|
448
498
|
|
|
449
499
|
/*
|
|
450
500
|
* A sun highlight, for the few surfaces that ask for one.
|
|
@@ -507,6 +557,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
507
557
|
*/
|
|
508
558
|
vec3 sunHighlight =
|
|
509
559
|
sunColor * specularLobe(max(dot(n, halfway), 0.0), surfaceRoughness) * sunSpec * sunShade * sunFacing;
|
|
560
|
+
if (modelled) sunHighlight = sunModelSpecular;
|
|
510
561
|
/*
|
|
511
562
|
* The dielectric's share goes in here, where it has always gone, so the environment blend
|
|
512
563
|
* below dims it by exactly the reflectance it always dimmed it by. The metal's share is added
|
|
@@ -578,18 +629,20 @@ export const MAIN_GLSL = `void main() {
|
|
|
578
629
|
* whole block was skipped for a fully metallic surface and max(uReflectivity, metal) inside it
|
|
579
630
|
* was never reached. A chromed subject came out near black with a baked probe sitting unread.
|
|
580
631
|
*
|
|
581
|
-
* **Gated on
|
|
632
|
+
* **Gated on the ORM flag rather than on metal, and that is required rather than tidy.** The
|
|
582
633
|
* block takes fwidth of the mirror direction, and a derivative may only be taken under uniform
|
|
583
|
-
* control flow.
|
|
634
|
+
* control flow. The flag is a uniform; metal is a texture fetch, so branching on it would
|
|
584
635
|
* put that derivative in non-uniform control flow and WGSL refuses the module outright. See
|
|
585
636
|
* AGENTS.md, 2026-08-07. The per-texel decision still happens, one line down, in the amount.
|
|
586
637
|
*
|
|
587
|
-
*
|
|
638
|
+
* With the flag at 0 this is uReflectivity > 0.0 exactly, so nothing that binds no map takes a
|
|
588
639
|
* branch it did not take before.
|
|
589
640
|
*/
|
|
590
|
-
|
|
641
|
+
/* A model reflects its environment whatever the pass asked of the standard model: see
|
|
642
|
+
modelReflectance. On the switches alone, so the derivatives below stay in uniform flow. */
|
|
643
|
+
if (uReflectivity > 0.0 || uMaterialFlags.y != 0 || modelled) {
|
|
591
644
|
vec3 toEye = normalize(uCameraPos - vWorldPos);
|
|
592
|
-
vec3 mirrored = reflect(-toEye, n);
|
|
645
|
+
vec3 mirrored = reflect(-toEye, modelled ? modelReflectNormal(n) : n);
|
|
593
646
|
vec3 environment = mix(uAmbientGround, uAmbient, mirrored.y * 0.5 + 0.5);
|
|
594
647
|
#if ENVIRONMENT_PROBE
|
|
595
648
|
/*
|
|
@@ -845,6 +898,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
845
898
|
#else
|
|
846
899
|
float weight = clamp(fresnel * reflectAmount, 0.0, 1.0);
|
|
847
900
|
#endif
|
|
901
|
+
if (modelled) weight = modelReflectance(weight);
|
|
902
|
+
if (modelled) environment = modelEnvironment(environment);
|
|
848
903
|
lit = mix(
|
|
849
904
|
lit,
|
|
850
905
|
environment * mix(vec3(1.0), albedo, metal),
|
|
@@ -1151,7 +1206,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
1151
1206
|
*/
|
|
1152
1207
|
float backNdl =
|
|
1153
1208
|
glassTransmission > 0.0 ? max(-dot(n, toLight / max(dist, 1e-4)), 0.0) : 0.0;
|
|
1154
|
-
|
|
1209
|
+
/* A model that sees light from behind keeps the lamps behind the surface. See models.ts. */
|
|
1210
|
+
if (ndl <= 0.0 && backNdl <= 0.0 && !modelledBehind) continue;
|
|
1155
1211
|
|
|
1156
1212
|
/*
|
|
1157
1213
|
* Normal-offset bias.
|
|
@@ -1400,13 +1456,25 @@ export const MAIN_GLSL = `void main() {
|
|
|
1400
1456
|
|
|
1401
1457
|
float shape = (uLightFalloff == 1 ? falloff : falloff * falloff) * coneFalloff * photometric;
|
|
1402
1458
|
glassGlow += lightColor * backNdl * shape * lightWeight * shaded * lampGlass;
|
|
1403
|
-
if (ndl <= 0.0) continue;
|
|
1459
|
+
if (ndl <= 0.0 && !modelledBehind) continue;
|
|
1404
1460
|
/*
|
|
1405
1461
|
* The cookie tints rather than scales, because a mask may be coloured — a stained window is
|
|
1406
1462
|
* the case that makes the difference — and a greyscale one is exactly a scale. Applied to
|
|
1407
1463
|
* the light's colour rather than to \`shape\`, which is a scalar and could not carry it.
|
|
1408
1464
|
*/
|
|
1409
1465
|
lightColor *= cookieTint;
|
|
1466
|
+
/*
|
|
1467
|
+
* A model's answer to this lamp, held back and shadowed exactly as the standard terms below
|
|
1468
|
+
* are — the shadowed sum through \`modelShade\`, which is where skin's penumbra reddens.
|
|
1469
|
+
*/
|
|
1470
|
+
if (modelled) {
|
|
1471
|
+
modelLight(toLight / max(dist, 1e-4), lightSourceRadius, dist);
|
|
1472
|
+
vec3 lampModelLight = lightColor * shape * lightWeight;
|
|
1473
|
+
lampOpen += (mDiffuse + mSpecular) * lampModelLight;
|
|
1474
|
+
lampShadowed += (mDiffuse * modelShade(shaded) + mSpecular * shaded) * lampModelLight * lampGlass;
|
|
1475
|
+
lightShade = min(lightShade, mix(1.0, shaded, lightWeight));
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1410
1478
|
/*
|
|
1411
1479
|
* **A metal has no diffuse term, and this lamp kept giving it one.**
|
|
1412
1480
|
*
|
|
@@ -1648,6 +1716,22 @@ export const MAIN_GLSL = `void main() {
|
|
|
1648
1716
|
|
|
1649
1717
|
glassGlow += uAreaLightColor[a] * backForm * areaOccl * areaGlass;
|
|
1650
1718
|
if (form <= 0.0) continue;
|
|
1719
|
+
/*
|
|
1720
|
+
* A model answers a rectangle as it answers a lamp at its centre, carried at the rectangle's
|
|
1721
|
+
* own energy: the diffuse by the exact form factor over the centre's cosine, the specular by
|
|
1722
|
+
* the form factor. **An approximation, stated**: a model's lobe is not integrated over the
|
|
1723
|
+
* rectangle as the standard lobe is, so a glossy model under a large softbox shows a lobe
|
|
1724
|
+
* from its centre rather than its shape.
|
|
1725
|
+
*/
|
|
1726
|
+
if (modelled) {
|
|
1727
|
+
vec3 areaL = normalize(centre - vWorldPos);
|
|
1728
|
+
modelLight(areaL, 0.0, 0.0);
|
|
1729
|
+
vec3 areaModel = (mDiffuse * (form / max(dot(n, areaL), 1e-3)) + mSpecular * form)
|
|
1730
|
+
* uAreaLightColor[a];
|
|
1731
|
+
lampOpen += areaModel;
|
|
1732
|
+
lampShadowed += areaModel * areaOccl * areaGlass;
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1651
1735
|
/* Held back with the point lamps', and shadowed by the same term it always was. */
|
|
1652
1736
|
vec3 areaDiffuse = albedo * uAreaLightColor[a] * form * (1.0 - metal);
|
|
1653
1737
|
lampOpen += areaDiffuse;
|
|
@@ -1741,7 +1825,12 @@ export const MAIN_GLSL = `void main() {
|
|
|
1741
1825
|
if (glassTransmission > 0.0) {
|
|
1742
1826
|
glassGlow += driftLightIrradiance(vWorldPos, -n) * (driftShare * uDriftLight.w);
|
|
1743
1827
|
}
|
|
1744
|
-
|
|
1828
|
+
/* The standard expression in its own order — albedo times the field, then the share — so a
|
|
1829
|
+
pipeline with no model rounds exactly as it did; a model is handed the light alone. */
|
|
1830
|
+
vec3 driftField = driftLightIrradiance(vWorldPos, n);
|
|
1831
|
+
lit += modelled
|
|
1832
|
+
? modelIrradiance(driftField * (driftShare * uDriftLight.w))
|
|
1833
|
+
: albedo * driftField * (driftShare * uDriftLight.w) * (1.0 - metal);
|
|
1745
1834
|
}
|
|
1746
1835
|
|
|
1747
1836
|
/*
|
|
@@ -1793,6 +1882,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
1793
1882
|
* underneath: a ceiling emitting a dull warm haze over a pale panel cannot be expressed
|
|
1794
1883
|
* by scaling the panel, and scaling it gets the brightness right and the hue wrong.
|
|
1795
1884
|
*/
|
|
1885
|
+
diffuseAlone = lit;
|
|
1796
1886
|
vec3 emissiveTint = vEmissiveColor.r < 0.0 ? albedo : vEmissiveColor;
|
|
1797
1887
|
/*
|
|
1798
1888
|
* Where the surface glows, from an image, or everywhere if it binds none.
|
|
@@ -1811,7 +1901,7 @@ export const MAIN_GLSL = `void main() {
|
|
|
1811
1901
|
* explicit level — the 2026-08-07 rule is satisfied by the gate rather than by a \`textureLod\`.
|
|
1812
1902
|
*/
|
|
1813
1903
|
vec3 emissiveMapped = vec3(1.0);
|
|
1814
|
-
if (
|
|
1904
|
+
if (uMaterialFlags.z != 0) {
|
|
1815
1905
|
emissiveMapped = texture(uEmissiveMap, surfaceAt).rgb * uEmissiveScale;
|
|
1816
1906
|
}
|
|
1817
1907
|
/* Pulse, flicker and fade from the layer's effects; exactly 1 where it names none. */
|
|
@@ -1890,8 +1980,14 @@ export const MAIN_GLSL = `void main() {
|
|
|
1890
1980
|
// Water is a camera medium, not a property of individual objects.
|
|
1891
1981
|
vec3 waterTransmission = vec3(0.42) + uUnderwaterColor * 2.0;
|
|
1892
1982
|
lit = mix(lit, lit * waterTransmission, uUnderwaterFactor * 0.55);
|
|
1983
|
+
diffuseAlone = mix(diffuseAlone, diffuseAlone * waterTransmission, uUnderwaterFactor * 0.55);
|
|
1893
1984
|
fog = mediumFog(distance(vWorldPos, uCameraPos), vWorldPos.y);
|
|
1894
1985
|
}
|
|
1986
|
+
/* The diffuse pass leaves here, with skin's diffuse and the profile it is spread by. */
|
|
1987
|
+
if (modelDiffuseAlone()) {
|
|
1988
|
+
outColor = vec4(modelDiffuseOut(diffuseAlone * ormOcclusion, fog), skinProfileCode());
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1895
1991
|
|
|
1896
1992
|
/*
|
|
1897
1993
|
* A surface recedes into the medium; light added to the frame fades in it (\`uFogEnabled\` at 2,
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lit stage's shading models: how a material that is not the standard one answers light.
|
|
3
|
+
*
|
|
4
|
+
* **One switch a model, each a pipeline constant, and a branch only ever on the switch itself.**
|
|
5
|
+
* `MODEL_ANISOTROPIC`, `MODEL_HAIR`, `MODEL_SKIN` and `MODEL_EYE` are off in every pipeline but the
|
|
6
|
+
* ones a material with that model asks for, so the device compiles every model out of the standard
|
|
7
|
+
* pipelines and a scene that names none carries none — the arrangement `litSwitchesGlsl` has, for
|
|
8
|
+
* its reason. Not one constant numbering the models: a comparison against an override is a
|
|
9
|
+
* specialisation-constant operation, which the WGSL generator refuses (`overridableConstants`), so
|
|
10
|
+
* every way in is a function that branches on the switches one at a time.
|
|
11
|
+
*
|
|
12
|
+
* **Where the models plug in.** `main` gathers the surface once (`modelSurface`), then asks at each
|
|
13
|
+
* place light arrives: `modelLight` for the sun, each lamp and each rectangle, `modelAmbient` for
|
|
14
|
+
* the sky and the probe's irradiance, `modelIrradiance` for DriftLight, `modelReflectNormal` and
|
|
15
|
+
* `modelReflectance` for the environment's reflection. Each returns today's term for the standard
|
|
16
|
+
* model, and each model's file is where its own answers are.
|
|
17
|
+
*
|
|
18
|
+
* **What a model returns from a light** is the two halves the standard terms are: a diffuse already
|
|
19
|
+
* weighted by the angle of arrival, and a specular lobe in the engine's peak-normalised units (see
|
|
20
|
+
* `specularLobe`), both before any shadow — the caller shadows the specular, and the diffuse through
|
|
21
|
+
* `modelShade`, which is the identity but for skin, whose penumbra reddens.
|
|
22
|
+
*/
|
|
23
|
+
import { SURFACE_MODEL_SWITCH } from '../../surfaceModel.ts';
|
|
24
|
+
import type { SurfaceModelKind } from '../../surfaceModel.ts';
|
|
25
|
+
import { ANISOTROPIC_GLSL } from './anisotropic.ts';
|
|
26
|
+
import { HAIR_GLSL } from './hair.ts';
|
|
27
|
+
import { SKIN_GLSL } from './skin.ts';
|
|
28
|
+
import { EYE_GLSL } from './eye.ts';
|
|
29
|
+
|
|
30
|
+
/** The switches, the numbers, the surface every model reads, and the standard answer it falls back on. */
|
|
31
|
+
const MODELS_HEAD = /* glsl */ `
|
|
32
|
+
/* Which model a pipeline shades by. Each off but where a material asks for it: see models.ts. */
|
|
33
|
+
const bool MODEL_ANISOTROPIC = false; // wgsl:override
|
|
34
|
+
const bool MODEL_HAIR = false; // wgsl:override
|
|
35
|
+
const bool MODEL_SKIN = false; // wgsl:override
|
|
36
|
+
const bool MODEL_EYE = false; // wgsl:override
|
|
37
|
+
/*
|
|
38
|
+
* Skin's screen-space scattering, as two halves of one surface: the scene pass without its diffuse,
|
|
39
|
+
* and a pass that is nothing but it, which the blur spreads before it is added back. Both off but
|
|
40
|
+
* where \`skinScattering\` is \`'screen-space'\`; see \`skinBlur.ts\`.
|
|
41
|
+
*/
|
|
42
|
+
const bool SKIN_SCREEN = false; // wgsl:override
|
|
43
|
+
const bool SKIN_DIFFUSE = false; // wgsl:override
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
* A model's numbers, two vectors a material — the lit stage's uniform budget has no more room
|
|
47
|
+
* (surfaceModel.ts) — and whether a model map is bound, in the last component.
|
|
48
|
+
*/
|
|
49
|
+
uniform vec4 uModelParams[2];
|
|
50
|
+
/* The model's own channels, in the albedo's layout: what each means is each model's to say. */
|
|
51
|
+
uniform highp sampler2DArray uModelMap;
|
|
52
|
+
|
|
53
|
+
/** Whether this pipeline shades by any model but the standard one. */
|
|
54
|
+
bool modelOn() {
|
|
55
|
+
if (MODEL_ANISOTROPIC) return true;
|
|
56
|
+
if (MODEL_HAIR) return true;
|
|
57
|
+
if (MODEL_SKIN) return true;
|
|
58
|
+
if (MODEL_EYE) return true;
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Whether this pipeline draws skin's diffuse alone, for the screen-space blur. */
|
|
63
|
+
bool modelDiffuseAlone() {
|
|
64
|
+
if (MODEL_SKIN) {
|
|
65
|
+
if (SKIN_DIFFUSE) return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Whether light from behind the surface reaches the eye through it: hair, and skin's thin parts. */
|
|
71
|
+
bool modelSeesBehind() {
|
|
72
|
+
if (MODEL_HAIR) return true;
|
|
73
|
+
if (MODEL_SKIN) return true;
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* The surface a model shades, gathered once in main before any light reaches it. */
|
|
78
|
+
vec3 mNormal;
|
|
79
|
+
vec3 mToEye;
|
|
80
|
+
vec3 mTangent;
|
|
81
|
+
vec3 mBitangent;
|
|
82
|
+
vec3 mAlbedo;
|
|
83
|
+
vec3 mSpecColor;
|
|
84
|
+
float mRoughness;
|
|
85
|
+
float mMetal;
|
|
86
|
+
vec4 mMap;
|
|
87
|
+
/*
|
|
88
|
+
* What \`modelLight\` leaves: the diffuse weighted by its angle and the lobe, neither shadowed.
|
|
89
|
+
* Globals rather than out parameters, because every parameter a call passes is a local, a store and
|
|
90
|
+
* a pointer at each call site in \`main\` — sixteen copies of it, one a permutation.
|
|
91
|
+
*/
|
|
92
|
+
vec3 mDiffuse;
|
|
93
|
+
vec3 mSpecular;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Normalised GGX — the distribution itself, not the engine's peak-normalised look — for the models
|
|
97
|
+
* whose highlight is physical: skin's and the cornea's.
|
|
98
|
+
*/
|
|
99
|
+
float ggxLobe(float ndh, float alpha) {
|
|
100
|
+
float a2 = alpha * alpha;
|
|
101
|
+
float d = ndh * ndh * (a2 - 1.0) + 1.0;
|
|
102
|
+
return a2 / (3.14159265 * d * d);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Smith's height-correlated masking with the BRDF's 1 / 4 N·L N·V folded in, for the same. */
|
|
106
|
+
float smithMasking(float ndl, float ndv, float alpha) {
|
|
107
|
+
float a2 = alpha * alpha;
|
|
108
|
+
return 0.5 / max(
|
|
109
|
+
ndl * sqrt(ndv * ndv * (1.0 - a2) + a2) + ndv * sqrt(ndl * ndl * (1.0 - a2) + a2),
|
|
110
|
+
1e-5
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The standard model's answer to one light, which a model falls back to for what it does not change. */
|
|
115
|
+
void standardLight(vec3 l, float sourceRadius, float dist) {
|
|
116
|
+
float ndl = max(dot(mNormal, l), 0.0);
|
|
117
|
+
vec3 h = normalize(l + mToEye);
|
|
118
|
+
float ndh = max(dot(mNormal, h), 0.0);
|
|
119
|
+
float lobe = sourceRadius > 0.0
|
|
120
|
+
? sphereLobe(ndh, mRoughness, sourceRadius, dist)
|
|
121
|
+
: specularLobe(ndh, mRoughness);
|
|
122
|
+
vec3 tint = mix(mSpecColor, vec3(1.0), pow(1.0 - max(dot(mToEye, h), 0.0), 5.0) * mMetal);
|
|
123
|
+
mDiffuse = mAlbedo * ndl * (1.0 - mMetal);
|
|
124
|
+
mSpecular = ndl > 0.0 ? tint * lobe : vec3(0.0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
/** Where `main` asks: each model's answer, and the standard's where a model keeps it. */
|
|
130
|
+
const MODELS_DISPATCH = /* glsl */ `
|
|
131
|
+
/**
|
|
132
|
+
* Where the material's maps are read: the surface's own coordinate, but for an eye, whose iris is
|
|
133
|
+
* seen through its cornea and moved by it (\`eyeRefraction.ts\`). Before any map is read, and on a
|
|
134
|
+
* branch on the switches alone, so its derivatives are in uniform control flow.
|
|
135
|
+
*/
|
|
136
|
+
vec3 modelSurfaceAt(vec3 at) {
|
|
137
|
+
if (MODEL_EYE) return eyeRefract(at);
|
|
138
|
+
return at;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Gather the surface. The frame and the map read derivatives, so this is called under a branch on
|
|
143
|
+
* the switches alone, which is uniform control flow by construction.
|
|
144
|
+
*/
|
|
145
|
+
void modelSurface(vec3 n, vec3 albedo, float roughness, float metal, vec3 at) {
|
|
146
|
+
mNormal = n;
|
|
147
|
+
mToEye = normalize(uCameraPos - vWorldPos);
|
|
148
|
+
mat3 frame = tangentFrame(n, vWorldPos, vUv.xy, vTangent, vHasTangents);
|
|
149
|
+
mTangent = frame[0];
|
|
150
|
+
mBitangent = frame[1];
|
|
151
|
+
mAlbedo = albedo;
|
|
152
|
+
mSpecColor = mix(vec3(vSpecular), albedo, metal);
|
|
153
|
+
mRoughness = roughness;
|
|
154
|
+
mMetal = metal;
|
|
155
|
+
mMap = vec4(0.5, 0.5, 1.0, 1.0);
|
|
156
|
+
if (uModelParams[1].w > 0.0) mMap = texture(uModelMap, at);
|
|
157
|
+
if (MODEL_ANISOTROPIC) anisotropicSurface();
|
|
158
|
+
if (MODEL_HAIR) hairSurface();
|
|
159
|
+
if (MODEL_SKIN) skinSurface();
|
|
160
|
+
if (MODEL_EYE) eyeSurface(at);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* One light, arriving along \`l\` (unit, toward the light), from a source \`sourceRadius\` across at
|
|
165
|
+
* \`dist\` — 0 for the sun. Into \`mDiffuse\`, weighted by its angle, and \`mSpecular\`, in lobe
|
|
166
|
+
* units, neither shadowed.
|
|
167
|
+
*/
|
|
168
|
+
void modelLight(vec3 l, float sourceRadius, float dist) {
|
|
169
|
+
if (MODEL_ANISOTROPIC) {
|
|
170
|
+
anisotropicLight(l, sourceRadius, dist);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (MODEL_HAIR) {
|
|
174
|
+
hairLight(l, sourceRadius, dist);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (MODEL_SKIN) {
|
|
178
|
+
skinLight(l, sourceRadius, dist);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (MODEL_EYE) {
|
|
182
|
+
eyeLight(l, sourceRadius, dist);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
standardLight(l, sourceRadius, dist);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** How a shadow of \`shadow\` darkens this model's diffuse: the identity, but for hair and skin. */
|
|
189
|
+
vec3 modelShade(float shadow) {
|
|
190
|
+
if (MODEL_HAIR) return hairShade(shadow);
|
|
191
|
+
if (MODEL_SKIN) return skinShade(shadow);
|
|
192
|
+
return vec3(shadow);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** The sky's or the probe's light on this surface, before any reflection. */
|
|
196
|
+
vec3 modelAmbient(vec3 ambient) {
|
|
197
|
+
if (MODEL_HAIR) return hairAround(ambient);
|
|
198
|
+
if (MODEL_SKIN) {
|
|
199
|
+
if (SKIN_SCREEN) return vec3(0.0);
|
|
200
|
+
}
|
|
201
|
+
return mAlbedo * ambient;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Light arriving from every side at once — DriftLight's — as this surface returns it. */
|
|
205
|
+
vec3 modelIrradiance(vec3 irradiance) {
|
|
206
|
+
if (MODEL_HAIR) return hairAround(irradiance);
|
|
207
|
+
if (MODEL_SKIN) {
|
|
208
|
+
if (SKIN_SCREEN) return vec3(0.0);
|
|
209
|
+
}
|
|
210
|
+
return mAlbedo * irradiance * (1.0 - mMetal);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** The normal the environment is reflected about: the surface's own, but for a stretched lobe. */
|
|
214
|
+
vec3 modelReflectNormal(vec3 n) {
|
|
215
|
+
if (MODEL_ANISOTROPIC) return anisotropicReflectNormal(n);
|
|
216
|
+
if (MODEL_HAIR) return hairAcross();
|
|
217
|
+
return n;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The environment the reflection blends toward: black in skin's diffuse pass, so the blend dims
|
|
222
|
+
* the light before it by the reflection's share — as the whole surface's does, and only that light,
|
|
223
|
+
* the lamps arriving after it — and adds none of the reflection, which is the scene pass's half.
|
|
224
|
+
*/
|
|
225
|
+
vec3 modelEnvironment(vec3 environment) {
|
|
226
|
+
if (MODEL_SKIN) {
|
|
227
|
+
if (SKIN_DIFFUSE) return vec3(0.0);
|
|
228
|
+
}
|
|
229
|
+
return environment;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Skin's diffuse as the diffuse pass writes it, from the lit total before emission, fogged. */
|
|
233
|
+
vec3 modelDiffuseOut(vec3 diffuse, float fog) {
|
|
234
|
+
return diffuse * (1.0 - fog);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* How much of the environment the surface reflects — the blend's weight, Fresnel and all — from
|
|
239
|
+
* what the standard model would.
|
|
240
|
+
*/
|
|
241
|
+
float modelReflectance(float standard) {
|
|
242
|
+
if (MODEL_HAIR) return hairReflectance();
|
|
243
|
+
if (MODEL_SKIN) return skinReflectance();
|
|
244
|
+
if (MODEL_EYE) return eyeReflectance();
|
|
245
|
+
return standard;
|
|
246
|
+
}
|
|
247
|
+
`;
|
|
248
|
+
|
|
249
|
+
/** The models' whole chunk, at depth zero before `main`: see `flatFrag`. */
|
|
250
|
+
export const MODELS_GLSL = [
|
|
251
|
+
MODELS_HEAD,
|
|
252
|
+
ANISOTROPIC_GLSL,
|
|
253
|
+
HAIR_GLSL,
|
|
254
|
+
SKIN_GLSL,
|
|
255
|
+
EYE_GLSL,
|
|
256
|
+
MODELS_DISPATCH,
|
|
257
|
+
].join('\n');
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Which half of a skin a lit pipeline draws under `skinScattering: 'screen-space'`: the whole
|
|
261
|
+
* surface (every other pipeline), the frame's half without its diffuse, or the diffuse alone into
|
|
262
|
+
* the blur's target — that last one depth-tested for equality against what the frame's half wrote
|
|
263
|
+
* and writing none, since it is the same surface drawn again. See `skinBlur.ts`.
|
|
264
|
+
*/
|
|
265
|
+
export type SkinHalf = 'whole' | 'scene' | 'diffuse';
|
|
266
|
+
|
|
267
|
+
/** A skin lit stage with one half's switch on, WebGL2's way: see `SkinHalf`. */
|
|
268
|
+
export function skinHalfBound(source: string, half: SkinHalf): string {
|
|
269
|
+
if (half === 'whole') return source;
|
|
270
|
+
const name = half === 'scene' ? 'SKIN_SCREEN' : 'SKIN_DIFFUSE';
|
|
271
|
+
const off = `const bool ${name} = false;`;
|
|
272
|
+
if (!source.includes(off)) {
|
|
273
|
+
throw new Error(`skinHalfBound: this lit stage declares no ${name} constant`);
|
|
274
|
+
}
|
|
275
|
+
return source.replace(off, `const bool ${name} = true;`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** A lit stage with one model's switch on: the same source, the constant flipped — WebGL2's way. */
|
|
279
|
+
export function modelBound(source: string, kind: SurfaceModelKind): string {
|
|
280
|
+
const name = SURFACE_MODEL_SWITCH[kind];
|
|
281
|
+
const off = `const bool ${name} = false;`;
|
|
282
|
+
if (!source.includes(off)) {
|
|
283
|
+
throw new Error(`modelBound: this lit stage declares no ${name} constant`);
|
|
284
|
+
}
|
|
285
|
+
return source.replace(off, `const bool ${name} = true;`);
|
|
286
|
+
}
|
|
@@ -139,8 +139,22 @@ flat in int vHasTangents;
|
|
|
139
139
|
* white shades identically to one with no texture at all.
|
|
140
140
|
*/
|
|
141
141
|
uniform mediump sampler2DArray uAlbedo;
|
|
142
|
-
/**
|
|
143
|
-
|
|
142
|
+
/**
|
|
143
|
+
* **The material's four switches in one row**, because every loose scalar is a row of the 256 an
|
|
144
|
+
* Adreno 740 offers and the eight-light build had none to spare once a shading model asked for two
|
|
145
|
+
* (\`models.ts\`): four ints were four rows, and this is one.
|
|
146
|
+
*
|
|
147
|
+
* - **x, the albedo**: 0 leaves every textured path unevaluated, for the majority of geometry with
|
|
148
|
+
* no image.
|
|
149
|
+
* - **y, the ORM map**: 1 when one is bound, gating its whole block. A uniform rather than a
|
|
150
|
+
* permutation flag, because a fifth flag takes the sixteen permutations flatSource.test.ts sweeps
|
|
151
|
+
* to thirty-two and roughly doubles the generated WGSL every consumer bundles.
|
|
152
|
+
* - **z, the emissive map**: 1 when one is bound, gating its sample, for the same reason.
|
|
153
|
+
* - **w, two-sided**: 1 for a surface seen from both faces — glTF's doubleSided, a curtain, a leaf
|
|
154
|
+
* card. Its draw culls nothing, and a back face is lit as a front, with the shading normal turned
|
|
155
|
+
* toward the viewer.
|
|
156
|
+
*/
|
|
157
|
+
uniform ivec4 uMaterialFlags;
|
|
144
158
|
/**
|
|
145
159
|
* Alpha below which a textured fragment is thrown away entirely, 0 to disable.
|
|
146
160
|
*
|
|
@@ -152,8 +166,13 @@ uniform int uAlbedoEnabled;
|
|
|
152
166
|
*
|
|
153
167
|
* Zero by default, so a texture with no alpha to speak of costs one compare and nothing
|
|
154
168
|
* else changes.
|
|
169
|
+
*
|
|
170
|
+
* **Four numbers in one row**: x is the cutoff, y how this frame resolves the edge (0 the hard test,
|
|
171
|
+
* 1 a dither the temporal resolve averages, 2 alpha-to-coverage; see \`cutoutDither.ts\`), z the
|
|
172
|
+
* frame the dither's pattern is offset by, w spare. One vector rather than a float and two more,
|
|
173
|
+
* because every loose scalar is a row of WebGL2's 224 that the light budget gives up.
|
|
155
174
|
*/
|
|
156
|
-
uniform
|
|
175
|
+
uniform vec4 uCutout;
|
|
157
176
|
/**
|
|
158
177
|
* The albedo array's per-layer effects table: six texels a layer, read with \`texelFetch\` at the
|
|
159
178
|
* layer a vertex carries. A 1×1 stand-in where the array has none, which the size test below reads
|
|
@@ -161,11 +180,6 @@ uniform float uAlbedoCutout;
|
|
|
161
180
|
*/
|
|
162
181
|
uniform mediump sampler2D uSurfaceEffects;
|
|
163
182
|
|
|
164
|
-
/**
|
|
165
|
-
* 1 for a surface seen from both faces: glTF's doubleSided, a curtain or a leaf card. Its draw culls
|
|
166
|
-
* nothing, and a back face is lit as a front, with the shading normal turned toward the viewer.
|
|
167
|
-
*/
|
|
168
|
-
uniform int uDoubleSided;
|
|
169
183
|
|
|
170
184
|
/**
|
|
171
185
|
* A normal map, in the surface's own space.
|
|
@@ -181,7 +195,7 @@ uniform highp sampler2DArray uNormalMap;
|
|
|
181
195
|
/**
|
|
182
196
|
* How hard the map turns the shading normal. 0 is no map at all, and gates the whole block.
|
|
183
197
|
*
|
|
184
|
-
* A uniform rather than a permutation flag, following
|
|
198
|
+
* A uniform rather than a permutation flag, following the albedo's flag: a fifth boolean would take
|
|
185
199
|
* the exhaustive permutation sweep from sixteen shaders to thirty-two, and the generated WGSL that
|
|
186
200
|
* consumers bundle as source from 766 KB to about 1.5 MB. A permutation is the right tool for the
|
|
187
201
|
* shadow samplers, where a profile with shadows off should declare none of them; it is the wrong
|
|
@@ -200,16 +214,6 @@ uniform float uNormalStrength;
|
|
|
200
214
|
* decoding them as display values would bend every one of them toward its floor.
|
|
201
215
|
*/
|
|
202
216
|
uniform highp sampler2DArray uOrmMap;
|
|
203
|
-
/**
|
|
204
|
-
* 1 when a map is bound. Gates the whole block, following uAlbedoEnabled.
|
|
205
|
-
*
|
|
206
|
-
* A uniform rather than a fifth permutation flag: FlatShaderOptions has four booleans and
|
|
207
|
-
* flatSource.test.ts sweeps all sixteen exhaustively, so a fifth takes that to thirty-two and
|
|
208
|
-
* roughly doubles the generated WGSL that consumers bundle as source, this engine having no build
|
|
209
|
-
* step. A permutation is the right tool for the shadow samplers, where a profile with shadows off
|
|
210
|
-
* should declare none of them. It is the wrong tool for one more image.
|
|
211
|
-
*/
|
|
212
|
-
uniform int uOrmEnabled;
|
|
213
217
|
/**
|
|
214
218
|
* What each channel is scaled by, component-aligned with the map: r occlusion, g roughness,
|
|
215
219
|
* b metallic. The alignment is the documentation.
|
|
@@ -227,7 +231,7 @@ uniform vec3 uOrmScale;
|
|
|
227
231
|
* wants a roughness combined in quadrature. Likewise the weight: the split sum is the correct share
|
|
228
232
|
* of a *prefiltered* environment and asserts an integral that was never performed over a box chain.
|
|
229
233
|
*
|
|
230
|
-
* A uniform rather than a permutation flag, following \`
|
|
234
|
+
* A uniform rather than a permutation flag, following the ORM map's in \`uMaterialFlags\`: a fifth boolean doubles the
|
|
231
235
|
* generated WGSL every consumer bundles as source, and this is a few multiply-adds.
|
|
232
236
|
*/
|
|
233
237
|
uniform float uEnvironmentPrefiltered;
|
|
@@ -241,13 +245,11 @@ uniform float uEnvironmentPrefiltered;
|
|
|
241
245
|
* where it should be saturated — but it is silent, because a glow that is too dim reads as a
|
|
242
246
|
* material choice rather than as a decode.
|
|
243
247
|
*
|
|
244
|
-
* A uniform gate rather than a permutation flag, for exactly the reasons \`
|
|
248
|
+
* A uniform gate rather than a permutation flag, for exactly the reasons \`uMaterialFlags\` gives
|
|
245
249
|
* above, and now with a number behind them: a fifth flag was built and measured at 49% of the
|
|
246
250
|
* bundle. See \`ARCHITECTURE.md\` §1.
|
|
247
251
|
*/
|
|
248
252
|
uniform highp sampler2DArray uEmissiveMap;
|
|
249
|
-
/** 1 when a map is bound. Gates the sample, following uOrmEnabled. */
|
|
250
|
-
uniform int uEmissiveMapEnabled;
|
|
251
253
|
/**
|
|
252
254
|
* What the map is scaled by, per channel. 1 is the identity and the default.
|
|
253
255
|
*
|
|
@@ -423,7 +425,7 @@ bool ditherKeeps(float amount) {
|
|
|
423
425
|
* block member the shader does not already carry the size of; it is one \`int\` beside
|
|
424
426
|
* \`uOpacity\`, read once. And the branch it drives is on a value that is the same for every
|
|
425
427
|
* fragment in one draw call, which is exactly the guarantee \`uReflectivity\` and
|
|
426
|
-
* \`
|
|
428
|
+
* \`uMaterialFlags\` already lean on above: a compiler asked to predicate a whole wavefront on
|
|
427
429
|
* one uniform skips the untaken side rather than computing both and discarding one, which is
|
|
428
430
|
* only true because nothing here reads it through a *varying*-dependent branch.
|
|
429
431
|
*/
|