@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 @@
|
|
|
1
|
+
{"version":3,"file":"pathTrace.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/pathTrace.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8+BzC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The presentation pass: divides the accumulation buffer by its sample count and applies exposure, tone mapping,
|
|
3
|
+
* vignette and grain. The operators are ports of `color.ts` in the CPU renderer, which are in turn Three's ACES, AgX
|
|
4
|
+
* and Khronos neutral curves, so the two renderers agree on the display transform even when they disagree on the light.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PRESENT_WGSL = "\nstruct Present {\n resolution: vec2<u32>,\n exposure: f32,\n toneMapping: u32,\n vignette: f32,\n grain: f32,\n frameIndex: u32,\n // A transparent background: the canvas gets the coverage as alpha, premultiplied as it is configured.\n transparent: u32,\n // Whether the colour comes from the denoiser's output rather than straight from the accumulation.\n denoised: u32,\n};\n\n@group(0) @binding(0) var<uniform> present: Present;\n@group(0) @binding(1) var<storage, read> accumulation: array<vec4<f32>>;\n@group(0) @binding(2) var<storage, read> denoised: array<vec4<f32>>;\n\n@vertex\nfn vertexMain(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> {\n // One oversized triangle covers the viewport with no vertex buffer.\n let x = f32(index / 2u) * 4.0 - 1.0;\n let y = f32(index & 1u) * 4.0 - 1.0;\n\n return vec4<f32>(x, y, 0.0, 1.0);\n}\n\nfn toneMapAces(color: vec3<f32>) -> vec3<f32> {\n let toAces = mat3x3<f32>(\n vec3<f32>(0.59719, 0.076, 0.0284),\n vec3<f32>(0.35458, 0.90834, 0.13383),\n vec3<f32>(0.04823, 0.01566, 0.83777));\n let fromAces = mat3x3<f32>(\n vec3<f32>(1.60475, -0.10208, -0.00327),\n vec3<f32>(-0.53108, 1.10813, -0.07276),\n vec3<f32>(-0.07367, -0.00605, 1.07602));\n let v = toAces * (color / 0.6);\n\n return fromAces * ((v * (v + 0.0245786) - 0.000090537) / (v * (0.983729 * v + 0.432951) + 0.238081));\n}\n\nfn toneMapAgx(color: vec3<f32>) -> vec3<f32> {\n let toAgx = mat3x3<f32>(\n vec3<f32>(0.6274, 0.0691, 0.0164),\n vec3<f32>(0.3293, 0.9195, 0.088),\n vec3<f32>(0.0433, 0.0113, 0.8956));\n let inset = mat3x3<f32>(\n vec3<f32>(0.856627153315983, 0.137318972929847, 0.11189821299995),\n vec3<f32>(0.0951212405381588, 0.761241990602591, 0.0767994186031903),\n vec3<f32>(0.0482516061458583, 0.101439036467562, 0.811302368396859));\n let outset = mat3x3<f32>(\n vec3<f32>(1.1271005818144368, -0.1413297634984383, -0.14132976349843826),\n vec3<f32>(-0.11060664309660323, 1.157823702216272, -0.11060664309660294),\n vec3<f32>(-0.016493938717834573, -0.016493938717834257, 1.2519364065950405));\n let fromAgx = mat3x3<f32>(\n vec3<f32>(1.6605, -0.1246, -0.0182),\n vec3<f32>(-0.5876, 1.1329, -0.1006),\n vec3<f32>(-0.0728, -0.0083, 1.1187));\n let logged = inset * (toAgx * color);\n let x = clamp((log2(max(logged, vec3<f32>(1e-10))) + 12.47393) / 16.499999, vec3<f32>(0.0), vec3<f32>(1.0));\n let curved = 15.5 * pow(x, vec3<f32>(6.0)) - 40.14 * pow(x, vec3<f32>(5.0)) + 31.96 * pow(x, vec3<f32>(4.0))\n - 6.868 * pow(x, vec3<f32>(3.0)) + 0.4298 * x * x + 0.1191 * x - 0.00232;\n\n return fromAgx * pow(max(outset * curved, vec3<f32>(0.0)), vec3<f32>(2.2));\n}\n\nfn toneMapNeutral(color: vec3<f32>) -> vec3<f32> {\n let low = min(color.r, min(color.g, color.b));\n let offset = select(0.04, low - 6.25 * low * low, low < 0.08);\n let shifted = color - offset;\n let peak = max(shifted.r, max(shifted.g, shifted.b));\n\n if (peak < 0.76) {\n return shifted;\n }\n\n let newPeak = 1.0 - 0.24 * 0.24 / (peak + 0.24 - 0.76);\n let g = 1.0 - 1.0 / (0.15 * (peak - newPeak) + 1.0);\n\n return shifted * (newPeak / peak) * (1.0 - g) + newPeak * g;\n}\n\nfn toneMap(color: vec3<f32>) -> vec3<f32> {\n let exposed = max(color * present.exposure, vec3<f32>(0.0));\n var mapped = exposed;\n\n if (present.toneMapping == 0u) {\n mapped = toneMapAces(exposed);\n } else if (present.toneMapping == 1u) {\n mapped = toneMapAgx(exposed);\n } else if (present.toneMapping == 2u) {\n mapped = toneMapNeutral(exposed);\n }\n\n return clamp(mapped, vec3<f32>(0.0), vec3<f32>(1.0));\n}\n\nfn linearToSrgb(color: vec3<f32>) -> vec3<f32> {\n let low = color * 12.92;\n let high = 1.055 * pow(max(color, vec3<f32>(0.0)), vec3<f32>(1.0 / 2.4)) - 0.055;\n\n return select(high, low, color <= vec3<f32>(0.0031308));\n}\n\nfn hash(value: u32) -> f32 {\n var state = value * 747796405u + 2891336453u;\n state = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\n\n return f32((state >> 22u) ^ state) * 2.3283064365386963e-10;\n}\n\n@fragment\nfn fragmentMain(@builtin(position) position: vec4<f32>) -> @location(0) vec4<f32> {\n let pixel = vec2<u32>(position.xy);\n let index = pixel.y * present.resolution.x + pixel.x;\n let stored = accumulation[index];\n\n // Nothing has been traced into this pixel yet: it stays see-through, so whatever is under the canvas shows.\n if (stored.w <= 0.0) {\n return vec4<f32>(0.0);\n }\n\n var color = select(stored.rgb / stored.w, vec3<f32>(0.0), stored.w <= 0.0);\n\n if (present.denoised != 0u) {\n color = denoised[index].rgb;\n }\n var alpha = 1.0;\n\n if (present.transparent != 0u) {\n let coverage = accumulation[present.resolution.x * present.resolution.y + index].x;\n alpha = select(clamp(coverage / stored.w, 0.0, 1.0), 0.0, stored.w <= 0.0);\n // The radiance is premultiplied by the coverage; the tone curve applies to the colour itself.\n color = select(color / alpha, vec3<f32>(0.0), alpha <= 0.0);\n }\n\n let centred = position.xy / vec2<f32>(present.resolution) - 0.5;\n let falloff = 1.0 - present.vignette * dot(centred, centred) * 2.0;\n var output = linearToSrgb(toneMap(color * max(falloff, 0.0)));\n\n if (present.grain > 0.0) {\n let noise = hash(index * 9781u + present.frameIndex * 6151u) - 0.5;\n output = clamp(output + noise * present.grain * 0.1, vec3<f32>(0.0), vec3<f32>(1.0));\n }\n\n return vec4<f32>(output * alpha, alpha);\n}\n";
|
|
7
|
+
//# sourceMappingURL=present.wgsl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"present.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/present.wgsl.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,YAAY,63KAmJxB,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The presentation pass: divides the accumulation buffer by its sample count and applies exposure, tone mapping,
|
|
3
|
+
* vignette and grain. The operators are ports of `color.ts` in the CPU renderer, which are in turn Three's ACES, AgX
|
|
4
|
+
* and Khronos neutral curves, so the two renderers agree on the display transform even when they disagree on the light.
|
|
5
|
+
*/
|
|
6
|
+
export const PRESENT_WGSL = /* wgsl */ `
|
|
7
|
+
struct Present {
|
|
8
|
+
resolution: vec2<u32>,
|
|
9
|
+
exposure: f32,
|
|
10
|
+
toneMapping: u32,
|
|
11
|
+
vignette: f32,
|
|
12
|
+
grain: f32,
|
|
13
|
+
frameIndex: u32,
|
|
14
|
+
// A transparent background: the canvas gets the coverage as alpha, premultiplied as it is configured.
|
|
15
|
+
transparent: u32,
|
|
16
|
+
// Whether the colour comes from the denoiser's output rather than straight from the accumulation.
|
|
17
|
+
denoised: u32,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
@group(0) @binding(0) var<uniform> present: Present;
|
|
21
|
+
@group(0) @binding(1) var<storage, read> accumulation: array<vec4<f32>>;
|
|
22
|
+
@group(0) @binding(2) var<storage, read> denoised: array<vec4<f32>>;
|
|
23
|
+
|
|
24
|
+
@vertex
|
|
25
|
+
fn vertexMain(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> {
|
|
26
|
+
// One oversized triangle covers the viewport with no vertex buffer.
|
|
27
|
+
let x = f32(index / 2u) * 4.0 - 1.0;
|
|
28
|
+
let y = f32(index & 1u) * 4.0 - 1.0;
|
|
29
|
+
|
|
30
|
+
return vec4<f32>(x, y, 0.0, 1.0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn toneMapAces(color: vec3<f32>) -> vec3<f32> {
|
|
34
|
+
let toAces = mat3x3<f32>(
|
|
35
|
+
vec3<f32>(0.59719, 0.076, 0.0284),
|
|
36
|
+
vec3<f32>(0.35458, 0.90834, 0.13383),
|
|
37
|
+
vec3<f32>(0.04823, 0.01566, 0.83777));
|
|
38
|
+
let fromAces = mat3x3<f32>(
|
|
39
|
+
vec3<f32>(1.60475, -0.10208, -0.00327),
|
|
40
|
+
vec3<f32>(-0.53108, 1.10813, -0.07276),
|
|
41
|
+
vec3<f32>(-0.07367, -0.00605, 1.07602));
|
|
42
|
+
let v = toAces * (color / 0.6);
|
|
43
|
+
|
|
44
|
+
return fromAces * ((v * (v + 0.0245786) - 0.000090537) / (v * (0.983729 * v + 0.432951) + 0.238081));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn toneMapAgx(color: vec3<f32>) -> vec3<f32> {
|
|
48
|
+
let toAgx = mat3x3<f32>(
|
|
49
|
+
vec3<f32>(0.6274, 0.0691, 0.0164),
|
|
50
|
+
vec3<f32>(0.3293, 0.9195, 0.088),
|
|
51
|
+
vec3<f32>(0.0433, 0.0113, 0.8956));
|
|
52
|
+
let inset = mat3x3<f32>(
|
|
53
|
+
vec3<f32>(0.856627153315983, 0.137318972929847, 0.11189821299995),
|
|
54
|
+
vec3<f32>(0.0951212405381588, 0.761241990602591, 0.0767994186031903),
|
|
55
|
+
vec3<f32>(0.0482516061458583, 0.101439036467562, 0.811302368396859));
|
|
56
|
+
let outset = mat3x3<f32>(
|
|
57
|
+
vec3<f32>(1.1271005818144368, -0.1413297634984383, -0.14132976349843826),
|
|
58
|
+
vec3<f32>(-0.11060664309660323, 1.157823702216272, -0.11060664309660294),
|
|
59
|
+
vec3<f32>(-0.016493938717834573, -0.016493938717834257, 1.2519364065950405));
|
|
60
|
+
let fromAgx = mat3x3<f32>(
|
|
61
|
+
vec3<f32>(1.6605, -0.1246, -0.0182),
|
|
62
|
+
vec3<f32>(-0.5876, 1.1329, -0.1006),
|
|
63
|
+
vec3<f32>(-0.0728, -0.0083, 1.1187));
|
|
64
|
+
let logged = inset * (toAgx * color);
|
|
65
|
+
let x = clamp((log2(max(logged, vec3<f32>(1e-10))) + 12.47393) / 16.499999, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
66
|
+
let curved = 15.5 * pow(x, vec3<f32>(6.0)) - 40.14 * pow(x, vec3<f32>(5.0)) + 31.96 * pow(x, vec3<f32>(4.0))
|
|
67
|
+
- 6.868 * pow(x, vec3<f32>(3.0)) + 0.4298 * x * x + 0.1191 * x - 0.00232;
|
|
68
|
+
|
|
69
|
+
return fromAgx * pow(max(outset * curved, vec3<f32>(0.0)), vec3<f32>(2.2));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn toneMapNeutral(color: vec3<f32>) -> vec3<f32> {
|
|
73
|
+
let low = min(color.r, min(color.g, color.b));
|
|
74
|
+
let offset = select(0.04, low - 6.25 * low * low, low < 0.08);
|
|
75
|
+
let shifted = color - offset;
|
|
76
|
+
let peak = max(shifted.r, max(shifted.g, shifted.b));
|
|
77
|
+
|
|
78
|
+
if (peak < 0.76) {
|
|
79
|
+
return shifted;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let newPeak = 1.0 - 0.24 * 0.24 / (peak + 0.24 - 0.76);
|
|
83
|
+
let g = 1.0 - 1.0 / (0.15 * (peak - newPeak) + 1.0);
|
|
84
|
+
|
|
85
|
+
return shifted * (newPeak / peak) * (1.0 - g) + newPeak * g;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fn toneMap(color: vec3<f32>) -> vec3<f32> {
|
|
89
|
+
let exposed = max(color * present.exposure, vec3<f32>(0.0));
|
|
90
|
+
var mapped = exposed;
|
|
91
|
+
|
|
92
|
+
if (present.toneMapping == 0u) {
|
|
93
|
+
mapped = toneMapAces(exposed);
|
|
94
|
+
} else if (present.toneMapping == 1u) {
|
|
95
|
+
mapped = toneMapAgx(exposed);
|
|
96
|
+
} else if (present.toneMapping == 2u) {
|
|
97
|
+
mapped = toneMapNeutral(exposed);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return clamp(mapped, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn linearToSrgb(color: vec3<f32>) -> vec3<f32> {
|
|
104
|
+
let low = color * 12.92;
|
|
105
|
+
let high = 1.055 * pow(max(color, vec3<f32>(0.0)), vec3<f32>(1.0 / 2.4)) - 0.055;
|
|
106
|
+
|
|
107
|
+
return select(high, low, color <= vec3<f32>(0.0031308));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn hash(value: u32) -> f32 {
|
|
111
|
+
var state = value * 747796405u + 2891336453u;
|
|
112
|
+
state = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
|
|
113
|
+
|
|
114
|
+
return f32((state >> 22u) ^ state) * 2.3283064365386963e-10;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@fragment
|
|
118
|
+
fn fragmentMain(@builtin(position) position: vec4<f32>) -> @location(0) vec4<f32> {
|
|
119
|
+
let pixel = vec2<u32>(position.xy);
|
|
120
|
+
let index = pixel.y * present.resolution.x + pixel.x;
|
|
121
|
+
let stored = accumulation[index];
|
|
122
|
+
|
|
123
|
+
// Nothing has been traced into this pixel yet: it stays see-through, so whatever is under the canvas shows.
|
|
124
|
+
if (stored.w <= 0.0) {
|
|
125
|
+
return vec4<f32>(0.0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
var color = select(stored.rgb / stored.w, vec3<f32>(0.0), stored.w <= 0.0);
|
|
129
|
+
|
|
130
|
+
if (present.denoised != 0u) {
|
|
131
|
+
color = denoised[index].rgb;
|
|
132
|
+
}
|
|
133
|
+
var alpha = 1.0;
|
|
134
|
+
|
|
135
|
+
if (present.transparent != 0u) {
|
|
136
|
+
let coverage = accumulation[present.resolution.x * present.resolution.y + index].x;
|
|
137
|
+
alpha = select(clamp(coverage / stored.w, 0.0, 1.0), 0.0, stored.w <= 0.0);
|
|
138
|
+
// The radiance is premultiplied by the coverage; the tone curve applies to the colour itself.
|
|
139
|
+
color = select(color / alpha, vec3<f32>(0.0), alpha <= 0.0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let centred = position.xy / vec2<f32>(present.resolution) - 0.5;
|
|
143
|
+
let falloff = 1.0 - present.vignette * dot(centred, centred) * 2.0;
|
|
144
|
+
var output = linearToSrgb(toneMap(color * max(falloff, 0.0)));
|
|
145
|
+
|
|
146
|
+
if (present.grain > 0.0) {
|
|
147
|
+
let noise = hash(index * 9781u + present.frameIndex * 6151u) - 0.5;
|
|
148
|
+
output = clamp(output + noise * present.grain * 0.1, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return vec4<f32>(output * alpha, alpha);
|
|
152
|
+
}
|
|
153
|
+
`;
|
|
154
|
+
//# sourceMappingURL=present.wgsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"present.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/present.wgsl.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmJtC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sample generator: a Sobol sequence with hash based Owen scrambling (Burley, "Practical Hash-based Owen
|
|
3
|
+
* Scrambling", 2020). Every pixel draws its own scrambled sequence, decorrelated from its neighbours, and inside a pixel
|
|
4
|
+
* the samples are stratified in every pair of dimensions, so the estimate converges faster than with independent
|
|
5
|
+
* random numbers. Only the sample index, the pixel and a dimension counter feed the generator, so a pixel draws the same
|
|
6
|
+
* numbers whichever dispatch happens to cover it and the image does not depend on the tiling.
|
|
7
|
+
*/
|
|
8
|
+
export declare const RANDOM_WGSL = "\nfn pcgHash(value: u32) -> u32 {\n let state = value * 747796405u + 2891336453u;\n let word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\n\n return (word >> 22u) ^ word;\n}\n\nstruct PathSampler {\n seed: u32,\n index: u32,\n dimension: u32,\n};\n\nfn makeSampler(pixel: vec2<u32>, index: u32) -> PathSampler {\n var rng: PathSampler;\n rng.seed = pcgHash(pixel.x * 1973u + pixel.y * 9277u + 1u);\n rng.index = index;\n rng.dimension = 0u;\n\n return rng;\n}\n\n/**\n * The Laine-Karras hash, which permutes the high bits in a way that nests: what Owen scrambling asks for. Every\n * multiplier has to be even, or a bit stops depending on itself and the map is no longer a permutation.\n */\nfn laineKarras(value: u32, seed: u32) -> u32 {\n var x = value + seed;\n x = x ^ (x * 0x6c50b47cu);\n x = x ^ (x * 0xb82f1e52u);\n x = x ^ (x * 0xc7afe784u);\n x = x ^ (x * 0x8d22f6e6u);\n\n return x;\n}\n\nfn nestedScramble(value: u32, seed: u32) -> u32 {\n return reverseBits(laineKarras(reverseBits(value), seed));\n}\n\n/** The first two Sobol dimensions, as fixed point fractions. */\nfn sobol2(index: u32) -> vec2<u32> {\n var second = 0u;\n var direction = 0x80000000u;\n var bits = index;\n\n loop {\n if (bits == 0u) {\n break;\n }\n\n if ((bits & 1u) != 0u) {\n second = second ^ direction;\n }\n\n bits = bits >> 1u;\n direction = direction ^ (direction >> 1u);\n }\n\n return vec2<u32>(reverseBits(index), second);\n}\n\nfn toUnit(value: u32) -> f32 {\n return min(f32(value) * 2.3283064365386963e-10, 0.99999994);\n}\n\n/**\n * The next pair of dimensions, in [0, 1). The sample index is shuffled per pair as well, which pads the two dimensional\n * sequence out to as many dimensions as a path needs without correlating them.\n */\nfn sample2D(rng: ptr<function, PathSampler>) -> vec2<f32> {\n let pair = (*rng).seed + (*rng).dimension * 2654435761u;\n (*rng).dimension = (*rng).dimension + 1u;\n\n let shuffled = nestedScramble((*rng).index, pcgHash(pair));\n let point = sobol2(shuffled);\n\n return vec2<f32>(\n toUnit(nestedScramble(point.x, pcgHash(pair + 0x9e3779b9u))),\n toUnit(nestedScramble(point.y, pcgHash(pair + 0x3c6ef372u))));\n}\n";
|
|
9
|
+
//# sourceMappingURL=random.wgsl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"random.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/random.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,4rEAkFvB,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sample generator: a Sobol sequence with hash based Owen scrambling (Burley, "Practical Hash-based Owen
|
|
3
|
+
* Scrambling", 2020). Every pixel draws its own scrambled sequence, decorrelated from its neighbours, and inside a pixel
|
|
4
|
+
* the samples are stratified in every pair of dimensions, so the estimate converges faster than with independent
|
|
5
|
+
* random numbers. Only the sample index, the pixel and a dimension counter feed the generator, so a pixel draws the same
|
|
6
|
+
* numbers whichever dispatch happens to cover it and the image does not depend on the tiling.
|
|
7
|
+
*/
|
|
8
|
+
export const RANDOM_WGSL = /* wgsl */ `
|
|
9
|
+
fn pcgHash(value: u32) -> u32 {
|
|
10
|
+
let state = value * 747796405u + 2891336453u;
|
|
11
|
+
let word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
|
|
12
|
+
|
|
13
|
+
return (word >> 22u) ^ word;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
struct PathSampler {
|
|
17
|
+
seed: u32,
|
|
18
|
+
index: u32,
|
|
19
|
+
dimension: u32,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
fn makeSampler(pixel: vec2<u32>, index: u32) -> PathSampler {
|
|
23
|
+
var rng: PathSampler;
|
|
24
|
+
rng.seed = pcgHash(pixel.x * 1973u + pixel.y * 9277u + 1u);
|
|
25
|
+
rng.index = index;
|
|
26
|
+
rng.dimension = 0u;
|
|
27
|
+
|
|
28
|
+
return rng;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The Laine-Karras hash, which permutes the high bits in a way that nests: what Owen scrambling asks for. Every
|
|
33
|
+
* multiplier has to be even, or a bit stops depending on itself and the map is no longer a permutation.
|
|
34
|
+
*/
|
|
35
|
+
fn laineKarras(value: u32, seed: u32) -> u32 {
|
|
36
|
+
var x = value + seed;
|
|
37
|
+
x = x ^ (x * 0x6c50b47cu);
|
|
38
|
+
x = x ^ (x * 0xb82f1e52u);
|
|
39
|
+
x = x ^ (x * 0xc7afe784u);
|
|
40
|
+
x = x ^ (x * 0x8d22f6e6u);
|
|
41
|
+
|
|
42
|
+
return x;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn nestedScramble(value: u32, seed: u32) -> u32 {
|
|
46
|
+
return reverseBits(laineKarras(reverseBits(value), seed));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The first two Sobol dimensions, as fixed point fractions. */
|
|
50
|
+
fn sobol2(index: u32) -> vec2<u32> {
|
|
51
|
+
var second = 0u;
|
|
52
|
+
var direction = 0x80000000u;
|
|
53
|
+
var bits = index;
|
|
54
|
+
|
|
55
|
+
loop {
|
|
56
|
+
if (bits == 0u) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if ((bits & 1u) != 0u) {
|
|
61
|
+
second = second ^ direction;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
bits = bits >> 1u;
|
|
65
|
+
direction = direction ^ (direction >> 1u);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return vec2<u32>(reverseBits(index), second);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn toUnit(value: u32) -> f32 {
|
|
72
|
+
return min(f32(value) * 2.3283064365386963e-10, 0.99999994);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The next pair of dimensions, in [0, 1). The sample index is shuffled per pair as well, which pads the two dimensional
|
|
77
|
+
* sequence out to as many dimensions as a path needs without correlating them.
|
|
78
|
+
*/
|
|
79
|
+
fn sample2D(rng: ptr<function, PathSampler>) -> vec2<f32> {
|
|
80
|
+
let pair = (*rng).seed + (*rng).dimension * 2654435761u;
|
|
81
|
+
(*rng).dimension = (*rng).dimension + 1u;
|
|
82
|
+
|
|
83
|
+
let shuffled = nestedScramble((*rng).index, pcgHash(pair));
|
|
84
|
+
let point = sobol2(shuffled);
|
|
85
|
+
|
|
86
|
+
return vec2<f32>(
|
|
87
|
+
toUnit(nestedScramble(point.x, pcgHash(pair + 0x9e3779b9u))),
|
|
88
|
+
toUnit(nestedScramble(point.y, pcgHash(pair + 0x3c6ef372u))));
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
//# sourceMappingURL=random.wgsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"random.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/random.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFrC,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** The geometric primitives the lobes and the light sampling are built from. */
|
|
2
|
+
export declare const SAMPLING_WGSL = "\n/** Branchless orthonormal basis around a unit normal (Duff and others, 2017). */\nfn orthonormalBasis(n: vec3<f32>) -> mat3x3<f32> {\n let s = select(-1.0, 1.0, n.z >= 0.0);\n let a = -1.0 / (s + n.z);\n let b = n.x * n.y * a;\n\n return mat3x3<f32>(\n vec3<f32>(1.0 + s * n.x * n.x * a, s * b, -s * n.x),\n vec3<f32>(b, s + n.y * n.y * a, -n.y),\n n);\n}\n\n/** Cosine weighted direction in the hemisphere around the normal; the weight cancels the Lambert cosine. */\nfn cosineDirection(normal: vec3<f32>, u: vec2<f32>) -> vec3<f32> {\n let radius = sqrt(u.x);\n let angle = 2.0 * PI * u.y;\n let local = vec3<f32>(radius * cos(angle), radius * sin(angle), sqrt(max(0.0, 1.0 - u.x)));\n\n return normalize(orthonormalBasis(normal) * local);\n}\n\n/** The unit normal of the triangle's plane; which side it faces does not matter, every emitter is two sided. */\nfn triangleNormal(triangle: u32) -> vec3<f32> {\n let a = triangleVertex(triangle, 0u);\n\n return normalize(cross(triangleVertex(triangle, 1u) - a, triangleVertex(triangle, 2u) - a));\n}\n";
|
|
3
|
+
//# sourceMappingURL=sampling.wgsl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampling.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/sampling.wgsl.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,aAAa,sjCA4BzB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** The geometric primitives the lobes and the light sampling are built from. */
|
|
2
|
+
export const SAMPLING_WGSL = /* wgsl */ `
|
|
3
|
+
/** Branchless orthonormal basis around a unit normal (Duff and others, 2017). */
|
|
4
|
+
fn orthonormalBasis(n: vec3<f32>) -> mat3x3<f32> {
|
|
5
|
+
let s = select(-1.0, 1.0, n.z >= 0.0);
|
|
6
|
+
let a = -1.0 / (s + n.z);
|
|
7
|
+
let b = n.x * n.y * a;
|
|
8
|
+
|
|
9
|
+
return mat3x3<f32>(
|
|
10
|
+
vec3<f32>(1.0 + s * n.x * n.x * a, s * b, -s * n.x),
|
|
11
|
+
vec3<f32>(b, s + n.y * n.y * a, -n.y),
|
|
12
|
+
n);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Cosine weighted direction in the hemisphere around the normal; the weight cancels the Lambert cosine. */
|
|
16
|
+
fn cosineDirection(normal: vec3<f32>, u: vec2<f32>) -> vec3<f32> {
|
|
17
|
+
let radius = sqrt(u.x);
|
|
18
|
+
let angle = 2.0 * PI * u.y;
|
|
19
|
+
let local = vec3<f32>(radius * cos(angle), radius * sin(angle), sqrt(max(0.0, 1.0 - u.x)));
|
|
20
|
+
|
|
21
|
+
return normalize(orthonormalBasis(normal) * local);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The unit normal of the triangle's plane; which side it faces does not matter, every emitter is two sided. */
|
|
25
|
+
fn triangleNormal(triangle: u32) -> vec3<f32> {
|
|
26
|
+
let a = triangleVertex(triangle, 0u);
|
|
27
|
+
|
|
28
|
+
return normalize(cross(triangleVertex(triangle, 1u) - a, triangleVertex(triangle, 2u) - a));
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
//# sourceMappingURL=sampling.wgsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampling.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/sampling.wgsl.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BvC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a translated shader reads from the kernel: its inputs at a hit, its texture calls and its varyings. The
|
|
3
|
+
* programs themselves are generated per scene by shaderProgram.ts and come right after this.
|
|
4
|
+
*/
|
|
5
|
+
export declare const SHADER_SUPPORT_WGSL = "\n/** Everything a fragment shader can ask about the point it runs at. */\nstruct ShaderInput {\n // Where the material's uniform values start in the texture buffer.\n data: u32,\n material: u32,\n triangle: u32,\n frontFacing: bool,\n bary: vec2<f32>,\n // The barycentrics the pixel to the right and the pixel above would have found on the same plane.\n baryDx: vec2<f32>,\n baryDy: vec2<f32>,\n // GL window coordinates: x right, y up from the bottom, z the depth, w the reciprocal of clip w.\n fragCoord: vec4<f32>,\n pointCoord: vec2<f32>,\n // A sky's view direction.\n direction: vec3<f32>,\n // A built-in material's state before lighting, for its hooks, in Three's view space.\n diffuseColor: vec4<f32>,\n normal: vec3<f32>,\n roughness: f32,\n metalness: f32,\n emissive: vec3<f32>,\n viewPosition: vec3<f32>,\n geometryNormal: vec3<f32>,\n uv: vec2<f32>,\n // What gl_FragColor holds when a hook after <opaque_fragment> starts.\n fragColor: vec4<f32>,\n};\n\nstruct ShaderOutput {\n color: vec4<f32>,\n discarded: bool,\n diffuseColor: vec4<f32>,\n normal: vec3<f32>,\n roughness: f32,\n metalness: f32,\n emissive: vec3<f32>,\n};\n\n/** The output of a shader that changed nothing, which is what a hook that does not touch a value leaves. */\nfn noShaderOutput(input: ShaderInput) -> ShaderOutput {\n var output: ShaderOutput;\n output.color = vec4<f32>(0.0, 0.0, 0.0, 1.0);\n output.discarded = false;\n output.diffuseColor = input.diffuseColor;\n output.normal = input.normal;\n output.roughness = input.roughness;\n output.metalness = input.metalness;\n output.emissive = input.emissive;\n\n return output;\n}\n\n/** texture2D: an unbound sampler reads black, as WebGL's does. */\nfn shaderSample2D(index: u32, uv: vec2<f32>) -> vec4<f32> {\n if (index == NO_TEXTURE) {\n return vec4<f32>(0.0, 0.0, 0.0, 1.0);\n }\n\n return sampleTextureRaw(index, uv);\n}\n\n/** textureCube: the snapshot hands a cube map over unwrapped, mirrored the way Three samples a cube texture. */\nfn shaderSampleCube(index: u32, direction: vec3<f32>) -> vec4<f32> {\n if (index == NO_TEXTURE) {\n return vec4<f32>(0.0, 0.0, 0.0, 1.0);\n }\n\n return sampleEquirect(index, direction);\n}\n\nfn shaderTexelFetch(index: u32, texel: vec2<i32>) -> vec4<f32> {\n if (index == NO_TEXTURE) {\n return vec4<f32>(0.0, 0.0, 0.0, 1.0);\n }\n\n let info = textureInfo(index);\n let size = vec2<i32>(info.size);\n let at = clamp(texel, vec2<i32>(0), max(size - vec2<i32>(1), vec2<i32>(0)));\n // texelFetch reads rows as GL stores them; a texture flipped on upload is flipped back here.\n let row = select(at.y, size.y - 1 - at.y, (info.flags & 8u) != 0u);\n\n return readTexel(info, at.x, row);\n}\n\nfn shaderTextureSize(index: u32) -> vec2<i32> {\n if (index == NO_TEXTURE) {\n return vec2<i32>(0);\n }\n\n return vec2<i32>(textureInfo(index).size);\n}\n\n/** Where a triangle's varyings start (x) and how many floats one corner has (y). */\nfn shaderVaryingAddress(material: u32, triangle: u32) -> vec2<u32> {\n let stride = materials[material].varyingStride;\n let local = triangle - materials[material].firstTriangle;\n\n return vec2<u32>(frame.varyingOffset + materials[material].varyingBase + local * 3u * stride, stride);\n}\n\n/** One component of a varying at a point: interpolated, or the last vertex's for a flat one, as GL takes it. */\nfn shaderVarying(at: vec2<u32>, component: u32, bary: vec2<f32>, flat: bool) -> f32 {\n let v0 = attributes[at.x + component];\n let v1 = attributes[at.x + at.y + component];\n let v2 = attributes[at.x + 2u * at.y + component];\n\n if (flat) {\n return v2;\n }\n\n return v0 * (1.0 - bary.x - bary.y) + v1 * bary.x + v2 * bary.y;\n}\n";
|
|
6
|
+
//# sourceMappingURL=shaderSupport.wgsl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shaderSupport.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/shaderSupport.wgsl.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,mBAAmB,sqHAkH/B,CAAC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a translated shader reads from the kernel: its inputs at a hit, its texture calls and its varyings. The
|
|
3
|
+
* programs themselves are generated per scene by shaderProgram.ts and come right after this.
|
|
4
|
+
*/
|
|
5
|
+
export const SHADER_SUPPORT_WGSL = /* wgsl */ `
|
|
6
|
+
/** Everything a fragment shader can ask about the point it runs at. */
|
|
7
|
+
struct ShaderInput {
|
|
8
|
+
// Where the material's uniform values start in the texture buffer.
|
|
9
|
+
data: u32,
|
|
10
|
+
material: u32,
|
|
11
|
+
triangle: u32,
|
|
12
|
+
frontFacing: bool,
|
|
13
|
+
bary: vec2<f32>,
|
|
14
|
+
// The barycentrics the pixel to the right and the pixel above would have found on the same plane.
|
|
15
|
+
baryDx: vec2<f32>,
|
|
16
|
+
baryDy: vec2<f32>,
|
|
17
|
+
// GL window coordinates: x right, y up from the bottom, z the depth, w the reciprocal of clip w.
|
|
18
|
+
fragCoord: vec4<f32>,
|
|
19
|
+
pointCoord: vec2<f32>,
|
|
20
|
+
// A sky's view direction.
|
|
21
|
+
direction: vec3<f32>,
|
|
22
|
+
// A built-in material's state before lighting, for its hooks, in Three's view space.
|
|
23
|
+
diffuseColor: vec4<f32>,
|
|
24
|
+
normal: vec3<f32>,
|
|
25
|
+
roughness: f32,
|
|
26
|
+
metalness: f32,
|
|
27
|
+
emissive: vec3<f32>,
|
|
28
|
+
viewPosition: vec3<f32>,
|
|
29
|
+
geometryNormal: vec3<f32>,
|
|
30
|
+
uv: vec2<f32>,
|
|
31
|
+
// What gl_FragColor holds when a hook after <opaque_fragment> starts.
|
|
32
|
+
fragColor: vec4<f32>,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
struct ShaderOutput {
|
|
36
|
+
color: vec4<f32>,
|
|
37
|
+
discarded: bool,
|
|
38
|
+
diffuseColor: vec4<f32>,
|
|
39
|
+
normal: vec3<f32>,
|
|
40
|
+
roughness: f32,
|
|
41
|
+
metalness: f32,
|
|
42
|
+
emissive: vec3<f32>,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** The output of a shader that changed nothing, which is what a hook that does not touch a value leaves. */
|
|
46
|
+
fn noShaderOutput(input: ShaderInput) -> ShaderOutput {
|
|
47
|
+
var output: ShaderOutput;
|
|
48
|
+
output.color = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
49
|
+
output.discarded = false;
|
|
50
|
+
output.diffuseColor = input.diffuseColor;
|
|
51
|
+
output.normal = input.normal;
|
|
52
|
+
output.roughness = input.roughness;
|
|
53
|
+
output.metalness = input.metalness;
|
|
54
|
+
output.emissive = input.emissive;
|
|
55
|
+
|
|
56
|
+
return output;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** texture2D: an unbound sampler reads black, as WebGL's does. */
|
|
60
|
+
fn shaderSample2D(index: u32, uv: vec2<f32>) -> vec4<f32> {
|
|
61
|
+
if (index == NO_TEXTURE) {
|
|
62
|
+
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return sampleTextureRaw(index, uv);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** textureCube: the snapshot hands a cube map over unwrapped, mirrored the way Three samples a cube texture. */
|
|
69
|
+
fn shaderSampleCube(index: u32, direction: vec3<f32>) -> vec4<f32> {
|
|
70
|
+
if (index == NO_TEXTURE) {
|
|
71
|
+
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return sampleEquirect(index, direction);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn shaderTexelFetch(index: u32, texel: vec2<i32>) -> vec4<f32> {
|
|
78
|
+
if (index == NO_TEXTURE) {
|
|
79
|
+
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let info = textureInfo(index);
|
|
83
|
+
let size = vec2<i32>(info.size);
|
|
84
|
+
let at = clamp(texel, vec2<i32>(0), max(size - vec2<i32>(1), vec2<i32>(0)));
|
|
85
|
+
// texelFetch reads rows as GL stores them; a texture flipped on upload is flipped back here.
|
|
86
|
+
let row = select(at.y, size.y - 1 - at.y, (info.flags & 8u) != 0u);
|
|
87
|
+
|
|
88
|
+
return readTexel(info, at.x, row);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fn shaderTextureSize(index: u32) -> vec2<i32> {
|
|
92
|
+
if (index == NO_TEXTURE) {
|
|
93
|
+
return vec2<i32>(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return vec2<i32>(textureInfo(index).size);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Where a triangle's varyings start (x) and how many floats one corner has (y). */
|
|
100
|
+
fn shaderVaryingAddress(material: u32, triangle: u32) -> vec2<u32> {
|
|
101
|
+
let stride = materials[material].varyingStride;
|
|
102
|
+
let local = triangle - materials[material].firstTriangle;
|
|
103
|
+
|
|
104
|
+
return vec2<u32>(frame.varyingOffset + materials[material].varyingBase + local * 3u * stride, stride);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** One component of a varying at a point: interpolated, or the last vertex's for a flat one, as GL takes it. */
|
|
108
|
+
fn shaderVarying(at: vec2<u32>, component: u32, bary: vec2<f32>, flat: bool) -> f32 {
|
|
109
|
+
let v0 = attributes[at.x + component];
|
|
110
|
+
let v1 = attributes[at.x + at.y + component];
|
|
111
|
+
let v2 = attributes[at.x + 2u * at.y + component];
|
|
112
|
+
|
|
113
|
+
if (flat) {
|
|
114
|
+
return v2;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return v0 * (1.0 - bary.x - bary.y) + v1 * bary.x + v2 * bary.y;
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
//# sourceMappingURL=shaderSupport.wgsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shaderSupport.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/shaderSupport.wgsl.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkH7C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a hit actually is once the maps are read: a material resolved at one point on one triangle. The integrator
|
|
3
|
+
* resolves a hit once and then asks the lobes about it, so no texture is read twice for the same point.
|
|
4
|
+
*
|
|
5
|
+
* The channel each map reads from and the way each value combines mirror Three's own shader chunks (r185), which the
|
|
6
|
+
* CPU renderer follows too: roughness from green, metalness from blue, the alpha map from green, clearcoat from red,
|
|
7
|
+
* its roughness from green, sheen roughness from alpha, and so on.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SURFACE_WGSL = "\nstruct Surface {\n // The shading normal, maps applied, facing the ray.\n normal: vec3<f32>,\n albedo: vec3<f32>,\n // What the surface radiates by itself: its emission, or the whole colour of a material Three does not light.\n emissive: vec3<f32>,\n // The dielectric's reflectance at normal incidence and at grazing angles, before metalness turns it to the albedo.\n specularColor: vec3<f32>,\n specularF90: f32,\n roughness: f32,\n metalness: f32,\n // How much of the surface actually covers this point: a cutout, a transparency or a discarded fragment.\n opacity: f32,\n // Three's diffuseColor.a, which blending other than normal weighs the surface by.\n alpha: f32,\n flags: u32,\n kind: u32,\n // Share of the diffuse layer that lets light through, with metal already taken out of it.\n transmission: f32,\n ior: f32,\n thickness: f32,\n dispersion: f32,\n // What a unit of distance inside the volume absorbs, per channel: zero for a volume that never darkens.\n absorption: vec3<f32>,\n // The interpolated normal facing the ray, before any map: thin walls, shadow rays and the clearcoat read this one.\n facing: vec3<f32>,\n // Whether the ray arrived from the side the normals point to, which is the outside of a closed volume.\n front: bool,\n clearcoat: f32,\n clearcoatRoughness: f32,\n clearcoatNormal: vec3<f32>,\n sheenColor: vec3<f32>,\n sheenRoughness: f32,\n iridescence: f32,\n iridescenceFresnel: vec3<f32>,\n // Anisotropic GGX: its strength, the roughness along the tangent, and the tangent frame it stretches along.\n anisotropy: f32,\n alphaT: f32,\n anisotropyT: vec3<f32>,\n anisotropyB: vec3<f32>,\n shininess: f32,\n // Phong's specular map, which scales its highlight.\n specularStrength: f32,\n gradientMap: u32,\n // The material's own environment map, which the paths leaving it see instead of the scene's.\n envMap: u32,\n envMapIntensity: f32,\n // Basic, Lambert and Phong blend their environment map over the lit colour: what the map shows here, and how much.\n legacyEnv: vec3<f32>,\n legacyAmount: f32,\n combine: u32,\n // Light a light map bakes into this point, which only the camera sees: the path traces the rest itself.\n lightMap: vec3<f32>,\n blending: u32,\n material: u32,\n};\n\n/** Barycentric weights of the two trailing vertices, for a point drawn uniformly over a triangle's area. */\nfn triangleBarycentric(u: vec2<f32>) -> vec2<f32> {\n let root = sqrt(u.x);\n\n return vec2<f32>(1.0 - root, u.y * root);\n}\n\nfn trianglePointAt(triangle: u32, bary: vec2<f32>) -> vec3<f32> {\n return triangleVertex(triangle, 0u) * (1.0 - bary.x - bary.y)\n + triangleVertex(triangle, 1u) * bary.x\n + triangleVertex(triangle, 2u) * bary.y;\n}\n\nfn triangleUv(triangle: u32, bary: vec2<f32>, channel: u32) -> vec2<f32> {\n return attributeUv(triangle, 0u, channel) * (1.0 - bary.x - bary.y)\n + attributeUv(triangle, 1u, channel) * bary.x\n + attributeUv(triangle, 2u, channel) * bary.y;\n}\n\nfn triangleColor(triangle: u32, bary: vec2<f32>) -> vec4<f32> {\n return attributeColor(triangle, 0u) * (1.0 - bary.x - bary.y)\n + attributeColor(triangle, 1u) * bary.x\n + attributeColor(triangle, 2u) * bary.y;\n}\n\n/** A map slot, sampled through the UV set the texture asks for. Missing maps read as one, so a multiply is always safe. */\nfn sampleMap(index: u32, triangle: u32, bary: vec2<f32>) -> vec4<f32> {\n if (index == NO_TEXTURE) {\n return vec4<f32>(1.0);\n }\n\n return sampleTextureAt(index, triangleUv(triangle, bary, textureInfo(index).channel));\n}\n\n/** Whether the ray sees the side Three calls the front: the winding, which packing keeps counter-clockwise. */\nfn isFrontFace(triangle: u32, direction: vec3<f32>) -> bool {\n return dot(triangleNormal(triangle), direction) < 0.0;\n}\n\n/** Three culls the side a material does not draw. */\nfn isCulled(material: u32, triangle: u32, direction: vec3<f32>) -> bool {\n let side = materials[material].side;\n let front = isFrontFace(triangle, direction);\n\n return (side == SIDE_FRONT && !front) || (side == SIDE_BACK && front);\n}\n\n/** What the triangle radiates at this point, its emissive map included so direct and indirect light agree on it. */\nfn emissionAt(triangle: u32, bary: vec2<f32>) -> vec3<f32> {\n let m = materialIndexOf(triangle);\n let map = sampleMap(materials[m].emissiveMap, triangle, bary);\n\n return materials[m].emissive.rgb * materials[m].emissive.w * map.rgb;\n}\n\n/** Three's material clipping planes: a point on the far side of one (or of all, with clipIntersection) is cut away. */\nfn isClipped(m: u32, point: vec3<f32>) -> bool {\n let count = materials[m].clipCount;\n\n if (count == 0u) {\n return false;\n }\n\n let intersection = (materials[m].flags & MATERIAL_CLIP_INTERSECTION) != 0u;\n var every = true;\n\n for (var i = 0u; i < count; i = i + 1u) {\n let plane = extraVec4(materials[m].clipStart + i * 4u);\n let outside = dot(plane.xyz, point) + plane.w < 0.0;\n\n if (outside && !intersection) {\n return true;\n }\n\n every = every && outside;\n }\n\n return intersection && every;\n}\n\n/** How large one pixel is at this point, in world units: what a one pixel wide line covers. */\nfn pixelFootprint(point: vec3<f32>) -> f32 {\n let scale = 2.0 / (frame.projection[1][1] * f32(frame.resolution.y));\n\n if (frame.cameraKind == 1u) {\n return scale;\n }\n\n return scale * max(-(frame.worldToCamera * vec4<f32>(point, 1.0)).z, 1e-6);\n}\n\nfn edgeDistance(point: vec3<f32>, a: vec3<f32>, b: vec3<f32>) -> f32 {\n let edge = b - a;\n\n return length(cross(point - a, edge)) / max(length(edge), 1e-20);\n}\n\n/** A wireframe draws only the edges of each triangle, one pixel wide as WebGL draws every line. */\nfn onWireframe(triangle: u32, point: vec3<f32>) -> bool {\n let a = triangleVertex(triangle, 0u);\n let b = triangleVertex(triangle, 1u);\n let c = triangleVertex(triangle, 2u);\n let nearest = min(edgeDistance(point, a, b), min(edgeDistance(point, b, c), edgeDistance(point, c, a)));\n\n return nearest <= 0.5 * pixelFootprint(point);\n}\n\n/** Three's diffuseColor.a: opacity, the colour map's alpha, the alpha map's green and the vertex alpha. */\nfn materialAlpha(m: u32, triangle: u32, bary: vec2<f32>) -> f32 {\n var alpha = materials[m].color.w * sampleMap(materials[m].map, triangle, bary).a\n * sampleMap(materials[m].alphaMap, triangle, bary).g;\n\n if ((materials[m].flags & MATERIAL_VERTEX_COLORS) != 0u) {\n alpha = alpha * triangleColor(triangle, bary).a;\n }\n\n return alpha;\n}\n\n/**\n * How much of the surface is there, from its alpha the way Three's chunks use it: alphaTest cuts, alphaHash keeps the\n * share, and only a transparent material with normal blending lets the rest through; an opaque one sets alpha to one.\n */\nfn coverageOf(m: u32, alpha: f32) -> f32 {\n if (materials[m].alphaTest > 0.0 && alpha < materials[m].alphaTest) {\n return 0.0;\n }\n\n var coverage = 1.0;\n\n if ((materials[m].flags & MATERIAL_ALPHA_HASH) != 0u) {\n coverage = clamp(alpha, 0.0, 1.0);\n }\n\n if ((materials[m].flags & MATERIAL_TRANSPARENT) != 0u && materials[m].blending == BLEND_NORMAL) {\n coverage = coverage * clamp(alpha, 0.0, 1.0);\n }\n\n return coverage;\n}\n\n/** Barycentrics on the triangle's plane of a ray, extrapolated past its edges: where a neighbouring pixel lands. */\nfn planeBarycentric(triangle: u32, origin: vec3<f32>, direction: vec3<f32>, fallback: vec2<f32>) -> vec2<f32> {\n let a = triangleVertex(triangle, 0u);\n let e1 = triangleVertex(triangle, 1u) - a;\n let e2 = triangleVertex(triangle, 2u) - a;\n let n = cross(e1, e2);\n let denominator = dot(n, direction);\n\n if (abs(denominator) < 1e-20) {\n return fallback;\n }\n\n let p = origin + direction * (dot(n, a - origin) / denominator) - a;\n let d00 = dot(e1, e1);\n let d01 = dot(e1, e2);\n let d11 = dot(e2, e2);\n let d20 = dot(p, e1);\n let d21 = dot(p, e2);\n let det = d00 * d11 - d01 * d01;\n\n if (abs(det) < 1e-30) {\n return fallback;\n }\n\n return vec2<f32>((d11 * d20 - d01 * d21) / det, (d00 * d21 - d01 * d20) / det);\n}\n\n/** The camera ray through a point given in normalized device coordinates: origin on the near plane, and its length. */\nfn ndcRay(ndc: vec2<f32>) -> array<vec4<f32>, 2> {\n let nearView = frame.inverseProjection * vec4<f32>(ndc, -1.0, 1.0);\n let farView = frame.inverseProjection * vec4<f32>(ndc, 1.0, 1.0);\n let near = (frame.cameraToWorld * vec4<f32>(nearView.xyz / nearView.w, 1.0)).xyz;\n let far = (frame.cameraToWorld * vec4<f32>(farView.xyz / farView.w, 1.0)).xyz;\n let span = far - near;\n let extent = max(length(span), 1e-20);\n\n return array<vec4<f32>, 2>(vec4<f32>(near, 0.0), vec4<f32>(span / extent, extent));\n}\n\n/** What a fragment shader knows about the point it runs at, in GL's own terms. */\nfn shaderInputAt(m: u32, triangle: u32, bary: vec2<f32>, direction: vec3<f32>) -> ShaderInput {\n var input: ShaderInput;\n let point = trianglePointAt(triangle, bary);\n let view = frame.worldToCamera * vec4<f32>(point, 1.0);\n let clip = frame.projection * view;\n let w = select(clip.w, 1e-20, abs(clip.w) < 1e-20);\n let ndc = clip.xyz / w;\n let size = vec2<f32>(frame.resolution);\n let pixel = (ndc.xy * 0.5 + 0.5) * size;\n let right = ndcRay(vec2<f32>(ndc.x + 2.0 / size.x, ndc.y));\n let above = ndcRay(vec2<f32>(ndc.x, ndc.y + 2.0 / size.y));\n\n input.data = materials[m].shaderData;\n input.material = m;\n input.triangle = triangle;\n input.frontFacing = isFrontFace(triangle, direction);\n input.bary = bary;\n input.baryDx = planeBarycentric(triangle, right[0].xyz, right[1].xyz, bary);\n input.baryDy = planeBarycentric(triangle, above[0].xyz, above[1].xyz, bary);\n input.fragCoord = vec4<f32>(pixel, ndc.z * 0.5 + 0.5, 1.0 / w);\n let uv = triangleUv(triangle, bary, 0u);\n input.uv = uv;\n input.fragColor = vec4<f32>(0.0, 0.0, 0.0, 1.0);\n // A point's quad carries gl_PointCoord in its first UV set, with v running up as a texture's does.\n input.pointCoord = vec2<f32>(uv.x, 1.0 - uv.y);\n input.direction = direction;\n input.diffuseColor = vec4<f32>(1.0);\n input.normal = vec3<f32>(0.0, 0.0, 1.0);\n input.geometryNormal = vec3<f32>(0.0, 0.0, 1.0);\n input.roughness = 1.0;\n input.metalness = 0.0;\n input.emissive = vec3<f32>(0.0);\n input.viewPosition = -view.xyz;\n\n return input;\n}\n\n/** World to view space for a direction, which is the space Three's shaders keep their normals in. */\nfn toView(direction: vec3<f32>) -> vec3<f32> {\n return normalize((frame.worldToCamera * vec4<f32>(direction, 0.0)).xyz);\n}\n\nfn toWorld(direction: vec3<f32>) -> vec3<f32> {\n return normalize((frame.cameraToWorld * vec4<f32>(direction, 0.0)).xyz);\n}\n\n/**\n * How much of the surface covers this point for a shadow ray: the same alpha, cut, clipping and wireframe the camera\n * sees, and a shader's own discard. Blending other than normal is handled by the caller.\n */\nfn coverageAt(triangle: u32, bary: vec2<f32>, direction: vec3<f32>) -> f32 {\n let m = materialIndexOf(triangle);\n let flags = materials[m].flags;\n\n if (materials[m].clipCount > 0u || (flags & MATERIAL_WIREFRAME) != 0u) {\n let point = trianglePointAt(triangle, bary);\n\n if (isClipped(m, point)) {\n return 0.0;\n }\n\n if ((flags & MATERIAL_WIREFRAME) != 0u && !onWireframe(triangle, point)) {\n return 0.0;\n }\n }\n\n var alpha = materialAlpha(m, triangle, bary);\n\n if (materials[m].program != NO_PROGRAM) {\n var input = shaderInputAt(m, triangle, bary, direction);\n input.diffuseColor = vec4<f32>(materials[m].color.rgb, alpha);\n let output = runProgram(materials[m].program, input);\n\n if (output.discarded) {\n return 0.0;\n }\n\n alpha = select(output.color.a, output.diffuseColor.a, (flags & MATERIAL_PATCHED) != 0u);\n }\n\n return coverageOf(m, alpha);\n}\n\n/** A tangent frame from the triangle's own UVs, which is what Three's tangents amount to for a flat triangle. */\nstruct TangentFrame {\n tangent: vec3<f32>,\n bitangent: vec3<f32>,\n valid: bool,\n};\n\nfn tangentFrame(triangle: u32, channel: u32, normal: vec3<f32>) -> TangentFrame {\n var tangents: TangentFrame;\n tangents.valid = false;\n\n let p0 = triangleVertex(triangle, 0u);\n let edge1 = triangleVertex(triangle, 1u) - p0;\n let edge2 = triangleVertex(triangle, 2u) - p0;\n let uv0 = attributeUv(triangle, 0u, channel);\n let delta1 = attributeUv(triangle, 1u, channel) - uv0;\n let delta2 = attributeUv(triangle, 2u, channel) - uv0;\n let determinant = delta1.x * delta2.y - delta2.x * delta1.y;\n\n // A triangle with no UV area has no tangent frame to offer.\n if (abs(determinant) < 1e-16) {\n let basis = orthonormalBasis(normal);\n tangents.tangent = basis[0];\n tangents.bitangent = basis[1];\n\n return tangents;\n }\n\n let raw = (edge1 * delta2.y - edge2 * delta1.y) / determinant;\n // Made perpendicular to the shading normal, so a map tilts the interpolated normal rather than the flat one.\n let tangent = normalize(raw - normal * dot(normal, raw));\n\n if (!all(tangent == tangent)) {\n let basis = orthonormalBasis(normal);\n tangents.tangent = basis[0];\n tangents.bitangent = basis[1];\n\n return tangents;\n }\n\n tangents.tangent = tangent;\n tangents.bitangent = cross(normal, tangent) * select(-1.0, 1.0, determinant >= 0.0);\n tangents.valid = true;\n\n return tangents;\n}\n\n/** A tangent space normal map over a normal, scaled the way Three's normalScale scales it. */\nfn tangentNormal(index: u32, triangle: u32, bary: vec2<f32>, normal: vec3<f32>, scale: vec2<f32>) -> vec3<f32> {\n if (index == NO_TEXTURE) {\n return normal;\n }\n\n let channel = textureInfo(index).channel;\n let tangents = tangentFrame(triangle, channel, normal);\n\n if (!tangents.valid) {\n return normal;\n }\n\n let texel = sampleTextureAt(index, triangleUv(triangle, bary, channel)).xyz * 2.0 - 1.0;\n let mapped = normalize(\n tangents.tangent * (texel.x * scale.x)\n + tangents.bitangent * (texel.y * scale.y)\n + normal * texel.z);\n\n return select(mapped, normal, !all(mapped == mapped));\n}\n\n/** The normal the material asks for: an object space or tangent space normal map, or else a bump map. */\nfn mappedNormal(m: u32, triangle: u32, bary: vec2<f32>, normal: vec3<f32>) -> vec3<f32> {\n let normalMap = materials[m].normalMap;\n\n if (normalMap != NO_TEXTURE && (materials[m].flags & MATERIAL_OBJECT_NORMAL_MAP) != 0u) {\n let texel = sampleMap(normalMap, triangle, bary).xyz * 2.0 - 1.0;\n let world = normalize(vec3<f32>(\n dot(materials[m].normalRow0.xyz, texel),\n dot(materials[m].normalRow1.xyz, texel),\n dot(materials[m].normalRow2.xyz, texel)));\n\n // Seen from behind, a double sided surface turns its map around with it.\n return select(world, -world, dot(world, normal) < 0.0);\n }\n\n if (normalMap != NO_TEXTURE) {\n return tangentNormal(normalMap, triangle, bary, normal, vec2<f32>(materials[m].normalScaleX, materials[m].normalScaleY));\n }\n\n let bumpMap = materials[m].bumpMap;\n\n if (bumpMap != NO_TEXTURE) {\n // The CPU renderer's bump: the height's change over one texel, along the tangent frame of the map's UVs.\n let info = textureInfo(bumpMap);\n let channel = info.channel;\n let tangents = tangentFrame(triangle, channel, normal);\n let uv = triangleUv(triangle, bary, channel);\n let du = 1.0 / f32(max(info.size.x, 1u));\n let dv = 1.0 / f32(max(info.size.y, 1u));\n let scale = materials[m].bumpScale;\n let height = sampleTextureAt(bumpMap, uv).x;\n let dx = (sampleTextureAt(bumpMap, uv + vec2<f32>(du, 0.0)).x - height) * scale;\n let dy = (sampleTextureAt(bumpMap, uv + vec2<f32>(0.0, dv)).x - height) * scale;\n let bumped = normalize(normal - (tangents.tangent * dx + tangents.bitangent * dy));\n\n return select(bumped, normal, !all(bumped == bumped));\n }\n\n return normal;\n}\n\n/** The interpolated vertex normal as the mesh authored it, pointing out of a closed volume. */\nfn authoredNormal(triangle: u32, bary: vec2<f32>) -> vec3<f32> {\n let base = triangle * 9u;\n let n0 = vec3<f32>(attributes[base], attributes[base + 1u], attributes[base + 2u]);\n let n1 = vec3<f32>(attributes[base + 3u], attributes[base + 4u], attributes[base + 5u]);\n let n2 = vec3<f32>(attributes[base + 6u], attributes[base + 7u], attributes[base + 8u]);\n\n return normalize(n0 * (1.0 - bary.x - bary.y) + n1 * bary.x + n2 * bary.y);\n}\n\n/** The interpolated vertex normal, or the face's for flat shading, turned to face the ray. */\nfn interpolatedNormal(m: u32, triangle: u32, bary: vec2<f32>, direction: vec3<f32>) -> vec3<f32> {\n var normal = authoredNormal(triangle, bary);\n\n if ((materials[m].flags & MATERIAL_FLAT) != 0u || !all(normal == normal)) {\n normal = triangleNormal(triangle);\n }\n\n return select(normal, -normal, dot(normal, direction) > 0.0);\n}\n\n/** Three's transmission at this point: the factor, its map's red channel, and none of it on metal. */\nfn transmissionAt(m: u32, triangle: u32, bary: vec2<f32>, metalness: f32) -> f32 {\n if (materials[m].transmission <= 0.0) {\n return 0.0;\n }\n\n let map = sampleMap(materials[m].transmissionMap, triangle, bary).r;\n\n return clamp(materials[m].transmission * map, 0.0, 1.0) * (1.0 - metalness);\n}\n\n/** Beer-Lambert: the colour reached at the attenuation distance fixes how fast each channel fades. */\nfn absorptionOf(m: u32) -> vec3<f32> {\n let attenuation = materials[m].attenuation;\n\n if (attenuation.w <= 0.0) {\n return vec3<f32>(0.0);\n }\n\n return -log(max(attenuation.rgb, vec3<f32>(1e-6))) / attenuation.w;\n}\n\n/** Three's evalSensitivity: the XYZ response of a thin film, Fourier-fitted, in linear sRGB. */\nfn filmSensitivity(opd: f32, shift: vec3<f32>) -> vec3<f32> {\n let phase = 2.0 * PI * opd * 1.0e-9;\n let value = vec3<f32>(5.4856e-13, 4.4201e-13, 5.2481e-13);\n let position = vec3<f32>(1.6810e+06, 1.7953e+06, 2.2084e+06);\n let variance = vec3<f32>(4.3278e+09, 9.3046e+09, 6.6121e+09);\n var xyz = value * sqrt(2.0 * PI * variance) * cos(position * phase + shift) * exp(-phase * phase * variance);\n\n xyz.x = xyz.x + 9.7470e-14 * sqrt(2.0 * PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift.x)\n * exp(-4.5282e+09 * phase * phase);\n xyz = xyz / 1.0685e-7;\n\n return mat3x3<f32>(\n vec3<f32>(3.2404542, -0.9692660, 0.0556434),\n vec3<f32>(-1.5371385, 1.8760108, -0.2040259),\n vec3<f32>(-0.4985314, 0.0415560, 1.0572252)) * xyz;\n}\n\nfn schlick3(f0: vec3<f32>, f90: f32, cosine: f32) -> vec3<f32> {\n let t = 1.0 - cosine;\n let k = t * t * t * t * t;\n\n return f0 + (vec3<f32>(f90) - f0) * k;\n}\n\n/** Three's evalIridescence (Belcour and Barla 2017): the Fresnel of a thin film over a base of reflectance baseF0. */\nfn evalIridescence(filmIor: f32, cosTheta1: f32, thickness: f32, baseF0: vec3<f32>) -> vec3<f32> {\n let ior = mix(1.0, filmIor, smoothstep(0.0, 0.03, thickness));\n let sinTheta2Sq = (1.0 / ior) * (1.0 / ior) * (1.0 - cosTheta1 * cosTheta1);\n let cosTheta2Sq = 1.0 - sinTheta2Sq;\n\n if (cosTheta2Sq < 0.0) {\n return vec3<f32>(1.0);\n }\n\n let cosTheta2 = sqrt(cosTheta2Sq);\n let r0 = ((ior - 1.0) / (ior + 1.0)) * ((ior - 1.0) / (ior + 1.0));\n let r12 = r0 + (1.0 - r0) * pow(1.0 - cosTheta1, 5.0);\n let t121 = 1.0 - r12;\n let phi12 = select(0.0, PI, ior < 1.0);\n let phi21 = PI - phi12;\n let root = sqrt(clamp(baseF0, vec3<f32>(0.0), vec3<f32>(0.9999)));\n let baseIor = (vec3<f32>(1.0) + root) / (vec3<f32>(1.0) - root);\n let r1 = ((baseIor - vec3<f32>(ior)) / (baseIor + vec3<f32>(ior))) * ((baseIor - vec3<f32>(ior)) / (baseIor + vec3<f32>(ior)));\n let r23 = schlick3(r1, 1.0, cosTheta2);\n let phi23 = select(vec3<f32>(0.0), vec3<f32>(PI), baseIor < vec3<f32>(ior));\n let opd = 2.0 * ior * thickness * cosTheta2;\n let phi = vec3<f32>(phi21) + phi23;\n let r123 = clamp(r12 * r23, vec3<f32>(1e-5), vec3<f32>(0.9999));\n let r123Root = sqrt(r123);\n let rs = t121 * t121 * r23 / (vec3<f32>(1.0) - r123);\n var intensity = vec3<f32>(r12) + rs;\n var cm = rs - vec3<f32>(t121);\n\n for (var m = 1; m <= 2; m = m + 1) {\n cm = cm * r123Root;\n intensity = intensity + cm * 2.0 * filmSensitivity(f32(m) * opd, f32(m) * phi);\n }\n\n return max(intensity, vec3<f32>(0.0));\n}\n\n/** Three's toon ramp: the gradient map across dot(n, l) from -1 to 1, or a single step at 0.7 without one. */\nfn toonRamp(gradientMap: u32, cosine: f32) -> f32 {\n let coordinate = cosine * 0.5 + 0.5;\n\n if (gradientMap == NO_TEXTURE) {\n return select(0.7, 1.0, coordinate >= 0.7);\n }\n\n return sampleTextureRaw(gradientMap, vec2<f32>(coordinate, 0.0)).r;\n}\n\n/** Everything the lobes need about one point of one triangle. */\nfn resolveSurface(triangle: u32, bary: vec2<f32>, direction: vec3<f32>) -> Surface {\n let m = materialIndexOf(triangle);\n let flags = materials[m].flags;\n let kind = materials[m].kind;\n let base = sampleMap(materials[m].map, triangle, bary);\n let facing = interpolatedNormal(m, triangle, bary, direction);\n let view = -direction;\n var surface: Surface;\n\n surface.material = m;\n surface.flags = flags;\n surface.kind = kind;\n surface.blending = materials[m].blending;\n surface.facing = facing;\n surface.front = dot(authoredNormal(triangle, bary), direction) < 0.0;\n surface.normal = mappedNormal(m, triangle, bary, facing);\n\n // A normal map may tilt past the horizon, where the surface would be lit from behind itself.\n if (dot(surface.normal, view) <= 0.0) {\n surface.normal = facing;\n }\n\n var albedo = materials[m].color.rgb * base.rgb;\n var alpha = materials[m].color.w * base.a * sampleMap(materials[m].alphaMap, triangle, bary).g;\n\n if ((flags & MATERIAL_VERTEX_COLORS) != 0u) {\n let color = triangleColor(triangle, bary);\n albedo = albedo * color.rgb;\n alpha = alpha * color.a;\n }\n\n var roughness = materials[m].specular.w * sampleMap(materials[m].roughnessMap, triangle, bary).g;\n var metalness = materials[m].metalness * sampleMap(materials[m].metalnessMap, triangle, bary).b;\n var emissive = emissionAt(triangle, bary);\n\n surface.opacity = 1.0;\n\n // A built-in material's hooks run on its values before lighting, in Three's view space; a shader material's output\n // is the colour itself.\n if (materials[m].program != NO_PROGRAM) {\n var input = shaderInputAt(m, triangle, bary, direction);\n input.diffuseColor = vec4<f32>(albedo, alpha);\n input.normal = toView(surface.normal);\n input.geometryNormal = toView(facing);\n input.roughness = roughness;\n input.metalness = metalness;\n input.emissive = emissive;\n let output = runProgram(materials[m].program, input);\n\n if (output.discarded) {\n surface.opacity = 0.0;\n }\n\n if ((flags & MATERIAL_PATCHED) != 0u) {\n albedo = output.diffuseColor.rgb;\n alpha = output.diffuseColor.a;\n roughness = output.roughness;\n metalness = output.metalness;\n emissive = output.emissive;\n let normal = toWorld(output.normal);\n\n if (all(normal == normal)) {\n surface.normal = select(normal, -normal, dot(normal, view) < 0.0);\n }\n } else {\n albedo = output.color.rgb;\n alpha = output.color.a;\n }\n }\n\n surface.opacity = surface.opacity * coverageOf(m, alpha);\n surface.alpha = alpha;\n surface.albedo = albedo;\n surface.roughness = clamp(roughness, 0.0525, 1.0);\n surface.metalness = clamp(metalness, 0.0, 1.0);\n surface.emissive = emissive;\n surface.transmission = transmissionAt(m, triangle, bary, surface.metalness);\n surface.ior = max(materials[m].ior, 1.0);\n surface.thickness = materials[m].thickness * sampleMap(materials[m].thicknessMap, triangle, bary).g;\n surface.dispersion = materials[m].dispersion;\n surface.absorption = absorptionOf(m);\n surface.shininess = materials[m].shininess;\n surface.specularStrength = sampleMap(materials[m].specularMap, triangle, bary).r;\n surface.gradientMap = materials[m].gradientMap;\n surface.envMap = NO_TEXTURE;\n surface.envMapIntensity = materials[m].envMapIntensity;\n surface.legacyAmount = 0.0;\n surface.legacyEnv = vec3<f32>(0.0);\n surface.combine = materials[m].combine;\n surface.lightMap = vec3<f32>(0.0);\n surface.clearcoat = 0.0;\n surface.iridescence = 0.0;\n surface.anisotropy = 0.0;\n surface.sheenColor = vec3<f32>(0.0);\n surface.specularColor = materials[m].specular.rgb;\n surface.specularF90 = 1.0;\n\n if ((flags & MATERIAL_PBR) != 0u) {\n if ((flags & MATERIAL_SPECULAR) != 0u) {\n // A physical material: the index of refraction, the specular colour and intensity and their maps.\n let intensity = materials[m].specularIntensity * sampleMap(materials[m].specularIntensityMap, triangle, bary).a;\n let color = materials[m].specular.rgb * sampleMap(materials[m].specularColorMap, triangle, bary).rgb;\n\n surface.specularColor = min(materials[m].dielectric * color, vec3<f32>(1.0)) * intensity;\n surface.specularF90 = mix(intensity, 1.0, surface.metalness);\n } else {\n // Three's standard material has no index of refraction and reflects 0.04.\n surface.specularColor = vec3<f32>(0.04);\n }\n\n surface.envMap = materials[m].envMap;\n\n if (materials[m].clearcoat > 0.0) {\n surface.clearcoat = clamp(materials[m].clearcoat * sampleMap(materials[m].clearcoatMap, triangle, bary).r, 0.0, 1.0);\n surface.clearcoatRoughness = clamp(\n materials[m].clearcoatRoughness * sampleMap(materials[m].clearcoatRoughnessMap, triangle, bary).g, 0.0525, 1.0);\n // The coat ignores the base's normal map: it has its own, over the interpolated normal.\n surface.clearcoatNormal = tangentNormal(materials[m].clearcoatNormalMap, triangle, bary, facing,\n vec2<f32>(materials[m].clearcoatNormalScaleX, materials[m].clearcoatNormalScaleY));\n\n if (dot(surface.clearcoatNormal, view) <= 0.0) {\n surface.clearcoatNormal = facing;\n }\n }\n\n if (any(materials[m].sheen.rgb > vec3<f32>(0.0))) {\n let map = sampleMap(materials[m].sheenRoughnessMap, triangle, bary).a;\n surface.sheenColor = materials[m].sheen.rgb * sampleMap(materials[m].sheenColorMap, triangle, bary).rgb;\n surface.sheenRoughness = clamp(materials[m].sheen.w, 0.0001, 1.0) * map;\n }\n\n if (materials[m].iridescence > 0.0) {\n var thickness = materials[m].iridescenceThicknessMax;\n\n if (materials[m].iridescenceThicknessMap != NO_TEXTURE) {\n thickness = mix(materials[m].iridescenceThicknessMin, materials[m].iridescenceThicknessMax,\n sampleMap(materials[m].iridescenceThicknessMap, triangle, bary).g);\n }\n\n if (thickness > 0.0) {\n surface.iridescence = clamp(\n materials[m].iridescence * sampleMap(materials[m].iridescenceMap, triangle, bary).r, 0.0, 1.0);\n\n let nv = clamp(dot(surface.normal, view), 0.0, 1.0);\n surface.iridescenceFresnel = mix(\n evalIridescence(materials[m].iridescenceIor, nv, thickness, surface.specularColor),\n evalIridescence(materials[m].iridescenceIor, nv, thickness, albedo),\n surface.metalness);\n }\n }\n\n if (materials[m].anisotropy != 0.0) {\n let rotation = materials[m].anisotropyRotation;\n var vector = vec2<f32>(cos(rotation), sin(rotation)) * materials[m].anisotropy;\n let channel = select(0u, textureInfo(materials[m].anisotropyMap).channel, materials[m].anisotropyMap != NO_TEXTURE);\n let tangents = tangentFrame(triangle, channel, surface.normal);\n\n if (materials[m].anisotropyMap != NO_TEXTURE) {\n let polar = sampleMap(materials[m].anisotropyMap, triangle, bary).rgb;\n let turn = mat2x2<f32>(vector.x, vector.y, -vector.y, vector.x);\n vector = turn * normalize(2.0 * polar.rg - vec2<f32>(1.0)) * polar.b;\n }\n\n let strength = length(vector);\n let along = select(vector / max(strength, 1e-20), vec2<f32>(1.0, 0.0), strength == 0.0);\n\n surface.anisotropy = clamp(strength, 0.0, 1.0);\n surface.alphaT = mix(surface.roughness * surface.roughness, 1.0, surface.anisotropy * surface.anisotropy);\n surface.anisotropyT = tangents.tangent * along.x + tangents.bitangent * along.y;\n surface.anisotropyB = tangents.bitangent * along.x - tangents.tangent * along.y;\n }\n } else if (materials[m].envMap != NO_TEXTURE\n && (kind == KIND_BASIC || kind == KIND_LAMBERT || kind == KIND_PHONG)) {\n // Basic, Lambert and Phong blend their own map over the lit colour, mirrored or bent the way the map says.\n let info = textureInfo(materials[m].envMap);\n let bent = refract(direction, surface.normal, materials[m].refractionRatio);\n let lookup = select(reflect(direction, surface.normal), bent, (info.flags & 16u) != 0u && any(bent != vec3<f32>(0.0)));\n\n surface.legacyEnv = sampleEquirect(materials[m].envMap, lookup).rgb * materials[m].envMapIntensity;\n surface.legacyAmount = clamp(materials[m].reflectivity * surface.specularStrength, 0.0, 1.0);\n }\n\n if (materials[m].lightMap != NO_TEXTURE) {\n let baked = sampleMap(materials[m].lightMap, triangle, bary).rgb * materials[m].lightMapIntensity;\n surface.lightMap = baked * albedo * (1.0 - surface.metalness) / PI;\n }\n\n // A material Three does not light reads as its own colour, which in a path tracer is the same as emitting it.\n if ((flags & MATERIAL_LIT) == 0u) {\n var color = albedo;\n\n if (kind == KIND_NORMAL) {\n color = toView(surface.normal) * 0.5 + 0.5;\n } else if (kind == KIND_DEPTH) {\n let clip = frame.projection * (frame.worldToCamera * vec4<f32>(trianglePointAt(triangle, bary), 1.0));\n color = vec3<f32>(1.0 - (clip.z / clip.w * 0.5 + 0.5));\n } else if (kind == KIND_MATCAP) {\n let point = trianglePointAt(triangle, bary);\n let viewDirection = normalize(-(frame.worldToCamera * vec4<f32>(point, 1.0)).xyz);\n let x = normalize(vec3<f32>(viewDirection.z, 0.0, -viewDirection.x));\n let y = cross(viewDirection, x);\n let normal = toView(surface.normal);\n let uv = vec2<f32>(dot(x, normal), dot(y, normal)) * 0.495 + 0.5;\n var matcap = vec3<f32>(mix(0.2, 0.8, uv.y));\n\n if (materials[m].matcap != NO_TEXTURE) {\n matcap = sampleTextureRaw(materials[m].matcap, uv).rgb;\n }\n\n color = albedo * matcap;\n } else if (kind == KIND_BASIC && materials[m].lightMap != NO_TEXTURE) {\n // A basic material with a light map shows the map's light instead of full brightness.\n color = albedo * sampleMap(materials[m].lightMap, triangle, bary).rgb * materials[m].lightMapIntensity / PI;\n }\n\n if (surface.legacyAmount > 0.0) {\n let amount = surface.legacyAmount;\n\n if (surface.combine == 1u) {\n color = mix(color, surface.legacyEnv, amount);\n } else if (surface.combine == 2u) {\n color = color + surface.legacyEnv * amount;\n } else {\n color = mix(color, color * surface.legacyEnv, amount);\n }\n\n surface.legacyAmount = 0.0;\n }\n\n surface.emissive = surface.emissive + color;\n surface.lightMap = vec3<f32>(0.0);\n }\n\n return surface;\n}\n";
|
|
10
|
+
//# sourceMappingURL=surface.wgsl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/surface.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,2u9BAgxBxB,CAAC"}
|