@zephyr3d/scene 0.9.25 → 0.9.26

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 (262) hide show
  1. package/dist/animation/hair/gpu_hair_simulation.js +1090 -0
  2. package/dist/animation/hair/gpu_hair_simulation.js.map +1 -0
  3. package/dist/animation/joint_dynamics/controller.js +7 -5
  4. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  5. package/dist/animation/joint_dynamics/joint_dynamics_system.js +81 -5
  6. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  7. package/dist/animation/morphtrack.js +3 -4
  8. package/dist/animation/morphtrack.js.map +1 -1
  9. package/dist/animation/skeleton.js +58 -24
  10. package/dist/animation/skeleton.js.map +1 -1
  11. package/dist/animation/spring/multi_chain_spring_system.js +577 -179
  12. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  13. package/dist/animation/spring/spring_collider.js +4 -3
  14. package/dist/animation/spring/spring_collider.js.map +1 -1
  15. package/dist/animation/spring/spring_node_pose_tracker.js +40 -0
  16. package/dist/animation/spring/spring_node_pose_tracker.js.map +1 -0
  17. package/dist/animation/spring/spring_particle.js +2 -0
  18. package/dist/animation/spring/spring_particle.js.map +1 -1
  19. package/dist/animation/spring/spring_system.js +11 -3
  20. package/dist/animation/spring/spring_system.js.map +1 -1
  21. package/dist/animation/spring_modifier.js +10 -0
  22. package/dist/animation/spring_modifier.js.map +1 -1
  23. package/dist/app/engine.js +53 -43
  24. package/dist/app/engine.js.map +1 -1
  25. package/dist/app/scriptingsystem.js +119 -6
  26. package/dist/app/scriptingsystem.js.map +1 -1
  27. package/dist/app/scriptregistry.js +3 -2
  28. package/dist/app/scriptregistry.js.map +1 -1
  29. package/dist/asset/assetmanager.js +284 -67
  30. package/dist/asset/assetmanager.js.map +1 -1
  31. package/dist/asset/loaders/zhair/zhair_format.js +288 -0
  32. package/dist/asset/loaders/zhair/zhair_format.js.map +1 -0
  33. package/dist/asset/loaders/zhair/zhair_loader.js +221 -0
  34. package/dist/asset/loaders/zhair/zhair_loader.js.map +1 -0
  35. package/dist/asset/model.js +178 -67
  36. package/dist/asset/model.js.map +1 -1
  37. package/dist/camera/camera.js +193 -21
  38. package/dist/camera/camera.js.map +1 -1
  39. package/dist/camera/perspectivecamera.js +3 -1
  40. package/dist/camera/perspectivecamera.js.map +1 -1
  41. package/dist/index.d.ts +4019 -1278
  42. package/dist/index.js +15 -5
  43. package/dist/index.js.map +1 -1
  44. package/dist/material/eye.js +932 -0
  45. package/dist/material/eye.js.map +1 -0
  46. package/dist/material/hair.js +917 -0
  47. package/dist/material/hair.js.map +1 -0
  48. package/dist/material/hairstrand.js +842 -0
  49. package/dist/material/hairstrand.js.map +1 -0
  50. package/dist/material/hairstrand_data.js +162 -0
  51. package/dist/material/hairstrand_data.js.map +1 -0
  52. package/dist/material/lambert.js +5 -2
  53. package/dist/material/lambert.js.map +1 -1
  54. package/dist/material/material.js +338 -300
  55. package/dist/material/material.js.map +1 -1
  56. package/dist/material/meshmaterial.js +120 -19
  57. package/dist/material/meshmaterial.js.map +1 -1
  58. package/dist/material/mixins/albedocolor.js +1 -1
  59. package/dist/material/mixins/albedocolor.js.map +1 -1
  60. package/dist/material/mixins/lightmodel/blinnphong.js +5 -3
  61. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  62. package/dist/material/mixins/lightmodel/lambert.js +5 -4
  63. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  64. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +3 -3
  65. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  66. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +5 -5
  67. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  68. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +3 -3
  69. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  70. package/dist/material/mixins/lit.js +73 -18
  71. package/dist/material/mixins/lit.js.map +1 -1
  72. package/dist/material/mixins/pbr/common.js +4 -4
  73. package/dist/material/mixins/pbr/common.js.map +1 -1
  74. package/dist/material/mixins/texture.js +48 -11
  75. package/dist/material/mixins/texture.js.map +1 -1
  76. package/dist/material/mtoon.js +6 -5
  77. package/dist/material/mtoon.js.map +1 -1
  78. package/dist/material/pbrblueprint.js +146 -28
  79. package/dist/material/pbrblueprint.js.map +1 -1
  80. package/dist/material/pbrblueprint_instance.js +501 -0
  81. package/dist/material/pbrblueprint_instance.js.map +1 -0
  82. package/dist/material/pbrmr.js +15 -16
  83. package/dist/material/pbrmr.js.map +1 -1
  84. package/dist/material/shader/helper.js +330 -20
  85. package/dist/material/shader/helper.js.map +1 -1
  86. package/dist/material/shader/terrain_blend.js +55 -0
  87. package/dist/material/shader/terrain_blend.js.map +1 -0
  88. package/dist/material/skin.js +192 -32
  89. package/dist/material/skin.js.map +1 -1
  90. package/dist/material/spriteblueprint.js +16 -1
  91. package/dist/material/spriteblueprint.js.map +1 -1
  92. package/dist/material/subsurfaceprofile.js +86 -23
  93. package/dist/material/subsurfaceprofile.js.map +1 -1
  94. package/dist/material/water.js +3 -1
  95. package/dist/material/water.js.map +1 -1
  96. package/dist/posteffect/bloom.js +167 -7
  97. package/dist/posteffect/bloom.js.map +1 -1
  98. package/dist/posteffect/coloradjust.js +2 -1
  99. package/dist/posteffect/coloradjust.js.map +1 -1
  100. package/dist/posteffect/compositor.js.map +1 -1
  101. package/dist/posteffect/fxaa.js +2 -1
  102. package/dist/posteffect/fxaa.js.map +1 -1
  103. package/dist/posteffect/grayscale.js +3 -1
  104. package/dist/posteffect/grayscale.js.map +1 -1
  105. package/dist/posteffect/motionblur.js +4 -1
  106. package/dist/posteffect/motionblur.js.map +1 -1
  107. package/dist/posteffect/posteffect.js.map +1 -1
  108. package/dist/posteffect/skinsss.js +368 -69
  109. package/dist/posteffect/skinsss.js.map +1 -1
  110. package/dist/posteffect/ssgi.js +16 -9
  111. package/dist/posteffect/ssgi.js.map +1 -1
  112. package/dist/posteffect/ssr.js +3 -0
  113. package/dist/posteffect/ssr.js.map +1 -1
  114. package/dist/posteffect/sss.js +91 -34
  115. package/dist/posteffect/sss.js.map +1 -1
  116. package/dist/posteffect/taa.js.map +1 -1
  117. package/dist/posteffect/tonemap.js +44 -1
  118. package/dist/posteffect/tonemap.js.map +1 -1
  119. package/dist/render/abuffer_oit.js +45 -39
  120. package/dist/render/abuffer_oit.js.map +1 -1
  121. package/dist/render/cull_visitor.js +19 -0
  122. package/dist/render/cull_visitor.js.map +1 -1
  123. package/dist/render/depthpass.js +36 -2
  124. package/dist/render/depthpass.js.map +1 -1
  125. package/dist/render/drawable_mixin.js +26 -1
  126. package/dist/render/drawable_mixin.js.map +1 -1
  127. package/dist/render/dualdepthpeeling_oit.js +52 -51
  128. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  129. package/dist/render/envlight.js +1 -1
  130. package/dist/render/envlight.js.map +1 -1
  131. package/dist/render/fbm_wavegenerator.js +3 -0
  132. package/dist/render/fbm_wavegenerator.js.map +1 -1
  133. package/dist/render/gerstner_wavegenerator.js +3 -0
  134. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  135. package/dist/render/globalbindgroup_allocator.js +3 -0
  136. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  137. package/dist/render/oit.js +29 -0
  138. package/dist/render/oit.js.map +1 -0
  139. package/dist/render/render_queue.js +3 -0
  140. package/dist/render/render_queue.js.map +1 -1
  141. package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
  142. package/dist/render/rendergraph/executor.js +2 -0
  143. package/dist/render/rendergraph/executor.js.map +1 -1
  144. package/dist/render/rendergraph/forward_plus_builder.js +61 -1
  145. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  146. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  147. package/dist/render/rendergraph/texture_affinity_cache.js +1 -1
  148. package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -1
  149. package/dist/render/rendergraph/types.js.map +1 -1
  150. package/dist/render/shadow_mask_pass.js +73 -8
  151. package/dist/render/shadow_mask_pass.js.map +1 -1
  152. package/dist/render/sky.js +166 -126
  153. package/dist/render/sky.js.map +1 -1
  154. package/dist/render/weightedblended_oit.js +36 -35
  155. package/dist/render/weightedblended_oit.js.map +1 -1
  156. package/dist/scene/basesprite.js +8 -0
  157. package/dist/scene/basesprite.js.map +1 -1
  158. package/dist/scene/batchgroup.js +3 -0
  159. package/dist/scene/batchgroup.js.map +1 -1
  160. package/dist/scene/environment.js +4 -1
  161. package/dist/scene/environment.js.map +1 -1
  162. package/dist/scene/hair.js +963 -0
  163. package/dist/scene/hair.js.map +1 -0
  164. package/dist/scene/light.js +5 -1
  165. package/dist/scene/light.js.map +1 -1
  166. package/dist/scene/mesh.js +481 -47
  167. package/dist/scene/mesh.js.map +1 -1
  168. package/dist/scene/meshdrawable.js +6 -0
  169. package/dist/scene/meshdrawable.js.map +1 -1
  170. package/dist/scene/msdftext.js +3 -0
  171. package/dist/scene/msdftext.js.map +1 -1
  172. package/dist/scene/msdftextsprite.js +3 -0
  173. package/dist/scene/msdftextsprite.js.map +1 -1
  174. package/dist/scene/particlesys.js +6 -0
  175. package/dist/scene/particlesys.js.map +1 -1
  176. package/dist/scene/scene.js +3 -0
  177. package/dist/scene/scene.js.map +1 -1
  178. package/dist/scene/scene_node.js +6 -1
  179. package/dist/scene/scene_node.js.map +1 -1
  180. package/dist/scene/terrain-cm/grass.js +3 -0
  181. package/dist/scene/terrain-cm/grass.js.map +1 -1
  182. package/dist/scene/terrain-cm/grassmaterial.js +3 -0
  183. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  184. package/dist/scene/terrain-cm/terrain-cm.js +6 -0
  185. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  186. package/dist/scene/terrain-cm/texture_cache.js +309 -0
  187. package/dist/scene/terrain-cm/texture_cache.js.map +1 -0
  188. package/dist/scene/water.js +6 -0
  189. package/dist/scene/water.js.map +1 -1
  190. package/dist/shaders/atmosphere.js.map +1 -1
  191. package/dist/shaders/diffusion.js +54 -0
  192. package/dist/shaders/diffusion.js.map +1 -0
  193. package/dist/shaders/shadow.js +33 -21
  194. package/dist/shaders/shadow.js.map +1 -1
  195. package/dist/shaders/temporal.js +71 -33
  196. package/dist/shaders/temporal.js.map +1 -1
  197. package/dist/shadow/dom.js +546 -0
  198. package/dist/shadow/dom.js.map +1 -0
  199. package/dist/shadow/pcf_opt.js +3 -5
  200. package/dist/shadow/pcf_opt.js.map +1 -1
  201. package/dist/shadow/pcf_pd.js +1 -1
  202. package/dist/shadow/pcf_pd.js.map +1 -1
  203. package/dist/shadow/receiver_bias.js +41 -0
  204. package/dist/shadow/receiver_bias.js.map +1 -0
  205. package/dist/shadow/shader.js +20 -60
  206. package/dist/shadow/shader.js.map +1 -1
  207. package/dist/shadow/shadow_impl.js +89 -0
  208. package/dist/shadow/shadow_impl.js.map +1 -1
  209. package/dist/shadow/shadow_region.js +50 -50
  210. package/dist/shadow/shadow_region.js.map +1 -1
  211. package/dist/shadow/shadowmapper.js +150 -37
  212. package/dist/shadow/shadowmapper.js.map +1 -1
  213. package/dist/shapes/shape.js +11 -2
  214. package/dist/shapes/shape.js.map +1 -1
  215. package/dist/shapes/sphere.js +50 -9
  216. package/dist/shapes/sphere.js.map +1 -1
  217. package/dist/utility/blueprint/common/constants.js +112 -2
  218. package/dist/utility/blueprint/common/constants.js.map +1 -1
  219. package/dist/utility/blueprint/material/func.js +142 -49
  220. package/dist/utility/blueprint/material/func.js.map +1 -1
  221. package/dist/utility/blueprint/material/ir.js +490 -25
  222. package/dist/utility/blueprint/material/ir.js.map +1 -1
  223. package/dist/utility/blueprint/material/texture.js +898 -4
  224. package/dist/utility/blueprint/material/texture.js.map +1 -1
  225. package/dist/utility/serialization/manager.js +431 -100
  226. package/dist/utility/serialization/manager.js.map +1 -1
  227. package/dist/utility/serialization/scene/animation.js +370 -3
  228. package/dist/utility/serialization/scene/animation.js.map +1 -1
  229. package/dist/utility/serialization/scene/batch.js +4 -0
  230. package/dist/utility/serialization/scene/batch.js.map +1 -1
  231. package/dist/utility/serialization/scene/camera.js +133 -1
  232. package/dist/utility/serialization/scene/camera.js.map +1 -1
  233. package/dist/utility/serialization/scene/common.js +26 -5
  234. package/dist/utility/serialization/scene/common.js.map +1 -1
  235. package/dist/utility/serialization/scene/hair.js +1141 -0
  236. package/dist/utility/serialization/scene/hair.js.map +1 -0
  237. package/dist/utility/serialization/scene/light.js +84 -4
  238. package/dist/utility/serialization/scene/light.js.map +1 -1
  239. package/dist/utility/serialization/scene/material.js +1867 -166
  240. package/dist/utility/serialization/scene/material.js.map +1 -1
  241. package/dist/utility/serialization/scene/mesh.js +231 -14
  242. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  243. package/dist/utility/serialization/scene/node.js +141 -7
  244. package/dist/utility/serialization/scene/node.js.map +1 -1
  245. package/dist/utility/serialization/scene/particle.js +4 -0
  246. package/dist/utility/serialization/scene/particle.js.map +1 -1
  247. package/dist/utility/serialization/scene/primitive.js +18 -0
  248. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  249. package/dist/utility/serialization/scene/sprite.js +4 -0
  250. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  251. package/dist/utility/serialization/scene/terrain.js +4 -0
  252. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  253. package/dist/utility/serialization/scene/text.js +4 -0
  254. package/dist/utility/serialization/scene/text.js.map +1 -1
  255. package/dist/utility/serialization/scene/water.js +4 -0
  256. package/dist/utility/serialization/scene/water.js.map +1 -1
  257. package/dist/utility/serialization/types.js.map +1 -1
  258. package/dist/utility/shprojector.js +96 -95
  259. package/dist/utility/shprojector.js.map +1 -1
  260. package/dist/values.js +49 -2
  261. package/dist/values.js.map +1 -1
  262. package/package.json +3 -3
@@ -6,6 +6,7 @@ import { SSGI } from '../posteffect/ssgi.js';
6
6
  import { SSR } from '../posteffect/ssr.js';
7
7
  import { SSS } from '../posteffect/sss.js';
8
8
  import { SkinSSS } from '../posteffect/skinsss.js';
9
+ import { SubsurfaceProfile } from '../material/subsurfaceprofile.js';
9
10
  import { Tonemap } from '../posteffect/tonemap.js';
10
11
  import { FXAA } from '../posteffect/fxaa.js';
11
12
  import { Bloom } from '../posteffect/bloom.js';
@@ -89,7 +90,9 @@ const SSS_QUALITY_PRESET_SETTINGS = {
89
90
  *
90
91
  * @public
91
92
  */ class Camera extends SceneNode {
92
- /** @internal Halton 2-3 sequence used for TAA jittering. */ static _halton23 = halton23(16);
93
+ /** @internal Halton 2-3 sequence used for TAA jittering. An 8-phase cycle
94
+ * repeats faster than the history accumulation window, so residual jitter
95
+ * ripple is attenuated further than with a 16-phase cycle. */ static _halton23 = halton23(8);
93
96
  /** @internal Per-camera history resources. */ static _historyData = new WeakMap();
94
97
  /** @internal Per-camera history resource manager. */ static _historyResourceManager = new WeakMap();
95
98
  /** @internal Screen adapter for this camera */ _screenAdapter;
@@ -141,6 +144,8 @@ const SSS_QUALITY_PRESET_SETTINGS = {
141
144
  /** @internal Bloom threshold. */ _bloomThreshold;
142
145
  /** @internal Bloom threshold knee (soft thresholding). */ _bloomThresholdKnee;
143
146
  /** @internal Bloom intensity. */ _bloomIntensity;
147
+ /** @internal */ _bloomFilterRadius;
148
+ /** @internal */ _bloomKarisAverage;
144
149
  /** @internal Color adjustment enable flag (via post effect). */ _colorAdjustEnabled;
145
150
  /** @internal Color adjustment post effect reference. */ _postEffectColorAdjust;
146
151
  /** @internal Color adjustment saturation. */ _colorAdjustSaturation;
@@ -196,9 +201,15 @@ const SSS_QUALITY_PRESET_SETTINGS = {
196
201
  /** @internal Skin SSS post effect reference. */ _postEffectSkinSSS;
197
202
  /** @internal Skin SSS final blend strength. */ _skinSSSStrength;
198
203
  /** @internal Skin SSS mask opacity bias. */ _skinSSSOpacity;
199
- /** @internal Skin SSS blur tap spacing in pixels. */ _skinSSSSampleStep;
204
+ /** @internal Skin SSS maximum blur tap spacing in pixels. */ _skinSSSSampleStep;
205
+ /** @internal Skin SSS world-space scatter radius. */ _skinSSSScatterRadius;
206
+ /** @internal Skin SSS smoothing ("beauty filter") amount. */ _skinSSSSmoothness;
200
207
  /** @internal Skin SSS depth rejection scale. */ _skinSSSDepthScale;
201
208
  /** @internal Skin SSS blurred multiplier boost. */ _skinSSSColorBoost;
209
+ _skinSSSGlow;
210
+ _skinSSSProfilePreset;
211
+ _skinSSSProfile;
212
+ _skinSSSScatterTint;
202
213
  /** @internal SSAO enable flag (via post effect). */ _SSAO;
203
214
  /** @internal SSAO post effect reference. */ _postEffectSSAO;
204
215
  /** @internal SSAO scale (sampling radius multiplier). */ _SSAOScale;
@@ -279,6 +290,8 @@ const SSS_QUALITY_PRESET_SETTINGS = {
279
290
  this._bloomThreshold = 0.8;
280
291
  this._bloomThresholdKnee = 0;
281
292
  this._bloomIntensity = 1;
293
+ this._bloomFilterRadius = 1;
294
+ this._bloomKarisAverage = true;
282
295
  this._colorAdjustEnabled = false;
283
296
  this._postEffectColorAdjust = new DRef();
284
297
  this._colorAdjustSaturation = 1;
@@ -346,8 +359,14 @@ const SSS_QUALITY_PRESET_SETTINGS = {
346
359
  this._skinSSSStrength = 1;
347
360
  this._skinSSSOpacity = 0.18;
348
361
  this._skinSSSSampleStep = 2;
362
+ this._skinSSSScatterRadius = 0.02;
363
+ this._skinSSSSmoothness = 0;
349
364
  this._skinSSSDepthScale = 80;
350
365
  this._skinSSSColorBoost = 1;
366
+ this._skinSSSGlow = 0;
367
+ this._skinSSSProfilePreset = 'skin_default';
368
+ this._skinSSSProfile = null;
369
+ this._skinSSSScatterTint = new Vector4(1, 1, 1, 1);
351
370
  this._SSAO = false;
352
371
  this._postEffectSSAO = new DRef();
353
372
  this._SSAOScale = 10;
@@ -542,6 +561,48 @@ const SSS_QUALITY_PRESET_SETTINGS = {
542
561
  this._postEffectBloom.get().intensity = val;
543
562
  }
544
563
  }
564
+ /**
565
+ * Radius of the tent filter used to upsample the bloom pyramid, in source texels.
566
+ *
567
+ * @remarks
568
+ * See {@link Bloom.filterRadius}. 1 is the natural width; raising it softens the halo but past
569
+ * ~2 the tent's taps stop overlapping and a grid pattern reappears.
570
+ */ get bloomFilterRadius() {
571
+ return this._bloomFilterRadius;
572
+ }
573
+ set bloomFilterRadius(val) {
574
+ this._bloomFilterRadius = Math.max(0, val);
575
+ if (this._postEffectBloom.get()) {
576
+ this._postEffectBloom.get().filterRadius = this._bloomFilterRadius;
577
+ }
578
+ }
579
+ /**
580
+ * Whether the first bloom downsample uses the Karis average to suppress fireflies.
581
+ *
582
+ * @remarks
583
+ * See {@link Bloom.karisAverage}. On by default; costs a little halo reach in exchange for
584
+ * stability on high-frequency speculars.
585
+ */ get bloomKarisAverage() {
586
+ return this._bloomKarisAverage;
587
+ }
588
+ set bloomKarisAverage(val) {
589
+ this._bloomKarisAverage = !!val;
590
+ if (this._postEffectBloom.get()) {
591
+ this._postEffectBloom.get().karisAverage = this._bloomKarisAverage;
592
+ }
593
+ }
594
+ /**
595
+ * Whether tone mapping dithers its result before the 8-bit write.
596
+ *
597
+ * @remarks
598
+ * See {@link Tonemap.dither}. On by default; removes the banding shallow gradients such as bloom
599
+ * halos otherwise show once quantized. Turn it off for reproducible pixel captures.
600
+ */ get toneMapDither() {
601
+ return this._postEffectTonemap.get().dither;
602
+ }
603
+ set toneMapDither(val) {
604
+ this._postEffectTonemap.get().dither = !!val;
605
+ }
545
606
  /** Whether color adjustment is enabled. */ get colorAdjust() {
546
607
  return this._postEffectColorAdjust.get().enabled;
547
608
  }
@@ -825,7 +886,7 @@ const SSS_QUALITY_PRESET_SETTINGS = {
825
886
  this.invalidateSSGIHistory();
826
887
  }
827
888
  }
828
- /** Whether WebGPU SSGI temporal accumulation is enabled. */ get ssgiTemporal() {
889
+ /** Whether SSGI temporal accumulation is enabled. Has no effect on WebGL1, which has no motion vectors. */ get ssgiTemporal() {
829
890
  return this._ssgiTemporal;
830
891
  }
831
892
  set ssgiTemporal(val) {
@@ -1055,7 +1116,7 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1055
1116
  this._postEffectSkinSSS.get().opacity = this._skinSSSOpacity;
1056
1117
  }
1057
1118
  }
1058
- /** Pixel spacing between blur taps. The reference shader uses 2. */ get skinSSSSampleStep() {
1119
+ /** Maximum pixel spacing between blur taps. Caps the projected scatter radius for close-ups. */ get skinSSSSampleStep() {
1059
1120
  return this._skinSSSSampleStep;
1060
1121
  }
1061
1122
  set skinSSSSampleStep(val) {
@@ -1064,6 +1125,24 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1064
1125
  this._postEffectSkinSSS.get().sampleStep = this._skinSSSSampleStep;
1065
1126
  }
1066
1127
  }
1128
+ /** World-space scatter radius. The blur width shrinks with distance to keep this constant. */ get skinSSSScatterRadius() {
1129
+ return this._skinSSSScatterRadius;
1130
+ }
1131
+ set skinSSSScatterRadius(val) {
1132
+ this._skinSSSScatterRadius = Math.max(0, val ?? 0);
1133
+ if (this._postEffectSkinSSS.get()) {
1134
+ this._postEffectSkinSSS.get().scatterRadius = this._skinSSSScatterRadius;
1135
+ }
1136
+ }
1137
+ /** Skin smoothing ("beauty filter") amount for the dedicated Skin SSS post effect. */ get skinSSSSmoothness() {
1138
+ return this._skinSSSSmoothness;
1139
+ }
1140
+ set skinSSSSmoothness(val) {
1141
+ this._skinSSSSmoothness = Math.max(0, Math.min(1, val ?? 0));
1142
+ if (this._postEffectSkinSSS.get()) {
1143
+ this._postEffectSkinSSS.get().smoothness = this._skinSSSSmoothness;
1144
+ }
1145
+ }
1067
1146
  /** Depth rejection scale. The reference shader uses 80. */ get skinSSSDepthScale() {
1068
1147
  return this._skinSSSDepthScale;
1069
1148
  }
@@ -1082,6 +1161,76 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1082
1161
  this._postEffectSkinSSS.get().colorBoost = this._skinSSSColorBoost;
1083
1162
  }
1084
1163
  }
1164
+ /**
1165
+ * Additive, deliberately non-conserving bleed for the Skin SSS post effect.
1166
+ *
1167
+ * @remarks
1168
+ * 0 (the default) keeps the effect energy conserving: light added to the dark
1169
+ * side of the terminator is light removed from the lit side. Raising it adds
1170
+ * the diffused term again without subtracting anything, for skin that reads as
1171
+ * lit from within; around 1 approximates the look the effect had before it
1172
+ * conserved energy.
1173
+ */ get skinSSSGlow() {
1174
+ return this._skinSSSGlow;
1175
+ }
1176
+ set skinSSSGlow(val) {
1177
+ this._skinSSSGlow = Math.max(0, val ?? 0);
1178
+ if (this._postEffectSkinSSS.get()) {
1179
+ this._postEffectSkinSSS.get().glow = this._skinSSSGlow;
1180
+ }
1181
+ }
1182
+ /**
1183
+ * Subsurface profile preset driving the Skin SSS per-channel scatter radii.
1184
+ *
1185
+ * @remarks
1186
+ * The ratio between the red, green and blue radii is what gives a scattering
1187
+ * surface its character - red travels furthest in skin, which is the
1188
+ * red-to-yellow gradient at the terminator. Switching presets changes that
1189
+ * ratio, so `wax` and `jade` are the same code path rather than special cases;
1190
+ * {@link Camera.skinSSSScatterRadius} still sets how far the light reaches.
1191
+ *
1192
+ * Applies to the whole pass. Per-material profiles need the profile-slot path
1193
+ * used by {@link SSS} instead.
1194
+ */ get skinSSSProfilePreset() {
1195
+ return this._skinSSSProfilePreset;
1196
+ }
1197
+ set skinSSSProfilePreset(val) {
1198
+ this._skinSSSProfilePreset = val ?? 'skin_default';
1199
+ if (this._skinSSSProfile) {
1200
+ this._skinSSSProfile.preset = this._skinSSSProfilePreset;
1201
+ }
1202
+ if (this._postEffectSkinSSS.get()) {
1203
+ this._postEffectSkinSSS.get().profile = this.getSkinSSSProfile();
1204
+ }
1205
+ }
1206
+ /**
1207
+ * The profile object backing {@link Camera.skinSSSProfilePreset}, created on
1208
+ * first use so that cameras which never enable Skin SSS do not consume one of
1209
+ * the 255 global profile slots.
1210
+ * @internal
1211
+ */ getSkinSSSProfile() {
1212
+ if (!this._skinSSSProfile) {
1213
+ this._skinSSSProfile = new SubsurfaceProfile();
1214
+ this._skinSSSProfile.preset = this._skinSSSProfilePreset;
1215
+ }
1216
+ return this._skinSSSProfile;
1217
+ }
1218
+ /**
1219
+ * Tint applied to the light the Skin SSS post effect redistributes.
1220
+ *
1221
+ * @remarks
1222
+ * White (the default) leaves the effect energy conserving. Because it
1223
+ * multiplies only the difference between the diffused and original diffuse, a
1224
+ * warm tint colors the terminator without washing the whole surface.
1225
+ */ get skinSSSScatterTint() {
1226
+ return this._skinSSSScatterTint;
1227
+ }
1228
+ set skinSSSScatterTint(val) {
1229
+ this._skinSSSScatterTint.set(val);
1230
+ if (this._postEffectSkinSSS.get()) {
1231
+ this._postEffectSkinSSS.get().scatterTint = this._skinSSSScatterTint;
1232
+ }
1233
+ }
1085
1234
  /** @internal */ get ssrParams() {
1086
1235
  return this._ssrParams;
1087
1236
  }
@@ -1546,8 +1695,13 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1546
1695
  skinSSS.strength = this._skinSSSStrength;
1547
1696
  skinSSS.opacity = this._skinSSSOpacity;
1548
1697
  skinSSS.sampleStep = this._skinSSSSampleStep;
1698
+ skinSSS.scatterRadius = this._skinSSSScatterRadius;
1699
+ skinSSS.smoothness = this._skinSSSSmoothness;
1549
1700
  skinSSS.depthScale = this._skinSSSDepthScale;
1550
1701
  skinSSS.colorBoost = this._skinSSSColorBoost;
1702
+ skinSSS.glow = this._skinSSSGlow;
1703
+ skinSSS.profile = this.getSkinSSSProfile();
1704
+ skinSSS.scatterTint = this._skinSSSScatterTint;
1551
1705
  this._postEffectSkinSSS.set(skinSSS);
1552
1706
  this._compositor.appendPostEffect(skinSSS);
1553
1707
  }
@@ -1562,6 +1716,22 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1562
1716
  this._postEffectSSAO.set(ssao);
1563
1717
  this._compositor.appendPostEffect(ssao);
1564
1718
  }
1719
+ // Every effect from here down shares the `end` layer, and appendPostEffect keeps insertion
1720
+ // order within a layer, so the chain is TAA -> MotionBlur -> Bloom -> Tonemap -> ColorAdjust
1721
+ // -> FXAA.
1722
+ //
1723
+ // The resolve leads because everything after it spreads energy -- Bloom spatially, MotionBlur
1724
+ // along the velocity field -- and doing that to an unconverged frame turns the per-frame
1725
+ // brightness swing of a sub-pixel specular highlight into a pulsing halo that no later pass can
1726
+ // take back: the halo is low frequency, so the 3x3 neighbourhood TAA's history clip is built
1727
+ // from pulses in step with it and admits the pulsing value as valid.
1728
+ //
1729
+ // The display chain sits in `end` rather than `transparent` specifically so that TAA does not
1730
+ // have to move. Effects in the transparent layer read the linear depth from before the
1731
+ // TransmissionDepth pass rewrites it with the full scene depth, and TAA dilates its velocities
1732
+ // by that depth; run there, it reads opaque-only depth and picks up its neighbours' motion on
1733
+ // any geometry the prepass never saw (alpha-blended hair, most visibly). None of the display
1734
+ // effects read depth at all, so moving them instead is free.
1565
1735
  if (!this._postEffectTAA.get()) {
1566
1736
  const taa = new TAA();
1567
1737
  taa.enabled = false;
@@ -1575,6 +1745,23 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1575
1745
  this._postEffectMotionBlur.set(motionBlur);
1576
1746
  this._compositor.appendPostEffect(motionBlur);
1577
1747
  }
1748
+ // Bloom is created before Tonemap because it must see scene-linear radiance:
1749
+ // its threshold, its blur and its additive compose are all only meaningful in linear light.
1750
+ // Running it after the ACES curve compressed everything into [0, 1] flattened the emitter range
1751
+ // (a 100x brighter emitter produced the same halo) and made the compose hard-clip.
1752
+ if (!this._postEffectBloom.get()) {
1753
+ const bloom = new Bloom();
1754
+ bloom.enabled = false;
1755
+ bloom.maxDownsampleLevel = this._bloomMaxDownsampleLevels;
1756
+ bloom.downsampleLimit = this._bloomDownsampleLimit;
1757
+ bloom.threshold = this._bloomThreshold;
1758
+ bloom.thresholdKnee = this._bloomThresholdKnee;
1759
+ bloom.intensity = this._bloomIntensity;
1760
+ bloom.filterRadius = this._bloomFilterRadius;
1761
+ bloom.karisAverage = this._bloomKarisAverage;
1762
+ this._postEffectBloom.set(bloom);
1763
+ this._compositor.appendPostEffect(bloom);
1764
+ }
1578
1765
  if (!this._postEffectTonemap.get()) {
1579
1766
  const tonemap = new Tonemap();
1580
1767
  tonemap.enabled = true;
@@ -1598,17 +1785,6 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1598
1785
  this._postEffectFXAA.set(fxaa);
1599
1786
  this._compositor.appendPostEffect(fxaa);
1600
1787
  }
1601
- if (!this._postEffectBloom.get()) {
1602
- const bloom = new Bloom();
1603
- bloom.enabled = false;
1604
- bloom.maxDownsampleLevel = this._bloomMaxDownsampleLevels;
1605
- bloom.downsampleLimit = this._bloomDownsampleLimit;
1606
- bloom.threshold = this._bloomThreshold;
1607
- bloom.thresholdKnee = this._bloomThresholdKnee;
1608
- bloom.intensity = this._bloomIntensity;
1609
- this._postEffectBloom.set(bloom);
1610
- this._compositor.appendPostEffect(bloom);
1611
- }
1612
1788
  }
1613
1789
  /** @internal */ syncPostProcessingMode(scene) {
1614
1790
  const tonemap = this._postEffectTonemap.get();
@@ -1617,15 +1793,11 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1617
1793
  // keeps its plain exposure multiplier.
1618
1794
  const usePhysicalExposure = scene.lightingMode === 'physical';
1619
1795
  tonemap.exposure = usePhysicalExposure ? 1 : this._tonemapExposure;
1620
- const bloom = this._postEffectBloom.get();
1796
+ // The chain order no longer depends on the lighting mode: Bloom always precedes Tonemap, so it
1797
+ // always works on scene-linear radiance. Only the SSGI histories still care about the switch.
1621
1798
  if (this._postProcessingLightingMode !== scene.lightingMode) {
1622
1799
  // SSGI histories contain scene-linear radiance. Never reuse them across lighting unit models.
1623
1800
  this.invalidateSSGIHistory();
1624
- if (scene.lightingMode === 'physical') {
1625
- this._compositor.movePostEffectBefore(bloom, tonemap);
1626
- } else {
1627
- this._compositor.movePostEffectAfter(bloom, this._postEffectFXAA.get());
1628
- }
1629
1801
  this._postProcessingLightingMode = scene.lightingMode;
1630
1802
  }
1631
1803
  }