@zephyr3d/scene 0.9.22 → 0.9.24

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 -8740
  30. package/dist/index.js +10 -2
  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 +512 -334
  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 +22 -16
  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 +257 -95
  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 +149 -40
  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 +16 -11
  189. package/dist/shadow/pcss.js.map +1 -1
  190. package/dist/shadow/shadow_impl.js +29 -3
  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 +59 -65
  233. package/dist/values.js.map +1 -1
  234. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
1
  import { ShaderHelper } from '../material/shader/helper.js';
2
+ import { REVERSE_Z } from '@zephyr3d/base';
2
3
 
3
- const MAX_FLOAT_VALUE = 3.402823466e38;
4
4
  /** @internal */ function SSR_calcJitter(scope, viewPos, roughness) {
5
5
  const pb = scope.$builder;
6
6
  pb.func('SSR_calcJitter', [
@@ -21,14 +21,14 @@ const MAX_FLOAT_VALUE = 3.402823466e38;
21
21
  pb.vec3('p'),
22
22
  pb.mat4('mat')
23
23
  ], function() {
24
- this.$l.c = pb.sub(pb.mul(this.p, 2), pb.vec3(1));
24
+ this.$l.c = pb.vec3(pb.sub(pb.mul(this.p.xy, 2), pb.vec2(1)), ShaderHelper.deviceDepthToClipZ(this, this.p.z));
25
25
  this.$l.u = pb.mul(this.mat, pb.vec4(this.c, 1));
26
26
  this.u = pb.div(this.u, this.u.w);
27
27
  this.$return(this.u.xyz);
28
28
  });
29
29
  return scope.invProjectPosition(pos, mat);
30
30
  }
31
- /** @internal */ function validateHit(scope, hit2D, hit3D, surfaceZ, thickness, uv, traceRay, viewMatrix, invProjMatrix, textureSize, normalTexture) {
31
+ /** @internal */ function validateHit(scope, hit2D, hit3D, surfaceZ, thickness, uv, traceRay, viewMatrix, invProjMatrix, textureSize, normalTexture, debugValidationOut) {
32
32
  const pb = scope.$builder;
33
33
  const funcName = hit3D ? 'SSR_validateHit_HiZ' : 'SSR_validateHit';
34
34
  pb.func(funcName, [
@@ -44,19 +44,34 @@ const MAX_FLOAT_VALUE = 3.402823466e38;
44
44
  pb.vec3('viewSpaceRayDirection'),
45
45
  pb.mat4('viewMatrix'),
46
46
  pb.mat4('invProjMatrix'),
47
- pb.vec4('textureSize')
47
+ pb.vec4('textureSize'),
48
+ ...debugValidationOut ? [
49
+ pb.vec3('debugValidation').out()
50
+ ] : []
48
51
  ], function() {
52
+ if (debugValidationOut) {
53
+ this.debugValidation = pb.vec3(1, 1, 0);
54
+ }
49
55
  this.$if(pb.or(pb.any(pb.lessThan(this.hit2d, pb.vec2(0))), pb.any(pb.greaterThan(this.hit2d, pb.vec2(1)))), function() {
56
+ if (debugValidationOut) {
57
+ this.debugValidation = pb.vec3(0);
58
+ }
50
59
  this.$return(pb.float(0));
51
60
  });
52
61
  this.$l.manhattanDist = pb.abs(pb.sub(this.hit2d, this.uv));
53
62
  this.$if(pb.all(pb.lessThan(this.manhattanDist, pb.vec2(pb.div(pb.vec2(2), this.textureSize.xy)))), function() {
63
+ if (debugValidationOut) {
64
+ this.debugValidation.x = 0;
65
+ }
54
66
  this.$return(pb.float(0));
55
67
  });
56
68
  if (normalTexture) {
57
69
  this.$l.hitNormalWS = pb.sub(pb.mul(pb.textureSampleLevel(normalTexture, this.hit2d, 0).rgb, 2), pb.vec3(1));
58
70
  this.$l.hitNormalVS = pb.mul(this.viewMatrix, pb.vec4(this.hitNormalWS, 0)).xyz;
59
71
  this.$if(pb.greaterThan(pb.dot(this.hitNormalVS, this.viewSpaceRayDirection), 0), function() {
72
+ if (debugValidationOut) {
73
+ this.debugValidation.y = 0;
74
+ }
60
75
  this.$return(pb.float(0));
61
76
  });
62
77
  }
@@ -67,14 +82,38 @@ const MAX_FLOAT_VALUE = 3.402823466e38;
67
82
  if (this.hit3d) {
68
83
  this.$l.distance = pb.distance(this.viewSpaceSurface, this.hit3d);
69
84
  this.$l.confidence = pb.sub(1, pb.smoothStep(0, this.thickness, this.distance));
85
+ if (debugValidationOut) {
86
+ this.debugValidation.z = this.confidence;
87
+ }
70
88
  this.$return(pb.mul(this.vignette, this.confidence, this.confidence));
71
89
  } else {
72
90
  this.$return(this.vignette);
73
91
  }
74
92
  });
75
- return hit3D ? scope[funcName](hit2D, hit3D, thickness, surfaceZ, uv, traceRay, viewMatrix, invProjMatrix, textureSize) : scope[funcName](hit2D, surfaceZ, uv, traceRay, viewMatrix, invProjMatrix, textureSize);
93
+ return hit3D ? debugValidationOut ? scope[funcName](hit2D, hit3D, thickness, surfaceZ, uv, traceRay, viewMatrix, invProjMatrix, textureSize, debugValidationOut) : scope[funcName](hit2D, hit3D, thickness, surfaceZ, uv, traceRay, viewMatrix, invProjMatrix, textureSize) : scope[funcName](hit2D, surfaceZ, uv, traceRay, viewMatrix, invProjMatrix, textureSize);
76
94
  }
77
- function screenSpaceRayTracing_Linear2D(scope, viewPos, traceRay, viewMatrix, projMatrix, invProjMatrix, cameraNearFar, maxDistance, maxIterations, thickness, stride, textureSize, linearDepthTex, normalTexture, useBackfaceDepth) {
95
+ /**
96
+ * Optional `giTraceOut` (both tracers): `vec3(occluded, escaped, rawConfidence)`.
97
+ *
98
+ * Reflections only care whether a hit is usable, so the returned confidence folds
99
+ * together "was the ray blocked" and "can its radiance be trusted". Diffuse GI
100
+ * needs those apart: a hit whose colour is unusable still occludes the sky.
101
+ * - `occluded`: the march intersected geometry (not a sky texel).
102
+ * - `escaped`: the march missed and the ray was provably unoccluded where it
103
+ * stopped. Neither flag set means the outcome is indeterminate (ran out of
104
+ * screen or iterations behind geometry) and callers should exclude the sample
105
+ * rather than treat it as unoccluded.
106
+ * - `certainty`: how certain the intersection is *geometrically* — the hit's
107
+ * distance from the sampled surface relative to the thickness, so a thin-object
108
+ * false positive does not occlude. Deliberately excludes the screen-border,
109
+ * near-self and backface vetoes {@link validateHit} applies: those bound
110
+ * reflection artifacts, whereas for diffuse GI a border, adjacent or
111
+ * back-facing hit is real geometry whose screen colour is still the best
112
+ * available estimate of its outgoing radiance. The march-length attenuation is
113
+ * excluded too, as it would make occlusion depend on camera distance.
114
+ *
115
+ * Callers that omit the parameter generate exactly the same shader as before.
116
+ */ function screenSpaceRayTracing_Linear2D(scope, viewPos, traceRay, viewMatrix, projMatrix, invProjMatrix, cameraNearFar, maxDistance, maxIterations, thickness, stride, textureSize, linearDepthTex, normalTexture, useBackfaceDepth, giTraceOut) {
78
117
  const pb = scope.$builder;
79
118
  pb.func('distanceSquared', [
80
119
  pb.vec2('a'),
@@ -211,7 +250,7 @@ function screenSpaceRayTracing_Linear2D(scope, viewPos, traceRay, viewMatrix, pr
211
250
  this.hit3D = pb.div(this.Q0, this.pqk.w);
212
251
  this.$return(this.intersected);
213
252
  });
214
- pb.func('SSR_Linear2D', [
253
+ pb.func(giTraceOut ? 'SSR_Linear2D_GI' : 'SSR_Linear2D', [
215
254
  pb.vec3('rayOrigin'),
216
255
  pb.vec3('rayDirection'),
217
256
  pb.mat4('viewMatrix'),
@@ -222,22 +261,50 @@ function screenSpaceRayTracing_Linear2D(scope, viewPos, traceRay, viewMatrix, pr
222
261
  pb.float('maxIterations'),
223
262
  pb.float('thickness'),
224
263
  pb.vec2('cameraNearFar'),
225
- pb.vec4('textureSize')
264
+ pb.vec4('textureSize'),
265
+ ...giTraceOut ? [
266
+ pb.vec3('giTrace').out()
267
+ ] : []
226
268
  ], function() {
269
+ if (giTraceOut) {
270
+ this.giTrace = pb.vec3(0);
271
+ }
227
272
  this.$l.hit2D = pb.vec3();
228
273
  this.$l.hit3D = pb.vec3();
229
274
  this.$l.origin = pb.vec2();
230
275
  this.$l.numIterations = pb.float();
231
276
  this.$l.intersected = this.traceRayLinear2D(this.rayOrigin, this.rayDirection, this.stride, this.maxDistance, this.maxIterations, this.thickness, this.cameraNearFar, this.projMatrix, this.textureSize, this.hit2D, this.hit3D, this.origin, this.numIterations);
232
- this.$if(pb.not(this.intersected), function() {
233
- this.$return(pb.vec4(0));
234
- });
235
- this.$l.surfaceZ01 = ShaderHelper.sampleLinearDepth(this, linearDepthTex, this.hit2D.xy, 0);
277
+ if (giTraceOut) {
278
+ // Depth where the march stopped. A miss only counts as a free escape
279
+ // when the ray was still in front of the depth buffer there; a ray that
280
+ // left the screen from behind geometry is indeterminate, not unoccluded.
281
+ this.$l.exitSurfaceZ01 = ShaderHelper.sampleLinearDepth(this, linearDepthTex, this.hit2D.xy, 0);
282
+ this.$if(pb.not(this.intersected), function() {
283
+ this.$l.exitSceneZ = pb.neg(pb.mul(this.exitSurfaceZ01, this.cameraNearFar.y));
284
+ this.giTrace = pb.vec3(0, pb.float(pb.or(pb.greaterThanEqual(this.exitSurfaceZ01, 1), pb.greaterThan(this.hit2D.z, this.exitSceneZ))), 0);
285
+ this.$return(pb.vec4(0));
286
+ });
287
+ } else {
288
+ this.$if(pb.not(this.intersected), function() {
289
+ this.$return(pb.vec4(0));
290
+ });
291
+ }
292
+ this.$l.surfaceZ01 = giTraceOut ? this.exitSurfaceZ01 : ShaderHelper.sampleLinearDepth(this, linearDepthTex, this.hit2D.xy, 0);
236
293
  this.$if(pb.equal(this.surfaceZ01, 1), function() {
294
+ if (giTraceOut) {
295
+ // The intersection resolved onto a sky texel, so the ray escaped.
296
+ this.giTrace = pb.vec3(0, 1, 0);
297
+ }
237
298
  this.$return(pb.vec4(0));
238
299
  });
239
300
  this.$l.surfaceZ = ShaderHelper.linearDepthToNonLinear(this, pb.mul(this.surfaceZ01, this.cameraNearFar.y), this.cameraNearFar);
240
301
  this.$l.confidence = validateHit(this, this.hit2D.xy, null, this.surfaceZ, null, this.origin, this.rayDirection, this.viewMatrix, this.invProjMatrix, this.textureSize, normalTexture);
302
+ if (giTraceOut) {
303
+ // rayIntersectDepth already enforced the thickness window, so an
304
+ // intersection here is geometrically certain regardless of the vetoes
305
+ // validateHit applies for reflections.
306
+ this.giTrace = pb.vec3(1, 0, 1);
307
+ }
241
308
  this.$l.iterationAttenuation = pb.sub(1, pb.smoothStep(0, this.maxIterations, this.numIterations));
242
309
  //this.$l.iterationAttenuation = pb.smoothStep(this.maxIterations, 1, this.numIterations);
243
310
  this.confidence = pb.mul(this.confidence, this.iterationAttenuation);
@@ -246,15 +313,50 @@ function screenSpaceRayTracing_Linear2D(scope, viewPos, traceRay, viewMatrix, pr
246
313
  this.$l.hitDistance = pb.length(pb.sub(this.hitPixel, this.startPixel));
247
314
  this.$return(pb.vec4(this.hit2D.xy, this.hitDistance, this.confidence));
248
315
  });
249
- return scope.SSR_Linear2D(viewPos, traceRay, viewMatrix, projMatrix, invProjMatrix, stride, maxDistance, maxIterations, thickness, cameraNearFar, textureSize);
316
+ return giTraceOut ? scope.SSR_Linear2D_GI(viewPos, traceRay, viewMatrix, projMatrix, invProjMatrix, stride, maxDistance, maxIterations, thickness, cameraNearFar, textureSize, giTraceOut) : scope.SSR_Linear2D(viewPos, traceRay, viewMatrix, projMatrix, invProjMatrix, stride, maxDistance, maxIterations, thickness, cameraNearFar, textureSize);
250
317
  }
251
- function screenSpaceRayTracing_HiZ(scope, viewPos, traceRay, viewMatrix, projMatrix, invProjMatrix, cameraNearFar, maxMipLevel, maxIterations, maxDistance, thickness, textureSize, HiZTexture, normalTexture) {
318
+ /**
319
+ * UE5 Random.ush InterleavedGradientNoise. Used as the temporal jitter
320
+ * (StepOffset) for the fixed-step HZB march below.
321
+ */ function SSR_interleavedGradientNoise(scope, uv, frameId) {
252
322
  const pb = scope.$builder;
253
- pb.func('getMipResolution', [
254
- pb.int('mipLevel')
323
+ pb.func('SSR_IGN', [
324
+ pb.vec2('uv'),
325
+ pb.float('frameId')
255
326
  ], function() {
256
- this.$return(pb.vec2(pb.textureDimensions(HiZTexture, this.mipLevel)));
327
+ this.$l.p = pb.add(this.uv, pb.mul(pb.vec2(47, 17), 0.695, this.frameId));
328
+ this.$return(pb.fract(pb.mul(52.9829189, pb.fract(pb.dot(this.p, pb.vec2(0.06711056, 0.00583715))))));
257
329
  });
330
+ return scope.SSR_IGN(uv, frameId);
331
+ }
332
+ /**
333
+ * Screen space ray intersection against the HZB, ported from UE5's
334
+ * SSRTRayCast.ush (InitScreenSpaceRayFromWorldSpace + CastScreenSpaceRay):
335
+ * a fixed-step linear march over the ray clipped/extended to the screen
336
+ * border, sampling the furthest-depth pyramid at a mip level that grows with
337
+ * roughness, with a slope-based CompareTolerance hit window and line-segment
338
+ * hit refinement.
339
+ *
340
+ * Convention differences vs UE5:
341
+ * - UE is reversed-Z (larger = closer); we use standard Z, so every depth
342
+ * difference is mirrored (`Diff = SampleDepth - SampleZ`).
343
+ * - UE view space has +z forward; ours has -z forward.
344
+ * - UE's HZB is a half-res pow2 pyramid and starts marching at its mip 1;
345
+ * ours is full-res at mip0. Under standard-Z the march starts at
346
+ * START_MIP = 0, because the coarse furthest-depth blocks quantize into a
347
+ * staircase that grazing rays periodically self-intersect, showing up as
348
+ * regular stripes TAA cannot converge. Under reverse-Z depth precision is
349
+ * nearly uniform, so the march starts at mip 1 like UE; rough reflections
350
+ * still climb the pyramid via the roughness mip ramp in both cases.
351
+ *
352
+ * `maxIterations` is the number of linear samples along the ray (UE NumSteps,
353
+ * typically 8..64), no longer the traversal iteration cap of the previous
354
+ * FidelityFX-style implementation.
355
+ */ function screenSpaceRayTracing_HiZ(scope, viewPos, traceRay, viewMatrix, projMatrix, invProjMatrix, cameraNearFar, maxMipLevel, maxIterations, maxDistance, thickness, textureSize, HiZTexture, normalTexture, giTraceOut, roughness, stepOffset) {
356
+ const pb = scope.$builder;
357
+ // The r32f pyramid stores the furthest depth (max reduction; mip0 is the
358
+ // raw depth). Matches UE5 marching against the FurthestHZBTexture (UE is
359
+ // reversed-Z, so its furthest pyramid is the min reduction instead).
258
360
  pb.func('SSR_loadDepth', [
259
361
  pb.vec2('uv'),
260
362
  pb.float('level')
@@ -265,130 +367,172 @@ function screenSpaceRayTracing_HiZ(scope, viewPos, traceRay, viewMatrix, projMat
265
367
  pb.vec3('p'),
266
368
  pb.mat4('mat')
267
369
  ], function() {
268
- this.$l.c = pb.sub(pb.mul(this.p, 2), pb.vec3(1));
370
+ this.$l.c = pb.vec3(pb.sub(pb.mul(this.p.xy, 2), pb.vec2(1)), ShaderHelper.deviceDepthToClipZ(this, this.p.z));
269
371
  this.$l.u = pb.mul(this.mat, pb.vec4(this.c, 1));
270
372
  this.u = pb.div(this.u, this.u.w);
271
373
  this.$return(this.u.xyz);
272
374
  });
273
- pb.func('projectPosition', [
274
- pb.vec3('p'),
275
- pb.mat4('mat')
375
+ // Port of UE5 GetStepScreenFactorToClipAtScreenEdge. Returns the multiplier
376
+ // for rayStepNDC so the ray ends exactly at the screen border. A factor
377
+ // above 1 extends short rays to the border (UE bExtendRayToScreenBorder).
378
+ pb.func('SSR_clipRayToScreenEdge', [
379
+ pb.vec2('rayStartNDC'),
380
+ pb.vec2('rayStepNDC')
276
381
  ], function() {
277
- this.$l.projected = pb.mul(this.mat, pb.vec4(this.p, 1));
278
- this.projected = pb.div(this.projected, this.projected.w);
279
- this.projected = pb.add(pb.mul(this.projected, 0.5), pb.vec4(0.5));
280
- this.$return(this.projected.xyz);
382
+ this.$l.rayStepInvFactor = pb.mul(0.5, pb.length(this.rayStepNDC));
383
+ this.$l.absStep = pb.max(pb.abs(this.rayStepNDC), pb.vec2(1e-8));
384
+ this.$l.s = pb.sub(pb.vec2(1), pb.div(pb.max(pb.sub(pb.abs(pb.add(this.rayStepNDC, pb.mul(this.rayStartNDC, this.rayStepInvFactor))), pb.vec2(this.rayStepInvFactor)), pb.vec2(0)), this.absStep));
385
+ this.$return(pb.div(pb.min(this.s.x, this.s.y), pb.max(this.rayStepInvFactor, 1e-8)));
281
386
  });
282
- pb.func('projectDirection', [
283
- pb.vec3('p'),
284
- pb.vec3('d'),
285
- pb.vec3('screenSpacePos'),
286
- pb.mat4('mat')
287
- ], function() {
288
- this.$return(pb.sub(this.projectPosition(pb.add(this.p, this.d), this.mat), this.screenSpacePos));
289
- });
290
- pb.func('SSR_initialAdvanceRay', [
291
- pb.vec3('origin'),
292
- pb.vec3('direction'),
293
- pb.vec3('invDirection'),
294
- pb.vec2('currentMipResolution'),
295
- pb.vec2('invCurrentMipResolution'),
296
- pb.vec2('floorOffset'),
297
- pb.vec2('uvOffset'),
298
- pb.vec3('position').out(),
299
- pb.float('currentT').out()
387
+ // Port of UE5 InitScreenSpaceRayFromWorldSpace, in view space (-z forward).
388
+ // Outputs the ray in "texture space": uv in [0,1], z = device depth in
389
+ // [0,1], the same mapping the HZB stores.
390
+ pb.func('SSR_initScreenSpaceRay', [
391
+ pb.vec3('viewPos'),
392
+ pb.vec3('rayDirection'),
393
+ pb.float('maxDistance'),
394
+ pb.float('slopeCompareToleranceScale'),
395
+ pb.mat4('projMatrix'),
396
+ pb.vec3('rayStartTS').out(),
397
+ pb.vec3('rayStepTS').out(),
398
+ pb.float('compareTolerance').out()
300
399
  ], function() {
301
- this.$l.currentMipPosition = pb.mul(this.currentMipResolution, this.origin.xy);
302
- this.$l.xyPlane = pb.add(pb.floor(this.currentMipPosition), this.floorOffset);
303
- this.xyPlane = pb.add(pb.mul(this.xyPlane, this.invCurrentMipResolution), this.uvOffset);
304
- this.$l.t = pb.mul(pb.sub(this.xyPlane, this.origin.xy), this.invDirection.xy);
305
- this.currentT = pb.min(this.t.x, this.t.y);
306
- this.position = pb.add(this.origin, pb.mul(this.direction, this.currentT));
400
+ // Rays heading towards the camera stop before reaching 5% of the
401
+ // surface view depth (UE: min(-0.95 * SceneDepth / ViewDirZ, TMax)).
402
+ this.$l.rayLength = this.$choice(pb.greaterThan(this.rayDirection.z, 0), pb.min(this.maxDistance, pb.div(pb.mul(-0.95, this.viewPos.z), pb.max(this.rayDirection.z, 1e-6))), this.maxDistance);
403
+ this.$l.rayEnd = pb.add(this.viewPos, pb.mul(this.rayDirection, this.rayLength));
404
+ this.$l.startH = pb.mul(this.projMatrix, pb.vec4(this.viewPos, 1));
405
+ this.$l.endH = pb.mul(this.projMatrix, pb.vec4(this.rayEnd, 1));
406
+ this.$l.startNDC = pb.div(this.startH.xyz, this.startH.w);
407
+ this.$l.endNDC = pb.div(this.endH.xyz, this.endH.w);
408
+ this.$l.stepNDC = pb.sub(this.endNDC, this.startNDC);
409
+ // Always extend/clip the ray to the screen border so NumSteps samples
410
+ // cover the whole visible segment.
411
+ this.stepNDC = pb.mul(this.stepNDC, this.SSR_clipRayToScreenEdge(this.startNDC.xy, this.stepNDC.xy));
412
+ if (REVERSE_Z) {
413
+ // Reverse ZO canonical clip space: NDC z is already the device depth.
414
+ this.rayStartTS = pb.vec3(pb.add(pb.mul(this.startNDC.xy, 0.5), pb.vec2(0.5)), this.startNDC.z);
415
+ this.rayStepTS = pb.vec3(pb.mul(this.stepNDC.xy, 0.5), this.stepNDC.z);
416
+ } else {
417
+ this.rayStartTS = pb.add(pb.mul(this.startNDC, 0.5), pb.vec3(0.5));
418
+ this.rayStepTS = pb.mul(this.stepNDC, 0.5);
419
+ }
420
+ // Depth-only step (UE RayDepthScreen): project the point at rayLength
421
+ // straight along the view forward axis for the slope tolerance.
422
+ this.$l.depthH = pb.mul(this.projMatrix, pb.vec4(this.viewPos.xy, pb.sub(this.viewPos.z, this.rayLength), 1));
423
+ this.$l.depthTSz = REVERSE_Z ? pb.div(this.depthH.z, this.depthH.w) : pb.add(pb.mul(pb.div(this.depthH.z, this.depthH.w), 0.5), 0.5);
424
+ // Under standard Z depth grows away from the camera, mirroring UE's
425
+ // reversed-Z expression (RayStartScreen.z - RayDepthScreen.z); under
426
+ // reverse-Z the UE orientation applies directly.
427
+ this.compareTolerance = pb.max(pb.abs(this.rayStepTS.z), pb.mul(REVERSE_Z ? pb.sub(this.rayStartTS.z, this.depthTSz) : pb.sub(this.depthTSz, this.rayStartTS.z), this.slopeCompareToleranceScale));
307
428
  });
308
- pb.func('SSR_advanceRay', [
309
- pb.vec3('origin'),
310
- pb.vec3('direction'),
311
- pb.vec3('invDirection'),
312
- pb.vec2('currentMipPosition'),
313
- pb.vec2('invCurrentMipResolution'),
314
- pb.vec2('floorOffset'),
315
- pb.vec2('uvOffset'),
316
- pb.float('surfaceZ'),
317
- pb.float('surfaceZMax'),
318
- pb.int('currentMip'),
319
- pb.vec3('position').inout(),
320
- pb.float('currentT').inout()
429
+ // Port of UE5 CastScreenSpaceRay: NumSteps uniform samples in batches of 4,
430
+ // mip level ramped by roughness, tolerance-window hit test, uncertainty
431
+ // tracking and line-segment hit refinement.
432
+ // Standard-Z keeps 0 instead of UE's StartMipLevel=1: coarse-mip depth
433
+ // staircases combined with the poor far-depth resolution near 1.0 cause
434
+ // grazing-angle stripe artifacts. Under reverse-Z depth precision is nearly
435
+ // uniform along the ray, so the march starts at mip 1 like UE for better
436
+ // texture cache behavior.
437
+ const START_MIP = REVERSE_Z ? 1 : 0;
438
+ pb.func('SSR_castScreenSpaceRay', [
439
+ pb.vec3('rayStartTS'),
440
+ pb.vec3('rayStepTS'),
441
+ pb.float('compareToleranceIn'),
442
+ pb.float('numSteps'),
443
+ pb.float('stepOffset'),
444
+ pb.float('roughness'),
445
+ pb.float('maxMipLevel'),
446
+ pb.vec3('hitUVz').out(),
447
+ pb.bool('foundHit').out(),
448
+ pb.bool('uncertain').out(),
449
+ pb.float('numIterations').out()
321
450
  ], function() {
322
- this.$l.xyPlane = pb.add(pb.floor(this.currentMipPosition), this.floorOffset);
323
- this.xyPlane = pb.add(pb.mul(this.xyPlane, this.invCurrentMipResolution), this.uvOffset);
324
- this.$l.skippedTile = pb.bool();
325
- this.$if(pb.greaterThan(this.direction.z, 0), function() {
326
- // Preserve the proven min-depth traversal for rays moving away from the camera.
327
- this.$l.boundaryPlanes = pb.vec3(this.xyPlane, this.surfaceZ);
328
- this.$l.forwardT = pb.mul(pb.sub(this.boundaryPlanes, this.origin), this.invDirection);
329
- this.$l.tMin = pb.min(pb.min(this.forwardT.x, this.forwardT.y), this.forwardT.z);
330
- this.$l.aboveSurface = pb.greaterThan(this.surfaceZ, this.position.z);
331
- this.skippedTile = pb.and(pb.notEqual(pb.floatBitsToUint(this.tMin), pb.floatBitsToUint(this.forwardT.z)), this.aboveSurface);
332
- this.currentT = this.$choice(this.aboveSurface, this.tMin, this.currentT);
333
- this.position = pb.add(this.origin, pb.mul(this.direction, this.currentT));
334
- }).$else(function() {
335
- this.$l.reverseT = pb.mul(pb.sub(this.xyPlane, this.origin.xy), this.invDirection.xy);
336
- this.$l.tExit = pb.min(this.reverseT.x, this.reverseT.y);
337
- this.$l.exitZ = pb.add(this.origin.z, pb.mul(this.direction.z, this.tExit));
338
- this.$l.rayMinZ = pb.min(this.position.z, this.exitZ);
339
- this.$l.rayMaxZ = pb.max(this.position.z, this.exitZ);
340
- this.$l.overlapsDepth = pb.and(pb.greaterThanEqual(this.rayMaxZ, this.surfaceZ), pb.lessThanEqual(this.rayMinZ, this.surfaceZMax));
341
- this.skippedTile = pb.not(this.overlapsDepth);
342
- this.$if(this.skippedTile, function() {
343
- this.currentT = this.tExit;
344
- this.position = pb.add(this.origin, pb.mul(this.direction, this.currentT));
345
- }).$elseif(pb.equal(this.currentMip, 0), function() {
346
- this.$if(pb.greaterThan(pb.abs(this.direction.z), 1e-6), function() {
347
- this.currentT = pb.div(pb.sub(this.surfaceZMax, this.origin.z), this.direction.z);
348
- this.position = pb.add(this.origin, pb.mul(this.direction, this.currentT));
451
+ this.$l.step = pb.div(1, this.numSteps);
452
+ this.$l.compareTolerance = pb.mul(this.compareToleranceIn, this.step);
453
+ this.$l.rayStepUVz = pb.mul(this.rayStepTS, this.step);
454
+ this.$l.rayUVz = pb.add(this.rayStartTS, pb.mul(this.rayStepUVz, this.stepOffset));
455
+ this.$l.level = pb.float(START_MIP);
456
+ this.$l.mipInc = pb.mul(pb.div(8, this.numSteps), this.roughness);
457
+ this.$l.lastDiff = pb.float(0);
458
+ this.foundHit = false;
459
+ this.uncertain = false;
460
+ this.$l.marchBase = pb.float(0);
461
+ // Depth diffs / hit flags of the current 4-sample batch, kept for the
462
+ // post-loop hit refinement.
463
+ this.$l.diff0 = pb.float(0);
464
+ this.$l.diff1 = pb.float(0);
465
+ this.$l.diff2 = pb.float(0);
466
+ this.$l.diff3 = pb.float(0);
467
+ this.$l.hit0 = pb.bool(false);
468
+ this.$l.hit1 = pb.bool(false);
469
+ this.$l.hit2 = pb.bool(false);
470
+ this.$l.hit3 = pb.bool(false);
471
+ this.$l.numBatches = pb.ceil(pb.mul(this.numSteps, 0.25));
472
+ this.$for(pb.float('i'), 0, pb.getDevice().type === 'webgl' ? 64 : this.numBatches, function() {
473
+ if (pb.getDevice().type === 'webgl') {
474
+ this.$if(pb.greaterThanEqual(this.i, this.numBatches), function() {
475
+ this.$break();
349
476
  });
477
+ }
478
+ this.marchBase = pb.mul(this.i, 4);
479
+ // Two samples per mip step (UE: SamplesMip.xy = Level; Level += inc).
480
+ this.$l.mip01 = pb.min(this.level, this.maxMipLevel);
481
+ this.$l.mip23 = pb.min(pb.add(this.level, this.mipInc), this.maxMipLevel);
482
+ this.level = pb.add(this.level, pb.mul(this.mipInc, 2));
483
+ for(let j = 0; j < 4; j++){
484
+ this.$l[`sampleT${j}`] = pb.add(this.marchBase, j + 1);
485
+ this.$l[`sampleUV${j}`] = pb.add(this.rayUVz.xy, pb.mul(this.rayStepUVz.xy, this[`sampleT${j}`]));
486
+ this.$l[`sampleZ${j}`] = pb.add(this.rayUVz.z, pb.mul(this.rayStepUVz.z, this[`sampleT${j}`]));
487
+ // Negative when the ray is behind the surface, hit window (-2T, 0).
488
+ // Under reverse-Z this is UE's original `SamplesZ - SampleDepth`;
489
+ // under standard Z the difference is mirrored.
490
+ this[`diff${j}`] = REVERSE_Z ? pb.sub(this[`sampleZ${j}`], this.SSR_loadDepth(this[`sampleUV${j}`], j < 2 ? this.mip01 : this.mip23)) : pb.sub(this.SSR_loadDepth(this[`sampleUV${j}`], j < 2 ? this.mip01 : this.mip23), this[`sampleZ${j}`]);
491
+ this[`hit${j}`] = pb.lessThan(pb.abs(pb.add(this[`diff${j}`], this.compareTolerance)), this.compareTolerance);
492
+ this.foundHit = pb.or(this.foundHit, this[`hit${j}`]);
493
+ // The ray went far behind geometry before any hit: its outcome
494
+ // cannot be resolved from the depth buffer (UE bUncertain).
495
+ this.uncertain = pb.or(this.uncertain, pb.and(pb.lessThan(pb.add(this[`diff${j}`], this.compareTolerance), pb.neg(this.compareTolerance)), pb.not(this.foundHit)));
496
+ }
497
+ this.$if(this.foundHit, function() {
498
+ this.$break();
350
499
  });
500
+ this.lastDiff = this.diff3;
351
501
  });
352
- this.$return(this.skippedTile);
353
- });
354
- pb.func('SSR_RaymarchHiZ', [
355
- pb.vec3('screenSpacePos'),
356
- pb.vec3('screenSpaceDirection'),
357
- pb.vec2('cameraNearFar'),
358
- pb.vec2('screenSize'),
359
- pb.int('mostDetailMip'),
360
- pb.int('maxMipLevel'),
361
- pb.float('maxIterations'),
362
- pb.float('maxT'),
363
- pb.float('numIterations').out()
364
- ], function() {
365
- this.$l.invDirection = pb.vec3(this.$choice(pb.greaterThan(pb.abs(this.screenSpaceDirection.x), 1e-8), pb.div(1, this.screenSpaceDirection.x), MAX_FLOAT_VALUE), this.$choice(pb.greaterThan(pb.abs(this.screenSpaceDirection.y), 1e-8), pb.div(1, this.screenSpaceDirection.y), MAX_FLOAT_VALUE), this.$choice(pb.greaterThan(pb.abs(this.screenSpaceDirection.z), 1e-8), pb.div(1, this.screenSpaceDirection.z), MAX_FLOAT_VALUE));
366
- this.$l.currentMip = this.mostDetailMip;
367
- this.$l.currentMipResolution = this.getMipResolution(this.currentMip);
368
- this.$l.invCurrentMipResolution = pb.div(pb.vec2(1), this.currentMipResolution);
369
- this.$l.uvOffset = pb.div(pb.mul(0.005, pb.exp2(pb.float(this.mostDetailMip))), this.screenSize);
370
- this.uvOffset = pb.vec2(this.$choice(pb.lessThan(this.screenSpaceDirection.x, 0), pb.neg(this.uvOffset.x), this.uvOffset.x), this.$choice(pb.lessThan(this.screenSpaceDirection.y, 0), pb.neg(this.uvOffset.y), this.uvOffset.y));
371
- this.$l.floorOffset = pb.vec2(this.$choice(pb.lessThan(this.screenSpaceDirection.x, 0), pb.float(0), pb.float(1)), this.$choice(pb.lessThan(this.screenSpaceDirection.y, 0), pb.float(0), pb.float(1)));
372
- this.$l.currentT = pb.float();
373
- this.$l.position = pb.vec3();
374
- this.SSR_initialAdvanceRay(this.screenSpacePos, this.screenSpaceDirection, this.invDirection, this.currentMipResolution, this.invCurrentMipResolution, this.floorOffset, this.uvOffset, this.position, this.currentT);
375
- this.numIterations = pb.float(0);
376
- this.$while(pb.and(pb.and(pb.lessThan(this.numIterations, this.maxIterations), pb.greaterThanEqual(this.currentMip, this.mostDetailMip)), pb.and(pb.lessThanEqual(this.currentT, this.maxT), pb.all(pb.greaterThanEqual(this.position.xy, pb.vec2(0))), pb.all(pb.lessThanEqual(this.position.xy, pb.vec2(1))))), function() {
377
- this.$l.currentMipPosition = pb.mul(this.currentMipResolution, this.position.xy);
378
- this.$l.depthRange = pb.textureLoad(HiZTexture, pb.ivec2(this.currentMipPosition), this.currentMip).rg;
379
- this.$l.skippedTile = this.SSR_advanceRay(this.screenSpacePos, this.screenSpaceDirection, this.invDirection, this.currentMipPosition, this.invCurrentMipResolution, this.floorOffset, this.uvOffset, this.depthRange.x, this.depthRange.y, this.currentMip, this.position, this.currentT);
380
- this.$l.nextMipIsOutOfRange = pb.and(this.skippedTile, pb.greaterThanEqual(this.currentMip, this.maxMipLevel));
381
- this.$if(pb.not(this.nextMipIsOutOfRange), function() {
382
- this.currentMip = pb.add(this.currentMip, this.$choice(this.skippedTile, pb.int(1), pb.int(-1)));
383
- this.currentMipResolution = pb.mul(this.currentMipResolution, this.$choice(this.skippedTile, 0.5, 2));
384
- this.invCurrentMipResolution = pb.mul(this.invCurrentMipResolution, this.$choice(this.skippedTile, 2, 0.5));
502
+ this.$if(this.foundHit, function() {
503
+ // Locate the first hit sample of the batch, then refine with a line
504
+ // segment intersection (UE: TimeLerp = saturate(D0 / (D0 - D1))).
505
+ this.$l.depthDiff0 = this.diff2;
506
+ this.$l.depthDiff1 = this.diff3;
507
+ this.$l.time0 = pb.float(3);
508
+ this.$if(this.hit2, function() {
509
+ this.depthDiff0 = this.diff1;
510
+ this.depthDiff1 = this.diff2;
511
+ this.time0 = 2;
385
512
  });
386
- this.numIterations = pb.add(this.numIterations, 1);
513
+ this.$if(this.hit1, function() {
514
+ this.depthDiff0 = this.diff0;
515
+ this.depthDiff1 = this.diff1;
516
+ this.time0 = 1;
517
+ });
518
+ this.$if(this.hit0, function() {
519
+ this.depthDiff0 = this.lastDiff;
520
+ this.depthDiff1 = this.diff0;
521
+ this.time0 = 0;
522
+ });
523
+ this.time0 = pb.add(this.time0, this.marchBase);
524
+ this.$l.denom = pb.sub(this.depthDiff0, this.depthDiff1);
525
+ this.$l.timeLerp = this.$choice(pb.greaterThan(pb.abs(this.denom), 1e-20), pb.clamp(pb.div(this.depthDiff0, this.denom), 0, 1), pb.float(0.5));
526
+ this.$l.intersectTime = pb.add(this.time0, this.timeLerp);
527
+ this.hitUVz = pb.add(this.rayUVz, pb.mul(this.rayStepUVz, this.intersectTime));
528
+ this.numIterations = this.intersectTime;
529
+ }).$else(function() {
530
+ // No certain intersection - the march covered the whole clipped ray.
531
+ this.hitUVz = pb.add(this.rayUVz, pb.mul(this.rayStepUVz, this.numSteps));
532
+ this.numIterations = this.numSteps;
387
533
  });
388
- this.$l.validHit = this.$choice(pb.and(pb.lessThan(this.currentMip, this.mostDetailMip), pb.and(pb.greaterThanEqual(this.currentT, 0), pb.lessThanEqual(this.currentT, this.maxT))), pb.float(1), pb.float(0));
389
- this.$return(pb.vec4(this.position, this.validHit));
390
534
  });
391
- pb.func('SSR_HiZ', [
535
+ pb.func(giTraceOut ? 'SSR_HiZ_GI' : 'SSR_HiZ', [
392
536
  pb.vec3('viewPos'),
393
537
  pb.vec3('traceRay'),
394
538
  pb.mat4('viewMatrix'),
@@ -399,44 +543,68 @@ function screenSpaceRayTracing_HiZ(scope, viewPos, traceRay, viewMatrix, projMat
399
543
  pb.float('thickness'),
400
544
  pb.vec4('textureSize'),
401
545
  pb.int('maxMipLevel'),
402
- pb.float('maxIterations')
546
+ pb.float('maxIterations'),
547
+ pb.float('roughness'),
548
+ pb.float('stepOffset'),
549
+ ...giTraceOut ? [
550
+ pb.vec3('giTrace').out()
551
+ ] : []
403
552
  ], function() {
553
+ if (giTraceOut) {
554
+ this.giTrace = pb.vec3(0);
555
+ }
404
556
  this.$l.rayDirection = pb.normalize(this.traceRay);
405
- this.$l.reverseRay = pb.greaterThan(this.rayDirection.z, 1e-6);
406
- this.$l.rayLength = this.maxDistance;
407
- this.$if(this.reverseRay, function() {
408
- this.rayLength = pb.min(this.rayLength, pb.div(pb.sub(pb.neg(this.cameraNearFar.x), this.viewPos.z), this.rayDirection.z));
409
- });
410
- this.$if(pb.lessThanEqual(this.rayLength, 0), function() {
411
- this.$return(pb.vec4(0));
412
- });
413
- this.$l.originH = pb.mul(this.projMatrix, pb.vec4(this.viewPos, 1));
414
- this.$l.originCS = pb.div(this.originH.xyz, this.originH.w);
415
- this.$l.originTS = pb.add(pb.mul(this.originCS, 0.5), pb.vec3(0.5));
416
- this.$l.endTS = this.projectPosition(pb.add(this.viewPos, pb.mul(this.rayDirection, this.rayLength)), this.projMatrix);
417
- this.$l.directionTS = this.$choice(this.reverseRay, pb.sub(this.endTS, this.originTS), this.projectDirection(this.viewPos, this.rayDirection, this.originTS, this.projMatrix));
418
- this.$l.maxT = this.$choice(this.reverseRay, pb.float(1), pb.float(MAX_FLOAT_VALUE));
419
- this.$l.mostDetailMip = pb.int(0);
557
+ this.$l.rayStartTS = pb.vec3();
558
+ this.$l.rayStepTS = pb.vec3();
559
+ this.$l.compareTolerance = pb.float();
560
+ // UE uses SlopeCompareToleranceScale 4 for SSR and 2 for SSGI.
561
+ this.SSR_initScreenSpaceRay(this.viewPos, this.rayDirection, this.maxDistance, pb.float(giTraceOut ? 2 : 4), this.projMatrix, this.rayStartTS, this.rayStepTS, this.compareTolerance);
562
+ this.$l.hitUVz = pb.vec3();
563
+ this.$l.foundHit = pb.bool();
564
+ this.$l.uncertain = pb.bool();
420
565
  this.$l.numIterations = pb.float();
421
- this.$l.hit = this.SSR_RaymarchHiZ(this.originTS, this.directionTS, this.cameraNearFar, this.textureSize.zw, this.mostDetailMip, this.maxMipLevel, this.maxIterations, this.maxT, this.numIterations);
566
+ this.SSR_castScreenSpaceRay(this.rayStartTS, this.rayStepTS, this.compareTolerance, pb.max(this.maxIterations, 4), this.stepOffset, this.roughness, pb.float(this.maxMipLevel), this.hitUVz, this.foundHit, this.uncertain, this.numIterations);
422
567
  this.$l.confidence = pb.float(0);
423
- this.$if(pb.notEqual(this.hit.w, 0), function() {
424
- this.$l.surfaceZ = pb.textureSampleLevel(HiZTexture, this.hit.xy, 0).r;
425
- this.$if(pb.equal(this.surfaceZ, 1), function() {
568
+ const hitBranch = this.$if(this.foundHit, function() {
569
+ this.$l.surfaceZ = this.SSR_loadDepth(this.hitUVz.xy, 0);
570
+ this.$if(ShaderHelper.isFarthestDepth(this, this.surfaceZ), function() {
571
+ if (giTraceOut) {
572
+ // The intersection resolved onto a sky texel, so the ray escaped.
573
+ this.giTrace = pb.vec3(0, 1, 0);
574
+ }
426
575
  this.$return(pb.vec4(0));
427
576
  });
428
- this.$l.hit3D = invProjectPosition(this, this.hit.xyz, this.invProjMatrix);
429
- this.confidence = validateHit(this, this.hit.xy, this.hit3D, this.surfaceZ, this.thickness, this.originTS.xy, this.rayDirection, this.viewMatrix, this.invProjMatrix, this.textureSize, normalTexture);
577
+ this.$l.hit3D = invProjectPosition(this, this.hitUVz, this.invProjMatrix);
578
+ this.confidence = validateHit(this, this.hitUVz.xy, this.hit3D, this.surfaceZ, this.thickness, this.rayStartTS.xy, this.rayDirection, this.viewMatrix, this.invProjMatrix, this.textureSize, normalTexture);
579
+ if (giTraceOut) {
580
+ // Keep only the thickness term of validateHit: a hit far from the
581
+ // sampled surface may be a thin-object false positive, which is a
582
+ // genuine geometric doubt. The border, near-self and backface vetoes
583
+ // bound reflection artifacts and must not gate diffuse occlusion.
584
+ this.$l.giSurfaceVS = invProjectPosition(this, pb.vec3(this.hitUVz.xy, this.surfaceZ), this.invProjMatrix);
585
+ this.$l.giThicknessFade = pb.sub(1, pb.smoothStep(0, this.thickness, pb.distance(this.giSurfaceVS, this.hit3D)));
586
+ this.giTrace = pb.vec3(1, 0, pb.mul(this.giThicknessFade, this.giThicknessFade));
587
+ }
430
588
  });
589
+ if (giTraceOut) {
590
+ hitBranch.$else(function() {
591
+ // The ray is always extended to the screen border, so a march that
592
+ // finished without ever going behind geometry is a proven escape.
593
+ // An uncertain march (went far behind geometry before any hit) is
594
+ // indeterminate, not unoccluded (UE bUncertain semantics).
595
+ this.giTrace = pb.vec3(0, pb.float(pb.not(this.uncertain)), 0);
596
+ });
597
+ }
431
598
  this.$l.iterationAttenuation = pb.sub(1, pb.smoothStep(0, this.maxIterations, this.numIterations));
432
- //this.$l.iterationAttenuation = pb.smoothStep(this.maxIterations, 1, this.numIterations);
433
599
  this.confidence = pb.mul(this.confidence, this.iterationAttenuation);
434
- this.$l.hitPixel = pb.mul(this.hit.xy, this.textureSize.zw);
435
- this.$l.startPixel = pb.mul(this.originTS.xy, this.textureSize.zw);
600
+ this.$l.hitPixel = pb.mul(this.hitUVz.xy, this.textureSize.zw);
601
+ this.$l.startPixel = pb.mul(this.rayStartTS.xy, this.textureSize.zw);
436
602
  this.$l.hitDistance = pb.length(pb.sub(this.hitPixel, this.startPixel));
437
- this.$return(pb.vec4(this.hit.xy, this.hitDistance, this.confidence));
603
+ this.$return(pb.vec4(this.hitUVz.xy, this.hitDistance, this.confidence));
438
604
  });
439
- return scope.SSR_HiZ(viewPos, traceRay, viewMatrix, projMatrix, invProjMatrix, cameraNearFar, maxDistance, thickness, textureSize, pb.sub(maxMipLevel, 1), maxIterations);
605
+ const roughnessArg = roughness ?? 0;
606
+ const stepOffsetArg = stepOffset ?? 0.5;
607
+ return giTraceOut ? scope.SSR_HiZ_GI(viewPos, traceRay, viewMatrix, projMatrix, invProjMatrix, cameraNearFar, maxDistance, thickness, textureSize, pb.sub(maxMipLevel, 1), maxIterations, roughnessArg, stepOffsetArg, giTraceOut) : scope.SSR_HiZ(viewPos, traceRay, viewMatrix, projMatrix, invProjMatrix, cameraNearFar, maxDistance, thickness, textureSize, pb.sub(maxMipLevel, 1), maxIterations, roughnessArg, stepOffsetArg);
440
608
  } /*
441
609
  float2 cell(float2 ray, float2 cell_count, uint camera) {
442
610
  return floor(ray.xy * cell_count);
@@ -517,5 +685,5 @@ float3 hi_z_trace(float3 p, float3 v, in uint camera, out uint iterations) {
517
685
  }
518
686
  */
519
687
 
520
- export { SSR_calcJitter, screenSpaceRayTracing_HiZ, screenSpaceRayTracing_Linear2D };
688
+ export { SSR_calcJitter, SSR_interleavedGradientNoise, screenSpaceRayTracing_HiZ, screenSpaceRayTracing_Linear2D };
521
689
  //# sourceMappingURL=ssr.js.map