@zephyr3d/scene 0.9.25 → 0.9.27

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 (299) hide show
  1. package/dist/animation/animationset.js +92 -30
  2. package/dist/animation/animationset.js.map +1 -1
  3. package/dist/animation/hair/gpu_hair_simulation.js +1090 -0
  4. package/dist/animation/hair/gpu_hair_simulation.js.map +1 -0
  5. package/dist/animation/joint_dynamics/controller.js +7 -5
  6. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  7. package/dist/animation/joint_dynamics/convex_collider.js +320 -0
  8. package/dist/animation/joint_dynamics/convex_collider.js.map +1 -0
  9. package/dist/animation/joint_dynamics/joint_dynamics_system.js +81 -5
  10. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  11. package/dist/animation/morphtrack.js +3 -4
  12. package/dist/animation/morphtrack.js.map +1 -1
  13. package/dist/animation/skeleton.js +79 -25
  14. package/dist/animation/skeleton.js.map +1 -1
  15. package/dist/animation/spring/multi_chain_spring_system.js +577 -179
  16. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  17. package/dist/animation/spring/spring_collider.js +4 -3
  18. package/dist/animation/spring/spring_collider.js.map +1 -1
  19. package/dist/animation/spring/spring_node_pose_tracker.js +40 -0
  20. package/dist/animation/spring/spring_node_pose_tracker.js.map +1 -0
  21. package/dist/animation/spring/spring_particle.js +2 -0
  22. package/dist/animation/spring/spring_particle.js.map +1 -1
  23. package/dist/animation/spring/spring_system.js +11 -3
  24. package/dist/animation/spring/spring_system.js.map +1 -1
  25. package/dist/animation/spring_modifier.js +10 -0
  26. package/dist/animation/spring_modifier.js.map +1 -1
  27. package/dist/app/engine.js +53 -43
  28. package/dist/app/engine.js.map +1 -1
  29. package/dist/app/scriptingsystem.js +119 -6
  30. package/dist/app/scriptingsystem.js.map +1 -1
  31. package/dist/app/scriptregistry.js +3 -2
  32. package/dist/app/scriptregistry.js.map +1 -1
  33. package/dist/asset/assetmanager.js +284 -67
  34. package/dist/asset/assetmanager.js.map +1 -1
  35. package/dist/asset/loaders/zhair/zhair_format.js +288 -0
  36. package/dist/asset/loaders/zhair/zhair_format.js.map +1 -0
  37. package/dist/asset/loaders/zhair/zhair_loader.js +221 -0
  38. package/dist/asset/loaders/zhair/zhair_loader.js.map +1 -0
  39. package/dist/asset/model.js +184 -69
  40. package/dist/asset/model.js.map +1 -1
  41. package/dist/camera/camera.js +268 -44
  42. package/dist/camera/camera.js.map +1 -1
  43. package/dist/camera/perspectivecamera.js +3 -1
  44. package/dist/camera/perspectivecamera.js.map +1 -1
  45. package/dist/index.d.ts +9044 -5139
  46. package/dist/index.js +17 -6
  47. package/dist/index.js.map +1 -1
  48. package/dist/material/eye.js +932 -0
  49. package/dist/material/eye.js.map +1 -0
  50. package/dist/material/hair.js +917 -0
  51. package/dist/material/hair.js.map +1 -0
  52. package/dist/material/hairstrand.js +842 -0
  53. package/dist/material/hairstrand.js.map +1 -0
  54. package/dist/material/hairstrand_data.js +162 -0
  55. package/dist/material/hairstrand_data.js.map +1 -0
  56. package/dist/material/lambert.js +5 -2
  57. package/dist/material/lambert.js.map +1 -1
  58. package/dist/material/material.js +48 -10
  59. package/dist/material/material.js.map +1 -1
  60. package/dist/material/meshmaterial.js +120 -19
  61. package/dist/material/meshmaterial.js.map +1 -1
  62. package/dist/material/mixins/albedocolor.js +1 -1
  63. package/dist/material/mixins/albedocolor.js.map +1 -1
  64. package/dist/material/mixins/lightmodel/blinnphong.js +5 -3
  65. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  66. package/dist/material/mixins/lightmodel/lambert.js +5 -4
  67. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  68. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +3 -3
  69. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  70. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +5 -5
  71. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  72. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +3 -3
  73. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  74. package/dist/material/mixins/lit.js +106 -19
  75. package/dist/material/mixins/lit.js.map +1 -1
  76. package/dist/material/mixins/pbr/common.js +4 -4
  77. package/dist/material/mixins/pbr/common.js.map +1 -1
  78. package/dist/material/mixins/texture.js +48 -11
  79. package/dist/material/mixins/texture.js.map +1 -1
  80. package/dist/material/mtoon.js +6 -5
  81. package/dist/material/mtoon.js.map +1 -1
  82. package/dist/material/pbrblueprint.js +146 -28
  83. package/dist/material/pbrblueprint.js.map +1 -1
  84. package/dist/material/pbrblueprint_instance.js +501 -0
  85. package/dist/material/pbrblueprint_instance.js.map +1 -0
  86. package/dist/material/pbrmr.js +15 -16
  87. package/dist/material/pbrmr.js.map +1 -1
  88. package/dist/material/shader/helper.js +734 -23
  89. package/dist/material/shader/helper.js.map +1 -1
  90. package/dist/material/shader/terrain_blend.js +55 -0
  91. package/dist/material/shader/terrain_blend.js.map +1 -0
  92. package/dist/material/skin.js +192 -32
  93. package/dist/material/skin.js.map +1 -1
  94. package/dist/material/spriteblueprint.js +16 -1
  95. package/dist/material/spriteblueprint.js.map +1 -1
  96. package/dist/material/subsurfaceprofile.js +86 -23
  97. package/dist/material/subsurfaceprofile.js.map +1 -1
  98. package/dist/material/water.js +2059 -72
  99. package/dist/material/water.js.map +1 -1
  100. package/dist/posteffect/bloom.js +167 -7
  101. package/dist/posteffect/bloom.js.map +1 -1
  102. package/dist/posteffect/coloradjust.js +2 -1
  103. package/dist/posteffect/coloradjust.js.map +1 -1
  104. package/dist/posteffect/compositor.js.map +1 -1
  105. package/dist/posteffect/fxaa.js +2 -1
  106. package/dist/posteffect/fxaa.js.map +1 -1
  107. package/dist/posteffect/grayscale.js +3 -1
  108. package/dist/posteffect/grayscale.js.map +1 -1
  109. package/dist/posteffect/motionblur.js +664 -56
  110. package/dist/posteffect/motionblur.js.map +1 -1
  111. package/dist/posteffect/posteffect.js +12 -1
  112. package/dist/posteffect/posteffect.js.map +1 -1
  113. package/dist/posteffect/sao.js +46 -44
  114. package/dist/posteffect/sao.js.map +1 -1
  115. package/dist/posteffect/skinsss.js +368 -69
  116. package/dist/posteffect/skinsss.js.map +1 -1
  117. package/dist/posteffect/ssgi.js +16 -9
  118. package/dist/posteffect/ssgi.js.map +1 -1
  119. package/dist/posteffect/ssr.js +3 -0
  120. package/dist/posteffect/ssr.js.map +1 -1
  121. package/dist/posteffect/sss.js +121 -36
  122. package/dist/posteffect/sss.js.map +1 -1
  123. package/dist/posteffect/taa.js.map +1 -1
  124. package/dist/posteffect/tonemap.js +44 -1
  125. package/dist/posteffect/tonemap.js.map +1 -1
  126. package/dist/render/abuffer_oit.js +8 -2
  127. package/dist/render/abuffer_oit.js.map +1 -1
  128. package/dist/render/clipmap.js +156 -5
  129. package/dist/render/clipmap.js.map +1 -1
  130. package/dist/render/cull_visitor.js +20 -0
  131. package/dist/render/cull_visitor.js.map +1 -1
  132. package/dist/render/depthpass.js +36 -2
  133. package/dist/render/depthpass.js.map +1 -1
  134. package/dist/render/drawable_mixin.js +26 -1
  135. package/dist/render/drawable_mixin.js.map +1 -1
  136. package/dist/render/dualdepthpeeling_oit.js +3 -2
  137. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  138. package/dist/render/envlight.js +1 -1
  139. package/dist/render/envlight.js.map +1 -1
  140. package/dist/render/fbm_wavegenerator.js +6 -3
  141. package/dist/render/fbm_wavegenerator.js.map +1 -1
  142. package/dist/render/fft_wavegenerator.js +57 -15
  143. package/dist/render/fft_wavegenerator.js.map +1 -1
  144. package/dist/render/gerstner_wavegenerator.js +130 -20
  145. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  146. package/dist/render/globalbindgroup_allocator.js +3 -0
  147. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  148. package/dist/render/hzb.js +80 -22
  149. package/dist/render/hzb.js.map +1 -1
  150. package/dist/render/lightpass.js +6 -3
  151. package/dist/render/lightpass.js.map +1 -1
  152. package/dist/render/oit.js +29 -0
  153. package/dist/render/oit.js.map +1 -0
  154. package/dist/render/render_queue.js +39 -0
  155. package/dist/render/render_queue.js.map +1 -1
  156. package/dist/render/renderer.js +4 -0
  157. package/dist/render/renderer.js.map +1 -1
  158. package/dist/render/rendergraph/blackboard.js +1 -0
  159. package/dist/render/rendergraph/blackboard.js.map +1 -1
  160. package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
  161. package/dist/render/rendergraph/executor.js +2 -0
  162. package/dist/render/rendergraph/executor.js.map +1 -1
  163. package/dist/render/rendergraph/forward_plus_builder.js +379 -10
  164. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  165. package/dist/render/rendergraph/frame_resource_requirements.js +1 -0
  166. package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -1
  167. package/dist/render/rendergraph/history_resources.js +8 -1
  168. package/dist/render/rendergraph/history_resources.js.map +1 -1
  169. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  170. package/dist/render/rendergraph/texture_affinity_cache.js +1 -1
  171. package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -1
  172. package/dist/render/rendergraph/types.js.map +1 -1
  173. package/dist/render/shadow_mask_pass.js +73 -8
  174. package/dist/render/shadow_mask_pass.js.map +1 -1
  175. package/dist/render/sky.js +166 -126
  176. package/dist/render/sky.js.map +1 -1
  177. package/dist/render/underwater.js +624 -0
  178. package/dist/render/underwater.js.map +1 -0
  179. package/dist/render/water_caustics.js +1380 -0
  180. package/dist/render/water_caustics.js.map +1 -0
  181. package/dist/render/weightedblended_oit.js +3 -2
  182. package/dist/render/weightedblended_oit.js.map +1 -1
  183. package/dist/scene/basesprite.js +8 -0
  184. package/dist/scene/basesprite.js.map +1 -1
  185. package/dist/scene/batchgroup.js +3 -0
  186. package/dist/scene/batchgroup.js.map +1 -1
  187. package/dist/scene/environment.js +4 -1
  188. package/dist/scene/environment.js.map +1 -1
  189. package/dist/scene/hair.js +963 -0
  190. package/dist/scene/hair.js.map +1 -0
  191. package/dist/scene/light.js +5 -1
  192. package/dist/scene/light.js.map +1 -1
  193. package/dist/scene/mesh.js +481 -47
  194. package/dist/scene/mesh.js.map +1 -1
  195. package/dist/scene/meshdrawable.js +6 -0
  196. package/dist/scene/meshdrawable.js.map +1 -1
  197. package/dist/scene/msdftext.js +3 -0
  198. package/dist/scene/msdftext.js.map +1 -1
  199. package/dist/scene/msdftextsprite.js +3 -0
  200. package/dist/scene/msdftextsprite.js.map +1 -1
  201. package/dist/scene/particlesys.js +6 -0
  202. package/dist/scene/particlesys.js.map +1 -1
  203. package/dist/scene/scene.js +3 -0
  204. package/dist/scene/scene.js.map +1 -1
  205. package/dist/scene/scene_node.js +6 -1
  206. package/dist/scene/scene_node.js.map +1 -1
  207. package/dist/scene/terrain-cm/grass.js +3 -0
  208. package/dist/scene/terrain-cm/grass.js.map +1 -1
  209. package/dist/scene/terrain-cm/grassmaterial.js +3 -0
  210. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  211. package/dist/scene/terrain-cm/terrain-cm.js +6 -0
  212. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  213. package/dist/scene/terrain-cm/texture_cache.js +309 -0
  214. package/dist/scene/terrain-cm/texture_cache.js.map +1 -0
  215. package/dist/scene/water.js +546 -4
  216. package/dist/scene/water.js.map +1 -1
  217. package/dist/shaders/atmosphere.js.map +1 -1
  218. package/dist/shaders/diffusion.js +54 -0
  219. package/dist/shaders/diffusion.js.map +1 -0
  220. package/dist/shaders/shadow.js +33 -21
  221. package/dist/shaders/shadow.js.map +1 -1
  222. package/dist/shaders/temporal.js +71 -33
  223. package/dist/shaders/temporal.js.map +1 -1
  224. package/dist/shaders/water.js +22 -19
  225. package/dist/shaders/water.js.map +1 -1
  226. package/dist/shaders/water_medium.js +56 -0
  227. package/dist/shaders/water_medium.js.map +1 -0
  228. package/dist/shadow/dom.js +546 -0
  229. package/dist/shadow/dom.js.map +1 -0
  230. package/dist/shadow/pcf_opt.js +3 -5
  231. package/dist/shadow/pcf_opt.js.map +1 -1
  232. package/dist/shadow/pcf_pd.js +1 -1
  233. package/dist/shadow/pcf_pd.js.map +1 -1
  234. package/dist/shadow/receiver_bias.js +41 -0
  235. package/dist/shadow/receiver_bias.js.map +1 -0
  236. package/dist/shadow/shader.js +20 -60
  237. package/dist/shadow/shader.js.map +1 -1
  238. package/dist/shadow/shadow_impl.js +89 -0
  239. package/dist/shadow/shadow_impl.js.map +1 -1
  240. package/dist/shadow/shadow_region.js +1 -1
  241. package/dist/shadow/shadow_region.js.map +1 -1
  242. package/dist/shadow/shadowmapper.js +150 -37
  243. package/dist/shadow/shadowmapper.js.map +1 -1
  244. package/dist/shapes/shape.js +11 -2
  245. package/dist/shapes/shape.js.map +1 -1
  246. package/dist/shapes/sphere.js +50 -9
  247. package/dist/shapes/sphere.js.map +1 -1
  248. package/dist/utility/blueprint/common/constants.js +112 -2
  249. package/dist/utility/blueprint/common/constants.js.map +1 -1
  250. package/dist/utility/blueprint/material/func.js +142 -49
  251. package/dist/utility/blueprint/material/func.js.map +1 -1
  252. package/dist/utility/blueprint/material/ir.js +490 -25
  253. package/dist/utility/blueprint/material/ir.js.map +1 -1
  254. package/dist/utility/blueprint/material/texture.js +898 -4
  255. package/dist/utility/blueprint/material/texture.js.map +1 -1
  256. package/dist/utility/serialization/manager.js +434 -101
  257. package/dist/utility/serialization/manager.js.map +1 -1
  258. package/dist/utility/serialization/scene/animation.js +401 -5
  259. package/dist/utility/serialization/scene/animation.js.map +1 -1
  260. package/dist/utility/serialization/scene/batch.js +4 -0
  261. package/dist/utility/serialization/scene/batch.js.map +1 -1
  262. package/dist/utility/serialization/scene/camera.js +186 -26
  263. package/dist/utility/serialization/scene/camera.js.map +1 -1
  264. package/dist/utility/serialization/scene/common.js +26 -5
  265. package/dist/utility/serialization/scene/common.js.map +1 -1
  266. package/dist/utility/serialization/scene/hair.js +1141 -0
  267. package/dist/utility/serialization/scene/hair.js.map +1 -0
  268. package/dist/utility/serialization/scene/light.js +84 -4
  269. package/dist/utility/serialization/scene/light.js.map +1 -1
  270. package/dist/utility/serialization/scene/material.js +1867 -166
  271. package/dist/utility/serialization/scene/material.js.map +1 -1
  272. package/dist/utility/serialization/scene/mesh.js +231 -14
  273. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  274. package/dist/utility/serialization/scene/node.js +141 -7
  275. package/dist/utility/serialization/scene/node.js.map +1 -1
  276. package/dist/utility/serialization/scene/particle.js +4 -0
  277. package/dist/utility/serialization/scene/particle.js.map +1 -1
  278. package/dist/utility/serialization/scene/primitive.js +18 -0
  279. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  280. package/dist/utility/serialization/scene/sprite.js +4 -0
  281. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  282. package/dist/utility/serialization/scene/terrain.js +4 -0
  283. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  284. package/dist/utility/serialization/scene/text.js +4 -0
  285. package/dist/utility/serialization/scene/text.js.map +1 -1
  286. package/dist/utility/serialization/scene/water.js +1105 -21
  287. package/dist/utility/serialization/scene/water.js.map +1 -1
  288. package/dist/utility/serialization/types.js.map +1 -1
  289. package/dist/utility/shprojector.js +9 -8
  290. package/dist/utility/shprojector.js.map +1 -1
  291. package/dist/values.js +49 -2
  292. package/dist/values.js.map +1 -1
  293. package/package.json +3 -3
  294. package/dist/render/rendergraph/frame_graph_context.js +0 -41
  295. package/dist/render/rendergraph/frame_graph_context.js.map +0 -1
  296. package/dist/shaders/ssgi.js +0 -63
  297. package/dist/shaders/ssgi.js.map +0 -1
  298. package/dist/shaders/ssgi_reprojection.js +0 -118
  299. package/dist/shaders/ssgi_reprojection.js.map +0 -1
@@ -6,9 +6,12 @@ import '../material/unlit.js';
6
6
  import '../material/particle.js';
7
7
  import { SubsurfaceProfile } from '../material/subsurfaceprofile.js';
8
8
  import '../material/skin.js';
9
+ import '../material/eye.js';
9
10
  import '@zephyr3d/device';
10
11
  import { LIGHT_TYPE_DIRECTIONAL, LIGHT_TYPE_POINT, LIGHT_TYPE_SPOT, LIGHT_TYPE_RECT } from '../values.js';
11
12
  import '../material/meshmaterial.js';
13
+ import '../material/hair.js';
14
+ import '../material/hairstrand.js';
12
15
  import '../material/grassmaterial.js';
13
16
  import '../material/terrain-cm.js';
14
17
  import '../material/pbrmr.js';
@@ -32,11 +35,12 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
32
35
  * @internal
33
36
  */ class SSS extends AbstractPostEffect {
34
37
  static _profileLUTWidth = 256;
35
- static _profileLUTHeight = 7;
38
+ static _profileLUTHeight = 8;
36
39
  static _profileRadiusEncodeMax = 8;
37
40
  static _profileWorldScaleEncodeMax = 4;
38
41
  static _profileNormalScaleEncodeMax = 2;
39
42
  static _profileExtinctionEncodeMax = 4;
43
+ static _profileSpecularRadiusEncodeMax = 4;
40
44
  static _debugViewMap = {
41
45
  none: 0,
42
46
  scatter_mask: 1,
@@ -187,6 +191,7 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
187
191
  bindGroup.setValue('targetSize', new Vector4(targetWidth, targetHeight, targetWidth, targetHeight));
188
192
  bindGroup.setValue('sampleOffsets', this.createSampleOffsets(kernelRadius));
189
193
  bindGroup.setValue('blurScale', ctx.camera.sssBlurScale);
194
+ bindGroup.setValue('sssStrength', ctx.camera.sssStrength);
190
195
  bindGroup.setValue('blurStdDev', sssSettings.blurStdDev);
191
196
  bindGroup.setValue('depthCutoff', sssSettings.blurDepthCutoff);
192
197
  bindGroup.setValue('normalCutoff', sssSettings.normalCutoff);
@@ -368,6 +373,7 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
368
373
  this.targetSize = pb.vec4().uniform(0);
369
374
  this.sampleOffsets = pb.vec4[sampleCount + 1]().uniform(0);
370
375
  this.blurScale = pb.float().uniform(0);
376
+ this.sssStrength = pb.float().uniform(0);
371
377
  this.blurStdDev = pb.float().uniform(0);
372
378
  this.depthCutoff = pb.float().uniform(0);
373
379
  this.normalCutoff = pb.float().uniform(0);
@@ -487,7 +493,8 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
487
493
  this.$l.profileScatterScaleNorm = pb.clamp(pb.div(this.profileScatterScale, SSS._profileWorldScaleEncodeMax), 0, 1);
488
494
  this.$l.profileStrengthNorm = pb.clamp(pb.mul(this.profileStrengthScale, 0.5), 0, 1);
489
495
  this.$l.depthThreshold = pb.div(pb.mul(this.depthCutoff, pb.add(0.45, pb.mul(this.widthStrength, 0.42)), pb.mix(0.88, 1.16, this.profileWorldScaleNorm)), pb.max(pb.sub(pb.log(pb.add(this.cameraNearFar.y, 1)), pb.log(pb.add(this.cameraNearFar.x, 1))), 1e-4));
490
- this.$l.normalThreshold = pb.clamp(pb.add(pb.max(pb.sub(this.normalCutoff, pb.mul(this.widthStrength, 0.12)), 0.58), pb.sub(pb.mul(this.profileNormalScale, 0.08), 0.08), pb.neg(pb.mul(this.profileBoundaryBleed, 0.04))), 0.52, 0.985);
496
+ this.$l.normalDetailSoftening = pb.mul(pb.clamp(this.sssStrength, 0, 1), pb.add(0.14, pb.mul(this.widthStrength, 0.1)));
497
+ this.$l.normalThreshold = pb.clamp(pb.add(pb.max(pb.sub(this.normalCutoff, pb.add(this.normalDetailSoftening, pb.mul(this.widthStrength, 0.12))), 0.48), pb.sub(pb.mul(this.profileNormalScale, 0.08), 0.08), pb.neg(pb.mul(this.profileBoundaryBleed, 0.04))), 0.44, 0.92);
491
498
  this.$l.centerNormal = this.decodeNormal(this.uv);
492
499
  this.$l.profileRadius = pb.mul(this.readProfileRadius(this.centerSlot), this.profileWorldScale, pb.mix(0.76, 1.44, this.profileScatterScaleNorm), pb.mix(0.92, 1.08, this.profileStrengthNorm));
493
500
  this.$l.profileFalloff = this.readProfileFalloff(this.centerSlot);
@@ -683,15 +690,17 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
683
690
  this.$l.raw = pb.textureSampleLevel(this.profileLUTTex, this.uv, 0);
684
691
  this.$return(pb.vec4(pb.mul(this.raw.x, SSS._profileExtinctionEncodeMax), pb.mul(this.raw.y, SSS._profileWorldScaleEncodeMax), pb.mul(this.raw.z, 2), this.raw.w));
685
692
  });
686
- pb.func('readProfileSlot', [
687
- pb.vec4('param')
693
+ pb.func('readProfileSpecularSettings', [
694
+ pb.float('slot')
688
695
  ], function() {
689
- this.$return(pb.floor(pb.add(pb.mul(this.param.r, 255), 0.5)));
696
+ this.$l.uv = pb.vec2(pb.div(pb.add(this.slot, 0.5), SSS._profileLUTWidth), SSS.profileRowV(7));
697
+ this.$l.raw = pb.textureSampleLevel(this.profileLUTTex, this.uv, 0);
698
+ this.$return(pb.vec2(this.raw.x, pb.mul(this.raw.y, SSS._profileSpecularRadiusEncodeMax)));
690
699
  });
691
- pb.func('readProfilePreset', [
700
+ pb.func('readProfileSlot', [
692
701
  pb.vec4('param')
693
702
  ], function() {
694
- this.$return(pb.floor(pb.add(pb.mul(this.param.b, 255), 0.5)));
703
+ this.$return(pb.floor(pb.add(pb.mul(this.param.r, 255), 0.5)));
695
704
  });
696
705
  pb.func('decodeNormal', [
697
706
  pb.vec2('uv')
@@ -707,6 +716,26 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
707
716
  ], function() {
708
717
  this.$return(ShaderHelper.sampleLinearDepth(this, this.depthTex, this.uv, 0));
709
718
  });
719
+ pb.func('sampleCompatibleSpecular', [
720
+ pb.vec2('uv'),
721
+ pb.float('profileSlot'),
722
+ pb.float('centerLogDepth')
723
+ ], function() {
724
+ this.$l.sampleUV = pb.clamp(this.uv, pb.vec2(0), pb.vec2(1));
725
+ this.$l.sampleParam = pb.textureSampleLevel(this.paramTex, this.sampleUV, 0);
726
+ this.$l.sampleSlot = this.readProfileSlot(this.sampleParam);
727
+ this.$l.sampleDepth01 = this.readDepth01(this.sampleUV);
728
+ this.$if(pb.or(pb.or(pb.lessThanEqual(this.sampleParam.a, 0.5), pb.notEqual(this.sampleSlot, this.profileSlot)), pb.greaterThanEqual(this.sampleDepth01, 1)), function() {
729
+ this.$return(pb.vec4(0));
730
+ });
731
+ this.$l.sampleLogDepth = pb.log(pb.add(pb.mul(this.sampleDepth01, this.cameraNearFar.y), 1));
732
+ this.$if(pb.greaterThan(pb.abs(pb.sub(this.sampleLogDepth, this.centerLogDepth)), 0.022), function() {
733
+ this.$return(pb.vec4(0));
734
+ });
735
+ this.$l.sampleScene = pb.textureSampleLevel(this.colorTex, this.sampleUV, 0).rgb;
736
+ this.$l.sampleDiffuse = pb.textureSampleLevel(this.diffuseTex, this.sampleUV, 0).rgb;
737
+ this.$return(pb.vec4(pb.max(pb.sub(this.sampleScene, this.sampleDiffuse), pb.vec3(0)), 1));
738
+ });
710
739
  pb.func('estimateScreenThinnessComponents', [
711
740
  pb.vec2('uv'),
712
741
  pb.float('depth01'),
@@ -714,13 +743,27 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
714
743
  pb.float('radiusScale')
715
744
  ], function() {
716
745
  this.$l.pixelStep = pb.div(pb.add(pb.vec2(1.2), pb.mul(pb.vec2(2.4), this.radiusScale)), this.targetSize.xy);
746
+ // Depth differences are accumulated as a ratio to the center depth
747
+ // rather than raw. readDepth01 returns linear depth already divided
748
+ // by the far plane, so both terms carry the same normalization and
749
+ // it cancels: |dz|/z is a pure ratio, independent of the far plane,
750
+ // of the camera distance, and of the scale the model was built at.
751
+ //
752
+ // The raw difference this replaces was scaled by a fixed constant
753
+ // instead, which left sensitivity proportional to 1 / farPlane - an
754
+ // otherwise identical scene with far = 1000 got a tenth the response
755
+ // of one with far = 100. At ordinary far planes the interior
756
+ // collapsed to zero and only the silhouette survived, where the
757
+ // difference saturates no matter how it is scaled. That is what made
758
+ // automatic thin-region inference read as an outline.
759
+ this.$l.invCenterDepth = pb.div(1, pb.max(this.depth01, 1e-5));
717
760
  this.$l.depthAccum = pb.float(0);
718
761
  this.$l.normalAccum = pb.float(0);
719
762
  this.$l.sampleCount = pb.float(0);
720
763
  const sampleAt = (offsetX, offsetY)=>{
721
764
  this.$l.sampleUV = pb.clamp(pb.add(this.uv, pb.mul(this.pixelStep, pb.vec2(offsetX, offsetY))), pb.vec2(0), pb.vec2(1));
722
765
  this.$l.sampleDepth01 = this.readDepth01(this.sampleUV);
723
- this.depthAccum = pb.add(this.depthAccum, pb.abs(pb.sub(this.sampleDepth01, this.depth01)));
766
+ this.depthAccum = pb.add(this.depthAccum, pb.mul(pb.abs(pb.sub(this.sampleDepth01, this.depth01)), this.invCenterDepth));
724
767
  this.$if(pb.notEqual(this.hasNormalTex, 0), function() {
725
768
  this.normalAccum = pb.add(this.normalAccum, pb.clamp(pb.sub(1, pb.dot(this.decodeNormal(this.sampleUV), this.normalWS)), 0, 1));
726
769
  });
@@ -734,7 +777,21 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
734
777
  sampleAt(-1, -1);
735
778
  this.$l.depthMean = pb.div(this.depthAccum, pb.max(this.sampleCount, 1));
736
779
  this.$l.normalMean = pb.div(this.normalAccum, pb.max(this.sampleCount, 1));
737
- this.$l.depthThin = pb.clamp(pb.mul(this.depthMean, pb.add(52, pb.mul(this.radiusScale, 18))), 0, 1);
780
+ // Recalibrated for the relative gradient above; the old 52 applied to
781
+ // a raw normalized difference and is not comparable.
782
+ //
783
+ // For a surface turned by angle t from the view direction, the ratio
784
+ // over a k-pixel step is about tan(t) * k / (0.5 * height * P11), and
785
+ // the six-tap pattern averages roughly two thirds of that. At 1080p
786
+ // with a 40 degree vertical FOV this puts an ear rim or nostril wing
787
+ // (t around 80 degrees) near 0.5, keeps a flat cheek under 0.1, and
788
+ // still saturates on a silhouette.
789
+ //
790
+ // A residual dependence on vertical resolution and FOV remains,
791
+ // because pixelStep is expressed in pixels. That is bounded and
792
+ // predictable, unlike the far-plane scaling it replaces, so it is
793
+ // left alone rather than fixed by hand here.
794
+ this.$l.depthThin = pb.clamp(pb.mul(this.depthMean, pb.add(80, pb.mul(this.radiusScale, 28))), 0, 1);
738
795
  this.$l.normalThin = pb.clamp(pb.mul(this.normalMean, pb.add(1.4, pb.mul(this.radiusScale, 0.5))), 0, 1);
739
796
  this.$l.combinedThin = pb.clamp(pb.add(pb.mul(this.depthThin, 0.72), pb.mul(this.normalThin, 0.52)), 0, 1);
740
797
  this.$return(pb.vec3(this.depthThin, this.normalThin, this.combinedThin));
@@ -838,10 +895,9 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
838
895
  this.$l.scatterSoftnessAuthor = pb.clamp(pb.mul(pb.sub(this.param.a, 0.75), 4), 0, 1);
839
896
  this.$l.roughnessInfo = pb.textureSampleLevel(this.roughnessTex, this.uv, 0);
840
897
  this.$l.profileStrength = this.max3(this.profile.rgb);
898
+ this.$l.automaticTransmission = pb.lessThan(this.profile.a, 0);
841
899
  this.$l.materialTransmissionMask = pb.clamp(this.profile.a, 0, 1);
842
- this.debugTransmissionMask = this.materialTransmissionMask;
843
900
  this.$l.profileSlot = this.readProfileSlot(this.param);
844
- this.$l.profilePreset = this.readProfilePreset(this.param);
845
901
  this.$l.depth01 = this.readDepth01(this.uv);
846
902
  this.$l.result = this.baseColor.rgb;
847
903
  // Transmission shadow. On the PCSS path the shadow uses implicit
@@ -871,6 +927,7 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
871
927
  this.$l.profileSettingsA = this.readProfileSettingsA(this.profileSlot);
872
928
  this.$l.profileTransmissionTint = this.readProfileTransmissionTint(this.profileSlot);
873
929
  this.$l.profileSettingsB = this.readProfileSettingsB(this.profileSlot);
930
+ this.$l.profileSpecularSettings = this.readProfileSpecularSettings(this.profileSlot);
874
931
  this.$l.profileWorldScale = pb.clamp(this.profileSettingsA.x, 0.05, SSS._profileWorldScaleEncodeMax);
875
932
  this.$l.profileBoundaryBleed = pb.clamp(this.profileSettingsA.y, 0, 1);
876
933
  this.$l.profileNormalScale = pb.clamp(this.profileSettingsA.z, 0, SSS._profileNormalScaleEncodeMax);
@@ -878,6 +935,8 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
878
935
  this.$l.profileExtinctionScale = pb.clamp(this.profileSettingsB.x, 0, SSS._profileExtinctionEncodeMax);
879
936
  this.$l.profileScatterScale = pb.clamp(this.profileSettingsB.y, 0.05, SSS._profileWorldScaleEncodeMax);
880
937
  this.$l.profileStrengthScale = pb.clamp(this.profileSettingsB.z, 0.05, 2);
938
+ this.$l.profileSpecularSoftness = pb.clamp(this.profileSpecularSettings.x, 0, 1);
939
+ this.$l.profileSpecularRadius = pb.clamp(this.profileSpecularSettings.y, 0, SSS._profileSpecularRadiusEncodeMax);
881
940
  this.$l.profileWorldScaleNorm = pb.clamp(pb.div(this.profileWorldScale, SSS._profileWorldScaleEncodeMax), 0, 1);
882
941
  this.$l.profileScatterScaleNorm = pb.clamp(pb.div(this.profileScatterScale, SSS._profileWorldScaleEncodeMax), 0, 1);
883
942
  this.$l.profileStrengthNorm = pb.clamp(pb.mul(this.profileStrengthScale, 0.5), 0, 1);
@@ -896,7 +955,6 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
896
955
  this.$l.presetResponse = this.readProfilePresetResponse(this.profileSlot);
897
956
  this.$l.presetFrontScatter = this.presetResponse.x;
898
957
  this.$l.presetSoftness = this.presetResponse.y;
899
- this.$l.presetEnergyBoost = this.presetResponse.z;
900
958
  this.$l.presetTransmissionBoost = this.presetResponse.w;
901
959
  this.$l.presetTint = pb.mul(this.profileTintBase, this.presetTintBias, pb.mix(pb.vec3(1), this.profileTransmissionTint, pb.mul(this.profileBoundaryBleed, 0.22)));
902
960
  this.$l.tint = pb.div(pb.max(this.presetTint, pb.vec3(1e-4)), pb.max(this.max3(this.presetTint), 1e-4));
@@ -918,6 +976,11 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
918
976
  this.$l.depthThinness = this.thinnessComponents.x;
919
977
  this.$l.curvatureThinness = this.thinnessComponents.y;
920
978
  this.$l.screenThinness = this.thinnessComponents.z;
979
+ this.$l.backLightFacing = pb.clamp(pb.dot(pb.neg(this.normalWS), this.sunDirWS), 0, 1);
980
+ this.$l.backLightGate = pb.pow(this.backLightFacing, pb.max(this.transmissionPower, 0.1));
981
+ this.$l.automaticThinMask = pb.mul(pb.smoothStep(0.24, 0.72, this.screenThinness), pb.mix(0.58, 1, this.backLightGate));
982
+ this.materialTransmissionMask = this.$choice(this.automaticTransmission, this.automaticThinMask, this.materialTransmissionMask);
983
+ this.debugTransmissionMask = this.materialTransmissionMask;
921
984
  this.debugThinness = this.screenThinness;
922
985
  this.debugThinnessLayers = pb.vec3(this.depthThinness, this.curvatureThinness, 0);
923
986
  this.$l.wrappedFront = pb.smoothStep(pb.add(-0.32, pb.mul(this.profileSpread, 0.08)), pb.add(0.42, pb.mul(this.radiusResponse, 0.16)), pb.add(this.frontLit, pb.mul(this.radiusResponse, 0.18)));
@@ -933,7 +996,6 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
933
996
  this.$l.edgeThinBoost = pb.clamp(pb.mul(this.screenThinness, pb.add(0.14, pb.mul(this.materialThicknessField, 0.22))), 0, 0.32);
934
997
  this.$l.curvatureThinBoost = pb.clamp(pb.mul(this.curvatureThinness, pb.add(pb.add(0.08, pb.mul(this.materialThicknessField, 0.12)), pb.mul(pb.sub(this.profileNormalScale, 1), 0.08))), 0, 0.18);
935
998
  this.$l.scatterMask = pb.clamp(pb.add(this.surfaceScatterMask, pb.add(pb.add(pb.mul(this.materialScatterBoost, 0.28), this.presetFrontScatter), pb.add(pb.mul(pb.clamp(pb.sub(1, pb.max(this.frontLit, 0)), 0, 1), pb.add(pb.mul(this.materialScatterBoost, 0.22), pb.mul(this.presetFrontScatter, 0.7))), pb.mul(this.screenThinness, pb.add(0.12, pb.mul(this.presetSoftness, 0.08), pb.mul(this.presetFrontScatter, 0.08))), pb.mul(this.materialTransmissionSoftness, 0.1)))), pb.add(pb.add(0.05, pb.mul(this.materialScatterBoost, 0.12)), pb.mul(this.presetFrontScatter, 0.3)), 1);
936
- this.$l.scatterMix = pb.add(pb.add(0.54, pb.mul(this.radiusResponse, 0.94)), pb.add(pb.add(pb.mul(this.avgFalloff, 0.22), pb.mul(this.profileSpread, 0.08)), pb.add(pb.mul(this.materialScatterBoost, 0.3), this.presetSoftness)));
937
999
  this.$l.scatterStrength = pb.clamp(pb.mul(pb.max(pb.mul(this.profileStrength, pb.mix(0.88, 1.24, this.profileStrengthNorm)), pb.mul(this.widthStrength, 0.45)), this.sssStrength, this.profileEnergyScale, pb.add(pb.add(0.62, pb.mul(this.avgFalloff, 0.56)), pb.add(pb.add(pb.mul(this.profileSpread, 0.28), pb.mul(this.screenThinness, 0.18)), pb.add(pb.add(pb.mul(this.materialScatterBoost, 0.42), pb.mul(this.presetSoftness, 0.5)), pb.mul(this.materialTransmissionSoftness, 0.22))))), 0, pb.add(pb.add(pb.add(1.05, pb.mul(this.materialScatterBoost, 1.1)), pb.add(pb.mul(this.presetSoftness, 0.8), pb.mul(this.profileEnergyScale, 0.24))), pb.mul(this.screenThinness, 0.22)));
938
1000
  this.$l.blurred = pb.textureSampleLevel(this.blurTex, this.uv, 0).rgb;
939
1001
  this.$l.roughness = pb.float(1);
@@ -953,26 +1015,44 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
953
1015
  this.$l.skinTint = pb.normalize(pb.max(pb.mix(this.baseDiffuseSafe, pb.mul(this.baseDiffuseSafe, pb.mix(this.tint, this.profileTransmissionTint, pb.mul(this.profileBoundaryBleed, 0.2))), this.epidermalMix), pb.vec3(1e-4)));
954
1016
  this.$l.deepTint = pb.normalize(pb.max(pb.mix(this.skinTint, pb.mul(this.tint, pb.mix(pb.vec3(1), this.profileTransmissionTint, pb.clamp(pb.add(pb.mul(this.profileBoundaryBleed, 0.35), pb.mul(pb.sub(1, pb.clamp(pb.div(this.profileExtinctionScale, SSS._profileExtinctionEncodeMax), 0, 1)), 0.28)), 0, 0.8))), this.deepMix), pb.vec3(1e-4)));
955
1017
  this.$l.baseSpecular = pb.max(pb.sub(this.baseColor.rgb, this.baseDiffuseColor), pb.vec3(0));
956
- this.$l.scatterBlend = pb.clamp(pb.mul(this.scatterStrength, pb.add(0.12, pb.mul(this.scatterMix, 0.24)), this.scatterMask), 0, pb.add(pb.add(0.7, pb.mul(this.materialScatterBoost, 0.22)), this.$choice(pb.equal(this.profilePreset, 1), 0.12, this.$choice(pb.equal(this.profilePreset, 2), 0.08, 0))));
1018
+ this.$l.softenedSpecular = this.baseSpecular;
1019
+ this.$if(pb.notEqual(this.hasDiffuseTex, 0), function() {
1020
+ // The wide SSS pass contains diffuse lighting only. Preserve broad
1021
+ // highlights, but diffuse the pixel-scale normal detail that would
1022
+ // otherwise be added back unchanged with the specular component.
1023
+ this.$l.specularRadius = pb.clamp(pb.mul(this.profileSpecularRadius, pb.mix(0.9, 1.1, this.radiusResponse)), 0, pb.mul(SSS._profileSpecularRadiusEncodeMax, 1.1));
1024
+ this.$l.specularTexel = pb.div(pb.vec2(this.specularRadius), this.targetSize.xy);
1025
+ this.$l.centerLogDepth = pb.log(pb.add(pb.mul(this.depth01, this.cameraNearFar.y), 1));
1026
+ this.$l.specularSum = this.baseSpecular;
1027
+ this.$l.specularWeight = pb.float(1);
1028
+ const sampleSpecularAt = (offsetX, offsetY)=>{
1029
+ this.$l.specularNeighbor = this.sampleCompatibleSpecular(pb.add(this.uv, pb.mul(this.specularTexel, pb.vec2(offsetX, offsetY))), this.profileSlot, this.centerLogDepth);
1030
+ this.specularSum = pb.add(this.specularSum, pb.mul(this.specularNeighbor.rgb, this.specularNeighbor.a, 0.72));
1031
+ this.specularWeight = pb.add(this.specularWeight, pb.mul(this.specularNeighbor.a, 0.72));
1032
+ };
1033
+ sampleSpecularAt(1, 0);
1034
+ sampleSpecularAt(-1, 0);
1035
+ sampleSpecularAt(0, 1);
1036
+ sampleSpecularAt(0, -1);
1037
+ this.$l.filteredSpecular = pb.div(this.specularSum, pb.max(this.specularWeight, 1));
1038
+ this.$l.specularSoftnessBase = pb.clamp(pb.mul(this.profileSpecularSoftness, this.profileStrength, this.sssStrength), 0, 0.95);
1039
+ this.$l.specularSoftness = pb.sub(1, pb.mul(pb.sub(1, this.specularSoftnessBase), pb.sub(1, this.specularSoftnessBase)));
1040
+ this.softenedSpecular = pb.mix(this.baseSpecular, this.filteredSpecular, this.specularSoftness);
1041
+ });
1042
+ this.$l.scatterBlendBase = pb.clamp(pb.mul(this.profileStrength, this.sssStrength, pb.add(0.28, pb.add(pb.mul(this.radiusResponse, 0.42), pb.mul(this.widthStrength, 0.3))), pb.mix(0.72, 1, this.scatterMask)), 0, 0.92);
1043
+ // Diffusion should visibly suppress high-frequency normal lighting.
1044
+ // A squared complement keeps low strengths controllable while making
1045
+ // a fully enabled skin profile approach the blurred diffuse result.
1046
+ this.$l.scatterBlend = pb.clamp(pb.sub(1, pb.mul(pb.sub(1, this.scatterBlendBase), pb.sub(1, this.scatterBlendBase))), 0, 0.97);
957
1047
  this.$l.blurredLuma = pb.dot(this.blurred, pb.vec3(0.2126, 0.7152, 0.0722));
958
- this.$l.profileBlurred = pb.mix(this.blurred, pb.mul(this.blurred, pb.mix(pb.vec3(1), this.skinTint, pb.clamp(pb.add(pb.add(0.2, pb.mul(this.avgFalloff, 0.26)), pb.add(pb.mul(this.materialScatterBoost, 0.16), pb.mul(this.presetSoftness, 0.18))), 0, 0.72))), pb.clamp(pb.add(pb.add(0.14, pb.mul(this.radiusResponse, 0.18)), pb.add(pb.mul(this.avgFalloff, 0.08), pb.add(pb.mul(this.materialScatterBoost, 0.16), pb.mul(this.presetSoftness, 0.16)))), 0, 0.58));
959
- this.$l.warmBlur = pb.mul(pb.max(this.blurredLuma, pb.mul(this.baseDiffuseLuma, 0.82)), this.deepTint);
960
- this.profileBlurred = pb.mix(this.profileBlurred, this.warmBlur, pb.clamp(pb.add(pb.add(0.1, pb.mul(this.avgFalloff, 0.08)), pb.add(pb.mul(this.materialScatterBoost, 0.12), pb.mul(this.presetSoftness, 0.08))), 0.08, 0.32));
961
- this.$l.blurredClamped = pb.min(this.profileBlurred, pb.add(this.baseDiffuseColor, pb.mul(this.deepTint, pb.mul(this.scatterMask, pb.add(pb.add(0.06, pb.mul(this.radiusResponse, 0.18)), pb.add(pb.mul(this.avgFalloff, 0.08), pb.add(pb.mul(this.materialScatterBoost, 0.2), pb.mul(this.presetEnergyBoost, 0.18))))))));
1048
+ this.$l.profileBlurred = this.blurred;
1049
+ this.$l.blurredClamped = this.profileBlurred;
962
1050
  this.$l.scattered = pb.mix(this.baseDiffuseColor, this.blurredClamped, this.scatterBlend);
963
- this.$l.terminatorLift = pb.mul(this.baseDiffuseColor, this.deepTint, this.scatterStrength, this.terminatorWrap, pb.add(pb.add(0.014, pb.mul(this.radiusResponse, 0.022)), pb.add(pb.mul(this.profileSpread, 0.012), pb.add(pb.mul(this.materialScatterBoost, 0.02), pb.mul(this.presetSoftness, 0.012)))), pb.sub(1, pb.mul(this.specMask, 0.45)));
964
1051
  this.$l.multiScatterLift = pb.mul(pb.max(pb.sub(this.blurredClamped, this.baseDiffuseColor), pb.vec3(0)), this.scatterStrength, this.multiScatter, this.scatterMask, pb.add(0.08, pb.mul(this.radiusResponse, pb.add(pb.add(0.09, pb.mul(this.materialScatterBoost, 0.08)), pb.add(pb.mul(this.presetSoftness, 0.06), pb.add(pb.mul(pb.sub(this.profileScatterScale, 1), 0.04), pb.mul(this.profileDiffusionSoftness, 0.03)))))));
965
- this.$l.subdermalFill = pb.mul(this.deepTint, this.scatterStrength, pb.add(pb.mul(this.shadowMask, pb.add(pb.add(0.026, pb.mul(this.radiusResponse, 0.08)), pb.add(pb.mul(this.profileSpread, 0.045), pb.mul(this.materialScatterBoost, 0.05)))), pb.add(pb.add(0.004, pb.mul(this.avgFalloff, 0.012)), pb.add(pb.mul(this.profileSpread, 0.004), pb.mul(this.materialScatterBoost, 0.008)))), this.scatterMask, pb.sub(1, pb.mul(this.specMask, 0.62)));
966
- this.$l.veilColor = pb.mix(this.blurredClamped, pb.mul(this.blurredClamped, this.skinTint), 0.16);
967
- this.$l.softVeil = pb.mul(this.veilColor, this.scatterStrength, this.scatterMask, pb.add(pb.add(pb.add(0.002, pb.mul(this.materialScatterBoost, 0.004)), pb.mul(this.presetSoftness, 0.004)), pb.mul(this.radiusResponse, pb.add(pb.add(0.007, pb.mul(this.avgFalloff, 0.007)), pb.add(pb.mul(this.profileSpread, 0.003), pb.add(pb.mul(this.materialScatterBoost, 0.006), pb.mul(this.presetSoftness, 0.004)))))), pb.sub(1, pb.mul(this.specMask, 0.55)));
968
- this.$l.scatteredDiffuse = pb.add(this.scattered, pb.add(this.terminatorLift, pb.add(this.multiScatterLift, pb.add(this.subdermalFill, this.softVeil))));
969
- this.$l.energyLimit = pb.add(this.baseDiffuseColor, pb.mul(this.deepTint, this.scatterStrength, this.scatterMask, pb.add(pb.add(pb.add(0.1, pb.mul(this.radiusResponse, 0.18)), pb.add(pb.mul(this.avgFalloff, 0.08), pb.mul(this.profileSpread, 0.04))), pb.add(pb.mul(this.materialScatterBoost, 0.28), this.presetEnergyBoost))));
970
- this.$l.scatterBudget = pb.max(pb.sub(this.energyLimit, this.baseDiffuseColor), pb.vec3(0));
971
- this.$l.scatterDelta = pb.max(pb.sub(this.scatteredDiffuse, this.baseDiffuseColor), pb.vec3(0));
972
- this.$l.scatteredDiffuse = pb.add(this.baseDiffuseColor, pb.min(this.scatterDelta, this.scatterBudget));
1052
+ this.$l.scatteredDiffuse = pb.add(this.scattered, this.multiScatterLift);
973
1053
  this.$l.transmissionBudget = pb.mul(this.deepTint, this.scatterStrength, this.materialTransmissionResponse, this.transmissionStrength, this.profileTransmissionScale, pb.add(pb.add(pb.add(0.12, pb.mul(this.radiusResponse, 0.2)), pb.mul(this.materialTransmissionSoftness, 0.2)), pb.add(pb.mul(this.avgFalloff, 0.12), pb.add(pb.mul(this.profileSpread, 0.12), pb.add(pb.mul(this.materialScatterBoost, 0.12), pb.mul(this.presetTransmissionBoost, 0.22))))), pb.mix(0.82, 1.18, this.profileScatterScaleNorm));
974
1054
  this.$l.diffuseHeadroom = pb.mul(this.transmissionBudget, pb.add(pb.add(1.65, pb.mul(this.materialTransmissionResponse, 0.9)), pb.add(pb.add(pb.mul(this.materialTransmissionSoftness, 0.55), pb.mul(this.profileSpread, 0.2)), pb.mul(this.profileTransmissionScale, 0.22))));
975
- this.result = pb.add(this.baseSpecular, this.scatteredDiffuse);
1055
+ this.result = pb.add(this.softenedSpecular, this.scatteredDiffuse);
976
1056
  this.$if(pb.greaterThan(this.transmissionStrength, 0), function() {
977
1057
  this.$l.viewDirVS = pb.normalize(pb.neg(this.viewPos));
978
1058
  this.$l.viewDirWS = pb.normalize(pb.mul(this.invViewMatrix, pb.vec4(this.viewDirVS, 0)).xyz);
@@ -984,8 +1064,9 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
984
1064
  this.debugTransmissionShadow = this.transmissionShadow;
985
1065
  this.debugProfileBoundary = pb.clamp(pb.vec3(this.profileBoundaryBleed, this.transmissionShadowSoftness, this.transmissionShadowFloor), pb.vec3(0), pb.vec3(1));
986
1066
  this.debugProfileTransmission = pb.clamp(pb.vec3(pb.div(this.profileTransmissionScale, 1.35), this.materialTransmissionResponse, this.transmissionShadow), pb.vec3(0), pb.vec3(1));
987
- this.$l.backLit = pb.mul(pb.pow(pb.clamp(pb.dot(pb.neg(this.normalWS), this.sunDirWS), 0, 1), 1.5), this.transmissionLightAttenuation);
988
- this.$l.thicknessBackScatter = pb.clamp(pb.add(pb.add(pb.mul(this.backLit, 0.82), pb.mul(this.rim, 0.24)), pb.mul(this.materialThicknessField, 0.08)), 0, 1);
1067
+ // Keep warm thin-shell lighting on the back-facing side of the surface.
1068
+ this.$l.backLit = pb.mul(this.backLightGate, this.transmissionLightAttenuation);
1069
+ this.$l.thicknessBackScatter = pb.clamp(pb.mul(this.backLit, pb.add(0.82, pb.mul(this.rim, 0.18))), 0, 1);
989
1070
  this.$l.thinPotential = pb.clamp(pb.add(pb.add(pb.mul(this.materialThicknessField, 0.78), this.edgeThinBoost), pb.add(this.curvatureThinBoost, pb.mul(pb.sqrt(pb.max(pb.mul(this.depthThinness, this.curvatureThinness), 0)), pb.add(pb.add(0.08, pb.mul(this.profileSpread, 0.06)), pb.mul(pb.sub(this.profileDistribution, 0.5), 0.06))))), 0, 1);
990
1071
  this.$l.transmissionFocus = pb.clamp(pb.mul(pb.add(pb.mul(this.materialThicknessField, 0.72), pb.mul(this.thinPotential, 0.38)), pb.add(pb.add(0.22, pb.mul(this.thicknessBackScatter, 0.72)), pb.mul(this.backLit, 0.08))), 0, 1);
991
1072
  this.$l.thinRegion = pb.clamp(pb.add(pb.add(pb.mul(this.materialThicknessField, 0.86), pb.mul(this.transmissionFocus, 0.74)), pb.add(pb.add(pb.mul(this.edgeThinBoost, 0.9), pb.mul(this.curvatureThinBoost, 0.5)), pb.add(pb.mul(this.backLit, 0.1), pb.mul(this.profileSpread, 0.05)))), 0, 1);
@@ -1010,13 +1091,11 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
1010
1091
  this.$l.transmittedDiffuseLuma = pb.dot(this.transmittedDiffuse, pb.vec3(0.2126, 0.7152, 0.0722));
1011
1092
  this.$l.visibleTransmissionMask = pb.clamp(pb.add(pb.add(pb.mul(this.materialThicknessField, 0.72), pb.mul(this.thinRegion, 0.34)), pb.add(pb.mul(this.thicknessBackScatter, 0.28), pb.add(pb.mul(this.materialTransmissionSoftness, 0.08), pb.add(pb.mul(this.profileTransmissionScale, 0.12), pb.mul(this.profileDiffusionSoftness, 0.06))))), 0, 1);
1012
1093
  this.$l.visibleTransmissionChroma = pb.clamp(pb.add(pb.add(0.66, pb.mul(this.visibleTransmissionMask, 0.32)), pb.add(pb.mul(this.thicknessBackScatter, 0.24), pb.mul(this.presetTransmissionBoost, 0.14))), 0.52, 1);
1013
- this.$l.visibleTransmissionTint = pb.normalize(pb.max(pb.mix(this.warmTransmissionTint, pb.mul(this.profileTransmissionTint, pb.vec3(1.0, 0.14, 0.08)), pb.clamp(pb.add(pb.add(0.5, pb.mul(this.visibleTransmissionMask, 0.22)), pb.mul(this.thicknessBackScatter, 0.18)), 0, 0.92)), pb.vec3(1e-4)));
1094
+ this.$l.visibleTransmissionTint = pb.normalize(pb.max(pb.mix(this.warmTransmissionTint, this.profileTransmissionTint, pb.clamp(pb.add(pb.mul(this.visibleTransmissionMask, 0.24), pb.mul(this.thicknessBackScatter, 0.2)), 0, 0.6)), pb.vec3(1e-4)));
1014
1095
  this.$l.visibleTransmissionWarm = pb.mix(this.transmittedDiffuse, pb.mul(this.visibleTransmissionTint, this.transmittedDiffuseLuma), this.visibleTransmissionChroma);
1015
- this.$l.visibleTransmissionGain = pb.clamp(pb.add(pb.add(2.35, pb.mul(this.visibleTransmissionMask, 1.85)), pb.add(pb.mul(this.thicknessBackScatter, 1.3), pb.add(pb.mul(this.materialTransmissionSoftness, 0.42), pb.mul(this.profileTransmissionScale, 0.72)))), 2, 6.4);
1096
+ this.$l.visibleTransmissionGain = pb.clamp(pb.add(pb.add(0.78, pb.mul(this.visibleTransmissionMask, 0.72)), pb.add(pb.mul(this.thicknessBackScatter, 0.48), pb.add(pb.mul(this.materialTransmissionSoftness, 0.18), pb.mul(this.profileTransmissionScale, 0.24)))), 0.7, 2.4);
1016
1097
  this.$l.visibleTransmissionHeadroom = pb.add(pb.mul(this.transmissionHeadroom, pb.add(pb.add(2.15, pb.mul(this.visibleTransmissionMask, 1.1)), pb.mul(this.thicknessBackScatter, 0.72))), pb.mul(this.visibleTransmissionTint, this.visibleTransmissionMask, this.transmittedDiffuseLuma, pb.add(pb.add(0.12, pb.mul(this.thicknessBackScatter, 0.18)), pb.mul(this.materialThicknessField, 0.14))));
1017
- this.$l.visibleTransmission = pb.min(pb.mul(this.visibleTransmissionWarm, this.visibleTransmissionGain, this.transmissionShadow), this.visibleTransmissionHeadroom);
1018
- this.$l.visibleTransmissionReveal = pb.clamp(pb.mul(this.visibleTransmissionMask, this.transmissionShadow, pb.add(pb.add(0.1, pb.mul(this.thicknessBackScatter, 0.25)), pb.mul(this.materialThicknessField, 0.12))), 0, 0.38);
1019
- this.result = pb.mix(this.result, pb.mul(this.result, pb.vec3(0.84, 0.68, 0.64)), this.visibleTransmissionReveal);
1098
+ this.$l.visibleTransmission = pb.min(pb.mul(this.visibleTransmissionWarm, this.visibleTransmissionGain, this.transmissionShadow, this.backLightGate), this.visibleTransmissionHeadroom);
1020
1099
  this.debugThinLighting = this.visibleTransmission;
1021
1100
  this.result = pb.add(this.result, this.visibleTransmission);
1022
1101
  });
@@ -1079,7 +1158,8 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
1079
1158
  }
1080
1159
  ensureProfileLUT(ctx) {
1081
1160
  const device = ctx.device;
1082
- if (!SSS._profileLUT || SSS._profileLUT.device !== device) {
1161
+ if (!SSS._profileLUT || SSS._profileLUT.device !== device || SSS._profileLUT.width !== SSS._profileLUTWidth || SSS._profileLUT.height !== SSS._profileLUTHeight) {
1162
+ SSS._profileLUT?.dispose();
1083
1163
  SSS._profileLUT = device.createTexture2D('rgba8unorm', SSS._profileLUTWidth, SSS._profileLUTHeight, {
1084
1164
  mipmapping: false
1085
1165
  });
@@ -1131,6 +1211,11 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
1131
1211
  data[settingsBOffset + 1] = encodeScaled(profile.scale, SSS._profileWorldScaleEncodeMax);
1132
1212
  data[settingsBOffset + 2] = encodeUnit(profile.strength / 2);
1133
1213
  data[settingsBOffset + 3] = 255;
1214
+ const specularSettingsOffset = (SSS._profileLUTWidth * 7 + slot) * 4;
1215
+ data[specularSettingsOffset + 0] = encodeUnit(profile.specularDetailSoftness);
1216
+ data[specularSettingsOffset + 1] = encodeScaled(profile.specularDetailRadius, SSS._profileSpecularRadiusEncodeMax);
1217
+ data[specularSettingsOffset + 2] = 0;
1218
+ data[specularSettingsOffset + 3] = 255;
1134
1219
  }
1135
1220
  SSS._profileLUT.update(data, 0, 0, SSS._profileLUTWidth, SSS._profileLUTHeight);
1136
1221
  SSS._profileLUTVersion = SubsurfaceProfile.version;