@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
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/** Triangle intersection and the iterative traversal, with the fixed stack the builder caps its depth to. */
|
|
2
|
+
export const BVH_WGSL = /* wgsl */ `
|
|
3
|
+
fn triangleVertex(triangle: u32, corner: u32) -> vec3<f32> {
|
|
4
|
+
let base = triangle * 9u + corner * 3u;
|
|
5
|
+
|
|
6
|
+
return vec3<f32>(positions[base], positions[base + 1u], positions[base + 2u]);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Moller-Trumbore. Returns the distance in x and the barycentric coordinates in yz, or a negative distance. */
|
|
10
|
+
fn intersectTriangle(triangle: u32, origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
11
|
+
let miss = vec3<f32>(-1.0, 0.0, 0.0);
|
|
12
|
+
let a = triangleVertex(triangle, 0u);
|
|
13
|
+
let edge1 = triangleVertex(triangle, 1u) - a;
|
|
14
|
+
let edge2 = triangleVertex(triangle, 2u) - a;
|
|
15
|
+
let p = cross(direction, edge2);
|
|
16
|
+
let determinant = dot(edge1, p);
|
|
17
|
+
|
|
18
|
+
if (abs(determinant) < 1e-12) {
|
|
19
|
+
return miss;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let inverse = 1.0 / determinant;
|
|
23
|
+
let offset = origin - a;
|
|
24
|
+
let u = dot(offset, p) * inverse;
|
|
25
|
+
|
|
26
|
+
if (u < 0.0 || u > 1.0) {
|
|
27
|
+
return miss;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let q = cross(offset, edge1);
|
|
31
|
+
let v = dot(direction, q) * inverse;
|
|
32
|
+
|
|
33
|
+
if (v < 0.0 || u + v > 1.0) {
|
|
34
|
+
return miss;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return vec3<f32>(dot(edge2, q) * inverse, u, v);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Where a ray enters a node's box, or 1e30 when it misses it or only meets it past maxDistance. */
|
|
41
|
+
fn enterBox(node: BvhNode, origin: vec3<f32>, inverse: vec3<f32>, maxDistance: f32) -> f32 {
|
|
42
|
+
let first = (node.low - origin) * inverse;
|
|
43
|
+
let second = (node.high - origin) * inverse;
|
|
44
|
+
let near = min(first, second);
|
|
45
|
+
let far = max(first, second);
|
|
46
|
+
let enter = max(max(near.x, near.y), max(near.z, 0.0));
|
|
47
|
+
let exit = min(min(far.x, far.y), min(far.z, maxDistance));
|
|
48
|
+
|
|
49
|
+
return select(1e30, enter, enter <= exit);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The triangles of a leaf, against the closest hit so far. */
|
|
53
|
+
fn intersectLeaf(node: BvhNode, origin: vec3<f32>, direction: vec3<f32>, hit: ptr<function, Hit>) {
|
|
54
|
+
let start = u32(-1 - node.link);
|
|
55
|
+
|
|
56
|
+
for (var i = 0u; i < u32(node.count); i = i + 1u) {
|
|
57
|
+
let triangle = orderedTriangle(start + i);
|
|
58
|
+
let result = intersectTriangle(triangle, origin, direction);
|
|
59
|
+
|
|
60
|
+
if (result.x > RAY_EPSILON && result.x < (*hit).distance) {
|
|
61
|
+
(*hit).distance = result.x;
|
|
62
|
+
(*hit).triangle = triangle;
|
|
63
|
+
(*hit).u = result.y;
|
|
64
|
+
(*hit).v = result.z;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The closest hit along a ray. Both children of a node are tested together and the nearer one is visited first, so
|
|
71
|
+
* the closest hit shrinks the search early and whole subtrees behind it are skipped; each node remembers where the ray
|
|
72
|
+
* entered it, and one whose entry is already past the closest hit is dropped without being opened.
|
|
73
|
+
*/
|
|
74
|
+
fn traceScene(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> Hit {
|
|
75
|
+
var hit: Hit;
|
|
76
|
+
hit.distance = maxDistance;
|
|
77
|
+
hit.triangle = NO_HIT;
|
|
78
|
+
hit.u = 0.0;
|
|
79
|
+
hit.v = 0.0;
|
|
80
|
+
|
|
81
|
+
if (frame.triangleCount == 0u) {
|
|
82
|
+
return hit;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// A huge finite reciprocal instead of an infinite one: an axis aligned slab would otherwise produce 0 * inf.
|
|
86
|
+
let signs = select(vec3<f32>(-1.0), vec3<f32>(1.0), direction >= vec3<f32>(0.0));
|
|
87
|
+
let inverse = signs / max(abs(direction), vec3<f32>(1e-20));
|
|
88
|
+
let rootEnter = enterBox(nodes[0], origin, inverse, hit.distance);
|
|
89
|
+
|
|
90
|
+
if (rootEnter >= 1e30) {
|
|
91
|
+
return hit;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var stack: array<u32, 32>;
|
|
95
|
+
var enters: array<f32, 32>;
|
|
96
|
+
var depth = 1u;
|
|
97
|
+
stack[0] = 0u;
|
|
98
|
+
enters[0] = rootEnter;
|
|
99
|
+
|
|
100
|
+
loop {
|
|
101
|
+
if (depth == 0u) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
depth = depth - 1u;
|
|
106
|
+
|
|
107
|
+
// Something closer was found since this node was pushed.
|
|
108
|
+
if (enters[depth] > hit.distance) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let node = nodes[stack[depth]];
|
|
113
|
+
|
|
114
|
+
if (node.link < 0) {
|
|
115
|
+
intersectLeaf(node, origin, direction, &hit);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let leftIndex = u32(node.link);
|
|
120
|
+
let rightIndex = u32(node.count);
|
|
121
|
+
let leftEnter = enterBox(nodes[leftIndex], origin, inverse, hit.distance);
|
|
122
|
+
let rightEnter = enterBox(nodes[rightIndex], origin, inverse, hit.distance);
|
|
123
|
+
let leftHit = leftEnter < 1e30;
|
|
124
|
+
let rightHit = rightEnter < 1e30;
|
|
125
|
+
|
|
126
|
+
// The builder caps its depth so both children always fit; the guard keeps a malformed tree inside the stack.
|
|
127
|
+
if (depth + 2u > 32u) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (leftHit && rightHit) {
|
|
132
|
+
// The farther first, so the nearer comes off the stack next.
|
|
133
|
+
let leftNearer = leftEnter <= rightEnter;
|
|
134
|
+
stack[depth] = select(leftIndex, rightIndex, leftNearer);
|
|
135
|
+
enters[depth] = select(leftEnter, rightEnter, leftNearer);
|
|
136
|
+
stack[depth + 1u] = select(rightIndex, leftIndex, leftNearer);
|
|
137
|
+
enters[depth + 1u] = select(rightEnter, leftEnter, leftNearer);
|
|
138
|
+
depth = depth + 2u;
|
|
139
|
+
} else if (leftHit) {
|
|
140
|
+
stack[depth] = leftIndex;
|
|
141
|
+
enters[depth] = leftEnter;
|
|
142
|
+
depth = depth + 1u;
|
|
143
|
+
} else if (rightHit) {
|
|
144
|
+
stack[depth] = rightIndex;
|
|
145
|
+
enters[depth] = rightEnter;
|
|
146
|
+
depth = depth + 1u;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return hit;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Whether anything at all lies along a ray before maxDistance, stopping at the first triangle it finds rather than the
|
|
155
|
+
* closest: what a shadow ray asks when every surface it could meet blocks it. Returns that triangle, or NO_HIT.
|
|
156
|
+
*/
|
|
157
|
+
fn traceAny(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> u32 {
|
|
158
|
+
if (frame.triangleCount == 0u) {
|
|
159
|
+
return NO_HIT;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let signs = select(vec3<f32>(-1.0), vec3<f32>(1.0), direction >= vec3<f32>(0.0));
|
|
163
|
+
let inverse = signs / max(abs(direction), vec3<f32>(1e-20));
|
|
164
|
+
|
|
165
|
+
if (enterBox(nodes[0], origin, inverse, maxDistance) >= 1e30) {
|
|
166
|
+
return NO_HIT;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
var stack: array<u32, 32>;
|
|
170
|
+
var depth = 1u;
|
|
171
|
+
stack[0] = 0u;
|
|
172
|
+
|
|
173
|
+
loop {
|
|
174
|
+
if (depth == 0u) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
depth = depth - 1u;
|
|
179
|
+
|
|
180
|
+
let node = nodes[stack[depth]];
|
|
181
|
+
|
|
182
|
+
if (node.link < 0) {
|
|
183
|
+
let start = u32(-1 - node.link);
|
|
184
|
+
|
|
185
|
+
for (var i = 0u; i < u32(node.count); i = i + 1u) {
|
|
186
|
+
let triangle = orderedTriangle(start + i);
|
|
187
|
+
let result = intersectTriangle(triangle, origin, direction);
|
|
188
|
+
|
|
189
|
+
if (result.x > RAY_EPSILON && result.x < maxDistance) {
|
|
190
|
+
return triangle;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let leftIndex = u32(node.link);
|
|
198
|
+
let rightIndex = u32(node.count);
|
|
199
|
+
|
|
200
|
+
if (depth + 2u > 32u) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (enterBox(nodes[leftIndex], origin, inverse, maxDistance) < 1e30) {
|
|
205
|
+
stack[depth] = leftIndex;
|
|
206
|
+
depth = depth + 1u;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (enterBox(nodes[rightIndex], origin, inverse, maxDistance) < 1e30) {
|
|
210
|
+
stack[depth] = rightIndex;
|
|
211
|
+
depth = depth + 1u;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return NO_HIT;
|
|
216
|
+
}
|
|
217
|
+
`;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { FRAME_LAYOUT } from "../frameLayout.js";
|
|
2
|
+
import { MATERIAL_KIND, MATERIAL_LAYOUT } from "../materialLayout.js";
|
|
3
|
+
|
|
4
|
+
/** Bindings and structs shared by every stage of the path tracing module. */
|
|
5
|
+
export const COMMON_WGSL = /* wgsl */ `
|
|
6
|
+
${FRAME_LAYOUT.wgsl}
|
|
7
|
+
// Thirty two bytes: a corner and a link, twice. A leaf stores -1 - start in place of its left child.
|
|
8
|
+
struct BvhNode {
|
|
9
|
+
low: vec3<f32>,
|
|
10
|
+
link: i32,
|
|
11
|
+
high: vec3<f32>,
|
|
12
|
+
count: i32,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** As materialLayout.ts packs it. A map index of NO_TEXTURE means the slot is empty. */
|
|
16
|
+
${MATERIAL_LAYOUT.wgsl}
|
|
17
|
+
/** Sixty four bytes, as texturePack.ts writes it. */
|
|
18
|
+
struct TextureInfo {
|
|
19
|
+
size: vec2<u32>,
|
|
20
|
+
// Where the texels start, in words for a byte texture and in floats for a float one.
|
|
21
|
+
offset: u32,
|
|
22
|
+
flags: u32,
|
|
23
|
+
wrapS: u32,
|
|
24
|
+
wrapT: u32,
|
|
25
|
+
// Which of the geometry's UV sets this texture reads.
|
|
26
|
+
channel: u32,
|
|
27
|
+
padding: u32,
|
|
28
|
+
// The rows of Three's own UV transform: xyz against (u, v, 1).
|
|
29
|
+
rowU: vec4<f32>,
|
|
30
|
+
rowV: vec4<f32>,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Something the integrator can aim a shadow ray at. The emissive triangles come first and carry only a triangle index,
|
|
35
|
+
* their emission being read from its material; the analytic lights that follow carry Three's own parameters.
|
|
36
|
+
*
|
|
37
|
+
* pdf is the density with which this entry is chosen, in the measure that suits its kind: per unit area for a surface,
|
|
38
|
+
* and simply the probability of the pick for a light that occupies no area.
|
|
39
|
+
*/
|
|
40
|
+
struct Light {
|
|
41
|
+
kind: u32,
|
|
42
|
+
cdf: f32,
|
|
43
|
+
pdf: f32,
|
|
44
|
+
// The triangle of an emissive entry; for a spot light with a projected map, where that map's record starts.
|
|
45
|
+
triangle: u32,
|
|
46
|
+
color: vec3<f32>,
|
|
47
|
+
// Three's light.distance: beyond it a point or spot light delivers nothing. Zero means no cutoff.
|
|
48
|
+
cutoff: f32,
|
|
49
|
+
position: vec3<f32>,
|
|
50
|
+
decay: f32,
|
|
51
|
+
// Towards the light from its target, as Three orients a directional or spot light.
|
|
52
|
+
direction: vec3<f32>,
|
|
53
|
+
outerCos: f32,
|
|
54
|
+
// Half the width of a rectangular light, along its own axes.
|
|
55
|
+
tangent: vec3<f32>,
|
|
56
|
+
innerCos: f32,
|
|
57
|
+
bitangent: vec3<f32>,
|
|
58
|
+
flags: u32,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
struct Hit {
|
|
62
|
+
distance: f32,
|
|
63
|
+
triangle: u32,
|
|
64
|
+
u: f32,
|
|
65
|
+
v: f32,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const NO_HIT: u32 = 0xffffffffu;
|
|
69
|
+
const RAY_EPSILON: f32 = 1e-4;
|
|
70
|
+
const PI: f32 = 3.141592653589793;
|
|
71
|
+
|
|
72
|
+
const LIGHT_AREA: u32 = 0u;
|
|
73
|
+
const LIGHT_DIRECTIONAL: u32 = 1u;
|
|
74
|
+
const LIGHT_POINT: u32 = 2u;
|
|
75
|
+
const LIGHT_SPOT: u32 = 3u;
|
|
76
|
+
const LIGHT_RECT: u32 = 4u;
|
|
77
|
+
const LIGHT_ENVIRONMENT: u32 = 5u;
|
|
78
|
+
const LIGHT_CASTS_SHADOW: u32 = 1u;
|
|
79
|
+
// A spot light that projects a texture; its triangle field says where the map and its matrix are.
|
|
80
|
+
const LIGHT_HAS_MAP: u32 = 2u;
|
|
81
|
+
|
|
82
|
+
// A surface Three shades with light at all: a basic, points, line or sprite material is not one.
|
|
83
|
+
const MATERIAL_LIT: u32 = 1u;
|
|
84
|
+
// A surface with Three's metalness workflow, which is the only one that gets a GGX specular lobe.
|
|
85
|
+
const MATERIAL_PBR: u32 = 2u;
|
|
86
|
+
const MATERIAL_TRANSPARENT: u32 = 4u;
|
|
87
|
+
const MATERIAL_VERTEX_COLORS: u32 = 8u;
|
|
88
|
+
const MATERIAL_FLAT: u32 = 16u;
|
|
89
|
+
const MATERIAL_WIREFRAME: u32 = 32u;
|
|
90
|
+
const MATERIAL_FOG: u32 = 64u;
|
|
91
|
+
const MATERIAL_CLIP_INTERSECTION: u32 = 128u;
|
|
92
|
+
const MATERIAL_OBJECT_NORMAL_MAP: u32 = 256u;
|
|
93
|
+
const MATERIAL_PATCHED: u32 = 512u;
|
|
94
|
+
const MATERIAL_ALPHA_HASH: u32 = 2048u;
|
|
95
|
+
const MATERIAL_SPECULAR: u32 = 4096u;
|
|
96
|
+
|
|
97
|
+
const KIND_STANDARD: u32 = ${MATERIAL_KIND.standard}u;
|
|
98
|
+
const KIND_PHYSICAL: u32 = ${MATERIAL_KIND.physical}u;
|
|
99
|
+
const KIND_LAMBERT: u32 = ${MATERIAL_KIND.lambert}u;
|
|
100
|
+
const KIND_PHONG: u32 = ${MATERIAL_KIND.phong}u;
|
|
101
|
+
const KIND_TOON: u32 = ${MATERIAL_KIND.toon}u;
|
|
102
|
+
const KIND_BASIC: u32 = ${MATERIAL_KIND.basic}u;
|
|
103
|
+
const KIND_NORMAL: u32 = ${MATERIAL_KIND.normal}u;
|
|
104
|
+
const KIND_DEPTH: u32 = ${MATERIAL_KIND.depth}u;
|
|
105
|
+
const KIND_MATCAP: u32 = ${MATERIAL_KIND.matcap}u;
|
|
106
|
+
const KIND_SHADOW: u32 = ${MATERIAL_KIND.shadow}u;
|
|
107
|
+
const KIND_SHADER: u32 = ${MATERIAL_KIND.shader}u;
|
|
108
|
+
|
|
109
|
+
// Three's side and blending constants.
|
|
110
|
+
const SIDE_FRONT: u32 = 0u;
|
|
111
|
+
const SIDE_BACK: u32 = 1u;
|
|
112
|
+
const BLEND_NONE: u32 = 0u;
|
|
113
|
+
const BLEND_NORMAL: u32 = 1u;
|
|
114
|
+
const BLEND_ADDITIVE: u32 = 2u;
|
|
115
|
+
const BLEND_SUBTRACTIVE: u32 = 3u;
|
|
116
|
+
const BLEND_MULTIPLY: u32 = 4u;
|
|
117
|
+
|
|
118
|
+
/** What a material's map index reads as when it has no such map. */
|
|
119
|
+
const NO_TEXTURE: u32 = 0xffffffffu;
|
|
120
|
+
const NO_PROGRAM: u32 = 0xffffffffu;
|
|
121
|
+
|
|
122
|
+
// A WebGPU device only has to offer eight storage buffers per stage, so arrays of the same kind share one binding and
|
|
123
|
+
// the uniform says where the second one starts. See sceneUpload.ts, which joins them.
|
|
124
|
+
@group(0) @binding(0) var<uniform> frame: Frame;
|
|
125
|
+
@group(0) @binding(1) var<storage, read> positions: array<f32>;
|
|
126
|
+
// Normals, then the UV sets from frame.uvOffset, vertex colours from frame.colorOffset and shader varyings from
|
|
127
|
+
// frame.varyingOffset.
|
|
128
|
+
@group(0) @binding(2) var<storage, read> attributes: array<f32>;
|
|
129
|
+
// One material index per triangle, then the hierarchy's triangle order from frame.orderOffset on.
|
|
130
|
+
@group(0) @binding(3) var<storage, read> indices: array<u32>;
|
|
131
|
+
@group(0) @binding(4) var<storage, read> materials: array<Material>;
|
|
132
|
+
@group(0) @binding(5) var<storage, read> nodes: array<BvhNode>;
|
|
133
|
+
@group(0) @binding(6) var<storage, read> lights: array<Light>;
|
|
134
|
+
// Radiance and sample count per pixel, then the coverage per pixel from frame.pixelCount on.
|
|
135
|
+
@group(0) @binding(7) var<storage, read_write> accumulation: array<vec4<f32>>;
|
|
136
|
+
// The texture table, then the texels it points at, then everything else that is read by offset: the environment's
|
|
137
|
+
// distributions, clipping planes, projected light maps and shader uniforms. See texturePack.ts.
|
|
138
|
+
@group(0) @binding(8) var<storage, read> textures: array<u32>;
|
|
139
|
+
|
|
140
|
+
fn materialIndexOf(triangle: u32) -> u32 {
|
|
141
|
+
return indices[triangle];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The triangle the hierarchy holds at this position of its order. */
|
|
145
|
+
fn orderedTriangle(at: u32) -> u32 {
|
|
146
|
+
return indices[frame.orderOffset + at];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** One word of a UV set: three vertices of four components, two per set. */
|
|
150
|
+
fn attributeUv(triangle: u32, corner: u32, channel: u32) -> vec2<f32> {
|
|
151
|
+
let at = frame.uvOffset + triangle * 12u + corner * 4u + channel * 2u;
|
|
152
|
+
|
|
153
|
+
return vec2<f32>(attributes[at], attributes[at + 1u]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** A corner's vertex colour, or white when the scene has none. */
|
|
157
|
+
fn attributeColor(triangle: u32, corner: u32) -> vec4<f32> {
|
|
158
|
+
if (frame.hasColors == 0u) {
|
|
159
|
+
return vec4<f32>(1.0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let at = frame.colorOffset + triangle * 12u + corner * 4u;
|
|
163
|
+
|
|
164
|
+
return vec4<f32>(attributes[at], attributes[at + 1u], attributes[at + 2u], attributes[at + 3u]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
fn extraFloat(at: u32) -> f32 {
|
|
168
|
+
return bitcast<f32>(textures[at]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fn extraVec4(at: u32) -> vec4<f32> {
|
|
172
|
+
return vec4<f32>(extraFloat(at), extraFloat(at + 1u), extraFloat(at + 2u), extraFloat(at + 3u));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** One entry of the texture table, unpacked from the words it shares with the texels. */
|
|
176
|
+
fn textureInfo(index: u32) -> TextureInfo {
|
|
177
|
+
let at = index * 16u;
|
|
178
|
+
var info: TextureInfo;
|
|
179
|
+
|
|
180
|
+
info.size = vec2<u32>(textures[at], textures[at + 1u]);
|
|
181
|
+
info.offset = textures[at + 2u];
|
|
182
|
+
info.flags = textures[at + 3u];
|
|
183
|
+
info.wrapS = textures[at + 4u];
|
|
184
|
+
info.wrapT = textures[at + 5u];
|
|
185
|
+
info.channel = textures[at + 6u];
|
|
186
|
+
info.rowU = vec4<f32>(
|
|
187
|
+
bitcast<f32>(textures[at + 8u]), bitcast<f32>(textures[at + 9u]), bitcast<f32>(textures[at + 10u]), 0.0);
|
|
188
|
+
info.rowV = vec4<f32>(
|
|
189
|
+
bitcast<f32>(textures[at + 12u]), bitcast<f32>(textures[at + 13u]), bitcast<f32>(textures[at + 14u]), 0.0);
|
|
190
|
+
|
|
191
|
+
return info;
|
|
192
|
+
}
|
|
193
|
+
`;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The light that surrounds the scene, and how a shadow ray aims at the bright parts of it.
|
|
3
|
+
*
|
|
4
|
+
* A sun in an equirectangular image is the same problem a small bright lamp is: a cosine weighted bounce almost never
|
|
5
|
+
* finds it, so the image takes thousands of samples to settle. The cumulative distributions built in environment.ts
|
|
6
|
+
* turn two random numbers into a direction drawn by luminance, which the power heuristic then balances against the
|
|
7
|
+
* bounce that might have found it anyway.
|
|
8
|
+
*/
|
|
9
|
+
export const ENVIRONMENT_WGSL = /* wgsl */ `
|
|
10
|
+
struct EnvironmentSample {
|
|
11
|
+
direction: vec3<f32>,
|
|
12
|
+
// Density per solid angle, before the probability of picking the environment at all.
|
|
13
|
+
pdf: f32,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** From world space into the image's own frame, which is what the rotation setting turns. */
|
|
17
|
+
fn environmentRotate(direction: vec3<f32>) -> vec3<f32> {
|
|
18
|
+
let c = cos(frame.environmentRotation);
|
|
19
|
+
let s = sin(frame.environmentRotation);
|
|
20
|
+
|
|
21
|
+
return vec3<f32>(direction.x * c - direction.z * s, direction.y, direction.x * s + direction.z * c);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fn environmentUnrotate(direction: vec3<f32>) -> vec3<f32> {
|
|
25
|
+
let c = cos(frame.environmentRotation);
|
|
26
|
+
let s = sin(frame.environmentRotation);
|
|
27
|
+
|
|
28
|
+
return vec3<f32>(direction.x * c + direction.z * s, direction.y, -direction.x * s + direction.z * c);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Where a direction lands on an equirectangular image: longitude across, colatitude down from the zenith. */
|
|
32
|
+
fn environmentUv(direction: vec3<f32>) -> vec2<f32> {
|
|
33
|
+
return vec2<f32>(
|
|
34
|
+
atan2(direction.z, direction.x) / (2.0 * PI) + 0.5,
|
|
35
|
+
acos(clamp(direction.y, -1.0, 1.0)) / PI);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** And back again. */
|
|
39
|
+
fn environmentDirection(uv: vec2<f32>) -> vec3<f32> {
|
|
40
|
+
let phi = (uv.x - 0.5) * 2.0 * PI;
|
|
41
|
+
let theta = uv.y * PI;
|
|
42
|
+
|
|
43
|
+
return vec3<f32>(sin(theta) * cos(phi), cos(theta), sin(theta) * sin(phi));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** What arrives from a direction: the image if there is one, and the gradient otherwise. */
|
|
47
|
+
fn environmentRadiance(direction: vec3<f32>) -> vec3<f32> {
|
|
48
|
+
if (frame.environmentTexture == NO_TEXTURE) {
|
|
49
|
+
let height = pow(clamp(direction.y * 0.5 + 0.5, 0.0, 1.0), frame.environmentTop.w);
|
|
50
|
+
|
|
51
|
+
return mix(frame.environmentBottom.rgb, frame.environmentTop.rgb, height) * frame.environmentBottom.w;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let uv = environmentUv(environmentRotate(direction));
|
|
55
|
+
|
|
56
|
+
return sampleTextureAt(frame.environmentTexture, uv).rgb * frame.environmentBottom.w;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn environmentRow(y: u32) -> f32 {
|
|
60
|
+
return bitcast<f32>(textures[frame.environmentCdf + y]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fn environmentColumn(index: u32) -> f32 {
|
|
64
|
+
return bitcast<f32>(textures[frame.environmentCdf + frame.environmentCdfSize.y + index]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn environmentTexelLuminance(index: u32) -> f32 {
|
|
68
|
+
let base = frame.environmentCdf + frame.environmentCdfSize.y
|
|
69
|
+
+ frame.environmentCdfSize.x * frame.environmentCdfSize.y;
|
|
70
|
+
|
|
71
|
+
return bitcast<f32>(textures[base + index]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The first entry of a cumulative range whose value passes u. */
|
|
75
|
+
fn searchCdf(start: u32, count: u32, u: f32, rows: bool) -> u32 {
|
|
76
|
+
var low = 0u;
|
|
77
|
+
var high = count - 1u;
|
|
78
|
+
|
|
79
|
+
loop {
|
|
80
|
+
if (low >= high) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let middle = (low + high) / 2u;
|
|
85
|
+
let value = select(environmentColumn(start + middle), environmentRow(middle), rows);
|
|
86
|
+
|
|
87
|
+
if (u < value) {
|
|
88
|
+
high = middle;
|
|
89
|
+
} else {
|
|
90
|
+
low = middle + 1u;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return low;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A direction drawn by the image's own luminance. The remainder of each random number places the sample inside the
|
|
99
|
+
* texel it chose, so two dimensions do the work of four.
|
|
100
|
+
*/
|
|
101
|
+
fn sampleEnvironmentDirection(u: vec2<f32>) -> EnvironmentSample {
|
|
102
|
+
var result: EnvironmentSample;
|
|
103
|
+
result.pdf = 0.0;
|
|
104
|
+
result.direction = vec3<f32>(0.0, 1.0, 0.0);
|
|
105
|
+
|
|
106
|
+
if (frame.environmentTotal <= 0.0) {
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let width = frame.environmentCdfSize.x;
|
|
111
|
+
let height = frame.environmentCdfSize.y;
|
|
112
|
+
let y = searchCdf(0u, height, u.x, true);
|
|
113
|
+
let rowStart = select(environmentRow(y - 1u), 0.0, y == 0u);
|
|
114
|
+
let rowSpan = environmentRow(y) - rowStart;
|
|
115
|
+
let x = searchCdf(y * width, width, u.y, false);
|
|
116
|
+
let columnStart = select(environmentColumn(y * width + x - 1u), 0.0, x == 0u);
|
|
117
|
+
let columnSpan = environmentColumn(y * width + x) - columnStart;
|
|
118
|
+
let jitterY = select(0.5, clamp((u.x - rowStart) / rowSpan, 0.0, 0.999999), rowSpan > 0.0);
|
|
119
|
+
let jitterX = select(0.5, clamp((u.y - columnStart) / columnSpan, 0.0, 0.999999), columnSpan > 0.0);
|
|
120
|
+
let uv = vec2<f32>((f32(x) + jitterX) / f32(width), (f32(y) + jitterY) / f32(height));
|
|
121
|
+
|
|
122
|
+
result.direction = normalize(environmentUnrotate(environmentDirection(uv)));
|
|
123
|
+
result.pdf = environmentTexelLuminance(y * width + x) * f32(width) * f32(height)
|
|
124
|
+
/ (2.0 * PI * PI * frame.environmentTotal);
|
|
125
|
+
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The density with which that sampling would have produced this direction, for the power heuristic. */
|
|
130
|
+
fn environmentPdf(direction: vec3<f32>) -> f32 {
|
|
131
|
+
if (frame.environmentTotal <= 0.0) {
|
|
132
|
+
return 0.0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let width = frame.environmentCdfSize.x;
|
|
136
|
+
let height = frame.environmentCdfSize.y;
|
|
137
|
+
let uv = environmentUv(environmentRotate(direction));
|
|
138
|
+
let x = min(u32(max(floor(uv.x * f32(width)), 0.0)), width - 1u);
|
|
139
|
+
let y = min(u32(max(floor(uv.y * f32(height)), 0.0)), height - 1u);
|
|
140
|
+
|
|
141
|
+
return environmentTexelLuminance(y * width + x) * f32(width) * f32(height)
|
|
142
|
+
/ (2.0 * PI * PI * frame.environmentTotal);
|
|
143
|
+
}
|
|
144
|
+
`;
|