@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.
- package/README.md +3 -0
- package/dist/assets/bvh.worker.js +1 -1
- package/dist/mutracer.umd.js +8949 -7915
- package/docs/api.md +42 -21
- package/docs/caustics.md +80 -0
- package/docs/materials.md +5 -4
- package/docs/progressive-rendering.md +50 -9
- package/docs/technical/continuous-spectrum.md +93 -0
- package/docs/updates.md +3 -1
- package/docs/viewer-checklist.md +65 -0
- package/docs/viewer.md +7 -5
- package/lib/Display.js +1 -1
- package/lib/Display.js.map +1 -1
- package/lib/Engine.d.ts +2 -0
- package/lib/Engine.js +47 -12
- package/lib/Engine.js.map +1 -1
- package/lib/Render.d.ts +61 -13
- package/lib/Render.js +169 -19
- package/lib/Render.js.map +1 -1
- package/lib/RenderLoop.d.ts +7 -3
- package/lib/RenderLoop.js +68 -44
- package/lib/RenderLoop.js.map +1 -1
- package/lib/main.d.ts +1 -1
- package/lib/main.js.map +1 -1
- package/lib/oidn.d.ts +1 -1
- package/lib/oidnBundledWeights.cdn.d.ts +2 -2
- package/lib/renderer/GPUTraceQueue.d.ts +2 -1
- package/lib/renderer/GPUTraceQueue.js +3 -1
- package/lib/renderer/GPUTraceQueue.js.map +1 -1
- package/lib/renderer/RenderingPipeline.d.ts +4 -0
- package/lib/renderer/RenderingPipeline.js.map +1 -1
- package/lib/renderer/WGPUCausticLightPass.d.ts +48 -0
- package/lib/renderer/WGPUCausticLightPass.js +217 -0
- package/lib/renderer/WGPUCausticLightPass.js.map +1 -0
- package/lib/renderer/WGPUEnvMapDistribution.js +18 -18
- package/lib/renderer/WGPUFullQuadPass.js +3 -3
- package/lib/renderer/WGPUPhotonMap.d.ts +37 -0
- package/lib/renderer/WGPUPhotonMap.js +191 -0
- package/lib/renderer/WGPUPhotonMap.js.map +1 -0
- package/lib/renderer/WGPURayTraceOutput.d.ts +13 -1
- package/lib/renderer/WGPURayTraceOutput.js +122 -28
- package/lib/renderer/WGPURayTraceOutput.js.map +1 -1
- package/lib/renderer/WGPURayTracePass.d.ts +48 -17
- package/lib/renderer/WGPURayTracePass.js +538 -340
- package/lib/renderer/WGPURayTracePass.js.map +1 -1
- package/lib/renderer/WGPURenderingPipeline.js +83 -47
- package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLFullQuadPass.d.ts +2 -1
- package/lib/renderer/WebGLFullQuadPass.js +4 -0
- package/lib/renderer/WebGLFullQuadPass.js.map +1 -1
- package/lib/renderer/WebGLJNLMDenoiser.js +66 -66
- package/lib/renderer/WebGLRayTraceOutputPass.js +4 -4
- package/lib/renderer/WebGLRayTracePass.d.ts +6 -1
- package/lib/renderer/WebGLRayTracePass.js +43 -6
- package/lib/renderer/WebGLRayTracePass.js.map +1 -1
- package/lib/renderer/WebGLRenderingPipeline.js +10 -14
- package/lib/renderer/WebGLRenderingPipeline.js.map +1 -1
- package/lib/renderer/WebGLSkyBaker.js +60 -60
- package/lib/renderer/accumulationTarget.d.ts +2 -1
- package/lib/renderer/accumulationTarget.js.map +1 -1
- package/lib/renderer/causticDiagnostics.d.ts +31 -0
- package/lib/renderer/causticDiagnostics.js +5 -0
- package/lib/renderer/causticDiagnostics.js.map +1 -0
- package/lib/renderer/causticEmission.d.ts +10 -0
- package/lib/renderer/causticEmission.js +40 -0
- package/lib/renderer/causticEmission.js.map +1 -0
- package/lib/renderer/causticUniform.d.ts +12 -0
- package/lib/renderer/causticUniform.js +22 -0
- package/lib/renderer/causticUniform.js.map +1 -0
- package/lib/renderer/caustics.d.ts +16 -0
- package/lib/renderer/caustics.js +93 -0
- package/lib/renderer/caustics.js.map +1 -0
- package/lib/renderer/glsl/aovDisplay.frag.js +5 -5
- package/lib/renderer/glsl/aovReflectance.glsl.js +53 -53
- package/lib/renderer/glsl/aovReflectanceSamples.glsl.js +1 -1
- package/lib/renderer/glsl/aovTrace.frag.js +33 -33
- package/lib/renderer/glsl/chunks/cameraRay.glsl.js +9 -9
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.d.ts +2 -0
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.js +35 -0
- package/lib/renderer/glsl/chunks/emissiveLight.glsl.js.map +1 -0
- package/lib/renderer/glsl/chunks/envMap.glsl.js +73 -73
- package/lib/renderer/glsl/chunks/intersect.glsl.js +426 -428
- package/lib/renderer/glsl/chunks/intersect.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +156 -153
- package/lib/renderer/glsl/chunks/materialBuffer.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +771 -771
- package/lib/renderer/glsl/chunks/rand.glsl.js +3 -3
- package/lib/renderer/glsl/chunks/random.glsl.js +6 -6
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +68 -63
- package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js +94 -76
- package/lib/renderer/glsl/chunks/sampleLight.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.d.ts +1 -0
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +140 -117
- package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sampleShadowCatcher.glsl.js +14 -14
- package/lib/renderer/glsl/chunks/sampling.glsl.js +89 -92
- package/lib/renderer/glsl/chunks/sampling.glsl.js.map +1 -1
- package/lib/renderer/glsl/chunks/sobol.glsl.js +79 -79
- package/lib/renderer/glsl/chunks/textureLinear.glsl.js +5 -5
- package/lib/renderer/glsl/chunks/uvProjection.glsl.js +11 -11
- package/lib/renderer/glsl/depthQuery.frag.js +2 -2
- package/lib/renderer/glsl/normalizeRayTraceOutput.frag.js +10 -10
- package/lib/renderer/glsl/rayTrace.frag.d.ts +1 -1
- package/lib/renderer/glsl/rayTrace.frag.js +260 -237
- package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
- package/lib/renderer/glsl/shadowCatcher.frag.js +26 -26
- package/lib/renderer/glsl/shadowCatcher.frag.js.map +1 -1
- package/lib/renderer/glsl/webglPipeline.frag.js +59 -59
- package/lib/renderer/lightSampling.d.ts +3 -2
- package/lib/renderer/lightSampling.js +14 -8
- package/lib/renderer/lightSampling.js.map +1 -1
- package/lib/renderer/progressiveScheduler.js +4 -3
- package/lib/renderer/progressiveScheduler.js.map +1 -1
- package/lib/renderer/renderPipeline.d.ts +1 -0
- package/lib/renderer/renderPipeline.js.map +1 -1
- package/lib/renderer/researchTransport.d.ts +31 -0
- package/lib/renderer/researchTransport.js +5 -0
- package/lib/renderer/researchTransport.js.map +1 -0
- package/lib/renderer/sceneLayoutSchema.d.ts +1 -0
- package/lib/renderer/sceneLayoutSchema.js +2 -2
- package/lib/renderer/sceneLayoutSchema.js.map +1 -1
- package/lib/renderer/scratchSampling.js +6 -6
- package/lib/renderer/spectrum.d.ts +21 -0
- package/lib/renderer/spectrum.js +154 -0
- package/lib/renderer/spectrum.js.map +1 -0
- package/lib/renderer/webglLightSampling.d.ts +39 -0
- package/lib/renderer/webglLightSampling.js +15 -0
- package/lib/renderer/webglLightSampling.js.map +1 -0
- package/lib/renderer/wgpuBindlessSource.js +3 -3
- package/lib/renderer/wgpuShaderSource.d.ts +7 -0
- package/lib/renderer/wgpuShaderSource.js +2 -0
- package/lib/renderer/wgpuShaderSource.js.map +1 -1
- package/lib/renderer/wgsl/aovDisplay.wgsl.js +9 -9
- package/lib/renderer/wgsl/aovReflectance.wgsl.js +55 -55
- package/lib/renderer/wgsl/aovReflectanceSamples.wgsl.js +1 -1
- package/lib/renderer/wgsl/aovResolve.wgsl.js +2 -2
- package/lib/renderer/wgsl/aovSnapshot.wgsl.js +1 -1
- package/lib/renderer/wgsl/aovTrace.wgsl.js +33 -33
- package/lib/renderer/wgsl/bloom.wgsl.js +18 -18
- package/lib/renderer/wgsl/causticLight.compute.d.ts +6 -0
- package/lib/renderer/wgsl/causticLight.compute.js +537 -0
- package/lib/renderer/wgsl/causticLight.compute.js.map +1 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.js +102 -0
- package/lib/renderer/wgsl/chunks/background.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js +12 -0
- package/lib/renderer/wgsl/chunks/causticAdjoint.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.d.ts +2 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js +16 -0
- package/lib/renderer/wgsl/chunks/causticAtomic.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.d.ts +1 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.js +62 -0
- package/lib/renderer/wgsl/chunks/caustics.wgsl.js.map +1 -0
- package/lib/renderer/wgsl/chunks/common.wgsl.js +4 -4
- package/lib/renderer/wgsl/chunks/envMap.wgsl.js +37 -37
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js +598 -592
- package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/intersectionAlpha.wgsl.js +14 -14
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.d.ts +2 -1
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js +75 -73
- package/lib/renderer/wgsl/chunks/materialBuffer.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +794 -794
- package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +139 -139
- package/lib/renderer/wgsl/chunks/rand.wgsl.js +3 -3
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.d.ts +2 -1
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +35 -25
- package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +142 -138
- package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +204 -187
- package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js +11 -11
- package/lib/renderer/wgsl/chunks/sampleShadowCatcher.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sampling.wgsl.d.ts +1 -1
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js +89 -93
- package/lib/renderer/wgsl/chunks/sampling.wgsl.js.map +1 -1
- package/lib/renderer/wgsl/chunks/sobol.wgsl.js +83 -83
- package/lib/renderer/wgsl/chunks/uvProjection.wgsl.js +11 -11
- package/lib/renderer/wgsl/compose.frag.wgsl.js +98 -98
- package/lib/renderer/wgsl/customDenoise.wgsl.js +10 -10
- package/lib/renderer/wgsl/depthQuery.wgsl.js +2 -2
- package/lib/renderer/wgsl/jnlm.compute.js +74 -74
- package/lib/renderer/wgsl/offlineAdaptive.compute.js +25 -25
- package/lib/renderer/wgsl/oidnAuxiliary.wgsl.js +3 -3
- package/lib/renderer/wgsl/photonMap.compute.d.ts +29 -0
- package/lib/renderer/wgsl/photonMap.compute.js +352 -0
- package/lib/renderer/wgsl/photonMap.compute.js.map +1 -0
- package/lib/renderer/wgsl/proceduralSky.compute.js +64 -64
- package/lib/renderer/wgsl/rayTrace.compute.d.ts +9 -1
- package/lib/renderer/wgsl/rayTrace.compute.js +448 -522
- package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
- package/lib/scene/MaterialState.d.ts +2 -2
- package/lib/scene/MaterialState.js +2 -2
- package/lib/scene/MaterialState.js.map +1 -1
- package/lib/settings/render.d.ts +11 -0
- package/lib/settings/render.js +1 -0
- package/lib/settings/render.js.map +1 -1
- package/lib/util/fromTHREE.js +2 -2
- package/lib/util/fromTHREE.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/llms.txt +2 -1
- 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
|
|
26
|
-
fn
|
|
27
|
-
fn
|
|
28
|
-
fn
|
|
29
|
-
fn
|
|
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
|
|
35
|
-
fn
|
|
36
|
-
fn
|
|
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
|
|
49
|
-
return
|
|
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
|
|
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
|
|
56
|
-
fn
|
|
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 =
|
|
60
|
-
var len =
|
|
61
|
-
if (ground) { len =
|
|
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
|
|
64
|
-
var
|
|
65
|
-
if (full) {
|
|
66
|
-
var result =
|
|
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 =
|
|
76
|
+
let d = oo(pr);
|
|
77
77
|
let e = extinction(d);
|
|
78
|
-
let
|
|
79
|
-
let
|
|
80
|
-
let
|
|
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 =
|
|
83
|
-
let source =
|
|
84
|
-
let
|
|
85
|
-
let weight = (1.0 -
|
|
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 * (
|
|
88
|
-
view *=
|
|
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
|
|
94
|
-
let irradiance =
|
|
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
|
|
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
|
|
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 +
|
|
112
|
-
if (
|
|
113
|
-
let len =
|
|
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(
|
|
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 +
|
|
124
|
+
let r = R_GROUND + mB(f32(id.x) / f32(MS_ALTITUDES - 1u));
|
|
125
125
|
var second = vec3f(0.0);
|
|
126
|
-
var
|
|
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 =
|
|
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
|
-
|
|
133
|
+
qC += result.throughput / 64.0;
|
|
134
134
|
}
|
|
135
|
-
textureStore(output, vec2i(id.xy), vec4f(second / (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 =
|
|
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 =
|
|
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 +=
|
|
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
|
|
161
|
-
let
|
|
162
|
-
let horizontal = dot(sky,
|
|
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(
|
|
164
|
+
textureStore(output, vec2i(id.xy), vec4f(vk, scale));
|
|
165
165
|
}
|
|
166
|
-
fn
|
|
167
|
-
let theta =
|
|
168
|
-
let phi =
|
|
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
|
|
175
|
-
let dir =
|
|
176
|
-
let
|
|
177
|
-
let
|
|
178
|
-
let
|
|
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 =
|
|
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
|
|
184
|
-
if (acos(clamp(dot(dir, params.sunWorld.xyz), -1.0, 1.0)) < radius + 3.0 *
|
|
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 =
|
|
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
|
|
193
|
-
radiance += coverage / 36.0 / (
|
|
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(
|
|
40
|
+
export default function rayTraceCompute(options: RayTraceShaderOptions, sceneIntersections?: string, pathDiagnostic?: boolean, inputHooks?: RayTraceInputHooks): string;
|