@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
@@ -0,0 +1,352 @@
1
+ export const PHOTON_BYTES = 20;
2
+ export const PHOTON_GRID_SIZE = 262144;
3
+ export const PHOTON_SCAN_BLOCK = 256;
4
+ export const PHOTON_REFERENCE_COUNT = 2 ** 18;
5
+ /** Internal density A/B, with the same progressive schedule in all modes. */
6
+ export function photonRadius(initial, batch, alpha = 2 / 3, photons = PHOTON_REFERENCE_COUNT, scaling = 'none') {
7
+ const radius = initial * Math.pow(Math.max(1, batch), (alpha - 1) / 2);
8
+ if (scaling === 'none' || photons === PHOTON_REFERENCE_COUNT)
9
+ return radius;
10
+ const exponent = scaling === 'ppm' ? (alpha - 1) / 2 : -1 / 2;
11
+ return radius * Math.pow(Math.max(1, photons) / PHOTON_REFERENCE_COUNT, exponent);
12
+ }
13
+ /** Default photon-map batch: pixel count / 4, capped at 2^18. */
14
+ export function defaultPhotonMapPhotons(width, height, divisor = 4) {
15
+ return Math.min(2 ** 18, Math.max(1, Math.round((width * height) / divisor)));
16
+ }
17
+ /** Power-of-two hash capacity, bounded independently of large photon batches. */
18
+ export function photonBucketCount(photons) {
19
+ return Math.min(PHOTON_GRID_SIZE, 2 ** Math.ceil(Math.log2(Math.max(256, photons * 2))));
20
+ }
21
+ /** Shared texture address contract; payload is stored once, indices twice. */
22
+ export function photonLayout(count, perCasterRadius = false) {
23
+ const buckets = photonBucketCount(count);
24
+ const coarseHeaders = 8;
25
+ const fineHeaders = coarseHeaders + buckets * 2;
26
+ const coarseIndices = fineHeaders + buckets * 2;
27
+ const fineIndices = coarseIndices + count;
28
+ const directions = fineIndices + count;
29
+ return { buckets, coarseHeaders, fineHeaders, coarseIndices, fineIndices, directions, texels: directions + count * (perCasterRadius ? 2 : 1) };
30
+ }
31
+ /** Batch-local hash buckets: count, two-level parallel prefix sum, then parallel scatter. */
32
+ export const photonGridCompute = /* wgsl */ `
33
+ struct Bucket {
34
+ count: atomic<u32>,
35
+ offset: u32,
36
+ cursor: atomic<u32>,
37
+ padding: u32,
38
+ }
39
+ struct Parameters {
40
+ radius: f32,
41
+ count: u32,
42
+ screenPixels: f32,
43
+ skipQuery: f32,
44
+ buckets: u32,
45
+ fineRadius: f32,
46
+ experiments: u32,
47
+ padding2: u32,
48
+ }
49
+ @group(0)@binding(0)var photons:texture_2d<u32>;
50
+ @group(0)@binding(1)var<storage,read_write>grid:array<Bucket>;
51
+ @group(0)@binding(2)var<uniform>parameters:Parameters;
52
+ @group(0)@binding(4)var headers:texture_storage_2d<r32uint,write>;
53
+ fn xu(yp:vec3i)->u32 {
54
+ let p=bitcast<vec3u>(yp);
55
+ return ((p.x*73856093u)^(p.y*19349663u)^(p.z*83492791u))&(parameters.buckets-1u);
56
+ }
57
+ fn tM(p:vec3f,layer:u32)->u32 {
58
+ let radius=select(parameters.radius,parameters.fineRadius,layer==1u);
59
+ let lv=select(radius,2.0*radius,layer==1u);
60
+ return xu(vec3i(floor(p/lv)));
61
+ }
62
+ fn os(layer:u32)->u32 {
63
+ return layer*(parameters.buckets+parameters.buckets/256u);
64
+ }
65
+ fn fm(i:u32,value:u32) {
66
+ let width=textureDimensions(headers).x;
67
+ textureStore(headers,vec2i(vec2u(i%width,i/width)),vec4u(value));
68
+ }
69
+ fn pi(i:u32)->vec4u {
70
+ let width=textureDimensions(photons).x;
71
+ return textureLoad(photons,vec2i(vec2u(i%width,i/width)),0);
72
+ }
73
+ fn qv(i:u32)->vec3f {
74
+ return bitcast<vec3f>(pi(i).xyz);
75
+ }
76
+ @compute @workgroup_size(64)
77
+ fn count(@builtin(global_invocation_id)id:vec3u) {
78
+ if(id.x+id.y*4194240u>=parameters.count) {
79
+ return;
80
+ }
81
+ let index=id.x+id.y*4194240u;
82
+ if(pi(index).w==0u) {
83
+ return;
84
+ }
85
+ for(var layer=0u;layer<2u;layer++) {
86
+ atomicAdd(&grid[os(layer)+tM(qv(index),layer)].count,1u);
87
+ }
88
+ }
89
+
90
+ var<workgroup> scan:array<u32,256>;
91
+ fn iU(local:u32,value:u32)->u32 {
92
+ scan[local]=value;
93
+ workgroupBarrier();
94
+ for(var stride=1u;stride<256u;stride*=2u) {
95
+ var previous=0u;
96
+ if(local>=stride) {
97
+ previous=scan[local-stride];
98
+ }
99
+ workgroupBarrier();
100
+ scan[local]+=previous;
101
+ workgroupBarrier();
102
+ }
103
+ return scan[local];
104
+ }
105
+ @compute @workgroup_size(256)
106
+ fn scanBlocks(@builtin(local_invocation_index)local:u32,@builtin(workgroup_id)group:vec3u) {
107
+ let index=os(group.y)+group.x*256u+local;
108
+ let count=atomicLoad(&grid[index].count);
109
+ let prefix=iU(local,count);
110
+ grid[index].offset=prefix-count;
111
+ if(local==255u) {
112
+ atomicStore(&grid[os(group.y)+parameters.buckets+group.x].count,prefix);
113
+ }
114
+ }
115
+ @compute @workgroup_size(256)
116
+ fn scanSums(@builtin(local_invocation_index)local:u32,@builtin(workgroup_id)group:vec3u) {
117
+ let blocks=parameters.buckets/256u;
118
+ let xo=(blocks+255u)/256u;
119
+ let first=local*xo;
120
+ let start=os(group.y)+parameters.buckets;
121
+ var count=0u;
122
+ for(var i=first;i<min(first+xo,blocks);i++) {
123
+ count+=atomicLoad(&grid[start+i].count);
124
+ }
125
+ let prefix=iU(local,count);
126
+ var offset=prefix-count;
127
+ for(var i=first;i<min(first+xo,blocks);i++) {
128
+ grid[start+i].offset=offset;
129
+ offset+=atomicLoad(&grid[start+i].count);
130
+ }
131
+ if(local==255u&&group.y==0u) {
132
+ fm(0u,bitcast<u32>(parameters.radius));
133
+ fm(1u,prefix);
134
+ fm(2u,bitcast<u32>(parameters.screenPixels));
135
+ fm(3u,bitcast<u32>(parameters.skipQuery));
136
+ fm(4u,parameters.count);
137
+ fm(5u,parameters.buckets);
138
+ fm(6u,bitcast<u32>(parameters.fineRadius));
139
+ fm(7u,parameters.experiments);
140
+ }
141
+ }
142
+ @compute @workgroup_size(64)
143
+ fn scatter(@builtin(global_invocation_id)id:vec3u) {
144
+ let eA=id.x+id.y*4194240u;
145
+ for(var layer=0u;layer<2u;layer++) {
146
+ let start=os(layer);
147
+ if(eA<parameters.buckets) {
148
+ let offset=grid[start+eA].offset+grid[start+parameters.buckets+eA/256u].offset;
149
+ fm(8u+layer*parameters.buckets*2u+eA*2u,offset);
150
+ fm(9u+layer*parameters.buckets*2u+eA*2u,atomicLoad(&grid[start+eA].count));
151
+ }
152
+ if(eA<parameters.count&&pi(eA).w!=0u) {
153
+ let su=tM(qv(eA),layer);
154
+ let index=grid[start+su].offset+grid[start+parameters.buckets+su/256u].offset+atomicAdd(&grid[start+su].cursor,1u);
155
+ fm(8u+parameters.buckets*4u+layer*parameters.count+index,eA);
156
+ }
157
+ }
158
+ }
159
+ `;
160
+ /** Packed photon payload: position f32 x3, RGBE8 flux, two octahedral directions. */
161
+ export const photonPackingWgsl = /* wgsl */ `
162
+ fn rd(v:vec2f)->vec2f {
163
+ return select(vec2f(-1.0),vec2f(1.0),v>=vec2f(0.0));
164
+ }
165
+ fn pN(n:vec3f)->vec2f {
166
+ let p=n/(abs(n.x)+abs(n.y)+abs(n.z));
167
+ let uv=select(p.xy,(vec2f(1.0)-abs(p.yx))*rd(p.xy),p.z<0.0);
168
+ return uv*.5+.5;
169
+ }
170
+ fn pM(uv:vec2f)->vec3f {
171
+ let p=uv*2.0-1.0;
172
+ var n=vec3f(p,1.0-abs(p.x)-abs(p.y));
173
+ if(n.z<0.0) {
174
+ n=vec3f((vec2f(1.0)-abs(n.yx))*rd(n.xy),n.z);
175
+ }
176
+ return normalize(n);
177
+ }
178
+
179
+
180
+ fn sS(value:vec3f)->u32 {
181
+ let peak=max(max(value.x,value.y),value.z);
182
+ if(!(peak>0.0)) {
183
+ return 0u;
184
+ }
185
+ let exponent=clamp(i32(floor(log2(peak)))+1,-126,127);
186
+ let rgb=vec3u(clamp(round(value*exp2(-f32(exponent))*255.0),vec3f(0.0),vec3f(255.0)));
187
+ return rgb.x|(rgb.y<<8u)|(rgb.z<<16u)|(u32(exponent+127)<<24u);
188
+ }
189
+ fn photonDecodeFlux(value:u32)->vec3f {
190
+ if(value==0u) {
191
+ return vec3f(0.0);
192
+ }
193
+ return vec3f(f32(value&255u),f32((value>>8u)&255u),f32((value>>16u)&255u))*exp2(f32(i32(value>>24u)-127))/255.0;
194
+ }
195
+ `;
196
+ export const photonQueryWgsl = /* wgsl */ `${photonPackingWgsl}
197
+ @group(1)@binding(11)var photonMapData:texture_2d<u32>;
198
+ @group(1)@binding(12)var photonMapGrid:texture_2d<u32>;
199
+ fn photonLoad(i:u32)->vec4u {
200
+ let width=textureDimensions(photonMapData).x;
201
+ return textureLoad(photonMapData,vec2i(vec2u(i%width,i/width)),0);
202
+ }
203
+ fn photonIndexWord(i:u32)->u32 {
204
+ let width=textureDimensions(photonMapGrid).x;
205
+ return textureLoad(photonMapGrid,vec2i(vec2u(i%width,i/width)),0).x;
206
+ }
207
+ fn wp(yp:vec3i)->u32 {
208
+ let p=bitcast<vec3u>(yp);
209
+ return ((p.x*73856093u)^(p.y*19349663u)^(p.z*83492791u))&(photonIndexWord(5u)-1u);
210
+ }
211
+
212
+
213
+
214
+ fn photonPixelFootprint(si:SurfaceInteraction)->f32 {
215
+ let inverse=cameraUniform.viewProjectionInv;
216
+ let a=inverse*vec4f(0.0,0.0,-1.0,1.0);
217
+ let b=inverse*vec4f(2.0/rtUniform.outputSize.x,0.0,-1.0,1.0);
218
+ let origin=a.xyz/a.w;
219
+ let offset=b.xyz/b.w;
220
+ let eye=cameraUniform.viewInv[3].xyz;
221
+ let forward=normalize(cameraUniform.viewInv[2].xyz);
222
+ let depth=abs(dot(si.position-eye,forward));
223
+ let xe=max(abs(dot(origin-eye,forward)),1e-8);
224
+ let perspective=abs(inverse[2].w)>1e-8;
225
+ let footprint=length(offset-origin)*select(1.0,depth/xe,perspective);
226
+ let cosine=abs(dot(si.faceNormal,normalize(si.position-eye)));
227
+ return max(1e-8,footprint/sqrt(max(.0625,cosine)));
228
+ }
229
+ fn rI(si:SurfaceInteraction,prepared:OpenPbrPreparedBsdf,vb:bool,visible:bool)->vec3f {
230
+ if(samplingBounce>CAUSTIC_PHOTON_MAX_DEPTH||isCausticSpecular(prepared.material)||prepared.material.materialType==SHADOW_CATCHER) {
231
+ return vec3f(0.0);
232
+ }
233
+ if(CAUSTIC_LIGHT_TRACING&&vb) {
234
+ return vec3f(0.0);
235
+ }
236
+ let mn=bitcast<f32>(photonIndexWord(0u));
237
+ let screenPixels=bitcast<f32>(photonIndexWord(2u));
238
+ if(!(mn>0.0)||bitcast<f32>(photonIndexWord(3u))==1.0||(bitcast<f32>(photonIndexWord(3u))==2.0&&visible)) {
239
+ return vec3f(0.0);
240
+ }
241
+ var radius=mn;
242
+ let experiments=photonIndexWord(7u);
243
+ let wa=f32(experiments>>8u);
244
+ let tf=select(wa,screenPixels,visible);
245
+ if(tf>0.0) {
246
+ radius=min(radius,photonPixelFootprint(si)*tf);
247
+ }
248
+ let layer=select(0u,1u,visible&&screenPixels>0.0);
249
+ let lv=select(mn,2.0*bitcast<f32>(photonIndexWord(6u)),layer==1u);
250
+ let sG=vec3i(floor((si.position-vec3f(radius))/lv));
251
+ let tS=vec3i(floor((si.position+vec3f(radius))/lv));
252
+ let buckets=photonIndexWord(5u);
253
+ let tb=8u+layer*buckets*2u;
254
+ let indexStart=8u+buckets*4u+layer*photonIndexWord(4u);
255
+ let emitted=f32(photonIndexWord(4u));
256
+ var sum=vec3f(0.0);
257
+ for(var z=sG.z;z<=tS.z;z++) {
258
+ for(var y=sG.y;y<=tS.y;y++) {
259
+ for(var x=sG.x;x<=tS.x;x++) {
260
+ let sU=vec3i(x,y,z);
261
+
262
+
263
+ let uP=vec3f(sU)*lv;
264
+ let padding=max(vec3f(lv*1e-5),abs(uP)*1e-6);
265
+ let xV=clamp(si.position,uP-padding,uP+vec3f(lv)+padding);
266
+ if(distance(xV,si.position)>=radius) { continue; }
267
+ let su=wp(sU);
268
+ let offset=photonIndexWord(tb+su*2u);
269
+ let count=photonIndexWord(tb+1u+su*2u);
270
+ for(var i=offset;i<offset+count;i++) {
271
+ let index=photonIndexWord(indexStart+i);
272
+ let record=photonLoad(index);
273
+ let position=bitcast<vec3f>(record.xyz);
274
+
275
+ if(any(vec3i(floor(position/lv))!=sU)||distance(position,si.position)>=radius) {
276
+ continue;
277
+ }
278
+ var jY=radius;
279
+ if((experiments&1u)!=0u) {
280
+ let scale=bitcast<f32>(photonIndexWord(8u+buckets*4u+u32(emitted)*3u+index));
281
+ jY=min(radius,mn*scale);
282
+ if(!(jY>0.0)||distance(position,si.position)>=jY) { continue; }
283
+ }
284
+ let directions=photonIndexWord(8u+buckets*4u+u32(emitted)*2u+index);
285
+ let normal=pM(vec2f(f32(directions&255u)/255.0,f32((directions>>8u)&127u)/127.0));
286
+ if(dot(normal,si.faceNormal)<0.9) {
287
+ continue;
288
+ }
289
+ 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) {
290
+ continue;
291
+ }
292
+ let hQ=pM(vec2f(f32((directions>>16u)&255u),f32(directions>>24u))/255.0);
293
+ let cosine=abs(dot(si.faceNormal,hQ));
294
+ if(cosine<=0.0) {
295
+ continue;
296
+ }
297
+ var flux=photonDecodeFlux(record.w)/emitted;
298
+ if(SPECTRAL) {
299
+ flux=sampling_spectral_rgb_filter(flux,si.spectralChannel);
300
+ }
301
+ var pdf:f32;
302
+ let response=openpbr_eval_prepared(prepared,hQ,&pdf);
303
+ sum+=response*flux/cosine*(radius*radius)/(jY*jY);
304
+ }
305
+ }
306
+ }
307
+ }
308
+ return sum/(PI*radius*radius);
309
+ }
310
+ `;
311
+ // Each translated 2x2 tile assigns all four labels once. Conditional on any
312
+ // stored photon, a uniform pixel label accepts it with probability 1/4; scaling
313
+ // accepted flux by four preserves the original expected density estimate.
314
+ // Only the first camera hit participates; specular prefixes and deeper queries
315
+ // keep the original estimator. Disjoint salts consume no sampling dimensions.
316
+ export function photonDirectQuerySource(enabled = false) {
317
+ if (!enabled)
318
+ return photonQueryWgsl;
319
+ return photonQueryWgsl
320
+ .replace('var sum=vec3f(0.0);', `let rL=ak(aC,rtUniform.sequenceSeed^0x91e10da5u);
321
+ let vT=vec2u(cB(rL^0x68bc21ebu),cB(rL^0x967a889bu));
322
+ let tj=aI+vT;
323
+ let vS=(tj.x&1u)|((tj.y&1u)<<1u);
324
+ var sum=vec3f(0.0);`)
325
+ .replace('let record=photonLoad(index);', `if(vb&&(ak(index,rL^0x02e5be93u)&3u)!=vS) { continue; }
326
+ let record=photonLoad(index);`)
327
+ .replace('var flux=photonDecodeFlux(record.w)/emitted;', `var flux=photonDecodeFlux(record.w)/emitted;
328
+ if(vb) { flux*=4.0; }`);
329
+ }
330
+ /** Evidence-only per-query counters; no extra bindings or public render options. */
331
+ export function photonQuerySource(debugGroup = -1, directSubset = false) {
332
+ const source = photonDirectQuerySource(directSubset);
333
+ if (debugGroup < 50)
334
+ return source;
335
+ // Offline union-of-photons audit. Each depth is replayed with the same seed;
336
+ // payloads are positions/normals rather than radiance, before path throughput.
337
+ if (debugGroup >= 80) {
338
+ const normal = debugGroup >= 130 || debugGroup === 81;
339
+ const visible = debugGroup < 100;
340
+ const depth = debugGroup - (normal ? 130 : 100);
341
+ const selected = visible ? 'visible' : `!visible&&samplingBounce==${depth}u`;
342
+ const payload = debugGroup === 82 ? 'vec3f(photonPixelFootprint(si),radius,1.0)' : normal ? 'si.faceNormal' : 'si.position';
343
+ return 'var<private> photonQueryStats:vec3f;\n' + source.replace('return sum/(PI*radius*radius);', `if(${selected}){photonQueryStats=${payload};}return sum/(PI*radius*radius);`);
344
+ }
345
+ const selected = debugGroup === 50 || debugGroup === 73 ? 'visible' : debugGroup === 72 ? '!visible' : `!visible&&samplingBounce==${debugGroup - 51}u`;
346
+ return 'var<private> photonQueryStats:vec3f;\n' + source
347
+ .replace('var sum=vec3f(0.0);', 'var sum=vec3f(0.0);var reads=0u;var passed=0u;')
348
+ .replace('let index=photonIndexWord(indexStart+i);', 'reads++;let index=photonIndexWord(indexStart+i);')
349
+ .replace(debugGroup === 73 ? 'var flux=photonDecodeFlux' : 'let directions=photonIndexWord', debugGroup === 73 ? 'passed++;var flux=photonDecodeFlux' : 'passed++;let directions=photonIndexWord')
350
+ .replace('return sum/(PI*radius*radius);', `if(${selected}){photonQueryStats${debugGroup === 72 ? '+' : ''}=vec3f(f32(reads),f32(passed),1.0);}return sum/(PI*radius*radius);`);
351
+ }
352
+ //# sourceMappingURL=photonMap.compute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"photonMap.compute.js","sourceRoot":"","sources":["../../../src/renderer/wgsl/photonMap.compute.ts"],"names":[],"mappings":"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","sourcesContent":["export const PHOTON_BYTES = 20;\nexport const PHOTON_GRID_SIZE = 262144;\nexport const PHOTON_SCAN_BLOCK = 256;\nexport const PHOTON_REFERENCE_COUNT = 2 ** 18;\nexport type PhotonRadiusScaling = 'none' | 'ppm' | 'constant-count';\n/** Internal density A/B, with the same progressive schedule in all modes. */\nexport function photonRadius(\n initial: number,\n batch: number,\n alpha = 2 / 3,\n photons = PHOTON_REFERENCE_COUNT,\n scaling: PhotonRadiusScaling = 'none'\n) {\n const radius = initial * Math.pow(Math.max(1, batch), (alpha - 1) / 2);\n if (scaling === 'none' || photons === PHOTON_REFERENCE_COUNT) return radius;\n const exponent = scaling === 'ppm' ? (alpha - 1) / 2 : -1 / 2;\n return radius * Math.pow(Math.max(1, photons) / PHOTON_REFERENCE_COUNT, exponent);\n}\n/** Default photon-map batch: pixel count / 4, capped at 2^18. */\nexport function defaultPhotonMapPhotons(width: number, height: number, divisor = 4) {\n return Math.min(2 ** 18, Math.max(1, Math.round((width * height) / divisor)));\n}\n/** Power-of-two hash capacity, bounded independently of large photon batches. */\nexport function photonBucketCount(photons: number) {\n return Math.min(PHOTON_GRID_SIZE, 2 ** Math.ceil(Math.log2(Math.max(256, photons * 2))));\n}\n/** Shared texture address contract; payload is stored once, indices twice. */\nexport function photonLayout(count: number, perCasterRadius = false) {\n const buckets = photonBucketCount(count);\n const coarseHeaders = 8;\n const fineHeaders = coarseHeaders + buckets * 2;\n const coarseIndices = fineHeaders + buckets * 2;\n const fineIndices = coarseIndices + count;\n const directions = fineIndices + count;\n return {buckets, coarseHeaders, fineHeaders, coarseIndices, fineIndices, directions, texels: directions + count * (perCasterRadius ? 2 : 1)};\n}\n/** Batch-local hash buckets: count, two-level parallel prefix sum, then parallel scatter. */\nexport const photonGridCompute = /* wgsl */ `\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`;\n/** Packed photon payload: position f32 x3, RGBE8 flux, two octahedral directions. */\nexport const photonPackingWgsl = /* wgsl */ `\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`;\nexport const photonQueryWgsl = /* wgsl */ `${photonPackingWgsl}\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`;\n\n// Each translated 2x2 tile assigns all four labels once. Conditional on any\n// stored photon, a uniform pixel label accepts it with probability 1/4; scaling\n// accepted flux by four preserves the original expected density estimate.\n// Only the first camera hit participates; specular prefixes and deeper queries\n// keep the original estimator. Disjoint salts consume no sampling dimensions.\nexport function photonDirectQuerySource(enabled = false) {\n if (!enabled) return photonQueryWgsl;\n return photonQueryWgsl\n .replace('var sum=vec3f(0.0);', `let subsetSeed=sampleSeed(pixelSampleIndex,rtUniform.sequenceSeed^0x91e10da5u);\n let subsetShift=vec2u(sampleHash(subsetSeed^0x68bc21ebu),sampleHash(subsetSeed^0x967a889bu));\n let subsetPixel=samplingPixel+subsetShift;\n let subsetGroup=(subsetPixel.x&1u)|((subsetPixel.y&1u)<<1u);\n var sum=vec3f(0.0);`)\n .replace('let record=photonLoad(index);', `if(primary&&(sampleSeed(index,subsetSeed^0x02e5be93u)&3u)!=subsetGroup) { continue; }\n let record=photonLoad(index);`)\n .replace('var flux=photonDecodeFlux(record.w)/emitted;', `var flux=photonDecodeFlux(record.w)/emitted;\n if(primary) { flux*=4.0; }`);\n}\n/** Evidence-only per-query counters; no extra bindings or public render options. */\nexport function photonQuerySource(debugGroup = -1, directSubset = false) {\n const source = photonDirectQuerySource(directSubset);\n if (debugGroup < 50) return source;\n // Offline union-of-photons audit. Each depth is replayed with the same seed;\n // payloads are positions/normals rather than radiance, before path throughput.\n if (debugGroup >= 80) {\n const normal = debugGroup >= 130 || debugGroup === 81;\n const visible = debugGroup < 100;\n const depth = debugGroup - (normal ? 130 : 100);\n const selected = visible ? 'visible' : `!visible&&samplingBounce==${depth}u`;\n const payload = debugGroup === 82 ? 'vec3f(photonPixelFootprint(si),radius,1.0)' : normal ? 'si.faceNormal' : 'si.position';\n return 'var<private> photonQueryStats:vec3f;\\n' + source.replace('return sum/(PI*radius*radius);', `if(${selected}){photonQueryStats=${payload};}return sum/(PI*radius*radius);`);\n }\n const selected = debugGroup === 50 || debugGroup === 73 ? 'visible' : debugGroup === 72 ? '!visible' : `!visible&&samplingBounce==${debugGroup - 51}u`;\n return 'var<private> photonQueryStats:vec3f;\\n' + source\n .replace('var sum=vec3f(0.0);', 'var sum=vec3f(0.0);var reads=0u;var passed=0u;')\n .replace('let index=photonIndexWord(indexStart+i);', 'reads++;let index=photonIndexWord(indexStart+i);')\n .replace(debugGroup === 73 ? 'var flux=photonDecodeFlux' : 'let directions=photonIndexWord',\n debugGroup === 73 ? 'passed++;var flux=photonDecodeFlux' : 'passed++;let directions=photonIndexWord')\n .replace('return sum/(PI*radius*radius);', `if(${selected}){photonQueryStats${debugGroup === 72 ? '+' : ''}=vec3f(f32(reads),f32(passed),1.0);}return sum/(PI*radius*radius);`);\n}\n"]}
@@ -22,18 +22,18 @@ const T_SIZE = vec2u(256, 64);
22
22
  const MS_ALTITUDES = 32u;
23
23
  const SV_SIZE = vec2u(96, 192);
24
24
 
25
- fn br(r: f32, mu: f32, radius: f32) -> f32 { return (radius - r) * (radius + r) + r * r * mu * mu; }
26
- fn np(r: f32, mu: f32) -> bool { return mu < 0.0 && br(r, mu, R_GROUND) >= 0.0; }
27
- fn ll(r: f32, mu: f32) -> f32 { return max(-r * mu - sqrt(max(br(r, mu, R_GROUND), 0.0)), 0.0); }
28
- fn jm(r: f32, mu: f32) -> f32 { return max(-r * mu + sqrt(max(br(r, mu, R_TOP), 0.0)), 0.0); }
29
- fn oz(r: f32) -> vec3f {
25
+ fn bj(r: f32, mu: f32, radius: f32) -> f32 { return (radius - r) * (radius + r) + r * r * mu * mu; }
26
+ fn mV(r: f32, mu: f32) -> bool { return mu < 0.0 && bj(r, mu, R_GROUND) >= 0.0; }
27
+ fn kT(r: f32, mu: f32) -> f32 { return max(-r * mu - sqrt(max(bj(r, mu, R_GROUND), 0.0)), 0.0); }
28
+ fn iS(r: f32, mu: f32) -> f32 { return max(-r * mu + sqrt(max(bj(r, mu, R_TOP), 0.0)), 0.0); }
29
+ fn oo(r: f32) -> vec3f {
30
30
  let h = max(r - R_GROUND, 0.0);
31
31
  return vec3f(exp(-h / 8.0), exp(-h / 1.2) * ${TURBIDITY}, max(0.0, 1.0 - abs(h - 25.0) / 15.0));
32
32
  }
33
33
  fn extinction(d: vec3f) -> vec3f { return RAYLEIGH * d.x + MIE_EXTINCTION * d.y + OZONE * d.z; }
34
- fn mP(x: f32) -> f32 { return x * x * (R_TOP - R_GROUND); }
35
- fn hR(h: f32) -> f32 { return sqrt(clamp(h / (R_TOP - R_GROUND), 0.0, 1.0)); }
36
- fn fh(table: texture_2d<f32>, p: vec2f) -> vec3f {
34
+ fn mB(x: f32) -> f32 { return x * x * (R_TOP - R_GROUND); }
35
+ fn hy(h: f32) -> f32 { return sqrt(clamp(h / (R_TOP - R_GROUND), 0.0, 1.0)); }
36
+ fn eR(table: texture_2d<f32>, p: vec2f) -> vec3f {
37
37
  let size = vec2i(textureDimensions(table));
38
38
  let q = clamp(p, vec2f(0.0), vec2f(size - 1));
39
39
  let i = min(vec2i(floor(q)), max(size - 2, vec2i(0)));
@@ -45,25 +45,25 @@ let c = textureLoad(table, vec2i(i.x, j.y), 0).rgb;
45
45
  let d = textureLoad(table, j, 0).rgb;
46
46
  return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
47
47
  }
48
- fn fO(r: f32, mu: f32) -> vec3f {
49
- return fh(transmittanceTable, vec2f((clamp(mu, -1.0, 1.0) + 1.0) * 0.5 * f32(T_SIZE.x - 1u), hR(r - R_GROUND) * f32(T_SIZE.y - 1u)));
48
+ fn fv(r: f32, mu: f32) -> vec3f {
49
+ return eR(transmittanceTable, vec2f((clamp(mu, -1.0, 1.0) + 1.0) * 0.5 * f32(T_SIZE.x - 1u), hy(r - R_GROUND) * f32(T_SIZE.y - 1u)));
50
50
  }
51
- fn mW(nu: f32) -> f32 {
51
+ fn mI(nu: f32) -> f32 {
52
52
  let g2 = MIE_G * MIE_G;
53
53
  return 3.0 / (8.0 * PI) * ((1.0 - g2) * (1.0 + nu * nu)) / ((2.0 + g2) * pow(1.0 + g2 - 2.0 * MIE_G * nu, 1.5));
54
54
  }
55
- struct xJ { radiance: vec3f, throughput: vec3f }
56
- fn vt(origin: vec3f, dir: vec3f, steps: u32, full: bool, uw: vec3f) -> xJ {
55
+ struct yk { radiance: vec3f, throughput: vec3f }
56
+ fn vG(origin: vec3f, dir: vec3f, steps: u32, full: bool, uD: vec3f) -> yk {
57
57
  let r = length(origin);
58
58
  let mu = dot(origin, dir) / r;
59
- let ground = np(r, mu);
60
- var len = jm(r, mu);
61
- if (ground) { len = ll(r, mu); }
59
+ let ground = mV(r, mu);
60
+ var len = iS(r, mu);
61
+ if (ground) { len = kT(r, mu); }
62
62
  let nu = dot(dir, params.sun.xyz);
63
- var jv = 1.0 / (4.0 * PI);
64
- var sn = jv;
65
- if (full) { jv = 3.0 / (16.0 * PI) * (1.0 + nu * nu); sn = mW(nu); }
66
- var result = xJ(vec3f(0.0), vec3f(0.0));
63
+ var ja = 1.0 / (4.0 * PI);
64
+ var sh = ja;
65
+ if (full) { ja = 3.0 / (16.0 * PI) * (1.0 + nu * nu); sh = mI(nu); }
66
+ var result = yk(vec3f(0.0), vec3f(0.0));
67
67
  var view = vec3f(1.0);
68
68
  var previous = 0.0;
69
69
  for (var i = 0u; i < steps; i++) {
@@ -73,34 +73,34 @@ let dt = next - previous;
73
73
  let p = origin + dir * (0.5 * (previous + next));
74
74
  previous = next;
75
75
  let pr = length(p);
76
- let d = oz(pr);
76
+ let d = oo(pr);
77
77
  let e = extinction(d);
78
- let vb = fO(pr, dot(p, params.sun.xyz) / pr);
79
- let nG = RAYLEIGH * d.x;
80
- let nF = MIE_SCATTERING * d.y;
78
+ let vk = fv(pr, dot(p, params.sun.xyz) / pr);
79
+ let np = RAYLEIGH * d.x;
80
+ let nl = MIE_SCATTERING * d.y;
81
81
  var ms = vec3f(0.0);
82
- if (full) { ms = fh(multipleTable, vec2f(hR(pr - R_GROUND) * f32(MS_ALTITUDES - 1u), 0.0)); }
83
- let source = vb * (nG * jv + nF * sn) + (nG + nF) * ms;
84
- let st = exp(-e * dt);
85
- let weight = (1.0 - st) / max(e, vec3f(1e-9));
82
+ if (full) { ms = eR(multipleTable, vec2f(hy(pr - R_GROUND) * f32(MS_ALTITUDES - 1u), 0.0)); }
83
+ let source = vk * (np * ja + nl * sh) + (np + nl) * ms;
84
+ let segment = exp(-e * dt);
85
+ let weight = (1.0 - segment) / max(e, vec3f(1e-9));
86
86
  result.radiance += view * source * weight;
87
- result.throughput += view * (nG + nF) * weight;
88
- view *= st;
87
+ result.throughput += view * (np + nl) * weight;
88
+ view *= segment;
89
89
  }
90
90
  if (ground) {
91
91
  let p = origin + dir * len;
92
92
  let pr = length(p);
93
- let tC = dot(p, params.sun.xyz) / pr;
94
- let irradiance = fO(pr, tC) * max(tC, 0.0) + uw;
93
+ let tD = dot(p, params.sun.xyz) / pr;
94
+ let irradiance = fv(pr, tD) * max(tD, 0.0) + uD;
95
95
  result.radiance += view * ALBEDO / PI * irradiance;
96
96
  }
97
97
  return result;
98
98
  }
99
- fn qk(row: f32) -> f32 {
99
+ fn qd(row: f32) -> f32 {
100
100
  let x = row / f32(SV_SIZE.y - 1u) * 2.0 - 1.0;
101
101
  return sign(x) * x * x * (PI / 2.0);
102
102
  }
103
- fn ik(e: f32) -> f32 {
103
+ fn hL(e: f32) -> f32 {
104
104
  let x = sign(e) * sqrt(abs(e) / (PI / 2.0));
105
105
  return clamp((x + 1.0) * 0.5, 0.0, 1.0) * f32(SV_SIZE.y - 1u);
106
106
  }
@@ -108,40 +108,40 @@ return clamp((x + 1.0) * 0.5, 0.0, 1.0) * f32(SV_SIZE.y - 1u);
108
108
  fn transmittance(@builtin(global_invocation_id) id: vec3u) {
109
109
  if (any(id.xy >= T_SIZE)) { return; }
110
110
  let mu = f32(id.x) / f32(T_SIZE.x - 1u) * 2.0 - 1.0;
111
- let r = R_GROUND + mP(f32(id.y) / f32(T_SIZE.y - 1u));
112
- if (np(r, mu)) { textureStore(output, vec2i(id.xy), vec4f(0.0, 0.0, 0.0, 1.0)); return; }
113
- let len = jm(r, mu);
111
+ let r = R_GROUND + mB(f32(id.y) / f32(T_SIZE.y - 1u));
112
+ if (mV(r, mu)) { textureStore(output, vec2i(id.xy), vec4f(0.0, 0.0, 0.0, 1.0)); return; }
113
+ let len = iS(r, mu);
114
114
  var depth = vec3f(0.0);
115
115
  for (var i = 0u; i < 48u; i++) {
116
116
  let t = (f32(i) + 0.5) / 48.0 * len;
117
- depth += extinction(oz(sqrt(t * t + 2.0 * r * mu * t + r * r))) * (len / 48.0);
117
+ depth += extinction(oo(sqrt(t * t + 2.0 * r * mu * t + r * r))) * (len / 48.0);
118
118
  }
119
119
  textureStore(output, vec2i(id.xy), vec4f(exp(-depth), 1.0));
120
120
  }
121
121
  @compute @workgroup_size(8, 8)
122
122
  fn multiple(@builtin(global_invocation_id) id: vec3u) {
123
123
  if (id.x >= MS_ALTITUDES || id.y > 0u) { return; }
124
- let r = R_GROUND + mP(f32(id.x) / f32(MS_ALTITUDES - 1u));
124
+ let r = R_GROUND + mB(f32(id.x) / f32(MS_ALTITUDES - 1u));
125
125
  var second = vec3f(0.0);
126
- var qK = vec3f(0.0);
126
+ var qC = vec3f(0.0);
127
127
  for (var i = 0u; i < 64u; i++) {
128
128
  let mu = 1.0 - 2.0 * (f32(i) + 0.5) / 64.0;
129
129
  let phi = f32(i) * PI * (3.0 - sqrt(5.0));
130
130
  let s = sqrt(1.0 - mu * mu);
131
- let result = vt(vec3f(0.0, r, 0.0), vec3f(s * cos(phi), mu, s * sin(phi)), 20u, false, vec3f(0.0));
131
+ let result = vG(vec3f(0.0, r, 0.0), vec3f(s * cos(phi), mu, s * sin(phi)), 20u, false, vec3f(0.0));
132
132
  second += result.radiance / 64.0;
133
- qK += result.throughput / 64.0;
133
+ qC += result.throughput / 64.0;
134
134
  }
135
- textureStore(output, vec2i(id.xy), vec4f(second / (1.0 - qK), 1.0));
135
+ textureStore(output, vec2i(id.xy), vec4f(second / (1.0 - qC), 1.0));
136
136
  }
137
137
  @compute @workgroup_size(8, 8)
138
138
  fn skyView(@builtin(global_invocation_id) id: vec3u) {
139
139
  if (any(id.xy >= SV_SIZE)) { return; }
140
- let e = qk(f32(id.y));
140
+ let e = qd(f32(id.y));
141
141
  let phi = f32(id.x) / f32(SV_SIZE.x - 1u) * PI;
142
142
  var sky = vec3f(0.0);
143
143
  if (params.size.w > 0.0) { sky = textureLoad(irradianceTable, vec2i(0), 0).rgb; }
144
- let result = vt(vec3f(0.0, R_GROUND + CAMERA_ALTITUDE, 0.0), vec3f(cos(e) * cos(phi), sin(e), cos(e) * sin(phi)), 40u, true, sky);
144
+ let result = vG(vec3f(0.0, R_GROUND + CAMERA_ALTITUDE, 0.0), vec3f(cos(e) * cos(phi), sin(e), cos(e) * sin(phi)), 40u, true, sky);
145
145
  textureStore(output, vec2i(id.xy), vec4f(result.radiance, 1.0));
146
146
  }
147
147
  @compute @workgroup_size(8, 8)
@@ -153,44 +153,44 @@ let e = (f32(i) + 0.5) / 128.0 * (PI / 2.0);
153
153
  let weight = 2.0 * sin(e) * cos(e) * (PI / 2.0 / 128.0) * (PI / 48.0);
154
154
  for (var j = 0u; j < 48u; j++) {
155
155
  let phi = (f32(j) + 0.5) / 48.0 * PI;
156
- sky += fh(skyTable, vec2f(phi / PI * f32(SV_SIZE.x - 1u), ik(e))) * weight;
156
+ sky += eR(skyTable, vec2f(phi / PI * f32(SV_SIZE.x - 1u), hL(e))) * weight;
157
157
  }
158
158
  }
159
159
  if (id.x == 0u) { textureStore(output, vec2i(id.xy), vec4f(sky, 1.0)); return; }
160
- let vb = fO(R_GROUND + CAMERA_ALTITUDE, params.sun.y);
161
- let xc = vec3f(0.2126, 0.7152, 0.0722);
162
- let horizontal = dot(sky, xc) + dot(vb, xc) * max(params.sun.y, 0.0);
160
+ let vk = fv(R_GROUND + CAMERA_ALTITUDE, params.sun.y);
161
+ let xE = vec3f(0.2126, 0.7152, 0.0722);
162
+ let horizontal = dot(sky, xE) + dot(vk, xE) * max(params.sun.y, 0.0);
163
163
  let scale = ${ILLUMINANCE} / sqrt(max(horizontal, ${REFERENCE_ILLUMINANCE} * 1e-3) * ${REFERENCE_ILLUMINANCE});
164
- textureStore(output, vec2i(id.xy), vec4f(vb, scale));
164
+ textureStore(output, vec2i(id.xy), vec4f(vk, scale));
165
165
  }
166
- fn hn(dk: vec2f) -> vec3f {
167
- let theta = dk.y / params.size.y * PI;
168
- let phi = dk.x / params.size.x * 2.0 * PI;
166
+ fn gU(cR: vec2f) -> vec3f {
167
+ let theta = cR.y / params.size.y * PI;
168
+ let phi = cR.x / params.size.x * 2.0 * PI;
169
169
  return vec3f(-sin(theta) * cos(phi), cos(theta), -sin(theta) * sin(phi));
170
170
  }
171
171
  @compute @workgroup_size(8, 8)
172
172
  fn map(@builtin(global_invocation_id) id: vec3u) {
173
173
  if (any(id.xy >= vec2u(params.size.xy))) { return; }
174
- let dk = vec2f(id.xy) + 0.5;
175
- let dir = hn(dk);
176
- let th = abs(atan2(dir.z, dir.x) - params.sun.w);
177
- let wr = th - floor(th / (2.0 * PI)) * (2.0 * PI);
178
- let ek = min(wr, 2.0 * PI - wr);
174
+ let cR = vec2f(id.xy) + 0.5;
175
+ let dir = gU(cR);
176
+ let tg = abs(atan2(dir.z, dir.x) - params.sun.w);
177
+ let wO = tg - floor(tg / (2.0 * PI)) * (2.0 * PI);
178
+ let dU = min(wO, 2.0 * PI - wO);
179
179
  let e = asin(clamp(dir.y, -1.0, 1.0));
180
- var radiance = fh(skyTable, vec2f(ek / PI * f32(SV_SIZE.x - 1u), ik(e)));
180
+ var radiance = eR(skyTable, vec2f(dU / PI * f32(SV_SIZE.x - 1u), hL(e)));
181
181
  let sun = textureLoad(irradianceTable, vec2i(1, 0), 0);
182
182
  let radius = params.sunWorld.w;
183
- let rP = PI / params.size.y;
184
- if (acos(clamp(dot(dir, params.sunWorld.xyz), -1.0, 1.0)) < radius + 3.0 * rP / max(sqrt(1.0 - dir.y * dir.y), 0.05)) {
183
+ let rM = PI / params.size.y;
184
+ if (acos(clamp(dot(dir, params.sunWorld.xyz), -1.0, 1.0)) < radius + 3.0 * rM / max(sqrt(1.0 - dir.y * dir.y), 0.05)) {
185
185
  var coverage = 0.0;
186
186
  for (var y = 0u; y < 6u; y++) { for (var x = 0u; x < 6u; x++) {
187
- let d = hn(dk - 0.5 + (vec2f(f32(x), f32(y)) + 0.5) / 6.0);
187
+ let d = gU(cR - 0.5 + (vec2f(f32(x), f32(y)) + 0.5) / 6.0);
188
188
  let angle = 2.0 * asin(min(length(d - params.sunWorld.xyz) * 0.5, 1.0));
189
189
  let t = angle / radius;
190
190
  if (t < 1.0) { coverage += 1.0 - 0.6 * (1.0 - sqrt(1.0 - t * t)); }
191
191
  } }
192
- let rO = 2.0 * PI * (1.0 - cos(radius));
193
- radiance += coverage / 36.0 / (rO * (1.0 - 0.6 / 3.0)) * sun.rgb;
192
+ let rK = 2.0 * PI * (1.0 - cos(radius));
193
+ radiance += coverage / 36.0 / (rK * (1.0 - 0.6 / 3.0)) * sun.rgb;
194
194
  }
195
195
  textureStore(output, vec2i(id.xy), vec4f(radiance * sun.a * params.scale.xyz + params.ambient.xyz, 1.0));
196
196
  }
@@ -10,6 +10,14 @@ export interface RayTraceInputHooks {
10
10
  }
11
11
  export interface RayTraceShaderOptions {
12
12
  hasDisplacement: boolean;
13
+ debugGroup?: number;
14
+ /** Invocation block edge; the native host matches dispatch to this value. Default 8. */
15
+ workgroupSize?: number;
16
+ lightTracing?: boolean;
17
+ photonMapping?: boolean;
18
+ photonMaxDepth?: number;
19
+ /** Internal A/B override for directly visible PM receivers. Default true; ignored with light tracing. */
20
+ photonDirectSubset?: boolean;
13
21
  /** Include direction frames only when used, preserving the legacy shader arithmetic otherwise. */
14
22
  hasAnisotropyDirections?: boolean;
15
23
  /** Include independent coat normals only when a material uses that slot. */
@@ -29,4 +37,4 @@ export interface RayTraceShaderOptions {
29
37
  lightCapabilities?: number;
30
38
  }
31
39
  /** Generates the WebGPU megakernel path tracing compute shader. */
32
- export default function rayTraceCompute({ hasDisplacement, hasAnisotropyDirections, hasCoatNormals, hasAlpha, hasSceneAlpha, blasLayoutMask, spectral, sceneCapabilities, dynamicEnvironment, materialCapabilities, lightCapabilities }: RayTraceShaderOptions, sceneIntersections?: string, pathDiagnostic?: boolean, inputHooks?: RayTraceInputHooks): string;
40
+ export default function rayTraceCompute(options: RayTraceShaderOptions, sceneIntersections?: string, pathDiagnostic?: boolean, inputHooks?: RayTraceInputHooks): string;