@zephyr3d/scene 0.9.21 → 0.9.23

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 (238) hide show
  1. package/dist/animation/joint_dynamics/collision.js +1 -7
  2. package/dist/animation/joint_dynamics/collision.js.map +1 -1
  3. package/dist/animation/joint_dynamics/constraints.js +2 -17
  4. package/dist/animation/joint_dynamics/constraints.js.map +1 -1
  5. package/dist/animation/joint_dynamics/controller.js +2 -20
  6. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  7. package/dist/animation/joint_dynamics/joint_dynamics_system.js +1 -3
  8. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  9. package/dist/animation/joint_dynamics/solver.js +14 -60
  10. package/dist/animation/joint_dynamics/solver.js.map +1 -1
  11. package/dist/animation/joint_dynamics/types.js +3 -3
  12. package/dist/animation/joint_dynamics/types.js.map +1 -1
  13. package/dist/app/app.js +23 -0
  14. package/dist/app/app.js.map +1 -1
  15. package/dist/app/scriptregistry.js +16 -53
  16. package/dist/app/scriptregistry.js.map +1 -1
  17. package/dist/asset/assetmanager.js +2 -0
  18. package/dist/asset/assetmanager.js.map +1 -1
  19. package/dist/asset/model.js +22 -0
  20. package/dist/asset/model.js.map +1 -1
  21. package/dist/blitter/bilateralblur.js +3 -1
  22. package/dist/blitter/bilateralblur.js.map +1 -1
  23. package/dist/blitter/blitter.js +3 -3
  24. package/dist/blitter/blitter.js.map +1 -1
  25. package/dist/camera/camera.js +398 -45
  26. package/dist/camera/camera.js.map +1 -1
  27. package/dist/camera/perspectivecamera.js +69 -2
  28. package/dist/camera/perspectivecamera.js.map +1 -1
  29. package/dist/index.d.ts +9100 -8712
  30. package/dist/index.js +9 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/material/blinn.js +1 -1
  33. package/dist/material/blinn.js.map +1 -1
  34. package/dist/material/lambert.js +3 -3
  35. package/dist/material/lambert.js.map +1 -1
  36. package/dist/material/material.js +8 -5
  37. package/dist/material/material.js.map +1 -1
  38. package/dist/material/meshmaterial.js +17 -10
  39. package/dist/material/meshmaterial.js.map +1 -1
  40. package/dist/material/mixins/lightmodel/blinnphong.js +1 -1
  41. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  42. package/dist/material/mixins/lightmodel/lambert.js +1 -1
  43. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  44. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +21 -6
  45. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  46. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +1 -1
  47. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  48. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +1 -1
  49. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  50. package/dist/material/mixins/lit.js +33 -11
  51. package/dist/material/mixins/lit.js.map +1 -1
  52. package/dist/material/mixins/pbr/brdf.js +44 -12
  53. package/dist/material/mixins/pbr/brdf.js.map +1 -1
  54. package/dist/material/mixins/pbr/common.js +149 -14
  55. package/dist/material/mixins/pbr/common.js.map +1 -1
  56. package/dist/material/mtoon.js +7 -4
  57. package/dist/material/mtoon.js.map +1 -1
  58. package/dist/material/pbrblueprint.js +5 -1
  59. package/dist/material/pbrblueprint.js.map +1 -1
  60. package/dist/material/pbrmr.js +1 -1
  61. package/dist/material/pbrmr.js.map +1 -1
  62. package/dist/material/pbrsg.js +1 -1
  63. package/dist/material/pbrsg.js.map +1 -1
  64. package/dist/material/shader/helper.js +186 -11
  65. package/dist/material/shader/helper.js.map +1 -1
  66. package/dist/material/skin.js +2 -2
  67. package/dist/material/skin.js.map +1 -1
  68. package/dist/material/terrain-cm.js +5 -5
  69. package/dist/material/terrain-cm.js.map +1 -1
  70. package/dist/material/water.js +6 -4
  71. package/dist/material/water.js.map +1 -1
  72. package/dist/posteffect/bloom.js +54 -4
  73. package/dist/posteffect/bloom.js.map +1 -1
  74. package/dist/posteffect/compositor.js +41 -0
  75. package/dist/posteffect/compositor.js.map +1 -1
  76. package/dist/posteffect/posteffect.js +51 -0
  77. package/dist/posteffect/posteffect.js.map +1 -1
  78. package/dist/posteffect/sao.js +7 -6
  79. package/dist/posteffect/sao.js.map +1 -1
  80. package/dist/posteffect/skinsss.js +3 -3
  81. package/dist/posteffect/skinsss.js.map +1 -1
  82. package/dist/posteffect/ssgi.js +1196 -0
  83. package/dist/posteffect/ssgi.js.map +1 -0
  84. package/dist/posteffect/ssr.js +83 -45
  85. package/dist/posteffect/ssr.js.map +1 -1
  86. package/dist/posteffect/sss.js +137 -49
  87. package/dist/posteffect/sss.js.map +1 -1
  88. package/dist/posteffect/taa.js +5 -4
  89. package/dist/posteffect/taa.js.map +1 -1
  90. package/dist/posteffect/tonemap.js +15 -3
  91. package/dist/posteffect/tonemap.js.map +1 -1
  92. package/dist/render/abuffer_oit.js +2 -2
  93. package/dist/render/abuffer_oit.js.map +1 -1
  94. package/dist/render/cluster_light.js +12 -6
  95. package/dist/render/cluster_light.js.map +1 -1
  96. package/dist/render/drawable_mixin.js +8 -5
  97. package/dist/render/drawable_mixin.js.map +1 -1
  98. package/dist/render/dualdepthpeeling_oit.js +10 -5
  99. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  100. package/dist/render/envlight.js +298 -5
  101. package/dist/render/envlight.js.map +1 -1
  102. package/dist/render/gpu_picking.js +7 -5
  103. package/dist/render/gpu_picking.js.map +1 -1
  104. package/dist/render/hzb.js +28 -19
  105. package/dist/render/hzb.js.map +1 -1
  106. package/dist/render/lightpass.js +18 -4
  107. package/dist/render/lightpass.js.map +1 -1
  108. package/dist/render/render_queue.js +37 -4
  109. package/dist/render/render_queue.js.map +1 -1
  110. package/dist/render/renderer.js +8 -3
  111. package/dist/render/renderer.js.map +1 -1
  112. package/dist/render/rendergraph/blackboard.js +20 -48
  113. package/dist/render/rendergraph/blackboard.js.map +1 -1
  114. package/dist/render/rendergraph/device_pool_allocator.js +18 -58
  115. package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
  116. package/dist/render/rendergraph/executor.js +89 -115
  117. package/dist/render/rendergraph/executor.js.map +1 -1
  118. package/dist/render/rendergraph/forward_plus_builder.js +1003 -715
  119. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  120. package/dist/render/rendergraph/frame_graph_context.js +41 -0
  121. package/dist/render/rendergraph/frame_graph_context.js.map +1 -0
  122. package/dist/render/rendergraph/frame_resource_requirements.js +14 -0
  123. package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -0
  124. package/dist/render/rendergraph/history_resource_manager.js +74 -140
  125. package/dist/render/rendergraph/history_resource_manager.js.map +1 -1
  126. package/dist/render/rendergraph/history_resources.js +7 -5
  127. package/dist/render/rendergraph/history_resources.js.map +1 -1
  128. package/dist/render/rendergraph/render_context.js +19 -0
  129. package/dist/render/rendergraph/render_context.js.map +1 -0
  130. package/dist/render/rendergraph/render_pipeline.js +315 -0
  131. package/dist/render/rendergraph/render_pipeline.js.map +1 -0
  132. package/dist/render/rendergraph/rendergraph.js +158 -141
  133. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  134. package/dist/render/rendergraph/scene_render_context.js +267 -0
  135. package/dist/render/rendergraph/scene_render_context.js.map +1 -0
  136. package/dist/render/rendergraph/texture_affinity_cache.js +19 -0
  137. package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -0
  138. package/dist/render/rendergraph/types.js +19 -31
  139. package/dist/render/rendergraph/types.js.map +1 -1
  140. package/dist/render/renderpass.js +3 -3
  141. package/dist/render/renderpass.js.map +1 -1
  142. package/dist/render/shadow_mask_pass.js +10 -6
  143. package/dist/render/shadow_mask_pass.js.map +1 -1
  144. package/dist/render/sky.js +197 -35
  145. package/dist/render/sky.js.map +1 -1
  146. package/dist/render/weightedblended_oit.js +4 -3
  147. package/dist/render/weightedblended_oit.js.map +1 -1
  148. package/dist/scene/basesprite.js +2 -0
  149. package/dist/scene/basesprite.js.map +1 -1
  150. package/dist/scene/batchgroup.js +10 -0
  151. package/dist/scene/batchgroup.js.map +1 -1
  152. package/dist/scene/environment.js +61 -2
  153. package/dist/scene/environment.js.map +1 -1
  154. package/dist/scene/light.js +178 -10
  155. package/dist/scene/light.js.map +1 -1
  156. package/dist/scene/meshdrawable.js +89 -2
  157. package/dist/scene/meshdrawable.js.map +1 -1
  158. package/dist/scene/msdftext.js +2 -0
  159. package/dist/scene/msdftext.js.map +1 -1
  160. package/dist/scene/msdftextsprite.js +2 -0
  161. package/dist/scene/msdftextsprite.js.map +1 -1
  162. package/dist/scene/particlesys.js +2 -0
  163. package/dist/scene/particlesys.js.map +1 -1
  164. package/dist/scene/scene.js +34 -1
  165. package/dist/scene/scene.js.map +1 -1
  166. package/dist/scene/terrain-cm/grass.js +2 -1
  167. package/dist/scene/terrain-cm/grass.js.map +1 -1
  168. package/dist/scene/terrain-cm/terrain-cm.js +2 -0
  169. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  170. package/dist/scene/water.js +2 -0
  171. package/dist/scene/water.js.map +1 -1
  172. package/dist/shaders/atmosphere.js +5 -4
  173. package/dist/shaders/atmosphere.js.map +1 -1
  174. package/dist/shaders/fog.js +10 -3
  175. package/dist/shaders/fog.js.map +1 -1
  176. package/dist/shaders/shadow.js +96 -29
  177. package/dist/shaders/shadow.js.map +1 -1
  178. package/dist/shaders/ssgi.js +63 -0
  179. package/dist/shaders/ssgi.js.map +1 -0
  180. package/dist/shaders/ssgi_reprojection.js +118 -0
  181. package/dist/shaders/ssgi_reprojection.js.map +1 -0
  182. package/dist/shaders/ssr.js +322 -154
  183. package/dist/shaders/ssr.js.map +1 -1
  184. package/dist/shadow/esm.js +13 -8
  185. package/dist/shadow/esm.js.map +1 -1
  186. package/dist/shadow/pcf_opt.js +15 -12
  187. package/dist/shadow/pcf_opt.js.map +1 -1
  188. package/dist/shadow/pcf_pd.js +15 -12
  189. package/dist/shadow/pcf_pd.js.map +1 -1
  190. package/dist/shadow/pcss.js +17 -11
  191. package/dist/shadow/pcss.js.map +1 -1
  192. package/dist/shadow/shadow_impl.js +20 -2
  193. package/dist/shadow/shadow_impl.js.map +1 -1
  194. package/dist/shadow/shadowmapper.js +10 -3
  195. package/dist/shadow/shadowmapper.js.map +1 -1
  196. package/dist/shadow/ssm.js +16 -13
  197. package/dist/shadow/ssm.js.map +1 -1
  198. package/dist/shadow/vsm.js +10 -8
  199. package/dist/shadow/vsm.js.map +1 -1
  200. package/dist/utility/physical.js +74 -0
  201. package/dist/utility/physical.js.map +1 -0
  202. package/dist/utility/rendermipmap.js +2 -2
  203. package/dist/utility/rendermipmap.js.map +1 -1
  204. package/dist/utility/serialization/manager.js +3 -1
  205. package/dist/utility/serialization/manager.js.map +1 -1
  206. package/dist/utility/serialization/scene/animation.js +3 -1
  207. package/dist/utility/serialization/scene/animation.js.map +1 -1
  208. package/dist/utility/serialization/scene/batch.js +3 -1
  209. package/dist/utility/serialization/scene/batch.js.map +1 -1
  210. package/dist/utility/serialization/scene/camera.js +598 -2
  211. package/dist/utility/serialization/scene/camera.js.map +1 -1
  212. package/dist/utility/serialization/scene/light.js +180 -9
  213. package/dist/utility/serialization/scene/light.js.map +1 -1
  214. package/dist/utility/serialization/scene/material.js +43 -3
  215. package/dist/utility/serialization/scene/material.js.map +1 -1
  216. package/dist/utility/serialization/scene/mesh.js +3 -1
  217. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  218. package/dist/utility/serialization/scene/node.js +3 -1
  219. package/dist/utility/serialization/scene/node.js.map +1 -1
  220. package/dist/utility/serialization/scene/particle.js +3 -1
  221. package/dist/utility/serialization/scene/particle.js.map +1 -1
  222. package/dist/utility/serialization/scene/primitive.js +2 -0
  223. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  224. package/dist/utility/serialization/scene/scene.js +70 -0
  225. package/dist/utility/serialization/scene/scene.js.map +1 -1
  226. package/dist/utility/serialization/scene/sprite.js +3 -1
  227. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  228. package/dist/utility/serialization/scene/terrain.js +3 -1
  229. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  230. package/dist/utility/serialization/scene/text.js +3 -0
  231. package/dist/utility/serialization/scene/text.js.map +1 -1
  232. package/dist/utility/serialization/scene/water.js +3 -1
  233. package/dist/utility/serialization/scene/water.js.map +1 -1
  234. package/dist/values.js +2 -3
  235. package/dist/values.js.map +1 -1
  236. package/package.json +3 -3
  237. package/dist/animation/joint_dynamics/convex_collider.js +0 -320
  238. package/dist/animation/joint_dynamics/convex_collider.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { Vector2, Vector4, Matrix4x4 } from '@zephyr3d/base';
1
+ import { Vector2, Vector4, Matrix4x4, DEPTH_COMPARE_FARTHER, DEPTH_FARTHEST } from '@zephyr3d/base';
2
2
  import { ShaderHelper } from '../material/shader/helper.js';
3
3
  import '../material/lambert.js';
4
4
  import '../material/blinn.js';
@@ -79,9 +79,32 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
79
79
  }
80
80
  return null;
81
81
  }
82
+ /**
83
+ * Whether the combine pass should read the screen-space shadow mask for the
84
+ * combine light instead of recomputing its shadow. Enabled when the camera
85
+ * renders the mask and the combine light was actually written into it: the
86
+ * mask already holds the identical shadow — same shadow impl (PCSS/VSM/…),
87
+ * distance fade and shadowStrength — so this skips a full redundant per-pixel
88
+ * shadow evaluation. Shadow mode is irrelevant: the mask is rendered with the
89
+ * light's own impl. Falls back to the inline path when the light is not in the
90
+ * mask (e.g. it overflows {@link MAX_SHADOW_MASK_LIGHTS}).
91
+ *
92
+ * Only valid at execute time, after the ShadowMaps and ShadowMask passes have
93
+ * populated shadowMapInfo / maskOrdinal.
94
+ */ useCombineShadowMask(ctx, shadowLight) {
95
+ if (!shadowLight || !ctx.screenSpaceShadowMask) {
96
+ return false;
97
+ }
98
+ const shadowMapParams = ctx.shadowMapInfo?.get(shadowLight);
99
+ return !!(shadowMapParams?.shadowMap && shadowMapParams.impl && shadowMapParams.maskOrdinal != null);
100
+ }
82
101
  getCombineProgramKey(ctx, shadowLight) {
83
102
  const shadowMapParams = shadowLight ? ctx.shadowMapInfo?.get(shadowLight) : null;
84
- return shadowMapParams?.shaderHash ? `shadow:${shadowMapParams.shaderHash}` : 'default';
103
+ if (!shadowMapParams?.shaderHash) {
104
+ return 'default';
105
+ }
106
+ // The mask-sampling shader is independent of the PCSS impl hash.
107
+ return this.useCombineShadowMask(ctx, shadowLight) ? 'shadowMask' : `shadow:${shadowMapParams.shaderHash}`;
85
108
  }
86
109
  requireLinearDepthTexture() {
87
110
  return true;
@@ -89,6 +112,17 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
89
112
  requireDepthAttachment() {
90
113
  return true;
91
114
  }
115
+ requireSceneNormalTexture() {
116
+ return true;
117
+ }
118
+ requireShadowMask(ctx) {
119
+ // Runs at graph-build time, before shadowMapInfo is populated (that happens
120
+ // during the ShadowMaps pass execute), so it cannot inspect the combine
121
+ // light. Keep the mask alive whenever the camera renders it; combine()
122
+ // decides at execute time whether to sample it, and _setupFromApply only
123
+ // reads the handle when the mask was actually produced this frame.
124
+ return !!ctx.screenSpaceShadowMask;
125
+ }
92
126
  apply(ctx, inputColorTexture, sceneDepthTexture, srgbOutput) {
93
127
  const device = ctx.device;
94
128
  const outputFramebuffer = device.getFramebuffer();
@@ -130,8 +164,8 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
130
164
  }
131
165
  blur(ctx, inputColorTexture, sceneDepthTexture, profileLUT, targetWidth, targetHeight, kernelRadius, horizontal, sssSettings) {
132
166
  const device = ctx.device;
133
- const sssNormalTexture = ctx.SSRNormalTexture ?? ctx.SSSParamTexture;
134
- const hasNormalTexture = !!ctx.SSRNormalTexture;
167
+ const sssNormalTexture = ctx.SceneNormalTexture ?? ctx.SSSParamTexture;
168
+ const hasNormalTexture = !!ctx.SceneNormalTexture;
135
169
  const key = `${horizontal ? 'h' : 'v'}:${kernelRadius}`;
136
170
  let program = SSS._blurPrograms[key];
137
171
  if (!program) {
@@ -164,10 +198,10 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
164
198
  }
165
199
  combine(ctx, inputColorTexture, blurredTexture, sceneDepthTexture, profileLUT, srgbOutput) {
166
200
  const device = ctx.device;
167
- const sssRoughnessTexture = ctx.SSRRoughnessTexture ?? ctx.SSSParamTexture;
168
- const sssNormalTexture = ctx.SSRNormalTexture ?? ctx.SSSParamTexture;
169
- const hasRoughnessTexture = !!ctx.SSRRoughnessTexture;
170
- const hasNormalTexture = !!ctx.SSRNormalTexture;
201
+ const sssRoughnessTexture = ctx.SceneRoughnessTexture ?? ctx.SSSParamTexture;
202
+ const sssNormalTexture = ctx.SceneNormalTexture ?? ctx.SSSParamTexture;
203
+ const hasRoughnessTexture = !!ctx.SceneRoughnessTexture;
204
+ const hasNormalTexture = !!ctx.SceneNormalTexture;
171
205
  const shadowLight = this.getCombineShadowLight(ctx);
172
206
  const programKey = this.getCombineProgramKey(ctx, shadowLight);
173
207
  let program = SSS._combinePrograms[programKey];
@@ -187,7 +221,7 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
187
221
  y: 0,
188
222
  z: 1
189
223
  };
190
- const sunColorIntensity = mainTransmissionLight ? mainTransmissionLight.diffuseAndIntensity : {
224
+ const sunColorIntensity = mainTransmissionLight ? ShaderHelper.getPreExposedColorIntensity(mainTransmissionLight, ctx) : {
191
225
  x: 1,
192
226
  y: 1,
193
227
  z: 1,
@@ -257,37 +291,56 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
257
291
  bindGroup.setValue('srgbOut', srgbOutput ? 1 : 0);
258
292
  if (shadowLight) {
259
293
  const shadowMapParams = ctx.shadowMapInfo.get(shadowLight);
260
- const cameraPos = ctx.camera.getWorldPosition();
261
- bindGroup.setValue('camera', {
262
- position: new Vector4(cameraPos.x, cameraPos.y, cameraPos.z, 0),
263
- params: new Vector4(ctx.camera.getNearPlane(), ctx.camera.getFarPlane(), 1, 1),
264
- shadowDebugCascades: ctx.camera.shadowDebugCascades ? 1 : 0
265
- });
266
- bindGroup.setValue('light', {
267
- sunDir: new Float32Array([
268
- sunDir.x,
269
- sunDir.y,
270
- sunDir.z
271
- ]),
272
- envLightStrength: ctx.env?.light.strength ?? 0,
273
- envLightSpecularStrength: ctx.env?.light.specularStrength ?? 1,
274
- shadowStrength: shadowLight.shadow.shadowStrength,
275
- shadowCascades: shadowMapParams.numShadowCascades,
276
- positionAndRange: shadowLight.positionAndRange,
277
- directionAndCutoff: shadowLight.directionAndCutoff,
278
- diffuseAndIntensity: shadowLight.diffuseAndIntensity,
279
- extraParams: shadowLight.extraParams,
280
- cascadeDistances: shadowMapParams.cascadeDistances,
281
- depthBiasValues: shadowMapParams.depthBiasValues[0],
282
- shadowCameraParams: shadowMapParams.cameraParams,
283
- depthBiasScales: shadowMapParams.depthBiasScales,
284
- shadowMatrices: new Float32Array(shadowMapParams.shadowMatrices)
285
- });
286
- bindGroup.setTexture('Z_UniformShadowMap', shadowMapParams.shadowMap, shadowMapParams.shadowMapSampler);
294
+ if (this.useCombineShadowMask(ctx, shadowLight)) {
295
+ // Screen-space shadow mask path: bind the mask array and select this
296
+ // light's layer/channel from its recorded ordinal (layer = s>>2,
297
+ // channel = s&3). A missing mask falls back to the fully-lit dummy.
298
+ const ordinal = shadowMapParams.maskOrdinal ?? 0;
299
+ const layer = ordinal >> 2;
300
+ const channel = ordinal & 3;
301
+ bindGroup.setValue('shadowMaskLayer', layer);
302
+ bindGroup.setValue('shadowMaskChannel', new Float32Array([
303
+ channel === 0 ? 1 : 0,
304
+ channel === 1 ? 1 : 0,
305
+ channel === 2 ? 1 : 0,
306
+ channel === 3 ? 1 : 0
307
+ ]));
308
+ bindGroup.setTexture('Z_UniformShadowMask', ctx.shadowMaskTexture ?? ShaderHelper.getDummyShadowMask(device), fetchSampler('clamp_nearest'));
309
+ } else {
310
+ const cameraPos = ctx.camera.getWorldPosition();
311
+ bindGroup.setValue('camera', {
312
+ position: new Vector4(cameraPos.x, cameraPos.y, cameraPos.z, 0),
313
+ params: new Vector4(ctx.camera.getNearPlane(), ctx.camera.getFarPlane(), 1, 1),
314
+ shadowDebugCascades: ctx.camera.shadowDebugCascades ? 1 : 0,
315
+ framestamp: ctx.device.frameInfo.frameCounter
316
+ });
317
+ bindGroup.setValue('light', {
318
+ sunDir: new Float32Array([
319
+ sunDir.x,
320
+ sunDir.y,
321
+ sunDir.z
322
+ ]),
323
+ envLightStrength: ShaderHelper.getEnvLightLuminance(ctx),
324
+ envLightSpecularStrength: ctx.env?.light.specularStrength ?? 1,
325
+ shadowStrength: shadowLight.shadow.shadowStrength,
326
+ shadowCascades: shadowMapParams.numShadowCascades,
327
+ positionAndRange: shadowLight.positionAndRange,
328
+ directionAndCutoff: shadowLight.directionAndCutoff,
329
+ diffuseAndIntensity: ShaderHelper.getPreExposedColorIntensity(shadowLight, ctx),
330
+ extraParams: shadowLight.extraParams,
331
+ implParams: shadowMapParams.impl.getParams(),
332
+ cascadeDistances: shadowMapParams.cascadeDistances,
333
+ depthBiasValues: shadowMapParams.depthBiasValues[0],
334
+ shadowCameraParams: shadowMapParams.cameraParams,
335
+ depthBiasScales: shadowMapParams.depthBiasScales,
336
+ shadowMatrices: new Float32Array(shadowMapParams.shadowMatrices)
337
+ });
338
+ bindGroup.setTexture('Z_UniformShadowMap', shadowMapParams.shadowMap, shadowMapParams.shadowMapSampler);
339
+ }
287
340
  }
288
341
  device.setProgram(program);
289
342
  device.setBindGroup(0, bindGroup);
290
- this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, 'gt'));
343
+ this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, DEPTH_COMPARE_FARTHER));
291
344
  }
292
345
  createBlurProgram(ctx, kernelRadius, horizontal) {
293
346
  const sampleCount = Math.max(1, Math.ceil(kernelRadius / 2));
@@ -297,7 +350,7 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
297
350
  this.$inputs.pos = pb.vec2().attrib('position');
298
351
  this.$outputs.uv = pb.vec2();
299
352
  pb.main(function() {
300
- this.$builtins.position = pb.vec4(this.$inputs.pos, 1, 1);
353
+ this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
301
354
  this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
302
355
  this.$if(pb.notEqual(this.flip, 0), function() {
303
356
  this.$builtins.position.y = pb.neg(this.$builtins.position.y);
@@ -490,13 +543,18 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
490
543
  createCombineProgram(ctx, shadowLight) {
491
544
  const shadowMapParams = shadowLight ? ctx.shadowMapInfo?.get(shadowLight) : null;
492
545
  const shadowEnabled = !!(shadowLight && shadowMapParams?.shadowMap && shadowMapParams.impl);
546
+ // When the screen-space shadow mask is available for the combine light,
547
+ // sample it instead of recomputing PCSS (which also avoids the dpdx-in-
548
+ // non-uniform-control-flow constraint entirely on this path).
549
+ const useShadowMask = this.useCombineShadowMask(ctx, shadowLight);
550
+ const pcssEnabled = shadowEnabled && !useShadowMask;
493
551
  const program = ctx.device.buildRenderProgram({
494
552
  vertex (pb) {
495
553
  this.flip = pb.int().uniform(0);
496
554
  this.$inputs.pos = pb.vec2().attrib('position');
497
555
  this.$outputs.uv = pb.vec2();
498
556
  pb.main(function() {
499
- this.$builtins.position = pb.vec4(this.$inputs.pos, 1, 1);
557
+ this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
500
558
  this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
501
559
  this.$if(pb.notEqual(this.flip, 0), function() {
502
560
  this.$builtins.position.y = pb.neg(this.$builtins.position.y);
@@ -504,11 +562,12 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
504
562
  });
505
563
  },
506
564
  fragment (pb) {
507
- if (shadowEnabled) {
565
+ if (pcssEnabled) {
508
566
  const cameraStruct = pb.defineStruct([
509
567
  pb.vec4('position'),
510
568
  pb.vec4('params'),
511
- pb.float('shadowDebugCascades')
569
+ pb.float('shadowDebugCascades'),
570
+ pb.int('framestamp')
512
571
  ]);
513
572
  const lightStruct = pb.defineStruct([
514
573
  pb.vec3('sunDir'),
@@ -522,6 +581,7 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
522
581
  pb.vec4('extraParams'),
523
582
  pb.vec4('cascadeDistances'),
524
583
  pb.vec4('depthBiasValues'),
584
+ pb.vec4('implParams'),
525
585
  pb.vec4('shadowCameraParams'),
526
586
  pb.vec4('depthBiasScales'),
527
587
  pb.vec4[16]('shadowMatrices')
@@ -535,6 +595,14 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
535
595
  }
536
596
  this.Z_UniformShadowMap = shadowTex.uniform(0);
537
597
  }
598
+ if (useShadowMask) {
599
+ // Screen-space shadow mask: one RGBA8 array layer per 4 lights. The
600
+ // combine light's visibility is one channel of one layer, selected via
601
+ // the layer index and a one-hot channel selector bound at draw time.
602
+ this.Z_UniformShadowMask = pb.tex2DArray().uniform(0);
603
+ this.shadowMaskLayer = pb.int().uniform(0);
604
+ this.shadowMaskChannel = pb.vec4().uniform(0);
605
+ }
538
606
  this.colorTex = pb.tex2D().uniform(0);
539
607
  this.diffuseTex = pb.tex2D().uniform(0);
540
608
  this.blurTex = pb.tex2D().uniform(0);
@@ -676,8 +744,8 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
676
744
  pb.float('linearDepth')
677
745
  ], function() {
678
746
  this.$l.viewDepth = pb.max(pb.mul(this.linearDepth, this.cameraNearFar.y), 1e-5);
679
- this.$l.nonLinearDepth = pb.div(pb.sub(pb.div(pb.mul(this.cameraNearFar.x, this.cameraNearFar.y), this.viewDepth), this.cameraNearFar.y), pb.sub(this.cameraNearFar.x, this.cameraNearFar.y));
680
- this.$l.clipPos = pb.vec4(pb.sub(pb.mul(this.uv, 2), pb.vec2(1)), pb.sub(pb.mul(pb.clamp(this.nonLinearDepth, 0, 1), 2), 1), 1);
747
+ this.$l.nonLinearDepth = ShaderHelper.linearDepthToNonLinear(this, this.viewDepth, this.cameraNearFar);
748
+ this.$l.clipPos = pb.vec4(pb.sub(pb.mul(this.uv, 2), pb.vec2(1)), ShaderHelper.deviceDepthToClipZ(this, pb.clamp(this.nonLinearDepth, 0, 1)), 1);
681
749
  this.$l.viewPos = pb.mul(this.invProjMatrix, this.clipPos);
682
750
  this.$return(pb.div(this.viewPos.xyz, pb.max(this.viewPos.w, 1e-5)));
683
751
  });
@@ -708,7 +776,7 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
708
776
  this.spotFactor = pb.smoothStep(this.dirCutoff.w, pb.mix(this.dirCutoff.w, 1, 0.5), this.spotFactor);
709
777
  this.$return(pb.mul(this.spotFactor, this.falloff, this.falloff));
710
778
  });
711
- if (shadowEnabled) {
779
+ if (pcssEnabled) {
712
780
  pb.func('calculateTransmissionShadow', [
713
781
  pb.vec3('worldPos'),
714
782
  pb.float('depth01'),
@@ -776,6 +844,28 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
776
844
  this.$l.profilePreset = this.readProfilePreset(this.param);
777
845
  this.$l.depth01 = this.readDepth01(this.uv);
778
846
  this.$l.result = this.baseColor.rgb;
847
+ // Transmission shadow. On the PCSS path the shadow uses implicit
848
+ // derivatives (dpdx) inside the impl; WGSL requires those in uniform
849
+ // control flow, so it is computed here rather than inside the
850
+ // profile-active branch below (a per-pixel, non-uniform branch). On the
851
+ // shadow-mask path the value is a single texture fetch (no PCSS).
852
+ this.$l.transmissionShadowRaw = pb.float(1);
853
+ this.$l.transmissionShadow = pb.float(1);
854
+ if (pcssEnabled) {
855
+ this.$l.shadowNormalWS = this.decodeNormal(this.uv);
856
+ this.$l.shadowViewPos = this.reconstructViewPos(this.uv, this.depth01);
857
+ this.$l.shadowWorldPos = pb.mul(this.invViewMatrix, pb.vec4(this.shadowViewPos, 1)).xyz;
858
+ this.$l.shadowSunDirWS = this.calculateMainLightDirection(this.shadowWorldPos, this.mainLightType, this.mainLightPosRange, this.mainLightDirCutoff, this.sunDir);
859
+ this.transmissionShadowRaw = this.calculateTransmissionShadow(this.shadowWorldPos, this.depth01, pb.clamp(pb.abs(pb.dot(this.shadowNormalWS, this.shadowSunDirWS)), 0, 1));
860
+ this.transmissionShadow = this.transmissionShadowRaw;
861
+ } else if (useShadowMask) {
862
+ // The mask already holds the identical shadow (same PCSS + distance
863
+ // fade + shadowStrength) rendered by the ShadowMaskPass; read the
864
+ // combine light's channel. Sample by screen UV, matching how the
865
+ // other screen-space inputs (color/depth) are sampled above.
866
+ this.transmissionShadowRaw = pb.dot(pb.textureArraySampleLevel(this.Z_UniformShadowMask, this.uv, this.shadowMaskLayer, 0), this.shadowMaskChannel);
867
+ this.transmissionShadow = this.transmissionShadowRaw;
868
+ }
779
869
  this.$if(pb.and(pb.and(pb.and(pb.greaterThan(this.profileStrength, 1e-4), pb.greaterThan(this.profileSlot, 0)), this.profileActive), pb.lessThan(this.depth01, 1)), function() {
780
870
  this.$l.widthStrength = pb.clamp(this.param.g, 0, 0.999);
781
871
  this.$l.profileSettingsA = this.readProfileSettingsA(this.profileSlot);
@@ -819,11 +909,9 @@ import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
819
909
  this.$l.sunDirWS = this.calculateMainLightDirection(this.worldPos, this.mainLightType, this.mainLightPosRange, this.mainLightDirCutoff, this.sunDir);
820
910
  this.$l.mainLightAttenuation = this.calculateMainLightAttenuation(this.worldPos, this.mainLightType, this.mainLightPosRange, this.mainLightDirCutoff);
821
911
  this.$l.frontLit = pb.mul(pb.dot(this.normalWS, this.sunDirWS), this.mainLightAttenuation);
822
- this.$l.transmissionShadowRaw = pb.float(1);
823
- this.$l.transmissionShadow = pb.float(1);
824
- if (shadowEnabled) {
825
- this.transmissionShadowRaw = this.calculateTransmissionShadow(this.worldPos, this.depth01, pb.clamp(pb.abs(pb.dot(this.normalWS, this.sunDirWS)), 0, 1));
826
- }
912
+ // transmissionShadowRaw/transmissionShadow were computed in uniform
913
+ // control flow at the top of main (PCSS sampling uses dpdx, which
914
+ // WGSL forbids inside this non-uniform branch); reuse them here.
827
915
  this.transmissionShadow = this.transmissionShadowRaw;
828
916
  this.$l.transmissionLightAttenuation = pb.mul(this.mainLightAttenuation, this.transmissionShadow);
829
917
  this.$l.thinnessComponents = this.estimateScreenThinnessComponents(this.uv, this.depth01, this.normalWS, this.radiusResponse);