@driftengine/core 4.8.3 → 4.8.5
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 +82 -0
- package/README.md +1 -1
- package/dist/build/shaderComments.js +0 -0
- package/dist/geometry/meshConcat.js +2 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +16 -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/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 +136 -3
- package/dist/render/backend/webgl2/renderer.js +604 -63
- 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/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 +16 -6
- package/dist/render/backend/webgpu/depthPass.js +39 -9
- package/dist/render/backend/webgpu/drawCommand.js +6 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +38 -4
- package/dist/render/backend/webgpu/flatPass.js +109 -12
- package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
- package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
- package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
- package/dist/render/backend/webgpu/motionPass.js +106 -9
- package/dist/render/backend/webgpu/postPass.d.ts +3 -1
- package/dist/render/backend/webgpu/postPass.js +6 -3
- package/dist/render/backend/webgpu/renderer.d.ts +113 -5
- package/dist/render/backend/webgpu/renderer.js +498 -48
- 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/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 +2 -0
- package/dist/render/cutoutCaster.js +2 -0
- package/dist/render/cutoutDither.d.ts +51 -0
- package/dist/render/cutoutDither.js +90 -0
- 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/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/mesh.d.ts +7 -0
- package/dist/render/mesh.js +24 -0
- package/dist/render/recon/motionVectors.d.ts +4 -2
- package/dist/render/recon/motionVectors.js +6 -3
- package/dist/render/renderQuality.d.ts +20 -0
- package/dist/render/renderQuality.js +5 -0
- package/dist/render/rendererHarness.js +4 -0
- package/dist/render/sceneTarget.d.ts +12 -0
- package/dist/render/sceneTarget.js +14 -0
- package/dist/render/shaders/clothBinding.d.ts +22 -0
- package/dist/render/shaders/clothBinding.js +80 -0
- package/dist/render/shaders/depth.js +42 -6
- 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 +36 -2
- 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 +38 -16
- package/dist/render/shaders/generated/depth.wgsl.js +38 -16
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1790 -1704
- package/dist/render/shaders/generated/flat.wgsl.js +1828 -1742
- 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/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +102 -0
- 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/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/surfaceModel.d.ts +129 -0
- package/dist/render/surfaceModel.js +152 -0
- package/dist/render/surfaceTexture.d.ts +44 -4
- package/dist/render/surfaceTexture.js +60 -46
- package/dist/render/vertexDefaults.js +7 -0
- package/package.json +6 -6
- package/src/build/shaderComments.ts +0 -0
- package/src/geometry/meshConcat.ts +2 -0
- package/src/index.ts +40 -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/select.ts +8 -0
- package/src/render/backend/webgl2/clothTextures.ts +120 -0
- package/src/render/backend/webgl2/renderer.ts +693 -64
- package/src/render/backend/webgl2/skinScatter.ts +309 -0
- package/src/render/backend/webgpu/buffers.ts +137 -63
- 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 +39 -7
- package/src/render/backend/webgpu/drawCommand.ts +6 -2
- package/src/render/backend/webgpu/flatPass.ts +145 -7
- package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
- package/src/render/backend/webgpu/motionPass.ts +128 -9
- package/src/render/backend/webgpu/postPass.ts +5 -1
- package/src/render/backend/webgpu/renderer.ts +663 -43
- 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/clothBindingData.ts +89 -0
- package/src/render/clothPlacement.ts +114 -0
- package/src/render/compressedSource.ts +198 -0
- package/src/render/cutoutCaster.ts +5 -0
- package/src/render/cutoutDither.ts +99 -0
- package/src/render/eyeRefraction.ts +72 -0
- package/src/render/glCompressed.ts +86 -0
- package/src/render/hairLobes.ts +188 -0
- package/src/render/lightBudget.ts +25 -0
- package/src/render/mesh.ts +23 -0
- package/src/render/recon/motionVectors.ts +6 -3
- package/src/render/renderQuality.ts +28 -0
- package/src/render/rendererHarness.ts +4 -0
- package/src/render/sceneTarget.ts +21 -0
- package/src/render/shaders/clothBinding.ts +81 -0
- package/src/render/shaders/depth.ts +42 -6
- 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 +36 -2
- 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 +38 -16
- package/src/render/shaders/generated/flat.wgsl.ts +1828 -1742
- package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
- package/src/render/shaders/recon/motion.wgsl.ts +102 -0
- package/src/render/shaders/skinBlur.ts +105 -0
- package/src/render/shaders/skinning.ts +31 -4
- 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/surfaceModel.ts +274 -0
- package/src/render/surfaceTexture.ts +123 -63
- package/src/render/vertexDefaults.ts +7 -0
- package/dist/math/exact.d.ts +0 -76
- package/dist/math/exact.js +0 -327
- package/src/math/exact.ts +0 -340
|
@@ -3,15 +3,18 @@ import { OCTAHEDRAL_GLSL } from '../octahedral.ts';
|
|
|
3
3
|
import { PROBEGRID_GLSL } from './probeGrid.ts';
|
|
4
4
|
import { TANGENT_FRAME_GLSL } from '../tangentFrame.ts';
|
|
5
5
|
import { CUTOUT_COVERAGE_GLSL } from '../cutoutCoverage.ts';
|
|
6
|
+
import { CUTOUT_DITHER_GLSL } from '../../cutoutDither.ts';
|
|
6
7
|
import { preambleGlsl } from './preamble.ts';
|
|
7
8
|
import { LOBES_GLSL } from './lobes.ts';
|
|
8
9
|
import { POINTSHADOW_GLSL } from './pointShadow.ts';
|
|
9
10
|
import { DIRECTIONALSHADOW_GLSL } from './directionalShadow.ts';
|
|
10
11
|
import { glassTintGlsl, litSwitchesGlsl } from './glassTint.ts';
|
|
11
12
|
import { MORPH_GLSL } from '../morph.ts';
|
|
13
|
+
import { CLOTH_BINDING_GLSL } from '../clothBinding.ts';
|
|
12
14
|
import { SKINNING_GLSL } from '../skinning.ts';
|
|
13
15
|
import { SURFACE_GLSL } from './surface.ts';
|
|
14
16
|
import { MAIN_GLSL } from './main.ts';
|
|
17
|
+
import { MODELS_GLSL } from './models.ts';
|
|
15
18
|
import { DRIFT_LIGHT_GLSL } from './driftLight.ts';
|
|
16
19
|
import { SURFACE_EFFECTS_GLSL } from './surfaceEffects.ts';
|
|
17
20
|
import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from '../vertexChannel.ts';
|
|
@@ -145,6 +148,10 @@ ${CHANNEL_BEND}
|
|
|
145
148
|
#if MORPHED
|
|
146
149
|
${MORPH_GLSL}
|
|
147
150
|
#endif
|
|
151
|
+
#if SKINNED
|
|
152
|
+
/* Samplers and a constant alone, so no field of the vertex block moves. See clothBinding.ts. */
|
|
153
|
+
${CLOTH_BINDING_GLSL}
|
|
154
|
+
#endif
|
|
148
155
|
|
|
149
156
|
out vec3 vNormal;
|
|
150
157
|
out vec3 vColor;
|
|
@@ -218,9 +225,11 @@ void main() {
|
|
|
218
225
|
mat4 skin = skinMatrix();
|
|
219
226
|
vec4 local = skin * vec4(basePosition, 1.0);
|
|
220
227
|
vec3 localNormal = mat3(skin) * aNormal;
|
|
228
|
+
vec3 localTangent = mat3(skin) * aTangent.xyz;
|
|
221
229
|
#else
|
|
222
230
|
vec4 local = vec4(basePosition, 1.0);
|
|
223
231
|
vec3 localNormal = aNormal;
|
|
232
|
+
vec3 localTangent = aTangent.xyz;
|
|
224
233
|
#endif
|
|
225
234
|
#if INSTANCED
|
|
226
235
|
mat4 model = mat4(aInstanceModel0, aInstanceModel1, aInstanceModel2, aInstanceModel3);
|
|
@@ -241,9 +250,31 @@ void main() {
|
|
|
241
250
|
vec3 bent = channelBend(world.xyz, aChannel.x);
|
|
242
251
|
#endif
|
|
243
252
|
world = vec4(bent, world.w);
|
|
244
|
-
vWorldPos = world.xyz;
|
|
245
253
|
// Rotation/translation-only models (our case): mat3 is a valid normal matrix.
|
|
246
254
|
vec3 worldNormal = mat3(model) * localNormal;
|
|
255
|
+
/*
|
|
256
|
+
* **The tangent turns as the normal does**, because the fragment stage crosses it with the
|
|
257
|
+
* world-space normal to build its frame. It went out in the mesh's own space until 4.8.4, which
|
|
258
|
+
* is world space only for a mesh that is moved and never turned: a turned or skinned mesh lit its
|
|
259
|
+
* normal map, and anything reading a direction along its surface, from the wrong side.
|
|
260
|
+
*/
|
|
261
|
+
vec3 worldTangent = mat3(model) * localTangent;
|
|
262
|
+
#if SKINNED
|
|
263
|
+
/*
|
|
264
|
+
* **Toward the cloth, after the skeleton and the bend.** The simulation already swings with the
|
|
265
|
+
* wind it was given, so the bend moves only the skinned part; the weight painted per vertex says
|
|
266
|
+
* how much of the cloth's place and turn the vertex takes. See clothBinding.ts.
|
|
267
|
+
*/
|
|
268
|
+
if (CLOTH_BOUND) {
|
|
269
|
+
vec3 clothPosition;
|
|
270
|
+
mat3 turn;
|
|
271
|
+
float follow = clothPlace(clothPosition, turn);
|
|
272
|
+
world = vec4(mix(world.xyz, clothPosition, follow), world.w);
|
|
273
|
+
worldNormal = normalize(mix(worldNormal, turn * aNormal, follow));
|
|
274
|
+
worldTangent = mix(worldTangent, turn * aTangent.xyz, follow);
|
|
275
|
+
}
|
|
276
|
+
#endif
|
|
277
|
+
vWorldPos = world.xyz;
|
|
247
278
|
vNormal = worldNormal;
|
|
248
279
|
vLightPos = uLightViewProj * world;
|
|
249
280
|
vColor = aColor * tint;
|
|
@@ -254,7 +285,7 @@ void main() {
|
|
|
254
285
|
vRoughness = aRoughness;
|
|
255
286
|
vGrain = aGrain;
|
|
256
287
|
vRelief = aRelief;
|
|
257
|
-
vTangent = aTangent;
|
|
288
|
+
vTangent = vec4(worldTangent, aTangent.w);
|
|
258
289
|
#if INSTANCED
|
|
259
290
|
/* No channel on this path; the neutral values are what every mesh read before it existed. */
|
|
260
291
|
vSkyDirect = 1.0;
|
|
@@ -534,8 +565,11 @@ export function flatFrag(options: FlatShaderOptions): string {
|
|
|
534
565
|
TANGENT_FRAME_GLSL,
|
|
535
566
|
/* Unconditional for the same reason, and called only where a cutoff was asked for. */
|
|
536
567
|
CUTOUT_COVERAGE_GLSL,
|
|
568
|
+
CUTOUT_DITHER_GLSL,
|
|
537
569
|
DRIFT_LIGHT_GLSL,
|
|
538
570
|
SURFACE_EFFECTS_GLSL,
|
|
571
|
+
/* Depth zero and after the tangent frame and the lobes it calls; see models.ts. */
|
|
572
|
+
MODELS_GLSL,
|
|
539
573
|
MAIN_GLSL,
|
|
540
574
|
].join('\n'),
|
|
541
575
|
{
|
|
@@ -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
|
+
}
|