@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
@@ -1,7 +1,8 @@
1
- import { halton23, Matrix4x4, Vector4, DRef, Vector2, Vector3, Ray, Frustum } from '@zephyr3d/base';
1
+ import { halton23, Matrix4x4, Vector4, DEPTH_CLEAR_VALUE, DRef, Vector2, REVERSE_Z, Vector3, Ray, Frustum } from '@zephyr3d/base';
2
2
  import { SceneNode } from '../scene/scene_node.js';
3
3
  import { Compositor } from '../posteffect/compositor.js';
4
4
  import { TAA } from '../posteffect/taa.js';
5
+ import { SSGI } from '../posteffect/ssgi.js';
5
6
  import { SSR } from '../posteffect/ssr.js';
6
7
  import { SSS } from '../posteffect/sss.js';
7
8
  import { SkinSSS } from '../posteffect/skinsss.js';
@@ -16,7 +17,29 @@ import { ScreenAdapter } from '../app/screen.js';
16
17
  import { ABufferOIT } from '../render/abuffer_oit.js';
17
18
  import { WeightedBlendedOIT } from '../render/weightedblended_oit.js';
18
19
  import { DualDepthPeelingOIT } from '../render/dualdepthpeeling_oit.js';
20
+ import { RGHistoryResources } from '../render/rendergraph/history_resources.js';
21
+ import { calculateEV100, calculatePhysicalExposure } from '../utility/physical.js';
19
22
 
23
+ const SSGI_QUALITY_PRESET_SETTINGS = {
24
+ quality: {
25
+ halfRes: false,
26
+ raysPerPixel: 2,
27
+ maxSteps: 64,
28
+ denoisePasses: 4
29
+ },
30
+ balanced: {
31
+ halfRes: true,
32
+ raysPerPixel: 1,
33
+ maxSteps: 48,
34
+ denoisePasses: 4
35
+ },
36
+ performance: {
37
+ halfRes: true,
38
+ raysPerPixel: 1,
39
+ maxSteps: 24,
40
+ denoisePasses: 4
41
+ }
42
+ };
20
43
  const SSS_DEFAULT_SETTINGS = {
21
44
  blurScale: 11,
22
45
  strength: 0.65,
@@ -103,6 +126,11 @@ const SSS_QUALITY_PRESET_SETTINGS = {
103
126
  /** @internal Tonemap enable flag (via post effect). */ _toneMap;
104
127
  /** @internal Tonemap post effect reference. */ _postEffectTonemap;
105
128
  /** @internal Tonemap exposure. */ _tonemapExposure;
129
+ /** @internal Lens aperture expressed as an f-number. */ _aperture;
130
+ /** @internal Shutter-open time in seconds. */ _shutterSpeed;
131
+ /** @internal Sensor sensitivity. */ _ISO;
132
+ /** @internal Exposure compensation in stops. */ _exposureCompensation;
133
+ /** @internal Lighting mode currently reflected by the post effect ordering. */ _postProcessingLightingMode;
106
134
  /** @internal Motion blur enable flag (via post effect). */ _motionBlur;
107
135
  /** @internal Motion blur post effect reference. */ _postEffectMotionBlur;
108
136
  /** @internal Motion blur strength. */ _motionBlurStrength;
@@ -138,6 +166,22 @@ const SSS_QUALITY_PRESET_SETTINGS = {
138
166
  /** @internal SSR Gaussian blur standard deviation. */ _ssrBlurStdDev;
139
167
  /** @internal Whether SSR temporal accumulation is enabled. */ _ssrTemporal;
140
168
  /** @internal SSR temporal blending weight in [0, 1]. */ _ssrTemporalWeight;
169
+ /** @internal SSGI enable flag (via post effect). */ _SSGI;
170
+ /** @internal SSGI post effect reference. */ _postEffectSSGI;
171
+ /** @internal SSGI quality preset. */ _ssgiQualityPreset;
172
+ /** @internal Resolved SSGI trace/denoise settings. */ _ssgiResolvedSettings;
173
+ /** @internal SSGI irradiance composite strength. */ _ssgiIntensity;
174
+ /** @internal Strength of the ambient occlusion traced alongside SSGI. */ _ssgiAOIntensity;
175
+ /** @internal Contrast curve of the SSGI ambient occlusion. */ _ssgiAOPower;
176
+ /** @internal Fraction of environment irradiance removed by an occluding hit. */ _ssgiSkyOcclusion;
177
+ /** @internal Maximum view-space ray distance. */ _ssgiMaxDistance;
178
+ /** @internal Ray hit thickness. */ _ssgiThickness;
179
+ /** @internal Linear trace stride. */ _ssgiStride;
180
+ /** @internal Firefly clamp applied to sampled radiance. */ _ssgiMaxRayIntensity;
181
+ /** @internal Whether SSGI temporal accumulation is enabled. */ _ssgiTemporal;
182
+ /** @internal Weight of valid reprojected irradiance. */ _ssgiTemporalWeight;
183
+ /** @internal Maximum linear-depth delta in scene units accepted by reprojection. */ _ssgiDepthReject;
184
+ /** @internal Minimum normal dot product accepted by reprojection. */ _ssgiNormalReject;
141
185
  /** @internal SSS enable flag (via post effect). */ _SSS;
142
186
  /** @internal SSS post effect reference. */ _postEffectSSS;
143
187
  /** @internal SSS blur scale in pixels per authored width unit. */ _sssBlurScale;
@@ -194,7 +238,7 @@ const SSS_QUALITY_PRESET_SETTINGS = {
194
238
  this._invViewProjMatrix = Matrix4x4.identity();
195
239
  this._clipPlane = null;
196
240
  this._clearColor = new Vector4(0, 0, 0, 1);
197
- this._clearDepth = 1;
241
+ this._clearDepth = DEPTH_CLEAR_VALUE;
198
242
  this._clearStencil = 0;
199
243
  this._dirty = true;
200
244
  this._controller = null;
@@ -220,6 +264,11 @@ const SSS_QUALITY_PRESET_SETTINGS = {
220
264
  this._toneMap = true;
221
265
  this._postEffectTonemap = new DRef();
222
266
  this._tonemapExposure = 1;
267
+ this._aperture = 16;
268
+ this._shutterSpeed = 1 / 125;
269
+ this._ISO = 100;
270
+ this._exposureCompensation = 0;
271
+ this._postProcessingLightingMode = null;
223
272
  this._motionBlur = false;
224
273
  this._postEffectMotionBlur = new DRef();
225
274
  this._motionBlurStrength = 1;
@@ -255,6 +304,25 @@ const SSS_QUALITY_PRESET_SETTINGS = {
255
304
  this._ssrBlurStdDev = 4;
256
305
  this._ssrTemporal = true;
257
306
  this._ssrTemporalWeight = 0.85;
307
+ this._SSGI = false;
308
+ this._postEffectSSGI = new DRef();
309
+ this._ssgiQualityPreset = 'quality';
310
+ const defaultSSGIQualityPreset = SSGI_QUALITY_PRESET_SETTINGS.quality;
311
+ this._ssgiResolvedSettings = {
312
+ ...defaultSSGIQualityPreset
313
+ };
314
+ this._ssgiIntensity = 0.7;
315
+ this._ssgiAOIntensity = 0.8;
316
+ this._ssgiAOPower = 1;
317
+ this._ssgiSkyOcclusion = 1;
318
+ this._ssgiMaxDistance = 32;
319
+ this._ssgiThickness = 0.5;
320
+ this._ssgiStride = 1;
321
+ this._ssgiMaxRayIntensity = 10;
322
+ this._ssgiTemporal = true;
323
+ this._ssgiTemporalWeight = 0.94;
324
+ this._ssgiDepthReject = 0.5;
325
+ this._ssgiNormalReject = 0.75;
258
326
  this._SSS = false;
259
327
  this._postEffectSSS = new DRef();
260
328
  this._sssBlurScale = SSS_DEFAULT_SETTINGS.blurScale;
@@ -530,10 +598,39 @@ const SSS_QUALITY_PRESET_SETTINGS = {
530
598
  return this._tonemapExposure;
531
599
  }
532
600
  set toneMapExposure(val) {
601
+ // syncPostProcessingMode() re-uploads the resolved exposure every frame, so this only needs to
602
+ // record the authored value. Legacy scenes read it back verbatim; physical scenes ignore it.
533
603
  this._tonemapExposure = val;
534
- if (this._postEffectTonemap.get()) {
535
- this._postEffectTonemap.get().exposure = val;
536
- }
604
+ }
605
+ /** Lens aperture as an f-number. */ get aperture() {
606
+ return this._aperture;
607
+ }
608
+ set aperture(val) {
609
+ this._aperture = Math.max(0.0001, val);
610
+ }
611
+ /** Shutter-open time in seconds. */ get shutterSpeed() {
612
+ return this._shutterSpeed;
613
+ }
614
+ set shutterSpeed(val) {
615
+ this._shutterSpeed = Math.max(0.000001, val);
616
+ }
617
+ /** Sensor sensitivity in ISO units. */ get ISO() {
618
+ return this._ISO;
619
+ }
620
+ set ISO(val) {
621
+ this._ISO = Math.max(0.0001, val);
622
+ }
623
+ /** Exposure compensation in stops (EV). */ get exposureCompensation() {
624
+ return this._exposureCompensation;
625
+ }
626
+ set exposureCompensation(val) {
627
+ this._exposureCompensation = val;
628
+ }
629
+ /** Photographic EV100 calculated from aperture, shutter time and ISO. */ get EV100() {
630
+ return calculateEV100(this._aperture, this._shutterSpeed, this._ISO);
631
+ }
632
+ /** Scene-linear physical exposure multiplier, including exposure compensation. */ get exposure() {
633
+ return calculatePhysicalExposure(this._aperture, this._shutterSpeed, this._ISO, this._exposureCompensation);
537
634
  }
538
635
  /**
539
636
  * Gets whether TAA is enabled.
@@ -567,6 +664,195 @@ const SSS_QUALITY_PRESET_SETTINGS = {
567
664
  set SSR(val) {
568
665
  this._postEffectSSR.get().enabled = !!val;
569
666
  }
667
+ /** Gets whether Screen Space Global Illumination is enabled for this camera. */ get SSGI() {
668
+ return this._postEffectSSGI.get().enabled;
669
+ }
670
+ set SSGI(val) {
671
+ const next = !!val;
672
+ if (next !== this._postEffectSSGI.get().enabled) {
673
+ this._postEffectSSGI.get().enabled = next;
674
+ this.invalidateSSGIHistory();
675
+ }
676
+ }
677
+ /** High-level SSGI trace and denoise quality preset. */ get ssgiQualityPreset() {
678
+ return this._ssgiQualityPreset;
679
+ }
680
+ set ssgiQualityPreset(val) {
681
+ const next = Camera.resolveSSGIQualityPreset(val);
682
+ if (next !== this._ssgiQualityPreset) {
683
+ this._ssgiQualityPreset = next;
684
+ if (next !== 'custom') {
685
+ this._ssgiResolvedSettings = {
686
+ ...SSGI_QUALITY_PRESET_SETTINGS[next]
687
+ };
688
+ }
689
+ this.invalidateSSGIHistory();
690
+ }
691
+ }
692
+ /** Resolved trace and denoise settings for the current SSGI preset. */ get ssgiResolvedSettings() {
693
+ return this._ssgiResolvedSettings;
694
+ }
695
+ /** Whether the custom SSGI trace runs at half resolution. */ get ssgiHalfResolution() {
696
+ return this._ssgiResolvedSettings.halfRes;
697
+ }
698
+ set ssgiHalfResolution(val) {
699
+ this.updateSSGICustomSettings({
700
+ halfRes: !!val
701
+ });
702
+ }
703
+ /** Number of diffuse rays traced per pixel by the custom SSGI preset. */ get ssgiRaysPerPixel() {
704
+ return this._ssgiResolvedSettings.raysPerPixel;
705
+ }
706
+ set ssgiRaysPerPixel(val) {
707
+ this.updateSSGICustomSettings({
708
+ raysPerPixel: Math.max(1, Math.min(4, Math.round(val ?? 1)))
709
+ });
710
+ }
711
+ /** Maximum screen-space ray-march iterations used by the custom SSGI preset. */ get ssgiMaxSteps() {
712
+ return this._ssgiResolvedSettings.maxSteps;
713
+ }
714
+ set ssgiMaxSteps(val) {
715
+ this.updateSSGICustomSettings({
716
+ maxSteps: Math.max(1, Math.min(256, Math.round(val ?? 1)))
717
+ });
718
+ }
719
+ /** Number of cross-bilateral a-trous passes used by the custom SSGI preset. */ get ssgiDenoisePasses() {
720
+ return this._ssgiResolvedSettings.denoisePasses;
721
+ }
722
+ set ssgiDenoisePasses(val) {
723
+ this.updateSSGICustomSettings({
724
+ denoisePasses: Math.max(0, Math.min(5, Math.round(val ?? 0)))
725
+ });
726
+ }
727
+ /** SSGI diffuse irradiance multiplier. */ get ssgiIntensity() {
728
+ return this._ssgiIntensity;
729
+ }
730
+ set ssgiIntensity(val) {
731
+ const next = Math.max(0, val ?? 0);
732
+ if (next !== this._ssgiIntensity) {
733
+ this._ssgiIntensity = next;
734
+ this.invalidateSSGIHistory();
735
+ }
736
+ }
737
+ /**
738
+ * How much environment irradiance an occluding hit removes, in [0, 1].
739
+ *
740
+ * A screen-space ray that hits geometry blocks the sky for that direction. At 1
741
+ * the environment contribution is removed in full, which is what produces sky
742
+ * occlusion in interiors and corners; lower values dim the sky less than the
743
+ * geometry implies, for art direction or to compensate for the hemisphere a
744
+ * screen-space trace cannot see. Only the removal is scaled — bounce light
745
+ * measured at the hit is always kept, so lowering this brightens without ever
746
+ * discarding indirect light. {@link Camera.ssgiIntensity} still bounds how dark
747
+ * the result can get, so full occlusion also needs an intensity of 1.
748
+ */ get ssgiSkyOcclusion() {
749
+ return this._ssgiSkyOcclusion;
750
+ }
751
+ set ssgiSkyOcclusion(val) {
752
+ const next = Math.max(0, Math.min(1, val ?? 0));
753
+ if (next !== this._ssgiSkyOcclusion) {
754
+ this._ssgiSkyOcclusion = next;
755
+ this.invalidateSSGIHistory();
756
+ }
757
+ }
758
+ /**
759
+ * How strongly the ambient occlusion traced alongside SSGI darkens the scene,
760
+ * in [0, 1]. 0 disables the composite entirely.
761
+ *
762
+ * The occlusion comes from the same rays that produce the irradiance, at no
763
+ * extra trace cost, and is multiplied into the final opaque color — the same
764
+ * semantics the standalone {@link SAO} post effect applies. **Enabling both at
765
+ * once therefore darkens twice.** Its range is the SSGI trace range
766
+ * ({@link Camera.ssgiMaxDistance}), so this is long-range occlusion rather than
767
+ * the small-radius contact darkening a dedicated AO pass produces.
768
+ *
769
+ * Defaults to 0.8 rather than 1: SSGI irradiance already has sky occlusion
770
+ * subtracted, so diffuse is partly occluded before this multiply is applied.
771
+ */ get ssgiAOIntensity() {
772
+ return this._ssgiAOIntensity;
773
+ }
774
+ set ssgiAOIntensity(val) {
775
+ this._ssgiAOIntensity = Math.max(0, Math.min(1, val ?? 0));
776
+ }
777
+ /**
778
+ * Contrast curve applied to the SSGI ambient occlusion before it is composited.
779
+ *
780
+ * Values above 1 deepen the occluded regions, below 1 lift them. Purely an art
781
+ * direction control on an already-traced quantity.
782
+ */ get ssgiAOPower() {
783
+ return this._ssgiAOPower;
784
+ }
785
+ set ssgiAOPower(val) {
786
+ this._ssgiAOPower = Math.max(0.01, val ?? 1);
787
+ }
788
+ /** Maximum SSGI ray length in view-space units. */ get ssgiMaxDistance() {
789
+ return this._ssgiMaxDistance;
790
+ }
791
+ set ssgiMaxDistance(val) {
792
+ const next = Math.max(0, val ?? 0);
793
+ if (next !== this._ssgiMaxDistance) {
794
+ this._ssgiMaxDistance = next;
795
+ this.invalidateSSGIHistory();
796
+ }
797
+ }
798
+ /** Depth thickness used by screen-space ray intersection tests. */ get ssgiThickness() {
799
+ return this._ssgiThickness;
800
+ }
801
+ set ssgiThickness(val) {
802
+ const next = Math.max(0.0001, val ?? 0.0001);
803
+ if (next !== this._ssgiThickness) {
804
+ this._ssgiThickness = next;
805
+ this.invalidateSSGIHistory();
806
+ }
807
+ }
808
+ /** Pixel stride used by the WebGL linear ray marcher. */ get ssgiStride() {
809
+ return this._ssgiStride;
810
+ }
811
+ set ssgiStride(val) {
812
+ const next = Math.max(1, Math.round(val ?? 1));
813
+ if (next !== this._ssgiStride) {
814
+ this._ssgiStride = next;
815
+ this.invalidateSSGIHistory();
816
+ }
817
+ }
818
+ /** Maximum per-ray radiance before temporal accumulation (firefly clamp). */ get ssgiMaxRayIntensity() {
819
+ return this._ssgiMaxRayIntensity;
820
+ }
821
+ set ssgiMaxRayIntensity(val) {
822
+ const next = Math.max(0, val ?? 0);
823
+ if (next !== this._ssgiMaxRayIntensity) {
824
+ this._ssgiMaxRayIntensity = next;
825
+ this.invalidateSSGIHistory();
826
+ }
827
+ }
828
+ /** Whether WebGPU SSGI temporal accumulation is enabled. */ get ssgiTemporal() {
829
+ return this._ssgiTemporal;
830
+ }
831
+ set ssgiTemporal(val) {
832
+ const next = !!val;
833
+ if (next !== this._ssgiTemporal) {
834
+ this._ssgiTemporal = next;
835
+ this.invalidateSSGIHistory();
836
+ }
837
+ }
838
+ /** Weight assigned to valid reprojected SSGI history. */ get ssgiTemporalWeight() {
839
+ return this._ssgiTemporalWeight;
840
+ }
841
+ set ssgiTemporalWeight(val) {
842
+ this._ssgiTemporalWeight = Math.max(0, Math.min(1, val ?? 0));
843
+ }
844
+ /** Relative linear-depth rejection threshold for SSGI reprojection. */ get ssgiDepthReject() {
845
+ return this._ssgiDepthReject;
846
+ }
847
+ set ssgiDepthReject(val) {
848
+ this._ssgiDepthReject = Math.max(0, val ?? 0);
849
+ }
850
+ /** Minimum world-normal dot product accepted by SSGI reprojection. */ get ssgiNormalReject() {
851
+ return this._ssgiNormalReject;
852
+ }
853
+ set ssgiNormalReject(val) {
854
+ this._ssgiNormalReject = Math.max(-1, Math.min(1, val ?? 0));
855
+ }
570
856
  /**
571
857
  * Gets the maximum roughness value for screen space reflections.
572
858
  * Controls the cutoff point where surfaces are considered too rough for SSR.
@@ -1019,8 +1305,8 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1019
1305
  const height = this.viewport ? this.viewport[3] : getDevice().getViewport().height;
1020
1306
  const ndcX = 2 * x / width - 1;
1021
1307
  const ndcY = 1 - 2 * y / height;
1022
- const nearClip = new Vector4(ndcX, ndcY, -1, 1);
1023
- const farClip = new Vector4(ndcX, ndcY, 1, 1);
1308
+ const nearClip = new Vector4(ndcX, ndcY, REVERSE_Z ? 1 : -1, 1);
1309
+ const farClip = new Vector4(ndcX, ndcY, REVERSE_Z ? 0 : 1, 1);
1024
1310
  const nearWorld = this.invViewProjectionMatrix.transform(nearClip);
1025
1311
  const farWorld = this.invViewProjectionMatrix.transform(farClip);
1026
1312
  if (this.isPerspective()) {
@@ -1235,6 +1521,13 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1235
1521
  }
1236
1522
  /** @internal */ updatePostProcessing() {
1237
1523
  this._compositor.clear();
1524
+ if (!this._postEffectSSGI.get()) {
1525
+ const ssgi = new SSGI();
1526
+ ssgi.enabled = false;
1527
+ this._postEffectSSGI.set(ssgi);
1528
+ // SSGI resolves opaque linear HDR before SSR, transparency and AA.
1529
+ this._compositor.appendPostEffect(ssgi);
1530
+ }
1238
1531
  if (!this._postEffectSSR.get()) {
1239
1532
  const ssr = new SSR();
1240
1533
  ssr.enabled = false;
@@ -1317,6 +1610,25 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1317
1610
  this._compositor.appendPostEffect(bloom);
1318
1611
  }
1319
1612
  }
1613
+ /** @internal */ syncPostProcessingMode(scene) {
1614
+ const tonemap = this._postEffectTonemap.get();
1615
+ // Physical lighting pre-exposes every light quantity on the CPU (see ShaderHelper.getPreExposure),
1616
+ // so the HDR buffer already sits near 1.0 and tonemapping only applies the ACES curve. Legacy
1617
+ // keeps its plain exposure multiplier.
1618
+ const usePhysicalExposure = scene.lightingMode === 'physical';
1619
+ tonemap.exposure = usePhysicalExposure ? 1 : this._tonemapExposure;
1620
+ const bloom = this._postEffectBloom.get();
1621
+ if (this._postProcessingLightingMode !== scene.lightingMode) {
1622
+ // SSGI histories contain scene-linear radiance. Never reuse them across lighting unit models.
1623
+ this.invalidateSSGIHistory();
1624
+ if (scene.lightingMode === 'physical') {
1625
+ this._compositor.movePostEffectBefore(bloom, tonemap);
1626
+ } else {
1627
+ this._compositor.movePostEffectAfter(bloom, this._postEffectFXAA.get());
1628
+ }
1629
+ this._postProcessingLightingMode = scene.lightingMode;
1630
+ }
1631
+ }
1320
1632
  static resolveSSSQualityPreset(val) {
1321
1633
  switch(val){
1322
1634
  case 'quality':
@@ -1327,6 +1639,36 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1327
1639
  return 'balanced';
1328
1640
  }
1329
1641
  }
1642
+ static resolveSSGIQualityPreset(val) {
1643
+ switch(val){
1644
+ case 'quality':
1645
+ case 'balanced':
1646
+ case 'performance':
1647
+ case 'custom':
1648
+ return val;
1649
+ default:
1650
+ return 'quality';
1651
+ }
1652
+ }
1653
+ updateSSGICustomSettings(settings) {
1654
+ const next = {
1655
+ ...this._ssgiResolvedSettings,
1656
+ ...settings
1657
+ };
1658
+ if (this._ssgiQualityPreset !== 'custom' || next.halfRes !== this._ssgiResolvedSettings.halfRes || next.raysPerPixel !== this._ssgiResolvedSettings.raysPerPixel || next.maxSteps !== this._ssgiResolvedSettings.maxSteps || next.denoisePasses !== this._ssgiResolvedSettings.denoisePasses) {
1659
+ this._ssgiQualityPreset = 'custom';
1660
+ this._ssgiResolvedSettings = next;
1661
+ this.invalidateSSGIHistory();
1662
+ }
1663
+ }
1664
+ /** @internal Invalidate only the histories owned by SSGI. */ invalidateSSGIHistory() {
1665
+ const history = Camera._historyResourceManager.get(this);
1666
+ history?.invalidate(RGHistoryResources.SSGI_SCENE_COLOR);
1667
+ history?.invalidate(RGHistoryResources.SSGI_IRRADIANCE);
1668
+ history?.invalidate(RGHistoryResources.SSGI_SURFACE);
1669
+ history?.invalidate(RGHistoryResources.SSGI_MOMENTS);
1670
+ history?.invalidate(RGHistoryResources.SSGI_AO);
1671
+ }
1330
1672
  updateSSSResolvedSettings() {
1331
1673
  const source = SSS_QUALITY_PRESET_SETTINGS[this._sssQualityPreset];
1332
1674
  this._sssResolvedSettings.halfRes = source.halfRes;
@@ -1341,53 +1683,51 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1341
1683
  * @param compositor - Compositor instance that will be used to apply postprocess effects
1342
1684
  */ render(scene) {
1343
1685
  const device = getDevice();
1344
- //this.updatePostProcessing(device);
1345
- const useMotionVector = (this.TAA || this.motionBlur || this.SSR && this.ssrTemporal) && device.type !== 'webgl';
1346
- const useTAA = useMotionVector && this.TAA;
1686
+ this.syncPostProcessingMode(scene);
1347
1687
  scene.dispatchEvent('startrender', scene, this, this._compositor);
1348
- if (useMotionVector) {
1349
- const width = device.getDrawingBufferWidth();
1350
- const height = device.getDrawingBufferHeight();
1351
- if (useTAA) {
1352
- const halton = Camera._halton23[device.frameInfo.frameCounter % Camera._halton23.length];
1353
- this._jitterValue.setXY(halton[0] * 2 / width, halton[1] * 2 / height);
1354
- } else {
1355
- this._jitterValue.setXY(0, 0);
1356
- }
1357
- this._jitteredVPMatrix.set(this.getProjectionMatrix());
1358
- this._jitteredVPMatrix[8] += this._jitterValue.x;
1359
- this._jitteredVPMatrix[9] += this._jitterValue.y;
1360
- this._jitteredVPMatrix.multiplyRight(this.viewMatrix);
1361
- Matrix4x4.invert(this._jitteredVPMatrix, this._jitteredInvVPMatrix);
1362
- if (!this._prevJitteredVPMatrix) {
1363
- this._prevJitteredVPMatrix = new Matrix4x4();
1364
- this._prevJitteredVPMatrix.set(this._jitteredVPMatrix);
1365
- this._prevJitterValue = new Vector2(this._jitterValue);
1366
- }
1367
- if (!this._prevVPMatrix) {
1368
- this._prevVPMatrix = new Matrix4x4();
1369
- this._prevVPMatrix.set(this.viewProjectionMatrix);
1370
- this._prevPosition = this.getWorldPosition();
1371
- }
1372
- } else {
1373
- this._jitterValue.setXY(0, 0);
1374
- this._prevVPMatrix = null;
1375
- this._prevPosition = null;
1376
- this._prevJitteredVPMatrix = null;
1377
- this._prevJitterValue = null;
1378
- this._jitteredInvVPMatrix.set(this.invViewProjectionMatrix);
1379
- }
1380
1688
  device.pushDeviceStates();
1381
1689
  device.reverseVertexWindingOrder(false);
1382
1690
  scene.getRenderer().renderScene(scene, this);
1383
1691
  device.popDeviceStates();
1384
- if (useMotionVector) {
1692
+ scene.dispatchEvent('endrender', scene, this, this._compositor);
1693
+ }
1694
+ /** Prepare current/previous camera transforms used by a motion-vector pass. @internal */ prepareMotionVectorFrame(useTAA, width, height) {
1695
+ const device = getDevice();
1696
+ if (useTAA) {
1697
+ const halton = Camera._halton23[device.frameInfo.frameCounter % Camera._halton23.length];
1698
+ this._jitterValue.setXY(halton[0] * 2 / width, halton[1] * 2 / height);
1699
+ } else {
1700
+ this._jitterValue.setXY(0, 0);
1701
+ }
1702
+ this._jitteredVPMatrix.set(this.getProjectionMatrix());
1703
+ this._jitteredVPMatrix[8] += this._jitterValue.x;
1704
+ this._jitteredVPMatrix[9] += this._jitterValue.y;
1705
+ this._jitteredVPMatrix.multiplyRight(this.viewMatrix);
1706
+ Matrix4x4.invert(this._jitteredVPMatrix, this._jitteredInvVPMatrix);
1707
+ if (!this._prevJitteredVPMatrix) {
1708
+ this._prevJitteredVPMatrix = new Matrix4x4();
1385
1709
  this._prevJitteredVPMatrix.set(this._jitteredVPMatrix);
1386
- this._prevJitterValue.set(this._jitterValue);
1710
+ this._prevJitterValue = new Vector2(this._jitterValue);
1711
+ }
1712
+ if (!this._prevVPMatrix) {
1713
+ this._prevVPMatrix = new Matrix4x4();
1387
1714
  this._prevVPMatrix.set(this.viewProjectionMatrix);
1388
1715
  this._prevPosition = this.getWorldPosition();
1389
1716
  }
1390
- scene.dispatchEvent('endrender', scene, this, this._compositor);
1717
+ }
1718
+ /** Commit camera transforms after a motion-vector frame completes. @internal */ commitMotionVectorFrame() {
1719
+ this._prevJitteredVPMatrix.set(this._jitteredVPMatrix);
1720
+ this._prevJitterValue.set(this._jitterValue);
1721
+ this._prevVPMatrix.set(this.viewProjectionMatrix);
1722
+ this._prevPosition = this.getWorldPosition();
1723
+ }
1724
+ /** Clear temporal camera transforms when motion vectors are not in use. @internal */ clearMotionVectorFrame() {
1725
+ this._jitterValue.setXY(0, 0);
1726
+ this._prevVPMatrix = null;
1727
+ this._prevPosition = null;
1728
+ this._prevJitteredVPMatrix = null;
1729
+ this._prevJitterValue = null;
1730
+ this._jitteredInvVPMatrix.set(this.invViewProjectionMatrix);
1391
1731
  }
1392
1732
  async pickAsync(posX, posY) {
1393
1733
  this._pickPosX = posX;
@@ -1459,6 +1799,18 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1459
1799
  }
1460
1800
  Camera._historyResourceManager.set(this, manager);
1461
1801
  }
1802
+ /** @internal */ _renderPipeline = null;
1803
+ /**
1804
+ * The render pipeline that assembles this camera's frame graph. When null
1805
+ * (the default), the shared default Forward+ pipeline is used. Assign a
1806
+ * customized pipeline to override rendering for this camera only, e.g.
1807
+ * `camera.renderPipeline = createForwardPlusPipeline().insertAfter('SkyPass', myModule)`.
1808
+ */ get renderPipeline() {
1809
+ return this._renderPipeline;
1810
+ }
1811
+ set renderPipeline(pipeline) {
1812
+ this._renderPipeline = pipeline;
1813
+ }
1462
1814
  /** @internal */ setController(controller) {
1463
1815
  if (this._controller !== controller) {
1464
1816
  if (controller && controller._getCamera() && controller._getCamera() !== this) {
@@ -1502,6 +1854,7 @@ const SSS_QUALITY_PRESET_SETTINGS = {
1502
1854
  this._postEffectSSAO.dispose();
1503
1855
  this._postEffectSSS.dispose();
1504
1856
  this._postEffectSkinSSS.dispose();
1857
+ this._postEffectSSGI.dispose();
1505
1858
  this._postEffectSSR.dispose();
1506
1859
  this._postEffectTAA.dispose();
1507
1860
  this._postEffectTonemap.dispose();