@meimu/mutracer 0.2.0 → 0.3.0

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 (211) hide show
  1. package/README.md +3 -0
  2. package/dist/assets/bvh.worker.js +1 -1
  3. package/dist/mutracer.umd.js +8949 -7915
  4. package/docs/api.md +42 -21
  5. package/docs/caustics.md +80 -0
  6. package/docs/materials.md +5 -4
  7. package/docs/progressive-rendering.md +50 -9
  8. package/docs/technical/continuous-spectrum.md +93 -0
  9. package/docs/updates.md +3 -1
  10. package/docs/viewer-checklist.md +65 -0
  11. package/docs/viewer.md +7 -5
  12. package/lib/Display.js +1 -1
  13. package/lib/Display.js.map +1 -1
  14. package/lib/Engine.d.ts +2 -0
  15. package/lib/Engine.js +47 -12
  16. package/lib/Engine.js.map +1 -1
  17. package/lib/Render.d.ts +61 -13
  18. package/lib/Render.js +169 -19
  19. package/lib/Render.js.map +1 -1
  20. package/lib/RenderLoop.d.ts +7 -3
  21. package/lib/RenderLoop.js +68 -44
  22. package/lib/RenderLoop.js.map +1 -1
  23. package/lib/main.d.ts +1 -1
  24. package/lib/main.js.map +1 -1
  25. package/lib/oidn.d.ts +1 -1
  26. package/lib/oidnBundledWeights.cdn.d.ts +2 -2
  27. package/lib/renderer/GPUTraceQueue.d.ts +2 -1
  28. package/lib/renderer/GPUTraceQueue.js +3 -1
  29. package/lib/renderer/GPUTraceQueue.js.map +1 -1
  30. package/lib/renderer/RenderingPipeline.d.ts +4 -0
  31. package/lib/renderer/RenderingPipeline.js.map +1 -1
  32. package/lib/renderer/WGPUCausticLightPass.d.ts +48 -0
  33. package/lib/renderer/WGPUCausticLightPass.js +217 -0
  34. package/lib/renderer/WGPUCausticLightPass.js.map +1 -0
  35. package/lib/renderer/WGPUEnvMapDistribution.js +18 -18
  36. package/lib/renderer/WGPUFullQuadPass.js +3 -3
  37. package/lib/renderer/WGPUPhotonMap.d.ts +37 -0
  38. package/lib/renderer/WGPUPhotonMap.js +191 -0
  39. package/lib/renderer/WGPUPhotonMap.js.map +1 -0
  40. package/lib/renderer/WGPURayTraceOutput.d.ts +13 -1
  41. package/lib/renderer/WGPURayTraceOutput.js +122 -28
  42. package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
  43. package/lib/renderer/WGPURayTracePass.d.ts +48 -17
  44. package/lib/renderer/WGPURayTracePass.js +538 -340
  45. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  46. package/lib/renderer/WGPURenderingPipeline.js +83 -47
  47. package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
  48. package/lib/renderer/WebGLFullQuadPass.d.ts +2 -1
  49. package/lib/renderer/WebGLFullQuadPass.js +4 -0
  50. package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
  51. package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
  52. package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
  53. package/lib/renderer/WebGLRayTracePass.d.ts +6 -1
  54. package/lib/renderer/WebGLRayTracePass.js +43 -6
  55. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  56. package/lib/renderer/WebGLRenderingPipeline.js +10 -14
  57. package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
  58. package/lib/renderer/WebGLSkyBaker.js +60 -60
  59. package/lib/renderer/accumulationTarget.d.ts +2 -1
  60. package/lib/renderer/accumulationTarget.js.map +1 -1
  61. package/lib/renderer/causticDiagnostics.d.ts +31 -0
  62. package/lib/renderer/causticDiagnostics.js +5 -0
  63. package/lib/renderer/causticDiagnostics.js.map +1 -0
  64. package/lib/renderer/causticEmission.d.ts +10 -0
  65. package/lib/renderer/causticEmission.js +40 -0
  66. package/lib/renderer/causticEmission.js.map +1 -0
  67. package/lib/renderer/causticUniform.d.ts +12 -0
  68. package/lib/renderer/causticUniform.js +22 -0
  69. package/lib/renderer/causticUniform.js.map +1 -0
  70. package/lib/renderer/caustics.d.ts +16 -0
  71. package/lib/renderer/caustics.js +93 -0
  72. package/lib/renderer/caustics.js.map +1 -0
  73. package/lib/renderer/glsl/aovDisplay.frag.js +5 -5
  74. package/lib/renderer/glsl/aovReflectance.glsl.js +53 -53
  75. package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +1 -1
  76. package/lib/renderer/glsl/aovTrace.frag.js +33 -33
  77. package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -9
  78. package/lib/renderer/glsl/chunks/emissiveLight.glsl.d.ts +2 -0
  79. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +35 -0
  80. package/lib/renderer/glsl/chunks/emissiveLight.glsl.js.map +1 -0
  81. package/lib/renderer/glsl/chunks/envMap.glsl.js +73 -73
  82. package/lib/renderer/glsl/chunks/intersect.glsl.js +426 -428
  83. package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
  84. package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +1 -0
  85. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +156 -153
  86. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
  87. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +771 -771
  88. package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
  89. package/lib/renderer/glsl/chunks/random.glsl.js +6 -6
  90. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +1 -0
  91. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +68 -63
  92. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
  93. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +94 -76
  94. package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
  95. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -0
  96. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +140 -117
  97. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
  98. package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +14 -14
  99. package/lib/renderer/glsl/chunks/sampling.glsl.js +89 -92
  100. package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -1
  101. package/lib/renderer/glsl/chunks/sobol.glsl.js +79 -79
  102. package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
  103. package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
  104. package/lib/renderer/glsl/depthQuery.frag.js +2 -2
  105. package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +10 -10
  106. package/lib/renderer/glsl/rayTrace.frag.d.ts +1 -1
  107. package/lib/renderer/glsl/rayTrace.frag.js +260 -237
  108. package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
  109. package/lib/renderer/glsl/shadowCatcher.frag.js +26 -26
  110. package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
  111. package/lib/renderer/glsl/webglPipeline.frag.js +59 -59
  112. package/lib/renderer/lightSampling.d.ts +3 -2
  113. package/lib/renderer/lightSampling.js +14 -8
  114. package/lib/renderer/lightSampling.js.map +1 -1
  115. package/lib/renderer/progressiveScheduler.js +4 -3
  116. package/lib/renderer/progressiveScheduler.js.map +1 -1
  117. package/lib/renderer/renderPipeline.d.ts +1 -0
  118. package/lib/renderer/renderPipeline.js.map +1 -1
  119. package/lib/renderer/researchTransport.d.ts +31 -0
  120. package/lib/renderer/researchTransport.js +5 -0
  121. package/lib/renderer/researchTransport.js.map +1 -0
  122. package/lib/renderer/sceneLayoutSchema.d.ts +1 -0
  123. package/lib/renderer/sceneLayoutSchema.js +2 -2
  124. package/lib/renderer/sceneLayoutSchema.js.map +1 -1
  125. package/lib/renderer/scratchSampling.js +6 -6
  126. package/lib/renderer/spectrum.d.ts +21 -0
  127. package/lib/renderer/spectrum.js +154 -0
  128. package/lib/renderer/spectrum.js.map +1 -0
  129. package/lib/renderer/webglLightSampling.d.ts +39 -0
  130. package/lib/renderer/webglLightSampling.js +15 -0
  131. package/lib/renderer/webglLightSampling.js.map +1 -0
  132. package/lib/renderer/wgpuBindlessSource.js +3 -3
  133. package/lib/renderer/wgpuShaderSource.d.ts +7 -0
  134. package/lib/renderer/wgpuShaderSource.js +2 -0
  135. package/lib/renderer/wgpuShaderSource.js.map +1 -1
  136. package/lib/renderer/wgsl/aovDisplay.wgsl.js +9 -9
  137. package/lib/renderer/wgsl/aovReflectance.wgsl.js +55 -55
  138. package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +1 -1
  139. package/lib/renderer/wgsl/aovResolve.wgsl.js +2 -2
  140. package/lib/renderer/wgsl/aovSnapshot.wgsl.js +1 -1
  141. package/lib/renderer/wgsl/aovTrace.wgsl.js +33 -33
  142. package/lib/renderer/wgsl/bloom.wgsl.js +18 -18
  143. package/lib/renderer/wgsl/causticLight.compute.d.ts +6 -0
  144. package/lib/renderer/wgsl/causticLight.compute.js +537 -0
  145. package/lib/renderer/wgsl/causticLight.compute.js.map +1 -0
  146. package/lib/renderer/wgsl/chunks/background.wgsl.d.ts +2 -0
  147. package/lib/renderer/wgsl/chunks/background.wgsl.js +102 -0
  148. package/lib/renderer/wgsl/chunks/background.wgsl.js.map +1 -0
  149. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.d.ts +2 -0
  150. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +12 -0
  151. package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js.map +1 -0
  152. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.d.ts +2 -0
  153. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +16 -0
  154. package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js.map +1 -0
  155. package/lib/renderer/wgsl/chunks/caustics.wgsl.d.ts +1 -0
  156. package/lib/renderer/wgsl/chunks/caustics.wgsl.js +62 -0
  157. package/lib/renderer/wgsl/chunks/caustics.wgsl.js.map +1 -0
  158. package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
  159. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +37 -37
  160. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +598 -592
  161. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  162. package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +14 -14
  163. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +2 -1
  164. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +75 -73
  165. package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
  166. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +794 -794
  167. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +139 -139
  168. package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
  169. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +2 -1
  170. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +35 -25
  171. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
  172. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +1 -1
  173. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +142 -138
  174. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -1
  175. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
  176. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +204 -187
  177. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
  178. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +1 -1
  179. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +11 -11
  180. package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -1
  181. package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +1 -1
  182. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +89 -93
  183. package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -1
  184. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +83 -83
  185. package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
  186. package/lib/renderer/wgsl/compose.frag.wgsl.js +98 -98
  187. package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
  188. package/lib/renderer/wgsl/depthQuery.wgsl.js +2 -2
  189. package/lib/renderer/wgsl/jnlm.compute.js +74 -74
  190. package/lib/renderer/wgsl/offlineAdaptive.compute.js +25 -25
  191. package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +3 -3
  192. package/lib/renderer/wgsl/photonMap.compute.d.ts +29 -0
  193. package/lib/renderer/wgsl/photonMap.compute.js +352 -0
  194. package/lib/renderer/wgsl/photonMap.compute.js.map +1 -0
  195. package/lib/renderer/wgsl/proceduralSky.compute.js +64 -64
  196. package/lib/renderer/wgsl/rayTrace.compute.d.ts +9 -1
  197. package/lib/renderer/wgsl/rayTrace.compute.js +448 -522
  198. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  199. package/lib/scene/MaterialState.d.ts +2 -2
  200. package/lib/scene/MaterialState.js +2 -2
  201. package/lib/scene/MaterialState.js.map +1 -1
  202. package/lib/settings/render.d.ts +11 -0
  203. package/lib/settings/render.js +1 -0
  204. package/lib/settings/render.js.map +1 -1
  205. package/lib/util/fromTHREE.js +2 -2
  206. package/lib/util/fromTHREE.js.map +1 -1
  207. package/lib/version.d.ts +1 -1
  208. package/lib/version.js +1 -1
  209. package/lib/version.js.map +1 -1
  210. package/llms.txt +2 -1
  211. package/package.json +3 -3
@@ -7,7 +7,7 @@ function guideBinding(guides, binding, name) {
7
7
  : `@group(0) @binding(${binding}) var<storage, read> ${name}: array<vec4f>;`;
8
8
  }
9
9
  function loadGuide(guides, name, position) {
10
- return guides === 'textures' ? `textureLoad(${name}, ${position}, 0)` : `${name}[gv(${position})]`;
10
+ return guides === 'textures' ? `textureLoad(${name}, ${position}, 0)` : `${name}[gf(${position})]`;
11
11
  }
12
12
  // Shared uniform packing also fixes sensitivity and dilation for native callers.
13
13
  export function packJNLMParameters(width, height, strength, pass) {
@@ -43,9 +43,9 @@ ${shadowCatcherAccumulationWGSL}
43
43
  fn main(@builtin(global_invocation_id) id: vec3u) {
44
44
  let size = params.size;
45
45
  if (id.x >= size.x || id.y >= size.y) { return; }
46
- let qE = id.x + (size.y - 1u - id.y) * size.x;
47
- let n = normalBuffer[qE].a;
48
- let moments = accumulation[qE].moments;
46
+ let qw = id.x + (size.y - 1u - id.y) * size.x;
47
+ let n = normalBuffer[qw].a;
48
+ let moments = accumulation[qw].moments;
49
49
  let mean = moments.x / max(n, 1.0);
50
50
 
51
51
  var variance = -1.0;
@@ -69,28 +69,28 @@ fn main(@builtin(global_invocation_id) id: vec3u) {
69
69
  let size = params.size;
70
70
  if (id.x >= size.x || id.y >= size.y) { return; }
71
71
  let center = rawGuide[id.x + id.y * size.x];
72
- var mf = 0.0;
73
- var jx = 0.0;
74
- var nW = 0.0;
75
- var jp = 0.0;
72
+ var lM = 0.0;
73
+ var je = 0.0;
74
+ var nM = 0.0;
75
+ var iV = 0.0;
76
76
  for (var dy = -1; dy <= 1; dy++) {
77
77
  for (var dx = -1; dx <= 1; dx++) {
78
78
  let q = clamp(vec2i(id.xy) + vec2i(dx, dy), vec2i(0), vec2i(size) - 1);
79
79
  let value = rawGuide[u32(q.x) + u32(q.y) * size.x];
80
- nW += value.x;
81
- jp += value.x * value.x;
80
+ nM += value.x;
81
+ iV += value.x * value.x;
82
82
  if (value.y >= 0.0) {
83
- mf += value.y;
84
- jx += 1.0;
83
+ lM += value.y;
84
+ je += 1.0;
85
85
  }
86
86
  }
87
87
  }
88
88
  var variance: f32;
89
89
  if (center.y >= 0.0) {
90
- variance = mf / jx;
90
+ variance = lM / je;
91
91
  } else {
92
- let me = nW / 9.0;
93
- variance = max(jp / 9.0 - me * me, 0.0) * 9.0 / 8.0;
92
+ let lL = nM / 9.0;
93
+ variance = max(iV / 9.0 - lL * lL, 0.0) * 9.0 / 8.0;
94
94
  }
95
95
  guide[id.x + id.y * size.x] = vec2f(center.x, variance);
96
96
  }
@@ -114,11 +114,11 @@ fn main(@builtin(global_invocation_id) id: vec3u) {
114
114
  if (id.x >= params.size.x || id.y >= params.size.y) { return; }
115
115
  outColor[id.x + id.y * params.size.x] = textureLoad(colorTex, vec2i(id.xy), 0);
116
116
  ${guides === 'buffers' ? ` let index = id.x + id.y * params.size.x;
117
- let qE = id.x + (params.size.y - 1u - id.y) * params.size.x;
118
- let n = normalAccumulation[qE];
117
+ let qw = id.x + (params.size.y - 1u - id.y) * params.size.x;
118
+ let n = normalAccumulation[qw];
119
119
  let samples = max(n.a, 1.0);
120
120
  normal[index] = vec4f(n.rgb / samples * 0.5 + 0.5, select(0.0, 1.0, dot(n.rgb, n.rgb) > 0.1));
121
- shadow[index] = shadowAccumulation[qE].mask / samples;
121
+ shadow[index] = shadowAccumulation[qw].mask / samples;
122
122
  albedo[index] = textureLoad(albedoTex, vec2i(id.xy), 0);` : ''}
123
123
  }
124
124
  `;
@@ -155,23 +155,23 @@ const INV_NORMAL_BANDWIDTH2 = ${(1 / normalBandwidth ** 2).toFixed(6)};
155
155
 
156
156
  const CENTER_WEIGHT_MIN = 0.01;
157
157
 
158
- fn gv(p: vec2i) -> u32 {
158
+ fn gf(p: vec2i) -> u32 {
159
159
  let q = clamp(p, vec2i(0), vec2i(params.size) - 1);
160
160
  return u32(q.x) + u32(q.y) * params.size.x;
161
161
  }
162
162
 
163
- fn mi(q: vec2i, bC: vec4f, hg: vec3f) -> f32 {
163
+ fn lP(q: vec2i, bo: vec4f, gO: vec3f) -> f32 {
164
164
  let albedo = ${loadGuide(guides, 'albedoTex', 'q')};
165
165
 
166
166
 
167
- let qd = max((albedo.rgb + bC.rgb) * 0.5, vec3f(${JNLM_ALBEDO_FLOOR}));
168
- let lN = (albedo.rgb - bC.rgb) / qd;
169
- let jk = albedo.a - bC.a;
170
- let lX = ${loadGuide(guides, 'normalTex', 'q')}.rgb - hg;
167
+ let pY = max((albedo.rgb + bo.rgb) * 0.5, vec3f(${JNLM_ALBEDO_FLOOR}));
168
+ let lu = (albedo.rgb - bo.rgb) / pY;
169
+ let iQ = albedo.a - bo.a;
170
+ let lG = ${loadGuide(guides, 'normalTex', 'q')}.rgb - gO;
171
171
  return max(
172
- dot(lN, lN) * INV_ALBEDO_BANDWIDTH2
173
- + jk * jk * INV_COVERAGE_BANDWIDTH2,
174
- dot(lX, lX) * INV_NORMAL_BANDWIDTH2
172
+ dot(lu, lu) * INV_ALBEDO_BANDWIDTH2
173
+ + iQ * iQ * INV_COVERAGE_BANDWIDTH2,
174
+ dot(lG, lG) * INV_NORMAL_BANDWIDTH2
175
175
  );
176
176
  }
177
177
 
@@ -185,37 +185,37 @@ let size = vec2i(params.size);
185
185
  let p = vec2i(id.xy);
186
186
  if (p.x >= size.x || p.y >= size.y) { return; }
187
187
  let step = i32(params.dilation);
188
- let bC = ${loadGuide(guides, 'albedoTex', 'p')};
189
- let fR = ${loadGuide(guides, 'normalTex', 'p')};
190
- let hg = fR.rgb;
191
- let index = gv(p);
188
+ let bo = ${loadGuide(guides, 'albedoTex', 'p')};
189
+ let fC = ${loadGuide(guides, 'normalTex', 'p')};
190
+ let gO = fC.rgb;
191
+ let index = gf(p);
192
192
 
193
193
 
194
- if (fR.a <= 0.0 && ${loadGuide(guides, 'shadowCatcherTex', 'p')}.r <= 0.0) {
194
+ if (fC.a <= 0.0 && ${loadGuide(guides, 'shadowCatcherTex', 'p')}.r <= 0.0) {
195
195
  outColor[index] = color[index];
196
196
  outGuide[index] = guide[index];
197
197
  return;
198
198
  }
199
199
 
200
- var lP: array<vec2f, PATCH_SIZE>;
200
+ var lA: array<vec2f, PATCH_SIZE>;
201
201
  var k = 0;
202
202
  for (var dy = -PATCH_RADIUS; dy <= PATCH_RADIUS; dy++) {
203
203
  for (var dx = -PATCH_RADIUS; dx <= PATCH_RADIUS; dx++) {
204
- lP[k] = guide[gv(p + vec2i(dx, dy) * step)];
204
+ lA[k] = guide[gf(p + vec2i(dx, dy) * step)];
205
205
  k++;
206
206
  }
207
207
  }
208
208
  let k2 = params.colorSensitivity * params.colorSensitivity;
209
209
 
210
210
  var sum = vec4f(0.0);
211
- let hk = max(bC.rgb, vec3f(${JNLM_ALBEDO_FLOOR}));
212
- var ng = vec3f(0.0);
213
- var gN = vec3f(0.0);
214
- var hG = vec3f(0.0);
215
- var qp = 0.0;
216
- var gP = 0.0;
217
- var oP = 0.0;
218
- var lt = 0.0;
211
+ let gR = max(bo.rgb, vec3f(${JNLM_ALBEDO_FLOOR}));
212
+ var mQ = vec3f(0.0);
213
+ var gv = vec3f(0.0);
214
+ var hm = vec3f(0.0);
215
+ var qf = 0.0;
216
+ var gx = 0.0;
217
+ var oE = 0.0;
218
+ var kX = 0.0;
219
219
  for (var sy = -SEARCH_RADIUS; sy <= SEARCH_RADIUS; sy++) {
220
220
  for (var sx = -SEARCH_RADIUS; sx <= SEARCH_RADIUS; sx++) {
221
221
  let q = p + vec2i(sx, sy) * step;
@@ -224,63 +224,63 @@ if (
224
224
  q.x < 0 || q.y < 0 || q.x >= size.x || q.y >= size.y
225
225
  ) { continue; }
226
226
 
227
- let features = mi(q, bC, hg);
227
+ let features = lP(q, bo, gO);
228
228
  if (features > 16.0) { continue; }
229
229
 
230
- var gk = 0.0;
230
+ var fW = 0.0;
231
231
  k = 0;
232
232
  for (var dy = -PATCH_RADIUS; dy <= PATCH_RADIUS; dy++) {
233
233
  for (var dx = -PATCH_RADIUS; dx <= PATCH_RADIUS; dx++) {
234
- let a = lP[k];
235
- let b = guide[gv(q + vec2i(dx, dy) * step)];
236
- let rZ = a.x - b.x;
237
- gk += (rZ * rZ
234
+ let a = lA[k];
235
+ let b = guide[gf(q + vec2i(dx, dy) * step)];
236
+ let rV = a.x - b.x;
237
+ fW += (rV * rV
238
238
  - VARIANCE_CANCELLATION * (a.y + min(a.y, b.y)))
239
239
  / (1e-10 + k2 * (a.y + b.y));
240
240
  k++;
241
241
  }
242
242
  }
243
- gk /= f32(PATCH_SIZE);
243
+ fW /= f32(PATCH_SIZE);
244
244
 
245
- let weight = exp(-max(max(gk, 0.0), features));
246
- let index = gv(q);
245
+ let weight = exp(-max(max(fW, 0.0), features));
246
+ let index = gf(q);
247
247
  let c = color[index];
248
248
  let g = guide[index];
249
249
 
250
- let a = (${loadGuide(guides, 'albedoTex', 'q')}.rgb - bC.rgb) / hk;
250
+ let a = (${loadGuide(guides, 'albedoTex', 'q')}.rgb - bo.rgb) / gR;
251
251
  let value = premultiplied(c);
252
252
  sum += value * weight;
253
- ng += a * weight;
254
- gN += a * a * weight;
255
- hG += a * value.rgb * weight;
256
- qp += g.x * weight;
257
- gP += g.y * weight * weight;
258
- oP += weight;
259
- lt = max(lt, weight);
253
+ mQ += a * weight;
254
+ gv += a * a * weight;
255
+ hm += a * value.rgb * weight;
256
+ qf += g.x * weight;
257
+ gx += g.y * weight * weight;
258
+ oE += weight;
259
+ kX = max(kX, weight);
260
260
  }
261
261
  }
262
262
 
263
263
 
264
264
 
265
265
  let center = color[index];
266
- let lO = guide[index];
267
- let dR = max(lt, CENTER_WEIGHT_MIN);
268
- let totalWeight = oP + dR;
269
- let vj = premultiplied(center);
270
- let mean = (sum + vj * dR) / totalWeight;
266
+ let lw = guide[index];
267
+ let dE = max(kX, CENTER_WEIGHT_MIN);
268
+ let totalWeight = oE + dE;
269
+ let vs = premultiplied(center);
270
+ let mean = (sum + vs * dE) / totalWeight;
271
271
 
272
272
 
273
- let eI = ng / totalWeight;
274
- let mj = max(gN / totalWeight - eI * eI, vec3f(0.0));
275
- let rv = hG / totalWeight - eI * mean.rgb;
273
+ let er = mQ / totalWeight;
274
+ let lQ = max(gv / totalWeight - er * er, vec3f(0.0));
275
+ let rq = hm / totalWeight - er * mean.rgb;
276
276
 
277
277
 
278
278
 
279
- let rI = bC.rgb + eI * hk;
280
- let po = mean.rgb * hk / max(rI, vec3f(${JNLM_ALBEDO_FLOOR}));
281
- let xg = clamp(rv / (mj + vec3f(${JNLM_ALBEDO_REGULARIZATION})), vec3f(0.0), po);
282
- let sD = mean.rgb - xg * eI;
283
- let filtered = vec4f(max(sD, vec3f(0.0)), max(mean.a, 0.0));
279
+ let rD = bo.rgb + er * gR;
280
+ let pd = mean.rgb * gR / max(rD, vec3f(${JNLM_ALBEDO_FLOOR}));
281
+ let xI = clamp(rq / (lQ + vec3f(${JNLM_ALBEDO_REGULARIZATION})), vec3f(0.0), pd);
282
+ let sz = mean.rgb - xI * er;
283
+ let filtered = vec4f(max(sz, vec3f(0.0)), max(mean.a, 0.0));
284
284
  let rgb = select(
285
285
  center.rgb,
286
286
  filtered.rgb / max(filtered.a, 1e-6),
@@ -288,8 +288,8 @@ filtered.a > 1e-6
288
288
  );
289
289
  outColor[index] = vec4f(rgb, filtered.a);
290
290
  outGuide[index] = vec2f(
291
- (qp + lO.x * dR) / totalWeight,
292
- (gP + lO.y * dR * dR)
291
+ (qf + lw.x * dE) / totalWeight,
292
+ (gx + lw.y * dE * dE)
293
293
  / (totalWeight * totalWeight)
294
294
  );
295
295
  }
@@ -27,8 +27,8 @@ _padding: f32,
27
27
  const LUMINANCE = vec3f(0.2126, 0.7152, 0.0722);
28
28
 
29
29
  @compute @workgroup_size(64)
30
- fn main(@builtin(global_invocation_id) vQ: vec3u) {
31
- let tile = vQ.x;
30
+ fn main(@builtin(global_invocation_id) eA: vec3u) {
31
+ let tile = eA.x;
32
32
  let tileCount = params.columns * params.rows;
33
33
  if (tile >= tileCount) {
34
34
  return;
@@ -36,16 +36,16 @@ return;
36
36
 
37
37
  let tileX = tile % params.columns;
38
38
  let tileY = tile / params.columns;
39
- let vY = tileX * params.tileSize;
40
- let vZ = tileY * params.tileSize;
41
- let ye = min(vY + params.tileSize, params.width);
42
- let yf = min(vZ + params.tileSize, params.height);
43
- var nW = 0.0;
44
- var jB = 0.0;
39
+ let ws = tileX * params.tileSize;
40
+ let wt = tileY * params.tileSize;
41
+ let yF = min(ws + params.tileSize, params.width);
42
+ let yG = min(wt + params.tileSize, params.height);
43
+ var nM = 0.0;
44
+ var jh = 0.0;
45
45
  var count = 0u;
46
46
 
47
- for (var y = vZ; y < yf; y++) {
48
- for (var x = vY; x < ye; x++) {
47
+ for (var y = wt; y < yG; y++) {
48
+ for (var x = ws; x < yF; x++) {
49
49
  let pixel = y * params.width + x;
50
50
  let offset = pixel * 4u;
51
51
  let samples = pilotMoments[offset + 3u];
@@ -60,28 +60,28 @@ pilotColor[offset + 2u]
60
60
  ),
61
61
  LUMINANCE
62
62
  ) / samples;
63
- let va = pilotMoments[offset] / samples;
64
- let qg = max(va - mean * mean, 0.0);
65
- var variance = qg;
63
+ let vj = pilotMoments[offset] / samples;
64
+ let qb = max(vj - mean * mean, 0.0);
65
+ var variance = qb;
66
66
  if (samples > 1.0) {
67
- variance = qg * samples / (samples - 1.0);
67
+ variance = qb * samples / (samples - 1.0);
68
68
  }
69
- nW += mean;
70
- jB = max(jB, variance);
69
+ nM += mean;
70
+ jh = max(jh, variance);
71
71
  count += 1u;
72
72
  }
73
73
  }
74
74
 
75
- let mean = nW / f32(max(count, 1u));
76
- var pT = 0.0;
75
+ let mean = nM / f32(max(count, 1u));
76
+ var pK = 0.0;
77
77
  if (params.hasPrevious != 0u) {
78
- pT = previousVariance[tile];
78
+ pK = previousVariance[tile];
79
79
  }
80
- let tu = max(jB, pT);
81
- let uD = params.absoluteError + params.relativeError * abs(mean);
80
+ let tu = max(jh, pK);
81
+ let uH = params.absoluteError + params.relativeError * abs(mean);
82
82
  let required = ceil(
83
83
  params.confidence * params.confidence * tu /
84
- max(uD * uD, 1e-16)
84
+ max(uH * uH, 1e-16)
85
85
  );
86
86
  let targetSamples = u32(min(
87
87
  f32(params.maxSamples),
@@ -91,11 +91,11 @@ max(f32(params.minSamples), required)
91
91
  tileMeans[tile] = mean;
92
92
 
93
93
 
94
- tileVariances[tile] = jB;
94
+ tileVariances[tile] = jh;
95
95
  tileTargets[tile] = targetSamples;
96
96
 
97
- for (var y = vZ; y < yf; y++) {
98
- for (var x = vY; x < ye; x++) {
97
+ for (var y = wt; y < yG; y++) {
98
+ for (var x = ws; x < yF; x++) {
99
99
  textureStore(
100
100
  targetTexture,
101
101
  vec2i(i32(x), i32(y)),
@@ -1,16 +1,16 @@
1
1
  // OIDN's LDR auxiliary networks use sRGB albedo and normals in [0,1].
2
2
  export const oidnAuxiliaryDefine = /* wgsl */ `
3
- fn sT(value: vec3f) -> vec3f {
3
+ fn sQ(value: vec3f) -> vec3f {
4
4
  let linear = clamp(value, vec3f(0.0), vec3f(1.0));
5
5
  return select(1.055 * pow(linear, vec3f(1.0 / 2.4)) - 0.055,
6
6
  12.92 * linear, linear <= vec3f(0.0031308));
7
7
  }
8
- fn sS(value: vec3f) -> vec3f {
8
+ fn sP(value: vec3f) -> vec3f {
9
9
  let encoded = clamp(value, vec3f(0.0), vec3f(1.0));
10
10
  return select(pow((encoded + 0.055) / 1.055, vec3f(2.4)),
11
11
  encoded / 12.92, encoded <= vec3f(0.04045));
12
12
  }
13
- fn rL(value: vec3f) -> vec3f {
13
+ fn rG(value: vec3f) -> vec3f {
14
14
  return clamp(value, vec3f(0.0), vec3f(1.0));
15
15
  }
16
16
  `;
@@ -0,0 +1,29 @@
1
+ export declare const PHOTON_BYTES = 20;
2
+ export declare const PHOTON_GRID_SIZE = 262144;
3
+ export declare const PHOTON_SCAN_BLOCK = 256;
4
+ export declare const PHOTON_REFERENCE_COUNT: number;
5
+ export type PhotonRadiusScaling = 'none' | 'ppm' | 'constant-count';
6
+ /** Internal density A/B, with the same progressive schedule in all modes. */
7
+ export declare function photonRadius(initial: number, batch: number, alpha?: number, photons?: number, scaling?: PhotonRadiusScaling): number;
8
+ /** Default photon-map batch: pixel count / 4, capped at 2^18. */
9
+ export declare function defaultPhotonMapPhotons(width: number, height: number, divisor?: number): number;
10
+ /** Power-of-two hash capacity, bounded independently of large photon batches. */
11
+ export declare function photonBucketCount(photons: number): number;
12
+ /** Shared texture address contract; payload is stored once, indices twice. */
13
+ export declare function photonLayout(count: number, perCasterRadius?: boolean): {
14
+ buckets: number;
15
+ coarseHeaders: number;
16
+ fineHeaders: number;
17
+ coarseIndices: number;
18
+ fineIndices: number;
19
+ directions: number;
20
+ texels: number;
21
+ };
22
+ /** Batch-local hash buckets: count, two-level parallel prefix sum, then parallel scatter. */
23
+ export declare const photonGridCompute = "\n struct Bucket {\n count: atomic<u32>,\n offset: u32,\n cursor: atomic<u32>,\n padding: u32,\n }\n struct Parameters {\n radius: f32,\n count: u32,\n screenPixels: f32,\n skipQuery: f32,\n buckets: u32,\n fineRadius: f32,\n experiments: u32,\n padding2: u32,\n }\n @group(0)@binding(0)var photons:texture_2d<u32>;\n @group(0)@binding(1)var<storage,read_write>grid:array<Bucket>;\n @group(0)@binding(2)var<uniform>parameters:Parameters;\n @group(0)@binding(4)var headers:texture_storage_2d<r32uint,write>;\n fn hashCell(cell:vec3i)->u32 {\n let p=bitcast<vec3u>(cell);\n return ((p.x*73856093u)^(p.y*19349663u)^(p.z*83492791u))&(parameters.buckets-1u);\n }\n fn hashPhoton(p:vec3f,layer:u32)->u32 {\n let radius=select(parameters.radius,parameters.fineRadius,layer==1u);\n let cellSize=select(radius,2.0*radius,layer==1u);\n return hashCell(vec3i(floor(p/cellSize)));\n }\n fn gridStart(layer:u32)->u32 {\n return layer*(parameters.buckets+parameters.buckets/256u);\n }\n fn storeIndex(i:u32,value:u32) {\n let width=textureDimensions(headers).x;\n textureStore(headers,vec2i(vec2u(i%width,i/width)),vec4u(value));\n }\n fn photonRecord(i:u32)->vec4u {\n let width=textureDimensions(photons).x;\n return textureLoad(photons,vec2i(vec2u(i%width,i/width)),0);\n }\n fn photonPosition(i:u32)->vec3f {\n return bitcast<vec3f>(photonRecord(i).xyz);\n }\n @compute @workgroup_size(64)\n fn count(@builtin(global_invocation_id)id:vec3u) {\n if(id.x+id.y*4194240u>=parameters.count) {\n return;\n }\n let index=id.x+id.y*4194240u;\n if(photonRecord(index).w==0u) {\n return;\n }\n for(var layer=0u;layer<2u;layer++) {\n atomicAdd(&grid[gridStart(layer)+hashPhoton(photonPosition(index),layer)].count,1u);\n }\n }\n // Each block scans 256 buckets; the next workgroup scans chunks of block totals.\n var<workgroup> scan:array<u32,256>;\n fn inclusiveScan(local:u32,value:u32)->u32 {\n scan[local]=value;\n workgroupBarrier();\n for(var stride=1u;stride<256u;stride*=2u) {\n var previous=0u;\n if(local>=stride) {\n previous=scan[local-stride];\n }\n workgroupBarrier();\n scan[local]+=previous;\n workgroupBarrier();\n }\n return scan[local];\n }\n @compute @workgroup_size(256)\n fn scanBlocks(@builtin(local_invocation_index)local:u32,@builtin(workgroup_id)group:vec3u) {\n let index=gridStart(group.y)+group.x*256u+local;\n let count=atomicLoad(&grid[index].count);\n let prefix=inclusiveScan(local,count);\n grid[index].offset=prefix-count;\n if(local==255u) {\n atomicStore(&grid[gridStart(group.y)+parameters.buckets+group.x].count,prefix);\n }\n }\n @compute @workgroup_size(256)\n fn scanSums(@builtin(local_invocation_index)local:u32,@builtin(workgroup_id)group:vec3u) {\n let blocks=parameters.buckets/256u;\n let chunk=(blocks+255u)/256u;\n let first=local*chunk;\n let start=gridStart(group.y)+parameters.buckets;\n var count=0u;\n for(var i=first;i<min(first+chunk,blocks);i++) {\n count+=atomicLoad(&grid[start+i].count);\n }\n let prefix=inclusiveScan(local,count);\n var offset=prefix-count;\n for(var i=first;i<min(first+chunk,blocks);i++) {\n grid[start+i].offset=offset;\n offset+=atomicLoad(&grid[start+i].count);\n }\n if(local==255u&&group.y==0u) {\n storeIndex(0u,bitcast<u32>(parameters.radius));\n storeIndex(1u,prefix);\n storeIndex(2u,bitcast<u32>(parameters.screenPixels));\n storeIndex(3u,bitcast<u32>(parameters.skipQuery));\n storeIndex(4u,parameters.count);\n storeIndex(5u,parameters.buckets);\n storeIndex(6u,bitcast<u32>(parameters.fineRadius));\n storeIndex(7u,parameters.experiments);\n }\n }\n @compute @workgroup_size(64)\n fn scatter(@builtin(global_invocation_id)id:vec3u) {\n let invocation=id.x+id.y*4194240u;\n for(var layer=0u;layer<2u;layer++) {\n let start=gridStart(layer);\n if(invocation<parameters.buckets) {\n let offset=grid[start+invocation].offset+grid[start+parameters.buckets+invocation/256u].offset;\n storeIndex(8u+layer*parameters.buckets*2u+invocation*2u,offset);\n storeIndex(9u+layer*parameters.buckets*2u+invocation*2u,atomicLoad(&grid[start+invocation].count));\n }\n if(invocation<parameters.count&&photonRecord(invocation).w!=0u) {\n let bucket=hashPhoton(photonPosition(invocation),layer);\n let index=grid[start+bucket].offset+grid[start+parameters.buckets+bucket/256u].offset+atomicAdd(&grid[start+bucket].cursor,1u);\n storeIndex(8u+parameters.buckets*4u+layer*parameters.count+index,invocation);\n }\n }\n }\n";
24
+ /** Packed photon payload: position f32 x3, RGBE8 flux, two octahedral directions. */
25
+ export declare const photonPackingWgsl = "\n fn photonOctSign(v:vec2f)->vec2f {\n return select(vec2f(-1.0),vec2f(1.0),v>=vec2f(0.0));\n }\n fn photonEncodeOct(n:vec3f)->vec2f {\n let p=n/(abs(n.x)+abs(n.y)+abs(n.z));\n let uv=select(p.xy,(vec2f(1.0)-abs(p.yx))*photonOctSign(p.xy),p.z<0.0);\n return uv*.5+.5;\n }\n fn photonDecodeOct(uv:vec2f)->vec3f {\n let p=uv*2.0-1.0;\n var n=vec3f(p,1.0-abs(p.x)-abs(p.y));\n if(n.z<0.0) {\n n=vec3f((vec2f(1.0)-abs(n.yx))*photonOctSign(n.xy),n.z);\n }\n return normalize(n);\n }\n // An eight-bit exponent avoids RGB9E5's limited range for very dim/bright lights.\n // Flux is stored before dividing by emitted count, preserving tiny large batches.\n fn photonEncodeFlux(value:vec3f)->u32 {\n let peak=max(max(value.x,value.y),value.z);\n if(!(peak>0.0)) {\n return 0u;\n }\n let exponent=clamp(i32(floor(log2(peak)))+1,-126,127);\n let rgb=vec3u(clamp(round(value*exp2(-f32(exponent))*255.0),vec3f(0.0),vec3f(255.0)));\n return rgb.x|(rgb.y<<8u)|(rgb.z<<16u)|(u32(exponent+127)<<24u);\n }\n fn photonDecodeFlux(value:u32)->vec3f {\n if(value==0u) {\n return vec3f(0.0);\n }\n return vec3f(f32(value&255u),f32((value>>8u)&255u),f32((value>>16u)&255u))*exp2(f32(i32(value>>24u)-127))/255.0;\n }\n";
26
+ export declare const photonQueryWgsl = "\n fn photonOctSign(v:vec2f)->vec2f {\n return select(vec2f(-1.0),vec2f(1.0),v>=vec2f(0.0));\n }\n fn photonEncodeOct(n:vec3f)->vec2f {\n let p=n/(abs(n.x)+abs(n.y)+abs(n.z));\n let uv=select(p.xy,(vec2f(1.0)-abs(p.yx))*photonOctSign(p.xy),p.z<0.0);\n return uv*.5+.5;\n }\n fn photonDecodeOct(uv:vec2f)->vec3f {\n let p=uv*2.0-1.0;\n var n=vec3f(p,1.0-abs(p.x)-abs(p.y));\n if(n.z<0.0) {\n n=vec3f((vec2f(1.0)-abs(n.yx))*photonOctSign(n.xy),n.z);\n }\n return normalize(n);\n }\n // An eight-bit exponent avoids RGB9E5's limited range for very dim/bright lights.\n // Flux is stored before dividing by emitted count, preserving tiny large batches.\n fn photonEncodeFlux(value:vec3f)->u32 {\n let peak=max(max(value.x,value.y),value.z);\n if(!(peak>0.0)) {\n return 0u;\n }\n let exponent=clamp(i32(floor(log2(peak)))+1,-126,127);\n let rgb=vec3u(clamp(round(value*exp2(-f32(exponent))*255.0),vec3f(0.0),vec3f(255.0)));\n return rgb.x|(rgb.y<<8u)|(rgb.z<<16u)|(u32(exponent+127)<<24u);\n }\n fn photonDecodeFlux(value:u32)->vec3f {\n if(value==0u) {\n return vec3f(0.0);\n }\n return vec3f(f32(value&255u),f32((value>>8u)&255u),f32((value>>16u)&255u))*exp2(f32(i32(value>>24u)-127))/255.0;\n }\n\n @group(1)@binding(11)var photonMapData:texture_2d<u32>;\n @group(1)@binding(12)var photonMapGrid:texture_2d<u32>;\n fn photonLoad(i:u32)->vec4u {\n let width=textureDimensions(photonMapData).x;\n return textureLoad(photonMapData,vec2i(vec2u(i%width,i/width)),0);\n }\n fn photonIndexWord(i:u32)->u32 {\n let width=textureDimensions(photonMapGrid).x;\n return textureLoad(photonMapGrid,vec2i(vec2u(i%width,i/width)),0).x;\n }\n fn photonHash(cell:vec3i)->u32 {\n let p=bitcast<vec3u>(cell);\n return ((p.x*73856093u)^(p.y*19349663u)^(p.z*83492791u))&(photonIndexWord(5u)-1u);\n }\n // Approximate a specular prefix by the camera-space depth of its first diffuse\n // receiver. Orthographic footprints do not grow with distance. Grazing surfaces\n // expand the disk conservatively; the progressive world radius remains the cap.\n fn photonPixelFootprint(si:SurfaceInteraction)->f32 {\n let inverse=cameraUniform.viewProjectionInv;\n let a=inverse*vec4f(0.0,0.0,-1.0,1.0);\n let b=inverse*vec4f(2.0/rtUniform.outputSize.x,0.0,-1.0,1.0);\n let origin=a.xyz/a.w;\n let offset=b.xyz/b.w;\n let eye=cameraUniform.viewInv[3].xyz;\n let forward=normalize(cameraUniform.viewInv[2].xyz);\n let depth=abs(dot(si.position-eye,forward));\n let nearDepth=max(abs(dot(origin-eye,forward)),1e-8);\n let perspective=abs(inverse[2].w)>1e-8;\n let footprint=length(offset-origin)*select(1.0,depth/nearDepth,perspective);\n let cosine=abs(dot(si.faceNormal,normalize(si.position-eye)));\n return max(1e-8,footprint/sqrt(max(.0625,cosine)));\n }\n fn photonContribution(si:SurfaceInteraction,prepared:OpenPbrPreparedBsdf,primary:bool,visible:bool)->vec3f {\n if(samplingBounce>CAUSTIC_PHOTON_MAX_DEPTH||isCausticSpecular(prepared.material)||prepared.material.materialType==SHADOW_CATCHER) {\n return vec3f(0.0);\n }\n if(CAUSTIC_LIGHT_TRACING&&primary) {\n return vec3f(0.0);\n }\n let globalRadius=bitcast<f32>(photonIndexWord(0u));\n let screenPixels=bitcast<f32>(photonIndexWord(2u));\n if(!(globalRadius>0.0)||bitcast<f32>(photonIndexWord(3u))==1.0||(bitcast<f32>(photonIndexWord(3u))==2.0&&visible)) {\n return vec3f(0.0);\n }\n var radius=globalRadius;\n let experiments=photonIndexWord(7u);\n let deepPixels=f32(experiments>>8u);\n let queryPixels=select(deepPixels,screenPixels,visible);\n if(queryPixels>0.0) {\n radius=min(radius,photonPixelFootprint(si)*queryPixels);\n }\n let layer=select(0u,1u,visible&&screenPixels>0.0);\n let cellSize=select(globalRadius,2.0*bitcast<f32>(photonIndexWord(6u)),layer==1u);\n let firstCell=vec3i(floor((si.position-vec3f(radius))/cellSize));\n let lastCell=vec3i(floor((si.position+vec3f(radius))/cellSize));\n let buckets=photonIndexWord(5u);\n let headerStart=8u+layer*buckets*2u;\n let indexStart=8u+buckets*4u+layer*photonIndexWord(4u);\n let emitted=f32(photonIndexWord(4u));\n var sum=vec3f(0.0);\n for(var z=firstCell.z;z<=lastCell.z;z++) {\n for(var y=firstCell.y;y<=lastCell.y;y++) {\n for(var x=firstCell.x;x<=lastCell.x;x++) {\n let queryCell=vec3i(x,y,z);\n // Reject cube corners before loading their photon lists. Padding keeps\n // the bound conservative when cell multiplication rounds in f32.\n let cellMin=vec3f(queryCell)*cellSize;\n let padding=max(vec3f(cellSize*1e-5),abs(cellMin)*1e-6);\n let closest=clamp(si.position,cellMin-padding,cellMin+vec3f(cellSize)+padding);\n if(distance(closest,si.position)>=radius) { continue; }\n let bucket=photonHash(queryCell);\n let offset=photonIndexWord(headerStart+bucket*2u);\n let count=photonIndexWord(headerStart+1u+bucket*2u);\n for(var i=offset;i<offset+count;i++) {\n let index=photonIndexWord(indexStart+i);\n let record=photonLoad(index);\n let position=bitcast<vec3f>(record.xyz);\n // Hash collisions and neighboring buckets never count a photon twice.\n if(any(vec3i(floor(position/cellSize))!=queryCell)||distance(position,si.position)>=radius) {\n continue;\n }\n var kernelRadius=radius;\n if((experiments&1u)!=0u) {\n let scale=bitcast<f32>(photonIndexWord(8u+buckets*4u+u32(emitted)*3u+index));\n kernelRadius=min(radius,globalRadius*scale);\n if(!(kernelRadius>0.0)||distance(position,si.position)>=kernelRadius) { continue; }\n }\n let directions=photonIndexWord(8u+buckets*4u+u32(emitted)*2u+index);\n let normal=photonDecodeOct(vec2f(f32(directions&255u)/255.0,f32((directions>>8u)&127u)/127.0));\n if(dot(normal,si.faceNormal)<0.9) {\n continue;\n }\n if(CAUSTIC_MNEE&&samplingBounce<=CAUSTIC_MNEE_MAX_DEPTH&&(directions&32768u)!=0u&&prepared.material.baseWeight*(1.0-prepared.material.baseMetalness)*(1.0-prepared.material.transmissionWeight)>0.0&&prepared.material.subsurfaceWeight==0.0) {\n continue;\n }\n let incoming=photonDecodeOct(vec2f(f32((directions>>16u)&255u),f32(directions>>24u))/255.0);\n let cosine=abs(dot(si.faceNormal,incoming));\n if(cosine<=0.0) {\n continue;\n }\n var flux=photonDecodeFlux(record.w)/emitted;\n if(SPECTRAL) {\n flux=sampling_spectral_rgb_filter(flux,si.spectralChannel);\n }\n var pdf:f32;\n let response=openpbr_eval_prepared(prepared,incoming,&pdf);\n sum+=response*flux/cosine*(radius*radius)/(kernelRadius*kernelRadius);\n }\n }\n }\n }\n return sum/(PI*radius*radius);\n }\n";
27
+ export declare function photonDirectQuerySource(enabled?: boolean): string;
28
+ /** Evidence-only per-query counters; no extra bindings or public render options. */
29
+ export declare function photonQuerySource(debugGroup?: number, directSubset?: boolean): string;