@tinypoly/gpu-renderer 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +339 -0
- package/LICENSE +21 -0
- package/README.md +284 -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 +296 -0
- package/dist/bvh.js.map +1 -0
- package/dist/bvhBuilder.d.ts +72 -0
- package/dist/bvhBuilder.d.ts.map +1 -0
- package/dist/bvhBuilder.js +126 -0
- package/dist/bvhBuilder.js.map +1 -0
- package/dist/bvhWorker.d.ts +2 -0
- package/dist/bvhWorker.d.ts.map +1 -0
- package/dist/bvhWorker.js +8 -0
- package/dist/bvhWorker.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 +799 -0
- package/dist/geometry.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -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 +209 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +847 -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 +77 -3
- package/src/accumulator.ts +64 -0
- package/src/bvh.ts +336 -0
- package/src/bvhBuilder.ts +178 -0
- package/src/bvhWorker.ts +13 -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 +1067 -0
- package/src/index.ts +64 -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 +1145 -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
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"}
|
package/dist/denoise.js
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { createShaderModule } from "./shaderModule.js";
|
|
2
|
+
export const DEFAULT_DENOISER = Object.freeze({
|
|
3
|
+
strength: 1,
|
|
4
|
+
passes: 3,
|
|
5
|
+
edge: 2,
|
|
6
|
+
onlyNoisy: true,
|
|
7
|
+
antiFirefly: true,
|
|
8
|
+
});
|
|
9
|
+
/** The settings with `input` applied, each field kept in range. */
|
|
10
|
+
export function mergeDenoiser(base, input = {}) {
|
|
11
|
+
const merged = { ...base, ...Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined)) };
|
|
12
|
+
return {
|
|
13
|
+
strength: Math.min(1, Math.max(0, Number(merged.strength) || 0)),
|
|
14
|
+
passes: Math.min(5, Math.max(1, Math.round(Number(merged.passes) || 1))),
|
|
15
|
+
edge: Math.max(0.01, Number(merged.edge) || DEFAULT_DENOISER.edge),
|
|
16
|
+
onlyNoisy: Boolean(merged.onlyNoisy),
|
|
17
|
+
antiFirefly: Boolean(merged.antiFirefly),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** The error a pixel counts as converged under, the one adaptive sampling stops at. */
|
|
21
|
+
export const CONVERGED_ERROR = 0.004;
|
|
22
|
+
/**
|
|
23
|
+
* The denoiser: the spatial half of SVGF (Schied and others, 2017), with the diffuse and the specular light filtered
|
|
24
|
+
* apart, as NVIDIA's RELAX and REBLUR do.
|
|
25
|
+
*
|
|
26
|
+
* The path tracer splits every path at its first scatter, by the lobe it took, so each pixel holds a diffuse and a
|
|
27
|
+
* specular signal, each with its own variance. Each is divided by its own albedo (the diffuse colour, and the GGX
|
|
28
|
+
* lobe's directional albedo), so the filter sees light and never blurs a texture or a colour; both are multiplied back
|
|
29
|
+
* at the end. A pixel brighter than all of its neighbours is first clamped to the brightest of them.
|
|
30
|
+
*
|
|
31
|
+
* Then a few passes of an edge-avoiding a-trous wavelet, its taps twice as far apart each pass. The diffuse light is
|
|
32
|
+
* weighed by the agreement of normals and depths and by its luminance against the pixel's own variance. The specular
|
|
33
|
+
* light also by roughness, by normals more sharply the smoother the surface, and within the footprint its lobe leaves on
|
|
34
|
+
* the screen: the lobe's angle times the distance to what it reflects, seen from the camera. A sharp reflection of
|
|
35
|
+
* something near is left almost alone; a rough reflection of something far is smoothed across a wide area.
|
|
36
|
+
*
|
|
37
|
+
* The weights are normalized, which keeps every flat area at exactly its mean, and each signal is mixed with what was
|
|
38
|
+
* traced by how noisy it still is, so what has converged keeps exactly its traced value.
|
|
39
|
+
*/
|
|
40
|
+
export const DENOISE_WGSL = /* wgsl */ `
|
|
41
|
+
struct Params {
|
|
42
|
+
size: vec2<u32>,
|
|
43
|
+
step: u32,
|
|
44
|
+
count: u32,
|
|
45
|
+
edge: f32,
|
|
46
|
+
strength: f32,
|
|
47
|
+
onlyNoisy: u32,
|
|
48
|
+
converged: f32,
|
|
49
|
+
pixelScale: f32,
|
|
50
|
+
orthographic: u32,
|
|
51
|
+
padding: vec2<u32>,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
@group(0) @binding(0) var<uniform> params: Params;
|
|
55
|
+
@group(0) @binding(1) var<storage, read> accumulation: array<vec4<f32>>;
|
|
56
|
+
@group(0) @binding(2) var<storage, read> diffuseIn: array<vec4<f32>>;
|
|
57
|
+
@group(0) @binding(3) var<storage, read> specularIn: array<vec4<f32>>;
|
|
58
|
+
@group(0) @binding(4) var<storage, read_write> diffuseOut: array<vec4<f32>>;
|
|
59
|
+
@group(0) @binding(5) var<storage, read_write> specularOut: array<vec4<f32>>;
|
|
60
|
+
|
|
61
|
+
fn luminance(color: vec3<f32>) -> f32 {
|
|
62
|
+
return dot(color, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn samples(index: u32) -> f32 {
|
|
66
|
+
return max(accumulation[index].w, 1.0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Each signal's mean radiance: the diffuse one stored apart, the specular one the rest. */
|
|
70
|
+
fn diffuseMean(index: u32) -> vec3<f32> {
|
|
71
|
+
return accumulation[4u * params.count + index].rgb / samples(index);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn specularMean(index: u32) -> vec3<f32> {
|
|
75
|
+
return accumulation[index].rgb / samples(index) - diffuseMean(index);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The albedos the light is divided by, kept off zero so the division never explodes. */
|
|
79
|
+
fn diffuseAlbedo(index: u32) -> vec3<f32> {
|
|
80
|
+
return max(accumulation[2u * params.count + index].rgb / samples(index), vec3<f32>(0.01));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn specularAlbedo(index: u32) -> vec3<f32> {
|
|
84
|
+
return max(accumulation[5u * params.count + index].rgb / samples(index), vec3<f32>(0.01));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fn guideNormal(index: u32) -> vec3<f32> {
|
|
88
|
+
let sum = accumulation[3u * params.count + index].xyz;
|
|
89
|
+
let size = length(sum);
|
|
90
|
+
|
|
91
|
+
return select(vec3<f32>(0.0), sum / size, size > 1e-6);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn guideDepth(index: u32) -> f32 {
|
|
95
|
+
return accumulation[3u * params.count + index].w / samples(index);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fn guideRoughness(index: u32) -> f32 {
|
|
99
|
+
return accumulation[2u * params.count + index].w / samples(index);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn specularDistance(index: u32) -> f32 {
|
|
103
|
+
return accumulation[4u * params.count + index].w / samples(index);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The variance of a signal's mean, in the space of its demodulated light. */
|
|
107
|
+
fn variance(mean: vec3<f32>, square: f32, count: f32, albedo: vec3<f32>) -> f32 {
|
|
108
|
+
let m = luminance(mean);
|
|
109
|
+
let a = max(luminance(albedo), 0.01);
|
|
110
|
+
|
|
111
|
+
return max(square / count - m * m, 0.0) / count / (a * a);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Both signals, divided by their albedos, with the variance of their means. */
|
|
115
|
+
@compute @workgroup_size(8, 8, 1)
|
|
116
|
+
fn prepare(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
117
|
+
if (id.x >= params.size.x || id.y >= params.size.y) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let index = id.y * params.size.x + id.x;
|
|
122
|
+
let count = accumulation[index].w;
|
|
123
|
+
|
|
124
|
+
if (count <= 0.0) {
|
|
125
|
+
diffuseOut[index] = vec4<f32>(0.0);
|
|
126
|
+
specularOut[index] = vec4<f32>(0.0);
|
|
127
|
+
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let squares = accumulation[params.count + index];
|
|
132
|
+
let diffuse = diffuseMean(index);
|
|
133
|
+
let specular = specularMean(index);
|
|
134
|
+
|
|
135
|
+
diffuseOut[index] = vec4<f32>(diffuse / diffuseAlbedo(index), variance(diffuse, squares.z, count, diffuseAlbedo(index)));
|
|
136
|
+
specularOut[index] = vec4<f32>(
|
|
137
|
+
specular / specularAlbedo(index), variance(specular, squares.w, count, specularAlbedo(index)));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fn inside(q: vec2<i32>) -> bool {
|
|
141
|
+
return q.x >= 0 && q.y >= 0 && q.x < i32(params.size.x) && q.y < i32(params.size.y);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A value brighter than every neighbour, scaled down to the brightest of them: a firefly, not a feature. */
|
|
145
|
+
fn clampFirefly(value: vec4<f32>, brightest: f32) -> vec4<f32> {
|
|
146
|
+
let own = luminance(value.rgb);
|
|
147
|
+
|
|
148
|
+
if (own <= brightest || own <= 0.0) {
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return vec4<f32>(value.rgb * (brightest / own), value.w);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@compute @workgroup_size(8, 8, 1)
|
|
156
|
+
fn antiFirefly(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
157
|
+
if (id.x >= params.size.x || id.y >= params.size.y) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let pixel = vec2<i32>(id.xy);
|
|
162
|
+
let index = id.y * params.size.x + id.x;
|
|
163
|
+
var diffuse = 0.0;
|
|
164
|
+
var specular = 0.0;
|
|
165
|
+
|
|
166
|
+
for (var dy = -1; dy <= 1; dy = dy + 1) {
|
|
167
|
+
for (var dx = -1; dx <= 1; dx = dx + 1) {
|
|
168
|
+
let q = pixel + vec2<i32>(dx, dy);
|
|
169
|
+
|
|
170
|
+
if ((dx == 0 && dy == 0) || !inside(q)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let other = u32(q.y) * params.size.x + u32(q.x);
|
|
175
|
+
diffuse = max(diffuse, luminance(diffuseIn[other].rgb));
|
|
176
|
+
specular = max(specular, luminance(specularIn[other].rgb));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
diffuseOut[index] = clampFirefly(diffuseIn[index], diffuse);
|
|
181
|
+
specularOut[index] = clampFirefly(specularIn[index], specular);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const KERNEL = array<f32, 5>(0.0625, 0.25, 0.375, 0.25, 0.0625);
|
|
185
|
+
|
|
186
|
+
/** The variance around a pixel, blurred over 3 x 3, which steadies the luminance test on a few samples. */
|
|
187
|
+
fn localVariance(pixel: vec2<i32>, specular: bool) -> f32 {
|
|
188
|
+
var sum = 0.0;
|
|
189
|
+
var weight = 0.0;
|
|
190
|
+
|
|
191
|
+
for (var dy = -1; dy <= 1; dy = dy + 1) {
|
|
192
|
+
for (var dx = -1; dx <= 1; dx = dx + 1) {
|
|
193
|
+
let q = pixel + vec2<i32>(dx, dy);
|
|
194
|
+
|
|
195
|
+
if (!inside(q)) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let other = u32(q.y) * params.size.x + u32(q.x);
|
|
200
|
+
let w = select(0.5, 1.0, dx == 0) * select(0.5, 1.0, dy == 0);
|
|
201
|
+
sum = sum + select(diffuseIn[other].w, specularIn[other].w, specular) * w;
|
|
202
|
+
weight = weight + w;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return sum / max(weight, 1e-6);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* How many pixels the specular lobe's footprint covers: the lobe's angle times the distance to what it reflects, seen
|
|
211
|
+
* from the camera across the distance to the surface plus that.
|
|
212
|
+
*/
|
|
213
|
+
fn specularRadius(index: u32, depth: f32) -> f32 {
|
|
214
|
+
let alpha = guideRoughness(index) * guideRoughness(index);
|
|
215
|
+
let reflected = specularDistance(index);
|
|
216
|
+
|
|
217
|
+
if (params.orthographic != 0u) {
|
|
218
|
+
return min(alpha * reflected / params.pixelScale, 64.0);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return min(alpha * reflected / max(depth + reflected, 1e-6) / params.pixelScale, 64.0);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** One a-trous pass over both signals, its taps params.step pixels apart. */
|
|
225
|
+
@compute @workgroup_size(8, 8, 1)
|
|
226
|
+
fn atrous(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
227
|
+
if (id.x >= params.size.x || id.y >= params.size.y) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let pixel = vec2<i32>(id.xy);
|
|
232
|
+
let index = id.y * params.size.x + id.x;
|
|
233
|
+
let diffuseCenter = diffuseIn[index];
|
|
234
|
+
let specularCenter = specularIn[index];
|
|
235
|
+
|
|
236
|
+
if (accumulation[index].w <= 0.0) {
|
|
237
|
+
diffuseOut[index] = diffuseCenter;
|
|
238
|
+
specularOut[index] = specularCenter;
|
|
239
|
+
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let normal = guideNormal(index);
|
|
244
|
+
let depth = guideDepth(index);
|
|
245
|
+
let roughness = guideRoughness(index);
|
|
246
|
+
let diffuseSigma = params.edge * sqrt(max(localVariance(pixel, false), 0.0)) + 1e-6;
|
|
247
|
+
let specularSigma = params.edge * sqrt(max(localVariance(pixel, true), 0.0)) + 1e-6;
|
|
248
|
+
let radius = specularRadius(index, depth);
|
|
249
|
+
// A smoother surface's reflection turns faster with its normal: the normal test sharpens with it.
|
|
250
|
+
let specularPower = min(128.0 / max(roughness * roughness, 0.03), 4096.0);
|
|
251
|
+
let step = i32(params.step);
|
|
252
|
+
var diffuse = vec4<f32>(0.0);
|
|
253
|
+
var specular = vec4<f32>(0.0);
|
|
254
|
+
var diffuseTotal = 0.0;
|
|
255
|
+
var specularTotal = 0.0;
|
|
256
|
+
|
|
257
|
+
for (var dy = -2; dy <= 2; dy = dy + 1) {
|
|
258
|
+
for (var dx = -2; dx <= 2; dx = dx + 1) {
|
|
259
|
+
let q = pixel + vec2<i32>(dx, dy) * step;
|
|
260
|
+
|
|
261
|
+
if (!inside(q)) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let other = u32(q.y) * params.size.x + u32(q.x);
|
|
266
|
+
let d = diffuseIn[other];
|
|
267
|
+
let s = specularIn[other];
|
|
268
|
+
let base = KERNEL[dx + 2] * KERNEL[dy + 2];
|
|
269
|
+
var wd = base;
|
|
270
|
+
var ws = base;
|
|
271
|
+
|
|
272
|
+
if (dx != 0 || dy != 0) {
|
|
273
|
+
if (accumulation[other].w <= 0.0) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let offset = length(vec2<f32>(f32(dx * step), f32(dy * step)));
|
|
278
|
+
let alignment = max(dot(normal, guideNormal(other)), 0.0);
|
|
279
|
+
let depthWeight = exp(-abs(depth - guideDepth(other)) / (0.02 * depth * offset + 1e-3));
|
|
280
|
+
|
|
281
|
+
wd = wd * pow(alignment, 128.0) * depthWeight
|
|
282
|
+
* exp(-abs(luminance(d.rgb) - luminance(diffuseCenter.rgb)) / diffuseSigma);
|
|
283
|
+
|
|
284
|
+
ws = ws * pow(alignment, specularPower) * depthWeight
|
|
285
|
+
* exp(-abs(guideRoughness(other) - roughness) * 10.0)
|
|
286
|
+
* exp(-abs(luminance(s.rgb) - luminance(specularCenter.rgb)) / specularSigma)
|
|
287
|
+
* select(0.0, exp(-offset * offset / (radius * radius)), radius >= 0.5);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
diffuse = diffuse + vec4<f32>(d.rgb * wd, d.w * wd * wd);
|
|
291
|
+
specular = specular + vec4<f32>(s.rgb * ws, s.w * ws * ws);
|
|
292
|
+
diffuseTotal = diffuseTotal + wd;
|
|
293
|
+
specularTotal = specularTotal + ws;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
diffuseOut[index] = vec4<f32>(diffuse.rgb / diffuseTotal, diffuse.w / (diffuseTotal * diffuseTotal));
|
|
298
|
+
specularOut[index] = vec4<f32>(specular.rgb / specularTotal, specular.w / (specularTotal * specularTotal));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** How much of the filtered signal replaces the traced one: by how noisy the signal still is, when asked. */
|
|
302
|
+
fn amount(mean: vec3<f32>, square: f32, count: f32) -> f32 {
|
|
303
|
+
if (params.onlyNoisy == 0u || count <= 1.0) {
|
|
304
|
+
return params.strength;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let m = luminance(mean);
|
|
308
|
+
let error = sqrt(max(square / count - m * m, 0.0) / count) / sqrt(max(m, 0.0) + 1e-3);
|
|
309
|
+
|
|
310
|
+
return params.strength * smoothstep(params.converged, 4.0 * params.converged, error);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Both filtered signals, times the albedos they were divided by, each mixed with what was traced. */
|
|
314
|
+
@compute @workgroup_size(8, 8, 1)
|
|
315
|
+
fn finish(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
316
|
+
if (id.x >= params.size.x || id.y >= params.size.y) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let index = id.y * params.size.x + id.x;
|
|
321
|
+
let count = accumulation[index].w;
|
|
322
|
+
|
|
323
|
+
if (count <= 0.0) {
|
|
324
|
+
diffuseOut[index] = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
325
|
+
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let squares = accumulation[params.count + index];
|
|
330
|
+
let diffuse = diffuseMean(index);
|
|
331
|
+
let specular = specularMean(index);
|
|
332
|
+
let filteredDiffuse = diffuseIn[index].rgb * diffuseAlbedo(index);
|
|
333
|
+
let filteredSpecular = specularIn[index].rgb * specularAlbedo(index);
|
|
334
|
+
|
|
335
|
+
diffuseOut[index] = vec4<f32>(
|
|
336
|
+
mix(diffuse, filteredDiffuse, amount(diffuse, squares.z, count))
|
|
337
|
+
+ mix(specular, filteredSpecular, amount(specular, squares.w, count)), 1.0);
|
|
338
|
+
}
|
|
339
|
+
`;
|
|
340
|
+
/** The a-trous passes, their taps 1, 2, 4, 8 and 16 pixels apart. */
|
|
341
|
+
const STEPS = [1, 2, 4, 8, 16];
|
|
342
|
+
/** Uniforms: prepare, anti-firefly, one per a-trous pass, finish. */
|
|
343
|
+
const STAGES = STEPS.length + 3;
|
|
344
|
+
export class DenoisePass {
|
|
345
|
+
device;
|
|
346
|
+
layout;
|
|
347
|
+
prepare;
|
|
348
|
+
antiFirefly;
|
|
349
|
+
atrous;
|
|
350
|
+
finish;
|
|
351
|
+
/** One uniform per stage, so a whole denoise is a single submit. */
|
|
352
|
+
params;
|
|
353
|
+
/** Two ping-pong pairs, diffuse and specular, and the output. */
|
|
354
|
+
buffers = [];
|
|
355
|
+
width = 0;
|
|
356
|
+
height = 0;
|
|
357
|
+
/** The denoised radiance, one vec4 per pixel, once `encode` has run. */
|
|
358
|
+
output = null;
|
|
359
|
+
constructor(device) {
|
|
360
|
+
this.device = device;
|
|
361
|
+
const module = createShaderModule(device, "denoise", DENOISE_WGSL);
|
|
362
|
+
const storage = (type) => ({ type });
|
|
363
|
+
const visibility = GPUShaderStage.COMPUTE;
|
|
364
|
+
this.layout = device.createBindGroupLayout({
|
|
365
|
+
entries: [
|
|
366
|
+
{ binding: 0, visibility, buffer: { type: "uniform" } },
|
|
367
|
+
{ binding: 1, visibility, buffer: storage("read-only-storage") },
|
|
368
|
+
{ binding: 2, visibility, buffer: storage("read-only-storage") },
|
|
369
|
+
{ binding: 3, visibility, buffer: storage("read-only-storage") },
|
|
370
|
+
{ binding: 4, visibility, buffer: storage("storage") },
|
|
371
|
+
{ binding: 5, visibility, buffer: storage("storage") },
|
|
372
|
+
],
|
|
373
|
+
});
|
|
374
|
+
const layout = device.createPipelineLayout({ bindGroupLayouts: [this.layout] });
|
|
375
|
+
const pipeline = (entryPoint) => device.createComputePipeline({ layout, compute: { module, entryPoint } });
|
|
376
|
+
this.prepare = pipeline("prepare");
|
|
377
|
+
this.antiFirefly = pipeline("antiFirefly");
|
|
378
|
+
this.atrous = pipeline("atrous");
|
|
379
|
+
this.finish = pipeline("finish");
|
|
380
|
+
this.params = Array.from({ length: STAGES }, () => device.createBuffer({ size: 48, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }));
|
|
381
|
+
}
|
|
382
|
+
resize(width, height) {
|
|
383
|
+
if (width === this.width && height === this.height && this.buffers.length)
|
|
384
|
+
return;
|
|
385
|
+
for (const buffer of this.buffers)
|
|
386
|
+
buffer.destroy();
|
|
387
|
+
const size = Math.max(16, width * height * 16);
|
|
388
|
+
this.buffers = Array.from({ length: 5 }, () => this.device.createBuffer({ size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC }));
|
|
389
|
+
this.output = this.buffers[4];
|
|
390
|
+
this.width = width;
|
|
391
|
+
this.height = height;
|
|
392
|
+
}
|
|
393
|
+
writeParams(width, height, settings, view) {
|
|
394
|
+
const steps = [1, 1, ...STEPS, 1];
|
|
395
|
+
steps.forEach((step, k) => {
|
|
396
|
+
const values = new ArrayBuffer(48);
|
|
397
|
+
const words = new Uint32Array(values);
|
|
398
|
+
const floats = new Float32Array(values);
|
|
399
|
+
words.set([width, height, step, width * height]);
|
|
400
|
+
floats[4] = settings.edge;
|
|
401
|
+
floats[5] = settings.strength;
|
|
402
|
+
words[6] = settings.onlyNoisy ? 1 : 0;
|
|
403
|
+
floats[7] = CONVERGED_ERROR;
|
|
404
|
+
floats[8] = view.pixelScale;
|
|
405
|
+
words[9] = view.orthographic ? 1 : 0;
|
|
406
|
+
this.device.queue.writeBuffer(this.params[k], 0, values);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
/** Denoises what `accumulation` holds into `output`. */
|
|
410
|
+
encode(encoder, accumulation, width, height, settings = DEFAULT_DENOISER, view = { pixelScale: 1 / height, orthographic: false }) {
|
|
411
|
+
this.resize(width, height);
|
|
412
|
+
this.writeParams(width, height, settings, view);
|
|
413
|
+
const [diffuseA, diffuseB, specularA, specularB, output] = this.buffers;
|
|
414
|
+
const groups = [Math.ceil(width / 8), Math.ceil(height / 8)];
|
|
415
|
+
const run = (pipeline, params, sources, targets) => {
|
|
416
|
+
const group = this.device.createBindGroup({
|
|
417
|
+
layout: this.layout,
|
|
418
|
+
entries: [
|
|
419
|
+
{ binding: 0, resource: { buffer: params } },
|
|
420
|
+
{ binding: 1, resource: { buffer: accumulation } },
|
|
421
|
+
{ binding: 2, resource: { buffer: sources[0] } },
|
|
422
|
+
{ binding: 3, resource: { buffer: sources[1] } },
|
|
423
|
+
{ binding: 4, resource: { buffer: targets[0] } },
|
|
424
|
+
{ binding: 5, resource: { buffer: targets[1] } },
|
|
425
|
+
],
|
|
426
|
+
});
|
|
427
|
+
const pass = encoder.beginComputePass({ label: "denoise" });
|
|
428
|
+
pass.setPipeline(pipeline);
|
|
429
|
+
pass.setBindGroup(0, group);
|
|
430
|
+
pass.dispatchWorkgroups(groups[0], groups[1]);
|
|
431
|
+
pass.end();
|
|
432
|
+
};
|
|
433
|
+
// prepare reads nothing from its sources; the B buffers stand in so the bindings are valid.
|
|
434
|
+
run(this.prepare, this.params[0], [diffuseB, specularB], [diffuseA, specularA]);
|
|
435
|
+
let from = [diffuseA, specularA];
|
|
436
|
+
let to = [diffuseB, specularB];
|
|
437
|
+
if (settings.antiFirefly) {
|
|
438
|
+
run(this.antiFirefly, this.params[1], from, to);
|
|
439
|
+
[from, to] = [to, from];
|
|
440
|
+
}
|
|
441
|
+
STEPS.slice(0, settings.passes).forEach((_, k) => {
|
|
442
|
+
run(this.atrous, this.params[k + 2], from, to);
|
|
443
|
+
[from, to] = [to, from];
|
|
444
|
+
});
|
|
445
|
+
// finish writes the result to the output; the free specular buffer fills the second, unused, target.
|
|
446
|
+
run(this.finish, this.params[STAGES - 1], from, [output, to[1]]);
|
|
447
|
+
}
|
|
448
|
+
destroy() {
|
|
449
|
+
for (const buffer of [...this.buffers, ...this.params])
|
|
450
|
+
buffer.destroy();
|
|
451
|
+
this.buffers = [];
|
|
452
|
+
this.output = null;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
//# sourceMappingURL=denoise.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"denoise.js","sourceRoot":"","sources":["../src/denoise.ts"],"names":[],"mappings":"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"}
|
package/dist/device.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Acquiring a device, and the questions a host asks before choosing this renderer at all. */
|
|
2
|
+
/** What the renderer needs from the platform, and what is missing when it cannot run. */
|
|
3
|
+
export interface WebGpuSupport {
|
|
4
|
+
/** True when an adapter answered. */
|
|
5
|
+
supported: boolean;
|
|
6
|
+
/** Why the renderer cannot run here, in one sentence. Empty when `supported`. */
|
|
7
|
+
reason: string;
|
|
8
|
+
}
|
|
9
|
+
export interface RenderDevice {
|
|
10
|
+
adapter: GPUAdapter;
|
|
11
|
+
device: GPUDevice;
|
|
12
|
+
/** Largest storage buffer this device accepts, which is the ceiling on how big a scene can be. */
|
|
13
|
+
maxStorageBufferBindingSize: number;
|
|
14
|
+
maxBufferSize: number;
|
|
15
|
+
}
|
|
16
|
+
export interface RenderDeviceOptions {
|
|
17
|
+
/** Called when the device is lost. The renderer stops; choosing what to do next is the host's decision. */
|
|
18
|
+
onLost?: (reason: string) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether this context exposes WebGPU at all. It is a cheap, synchronous check for a fallback decision;
|
|
22
|
+
* it does not prove that an adapter exists, which only `requestWebGpuSupport` can tell.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isWebGpuAvailable(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Asks the platform for an adapter, which is the only honest answer to "can this machine render?".
|
|
27
|
+
* A browser can expose `navigator.gpu` and still hand back no adapter (a blocklisted driver, a headless session
|
|
28
|
+
* without a software fallback), so hosts deciding between renderers should await this one.
|
|
29
|
+
*/
|
|
30
|
+
export declare function requestWebGpuSupport(): Promise<WebGpuSupport>;
|
|
31
|
+
/**
|
|
32
|
+
* The device the renderer runs on, asking for everything the adapter offers. Scene geometry, the hierarchy and the
|
|
33
|
+
* accumulation buffer all live in storage buffers, so the default 128 MiB binding size is the first ceiling a real
|
|
34
|
+
* scene meets, and adapters routinely allow far more.
|
|
35
|
+
*/
|
|
36
|
+
export declare function requestRenderDevice(options?: RenderDeviceOptions): Promise<RenderDevice>;
|
|
37
|
+
//# sourceMappingURL=device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAE9F,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,SAAS,CAAC;IAClB,kGAAkG;IAClG,2BAA2B,EAAE,MAAM,CAAC;IACpC,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,2GAA2G;IAC3G,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,aAAa,CAAC,CAanE;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBlG"}
|