pelican-engine 0.0.0-stage → 0.3.1

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 (192) hide show
  1. package/AGENTS.md +147 -0
  2. package/LICENSE +22 -0
  3. package/README.md +129 -2
  4. package/distribution.json +197 -0
  5. package/docs/AI-WORKFLOW.md +517 -0
  6. package/docs/ARCHITECTURE.md +268 -0
  7. package/docs/CAPABILITIES.md +264 -0
  8. package/docs/DECISIONS.md +107 -0
  9. package/docs/M3-USAGE.md +192 -0
  10. package/docs/PELICAN-MIGRATION.md +38 -0
  11. package/docs/RENDER-BACKENDS.md +50 -0
  12. package/docs/ROADMAP.md +239 -0
  13. package/docs/THIRD-PARTY-NOTICES.md +151 -0
  14. package/docs/api/REFERENCE.md +1435 -0
  15. package/docs/api/index.json +1139 -0
  16. package/docs/npm/DISTRIBUTION.md +130 -0
  17. package/docs/npm/GAME-AUTHOR.md +147 -0
  18. package/engine.manifest.json +15 -0
  19. package/lib/assets/audio.js +67 -0
  20. package/lib/assets/errors.js +36 -0
  21. package/lib/assets/glb.js +277 -0
  22. package/lib/assets/index.js +53 -0
  23. package/lib/assets/jpeg.js +199 -0
  24. package/lib/assets/model.js +558 -0
  25. package/lib/assets/optimize.js +95 -0
  26. package/lib/assets/texture.js +187 -0
  27. package/lib/compiler/distribution.js +17 -0
  28. package/lib/compiler/extract.js +425 -0
  29. package/lib/compiler/graph.js +366 -0
  30. package/lib/compiler/index.js +574 -0
  31. package/lib/compiler/normalize.js +1194 -0
  32. package/lib/compiler/source-map.js +25 -0
  33. package/lib/compiler/types.js +2 -0
  34. package/lib/compiler/version.js +93 -0
  35. package/lib/experiments/simulation/index.js +2 -0
  36. package/lib/experiments/simulation/math.js +41 -0
  37. package/lib/experiments/simulation/probe.js +215 -0
  38. package/lib/experiments/simulation/scenes.js +192 -0
  39. package/lib/experiments/simulation/solver.js +346 -0
  40. package/lib/experiments/simulation/types.js +1 -0
  41. package/lib/experiments/world/adapters.js +30 -0
  42. package/lib/experiments/world/geometry.js +180 -0
  43. package/lib/experiments/world/index.js +2 -0
  44. package/lib/experiments/world/probe.js +288 -0
  45. package/lib/experiments/world/types.js +1 -0
  46. package/lib/experiments/world/world.js +599 -0
  47. package/lib/navigation/index.js +157 -0
  48. package/lib/physics/index.js +909 -0
  49. package/lib/protocol/index.js +92 -0
  50. package/lib/renderer/acceleration.js +609 -0
  51. package/lib/renderer/browser.js +2015 -0
  52. package/lib/renderer/bsdf.js +260 -0
  53. package/lib/renderer/checkpoint.js +88 -0
  54. package/lib/renderer/compute-executor.js +1034 -0
  55. package/lib/renderer/compute-texture-assets.js +49 -0
  56. package/lib/renderer/compute-view-contract.js +1 -0
  57. package/lib/renderer/denoiser.js +361 -0
  58. package/lib/renderer/dielectric.js +360 -0
  59. package/lib/renderer/embedded-image-pixels.js +368 -0
  60. package/lib/renderer/emissive-lights.js +67 -0
  61. package/lib/renderer/environment-importance.js +55 -0
  62. package/lib/renderer/environment.js +346 -0
  63. package/lib/renderer/gameplay.js +464 -0
  64. package/lib/renderer/geometry.js +103 -0
  65. package/lib/renderer/graph-expressions.js +485 -0
  66. package/lib/renderer/graph-tsl.js +141 -0
  67. package/lib/renderer/graph.js +214 -0
  68. package/lib/renderer/managed-texture-sampling.js +29 -0
  69. package/lib/renderer/material-graphs.js +297 -0
  70. package/lib/renderer/medium.js +348 -0
  71. package/lib/renderer/models.js +1053 -0
  72. package/lib/renderer/motion-shader.js +40 -0
  73. package/lib/renderer/path-shader.js +196 -0
  74. package/lib/renderer/pathtrace.js +324 -0
  75. package/lib/renderer/postprocess-contract.js +1 -0
  76. package/lib/renderer/postprocess.js +494 -0
  77. package/lib/renderer/render-buffers.js +132 -0
  78. package/lib/renderer/render-textures.js +155 -0
  79. package/lib/renderer/startup.js +50 -0
  80. package/lib/renderer/surface-capture.js +199 -0
  81. package/lib/renderer/surface-gpu.js +270 -0
  82. package/lib/renderer/surface-motion.js +84 -0
  83. package/lib/renderer/surface-ray-shader.js +91 -0
  84. package/lib/renderer/surface-shader.js +88 -0
  85. package/lib/renderer/surface-tracer.js +1720 -0
  86. package/lib/renderer/surface.js +1425 -0
  87. package/lib/renderer/temporal-filter.js +512 -0
  88. package/lib/renderer/texture-pixels.js +207 -0
  89. package/lib/renderer/traversal-nodes.js +54 -0
  90. package/lib/renderer/ui.js +446 -0
  91. package/lib/renderer/vertex-bounds.js +127 -0
  92. package/lib/renderer/vertex-contract.js +1 -0
  93. package/lib/renderer/vertex-graphs.js +197 -0
  94. package/lib/renderer/volume-shader.js +50 -0
  95. package/lib/runtime/compute-contract.js +1 -0
  96. package/lib/runtime/compute-service.js +674 -0
  97. package/lib/runtime/contracts.js +1 -0
  98. package/lib/runtime/error.js +37 -0
  99. package/lib/runtime/events.js +189 -0
  100. package/lib/runtime/game-services.js +772 -0
  101. package/lib/runtime/host.js +839 -0
  102. package/lib/runtime/material-parameters.js +145 -0
  103. package/lib/runtime/plugins.js +1 -0
  104. package/lib/runtime/postprocess-contract.js +1 -0
  105. package/lib/runtime/postprocess.js +189 -0
  106. package/lib/runtime/prefabs.js +13 -0
  107. package/lib/runtime/random.js +88 -0
  108. package/lib/runtime/render-buffer-contract.js +1 -0
  109. package/lib/runtime/render-buffers.js +243 -0
  110. package/lib/runtime/render-capabilities.js +17 -0
  111. package/lib/runtime/render-settings.js +249 -0
  112. package/lib/runtime/render-texture-contract.js +1 -0
  113. package/lib/runtime/render-textures.js +271 -0
  114. package/lib/runtime/resources.js +648 -0
  115. package/lib/runtime/scheduler.js +765 -0
  116. package/lib/runtime/storage.js +169 -0
  117. package/lib/runtime/world.js +594 -0
  118. package/lib/schema/index.js +24 -0
  119. package/lib/sdk/graph-values.js +16 -0
  120. package/lib/sdk/index.d.ts +800 -0
  121. package/lib/sdk/index.js +82 -0
  122. package/lib/tools/api-index.js +113 -0
  123. package/lib/tools/browser-session.js +89 -0
  124. package/lib/tools/cli.js +513 -0
  125. package/lib/tools/compress-dist.js +59 -0
  126. package/lib/tools/control-client.js +153 -0
  127. package/lib/tools/control-server.js +633 -0
  128. package/lib/tools/diagnostic-code.js +3 -0
  129. package/lib/tools/doctor.js +496 -0
  130. package/lib/tools/npm/browser-cli.js +6 -0
  131. package/lib/tools/npm/entry.js +40 -0
  132. package/lib/tools/npm/layout.js +195 -0
  133. package/lib/tools/setup.js +153 -0
  134. package/licenses/@dimforge/rapier2d-compat/LICENSE +201 -0
  135. package/licenses/@dimforge/rapier3d-compat/LICENSE +201 -0
  136. package/licenses/@gltf-transform/core/LICENSE.md +21 -0
  137. package/licenses/@gltf-transform/extensions/LICENSE.md +21 -0
  138. package/licenses/@gltf-transform/functions/LICENSE.md +21 -0
  139. package/licenses/@img/colour/LICENSE.md +82 -0
  140. package/licenses/@img/sharp-win32-x64/LICENSE +191 -0
  141. package/licenses/@oxc-project/types/LICENSE +22 -0
  142. package/licenses/@rolldown/pluginutils/LICENSE +21 -0
  143. package/licenses/@tweenjs/tween.js/LICENSE +23 -0
  144. package/licenses/@types/ndarray/LICENSE +21 -0
  145. package/licenses/@types/node/LICENSE +21 -0
  146. package/licenses/@types/stats.js/LICENSE +21 -0
  147. package/licenses/@types/three/@dimforge/rapier3d-compat/LICENSE +201 -0
  148. package/licenses/@types/three/LICENSE +21 -0
  149. package/licenses/@types/webxr/LICENSE +21 -0
  150. package/licenses/@webgpu/types/LICENSE +26 -0
  151. package/licenses/THIRD-PARTY-NOTICES.md +139 -0
  152. package/licenses/cwise-compiler/LICENSE +22 -0
  153. package/licenses/detect-libc/LICENSE +201 -0
  154. package/licenses/esbuild/LICENSE.md +21 -0
  155. package/licenses/fast-png/LICENSE +21 -0
  156. package/licenses/fdir/LICENSE +7 -0
  157. package/licenses/fflate/LICENSE +21 -0
  158. package/licenses/iobuffer/LICENSE +22 -0
  159. package/licenses/iota-array/LICENSE +22 -0
  160. package/licenses/is-buffer/LICENSE +21 -0
  161. package/licenses/ktx-parse/LICENSE +21 -0
  162. package/licenses/lightningcss/LICENSE +373 -0
  163. package/licenses/lightningcss-win32-x64-msvc/LICENSE +373 -0
  164. package/licenses/meshoptimizer/LICENSE.md +21 -0
  165. package/licenses/nanoid/LICENSE +20 -0
  166. package/licenses/ndarray/LICENSE +22 -0
  167. package/licenses/ndarray-lanczos/LICENSE +21 -0
  168. package/licenses/ndarray-ops/LICENSE +22 -0
  169. package/licenses/ndarray-pixels/LICENSE +21 -0
  170. package/licenses/ngraph.path/LICENSE +21 -0
  171. package/licenses/picocolors/LICENSE +15 -0
  172. package/licenses/picomatch/LICENSE +21 -0
  173. package/licenses/playwright/LICENSE +202 -0
  174. package/licenses/playwright/NOTICE +5 -0
  175. package/licenses/playwright-core/LICENSE +202 -0
  176. package/licenses/playwright-core/NOTICE +5 -0
  177. package/licenses/postcss/LICENSE +20 -0
  178. package/licenses/prettier/LICENSE +7 -0
  179. package/licenses/property-graph/LICENSE +21 -0
  180. package/licenses/rolldown/LICENSE +25 -0
  181. package/licenses/semver/LICENSE +15 -0
  182. package/licenses/sharp/LICENSE +191 -0
  183. package/licenses/source-map-js/LICENSE +28 -0
  184. package/licenses/three/LICENSE +21 -0
  185. package/licenses/tinyglobby/LICENSE +21 -0
  186. package/licenses/tsx/LICENSE +21 -0
  187. package/licenses/typescript/LICENSE.txt +55 -0
  188. package/licenses/undici-types/LICENSE +21 -0
  189. package/licenses/uniq/LICENSE +22 -0
  190. package/licenses/vite/LICENSE.md +2084 -0
  191. package/package.json +49 -4
  192. package/web/index.html +34 -0
@@ -0,0 +1,40 @@
1
+ import { TRACE_PARAMETERS_SHADER, TRACE_RANDOM_SHADER } from "./path-shader.js";
2
+ /** Same primary visibility/sample sequence as transport; writes a separate bounded plane. */
3
+ export const MOTION_SHADER = TRACE_PARAMETERS_SHADER +
4
+ TRACE_RANDOM_SHADER +
5
+ /* wgsl */ String.raw `
6
+ @group(0) @binding(6) var<storage,read_write> motionOutput:array<vec4f>;
7
+ @group(0) @binding(7) var<uniform> motionParams:TraceParameters;
8
+ @group(0) @binding(8) var motionPrimary:texture_2d<f32>;
9
+ @compute @workgroup_size(8,8)
10
+ fn motionMain(@builtin(global_invocation_id) invocation:vec3u){
11
+ let width=u32(motionParams.screen.x);let height=u32(motionParams.screen.y);
12
+ if(invocation.x>=width||invocation.y>=height){return;}
13
+ let index=invocation.y*width+invocation.x;motionOutput[index]=vec4f(0);
14
+ if(motionParams.previousOrigin.w<0.5){return;}
15
+ var seed=index*1973u+u32(motionParams.screen.z)*9277u+89173u;
16
+ var jitter=vec2f(traceRandom(&seed),traceRandom(&seed));
17
+ if(motionParams.flags.x>0.5){jitter=vec2f(0.5+motionParams.forward.w,0.5+motionParams.right.w);}
18
+ let pixel=((vec2f(invocation.xy)+jitter)/vec2f(f32(width),f32(height)))*2.0-vec2f(1);
19
+ let direction=normalize(motionParams.forward.xyz+motionParams.right.xyz*pixel.x-motionParams.up.xyz*pixel.y);
20
+ let cosine=max(dot(direction,motionParams.forward.xyz),1e-5);
21
+ let origin=motionParams.origin.xyz+direction*(motionParams.screen.w/cosine);
22
+ var hit:SurfaceHit;
23
+ if(motionParams.flags.x>0.5){
24
+ let primary=textureLoad(motionPrimary,vec2i(invocation.xy),0);
25
+ if(primary.y< -1.5){hit=traceSurface(origin,direction,(motionParams.settings.w-motionParams.screen.w)/cosine,u32(motionParams.counts.x),u32(motionParams.counts.y),traceRandom(&seed));}
26
+ else{hit=SurfaceHit(abs(primary.x),i32(primary.y),vec3f(1.0-primary.z-primary.w,primary.z,primary.w),primary.x>=0.0);}
27
+ }else{
28
+ hit=traceSurface(origin,direction,(motionParams.settings.w-motionParams.screen.w)/cosine,u32(motionParams.counts.x),u32(motionParams.counts.y),traceRandom(&seed));
29
+ }
30
+ if(hit.index<0){return;}
31
+ let offset=u32(motionParams.counts.y)*18u+u32(hit.index)*3u;
32
+ let a=surfaceGeometry[offset];let b=surfaceGeometry[offset+1u];let c=surfaceGeometry[offset+2u];
33
+ if(a.w<0.5){return;}
34
+ let previous=a.xyz*hit.bary.x+b.xyz*hit.bary.y+c.xyz*hit.bary.z;
35
+ let clip=mat4x4f(motionParams.previousView0,motionParams.previousView1,motionParams.previousView2,motionParams.previousView3)*vec4f(previous,1);
36
+ if(clip.w<=0.0){return;}
37
+ let uv=clip.xy/clip.w*vec2f(0.5,-0.5)+vec2f(0.5);
38
+ motionOutput[index]=vec4f(uv,length(previous-motionParams.previousOrigin.xyz),1);
39
+ }
40
+ `;
@@ -0,0 +1,196 @@
1
+ /** Shared path and raster-visibility hybrid integrator. Core surface/BVH/BSDF are concatenated before this module. */
2
+ export const TRACE_PARAMETER_FLOATS = 92;
3
+ export const TRACE_PARAMETER_BYTES = TRACE_PARAMETER_FLOATS * 4;
4
+ export const TRACE_PARAMETERS_SHADER = /* wgsl */ String.raw `
5
+ struct TraceParameters{
6
+ origin:vec4f,forward:vec4f,right:vec4f,up:vec4f,background:vec4f,
7
+ screen:vec4f,counts:vec4f,settings:vec4f,flags:vec4f,
8
+ view0:vec4f,view1:vec4f,view2:vec4f,view3:vec4f,
9
+ environment:vec4f,environment0:vec4f,environment1:vec4f,environment2:vec4f,
10
+ medium:vec4f,
11
+ previousView0:vec4f,previousView1:vec4f,previousView2:vec4f,previousView3:vec4f,previousOrigin:vec4f,
12
+ }
13
+ `;
14
+ export const TRACE_RANDOM_SHADER = /* wgsl */ String.raw `
15
+ fn traceRandom(seed:ptr<function,u32>)->f32{*seed=(*seed)*747796405u+2891336453u;let shift=((*seed)>>28u)+4u;let word=(((*seed)>>shift)^(*seed))*277803737u;return min(f32((word>>22u)^word)/4294967296.0,0.99999994);}
16
+ `;
17
+ export const PATH_SHADER = TRACE_PARAMETERS_SHADER +
18
+ TRACE_RANDOM_SHADER +
19
+ /* wgsl */ String.raw `
20
+ struct TraceLight{position:vec4f,color:vec4f,extra:vec4f,}
21
+ @group(0) @binding(5) var<storage,read> traceLights:array<TraceLight>;
22
+ struct TraceOutput{faults:atomic<u32>,faultMask:atomic<u32>,faultDetail:atomic<u32>,faultPixel:atomic<u32>,pixels:array<vec4f>,}
23
+ @group(0) @binding(6) var<storage,read_write> traceOutput:TraceOutput;
24
+ @group(0) @binding(7) var<uniform> traceParams:TraceParameters;
25
+ @group(0) @binding(8) var primaryHits:texture_2d<f32>;
26
+ @group(0) @binding(9) var<storage,read> environmentImportance:array<vec4f>;
27
+ override traceVolumeEnabled:bool=false;
28
+ override traceCaptureFault:bool=false;
29
+ fn traceMediumIor(id:u32)->f32{if(id==0u){return 1.0;}return traceLights[u32(traceParams.medium.x)+id-1u].position.x;}
30
+ fn traceMediumSigma(id:u32)->vec3f{if(id==0u){return vec3f(0);}return traceLights[u32(traceParams.medium.x)+id-1u].position.yzw;}
31
+ fn traceShadowBeer(media:TraceShadowMedia,geometric:vec3f,direction:vec3f,distance:f32)->vec3f{
32
+ if(!traceVolumeEnabled){return vec3f(1);}return exp(-select(media.incident,media.transmitted,dot(geometric,direction)<0.0)*distance);
33
+ }
34
+ fn traceEnvironmentBase(direction:vec3f)->vec3f{
35
+ var result=traceParams.background.rgb;
36
+ if(traceParams.environment.x>=0.0){let local=normalize(mat3x3f(traceParams.environment0.xyz,traceParams.environment1.xyz,traceParams.environment2.xyz)*direction);let uv=vec2f((atan2(local.z,local.x)+3.141592653589793)/6.283185307179586,acos(clamp(local.y,-1.0,1.0))/3.141592653589793);result=surfaceMap(traceParams.environment.x,vec4f(uv,0,0)).rgb*traceParams.environment.y;}
37
+ return result;
38
+ }
39
+ fn traceEnvironmentAmbient(direction:vec3f)->vec3f{
40
+ var result=vec3f(0);for(var index=0u;index<u32(traceParams.counts.z);index++){let light=traceLights[index];
41
+ if(light.position.w==2.0){let weight=dot(direction,normalize(light.position.xyz))*0.5+0.5;result+=mix(light.extra.rgb,light.color.rgb,weight)/3.141592653589793;}
42
+ else if(light.position.w==3.0){result+=light.color.rgb/3.141592653589793;}
43
+ }return result;
44
+ }
45
+ fn tracePower(a:f32,b:f32)->f32{return a*a/max(a*a+b*b,1e-30);}
46
+ fn traceEnvironmentPdf(direction:vec3f)->f32{
47
+ if(traceParams.environment.x<0.0){return 0.0;}let local=normalize(mat3x3f(traceParams.environment0.xyz,traceParams.environment1.xyz,traceParams.environment2.xyz)*direction);let uv=vec2f((atan2(local.z,local.x)+3.141592653589793)/6.283185307179586,acos(clamp(local.y,-1.0,1.0))/3.141592653589793);let column=min(u32(uv.x*traceParams.environment.z),u32(traceParams.environment.z)-1u);let row=min(u32(uv.y*traceParams.environment.w),u32(traceParams.environment.w)-1u);let cell=environmentImportance[row*u32(traceParams.environment.z)+column];return cell.x/(cell.w*max(traceParams.origin.w,1.0));
48
+ }
49
+ fn traceEnvironmentDirect(point:TracePoint,geometric:vec3f,surface:SurfaceValue,view:vec3f,shadowMedia:TraceShadowMedia,continuation:bool,seed:ptr<function,u32>)->vec3f{
50
+ let position=point.position;
51
+ if(traceParams.environment.x<0.0){return vec3f(0);}let count=u32(traceParams.environment.z*traceParams.environment.w);let selection=traceRandom(seed)*f32(count);var selected=u32(selection);let proposal=environmentImportance[selected];if(fract(selection)>=proposal.z){selected=u32(proposal.y);}let cell=environmentImportance[selected];let row=selected/u32(traceParams.environment.z);let column=selected%u32(traceParams.environment.z);let phi=((f32(column)+traceRandom(seed))/traceParams.environment.z)*6.283185307179586-3.141592653589793;let cosine=mix(cos(f32(row)*3.141592653589793/traceParams.environment.w),cos(f32(row+1u)*3.141592653589793/traceParams.environment.w),traceRandom(seed));let sine=sqrt(max(0.0,1.0-cosine*cosine));let local=vec3f(sine*cos(phi),cosine,sine*sin(phi));let direction=transpose(mat3x3f(traceParams.environment0.xyz,traceParams.environment1.xyz,traceParams.environment2.xyz))*local;let noL=traceCosine(surface,direction);if(noL<=0.0||!traceValidDirection(geometric,surface,direction)){return vec3f(0);}let hit=occludedSurface(traceOffset(point,geometric,direction),direction,1e20,u32(traceParams.counts.x),u32(traceParams.counts.y),traceRandom(seed));if(hit){return vec3f(0);}let pdf=cell.x/(cell.w*max(traceParams.origin.w,1.0));if(pdf<=0.0){return vec3f(0);}let bsdf=traceEvaluate(surface,view,direction);let brdfPdf=tracePdf(surface,view,direction);return traceShadowBeer(shadowMedia,geometric,direction,1e20)*bsdf*traceEnvironmentBase(direction)*(noL*select(1.0,tracePower(pdf,brdfPdf),continuation)/pdf);
52
+ }
53
+ fn traceEmitterPdf(index:u32,origin:vec3f,position:vec3f)->f32{
54
+ for(var lightIndex=0u;lightIndex<u32(traceParams.counts.z);lightIndex++){
55
+ let light=traceLights[lightIndex];if(light.position.w!=4.0||u32(light.position.x)!=index){continue;}
56
+ let triangle=surfaceTriangle(index);let normal=normalize(cross(triangle.b.xyz-triangle.a.xyz,triangle.c.xyz-triangle.a.xyz));let delta=position-origin;let distance2=dot(delta,delta);let cosine=abs(dot(normal,-normalize(delta)));return light.position.z*distance2/max(light.position.y*cosine*max(traceParams.origin.w,1.0),1e-20);
57
+ }return 0.0;
58
+ }
59
+ fn traceEmitterDirect(point:TracePoint,geometric:vec3f,surface:SurfaceValue,view:vec3f,shadowMedia:TraceShadowMedia,continuation:bool,seed:ptr<function,u32>)->vec3f{
60
+ let position=point.position;
61
+ let selection=traceRandom(seed);var selected:i32=-1;
62
+ for(var lightIndex=0u;lightIndex<u32(traceParams.counts.z);lightIndex++){let light=traceLights[lightIndex];if(light.position.w==4.0&&selection<=light.extra.x){selected=i32(lightIndex);break;}}
63
+ if(selected<0){return vec3f(0);}let light=traceLights[u32(selected)];let index=u32(light.position.x);let triangle=surfaceTriangle(index);let root=sqrt(traceRandom(seed));let bary=vec3f(1.0-root,root*(1.0-traceRandom(seed)),0);let weights=vec3f(bary.xy,1.0-bary.x-bary.y);let emitterPoint=triangle.a.xyz*weights.x+triangle.b.xyz*weights.y+triangle.c.xyz*weights.z;
64
+ let delta=emitterPoint-position;let distance=length(delta);if(distance<=0.002){return vec3f(0);}let direction=delta/distance;let geometricCosine=dot(geometric,direction);let cosine=traceCosine(surface,direction);if(cosine<=0.0||!traceValidDirection(geometric,surface,direction)){return vec3f(0);}
65
+ let emitNormal=normalize(cross(triangle.b.xyz-triangle.a.xyz,triangle.c.xyz-triangle.a.xyz));let emitCosine=abs(dot(emitNormal,-direction));if(emitCosine<1e-6){return vec3f(0);}let front=surfaceOriginalFront(triangle,dot(emitNormal,-direction)>0.0);let emission=evaluateSurfaceView(index,weights,front,-direction);if(emission.discarded!=0u){return vec3f(0);}
66
+ let visibility=occludedSurface(traceOffset(point,geometric,direction),direction,distance-.002,u32(traceParams.counts.x),u32(traceParams.counts.y),traceRandom(seed));if(visibility){return vec3f(0);}
67
+ let pdf=light.position.z*distance*distance/max(light.position.y*emitCosine*max(traceParams.origin.w,1.0),1e-20);let brdfPdf=tracePdf(surface,view,direction);let weight=select(1.0,tracePower(pdf,brdfPdf),continuation);
68
+ return traceShadowBeer(shadowMedia,geometric,direction,distance)*traceEvaluate(surface,view,direction)*emission.emissive*(cosine*weight*select(1.0,emission.alpha,surfaceMaterials[u32(triangle.a.w)].normal.w==2.0)/pdf);
69
+ }
70
+ fn traceDirect(point:TracePoint,geometric:vec3f,surface:SurfaceValue,view:vec3f,shadowMedia:TraceShadowMedia,continuation:bool,seed:ptr<function,u32>)->vec3f{
71
+ let position=point.position;
72
+ let count=u32(traceParams.origin.w);if(count==0u){return vec3f(0);}let selected=min(u32(traceRandom(seed)*f32(count)),count-1u);var current=0u;
73
+ for(var index=0u;index<u32(traceParams.counts.z);index++){
74
+ let light=traceLights[index];if(light.position.w>=2.0){continue;}if(current!=selected){current++;continue;}
75
+ var delta=light.position.xyz;var distance=1e20;var attenuation=1.0;
76
+ if(light.position.w==0.0){delta-=position;distance=length(delta);if(distance<=0.0001){return vec3f(0);}attenuation=1.0/pow(max(distance,0.01),light.extra.w);if(light.color.w>0.0){let cutoff=clamp(1.0-pow(distance/light.color.w,4.0),0.0,1.0);attenuation*=cutoff*cutoff;}}
77
+ let direction=normalize(delta);let cosine=traceCosine(surface,direction);if(cosine==0.0||!traceValidDirection(geometric,surface,direction)){return vec3f(0);}
78
+ if(occludedSurface(traceOffset(point,geometric,direction),direction,distance-.002,u32(traceParams.counts.x),u32(traceParams.counts.y),traceRandom(seed))){return vec3f(0);}
79
+ return traceShadowBeer(shadowMedia,geometric,direction,distance)*traceEvaluate(surface,view,direction)*light.color.rgb*(cosine*attenuation*f32(count));
80
+ }
81
+ if(traceParams.up.w>0.5){if(selected==current){return traceEmitterDirect(point,geometric,surface,view,shadowMedia,continuation,seed);}current++;}
82
+ if(traceParams.environment.x>=0.0&&selected==current){return traceEnvironmentDirect(point,geometric,surface,view,shadowMedia,continuation,seed);}return vec3f(0);
83
+ }
84
+ @compute @workgroup_size(8,8)
85
+ fn pathMain(@builtin(global_invocation_id) invocation:vec3u){
86
+ let width=u32(traceParams.screen.x);let height=u32(traceParams.screen.y);if(invocation.x>=width||invocation.y>=height){return;}
87
+ let index=invocation.y*width+invocation.x;let pixelCount=width*height;
88
+ var seed=index*1973u+u32(traceParams.screen.z)*9277u+89173u;
89
+ var radianceSum=vec3f(0);var firstNormal=vec4f(0,0,0,-1);var firstAlbedo=vec4f(0);
90
+ for(var sample=0u;sample<u32(traceParams.settings.y);sample++){
91
+ let targeted=traceCaptureFault&&traceVolumeEnabled&&traceParams.environment0.w>=0.0&&index==u32(traceParams.environment0.w)&&f32(sample)==traceParams.environment1.w;
92
+ if(targeted){traceOutput.pixels[pixelCount*4u+5u]=vec4f(0);traceOutput.pixels[pixelCount*4u+12u]=vec4f(f32(index),traceParams.screen.z,0,f32(sample));}
93
+ let hybrid=traceParams.flags.x>0.5;
94
+ var jitter=vec2f(traceRandom(&seed),traceRandom(&seed));if(hybrid){jitter=vec2f(0.5+traceParams.forward.w,0.5+traceParams.right.w);}
95
+ let pixel=((vec2f(invocation.xy)+jitter)/vec2f(f32(width),f32(height)))*2.0-vec2f(1);
96
+ var direction=normalize(traceParams.forward.xyz+traceParams.right.xyz*pixel.x-traceParams.up.xyz*pixel.y);
97
+ var origin=traceParams.origin.xyz+direction*(traceParams.screen.w/max(dot(direction,traceParams.forward.xyz),1e-5));
98
+ var throughput=vec3f(1);var radiance=vec3f(0);var previousPdf=0.0;var previousDelta=false;var etaScale=1.0;var previousPosition=traceParams.origin.xyz;var media:array<u32,65>;var mediumDepth=1u;media[0]=0u;
99
+ var previousHit=-1;var previousGeometric=vec3f(0);var previousBary=vec3f(0);var previousEvent=0u;var previousFront=false;
100
+ if(traceVolumeEnabled){let initialCount=u32(traceParams.medium.y);if(initialCount>64u){atomicAdd(&traceOutput.faults,1u);atomicOr(&traceOutput.faultMask,8u);}else{for(var layer=0u;layer<initialCount;layer++){media[layer+1u]=u32(traceLights[u32(traceParams.medium.z)+layer].position.x);}mediumDepth+=initialCount;}}
101
+ for(var bounce=0u;bounce<u32(traceParams.settings.z);bounce++){
102
+ var hit:SurfaceHit;
103
+ if(hybrid&&bounce==0u){let primary=textureLoad(primaryHits,vec2i(invocation.xy),0);if(primary.y< -1.5){var coverageSeed=seed;let cosine=max(dot(direction,traceParams.forward.xyz),1e-5);let exact=traceSurface(origin,direction,(traceParams.settings.w-traceParams.screen.w)/cosine,u32(traceParams.counts.x),u32(traceParams.counts.y),traceRandom(&coverageSeed));hit=SurfaceHit(exact.distance+traceParams.screen.w/cosine,exact.index,exact.bary,exact.front);}else{hit=SurfaceHit(abs(primary.x),i32(primary.y),vec3f(1.0-primary.z-primary.w,primary.z,primary.w),primary.x>=0.0);}origin=traceParams.origin.xyz;}
104
+ else{var rayLimit=traceParams.settings.w;if(bounce==0u){rayLimit=(traceParams.settings.w-traceParams.screen.w)/max(dot(direction,traceParams.forward.xyz),1e-5);}hit=traceSurface(origin,direction,rayLimit,u32(traceParams.counts.x),u32(traceParams.counts.y),traceRandom(&seed));}
105
+ let debugSlot=pixelCount*4u+13u+bounce*8u;
106
+ if(targeted&&bounce<8u){for(var debugRow=0u;debugRow<8u;debugRow++){traceOutput.pixels[debugSlot+debugRow]=vec4f(0);}traceOutput.pixels[pixelCount*4u+12u].z=f32(bounce+1u);traceOutput.pixels[debugSlot]=vec4f(origin,f32(hit.index));traceOutput.pixels[debugSlot+1u]=vec4f(direction,hit.distance);traceOutput.pixels[debugSlot+2u]=vec4f(f32(mediumDepth),f32(media[mediumDepth-1u]),select(0.0,1.0,hit.front),0);}
107
+ if(hit.index<0){if(traceVolumeEnabled&&media[mediumDepth-1u]!=0u){let fault=atomicAdd(&traceOutput.faults,1u);if(traceCaptureFault&&((traceParams.environment0.w<0.0&&fault==0u)||targeted)){let tail=pixelCount*4u;traceOutput.pixels[tail]=vec4f(origin,f32(bounce));traceOutput.pixels[tail+1u]=vec4f(direction,f32(media[mediumDepth-1u]));traceOutput.pixels[tail+2u]=vec4f(previousPosition,f32(previousHit));traceOutput.pixels[tail+3u]=vec4f(previousGeometric,f32(previousEvent));traceOutput.pixels[tail+4u]=vec4f(previousBary,f32(index));traceOutput.pixels[tail+5u]=vec4f(select(0.0,1.0,previousFront),traceParams.screen.z,f32(sample),1);if(previousHit>=0){let oldTriangle=surfaceTriangle(u32(previousHit));traceOutput.pixels[tail+6u]=oldTriangle.a;traceOutput.pixels[tail+7u]=oldTriangle.b;traceOutput.pixels[tail+8u]=oldTriangle.c;traceOutput.pixels[tail+9u]=surfaceMaterials[u32(oldTriangle.a.w)].physical;traceOutput.pixels[tail+10u]=oldTriangle.nma;traceOutput.pixels[tail+11u]=oldTriangle.nmb;}}atomicOr(&traceOutput.faultMask,4u);atomicStore(&traceOutput.faultDetail,(mediumDepth<<24u)|media[mediumDepth-1u]);atomicStore(&traceOutput.faultPixel,index);break;}if(bounce==0u){radiance+=throughput*traceParams.background.rgb;}else{let weight=select(1.0,tracePower(previousPdf,traceEnvironmentPdf(direction)),traceParams.flags.z>0.5&&traceParams.environment.x>=0.0&&!previousDelta);radiance+=throughput*(traceEnvironmentBase(direction)*weight+traceEnvironmentAmbient(direction));}break;}
108
+ let triangle=surfaceTriangle(u32(hit.index));var surface=evaluateSurfaceView(u32(hit.index),hit.bary,hit.front,-direction);let point=tracePoint(triangle,hit.bary);let position=point.position;var shadowMedia=TraceShadowMedia(vec3f(0),vec3f(0));
109
+ if(traceVolumeEnabled){shadowMedia.incident=traceMediumSigma(media[mediumDepth-1u]);shadowMedia.transmitted=shadowMedia.incident;throughput*=exp(-traceMediumSigma(media[mediumDepth-1u])*length(position-previousPosition));if(surface.medium.x>0.0){let id=u32(triangle.nmb.w);if(id==0u||id>u32(traceParams.medium.w)){atomicAdd(&traceOutput.faults,1u);atomicOr(&traceOutput.faultMask,1u);break;}
110
+ if(hit.front){var duplicate=false;for(var layer=1u;layer<mediumDepth;layer++){if(media[layer]==id){duplicate=true;}}if(duplicate){atomicAdd(&traceOutput.faults,1u);atomicOr(&traceOutput.faultMask,2u);atomicStore(&traceOutput.faultDetail,(mediumDepth<<24u)|(id<<12u)|media[mediumDepth-1u]);atomicStore(&traceOutput.faultPixel,index);break;}shadowMedia.transmitted=traceMediumSigma(id);surface.medium=vec4f(surface.medium.x,traceMediumIor(media[mediumDepth-1u]),traceMediumIor(id),1.0);}
111
+ else{if(mediumDepth<2u||media[mediumDepth-1u]!=id){atomicAdd(&traceOutput.faults,1u);atomicOr(&traceOutput.faultMask,2u);atomicStore(&traceOutput.faultDetail,(mediumDepth<<24u)|(id<<12u)|media[mediumDepth-1u]);atomicStore(&traceOutput.faultPixel,index);break;}shadowMedia.transmitted=traceMediumSigma(media[mediumDepth-2u]);surface.medium=vec4f(surface.medium.x,traceMediumIor(media[mediumDepth-2u]),traceMediumIor(id),-1.0);}
112
+ }}
113
+ var geometric=normalize(cross(triangle.b.xyz-triangle.a.xyz,triangle.c.xyz-triangle.a.xyz));if(dot(geometric,direction)>0.0){geometric=-geometric;}
114
+ if(targeted&&bounce<8u){traceOutput.pixels[debugSlot+2u].w=triangle.nmb.w;traceOutput.pixels[debugSlot+3u]=vec4f(position,surface.roughness);traceOutput.pixels[debugSlot+6u]=vec4f(hit.bary,surface.medium.z);traceOutput.pixels[debugSlot+7u]=vec4f(surface.normal,surface.medium.y);}
115
+ if(sample==0u&&bounce==0u){firstNormal=vec4f(surface.normal,length(position-traceParams.origin.xyz));firstAlbedo=vec4f(surface.base,surface.roughness);}
116
+ if(bounce==0u&&traceParams.flags.y>0.0){
117
+ if(traceParams.flags.y==1.0){radiance=surface.base;}
118
+ else if(traceParams.flags.y==2.0){radiance=surface.normal*0.5+vec3f(0.5);}
119
+ else if(traceParams.flags.y==3.0){radiance=vec3f(clamp(hit.distance/traceParams.settings.w,0.0,1.0));}
120
+ break;
121
+ }
122
+ var emissionWeight=1.0;if(bounce>0u&&traceParams.flags.z>0.5&&!previousDelta){emissionWeight=tracePower(previousPdf,traceEmitterPdf(u32(hit.index),previousPosition,position));}
123
+ radiance+=throughput*surface.emissive*emissionWeight;
124
+ if(traceParams.flags.z>0.5){radiance+=throughput*traceDirect(point,geometric,surface,-direction,shadowMedia,traceParams.flags.w>0.5&&bounce+1u<u32(traceParams.settings.z),&seed);}
125
+ if(traceParams.flags.w<0.5){break;}
126
+ let branch=traceRandom(&seed);let random=vec3f(traceRandom(&seed),traceRandom(&seed),traceRandom(&seed));let next=traceScatter(surface,-direction,branch,random);
127
+ if(targeted&&bounce<8u){traceOutput.pixels[debugSlot+4u]=vec4f(next.direction,f32(next.event));traceOutput.pixels[debugSlot+5u]=vec4f(geometric,next.pdf);}
128
+ if(next.pdf<=0.0||!traceValidDirection(geometric,surface,next.direction)||(next.event==2u)!=(dot(geometric,next.direction)<0.0)){break;}
129
+ if(traceVolumeEnabled&&next.event==2u){let id=u32(triangle.nmb.w);if(hit.front){if(mediumDepth>=65u){atomicAdd(&traceOutput.faults,1u);atomicOr(&traceOutput.faultMask,8u);break;}media[mediumDepth]=id;mediumDepth++;}else{mediumDepth--;}etaScale*=next.eta*next.eta;}
130
+ throughput*=next.weight;previousPdf=next.pdf;previousDelta=next.delta!=0u;
131
+ if(bounce>=2u&&!previousDelta){let survival=clamp(max(throughput.r,max(throughput.g,throughput.b))*etaScale,0.05,0.95);if(traceRandom(&seed)>survival){break;}throughput/=survival;}
132
+ if(traceCaptureFault&&traceVolumeEnabled){previousHit=hit.index;previousGeometric=geometric;previousBary=hit.bary;previousEvent=next.event;previousFront=hit.front;}
133
+ previousPosition=position;origin=traceOffset(point,geometric,next.direction);direction=next.direction;
134
+ }
135
+ radianceSum+=radiance;
136
+ }
137
+ var old=vec4f(0);if(traceParams.counts.w<0.5){old=traceOutput.pixels[index];}
138
+ let accumulated=vec4f(old.rgb+radianceSum,old.a+traceParams.settings.y);traceOutput.pixels[index]=accumulated;
139
+ var average=vec4f(accumulated.rgb/accumulated.a,accumulated.a);if(traceParams.flags.y==4.0){average=vec4f(vec3f(log2(1.0+accumulated.a)/8.0),accumulated.a);}
140
+ traceOutput.pixels[index+pixelCount]=average;traceOutput.pixels[index+pixelCount*2u]=firstNormal;traceOutput.pixels[index+pixelCount*3u]=firstAlbedo;
141
+ }
142
+ `;
143
+ export const PRIMARY_SHADER = TRACE_PARAMETERS_SHADER +
144
+ /* wgsl */ String.raw `
145
+ @group(0) @binding(7) var<uniform> primaryParams:TraceParameters;
146
+ struct PrimaryVertex{@builtin(position) position:vec4f,@location(0) @interpolate(flat) index:u32,}
147
+ @vertex fn primaryVertex(@builtin(vertex_index) vertex:u32)->PrimaryVertex{
148
+ let index=vertex/3u;let corner=vertex%3u;let triangle=surfaceTriangle(index);var world=triangle.a.xyz;if(corner==1u){world=triangle.b.xyz;}if(corner==2u){world=triangle.c.xyz;}
149
+ let matrix=mat4x4f(primaryParams.view0,primaryParams.view1,primaryParams.view2,primaryParams.view3);return PrimaryVertex(matrix*vec4f(world,1),index);
150
+ }
151
+ @fragment fn primaryFragment(input:PrimaryVertex,@builtin(front_facing) rasterFront:bool)->@location(0) vec4f{
152
+ let front=surfaceOriginalFront(surfaceTriangle(input.index),rasterFront);
153
+ let triangle=surfaceTriangle(input.index);
154
+ // Raster subpixel setup is not precise barycentric data for ray-space motion.
155
+ let pixel=(input.position.xy+vec2f(primaryParams.forward.w,primaryParams.right.w))/primaryParams.screen.xy*2.0-vec2f(1);
156
+ let direction=normalize(primaryParams.forward.xyz+primaryParams.right.xyz*pixel.x-primaryParams.up.xyz*pixel.y);
157
+ let edge1=triangle.b.xyz-triangle.a.xyz;let edge2=triangle.c.xyz-triangle.a.xyz;
158
+ let p=cross(direction,edge2);let determinant=dot(edge1,p);if(determinant==0.0){return vec4f(0,-2,0,0);}
159
+ let relative=primaryParams.origin.xyz-triangle.a.xyz;let q=cross(relative,edge1);
160
+ let u=dot(relative,p)/determinant;let v=dot(direction,q)/determinant;
161
+ let bary=vec3f(1.0-u-v,u,v);let distance=dot(edge2,q)/determinant;
162
+ // Hardware raster coverage can select a neighboring face at subpixel edges.
163
+ // Mark uncertainty for an exact GPU closest-hit query, never clamp into a false boundary.
164
+ if(any(bary<vec3f(0))||any(bary>vec3f(1))||distance<=0.0||any((bitcast<vec4u>(vec4f(bary,distance))&vec4u(0x7f800000u))==vec4u(0x7f800000u))){return vec4f(0,-2,0,0);}
165
+ let surface=evaluateSurfaceView(input.index,bary,front,-direction);if(surface.discarded!=0u){discard;}
166
+ let material=surfaceMaterials[u32(triangle.a.w)];
167
+ if(material.normal.w==2.0){let threshold=fract(sin(dot(input.position.xy,vec2f(12.9898,78.233))+primaryParams.screen.z)*43758.5453);if(surfaceCoverageRandom(threshold,input.index)>surface.alpha){discard;}}
168
+ return vec4f(select(-distance,distance,front),f32(input.index),bary.yz);
169
+ }
170
+ `;
171
+ export const TRACE_PRESENT_SHADER = /* wgsl */ String.raw `
172
+ struct Present{size:vec4f,}
173
+ @group(0) @binding(0) var<storage,read> radiance:array<vec4f>;
174
+ @group(0) @binding(1) var<uniform> parameters:Present;
175
+ @vertex fn vertex(@builtin(vertex_index) id:u32)->@builtin(position) vec4f{let points=array<vec2f,3>(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3));return vec4f(points[id],0,1);}
176
+ fn displaySrgb(value:vec3f)->vec3f{return select(1.055*pow(max(value,vec3f(0)),vec3f(1.0/2.4))-vec3f(0.055),value*12.92,value<=vec3f(0.0031308));}
177
+ @fragment fn fragment(@builtin(position) pixel:vec4f)->@location(0) vec4f{
178
+ let index=u32(pixel.y)*u32(parameters.size.x)+u32(pixel.x);var color=max(radiance[index].rgb*parameters.size.z,vec3f(0));
179
+ if(parameters.size.w<0.5){color=clamp((color*(2.51*color+vec3f(0.03)))/(color*(2.43*color+vec3f(0.59))+vec3f(0.14)),vec3f(0),vec3f(1));}
180
+ return vec4f(displaySrgb(color),1);
181
+ }
182
+ `;
183
+ /** Engine-owned linear HDR handoff. Exposure and display conversion happen in the composer. */
184
+ export const TRACE_LINEAR_PRESENT_SHADER = /* wgsl */ String.raw `
185
+ struct Present{size:vec4f,}
186
+ @group(0) @binding(0) var<storage,read> radiance:array<vec4f>;
187
+ @group(0) @binding(1) var<storage,read> guides:array<vec4f>;
188
+ @group(0) @binding(2) var<uniform> parameters:Present;
189
+ struct Output{@location(0) color:vec4f,@location(1) normal:vec4f,@location(2) depth:vec4f,}
190
+ @vertex fn vertex(@builtin(vertex_index) id:u32)->@builtin(position) vec4f{let points=array<vec2f,3>(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3));return vec4f(points[id],0,1);}
191
+ @fragment fn fragment(@builtin(position) pixel:vec4f)->Output{
192
+ let index=u32(pixel.y)*u32(parameters.size.x)+u32(pixel.x);
193
+ let guide=guides[index];let hit=select(0.0,1.0,guide.w>=0.0);
194
+ return Output(vec4f(max(radiance[index].rgb,vec3f(0)),1),vec4f(guide.xyz,hit),vec4f(max(guide.w,0.0),0,0,hit));
195
+ }
196
+ `;
@@ -0,0 +1,324 @@
1
+ const shader = /* wgsl */ `
2
+ struct Triangle { a: vec4f, b: vec4f, c: vec4f, color: vec4f, pad: vec4f }
3
+ struct Node { lo: vec4f, hi: vec4f }
4
+ struct Params { origin: vec4f, forward: vec4f, right: vec4f, up: vec4f, screen: vec4f, settings: vec4f, lightPosition:vec4f, lightColor:vec4f, environment:vec4f }
5
+ @group(0) @binding(0) var<storage,read> triangles: array<Triangle>;
6
+ @group(0) @binding(1) var<storage,read> nodes: array<Node>;
7
+ @group(0) @binding(2) var<uniform> params: Params;
8
+ @group(0) @binding(3) var image: texture_storage_2d<rgba8unorm,write>;
9
+ @group(0) @binding(4) var<storage,read_write> history: array<vec4f>;
10
+ @group(0) @binding(5) var<storage,read_write> queryResult: array<vec4f>;
11
+ struct Hit { t: f32, index: i32, normal: vec3f }
12
+ fn random(seed: ptr<function,u32>) -> f32 { *seed = *seed * 1664525u + 1013904223u; var x = *seed; x = ((x >> 16u) ^ x) * 2246822519u; return f32(x >> 8u) / 16777216.0; }
13
+ fn boxHit(o: vec3f, d: vec3f, node: Node, maxT: f32) -> bool {
14
+ let inverse = 1.0 / (d + vec3f(1e-12));
15
+ let a = (node.lo.xyz - o) * inverse; let b = (node.hi.xyz - o) * inverse;
16
+ let low = min(a,b); let high = max(a,b);
17
+ return max(max(low.x,low.y),max(low.z,0.0)) <= min(min(high.x,high.y),min(high.z,maxT));
18
+ }
19
+ fn trace(o: vec3f,d: vec3f,maxT: f32) -> Hit {
20
+ var result = Hit(maxT,-1,vec3f(0));
21
+ var stack: array<u32,64>; stack[0] = 0u; var size = 1u;
22
+ loop {
23
+ if size == 0u { break; } size -= 1u; let node = nodes[stack[size]];
24
+ if !boxHit(o,d,node,result.t) { continue; }
25
+ if node.hi.w > 0.0 {
26
+ let start = u32(node.lo.w); let count = u32(node.hi.w);
27
+ for(var j=0u;j<count;j++) {
28
+ let id = start+j; let t = triangles[id]; let edge1 = t.b.xyz-t.a.xyz; let edge2 = t.c.xyz-t.a.xyz;
29
+ let p = cross(d,edge2); let det = dot(edge1,p); if abs(det)<1e-7 { continue; }
30
+ let inv = 1.0/det; let s = o-t.a.xyz; let u = dot(s,p)*inv; if u<0.0 || u>1.0 { continue; }
31
+ let q = cross(s,edge1); let v = dot(d,q)*inv; if v<0.0 || u+v>1.0 { continue; }
32
+ let distance = dot(edge2,q)*inv;
33
+ if distance>0.001 && distance<result.t { var n = normalize(cross(edge1,edge2)); if dot(n,d)>0.0 { n = -n; } result = Hit(distance,i32(id),n); }
34
+ }
35
+ } else if size < 62u { stack[size] = u32(node.lo.w); stack[size+1u] = u32(-node.hi.w); size += 2u; }
36
+ }
37
+ return result;
38
+ }
39
+ fn hemisphere(n: vec3f, seed: ptr<function,u32>) -> vec3f {
40
+ let u = random(seed); let phi = 6.2831853*random(seed); let r = sqrt(u);
41
+ let tangent = normalize(cross(n,select(vec3f(0,1,0),vec3f(1,0,0),abs(n.y)>0.9)));
42
+ return normalize(tangent*(r*cos(phi))+cross(n,tangent)*(r*sin(phi))+n*sqrt(1.0-u));
43
+ }
44
+ @compute @workgroup_size(1)
45
+ fn queryRay() { let hit=trace(params.origin.xyz,normalize(params.forward.xyz),1e20);queryResult[0]=vec4f(hit.t,f32(hit.index),0,0);queryResult[1]=vec4f(hit.normal,0); }
46
+ @compute @workgroup_size(8,8)
47
+ fn main(@builtin(global_invocation_id) id: vec3u) {
48
+ let width = u32(params.screen.x); let height = u32(params.screen.y); if id.x>=width || id.y>=height { return; }
49
+ let index = id.y*width+id.x; var seed = index*1973u+u32(params.screen.z)*9277u+89173u;
50
+ let jitter = vec2f(random(&seed),random(&seed));
51
+ let pixel = ((vec2f(id.xy)+jitter)/vec2f(f32(width),f32(height))*2.0-1.0);
52
+ var origin = params.origin.xyz;
53
+ var direction = normalize(params.forward.xyz+params.right.xyz*pixel.x-params.up.xyz*pixel.y);
54
+ origin += direction*params.settings.z;
55
+ var throughput = vec3f(1); var radiance = vec3f(0);
56
+ for(var bounce=0u;bounce<u32(params.settings.x);bounce++) {
57
+ let hit = trace(origin,direction,params.screen.w);
58
+ if hit.index<0 { radiance += throughput*params.environment.rgb*(0.2+max(direction.y,0.0))*params.environment.w; break; }
59
+ let triangle = triangles[u32(hit.index)]; let position = origin+direction*hit.t;
60
+ radiance += throughput*triangle.color.rgb*triangle.a.w;
61
+ let light = params.lightPosition.xyz+vec3f(random(&seed)-0.5,0,random(&seed)-0.5)*select(1.2,0.0,params.lightColor.w>0.5);
62
+ let toLight = select(light-position,light,params.lightColor.w>0.5); let distance = select(length(toLight),1e10,params.lightColor.w>0.5); let l = normalize(toLight);
63
+ if trace(position+hit.normal*0.003,l,distance-0.02).index<0 { radiance += throughput*triangle.color.rgb*params.lightColor.rgb*max(dot(hit.normal,l),0.0)*params.lightPosition.w/select(distance*distance,1.0,params.lightColor.w>0.5); }
64
+ let diffuse = hemisphere(hit.normal,&seed); let reflected = reflect(direction,hit.normal);
65
+ direction = normalize(mix(reflected,diffuse,triangle.b.w));
66
+ throughput *= triangle.color.rgb; origin = position+hit.normal*0.003;
67
+ if bounce>2u { let survival = min(max(max(throughput.r,throughput.g),throughput.b),0.95); if random(&seed)>survival { break; } throughput /= survival; }
68
+ }
69
+ let old = select(vec4f(0),history[index],params.settings.y>0.5); let accumulated = vec4f(old.rgb+radiance,old.a+1.0); history[index] = accumulated;
70
+ let linear = accumulated.rgb/accumulated.a; let mapped = pow(linear/(linear+vec3f(1)),vec3f(1.0/2.2));
71
+ textureStore(image,vec2i(id.xy),vec4f(mapped,1));
72
+ }
73
+ `;
74
+ const present = /* wgsl */ `
75
+ @group(0) @binding(0) var image: texture_2d<f32>;
76
+ @vertex fn vs(@builtin(vertex_index) i:u32)->@builtin(position) vec4f { let p=array<vec2f,3>(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3)); return vec4f(p[i],0,1); }
77
+ @fragment fn fs(@builtin(position) p:vec4f)->@location(0) vec4f { return textureLoad(image,vec2i(p.xy),0); }
78
+ `;
79
+ /** Internal M0 software triangle-BVH tracer. No hardware RT/production quality claim. */
80
+ export class GPUTracer {
81
+ device;
82
+ context;
83
+ compute;
84
+ render;
85
+ query;
86
+ uniform;
87
+ history;
88
+ output;
89
+ triangleBuffer;
90
+ nodeBuffer;
91
+ computeGroup;
92
+ presentGroup;
93
+ timestamps;
94
+ timestampResult;
95
+ frame = 0;
96
+ width;
97
+ height;
98
+ constructor(device, canvas, width, height) {
99
+ this.device = device;
100
+ this.width = width;
101
+ this.height = height;
102
+ if (device.features.has("timestamp-query")) {
103
+ this.timestamps = device.createQuerySet({ type: "timestamp", count: 2 });
104
+ this.timestampResult = device.createBuffer({
105
+ size: 256,
106
+ usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
107
+ });
108
+ }
109
+ canvas.width = width;
110
+ canvas.height = height;
111
+ this.context = canvas.getContext("webgpu");
112
+ const format = navigator.gpu.getPreferredCanvasFormat();
113
+ this.context.configure({ device, format, alphaMode: "opaque" });
114
+ const tracerModule = device.createShaderModule({ code: shader });
115
+ this.compute = device.createComputePipeline({
116
+ layout: "auto",
117
+ compute: { module: tracerModule, entryPoint: "main" },
118
+ });
119
+ this.query = device.createComputePipeline({
120
+ layout: "auto",
121
+ compute: { module: tracerModule, entryPoint: "queryRay" },
122
+ });
123
+ const module = device.createShaderModule({ code: present });
124
+ this.render = device.createRenderPipeline({
125
+ layout: "auto",
126
+ vertex: { module, entryPoint: "vs" },
127
+ fragment: { module, entryPoint: "fs", targets: [{ format }] },
128
+ primitive: { topology: "triangle-list" },
129
+ });
130
+ this.uniform = device.createBuffer({
131
+ size: 144,
132
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
133
+ });
134
+ this.history = device.createBuffer({
135
+ size: width * height * 16,
136
+ usage: GPUBufferUsage.STORAGE,
137
+ });
138
+ this.output = device.createTexture({
139
+ size: [width, height],
140
+ format: "rgba8unorm",
141
+ usage: GPUTextureUsage.STORAGE_BINDING |
142
+ GPUTextureUsage.TEXTURE_BINDING |
143
+ GPUTextureUsage.COPY_SRC,
144
+ });
145
+ this.presentGroup = device.createBindGroup({
146
+ layout: this.render.getBindGroupLayout(0),
147
+ entries: [{ binding: 0, resource: this.output.createView() }],
148
+ });
149
+ }
150
+ upload(geometry) {
151
+ this.triangleBuffer?.destroy();
152
+ this.nodeBuffer?.destroy();
153
+ const upload = (data) => {
154
+ const buffer = this.device.createBuffer({
155
+ size: data.byteLength,
156
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
157
+ });
158
+ this.device.queue.writeBuffer(buffer, 0, data.buffer, data.byteOffset, data.byteLength);
159
+ return buffer;
160
+ };
161
+ this.triangleBuffer = upload(geometry.triangles);
162
+ this.nodeBuffer = upload(geometry.nodes);
163
+ this.computeGroup = this.device.createBindGroup({
164
+ layout: this.compute.getBindGroupLayout(0),
165
+ entries: [
166
+ { binding: 0, resource: { buffer: this.triangleBuffer } },
167
+ { binding: 1, resource: { buffer: this.nodeBuffer } },
168
+ { binding: 2, resource: { buffer: this.uniform } },
169
+ { binding: 3, resource: this.output.createView() },
170
+ { binding: 4, resource: { buffer: this.history } },
171
+ ],
172
+ });
173
+ }
174
+ renderFrame(camera, mode, accumulate) {
175
+ const start = performance.now();
176
+ const vec = (v) => [...v, 0];
177
+ const data = new Float32Array([
178
+ ...vec(camera.origin),
179
+ ...vec(camera.forward),
180
+ ...vec(camera.right),
181
+ ...vec(camera.up),
182
+ this.width,
183
+ this.height,
184
+ this.frame++,
185
+ camera.far,
186
+ mode === "path" ? 5 : 2,
187
+ accumulate ? 1 : 0,
188
+ camera.near,
189
+ 0,
190
+ ...camera.light.position,
191
+ camera.light.intensity,
192
+ ...camera.light.color,
193
+ camera.light.directional ? 1 : 0,
194
+ ...camera.environment,
195
+ camera.ambient,
196
+ ]);
197
+ this.device.queue.writeBuffer(this.uniform, 0, data);
198
+ const encoder = this.device.createCommandEncoder();
199
+ const compute = encoder.beginComputePass(this.timestamps
200
+ ? {
201
+ timestampWrites: {
202
+ querySet: this.timestamps,
203
+ beginningOfPassWriteIndex: 0,
204
+ endOfPassWriteIndex: 1,
205
+ },
206
+ }
207
+ : {});
208
+ compute.setPipeline(this.compute);
209
+ compute.setBindGroup(0, this.computeGroup);
210
+ compute.dispatchWorkgroups(Math.ceil(this.width / 8), Math.ceil(this.height / 8));
211
+ compute.end();
212
+ if (this.timestamps)
213
+ encoder.resolveQuerySet(this.timestamps, 0, 2, this.timestampResult, 0);
214
+ const render = encoder.beginRenderPass({
215
+ colorAttachments: [
216
+ {
217
+ view: this.context.getCurrentTexture().createView(),
218
+ loadOp: "clear",
219
+ storeOp: "store",
220
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
221
+ },
222
+ ],
223
+ });
224
+ render.setPipeline(this.render);
225
+ render.setBindGroup(0, this.presentGroup);
226
+ render.draw(3);
227
+ render.end();
228
+ this.device.queue.submit([encoder.finish()]);
229
+ return performance.now() - start;
230
+ }
231
+ async queryRay(origin, direction) {
232
+ const output = this.device.createBuffer({
233
+ size: 32,
234
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
235
+ });
236
+ const read = this.device.createBuffer({
237
+ size: 32,
238
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
239
+ });
240
+ const values = new Float32Array(36);
241
+ values.set(origin, 0);
242
+ values.set(direction, 4);
243
+ this.device.queue.writeBuffer(this.uniform, 0, values);
244
+ const group = this.device.createBindGroup({
245
+ layout: this.query.getBindGroupLayout(0),
246
+ entries: [
247
+ { binding: 0, resource: { buffer: this.triangleBuffer } },
248
+ { binding: 1, resource: { buffer: this.nodeBuffer } },
249
+ { binding: 2, resource: { buffer: this.uniform } },
250
+ { binding: 5, resource: { buffer: output } },
251
+ ],
252
+ });
253
+ const encoder = this.device.createCommandEncoder();
254
+ const pass = encoder.beginComputePass();
255
+ pass.setPipeline(this.query);
256
+ pass.setBindGroup(0, group);
257
+ pass.dispatchWorkgroups(1);
258
+ pass.end();
259
+ encoder.copyBufferToBuffer(output, 0, read, 0, 32);
260
+ this.device.queue.submit([encoder.finish()]);
261
+ await read.mapAsync(GPUMapMode.READ);
262
+ const result = Array.from(new Float32Array(read.getMappedRange()));
263
+ read.unmap();
264
+ output.destroy();
265
+ read.destroy();
266
+ return {
267
+ distance: result[0],
268
+ triangle: result[1],
269
+ normal: result.slice(4, 7),
270
+ };
271
+ }
272
+ async gpuTimeMs() {
273
+ if (!this.timestampResult)
274
+ return null;
275
+ const buffer = this.device.createBuffer({
276
+ size: 16,
277
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
278
+ });
279
+ const encoder = this.device.createCommandEncoder();
280
+ encoder.copyBufferToBuffer(this.timestampResult, 0, buffer, 0, 16);
281
+ this.device.queue.submit([encoder.finish()]);
282
+ await buffer.mapAsync(GPUMapMode.READ);
283
+ const values = new BigUint64Array(buffer.getMappedRange());
284
+ const ms = Number(values[1] - values[0]) / 1e6;
285
+ buffer.unmap();
286
+ buffer.destroy();
287
+ return ms;
288
+ }
289
+ async pixelStats() {
290
+ const bytesPerRow = Math.ceil((this.width * 4) / 256) * 256;
291
+ const buffer = this.device.createBuffer({
292
+ size: bytesPerRow * this.height,
293
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
294
+ });
295
+ const encoder = this.device.createCommandEncoder();
296
+ encoder.copyTextureToBuffer({ texture: this.output }, { buffer, bytesPerRow }, { width: this.width, height: this.height });
297
+ this.device.queue.submit([encoder.finish()]);
298
+ await buffer.mapAsync(GPUMapMode.READ);
299
+ const bytes = new Uint8Array(buffer.getMappedRange());
300
+ let sum = 0, nonBlack = 0, hash = 2166136261;
301
+ for (let y = 0; y < this.height; y += 4)
302
+ for (let x = 0; x < this.width; x += 4) {
303
+ const i = y * bytesPerRow + x * 4;
304
+ const value = bytes[i] + bytes[i + 1] + bytes[i + 2];
305
+ sum += value;
306
+ if (value > 15)
307
+ nonBlack++;
308
+ hash = Math.imul(hash ^ value, 16777619);
309
+ }
310
+ buffer.unmap();
311
+ buffer.destroy();
312
+ return { sum, nonBlack, hash: hash >>> 0 };
313
+ }
314
+ dispose() {
315
+ this.triangleBuffer?.destroy();
316
+ this.nodeBuffer?.destroy();
317
+ this.uniform.destroy();
318
+ this.history.destroy();
319
+ this.output.destroy();
320
+ this.context.unconfigure();
321
+ this.timestamps?.destroy();
322
+ this.timestampResult?.destroy();
323
+ }
324
+ }
@@ -0,0 +1 @@
1
+ export {};