@zephyr3d/scene 0.9.22 → 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 (234) 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 +9113 -8734
  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 +35 -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 +988 -436
  83. package/dist/posteffect/ssgi.js.map +1 -1
  84. package/dist/posteffect/ssr.js +83 -45
  85. package/dist/posteffect/ssr.js.map +1 -1
  86. package/dist/posteffect/sss.js +18 -15
  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 +1 -1
  121. package/dist/render/rendergraph/frame_graph_context.js.map +1 -1
  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 +7 -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_reprojection.js +118 -0
  179. package/dist/shaders/ssgi_reprojection.js.map +1 -0
  180. package/dist/shaders/ssr.js +322 -154
  181. package/dist/shaders/ssr.js.map +1 -1
  182. package/dist/shadow/esm.js +10 -7
  183. package/dist/shadow/esm.js.map +1 -1
  184. package/dist/shadow/pcf_opt.js +12 -12
  185. package/dist/shadow/pcf_opt.js.map +1 -1
  186. package/dist/shadow/pcf_pd.js +12 -12
  187. package/dist/shadow/pcf_pd.js.map +1 -1
  188. package/dist/shadow/pcss.js +15 -11
  189. package/dist/shadow/pcss.js.map +1 -1
  190. package/dist/shadow/shadow_impl.js +20 -2
  191. package/dist/shadow/shadow_impl.js.map +1 -1
  192. package/dist/shadow/shadowmapper.js +5 -2
  193. package/dist/shadow/shadowmapper.js.map +1 -1
  194. package/dist/shadow/ssm.js +13 -13
  195. package/dist/shadow/ssm.js.map +1 -1
  196. package/dist/shadow/vsm.js +7 -7
  197. package/dist/shadow/vsm.js.map +1 -1
  198. package/dist/utility/physical.js +74 -0
  199. package/dist/utility/physical.js.map +1 -0
  200. package/dist/utility/rendermipmap.js +2 -2
  201. package/dist/utility/rendermipmap.js.map +1 -1
  202. package/dist/utility/serialization/manager.js +3 -1
  203. package/dist/utility/serialization/manager.js.map +1 -1
  204. package/dist/utility/serialization/scene/animation.js +3 -1
  205. package/dist/utility/serialization/scene/animation.js.map +1 -1
  206. package/dist/utility/serialization/scene/batch.js +3 -1
  207. package/dist/utility/serialization/scene/batch.js.map +1 -1
  208. package/dist/utility/serialization/scene/camera.js +598 -2
  209. package/dist/utility/serialization/scene/camera.js.map +1 -1
  210. package/dist/utility/serialization/scene/light.js +180 -9
  211. package/dist/utility/serialization/scene/light.js.map +1 -1
  212. package/dist/utility/serialization/scene/material.js +43 -3
  213. package/dist/utility/serialization/scene/material.js.map +1 -1
  214. package/dist/utility/serialization/scene/mesh.js +3 -1
  215. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  216. package/dist/utility/serialization/scene/node.js +3 -1
  217. package/dist/utility/serialization/scene/node.js.map +1 -1
  218. package/dist/utility/serialization/scene/particle.js +3 -1
  219. package/dist/utility/serialization/scene/particle.js.map +1 -1
  220. package/dist/utility/serialization/scene/primitive.js +2 -0
  221. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  222. package/dist/utility/serialization/scene/scene.js +70 -0
  223. package/dist/utility/serialization/scene/scene.js.map +1 -1
  224. package/dist/utility/serialization/scene/sprite.js +3 -1
  225. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  226. package/dist/utility/serialization/scene/terrain.js +3 -1
  227. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  228. package/dist/utility/serialization/scene/text.js +3 -0
  229. package/dist/utility/serialization/scene/text.js.map +1 -1
  230. package/dist/utility/serialization/scene/water.js +3 -1
  231. package/dist/utility/serialization/scene/water.js.map +1 -1
  232. package/dist/values.js +2 -3
  233. package/dist/values.js.map +1 -1
  234. package/package.json +3 -3
@@ -25,12 +25,16 @@ import { mixinPBRBRDF } from './brdf.js';
25
25
  let FEATURE_IRIDESCENCE = 0;
26
26
  const EMISSIVE_COLOR_UNIFORM = S.defineInstanceUniform('emissiveColor', 'rgb', 'EmissiveColor');
27
27
  const EMISSIVE_STRENGTH_UNIFORM = S.defineInstanceUniform('emissiveStrength', 'float', 'EmissiveStrength');
28
+ const EMISSIVE_LUMINANCE_UNIFORM = S.defineInstanceUniform('emissiveLuminance', 'float', 'EmissiveLuminance');
29
+ const EMISSIVE_EXPOSURE_WEIGHT_UNIFORM = S.defineInstanceUniform('emissiveExposureWeight', 'float', 'EmissiveExposureWeight');
28
30
  const RECT_SPECULAR_SCALE_UNIFORM = S.defineInstanceUniform('rectSpecularScale', 'float', 'RectSpecularScale');
29
31
  const cls = class extends S {
30
32
  static pbrCommonMixed = true;
31
33
  _f0;
32
34
  _emissiveColor;
33
35
  _emissiveStrength;
36
+ _emissiveLuminance;
37
+ _emissiveExposureWeight;
34
38
  _occlusionStrength;
35
39
  _rectSpecularScale;
36
40
  _sheenFactor;
@@ -46,6 +50,11 @@ import { mixinPBRBRDF } from './brdf.js';
46
50
  this._occlusionStrength = 1;
47
51
  this._emissiveColor = new Vector3(0, 0, 0);
48
52
  this._emissiveStrength = 1;
53
+ // 1000 cd/m² is a lit-display-class emitter: clearly visible at interior exposures and still
54
+ // readable under the Sunny-16 default, unlike the 1 nit that `emissiveStrength` would mean.
55
+ this._emissiveLuminance = 1000;
56
+ // Default 1: emissive is a photometric luminance that follows camera exposure, like Filament.
57
+ this._emissiveExposureWeight = 1;
49
58
  this._rectSpecularScale = 1;
50
59
  this._sheenFactor = Vector4.zero();
51
60
  this._clearcoatFactor = new Vector4(0, 0, 1, 0);
@@ -70,6 +79,8 @@ import { mixinPBRBRDF } from './brdf.js';
70
79
  this.rectSpecularScale = other.rectSpecularScale;
71
80
  this.emissiveColor = other.emissiveColor;
72
81
  this.emissiveStrength = other.emissiveStrength;
82
+ this.emissiveLuminance = other.emissiveLuminance;
83
+ this.emissiveExposureWeight = other.emissiveExposureWeight;
73
84
  this.transmission = other.transmission;
74
85
  this.iridescence = other.iridescence;
75
86
  this.clearcoat = other.clearcoat;
@@ -171,7 +182,14 @@ import { mixinPBRBRDF } from './brdf.js';
171
182
  this.uniformChanged();
172
183
  }
173
184
  }
174
- get rectSpecularScale() {
185
+ /**
186
+ * Artistic multiplier on the rect-light specular term.
187
+ *
188
+ * @remarks
189
+ * 1 is neutral. Physical lighting normalizes the LTC integral by `1/(2*pi)`, which is the
190
+ * reference implementation's factor, so the default needs no calibration fudge there. Legacy
191
+ * keeps its historical weighting, where existing scenes may have tuned this value.
192
+ */ get rectSpecularScale() {
175
193
  return this._rectSpecularScale;
176
194
  }
177
195
  set rectSpecularScale(val) {
@@ -189,7 +207,15 @@ import { mixinPBRBRDF } from './brdf.js';
189
207
  this.uniformChanged();
190
208
  }
191
209
  }
192
- get emissiveStrength() {
210
+ /**
211
+ * Display-referred multiplier on {@link emissiveColor}, used by legacy lighting.
212
+ *
213
+ * @remarks
214
+ * This is the glTF authoring quantity (`KHR_materials_emissive_strength`) and is unitless: the
215
+ * product with {@link emissiveColor} lands directly in the render target. Physical lighting
216
+ * ignores it and uses {@link emissiveLuminance} instead, exactly as a light's unitless
217
+ * `intensity` gives way to its photometric counterpart.
218
+ */ get emissiveStrength() {
193
219
  return this._emissiveStrength;
194
220
  }
195
221
  set emissiveStrength(val) {
@@ -198,6 +224,67 @@ import { mixinPBRBRDF } from './brdf.js';
198
224
  this.uniformChanged();
199
225
  }
200
226
  }
227
+ /**
228
+ * Multiplier on {@link emissiveColor} used by physical lighting. Legacy ignores it and uses
229
+ * {@link emissiveStrength}.
230
+ *
231
+ * @remarks
232
+ * The physical counterpart of {@link emissiveStrength}, mirroring how
233
+ * {@link DirectionalLight.illuminance} and {@link RectLight.luminance} shadow a light's unitless
234
+ * `intensity`. Keeping the two separate means switching {@link Scene.lightingMode} never
235
+ * reinterprets one authored number as two different quantities.
236
+ *
237
+ * **Its unit depends on {@link emissiveExposureWeight}**, because the shaded value is
238
+ * `emissiveColor * emissiveLuminance * mix(1, cameraExposure, emissiveExposureWeight)`:
239
+ *
240
+ * - At weight 1 (the constructor default) this is a photometric luminance in cd/m² (nit).
241
+ * Anchors: an office display is 200-1000, a lit interior wall a few hundred, an overcast sky
242
+ * ~2000, a neon sign 5,000-20,000. The default is 1000. Values around 1 are genuinely
243
+ * invisible, since a sunlit white surface is ~25,000 cd/m².
244
+ * - At weight 0 the camera exposure cancels out and this is a display-referred multiplier
245
+ * landing straight in the render target, so the useful range is around [0, 1] and 1 is very
246
+ * bright. **Materials imported from glTF/FBX are in this state**: the importer writes weight 0
247
+ * to preserve glTF's display-referred emissive, matching Filament's gltfio. Raise the weight
248
+ * to 1 (and rescale to nits) to author such a material photometrically.
249
+ */ get emissiveLuminance() {
250
+ return this._emissiveLuminance;
251
+ }
252
+ set emissiveLuminance(val) {
253
+ const luminance = Math.max(0, val);
254
+ if (this._emissiveLuminance !== luminance) {
255
+ this._emissiveLuminance = luminance;
256
+ this.uniformChanged();
257
+ }
258
+ }
259
+ /**
260
+ * How strongly the emissive term follows the camera exposure, in [0, 1].
261
+ *
262
+ * @remarks
263
+ * Mirrors Filament's `emissive.w`: the emitter is attenuated by
264
+ * `mix(1, cameraExposure, emissiveExposureWeight)`.
265
+ *
266
+ * At 1, the constructor default, {@link emissiveLuminance} is read as the cd/m² value it claims
267
+ * to be: a neon sign keeps a constant real-world brightness and darkens as the camera stops
268
+ * down, like every other lit surface.
269
+ *
270
+ * At 0 the exposure cancels out of the product and {@link emissiveLuminance} becomes a
271
+ * display-referred multiplier instead — bright at 1, and completely unresponsive to aperture,
272
+ * shutter and ISO. That is what a UI-like overlay wants, and what glTF content expects, so
273
+ * {@link Model} writes 0 on every imported material (Filament's gltfio writes `emissive.w = 0`
274
+ * for the same reason). Keep this in mind when an imported emitter looks far brighter than an
275
+ * authored one at the same {@link emissiveLuminance}.
276
+ *
277
+ * Has no effect in legacy lighting, where the pre-exposure is always 1.
278
+ */ get emissiveExposureWeight() {
279
+ return this._emissiveExposureWeight;
280
+ }
281
+ set emissiveExposureWeight(val) {
282
+ const weight = Math.max(0, Math.min(1, val));
283
+ if (this._emissiveExposureWeight !== weight) {
284
+ this._emissiveExposureWeight = weight;
285
+ this.uniformChanged();
286
+ }
287
+ }
201
288
  get transmission() {
202
289
  return this.featureUsed(FEATURE_TRANSMISSION);
203
290
  }
@@ -265,6 +352,8 @@ import { mixinPBRBRDF } from './brdf.js';
265
352
  if (this.needFragmentColor() && this.drawContext.materialFlags & MaterialVaryingFlags.INSTANCING) {
266
353
  scope.$outputs.zEmissiveColor = this.getInstancedUniform(scope, EMISSIVE_COLOR_UNIFORM);
267
354
  scope.$outputs.zEmissiveStrength = this.getInstancedUniform(scope, EMISSIVE_STRENGTH_UNIFORM);
355
+ scope.$outputs.zEmissiveLuminance = this.getInstancedUniform(scope, EMISSIVE_LUMINANCE_UNIFORM);
356
+ scope.$outputs.zEmissiveExposureWeight = this.getInstancedUniform(scope, EMISSIVE_EXPOSURE_WEIGHT_UNIFORM);
268
357
  scope.$outputs.zRectSpecularScale = this.getInstancedUniform(scope, RECT_SPECULAR_SCALE_UNIFORM);
269
358
  }
270
359
  }
@@ -276,6 +365,8 @@ import { mixinPBRBRDF } from './brdf.js';
276
365
  if (!(this.drawContext.materialFlags & MaterialVaryingFlags.INSTANCING)) {
277
366
  scope.zEmissiveColor = pb.vec3().uniform(2);
278
367
  scope.zEmissiveStrength = pb.float().uniform(2);
368
+ scope.zEmissiveLuminance = pb.float().uniform(2);
369
+ scope.zEmissiveExposureWeight = pb.float().uniform(2);
279
370
  scope.zRectSpecularScale = pb.float().uniform(2);
280
371
  }
281
372
  if (this.occlusionTexture) {
@@ -314,6 +405,8 @@ import { mixinPBRBRDF } from './brdf.js';
314
405
  if (!(ctx.materialFlags & MaterialVaryingFlags.INSTANCING)) {
315
406
  bindGroup.setValue('zEmissiveColor', this._emissiveColor);
316
407
  bindGroup.setValue('zEmissiveStrength', this._emissiveStrength);
408
+ bindGroup.setValue('zEmissiveLuminance', this._emissiveLuminance);
409
+ bindGroup.setValue('zEmissiveExposureWeight', this._emissiveExposureWeight);
317
410
  bindGroup.setValue('zRectSpecularScale', this._rectSpecularScale);
318
411
  }
319
412
  if (this.occlusionTexture) {
@@ -455,10 +548,18 @@ import { mixinPBRBRDF } from './brdf.js';
455
548
  const instancing = !!(this.drawContext.materialFlags & MaterialVaryingFlags.INSTANCING);
456
549
  return instancing ? scope.$inputs.zEmissiveColor : scope.zEmissiveColor;
457
550
  }
551
+ getEmissiveLuminance(scope) {
552
+ const instancing = !!(this.drawContext.materialFlags & MaterialVaryingFlags.INSTANCING);
553
+ return instancing ? scope.$inputs.zEmissiveLuminance : scope.zEmissiveLuminance;
554
+ }
458
555
  getEmissiveStrength(scope) {
459
556
  const instancing = !!(this.drawContext.materialFlags & MaterialVaryingFlags.INSTANCING);
460
557
  return instancing ? scope.$inputs.zEmissiveStrength : scope.zEmissiveStrength;
461
558
  }
559
+ getEmissiveExposureWeight(scope) {
560
+ const instancing = !!(this.drawContext.materialFlags & MaterialVaryingFlags.INSTANCING);
561
+ return instancing ? scope.$inputs.zEmissiveExposureWeight : scope.zEmissiveExposureWeight;
562
+ }
462
563
  getRectSpecularScale(scope) {
463
564
  const instancing = !!(this.drawContext.materialFlags & MaterialVaryingFlags.INSTANCING);
464
565
  return instancing ? scope.$inputs.zRectSpecularScale : scope.zRectSpecularScale;
@@ -471,11 +572,18 @@ import { mixinPBRBRDF } from './brdf.js';
471
572
  }
472
573
  calculateEmissiveColor(scope) {
473
574
  const pb = scope.$builder;
474
- if (this.emissiveTexture) {
475
- return pb.mul(this.sampleEmissiveTexture(scope).rgb, this.getEmissiveColor(scope), this.getEmissiveStrength(scope));
476
- } else {
477
- return pb.mul(this.getEmissiveColor(scope), this.getEmissiveStrength(scope));
478
- }
575
+ // Legacy multiplies by the unitless emissiveStrength, physical by the cd/m² emissiveLuminance.
576
+ // Same split as a light's `intensity` vs `illuminance`/`luminance`, so switching lightingMode
577
+ // never reinterprets one authored number as two different quantities. Safe to branch here
578
+ // because emissive is only evaluated in the light pass, whose program hash includes
579
+ // lightingMode (see LightPass._getGlobalBindGroupHash).
580
+ const strength = this.drawContext.scene.lightingMode === 'physical' ? this.getEmissiveLuminance(scope) : this.getEmissiveStrength(scope);
581
+ const base = this.emissiveTexture ? pb.mul(this.sampleEmissiveTexture(scope).rgb, this.getEmissiveColor(scope), strength) : pb.mul(this.getEmissiveColor(scope), strength);
582
+ // Filament's emissive attenuation: mix(1, exposure, exposureWeight). Lights are pre-exposed on
583
+ // the CPU, but a material-authored emitter is not, so it is exposed here. In legacy the
584
+ // pre-exposure is 1, which leaves this a no-op regardless of the weight.
585
+ const attenuation = pb.mix(pb.float(1), ShaderHelper.getPreExposureUniform(scope), this.getEmissiveExposureWeight(scope));
586
+ return pb.mul(base, attenuation);
479
587
  }
480
588
  getSheenAlbedoScaling(scope, Ndot, sheenColor, sheenRoughness) {
481
589
  const pb = scope.$builder;
@@ -748,7 +856,10 @@ import { mixinPBRBRDF } from './brdf.js';
748
856
  directRectLight(scope, worldPos, normal, viewVec, commonData, posRange, axisX, axisY, colorIntensity, outColor) {
749
857
  const pb = scope.$builder;
750
858
  const that = this;
751
- const funcName = 'Z_PBRRectLight';
859
+ const physical = this.drawContext.scene.lightingMode === 'physical';
860
+ // The two modes normalize the LTC integral differently, so they must not share a cached
861
+ // function body under one name.
862
+ const funcName = physical ? 'Z_PBRRectLightPhysical' : 'Z_PBRRectLight';
752
863
  const LUT_SIZE = 64;
753
864
  const LUT_SCALE = (LUT_SIZE - 1) / LUT_SIZE;
754
865
  const LUT_BIAS = 0.5 / LUT_SIZE;
@@ -939,8 +1050,17 @@ import { mixinPBRBRDF } from './brdf.js';
939
1050
  this.$l.lightColor = pb.mul(this.colorIntensity.rgb, this.colorIntensity.a, this.falloff);
940
1051
  this.$l.baseF0 = this.data.f0.rgb;
941
1052
  this.$l.rectSpecularScale = that.getRectSpecularScale(this);
942
- this.$l.specularLtc = pb.mul(this.spec, pb.add(pb.mul(this.baseF0, this.t2.x), pb.mul(pb.sub(pb.vec3(1), this.baseF0), this.t2.y)), this.data.specularWeight, this.rectSpecularScale);
943
- this.$l.diffuseLtc = pb.mul(this.diff, pb.div(this.data.diffuse.rgb, Math.PI));
1053
+ // Z_LTCEvaluateRect returns the raw edge sum; the reference implementation normalizes
1054
+ // it by 1/(2*pi). Physical applies that so the authored luminance (cd/m²) is
1055
+ // reproduced and diffuse/specular keep their correct ratio -- previously diffuse was
1056
+ // divided by pi and specular by nothing, leaving specular pi times too strong.
1057
+ //
1058
+ // Legacy deliberately keeps the historical (unnormalized) weighting: its rect
1059
+ // intensity is unitless and existing scenes are calibrated against it.
1060
+ const ltcNormalization = physical ? 1 / (2 * Math.PI) : 1;
1061
+ const diffuseNormalization = physical ? 1 / (2 * Math.PI) : 1 / Math.PI;
1062
+ this.$l.specularLtc = pb.mul(this.spec, pb.add(pb.mul(this.baseF0, this.t2.x), pb.mul(pb.sub(pb.vec3(1), this.baseF0), this.t2.y)), this.data.specularWeight, this.rectSpecularScale, ltcNormalization);
1063
+ this.$l.diffuseLtc = pb.mul(this.diff, pb.mul(this.data.diffuse.rgb, diffuseNormalization));
944
1064
  this.outColor = pb.add(this.outColor, pb.mul(this.lightColor, pb.add(this.diffuseLtc, this.specularLtc)));
945
1065
  });
946
1066
  });
@@ -1077,6 +1197,7 @@ import { mixinPBRBRDF } from './brdf.js';
1077
1197
  const pb = scope.$builder;
1078
1198
  const that = this;
1079
1199
  const ctx = that.drawContext;
1200
+ const useReprojectedSSGI = !!(ctx.SSGI && ctx.SSGIIrradianceHistoryTexture && ctx.SSGISurfaceHistoryTexture && (ctx.device.type !== 'webgpu' || ctx.motionVectorTexture));
1080
1201
  const funcName = 'Z_PBRIndirectLighting';
1081
1202
  pb.func(funcName, [
1082
1203
  pb.vec3('normal'),
@@ -1097,10 +1218,13 @@ import { mixinPBRBRDF } from './brdf.js';
1097
1218
  const envLightSpecularStrength = ShaderHelper.getEnvLightSpecularStrength(this);
1098
1219
  if (that.occlusionTexture) {
1099
1220
  const occlusionSample = that.sampleOcclusionTexture(this).r;
1100
- this.$l.occlusion = pb.mul(pb.add(pb.mul(this.zOcclusionStrength, pb.sub(occlusionSample, 1)), 1), envLightStrength);
1221
+ this.$l.ambientOcclusion = pb.add(pb.mul(this.zOcclusionStrength, pb.sub(occlusionSample, 1)), 1);
1222
+ this.$l.occlusion = pb.mul(this.ambientOcclusion, envLightStrength);
1101
1223
  } else {
1224
+ this.$l.ambientOcclusion = pb.float(1);
1102
1225
  this.$l.occlusion = envLightStrength;
1103
1226
  }
1227
+ this.$l.diffuseOcclusion = useReprojectedSSGI ? this.ambientOcclusion : this.occlusion;
1104
1228
  this.$l.NoV = pb.clamp(pb.dot(this.normal, this.viewVec), 0.0001, 1);
1105
1229
  if (that.sheen) {
1106
1230
  this.$l.sheenLutSample = pb.clamp(pb.textureSampleLevel(this.zSheenLut, pb.clamp(pb.vec2(this.NoV, this.data.sheenRoughness), pb.vec2(0), pb.vec2(1)), 0), pb.vec4(0), pb.vec4(1));
@@ -1127,13 +1251,24 @@ import { mixinPBRBRDF } from './brdf.js';
1127
1251
  }
1128
1252
  if (outRoughness) {
1129
1253
  this.outRoughness = pb.vec4(this.specularFactor /*this.data.f0.rgb*/ , this.data.roughness);
1130
- } else if (ctx.env.light.envLight.hasRadiance()) {
1254
+ }
1255
+ // Skip specular IBL only when the roughness target exists, since SSR
1256
+ // is its only requester and resolves the reflection from it; adding
1257
+ // it here too would count it twice. `outRoughness` alone is not that
1258
+ // signal: SSGI requests the scene normal target and materials raise
1259
+ // the roughness and normal flags together, so an SSGI-only frame
1260
+ // wrote outRoughness with nobody to resolve the reflection, which
1261
+ // silently dropped specular highlights. Keyed on the flag rather
1262
+ // than ctx.SSR because the flag is part of the light pass shader
1263
+ // hash, so cached programs cannot outlive a change here.
1264
+ const ssrResolvesSpecular = !!(ctx.materialFlags & MaterialVaryingFlags.SCENE_STORE_ROUGHNESS);
1265
+ if (!ssrResolvesSpecular && ctx.env.light.envLight.hasRadiance()) {
1131
1266
  this.$l.radiance = ctx.env.light.envLight.getRadiance(this, pb.reflect(pb.neg(this.viewVec), this.normal), this.data.roughness);
1132
1267
  this.outColor = pb.add(this.outColor, pb.mul(this.radiance, this.specularFactor, envLightSpecularStrength));
1133
1268
  }
1134
1269
  }
1135
1270
  if (ctx.env.light.envLight.hasIrradiance()) {
1136
- this.$l.irradiance = ctx.env.light.envLight.getIrradiance(this, this.normal);
1271
+ this.$l.irradiance = ctx.env.light.envLight.getIrradiance(this, this.normal, ctx);
1137
1272
  if (that.iridescence) {
1138
1273
  this.$l.iridescenceF0Max = pb.vec3(pb.max(pb.max(this.data.iridescenceF0.r, this.data.iridescenceF0.g), this.data.iridescenceF0.b));
1139
1274
  this.$l.mixedF0 = pb.mix(this.data.f0.rgb, this.iridescenceF0Max, this.data.iridescenceFactor.x);
@@ -1147,7 +1282,7 @@ import { mixinPBRBRDF } from './brdf.js';
1147
1282
  this.$l.F_avg = pb.mul(pb.add(this.mixedF0, pb.div(pb.sub(pb.vec3(1), this.mixedF0), 21)), this.data.specularWeight);
1148
1283
  this.$l.FmsEms = pb.div(pb.mul(this.FssEss, this.F_avg, this.Ems), pb.sub(pb.vec3(1), pb.mul(this.F_avg, this.Ems)));
1149
1284
  this.$l.k_D = pb.mul(this.data.diffuse.rgb, pb.add(pb.sub(pb.vec3(1), this.FssEss), this.FmsEms));
1150
- this.$l.iblDiffuse = pb.mul(pb.add(this.FmsEms, this.k_D), this.irradiance, this.occlusion);
1285
+ this.$l.iblDiffuse = pb.mul(pb.add(this.FmsEms, this.k_D), this.irradiance, this.diffuseOcclusion);
1151
1286
  if (that.clearcoat) {
1152
1287
  this.$if(pb.greaterThan(this.data.ccFactor.x, 0), function() {
1153
1288
  this.iblDiffuse = pb.mul(this.iblDiffuse, pb.clamp(pb.sub(1, pb.mul(this.data.ccFresnel, this.data.ccFactor.x)), 0, 1));