@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
@@ -2,7 +2,7 @@ import { linearToGamma } from '../shaders/misc.js';
2
2
  import { smoothNoise3D } from '../shaders/noise.js';
3
3
  import { getDevice, getEngine } from '../app/api.js';
4
4
  import { getDefaultAtmosphereParams, getAerialPerspectiveLut, renderAtmosphereLUTs, getAtmosphereParamsStruct, createTransmittanceLutProgram, createMultiScatteringLutProgram, createSkyViewLutProgram, createAPLutProgram, skyBox, atmosphereLUTRendered, getTransmittanceLut, getSkyViewLut } from '../shaders/atmosphere.js';
5
- import { Matrix4x4, Disposable, DRef, Vector4, Vector2, Vector3, CubeFace, objectKeys } from '@zephyr3d/base';
5
+ import { Matrix4x4, Disposable, DRef, Vector4, Vector2, Vector3, CubeFace, objectKeys, DEPTH_FARTHEST, DEPTH_COMPARE_DEFAULT, DEPTH_COMPARE_FARTHER } from '@zephyr3d/base';
6
6
  import { Primitive } from './primitive.js';
7
7
  import { BoxShape } from '../shapes/box.js';
8
8
  import '../shapes/capsule.js';
@@ -13,6 +13,7 @@ import '../shapes/sphere.js';
13
13
  import '../shapes/tetrahedron.js';
14
14
  import { prefilterCubemap } from '../utility/pmrem.js';
15
15
  import { ShaderHelper } from '../material/shader/helper.js';
16
+ import { PHYSICAL_BAKE_EXPOSURE } from '../utility/physical.js';
16
17
  import { fetchSampler } from '../utility/misc.js';
17
18
  import { CubemapSHProjector } from '../utility/shprojector.js';
18
19
  import { uniformSphereSamples, Fog } from '../values.js';
@@ -31,6 +32,26 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
31
32
  * The sky renderer
32
33
  * @public
33
34
  */ class SkyRenderer extends Disposable {
35
+ /**
36
+ * @internal
37
+ *
38
+ * The atmospheric scattering model predates photometric lighting and was authored around a
39
+ * sun input of 10. This normalization maps the physical daylight reference (100,000 lux at
40
+ * Sunny 16, whose camera exposure is 1 / 38,400) onto that model reference: 10 / (100000 / 38400).
41
+ */ static PHYSICAL_ATMOSPHERE_LUMINANCE_SCALE = 3.84;
42
+ /**
43
+ * @internal
44
+ * Fixed exposure the physical sky bake is stored at. See {@link PHYSICAL_BAKE_EXPOSURE}.
45
+ */ static PHYSICAL_BAKE_EXPOSURE = PHYSICAL_BAKE_EXPOSURE;
46
+ /**
47
+ * @internal
48
+ *
49
+ * Reference-exposure anchor previously used to lift authored fog colors into photometric space.
50
+ *
51
+ * @deprecated Physical fog now derives its luminance from `EnvLightWrapper.intensity`, mirroring
52
+ * Filament, which scales fog color by `iblLuminance`. Retained only because it documents the
53
+ * Sunny-16 equivalence: 38,400 = 1 / exposure(f/16, 1/125s, ISO 100).
54
+ */ static FOG_PHYSICAL_LUMINANCE = 38400;
34
55
  static _skyCamera = (()=>{
35
56
  return new PerspectiveCamera(null, Math.PI * 0.5, 1, 20, 1);
36
57
  })();
@@ -90,6 +111,14 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
90
111
  _bindgroupFogNoDepth = null;
91
112
  _format;
92
113
  /**
114
+ * @internal Scale lifting the authored 0..1 height-fog colors into the pre-exposed space for the
115
+ * current frame (1 in legacy, `EnvLightWrapper.intensity * cameraExposure` in physical).
116
+ */ _fogLuminanceScale;
117
+ /**
118
+ * @internal Ratio converting the fog shader's atmosphere-derived LUT samples from the fixed bake
119
+ * exposure to the live one (1 in legacy).
120
+ */ _fogPreExposure;
121
+ /**
93
122
  * Creates an instance of SkyRenderer
94
123
  */ constructor(){
95
124
  super();
@@ -131,6 +160,8 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
131
160
  this._bindgroupFog = new DRef();
132
161
  this._bindgroupFogNoDepth = new DRef();
133
162
  this._format = null;
163
+ this._fogLuminanceScale = 1;
164
+ this._fogPreExposure = 1;
134
165
  }
135
166
  /** @internal */ getHash(_ctx) {
136
167
  return `${this.skyType}:${this.fogType}`;
@@ -518,6 +549,18 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
518
549
  return this._skyDistantLightLut.get().getColorAttachments()[0];
519
550
  }
520
551
  update(ctx) {
552
+ // Latch the fog scales for this frame. The bake path (updateBakedSkyMap) uses the sky camera
553
+ // whose scene is null, so it cannot read lightingMode itself; both bake and main pass run after
554
+ // update(), so caching them here keeps them consistent.
555
+ // The fog shader mixes two differently-scaled sources, so it needs both factors:
556
+ // - the atmosphere-derived LUTs (distant sky, aerial perspective) are stored at the fixed bake
557
+ // exposure, so they convert with the bake-to-live ratio;
558
+ // - the authored 0..1 fog colors are lifted by the environment intensity (lux) -- the same
559
+ // anchor the sky and IBL use, mirroring Filament's `fogColor *= iblLuminance` -- and then
560
+ // take the plain camera pre-exposure.
561
+ // Legacy leaves both at 1 so its authored colors are untouched.
562
+ this._fogPreExposure = SkyRenderer.getBakeToPreExposedScale(ctx);
563
+ this._fogLuminanceScale = ctx.scene.lightingMode === 'physical' ? ctx.scene.env.light.intensity * ShaderHelper.getPreExposure(ctx) : 1;
521
564
  const useScatter = this._skyType === 'scatter';
522
565
  if (useScatter || !atmosphereLUTRendered()) {
523
566
  this.renderAtmosphereLUTs(ctx);
@@ -545,16 +588,11 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
545
588
  }
546
589
  if (this._bakedSkyboxDirty) {
547
590
  this._bakedSkyboxDirty = false;
591
+ // updateBakedSkyMap derives the IBL (specular prefilter + irradiance SH) from the fog-free
592
+ // cubemap internally, before compositing fog. The distant-light LUT is re-baked here against
593
+ // the fogged cubemap to preserve the pre-refactor fog shading appearance.
548
594
  this.updateBakedSkyMap(ctx);
549
595
  this.renderSkyDistantLut(ctx, this._bakedSkyboxTexture.get());
550
- if (ctx.scene.env.light.radianceMap && (ctx.scene.env.light.radianceMap === this.radianceMap || this._irradianceSH.get() && ctx.scene.env.light.irradianceSH === this.irradianceSH || this._irradianceSHFB.get() && ctx.scene.env.light.irradianceSHFB === this.irradianceSHFB)) {
551
- prefilterCubemap(this._bakedSkyboxTexture.get(), 'ggx', this.radianceFramebuffer, this._radianceConvSamples);
552
- ctx.scene.env.light.sheenRadianceMap = this.radianceMap;
553
- this._shProjector.projectCubemapToTexture(this._bakedSkyboxTexture.get(), this.irradianceSHFB);
554
- ctx.scene.env.light.irradianceSHFB = this.irradianceSHFB;
555
- ctx.scene.env.light.irradianceSH = null;
556
- ctx.scene.env.light.irradianceWindow = this._shWindowWeights;
557
- }
558
596
  }
559
597
  const newSunLight = oldSunLight ? Vector3.mul(ctx.sunLight.color.xyz(), this.sunTransmittance(ctx.sunLight)) : Vector3.zero();
560
598
  if (oldSunLight) {
@@ -565,7 +603,14 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
565
603
  const cameraY = Math.min(this._heightFogParams.parameter2.y + MAX_FOG_HEIGHT, ctx.camera.getWorldPosition().y);
566
604
  const p = Math.max(-125, Math.min(126, -this._heightFogParams.parameter1.w * (cameraY - this._heightFogParams.parameter2.y)));
567
605
  this._heightFogParams.parameter3.z = this._heightFogParams.parameter2.x * Math.pow(2, p);
568
- this._heightFogParams.lightColor.set(newSunLight);
606
+ // The legacy directional fog term expects a display-relative 0..1 light color. It cannot be
607
+ // mixed directly with photometric illuminance; physical fog instead receives its radiance
608
+ // from the distant-sky integration below.
609
+ if (ctx.scene.lightingMode === 'physical') {
610
+ this._heightFogParams.lightColor.setXYZ(0, 0, 0);
611
+ } else {
612
+ this._heightFogParams.lightColor.set(newSunLight);
613
+ }
569
614
  this._heightFogParams.lightDir.set(SkyRenderer._getSunDir(ctx.sunLight));
570
615
  }
571
616
  return oldSunLight;
@@ -573,7 +618,12 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
573
618
  renderAtmosphereLUTs(ctx) {
574
619
  this._atmosphereParams.lightDir.set(SkyRenderer._getSunDir(ctx.sunLight));
575
620
  this._atmosphereParams.lightColor.set(SkyRenderer._getSunColor(ctx.sunLight));
576
- this._atmosphereParams.lightColor.w *= this._atmosphereExposure;
621
+ // Physical: the sun input is photometric (lux), normalized onto the model's authored reference
622
+ // and stored at the fixed PHYSICAL_BAKE_EXPOSURE rather than the live camera exposure. That
623
+ // keeps the cached IBL bake exposure-independent while staying inside the environment cubemap's
624
+ // limited float range -- raw luminance would overflow it to Inf. Consumers rescale to the live
625
+ // exposure via getBakeToPreExposedScale().
626
+ this._atmosphereParams.lightColor.w *= this._atmosphereExposure * (ctx.scene.lightingMode === 'physical' ? SkyRenderer.PHYSICAL_ATMOSPHERE_LUMINANCE_SCALE * SkyRenderer.PHYSICAL_BAKE_EXPOSURE : 1);
577
627
  this._atmosphereParams.cameraAspect = ctx.camera.getAspect();
578
628
  this._atmosphereParams.cameraWorldMatrix.set(ctx.camera.worldMatrix);
579
629
  renderAtmosphereLUTs(this._atmosphereParams);
@@ -586,6 +636,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
586
636
  ctx.device.setFramebuffer(this._skyDistantLightLut.get());
587
637
  ctx.device.clearFrameBuffer(new Vector4(0, 0, 0, 1), null, null);
588
638
  this._bindgroupDistantLight.get().setTexture('skybox', skybox, fetchSampler('clamp_linear_nomip'));
639
+ this._bindgroupDistantLight.get().setValue('physical', ctx.scene.lightingMode === 'physical' ? 1 : 0);
589
640
  ctx.device.setBindGroup(0, this._bindgroupDistantLight.get());
590
641
  SkyRenderer._primitiveDistantLight.draw();
591
642
  ctx.device.popDeviceStates();
@@ -615,10 +666,24 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
615
666
  ]){
616
667
  camera.lookAtCubeFace(face);
617
668
  this._bakedSkyboxFrameBuffer.get().setColorAttachmentCubeFace(0, face);
618
- this._renderSky(camera, false);
669
+ // Direct sunlight is represented by the scene's directional sun light. Keep the analytic
670
+ // sun disk out of both diffuse and specular IBL to avoid baking that direct contribution a
671
+ // second time. Atmospheric scattering and clouds remain in the cubemap.
672
+ this._renderSky(camera, false, false, this._getSkyBakeLuminanceScale(ctx));
619
673
  }
620
674
  device.popDeviceStates();
621
675
  this.renderSkyDistantLut(ctx, tex);
676
+ // Publish the texture reference before deriving IBL so _updateIBLFromBakedSky reads the
677
+ // current cubemap.
678
+ this._bakedSkyboxTexture.set(tex);
679
+ // Derive the IBL from the fog-free cubemap. Height fog is a view/position-dependent participating
680
+ // medium applied per-pixel in screen space (see lightpass renderFog); baking it into the distant
681
+ // environment probe would both double-count it against that screen-space pass and wrongly lift the
682
+ // lower hemisphere (ground direction) from the atmosphere's dark value to a bright fog color. The
683
+ // same reasoning that keeps the sun disk out of the IBL applies to fog, for diffuse and specular alike.
684
+ this._updateIBLFromBakedSky(ctx, tex);
685
+ // Composite fog into the cubemap afterwards. This copy is only used as the visible skybox
686
+ // background (UNIFORM_NAME_BAKED_SKY_MAP), where the fogged distant sky is desired.
622
687
  device.pushDeviceStates();
623
688
  device.setFramebuffer(this._bakedSkyboxFrameBuffer.get());
624
689
  for (const face of [
@@ -635,7 +700,21 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
635
700
  }
636
701
  device.popDeviceStates();
637
702
  device.setRenderStates(saveRenderStates);
638
- this._bakedSkyboxTexture.set(tex);
703
+ }
704
+ /**
705
+ * @internal
706
+ * Updates the scene's IBL (GGX-prefiltered specular radiance map + irradiance SH) from the given
707
+ * cubemap, provided the scene environment light is still driven by this dynamic sky. Must be called
708
+ * with the fog-free cubemap so height fog does not leak into the environment lighting.
709
+ */ _updateIBLFromBakedSky(ctx, skybox) {
710
+ if (ctx.scene.env.light.radianceMap && (ctx.scene.env.light.radianceMap === this.radianceMap || this._irradianceSH.get() && ctx.scene.env.light.irradianceSH === this.irradianceSH || this._irradianceSHFB.get() && ctx.scene.env.light.irradianceSHFB === this.irradianceSHFB)) {
711
+ prefilterCubemap(skybox, 'ggx', this.radianceFramebuffer, this._radianceConvSamples);
712
+ ctx.scene.env.light.sheenRadianceMap = this.radianceMap;
713
+ this._shProjector.projectCubemapToTexture(skybox, this.irradianceSHFB);
714
+ ctx.scene.env.light.irradianceSHFB = this.irradianceSHFB;
715
+ ctx.scene.env.light.irradianceSH = null;
716
+ ctx.scene.env.light.irradianceWindow = this._shWindowWeights;
717
+ }
639
718
  }
640
719
  renderUberFog(camera, depthTexture) {
641
720
  const device = getDevice();
@@ -655,13 +734,34 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
655
734
  bindgroup.setValue('withAerialPerspective', this.skyType === 'scatter' ? 1 : 0);
656
735
  bindgroup.setValue('fogType', this.mappedFogType);
657
736
  bindgroup.setValue('atmosphereParams', this._atmosphereParams);
658
- bindgroup.setValue('heightFogParams', this._heightFogParams);
737
+ bindgroup.setValue('heightFogParams', this._getUploadHeightFogParams());
659
738
  device.setProgram(fogProgram);
660
739
  device.setBindGroup(0, bindgroup);
661
740
  device.setVertexLayout(SkyRenderer._vertexLayout);
662
741
  device.setRenderStates(renderStates);
663
742
  device.draw('triangle-strip', 0, 4);
664
743
  }
744
+ /**
745
+ * @internal
746
+ * Height-fog upload params with the authored base/directional colors lifted into the pre-exposed
747
+ * physical space when physical lighting is active, plus the pre-exposure the shader applies to its
748
+ * atmosphere-derived LUT samples. Legacy returns the stored params untouched so its upload stays
749
+ * byte-identical. Stored authored values are never mutated.
750
+ */ _getUploadHeightFogParams() {
751
+ const scale = this._fogLuminanceScale;
752
+ const p = this._heightFogParams;
753
+ if (scale === 1 && this._fogPreExposure === 1) {
754
+ return p;
755
+ }
756
+ const p1 = p.parameter1;
757
+ const p4 = p.parameter4;
758
+ return {
759
+ ...p,
760
+ parameter1: new Vector4(p1.x * scale, p1.y * scale, p1.z * scale, p1.w),
761
+ parameter4: new Vector4(p4.x * scale, p4.y * scale, p4.z * scale, p4.w),
762
+ preExposure: this._fogPreExposure
763
+ };
764
+ }
665
765
  /** @internal */ renderFog(camera) {
666
766
  const device = getDevice();
667
767
  const currentFramebuffer = device.getFramebuffer();
@@ -726,7 +826,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
726
826
  }
727
827
  const framebuffer = this._beginSingleColorPass(true);
728
828
  try {
729
- this._renderSky(skyCamera, true);
829
+ this._renderSky(skyCamera, true, true, this._getSkyScreenLuminanceScale(ctx));
730
830
  } finally{
731
831
  this._endSingleColorPass(framebuffer);
732
832
  }
@@ -774,23 +874,67 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
774
874
  });
775
875
  }
776
876
  }
777
- /** @internal */ _renderSky(camera, depthTest) {
877
+ /** @internal */ _renderSky(camera, depthTest, includeSunDisk, luminanceScale = 1) {
778
878
  const device = getDevice();
779
879
  const savedRenderStates = device.getRenderStates();
780
880
  this._prepareSkyBox(device);
781
881
  if (this._skyType === 'scatter') {
782
- this._drawScattering(camera, depthTest);
882
+ // The atmosphere LUTs already hold physical luminance, so only the caller's exposure applies.
883
+ this._drawScattering(camera, depthTest, includeSunDisk, luminanceScale);
783
884
  } else if (this._skyType === 'skybox' && this.skyboxTexture) {
784
- this._drawSkybox(camera, depthTest);
885
+ this._drawSkybox(camera, depthTest, luminanceScale);
785
886
  } else {
786
- this._drawSkyColor(camera, depthTest);
887
+ this._drawSkyColor(camera, depthTest, luminanceScale);
787
888
  }
788
889
  device.setRenderStates(savedRenderStates);
789
890
  }
790
- /** @internal */ _drawSkyColor(camera, depthTest) {
891
+ /**
892
+ * @internal
893
+ *
894
+ * Converts a value stored at {@link PHYSICAL_BAKE_EXPOSURE} into the live pre-exposed space.
895
+ *
896
+ * @remarks
897
+ * Everything derived from the cached sky bake (the IBL, the distant-light LUT, the visible baked
898
+ * skybox) is stored at the fixed reference exposure. Multiplying by this ratio yields the same
899
+ * result as if the live camera exposure had been baked in, without invalidating the cache when
900
+ * the camera stops down. Returns 1 in legacy.
901
+ */ static getBakeToPreExposedScale(ctx) {
902
+ return ctx.scene.lightingMode === 'physical' ? ShaderHelper.getPreExposure(ctx) / SkyRenderer.PHYSICAL_BAKE_EXPOSURE : 1;
903
+ }
904
+ /**
905
+ * @internal
906
+ *
907
+ * Scale that normalizes a sky into the IBL bake's storage space.
908
+ *
909
+ * @remarks
910
+ * An authored 0..1 `skybox` / `image` is treated as an emitter of `EnvLightWrapper.intensity` lux.
911
+ * The scattering atmosphere needs no lift: its LUTs already carry photometric sun illuminance.
912
+ *
913
+ * Both are then scaled to {@link PHYSICAL_BAKE_EXPOSURE}. The bake is cached and only invalidated
914
+ * by sun changes, so it must be exposure-independent; storing it at raw photometric magnitude
915
+ * would overflow the environment cubemap's float range (see {@link PHYSICAL_BAKE_EXPOSURE}).
916
+ *
917
+ * Returns 1 in legacy so its bake stays byte-identical.
918
+ */ _getSkyBakeLuminanceScale(ctx) {
919
+ if (ctx.scene.lightingMode !== 'physical') {
920
+ return 1;
921
+ }
922
+ // scatter already includes the bake exposure via renderAtmosphereLUTs().
923
+ return this._skyType === 'scatter' ? 1 : ctx.scene.env.light.intensity * SkyRenderer.PHYSICAL_BAKE_EXPOSURE;
924
+ }
925
+ /**
926
+ * @internal
927
+ *
928
+ * On-screen sky scale: the bake-time normalization converted from the stored reference exposure
929
+ * to the live one, so the visible sky lands in the same pre-exposed space as every lit surface.
930
+ */ _getSkyScreenLuminanceScale(ctx) {
931
+ return this._getSkyBakeLuminanceScale(ctx) * SkyRenderer.getBakeToPreExposedScale(ctx);
932
+ }
933
+ /** @internal */ _drawSkyColor(camera, depthTest, luminanceScale) {
791
934
  const device = getDevice();
792
935
  const bindgroup = this._bindgroupSky.image.get();
793
936
  bindgroup.setValue('color', this._skyColor);
937
+ bindgroup.setValue('luminanceScale', luminanceScale);
794
938
  bindgroup.setTexture('texture', this._skyImage.get() ?? SkyRenderer._defaultSkyImage.get(), fetchSampler('clamp_linear_nomip'));
795
939
  bindgroup.setValue('flip', device.getFramebuffer() && device.type === 'webgpu' ? -1 : 1);
796
940
  bindgroup.setValue('srgbOut', device.getFramebuffer() ? 0 : 1);
@@ -798,10 +942,11 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
798
942
  device.setBindGroup(0, bindgroup);
799
943
  drawFullscreenQuad(depthTest ? SkyRenderer._renderStatesSky : SkyRenderer._renderStatesSkyNoDepthTest);
800
944
  }
801
- /** @internal */ _drawSkybox(camera, depthTest) {
945
+ /** @internal */ _drawSkybox(camera, depthTest, luminanceScale) {
802
946
  const device = getDevice();
803
947
  const bindgroup = this._bindgroupSky.skybox.get();
804
948
  bindgroup.setTexture('skyCubeMap', this.skyboxTexture, fetchSampler('clamp_linear_nomip'));
949
+ bindgroup.setValue('luminanceScale', luminanceScale);
805
950
  bindgroup.setValue('flip', device.getFramebuffer() && device.type === 'webgpu' ? new Vector4(1, -1, 1, 1) : new Vector4(1, 1, 1, 1));
806
951
  bindgroup.setValue('viewProjMatrix', camera.viewProjectionMatrix);
807
952
  bindgroup.setValue('worldMatrix', this._skyWorldMatrix);
@@ -877,7 +1022,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
877
1022
  }
878
1023
  return new Vector3(Math.exp(-sum.x), Math.exp(-sum.y), Math.exp(-sum.z));
879
1024
  }
880
- /** @internal */ _drawScattering(camera, depthTest) {
1025
+ /** @internal */ _drawScattering(camera, depthTest, includeSunDisk, luminanceScale) {
881
1026
  const device = getDevice();
882
1027
  const tLut = getTransmittanceLut();
883
1028
  const skyLut = getSkyViewLut();
@@ -890,6 +1035,8 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
890
1035
  bindgroup.setValue('worldMatrix', this._skyWorldMatrix);
891
1036
  bindgroup.setValue('cameraPos', camera.getWorldPosition());
892
1037
  bindgroup.setValue('srgbOut', device.getFramebuffer() ? 0 : 1);
1038
+ bindgroup.setValue('includeSunDisk', includeSunDisk ? 1 : 0);
1039
+ bindgroup.setValue('luminanceScale', luminanceScale);
893
1040
  bindgroup.setTexture('tLut', tLut, fetchSampler('clamp_linear_nomip'));
894
1041
  bindgroup.setTexture('skyLut', skyLut, fetchSampler('clamp_linear_nomip'));
895
1042
  bindgroup.setValue('cloudy', this._cloudy);
@@ -930,9 +1077,14 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
930
1077
  fragment (pb) {
931
1078
  this.$outputs.color = pb.vec4();
932
1079
  this.skybox = pb.texCube().uniform(0);
1080
+ this.physical = pb.int().uniform(0);
933
1081
  pb.main(function() {
934
1082
  this.$l.sunColor = pb.vec4();
935
- this.$l.skyColor = pb.textureSampleLevel(this.skybox, this.$inputs.vector, 0).rgb;
1083
+ // Height-fog in-scattering is lit by the sky hemisphere. The old full-sphere average
1084
+ // is retained verbatim in legacy mode; in physical mode, mirror lower samples into the
1085
+ // upper hemisphere so a dark ground/lower sky cannot turn the horizon into a black band.
1086
+ this.$l.sampleDirection = this.$choice(pb.notEqual(this.physical, 0), pb.vec3(this.$inputs.vector.x, pb.abs(this.$inputs.vector.y), this.$inputs.vector.z), this.$inputs.vector);
1087
+ this.$l.skyColor = pb.textureSampleLevel(this.skybox, this.sampleDirection, 0).rgb;
936
1088
  this.$outputs.color = pb.vec4(pb.mul(this.skyColor, 1 / 64), 1);
937
1089
  });
938
1090
  }
@@ -962,7 +1114,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
962
1114
  this.$outputs.uv = pb.vec2();
963
1115
  this.flip = pb.int().uniform(0);
964
1116
  pb.main(function() {
965
- this.$builtins.position = pb.vec4(this.$inputs.pos, 1, 1);
1117
+ this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
966
1118
  this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
967
1119
  if (device.type !== 'webgpu') {
968
1120
  this.$builtins.position.y = pb.neg(this.$builtins.position.y);
@@ -974,9 +1126,12 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
974
1126
  this.color = pb.vec4().uniform(0);
975
1127
  this.texture = pb.tex2D().uniform(0);
976
1128
  this.srgbOut = pb.int().uniform(0);
1129
+ this.luminanceScale = pb.float().uniform(0);
977
1130
  pb.main(function() {
978
1131
  this.$l.sampleColor = pb.textureSampleLevel(this.texture, this.$inputs.uv, 0);
979
- this.$l.outColor = pb.mul(this.sampleColor, this.color);
1132
+ // luminanceScale is 1 in legacy and for the IBL bake; on screen in physical mode it
1133
+ // carries the environment intensity (lux) pre-multiplied by the camera exposure.
1134
+ this.$l.outColor = pb.mul(this.sampleColor, this.color, this.luminanceScale);
980
1135
  this.$if(pb.equal(this.srgbOut, 0), function() {
981
1136
  this.$outputs.outColor = pb.vec4(this.outColor.rgb, 1);
982
1137
  }).$else(function() {
@@ -1010,16 +1165,19 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
1010
1165
  this.$outputs.texCoord = this.$inputs.pos;
1011
1166
  this.$l.worldPos = pb.add(this.cameraPos, pb.mul(this.worldMatrix, pb.vec4(this.$inputs.pos, 0)).xyz);
1012
1167
  this.$builtins.position = pb.mul(this.viewProjMatrix, pb.vec4(this.worldPos, 1), this.flip);
1013
- this.$builtins.position.z = this.$builtins.position.w;
1168
+ this.$builtins.position.z = ShaderHelper.farthestClipZ(this, this.$builtins.position.w);
1014
1169
  });
1015
1170
  },
1016
1171
  fragment (pb) {
1017
1172
  this.$outputs.outColor = pb.vec4();
1018
1173
  this.skyCubeMap = pb.texCube().uniform(0);
1019
1174
  this.srgbOut = pb.int().uniform(0);
1175
+ this.luminanceScale = pb.float().uniform(0);
1020
1176
  pb.main(function() {
1021
1177
  this.$l.texCoord = pb.normalize(this.$inputs.texCoord);
1022
- this.$l.color = pb.textureSampleLevel(this.skyCubeMap, this.texCoord, 0).rgb;
1178
+ // luminanceScale is 1 in legacy and for the IBL bake; on screen in physical mode it
1179
+ // carries the environment intensity (lux) pre-multiplied by the camera exposure.
1180
+ this.$l.color = pb.mul(pb.textureSampleLevel(this.skyCubeMap, this.texCoord, 0).rgb, this.luminanceScale);
1023
1181
  this.$if(pb.equal(this.srgbOut, 0), function() {
1024
1182
  this.$outputs.outColor = pb.vec4(this.color, 1);
1025
1183
  }).$else(function() {
@@ -1035,7 +1193,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
1035
1193
  }
1036
1194
  if (!SkyRenderer._renderStatesSky) {
1037
1195
  SkyRenderer._renderStatesSky = device.createRenderStateSet();
1038
- SkyRenderer._renderStatesSky.useDepthState().enableTest(true).enableWrite(false).setCompareFunc('le');
1196
+ SkyRenderer._renderStatesSky.useDepthState().enableTest(true).enableWrite(false).setCompareFunc(DEPTH_COMPARE_DEFAULT);
1039
1197
  SkyRenderer._renderStatesSky.useRasterizerState().setCullMode('none');
1040
1198
  }
1041
1199
  if (!SkyRenderer._renderStatesSkyNoDepthTest) {
@@ -1053,7 +1211,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
1053
1211
  SkyRenderer._renderStatesFogScatter = device.createRenderStateSet();
1054
1212
  SkyRenderer._renderStatesFogScatter.useRasterizerState().setCullMode('none');
1055
1213
  SkyRenderer._renderStatesFogScatter.useBlendingState().enable(true).setBlendFunc('one', 'src-alpha');
1056
- SkyRenderer._renderStatesFogScatter.useDepthState().enableTest(true).enableWrite(false).setCompareFunc('gt');
1214
+ SkyRenderer._renderStatesFogScatter.useDepthState().enableTest(true).enableWrite(false).setCompareFunc(DEPTH_COMPARE_FARTHER);
1057
1215
  }
1058
1216
  if (!SkyRenderer._renderStatesDistantLight) {
1059
1217
  SkyRenderer._renderStatesDistantLight = device.createRenderStateSet();
@@ -1107,7 +1265,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
1107
1265
  this.$inputs.pos = pb.vec2().attrib('position');
1108
1266
  this.$outputs.uv = pb.vec2();
1109
1267
  pb.main(function() {
1110
- this.$builtins.position = pb.vec4(this.$inputs.pos, 1, 1);
1268
+ this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
1111
1269
  this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
1112
1270
  if (device.type === 'webgpu') {
1113
1271
  this.$if(pb.notEqual(this.rt, 0), function() {
@@ -1134,11 +1292,11 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
1134
1292
  this.srgbOut = pb.int().uniform(0);
1135
1293
  this.$outputs.outColor = pb.vec4();
1136
1294
  pb.main(function() {
1137
- this.$l.depthValue = noDepth ? pb.float(1) : pb.textureSample(this.depthTex, this.$inputs.uv).r;
1138
- this.$l.clipSpacePos = pb.vec4(pb.sub(pb.mul(this.$inputs.uv, 2), pb.vec2(1)), pb.sub(pb.mul(this.depthValue, 2), 1), 1);
1295
+ this.$l.depthValue = noDepth ? pb.float(DEPTH_FARTHEST) : pb.textureSample(this.depthTex, this.$inputs.uv).r;
1296
+ this.$l.clipSpacePos = pb.vec4(pb.sub(pb.mul(this.$inputs.uv, 2), pb.vec2(1)), ShaderHelper.deviceDepthToClipZ(this, this.depthValue), 1);
1139
1297
  this.$l.hPos = pb.mul(this.invProjViewMatrix, this.clipSpacePos);
1140
1298
  this.$l.worldPos = pb.div(this.$l.hPos, this.$l.hPos.w).xyz;
1141
- this.$l.isSky = pb.equal(this.$l.depthValue, 1);
1299
+ this.$l.isSky = ShaderHelper.isFarthestDepth(this, this.$l.depthValue);
1142
1300
  this.$l.color = calculateFog(this, this.withAerialPerspective, this.fogType, this.atmosphereParams, this.heightFogParams, this.$inputs.uv, this.isSky, this.cameraPosition, this.worldPos, 0, this.apLut, this.skyDistantLightLut);
1143
1301
  this.$if(pb.equal(this.srgbOut, 0), function() {
1144
1302
  this.$outputs.outColor = this.color;
@@ -1184,7 +1342,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
1184
1342
  pb.main(function() {
1185
1343
  this.$outputs.worldDirection = pb.mul(this.worldMatrix, pb.vec4(this.$inputs.pos, 0)).xyz;
1186
1344
  this.$builtins.position = pb.mul(this.viewProjMatrix, pb.vec4(pb.add(this.$outputs.worldDirection, this.cameraPos), 1), this.flip);
1187
- this.$builtins.position.z = this.$builtins.position.w;
1345
+ this.$builtins.position.z = ShaderHelper.farthestClipZ(this, this.$builtins.position.w);
1188
1346
  });
1189
1347
  },
1190
1348
  fragment (pb) {
@@ -1192,6 +1350,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
1192
1350
  this.tLut = pb.tex2D().uniform(0);
1193
1351
  this.skyLut = pb.tex2D().uniform(0);
1194
1352
  this.params = getAtmosphereParamsStruct(pb)().uniform(0);
1353
+ this.includeSunDisk = pb.int().uniform(0);
1195
1354
  if (cloud) {
1196
1355
  this.cloudy = pb.float().uniform(0);
1197
1356
  this.cloudIntensity = pb.float().uniform(0);
@@ -1199,6 +1358,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
1199
1358
  this.velocity = pb.vec2().uniform(0);
1200
1359
  }
1201
1360
  this.srgbOut = pb.int().uniform(0);
1361
+ this.luminanceScale = pb.float().uniform(0);
1202
1362
  pb.func('noise', [
1203
1363
  pb.vec3('p'),
1204
1364
  pb.float('t')
@@ -1222,7 +1382,7 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
1222
1382
  pb.main(function() {
1223
1383
  // Calculate sky color
1224
1384
  this.$l.sunColor = pb.vec4();
1225
- this.$l.skyColor = skyBox(this, this.params, this.sunColor, this.$inputs.worldDirection, pb.float(0.01), this.tLut, this.skyLut).rgb;
1385
+ this.$l.skyColor = skyBox(this, this.params, this.sunColor, this.$inputs.worldDirection, pb.float(0.01), this.includeSunDisk, this.tLut, this.skyLut).rgb;
1226
1386
  this.$l.rayDir = pb.normalize(this.$inputs.worldDirection);
1227
1387
  this.$l.sunDir = this.params.lightDir;
1228
1388
  // ad-hoc
@@ -1258,6 +1418,8 @@ const defaultSkyWorldMatrix = Matrix4x4.identity();
1258
1418
  } else {
1259
1419
  this.$l.color = this.skyColor;
1260
1420
  }
1421
+ // 1 for legacy and for the IBL bake; the camera pre-exposure when drawn on screen.
1422
+ this.color = pb.mul(this.color, this.luminanceScale);
1261
1423
  this.$if(pb.equal(this.srgbOut, 0), function() {
1262
1424
  this.$outputs.outColor = pb.vec4(this.color, 1);
1263
1425
  }).$else(function() {