@tinypoly/gpu-renderer 0.0.0-stage → 0.0.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/ARCHITECTURE.md +334 -0
- package/LICENSE +21 -0
- package/README.md +258 -2
- package/dist/accumulator.d.ts +27 -0
- package/dist/accumulator.d.ts.map +1 -0
- package/dist/accumulator.js +57 -0
- package/dist/accumulator.js.map +1 -0
- package/dist/bvh.d.ts +26 -0
- package/dist/bvh.d.ts.map +1 -0
- package/dist/bvh.js +207 -0
- package/dist/bvh.js.map +1 -0
- package/dist/camera.d.ts +29 -0
- package/dist/camera.d.ts.map +1 -0
- package/dist/camera.js +48 -0
- package/dist/camera.js.map +1 -0
- package/dist/canvas.d.ts +9 -0
- package/dist/canvas.d.ts.map +1 -0
- package/dist/canvas.js +11 -0
- package/dist/canvas.js.map +1 -0
- package/dist/denoise.d.ts +69 -0
- package/dist/denoise.d.ts.map +1 -0
- package/dist/denoise.js +455 -0
- package/dist/denoise.js.map +1 -0
- package/dist/device.d.ts +37 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/device.js +52 -0
- package/dist/device.js.map +1 -0
- package/dist/dispatchScheduler.d.ts +43 -0
- package/dist/dispatchScheduler.d.ts.map +1 -0
- package/dist/dispatchScheduler.js +49 -0
- package/dist/dispatchScheduler.js.map +1 -0
- package/dist/environment.d.ts +39 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +168 -0
- package/dist/environment.js.map +1 -0
- package/dist/frameLayout.d.ts +4 -0
- package/dist/frameLayout.d.ts.map +1 -0
- package/dist/frameLayout.js +58 -0
- package/dist/frameLayout.js.map +1 -0
- package/dist/geometry.d.ts +75 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +671 -0
- package/dist/geometry.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/lights.d.ts +64 -0
- package/dist/lights.d.ts.map +1 -0
- package/dist/lights.js +264 -0
- package/dist/lights.js.map +1 -0
- package/dist/materialLayout.d.ts +143 -0
- package/dist/materialLayout.d.ts.map +1 -0
- package/dist/materialLayout.js +297 -0
- package/dist/materialLayout.js.map +1 -0
- package/dist/math.d.ts +10 -0
- package/dist/math.d.ts.map +1 -0
- package/dist/math.js +40 -0
- package/dist/math.js.map +1 -0
- package/dist/pathTracePass.d.ts +27 -0
- package/dist/pathTracePass.d.ts.map +1 -0
- package/dist/pathTracePass.js +83 -0
- package/dist/pathTracePass.js.map +1 -0
- package/dist/planarCapture.d.ts +38 -0
- package/dist/planarCapture.d.ts.map +1 -0
- package/dist/planarCapture.js +163 -0
- package/dist/planarCapture.js.map +1 -0
- package/dist/presentPass.d.ts +31 -0
- package/dist/presentPass.d.ts.map +1 -0
- package/dist/presentPass.js +78 -0
- package/dist/presentPass.js.map +1 -0
- package/dist/renderer.d.ts +196 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +839 -0
- package/dist/renderer.js.map +1 -0
- package/dist/sceneAssets.d.ts +24 -0
- package/dist/sceneAssets.d.ts.map +1 -0
- package/dist/sceneAssets.js +77 -0
- package/dist/sceneAssets.js.map +1 -0
- package/dist/sceneUpload.d.ts +61 -0
- package/dist/sceneUpload.d.ts.map +1 -0
- package/dist/sceneUpload.js +98 -0
- package/dist/sceneUpload.js.map +1 -0
- package/dist/shaderModule.d.ts +7 -0
- package/dist/shaderModule.d.ts.map +1 -0
- package/dist/shaderModule.js +19 -0
- package/dist/shaderModule.js.map +1 -0
- package/dist/shaderProgram.d.ts +124 -0
- package/dist/shaderProgram.d.ts.map +1 -0
- package/dist/shaderProgram.js +1030 -0
- package/dist/shaderProgram.js.map +1 -0
- package/dist/structLayout.d.ts +28 -0
- package/dist/structLayout.d.ts.map +1 -0
- package/dist/structLayout.js +58 -0
- package/dist/structLayout.js.map +1 -0
- package/dist/texturePack.d.ts +54 -0
- package/dist/texturePack.d.ts.map +1 -0
- package/dist/texturePack.js +86 -0
- package/dist/texturePack.js.map +1 -0
- package/dist/upstream.d.ts +7 -0
- package/dist/upstream.d.ts.map +1 -0
- package/dist/upstream.js +11 -0
- package/dist/upstream.js.map +1 -0
- package/dist/volumetric.d.ts +10 -0
- package/dist/volumetric.d.ts.map +1 -0
- package/dist/volumetric.js +34 -0
- package/dist/volumetric.js.map +1 -0
- package/dist/wgsl/bsdf.wgsl.d.ts +13 -0
- package/dist/wgsl/bsdf.wgsl.d.ts.map +1 -0
- package/dist/wgsl/bsdf.wgsl.js +619 -0
- package/dist/wgsl/bsdf.wgsl.js.map +1 -0
- package/dist/wgsl/bvh.wgsl.d.ts +3 -0
- package/dist/wgsl/bvh.wgsl.d.ts.map +1 -0
- package/dist/wgsl/bvh.wgsl.js +218 -0
- package/dist/wgsl/bvh.wgsl.js.map +1 -0
- package/dist/wgsl/common.wgsl.d.ts +3 -0
- package/dist/wgsl/common.wgsl.d.ts.map +1 -0
- package/dist/wgsl/common.wgsl.js +193 -0
- package/dist/wgsl/common.wgsl.js.map +1 -0
- package/dist/wgsl/environment.wgsl.d.ts +10 -0
- package/dist/wgsl/environment.wgsl.d.ts.map +1 -0
- package/dist/wgsl/environment.wgsl.js +145 -0
- package/dist/wgsl/environment.wgsl.js.map +1 -0
- package/dist/wgsl/lights.wgsl.d.ts +10 -0
- package/dist/wgsl/lights.wgsl.d.ts.map +1 -0
- package/dist/wgsl/lights.wgsl.js +434 -0
- package/dist/wgsl/lights.wgsl.js.map +1 -0
- package/dist/wgsl/pathTrace.wgsl.d.ts +14 -0
- package/dist/wgsl/pathTrace.wgsl.d.ts.map +1 -0
- package/dist/wgsl/pathTrace.wgsl.js +1020 -0
- package/dist/wgsl/pathTrace.wgsl.js.map +1 -0
- package/dist/wgsl/present.wgsl.d.ts +7 -0
- package/dist/wgsl/present.wgsl.d.ts.map +1 -0
- package/dist/wgsl/present.wgsl.js +154 -0
- package/dist/wgsl/present.wgsl.js.map +1 -0
- package/dist/wgsl/random.wgsl.d.ts +9 -0
- package/dist/wgsl/random.wgsl.d.ts.map +1 -0
- package/dist/wgsl/random.wgsl.js +91 -0
- package/dist/wgsl/random.wgsl.js.map +1 -0
- package/dist/wgsl/sampling.wgsl.d.ts +3 -0
- package/dist/wgsl/sampling.wgsl.d.ts.map +1 -0
- package/dist/wgsl/sampling.wgsl.js +31 -0
- package/dist/wgsl/sampling.wgsl.js.map +1 -0
- package/dist/wgsl/shaderSupport.wgsl.d.ts +6 -0
- package/dist/wgsl/shaderSupport.wgsl.d.ts.map +1 -0
- package/dist/wgsl/shaderSupport.wgsl.js +120 -0
- package/dist/wgsl/shaderSupport.wgsl.js.map +1 -0
- package/dist/wgsl/surface.wgsl.d.ts +10 -0
- package/dist/wgsl/surface.wgsl.d.ts.map +1 -0
- package/dist/wgsl/surface.wgsl.js +794 -0
- package/dist/wgsl/surface.wgsl.js.map +1 -0
- package/dist/wgsl/texture.wgsl.d.ts +11 -0
- package/dist/wgsl/texture.wgsl.d.ts.map +1 -0
- package/dist/wgsl/texture.wgsl.js +132 -0
- package/dist/wgsl/texture.wgsl.js.map +1 -0
- package/package.json +76 -3
- package/src/accumulator.ts +64 -0
- package/src/bvh.ts +272 -0
- package/src/camera.ts +77 -0
- package/src/canvas.ts +13 -0
- package/src/denoise.ts +514 -0
- package/src/device.ts +81 -0
- package/src/dispatchScheduler.ts +71 -0
- package/src/environment.ts +233 -0
- package/src/frameLayout.ts +59 -0
- package/src/geometry.ts +899 -0
- package/src/index.ts +61 -0
- package/src/lights.ts +365 -0
- package/src/materialLayout.ts +408 -0
- package/src/math.ts +48 -0
- package/src/pathTracePass.ts +97 -0
- package/src/planarCapture.ts +225 -0
- package/src/presentPass.ts +99 -0
- package/src/renderer.ts +1117 -0
- package/src/sceneAssets.ts +118 -0
- package/src/sceneUpload.ts +185 -0
- package/src/shaderModule.ts +19 -0
- package/src/shaderProgram.ts +1370 -0
- package/src/structLayout.ts +93 -0
- package/src/texturePack.ts +152 -0
- package/src/upstream.ts +56 -0
- package/src/volumetric.ts +42 -0
- package/src/wgsl/bsdf.wgsl.ts +618 -0
- package/src/wgsl/bvh.wgsl.ts +217 -0
- package/src/wgsl/common.wgsl.ts +193 -0
- package/src/wgsl/environment.wgsl.ts +144 -0
- package/src/wgsl/lights.wgsl.ts +433 -0
- package/src/wgsl/pathTrace.wgsl.ts +1019 -0
- package/src/wgsl/present.wgsl.ts +153 -0
- package/src/wgsl/random.wgsl.ts +90 -0
- package/src/wgsl/sampling.wgsl.ts +30 -0
- package/src/wgsl/shaderSupport.wgsl.ts +119 -0
- package/src/wgsl/surface.wgsl.ts +793 -0
- package/src/wgsl/texture.wgsl.ts +131 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the samples pile up: one `vec4<f32>` per pixel, rgb summed radiance and w the number of samples that reached
|
|
3
|
+
* it, then a second `vec4<f32>` per pixel whose x sums the coverage, which is what a transparent background reads as
|
|
4
|
+
* alpha, y the depth of the first surface, z and w the squared luminance of the diffuse and the specular signal, which
|
|
5
|
+
* give each its variance. Then the denoiser's guides and signals, each summed: the diffuse albedo with the roughness,
|
|
6
|
+
* the normal with the distance, the diffuse radiance with the specular hit distance, and the specular albedo. Last comes
|
|
7
|
+
* one `vec4<f32>` per row, set when a pass traced anything in that row: adaptive sampling reads it back to know which
|
|
8
|
+
* rows still have pixels that have not converged. A storage buffer rather than a storage texture, because `rgba32float` only allows write access as a texture,
|
|
9
|
+
* and the presentation pass has to read back what is already there.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Accumulator {
|
|
12
|
+
private readonly device;
|
|
13
|
+
buffer: GPUBuffer;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
constructor(device: GPUDevice, width: number, height: number);
|
|
17
|
+
get byteLength(): number;
|
|
18
|
+
/** Where the per-row flags start, in bytes. */
|
|
19
|
+
get rowFlagsOffset(): number;
|
|
20
|
+
get rowFlagsLength(): number;
|
|
21
|
+
/** Reallocates only when the size actually changed; the caller clears it either way. */
|
|
22
|
+
resize(width: number, height: number): boolean;
|
|
23
|
+
clear(encoder: GPUCommandEncoder): void;
|
|
24
|
+
destroy(): void;
|
|
25
|
+
private create;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=accumulator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accumulator.d.ts","sourceRoot":"","sources":["../src/accumulator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,WAAW;IAKV,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;gBAEc,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAM7E,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,+CAA+C;IAC/C,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,wFAAwF;IACxF,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;IAa9C,KAAK,CAAC,OAAO,EAAE,iBAAiB;IAIhC,OAAO;IAIP,OAAO,CAAC,MAAM;CAOf"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the samples pile up: one `vec4<f32>` per pixel, rgb summed radiance and w the number of samples that reached
|
|
3
|
+
* it, then a second `vec4<f32>` per pixel whose x sums the coverage, which is what a transparent background reads as
|
|
4
|
+
* alpha, y the depth of the first surface, z and w the squared luminance of the diffuse and the specular signal, which
|
|
5
|
+
* give each its variance. Then the denoiser's guides and signals, each summed: the diffuse albedo with the roughness,
|
|
6
|
+
* the normal with the distance, the diffuse radiance with the specular hit distance, and the specular albedo. Last comes
|
|
7
|
+
* one `vec4<f32>` per row, set when a pass traced anything in that row: adaptive sampling reads it back to know which
|
|
8
|
+
* rows still have pixels that have not converged. A storage buffer rather than a storage texture, because `rgba32float` only allows write access as a texture,
|
|
9
|
+
* and the presentation pass has to read back what is already there.
|
|
10
|
+
*/
|
|
11
|
+
export class Accumulator {
|
|
12
|
+
device;
|
|
13
|
+
buffer;
|
|
14
|
+
width;
|
|
15
|
+
height;
|
|
16
|
+
constructor(device, width, height) {
|
|
17
|
+
this.device = device;
|
|
18
|
+
this.width = Math.max(1, Math.floor(width));
|
|
19
|
+
this.height = Math.max(1, Math.floor(height));
|
|
20
|
+
this.buffer = this.create();
|
|
21
|
+
}
|
|
22
|
+
get byteLength() {
|
|
23
|
+
return (this.width * this.height * 6 + this.height) * 16;
|
|
24
|
+
}
|
|
25
|
+
/** Where the per-row flags start, in bytes. */
|
|
26
|
+
get rowFlagsOffset() {
|
|
27
|
+
return this.width * this.height * 96;
|
|
28
|
+
}
|
|
29
|
+
get rowFlagsLength() {
|
|
30
|
+
return this.height * 16;
|
|
31
|
+
}
|
|
32
|
+
/** Reallocates only when the size actually changed; the caller clears it either way. */
|
|
33
|
+
resize(width, height) {
|
|
34
|
+
const next = { width: Math.max(1, Math.floor(width)), height: Math.max(1, Math.floor(height)) };
|
|
35
|
+
if (next.width === this.width && next.height === this.height)
|
|
36
|
+
return false;
|
|
37
|
+
this.buffer.destroy();
|
|
38
|
+
this.width = next.width;
|
|
39
|
+
this.height = next.height;
|
|
40
|
+
this.buffer = this.create();
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
clear(encoder) {
|
|
44
|
+
encoder.clearBuffer(this.buffer);
|
|
45
|
+
}
|
|
46
|
+
destroy() {
|
|
47
|
+
this.buffer.destroy();
|
|
48
|
+
}
|
|
49
|
+
create() {
|
|
50
|
+
return this.device.createBuffer({
|
|
51
|
+
size: this.byteLength,
|
|
52
|
+
// clearBuffer is a copy into the buffer: without COPY_DST it fails validation and clears nothing.
|
|
53
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=accumulator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accumulator.js","sourceRoot":"","sources":["../src/accumulator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,OAAO,WAAW;IAKO;IAJ7B,MAAM,CAAY;IAClB,KAAK,CAAS;IACd,MAAM,CAAS;IAEf,YAA6B,MAAiB,EAAE,KAAa,EAAE,MAAc;QAAhD,WAAM,GAAN,MAAM,CAAW;QAC5C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC9B,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;IAC3D,CAAC;IAED,+CAA+C;IAC/C,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACvC,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IAC1B,CAAC;IAED,wFAAwF;IACxF,MAAM,CAAC,KAAa,EAAE,MAAc;QAClC,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAEhG,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAE3E,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACtB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAE5B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,OAA0B;QAC9B,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,OAAO;QACL,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAEO,MAAM;QACZ,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC;YAC9B,IAAI,EAAE,IAAI,CAAC,UAAU;YACrB,kGAAkG;YAClG,KAAK,EAAE,cAAc,CAAC,OAAO,GAAG,cAAc,CAAC,QAAQ,GAAG,cAAc,CAAC,QAAQ;SAClF,CAAC,CAAC;IACL,CAAC;CACF"}
|
package/dist/bvh.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binned SAH bounding volume hierarchy, built straight into the layout the shader reads: eight words per node, the
|
|
3
|
+
* first four a minimum corner and a left child or leaf start, the last four a maximum corner and a right child or
|
|
4
|
+
* triangle count, matching `struct BvhNode` in `wgsl/bvh.wgsl.ts`.
|
|
5
|
+
*
|
|
6
|
+
* The bounds are computed from the same `Float32Array` the shader traverses, so every corner is already a float and
|
|
7
|
+
* needs no widening. Building from double precision positions, as the CPU renderer does, would need each minimum
|
|
8
|
+
* rounded down and each maximum rounded up before they reach the GPU.
|
|
9
|
+
*/
|
|
10
|
+
/** Words per node in the packed buffer. */
|
|
11
|
+
export declare const NODE_WORDS = 8;
|
|
12
|
+
/**
|
|
13
|
+
* Deepest node the builder produces. The shader traverses with a fixed stack, and one entry per level is enough
|
|
14
|
+
* because a node only pushes its far child. Depth is capped rather than trusted.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_BVH_DEPTH = 32;
|
|
17
|
+
export interface PackedBvh {
|
|
18
|
+
/** `nodeCount * NODE_WORDS` words, read as floats for the corners and as signed integers for the links. */
|
|
19
|
+
nodes: ArrayBuffer;
|
|
20
|
+
nodeCount: number;
|
|
21
|
+
/** Triangle indices in leaf order: a leaf covers `order[start] .. order[start + count - 1]`. */
|
|
22
|
+
order: Uint32Array;
|
|
23
|
+
maxDepth: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function buildBvh(positions: Float32Array, triangleCount: number): PackedBvh;
|
|
26
|
+
//# sourceMappingURL=bvh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bvh.d.ts","sourceRoot":"","sources":["../src/bvh.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,2CAA2C;AAC3C,eAAO,MAAM,UAAU,IAAI,CAAC;AAW5B;;;GAGG;AACH,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,MAAM,WAAW,SAAS;IACxB,2GAA2G;IAC3G,KAAK,EAAE,WAAW,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,gGAAgG;IAChG,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAgED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,CA4ElF"}
|
package/dist/bvh.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binned SAH bounding volume hierarchy, built straight into the layout the shader reads: eight words per node, the
|
|
3
|
+
* first four a minimum corner and a left child or leaf start, the last four a maximum corner and a right child or
|
|
4
|
+
* triangle count, matching `struct BvhNode` in `wgsl/bvh.wgsl.ts`.
|
|
5
|
+
*
|
|
6
|
+
* The bounds are computed from the same `Float32Array` the shader traverses, so every corner is already a float and
|
|
7
|
+
* needs no widening. Building from double precision positions, as the CPU renderer does, would need each minimum
|
|
8
|
+
* rounded down and each maximum rounded up before they reach the GPU.
|
|
9
|
+
*/
|
|
10
|
+
/** Words per node in the packed buffer. */
|
|
11
|
+
export const NODE_WORDS = 8;
|
|
12
|
+
/** Triangles a leaf may hold. */
|
|
13
|
+
const MAX_LEAF_SIZE = 4;
|
|
14
|
+
/** Candidate split positions evaluated per axis. */
|
|
15
|
+
const BIN_COUNT = 12;
|
|
16
|
+
/** What descending one more level costs, relative to intersecting one triangle. */
|
|
17
|
+
const TRAVERSAL_COST = 0.125;
|
|
18
|
+
/**
|
|
19
|
+
* Deepest node the builder produces. The shader traverses with a fixed stack, and one entry per level is enough
|
|
20
|
+
* because a node only pushes its far child. Depth is capped rather than trusted.
|
|
21
|
+
*/
|
|
22
|
+
export const MAX_BVH_DEPTH = 32;
|
|
23
|
+
/** Axis aligned bounds under construction, in doubles so the sweep does not accumulate rounding. */
|
|
24
|
+
class Bounds {
|
|
25
|
+
min = [Infinity, Infinity, Infinity];
|
|
26
|
+
max = [-Infinity, -Infinity, -Infinity];
|
|
27
|
+
reset() {
|
|
28
|
+
this.min = [Infinity, Infinity, Infinity];
|
|
29
|
+
this.max = [-Infinity, -Infinity, -Infinity];
|
|
30
|
+
}
|
|
31
|
+
addPoint(x, y, z) {
|
|
32
|
+
const point = [x, y, z];
|
|
33
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
34
|
+
if (point[axis] < this.min[axis])
|
|
35
|
+
this.min[axis] = point[axis];
|
|
36
|
+
if (point[axis] > this.max[axis])
|
|
37
|
+
this.max[axis] = point[axis];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
addBounds(other) {
|
|
41
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
42
|
+
if (other.min[axis] < this.min[axis])
|
|
43
|
+
this.min[axis] = other.min[axis];
|
|
44
|
+
if (other.max[axis] > this.max[axis])
|
|
45
|
+
this.max[axis] = other.max[axis];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Surface area, the cost the split heuristic weighs each side by. Empty bounds cost nothing. */
|
|
49
|
+
area() {
|
|
50
|
+
const x = this.max[0] - this.min[0], y = this.max[1] - this.min[1], z = this.max[2] - this.min[2];
|
|
51
|
+
return x < 0 || y < 0 || z < 0 ? 0 : 2 * (x * y + y * z + z * x);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Per-triangle bounds and centroids, the only inputs the split needs. */
|
|
55
|
+
function triangleBounds(positions, triangleCount) {
|
|
56
|
+
const min = new Float32Array(triangleCount * 3);
|
|
57
|
+
const max = new Float32Array(triangleCount * 3);
|
|
58
|
+
const centroid = new Float64Array(triangleCount * 3);
|
|
59
|
+
for (let triangle = 0; triangle < triangleCount; triangle++) {
|
|
60
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
61
|
+
const a = positions[triangle * 9 + axis];
|
|
62
|
+
const b = positions[triangle * 9 + 3 + axis];
|
|
63
|
+
const c = positions[triangle * 9 + 6 + axis];
|
|
64
|
+
min[triangle * 3 + axis] = Math.min(a, b, c);
|
|
65
|
+
max[triangle * 3 + axis] = Math.max(a, b, c);
|
|
66
|
+
centroid[triangle * 3 + axis] = (a + b + c) / 3;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { min, max, centroid };
|
|
70
|
+
}
|
|
71
|
+
export function buildBvh(positions, triangleCount) {
|
|
72
|
+
const order = new Uint32Array(triangleCount);
|
|
73
|
+
for (let i = 0; i < triangleCount; i++)
|
|
74
|
+
order[i] = i;
|
|
75
|
+
if (triangleCount === 0)
|
|
76
|
+
return { nodes: new ArrayBuffer(0), nodeCount: 0, order, maxDepth: 0 };
|
|
77
|
+
const { min, max, centroid } = triangleBounds(positions, triangleCount);
|
|
78
|
+
// The heuristic may split down to one triangle per leaf, so the pool starts at the common case and grows.
|
|
79
|
+
let nodes = new ArrayBuffer(Math.max(4, Math.ceil(triangleCount / 2)) * NODE_WORDS * 4);
|
|
80
|
+
let floats = new Float32Array(nodes);
|
|
81
|
+
let ints = new Int32Array(nodes);
|
|
82
|
+
const reserve = (needed) => {
|
|
83
|
+
if (needed * NODE_WORDS * 4 <= nodes.byteLength)
|
|
84
|
+
return;
|
|
85
|
+
const grown = new ArrayBuffer(Math.max(needed, Math.ceil(nodes.byteLength / (NODE_WORDS * 4) * 1.6))
|
|
86
|
+
* NODE_WORDS * 4);
|
|
87
|
+
new Float32Array(grown).set(floats);
|
|
88
|
+
nodes = grown;
|
|
89
|
+
floats = new Float32Array(nodes);
|
|
90
|
+
ints = new Int32Array(nodes);
|
|
91
|
+
};
|
|
92
|
+
const stack = [{ start: 0, count: triangleCount, node: 0, depth: 0 }];
|
|
93
|
+
const bounds = new Bounds();
|
|
94
|
+
let nodeCount = 1;
|
|
95
|
+
let maxDepth = 0;
|
|
96
|
+
while (stack.length) {
|
|
97
|
+
const range = stack.pop();
|
|
98
|
+
maxDepth = Math.max(maxDepth, range.depth);
|
|
99
|
+
bounds.reset();
|
|
100
|
+
for (let i = range.start; i < range.start + range.count; i++) {
|
|
101
|
+
const triangle = order[i] * 3;
|
|
102
|
+
bounds.addPoint(min[triangle], min[triangle + 1], min[triangle + 2]);
|
|
103
|
+
bounds.addPoint(max[triangle], max[triangle + 1], max[triangle + 2]);
|
|
104
|
+
}
|
|
105
|
+
const word = range.node * NODE_WORDS;
|
|
106
|
+
floats[word] = bounds.min[0];
|
|
107
|
+
floats[word + 1] = bounds.min[1];
|
|
108
|
+
floats[word + 2] = bounds.min[2];
|
|
109
|
+
floats[word + 4] = bounds.max[0];
|
|
110
|
+
floats[word + 5] = bounds.max[1];
|
|
111
|
+
floats[word + 6] = bounds.max[2];
|
|
112
|
+
const split = range.count > MAX_LEAF_SIZE && range.depth + 1 < MAX_BVH_DEPTH
|
|
113
|
+
? partition(order, centroid, min, max, range, bounds.area())
|
|
114
|
+
: -1;
|
|
115
|
+
if (split < 0) {
|
|
116
|
+
// A leaf points into `order` instead of into the node array, which the sign of the first link tells apart.
|
|
117
|
+
ints[word + 3] = -1 - range.start;
|
|
118
|
+
ints[word + 7] = range.count;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
reserve(nodeCount + 2);
|
|
122
|
+
const left = nodeCount++;
|
|
123
|
+
const right = nodeCount++;
|
|
124
|
+
ints[word + 3] = left;
|
|
125
|
+
ints[word + 7] = right;
|
|
126
|
+
stack.push({ start: range.start, count: split - range.start, node: left, depth: range.depth + 1 });
|
|
127
|
+
stack.push({ start: split, count: range.start + range.count - split, node: right, depth: range.depth + 1 });
|
|
128
|
+
}
|
|
129
|
+
return { nodes: nodes.slice(0, nodeCount * NODE_WORDS * 4), nodeCount, order, maxDepth };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Reorders `order` in place so the range splits in two, and returns where the right side starts, or -1 when no split
|
|
133
|
+
* beats keeping the range as a leaf. Bins the centroids on all three axes and sweeps the surface area heuristic.
|
|
134
|
+
*/
|
|
135
|
+
function partition(order, centroid, min, max, range, parentArea) {
|
|
136
|
+
if (parentArea <= 0)
|
|
137
|
+
return -1;
|
|
138
|
+
const end = range.start + range.count;
|
|
139
|
+
const centroidBounds = new Bounds();
|
|
140
|
+
for (let i = range.start; i < end; i++) {
|
|
141
|
+
const triangle = order[i] * 3;
|
|
142
|
+
centroidBounds.addPoint(centroid[triangle], centroid[triangle + 1], centroid[triangle + 2]);
|
|
143
|
+
}
|
|
144
|
+
// Splitting costs a traversal step, so a split only wins when it beats shading the whole range as one leaf.
|
|
145
|
+
let bestCost = range.count, bestAxis = -1, bestBin = -1, bestScale = 0;
|
|
146
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
147
|
+
const extent = centroidBounds.max[axis] - centroidBounds.min[axis];
|
|
148
|
+
// Every centroid sits at the same place on this axis: no split here separates anything.
|
|
149
|
+
if (extent <= 0)
|
|
150
|
+
continue;
|
|
151
|
+
const scale = BIN_COUNT / extent;
|
|
152
|
+
const binBounds = Array.from({ length: BIN_COUNT }, () => new Bounds());
|
|
153
|
+
const binCounts = new Int32Array(BIN_COUNT);
|
|
154
|
+
for (let i = range.start; i < end; i++) {
|
|
155
|
+
const triangle = order[i] * 3;
|
|
156
|
+
const bin = binOf(centroid[triangle + axis], centroidBounds.min[axis], scale);
|
|
157
|
+
binCounts[bin]++;
|
|
158
|
+
binBounds[bin].addPoint(min[triangle], min[triangle + 1], min[triangle + 2]);
|
|
159
|
+
binBounds[bin].addPoint(max[triangle], max[triangle + 1], max[triangle + 2]);
|
|
160
|
+
}
|
|
161
|
+
// Sweeping once from each side turns the heuristic into two passes instead of one per candidate split.
|
|
162
|
+
const rightArea = new Float64Array(BIN_COUNT);
|
|
163
|
+
const rightCount = new Int32Array(BIN_COUNT);
|
|
164
|
+
const accumulated = new Bounds();
|
|
165
|
+
let running = 0;
|
|
166
|
+
for (let bin = BIN_COUNT - 1; bin > 0; bin--) {
|
|
167
|
+
accumulated.addBounds(binBounds[bin]);
|
|
168
|
+
running += binCounts[bin];
|
|
169
|
+
rightArea[bin] = accumulated.area();
|
|
170
|
+
rightCount[bin] = running;
|
|
171
|
+
}
|
|
172
|
+
accumulated.reset();
|
|
173
|
+
running = 0;
|
|
174
|
+
for (let bin = 0; bin < BIN_COUNT - 1; bin++) {
|
|
175
|
+
accumulated.addBounds(binBounds[bin]);
|
|
176
|
+
running += binCounts[bin];
|
|
177
|
+
if (!running || !rightCount[bin + 1])
|
|
178
|
+
continue;
|
|
179
|
+
const cost = TRAVERSAL_COST
|
|
180
|
+
+ (accumulated.area() * running + rightArea[bin + 1] * rightCount[bin + 1]) / parentArea;
|
|
181
|
+
if (cost < bestCost) {
|
|
182
|
+
bestCost = cost;
|
|
183
|
+
bestAxis = axis;
|
|
184
|
+
bestBin = bin;
|
|
185
|
+
bestScale = scale;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (bestAxis < 0)
|
|
190
|
+
return -1;
|
|
191
|
+
const pivot = centroidBounds.min[bestAxis];
|
|
192
|
+
let left = range.start;
|
|
193
|
+
for (let i = range.start; i < end; i++) {
|
|
194
|
+
if (binOf(centroid[order[i] * 3 + bestAxis], pivot, bestScale) <= bestBin) {
|
|
195
|
+
const swap = order[i];
|
|
196
|
+
order[i] = order[left];
|
|
197
|
+
order[left] = swap;
|
|
198
|
+
left++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// The sweep only proposes splits both sides occupy, so this is a guard against rounding, not an expected outcome.
|
|
202
|
+
return left === range.start || left === end ? -1 : left;
|
|
203
|
+
}
|
|
204
|
+
function binOf(value, origin, scale) {
|
|
205
|
+
return Math.min(BIN_COUNT - 1, Math.max(0, Math.floor((value - origin) * scale)));
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=bvh.js.map
|
package/dist/bvh.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bvh.js","sourceRoot":"","sources":["../src/bvh.ts"],"names":[],"mappings":"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"}
|
package/dist/camera.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Matrix4 } from "three";
|
|
2
|
+
import type { CameraState } from "./upstream.js";
|
|
3
|
+
/**
|
|
4
|
+
* Everything the renderer derives from a camera snapshot. Rays are cast through the inverse of the camera's own
|
|
5
|
+
* projection, so zoom, film offsets, `setViewOffset` and any custom projection come out exactly as Three draws them,
|
|
6
|
+
* and so do the near and far planes, which clip a camera ray the way they clip a rasterized fragment.
|
|
7
|
+
*/
|
|
8
|
+
export interface CameraMatrices {
|
|
9
|
+
/** Column-major, as Three stores them. */
|
|
10
|
+
cameraWorld: number[];
|
|
11
|
+
view: number[];
|
|
12
|
+
projection: number[];
|
|
13
|
+
inverseProjection: number[];
|
|
14
|
+
orthographic: boolean;
|
|
15
|
+
near: number;
|
|
16
|
+
far: number;
|
|
17
|
+
/** Vertical field of view in degrees, for the lens; 50 when the snapshot has none. */
|
|
18
|
+
fov: number;
|
|
19
|
+
position: number[];
|
|
20
|
+
}
|
|
21
|
+
/** The camera's projection: the snapshot's own when it has one, otherwise rebuilt from its parameters. */
|
|
22
|
+
export declare function cameraProjection(camera: CameraState, aspect: number): Matrix4;
|
|
23
|
+
export declare function cameraMatrices(camera: CameraState, width: number, height: number): CameraMatrices;
|
|
24
|
+
/** A column-major 4x4 matrix times a point, with the w it produced. */
|
|
25
|
+
export declare function transform4(matrix: ArrayLike<number>, x: number, y: number, z: number, w?: number): number[];
|
|
26
|
+
/** A point in clip space back to world space, through the camera that produced it. */
|
|
27
|
+
export declare function clipToWorld(camera: CameraMatrices, clip: readonly number[]): number[];
|
|
28
|
+
export declare function worldToClip(camera: CameraMatrices, point: readonly number[]): number[];
|
|
29
|
+
//# sourceMappingURL=camera.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAyC,MAAM,OAAO,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,sFAAsF;IACtF,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,0GAA0G;AAC1G,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAW7E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAgBjG;AAED,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,SAAI,GAAG,MAAM,EAAE,CAOtG;AAED,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAKrF;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAItF"}
|
package/dist/camera.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Matrix4, OrthographicCamera, PerspectiveCamera } from "three";
|
|
2
|
+
/** The camera's projection: the snapshot's own when it has one, otherwise rebuilt from its parameters. */
|
|
3
|
+
export function cameraProjection(camera, aspect) {
|
|
4
|
+
if (camera.projectionMatrix)
|
|
5
|
+
return new Matrix4().fromArray(camera.projectionMatrix);
|
|
6
|
+
const projection = camera.type === "orthographic"
|
|
7
|
+
? new OrthographicCamera(camera.left, camera.right, camera.top, camera.bottom, camera.near, camera.far)
|
|
8
|
+
: new PerspectiveCamera(camera.fov ?? 50, camera.aspect ?? aspect, camera.near, camera.far);
|
|
9
|
+
projection.zoom = camera.zoom ?? 1;
|
|
10
|
+
projection.updateProjectionMatrix();
|
|
11
|
+
return projection.projectionMatrix;
|
|
12
|
+
}
|
|
13
|
+
export function cameraMatrices(camera, width, height) {
|
|
14
|
+
const world = new Matrix4().fromArray(camera.matrixWorld);
|
|
15
|
+
const projection = cameraProjection(camera, width / height);
|
|
16
|
+
const orthographic = camera.type === "orthographic" || (projection.elements[11] === 0 && projection.elements[15] === 1);
|
|
17
|
+
return {
|
|
18
|
+
cameraWorld: world.toArray(),
|
|
19
|
+
view: world.clone().invert().toArray(),
|
|
20
|
+
projection: projection.toArray(),
|
|
21
|
+
inverseProjection: projection.clone().invert().toArray(),
|
|
22
|
+
orthographic,
|
|
23
|
+
near: camera.near,
|
|
24
|
+
far: camera.far,
|
|
25
|
+
fov: camera.fov ?? 50,
|
|
26
|
+
position: [camera.matrixWorld[12], camera.matrixWorld[13], camera.matrixWorld[14]],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** A column-major 4x4 matrix times a point, with the w it produced. */
|
|
30
|
+
export function transform4(matrix, x, y, z, w = 1) {
|
|
31
|
+
return [
|
|
32
|
+
matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12] * w,
|
|
33
|
+
matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13] * w,
|
|
34
|
+
matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14] * w,
|
|
35
|
+
matrix[3] * x + matrix[7] * y + matrix[11] * z + matrix[15] * w,
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
/** A point in clip space back to world space, through the camera that produced it. */
|
|
39
|
+
export function clipToWorld(camera, clip) {
|
|
40
|
+
const view = transform4(camera.inverseProjection, clip[0], clip[1], clip[2], clip[3]);
|
|
41
|
+
const w = view[3] || 1e-30;
|
|
42
|
+
return transform4(camera.cameraWorld, view[0] / w, view[1] / w, view[2] / w).slice(0, 3);
|
|
43
|
+
}
|
|
44
|
+
export function worldToClip(camera, point) {
|
|
45
|
+
const view = transform4(camera.view, point[0], point[1], point[2]);
|
|
46
|
+
return transform4(camera.projection, view[0], view[1], view[2], view[3]);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=camera.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera.js","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"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"}
|
package/dist/canvas.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { GpuRenderer } from "./renderer.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sends the renderer's output to a canvas. It configures the WebGPU context, sizes the canvas to the render
|
|
4
|
+
* resolution and presents after every submit, so the image refines in place while it converges.
|
|
5
|
+
*
|
|
6
|
+
* Returns the function that detaches it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function attachCanvas(renderer: GpuRenderer, canvas: HTMLCanvasElement): () => void;
|
|
9
|
+
//# sourceMappingURL=canvas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAIzF"}
|
package/dist/canvas.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sends the renderer's output to a canvas. It configures the WebGPU context, sizes the canvas to the render
|
|
3
|
+
* resolution and presents after every submit, so the image refines in place while it converges.
|
|
4
|
+
*
|
|
5
|
+
* Returns the function that detaches it.
|
|
6
|
+
*/
|
|
7
|
+
export function attachCanvas(renderer, canvas) {
|
|
8
|
+
renderer.setOutputCanvas(canvas);
|
|
9
|
+
return () => renderer.setOutputCanvas(null);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=canvas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas.js","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,QAAqB,EAAE,MAAyB;IAC3E,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAEjC,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** How the denoiser filters. Every field is optional where it is taken, and falls back to `DEFAULT_DENOISER`. */
|
|
2
|
+
export interface DenoiserSettings {
|
|
3
|
+
/** How much of the filtered image replaces the traced one, from 0 (none) to 1. */
|
|
4
|
+
strength: number;
|
|
5
|
+
/** Wavelet passes, 1 to 5: how far the filter reaches, from 2 to 62 pixels. */
|
|
6
|
+
passes: number;
|
|
7
|
+
/**
|
|
8
|
+
* How large a luminance difference between two pixels still counts as noise, in standard deviations of the pixel's
|
|
9
|
+
* own noise. Lower keeps more detail and removes less noise.
|
|
10
|
+
*/
|
|
11
|
+
edge: number;
|
|
12
|
+
/** Leave the pixels that have converged as they were traced, and filter only where noise is still visible. */
|
|
13
|
+
onlyNoisy: boolean;
|
|
14
|
+
/** Clamp a pixel brighter than every one of its neighbours to the brightest of them, before filtering. */
|
|
15
|
+
antiFirefly: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_DENOISER: Readonly<DenoiserSettings>;
|
|
18
|
+
/** The settings with `input` applied, each field kept in range. */
|
|
19
|
+
export declare function mergeDenoiser(base: DenoiserSettings, input?: Partial<DenoiserSettings>): DenoiserSettings;
|
|
20
|
+
/** The error a pixel counts as converged under, the one adaptive sampling stops at. */
|
|
21
|
+
export declare const CONVERGED_ERROR = 0.004;
|
|
22
|
+
/** What the camera says about how large things look, which the specular filter's radius depends on. */
|
|
23
|
+
export interface DenoiseView {
|
|
24
|
+
/** World units one pixel spans at unit distance (perspective) or anywhere (orthographic). */
|
|
25
|
+
pixelScale: number;
|
|
26
|
+
orthographic: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The denoiser: the spatial half of SVGF (Schied and others, 2017), with the diffuse and the specular light filtered
|
|
30
|
+
* apart, as NVIDIA's RELAX and REBLUR do.
|
|
31
|
+
*
|
|
32
|
+
* The path tracer splits every path at its first scatter, by the lobe it took, so each pixel holds a diffuse and a
|
|
33
|
+
* specular signal, each with its own variance. Each is divided by its own albedo (the diffuse colour, and the GGX
|
|
34
|
+
* lobe's directional albedo), so the filter sees light and never blurs a texture or a colour; both are multiplied back
|
|
35
|
+
* at the end. A pixel brighter than all of its neighbours is first clamped to the brightest of them.
|
|
36
|
+
*
|
|
37
|
+
* Then a few passes of an edge-avoiding a-trous wavelet, its taps twice as far apart each pass. The diffuse light is
|
|
38
|
+
* weighed by the agreement of normals and depths and by its luminance against the pixel's own variance. The specular
|
|
39
|
+
* light also by roughness, by normals more sharply the smoother the surface, and within the footprint its lobe leaves on
|
|
40
|
+
* the screen: the lobe's angle times the distance to what it reflects, seen from the camera. A sharp reflection of
|
|
41
|
+
* something near is left almost alone; a rough reflection of something far is smoothed across a wide area.
|
|
42
|
+
*
|
|
43
|
+
* The weights are normalized, which keeps every flat area at exactly its mean, and each signal is mixed with what was
|
|
44
|
+
* traced by how noisy it still is, so what has converged keeps exactly its traced value.
|
|
45
|
+
*/
|
|
46
|
+
export declare const DENOISE_WGSL = "\nstruct Params {\n size: vec2<u32>,\n step: u32,\n count: u32,\n edge: f32,\n strength: f32,\n onlyNoisy: u32,\n converged: f32,\n pixelScale: f32,\n orthographic: u32,\n padding: vec2<u32>,\n};\n\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var<storage, read> accumulation: array<vec4<f32>>;\n@group(0) @binding(2) var<storage, read> diffuseIn: array<vec4<f32>>;\n@group(0) @binding(3) var<storage, read> specularIn: array<vec4<f32>>;\n@group(0) @binding(4) var<storage, read_write> diffuseOut: array<vec4<f32>>;\n@group(0) @binding(5) var<storage, read_write> specularOut: array<vec4<f32>>;\n\nfn luminance(color: vec3<f32>) -> f32 {\n return dot(color, vec3<f32>(0.2126, 0.7152, 0.0722));\n}\n\nfn samples(index: u32) -> f32 {\n return max(accumulation[index].w, 1.0);\n}\n\n/** Each signal's mean radiance: the diffuse one stored apart, the specular one the rest. */\nfn diffuseMean(index: u32) -> vec3<f32> {\n return accumulation[4u * params.count + index].rgb / samples(index);\n}\n\nfn specularMean(index: u32) -> vec3<f32> {\n return accumulation[index].rgb / samples(index) - diffuseMean(index);\n}\n\n/** The albedos the light is divided by, kept off zero so the division never explodes. */\nfn diffuseAlbedo(index: u32) -> vec3<f32> {\n return max(accumulation[2u * params.count + index].rgb / samples(index), vec3<f32>(0.01));\n}\n\nfn specularAlbedo(index: u32) -> vec3<f32> {\n return max(accumulation[5u * params.count + index].rgb / samples(index), vec3<f32>(0.01));\n}\n\nfn guideNormal(index: u32) -> vec3<f32> {\n let sum = accumulation[3u * params.count + index].xyz;\n let size = length(sum);\n\n return select(vec3<f32>(0.0), sum / size, size > 1e-6);\n}\n\nfn guideDepth(index: u32) -> f32 {\n return accumulation[3u * params.count + index].w / samples(index);\n}\n\nfn guideRoughness(index: u32) -> f32 {\n return accumulation[2u * params.count + index].w / samples(index);\n}\n\nfn specularDistance(index: u32) -> f32 {\n return accumulation[4u * params.count + index].w / samples(index);\n}\n\n/** The variance of a signal's mean, in the space of its demodulated light. */\nfn variance(mean: vec3<f32>, square: f32, count: f32, albedo: vec3<f32>) -> f32 {\n let m = luminance(mean);\n let a = max(luminance(albedo), 0.01);\n\n return max(square / count - m * m, 0.0) / count / (a * a);\n}\n\n/** Both signals, divided by their albedos, with the variance of their means. */\n@compute @workgroup_size(8, 8, 1)\nfn prepare(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= params.size.x || id.y >= params.size.y) {\n return;\n }\n\n let index = id.y * params.size.x + id.x;\n let count = accumulation[index].w;\n\n if (count <= 0.0) {\n diffuseOut[index] = vec4<f32>(0.0);\n specularOut[index] = vec4<f32>(0.0);\n\n return;\n }\n\n let squares = accumulation[params.count + index];\n let diffuse = diffuseMean(index);\n let specular = specularMean(index);\n\n diffuseOut[index] = vec4<f32>(diffuse / diffuseAlbedo(index), variance(diffuse, squares.z, count, diffuseAlbedo(index)));\n specularOut[index] = vec4<f32>(\n specular / specularAlbedo(index), variance(specular, squares.w, count, specularAlbedo(index)));\n}\n\nfn inside(q: vec2<i32>) -> bool {\n return q.x >= 0 && q.y >= 0 && q.x < i32(params.size.x) && q.y < i32(params.size.y);\n}\n\n/** A value brighter than every neighbour, scaled down to the brightest of them: a firefly, not a feature. */\nfn clampFirefly(value: vec4<f32>, brightest: f32) -> vec4<f32> {\n let own = luminance(value.rgb);\n\n if (own <= brightest || own <= 0.0) {\n return value;\n }\n\n return vec4<f32>(value.rgb * (brightest / own), value.w);\n}\n\n@compute @workgroup_size(8, 8, 1)\nfn antiFirefly(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= params.size.x || id.y >= params.size.y) {\n return;\n }\n\n let pixel = vec2<i32>(id.xy);\n let index = id.y * params.size.x + id.x;\n var diffuse = 0.0;\n var specular = 0.0;\n\n for (var dy = -1; dy <= 1; dy = dy + 1) {\n for (var dx = -1; dx <= 1; dx = dx + 1) {\n let q = pixel + vec2<i32>(dx, dy);\n\n if ((dx == 0 && dy == 0) || !inside(q)) {\n continue;\n }\n\n let other = u32(q.y) * params.size.x + u32(q.x);\n diffuse = max(diffuse, luminance(diffuseIn[other].rgb));\n specular = max(specular, luminance(specularIn[other].rgb));\n }\n }\n\n diffuseOut[index] = clampFirefly(diffuseIn[index], diffuse);\n specularOut[index] = clampFirefly(specularIn[index], specular);\n}\n\nconst KERNEL = array<f32, 5>(0.0625, 0.25, 0.375, 0.25, 0.0625);\n\n/** The variance around a pixel, blurred over 3 x 3, which steadies the luminance test on a few samples. */\nfn localVariance(pixel: vec2<i32>, specular: bool) -> f32 {\n var sum = 0.0;\n var weight = 0.0;\n\n for (var dy = -1; dy <= 1; dy = dy + 1) {\n for (var dx = -1; dx <= 1; dx = dx + 1) {\n let q = pixel + vec2<i32>(dx, dy);\n\n if (!inside(q)) {\n continue;\n }\n\n let other = u32(q.y) * params.size.x + u32(q.x);\n let w = select(0.5, 1.0, dx == 0) * select(0.5, 1.0, dy == 0);\n sum = sum + select(diffuseIn[other].w, specularIn[other].w, specular) * w;\n weight = weight + w;\n }\n }\n\n return sum / max(weight, 1e-6);\n}\n\n/**\n * How many pixels the specular lobe's footprint covers: the lobe's angle times the distance to what it reflects, seen\n * from the camera across the distance to the surface plus that.\n */\nfn specularRadius(index: u32, depth: f32) -> f32 {\n let alpha = guideRoughness(index) * guideRoughness(index);\n let reflected = specularDistance(index);\n\n if (params.orthographic != 0u) {\n return min(alpha * reflected / params.pixelScale, 64.0);\n }\n\n return min(alpha * reflected / max(depth + reflected, 1e-6) / params.pixelScale, 64.0);\n}\n\n/** One a-trous pass over both signals, its taps params.step pixels apart. */\n@compute @workgroup_size(8, 8, 1)\nfn atrous(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= params.size.x || id.y >= params.size.y) {\n return;\n }\n\n let pixel = vec2<i32>(id.xy);\n let index = id.y * params.size.x + id.x;\n let diffuseCenter = diffuseIn[index];\n let specularCenter = specularIn[index];\n\n if (accumulation[index].w <= 0.0) {\n diffuseOut[index] = diffuseCenter;\n specularOut[index] = specularCenter;\n\n return;\n }\n\n let normal = guideNormal(index);\n let depth = guideDepth(index);\n let roughness = guideRoughness(index);\n let diffuseSigma = params.edge * sqrt(max(localVariance(pixel, false), 0.0)) + 1e-6;\n let specularSigma = params.edge * sqrt(max(localVariance(pixel, true), 0.0)) + 1e-6;\n let radius = specularRadius(index, depth);\n // A smoother surface's reflection turns faster with its normal: the normal test sharpens with it.\n let specularPower = min(128.0 / max(roughness * roughness, 0.03), 4096.0);\n let step = i32(params.step);\n var diffuse = vec4<f32>(0.0);\n var specular = vec4<f32>(0.0);\n var diffuseTotal = 0.0;\n var specularTotal = 0.0;\n\n for (var dy = -2; dy <= 2; dy = dy + 1) {\n for (var dx = -2; dx <= 2; dx = dx + 1) {\n let q = pixel + vec2<i32>(dx, dy) * step;\n\n if (!inside(q)) {\n continue;\n }\n\n let other = u32(q.y) * params.size.x + u32(q.x);\n let d = diffuseIn[other];\n let s = specularIn[other];\n let base = KERNEL[dx + 2] * KERNEL[dy + 2];\n var wd = base;\n var ws = base;\n\n if (dx != 0 || dy != 0) {\n if (accumulation[other].w <= 0.0) {\n continue;\n }\n\n let offset = length(vec2<f32>(f32(dx * step), f32(dy * step)));\n let alignment = max(dot(normal, guideNormal(other)), 0.0);\n let depthWeight = exp(-abs(depth - guideDepth(other)) / (0.02 * depth * offset + 1e-3));\n\n wd = wd * pow(alignment, 128.0) * depthWeight\n * exp(-abs(luminance(d.rgb) - luminance(diffuseCenter.rgb)) / diffuseSigma);\n\n ws = ws * pow(alignment, specularPower) * depthWeight\n * exp(-abs(guideRoughness(other) - roughness) * 10.0)\n * exp(-abs(luminance(s.rgb) - luminance(specularCenter.rgb)) / specularSigma)\n * select(0.0, exp(-offset * offset / (radius * radius)), radius >= 0.5);\n }\n\n diffuse = diffuse + vec4<f32>(d.rgb * wd, d.w * wd * wd);\n specular = specular + vec4<f32>(s.rgb * ws, s.w * ws * ws);\n diffuseTotal = diffuseTotal + wd;\n specularTotal = specularTotal + ws;\n }\n }\n\n diffuseOut[index] = vec4<f32>(diffuse.rgb / diffuseTotal, diffuse.w / (diffuseTotal * diffuseTotal));\n specularOut[index] = vec4<f32>(specular.rgb / specularTotal, specular.w / (specularTotal * specularTotal));\n}\n\n/** How much of the filtered signal replaces the traced one: by how noisy the signal still is, when asked. */\nfn amount(mean: vec3<f32>, square: f32, count: f32) -> f32 {\n if (params.onlyNoisy == 0u || count <= 1.0) {\n return params.strength;\n }\n\n let m = luminance(mean);\n let error = sqrt(max(square / count - m * m, 0.0) / count) / sqrt(max(m, 0.0) + 1e-3);\n\n return params.strength * smoothstep(params.converged, 4.0 * params.converged, error);\n}\n\n/** Both filtered signals, times the albedos they were divided by, each mixed with what was traced. */\n@compute @workgroup_size(8, 8, 1)\nfn finish(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= params.size.x || id.y >= params.size.y) {\n return;\n }\n\n let index = id.y * params.size.x + id.x;\n let count = accumulation[index].w;\n\n if (count <= 0.0) {\n diffuseOut[index] = vec4<f32>(0.0, 0.0, 0.0, 1.0);\n\n return;\n }\n\n let squares = accumulation[params.count + index];\n let diffuse = diffuseMean(index);\n let specular = specularMean(index);\n let filteredDiffuse = diffuseIn[index].rgb * diffuseAlbedo(index);\n let filteredSpecular = specularIn[index].rgb * specularAlbedo(index);\n\n diffuseOut[index] = vec4<f32>(\n mix(diffuse, filteredDiffuse, amount(diffuse, squares.z, count))\n + mix(specular, filteredSpecular, amount(specular, squares.w, count)), 1.0);\n}\n";
|
|
47
|
+
export declare class DenoisePass {
|
|
48
|
+
private readonly device;
|
|
49
|
+
private readonly layout;
|
|
50
|
+
private readonly prepare;
|
|
51
|
+
private readonly antiFirefly;
|
|
52
|
+
private readonly atrous;
|
|
53
|
+
private readonly finish;
|
|
54
|
+
/** One uniform per stage, so a whole denoise is a single submit. */
|
|
55
|
+
private readonly params;
|
|
56
|
+
/** Two ping-pong pairs, diffuse and specular, and the output. */
|
|
57
|
+
private buffers;
|
|
58
|
+
private width;
|
|
59
|
+
private height;
|
|
60
|
+
/** The denoised radiance, one vec4 per pixel, once `encode` has run. */
|
|
61
|
+
output: GPUBuffer | null;
|
|
62
|
+
constructor(device: GPUDevice);
|
|
63
|
+
private resize;
|
|
64
|
+
private writeParams;
|
|
65
|
+
/** Denoises what `accumulation` holds into `output`. */
|
|
66
|
+
encode(encoder: GPUCommandEncoder, accumulation: GPUBuffer, width: number, height: number, settings?: DenoiserSettings, view?: DenoiseView): void;
|
|
67
|
+
destroy(): void;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=denoise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"denoise.d.ts","sourceRoot":"","sources":["../src/denoise.ts"],"names":[],"mappings":"AAEA,iHAAiH;AACjH,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,8GAA8G;IAC9G,SAAS,EAAE,OAAO,CAAC;IACnB,0GAA0G;IAC1G,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,CAMtD,CAAC;AAEH,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,GAAE,OAAO,CAAC,gBAAgB,CAAM,GAAG,gBAAgB,CAU7G;AAED,uFAAuF;AACvF,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,uGAAuG;AACvG,MAAM,WAAW,WAAW;IAC1B,6FAA6F;IAC7F,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,28TA2SxB,CAAC;AAQF,qBAAa,WAAW;IAgBV,OAAO,CAAC,QAAQ,CAAC,MAAM;IAfnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,iEAAiE;IACjE,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,MAAM,CAAK;IAEnB,wEAAwE;IACxE,MAAM,EAAE,SAAS,GAAG,IAAI,CAAQ;gBAEH,MAAM,EAAE,SAAS;IA2B9C,OAAO,CAAC,MAAM;IAcd,OAAO,CAAC,WAAW;IAmBnB,wDAAwD;IACxD,MAAM,CACJ,OAAO,EAAE,iBAAiB,EAC1B,YAAY,EAAE,SAAS,EACvB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,gBAAmC,EAC7C,IAAI,GAAE,WAA6D;IAiDrE,OAAO;CAMR"}
|