phoria2 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/dist/bloom.js +4 -4
  3. package/dist/bloom.js.map +1 -1
  4. package/dist/charts.js +377 -377
  5. package/dist/charts.js.map +1 -1
  6. package/dist/core.js +1052 -558
  7. package/dist/core.js.map +1 -1
  8. package/dist/edit.js +48 -48
  9. package/dist/edit.js.map +1 -1
  10. package/dist/explain.js +1 -1
  11. package/dist/gltf.js +207 -162
  12. package/dist/gltf.js.map +1 -1
  13. package/dist/motion.js +27 -27
  14. package/dist/motion.js.map +1 -1
  15. package/dist/particles.js +50 -50
  16. package/dist/particles.js.map +1 -1
  17. package/dist/passes.js +199 -97
  18. package/dist/passes.js.map +1 -1
  19. package/dist/phoria2.js +1 -1
  20. package/dist/picking.js +16 -21
  21. package/dist/picking.js.map +1 -1
  22. package/dist/snapshot.js +1 -1
  23. package/dist/tooltip.js +3 -3
  24. package/dist/tooltip.js.map +1 -1
  25. package/dist/types/View.d.ts +5 -0
  26. package/dist/types/geometry/Geometry.d.ts +6 -0
  27. package/dist/types/material/Material.d.ts +125 -0
  28. package/dist/types/material/Texture.d.ts +37 -5
  29. package/dist/types/webgl/WebGLRenderer.d.ts +6 -0
  30. package/dist/types/webgl/passes.d.ts +37 -13
  31. package/dist/types/webgl/resources.d.ts +24 -2
  32. package/dist/types/webgl/shaders.d.ts +4 -2
  33. package/llms-full.txt +96 -7
  34. package/package.json +1 -1
  35. package/schema/scene.schema.json +127 -0
  36. package/src/View.js +4 -2
  37. package/src/geometry/Geometry.js +8 -1
  38. package/src/gltf/load.js +50 -6
  39. package/src/material/Material.js +104 -19
  40. package/src/material/Texture.js +116 -11
  41. package/src/watch.js +19 -0
  42. package/src/webgl/WebGLRenderer.js +51 -12
  43. package/src/webgl/passes.js +190 -32
  44. package/src/webgl/picking.js +0 -1
  45. package/src/webgl/resources.js +89 -8
  46. package/src/webgl/shaders.js +350 -48
@@ -10,6 +10,8 @@ export const ATTRIBUTES = {
10
10
  color: 3,
11
11
  instanceMatrix: 4, // 4-7, a mat4 takes four slots
12
12
  instanceColor: 8,
13
+ tangent: 9, // brushed metal's direction: along the UVs' u, and which way their v runs
14
+ uv2: 10, // a second UV set, for baked occlusion and light maps
13
15
  // lines: per-segment endpoints and colours, and the corner of the quad being drawn
14
16
  segmentStart: 0,
15
17
  segmentEnd: 1,
@@ -62,16 +64,30 @@ vec3 fogged(vec3 c) { return c; }
62
64
  // depth or stencil only - of what isn't cut away
63
65
  // How a map is read: by UV, or projected from the mesh's three axes (triplanar) - the same in every pass
64
66
  // that reads one, so a cut-out's shadow has the holes it is drawn with. Needs vUv declared first.
67
+ // the samplers a mesh's maps are read through
68
+ const MAP_SAMPLERS = ['uMap', 'uRoughnessMap', 'uMetalnessMap', 'uNormalMap', 'uBumpMap', 'uAoMap', 'uEmissiveMap', 'uLightMap']
69
+
65
70
  const SAMPLING = /* glsl */ `
71
+ #ifdef UV2
72
+ in vec2 vUv2;
73
+ #endif
74
+ // each map's own transform - repeat, rotation and offset - and the UV set it reads: the first
75
+ // unless the renderer defines its set as vUv2. uMapCoord is where uMap is read and uMapTexel what
76
+ // it holds there - spelled out per map, as WebGL's GLSL has no token pasting
77
+ ${MAP_SAMPLERS.map((m) => `uniform mat3 ${m}Uv;
78
+ #ifndef ${m}Set
79
+ #define ${m}Set vUv
80
+ #endif
81
+ #define ${m}Coord (${m}Uv * vec3(${m}Set, 1.0)).xy`).join('\n')}
66
82
  #ifdef TRIPLANAR
67
83
  in vec3 vObjectPosition;
68
84
  in vec3 vObjectNormal;
69
85
  in mat3 vObjectToWorld;
70
86
  uniform float uTriplanar; // repeats per unit of the mesh's own space
71
- uniform vec4 uUvTransform; // the map's repeat xy and offset zw, applied to each projection as to UVs
72
87
 
73
- vec2 projected(vec2 p) {
74
- return p * uUvTransform.xy + uUvTransform.zw;
88
+ // a projection's coordinates through the map's transform, as UVs would be
89
+ vec2 projected(vec2 p, mat3 t) {
90
+ return (t * vec3(p, 1.0)).xy;
75
91
  }
76
92
 
77
93
  // how much each axis's projection counts: mostly the one the surface faces, blended at the turns
@@ -80,13 +96,13 @@ vec3 triplanarWeights(vec3 n) {
80
96
  return w / (w.x + w.y + w.z);
81
97
  }
82
98
  // a map seen from the three axes and blended - for meshes with no UVs, scans and CAD models
83
- vec4 triplanar(sampler2D s) {
99
+ vec4 triplanar(sampler2D s, mat3 t) {
84
100
  vec3 p = vObjectPosition * uTriplanar, w = triplanarWeights(normalize(vObjectNormal));
85
- return texture(s, projected(p.zy)) * w.x + texture(s, projected(p.xz)) * w.y + texture(s, projected(p.xy)) * w.z;
101
+ return texture(s, projected(p.zy, t)) * w.x + texture(s, projected(p.xz, t)) * w.y + texture(s, projected(p.xy, t)) * w.z;
86
102
  }
87
- #define SAMPLE(s) triplanar(s)
103
+ ${MAP_SAMPLERS.map((m) => `#define ${m}Texel triplanar(${m}, ${m}Uv)`).join('\n')}
88
104
  #else
89
- #define SAMPLE(s) texture(s, vUv)
105
+ ${MAP_SAMPLERS.map((m) => `#define ${m}Texel texture(${m}, ${m}Coord)`).join('\n')}
90
106
  #endif
91
107
  `
92
108
 
@@ -110,7 +126,7 @@ void main() {
110
126
  #ifdef ALPHA_TEST
111
127
  float alpha = uOpacity;
112
128
  #ifdef MAP
113
- alpha *= SAMPLE(uMap).a;
129
+ alpha *= uMapTexel.a;
114
130
  #endif
115
131
  if (alpha < uAlphaTest) discard;
116
132
  #endif
@@ -186,11 +202,18 @@ layout(location = 3) in vec3 color;
186
202
  layout(location = 4) in mat4 instanceMatrix;
187
203
  layout(location = 8) in vec3 instanceColor;
188
204
  #endif
205
+ #ifdef ANISOTROPY
206
+ layout(location = 9) in vec4 tangent;
207
+ out vec4 vTangent;
208
+ #endif
209
+ #ifdef UV2
210
+ layout(location = 10) in vec2 uv2;
211
+ out vec2 vUv2;
212
+ #endif
189
213
 
190
214
  uniform mat4 uModel;
191
215
  uniform mat3 uNormalMatrix;
192
216
  uniform mat4 uViewProjection;
193
- uniform vec4 uUvTransform; // repeat xy, offset zw
194
217
  uniform int uHighlightIndex; // -2 none, -1 the whole node, otherwise the instance
195
218
  #ifdef FOG
196
219
  uniform mat4 uView;
@@ -238,7 +261,18 @@ void main() {
238
261
  vec4 world = uModel * local;
239
262
  vWorldPosition = world.xyz;
240
263
  vNormal = uNormalMatrix * n;
241
- vUv = uv * uUvTransform.xy + uUvTransform.zw;
264
+ vUv = uv;
265
+ #ifdef UV2
266
+ vUv2 = uv2;
267
+ #endif
268
+ #ifdef ANISOTROPY
269
+ // a direction along the surface, so it turns as the model does
270
+ #ifdef INSTANCED
271
+ vTangent = vec4(mat3(uModel) * (m * tangent.xyz), tangent.w);
272
+ #else
273
+ vTangent = vec4(mat3(uModel) * tangent.xyz, tangent.w);
274
+ #endif
275
+ #endif
242
276
  #ifdef TRIPLANAR
243
277
  vObjectPosition = position;
244
278
  vObjectNormal = normal;
@@ -306,6 +340,38 @@ uniform float uBumpScale;
306
340
  uniform sampler2D uAoMap;
307
341
  uniform float uAoMapIntensity;
308
342
  #endif
343
+ #ifdef LIGHT_MAP
344
+ uniform sampler2D uLightMap;
345
+ uniform float uLightMapIntensity;
346
+ #endif
347
+ uniform float uEnvStrength; // this material's share of the environment's light and reflections
348
+ #ifdef TRANSMISSION
349
+ uniform float uTransmission;
350
+ #endif
351
+ #ifdef CLEARCOAT
352
+ uniform float uClearcoat;
353
+ uniform float uClearcoatRoughness;
354
+ #endif
355
+ uniform vec4 uSpecular; // a non-metal's reflectance face on (from its ior), and at grazing angles
356
+ #ifdef MATCAP
357
+ uniform sampler2D uMatcap;
358
+ uniform mat4 uView;
359
+ #endif
360
+ #ifdef TOON
361
+ uniform float uToon; // how many tones, 2 or more
362
+ #endif
363
+ #ifdef SHEEN
364
+ uniform vec3 uSheenColor; // linear, sheen applied
365
+ uniform float uSheenRoughness;
366
+ #endif
367
+ #ifdef ANISOTROPY
368
+ uniform float uAnisotropy;
369
+ uniform vec2 uAnisotropyDirection; // cos, sin of the brushing's turn from the UVs' u
370
+ in vec4 vTangent;
371
+ #endif
372
+ #ifdef TRANSLUCENCY
373
+ uniform vec4 uTranslucency; // the light let through: linear colour with the amount applied, and the amount
374
+ #endif
309
375
 
310
376
  uniform vec3 uAmbient;
311
377
  uniform vec3 uHemisphereSky;
@@ -377,25 +443,110 @@ float V_SmithGGXCorrelated(float NoV, float NoL, float a) {
377
443
  return 0.5 / max(gv + gl, 1e-5);
378
444
  }
379
445
 
380
- vec3 F_Schlick(float VoH, vec3 f0) {
381
- return f0 + (1.0 - f0) * pow(1.0 - VoH, 5.0);
446
+ vec3 F_Schlick(float VoH, vec3 f0, float f90) {
447
+ return f0 + (f90 - f0) * pow(1.0 - VoH, 5.0);
448
+ }
449
+
450
+ #ifdef ANISOTROPY
451
+ // brushed metal: the brushing's direction, the way across it, and roughness along and across it
452
+ vec3 brushT, brushB;
453
+ float brushAT, brushAB;
454
+
455
+ // GGX stretched along the brushing (KHR_materials_anisotropy): distribution and visibility together
456
+ float anisotropicDV(vec3 v, vec3 l, vec3 h, float NoV, float NoL, float NoH) {
457
+ float a2 = brushAT * brushAB;
458
+ vec3 f = vec3(brushAB * dot(brushT, h), brushAT * dot(brushB, h), a2 * NoH);
459
+ float w2 = a2 / dot(f, f);
460
+ float gv = NoL * length(vec3(brushAT * dot(brushT, v), brushAB * dot(brushB, v), NoV));
461
+ float gl = NoV * length(vec3(brushAT * dot(brushT, l), brushAB * dot(brushB, l), NoL));
462
+ return a2 * w2 * w2 / PI * 0.5 / max(gv + gl, 1e-5);
382
463
  }
464
+ #endif
383
465
 
384
466
  // Light intensity 1 on a white surface facing it gives white: the Lambert term isn't divided by
385
467
  // PI, so the specular term is multiplied by it to keep the balance. (glTF intensities in lux and
386
468
  // candela are divided by PI when loaded.)
387
- vec3 shade(vec3 n, vec3 v, vec3 l, vec3 radiance, vec3 diffuse, vec3 f0, float a) {
469
+ #ifdef TOON
470
+ // the highlight's colour: white on a non-metal, the surface's colour on a metal, fading as it roughens
471
+ vec3 toonHighlight;
472
+
473
+ // light in flat tones, antialiased where they meet: a step up a little before each tone's middle,
474
+ // so more of a surface is lit than dark
475
+ float toonTone(float x) {
476
+ float steps = uToon - 1.0;
477
+ float b = x * steps + 0.6;
478
+ float w = fwidth(b);
479
+ return min(floor(b) + smoothstep(1.0 - w, 1.0, fract(b)), steps) / steps;
480
+ }
481
+
482
+ // a cartoon's shading: flat tones of the colour, and one hard-edged highlight where GGX's is over
483
+ // half its peak
484
+ vec3 toonShade(vec3 n, vec3 v, vec3 l, vec3 radiance, vec3 diffuse, float a) {
485
+ float lit = toonTone(clamp(dot(n, l), 0.0, 1.0));
486
+ float s = D_GGX(clamp(dot(n, normalize(l + v)), 0.0, 1.0), a) * PI * a * a;
487
+ float w = fwidth(s);
488
+ float spot = smoothstep(0.5 - w, 0.5 + w, s) * step(0.001, lit);
489
+ return (diffuse * lit + toonHighlight * spot) * radiance;
490
+ }
491
+ #endif
492
+
493
+ vec3 shade(vec3 n, vec3 v, vec3 l, vec3 radiance, vec3 diffuse, vec3 f0, float f90, float a) {
494
+ #ifdef TOON
495
+ return toonShade(n, v, l, radiance, diffuse, a);
496
+ #endif
388
497
  float NoL = clamp(dot(n, l), 0.0, 1.0);
389
498
  if (NoL <= 0.0) return vec3(0.0);
390
499
  vec3 h = normalize(l + v);
391
500
  float NoV = clamp(abs(dot(n, v)), 1e-4, 1.0);
392
501
  float NoH = clamp(dot(n, h), 0.0, 1.0);
393
502
  float VoH = clamp(dot(v, h), 0.0, 1.0);
394
- vec3 F = F_Schlick(VoH, f0);
503
+ vec3 F = F_Schlick(VoH, f0, f90);
504
+ #ifdef ANISOTROPY
505
+ vec3 specular = anisotropicDV(v, l, h, NoV, NoL, NoH) * F * PI;
506
+ #else
395
507
  vec3 specular = D_GGX(NoH, a) * V_SmithGGXCorrelated(NoV, NoL, a) * F * PI;
508
+ #endif
396
509
  return (diffuse * (1.0 - F) + specular) * radiance * NoL;
397
510
  }
398
511
 
512
+ #ifdef SHEEN
513
+ // cloth's sheen: the Charlie distribution (Estevez and Kulla) with Neubelt's visibility
514
+ vec3 sheenShade(vec3 n, vec3 v, vec3 l, vec3 radiance, float a) {
515
+ float NoL = clamp(dot(n, l), 0.0, 1.0);
516
+ if (NoL <= 0.0) return vec3(0.0);
517
+ vec3 h = normalize(l + v);
518
+ float NoV = clamp(abs(dot(n, v)), 1e-4, 1.0);
519
+ float NoH = clamp(dot(n, h), 0.0, 1.0);
520
+ float invA = 1.0 / a;
521
+ float sin2h = max(1.0 - NoH * NoH, 0.0078125);
522
+ float D = (2.0 + invA) * pow(sin2h, invA * 0.5) / (2.0 * PI);
523
+ float V = clamp(1.0 / (4.0 * (NoL + NoV - NoL * NoV)), 0.0, 1.0);
524
+ return uSheenColor * D * V * PI * radiance * NoL;
525
+ }
526
+
527
+ // how much of the surroundings' light the sheen sends back: a fit to its integral (from three.js)
528
+ float sheenEnvironment(float NoV, float r) {
529
+ float r2 = r * r;
530
+ float a = r < 0.25 ? -339.2 * r2 + 161.4 * r - 25.9 : -8.48 * r2 + 14.3 * r - 9.95;
531
+ float b = r < 0.25 ? 44.0 * r2 - 23.7 * r + 3.26 : 1.97 * r2 - 3.27 * r + 0.72;
532
+ return min(exp(a * NoV + b) + (r < 0.25 ? 0.0 : 0.1 * (r - 0.25)), PI);
533
+ }
534
+ #endif
535
+
536
+ #ifdef CLEARCOAT
537
+ // a clear coat's highlight: a glossy dielectric layer (f0 0.04) on the surface's own normal, so
538
+ // normal and bump maps show beneath it, under a smooth coat
539
+ vec3 coatShade(vec3 n, vec3 v, vec3 l, vec3 radiance, float a) {
540
+ float NoL = clamp(dot(n, l), 0.0, 1.0);
541
+ if (NoL <= 0.0) return vec3(0.0);
542
+ vec3 h = normalize(l + v);
543
+ float NoV = clamp(abs(dot(n, v)), 1e-4, 1.0);
544
+ float NoH = clamp(dot(n, h), 0.0, 1.0);
545
+ float VoH = clamp(dot(v, h), 0.0, 1.0);
546
+ return D_GGX(NoH, a) * V_SmithGGXCorrelated(NoV, NoL, a) * F_Schlick(VoH, vec3(0.04), 1.0) * PI * radiance * NoL;
547
+ }
548
+ #endif
549
+
399
550
  // The built-in 'studio' environment: a soft, bright overhead box over a darker floor, as a
400
551
  // function of direction - no texture needed.
401
552
  vec3 studio(vec3 d) {
@@ -406,13 +557,13 @@ vec3 studio(vec3 d) {
406
557
  }
407
558
 
408
559
  // Karis' analytic approximation of the split-sum environment BRDF.
409
- vec3 envBRDF(vec3 f0, float roughness, float NoV) {
560
+ vec3 envBRDF(vec3 f0, float f90, float roughness, float NoV) {
410
561
  vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
411
562
  vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);
412
563
  vec4 r = roughness * c0 + c1;
413
564
  float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
414
565
  vec2 ab = vec2(-1.04, 1.04) * a004 + r.zw;
415
- return f0 * ab.x + ab.y;
566
+ return f0 * ab.x + ab.y * f90;
416
567
  }
417
568
 
418
569
  #ifdef ENV_MAP
@@ -429,7 +580,7 @@ vec3 envIrradiance(vec3 n) {
429
580
  // A height map as bumps: the normal tilted by how the height changes across the pixel, from
430
581
  // screen-space derivatives (Mikkelsen) - no tangents, and it works with UVs or triplanar alike
431
582
  vec3 bumpNormal(vec3 n) {
432
- float h = SAMPLE(uBumpMap).r * uBumpScale;
583
+ float h = uBumpMapTexel.r * uBumpScale;
433
584
  vec2 dh = vec2(dFdx(h), dFdy(h));
434
585
  vec3 sx = dFdx(vWorldPosition), sy = dFdy(vWorldPosition);
435
586
  vec3 r1 = cross(sy, n), r2 = cross(n, sx);
@@ -444,9 +595,9 @@ vec3 bumpNormal(vec3 n) {
444
595
  // 'whiteout' blend), blended, then turned into the world - no tangents and no UVs needed
445
596
  vec3 triplanarNormal(float strength) {
446
597
  vec3 n = normalize(vObjectNormal), p = vObjectPosition * uTriplanar, w = triplanarWeights(n);
447
- vec3 tx = texture(uNormalMap, projected(p.zy)).xyz * 2.0 - 1.0;
448
- vec3 ty = texture(uNormalMap, projected(p.xz)).xyz * 2.0 - 1.0;
449
- vec3 tz = texture(uNormalMap, projected(p.xy)).xyz * 2.0 - 1.0;
598
+ vec3 tx = texture(uNormalMap, projected(p.zy, uNormalMapUv)).xyz * 2.0 - 1.0;
599
+ vec3 ty = texture(uNormalMap, projected(p.xz, uNormalMapUv)).xyz * 2.0 - 1.0;
600
+ vec3 tz = texture(uNormalMap, projected(p.xy, uNormalMapUv)).xyz * 2.0 - 1.0;
450
601
  tx.xy *= strength; ty.xy *= strength; tz.xy *= strength;
451
602
  tx = vec3(tx.xy + n.zy, abs(tx.z) * n.x);
452
603
  ty = vec3(ty.xy + n.xz, abs(ty.z) * n.y);
@@ -455,12 +606,23 @@ vec3 triplanarNormal(float strength) {
455
606
  }
456
607
  #endif
457
608
 
609
+ #ifdef ANISOTROPY
610
+ // The brushing's direction: the vertices' tangents (along the UVs' u), smooth across the mesh,
611
+ // laid onto the shading normal and turned by the rotation
612
+ vec3 brushDirection(vec3 n) {
613
+ vec3 t = vTangent.xyz - n * dot(n, vTangent.xyz);
614
+ float len = length(t);
615
+ t = len > 1e-6 ? t / len : normalize(cross(abs(n.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0), n));
616
+ return t * uAnisotropyDirection.x + cross(n, t) * (vTangent.w < 0.0 ? -1.0 : 1.0) * uAnisotropyDirection.y;
617
+ }
618
+ #endif
619
+
458
620
  #ifdef NORMAL_MAP
459
621
  // Tangent space from screen-space derivatives (Schüler's cotangent frame), so geometry needs no
460
622
  // tangents. UVs run down the image (glTF) while normal maps point +Y up it, hence -b.
461
- vec3 perturbNormal(vec3 n, vec3 m) {
623
+ vec3 perturbNormal(vec3 n, vec3 m, vec2 uv) {
462
624
  vec3 dp1 = dFdx(vWorldPosition), dp2 = dFdy(vWorldPosition);
463
- vec2 duv1 = dFdx(vUv), duv2 = dFdy(vUv);
625
+ vec2 duv1 = dFdx(uv), duv2 = dFdy(uv);
464
626
  vec3 dp2perp = cross(dp2, n), dp1perp = cross(n, dp1);
465
627
  vec3 t = dp2perp * duv1.x + dp1perp * duv2.x;
466
628
  vec3 b = dp2perp * duv1.y + dp1perp * duv2.y;
@@ -526,21 +688,60 @@ float pointShadowAt(vec3 light, vec3 n, float distance) {
526
688
  return lit / 9.0;
527
689
  }
528
690
  #endif
691
+
692
+ #ifdef MATCAP
693
+ // A matcap - a picture of a lit sphere - looked up by the normal as the camera sees it, turned to
694
+ // face along the line of sight so it doesn't stretch towards the edges of the view
695
+ vec3 matcapAt(vec3 n, vec3 v) {
696
+ vec3 nv = normalize(mat3(uView) * n), vv = normalize(mat3(uView) * v);
697
+ vec3 x = normalize(vec3(vv.z, 0.0, -vv.x));
698
+ vec3 y = cross(vv, x);
699
+ vec2 uv = vec2(dot(x, nv), dot(y, nv)) * 0.495 + 0.5;
700
+ // the picture's top is up
701
+ return texture(uMatcap, vec2(uv.x, 1.0 - uv.y)).rgb;
702
+ }
703
+ #endif
704
+
705
+ // light arriving from all round, on a surface facing n: flat, hemisphere and environment light
706
+ vec3 irradianceAt(vec3 n) {
707
+ vec3 e = uAmbient + mix(uHemisphereGround, uHemisphereSky, n.y * 0.5 + 0.5);
708
+ #ifdef ENV_MAP
709
+ e += envIrradiance(n) * uEnvIntensity * uEnvStrength;
710
+ #elif defined(STUDIO)
711
+ e += studio(n) * 0.35 * uEnvStrength;
712
+ #endif
713
+ return e;
714
+ }
715
+
716
+ // what a surface reflects of its surroundings along r - blurrier the rougher it is
717
+ vec3 reflected(vec3 r, vec3 n, float roughness, vec3 irradiance) {
718
+ #ifdef ENV_MAP
719
+ // a lower mip, convolved for that roughness
720
+ return textureLod(uEnvMap, equirectUv(r), roughness * uEnvMaxLod).rgb * uEnvIntensity * uEnvStrength;
721
+ #elif defined(STUDIO)
722
+ // leaned towards the surface normal
723
+ return mix(studio(r), studio(n), roughness * roughness) * uEnvStrength;
724
+ #else
725
+ return irradiance * 0.5;
726
+ #endif
727
+ }
529
728
  #endif
530
729
 
531
730
  void main() {
532
731
  clipAway(vWorldPosition);
533
732
  vec4 base = vec4(uColor * vColor, uOpacity);
534
733
  #ifdef MAP
535
- base *= SAMPLE(uMap); // sRGB textures are decoded to linear by the hardware
734
+ base *= uMapTexel; // sRGB textures are decoded to linear by the hardware
536
735
  #endif
537
736
  #ifdef ALPHA_TEST
538
737
  if (base.a < uAlphaTest) discard;
539
738
  base.a = 1.0;
540
739
  #endif
740
+ // how much of what is behind it this hides: its opacity - less for glass, which lets light through
741
+ float coverage = base.a;
541
742
  vec3 emissive = uEmissive;
542
743
  #ifdef EMISSIVE_MAP
543
- emissive *= SAMPLE(uEmissiveMap).rgb;
744
+ emissive *= uEmissiveMapTexel.rgb;
544
745
  #endif
545
746
 
546
747
  #ifdef LIT
@@ -561,27 +762,64 @@ void main() {
561
762
  if (!gl_FrontFacing) n = -n;
562
763
  #endif
563
764
  #else
564
- vec3 m = texture(uNormalMap, vUv).xyz * 2.0 - 1.0;
765
+ // tangent space from the map's own UVs, so it turns with a rotated map
766
+ vec2 normalUv = uNormalMapCoord;
767
+ vec3 m = texture(uNormalMap, normalUv).xyz * 2.0 - 1.0;
565
768
  m.xy *= uNormalScale;
566
- n = perturbNormal(n, m);
769
+ n = perturbNormal(n, m, normalUv);
567
770
  #endif
568
771
  #endif
569
772
  #ifdef BUMP_MAP
570
773
  n = bumpNormal(n);
571
774
  #endif
572
775
  vec3 v = normalize(uCameraPosition - vWorldPosition);
776
+ #ifdef MATCAP
777
+ // its look from the picture, by the normal: no lights, and its colours kept as they are, as unlit ones are
778
+ vec3 color = base.rgb * matcapAt(n, v) + emissive;
779
+ #else
573
780
  float roughness = uRoughness;
574
781
  float metalness = uMetalness;
575
782
  #ifdef ROUGHNESS_MAP
576
- roughness *= SAMPLE(uRoughnessMap).g;
783
+ roughness *= uRoughnessMapTexel.g;
577
784
  #endif
578
785
  #ifdef METALNESS_MAP
579
- metalness *= SAMPLE(uMetalnessMap).b;
786
+ metalness *= uMetalnessMapTexel.b;
580
787
  #endif
581
788
  roughness = clamp(roughness, 0.04, 1.0);
582
789
  float a = roughness * roughness;
583
790
  vec3 diffuse = base.rgb * (1.0 - metalness);
584
- vec3 f0 = mix(vec3(0.04), base.rgb, metalness);
791
+ vec3 f0 = mix(uSpecular.rgb, base.rgb, metalness);
792
+ float f90 = mix(uSpecular.a, 1.0, metalness);
793
+ #ifdef TOON
794
+ // a cartoon's metal is its colour too, with a highlight of that colour
795
+ diffuse = base.rgb;
796
+ toonHighlight = mix(vec3(uSpecular.a), base.rgb, metalness) * (1.0 - roughness);
797
+ #endif
798
+ #ifdef TRANSMISSION
799
+ // glass: what passes through isn't scattered back, so less diffuse colour - and the reflections
800
+ // stay whole, however clear it is
801
+ diffuse *= 1.0 - uTransmission;
802
+ #endif
803
+ #ifdef CLEARCOAT
804
+ float coatRoughness = clamp(uClearcoatRoughness, 0.04, 1.0);
805
+ float coatA = coatRoughness * coatRoughness;
806
+ vec3 coat = vec3(0.0);
807
+ #endif
808
+ #ifdef SHEEN
809
+ float sheenRoughness = clamp(uSheenRoughness, 0.07, 1.0);
810
+ vec3 sheen = vec3(0.0);
811
+ #endif
812
+ #ifdef ANISOTROPY
813
+ brushT = brushDirection(n);
814
+ brushB = cross(n, brushT);
815
+ brushAT = mix(a, 1.0, uAnisotropy * uAnisotropy);
816
+ brushAB = max(a, 0.001);
817
+ #endif
818
+ #ifdef TRANSLUCENCY
819
+ // thin surfaces: what isn't scattered back comes through, lit from the far side
820
+ vec3 through = diffuse * uTranslucency.rgb;
821
+ diffuse *= 1.0 - uTranslucency.a;
822
+ #endif
585
823
 
586
824
  vec3 color = vec3(0.0);
587
825
  #if NUM_DIRECTIONAL > 0
@@ -591,7 +829,24 @@ void main() {
591
829
  // the shadow-casting light always comes first
592
830
  if (i == 0) radiance *= shadowAt(surfaceNormal, uDirectionalDirection[0]);
593
831
  #endif
594
- color += shade(n, v, uDirectionalDirection[i], radiance, diffuse, f0, a);
832
+ color += shade(n, v, uDirectionalDirection[i], radiance, diffuse, f0, f90, a);
833
+ #ifdef CLEARCOAT
834
+ coat += coatShade(surfaceNormal, v, uDirectionalDirection[i], radiance, coatA);
835
+ #endif
836
+ #ifdef SHEEN
837
+ sheen += sheenShade(n, v, uDirectionalDirection[i], radiance, sheenRoughness * sheenRoughness);
838
+ #endif
839
+ #ifdef TRANSLUCENCY
840
+ float behind = clamp(-dot(n, uDirectionalDirection[i]), 0.0, 1.0);
841
+ if (behind > 0.0) {
842
+ vec3 light = uDirectionalColor[i];
843
+ #ifdef SHADOW
844
+ // shadowed as the far side is: what lights it comes through
845
+ if (i == 0) light *= shadowAt(-surfaceNormal, uDirectionalDirection[0]);
846
+ #endif
847
+ color += through * light * behind;
848
+ }
849
+ #endif
595
850
  }
596
851
  #endif
597
852
  #if NUM_PUNCTUAL > 0
@@ -613,42 +868,89 @@ void main() {
613
868
  #ifdef POINT_SHADOW
614
869
  if (float(i) == uPointShadowIndex) radiance *= pointShadowAt(uPunctualPosition[i], surfaceNormal, d);
615
870
  #endif
616
- color += shade(n, v, l, radiance, diffuse, f0, a);
871
+ color += shade(n, v, l, radiance, diffuse, f0, f90, a);
872
+ #ifdef CLEARCOAT
873
+ coat += coatShade(surfaceNormal, v, l, radiance, coatA);
874
+ #endif
875
+ #ifdef SHEEN
876
+ sheen += sheenShade(n, v, l, radiance, sheenRoughness * sheenRoughness);
877
+ #endif
878
+ #ifdef TRANSLUCENCY
879
+ float behind = clamp(-dot(n, l), 0.0, 1.0);
880
+ if (behind > 0.0) {
881
+ vec3 light = uPunctualColor[i] * falloff * cone;
882
+ #ifdef SPOT_SHADOWS
883
+ if (tile >= 0.0 && cone > 0.0) light *= spotShadowAt(int(tile), -surfaceNormal, d);
884
+ #endif
885
+ #ifdef POINT_SHADOW
886
+ if (float(i) == uPointShadowIndex) light *= pointShadowAt(uPunctualPosition[i], -surfaceNormal, d);
887
+ #endif
888
+ color += through * light * behind;
889
+ }
890
+ #endif
617
891
  }
618
892
  #endif
619
893
 
620
894
  // ambient: flat, hemisphere and environment light
621
- vec3 irradiance = uAmbient + mix(uHemisphereGround, uHemisphereSky, n.y * 0.5 + 0.5);
895
+ vec3 irradiance = irradianceAt(n);
622
896
  float NoV = clamp(abs(dot(n, v)), 1e-4, 1.0);
623
- vec3 brdf = envBRDF(f0, roughness, NoV);
624
- vec3 ambientSpecular;
625
- #ifdef ENV_MAP
626
- irradiance += envIrradiance(n) * uEnvIntensity;
627
- vec3 r = reflect(-v, n);
628
- // rougher surfaces see a blurrier reflection: a lower mip, convolved for that roughness
629
- ambientSpecular = textureLod(uEnvMap, equirectUv(r), roughness * uEnvMaxLod).rgb * uEnvIntensity * brdf;
630
- #elif defined(STUDIO)
631
- irradiance += studio(n) * 0.35;
632
- vec3 r = reflect(-v, n);
633
- // rougher surfaces see a blurrier reflection: lean it towards the surface normal
634
- ambientSpecular = mix(studio(r), studio(n), roughness * roughness) * brdf;
897
+ vec3 brdf = envBRDF(f0, f90, roughness, NoV);
898
+ #ifdef ANISOTROPY
899
+ // reflections drawn out along the brushing: off a normal bent across it (KHR_materials_anisotropy)
900
+ vec3 brushNormal = cross(cross(brushB, v), brushB);
901
+ float bend = 1.0 - uAnisotropy * (1.0 - roughness);
902
+ bend *= bend;
903
+ vec3 r = reflect(-v, normalize(mix(brushNormal, n, bend * bend)));
635
904
  #else
636
- ambientSpecular = irradiance * brdf * 0.5;
905
+ vec3 r = reflect(-v, n);
637
906
  #endif
907
+ #ifdef TOON
908
+ // a flat fill, so the tones stay flat: the light from all round, averaged over up and down
909
+ vec3 indirect = diffuse * 0.5 * (irradianceAt(vec3(0.0, 1.0, 0.0)) + irradianceAt(vec3(0.0, -1.0, 0.0)));
910
+ #else
911
+ vec3 ambientSpecular = reflected(r, n, roughness, irradiance) * brdf;
638
912
  vec3 indirect = diffuse * irradiance + ambientSpecular;
913
+ #endif
914
+ #ifdef TRANSLUCENCY
915
+ indirect += through * irradianceAt(-n);
916
+ #endif
917
+ #ifdef LIGHT_MAP
918
+ // light baked into a texture, as more light from all round
919
+ indirect += diffuse * uLightMapTexel.rgb * uLightMapIntensity;
920
+ #endif
639
921
  #ifdef AO_MAP
640
- indirect *= mix(1.0, SAMPLE(uAoMap).r, uAoMapIntensity);
922
+ indirect *= mix(1.0, uAoMapTexel.r, uAoMapIntensity);
641
923
  #endif
642
924
  color += indirect + emissive;
925
+ #ifdef SHEEN
926
+ // over the base, which loses a little of its light to it
927
+ sheen += irradiance * uSheenColor * sheenEnvironment(NoV, sheenRoughness);
928
+ color = color * (1.0 - 0.157 * max(uSheenColor.r, max(uSheenColor.g, uSheenColor.b))) + sheen;
929
+ #endif
930
+ #ifdef CLEARCOAT
931
+ // the coat's reflection, over the base - which loses the light the coat reflects away
932
+ float coatNoV = clamp(abs(dot(surfaceNormal, v)), 1e-4, 1.0);
933
+ coat += reflected(reflect(-v, surfaceNormal), surfaceNormal, coatRoughness, irradiance) * envBRDF(vec3(0.04), 1.0, coatRoughness, coatNoV);
934
+ color = color * (1.0 - uClearcoat * F_Schlick(coatNoV, vec3(0.04), 1.0).x) + coat * uClearcoat;
935
+ #endif
936
+ #ifdef TRANSMISSION
937
+ // most of what is behind glass shows through face on; towards its edges, where it reflects nearly
938
+ // everything (Fresnel), less and less
939
+ coverage *= mix(1.0, clamp(max(brdf.r, max(brdf.g, brdf.b)), 0.0, 1.0), uTransmission);
940
+ #endif
643
941
  color = toneMap(color);
942
+ #endif
644
943
  #else
645
944
  vec3 color = base.rgb + emissive;
646
945
  #endif
647
946
  color = mix(color, uHighlight.rgb, vHighlight * uHighlight.a);
947
+ // a highlight shows on glass as on anything else
948
+ coverage = mix(coverage, base.a, vHighlight * uHighlight.a);
648
949
  color = fogged(color);
649
950
 
650
- // premultiplied alpha, so the canvas composites correctly over the page
651
- fragColor = vec4(linearToSrgb(color) * base.a, base.a);
951
+ // premultiplied alpha, so the canvas composites correctly over the page. The colour is the light
952
+ // the surface sends back - faded with its opacity; coverage is how much of what is behind it hides
953
+ fragColor = vec4(linearToSrgb(color) * base.a, coverage);
652
954
  }
653
955
  `
654
956