@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.
Files changed (259) hide show
  1. package/CHANGELOG.json +127 -0
  2. package/README.md +1 -1
  3. package/dist/geometry/meshConcat.js +2 -0
  4. package/dist/index.d.ts +11 -4
  5. package/dist/index.js +22 -1
  6. package/dist/math/scalar.js +1 -1
  7. package/dist/render/anisotropicLobe.d.ts +7 -0
  8. package/dist/render/anisotropicLobe.js +33 -0
  9. package/dist/render/backend/api.d.ts +41 -4
  10. package/dist/render/backend/materialChanges.d.ts +7 -2
  11. package/dist/render/backend/materialChanges.js +8 -3
  12. package/dist/render/backend/select.js +8 -0
  13. package/dist/render/backend/webgl2/clothTextures.d.ts +27 -0
  14. package/dist/render/backend/webgl2/clothTextures.js +93 -0
  15. package/dist/render/backend/webgl2/renderer.d.ts +159 -3
  16. package/dist/render/backend/webgl2/renderer.js +662 -80
  17. package/dist/render/backend/webgl2/skinScatter.d.ts +50 -0
  18. package/dist/render/backend/webgl2/skinScatter.js +256 -0
  19. package/dist/render/backend/webgpu/buffers.d.ts +25 -11
  20. package/dist/render/backend/webgpu/buffers.js +98 -49
  21. package/dist/render/backend/webgpu/causticsPass.js +2 -1
  22. package/dist/render/backend/webgpu/clothSolvePack.d.ts +27 -0
  23. package/dist/render/backend/webgpu/clothSolvePack.js +129 -0
  24. package/dist/render/backend/webgpu/clothTextures.d.ts +68 -0
  25. package/dist/render/backend/webgpu/clothTextures.js +145 -0
  26. package/dist/render/backend/webgpu/compressedUpload.d.ts +18 -0
  27. package/dist/render/backend/webgpu/compressedUpload.js +38 -0
  28. package/dist/render/backend/webgpu/computeHost.d.ts +21 -0
  29. package/dist/render/backend/webgpu/computeHost.js +5 -0
  30. package/dist/render/backend/webgpu/depthPass.d.ts +29 -9
  31. package/dist/render/backend/webgpu/depthPass.js +43 -12
  32. package/dist/render/backend/webgpu/drawCommand.js +6 -2
  33. package/dist/render/backend/webgpu/filmPass.js +2 -1
  34. package/dist/render/backend/webgpu/flatPass.d.ts +43 -4
  35. package/dist/render/backend/webgpu/flatPass.js +111 -13
  36. package/dist/render/backend/webgpu/gpuSkinnedCloth.d.ts +74 -0
  37. package/dist/render/backend/webgpu/gpuSkinnedCloth.js +452 -0
  38. package/dist/render/backend/webgpu/linePass.js +2 -1
  39. package/dist/render/backend/webgpu/motionPass.d.ts +20 -0
  40. package/dist/render/backend/webgpu/motionPass.js +106 -9
  41. package/dist/render/backend/webgpu/panelPass.js +2 -1
  42. package/dist/render/backend/webgpu/particlePass.js +2 -1
  43. package/dist/render/backend/webgpu/plumePass.js +2 -1
  44. package/dist/render/backend/webgpu/postPass.d.ts +15 -3
  45. package/dist/render/backend/webgpu/postPass.js +25 -8
  46. package/dist/render/backend/webgpu/renderer.d.ts +123 -6
  47. package/dist/render/backend/webgpu/renderer.js +534 -58
  48. package/dist/render/backend/webgpu/sdfTextPass.js +2 -1
  49. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +33 -0
  50. package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +469 -0
  51. package/dist/render/backend/webgpu/skinScatterPass.d.ts +51 -0
  52. package/dist/render/backend/webgpu/skinScatterPass.js +253 -0
  53. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +4 -1
  54. package/dist/render/backend/webgpu/surfaceTexturePass.js +37 -12
  55. package/dist/render/backend/webgpu/textPass.js +2 -1
  56. package/dist/render/backend/webgpu/waterPass.js +2 -1
  57. package/dist/render/backend/webgpu/windStreakPass.js +2 -1
  58. package/dist/render/boltBatch.js +2 -1
  59. package/dist/render/causticsRenderer.js +2 -1
  60. package/dist/render/clothBindingData.d.ts +41 -0
  61. package/dist/render/clothBindingData.js +72 -0
  62. package/dist/render/clothPlacement.d.ts +25 -0
  63. package/dist/render/clothPlacement.js +85 -0
  64. package/dist/render/compressedSource.d.ts +66 -0
  65. package/dist/render/compressedSource.js +138 -0
  66. package/dist/render/cutoutCaster.d.ts +5 -1
  67. package/dist/render/cutoutCaster.js +6 -0
  68. package/dist/render/cutoutDither.d.ts +51 -0
  69. package/dist/render/cutoutDither.js +90 -0
  70. package/dist/render/decalPass.js +2 -1
  71. package/dist/render/eyeRefraction.d.ts +35 -0
  72. package/dist/render/eyeRefraction.js +40 -0
  73. package/dist/render/glCompressed.d.ts +27 -0
  74. package/dist/render/glCompressed.js +53 -0
  75. package/dist/render/globalMediumPass.js +3 -2
  76. package/dist/render/hairLobes.d.ts +70 -0
  77. package/dist/render/hairLobes.js +120 -0
  78. package/dist/render/lightBudget.d.ts +22 -0
  79. package/dist/render/lightBudget.js +22 -0
  80. package/dist/render/lineBatch.js +5 -1
  81. package/dist/render/mesh.d.ts +7 -0
  82. package/dist/render/mesh.js +24 -0
  83. package/dist/render/oitPass.js +2 -1
  84. package/dist/render/particleBatch.js +3 -2
  85. package/dist/render/particlePool.d.ts +6 -2
  86. package/dist/render/plumeRenderer.js +3 -2
  87. package/dist/render/recon/motionVectors.d.ts +4 -2
  88. package/dist/render/recon/motionVectors.js +6 -3
  89. package/dist/render/renderQuality.d.ts +29 -2
  90. package/dist/render/renderQuality.js +5 -0
  91. package/dist/render/rendererHarness.js +4 -0
  92. package/dist/render/sceneCoverage.d.ts +54 -0
  93. package/dist/render/sceneCoverage.js +72 -0
  94. package/dist/render/sceneTarget.d.ts +12 -0
  95. package/dist/render/sceneTarget.js +14 -0
  96. package/dist/render/sdfTextRenderer.js +2 -1
  97. package/dist/render/shaders/clothBinding.d.ts +22 -0
  98. package/dist/render/shaders/clothBinding.js +80 -0
  99. package/dist/render/shaders/depth.js +45 -8
  100. package/dist/render/shaders/flat/anisotropic.d.ts +15 -0
  101. package/dist/render/shaders/flat/anisotropic.js +68 -0
  102. package/dist/render/shaders/flat/eye.d.ts +1 -0
  103. package/dist/render/shaders/flat/eye.js +81 -0
  104. package/dist/render/shaders/flat/hair.d.ts +1 -0
  105. package/dist/render/shaders/flat/hair.js +101 -0
  106. package/dist/render/shaders/flat/index.js +42 -3
  107. package/dist/render/shaders/flat/main.d.ts +1 -1
  108. package/dist/render/shaders/flat/main.js +113 -17
  109. package/dist/render/shaders/flat/models.d.ts +14 -0
  110. package/dist/render/shaders/flat/models.js +273 -0
  111. package/dist/render/shaders/flat/preamble.js +26 -24
  112. package/dist/render/shaders/flat/skin.d.ts +1 -0
  113. package/dist/render/shaders/flat/skin.js +114 -0
  114. package/dist/render/shaders/generated/depth.wgsl.d.ts +66 -24
  115. package/dist/render/shaders/generated/depth.wgsl.js +66 -24
  116. package/dist/render/shaders/generated/flat.wgsl.d.ts +1815 -1704
  117. package/dist/render/shaders/generated/flat.wgsl.js +1853 -1742
  118. package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
  119. package/dist/render/shaders/generated/line.wgsl.js +1 -1
  120. package/dist/render/shaders/generated/particle.wgsl.d.ts +3 -3
  121. package/dist/render/shaders/generated/particle.wgsl.js +3 -3
  122. package/dist/render/shaders/generated/rush.wgsl.d.ts +1 -1
  123. package/dist/render/shaders/generated/rush.wgsl.js +1 -1
  124. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +1 -1
  125. package/dist/render/shaders/generated/sdfText.wgsl.js +1 -1
  126. package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +48 -0
  127. package/dist/render/shaders/generated/skinBlur.wgsl.js +54 -0
  128. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +1 -1
  129. package/dist/render/shaders/generated/temporalResolve.wgsl.js +1 -1
  130. package/dist/render/shaders/line.d.ts +1 -1
  131. package/dist/render/shaders/outputTransform.d.ts +14 -1
  132. package/dist/render/shaders/outputTransform.js +23 -1
  133. package/dist/render/shaders/particle.d.ts +3 -3
  134. package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
  135. package/dist/render/shaders/recon/motion.wgsl.js +102 -0
  136. package/dist/render/shaders/recon/resolve.wgsl.js +15 -1
  137. package/dist/render/shaders/rush.js +13 -2
  138. package/dist/render/shaders/sdfText.d.ts +1 -1
  139. package/dist/render/shaders/skinBlur.d.ts +1 -0
  140. package/dist/render/shaders/skinBlur.js +103 -0
  141. package/dist/render/shaders/skinning.d.ts +3 -1
  142. package/dist/render/shaders/skinning.js +31 -4
  143. package/dist/render/shaders/temporalResolve.js +12 -5
  144. package/dist/render/shadowCasters.d.ts +13 -1
  145. package/dist/render/skinBlur.d.ts +41 -0
  146. package/dist/render/skinBlur.js +49 -0
  147. package/dist/render/skinScatter.d.ts +67 -0
  148. package/dist/render/skinScatter.js +80 -0
  149. package/dist/render/skinnedClothRun.d.ts +26 -0
  150. package/dist/render/skinnedClothRun.js +84 -0
  151. package/dist/render/ssrPass.js +2 -1
  152. package/dist/render/surfaceModel.d.ts +129 -0
  153. package/dist/render/surfaceModel.js +152 -0
  154. package/dist/render/surfaceTexture.d.ts +55 -4
  155. package/dist/render/surfaceTexture.js +60 -46
  156. package/dist/render/textRenderer.js +2 -1
  157. package/dist/render/vertexDefaults.js +8 -0
  158. package/dist/render/waterRenderer.js +2 -1
  159. package/dist/render/windStreakRenderer.js +2 -1
  160. package/package.json +6 -6
  161. package/src/geometry/meshConcat.ts +2 -0
  162. package/src/index.ts +53 -1
  163. package/src/math/scalar.ts +1 -1
  164. package/src/render/anisotropicLobe.ts +41 -0
  165. package/src/render/backend/api.ts +52 -3
  166. package/src/render/backend/materialChanges.ts +8 -3
  167. package/src/render/backend/select.ts +8 -0
  168. package/src/render/backend/webgl2/clothTextures.ts +120 -0
  169. package/src/render/backend/webgl2/renderer.ts +750 -80
  170. package/src/render/backend/webgl2/skinScatter.ts +309 -0
  171. package/src/render/backend/webgpu/buffers.ts +137 -63
  172. package/src/render/backend/webgpu/causticsPass.ts +2 -1
  173. package/src/render/backend/webgpu/clothSolvePack.ts +161 -0
  174. package/src/render/backend/webgpu/clothTextures.ts +194 -0
  175. package/src/render/backend/webgpu/compressedUpload.ts +65 -0
  176. package/src/render/backend/webgpu/computeHost.ts +25 -0
  177. package/src/render/backend/webgpu/depthPass.ts +43 -10
  178. package/src/render/backend/webgpu/drawCommand.ts +6 -2
  179. package/src/render/backend/webgpu/filmPass.ts +2 -1
  180. package/src/render/backend/webgpu/flatPass.ts +147 -8
  181. package/src/render/backend/webgpu/gpuSkinnedCloth.ts +502 -0
  182. package/src/render/backend/webgpu/linePass.ts +2 -1
  183. package/src/render/backend/webgpu/motionPass.ts +128 -9
  184. package/src/render/backend/webgpu/panelPass.ts +2 -1
  185. package/src/render/backend/webgpu/particlePass.ts +2 -1
  186. package/src/render/backend/webgpu/plumePass.ts +2 -1
  187. package/src/render/backend/webgpu/postPass.ts +25 -6
  188. package/src/render/backend/webgpu/renderer.ts +701 -52
  189. package/src/render/backend/webgpu/sdfTextPass.ts +2 -1
  190. package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +470 -0
  191. package/src/render/backend/webgpu/skinScatterPass.ts +309 -0
  192. package/src/render/backend/webgpu/surfaceTexturePass.ts +42 -12
  193. package/src/render/backend/webgpu/textPass.ts +2 -1
  194. package/src/render/backend/webgpu/waterPass.ts +2 -1
  195. package/src/render/backend/webgpu/windStreakPass.ts +2 -1
  196. package/src/render/boltBatch.ts +2 -1
  197. package/src/render/causticsRenderer.ts +2 -1
  198. package/src/render/clothBindingData.ts +89 -0
  199. package/src/render/clothPlacement.ts +114 -0
  200. package/src/render/compressedSource.ts +198 -0
  201. package/src/render/cutoutCaster.ts +20 -2
  202. package/src/render/cutoutDither.ts +99 -0
  203. package/src/render/decalPass.ts +2 -1
  204. package/src/render/eyeRefraction.ts +72 -0
  205. package/src/render/glCompressed.ts +86 -0
  206. package/src/render/globalMediumPass.ts +3 -2
  207. package/src/render/hairLobes.ts +188 -0
  208. package/src/render/lightBudget.ts +25 -0
  209. package/src/render/lineBatch.ts +3 -1
  210. package/src/render/mesh.ts +23 -0
  211. package/src/render/oitPass.ts +2 -1
  212. package/src/render/particleBatch.ts +3 -2
  213. package/src/render/particlePool.ts +6 -2
  214. package/src/render/plumeRenderer.ts +3 -2
  215. package/src/render/recon/motionVectors.ts +6 -3
  216. package/src/render/renderQuality.ts +37 -2
  217. package/src/render/rendererHarness.ts +4 -0
  218. package/src/render/sceneCoverage.ts +78 -0
  219. package/src/render/sceneTarget.ts +21 -0
  220. package/src/render/sdfTextRenderer.ts +2 -1
  221. package/src/render/shaders/clothBinding.ts +81 -0
  222. package/src/render/shaders/depth.ts +45 -8
  223. package/src/render/shaders/flat/anisotropic.ts +68 -0
  224. package/src/render/shaders/flat/eye.ts +82 -0
  225. package/src/render/shaders/flat/hair.ts +102 -0
  226. package/src/render/shaders/flat/index.ts +42 -3
  227. package/src/render/shaders/flat/main.ts +113 -17
  228. package/src/render/shaders/flat/models.ts +286 -0
  229. package/src/render/shaders/flat/preamble.ts +26 -24
  230. package/src/render/shaders/flat/skin.ts +116 -0
  231. package/src/render/shaders/generated/depth.wgsl.ts +66 -24
  232. package/src/render/shaders/generated/flat.wgsl.ts +1853 -1742
  233. package/src/render/shaders/generated/line.wgsl.ts +1 -1
  234. package/src/render/shaders/generated/particle.wgsl.ts +3 -3
  235. package/src/render/shaders/generated/rush.wgsl.ts +1 -1
  236. package/src/render/shaders/generated/sdfText.wgsl.ts +1 -1
  237. package/src/render/shaders/generated/skinBlur.wgsl.ts +56 -0
  238. package/src/render/shaders/generated/temporalResolve.wgsl.ts +1 -1
  239. package/src/render/shaders/outputTransform.ts +24 -1
  240. package/src/render/shaders/recon/motion.wgsl.ts +102 -0
  241. package/src/render/shaders/recon/resolve.wgsl.ts +15 -1
  242. package/src/render/shaders/rush.ts +13 -2
  243. package/src/render/shaders/skinBlur.ts +105 -0
  244. package/src/render/shaders/skinning.ts +31 -4
  245. package/src/render/shaders/temporalResolve.ts +12 -5
  246. package/src/render/shadowCasters.ts +13 -0
  247. package/src/render/skinBlur.ts +53 -0
  248. package/src/render/skinScatter.ts +92 -0
  249. package/src/render/skinnedClothRun.ts +128 -0
  250. package/src/render/ssrPass.ts +2 -1
  251. package/src/render/surfaceModel.ts +274 -0
  252. package/src/render/surfaceTexture.ts +134 -63
  253. package/src/render/textRenderer.ts +2 -1
  254. package/src/render/vertexDefaults.ts +8 -0
  255. package/src/render/waterRenderer.ts +2 -1
  256. package/src/render/windStreakRenderer.ts +2 -1
  257. package/dist/math/exact.d.ts +0 -76
  258. package/dist/math/exact.js +0 -327
  259. package/src/math/exact.ts +0 -340
@@ -0,0 +1,72 @@
1
+ /**
2
+ * What a blended draw into the scene does to its alpha, which holds how much of each pixel is still
3
+ * the opaque surface — the share ambient occlusion is allowed to darken.
4
+ *
5
+ * **Occlusion is applied in the composite, from the depth the opaque world left**, after every
6
+ * translucent draw has landed, and a particle or a pane writes no depth: so a puff of smoke in front
7
+ * of a wall was darkened by the corner behind it. Reported from a game as walls and two figures
8
+ * standing behind a cloud showing through it as outlines, and nothing at all with occlusion off.
9
+ *
10
+ * So the scene's alpha is a running share. The frame clears it to 1 and an opaque draw writes 1,
11
+ * which is the whole of the surface; then each draw blended in front of it says which of three
12
+ * things it is, and the composite darkens `share` of the pixel and leaves the rest:
13
+ *
14
+ * - **It covers**: glass, smoke, water, a line, text in the world, a panel. The share falls by what
15
+ * the draw lets through, `share * (1 - a)`.
16
+ * - **It transmits**: the medium, the order-independent composite. Its alpha already *is* what gets
17
+ * through, `share * a`.
18
+ * - **It keeps**: light added to the surface or a layer of the surface itself — caustics, a light
19
+ * volume, a bolt, a wet film, a decal, a reflection. The share is unchanged.
20
+ *
21
+ * **What it gives up** is added light in front of an occluded corner: a glow or a spark adds to the
22
+ * pixel and keeps the share, so the composite darkens what it added along with the surface — which
23
+ * is how every blended draw behaved before this, and one multiply cannot tell the two apart. **What
24
+ * would make it wrong** is an opaque pass writing an alpha below 1: its surface would then take less
25
+ * occlusion than it should, which is the safe direction, and is the lit shader's own case at a
26
+ * cutout's edge or under a per-vertex alpha lane.
27
+ *
28
+ * **It survives the temporal passes.** A temporal resolve passes the frame's alpha through, since
29
+ * it is a fact about this frame and takes no history; a reconstruction reads it from the scene it
30
+ * rebuilt into the picture it shows, and the blended draws that land after it cover that picture.
31
+ *
32
+ * **One pipeline does not take part**: the GPU-driven pass blends its own surfaces into a target
33
+ * whose alpha is the mask its blit reads to tell a drawn pixel from an undrawn one, and that blit
34
+ * writes the share back to 1. Its panes still take the occlusion of what they cover. What would
35
+ * close it is a channel for that mask other than alpha.
36
+ *
37
+ * A contributed pass drawing something blended into the scene states the same thing for its own
38
+ * pipeline with `SCENE_ALPHA_COVERS`, `SCENE_ALPHA_TRANSMITS` or `SCENE_ALPHA_KEEPS`.
39
+ */
40
+ /** A blended draw that covers the surface by its alpha: `share * (1 - a)`. */
41
+ export const SCENE_ALPHA_COVERS = {
42
+ srcFactor: 'zero',
43
+ dstFactor: 'one-minus-src-alpha',
44
+ operation: 'add',
45
+ };
46
+ /** A draw whose alpha is what gets through it, a transmittance or a revealage: `share * a`. */
47
+ export const SCENE_ALPHA_TRANSMITS = {
48
+ srcFactor: 'zero',
49
+ dstFactor: 'src-alpha',
50
+ operation: 'add',
51
+ };
52
+ /** Light added to the surface, or a layer of it: the share unchanged. */
53
+ export const SCENE_ALPHA_KEEPS = {
54
+ srcFactor: 'zero',
55
+ dstFactor: 'one',
56
+ operation: 'add',
57
+ };
58
+ /**
59
+ * The same three on WebGL2, as the alpha half of `blendFuncSeparate` beside the colour half a
60
+ * caller already had. `src` and `dst` are the colour factors.
61
+ */
62
+ export function blendCovering(gl, src, dst) {
63
+ gl.blendFuncSeparate(src, dst, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);
64
+ }
65
+ /** `SCENE_ALPHA_TRANSMITS` on WebGL2, beside the colour factors `src` and `dst`. */
66
+ export function blendTransmitting(gl, src, dst) {
67
+ gl.blendFuncSeparate(src, dst, gl.ZERO, gl.SRC_ALPHA);
68
+ }
69
+ /** `SCENE_ALPHA_KEEPS` on WebGL2, beside the colour factors `src` and `dst`. */
70
+ export function blendKeeping(gl, src, dst) {
71
+ gl.blendFuncSeparate(src, dst, gl.ZERO, gl.ONE);
72
+ }
@@ -195,6 +195,18 @@ export declare class SceneTarget {
195
195
  * must reject it in the same place or the accumulation and the revealage cover different
196
196
  * fragments.
197
197
  */
198
+ /**
199
+ * The depth the frame draws against, for a second framebuffer to share: skin's diffuse half,
200
+ * which tests against it and writes none. The multisampled renderbuffer where the frame is
201
+ * multisampled, the texture otherwise, with the size and sample count a colour beside it needs.
202
+ */
203
+ sharedDepth(): {
204
+ texture: WebGLTexture | null;
205
+ renderbuffer: WebGLRenderbuffer | null;
206
+ samples: number;
207
+ width: number;
208
+ height: number;
209
+ };
198
210
  depthAttachment(): WebGLTexture | null;
199
211
  /**
200
212
  * The colour the frame has drawn so far, for a pass that reads the picture rather than the depth.
@@ -432,6 +432,20 @@ export class SceneTarget {
432
432
  * must reject it in the same place or the accumulation and the revealage cover different
433
433
  * fragments.
434
434
  */
435
+ /**
436
+ * The depth the frame draws against, for a second framebuffer to share: skin's diffuse half,
437
+ * which tests against it and writes none. The multisampled renderbuffer where the frame is
438
+ * multisampled, the texture otherwise, with the size and sample count a colour beside it needs.
439
+ */
440
+ sharedDepth() {
441
+ return {
442
+ texture: this.msaaDepth === null ? this.depth : null,
443
+ renderbuffer: this.msaaDepth,
444
+ samples: this.samples,
445
+ width: this.width,
446
+ height: this.height,
447
+ };
448
+ }
435
449
  depthAttachment() {
436
450
  return this.depth;
437
451
  }
@@ -2,6 +2,7 @@ import { SdfTextLayout } from './sdfTextLayout.js';
2
2
  import { compileProgram, uniformLocations } from './shader.js';
3
3
  import { SDF_TEXT_FRAG, SDF_TEXT_VERT } from './shaders/sdfText.js';
4
4
  import { SDF_TEXT_TEXTURE_UNIT } from './lightBudget.js';
5
+ import { blendCovering } from './sceneCoverage.js';
5
6
  /**
6
7
  * Text from a signed distance field, on WebGL2.
7
8
  *
@@ -126,7 +127,7 @@ export class SdfTextRenderer {
126
127
  */
127
128
  gl.depthMask(false);
128
129
  gl.enable(gl.BLEND);
129
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
130
+ blendCovering(gl, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
130
131
  this.atlas.bind(gl, SDF_TEXT_TEXTURE_UNIT);
131
132
  const u = this.uniforms;
132
133
  gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The vertex-stage cloth binding: a skinned vertex moved toward the cloth simulation's triangle it
3
+ * is bound to, by a weight painted per vertex — the GLSL of `clothPlacement.ts`, line for line.
4
+ *
5
+ * **Textures read by vertex index, not attributes**, for the reason morph targets are: the skinned
6
+ * variants already use all sixteen attribute locations once eight influences are in. Three
7
+ * textures, each wrapped onto rows at its own width (read back with `textureSize`, so no uniform
8
+ * joins the vertex block and no shared field moves):
9
+ *
10
+ * - **the binding**, two texels a render vertex: the three particle indices and the weight, then
11
+ * the barycentric (u, v) for the second and third particle and the offset along the normal;
12
+ * - **the particles now**, one texel a particle, world space — the simulation's output;
13
+ * - **the particles at rest**, one texel a particle, model space — the rest frames are built from
14
+ * these, so a vertex's own bind-pose normal turns as its triangle has turned.
15
+ *
16
+ * **A pipeline constant, not a permutation**, as `SKIN_EIGHT` is: declared in the skinned variants
17
+ * and folded away where a draw binds no cloth. The textures are bound either way, to stand-ins
18
+ * where there is no cloth.
19
+ */
20
+ export declare const CLOTH_BINDING_GLSL = "\n/** Whether this pipeline places vertices by a cloth binding. */\nconst bool CLOTH_BOUND = false; // wgsl:override\nuniform highp sampler2D uClothBinding;\nuniform highp sampler2D uClothParticles;\nuniform highp sampler2D uClothRest;\n\n/** Texel i of a texture wrapped onto rows of the given width. */\nivec2 clothTexel(int i, int width) {\n return ivec2(i % width, i / width);\n}\n\n/** A triangle's frame: its first edge, the normal crossed with it, and its normal. */\nmat3 clothFrame(vec3 a, vec3 b, vec3 c) {\n vec3 edge = b - a;\n vec3 n = normalize(cross(edge, c - a));\n vec3 t = normalize(edge);\n return mat3(t, cross(n, t), n);\n}\n\n/**\n * Where this vertex's triangle puts it, the turn its normal takes, and how far it follows (the\n * painted weight: 0 skinned, 1 cloth). One particle named three times is a vertex bound to that\n * particle alone: on it, with no turn.\n */\nfloat clothPlace(out vec3 position, out mat3 turn) {\n int bindingWidth = textureSize(uClothBinding, 0).x;\n int particleWidth = textureSize(uClothParticles, 0).x;\n int restWidth = textureSize(uClothRest, 0).x;\n vec4 indices = texelFetch(uClothBinding, clothTexel(gl_VertexID * 2, bindingWidth), 0);\n vec4 at = texelFetch(uClothBinding, clothTexel(gl_VertexID * 2 + 1, bindingWidth), 0);\n int a = int(indices.x);\n int b = int(indices.y);\n int c = int(indices.z);\n vec3 pa = texelFetch(uClothParticles, clothTexel(a, particleWidth), 0).xyz;\n if (a == b && b == c) {\n position = pa;\n turn = mat3(1.0);\n return indices.w;\n }\n vec3 pb = texelFetch(uClothParticles, clothTexel(b, particleWidth), 0).xyz;\n vec3 pc = texelFetch(uClothParticles, clothTexel(c, particleWidth), 0).xyz;\n mat3 now = clothFrame(pa, pb, pc);\n mat3 rest = clothFrame(\n texelFetch(uClothRest, clothTexel(a, restWidth), 0).xyz,\n texelFetch(uClothRest, clothTexel(b, restWidth), 0).xyz,\n texelFetch(uClothRest, clothTexel(c, restWidth), 0).xyz\n );\n position = pa * (1.0 - at.x - at.y) + pb * at.x + pc * at.y + now[2] * at.z;\n turn = now * transpose(rest);\n return indices.w;\n}\n";
21
+ /** A skinned vertex stage with the cloth binding on: the same source, the constant flipped. */
22
+ export declare function clothBound(source: string): string;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The vertex-stage cloth binding: a skinned vertex moved toward the cloth simulation's triangle it
3
+ * is bound to, by a weight painted per vertex — the GLSL of `clothPlacement.ts`, line for line.
4
+ *
5
+ * **Textures read by vertex index, not attributes**, for the reason morph targets are: the skinned
6
+ * variants already use all sixteen attribute locations once eight influences are in. Three
7
+ * textures, each wrapped onto rows at its own width (read back with `textureSize`, so no uniform
8
+ * joins the vertex block and no shared field moves):
9
+ *
10
+ * - **the binding**, two texels a render vertex: the three particle indices and the weight, then
11
+ * the barycentric (u, v) for the second and third particle and the offset along the normal;
12
+ * - **the particles now**, one texel a particle, world space — the simulation's output;
13
+ * - **the particles at rest**, one texel a particle, model space — the rest frames are built from
14
+ * these, so a vertex's own bind-pose normal turns as its triangle has turned.
15
+ *
16
+ * **A pipeline constant, not a permutation**, as `SKIN_EIGHT` is: declared in the skinned variants
17
+ * and folded away where a draw binds no cloth. The textures are bound either way, to stand-ins
18
+ * where there is no cloth.
19
+ */
20
+ export const CLOTH_BINDING_GLSL = `
21
+ /** Whether this pipeline places vertices by a cloth binding. */
22
+ const bool CLOTH_BOUND = false; // wgsl:override
23
+ uniform highp sampler2D uClothBinding;
24
+ uniform highp sampler2D uClothParticles;
25
+ uniform highp sampler2D uClothRest;
26
+
27
+ /** Texel i of a texture wrapped onto rows of the given width. */
28
+ ivec2 clothTexel(int i, int width) {
29
+ return ivec2(i % width, i / width);
30
+ }
31
+
32
+ /** A triangle's frame: its first edge, the normal crossed with it, and its normal. */
33
+ mat3 clothFrame(vec3 a, vec3 b, vec3 c) {
34
+ vec3 edge = b - a;
35
+ vec3 n = normalize(cross(edge, c - a));
36
+ vec3 t = normalize(edge);
37
+ return mat3(t, cross(n, t), n);
38
+ }
39
+
40
+ /**
41
+ * Where this vertex's triangle puts it, the turn its normal takes, and how far it follows (the
42
+ * painted weight: 0 skinned, 1 cloth). One particle named three times is a vertex bound to that
43
+ * particle alone: on it, with no turn.
44
+ */
45
+ float clothPlace(out vec3 position, out mat3 turn) {
46
+ int bindingWidth = textureSize(uClothBinding, 0).x;
47
+ int particleWidth = textureSize(uClothParticles, 0).x;
48
+ int restWidth = textureSize(uClothRest, 0).x;
49
+ vec4 indices = texelFetch(uClothBinding, clothTexel(gl_VertexID * 2, bindingWidth), 0);
50
+ vec4 at = texelFetch(uClothBinding, clothTexel(gl_VertexID * 2 + 1, bindingWidth), 0);
51
+ int a = int(indices.x);
52
+ int b = int(indices.y);
53
+ int c = int(indices.z);
54
+ vec3 pa = texelFetch(uClothParticles, clothTexel(a, particleWidth), 0).xyz;
55
+ if (a == b && b == c) {
56
+ position = pa;
57
+ turn = mat3(1.0);
58
+ return indices.w;
59
+ }
60
+ vec3 pb = texelFetch(uClothParticles, clothTexel(b, particleWidth), 0).xyz;
61
+ vec3 pc = texelFetch(uClothParticles, clothTexel(c, particleWidth), 0).xyz;
62
+ mat3 now = clothFrame(pa, pb, pc);
63
+ mat3 rest = clothFrame(
64
+ texelFetch(uClothRest, clothTexel(a, restWidth), 0).xyz,
65
+ texelFetch(uClothRest, clothTexel(b, restWidth), 0).xyz,
66
+ texelFetch(uClothRest, clothTexel(c, restWidth), 0).xyz
67
+ );
68
+ position = pa * (1.0 - at.x - at.y) + pb * at.x + pc * at.y + now[2] * at.z;
69
+ turn = now * transpose(rest);
70
+ return indices.w;
71
+ }
72
+ `;
73
+ /** A skinned vertex stage with the cloth binding on: the same source, the constant flipped. */
74
+ export function clothBound(source) {
75
+ const off = 'const bool CLOTH_BOUND = false;';
76
+ if (!source.includes(off)) {
77
+ throw new Error('clothBound: this vertex stage declares no CLOTH_BOUND constant');
78
+ }
79
+ return source.replace(off, 'const bool CLOTH_BOUND = true;');
80
+ }
@@ -1,7 +1,9 @@
1
1
  import { resolveConditionals } from './conditionals.js';
2
2
  import { SUN_STATIC_LAYER } from '../shadowMap.js';
3
3
  import { CUTOUT_COVERAGE_GLSL } from './cutoutCoverage.js';
4
+ import { CUTOUT_DITHER_GLSL } from '../cutoutDither.js';
4
5
  import { SKINNING_GLSL } from './skinning.js';
6
+ import { CLOTH_BINDING_GLSL } from './clothBinding.js';
5
7
  import { CHANNEL_ATTRIBUTE, CHANNEL_BEND } from './vertexChannel.js';
6
8
  /**
7
9
  * Depth-only shadow pass. Its optional peel mode rejects the first recorded
@@ -20,6 +22,11 @@ const DEPTH_VERT_SOURCE = `#version 300 es
20
22
  layout(location = 0) in vec3 aPosition;
21
23
  #if SKINNED
22
24
  ${SKINNING_GLSL}
25
+ /* Not in the glass-tint caster: a garment is not glass, and its layout keeps its bindings. */
26
+ #if GLASS
27
+ #else
28
+ ${CLOTH_BINDING_GLSL}
29
+ #endif
23
30
  #endif
24
31
 
25
32
  uniform mat4 uLightViewProj;
@@ -59,20 +66,22 @@ ${CHANNEL_BEND}
59
66
 
60
67
  #if CUTOUT
61
68
  /*
62
- * **What a cutout caster adds, and where.** The UV at location 5, where every mesh carries it; two
69
+ * **What a cutout caster adds, and where.** The UV at location 5, where every mesh carries it; three
63
70
  * uniforms declared after every other, so no field of the plain variant moves and a binder written
64
71
  * against it stays right for this one; and two outputs after vLightPosition, which keeps location 0.
65
72
  */
66
73
  layout(location = 5) in vec3 aUv;
67
74
  uniform vec2 uUvScale;
68
- uniform float uAlphaCutout;
75
+ uniform vec2 uUvOffset;
76
+ /* x the cutoff, y 1 for a dithered edge: see the cutout fragment stages below. */
77
+ uniform vec2 uAlphaCutout;
69
78
  #endif
70
79
 
71
80
  out vec4 vLightPosition;
72
81
  #if CUTOUT
73
82
  /* The texture coordinate and, in z, the texture-array layer the face wears. */
74
83
  out vec3 vUv;
75
- flat out float vAlphaCutout;
84
+ flat out vec2 vAlphaCutout;
76
85
  #endif
77
86
  #if GLASS
78
87
  /* Where on the pane this is, for the tint's own normal and its angle to the light. Last, so the
@@ -99,11 +108,23 @@ void main() {
99
108
  vec3 bent = world.xyz;
100
109
  #else
101
110
  vec3 bent = channelBend(world.xyz, aChannel.x);
111
+ #endif
112
+ #if SKINNED
113
+ #if GLASS
114
+ #else
115
+ /* Toward the cloth exactly as the colour pass moves it, or the shadow parts from the garment. */
116
+ if (CLOTH_BOUND) {
117
+ vec3 clothPosition;
118
+ mat3 turn;
119
+ float follow = clothPlace(clothPosition, turn);
120
+ bent = mix(bent, clothPosition, follow);
121
+ }
122
+ #endif
102
123
  #endif
103
124
  vLightPosition = uLightViewProj * vec4(bent, world.w);
104
125
  gl_Position = vLightPosition;
105
126
  #if CUTOUT
106
- vUv = vec3(aUv.xy * uUvScale, aUv.z);
127
+ vUv = vec3(aUv.xy * uUvScale + uUvOffset, aUv.z);
107
128
  vAlphaCutout = uAlphaCutout;
108
129
  #endif
109
130
  #if GLASS
@@ -170,12 +191,13 @@ export const DEPTH_CUTOUT_FRAG = `#version 300 es
170
191
  precision highp float;
171
192
  in vec4 vLightPosition;
172
193
  in vec3 vUv;
173
- flat in float vAlphaCutout;
194
+ flat in vec2 vAlphaCutout;
174
195
 
175
196
  uniform highp sampler2DArray uPreviousShadowMap;
176
197
  uniform int uPeelShadowLayer;
177
198
  uniform highp sampler2DArray uCutoutMap;
178
199
  ${CUTOUT_COVERAGE_GLSL}
200
+ ${CUTOUT_DITHER_GLSL}
179
201
  void main() {
180
202
  /* Credited for its mip level as the surface's own test is, so a leaf casts the shape it draws. */
181
203
  vec3 at = vec3(vUv.xy, floor(vUv.z + 0.5));
@@ -186,7 +208,16 @@ void main() {
186
208
  float previousDepth = textureLod(uPreviousShadowMap, vec3(uv, ${SUN_STATIC_LAYER}.0), 0.0).r;
187
209
  if (gl_FragCoord.z <= previousDepth + 0.00001) discard;
188
210
  }
189
- if (alpha < vAlphaCutout) discard;
211
+ /*
212
+ * A dithered edge casts the share it covers, with a pattern that does not move: a shadow map has
213
+ * no temporal resolve of its own and a static layer keeps whatever it was drawn with, so a moving
214
+ * pattern would shimmer there. The map's filter averages it into a soft edge.
215
+ */
216
+ if (vAlphaCutout.y > 0.5) {
217
+ if (!cutoutKeeps(cutoutShare(alpha, vAlphaCutout.x), gl_FragCoord.xy, 0.0)) discard;
218
+ } else if (alpha < vAlphaCutout.x) {
219
+ discard;
220
+ }
190
221
  }
191
222
  `;
192
223
  /**
@@ -240,13 +271,14 @@ export const GLASS_TINT_CUTOUT_FRAG = `#version 300 es
240
271
  precision highp float;
241
272
  in vec4 vLightPosition;
242
273
  in vec3 vUv;
243
- flat in float vAlphaCutout;
274
+ flat in vec2 vAlphaCutout;
244
275
  in vec3 vGlassWorld;
245
276
 
246
277
  uniform vec4 uGlassPane;
247
278
  uniform vec4 uGlassLight;
248
279
  uniform highp sampler2DArray uCutoutMap;
249
280
  ${CUTOUT_COVERAGE_GLSL}
281
+ ${CUTOUT_DITHER_GLSL}
250
282
  out vec4 outTint;
251
283
 
252
284
  void main() {
@@ -254,6 +286,11 @@ void main() {
254
286
  vec3 at = vec3(vUv.xy, floor(vUv.z + 0.5));
255
287
  float alpha = cutoutAlpha(texture(uCutoutMap, at).a, vUv.xy * vec2(textureSize(uCutoutMap, 0).xy));
256
288
  ${GLASS_TINT_BODY}
257
- if (alpha < vAlphaCutout) discard;
289
+ /* The depth cutout's own test, so the colour lands exactly where the depth does. */
290
+ if (vAlphaCutout.y > 0.5) {
291
+ if (!cutoutKeeps(cutoutShare(alpha, vAlphaCutout.x), gl_FragCoord.xy, 0.0)) discard;
292
+ } else if (alpha < vAlphaCutout.x) {
293
+ discard;
294
+ }
258
295
  }
259
296
  `;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The anisotropic model in GLSL: GGX stretched along a direction in the surface, after glTF's
3
+ * `KHR_materials_anisotropy` — `anisotropicLobe.ts` is the same arithmetic, and its tests hold it to
4
+ * the standard lobe at strength 0 and to the widths' ratio at its half maximum.
5
+ *
6
+ * **The direction** is the tangent frame's, turned by the material's rotation, and by the model
7
+ * map's red and green where one is bound (a direction encoded −1 to 1, glTF's packing), its blue
8
+ * scaling the strength. **The environment** is reflected about a normal bent toward the stretch, as
9
+ * Filament and three.js do: a streak of sky across a brushed disc rather than a round spot, at the
10
+ * cost of a reflection that is not the exact anisotropic integral.
11
+ *
12
+ * Lamps widen both of the lobe's widths by the source's angle, as `sphereLobe` widens the standard
13
+ * lobe, with the energy kept; at strength 0 that is `sphereLobe` exactly.
14
+ */
15
+ export declare const ANISOTROPIC_GLSL = "\n/* The stretch's frame and strength, gathered once with the surface. */\nvec3 aAlong;\nvec3 aAcross;\nfloat aStrength;\n\nvoid anisotropicSurface() {\n vec2 direction = vec2(1.0, 0.0);\n float strength = uModelParams[0].x;\n if (uModelParams[1].w > 0.0) {\n vec2 mapped = mMap.rg * 2.0 - 1.0;\n direction = dot(mapped, mapped) > 1e-8 ? normalize(mapped) : direction;\n strength *= mMap.b;\n }\n vec2 turned = vec2(\n direction.x * uModelParams[0].y - direction.y * uModelParams[0].z,\n direction.x * uModelParams[0].z + direction.y * uModelParams[0].y\n );\n aAlong = normalize(mTangent * turned.x + mBitangent * turned.y);\n aAcross = cross(mNormal, aAlong);\n aStrength = strength;\n}\n\n/** The stretched lobe at a half vector, 1 at the mirror direction: anisotropicLobe.ts. */\nfloat anisotropicLobe(vec3 h, float sourceRadius, float dist) {\n float alpha = max(mRoughness * mRoughness, MIN_LOBE_ALPHA);\n float alongWidth = alpha + (1.0 - alpha) * aStrength * aStrength;\n float grow = sourceRadius / max(2.0 * dist, 1e-3);\n float alongWide = clamp(alongWidth + grow, alongWidth, 1.0);\n float acrossWide = clamp(alpha + grow, alpha, 1.0);\n float energy = (alongWidth / alongWide) * (alpha / acrossWide);\n float x = dot(aAlong, h) / alongWide;\n float y = dot(aAcross, h) / acrossWide;\n float z = dot(mNormal, h);\n float d = x * x + y * y + z * z;\n return energy / max(d * d, 1e-8);\n}\n\nvoid anisotropicLight(vec3 l, float sourceRadius, float dist) {\n float ndl = max(dot(mNormal, l), 0.0);\n vec3 h = normalize(l + mToEye);\n vec3 tint = mix(mSpecColor, vec3(1.0), pow(1.0 - max(dot(mToEye, h), 0.0), 5.0) * mMetal);\n mDiffuse = mAlbedo * ndl * (1.0 - mMetal);\n mSpecular = ndl > 0.0 ? tint * anisotropicLobe(h, sourceRadius, dist) : vec3(0.0);\n}\n\n/* The environment's normal, bent toward the stretch by its strength and the surface's roughness. */\nvec3 anisotropicReflectNormal(vec3 n) {\n vec3 across = cross(aAcross, mToEye);\n vec3 bent = cross(across, aAcross);\n float bend = aStrength * clamp(5.0 * mRoughness, 0.0, 1.0);\n return normalize(mix(n, bent, bend));\n}\n";
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The anisotropic model in GLSL: GGX stretched along a direction in the surface, after glTF's
3
+ * `KHR_materials_anisotropy` — `anisotropicLobe.ts` is the same arithmetic, and its tests hold it to
4
+ * the standard lobe at strength 0 and to the widths' ratio at its half maximum.
5
+ *
6
+ * **The direction** is the tangent frame's, turned by the material's rotation, and by the model
7
+ * map's red and green where one is bound (a direction encoded −1 to 1, glTF's packing), its blue
8
+ * scaling the strength. **The environment** is reflected about a normal bent toward the stretch, as
9
+ * Filament and three.js do: a streak of sky across a brushed disc rather than a round spot, at the
10
+ * cost of a reflection that is not the exact anisotropic integral.
11
+ *
12
+ * Lamps widen both of the lobe's widths by the source's angle, as `sphereLobe` widens the standard
13
+ * lobe, with the energy kept; at strength 0 that is `sphereLobe` exactly.
14
+ */
15
+ export const ANISOTROPIC_GLSL = /* glsl */ `
16
+ /* The stretch's frame and strength, gathered once with the surface. */
17
+ vec3 aAlong;
18
+ vec3 aAcross;
19
+ float aStrength;
20
+
21
+ void anisotropicSurface() {
22
+ vec2 direction = vec2(1.0, 0.0);
23
+ float strength = uModelParams[0].x;
24
+ if (uModelParams[1].w > 0.0) {
25
+ vec2 mapped = mMap.rg * 2.0 - 1.0;
26
+ direction = dot(mapped, mapped) > 1e-8 ? normalize(mapped) : direction;
27
+ strength *= mMap.b;
28
+ }
29
+ vec2 turned = vec2(
30
+ direction.x * uModelParams[0].y - direction.y * uModelParams[0].z,
31
+ direction.x * uModelParams[0].z + direction.y * uModelParams[0].y
32
+ );
33
+ aAlong = normalize(mTangent * turned.x + mBitangent * turned.y);
34
+ aAcross = cross(mNormal, aAlong);
35
+ aStrength = strength;
36
+ }
37
+
38
+ /** The stretched lobe at a half vector, 1 at the mirror direction: anisotropicLobe.ts. */
39
+ float anisotropicLobe(vec3 h, float sourceRadius, float dist) {
40
+ float alpha = max(mRoughness * mRoughness, MIN_LOBE_ALPHA);
41
+ float alongWidth = alpha + (1.0 - alpha) * aStrength * aStrength;
42
+ float grow = sourceRadius / max(2.0 * dist, 1e-3);
43
+ float alongWide = clamp(alongWidth + grow, alongWidth, 1.0);
44
+ float acrossWide = clamp(alpha + grow, alpha, 1.0);
45
+ float energy = (alongWidth / alongWide) * (alpha / acrossWide);
46
+ float x = dot(aAlong, h) / alongWide;
47
+ float y = dot(aAcross, h) / acrossWide;
48
+ float z = dot(mNormal, h);
49
+ float d = x * x + y * y + z * z;
50
+ return energy / max(d * d, 1e-8);
51
+ }
52
+
53
+ void anisotropicLight(vec3 l, float sourceRadius, float dist) {
54
+ float ndl = max(dot(mNormal, l), 0.0);
55
+ vec3 h = normalize(l + mToEye);
56
+ vec3 tint = mix(mSpecColor, vec3(1.0), pow(1.0 - max(dot(mToEye, h), 0.0), 5.0) * mMetal);
57
+ mDiffuse = mAlbedo * ndl * (1.0 - mMetal);
58
+ mSpecular = ndl > 0.0 ? tint * anisotropicLobe(h, sourceRadius, dist) : vec3(0.0);
59
+ }
60
+
61
+ /* The environment's normal, bent toward the stretch by its strength and the surface's roughness. */
62
+ vec3 anisotropicReflectNormal(vec3 n) {
63
+ vec3 across = cross(aAcross, mToEye);
64
+ vec3 bent = cross(across, aAcross);
65
+ float bend = aStrength * clamp(5.0 * mRoughness, 0.0, 1.0);
66
+ return normalize(mix(n, bent, bend));
67
+ }
68
+ `;
@@ -0,0 +1 @@
1
+ export declare const EYE_GLSL: string;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The eye model in GLSL: `eyeRefraction.ts` is the refraction's arithmetic, and its tests hold it
3
+ * to no shift head-on, the refracted crossing at 30°, a bounded shift edge-on and the map-less iris.
4
+ *
5
+ * **The iris is moved before any map is read** (`modelSurfaceAt`), by how far the eye's ray crosses
6
+ * the iris plane after the cornea bends it, so the whole iris — colour, relief, the model's own
7
+ * channels — moves as one. The crossing is turned into texture coordinates by the tangent frame and
8
+ * by how many of them a metre spans here, which the screen's derivatives say: so it is in uniform
9
+ * control flow, as `modelSurfaceAt` is. **What it gives up** is an anisotropic mapping — a texture
10
+ * stretched one way more than the other moves by the average of the two.
11
+ *
12
+ * **The model map**: red is the iris (1 inside), green the cornea's height above the iris plane as a
13
+ * share of `irisDepth`. Without one, the iris is a disc of `irisRadius` about the texture's centre
14
+ * and the height is `irisDepth` throughout.
15
+ *
16
+ * **Lit as an eye is**: the iris by a normal mirrored about the axis — concave where the cornea is
17
+ * convex, so light pools on the side away from it — and the sclera by the surface's own; and over
18
+ * both, the cornea's highlight, normalised GGX at `corneaRoughness` with the Fresnel of index 1.336,
19
+ * in lamp units like skin's. The environment is reflected at the cornea's split-sum share.
20
+ */
21
+ import { CORNEA_F0, EYE_MIN_DESCENT } from '../../eyeRefraction.js';
22
+ export const EYE_GLSL = /* glsl */ `
23
+ const float CORNEA_F0 = ${CORNEA_F0.toFixed(9)};
24
+ const float EYE_MIN_DESCENT = ${EYE_MIN_DESCENT.toFixed(4)};
25
+
26
+ /* How much of this texel is iris, gathered with the surface. */
27
+ float eIris;
28
+
29
+ /** Where the iris is without a map: a disc of the model's radius about the centre, a soft limbus. */
30
+ float eyeIrisDisc(vec2 uv) {
31
+ float r = length(uv - 0.5);
32
+ return 1.0 - smoothstep(uModelParams[0].x - 0.01, uModelParams[0].x + 0.01, r);
33
+ }
34
+
35
+ /** The iris's texture coordinate, moved by the cornea: \`eyeRefraction.ts\`. Before any map is read. */
36
+ vec3 eyeRefract(vec3 at) {
37
+ vec3 n = normalize(vNormal);
38
+ vec3 axis = uModelParams[1].xyz;
39
+ vec4 map = texture(uModelMap, at);
40
+ bool mapped = uModelParams[1].w > 0.0;
41
+ float iris = mapped ? map.r : eyeIrisDisc(at.xy);
42
+ float height = uModelParams[0].y * (mapped ? map.g : 1.0);
43
+ vec3 ray = refract(-normalize(uCameraPos - vWorldPos), n, 1.0 / uModelParams[0].z);
44
+ float descent = max(-dot(ray, axis), EYE_MIN_DESCENT);
45
+ vec3 travel = ray * (height / descent);
46
+ vec3 across = travel - axis * dot(travel, axis);
47
+ mat3 frame = tangentFrame(n, vWorldPos, vUv.xy, vTangent, vHasTangents);
48
+ /* How many texture coordinates a metre spans here, from the screen's two derivatives. */
49
+ float perMetre = length(fwidth(vUv.xy)) / max(length(fwidth(vWorldPos)), 1e-9);
50
+ vec2 shift = vec2(dot(across, frame[0]), dot(across, frame[1])) * (perMetre * iris);
51
+ return vec3(at.xy + shift, at.z);
52
+ }
53
+
54
+ void eyeSurface(vec3 at) {
55
+ eIris = uModelParams[1].w > 0.0 ? mMap.r : eyeIrisDisc(at.xy);
56
+ mRoughness = uModelParams[0].w;
57
+ }
58
+
59
+ void eyeLight(vec3 l, float sourceRadius, float dist) {
60
+ vec3 axis = uModelParams[1].xyz;
61
+ /* The iris is concave where the cornea over it is convex: the surface's normal mirrored about the axis. */
62
+ vec3 irisNormal = normalize(2.0 * dot(mNormal, axis) * axis - mNormal);
63
+ vec3 lit = normalize(mix(mNormal, irisNormal, eIris));
64
+ mDiffuse = mAlbedo * max(dot(lit, l), 0.0) * (1.0 - mMetal);
65
+
66
+ float ndl = max(dot(mNormal, l), 0.0);
67
+ vec3 h = normalize(l + mToEye);
68
+ float ndh = max(dot(mNormal, h), 0.0);
69
+ float ndv = max(dot(mNormal, mToEye), 1e-4);
70
+ float grow = sourceRadius / max(2.0 * dist, 1e-3);
71
+ float alpha = min(max(mRoughness * mRoughness, MIN_LOBE_ALPHA) + grow, 1.0);
72
+ float f = CORNEA_F0 + (1.0 - CORNEA_F0) * pow(1.0 - max(dot(mToEye, h), 0.0), 5.0);
73
+ mSpecular = vec3(3.14159265 * f * smithMasking(ndl, ndv, alpha) * ggxLobe(ndh, alpha) * ndl);
74
+ }
75
+
76
+ /** The environment at the cornea's Fresnel, by the split sum's share at its own roughness. */
77
+ float eyeReflectance() {
78
+ vec2 dfg = envBrdfApprox(max(dot(mNormal, mToEye), 0.0), uModelParams[0].w);
79
+ return CORNEA_F0 * dfg.x + dfg.y;
80
+ }
81
+ `;
@@ -0,0 +1 @@
1
+ export declare const HAIR_GLSL: string;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The hair model in GLSL: `hairLobes.ts` is the same arithmetic, and its tests hold the lobes to
3
+ * their tilted peaks, the transmitted lobe to the backlight, the sum to what a white strand
4
+ * receives, and every term to a finite answer where the geometry degenerates.
5
+ *
6
+ * **The strand is the tangent, root to tip.** The model map's green turns each strand's tilt by up
7
+ * to half of it either way (0.5 is none) and its blue is occlusion, which the sky, DriftLight and
8
+ * the environment's reflection take and the lamps do not — their shadows are theirs to cast. The
9
+ * red is not read: a colour from root to tip is the albedo map's to carry.
10
+ *
11
+ * **The environment** is the reflected lobe's: mirrored about a normal turned toward the eye across
12
+ * the strand, which is the cone R lies on, at the strand's own Fresnel — the lobe integrates to it
13
+ * over every direction of arrival. **What it gives up** is the two transmitted lobes against the
14
+ * sky, which a head of hair lit from behind by an open sky would show as a rim; the lamps carry
15
+ * them.
16
+ */
17
+ import { HAIR_F0, HAIR_MIN_BETA, HAIR_SCATTER_SPHERE } from '../../hairLobes.js';
18
+ export const HAIR_GLSL = /* glsl */ `
19
+ const float HAIR_F0 = ${HAIR_F0.toFixed(9)};
20
+ const float HAIR_MIN_BETA = ${HAIR_MIN_BETA.toFixed(3)};
21
+ const float HAIR_SCATTER_SPHERE = ${HAIR_SCATTER_SPHERE.toFixed(3)};
22
+
23
+ /* This texel's tilt, gathered once with the surface. */
24
+ float hShift;
25
+
26
+ void hairSurface() {
27
+ hShift = uModelParams[0].x * (0.5 + mMap.g);
28
+ }
29
+
30
+ /** A longitudinal lobe: a normalised Gaussian of width \`beta\` at \`x\` from its centre. */
31
+ float hairLongitudinal(float beta, float x) {
32
+ return exp(-0.5 * x * x / (beta * beta)) / (2.5066283 * beta);
33
+ }
34
+
35
+ float hairFresnel(float cosine) {
36
+ float c = 1.0 - clamp(cosine, 0.0, 1.0);
37
+ return HAIR_F0 + (1.0 - HAIR_F0) * c * c * c * c * c;
38
+ }
39
+
40
+ /** The normal turned toward the eye across the strand: the scatter's and the environment's. */
41
+ vec3 hairAcross() {
42
+ vec3 across = mToEye - mTangent * dot(mTangent, mToEye);
43
+ float length2 = dot(across, across);
44
+ return length2 > 1e-8 ? across * inversesqrt(length2) : mNormal;
45
+ }
46
+
47
+ void hairLight(vec3 l, float sourceRadius, float dist) {
48
+ vec3 t = mTangent;
49
+ float sinI = clamp(dot(t, l), -1.0, 1.0);
50
+ float sinO = clamp(dot(t, mToEye), -1.0, 1.0);
51
+ /* Floored for the half angle's limit, whose single-precision cosine is negative: hairLobes.ts. */
52
+ float cosD = max(cos(0.5 * abs(asin(sinO) - asin(sinI))), 1e-3);
53
+ vec3 lp = l - sinI * t;
54
+ vec3 vp = mToEye - sinO * t;
55
+ float cosPhi = dot(lp, vp) * inversesqrt(dot(lp, lp) * dot(vp, vp) + 1e-4);
56
+ float cosHalfPhi = sqrt(clamp(0.5 + 0.5 * cosPhi, 0.0, 1.0));
57
+ float beta = max(mRoughness * mRoughness, HAIR_MIN_BETA);
58
+ float grow = sourceRadius / max(2.0 * dist, 1e-3);
59
+ float x = sinI + sinO;
60
+
61
+ float r = hairLongitudinal(beta + grow, x + 2.0 * hShift) * 0.25 * cosHalfPhi
62
+ * hairFresnel(sqrt(clamp(0.5 + 0.5 * dot(l, mToEye), 0.0, 1.0)));
63
+
64
+ float a = 1.0 / (1.19 / cosD + 0.36 * cosD);
65
+ float h = cosHalfPhi * (1.0 + a * (0.6 - 0.8 * cosPhi));
66
+ float fTT = hairFresnel(cosD * sqrt(clamp(1.0 - h * h, 0.0, 1.0)));
67
+ float tt = hairLongitudinal(0.5 * beta + grow, x - hShift) * exp(-3.65 * cosPhi - 3.98)
68
+ * (1.0 - fTT) * (1.0 - fTT) * uModelParams[0].z;
69
+ float ttPower = 0.5 * sqrt(clamp(1.0 - h * h * a * a, 0.0, 1.0)) / cosD;
70
+
71
+ float fTRT = hairFresnel(0.5 * cosD);
72
+ float trt = hairLongitudinal(2.0 * beta + grow, x - 3.0 * hShift) * exp(17.0 * cosPhi - 16.78)
73
+ * (1.0 - fTRT) * (1.0 - fTRT) * fTRT;
74
+
75
+ /* Above zero, so pow never takes the logarithm of nothing: hairLobes.ts. */
76
+ vec3 fibre = max(mAlbedo, vec3(1e-4));
77
+ /* π: the published lobes are per unit irradiance, and a lamp here is π of that. */
78
+ mSpecular = 3.14159265 * (vec3(r) + tt * pow(fibre, vec3(ttPower)) + trt * pow(fibre, vec3(0.8 / cosD)));
79
+ float wrapped = clamp((dot(hairAcross(), l) + 1.0) * 0.25, 0.0, 1.0);
80
+ mDiffuse = sqrt(mAlbedo) * uModelParams[0].y * mix(wrapped, 1.0 - abs(sinI), 0.33);
81
+ }
82
+
83
+ /**
84
+ * A shadow on the scatter: light that reaches a strand in another's shadow has passed through
85
+ * hair to get there, so a penumbra deepens toward the fibre's own hue.
86
+ */
87
+ vec3 hairShade(float shadow) {
88
+ vec3 hue = mAlbedo / max(dot(mAlbedo, vec3(0.2126, 0.7152, 0.0722)), 1e-4);
89
+ return shadow * pow(max(hue, vec3(1e-4)), vec3(1.0 - shadow));
90
+ }
91
+
92
+ /** Light from every side, through the scatter term: \`HAIR_SCATTER_SPHERE\` says why that number. */
93
+ vec3 hairAround(vec3 light) {
94
+ return sqrt(mAlbedo) * (uModelParams[0].y * HAIR_SCATTER_SPHERE * mMap.b) * light;
95
+ }
96
+
97
+ /** The reflected lobe's share of the environment: its Fresnel, which is what it integrates to. */
98
+ float hairReflectance() {
99
+ return hairFresnel(dot(hairAcross(), mToEye)) * mMap.b;
100
+ }
101
+ `;