@zephyr3d/scene 0.9.24 → 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 (264) 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 +4040 -1290
  42. package/dist/index.js +16 -6
  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 +766 -456
  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/cluster_light.js +4 -4
  122. package/dist/render/cluster_light.js.map +1 -1
  123. package/dist/render/cull_visitor.js +19 -0
  124. package/dist/render/cull_visitor.js.map +1 -1
  125. package/dist/render/depthpass.js +36 -2
  126. package/dist/render/depthpass.js.map +1 -1
  127. package/dist/render/drawable_mixin.js +26 -1
  128. package/dist/render/drawable_mixin.js.map +1 -1
  129. package/dist/render/dualdepthpeeling_oit.js +52 -51
  130. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  131. package/dist/render/envlight.js +1 -1
  132. package/dist/render/envlight.js.map +1 -1
  133. package/dist/render/fbm_wavegenerator.js +3 -0
  134. package/dist/render/fbm_wavegenerator.js.map +1 -1
  135. package/dist/render/gerstner_wavegenerator.js +3 -0
  136. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  137. package/dist/render/globalbindgroup_allocator.js +3 -0
  138. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  139. package/dist/render/oit.js +29 -0
  140. package/dist/render/oit.js.map +1 -0
  141. package/dist/render/render_queue.js +3 -0
  142. package/dist/render/render_queue.js.map +1 -1
  143. package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
  144. package/dist/render/rendergraph/executor.js +2 -0
  145. package/dist/render/rendergraph/executor.js.map +1 -1
  146. package/dist/render/rendergraph/forward_plus_builder.js +61 -1
  147. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  148. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  149. package/dist/render/rendergraph/texture_affinity_cache.js +1 -1
  150. package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -1
  151. package/dist/render/rendergraph/types.js.map +1 -1
  152. package/dist/render/shadow_mask_pass.js +73 -8
  153. package/dist/render/shadow_mask_pass.js.map +1 -1
  154. package/dist/render/sky.js +166 -126
  155. package/dist/render/sky.js.map +1 -1
  156. package/dist/render/weightedblended_oit.js +36 -35
  157. package/dist/render/weightedblended_oit.js.map +1 -1
  158. package/dist/scene/basesprite.js +8 -0
  159. package/dist/scene/basesprite.js.map +1 -1
  160. package/dist/scene/batchgroup.js +3 -0
  161. package/dist/scene/batchgroup.js.map +1 -1
  162. package/dist/scene/environment.js +4 -1
  163. package/dist/scene/environment.js.map +1 -1
  164. package/dist/scene/hair.js +963 -0
  165. package/dist/scene/hair.js.map +1 -0
  166. package/dist/scene/light.js +5 -1
  167. package/dist/scene/light.js.map +1 -1
  168. package/dist/scene/mesh.js +481 -47
  169. package/dist/scene/mesh.js.map +1 -1
  170. package/dist/scene/meshdrawable.js +6 -0
  171. package/dist/scene/meshdrawable.js.map +1 -1
  172. package/dist/scene/msdftext.js +3 -0
  173. package/dist/scene/msdftext.js.map +1 -1
  174. package/dist/scene/msdftextsprite.js +3 -0
  175. package/dist/scene/msdftextsprite.js.map +1 -1
  176. package/dist/scene/particlesys.js +6 -0
  177. package/dist/scene/particlesys.js.map +1 -1
  178. package/dist/scene/scene.js +3 -0
  179. package/dist/scene/scene.js.map +1 -1
  180. package/dist/scene/scene_node.js +6 -1
  181. package/dist/scene/scene_node.js.map +1 -1
  182. package/dist/scene/terrain-cm/grass.js +273 -249
  183. package/dist/scene/terrain-cm/grass.js.map +1 -1
  184. package/dist/scene/terrain-cm/grassmaterial.js +3 -0
  185. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  186. package/dist/scene/terrain-cm/terrain-cm.js +6 -0
  187. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  188. package/dist/scene/terrain-cm/texture_cache.js +309 -0
  189. package/dist/scene/terrain-cm/texture_cache.js.map +1 -0
  190. package/dist/scene/water.js +6 -0
  191. package/dist/scene/water.js.map +1 -1
  192. package/dist/shaders/atmosphere.js.map +1 -1
  193. package/dist/shaders/diffusion.js +54 -0
  194. package/dist/shaders/diffusion.js.map +1 -0
  195. package/dist/shaders/shadow.js +33 -21
  196. package/dist/shaders/shadow.js.map +1 -1
  197. package/dist/shaders/temporal.js +71 -33
  198. package/dist/shaders/temporal.js.map +1 -1
  199. package/dist/shadow/dom.js +546 -0
  200. package/dist/shadow/dom.js.map +1 -0
  201. package/dist/shadow/pcf_opt.js +3 -5
  202. package/dist/shadow/pcf_opt.js.map +1 -1
  203. package/dist/shadow/pcf_pd.js +1 -1
  204. package/dist/shadow/pcf_pd.js.map +1 -1
  205. package/dist/shadow/receiver_bias.js +41 -0
  206. package/dist/shadow/receiver_bias.js.map +1 -0
  207. package/dist/shadow/shader.js +20 -60
  208. package/dist/shadow/shader.js.map +1 -1
  209. package/dist/shadow/shadow_impl.js +89 -0
  210. package/dist/shadow/shadow_impl.js.map +1 -1
  211. package/dist/shadow/shadow_region.js +50 -50
  212. package/dist/shadow/shadow_region.js.map +1 -1
  213. package/dist/shadow/shadowmapper.js +150 -37
  214. package/dist/shadow/shadowmapper.js.map +1 -1
  215. package/dist/shapes/shape.js +11 -2
  216. package/dist/shapes/shape.js.map +1 -1
  217. package/dist/shapes/sphere.js +50 -9
  218. package/dist/shapes/sphere.js.map +1 -1
  219. package/dist/utility/blueprint/common/constants.js +112 -2
  220. package/dist/utility/blueprint/common/constants.js.map +1 -1
  221. package/dist/utility/blueprint/material/func.js +142 -49
  222. package/dist/utility/blueprint/material/func.js.map +1 -1
  223. package/dist/utility/blueprint/material/ir.js +490 -25
  224. package/dist/utility/blueprint/material/ir.js.map +1 -1
  225. package/dist/utility/blueprint/material/texture.js +898 -4
  226. package/dist/utility/blueprint/material/texture.js.map +1 -1
  227. package/dist/utility/serialization/manager.js +431 -100
  228. package/dist/utility/serialization/manager.js.map +1 -1
  229. package/dist/utility/serialization/scene/animation.js +370 -3
  230. package/dist/utility/serialization/scene/animation.js.map +1 -1
  231. package/dist/utility/serialization/scene/batch.js +4 -0
  232. package/dist/utility/serialization/scene/batch.js.map +1 -1
  233. package/dist/utility/serialization/scene/camera.js +133 -1
  234. package/dist/utility/serialization/scene/camera.js.map +1 -1
  235. package/dist/utility/serialization/scene/common.js +26 -5
  236. package/dist/utility/serialization/scene/common.js.map +1 -1
  237. package/dist/utility/serialization/scene/hair.js +1141 -0
  238. package/dist/utility/serialization/scene/hair.js.map +1 -0
  239. package/dist/utility/serialization/scene/light.js +84 -4
  240. package/dist/utility/serialization/scene/light.js.map +1 -1
  241. package/dist/utility/serialization/scene/material.js +1867 -166
  242. package/dist/utility/serialization/scene/material.js.map +1 -1
  243. package/dist/utility/serialization/scene/mesh.js +231 -14
  244. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  245. package/dist/utility/serialization/scene/node.js +141 -7
  246. package/dist/utility/serialization/scene/node.js.map +1 -1
  247. package/dist/utility/serialization/scene/particle.js +4 -0
  248. package/dist/utility/serialization/scene/particle.js.map +1 -1
  249. package/dist/utility/serialization/scene/primitive.js +18 -0
  250. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  251. package/dist/utility/serialization/scene/sprite.js +4 -0
  252. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  253. package/dist/utility/serialization/scene/terrain.js +92 -69
  254. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  255. package/dist/utility/serialization/scene/text.js +4 -0
  256. package/dist/utility/serialization/scene/text.js.map +1 -1
  257. package/dist/utility/serialization/scene/water.js +4 -0
  258. package/dist/utility/serialization/scene/water.js.map +1 -1
  259. package/dist/utility/serialization/types.js.map +1 -1
  260. package/dist/utility/shprojector.js +96 -95
  261. package/dist/utility/shprojector.js.map +1 -1
  262. package/dist/values.js +105 -58
  263. package/dist/values.js.map +1 -1
  264. package/package.json +3 -3
@@ -1,5 +1,5 @@
1
1
  import { Vector3, Vector4, Vector2, REVERSE_Z, DEPTH_FARTHEST, DEPTH_REDUCE_CLOSER, DEPTH_REDUCE_FARTHER } from '@zephyr3d/base';
2
- import { RENDER_PASS_TYPE_DEPTH, RENDER_PASS_TYPE_SHADOWMAP, RENDER_PASS_TYPE_OBJECT_COLOR, RENDER_PASS_TYPE_LIGHT, MaterialVaryingFlags, MORPH_WEIGHTS_VECTOR_COUNT, MORPH_TARGET_POSITION, MORPH_TARGET_NORMAL, MORPH_TARGET_TANGENT, MORPH_ATTRIBUTE_VECTOR_COUNT } from '../../values.js';
2
+ import { RENDER_PASS_TYPE_DEPTH, RENDER_PASS_TYPE_SHADOWMAP, RENDER_PASS_TYPE_OBJECT_COLOR, RENDER_PASS_TYPE_LIGHT, MaterialVaryingFlags, MAX_SKIN_EXTRA_INFLUENCE_PAIRS, MORPH_WEIGHTS_VECTOR_COUNT, MORPH_TARGET_POSITION, MORPH_TARGET_NORMAL, MORPH_TARGET_TANGENT, MORPH_ATTRIBUTE_VECTOR_COUNT, LIGHT_TYPE_DIRECTIONAL } from '../../values.js';
3
3
  import { ProgramBuilder } from '@zephyr3d/device';
4
4
  import { decodeNormalizedFloatFromRGBA, linearToGamma } from '../../shaders/misc.js';
5
5
  import { getSamplerOptions, fetchSampler } from '../../utility/misc.js';
@@ -7,6 +7,7 @@ import { PHYSICAL_BAKE_EXPOSURE } from '../../utility/physical.js';
7
7
  import { getDevice } from '../../app/api.js';
8
8
  import { getDefaultAtmosphereParams, getAtmosphereParamsStruct } from '../../shaders/atmosphere.js';
9
9
  import { getDefaultHeightFogParams, getHeightFogParamsStruct, calculateFog } from '../../shaders/fog.js';
10
+ import { getShadowReceiverNoL, getShadowReceiverBiasFactor } from '../../shadow/receiver_bias.js';
10
11
 
11
12
  const UNIFORM_NAME_LIGHT_BUFFER = 'Z_UniformLightBuffer';
12
13
  const UNIFORM_NAME_LIGHT_INDEX_TEXTURE = 'Z_UniformLightIndexTex';
@@ -32,11 +33,13 @@ const UNIFORM_NAME_INSTANCE_DATA_OFFSET = 'Z_UniformInstanceDataOffset';
32
33
  const UNIFORM_NAME_BONE_MATRICES = 'Z_UniformBoneMatrices';
33
34
  const UNIFORM_NAME_BONE_TEXTURE_SIZE = 'Z_UniformBoneTexSize';
34
35
  const UNIFORM_NAME_BONE_INV_BIND_MATRIX = 'Z_UniformBoneInvBindMatrix';
36
+ const UNIFORM_NAME_SKIN_INFLUENCE_DATA = 'Z_UniformSkinInfluenceData';
37
+ const UNIFORM_NAME_SKIN_INFLUENCE_INFO = 'Z_UniformSkinInfluenceInfo';
35
38
  const UNIFORM_NAME_MORPH_DATA = 'Z_UniformMorphData';
36
39
  const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
37
- /**
38
- * Helper shader functions for the builtin material system
39
- * @public
40
+ /**
41
+ * Helper shader functions for the builtin material system
42
+ * @public
40
43
  */ class ShaderHelper {
41
44
  static BILLBOARD_SPHERICAL = 1;
42
45
  static BILLBOARD_SYLINDRAL = 2;
@@ -46,6 +49,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
46
49
  /** @internal */ static SKIN_MATRIX_NAME = 'Z_SkinMatrix';
47
50
  static SKIN_PREV_MATRIX_NAME = 'Z_PrevSkinMatrix';
48
51
  static SKIN_BONE_OFFSET = 'Z_boneOffset';
52
+ static RESOLVED_VERTEX_NORMAL_NAME = 'Z_ResolvedVertexNormal';
49
53
  /** @internal */ static _drawableBindGroupLayouts = {};
50
54
  /** @internal */ static _lightUniformShadow = {
51
55
  sunDir: new Vector3(),
@@ -101,6 +105,12 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
101
105
  static getBoneInvBindMatrixUniformName() {
102
106
  return UNIFORM_NAME_BONE_INV_BIND_MATRIX;
103
107
  }
108
+ static getSkinInfluenceDataUniformName() {
109
+ return UNIFORM_NAME_SKIN_INFLUENCE_DATA;
110
+ }
111
+ static getSkinInfluenceInfoUniformName() {
112
+ return UNIFORM_NAME_SKIN_INFLUENCE_INFO;
113
+ }
104
114
  static getMorphDataUniformName() {
105
115
  return UNIFORM_NAME_MORPH_DATA;
106
116
  }
@@ -129,25 +139,25 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
129
139
  }
130
140
  return bindGroupLayout;
131
141
  }
132
- /**
133
- * Prepares the fragment shader which is going to be used in our material system
134
- *
135
- * @remarks
136
- * This function will setup all nessesary uniforms acoording to the drawing context
137
- *
138
- * @param pb - The program builder
139
- * @param ctx - The drawing context
142
+ /**
143
+ * Prepares the fragment shader which is going to be used in our material system
144
+ *
145
+ * @remarks
146
+ * This function will setup all nessesary uniforms acoording to the drawing context
147
+ *
148
+ * @param pb - The program builder
149
+ * @param ctx - The drawing context
140
150
  */ static prepareFragmentShader(pb, ctx) {
141
151
  this.setupGlobalUniforms(pb, ctx);
142
152
  }
143
- /**
144
- * Prepares the vertex shader which is going to be used in our material system
145
- *
146
- * @remarks
147
- * This function will setup all nessesary uniforms according to the drawing context
148
- *
149
- * @param pb - The program builder
150
- * @param ctx - The drawing context
153
+ /**
154
+ * Prepares the vertex shader which is going to be used in our material system
155
+ *
156
+ * @remarks
157
+ * This function will setup all nessesary uniforms according to the drawing context
158
+ *
159
+ * @param pb - The program builder
160
+ * @param ctx - The drawing context
151
161
  */ static prepareVertexShader(pb, ctx) {
152
162
  this.setupGlobalUniforms(pb, ctx);
153
163
  this.prepareVertexShaderCommon(pb, ctx);
@@ -183,10 +193,22 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
183
193
  pb.vec4('directionCutoff'),
184
194
  pb.mat4('viewMatrix'),
185
195
  pb.vec4('depthBias'),
196
+ // Same slot the lighting pass exposes through getShadowImplParams. A
197
+ // caster normally has no use for it, but one that encodes something
198
+ // beyond depth - deep opacity map layers - needs the same parameters the
199
+ // receiver will decode with.
200
+ pb.vec4('implParams'),
186
201
  pb.int('lightType')
187
202
  ]);
188
203
  scope.camera = cameraStruct().uniform(0);
189
204
  scope.light = lightStruct().uniform(0);
205
+ // Lets the shadow implementation add its own caster inputs. Only deep
206
+ // opacity maps use this, to read the frontmost depth its first pass wrote.
207
+ const shadowLight = ctx.renderPass.light;
208
+ const casterParams = shadowLight ? ctx.shadowMapInfo?.get(shadowLight) : null;
209
+ if (casterParams) {
210
+ casterParams.impl?.declareCasterUniforms(scope, casterParams);
211
+ }
190
212
  } else if (ctx.renderPass.type === RENDER_PASS_TYPE_DEPTH || ctx.renderPass.type === RENDER_PASS_TYPE_OBJECT_COLOR) {
191
213
  scope.camera = cameraStruct().uniform(0);
192
214
  } else if (ctx.renderPass.type === RENDER_PASS_TYPE_LIGHT) {
@@ -293,35 +315,37 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
293
315
  }
294
316
  }
295
317
  }
296
- /**
297
- * This function checks if the shader needs to process skeletal animation.
298
- *
299
- * @param scope - Current shader scope
300
- *
301
- * @returns true if the shader needs to process skeletal animation, otherwise false.
318
+ /**
319
+ * This function checks if the shader needs to process skeletal animation.
320
+ *
321
+ * @param scope - Current shader scope
322
+ *
323
+ * @returns true if the shader needs to process skeletal animation, otherwise false.
302
324
  */ static hasSkinning(scope) {
303
325
  return !!scope[UNIFORM_NAME_BONE_MATRICES];
304
326
  }
305
- /**
306
- * This function checks if the shader needs to process morph target animation.
307
- *
308
- * @param scope - Current shader scope
309
- *
310
- * @returns true if the shader needs to process morph target animation, otherwise false.
327
+ /**
328
+ * This function checks if the shader needs to process morph target animation.
329
+ *
330
+ * @param scope - Current shader scope
331
+ *
332
+ * @returns true if the shader needs to process morph target animation, otherwise false.
311
333
  */ static hasMorphing(scope) {
312
334
  return !!scope[UNIFORM_NAME_MORPH_DATA];
313
335
  }
314
- /**
315
- * Calculate skinning matrix for current vertex
316
- *
317
- * @param scope - Current shader scope
318
- *
319
- * @returns Skinning matrix for current vertex, or null if there is not skeletal animation
336
+ /**
337
+ * Calculate skinning matrix for current vertex
338
+ *
339
+ * @param scope - Current shader scope
340
+ *
341
+ * @returns Skinning matrix for current vertex, or null if there is not skeletal animation
320
342
  */ static calculateSkinMatrix(scope) {
321
343
  if (!this.hasSkinning(scope)) {
322
344
  return null;
323
345
  }
324
346
  const pb = scope.$builder;
347
+ const isWebGL = pb.getDevice().type === 'webgl';
348
+ const supportsTextureLoad = !isWebGL;
325
349
  const funcNameGetBoneMatrixFromTexture = 'Z_getBoneMatrixFromTexture';
326
350
  pb.func(funcNameGetBoneMatrixFromTexture, [
327
351
  pb.int('boneIndex')
@@ -354,6 +378,33 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
354
378
  this.$l.m2 = scope.$g[funcNameGetBoneMatrixFromTexture](pb.int(blendIndices[2]));
355
379
  this.$l.m3 = scope.$g[funcNameGetBoneMatrixFromTexture](pb.int(blendIndices[3]));
356
380
  this.$l.m = pb.add(pb.mul(this.m0, blendWeights.x), pb.mul(this.m1, blendWeights.y), pb.mul(this.m2, blendWeights.z), pb.mul(this.m3, blendWeights.w));
381
+ this.$l.skinInfo = this[UNIFORM_NAME_SKIN_INFLUENCE_INFO];
382
+ this.$if(pb.greaterThan(this.skinInfo.z, 4), function() {
383
+ this.$l.vertexIndex = isWebGL ? pb.int(scope.$inputs.zFakeVertexID) : pb.int(scope.$builtins.vertexIndex);
384
+ this.$l.texWidth = pb.int(this.skinInfo.x);
385
+ this.$l.pairCount = pb.int(this.skinInfo.w);
386
+ this.$for(pb.int('pairIndex'), 0, MAX_SKIN_EXTRA_INFLUENCE_PAIRS, function() {
387
+ this.$if(pb.greaterThanEqual(this.pairIndex, this.pairCount), function() {
388
+ this.$break();
389
+ });
390
+ this.$l.pixelIndex = pb.add(pb.mul(this.vertexIndex, this.pairCount), this.pairIndex);
391
+ this.$l.xIndex = pb.mod(this.pixelIndex, this.texWidth);
392
+ this.$l.yIndex = pb.div(this.pixelIndex, this.texWidth);
393
+ if (supportsTextureLoad) {
394
+ this.$l.extra = pb.textureLoad(this[UNIFORM_NAME_SKIN_INFLUENCE_DATA], pb.ivec2(this.xIndex, this.yIndex), 0);
395
+ } else {
396
+ this.$l.u = pb.div(pb.add(pb.float(this.xIndex), 0.5), this.skinInfo.x);
397
+ this.$l.v = pb.div(pb.add(pb.float(this.yIndex), 0.5), this.skinInfo.y);
398
+ this.$l.extra = pb.textureSampleLevel(this[UNIFORM_NAME_SKIN_INFLUENCE_DATA], pb.vec2(this.u, this.v), 0);
399
+ }
400
+ this.$if(pb.greaterThan(this.extra.y, 0), function() {
401
+ this.m = pb.add(this.m, pb.mul(scope.$g[funcNameGetBoneMatrixFromTexture](pb.int(this.extra.x)), this.extra.y));
402
+ });
403
+ this.$if(pb.greaterThan(this.extra.w, 0), function() {
404
+ this.m = pb.add(this.m, pb.mul(scope.$g[funcNameGetBoneMatrixFromTexture](pb.int(this.extra.z)), this.extra.w));
405
+ });
406
+ });
407
+ });
357
408
  this.$return(pb.mul(invBindMatrix, this.m));
358
409
  });
359
410
  return scope.$g[funcNameGetSkinningMatrix]();
@@ -389,7 +440,8 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
389
440
  });
390
441
  this.$l.weight = morphInfo.at(pb.add(1, this.i)).at(this.j);
391
442
  this.$if(pb.notEqual(this.weight, 0), function() {
392
- this.$l.pixelIndex = pb.float(pb.add(this.offset, pb.mul(this.index, this.numVertices), this.vertexIndex));
443
+ this.$l.targetIndex = pb.int(morphInfo.at(pb.add(1 + MORPH_WEIGHTS_VECTOR_COUNT, this.i)).at(this.j));
444
+ this.$l.pixelIndex = pb.float(pb.add(this.offset, pb.mul(this.targetIndex, this.numVertices), this.vertexIndex));
393
445
  this.$l.xIndex = pb.mod(this.pixelIndex, this.texWidth);
394
446
  this.$l.yIndex = pb.floor(pb.div(this.pixelIndex, this.texWidth));
395
447
  this.$l.u = pb.div(pb.add(this.xIndex, 0.5), this.texWidth);
@@ -405,7 +457,8 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
405
457
  this.$l.j = pb.compAnd(this.t, 3);
406
458
  this.$l.weight = morphInfo.at(pb.add(1, this.i)).at(this.j);
407
459
  this.$if(pb.notEqual(this.weight, 0), function() {
408
- this.$l.pixelIndex = pb.float(pb.add(this.offset, pb.mul(this.t, this.numVertices), this.vertexIndex));
460
+ this.$l.targetIndex = pb.int(morphInfo.at(pb.add(1 + MORPH_WEIGHTS_VECTOR_COUNT, this.i)).at(this.j));
461
+ this.$l.pixelIndex = pb.float(pb.add(this.offset, pb.mul(this.targetIndex, this.numVertices), this.vertexIndex));
409
462
  this.$l.xIndex = pb.mod(this.pixelIndex, this.texWidth);
410
463
  this.$l.yIndex = pb.floor(pb.div(this.pixelIndex, this.texWidth));
411
464
  this.$l.u = pb.div(pb.add(this.xIndex, 0.5), this.texWidth);
@@ -417,17 +470,166 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
417
470
  }
418
471
  this.$return(this.value);
419
472
  });
420
- const pos = 1 + MORPH_WEIGHTS_VECTOR_COUNT + (attrib >> 2);
473
+ const pos = 1 + MORPH_WEIGHTS_VECTOR_COUNT * 2 + (attrib >> 2);
421
474
  const comp = attrib & 3;
422
475
  const offset = scope[this.getMorphInfoUniformName()][pos][comp];
423
476
  return scope[funcName](pb.int(offset));
424
477
  }
478
+ /**
479
+ * Normalize a morphed direction without allowing cancellation between
480
+ * multiple morph targets to turn a valid base direction into NaN/noise.
481
+ *
482
+ * Morph normal/tangent data is additive. When two targets cancel the base
483
+ * direction, the zero-length result is not a meaningful direction, so the
484
+ * authored vertex direction is the least surprising fallback.
485
+ * @internal
486
+ */ static defineNormalizeMorphedDirection(scope) {
487
+ const pb = scope.$builder;
488
+ const funcName = 'Z_normalizeMorphedDirection';
489
+ pb.func(funcName, [
490
+ pb.vec3('base'),
491
+ pb.vec3('delta')
492
+ ], function() {
493
+ this.$l.morphed = pb.add(this.base, this.delta);
494
+ this.$l.lengthSquared = pb.dot(this.morphed, this.morphed);
495
+ this.$if(pb.greaterThan(this.lengthSquared, 1e-6), function() {
496
+ this.$return(pb.mul(this.morphed, pb.inverseSqrt(this.lengthSquared)));
497
+ });
498
+ this.$l.baseLengthSquared = pb.dot(this.base, this.base);
499
+ this.$if(pb.greaterThan(this.baseLengthSquared, 1e-6), function() {
500
+ this.$return(pb.mul(this.base, pb.inverseSqrt(this.baseLengthSquared)));
501
+ });
502
+ this.$return(pb.vec3(0, 0, 1));
503
+ });
504
+ return funcName;
505
+ }
506
+ /**
507
+ * Mixes morph directions according to the amount of positional deformation
508
+ * contributed by each active target. This avoids letting a target with a
509
+ * large authored normal delta dominate a target that actually moves the
510
+ * surface much farther at the current vertex.
511
+ *
512
+ * The additive result remains the fallback for assets without position
513
+ * morph data, and for vertices whose position deltas are all zero.
514
+ * @internal
515
+ */ static calculateMorphDirectionByDisplacement(scope, base, attrib) {
516
+ const pb = scope.$builder;
517
+ const isWebGL1 = pb.getDevice().type === 'webgl';
518
+ if (pb.shaderKind !== 'vertex') {
519
+ throw new Error(`ShaderHelper.calculateMorphDirectionByDisplacement(): must be called at vertex stage`);
520
+ }
521
+ const that = this;
522
+ const funcName = 'Z_calculateMorphDirectionByDisplacement';
523
+ const normalizeName = this.defineNormalizeMorphedDirection(scope);
524
+ pb.func(funcName, [
525
+ pb.vec3('base'),
526
+ pb.int('directionOffset'),
527
+ pb.int('positionOffset')
528
+ ], function() {
529
+ this.$l.vertexIndex = isWebGL1 ? pb.int(scope.$inputs.zFakeVertexID) : pb.int(scope.$builtins.vertexIndex);
530
+ const morphInfo = scope[that.getMorphInfoUniformName()];
531
+ this.$l.metaData = pb.ivec4(morphInfo[0]);
532
+ this.$l.texWidth = pb.float(this.metaData.x);
533
+ this.$l.texHeight = pb.float(this.metaData.y);
534
+ this.$l.numVertices = this.metaData.z;
535
+ this.$l.numTargets = this.metaData.w;
536
+ this.$l.directionDelta = pb.vec3(0);
537
+ this.$l.weightedDirection = pb.vec3(0);
538
+ this.$l.weightSum = pb.float(0);
539
+ const accumulateTarget = (targetScope)=>{
540
+ targetScope.$if(pb.notEqual(targetScope.weight, 0), function() {
541
+ this.$l.targetIndex = pb.int(targetScope['targetIndex']);
542
+ this.$l.positionValue = pb.vec3(0);
543
+ this.$l.directionValue = pb.vec3(0);
544
+ this.$if(pb.greaterThanEqual(targetScope['directionOffset'], 0), function() {
545
+ this.$l.pixelIndex = pb.float(pb.add(targetScope['directionOffset'], pb.mul(this.targetIndex, targetScope['numVertices']), targetScope['vertexIndex']));
546
+ this.$l.xIndex = pb.mod(this.pixelIndex, targetScope['texWidth']);
547
+ this.$l.yIndex = pb.floor(pb.div(this.pixelIndex, targetScope['texWidth']));
548
+ this.$l.u = pb.div(pb.add(this.xIndex, 0.5), targetScope['texWidth']);
549
+ this.$l.v = pb.div(pb.add(this.yIndex, 0.5), targetScope['texHeight']);
550
+ this.directionValue = pb.textureSampleLevel(scope[that.getMorphDataUniformName()], pb.vec2(this.u, this.v), 0).xyz;
551
+ });
552
+ this.$if(pb.greaterThanEqual(targetScope['positionOffset'], 0), function() {
553
+ this.$l.pixelIndex = pb.float(pb.add(targetScope['positionOffset'], pb.mul(this.targetIndex, targetScope['numVertices']), targetScope['vertexIndex']));
554
+ this.$l.xIndex = pb.mod(this.pixelIndex, targetScope['texWidth']);
555
+ this.$l.yIndex = pb.floor(pb.div(this.pixelIndex, targetScope['texWidth']));
556
+ this.$l.u = pb.div(pb.add(this.xIndex, 0.5), targetScope['texWidth']);
557
+ this.$l.v = pb.div(pb.add(this.yIndex, 0.5), targetScope['texHeight']);
558
+ this.positionValue = pb.textureSampleLevel(scope[that.getMorphDataUniformName()], pb.vec2(this.u, this.v), 0).xyz;
559
+ });
560
+ this.directionDelta = pb.add(this.directionDelta, pb.mul(this.directionValue, targetScope.weight));
561
+ this.$l.targetDirection = scope.$g[normalizeName](this.base, pb.mul(this.directionValue, targetScope.weight));
562
+ this.$l.displacementWeight = pb.mul(pb.abs(targetScope.weight), pb.sqrt(pb.dot(this.positionValue, this.positionValue)));
563
+ this.weightedDirection = pb.add(this.weightedDirection, pb.mul(this.targetDirection, this.displacementWeight));
564
+ this.weightSum = pb.add(this.weightSum, this.displacementWeight);
565
+ });
566
+ };
567
+ if (isWebGL1) {
568
+ this.$for(pb.int('i'), 0, MORPH_WEIGHTS_VECTOR_COUNT, function() {
569
+ this.$for(pb.int('j'), 0, 4, function() {
570
+ this.$l.index = pb.add(pb.mul(this.i, 4), this.j);
571
+ this.$if(pb.greaterThanEqual(this.index, this.numTargets), function() {
572
+ this.$break();
573
+ });
574
+ this.$l.weight = morphInfo.at(pb.add(1, this.i)).at(this.j);
575
+ this.$l.targetIndex = pb.int(morphInfo.at(pb.add(1 + MORPH_WEIGHTS_VECTOR_COUNT, this.i)).at(this.j));
576
+ accumulateTarget(this);
577
+ });
578
+ });
579
+ } else {
580
+ this.$for(pb.int('t'), 0, this.numTargets, function() {
581
+ this.$l.i = pb.sar(this.t, 2);
582
+ this.$l.j = pb.compAnd(this.t, 3);
583
+ this.$l.weight = morphInfo.at(pb.add(1, this.i)).at(this.j);
584
+ this.$l.targetIndex = pb.int(morphInfo.at(pb.add(1 + MORPH_WEIGHTS_VECTOR_COUNT, this.i)).at(this.j));
585
+ accumulateTarget(this);
586
+ });
587
+ }
588
+ this.$l.fallback = scope.$g[normalizeName](this.base, this.directionDelta);
589
+ this.$if(pb.lessThan(this.weightSum, 1e-6), function() {
590
+ this.$return(this.fallback);
591
+ });
592
+ this.$l.weightedLengthSquared = pb.dot(this.weightedDirection, this.weightedDirection);
593
+ this.$if(pb.greaterThan(this.weightedLengthSquared, 1e-6), function() {
594
+ this.$return(pb.mul(this.weightedDirection, pb.inverseSqrt(this.weightedLengthSquared)));
595
+ });
596
+ this.$return(this.fallback);
597
+ });
598
+ const directionPos = 1 + MORPH_WEIGHTS_VECTOR_COUNT * 2 + (attrib >> 2);
599
+ const directionComp = attrib & 3;
600
+ const positionPos = 1 + MORPH_WEIGHTS_VECTOR_COUNT * 2 + (MORPH_TARGET_POSITION >> 2);
601
+ const positionComp = MORPH_TARGET_POSITION & 3;
602
+ const morphInfo = scope[this.getMorphInfoUniformName()];
603
+ return scope.$g[funcName](base.xyz, pb.int(morphInfo[directionPos][directionComp]), pb.int(morphInfo[positionPos][positionComp]));
604
+ }
605
+ /** @internal */ static orthogonalizeMorphedTangent(scope, normal, tangent) {
606
+ const pb = scope.$builder;
607
+ const funcName = 'Z_orthogonalizeMorphedTangent';
608
+ pb.func(funcName, [
609
+ pb.vec3('normal'),
610
+ pb.vec3('tangent')
611
+ ], function() {
612
+ this.$l.normalLengthSquared = pb.dot(this.normal, this.normal);
613
+ this.$if(pb.greaterThan(this.normalLengthSquared, 1e-6), function() {
614
+ this.$l.n = pb.mul(this.normal, pb.inverseSqrt(this.normalLengthSquared));
615
+ this.$l.projected = pb.sub(this.tangent, pb.mul(this.n, pb.dot(this.n, this.tangent)));
616
+ this.$l.tangentLengthSquared = pb.dot(this.projected, this.projected);
617
+ this.$if(pb.greaterThan(this.tangentLengthSquared, 1e-6), function() {
618
+ this.$return(pb.mul(this.projected, pb.inverseSqrt(this.tangentLengthSquared)));
619
+ });
620
+ });
621
+ this.$return(this.tangent);
622
+ });
623
+ return scope.$g[funcName](normal.xyz, tangent.xyz);
624
+ }
425
625
  /** @internal */ static prepareSkinAnimation(scope) {
426
626
  if (!this.hasSkinning(scope)) {
427
627
  return;
428
628
  }
429
629
  const that = this;
430
630
  const pb = scope.$builder;
631
+ const isWebGL = pb.getDevice().type === 'webgl';
632
+ const supportsTextureLoad = !isWebGL;
431
633
  const funcNameGetBoneMatrixFromTexture = 'Z_getBoneMatrixFromTexture';
432
634
  pb.func(funcNameGetBoneMatrixFromTexture, [
433
635
  pb.float('boneIndex'),
@@ -461,6 +663,33 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
461
663
  this.$l.m2 = scope.$g[funcNameGetBoneMatrixFromTexture](blendIndices[2], this.boneOffset);
462
664
  this.$l.m3 = scope.$g[funcNameGetBoneMatrixFromTexture](blendIndices[3], this.boneOffset);
463
665
  this.$l.m = pb.add(pb.mul(this.m0, blendWeights.x), pb.mul(this.m1, blendWeights.y), pb.mul(this.m2, blendWeights.z), pb.mul(this.m3, blendWeights.w));
666
+ this.$l.skinInfo = this[UNIFORM_NAME_SKIN_INFLUENCE_INFO];
667
+ this.$if(pb.greaterThan(this.skinInfo.z, 4), function() {
668
+ this.$l.vertexIndex = isWebGL ? pb.int(scope.$inputs.zFakeVertexID) : pb.int(scope.$builtins.vertexIndex);
669
+ this.$l.texWidth = pb.int(this.skinInfo.x);
670
+ this.$l.pairCount = pb.int(this.skinInfo.w);
671
+ this.$for(pb.int('pairIndex'), 0, MAX_SKIN_EXTRA_INFLUENCE_PAIRS, function() {
672
+ this.$if(pb.greaterThanEqual(this.pairIndex, this.pairCount), function() {
673
+ this.$break();
674
+ });
675
+ this.$l.pixelIndex = pb.add(pb.mul(this.vertexIndex, this.pairCount), this.pairIndex);
676
+ this.$l.xIndex = pb.mod(this.pixelIndex, this.texWidth);
677
+ this.$l.yIndex = pb.div(this.pixelIndex, this.texWidth);
678
+ if (supportsTextureLoad) {
679
+ this.$l.extra = pb.textureLoad(this[UNIFORM_NAME_SKIN_INFLUENCE_DATA], pb.ivec2(this.xIndex, this.yIndex), 0);
680
+ } else {
681
+ this.$l.u = pb.div(pb.add(pb.float(this.xIndex), 0.5), this.skinInfo.x);
682
+ this.$l.v = pb.div(pb.add(pb.float(this.yIndex), 0.5), this.skinInfo.y);
683
+ this.$l.extra = pb.textureSampleLevel(this[UNIFORM_NAME_SKIN_INFLUENCE_DATA], pb.vec2(this.u, this.v), 0);
684
+ }
685
+ this.$if(pb.greaterThan(this.extra.y, 0), function() {
686
+ this.m = pb.add(this.m, pb.mul(scope.$g[funcNameGetBoneMatrixFromTexture](this.extra.x, this.boneOffset), this.extra.y));
687
+ });
688
+ this.$if(pb.greaterThan(this.extra.w, 0), function() {
689
+ this.m = pb.add(this.m, pb.mul(scope.$g[funcNameGetBoneMatrixFromTexture](this.extra.z, this.boneOffset), this.extra.w));
690
+ });
691
+ });
692
+ });
464
693
  this.$return(pb.mul(invBindMatrix, this.m));
465
694
  });
466
695
  const motionVector = !!this.getUnjitteredViewProjectionMatrix(scope);
@@ -471,12 +700,12 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
471
700
  scope.$l[that.SKIN_PREV_MATRIX_NAME] = scope[funcNameGetSkinningMatrix](scope[that.SKIN_BONE_OFFSET].y);
472
701
  }
473
702
  }
474
- /**
475
- * Calculates the vertex position of type vec3 in object space
476
- *
477
- * @param scope - Current shader scope
478
- * @param skinMatrix - The skinning matrix if there is skeletal animation, otherwise null
479
- * @returns The calculated vertex position in object space, or null if pos is null
703
+ /**
704
+ * Calculates the vertex position of type vec3 in object space
705
+ *
706
+ * @param scope - Current shader scope
707
+ * @param skinMatrix - The skinning matrix if there is skeletal animation, otherwise null
708
+ * @returns The calculated vertex position in object space, or null if pos is null
480
709
  */ static resolveVertexPosition(scope) {
481
710
  const pb = scope.$builder;
482
711
  if (pb.shaderKind !== 'vertex') {
@@ -518,12 +747,12 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
518
747
  });
519
748
  return scope[that.SKIN_MATRIX_NAME] && scope[that.SKIN_PREV_MATRIX_NAME] ? scope.Z_resolveVertexPosition(scope[that.SKIN_MATRIX_NAME], scope[that.SKIN_PREV_MATRIX_NAME]) : scope[that.SKIN_MATRIX_NAME] ? scope.Z_resolveVertexPosition(scope[that.SKIN_MATRIX_NAME]) : scope.Z_resolveVertexPosition();
520
749
  }
521
- /**
522
- * Resolve motion vector
523
- *
524
- * @param scope - Current shader scope
525
- * @param worldPos - Current object position in world space
526
- * @param prevWorldPos - Previous object position in world space
750
+ /**
751
+ * Resolve motion vector
752
+ *
753
+ * @param scope - Current shader scope
754
+ * @param worldPos - Current object position in world space
755
+ * @param prevWorldPos - Previous object position in world space
527
756
  */ static resolveMotionVector(scope, worldPos, prevWorldPos) {
528
757
  const that = this;
529
758
  const pb = scope.$builder;
@@ -534,13 +763,26 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
534
763
  scope.$outputs.zMotionVectorPosPrev = pb.mul(prevUnjitteredVPMatrix, pb.vec4(prevWorldPos.xyz, 1));
535
764
  }
536
765
  }
537
- /**
538
- * Calculates the normal vector of type vec3 in object space
539
- *
540
- * @param scope - Current shader scope
541
- * @param normal - Vertex normal input, must be type of vec3, null if no vertex normal input
542
- * @param skinMatrix - The skinning matrix if there is skeletal animation, otherwise null
543
- * @returns The calculated normal vector in object space, or null if normal is null
766
+ /**
767
+ * Gets the skinning matrix in the vertex shader, if the mesh being drawn is skinned.
768
+ *
769
+ * @remarks
770
+ * Lets a material apply the same skinning transform to its own uniform-supplied
771
+ * object-space vectors that {@link ShaderHelper.resolveVertexNormal} applies to
772
+ * the normal attribute, so such vectors stay attached to the deforming mesh.
773
+ *
774
+ * @param scope - Current shader scope, must be vertex stage
775
+ * @returns The skinning matrix of type mat4, or null when the mesh is not skinned
776
+ */ static getSkinMatrix(scope) {
777
+ return scope[this.SKIN_MATRIX_NAME] ?? null;
778
+ }
779
+ /**
780
+ * Calculates the normal vector of type vec3 in object space
781
+ *
782
+ * @param scope - Current shader scope
783
+ * @param normal - Vertex normal input, must be type of vec3, null if no vertex normal input
784
+ * @param skinMatrix - The skinning matrix if there is skeletal animation, otherwise null
785
+ * @returns The calculated normal vector in object space, or null if normal is null
544
786
  */ static resolveVertexNormal(scope, normal) {
545
787
  const pb = scope.$builder;
546
788
  if (pb.shaderKind !== 'vertex') {
@@ -550,6 +792,10 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
550
792
  if (!funcScope || !funcScope.$isMain()) {
551
793
  throw new Error(`ShaderHelper.resolveVertexNormal(): must be called at entry function`);
552
794
  }
795
+ const useVertexAttrib = !normal;
796
+ if (useVertexAttrib && scope[this.RESOLVED_VERTEX_NORMAL_NAME]) {
797
+ return scope[this.RESOLVED_VERTEX_NORMAL_NAME];
798
+ }
553
799
  if (!normal) {
554
800
  if (!scope.$getVertexAttrib('normal')) {
555
801
  scope.$inputs.Z_normal = pb.vec3().attrib('normal');
@@ -557,21 +803,25 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
557
803
  normal = scope.$getVertexAttrib('normal');
558
804
  }
559
805
  if (this.hasMorphing(scope)) {
560
- normal = pb.normalize(pb.add(normal, this.calculateMorphDelta(scope, MORPH_TARGET_NORMAL).xyz));
806
+ normal = this.calculateMorphDirectionByDisplacement(scope, normal, MORPH_TARGET_NORMAL);
561
807
  }
808
+ let resolvedNormal = normal;
562
809
  if (scope[this.SKIN_MATRIX_NAME]) {
563
- return pb.mul(scope[this.SKIN_MATRIX_NAME], pb.vec4(normal, 0)).xyz;
564
- } else {
565
- return normal;
810
+ resolvedNormal = pb.mul(scope[this.SKIN_MATRIX_NAME], pb.vec4(normal, 0)).xyz;
566
811
  }
567
- }
568
- /**
569
- * Calculates the tangent vector of type vec3 in object space
570
- *
571
- * @param scope - Current shader scope
572
- * @param tangent - Vertex tangent input, must be type of vec4, null if no vertex tangent input
573
- * @param skinMatrix - The skinning matrix if there is skeletal animation, otherwise null
574
- * @returns The calculated tangent vector of type vec4 in object space, or null if tangent is null
812
+ if (useVertexAttrib) {
813
+ scope.$l[this.RESOLVED_VERTEX_NORMAL_NAME] = resolvedNormal;
814
+ resolvedNormal = scope[this.RESOLVED_VERTEX_NORMAL_NAME];
815
+ }
816
+ return resolvedNormal;
817
+ }
818
+ /**
819
+ * Calculates the tangent vector of type vec3 in object space
820
+ *
821
+ * @param scope - Current shader scope
822
+ * @param tangent - Vertex tangent input, must be type of vec4, null if no vertex tangent input
823
+ * @param skinMatrix - The skinning matrix if there is skeletal animation, otherwise null
824
+ * @returns The calculated tangent vector of type vec4 in object space, or null if tangent is null
575
825
  */ static resolveVertexTangent(scope, tangent) {
576
826
  const pb = scope.$builder;
577
827
  if (pb.shaderKind !== 'vertex') {
@@ -588,26 +838,29 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
588
838
  tangent = scope.$getVertexAttrib('tangent');
589
839
  }
590
840
  if (this.hasMorphing(scope)) {
591
- tangent = pb.normalize(pb.add(tangent, pb.vec4(this.calculateMorphDelta(scope, MORPH_TARGET_TANGENT).xyz, 0)));
841
+ tangent = pb.vec4(this.calculateMorphDirectionByDisplacement(scope, tangent.xyz, MORPH_TARGET_TANGENT), tangent.w);
592
842
  }
593
843
  if (scope[this.SKIN_MATRIX_NAME]) {
594
- return pb.vec4(pb.mul(scope[this.SKIN_MATRIX_NAME], pb.vec4(tangent.xyz, 0)).xyz, tangent.w);
595
- } else {
596
- return tangent;
844
+ tangent = pb.vec4(pb.mul(scope[this.SKIN_MATRIX_NAME], pb.vec4(tangent.xyz, 0)).xyz, tangent.w);
845
+ }
846
+ if (this.hasMorphing(scope) && scope.$getVertexAttrib('normal')) {
847
+ const resolvedNormal = scope[this.RESOLVED_VERTEX_NORMAL_NAME] ?? this.resolveVertexNormal(scope);
848
+ tangent = pb.vec4(this.orthogonalizeMorphedTangent(scope, resolvedNormal, tangent.xyz), tangent.w);
597
849
  }
850
+ return tangent;
598
851
  }
599
- /**
600
- * Gets the uniform variable of type mat4 which holds the world matrix of current object to be drawn
601
- * @param scope - Current shader scope
602
- * @returns The world matrix of current object to be drawn
852
+ /**
853
+ * Gets the uniform variable of type mat4 which holds the world matrix of current object to be drawn
854
+ * @param scope - Current shader scope
855
+ * @returns The world matrix of current object to be drawn
603
856
  */ static getWorldMatrix(scope) {
604
857
  const pb = scope.$builder;
605
858
  return scope[UNIFORM_NAME_WORLD_MATRIX] ?? pb.mat4(this.getInstancedUniform(scope, 0), this.getInstancedUniform(scope, 1), this.getInstancedUniform(scope, 2), this.getInstancedUniform(scope, 3));
606
859
  }
607
- /**
608
- * Gets the uniform variable of type mat4 which holds the world matrix at previous frame of current object to be drawn
609
- * @param scope - Current shader scope
610
- * @returns The world matrix at previous frame of current object to be drawn
860
+ /**
861
+ * Gets the uniform variable of type mat4 which holds the world matrix at previous frame of current object to be drawn
862
+ * @param scope - Current shader scope
863
+ * @returns The world matrix at previous frame of current object to be drawn
611
864
  */ static getPrevWorldMatrix(scope) {
612
865
  const pb = scope.$builder;
613
866
  const that = this;
@@ -638,28 +891,28 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
638
891
  return scope.Z_getPrevWorldMatrix(framestamp);
639
892
  }
640
893
  }
641
- /**
642
- * Gets the instance uniform value of type vec4 by uniform index
643
- * @param scope - Current shader scope
644
- * @returns instance uniform value
894
+ /**
895
+ * Gets the instance uniform value of type vec4 by uniform index
896
+ * @param scope - Current shader scope
897
+ * @returns instance uniform value
645
898
  */ static getInstancedUniform(scope, uniformIndex) {
646
899
  const pb = scope.$builder;
647
900
  return scope[UNIFORM_NAME_INSTANCE_DATA].at(pb.add(pb.mul(scope[UNIFORM_NAME_INSTANCE_DATA_STRIDE], pb.uint(scope.$builtins.instanceIndex)), scope[UNIFORM_NAME_INSTANCE_DATA_OFFSET], pb.uint(uniformIndex)));
648
901
  }
649
- /**
650
- * Gets the uniform variable of type mat4 which holds the normal matrix of current object to be drawn
651
- *
652
- * @param scope - Current shader scope
653
- * @returns The normal matrix of current object to be drawn
902
+ /**
903
+ * Gets the uniform variable of type mat4 which holds the normal matrix of current object to be drawn
904
+ *
905
+ * @param scope - Current shader scope
906
+ * @returns The normal matrix of current object to be drawn
654
907
  */ static getNormalMatrix(scope) {
655
908
  return this.getWorldMatrix(scope);
656
909
  }
657
- /**
658
- * Vertex shader drawable stuff
659
- *
660
- * @param scope - Current shader scope
661
- * @param skinning - true if skinning is used, otherwise false.
662
- * @param instanced - true if instancing is used, otherwise false.
910
+ /**
911
+ * Vertex shader drawable stuff
912
+ *
913
+ * @param scope - Current shader scope
914
+ * @param skinning - true if skinning is used, otherwise false.
915
+ * @param instanced - true if instancing is used, otherwise false.
663
916
  */ static vertexShaderDrawableStuff(scope, skinning, morphing, instanced) {
664
917
  const pb = scope.$builder;
665
918
  if (instanced) {
@@ -676,10 +929,12 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
676
929
  scope[UNIFORM_NAME_BONE_MATRICES] = pb.tex2D().uniform(1).sampleType('unfilterable-float');
677
930
  scope[UNIFORM_NAME_BONE_INV_BIND_MATRIX] = pb.mat4().uniform(1);
678
931
  scope[UNIFORM_NAME_BONE_TEXTURE_SIZE] = pb.vec2().uniform(1);
932
+ scope[UNIFORM_NAME_SKIN_INFLUENCE_DATA] = pb.tex2D().uniform(1).sampleType('unfilterable-float');
933
+ scope[UNIFORM_NAME_SKIN_INFLUENCE_INFO] = pb.vec4().uniform(1);
679
934
  }
680
935
  if (morphing) {
681
936
  scope[UNIFORM_NAME_MORPH_DATA] = pb.tex2D().uniform(1).sampleType('unfilterable-float');
682
- scope[UNIFORM_NAME_MORPH_INFO] = pb.vec4[1 + MORPH_WEIGHTS_VECTOR_COUNT + MORPH_ATTRIBUTE_VECTOR_COUNT]().uniformBuffer(1);
937
+ scope[UNIFORM_NAME_MORPH_INFO] = pb.vec4[1 + MORPH_WEIGHTS_VECTOR_COUNT * 2 + MORPH_ATTRIBUTE_VECTOR_COUNT]().uniformBuffer(1);
683
938
  }
684
939
  }
685
940
  /** @internal */ static prepareVertexShaderCommon(pb, ctx) {
@@ -735,8 +990,10 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
735
990
  directionCutoff: light.directionAndCutoff,
736
991
  viewMatrix: light.viewMatrix,
737
992
  depthBias: shadowMapParams.depthBiasValues[0],
993
+ implParams: shadowMapParams.impl.getParams(this._lightUniformShadow.implParams),
738
994
  lightType: light.lightType
739
995
  });
996
+ shadowMapParams.impl?.applyCasterUniforms(bindGroup, shadowMapParams);
740
997
  }
741
998
  }
742
999
  /** @internal */ static setFogUniforms(bindGroup, withAerialPerspective, fogType, additive, atmosphereParams, heightFogParams, aerialPerspectiveLUT, skyDistantLightLUT) {
@@ -749,57 +1006,57 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
749
1006
  bindGroup.setTexture(UNIFORM_NAME_AERIALPERSPECTIVE_LUT, aerialPerspectiveLUT);
750
1007
  bindGroup.setTexture(UNIFORM_NAME_SKYDISTANTLIGHT_LUT, skyDistantLightLUT);
751
1008
  }
752
- /**
753
- * @internal
754
- *
755
- * Camera pre-exposure factor for scene-linear lighting quantities.
756
- *
757
- * @remarks
758
- * Physical lighting authors light intensities in photometric units (lux, candela, cd/m²), whose
759
- * magnitudes (~1e5 for daylight) neither fit an rgba16f render target nor leave usable headroom
760
- * for specular highlights. Following Filament, the camera exposure is folded into every light
761
- * quantity on the CPU so the HDR target stays near 1.0 and downstream passes need no unit
762
- * awareness. Legacy returns 1 so its uploads stay byte-identical.
1009
+ /**
1010
+ * @internal
1011
+ *
1012
+ * Camera pre-exposure factor for scene-linear lighting quantities.
1013
+ *
1014
+ * @remarks
1015
+ * Physical lighting authors light intensities in photometric units (lux, candela, cd/m²), whose
1016
+ * magnitudes (~1e5 for daylight) neither fit an rgba16f render target nor leave usable headroom
1017
+ * for specular highlights. Following Filament, the camera exposure is folded into every light
1018
+ * quantity on the CPU so the HDR target stays near 1.0 and downstream passes need no unit
1019
+ * awareness. Legacy returns 1 so its uploads stay byte-identical.
763
1020
  */ static getPreExposure(ctx) {
764
1021
  return ctx.scene.lightingMode === 'physical' ? ctx.camera.exposure : 1;
765
1022
  }
766
- /**
767
- * @internal
768
- *
769
- * Light color/intensity vector with the camera pre-exposure folded into the intensity component.
770
- *
771
- * @remarks
772
- * Only the intensity (`w`) is scaled; the color stays untouched, matching Filament's
773
- * `FScene::prepareDynamicLights`. The light's own cached vector is never mutated because it is
774
- * shared across every camera rendering the scene.
1023
+ /**
1024
+ * @internal
1025
+ *
1026
+ * Light color/intensity vector with the camera pre-exposure folded into the intensity component.
1027
+ *
1028
+ * @remarks
1029
+ * Only the intensity (`w`) is scaled; the color stays untouched, matching Filament's
1030
+ * `FScene::prepareDynamicLights`. The light's own cached vector is never mutated because it is
1031
+ * shared across every camera rendering the scene.
775
1032
  */ static getPreExposedColorIntensity(light, ctx, out) {
776
1033
  const src = light.diffuseAndIntensity;
777
1034
  const result = out ?? new Vector4();
778
1035
  result.setXYZW(src.x, src.y, src.z, src.w * this.getPreExposure(ctx));
779
1036
  return result;
780
1037
  }
781
- /**
782
- * @internal
783
- *
784
- * Environment lighting scale uploaded as `light.envLightStrength`.
785
- *
786
- * @remarks
787
- * Legacy uses the unitless {@link EnvLightWrapper.strength}.
788
- *
789
- * Physical returns the ratio that converts the cached sky bake from its fixed storage exposure
790
- * (`SkyRenderer.PHYSICAL_BAKE_EXPOSURE`) to the live camera exposure. Every environment source is
791
- * normalized into that one space before it reaches the IBL -- the scattering atmosphere emits
792
- * photometric luminance, authored 0..1 skyboxes/panoramas are lifted by
793
- * {@link EnvLightWrapper.intensity} -- so no pass consuming `envLightStrength` has to know the
794
- * sky type.
795
- *
796
- * The bake cannot simply hold raw cd/m²: the environment cubemap is `rg11b10uf`/`rgba16f` and a
797
- * daylight sun overflows it to Inf, which `prefilterCubemap` then spreads across the whole IBL.
798
- *
799
- * {@link EnvLightWrapper.strength} multiplies the result in both modes. It is the only per-frame
800
- * dimmer for environment lighting: the photometric `intensity` reaches the image solely through
801
- * the cached bake, and a `scatter` sky does not even consult it (its brightness comes from the
802
- * sun), so without this factor there would be no way to turn the IBL down without re-baking.
1038
+ /**
1039
+ * @internal
1040
+ *
1041
+ * Environment lighting scale uploaded as `light.envLightStrength`.
1042
+ *
1043
+ * @remarks
1044
+ * Legacy uses the unitless {@link EnvLightWrapper.strength}.
1045
+ *
1046
+ * Physical returns the ratio that converts the cached sky bake from its fixed storage exposure
1047
+ * (`SkyRenderer.PHYSICAL_BAKE_EXPOSURE`) to the live camera exposure. Every environment source is
1048
+ * normalized into that one space before it reaches the IBL -- the scattering atmosphere emits
1049
+ * photometric luminance, authored 0..1 skyboxes/panoramas are lifted by
1050
+ * {@link EnvLightWrapper.intensity} -- so no pass consuming `envLightStrength` has to know the
1051
+ * sky type.
1052
+ *
1053
+ * The bake cannot simply hold raw cd/m²: the environment cubemap is `rg11b10uf`/`rgba16f` and a
1054
+ * daylight sun overflows it to Inf, which `prefilterCubemap` then spreads across the whole IBL.
1055
+ *
1056
+ * {@link EnvLightWrapper.strength} multiplies the result in both modes. It is the only per-frame
1057
+ * dimmer for environment lighting: the photometric `intensity` reaches the image solely through
1058
+ * the cached bake, and a `scatter` sky does not even consult it (its brightness comes from the
1059
+ * sun), so without this factor there would be no way to turn the IBL down without re-baking.
803
1060
  */ static getEnvLightLuminance(ctx) {
804
1061
  const env = ctx.env;
805
1062
  if (!env) {
@@ -862,174 +1119,174 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
862
1119
  ctx.env.light.envLight.updateBindGroup(bindGroup, ctx);
863
1120
  }
864
1121
  }
865
- /**
866
- * Gets the uniform variable of type float which holds the strength of the environment light
867
- *
868
- * @remarks
869
- * This function can only be used in the fragment shader
870
- *
871
- * @param scope - Current shader scope
872
- * @returns The uniform variable of which presents the strength of the environment light
1122
+ /**
1123
+ * Gets the uniform variable of type float which holds the strength of the environment light
1124
+ *
1125
+ * @remarks
1126
+ * This function can only be used in the fragment shader
1127
+ *
1128
+ * @param scope - Current shader scope
1129
+ * @returns The uniform variable of which presents the strength of the environment light
873
1130
  */ static getEnvLightStrength(scope) {
874
1131
  return scope.light.envLightStrength;
875
1132
  }
876
- /**
877
- * Gets the uniform variable of type float which holds the specular strength of the environment light
878
- *
879
- * @remarks
880
- * This function can only be used in the fragment shader
881
- *
882
- * @param scope - Current shader scope
883
- * @returns The uniform variable which presents the specular strength of the environment light
1133
+ /**
1134
+ * Gets the uniform variable of type float which holds the specular strength of the environment light
1135
+ *
1136
+ * @remarks
1137
+ * This function can only be used in the fragment shader
1138
+ *
1139
+ * @param scope - Current shader scope
1140
+ * @returns The uniform variable which presents the specular strength of the environment light
884
1141
  */ static getEnvLightSpecularStrength(scope) {
885
1142
  return scope.light.envLightSpecularStrength ?? scope.$builder.float(1);
886
1143
  }
887
- /**
888
- * Gets current scene color texture
889
- * @param scope - Current shader scope
890
- * @returns current scene color texture
1144
+ /**
1145
+ * Gets current scene color texture
1146
+ * @param scope - Current shader scope
1147
+ * @returns current scene color texture
891
1148
  */ static getSceneColorTexture(scope) {
892
1149
  return scope[UNIFORM_NAME_SCENE_COLOR_MAP];
893
1150
  }
894
- /**
895
- * Gets the size of current scene color texture
896
- * @param scope - Current shader scope
897
- * @returns The size of current scene color texture
1151
+ /**
1152
+ * Gets the size of current scene color texture
1153
+ * @param scope - Current shader scope
1154
+ * @returns The size of current scene color texture
898
1155
  */ static getSceneColorTextureSize(scope) {
899
1156
  return scope[UNIFORM_NAME_SCENE_COLOR_MAP_SIZE];
900
1157
  }
901
- /**
902
- * Gets current linear depth texture
903
- * @param scope - Current shader scope
904
- * @returns current linear depth texture
1158
+ /**
1159
+ * Gets current linear depth texture
1160
+ * @param scope - Current shader scope
1161
+ * @returns current linear depth texture
905
1162
  */ static getLinearDepthTexture(scope) {
906
1163
  return scope[UNIFORM_NAME_LINEAR_DEPTH_MAP];
907
1164
  }
908
- /**
909
- * Gets the size of current linear depth texture
910
- * @param scope - Current shader scope
911
- * @returns The size of current linear depth texture
1165
+ /**
1166
+ * Gets the size of current linear depth texture
1167
+ * @param scope - Current shader scope
1168
+ * @returns The size of current linear depth texture
912
1169
  */ static getLinearDepthTextureSize(scope) {
913
1170
  return scope[UNIFORM_NAME_LINEAR_DEPTH_MAP_SIZE];
914
1171
  }
915
- /**
916
- * Gets current HiZ depth texture
917
- * @param scope - Current shader scope
918
- * @returns current HiZ depth texture
1172
+ /**
1173
+ * Gets current HiZ depth texture
1174
+ * @param scope - Current shader scope
1175
+ * @returns current HiZ depth texture
919
1176
  */ static getHiZDepthTexture(scope) {
920
1177
  return scope[UNIFORM_NAME_HIZ_DEPTH_MAP];
921
1178
  }
922
- /**
923
- * Gets the size of current HiZ depth texture
924
- * @param scope - Current shader scope
925
- * @returns The size of current HiZ depth texture
1179
+ /**
1180
+ * Gets the size of current HiZ depth texture
1181
+ * @param scope - Current shader scope
1182
+ * @returns The size of current HiZ depth texture
926
1183
  */ static getHiZDepthTextureSize(scope) {
927
1184
  return scope[UNIFORM_NAME_HIZ_DEPTH_MAP_INFO].xy;
928
1185
  }
929
- /**
930
- * Gets the mipmap levels count of current HiZ depth texture
931
- * @param scope - Current shader scope
932
- * @returns The mipmap levels count of current HiZ depth texture
1186
+ /**
1187
+ * Gets the mipmap levels count of current HiZ depth texture
1188
+ * @param scope - Current shader scope
1189
+ * @returns The mipmap levels count of current HiZ depth texture
933
1190
  */ static getHiZDepthTextureMipLevelCount(scope) {
934
1191
  return scope[UNIFORM_NAME_HIZ_DEPTH_MAP_INFO].z;
935
1192
  }
936
- /**
937
- * Gets current baked skybox texture
938
- * @param scope - Current shader scope
939
- * @returns current baked skybox texture
1193
+ /**
1194
+ * Gets current baked skybox texture
1195
+ * @param scope - Current shader scope
1196
+ * @returns current baked skybox texture
940
1197
  */ static getBakedSkyTexture(scope) {
941
1198
  return scope[UNIFORM_NAME_BAKED_SKY_MAP];
942
1199
  }
943
- /**
944
- * Samples the baked sky cubemap in the same pre-exposed space as the scene color buffer.
945
- *
946
- * @remarks
947
- * The bake is cached and therefore exposure-independent, stored at the fixed
948
- * `PHYSICAL_BAKE_EXPOSURE`. Anything that blends it against already-pre-exposed lit color (water
949
- * reflection, blueprint sky lookups) must first convert it to the live exposure.
950
- * `envLightStrength` carries exactly that ratio in physical mode, and the legacy environment
951
- * strength (its established meaning) otherwise.
952
- *
953
- * @param scope - Current shader scope
954
- * @param direction - Sampling direction
955
- * @returns Pre-exposed sky radiance
1200
+ /**
1201
+ * Samples the baked sky cubemap in the same pre-exposed space as the scene color buffer.
1202
+ *
1203
+ * @remarks
1204
+ * The bake is cached and therefore exposure-independent, stored at the fixed
1205
+ * `PHYSICAL_BAKE_EXPOSURE`. Anything that blends it against already-pre-exposed lit color (water
1206
+ * reflection, blueprint sky lookups) must first convert it to the live exposure.
1207
+ * `envLightStrength` carries exactly that ratio in physical mode, and the legacy environment
1208
+ * strength (its established meaning) otherwise.
1209
+ *
1210
+ * @param scope - Current shader scope
1211
+ * @param direction - Sampling direction
1212
+ * @returns Pre-exposed sky radiance
956
1213
  */ static sampleBakedSkyPreExposed(scope, direction) {
957
1214
  const pb = scope.$builder;
958
1215
  return pb.mul(pb.textureSampleLevel(this.getBakedSkyTexture(scope), direction, 0).rgb, this.getEnvLightStrength(scope));
959
1216
  }
960
- /**
961
- * Gets the camera pre-exposure factor.
962
- *
963
- * @remarks
964
- * The multiplier every photometric quantity is scaled by before it reaches the HDR target. 1 in
965
- * legacy. Use it for material-authored emitters (emissive), which are not covered by the CPU-side
966
- * light pre-exposure.
967
- *
968
- * @param scope - Current shader scope
969
- * @returns The pre-exposure factor
1217
+ /**
1218
+ * Gets the camera pre-exposure factor.
1219
+ *
1220
+ * @remarks
1221
+ * The multiplier every photometric quantity is scaled by before it reaches the HDR target. 1 in
1222
+ * legacy. Use it for material-authored emitters (emissive), which are not covered by the CPU-side
1223
+ * light pre-exposure.
1224
+ *
1225
+ * @param scope - Current shader scope
1226
+ * @returns The pre-exposure factor
970
1227
  */ static getPreExposureUniform(scope) {
971
1228
  return scope.camera.preExposure;
972
1229
  }
973
- /**
974
- * Gets the elapsed time in seconds
975
- * @param scope - Current shader scope
976
- * @returns The elapsed time in seconds
1230
+ /**
1231
+ * Gets the elapsed time in seconds
1232
+ * @param scope - Current shader scope
1233
+ * @returns The elapsed time in seconds
977
1234
  */ static getElapsedTime(scope) {
978
1235
  return scope.camera.elapsedTime;
979
1236
  }
980
- /**
981
- * Gets the elapsed time since last frame in seconds
982
- * @param scope - Current shader scope
983
- * @returns The elapsed time since last frame in seconds
1237
+ /**
1238
+ * Gets the elapsed time since last frame in seconds
1239
+ * @param scope - Current shader scope
1240
+ * @returns The elapsed time since last frame in seconds
984
1241
  */ static getElapsedTimeFrame(scope) {
985
1242
  return scope.camera.frameDeltaTime;
986
1243
  }
987
- /**
988
- * Gets the uniform variable of type vec3 which holds the camera position
989
- * @param scope - Current shader scope
990
- * @returns The camera position
1244
+ /**
1245
+ * Gets the uniform variable of type vec3 which holds the camera position
1246
+ * @param scope - Current shader scope
1247
+ * @returns The camera position
991
1248
  */ static getCameraPosition(scope) {
992
1249
  return scope.camera.position.xyz;
993
1250
  }
994
- /**
995
- * Gets the uniform variable of type float which holds the roughness factor
996
- * @param scope - Current shader scope
997
- * @returns The roughness factor
1251
+ /**
1252
+ * Gets the uniform variable of type float which holds the roughness factor
1253
+ * @param scope - Current shader scope
1254
+ * @returns The roughness factor
998
1255
  */ static getCameraRoughnessFactor(scope) {
999
1256
  return scope.camera.roughnessFactor;
1000
1257
  }
1001
- /**
1002
- * Gets framebuffer size for rendering
1003
- * @param scope - Current shader scope
1004
- * @returns The roughness factor
1258
+ /**
1259
+ * Gets framebuffer size for rendering
1260
+ * @param scope - Current shader scope
1261
+ * @returns The roughness factor
1005
1262
  */ static getRenderSize(scope) {
1006
1263
  return scope.camera.renderSize;
1007
1264
  }
1008
- /**
1009
- * Gets the uniform variable of type uint which holds the framestamp
1010
- * @param scope - Current shader scope
1011
- * @returns The framestamp
1265
+ /**
1266
+ * Gets the uniform variable of type uint which holds the framestamp
1267
+ * @param scope - Current shader scope
1268
+ * @returns The framestamp
1012
1269
  */ static getFramestamp(scope) {
1013
1270
  return scope.camera.framestamp;
1014
1271
  }
1015
- /**
1016
- * Gets the clip plane flag
1017
- * @param scope - Current shader scope
1018
- * @returns A float value of 1 indices the clip plane presents, otherwise 0
1272
+ /**
1273
+ * Gets the clip plane flag
1274
+ * @param scope - Current shader scope
1275
+ * @returns A float value of 1 indices the clip plane presents, otherwise 0
1019
1276
  */ static getCameraClipPlaneFlag(scope) {
1020
1277
  return scope.camera.position.w;
1021
1278
  }
1022
- /**
1023
- * Gets the clip plane
1024
- * @param scope - Current shader scope
1025
- * @returns A vec4 presents the clip plane
1279
+ /**
1280
+ * Gets the clip plane
1281
+ * @param scope - Current shader scope
1282
+ * @returns A vec4 presents the clip plane
1026
1283
  */ static getCameraClipPlane(scope) {
1027
1284
  return scope.camera.clipPlane;
1028
1285
  }
1029
- /**
1030
- * Gets the uniform variable of type vec4 which holds the camera parameters
1031
- * @param scope - Current shader scope
1032
- * @returns The camera parameters
1286
+ /**
1287
+ * Gets the uniform variable of type vec4 which holds the camera parameters
1288
+ * @param scope - Current shader scope
1289
+ * @returns The camera parameters
1033
1290
  */ static getCameraParams(scope) {
1034
1291
  return scope.camera.params;
1035
1292
  }
@@ -1039,36 +1296,36 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1039
1296
  /** @internal */ static getCountParams(scope) {
1040
1297
  return scope.light.countParams;
1041
1298
  }
1042
- /**
1043
- * Number of shadow-casting lights at the head of the clustered light buffer.
1044
- * Only defined on the screen-space shadow mask path (ctx.screenSpaceShadowMask);
1045
- * a clustered light whose buffer index is `<= N` samples the shadow mask.
1046
- * @internal
1299
+ /**
1300
+ * Number of shadow-casting lights at the head of the clustered light buffer.
1301
+ * Only defined on the screen-space shadow mask path (ctx.screenSpaceShadowMask);
1302
+ * a clustered light whose buffer index is `<= N` samples the shadow mask.
1303
+ * @internal
1047
1304
  */ static getNumShadowLights(scope) {
1048
1305
  return scope.light.numShadowLights;
1049
1306
  }
1050
- /**
1051
- * Cluster shadow-mask mode for the current queue: 1 = sample the opaque shadow
1052
- * mask for shadow lights; 0 = skip shadow lights (they are lit inline by the
1053
- * additive passes, used for the transparent queue). Only defined on the
1054
- * screen-space shadow mask path.
1055
- * @internal
1307
+ /**
1308
+ * Cluster shadow-mask mode for the current queue: 1 = sample the opaque shadow
1309
+ * mask for shadow lights; 0 = skip shadow lights (they are lit inline by the
1310
+ * additive passes, used for the transparent queue). Only defined on the
1311
+ * screen-space shadow mask path.
1312
+ * @internal
1056
1313
  */ static getShadowMaskMode(scope) {
1057
1314
  return scope[UNIFORM_NAME_SHADOW_MASK_MODE];
1058
1315
  }
1059
- /**
1060
- * Bind the per-queue cluster shadow-mask mode. Call in the clustered light pass
1061
- * only when the screen-space shadow mask path is active.
1062
- * @internal
1316
+ /**
1317
+ * Bind the per-queue cluster shadow-mask mode. Call in the clustered light pass
1318
+ * only when the screen-space shadow mask path is active.
1319
+ * @internal
1063
1320
  */ static setShadowMaskMode(bindGroup, sampleMask) {
1064
1321
  bindGroup.setValue(UNIFORM_NAME_SHADOW_MASK_MODE, sampleMask ? 1 : 0);
1065
1322
  }
1066
- /**
1067
- * Lazily-created 1x1x1 rgba8unorm array texture used as the shadow-mask binding
1068
- * fallback when the screen-space shadow mask path is enabled but no mask was
1069
- * produced this frame (no shadow-casting lights). It is never sampled in that
1070
- * case, but the declared uniform still requires a valid binding.
1071
- * @internal
1323
+ /**
1324
+ * Lazily-created 1x1x1 rgba8unorm array texture used as the shadow-mask binding
1325
+ * fallback when the screen-space shadow mask path is enabled but no mask was
1326
+ * produced this frame (no shadow-casting lights). It is never sampled in that
1327
+ * case, but the declared uniform still requires a valid binding.
1328
+ * @internal
1072
1329
  */ static getDummyShadowMask(device) {
1073
1330
  let tex = this._dummyShadowMask;
1074
1331
  if (!tex || tex.disposed) {
@@ -1083,22 +1340,22 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1083
1340
  /** @internal */ static getClusteredLightIndexTexture(scope) {
1084
1341
  return scope[UNIFORM_NAME_LIGHT_INDEX_TEXTURE];
1085
1342
  }
1086
- /**
1087
- * Clustered shadow factor for a light, in `[0,1]` (1 = fully lit).
1088
- *
1089
- * - Lights whose clustered buffer index is greater than `numShadowLights` are not
1090
- * shadow-casters and return 1.0.
1091
- * - Shadow-casting lights on the opaque queue (shadow mask mode 1) return the
1092
- * screen-space mask value. Layer/channel are derived from the zero-based light
1093
- * ordinal `s = index-1`: `layer = s >> 2`, `channel = s & 3`, matching the
1094
- * ShadowMaskPass packing.
1095
- * - Shadow-casting lights on a transparent queue (mode 0) return 0: their clustered
1096
- * contribution is suppressed because they are lit inline by the additive passes,
1097
- * where shadows are sampled at the true (transparent) surface depth rather than
1098
- * from the opaque-depth mask.
1099
- *
1100
- * Only valid on the screen-space shadow mask path (ctx.screenSpaceShadowMask).
1101
- * @internal
1343
+ /**
1344
+ * Clustered shadow factor for a light, in `[0,1]` (1 = fully lit).
1345
+ *
1346
+ * - Lights whose clustered buffer index is greater than `numShadowLights` are not
1347
+ * shadow-casters and return 1.0.
1348
+ * - Shadow-casting lights on the opaque queue (shadow mask mode 1) return the
1349
+ * screen-space mask value. Layer/channel are derived from the zero-based light
1350
+ * ordinal `s = index-1`: `layer = s >> 2`, `channel = s & 3`, matching the
1351
+ * ShadowMaskPass packing.
1352
+ * - Shadow-casting lights on a transparent queue (mode 0) return 0: their clustered
1353
+ * contribution is suppressed because they are lit inline by the additive passes,
1354
+ * where shadows are sampled at the true (transparent) surface depth rather than
1355
+ * from the opaque-depth mask.
1356
+ *
1357
+ * Only valid on the screen-space shadow mask path (ctx.screenSpaceShadowMask).
1358
+ * @internal
1102
1359
  */ static sampleShadowMask(scope, lightIndex) {
1103
1360
  const pb = scope.$builder;
1104
1361
  const that = this;
@@ -1125,87 +1382,87 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1125
1382
  });
1126
1383
  return pb.getGlobalScope()[funcName](lightIndex);
1127
1384
  }
1128
- /**
1129
- * Gets the uniform variable that contains atmosphere parameters
1130
- * @param scope - Current shader scope
1131
- * @returns The atmosphere parameters
1385
+ /**
1386
+ * Gets the uniform variable that contains atmosphere parameters
1387
+ * @param scope - Current shader scope
1388
+ * @returns The atmosphere parameters
1132
1389
  */ static getAtmosphereParams(scope) {
1133
1390
  return scope.fog.atmosphereParams;
1134
1391
  }
1135
- /**
1136
- * Gets the aerial perspective LUT
1137
- * @param scope - Current shader scope
1138
- * @returns The aerial perspective LUT texture
1392
+ /**
1393
+ * Gets the aerial perspective LUT
1394
+ * @param scope - Current shader scope
1395
+ * @returns The aerial perspective LUT texture
1139
1396
  */ static getAerialPerspectiveLUT(scope) {
1140
1397
  return scope[UNIFORM_NAME_AERIALPERSPECTIVE_LUT];
1141
1398
  }
1142
- /**
1143
- * Gets the uniform variable of type mat4 which holds the view projection matrix of current camera
1144
- * @param scope - Current shader scope
1145
- * @returns The view projection matrix of current camera
1399
+ /**
1400
+ * Gets the uniform variable of type mat4 which holds the view projection matrix of current camera
1401
+ * @param scope - Current shader scope
1402
+ * @returns The view projection matrix of current camera
1146
1403
  */ static getViewProjectionMatrix(scope) {
1147
1404
  return scope.camera.viewProjectionMatrix;
1148
1405
  }
1149
- /**
1150
- * Gets the uniform variable of type mat4 which holds the inversed view projection matrix of current camera
1151
- * @param scope - Current shader scope
1152
- * @returns The inversed view projection matrix of current camera
1406
+ /**
1407
+ * Gets the uniform variable of type mat4 which holds the inversed view projection matrix of current camera
1408
+ * @param scope - Current shader scope
1409
+ * @returns The inversed view projection matrix of current camera
1153
1410
  */ static getInvViewProjectionMatrix(scope) {
1154
1411
  return scope.camera.invViewProjectionMatrix;
1155
1412
  }
1156
- /**
1157
- * Gets the uniform variable of type mat4 which holds the projection matrix of current camera
1158
- * @param scope - Current shader scope
1159
- * @returns The projection matrix of current camera
1413
+ /**
1414
+ * Gets the uniform variable of type mat4 which holds the projection matrix of current camera
1415
+ * @param scope - Current shader scope
1416
+ * @returns The projection matrix of current camera
1160
1417
  */ static getProjectionMatrix(scope) {
1161
1418
  return scope.camera.projectionMatrix;
1162
1419
  }
1163
- /**
1164
- * Gets the uniform variable of type mat4 which holds the inversed projection matrix of current camera
1165
- * @param scope - Current shader scope
1166
- * @returns The inversed projection matrix of current camera
1420
+ /**
1421
+ * Gets the uniform variable of type mat4 which holds the inversed projection matrix of current camera
1422
+ * @param scope - Current shader scope
1423
+ * @returns The inversed projection matrix of current camera
1167
1424
  */ static getInvProjectionMatrix(scope) {
1168
1425
  return scope.camera.invProjectionMatrix;
1169
1426
  }
1170
- /**
1171
- * Gets the uniform variable of type mat4 which holds the unjittered view projection matrix of current camera
1172
- * @param scope - Current shader scope
1173
- * @returns The unjittered view projection matrix of current camera
1427
+ /**
1428
+ * Gets the uniform variable of type mat4 which holds the unjittered view projection matrix of current camera
1429
+ * @param scope - Current shader scope
1430
+ * @returns The unjittered view projection matrix of current camera
1174
1431
  */ static getUnjitteredViewProjectionMatrix(scope) {
1175
1432
  return scope.camera.unjitteredVPMatrix;
1176
1433
  }
1177
- /**
1178
- * Gets the uniform variable of type vec2 which holds the jitter value of the projection matrix of current camera
1179
- * @param scope - Current shader scope
1180
- * @returns The jitter value of projection matrix of current camera
1434
+ /**
1435
+ * Gets the uniform variable of type vec2 which holds the jitter value of the projection matrix of current camera
1436
+ * @param scope - Current shader scope
1437
+ * @returns The jitter value of projection matrix of current camera
1181
1438
  */ static getProjectionMatrixJitterValue(scope) {
1182
1439
  return scope.camera.jitterValue;
1183
1440
  }
1184
- /**
1185
- * Gets the uniform variable of type mat4 which holds the jittered inversed view-projection matrix
1186
- * @param scope - Current shader scope
1187
- * @returns The jittered inversed view-projection matrix
1441
+ /**
1442
+ * Gets the uniform variable of type mat4 which holds the jittered inversed view-projection matrix
1443
+ * @param scope - Current shader scope
1444
+ * @returns The jittered inversed view-projection matrix
1188
1445
  */ static getJitteredInvVPMatrix(scope) {
1189
1446
  return scope.camera.jitteredInvVPMatrix;
1190
1447
  }
1191
- /**
1192
- * Gets the uniform variable of type mat4 which holds the unjittered view projection at previous frame matrix of current camera
1193
- * @param scope - Current shader scope
1194
- * @returns The unjittered view projection matrix at previous frame of current camera
1448
+ /**
1449
+ * Gets the uniform variable of type mat4 which holds the unjittered view projection at previous frame matrix of current camera
1450
+ * @param scope - Current shader scope
1451
+ * @returns The unjittered view projection matrix at previous frame of current camera
1195
1452
  */ static getPrevUnjitteredViewProjectionMatrix(scope) {
1196
1453
  return scope.camera.prevUnjitteredVPMatrix;
1197
1454
  }
1198
- /**
1199
- * Gets the uniform variable of type mat4 which holds the view matrix of current camera (world space to camera space)
1200
- * @param scope - Current shader scope
1201
- * @returns The view matrix of current camera
1455
+ /**
1456
+ * Gets the uniform variable of type mat4 which holds the view matrix of current camera (world space to camera space)
1457
+ * @param scope - Current shader scope
1458
+ * @returns The view matrix of current camera
1202
1459
  */ static getViewMatrix(scope) {
1203
1460
  return scope.camera.viewMatrix;
1204
1461
  }
1205
- /**
1206
- * Gets the uniform variable of type mat4 which holds the inv-view matrix of current camera (camera space to world space)
1207
- * @param scope - Current shader scope
1208
- * @returns The inv-view matrix of current camera
1462
+ /**
1463
+ * Gets the uniform variable of type mat4 which holds the inv-view matrix of current camera (camera space to world space)
1464
+ * @param scope - Current shader scope
1465
+ * @returns The inv-view matrix of current camera
1209
1466
  */ static getInvViewMatrix(scope) {
1210
1467
  return scope.camera.worldMatrix;
1211
1468
  }
@@ -1259,20 +1516,20 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1259
1516
  /** @internal */ static getLightExtra(scope, lightIndex) {
1260
1517
  return scope[UNIFORM_NAME_LIGHT_BUFFER].at(scope.$builder.add(scope.$builder.mul(lightIndex, 4), 3));
1261
1518
  }
1262
- /**
1263
- * Sets the clip space position in vertex shader
1264
- *
1265
- * @remarks
1266
- * Use this function instead of using
1267
- * <pre>
1268
- * // Do not use this
1269
- * this.$builtins.position = some_value;
1270
- * // Use this
1271
- * ShaderFramework.setClipSpacePosition(some_value);
1272
- * </pre>,
1273
- *
1274
- * @param scope - Current shader scope
1275
- * @param pos - The clip space position to be set
1519
+ /**
1520
+ * Sets the clip space position in vertex shader
1521
+ *
1522
+ * @remarks
1523
+ * Use this function instead of using
1524
+ * <pre>
1525
+ * // Do not use this
1526
+ * this.$builtins.position = some_value;
1527
+ * // Use this
1528
+ * ShaderFramework.setClipSpacePosition(some_value);
1529
+ * </pre>,
1530
+ *
1531
+ * @param scope - Current shader scope
1532
+ * @param pos - The clip space position to be set
1276
1533
  */ static setClipSpacePosition(scope, pos) {
1277
1534
  const pb = scope.$builder;
1278
1535
  const cameraParams = this.getCameraParams(scope);
@@ -1282,27 +1539,76 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1282
1539
  scope.$builtins.position = pos;
1283
1540
  }
1284
1541
  }
1285
- /**
1286
- * Get shadow map uniform value
1287
- *
1288
- * @param scope - Shader scope
1289
- * @returns The shadow map texture uniform
1542
+ /**
1543
+ * Get shadow map uniform value
1544
+ *
1545
+ * @param scope - Shader scope
1546
+ * @returns The shadow map texture uniform
1290
1547
  */ static getShadowMap(scope) {
1291
1548
  return scope[UNIFORM_NAME_SHADOW_MAP];
1292
1549
  }
1293
- /**
1294
- * Calculates shadow of current fragment
1295
- *
1296
- * @param scope - Shader scope
1297
- * @param NoL - NdotL vector
1298
- * @returns Shadow of current fragment, 1 means no shadow and 0 means full shadowed.
1299
- */ static calculateShadow(scope, worldPos, NoL, ctx) {
1550
+ /**
1551
+ * Per-cascade scale for both bias terms.
1552
+ *
1553
+ * @remarks
1554
+ * Only `depthBiasValues[0]` is uploaded; every cascade's value is recovered by
1555
+ * multiplying with this ratio. It is derived from the `.x` components, but
1556
+ * since `.x` and the normal offset `.y` are both proportional to the cascade's
1557
+ * texel world size, the same ratio applies to either.
1558
+ * @internal
1559
+ */ static getShadowCascadeBiasScale(scope, split) {
1560
+ const pb = scope.$builder;
1561
+ const splitFlags = pb.vec4(pb.float(pb.equal(split, 0)), pb.float(pb.equal(split, 1)), pb.float(pb.equal(split, 2)), pb.float(pb.equal(split, 3)));
1562
+ return pb.dot(this.getDepthBiasScales(scope), splitFlags);
1563
+ }
1564
+ /**
1565
+ * Moves the receiver along its normal before the shadow lookup.
1566
+ *
1567
+ * @remarks
1568
+ * Grazing-angle self-shadowing cannot be fixed with a depth bias: the depth
1569
+ * spanned by one shadow texel is `texelWorldSize * tan(theta)`, which diverges
1570
+ * as NdotL approaches zero. Offsetting the sample position sideways moves it
1571
+ * out of the offending texel instead, and the required distance is bounded.
1572
+ *
1573
+ * `depthBiasValues.y` carries the world-space distance of one `normalBias`
1574
+ * unit (see `ShadowMapper.calcDepthBiasParams`).
1575
+ * @internal
1576
+ */ static applyShadowNormalOffset(lightType, scope, worldPos, worldNormal, NdotL, cascadeScale) {
1577
+ const pb = scope.$builder;
1578
+ const depthBiasParam = this.getDepthBiasValues(scope);
1579
+ const receiverNoL = getShadowReceiverNoL(scope, NdotL, lightType);
1580
+ // sin(theta): 0 when facing the light, 1 at grazing incidence.
1581
+ const sinTheta = pb.sqrt(pb.clamp(pb.sub(1, pb.mul(receiverNoL, receiverNoL)), 0, 1));
1582
+ let offset = pb.mul(depthBiasParam.y, getShadowReceiverBiasFactor(scope), sinTheta);
1583
+ if (cascadeScale) {
1584
+ offset = pb.mul(offset, cascadeScale);
1585
+ }
1586
+ if (lightType !== LIGHT_TYPE_DIRECTIONAL) {
1587
+ // Perspective shadow cameras: one texel covers more world space further
1588
+ // from the light. depthBiasValues.w is the far/near footprint ratio.
1589
+ const posRange = this.getLightPositionAndRangeForShadow(scope);
1590
+ const linearDepth = pb.clamp(pb.div(pb.distance(worldPos, posRange.xyz), pb.max(posRange.w, 1e-6)), 0, 1);
1591
+ offset = pb.mul(offset, pb.mix(1, depthBiasParam.w, linearDepth));
1592
+ }
1593
+ // Guard against a degenerate interpolated normal rather than normalize()ing
1594
+ // a possibly-zero vector into NaN.
1595
+ const unitNormal = pb.mul(worldNormal, pb.div(1, pb.max(pb.length(worldNormal), 1e-6)));
1596
+ return pb.add(worldPos, pb.mul(unitNormal, offset));
1597
+ }
1598
+ /**
1599
+ * Calculates shadow of current fragment
1600
+ *
1601
+ * @param scope - Shader scope
1602
+ * @param NoL - NdotL vector
1603
+ * @returns Shadow of current fragment, 1 means no shadow and 0 means full shadowed.
1604
+ */ static calculateShadow(scope, worldPos, worldNormal, NoL, ctx) {
1300
1605
  const pb = scope.$builder;
1301
1606
  const that = this;
1302
1607
  const shadowMapParams = ctx.shadowMapInfo.get(ctx.currentShadowLight);
1303
1608
  const funcName = 'Z_calculateShadow';
1304
1609
  pb.func(funcName, [
1305
1610
  pb.vec3('worldPos'),
1611
+ pb.vec3('worldNormal'),
1306
1612
  pb.float('NoL')
1307
1613
  ], function() {
1308
1614
  if (shadowMapParams.numShadowCascades > 1) {
@@ -1313,18 +1619,21 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1313
1619
  this.$l.comparison = pb.vec4(pb.greaterThan(pb.vec4(this.linearDepth), this.splitDistances));
1314
1620
  this.$l.cascadeFlags = pb.vec4(pb.float(pb.greaterThan(this.shadowCascades, 0)), pb.float(pb.greaterThan(this.shadowCascades, 1)), pb.float(pb.greaterThan(this.shadowCascades, 2)), pb.float(pb.greaterThan(this.shadowCascades, 3)));
1315
1621
  this.$l.split = pb.int(pb.dot(this.comparison, this.cascadeFlags));
1622
+ // Normal offset bias: move the receiver out of the shadow texel that
1623
+ // would self-occlude it before projecting into shadow space. Scaled per
1624
+ // cascade, because each cascade has its own texel world size.
1625
+ this.$l.biasedPos = that.applyShadowNormalOffset(shadowMapParams.lightType, this, this.worldPos, this.worldNormal, this.NoL, that.getShadowCascadeBiasScale(this, this.split));
1316
1626
  if (ctx.device.type === 'webgl') {
1317
1627
  this.$l.shadowVertex = pb.vec4();
1318
1628
  this.$for(pb.int('cascade'), 0, 4, function() {
1319
1629
  this.$if(pb.equal(this.cascade, this.split), function() {
1320
- this.shadowVertex = that.calculateShadowSpaceVertex(this, pb.vec4(this.worldPos, 1), this.cascade);
1630
+ this.shadowVertex = that.calculateShadowSpaceVertex(this, pb.vec4(this.biasedPos, 1), this.cascade);
1321
1631
  this.$break();
1322
1632
  });
1323
1633
  });
1324
1634
  } else {
1325
- this.$l.shadowVertex = that.calculateShadowSpaceVertex(this, pb.vec4(this.worldPos, 1), this.split);
1635
+ this.$l.shadowVertex = that.calculateShadowSpaceVertex(this, pb.vec4(this.biasedPos, 1), this.split);
1326
1636
  }
1327
- const shadowMapParams = ctx.shadowMapInfo.get(ctx.currentShadowLight);
1328
1637
  this.$l.shadow = shadowMapParams.impl.computeShadowCSM(shadowMapParams, this, this.shadowVertex, this.NoL, this.split);
1329
1638
  this.shadow = pb.clamp(this.shadow, 0, 1);
1330
1639
  this.$l.shadowDistance = that.getShadowCameraParams(scope).w;
@@ -1336,8 +1645,9 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1336
1645
  });
1337
1646
  this.$return(this.shadow);
1338
1647
  } else {
1339
- this.$l.shadowVertex = that.calculateShadowSpaceVertex(this, pb.vec4(this.worldPos, 1));
1340
- const shadowMapParams = ctx.shadowMapInfo.get(ctx.currentShadowLight);
1648
+ // Normal offset bias - see the cascaded branch above.
1649
+ this.$l.biasedPos = that.applyShadowNormalOffset(shadowMapParams.lightType, this, this.worldPos, this.worldNormal, this.NoL);
1650
+ this.$l.shadowVertex = that.calculateShadowSpaceVertex(this, pb.vec4(this.biasedPos, 1));
1341
1651
  this.$l.shadow = shadowMapParams.impl.computeShadow(shadowMapParams, this, this.shadowVertex, this.NoL);
1342
1652
  this.shadow = pb.clamp(this.shadow, 0, 1);
1343
1653
  this.$l.shadowDistance = that.getShadowCameraParams(scope).w;
@@ -1347,7 +1657,7 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1347
1657
  this.$return(this.shadow);
1348
1658
  }
1349
1659
  });
1350
- return pb.getGlobalScope()[funcName](worldPos, NoL);
1660
+ return pb.getGlobalScope()[funcName](worldPos, worldNormal, NoL);
1351
1661
  }
1352
1662
  static applyFog(scope, worldPos, color, ctx) {
1353
1663
  const pb = scope.$builder;
@@ -1371,13 +1681,13 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1371
1681
  scope[funcName](worldPos, color);
1372
1682
  }
1373
1683
  }
1374
- /**
1375
- * Calculates the non-linear depth from linear depth
1376
- *
1377
- * @param scope - Current shader scope
1378
- * @param depth - The linear depth
1379
- * @param nearFar - A vector that contains the near clip plane in x component and the far clip plane in y component
1380
- * @returns The calculated non-linear depth
1684
+ /**
1685
+ * Calculates the non-linear depth from linear depth
1686
+ *
1687
+ * @param scope - Current shader scope
1688
+ * @param depth - The linear depth
1689
+ * @param nearFar - A vector that contains the near clip plane in x component and the far clip plane in y component
1690
+ * @returns The calculated non-linear depth
1381
1691
  */ static linearDepthToNonLinear(scope, depth, nearFar) {
1382
1692
  const pb = scope.$builder;
1383
1693
  nearFar = nearFar ?? this.getCameraParams(scope);
@@ -1386,13 +1696,13 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1386
1696
  }
1387
1697
  return pb.div(pb.sub(nearFar.y, pb.div(pb.mul(nearFar.x, nearFar.y), depth)), pb.sub(nearFar.y, nearFar.x));
1388
1698
  }
1389
- /**
1390
- * Calculates the linear depth from non-linear depth
1391
- *
1392
- * @param scope - Current shader scope
1393
- * @param depth - The non-linear depth
1394
- * @param nearFar - A vector that contains the near clip plane in x component and the far clip plane in y component
1395
- * @returns The calculated linear depth
1699
+ /**
1700
+ * Calculates the linear depth from non-linear depth
1701
+ *
1702
+ * @param scope - Current shader scope
1703
+ * @param depth - The non-linear depth
1704
+ * @param nearFar - A vector that contains the near clip plane in x component and the far clip plane in y component
1705
+ * @returns The calculated linear depth
1396
1706
  */ static nonLinearDepthToLinear(scope, depth, nearFar) {
1397
1707
  const pb = scope.$builder;
1398
1708
  nearFar = nearFar ?? this.getCameraParams(scope);
@@ -1401,13 +1711,13 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1401
1711
  }
1402
1712
  return pb.div(pb.mul(nearFar.x, nearFar.y), pb.mix(nearFar.y, nearFar.x, depth));
1403
1713
  }
1404
- /**
1405
- * Calculates the normalized linear depth from non-linear depth
1406
- *
1407
- * @param scope - Current shader scope
1408
- * @param depth - The non-linear depth
1409
- * @param nearFar - A vector that contains the near clip plane in x component and the far clip plane in y component
1410
- * @returns The calculated normalized linear depth
1714
+ /**
1715
+ * Calculates the normalized linear depth from non-linear depth
1716
+ *
1717
+ * @param scope - Current shader scope
1718
+ * @param depth - The non-linear depth
1719
+ * @param nearFar - A vector that contains the near clip plane in x component and the far clip plane in y component
1720
+ * @returns The calculated normalized linear depth
1411
1721
  */ static nonLinearDepthToLinearNormalized(scope, depth, nearFar) {
1412
1722
  const pb = scope.$builder;
1413
1723
  nearFar = nearFar ?? this.getCameraParams(scope);
@@ -1416,19 +1726,19 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1416
1726
  }
1417
1727
  return pb.div(nearFar.x, pb.mix(nearFar.y, nearFar.x, depth));
1418
1728
  }
1419
- /**
1420
- * Converts normalized linear depth back to device non-linear depth.
1421
- *
1422
- * @remarks
1423
- * The normalized linear depth convention (range [near/far, 1] with 1 at
1424
- * the far plane, as produced by {@link ShaderHelper.nonLinearDepthToLinearNormalized})
1425
- * is independent of the engine depth convention; only the mapping to
1426
- * device depth flips under reverse-Z.
1427
- *
1428
- * @param scope - Current shader scope
1429
- * @param depth - The normalized linear depth
1430
- * @param nearFar - A vector that contains the near clip plane in x component and the far clip plane in y component
1431
- * @returns The device non-linear depth
1729
+ /**
1730
+ * Converts normalized linear depth back to device non-linear depth.
1731
+ *
1732
+ * @remarks
1733
+ * The normalized linear depth convention (range [near/far, 1] with 1 at
1734
+ * the far plane, as produced by {@link ShaderHelper.nonLinearDepthToLinearNormalized})
1735
+ * is independent of the engine depth convention; only the mapping to
1736
+ * device depth flips under reverse-Z.
1737
+ *
1738
+ * @param scope - Current shader scope
1739
+ * @param depth - The normalized linear depth
1740
+ * @param nearFar - A vector that contains the near clip plane in x component and the far clip plane in y component
1741
+ * @returns The device non-linear depth
1432
1742
  */ static linearNormalizedToNonLinearDepth(scope, depth, nearFar) {
1433
1743
  const pb = scope.$builder;
1434
1744
  nearFar = nearFar ?? this.getCameraParams(scope);
@@ -1437,65 +1747,65 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1437
1747
  }
1438
1748
  return pb.div(pb.sub(pb.div(nearFar.x, depth), nearFar.y), pb.sub(nearFar.x, nearFar.y));
1439
1749
  }
1440
- /**
1441
- * Maps a device depth value to the canonical clip-space z coordinate
1442
- * (GL [-1,1] under standard-Z, zero-to-one under reverse-Z), suitable for
1443
- * unprojection with the engine's inverse projection matrices.
1444
- *
1445
- * @param scope - Current shader scope
1446
- * @param depth - The device depth value
1447
- * @returns The canonical clip-space z
1750
+ /**
1751
+ * Maps a device depth value to the canonical clip-space z coordinate
1752
+ * (GL [-1,1] under standard-Z, zero-to-one under reverse-Z), suitable for
1753
+ * unprojection with the engine's inverse projection matrices.
1754
+ *
1755
+ * @param scope - Current shader scope
1756
+ * @param depth - The device depth value
1757
+ * @returns The canonical clip-space z
1448
1758
  */ static deviceDepthToClipZ(scope, depth) {
1449
1759
  const pb = scope.$builder;
1450
1760
  return REVERSE_Z ? depth : pb.sub(pb.mul(depth, 2), 1);
1451
1761
  }
1452
- /**
1453
- * Clip-space z that places a vertex exactly on the far plane (sky domes,
1454
- * background geometry).
1455
- *
1456
- * @param scope - Current shader scope
1457
- * @param w - The clip-space w coordinate of the vertex
1458
- * @returns The clip-space z value for the far plane
1762
+ /**
1763
+ * Clip-space z that places a vertex exactly on the far plane (sky domes,
1764
+ * background geometry).
1765
+ *
1766
+ * @param scope - Current shader scope
1767
+ * @param w - The clip-space w coordinate of the vertex
1768
+ * @returns The clip-space z value for the far plane
1459
1769
  */ static farthestClipZ(scope, w) {
1460
1770
  return REVERSE_Z ? 0 : w;
1461
1771
  }
1462
- /**
1463
- * Tests whether a device depth value equals the cleared (farthest) depth,
1464
- * i.e. no geometry was rendered at this position.
1465
- *
1466
- * @param scope - Current shader scope
1467
- * @param depth - The device depth value
1468
- * @returns Boolean expression, true for background pixels
1772
+ /**
1773
+ * Tests whether a device depth value equals the cleared (farthest) depth,
1774
+ * i.e. no geometry was rendered at this position.
1775
+ *
1776
+ * @param scope - Current shader scope
1777
+ * @param depth - The device depth value
1778
+ * @returns Boolean expression, true for background pixels
1469
1779
  */ static isFarthestDepth(scope, depth) {
1470
1780
  return scope.$builder.equal(depth, DEPTH_FARTHEST);
1471
1781
  }
1472
- /**
1473
- * Selects the closer of two device depth values.
1474
- *
1475
- * @param scope - Current shader scope
1476
- * @param a - First device depth value
1477
- * @param b - Second device depth value
1478
- * @returns The value closer to the camera
1782
+ /**
1783
+ * Selects the closer of two device depth values.
1784
+ *
1785
+ * @param scope - Current shader scope
1786
+ * @param a - First device depth value
1787
+ * @param b - Second device depth value
1788
+ * @returns The value closer to the camera
1479
1789
  */ static closerDepth(scope, a, b) {
1480
1790
  return scope.$builder[DEPTH_REDUCE_CLOSER](a, b);
1481
1791
  }
1482
- /**
1483
- * Selects the farther of two device depth values.
1484
- *
1485
- * @param scope - Current shader scope
1486
- * @param a - First device depth value
1487
- * @param b - Second device depth value
1488
- * @returns The value farther from the camera
1792
+ /**
1793
+ * Selects the farther of two device depth values.
1794
+ *
1795
+ * @param scope - Current shader scope
1796
+ * @param a - First device depth value
1797
+ * @param b - Second device depth value
1798
+ * @returns The value farther from the camera
1489
1799
  */ static fartherDepth(scope, a, b) {
1490
1800
  return scope.$builder[DEPTH_REDUCE_FARTHER](a, b);
1491
1801
  }
1492
- /**
1493
- * Sample linear depth from linear depth texture
1494
- * @param scope - Current shader scope
1495
- * @param tex - The linear depth texture
1496
- * @param uv - The uv coordinates
1497
- * @param level - The mipmap level to sample
1498
- * @returns Linear depth value
1802
+ /**
1803
+ * Sample linear depth from linear depth texture
1804
+ * @param scope - Current shader scope
1805
+ * @param tex - The linear depth texture
1806
+ * @param uv - The uv coordinates
1807
+ * @param level - The mipmap level to sample
1808
+ * @returns Linear depth value
1499
1809
  */ static sampleLinearDepth(scope, tex, uv, level) {
1500
1810
  const pb = scope.$builder;
1501
1811
  const depth = pb.textureSampleLevel(tex, uv, level);
@@ -1517,23 +1827,23 @@ const UNIFORM_NAME_MORPH_INFO = 'Z_UniformMorphInfo';
1517
1827
  });
1518
1828
  return scope.zSamplePositionFromDepth(uv, cameraNearFar, mat);
1519
1829
  }
1520
- /**
1521
- * Sample linear depth from linear depth texture with backface
1522
- * @param scope - Current shader scope
1523
- * @param tex - The linear depth texture
1524
- * @param uv - The uv coordinates
1525
- * @param level - The mipmap level to sample
1526
- * @returns Linear depth value
1830
+ /**
1831
+ * Sample linear depth from linear depth texture with backface
1832
+ * @param scope - Current shader scope
1833
+ * @param tex - The linear depth texture
1834
+ * @param uv - The uv coordinates
1835
+ * @param level - The mipmap level to sample
1836
+ * @returns Linear depth value
1527
1837
  */ static sampleLinearDepthWithBackface(scope, tex, uv, level) {
1528
1838
  const pb = scope.$builder;
1529
1839
  return pb.textureSampleLevel(tex, uv, level).rg;
1530
1840
  }
1531
- /**
1532
- * Transform color to sRGB color space if nessesary
1533
- *
1534
- * @param scope - Current shader scope
1535
- * @param outputColor - The color to be transformed
1536
- * @returns The transformed color
1841
+ /**
1842
+ * Transform color to sRGB color space if nessesary
1843
+ *
1844
+ * @param scope - Current shader scope
1845
+ * @param outputColor - The color to be transformed
1846
+ * @returns The transformed color
1537
1847
  */ static encodeColorOutput(scope, outputColor) {
1538
1848
  const pb = scope.$builder;
1539
1849
  const that = this;