@bitefoundry/retrieviewer 0.0.0-stage → 0.0.2

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 (297) hide show
  1. package/ASSET-PROVENANCE.md +43 -0
  2. package/ASSETS-LICENSE.md +58 -0
  3. package/ATTRIBUTION.md +54 -0
  4. package/EMBEDDING.md +96 -0
  5. package/INSTALLING.md +126 -0
  6. package/LICENSE +16 -0
  7. package/LICENSE-CODE +21 -0
  8. package/PLUGIN.md +110 -0
  9. package/PUPPY-MEMORY.md +57 -0
  10. package/PUPPY-Q5-COMPARISON.md +73 -0
  11. package/README.md +86 -2
  12. package/RELEASING.md +70 -0
  13. package/THIRD-PARTY-NOTICES.md +17 -0
  14. package/TRADEMARKS.md +21 -0
  15. package/WINDOWS-COMPANION.md +113 -0
  16. package/braille.mjs +45 -0
  17. package/examples/bridge.py +62 -0
  18. package/examples/embed.mjs +15 -0
  19. package/examples/live.mjs +40 -0
  20. package/examples/plugin.mjs +21 -0
  21. package/integrations/opencode/runtime.mjs +195 -0
  22. package/integrations/opencode/tui.tsx +59 -0
  23. package/integrations/windows-overlay/Overlay.cs +259 -0
  24. package/integrations/windows-overlay/desktop.mjs +87 -0
  25. package/integrations/windows-overlay/protocol.mjs +39 -0
  26. package/integrations/windows-overlay/worker.mjs +60 -0
  27. package/locomotion-core.mjs +21 -0
  28. package/locomotion-puppy.animal +0 -0
  29. package/package.json +131 -4
  30. package/pet-q5-manifest.json +118 -0
  31. package/puppy-clean.mjs +51 -0
  32. package/puppy-companion-interactions-v3-q5.bin.gz +0 -0
  33. package/puppy-companion-interactions-v3.json +1 -0
  34. package/puppy-companion-life.mjs +182 -0
  35. package/puppy-companion-objects.mjs +30 -0
  36. package/puppy-companion-sprites.mjs +14 -0
  37. package/puppy-continuous.mjs +57 -0
  38. package/puppy-face.mjs +46 -0
  39. package/puppy-frame-predictive.mjs +147 -0
  40. package/puppy-frame-store.mjs +108 -0
  41. package/puppy-mouth-anchor.json +1 -0
  42. package/puppy-mouth-anchor.mjs +33 -0
  43. package/puppy-paper-actions-v9-q5.bin.gz +0 -0
  44. package/puppy-paper-actions-v9.json +1 -0
  45. package/puppy-paper-actions.mjs +48 -0
  46. package/puppy-paper-approach-q5.bin.gz +0 -0
  47. package/puppy-paper-approach.json +1 -0
  48. package/puppy-paper-area.mjs +15 -0
  49. package/puppy-paper-arrival-seat-q5.bin.gz +0 -0
  50. package/puppy-paper-arrival-seat.json +1 -0
  51. package/puppy-paper-behaviors-q5.bin.gz +0 -0
  52. package/puppy-paper-behaviors.json +1 -0
  53. package/puppy-paper-behaviors.mjs +8 -0
  54. package/puppy-paper-contact.mjs +103 -0
  55. package/puppy-paper-director.mjs +21 -0
  56. package/puppy-paper-paw-transport.mjs +76 -0
  57. package/puppy-paper-rear-gallop-q5.bin.gz +0 -0
  58. package/puppy-paper-rear-gallop.json +1 -0
  59. package/puppy-paper-seat-q5.bin.gz +0 -0
  60. package/puppy-paper-seat.json +14 -0
  61. package/puppy-paper-seat.mjs +13 -0
  62. package/puppy-paper-trick-timing.mjs +58 -0
  63. package/puppy-paper-world.mjs +108 -0
  64. package/puppy-paper.mjs +271 -0
  65. package/puppy-stable-meta.json +1606 -0
  66. package/puppy-stable.mjs +72 -0
  67. package/puppy-stride-meta.json +343 -0
  68. package/puppy-stride-tweens-q5.bin.gz +0 -0
  69. package/puppy-zoomies.mjs +27 -0
  70. package/puppy-zoomies30.mjs +12 -0
  71. package/retrieviewer-cli.mjs +94 -0
  72. package/retrieviewer-config.mjs +49 -0
  73. package/retrieviewer-install.mjs +157 -0
  74. package/retrieviewer-plugin.d.mts +44 -0
  75. package/retrieviewer-plugin.mjs +97 -0
  76. package/retrieviewer-stdio.mjs +38 -0
  77. package/retrieviewer.d.mts +85 -0
  78. package/retrieviewer.mjs +142 -0
  79. package/vendor/procedural-animals/LICENSE +21 -0
  80. package/vendor/procedural-animals/package.json +40 -0
  81. package/vendor/procedural-animals/src/animal.js +146 -0
  82. package/vendor/procedural-animals/src/bake.js +51 -0
  83. package/vendor/procedural-animals/src/build.js +81 -0
  84. package/vendor/procedural-animals/src/core/build/coatKit.js +121 -0
  85. package/vendor/procedural-animals/src/core/build/featherCards.js +189 -0
  86. package/vendor/procedural-animals/src/core/build/finEdges.js +85 -0
  87. package/vendor/procedural-animals/src/core/build/furSlope.js +115 -0
  88. package/vendor/procedural-animals/src/core/build/mesh.js +67 -0
  89. package/vendor/procedural-animals/src/core/build/pipeline.js +272 -0
  90. package/vendor/procedural-animals/src/core/build/seams.js +183 -0
  91. package/vendor/procedural-animals/src/core/build/warp.js +112 -0
  92. package/vendor/procedural-animals/src/core/build/weights.js +393 -0
  93. package/vendor/procedural-animals/src/core/math/vec.js +81 -0
  94. package/vendor/procedural-animals/src/core/motion/actions.js +800 -0
  95. package/vendor/procedural-animals/src/core/motion/bird.js +3063 -0
  96. package/vendor/procedural-animals/src/core/motion/birdActions.js +1059 -0
  97. package/vendor/procedural-animals/src/core/motion/common.js +158 -0
  98. package/vendor/procedural-animals/src/core/motion/index.js +22 -0
  99. package/vendor/procedural-animals/src/core/motion/quadruped.js +2875 -0
  100. package/vendor/procedural-animals/src/core/motion/snake.js +1594 -0
  101. package/vendor/procedural-animals/src/core/motion/spider.js +1810 -0
  102. package/vendor/procedural-animals/src/core/motion/swimmer.js +1047 -0
  103. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +613 -0
  104. package/vendor/procedural-animals/src/core/render/animalObject.js +262 -0
  105. package/vendor/procedural-animals/src/core/render/coatMaterial.js +1301 -0
  106. package/vendor/procedural-animals/src/core/render/dqs.js +159 -0
  107. package/vendor/procedural-animals/src/core/render/eyes.js +676 -0
  108. package/vendor/procedural-animals/src/core/render/membranes.js +77 -0
  109. package/vendor/procedural-animals/src/core/render/skeleton.js +41 -0
  110. package/vendor/procedural-animals/src/core/rig/bird.js +327 -0
  111. package/vendor/procedural-animals/src/core/rig/quadruped.js +76 -0
  112. package/vendor/procedural-animals/src/core/rig/rig.js +79 -0
  113. package/vendor/procedural-animals/src/core/rig/snake.js +76 -0
  114. package/vendor/procedural-animals/src/core/rig/spider.js +110 -0
  115. package/vendor/procedural-animals/src/core/rig/swimmer.js +181 -0
  116. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +53 -0
  117. package/vendor/procedural-animals/src/core/sdf/mesher.js +238 -0
  118. package/vendor/procedural-animals/src/core/sdf/sdf.js +263 -0
  119. package/vendor/procedural-animals/src/index.d.ts +98 -0
  120. package/vendor/procedural-animals/src/index.js +29 -0
  121. package/vendor/procedural-animals/src/registry.js +48 -0
  122. package/vendor/procedural-animals/src/species/bear/behaviour.js +463 -0
  123. package/vendor/procedural-animals/src/species/bear/claws.js +147 -0
  124. package/vendor/procedural-animals/src/species/bear/coat.js +726 -0
  125. package/vendor/procedural-animals/src/species/bear/index.js +139 -0
  126. package/vendor/procedural-animals/src/species/bear/motion.js +112 -0
  127. package/vendor/procedural-animals/src/species/bear/regions.js +78 -0
  128. package/vendor/procedural-animals/src/species/bear/rig.js +112 -0
  129. package/vendor/procedural-animals/src/species/bear/sculpt.js +272 -0
  130. package/vendor/procedural-animals/src/species/boar/behaviour.js +335 -0
  131. package/vendor/procedural-animals/src/species/boar/coat.js +372 -0
  132. package/vendor/procedural-animals/src/species/boar/index.js +110 -0
  133. package/vendor/procedural-animals/src/species/boar/motion.js +85 -0
  134. package/vendor/procedural-animals/src/species/boar/regions.js +88 -0
  135. package/vendor/procedural-animals/src/species/boar/rig.js +111 -0
  136. package/vendor/procedural-animals/src/species/boar/sculpt.js +285 -0
  137. package/vendor/procedural-animals/src/species/cat/behaviour.js +559 -0
  138. package/vendor/procedural-animals/src/species/cat/coat.js +841 -0
  139. package/vendor/procedural-animals/src/species/cat/index.js +211 -0
  140. package/vendor/procedural-animals/src/species/cat/motion.js +101 -0
  141. package/vendor/procedural-animals/src/species/cat/regions.js +82 -0
  142. package/vendor/procedural-animals/src/species/cat/rig.js +100 -0
  143. package/vendor/procedural-animals/src/species/cat/sculpt.js +266 -0
  144. package/vendor/procedural-animals/src/species/cat/whiskers.js +110 -0
  145. package/vendor/procedural-animals/src/species/cheetah/coat.js +427 -0
  146. package/vendor/procedural-animals/src/species/cheetah/index.js +51 -0
  147. package/vendor/procedural-animals/src/species/cheetah/joints.js +3 -0
  148. package/vendor/procedural-animals/src/species/cheetah/motion.js +77 -0
  149. package/vendor/procedural-animals/src/species/cheetah/regions.js +95 -0
  150. package/vendor/procedural-animals/src/species/cheetah/rig.js +66 -0
  151. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +158 -0
  152. package/vendor/procedural-animals/src/species/chicken/coat.js +313 -0
  153. package/vendor/procedural-animals/src/species/chicken/coverts.js +320 -0
  154. package/vendor/procedural-animals/src/species/chicken/index.js +110 -0
  155. package/vendor/procedural-animals/src/species/chicken/motion.js +177 -0
  156. package/vendor/procedural-animals/src/species/chicken/regions.js +66 -0
  157. package/vendor/procedural-animals/src/species/chicken/rig.js +144 -0
  158. package/vendor/procedural-animals/src/species/chicken/sculpt.js +389 -0
  159. package/vendor/procedural-animals/src/species/chicken/wingLook.js +66 -0
  160. package/vendor/procedural-animals/src/species/chicken/wingStack.js +124 -0
  161. package/vendor/procedural-animals/src/species/cow/behaviour.js +521 -0
  162. package/vendor/procedural-animals/src/species/cow/coat.js +465 -0
  163. package/vendor/procedural-animals/src/species/cow/index.js +118 -0
  164. package/vendor/procedural-animals/src/species/cow/motion.js +81 -0
  165. package/vendor/procedural-animals/src/species/cow/regions.js +98 -0
  166. package/vendor/procedural-animals/src/species/cow/rig.js +116 -0
  167. package/vendor/procedural-animals/src/species/cow/sculpt.js +339 -0
  168. package/vendor/procedural-animals/src/species/crow/coat.js +145 -0
  169. package/vendor/procedural-animals/src/species/crow/index.js +70 -0
  170. package/vendor/procedural-animals/src/species/crow/joints.js +3 -0
  171. package/vendor/procedural-animals/src/species/crow/motion.js +101 -0
  172. package/vendor/procedural-animals/src/species/crow/regions.js +50 -0
  173. package/vendor/procedural-animals/src/species/crow/rig.js +83 -0
  174. package/vendor/procedural-animals/src/species/crow/sculpt.js +118 -0
  175. package/vendor/procedural-animals/src/species/deer/behaviour.js +513 -0
  176. package/vendor/procedural-animals/src/species/deer/coat.js +391 -0
  177. package/vendor/procedural-animals/src/species/deer/index.js +106 -0
  178. package/vendor/procedural-animals/src/species/deer/motion.js +97 -0
  179. package/vendor/procedural-animals/src/species/deer/regions.js +60 -0
  180. package/vendor/procedural-animals/src/species/deer/rig.js +124 -0
  181. package/vendor/procedural-animals/src/species/deer/sculpt.js +297 -0
  182. package/vendor/procedural-animals/src/species/dog/behaviour.js +521 -0
  183. package/vendor/procedural-animals/src/species/dog/coat.js +495 -0
  184. package/vendor/procedural-animals/src/species/dog/index.js +144 -0
  185. package/vendor/procedural-animals/src/species/dog/motion.js +118 -0
  186. package/vendor/procedural-animals/src/species/dog/regions.js +61 -0
  187. package/vendor/procedural-animals/src/species/dog/rig.js +129 -0
  188. package/vendor/procedural-animals/src/species/dog/sculpt.js +246 -0
  189. package/vendor/procedural-animals/src/species/eagle/coat.js +250 -0
  190. package/vendor/procedural-animals/src/species/eagle/index.js +76 -0
  191. package/vendor/procedural-animals/src/species/eagle/joints.js +3 -0
  192. package/vendor/procedural-animals/src/species/eagle/motion.js +137 -0
  193. package/vendor/procedural-animals/src/species/eagle/regions.js +57 -0
  194. package/vendor/procedural-animals/src/species/eagle/rig.js +107 -0
  195. package/vendor/procedural-animals/src/species/eagle/sculpt.js +137 -0
  196. package/vendor/procedural-animals/src/species/fish/coat.js +271 -0
  197. package/vendor/procedural-animals/src/species/fish/index.js +88 -0
  198. package/vendor/procedural-animals/src/species/fish/motion.js +92 -0
  199. package/vendor/procedural-animals/src/species/fish/regions.js +27 -0
  200. package/vendor/procedural-animals/src/species/fish/rig.js +143 -0
  201. package/vendor/procedural-animals/src/species/fish/sculpt.js +214 -0
  202. package/vendor/procedural-animals/src/species/fish/variants.js +126 -0
  203. package/vendor/procedural-animals/src/species/fox/behaviour.js +314 -0
  204. package/vendor/procedural-animals/src/species/fox/coat.js +444 -0
  205. package/vendor/procedural-animals/src/species/fox/index.js +120 -0
  206. package/vendor/procedural-animals/src/species/fox/motion.js +81 -0
  207. package/vendor/procedural-animals/src/species/fox/regions.js +59 -0
  208. package/vendor/procedural-animals/src/species/fox/rig.js +91 -0
  209. package/vendor/procedural-animals/src/species/fox/sculpt.js +202 -0
  210. package/vendor/procedural-animals/src/species/frog/behaviour.js +590 -0
  211. package/vendor/procedural-animals/src/species/frog/coat.js +305 -0
  212. package/vendor/procedural-animals/src/species/frog/index.js +97 -0
  213. package/vendor/procedural-animals/src/species/frog/motion.js +69 -0
  214. package/vendor/procedural-animals/src/species/frog/regions.js +46 -0
  215. package/vendor/procedural-animals/src/species/frog/rig.js +156 -0
  216. package/vendor/procedural-animals/src/species/frog/sculpt.js +240 -0
  217. package/vendor/procedural-animals/src/species/goat/behaviour.js +509 -0
  218. package/vendor/procedural-animals/src/species/goat/coat.js +590 -0
  219. package/vendor/procedural-animals/src/species/goat/index.js +149 -0
  220. package/vendor/procedural-animals/src/species/goat/motion.js +95 -0
  221. package/vendor/procedural-animals/src/species/goat/regions.js +98 -0
  222. package/vendor/procedural-animals/src/species/goat/rig.js +181 -0
  223. package/vendor/procedural-animals/src/species/goat/sculpt.js +547 -0
  224. package/vendor/procedural-animals/src/species/horse/coat.js +361 -0
  225. package/vendor/procedural-animals/src/species/horse/coatSeam.js +122 -0
  226. package/vendor/procedural-animals/src/species/horse/index.js +80 -0
  227. package/vendor/procedural-animals/src/species/horse/motion.js +245 -0
  228. package/vendor/procedural-animals/src/species/horse/regions.js +60 -0
  229. package/vendor/procedural-animals/src/species/horse/rig.js +117 -0
  230. package/vendor/procedural-animals/src/species/horse/sculpt.js +316 -0
  231. package/vendor/procedural-animals/src/species/horse/seamWeights.js +272 -0
  232. package/vendor/procedural-animals/src/species/lion/behaviour.js +509 -0
  233. package/vendor/procedural-animals/src/species/lion/coat.js +1016 -0
  234. package/vendor/procedural-animals/src/species/lion/index.js +111 -0
  235. package/vendor/procedural-animals/src/species/lion/motion.js +87 -0
  236. package/vendor/procedural-animals/src/species/lion/regions.js +106 -0
  237. package/vendor/procedural-animals/src/species/lion/rig.js +110 -0
  238. package/vendor/procedural-animals/src/species/lion/sculpt.js +384 -0
  239. package/vendor/procedural-animals/src/species/lion/whiskers.js +132 -0
  240. package/vendor/procedural-animals/src/species/pig/behaviour.js +575 -0
  241. package/vendor/procedural-animals/src/species/pig/coat.js +730 -0
  242. package/vendor/procedural-animals/src/species/pig/ears.js +242 -0
  243. package/vendor/procedural-animals/src/species/pig/index.js +161 -0
  244. package/vendor/procedural-animals/src/species/pig/motion.js +105 -0
  245. package/vendor/procedural-animals/src/species/pig/regions.js +100 -0
  246. package/vendor/procedural-animals/src/species/pig/rig.js +152 -0
  247. package/vendor/procedural-animals/src/species/pig/sculpt.js +382 -0
  248. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +605 -0
  249. package/vendor/procedural-animals/src/species/rabbit/coat.js +285 -0
  250. package/vendor/procedural-animals/src/species/rabbit/index.js +85 -0
  251. package/vendor/procedural-animals/src/species/rabbit/motion.js +85 -0
  252. package/vendor/procedural-animals/src/species/rabbit/regions.js +48 -0
  253. package/vendor/procedural-animals/src/species/rabbit/rig.js +99 -0
  254. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +148 -0
  255. package/vendor/procedural-animals/src/species/rat/behaviour.js +539 -0
  256. package/vendor/procedural-animals/src/species/rat/coat.js +341 -0
  257. package/vendor/procedural-animals/src/species/rat/index.js +111 -0
  258. package/vendor/procedural-animals/src/species/rat/motion.js +90 -0
  259. package/vendor/procedural-animals/src/species/rat/regions.js +92 -0
  260. package/vendor/procedural-animals/src/species/rat/rig.js +112 -0
  261. package/vendor/procedural-animals/src/species/rat/sculpt.js +163 -0
  262. package/vendor/procedural-animals/src/species/rat/whiskers.js +119 -0
  263. package/vendor/procedural-animals/src/species/shark/coat.js +383 -0
  264. package/vendor/procedural-animals/src/species/shark/index.js +104 -0
  265. package/vendor/procedural-animals/src/species/shark/motion.js +69 -0
  266. package/vendor/procedural-animals/src/species/shark/regions.js +34 -0
  267. package/vendor/procedural-animals/src/species/shark/rig.js +194 -0
  268. package/vendor/procedural-animals/src/species/shark/sculpt.js +343 -0
  269. package/vendor/procedural-animals/src/species/shark/variants.js +105 -0
  270. package/vendor/procedural-animals/src/species/sheep/behaviour.js +522 -0
  271. package/vendor/procedural-animals/src/species/sheep/coat.js +529 -0
  272. package/vendor/procedural-animals/src/species/sheep/index.js +152 -0
  273. package/vendor/procedural-animals/src/species/sheep/motion.js +86 -0
  274. package/vendor/procedural-animals/src/species/sheep/neck.js +41 -0
  275. package/vendor/procedural-animals/src/species/sheep/regions.js +62 -0
  276. package/vendor/procedural-animals/src/species/sheep/rig.js +163 -0
  277. package/vendor/procedural-animals/src/species/sheep/sculpt.js +542 -0
  278. package/vendor/procedural-animals/src/species/snake/coat.js +315 -0
  279. package/vendor/procedural-animals/src/species/snake/index.js +73 -0
  280. package/vendor/procedural-animals/src/species/snake/motion.js +51 -0
  281. package/vendor/procedural-animals/src/species/snake/regions.js +51 -0
  282. package/vendor/procedural-animals/src/species/snake/rig.js +108 -0
  283. package/vendor/procedural-animals/src/species/snake/sculpt.js +122 -0
  284. package/vendor/procedural-animals/src/species/spider/coat.js +279 -0
  285. package/vendor/procedural-animals/src/species/spider/index.js +78 -0
  286. package/vendor/procedural-animals/src/species/spider/motion.js +72 -0
  287. package/vendor/procedural-animals/src/species/spider/regions.js +87 -0
  288. package/vendor/procedural-animals/src/species/spider/rig.js +118 -0
  289. package/vendor/procedural-animals/src/species/spider/sculpt.js +182 -0
  290. package/vendor/procedural-animals/src/species/wolf/behaviour.js +160 -0
  291. package/vendor/procedural-animals/src/species/wolf/coat.js +749 -0
  292. package/vendor/procedural-animals/src/species/wolf/index.js +107 -0
  293. package/vendor/procedural-animals/src/species/wolf/motion.js +80 -0
  294. package/vendor/procedural-animals/src/species/wolf/regions.js +57 -0
  295. package/vendor/procedural-animals/src/species/wolf/rig.js +87 -0
  296. package/vendor/procedural-animals/src/species/wolf/sculpt.js +339 -0
  297. package/vendor/procedural-animals/src/worker.js +29 -0
@@ -0,0 +1,1301 @@
1
+ // Coverings: fur, bare skin, leather, keratin, scales, wet skin, chitin, feathers and fin membranes
2
+ // (material 11: translucent, rays; drawn as a separate transparent pass, see membranes.js), all lit by the
3
+ // scene's own three.js lights (directional + shadows, point + shadows, spot + shadows, hemisphere,
4
+ // ambient, light probes), fog and tone mapping.
5
+ //
6
+ // Three passes share one shading model:
7
+ // - base: the opaque, dual-quaternion skinned surface. Every material id (tint.w) gets its own
8
+ // surface detail here (pebbled leather, pores, growth striations, overlapping scales, granular
9
+ // wet skin, chitin segments, feather shingles with rachis and barbs). Without shells (crowd tier,
10
+ // coverings hidden) fur still reads as fur on the base: hair-flow streaks and anisotropic sheen.
11
+ // - shells: ONE instanced draw; gl_InstanceID = shell index. Strands are hashed from bind-space
12
+ // position (so they stick to the skin under any pose) and grouped into clumps: going up the
13
+ // shells, strands converge toward their clump centre, so the top of the coat breaks into tufts
14
+ // with dark gaps between them. Clumps vary in length, shade and lean, and a clump either belongs
15
+ // to a spot or not, which rags the spot edges. Shells draw wherever furLen > 0 on fur (0), bare
16
+ // skin (4: sparse bristles) and feathers (9: a soft down fringe).
17
+ // - fins: one camera-facing strand card per skin vertex, shown only near the silhouette, so the
18
+ // outline is made of individual hairs instead of a smooth edge.
19
+ // Depth: the shells write depth (drawn inner layer to outer), so a coat hides what lies behind it however
20
+ // the draws are ordered: the far legs behind a belly fringe, another part's fins (drawn after the shells,
21
+ // they used to draw the outlines of every part behind the coat across it). Between the strands each
22
+ // layer carries an undercoat (render hint undercoat: the hair the strand grid does not resolve), an
23
+ // optical depth taken along the line of sight and spread over the shell count, so the coat is as
24
+ // opaque at every tier and where one looks along it, shaded like the deep coat it stands in for.
25
+ //
26
+ // Hair lighting per light: wrapped diffuse + two-lobe Kajiya-Kay (R / TRT) + back-scatter through
27
+ // thin fur, depth-in-fur and between-tuft occlusion. Surfaces use wrapped / subsurface diffuse,
28
+ // normalised Blinn-Phong, an optional clear coat and anisotropic lobes. Units follow
29
+ // MeshStandardMaterial: a light of intensity I lights a covering of albedo a like a standard
30
+ // material of colour a (radiance = a * E / PI), so animals sit correctly next to your scenery.
31
+ //
32
+ // Every animal owns its uniforms (three.js shares the compiled programs). Species render hints,
33
+ // `species.render = { markColor, strandDensity, clumpDensity, shellScale, finWidth }`:
34
+ // markColor linear rgb of crisp marks (tear lines, eye rims, lips) default cheetah black
35
+ // strandDensity strands per metre (planar hash grid) default 620
36
+ // clumpDensity multiplier on tuft density default 1
37
+ // shellScale multiplier on hair length for shells and fins default 1
38
+ // finWidth silhouette card width as a fraction of hair length default 0.5
39
+ // fins false: no silhouette fins (very long coats: bear) default true
40
+ // finMask true: on fur (material 0) the coat's surf.y (unused by fur) thins the silhouette fins,
41
+ // 0 = full fins .. 1 = none (the cat's head: the head's silhouette ring of fins stood
42
+ // in front of the neck fur as a hood round the face) default false
43
+ // finMinLen hair length (m) below which no silhouette fins are drawn default 0.0058 (a species with a
44
+ // short face coat and a long mane raises it: fins on the mane only)
45
+ // strandBlend 0: the shells' strands are hashed on one axis plane picked by the rest normal's largest
46
+ // component (default); > 0: near a switch of that axis the second plane's strands are
47
+ // cross-faded in over this band of normal components (e.g. 0.12), so no seam line shows where
48
+ // the plane switches (a short-coated broad face tilted ~45 deg: a lion's forehead drew a
49
+ // rectangle); costs a second strand evaluation per shell fragment
50
+ // scaleEdge contrast of the scale rims, overlap shadows and relief (material 6) default 1 (fish 0.15)
51
+ // featherEdge contrast of the contour-feather shingles (material 9) default 1 (pale birds ~0.5)
52
+ // denticles 0..1: material 6 is shark skin instead of overlapping scales: dermal denticles ridged
53
+ // along the comb (surf.y = denticle size), a matte fine grain streaked along the body, a
54
+ // soft broad anisotropic sheen across the ridges, no clear coat default 0
55
+ // raiseLen [from, full] hair length (m) over which piloerection (state.furRaise) lifts the hair
56
+ // default [0.025, 0.05] (wolf hackles); short-coated animals lower it (cat [0.006, 0.014])
57
+ // leatherTint 0..1: nose leather (material 1) takes its colour from the tint (pink or brick noses and
58
+ // pads as leather, cat); default 0: the dark leather of every other species
59
+ // leatherRelief 0..1: depth of the leather's pebbling (crease darkening, cobble shade, bump); default 1.
60
+ // A small nose in daylight (the cat's) read speckled at full relief
61
+ // skinScatter bare skin (material 4): [r, g, b, wrap], the tint of the light wrapped past the terminator
62
+ // (subsurface) and how far it wraps default [0.85, 0.35, 0.25, 0.55]
63
+ // skinTrans bare skin: [base, perVertex], light through the skin lit from behind = base + perVertex x
64
+ // surf.y (per vertex, unused otherwise by skin: thin ears and tails glow, a thick body
65
+ // does not) default [0.5, 0]
66
+ // noseRoughness roughness of the nose leather (material 1) default 0.42
67
+ // noseMarks 1: marks (coat mark < 0) are drawn on the nose leather too, as openings (the nostrils:
68
+ // mark colour, no sheen from inside); default 0 (the leather hides marks)
69
+ // noseTint 1: the nose leather takes its albedo from the coat's per-vertex colour (pale rims, a
70
+ // septum painted by the species) instead of the fixed dark grey; default 0. The same
71
+ // blend as leatherTint 1 (both read the per-vertex tint): the larger of the two applies
72
+ // clumpLen [from, full] hair length (m) over which the tufts form (clumping strength 0 -> 1)
73
+ // default [0.0055, 0.016]; a long coat with a sleek face raises it, so 1-3 cm facial and
74
+ // ear hair lies smooth instead of curling into round tufts (bear [0.012, 0.045])
75
+ // shellShadowRoot 0..1: where the shells look up cast shadows, from each layer's own position (0) to the hair's
76
+ // root (1). At 0 every layer of a long coat crosses a cast shadow's edge at its own place, and seen
77
+ // at a grazing angle the stack shows as torn dark sheets (bear: the neck's 5-10 cm hair in the
78
+ // head's shadow behind the jaw); at 1 a strand is lit or shadowed as one. default 0
79
+ // undercoat optical depth per unit of coat height of the hair between the strands, at the roots (fading
80
+ // to none at 0.9 of the hair's height): 0 = strands only (what lay under a long coat showed between
81
+ // them: a leg under a fringe, a folded wing in down) default 3
82
+ // shellDepthBias [k, from]: a long coat's shells are depth-tested k x (hair length - from) closer to the camera
83
+ // than they are (a strand's every layer, constant along the strand). The shells slide along the comb
84
+ // as they rise (up to 0.85 x the hair length), so where the skin creases (a sheep's throat when the
85
+ // head drops to graze or turns aside) the layers of one side dive under the skin of the other and the
86
+ // crease shows as a jagged bald line; the bias keeps them in front of a fold that shallow. Shorter hair
87
+ // than `from` is untouched (face, lids). default [0, 0] (off)
88
+ // Nose leather (material 1) takes its roughness from -surf.y when surf.y < 0 (a matte nose or paw pad:
89
+ // bear nose -0.62, paw pads -0.75); otherwise from the render hint noseRoughness (satin 0.42 by default).
90
+ // matBorders true: a material border runs through the middle of the edges between the two
91
+ // materials (interpolated one-hot weights) instead of rounding the interpolated id,
92
+ // which shades a strip of nose / dark skin / mouth (ids 1-3) between fur (0) and bare
93
+ // skin (4) or keratin (5): black-and-red rims round pink noses, udders and hooves.
94
+ // default false (the rounding); one more varying (vec4)
95
+ // flatMaterial true: each triangle takes one material (its provoking vertex's) instead of rounding
96
+ // the interpolated material id; default false. The id is a smooth varying, so a
97
+ // triangle from fur (0) to bare skin (4), keratin (5) or wet skin (7) passes through
98
+ // thin bands of the ids in between, whose albedos are fixed (nose 1 dark, lids 2 black,
99
+ // mouth 3 red): a dark / red rim along every bare-skin border (a cow's pink muzzle,
100
+ // udder, lid rims, coronets). Species whose bare skin meets fur opt in.
101
+ // matBorders (goat) answers the same bands by splitting each triangle through its edge
102
+ // middles instead; a species picks one (with both set, flatMaterial applies)
103
+ import * as THREE from 'three';
104
+ import { DQS_GLSL, DQS_ATTR_GLSL } from './dqs.js';
105
+
106
+ export const RENDER_DEFAULTS = {
107
+ markColor: [0.016, 0.0105, 0.0075],
108
+ strandDensity: 620,
109
+ clumpDensity: 1,
110
+ shellScale: 1,
111
+ finWidth: 0.5,
112
+ finMinLen: 0.0058,
113
+ strandBlend: 0,
114
+ scaleEdge: 1,
115
+ featherEdge: 1,
116
+ denticles: 0,
117
+ raiseLen: [0.025, 0.05],
118
+ leatherTint: 0,
119
+ leatherRelief: 1,
120
+ skinScatter: [0.85, 0.35, 0.25, 0.55],
121
+ skinTrans: [0.5, 0],
122
+ noseRoughness: 0.42,
123
+ noseMarks: 0,
124
+ noseTint: 0,
125
+ clumpLen: [0.0055, 0.016],
126
+ shellShadowRoot: 0,
127
+ shellDepthBias: [0, 0],
128
+ undercoat: 3,
129
+ flatMaterial: false,
130
+ };
131
+
132
+ export const MAX_PAINTED_EYES = 8;
133
+
134
+ const common = /* glsl */ `
135
+ uniform float uShells;
136
+ uniform float uShellsOn;
137
+ uniform float uFinFade; // fins: opacity of the whole fin pass (tier transitions, animalObject setQuality)
138
+ uniform float uUnder; // shells: the undercoat's optical depth per unit coat height at the roots (render hint undercoat)
139
+ uniform float uFurScale;
140
+ uniform vec3 uFurForce;
141
+ uniform float uFurRaise;
142
+ uniform vec2 uRaiseLen; // hair lengths over which piloerection sets in (render hint raiseLen)
143
+ uniform vec2 uClumpLen; // hair lengths over which the tufts form (render hint clumpLen)
144
+ uniform float uTime;
145
+ uniform float uStrandDensity;
146
+ uniform float uClumpDensity;
147
+ uniform float uFinWidth;
148
+ uniform float uFinMask;
149
+ uniform float uFinMinLen;
150
+ uniform float uStrandBlend;
151
+ uniform float uWind;
152
+ uniform float uDebug;
153
+ uniform float uForceMat;
154
+ uniform float uFlatMat; // render hint flatMaterial: the material id from the provoking vertex (vMatF)
155
+ uniform vec3 uMarkColor;
156
+ uniform float uNoseRough; // render hint noseRoughness
157
+ uniform float uNoseMarks; // render hint noseMarks
158
+ uniform float uNoseTint; // render hint noseTint
159
+ uniform float uFill;
160
+ uniform float uScaleEdge;
161
+ uniform float uFeatherEdge;
162
+ uniform float uLeatherTint;
163
+ uniform float uLeatherRelief;
164
+ uniform vec4 uSkinScatter; // bare skin: subsurface tint (rgb) and wrap (render hint skinScatter)
165
+ uniform vec2 uSkinTrans; // bare skin: transmission = x + y * surf.y (render hint skinTrans)
166
+ uniform float uDenticle;
167
+ uniform float uPaintEyes;
168
+ // environment light (setEnvironmentLight): the scene environment's diffuse irradiance as 9 world-space
169
+ // SH coefficients (three.js SphericalHarmonics3 / LightProbe convention), scaled; uEnvOn 0 = none (default)
170
+ uniform vec3 uEnvSH[9];
171
+ uniform float uEnvOn;
172
+ uniform vec3 uBaseTone; // fur albedo gain of the base when it is seen without shells (crowd tier)
173
+ uniform float uShellShadowRoot; // shells: cast shadows looked up toward the hair's root (render hint shellShadowRoot)
174
+ uniform vec2 uShellBias; // shells: depth pulled toward the camera by x * (hair length - y) (render hint shellDepthBias)
175
+ uniform vec2 uSeam; // cross-fade seam skirt (build/seams.js): x = sink depth (m), y = fade where the base stops
176
+ uniform vec4 uEyes[${MAX_PAINTED_EYES}];
177
+ uniform vec3 uEyeTint[${MAX_PAINTED_EYES}];
178
+ varying vec3 vRest;
179
+ varying vec3 vRestN;
180
+ varying vec3 vRestC;
181
+ varying vec3 vWorldPos;
182
+ varying vec3 vN;
183
+ varying vec3 vT;
184
+ varying vec4 vTint;
185
+ flat varying float vMatF;
186
+ varying vec4 vCoat;
187
+ varying vec4 vPat;
188
+ varying vec4 vSurf;
189
+ varying vec4 vFin; // x across 0..1 (fins), y up 0..1 (fins), z silhouette visibility, w = height in coat
190
+ #ifdef PA_DEBUG
191
+ varying vec3 vDbg;
192
+ #endif
193
+ // which material ids grow hair in the shells / fins: fur, bare skin (bristles), chitin (setae), feathers (down)
194
+ bool hairy(float m) { return m < 0.5 || abs(m - 4.0) < 0.5 || abs(m - 8.0) < 0.5 || abs(m - 9.0) < 0.5; }
195
+ float matId(float a) { return uForceMat >= 0.0 ? uForceMat : floor(a + 0.5); }
196
+ #ifdef PA_MAT_BORDERS
197
+ // (render hint matBorders) the material of a fragment is the one whose interpolated one-hot weight is the
198
+ // largest (ids 0..3 in vMatW; the rest, ids >= 4, recovered from the interpolated id), so the border
199
+ // between two materials runs through the middle of the edges between them. Rounding the interpolated
200
+ // id shaded a strip of every id in between: nose, dark skin and mouth (1..3) round a pink nose, an
201
+ // udder or a hoof (fur 0 next to skin 4 / keratin 5).
202
+ varying vec4 vMatW;
203
+ vec4 matOneHot(float a) { return vec4(equal(vec4(floor(a + 0.5)), vec4(0.0, 1.0, 2.0, 3.0))); }
204
+ float matIdFrag(float a) {
205
+ if (uForceMat >= 0.0) return uForceMat;
206
+ vec4 w = vMatW;
207
+ float r = 1.0 - (w.x + w.y + w.z + w.w), best = w.x, id = 0.0;
208
+ if (w.y > best) { best = w.y; id = 1.0; }
209
+ if (w.z > best) { best = w.z; id = 2.0; }
210
+ if (w.w > best) { best = w.w; id = 3.0; }
211
+ if (r > best) id = floor((a - (w.y + 2.0 * w.z + 3.0 * w.w)) / max(r, 1e-4) + 0.5);
212
+ return id;
213
+ }
214
+ #else
215
+ float matIdFrag(float a) { return matId(a); }
216
+ #endif
217
+ `;
218
+
219
+ // how a hair rises from the skin: lifted along the normal, laid down along the comb, bent by force
220
+ const hairProfile = /* glsl */ `
221
+ const float LIFT = 0.5;
222
+ const float SLIDE = 0.85;
223
+ float hash31(vec3 p) { p = fract(p * 0.1031); p += dot(p, p.yzx + 33.33); return fract((p.x + p.y) * p.z); }
224
+ vec3 furForce(vec3 rest, vec3 sN) {
225
+ float ph = hash31(floor(rest * 60.0)) * 6.2831;
226
+ vec3 flutter = vec3(sin(uTime * 7.0 + ph), 0.0, cos(uTime * 5.3 + ph * 1.3)) * uWind * 0.25;
227
+ vec3 force = uFurForce + flutter;
228
+ if (any(isnan(force)) || any(isinf(force))) force = vec3(0.0, -0.2, 0.0);
229
+ // remove the part of the force that would push the hair into the skin
230
+ force -= sN * min(0.0, dot(force, sN)) * 0.9;
231
+ return force;
232
+ }
233
+ // piloerection (hackles): long hair stands up off the skin
234
+ float hairRaise(float L) { return uFurRaise * smoothstep(uRaiseLen.x, uRaiseLen.y, L / max(uFurScale, 1e-3)); }
235
+ vec3 hairOffset(vec3 sN, vec3 sC, vec3 force, float L, float h) {
236
+ float r = hairRaise(L);
237
+ return sN * (L * (LIFT + 0.45 * r) * h) + sC * (L * SLIDE * (1.0 - 0.55 * r) * (h * h * 0.6 + h * 0.4)) + force * (L * h * h * (1.0 - 0.6 * r));
238
+ }
239
+ vec3 hairDir(vec3 sN, vec3 sC, vec3 force, float L, float h) {
240
+ float r = hairRaise(L);
241
+ return normalize(sN * (LIFT + 0.45 * r) + sC * SLIDE * (1.0 - 0.55 * r) * (0.4 + 1.2 * h) + force * (2.0 * h * (1.0 - 0.6 * r)));
242
+ }
243
+ void debugColour(vec4 si, vec4 sw) {
244
+ #ifdef PA_DEBUG
245
+ vec3 bc0 = fract(vec3(0.13, 0.37, 0.71) * si.x * 7.31 + vec3(0.1, 0.5, 0.3));
246
+ vec3 bc1 = fract(vec3(0.13, 0.37, 0.71) * si.y * 7.31 + vec3(0.1, 0.5, 0.3));
247
+ vec3 bc2 = fract(vec3(0.13, 0.37, 0.71) * si.z * 7.31 + vec3(0.1, 0.5, 0.3));
248
+ vec3 bc3 = fract(vec3(0.13, 0.37, 0.71) * si.w * 7.31 + vec3(0.1, 0.5, 0.3));
249
+ vDbg = bc0 * sw.x + bc1 * sw.y + bc2 * sw.z + bc3 * sw.w;
250
+ #endif
251
+ }
252
+ `;
253
+
254
+ const vertex = /* glsl */ `
255
+ #include <common>
256
+ #include <shadowmap_pars_vertex>
257
+ #include <fog_pars_vertex>
258
+ ${common}
259
+ ${DQS_GLSL}
260
+ ${DQS_ATTR_GLSL}
261
+ attribute vec3 aComb;
262
+ attribute vec4 aTint;
263
+ attribute vec4 aCoat;
264
+ attribute vec4 aPat;
265
+ attribute vec4 aSurf;
266
+ ${hairProfile}
267
+
268
+ void main() {
269
+ vec3 sP = position, sN = normal, sC = aComb;
270
+ dqsSkin(sP, sN, sC);
271
+ sP = (modelMatrix * vec4(sP, 1.0)).xyz;
272
+ sN = normalize(mat3(modelMatrix) * sN);
273
+ sC = mat3(modelMatrix) * sC;
274
+ sC = normalize(sC - sN * dot(sC, sN) + 1e-6);
275
+ #ifndef FUR_SHELL
276
+ // seams (build/seams.js): two overlapping surfaces hand over at fade 0.5 but do not coincide there.
277
+ // Past the midline the base does not stop at a step one could look through: it sinks along its
278
+ // normal under the surface taking over (which wins the depth test), down to fade uSeam.y.
279
+ // (quadratic: tangent to the skin at the midline, so coarse triangles across the band do not fold)
280
+ float seamT = clamp((0.5 - aCoat.w) / max(0.5 - uSeam.y, 1e-3), 0.0, 1.0);
281
+ sP -= sN * (uSeam.x * seamT * seamT);
282
+ #endif
283
+
284
+ float h = 0.0;
285
+ vec3 wp = sP;
286
+ vec3 strand = sC;
287
+ #ifdef FUR_SHELL
288
+ float L = aCoat.z * uFurScale;
289
+ vec3 force = furForce(position, sN);
290
+ h = (float(gl_InstanceID) + 1.0) / uShells;
291
+ // (a fractional shell count during a tier transition: the topmost partial layer lies at the tips and
292
+ // fades in by the fraction)
293
+ float layerFade = clamp(1.0 - (h - 1.0) * uShells, 0.0, 1.0);
294
+ h = min(h, 1.0);
295
+ wp = sP + hairOffset(sN, sC, force, L, h);
296
+ strand = hairDir(sN, sC, force, L, h);
297
+ #endif
298
+ debugColour(skinIndex, skinWeight);
299
+ vRest = position;
300
+ vRestN = normal;
301
+ vRestC = aComb;
302
+ vWorldPos = wp;
303
+ vN = sN;
304
+ vT = strand;
305
+ vTint = aTint;
306
+ #ifdef PA_MAT_BORDERS
307
+ vMatW = matOneHot(aTint.a);
308
+ #endif
309
+ vMatF = aTint.a;
310
+ vCoat = aCoat;
311
+ vPat = aPat;
312
+ vSurf = aSurf;
313
+ vFin = vec4(0.0, 0.0, 0.0, h);
314
+ #ifdef FUR_SHELL
315
+ vFin.z = layerFade;
316
+ #endif
317
+
318
+ vec4 worldPosition = vec4(wp, 1.0);
319
+ vec4 mvPosition = viewMatrix * worldPosition;
320
+ gl_Position = projectionMatrix * mvPosition;
321
+ #ifdef FUR_SHELL
322
+ // (render hint shellDepthBias: the layer's depth taken from a point that much nearer the camera, so a layer that
323
+ // slid into a crease of its own skin still shows; its place on screen is unchanged)
324
+ float depthBias = uShellBias.x * max(0.0, L - uShellBias.y);
325
+ if (depthBias > 0.0) {
326
+ vec4 q = projectionMatrix * viewMatrix * vec4(wp + normalize(cameraPosition - wp) * depthBias, 1.0);
327
+ gl_Position.z = q.z / q.w * gl_Position.w;
328
+ }
329
+ #endif
330
+ vec3 transformedNormal = (viewMatrix * vec4(sN, 0.0)).xyz;
331
+ #ifdef FUR_SHELL
332
+ // (render hint shellShadowRoot: the cast shadow of a layer taken toward its strand's root, 4 mm off the skin)
333
+ if (uShellShadowRoot > 0.0) worldPosition = vec4(mix(wp, sP + sN * 0.004, uShellShadowRoot), 1.0);
334
+ #endif
335
+ #include <shadowmap_vertex>
336
+ #include <fog_vertex>
337
+ }
338
+ `;
339
+
340
+ // Fins: the template quad (position.x in [-0.5, 0.5] across, position.y in [0, 1] up) is instanced
341
+ // once per skin vertex; all per-vertex data arrives as instanced attributes.
342
+ const finVertex = /* glsl */ `
343
+ #include <common>
344
+ #include <shadowmap_pars_vertex>
345
+ #include <fog_pars_vertex>
346
+ ${common}
347
+ ${DQS_GLSL}
348
+ attribute vec3 iPos;
349
+ attribute vec3 iNrm;
350
+ attribute vec3 iComb;
351
+ attribute vec4 iTint;
352
+ attribute vec4 iCoat;
353
+ attribute vec4 iPat;
354
+ attribute vec4 iSurf;
355
+ attribute vec4 iSkinIndex;
356
+ attribute vec4 iSkinWeight;
357
+ ${hairProfile}
358
+
359
+ void main() {
360
+ vec3 sP = iPos, sN = iNrm, sC = iComb;
361
+ dqsSkinW(sP, sN, sC, iSkinIndex, iSkinWeight);
362
+ sP = (modelMatrix * vec4(sP, 1.0)).xyz;
363
+ sN = normalize(mat3(modelMatrix) * sN);
364
+ sC = mat3(modelMatrix) * sC;
365
+ sC = normalize(sC - sN * dot(sC, sN) + 1e-6);
366
+ float L = iCoat.z * uFurScale;
367
+ vec3 V = normalize(cameraPosition - sP);
368
+ float ndv = dot(sN, V);
369
+ // only near the silhouette; everything else is collapsed before rasterisation
370
+ float vis = 1.0 - smoothstep(0.16, 0.42, abs(ndv));
371
+ float m = matId(iTint.a);
372
+ if (uFinMask > 0.5 && m < 0.5) vis *= clamp(1.0 - iSurf.y, 0.0, 1.0);
373
+ // (no fins on the short sleek face fur: they would blur the tear lines and eye rims)
374
+ if (vis < 0.01 || L < uFinMinLen || !hairy(m) || iCoat.w < 0.05) {
375
+ gl_Position = vec4(0.0, 0.0, -2.0, 1.0);
376
+ vFin = vec4(0.0);
377
+ return;
378
+ }
379
+ vec3 force = furForce(iPos, sN);
380
+ float y = position.y;
381
+ vec3 tipDir = hairDir(sN, sC, force, L, 1.0);
382
+ vec3 side = cross(tipDir, V);
383
+ side = dot(side, side) > 1e-8 ? normalize(side) : normalize(cross(sN, V) + 1e-4);
384
+ float w = clamp(L * uFinWidth, 0.0028, 0.012) * (abs(m - 4.0) < 0.5 ? 0.45 : 1.0);
385
+ // hairs on the silhouette stand a little prouder than the lying coat
386
+ vec3 wp = sP + hairOffset(sN, sC, force, L * 1.08, y) + sN * (L * 0.08 * y) + side * (position.x * w);
387
+ debugColour(iSkinIndex, iSkinWeight);
388
+ vRest = iPos;
389
+ vRestN = iNrm;
390
+ vRestC = iComb;
391
+ vWorldPos = wp;
392
+ vN = sN;
393
+ vT = hairDir(sN, sC, force, L, y);
394
+ vTint = iTint;
395
+ #ifdef PA_MAT_BORDERS
396
+ vMatW = matOneHot(iTint.a);
397
+ #endif
398
+ vMatF = iTint.a;
399
+ vCoat = iCoat;
400
+ vPat = iPat;
401
+ vSurf = iSurf;
402
+ vFin = vec4(position.x + 0.5, y, vis, y);
403
+
404
+ vec4 worldPosition = vec4(wp, 1.0);
405
+ vec4 mvPosition = viewMatrix * worldPosition;
406
+ gl_Position = projectionMatrix * mvPosition;
407
+ // (the shells write depth, so a coat in front hides what lies behind it, fins included; a fin is
408
+ // depth-tested from a point 0.6 hair lengths nearer the camera (the coat it stands in rises 0.5), so its
409
+ // own coat does not hide it, while a part in front of it (a leg, a belly fringe) still does, and so does
410
+ // the skin of a limb as thick as its hair: a tarantula's leg seen end-on hid the cards round its far side)
411
+ vec4 qF = projectionMatrix * viewMatrix * vec4(wp + V * (L * 0.6), 1.0);
412
+ gl_Position.z = qF.z / qF.w * gl_Position.w;
413
+ vec3 transformedNormal = (viewMatrix * vec4(sN, 0.0)).xyz;
414
+ #include <shadowmap_vertex>
415
+ #include <fog_vertex>
416
+ }
417
+ `;
418
+
419
+ const fragment = /* glsl */ `
420
+ #include <common>
421
+ #include <packing>
422
+ #include <lights_pars_begin>
423
+ #include <shadowmap_pars_fragment>
424
+ #include <fog_pars_fragment>
425
+ ${common}
426
+
427
+ float hash12(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * 0.1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); }
428
+ vec2 hash22(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973)); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.xx + p3.yz) * p3.zy); }
429
+ float hash13(vec3 p) { p = fract(p * 0.1031); p += dot(p, p.zyx + 31.32); return fract((p.x + p.y) * p.z); }
430
+ vec3 hash33(vec3 p) { p = fract(p * vec3(0.1031, 0.1030, 0.0973)); p += dot(p, p.yxz + 33.33); return fract((p.xxy + p.yxx) * p.zyx); }
431
+ float vnoise(vec3 p) {
432
+ vec3 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f);
433
+ return mix(mix(mix(hash13(i), hash13(i + vec3(1, 0, 0)), f.x), mix(hash13(i + vec3(0, 1, 0)), hash13(i + vec3(1, 1, 0)), f.x), f.y),
434
+ mix(mix(hash13(i + vec3(0, 0, 1)), hash13(i + vec3(1, 0, 1)), f.x), mix(hash13(i + vec3(0, 1, 1)), hash13(i + vec3(1, 1, 1)), f.x), f.y), f.z);
435
+ }
436
+ #ifdef FUR_SHELL
437
+ // strands and clumps of one planar projection (uv on the axis plane axisId): the coverage at shell height h
438
+ // (strand x clump footprint, before the coat's fade), and the strand / clump randoms for the shading
439
+ float strandLayer(vec2 uv, float axisId, float h, float furLen, float dens, float cDens, float clumpK, bool bristle,
440
+ out float sharp, out float sv, out vec4 ch, out float fromC, out float inClump) {
441
+ vec2 cq = uv * cDens;
442
+ vec2 cb = floor(cq) - 1.0;
443
+ float bd2 = 1e9; vec2 cc = cq; vec2 cId = cb;
444
+ if (clumpK > 0.001) for (int j = 0; j < 3; j++) for (int i = 0; i < 3; i++) {
445
+ vec2 id = cb + vec2(float(i), float(j));
446
+ vec2 c = id + 0.5 + (hash22(id + axisId * 57.3) - 0.5) * 0.9;
447
+ vec2 dv = cq - c;
448
+ float dd = dot(dv, dv);
449
+ if (dd < bd2) { bd2 = dd; cc = c; cId = id; }
450
+ }
451
+ // short sleek fur (face, paws, lower legs) barely clumps; the body coat tufts properly
452
+ ch = mix(vec4(0.5), vec4(hash22(cId + 11.3 + axisId * 57.3), hash22(cId + 23.9 + axisId * 57.3)), clumpK);
453
+ float clumpLen = mix(0.6, 1.12, ch.x);
454
+ // strands converge toward the clump centre as they rise: invert that to find the root seen here
455
+ float pull = 0.7 * pow(h, 1.35) * clumpK;
456
+ vec2 rootC = cc + (cq - cc) / max(0.25, 1.0 - pull);
457
+ fromC = length(rootC - cc);
458
+ vec2 q = rootC / cDens * dens;
459
+ vec2 ci = floor(q);
460
+ vec2 f = fract(q) - 0.5;
461
+ vec2 cid = ci + axisId * 131.7;
462
+ vec2 jit = (hash22(cid) - 0.5) * 0.5;
463
+ sv = hash12(cid + 7.1);
464
+ float sl = mix(0.55, 1.0, hash12(cid + 3.3)) * clumpLen;
465
+ float hr = h / sl;
466
+ float radius = 0.5 * (1.0 - 0.8 * min(hr * hr, 1.0)) * (bristle ? 0.3 : 1.0);
467
+ float d = length(f - jit);
468
+ float aa = fwidth(d) * 0.9 + 0.015;
469
+ sharp = (hr > 1.0) ? 0.0 : smoothstep(radius + aa, radius - aa, d);
470
+ // expected coverage when strands shrink below a pixel
471
+ float pLong = clamp((clumpLen - h) / (0.5 * clumpLen), 0.0, 1.0);
472
+ float expected = pLong * 3.14159 * 0.25 * (1.0 - 0.7 * h * h) * 0.9 * (bristle ? 0.06 : 1.0);
473
+ float pix = length(fwidth(q));
474
+ float cov = mix(sharp, expected, smoothstep(0.45, 1.4, pix));
475
+ // outside the clump's shrinking footprint there is no hair at this height: gaps between tufts
476
+ float ea = fwidth(fromC) * 0.8 + 0.03;
477
+ inClump = mix(1.0, smoothstep(0.56 + ea, 0.56 - ea, fromC), clumpK);
478
+ inClump = mix(inClump, 0.8, smoothstep(0.5, 1.5, fwidth(cq.x) + fwidth(cq.y)));
479
+ return cov * inClump;
480
+ }
481
+ #endif
482
+ float lum(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); }
483
+ // fades a procedural detail out once it gets smaller than a pixel (no shimmer at a distance)
484
+ float detailFade(float k) { return 1.0 - smoothstep(0.35, 0.9, length(fwidth(vRest)) * k); }
485
+ // noise stretched along a rest-space direction. The along-flow coordinate is measured from anchors
486
+ // snapped to 4 cm cells, so curving flow lines do not shear the pattern; two anchor lattices offset by
487
+ // half a cell are cross-faded so the cell borders do not show.
488
+ float edgeDist(vec3 f) { f = min(f, 1.0 - f); return min(min(f.x, f.y), f.z); }
489
+ float streak(vec3 P, vec3 Tr, float k, float s) {
490
+ vec3 g = P * 25.0;
491
+ float ea = edgeDist(fract(g)), eb = edgeDist(fract(g + 0.5));
492
+ vec3 aA = floor(g) * 0.04, aB = (floor(g + 0.5) - 0.5) * 0.04;
493
+ float st = k * (1.0 - 1.0 / s);
494
+ float nA = vnoise(P * k - Tr * (dot(P - aA, Tr) * st));
495
+ float nB = vnoise(P * k - Tr * (dot(P - aB, Tr) * st) + 17.3);
496
+ float w = ea * ea / (ea * ea + eb * eb + 1e-6);
497
+ return (mix(nB, nA, w) - 0.5) / sqrt(w * w + (1.0 - w) * (1.0 - w)) + 0.5;
498
+ }
499
+
500
+ // Overlapping shingles (scales, contour feathers): jittered cell centres in bind space, each one an
501
+ // ellipse attached at its front and free toward +comb; where several overlap, the most anterior one
502
+ // lies on top. Returns along (-1 front .. 1 free edge), across (-1..1), edge (ellipse radius 0..1),
503
+ // id, and shadow from the free edges of the scales in front.
504
+ struct Shingle { float along; float across; float edge; float id; float shadow; };
505
+ Shingle shingles(vec3 P, vec3 N, vec3 T, vec3 B, float s, float elong, float wid) {
506
+ Shingle r; r.along = 0.0; r.across = 0.0; r.edge = 1.0; r.id = 0.0; r.shadow = 0.0;
507
+ vec3 g = P / s; vec3 gi = floor(g);
508
+ float ords[27]; float es[27];
509
+ float bestOrd = 1e9;
510
+ for (int k = 0; k < 27; k++) {
511
+ vec3 id = gi + vec3(float(k % 3) - 1.0, float((k / 3) % 3) - 1.0, float(k / 9) - 1.0);
512
+ vec3 c = id + 0.5 + (hash33(id) - 0.5) * 0.75;
513
+ vec3 d = g - c;
514
+ es[k] = 9.0; ords[k] = 1e9;
515
+ if (abs(dot(d, N)) > 0.8) continue;
516
+ float a = dot(d, T), b = dot(d, B);
517
+ float al = (a - 0.35) / (0.85 * elong);
518
+ float e = length(vec2(al, b / wid));
519
+ float ord = dot(c, T) + (hash13(id + 3.1) - 0.5) * 0.4;
520
+ es[k] = e; ords[k] = ord;
521
+ if (e < 1.0 && ord < bestOrd) { bestOrd = ord; r.along = al; r.across = b / wid; r.edge = e; r.id = hash13(id + 7.7); }
522
+ }
523
+ for (int k = 0; k < 27; k++) if (ords[k] < bestOrd - 1e-4) r.shadow = max(r.shadow, smoothstep(1.45, 1.0, es[k]));
524
+ if (bestOrd > 1e8) { r.edge = 1.0; r.shadow = 1.0; }
525
+ return r;
526
+ }
527
+
528
+ // ---------------------------------------------------------------- light accumulation
529
+ vec3 gN, gNc, gT, gV, gAlb, gF0, gSSS, gAT, gDirect;
530
+ float gMemA = 1.0; // fin membrane opacity (material 11)
531
+ float gH, gOccl, gSv, gHair, gRough, gWrap, gCoat, gAniso, gEdge, gLit, gLitW, gTrans;
532
+ vec3 toWorld(vec3 dirView) { return normalize((vec4(dirView, 0.0) * viewMatrix).xyz); }
533
+ vec3 fresnel(vec3 f0, float c) { return f0 + (1.0 - f0) * pow(1.0 - clamp(c, 0.0, 1.0), 5.0); }
534
+
535
+ void shadeLight(vec3 L, vec3 C, float sh) {
536
+ C *= RECIPROCAL_PI;
537
+ float NdL = dot(gN, L);
538
+ vec3 H = normalize(L + gV);
539
+ float NdH = dot(gN, H);
540
+ float wrap = clamp((NdL + 0.4) / 1.4, 0.0, 1.0);
541
+ float w = lum(C);
542
+ gLitW += w; gLit += w * wrap * sh;
543
+ if (gHair > 0.5) {
544
+ float TdL = dot(gT, L);
545
+ float kk = sqrt(max(0.0, 1.0 - TdL * TdL));
546
+ float diff = wrap * mix(1.0, 0.55 + 0.45 * kk, 0.6);
547
+ // fur self shadowing: deep layers only lit when facing the light
548
+ float selfSh = mix(smoothstep(-0.25, 0.55, NdL), 1.0, gH * 0.85 + 0.15);
549
+ float shift = (gSv - 0.5) * 0.25;
550
+ vec3 T1 = normalize(gT + gN * (-0.08 + shift));
551
+ vec3 T2 = normalize(gT + gN * (0.14 + shift));
552
+ float t1 = dot(T1, H), t2 = dot(T2, H);
553
+ float s1 = pow(sqrt(max(0.0, 1.0 - t1 * t1)), 110.0);
554
+ float s2 = pow(sqrt(max(0.0, 1.0 - t2 * t2)), 26.0);
555
+ vec3 spec = (vec3(0.035) * s1 + gAlb * 0.4 * s2) * smoothstep(0.0, 0.6, NdH);
556
+ spec *= smoothstep(-0.1, 0.35, NdL) * gOccl;
557
+ // back-scatter through thin fur at silhouettes
558
+ float backLit = pow(clamp(dot(-L, gV), 0.0, 1.0), 2.5);
559
+ vec3 scatter = (gAlb + 0.04) * backLit * gEdge * 3.2 * (0.25 + 0.75 * gH);
560
+ gDirect += (gAlb * diff * selfSh * gOccl + spec) * C * sh + scatter * C * mix(0.35, 1.0, sh);
561
+ } else {
562
+ float ndl = max(NdL, 0.0);
563
+ // wrapped diffuse; the wrapped part is tinted like light scattered under the skin
564
+ float dw = clamp((NdL + gWrap) / (1.0 + gWrap), 0.0, 1.0) / (1.0 + gWrap);
565
+ vec3 diff = gAlb * (ndl + gSSS * max(dw - ndl, 0.0) * 2.0);
566
+ // normalised Blinn-Phong
567
+ float n = exp2(12.0 * (1.0 - gRough) + 1.0);
568
+ float VdH = max(dot(gV, H), 0.0);
569
+ vec3 F = fresnel(gF0, VdH);
570
+ vec3 spec = F * ((n + 2.0) / 8.0) * pow(max(NdH, 0.0), n) * ndl;
571
+ // anisotropic lobe along gAT (feather barbs, keratin striations, hair-like base fur)
572
+ if (gAniso > 0.0) {
573
+ float th = dot(gAT, H);
574
+ float an = n * 0.5 + 8.0;
575
+ spec += gAniso * F * ((an + 2.0) / 8.0) * pow(sqrt(max(0.0, 1.0 - th * th)), an) * smoothstep(-0.1, 0.3, NdL);
576
+ }
577
+ // clear coat (wet skin, polished keratin, fish scales)
578
+ // (the coat lies over the bumps: it follows the smooth normal)
579
+ if (gCoat > 0.0) { float cH = max(dot(gNc, H), 0.0); spec += gCoat * fresnel(vec3(0.04), VdH) * (802.0 / 8.0) * pow(cH, 800.0) * max(dot(gNc, L), 0.0); }
580
+ // light through thin edges (ear tips, fins, horn tips)
581
+ float backLit = pow(clamp(dot(-L, gV), 0.0, 1.0), 3.0) * gTrans;
582
+ gDirect += (diff + spec) * C * sh + gAlb * gSSS * backLit * C * mix(0.3, 1.0, sh);
583
+ // thin membranes (fins) glow through when lit from behind
584
+ if (gTrans > 0.9) gDirect += gSSS * max(-NdL, 0.0) * C * mix(0.35, 1.0, sh) * 0.9;
585
+ }
586
+ }
587
+
588
+ void main() {
589
+ float h = vFin.w;
590
+ float furLen = vCoat.z * uFurScale;
591
+ // (render hint flatMaterial: the triangle's provoking-vertex material; otherwise matIdFrag, which with
592
+ // matBorders splits the triangle through the edge middles; both off: the rounded interpolated id)
593
+ float mat = uFlatMat > 0.5 ? matId(vMatF) : matIdFrag(vTint.a);
594
+ vec4 surf = vSurf;
595
+
596
+ // ---------- strand pattern (planar hashing, axis picked from rest normal)
597
+ vec3 an = abs(vRestN);
598
+ float axisId; vec2 uv;
599
+ if (an.x > an.y && an.x > an.z) { uv = vRest.yz; axisId = 0.0; }
600
+ else if (an.y > an.z) { uv = vRest.xz; axisId = 1.0; }
601
+ else { uv = vRest.xy; axisId = 2.0; }
602
+ float dens = uStrandDensity * mix(1.3, 1.0, smoothstep(0.004, 0.014, furLen));
603
+
604
+ float alpha = 1.0;
605
+ float sv = 0.5; // per-strand random
606
+ vec4 ch = vec4(0.5); // per-clump randoms
607
+ float fromC = 0.0; // distance from the clump centre (0 .. ~0.7)
608
+ float clumpK = 0.0; // clumping strength
609
+ float hazeF = 0.0, hazeNV = 1.0; // shells: the undercoat's share of this fragment's opacity, |N.V|
610
+ bool bristle = abs(mat - 4.0) < 0.5, down = abs(mat - 9.0) < 0.5;
611
+ #ifdef FUR_SHELL
612
+ if (furLen < 0.0006 || !hairy(mat)) discard;
613
+ if (bristle) dens *= 0.2;
614
+ if (down && h > 0.3) discard; // down: a short haze near the skin, not a pelt
615
+ // ---- clumps: nearest strongly-jittered centre (3x3 cell search) -> organic, non-grid tufts
616
+ float cDens = mix(300.0, 118.0, smoothstep(0.004, 0.03, furLen)) * uClumpDensity;
617
+ clumpK = (bristle || down) ? 0.0 : smoothstep(uClumpLen.x, uClumpLen.y, furLen);
618
+ float sharp, inClump;
619
+ alpha = strandLayer(uv, axisId, h, furLen, dens, cDens, clumpK, bristle, sharp, sv, ch, fromC, inClump);
620
+ if (uStrandBlend > 0.0) {
621
+ // (opt-in, render hint strandBlend) the second-largest axis plane cross-faded in near a switch of the largest:
622
+ // half and half where the two components are equal, none beyond uStrandBlend
623
+ float a0 = axisId == 0.0 ? an.x : (axisId == 1.0 ? an.y : an.z);
624
+ float ax2 = axisId == 0.0 ? (an.y > an.z ? 1.0 : 2.0) : (axisId == 1.0 ? (an.x > an.z ? 0.0 : 2.0) : (an.x > an.y ? 0.0 : 1.0));
625
+ float b0 = ax2 == 0.0 ? an.x : (ax2 == 1.0 ? an.y : an.z);
626
+ vec2 uv2 = ax2 == 0.0 ? vRest.yz : (ax2 == 1.0 ? vRest.xz : vRest.xy);
627
+ float w2 = 0.5 * smoothstep(uStrandBlend, 0.0, a0 - b0);
628
+ float sharp2, sv2, fromC2, inClump2; vec4 ch2;
629
+ float alpha2 = strandLayer(uv2, ax2, h, furLen, dens, cDens, clumpK, bristle, sharp2, sv2, ch2, fromC2, inClump2);
630
+ alpha = mix(alpha, alpha2, w2);
631
+ sharp = mix(sharp, sharp2, w2); sv = mix(sv, sv2, w2); ch = mix(ch, ch2, w2); fromC = mix(fromC, fromC2, w2); inClump = mix(inClump, inClump2, w2);
632
+ }
633
+ // down: a soft haze of barbules rather than individual strands
634
+ if (bristle && sv > 0.45) alpha = 0.0; // bristles are sparse
635
+ if (down) alpha = (0.05 + 0.12 * sharp) * (1.0 - h / 0.3);
636
+ // thin out the very outer shells
637
+ alpha *= 1.0 - smoothstep(0.85, 1.0, h) * 0.5;
638
+ // undercoat: the hair the strand grid does not resolve (a pelt has thousands of hairs per cm2, the grid
639
+ // a few dozen strands). An optical depth per unit of coat height, dense at the roots and none at the
640
+ // tips, taken through this layer's slab (1 / uShells of the coat) along the line of sight (Beer-Lambert:
641
+ // the coat's opacity is the same at every shell count, and a coat seen along its lie, a fringe hanging
642
+ // over a leg or down round a folded wing, is as dense as it is). Without it the gaps between strands
643
+ // showed whatever lay under the outer layers: another part's lit skin, a wing, a leg's outline.
644
+ if (!bristle && !down && uUnder > 0.0) {
645
+ hazeNV = abs(dot(normalize(vN), normalize(cameraPosition - vWorldPos)));
646
+ float tau = uUnder * (1.0 - smoothstep(0.3, 0.9, h)) * mix(1.0, inClump, 0.6 * clumpK);
647
+ float fill = 1.0 - exp(-tau / (max(uShells, 1.0) * max(hazeNV, 0.2)));
648
+ float aS = alpha;
649
+ alpha = 1.0 - (1.0 - alpha) * (1.0 - fill);
650
+ hazeF = alpha > 1e-4 ? (1.0 - aS) * fill / alpha : 0.0;
651
+ }
652
+ // cross-fade of two overlapping surfaces (vCoat.w): each takes its share of the coat's optical depth, so the
653
+ // pair composites to one whole coat. Scaling the opacity instead left the middle of the band a quarter thinner
654
+ // (two half-opaque layers cover 75 %): a pale or dark line where a head and a neck surface meet (a dog's ruff)
655
+ alpha = vCoat.w > 1e-4 ? 1.0 - pow(max(1.0 - alpha, 1e-4), vCoat.w) : 0.0;
656
+ alpha *= vFin.z;
657
+ if (alpha < 0.02) discard;
658
+ #elif defined( FUR_FIN )
659
+ // ---- a fin carries a handful of tapered strands side by side
660
+ float nS = bristle ? 2.0 : 5.0;
661
+ float u = vFin.x * nS;
662
+ float y = vFin.y;
663
+ float cov = 0.0;
664
+ float i0 = floor(u);
665
+ vec2 fid = vRest.xy * 1733.1 + vRest.z * 917.3;
666
+ float uw = fwidth(u) * 0.8 + 0.02;
667
+ for (int k = -1; k <= 1; k++) {
668
+ float i = i0 + float(k);
669
+ if (i < 0.0 || i >= nS) continue;
670
+ vec2 hid = fid + vec2(i * 7.13, 0.0);
671
+ float r1 = hash12(hid), r2 = hash12(hid + 3.7), r3 = hash12(hid + 9.1);
672
+ float slen = mix(0.38, 1.1, r1 * r1 * (3.0 - 2.0 * r1));
673
+ if (y > slen) continue;
674
+ float cx = i + 0.5 + (r3 - 0.5) * 0.6 + (r2 - 0.5) * 1.4 * y * y;
675
+ float wdt = 0.2 * (1.0 - y / slen) + 0.03;
676
+ float c = smoothstep(wdt + uw, wdt - uw, abs(u - cx));
677
+ if (c > cov) { cov = c; sv = r2; ch = vec4(r1, r3, r2, r1 * r3); }
678
+ }
679
+ alpha = cov * vFin.z * vCoat.w * (1.0 - 0.35 * smoothstep(0.6, 1.0, y)) * uFinFade;
680
+ // the root half of a long hair's card lies inside its own coat, which the shells draw: drawn there too it
681
+ // traced the base surface's outline, deep inside the coat, as a dark line (a long tail's core, a leg
682
+ // under a fringe); the card shows the hair where it leaves the coat
683
+ alpha *= mix(1.0, smoothstep(0.08, 0.5, y), smoothstep(0.008, 0.03, furLen));
684
+ if (down) alpha *= 0.5;
685
+ if (alpha < 0.02) discard;
686
+ #else
687
+ // overlap band of two regions: each surface keeps its half, plus the sunk skirt past the midline
688
+ if (vCoat.w < uSeam.y) discard;
689
+ #endif
690
+
691
+ // ---------- frames: rest space (for procedural detail) and world space (for lighting)
692
+ vec3 Nw = normalize(vN);
693
+ vec3 Tw = dot(vT, vT) > 1e-12 ? normalize(vT) : Nw;
694
+ vec3 V = normalize(cameraPosition - vWorldPos);
695
+ vec3 N = Nw;
696
+ vec3 T = Tw;
697
+ vec3 Nr = normalize(vRestN);
698
+ vec3 Tr = vRestC - Nr * dot(vRestC, Nr);
699
+ Tr = dot(Tr, Tr) > 1e-10 ? normalize(Tr) : normalize(cross(Nr, vec3(0.31, 0.83, 0.47)));
700
+ vec3 Br = cross(Nr, Tr);
701
+ vec3 Tb = normalize(Tw - Nw * dot(Tw, Nw) + 1e-6);
702
+ vec3 Bw = cross(Nw, Tb);
703
+
704
+ // ---------- albedo: base colour, pattern, marks
705
+ vec3 base = vTint.rgb;
706
+ bool furBase = false;
707
+ // hair-level raggedness of pattern edges: whole tufts are in or out, single strands stray
708
+ float jitS = (sv - 0.5) * 0.0036 + (ch.y - 0.5) * 0.0042;
709
+ #if !defined( FUR_SHELL ) && !defined( FUR_FIN )
710
+ furBase = mat < 0.5;
711
+ if (furBase) {
712
+ // no strands here: jitter pattern edges with hair-flow streaks instead
713
+ float st = streak(vRest, Tr, 900.0, 6.0) * 0.6 + streak(vRest + 7.1, Tr, 300.0, 4.0) * 0.4;
714
+ jitS = (st - 0.5) * 0.014 * detailFade(300.0);
715
+ } else jitS = 0.0;
716
+ #endif
717
+ float pw = max(fwidth(vCoat.x), 0.0011);
718
+ // (a negative pattern intensity is a flight-feather vane's bar count, see the vane branch below)
719
+ float spot = smoothstep(pw, -pw, vCoat.x + jitS) * max(vPat.a, 0.0);
720
+ // facial marks (tear lines, eye rims, lips, tail rings) are crisp lines: almost no strand jitter
721
+ float mw = max(fwidth(vCoat.y), 0.0007);
722
+ float mark = smoothstep(mw, -mw, vCoat.y + jitS * 0.08);
723
+ vec3 alb = mix(base, vPat.rgb, spot);
724
+ alb = mix(alb, uMarkColor, mark);
725
+
726
+ // ---------- hair: strand / tuft shade variation, agouti banding, root darkening
727
+ #if defined( FUR_SHELL ) || defined( FUR_FIN )
728
+ if (bristle) alb = mix(base * 1.1 + 0.03, alb, 0.3);
729
+ if (down) alb = mix(alb, vec3(lum(alb)) * 1.2 + 0.02, 0.35);
730
+ alb *= (0.8 + 0.4 * sv) * mix(0.86, 1.12, ch.z);
731
+ // agouti: light roots, a dark band just below the tips (grizzle)
732
+ float agouti = surf.z;
733
+ if (agouti > 0.0) {
734
+ float bc = 0.74 + (sv - 0.5) * 0.16;
735
+ float band = smoothstep(0.14, 0.02, abs(h - bc)) * step(0.35, fract(sv * 7.3));
736
+ alb = mix(alb, alb * 0.12 + vec3(0.006, 0.005, 0.004), band * agouti);
737
+ }
738
+ // undercoat: lighter, greyer roots
739
+ alb = mix(alb, mix(alb, vec3(lum(alb)) * 1.35, 0.6), surf.w * (1.0 - smoothstep(0.1, 0.5, h)));
740
+ // root darkening / tip lightening
741
+ // (sparse bristles on bare skin: isolated hairs lit like the skin they grow from, no dark roots)
742
+ alb *= bristle ? mix(0.9, 1.05, h) : mix(0.42, 1.08, pow(h, 0.55));
743
+ #endif
744
+
745
+ // ---------- per material surface (base pass)
746
+ float occl = 1.0;
747
+ gHair = 1.0; gRough = 0.5; gWrap = 0.0; gCoat = 0.0; gAniso = 0.0; gTrans = 0.0;
748
+ gF0 = vec3(0.04); gSSS = vec3(0.0); gAT = Tb;
749
+ float aoAmb = 1.0;
750
+ vec3 pert = vec3(0.0); // normal perturbation in (T, B, N) of the rest frame
751
+ #if !defined( FUR_SHELL ) && !defined( FUR_FIN )
752
+ gHair = 0.0;
753
+ float fd = 1.0;
754
+ // crowd tier: no eye meshes, the eyes are painted into the base
755
+ float painted = 0.0; vec3 eyeAlb = vec3(0.0);
756
+ if (uPaintEyes > 0.5) {
757
+ for (int i = 0; i < ${MAX_PAINTED_EYES}; i++) {
758
+ if (uEyes[i].w <= 0.0) continue;
759
+ float de = length(vRest - uEyes[i].xyz) / uEyes[i].w;
760
+ if (de < 1.25) { painted = 1.0; eyeAlb = mix(uEyeTint[i], vec3(0.004), smoothstep(0.55, 0.3, de)); }
761
+ }
762
+ if (painted > 0.5) mat = 2.0;
763
+ }
764
+ if (mat < 0.5) {
765
+ // fur seen from the base
766
+ gHair = 1.0;
767
+ if (uShellsOn > 0.5) {
768
+ occl = 0.32; // deep in the coat, under the shells
769
+ } else {
770
+ // no shells: hair-flow streaks, per-lock shade and an anisotropic sheen make it read as fur
771
+ alb *= uBaseTone;
772
+ fd = detailFade(1200.0);
773
+ float fd2 = detailFade(450.0), fd3 = detailFade(140.0);
774
+ float s1 = streak(vRest, Tr, 1200.0, 8.0), s2 = streak(vRest + 3.7, Tr, 450.0, 5.0), s3 = streak(vRest + 9.2, Tr, 140.0, 3.0);
775
+ sv = mix(0.5, s1, fd);
776
+ // strands, locks and tufts: brighter crests, darker partings
777
+ alb *= mix(1.0, 0.62 + 0.76 * s1, fd) * mix(1.0, 0.72 + 0.56 * s2, fd2) * mix(1.0, 0.8 + 0.4 * s3, fd3);
778
+ alb *= mix(1.0, 1.0 - 0.6 * smoothstep(0.5, 0.75, s2), surf.z * fd2); // agouti speckle
779
+ occl = 0.8;
780
+ h = 0.6;
781
+ ch = vec4(s3, s2, 0.5, s2);
782
+ pert = vec3(0.0, ((s2 - 0.5) * 0.6 + (s3 - 0.5) * 0.5) * fd2, 0.0);
783
+ }
784
+ } else if (mat < 1.5) {
785
+ // nose leather: pebbled cobbles with dark creases, satin sheen
786
+ float k = 1.0 / 0.0007;
787
+ fd = detailFade(k);
788
+ vec3 g = vRest * k; vec3 gi = floor(g);
789
+ float d1 = 9.0, d2 = 9.0; vec3 best = vec3(0.0);
790
+ for (int kk = 0; kk < 27; kk++) {
791
+ vec3 o = vec3(float(kk % 3) - 1.0, float((kk / 3) % 3) - 1.0, float(kk / 9) - 1.0);
792
+ vec3 c = gi + o + hash33(gi + o);
793
+ vec3 dv = g - c; dv -= Nr * dot(dv, Nr) * 0.7;
794
+ float dd = length(dv);
795
+ if (dd < d1) { d2 = d1; d1 = dd; best = dv; } else if (dd < d2) d2 = dd;
796
+ }
797
+ float crease = smoothstep(0.18, 0.0, d2 - d1) * fd;
798
+ // (leatherTint: pink / brick leather in the tint colour, e.g. a cat's nose and pads; the tint keeps the
799
+ // material ids next to fur 0 -> 1, where a pink bare-skin nose (4) interpolated through 1, 2, 3 at its rim)
800
+ // (noseTint 1, the wolf's pale rims and painted septum, is the same blend at full strength: both take
801
+ // the coat's per-vertex colour vTint, so the larger of the two wins)
802
+ // (uLeatherRelief scales the pebbling: crease darkening, cobble shade and bump; 1 = the default)
803
+ crease *= uLeatherRelief;
804
+ alb = mix(vec3(0.05, 0.042, 0.04), vTint.rgb, max(uLeatherTint, uNoseTint)) * (1.0 + (0.4 * hash13(floor(g)) - 0.2) * uLeatherRelief) * (1.0 - 0.6 * crease);
805
+ pert = vec3(dot(best, Tr), dot(best, Br), 0.0) * 0.5 * fd * uLeatherRelief;
806
+ // (surf.y < 0: the roughness, -surf.y, of a drier, matte leather per vertex (bear); unset (>= 0) takes the
807
+ // render hint noseRoughness, satin 0.42 by default)
808
+ gRough = surf.y < 0.0 ? -surf.y : uNoseRough; gF0 = vec3(0.035); gWrap = 0.2; gSSS = vec3(0.3, 0.1, 0.08);
809
+ aoAmb = 1.0 - 0.5 * crease;
810
+ // (render hint noseMarks: marks on the leather are openings, the nostrils: dark, no sheen from inside)
811
+ float nm = mark * uNoseMarks;
812
+ alb = mix(alb, uMarkColor, nm); pert *= 1.0 - nm; gF0 *= 1.0 - nm; aoAmb *= 1.0 - 0.6 * nm;
813
+ } else if (mat < 2.5) {
814
+ // bare dark skin (lids, lips): smooth, slightly moist
815
+ alb = vec3(0.012, 0.01, 0.01);
816
+ gRough = 0.32; gWrap = 0.25; gSSS = vec3(0.5, 0.15, 0.1); gF0 = vec3(0.035);
817
+ if (painted > 0.5) { alb = eyeAlb; gRough = 0.1; gCoat = 1.0; gWrap = 0.0; }
818
+ } else if (mat < 3.5) {
819
+ // mouth: wet, pink-red mucosa
820
+ alb = vec3(0.25, 0.08, 0.08);
821
+ gRough = 0.22; gWrap = 0.6; gSSS = vec3(0.9, 0.25, 0.2); gCoat = 0.5;
822
+ } else if (mat < 4.5) {
823
+ // bare skin: colour from rgb, soft subsurface wrap, fine pores and creases
824
+ float k = 1.0 / 0.0009;
825
+ fd = detailFade(k);
826
+ float n1 = vnoise(vRest * k), n2 = vnoise(vRest * k * 0.23 + 5.1);
827
+ float pore = smoothstep(0.72, 0.9, n1) * fd;
828
+ alb *= (1.0 - 0.25 * pore) * (0.92 + 0.16 * n2);
829
+ pert = vec3((n1 - 0.5) * 0.12, (vnoise(vRest * k + 9.3) - 0.5) * 0.12, 0.0) * fd;
830
+ gRough = mix(0.62, 0.3, surf.x); gWrap = uSkinScatter.w; gSSS = uSkinScatter.rgb; gTrans = uSkinTrans.x + (uSkinTrans.y > 0.0 ? uSkinTrans.y * surf.y : 0.0);
831
+ aoAmb = 1.0 - 0.3 * pore;
832
+ } else if (mat < 5.5) {
833
+ // keratin (hoof, horn, beak, claw): glossy, growth striations running along the comb
834
+ float k = 1.0 / 0.0006;
835
+ fd = detailFade(k);
836
+ float fd2 = detailFade(k * 0.2);
837
+ float s1 = streak(vRest, Tr, k, 14.0), s2 = streak(vRest + 1.3, Tr, k * 0.2, 10.0);
838
+ float rings = 0.5 + 0.5 * sin(dot(vRest, Tr) * 700.0 + s2 * 4.0);
839
+ alb *= mix(1.0, 0.8 + 0.4 * s1, fd) * mix(1.0, (0.7 + 0.6 * s2) * (0.92 + 0.08 * rings), fd2);
840
+ pert = vec3(0.0, (s1 - 0.5) * 0.35 * fd + (s2 - 0.5) * 0.5 * fd2, 0.0);
841
+ // (surf.x < 0, opt-in: duller than gloss 0, -1 matte: the highlight and the grazing sheen are the same on any
842
+ // albedo, and on dark horn they read as chrome; 0 keeps the default 0.6)
843
+ float gl = surf.x > 0.0 ? surf.x : (surf.x < 0.0 ? 0.0 : 0.6);
844
+ float matte = clamp(-surf.x, 0.0, 1.0);
845
+ gRough = mix(mix(0.55, 0.12, gl), 0.85, matte); gCoat = 0.25 * gl; gAniso = 0.6 * (1.0 - matte); gAT = Tb; gWrap = 0.15;
846
+ gSSS = alb * 0.8 + 0.02; gTrans = 0.35;
847
+ } else if (mat < 6.5) {
848
+ // scales laid out along the comb: overlapping, domed, dark edges, optional keel and iridescence
849
+ float sz = surf.y > 0.0 ? surf.y : 0.004;
850
+ fd = detailFade(1.0 / sz * 1.5);
851
+ if (surf.z < -0.5) {
852
+ // ventral scutes (snakes): plates spanning the belly, one per surf.y along the comb, each one's
853
+ // free (posterior) edge lying over the next: a bright lip, a dark gap, then its shadow
854
+ float a = dot(vRest, Tr) / sz, f = fract(a);
855
+ float gap = smoothstep(0.035, 0.0, f) + smoothstep(0.965, 1.0, f);
856
+ float lip = smoothstep(0.78, 0.94, f) * (1.0 - smoothstep(0.94, 0.97, f));
857
+ float sh = smoothstep(0.3, 0.035, f);
858
+ alb *= mix(1.0, (0.92 + 0.12 * hash13(vec3(floor(a), 1.7, 3.1))) * (1.0 - 0.55 * gap) * (1.0 + 0.12 * lip) * (1.0 - 0.28 * sh), fd);
859
+ pert = vec3(-0.3 - 0.35 * (f - 0.5), 0.0, 0.0) * fd;
860
+ float gl = surf.x > 0.0 ? surf.x : 0.6;
861
+ gRough = mix(0.6, 0.16, gl); gCoat = 0.4 * gl; gWrap = 0.12;
862
+ aoAmb = 1.0 - 0.4 * max(gap, sh) * fd;
863
+ } else if (uDenticle > 0.0) {
864
+ // shark skin (render hint denticles): tiny keeled denticles in rows along the comb. Single
865
+ // denticles, rows of them and broader streaks fade out in turn with distance; the ridges tilt the
866
+ // normal across the comb, so the light breaks into a fine grain along the body. Matte: a soft,
867
+ // broad sheen stretched across the ridges (anisotropic lobe), no clear coat.
868
+ float kd = 1.0 / sz;
869
+ float fd2 = detailFade(kd * 0.25), fd3 = detailFade(kd * 0.06);
870
+ float g1 = streak(vRest, Tr, kd, 6.0), g2 = streak(vRest + 3.1, Tr, kd * 0.25, 5.0), g3 = streak(vRest + 8.7, Tr, kd * 0.06, 4.0);
871
+ float grain = mix(1.0, 0.8 + 0.4 * g1, fd) * mix(1.0, 0.86 + 0.28 * g2, fd2) * mix(1.0, 0.93 + 0.14 * g3, fd3);
872
+ alb *= mix(1.0, grain, uDenticle);
873
+ pert = vec3(0.0, (g1 - 0.5) * 0.5 * fd + (g2 - 0.5) * 0.35 * fd2 + (g3 - 0.5) * 0.18 * fd3, 0.0) * uDenticle;
874
+ float gl = surf.x > 0.0 ? surf.x : 0.2;
875
+ gRough = mix(0.78, 0.38, gl); gCoat = 0.0; gWrap = 0.3; gSSS = alb * 0.6;
876
+ gAniso = 0.55 * uDenticle; gAT = Tb;
877
+ aoAmb = 1.0 - 0.18 * (1.0 - g2) * fd2 * uDenticle;
878
+ } else {
879
+ // surf.w < 0: lower relief and edge contrast (smooth plates, clean satin scales), -1 = flat
880
+ float scC = surf.w < 0.0 ? clamp(1.0 + surf.w, 0.0, 1.0) : 1.0;
881
+ Shingle S = shingles(vRest, Nr, Tr, Br, sz * 0.62, 1.25, 0.7);
882
+ // free edge: a thin bright lip, then the dark gap under it; base of the scale in shadow
883
+ float rim = smoothstep(0.8, 0.98, S.edge);
884
+ float lip = smoothstep(0.62, 0.8, S.edge) * (1.0 - rim) * step(0.0, S.along);
885
+ float keel = surf.z * exp(-S.across * S.across / 0.02);
886
+ alb *= mix(1.0, (0.82 + 0.3 * S.id) * (1.0 - 0.6 * rim) * (1.0 + 0.25 * lip) * (1.0 - 0.7 * S.shadow) * mix(0.72, 1.0, smoothstep(-0.8, 0.4, S.along)) * (1.0 + 0.35 * keel), fd * uScaleEdge * scC);
887
+ // the scale rises toward its free edge and is domed across; the keel is a ridge down the middle
888
+ pert = vec3(-0.35 - 0.3 * S.along, -S.across * 0.45 - sign(S.across) * keel * 0.6, 0.0) * fd * scC * (0.35 + 0.65 * uScaleEdge);
889
+ float gl = surf.x > 0.0 ? surf.x : 0.45;
890
+ gRough = mix(0.6, 0.18, gl); gCoat = 0.35 * gl; gWrap = 0.1;
891
+ aoAmb = 1.0 - 0.5 * S.shadow * fd * scC * uScaleEdge;
892
+ if (surf.w > 0.0) {
893
+ float nv = clamp(dot(Nw, V), 0.0, 1.0);
894
+ vec3 film = 0.5 + 0.5 * cos(6.2831 * (nv * 1.6 + S.id * 0.25 + vec3(0.0, 0.33, 0.67)));
895
+ gF0 = mix(vec3(0.04), film * 0.35, surf.w);
896
+ alb = mix(alb, alb * 0.8 + film * lum(alb) * 0.25, surf.w * 0.4);
897
+ }
898
+ }
899
+ } else if (mat < 7.5) {
900
+ // wet skin (frog): granular bumps, glossy clear coat, strong subsurface
901
+ float sz = surf.y > 0.0 ? surf.y : 0.0015;
902
+ float k = 1.0 / sz;
903
+ fd = detailFade(k);
904
+ float b1 = vnoise(vRest * k), b2 = vnoise(vRest * k * 2.7 + 3.3);
905
+ float bump = smoothstep(0.45, 0.85, b1) * 0.7 + b2 * 0.3;
906
+ float e = 0.12;
907
+ float bx = vnoise((vRest + Tr * sz * e) * k), by = vnoise((vRest + Br * sz * e) * k);
908
+ pert = vec3(-(bx - b1), -(by - b1), 0.0) / e * 0.18 * fd;
909
+ alb *= mix(1.0, 0.85 + 0.3 * bump, fd);
910
+ float gl = surf.x > 0.0 ? surf.x : 0.85;
911
+ gRough = mix(0.5, 0.2, gl); gCoat = gl; gWrap = 0.6; gSSS = vec3(0.6, 0.75, 0.4) * 0.8; gTrans = 0.6;
912
+ } else if (mat < 8.5) {
913
+ // chitin: hard, glossy, dark, fine segment sutures and micro-pitting. A negative feature scale
914
+ // (surf.y < 0) means smooth cuticle without sutures (spiders), |surf.y| = pitting scale
915
+ float sz = surf.y > 0.0 ? surf.y : 0.003;
916
+ float pitK = surf.y < 0.0 ? 1.0 / max(-surf.y, 1e-5) : 2500.0;
917
+ fd = detailFade(surf.y < 0.0 ? pitK : 1.0 / 0.0004);
918
+ float seg = fract(dot(vRest, Tr) / sz + streak(vRest, Tr, 90.0, 3.0) * 0.3);
919
+ float suture = surf.y < 0.0 ? 0.0 : smoothstep(0.1, 0.0, seg) + smoothstep(0.93, 1.0, seg);
920
+ float pit = vnoise(vRest * pitK);
921
+ alb *= (1.0 - 0.7 * suture) * mix(1.0, 0.85 + 0.3 * pit, fd);
922
+ pert = vec3((seg - 0.5) * 0.5 + (pit - 0.5) * 0.15 * fd, 0.0, 0.0);
923
+ float gl = surf.x > 0.0 ? surf.x : 0.75;
924
+ gRough = mix(0.5, 0.15, gl); gCoat = 0.4 * gl; gWrap = 0.05;
925
+ gF0 = mix(vec3(0.045), vec3(0.08, 0.06, 0.03) + alb, surf.w);
926
+ aoAmb = 1.0 - 0.5 * suture;
927
+ } else if (mat > 10.5) {
928
+ // fin membrane (11): thin translucent skin stretched between bony rays. surf.y = ray phase (rays
929
+ // at integer values), surf.z = position along the ray (0 base .. 1 margin), surf.w = opacity.
930
+ // Rays are jointed and fork toward the margin; the membrane between them is clearer.
931
+ float along = clamp(surf.z, 0.0, 1.2);
932
+ float fork = smoothstep(0.5, 0.62, along);
933
+ float pr = surf.y * (1.0 + fork);
934
+ float dRay = abs(fract(pr + 0.5) - 0.5);
935
+ float fwR = fwidth(pr) * 0.8 + 1e-4;
936
+ float rw = mix(0.16, 0.1, along) * mix(1.0, 0.7, fork);
937
+ float rayFade = 1.0 - smoothstep(0.35, 0.7, fwR);
938
+ float ray = smoothstep(rw + fwR, rw - fwR, dRay) * rayFade;
939
+ float seg = abs(fract(along * 16.0 + surf.y * 0.37) - 0.5);
940
+ float joint = smoothstep(0.06, 0.0, abs(seg - 0.47)) * ray * detailFade(1.0 / 0.0015);
941
+ alb = mix(alb, alb * 0.72 + vec3(0.004), ray * 0.8);
942
+ alb = mix(alb, alb * 1.25 + 0.02, joint * 0.5);
943
+ gMemA = clamp(mix(surf.w, min(1.0, surf.w + 0.38), ray) * (1.0 - 0.3 * smoothstep(0.85, 1.05, along)), 0.0, 1.0);
944
+ pert = vec3(0.0, sign(fract(pr + 0.5) - 0.5) * ray * 0.22 * rayFade, 0.0);
945
+ float gl = surf.x > 0.0 ? surf.x : 0.7;
946
+ gRough = mix(0.55, 0.22, gl); gCoat = 0.3 * gl; gWrap = 0.45; gSSS = alb * 0.8 + 0.01; gTrans = 1.0;
947
+ } else if (mat < 9.5) {
948
+ // feathers: contour-feather shingles with a rachis, barbs and a barb-anisotropic sheen
949
+ float sz = surf.y > 0.0 ? surf.y : 0.012;
950
+ fd = detailFade(1.0 / sz * 2.0);
951
+ Shingle S = shingles(vRest, Nr, Tr, Br, sz * 0.55, 1.35, 0.8);
952
+ float rachis = smoothstep(0.06, 0.0, abs(S.across)) * smoothstep(1.0, 0.2, S.along);
953
+ float barbPh = (abs(S.across) * 0.9 - S.along * 0.55) * 22.0;
954
+ float fdB = detailFade(1.0 / sz * 30.0);
955
+ float barbs = (0.5 + 0.5 * sin(barbPh * 6.2831)) * fdB;
956
+ float tipFray = smoothstep(0.85, 1.0, S.edge) * smoothstep(0.0, 0.6, S.along);
957
+ if (surf.z < 0.0) {
958
+ // per-feather markings (poultry and game birds): surf.z = -(mode + amount), marking colour in
959
+ // the pattern colour (its intensity unused): 0 lacing (a band along the free edge), 1 bars across
960
+ // the feather, 2 a shaft streak down the middle, 3 spangle (a dark bar and a light tip). Far away
961
+ // the marking blends to its mean coverage instead of vanishing.
962
+ float mm = -surf.z, mode = floor(mm + 1e-3), amt = clamp(mm - mode, 0.0, 1.0);
963
+ float mk = 0.0, mMean = 0.3;
964
+ if (mode < 0.5) mk = smoothstep(0.6, 0.78, S.edge) * smoothstep(-0.4, 0.0, S.along);
965
+ else if (mode < 1.5) { mk = smoothstep(-0.3, 0.3, sin((S.along * 2.3 + S.id * 0.35) * 6.2831)); mMean = 0.5; }
966
+ else if (mode < 2.5) { mk = smoothstep(0.4, 0.16, abs(S.across)) * smoothstep(-0.7, 0.1, S.along); mMean = 0.18; }
967
+ else {
968
+ float tip = smoothstep(0.52, 0.68, S.along) * smoothstep(0.98, 0.85, S.edge);
969
+ float bar = smoothstep(0.15, 0.3, S.along) * (1.0 - smoothstep(0.5, 0.6, S.along));
970
+ alb = mix(alb, alb * 0.1, mix(0.15, bar, fd) * amt);
971
+ mk = tip; mMean = 0.15;
972
+ }
973
+ alb = mix(alb, vPat.rgb, mix(mMean, mk, fd) * amt);
974
+ }
975
+ alb *= mix(1.0, (0.85 + 0.25 * S.id) * (1.0 - 0.35 * S.shadow) * (0.9 + 0.2 * barbs) * (1.0 - 0.3 * tipFray), fd * uFeatherEdge);
976
+ alb = mix(alb, alb * 1.25 + 0.03, rachis * 0.6 * fd);
977
+ pert = vec3(-0.25 - 0.25 * S.along, -S.across * 0.3, 0.0) * fd;
978
+ vec3 barbDir = normalize(Tr * 0.55 + Br * sign(S.across) * 0.84);
979
+ gAT = normalize(Tb * dot(barbDir, Tr) + Bw * dot(barbDir, Br));
980
+ gAniso = 0.5 * fd; gRough = mix(0.8, 0.45, surf.x); gWrap = 0.3; gSSS = alb * 0.5; gTrans = 0.3;
981
+ aoAmb = 1.0 - 0.45 * S.shadow * fd;
982
+ if (surf.w > 0.0) {
983
+ // structural colour (crow / starling gloss): a thin-film tint of the specular lobe
984
+ float nv = clamp(dot(Nw, V), 0.0, 1.0);
985
+ vec3 film = mix(vec3(0.42, 0.3, 1.0), vec3(0.12, 0.62, 0.72), pow(1.0 - nv, 1.4) + (S.id - 0.5) * 0.25);
986
+ gF0 = mix(vec3(0.04), film * 0.14, surf.w);
987
+ }
988
+ } else {
989
+ // flight-feather vane (feather cards, core/build/featherCards.js): surf = [gloss, iridescence,
990
+ // across (-1 outer edge .. 0 rachis .. 1 inner edge), along (0 root .. 1 tip)], vCoat.x = signed
991
+ // metres from the rachis. Rachis, barbs leaving it toward the tip, barb splits, worn edges, a
992
+ // barb-anisotropic sheen, structural colour and light through the thin vane.
993
+ float across = surf.z, along = surf.w, ac = abs(across);
994
+ float xm = abs(vCoat.x), ym = dot(vRest, Tr), sgn = vCoat.x < 0.0 ? -1.0 : 1.0;
995
+ float aw = fwidth(across) + 1e-4;
996
+ float rw = mix(0.07, 0.018, along);
997
+ float rach = smoothstep(rw + aw, rw - aw, ac) * (1.0 - smoothstep(0.8, 1.0, along));
998
+ float bc = (ym - xm * 1.5) / 0.00032;
999
+ float fdB = detailFade(1.0 / 0.00032);
1000
+ float barb = (0.5 + 0.5 * sin(bc * 6.2831)) * fdB;
1001
+ float grp = floor((ym - xm * 1.5) / 0.0045);
1002
+ float split = step(0.88, hash12(vec2(grp, sgn * 7.0))) * smoothstep(0.35, 0.85, ac) * detailFade(220.0);
1003
+ float edge = smoothstep(0.8, 1.0, ac);
1004
+ fd = detailFade(220.0);
1005
+ if (vPat.a < 0.0) {
1006
+ // barred vanes (opt-in, featherCards colour().bar): -vPat.a bars from root to tip in vPat.rgb, bent
1007
+ // into shallow chevrons toward the edges; far away they blend to their mean
1008
+ float ph = along * -vPat.a + ac * 0.22;
1009
+ float fdBar = 1.0 - smoothstep(0.25, 0.5, fwidth(ph));
1010
+ float bar = smoothstep(-0.25, 0.25, sin(ph * 6.2831));
1011
+ alb = mix(alb, vPat.rgb, mix(0.5, bar, fdBar));
1012
+ }
1013
+ alb *= (0.9 + 0.2 * barb) * (1.0 - 0.4 * split * fd) * (1.0 - 0.22 * edge) * (0.94 + 0.12 * along);
1014
+ alb = mix(alb, alb * 1.35 + 0.012, rach * 0.55);
1015
+ pert = vec3((barb - 0.5) * 0.1, sgn * 0.2 * smoothstep(0.08, 1.0, ac) - sgn * 0.25 * split * fd, 0.0);
1016
+ vec3 barbDir = normalize(Tr * 0.83 + Br * sgn * 0.56);
1017
+ gAT = normalize(Tb * dot(barbDir, Tr) + Bw * dot(barbDir, Br));
1018
+ // light through the vane and the barb sheen scale with the pigment: melanin-dark feathers stay
1019
+ // dark from below and against the light, pale feathers glow
1020
+ float vLum = max(alb.r, max(alb.g, alb.b)), vTk = smoothstep(0.02, 0.3, vLum);
1021
+ gAniso = 0.45 * mix(0.12, 1.0, vTk); gRough = mix(0.8, 0.45, surf.x); gWrap = 0.2 * vTk; gSSS = alb * 0.6 + 0.004 * vTk; gTrans = 0.6 * vTk;
1022
+ if (surf.y > 0.0) {
1023
+ float nv = clamp(dot(Nw, V), 0.0, 1.0);
1024
+ vec3 film = mix(vec3(0.42, 0.3, 1.0), vec3(0.12, 0.62, 0.72), clamp(pow(1.0 - nv, 1.4) + along * 0.15, 0.0, 1.0));
1025
+ gF0 = mix(vec3(0.04), film * 0.09, surf.y);
1026
+ }
1027
+ aoAmb = 1.0 - 0.25 * edge - 0.2 * split * fd;
1028
+ }
1029
+ if (gHair < 0.5) N = normalize(Nw + Tb * pert.x + Bw * pert.y);
1030
+ else if (uShellsOn < 0.5) N = normalize(Nw + Bw * pert.y);
1031
+ #endif
1032
+
1033
+ // ---------- hair lighting frame
1034
+ if (gHair > 0.5) {
1035
+ // each tuft leans a little its own way: breaks the smooth shading into fur texture
1036
+ vec3 Bt = normalize(cross(N, T) + 1e-6);
1037
+ N = normalize(N + (T * (ch.w - 0.5) + Bt * (ch.z - 0.5)) * 0.55 * h);
1038
+ #if defined( FUR_SHELL ) || defined( FUR_FIN )
1039
+ occl = bristle ? mix(0.85, 1.0, h) : mix(0.3, 1.0, pow(h, 0.7));
1040
+ // the valleys between tufts are darker than the tufts themselves
1041
+ occl *= mix(1.0, 0.7, smoothstep(0.22, 0.56, fromC) * (1.0 - h) * clumpK);
1042
+ #endif
1043
+ }
1044
+ gN = N; gNc = normalize(mix(Nw, N, 0.25)); gT = T; gV = V; gAlb = alb; gH = h; gOccl = occl; gSv = sv;
1045
+ gEdge = pow(1.0 - abs(dot(Nw, V)), 1.6);
1046
+ #ifdef FUR_FIN
1047
+ gEdge = 1.0;
1048
+ #endif
1049
+ gDirect = vec3(0.0); gLit = 0.0; gLitW = 0.0;
1050
+
1051
+ // ---------- scene lights (three.js uniforms are in view space)
1052
+ IncidentLight directLight;
1053
+ float shD, shS, shP, shT; // (declared outside the unrolled loops)
1054
+ vec3 geoPos = (viewMatrix * vec4(vWorldPos, 1.0)).xyz;
1055
+ #if NUM_DIR_LIGHTS > 0
1056
+ DirectionalLight directionalLight;
1057
+ #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
1058
+ DirectionalLightShadow directionalLightShadow;
1059
+ #endif
1060
+ #pragma unroll_loop_start
1061
+ for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
1062
+ directionalLight = directionalLights[ i ];
1063
+ getDirectionalLightInfo( directionalLight, directLight );
1064
+ shD = 1.0;
1065
+ #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
1066
+ directionalLightShadow = directionalLightShadows[ i ];
1067
+ shD = receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
1068
+ #endif
1069
+ shadeLight( toWorld( directLight.direction ), directLight.color, shD );
1070
+ }
1071
+ #pragma unroll_loop_end
1072
+ #endif
1073
+ #if NUM_SUN_LIGHTS > 0
1074
+ SunLight sunLight;
1075
+ #if defined( USE_SHADOWMAP ) && NUM_SUN_LIGHT_SHADOWS > 0
1076
+ SunLightShadow sunLightShadow;
1077
+ #endif
1078
+ #pragma unroll_loop_start
1079
+ for ( int i = 0; i < NUM_SUN_LIGHTS; i ++ ) {
1080
+ sunLight = sunLights[ i ];
1081
+ getSunLightInfo( sunLight, directLight );
1082
+ shS = 1.0;
1083
+ #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SUN_LIGHT_SHADOWS )
1084
+ sunLightShadow = sunLightShadows[ i ];
1085
+ shS = receiveShadow ? getSunShadow( sunShadowMap[ i ], sunLightShadow, UNROLLED_LOOP_INDEX ) : 1.0;
1086
+ #endif
1087
+ shadeLight( toWorld( directLight.direction ), directLight.color, shS );
1088
+ }
1089
+ #pragma unroll_loop_end
1090
+ #endif
1091
+ #if NUM_POINT_LIGHTS > 0
1092
+ PointLight pointLight;
1093
+ #if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0
1094
+ PointLightShadow pointLightShadow;
1095
+ #endif
1096
+ #pragma unroll_loop_start
1097
+ for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {
1098
+ pointLight = pointLights[ i ];
1099
+ getPointLightInfo( pointLight, geoPos, directLight );
1100
+ shP = 1.0;
1101
+ #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS ) && ( defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_BASIC ) )
1102
+ pointLightShadow = pointLightShadows[ i ];
1103
+ shP = ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;
1104
+ #endif
1105
+ if ( directLight.visible ) shadeLight( toWorld( directLight.direction ), directLight.color, shP );
1106
+ }
1107
+ #pragma unroll_loop_end
1108
+ #endif
1109
+ #if NUM_SPOT_LIGHTS > 0
1110
+ SpotLight spotLight;
1111
+ #if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0
1112
+ SpotLightShadow spotLightShadow;
1113
+ #endif
1114
+ #pragma unroll_loop_start
1115
+ for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {
1116
+ spotLight = spotLights[ i ];
1117
+ getSpotLightInfo( spotLight, geoPos, directLight );
1118
+ shT = 1.0;
1119
+ #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
1120
+ spotLightShadow = spotLightShadows[ i ];
1121
+ shT = ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;
1122
+ #endif
1123
+ if ( directLight.visible ) shadeLight( toWorld( directLight.direction ), directLight.color, shT );
1124
+ }
1125
+ #pragma unroll_loop_end
1126
+ #endif
1127
+
1128
+ // ---------- ambient: hemisphere + ambient lights (+ light probes)
1129
+ vec3 irr = ambientLightColor;
1130
+ vec3 fillIrr = ambientLightColor * 2.0;
1131
+ vec3 R = reflect(-V, N);
1132
+ vec3 envIrr = ambientLightColor;
1133
+ #if NUM_HEMI_LIGHTS > 0
1134
+ for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {
1135
+ vec3 hd = toWorld( hemisphereLights[ i ].direction );
1136
+ irr += mix( hemisphereLights[ i ].groundColor, hemisphereLights[ i ].skyColor, 0.5 * dot( N, hd ) + 0.5 );
1137
+ envIrr += mix( hemisphereLights[ i ].groundColor, hemisphereLights[ i ].skyColor, smoothstep( -0.3, 0.3, dot( R, hd ) ) );
1138
+ fillIrr += hemisphereLights[ i ].skyColor + hemisphereLights[ i ].groundColor;
1139
+ }
1140
+ #endif
1141
+ // the scene environment's diffuse light (sky and ground of an image-based environment, which three.js
1142
+ // feeds only to its own standard materials): irradiance at the normal, the reflection's blurred
1143
+ // radiance, and the fill's sky + ground like the hemisphere lights' (setEnvironmentLight)
1144
+ if (uEnvOn > 0.0) {
1145
+ irr += shGetIrradianceAt( N, uEnvSH );
1146
+ envIrr += shGetIrradianceAt( R, uEnvSH );
1147
+ fillIrr += shGetIrradianceAt( vec3(0.0, 1.0, 0.0), uEnvSH ) + shGetIrradianceAt( vec3(0.0, -1.0, 0.0), uEnvSH );
1148
+ }
1149
+ #if defined( USE_LIGHT_PROBES )
1150
+ vec3 pN = ( viewMatrix * vec4( N, 0.0 ) ).xyz;
1151
+ irr += getLightProbeIrradiance( lightProbe, pN );
1152
+ envIrr += getLightProbeIrradiance( lightProbe, ( viewMatrix * vec4( R, 0.0 ) ).xyz );
1153
+ #endif
1154
+ irr *= RECIPROCAL_PI; fillIrr *= RECIPROCAL_PI; envIrr *= RECIPROCAL_PI;
1155
+ // soft camera-side fill (the "bounce card" of wildlife cinematography): lifts the side turned away
1156
+ // from the key light so it reads as warm tan instead of mud, without flattening the lit side
1157
+ float litFrac = gLitW > 0.0 ? gLit / gLitW : 0.0;
1158
+ vec3 fill = fillIrr * uFill * clamp(dot(N, V) * 0.6 + 0.4, 0.0, 1.0) * (1.0 - litFrac);
1159
+ vec3 col;
1160
+ if (gHair > 0.5) {
1161
+ col = alb * irr * pow(occl, 0.75) + alb * fill + gDirect;
1162
+ } else {
1163
+ float nv = max(dot(N, V), 0.0);
1164
+ float gloss = (1.0 - gRough) * (1.0 - gRough);
1165
+ vec3 envSpec = envIrr * (fresnel(gF0, nv) * gloss + gCoat * fresnel(vec3(0.04), max(dot(gNc, V), 0.0)));
1166
+ col = (alb * irr + alb * fill) * aoAmb + gDirect + envSpec * aoAmb;
1167
+ }
1168
+
1169
+ #ifdef PA_DEBUG
1170
+ #if !defined(FUR_SHELL) && !defined(FUR_FIN) && !defined(FIN_MEMBRANE)
1171
+ // 'holes': the inside of the skin seen through a gap (not the seam skirt, sunk under the other surface on purpose)
1172
+ if (uDebug > 3.5 && !gl_FrontFacing && vCoat.w >= 0.5) col = vec3(1.0, 0.0, 0.0);
1173
+ #endif
1174
+ if (uDebug > 0.5 && uDebug < 1.5) col = alb;
1175
+ if (uDebug > 1.5 && uDebug < 2.5) col = vDbg * 0.8;
1176
+ if (uDebug > 2.5 && uDebug < 3.5) {
1177
+ float m = mat;
1178
+ col = m < 0.5 ? vec3(0.8, 0.55, 0.25) : m < 1.5 ? vec3(0.9, 0.1, 0.5) : m < 2.5 ? vec3(0.15, 0.15, 0.15) : m < 3.5 ? vec3(1.0, 0.2, 0.2)
1179
+ : m < 4.5 ? vec3(1.0, 0.7, 0.6) : m < 5.5 ? vec3(0.9, 0.9, 0.3) : m < 6.5 ? vec3(0.2, 0.8, 0.3) : m < 7.5 ? vec3(0.2, 0.6, 1.0)
1180
+ : m < 8.5 ? vec3(0.5, 0.2, 0.9) : m < 9.5 ? vec3(0.3, 0.9, 0.9) : m < 10.5 ? vec3(0.1, 0.4, 0.9) : vec3(0.95, 0.55, 0.9);
1181
+ col *= 0.6;
1182
+ }
1183
+ #endif
1184
+ #ifdef FUR_SHELL
1185
+ // the undercoat stands for what the line of sight met between the strands before it existed: looking
1186
+ // into the coat, the deep coat (the base's dark fur, occlusion 0.32); along it, more hair at this
1187
+ // height. So the coat keeps its tone and its dark partings, and only what lay beneath is covered.
1188
+ if (hazeF > 0.0) {
1189
+ float shellShade = mix(0.42, 1.08, pow(h, 0.55)) * occl;
1190
+ float kH = mix(1.0, clamp(0.32 / max(shellShade, 1e-3), 0.3, 1.0), smoothstep(0.2, 0.7, hazeNV));
1191
+ col *= mix(1.0, kH, hazeF);
1192
+ }
1193
+ #endif
1194
+ col = max(col, vec3(0.0));
1195
+ #ifdef FIN_MEMBRANE
1196
+ alpha = gMemA;
1197
+ #endif
1198
+ gl_FragColor = vec4(col, alpha);
1199
+ #include <tonemapping_fragment>
1200
+ #include <colorspace_fragment>
1201
+ #include <fog_fragment>
1202
+ }
1203
+ `;
1204
+
1205
+ // shadow casters: the same dual-quaternion deformation as the visible surface
1206
+ function dqsPatch(material, uniforms, key) {
1207
+ material.onBeforeCompile = (sh) => {
1208
+ sh.uniforms.uDQ = uniforms.uDQ;
1209
+ sh.vertexShader = sh.vertexShader
1210
+ .replace('#include <common>', '#include <common>\n' + DQS_GLSL + DQS_ATTR_GLSL)
1211
+ .replace('#include <begin_vertex>', '#include <begin_vertex>\n { vec3 dn = vec3(0.0, 1.0, 0.0), dt = vec3(1.0, 0.0, 0.0); dqsSkin(transformed, dn, dt); }');
1212
+ };
1213
+ material.customProgramCacheKey = () => key;
1214
+ return material;
1215
+ }
1216
+
1217
+ export function createShadowMaterials(uniforms) {
1218
+ return {
1219
+ depth: dqsPatch(new THREE.MeshDepthMaterial(), uniforms, 'pa-dqs-depth'),
1220
+ distance: dqsPatch(new THREE.MeshDistanceMaterial(), uniforms, 'pa-dqs-distance'),
1221
+ };
1222
+ }
1223
+
1224
+ export function createCoatMaterials(uniforms) {
1225
+ const make = (params) => {
1226
+ const u = THREE.UniformsUtils.merge([THREE.UniformsLib.lights, THREE.UniformsLib.fog]);
1227
+ // this animal's own uniform objects, shared by its three passes
1228
+ Object.assign(u, uniforms);
1229
+ return new THREE.ShaderMaterial({ uniforms: u, lights: true, fog: true, ...params });
1230
+ };
1231
+ // (render hint matBorders: material borders through the edge middles, see matIdFrag; off by default)
1232
+ const mb = uniforms.uMatBorders && uniforms.uMatBorders.value > 0 ? { PA_MAT_BORDERS: 1 } : {};
1233
+ const base = make({ vertexShader: vertex, fragmentShader: fragment, defines: { ...mb } });
1234
+ const shells = make({ vertexShader: vertex, fragmentShader: fragment, transparent: true, depthWrite: true, defines: { FUR_SHELL: 1, ...mb } });
1235
+ const fins = make({ vertexShader: finVertex, fragmentShader: fragment, transparent: true, depthWrite: false, side: THREE.DoubleSide, defines: { FUR_FIN: 1, ...mb } });
1236
+ // translucent fin membranes (material 11), drawn from their own index subset (membranes.js)
1237
+ const membrane = make({ vertexShader: vertex, fragmentShader: fragment, transparent: true, depthWrite: false, defines: { FIN_MEMBRANE: 1, ...mb } });
1238
+ return { base, shells, fins, membrane };
1239
+ }
1240
+
1241
+ // debug colours compile only on demand
1242
+ export function setMaterialsDebug(mats, on) {
1243
+ for (const m of Object.values(mats)) {
1244
+ const has = !!m.defines.PA_DEBUG;
1245
+ if (has === on) continue;
1246
+ if (on) m.defines.PA_DEBUG = 1;
1247
+ else delete m.defines.PA_DEBUG;
1248
+ m.needsUpdate = true;
1249
+ }
1250
+ }
1251
+
1252
+ export function makeCoatUniforms(hints = {}) {
1253
+ const r = { ...RENDER_DEFAULTS, ...hints };
1254
+ return {
1255
+ uShells: { value: 28 },
1256
+ uShellsOn: { value: 1 },
1257
+ uUnder: { value: r.undercoat },
1258
+ uFinFade: { value: 1 },
1259
+ uFurScale: { value: r.shellScale },
1260
+ uFurForce: { value: new THREE.Vector3(0, -0.15, 0) },
1261
+ uFurRaise: { value: 0 },
1262
+ uRaiseLen: { value: new THREE.Vector2(...r.raiseLen) },
1263
+ uClumpLen: { value: new THREE.Vector2(...r.clumpLen) },
1264
+ uTime: { value: 0 },
1265
+ uStrandDensity: { value: r.strandDensity },
1266
+ uClumpDensity: { value: r.clumpDensity },
1267
+ uFinWidth: { value: r.finWidth },
1268
+ uFinMask: { value: r.finMask ? 1 : 0 },
1269
+ uFinMinLen: { value: r.finMinLen },
1270
+ uStrandBlend: { value: r.strandBlend || 0 },
1271
+ uMarkColor: { value: new THREE.Color(...r.markColor) },
1272
+ uNoseRough: { value: r.noseRoughness },
1273
+ uNoseMarks: { value: r.noseMarks ? 1 : 0 },
1274
+ uNoseTint: { value: r.noseTint ? 1 : 0 },
1275
+ uFill: { value: 0.45 },
1276
+ uScaleEdge: { value: r.scaleEdge },
1277
+ uFeatherEdge: { value: r.featherEdge },
1278
+ uLeatherTint: { value: +r.leatherTint || 0 },
1279
+ uLeatherRelief: { value: r.leatherRelief ?? 1 },
1280
+ uSkinScatter: { value: new THREE.Vector4(...r.skinScatter) },
1281
+ uSkinTrans: { value: new THREE.Vector2(...r.skinTrans) },
1282
+ uDenticle: { value: r.denticles },
1283
+ uEnvSH: { value: Array.from({ length: 9 }, () => new THREE.Vector3()) },
1284
+ uEnvOn: { value: 0 },
1285
+ uWind: { value: 0.1 },
1286
+ uDebug: { value: 0 },
1287
+ uForceMat: { value: -1 },
1288
+ uFlatMat: { value: r.flatMaterial ? 1 : 0 },
1289
+ uPaintEyes: { value: 0 },
1290
+ uBaseTone: { value: new THREE.Vector3(1, 1, 1) },
1291
+ uShellShadowRoot: { value: r.shellShadowRoot },
1292
+ uShellBias: { value: new THREE.Vector2(...r.shellDepthBias) },
1293
+ // no skirt (older bakes without data.seamSink): each surface stops at its midline
1294
+ uSeam: { value: new THREE.Vector2(0, 0.5) },
1295
+ // (not read by the shaders: createCoatMaterials compiles the matBorders variant when it is set)
1296
+ uMatBorders: { value: r.matBorders ? 1 : 0 },
1297
+ uEyes: { value: Array.from({ length: MAX_PAINTED_EYES }, () => new THREE.Vector4(0, 0, 0, 0)) },
1298
+ uEyeTint: { value: Array.from({ length: MAX_PAINTED_EYES }, () => new THREE.Color(0.1, 0.05, 0.01)) },
1299
+ uDQ: { value: null },
1300
+ };
1301
+ }