@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,218 @@
|
|
|
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
|
+
`;
|
|
218
|
+
//# sourceMappingURL=bvh.wgsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bvh.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/bvh.wgsl.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuNlC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/common.wgsl.ts"],"names":[],"mappings":"AAGA,6EAA6E;AAC7E,eAAO,MAAM,WAAW,QA4LvB,CAAC"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { FRAME_LAYOUT } from "../frameLayout.js";
|
|
2
|
+
import { MATERIAL_KIND, MATERIAL_LAYOUT } from "../materialLayout.js";
|
|
3
|
+
/** Bindings and structs shared by every stage of the path tracing module. */
|
|
4
|
+
export const COMMON_WGSL = /* wgsl */ `
|
|
5
|
+
${FRAME_LAYOUT.wgsl}
|
|
6
|
+
// Thirty two bytes: a corner and a link, twice. A leaf stores -1 - start in place of its left child.
|
|
7
|
+
struct BvhNode {
|
|
8
|
+
low: vec3<f32>,
|
|
9
|
+
link: i32,
|
|
10
|
+
high: vec3<f32>,
|
|
11
|
+
count: i32,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** As materialLayout.ts packs it. A map index of NO_TEXTURE means the slot is empty. */
|
|
15
|
+
${MATERIAL_LAYOUT.wgsl}
|
|
16
|
+
/** Sixty four bytes, as texturePack.ts writes it. */
|
|
17
|
+
struct TextureInfo {
|
|
18
|
+
size: vec2<u32>,
|
|
19
|
+
// Where the texels start, in words for a byte texture and in floats for a float one.
|
|
20
|
+
offset: u32,
|
|
21
|
+
flags: u32,
|
|
22
|
+
wrapS: u32,
|
|
23
|
+
wrapT: u32,
|
|
24
|
+
// Which of the geometry's UV sets this texture reads.
|
|
25
|
+
channel: u32,
|
|
26
|
+
padding: u32,
|
|
27
|
+
// The rows of Three's own UV transform: xyz against (u, v, 1).
|
|
28
|
+
rowU: vec4<f32>,
|
|
29
|
+
rowV: vec4<f32>,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Something the integrator can aim a shadow ray at. The emissive triangles come first and carry only a triangle index,
|
|
34
|
+
* their emission being read from its material; the analytic lights that follow carry Three's own parameters.
|
|
35
|
+
*
|
|
36
|
+
* pdf is the density with which this entry is chosen, in the measure that suits its kind: per unit area for a surface,
|
|
37
|
+
* and simply the probability of the pick for a light that occupies no area.
|
|
38
|
+
*/
|
|
39
|
+
struct Light {
|
|
40
|
+
kind: u32,
|
|
41
|
+
cdf: f32,
|
|
42
|
+
pdf: f32,
|
|
43
|
+
// The triangle of an emissive entry; for a spot light with a projected map, where that map's record starts.
|
|
44
|
+
triangle: u32,
|
|
45
|
+
color: vec3<f32>,
|
|
46
|
+
// Three's light.distance: beyond it a point or spot light delivers nothing. Zero means no cutoff.
|
|
47
|
+
cutoff: f32,
|
|
48
|
+
position: vec3<f32>,
|
|
49
|
+
decay: f32,
|
|
50
|
+
// Towards the light from its target, as Three orients a directional or spot light.
|
|
51
|
+
direction: vec3<f32>,
|
|
52
|
+
outerCos: f32,
|
|
53
|
+
// Half the width of a rectangular light, along its own axes.
|
|
54
|
+
tangent: vec3<f32>,
|
|
55
|
+
innerCos: f32,
|
|
56
|
+
bitangent: vec3<f32>,
|
|
57
|
+
flags: u32,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
struct Hit {
|
|
61
|
+
distance: f32,
|
|
62
|
+
triangle: u32,
|
|
63
|
+
u: f32,
|
|
64
|
+
v: f32,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const NO_HIT: u32 = 0xffffffffu;
|
|
68
|
+
const RAY_EPSILON: f32 = 1e-4;
|
|
69
|
+
const PI: f32 = 3.141592653589793;
|
|
70
|
+
|
|
71
|
+
const LIGHT_AREA: u32 = 0u;
|
|
72
|
+
const LIGHT_DIRECTIONAL: u32 = 1u;
|
|
73
|
+
const LIGHT_POINT: u32 = 2u;
|
|
74
|
+
const LIGHT_SPOT: u32 = 3u;
|
|
75
|
+
const LIGHT_RECT: u32 = 4u;
|
|
76
|
+
const LIGHT_ENVIRONMENT: u32 = 5u;
|
|
77
|
+
const LIGHT_CASTS_SHADOW: u32 = 1u;
|
|
78
|
+
// A spot light that projects a texture; its triangle field says where the map and its matrix are.
|
|
79
|
+
const LIGHT_HAS_MAP: u32 = 2u;
|
|
80
|
+
|
|
81
|
+
// A surface Three shades with light at all: a basic, points, line or sprite material is not one.
|
|
82
|
+
const MATERIAL_LIT: u32 = 1u;
|
|
83
|
+
// A surface with Three's metalness workflow, which is the only one that gets a GGX specular lobe.
|
|
84
|
+
const MATERIAL_PBR: u32 = 2u;
|
|
85
|
+
const MATERIAL_TRANSPARENT: u32 = 4u;
|
|
86
|
+
const MATERIAL_VERTEX_COLORS: u32 = 8u;
|
|
87
|
+
const MATERIAL_FLAT: u32 = 16u;
|
|
88
|
+
const MATERIAL_WIREFRAME: u32 = 32u;
|
|
89
|
+
const MATERIAL_FOG: u32 = 64u;
|
|
90
|
+
const MATERIAL_CLIP_INTERSECTION: u32 = 128u;
|
|
91
|
+
const MATERIAL_OBJECT_NORMAL_MAP: u32 = 256u;
|
|
92
|
+
const MATERIAL_PATCHED: u32 = 512u;
|
|
93
|
+
const MATERIAL_ALPHA_HASH: u32 = 2048u;
|
|
94
|
+
const MATERIAL_SPECULAR: u32 = 4096u;
|
|
95
|
+
|
|
96
|
+
const KIND_STANDARD: u32 = ${MATERIAL_KIND.standard}u;
|
|
97
|
+
const KIND_PHYSICAL: u32 = ${MATERIAL_KIND.physical}u;
|
|
98
|
+
const KIND_LAMBERT: u32 = ${MATERIAL_KIND.lambert}u;
|
|
99
|
+
const KIND_PHONG: u32 = ${MATERIAL_KIND.phong}u;
|
|
100
|
+
const KIND_TOON: u32 = ${MATERIAL_KIND.toon}u;
|
|
101
|
+
const KIND_BASIC: u32 = ${MATERIAL_KIND.basic}u;
|
|
102
|
+
const KIND_NORMAL: u32 = ${MATERIAL_KIND.normal}u;
|
|
103
|
+
const KIND_DEPTH: u32 = ${MATERIAL_KIND.depth}u;
|
|
104
|
+
const KIND_MATCAP: u32 = ${MATERIAL_KIND.matcap}u;
|
|
105
|
+
const KIND_SHADOW: u32 = ${MATERIAL_KIND.shadow}u;
|
|
106
|
+
const KIND_SHADER: u32 = ${MATERIAL_KIND.shader}u;
|
|
107
|
+
|
|
108
|
+
// Three's side and blending constants.
|
|
109
|
+
const SIDE_FRONT: u32 = 0u;
|
|
110
|
+
const SIDE_BACK: u32 = 1u;
|
|
111
|
+
const BLEND_NONE: u32 = 0u;
|
|
112
|
+
const BLEND_NORMAL: u32 = 1u;
|
|
113
|
+
const BLEND_ADDITIVE: u32 = 2u;
|
|
114
|
+
const BLEND_SUBTRACTIVE: u32 = 3u;
|
|
115
|
+
const BLEND_MULTIPLY: u32 = 4u;
|
|
116
|
+
|
|
117
|
+
/** What a material's map index reads as when it has no such map. */
|
|
118
|
+
const NO_TEXTURE: u32 = 0xffffffffu;
|
|
119
|
+
const NO_PROGRAM: u32 = 0xffffffffu;
|
|
120
|
+
|
|
121
|
+
// A WebGPU device only has to offer eight storage buffers per stage, so arrays of the same kind share one binding and
|
|
122
|
+
// the uniform says where the second one starts. See sceneUpload.ts, which joins them.
|
|
123
|
+
@group(0) @binding(0) var<uniform> frame: Frame;
|
|
124
|
+
@group(0) @binding(1) var<storage, read> positions: array<f32>;
|
|
125
|
+
// Normals, then the UV sets from frame.uvOffset, vertex colours from frame.colorOffset and shader varyings from
|
|
126
|
+
// frame.varyingOffset.
|
|
127
|
+
@group(0) @binding(2) var<storage, read> attributes: array<f32>;
|
|
128
|
+
// One material index per triangle, then the hierarchy's triangle order from frame.orderOffset on.
|
|
129
|
+
@group(0) @binding(3) var<storage, read> indices: array<u32>;
|
|
130
|
+
@group(0) @binding(4) var<storage, read> materials: array<Material>;
|
|
131
|
+
@group(0) @binding(5) var<storage, read> nodes: array<BvhNode>;
|
|
132
|
+
@group(0) @binding(6) var<storage, read> lights: array<Light>;
|
|
133
|
+
// Radiance and sample count per pixel, then the coverage per pixel from frame.pixelCount on.
|
|
134
|
+
@group(0) @binding(7) var<storage, read_write> accumulation: array<vec4<f32>>;
|
|
135
|
+
// The texture table, then the texels it points at, then everything else that is read by offset: the environment's
|
|
136
|
+
// distributions, clipping planes, projected light maps and shader uniforms. See texturePack.ts.
|
|
137
|
+
@group(0) @binding(8) var<storage, read> textures: array<u32>;
|
|
138
|
+
|
|
139
|
+
fn materialIndexOf(triangle: u32) -> u32 {
|
|
140
|
+
return indices[triangle];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The triangle the hierarchy holds at this position of its order. */
|
|
144
|
+
fn orderedTriangle(at: u32) -> u32 {
|
|
145
|
+
return indices[frame.orderOffset + at];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** One word of a UV set: three vertices of four components, two per set. */
|
|
149
|
+
fn attributeUv(triangle: u32, corner: u32, channel: u32) -> vec2<f32> {
|
|
150
|
+
let at = frame.uvOffset + triangle * 12u + corner * 4u + channel * 2u;
|
|
151
|
+
|
|
152
|
+
return vec2<f32>(attributes[at], attributes[at + 1u]);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** A corner's vertex colour, or white when the scene has none. */
|
|
156
|
+
fn attributeColor(triangle: u32, corner: u32) -> vec4<f32> {
|
|
157
|
+
if (frame.hasColors == 0u) {
|
|
158
|
+
return vec4<f32>(1.0);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let at = frame.colorOffset + triangle * 12u + corner * 4u;
|
|
162
|
+
|
|
163
|
+
return vec4<f32>(attributes[at], attributes[at + 1u], attributes[at + 2u], attributes[at + 3u]);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn extraFloat(at: u32) -> f32 {
|
|
167
|
+
return bitcast<f32>(textures[at]);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn extraVec4(at: u32) -> vec4<f32> {
|
|
171
|
+
return vec4<f32>(extraFloat(at), extraFloat(at + 1u), extraFloat(at + 2u), extraFloat(at + 3u));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** One entry of the texture table, unpacked from the words it shares with the texels. */
|
|
175
|
+
fn textureInfo(index: u32) -> TextureInfo {
|
|
176
|
+
let at = index * 16u;
|
|
177
|
+
var info: TextureInfo;
|
|
178
|
+
|
|
179
|
+
info.size = vec2<u32>(textures[at], textures[at + 1u]);
|
|
180
|
+
info.offset = textures[at + 2u];
|
|
181
|
+
info.flags = textures[at + 3u];
|
|
182
|
+
info.wrapS = textures[at + 4u];
|
|
183
|
+
info.wrapT = textures[at + 5u];
|
|
184
|
+
info.channel = textures[at + 6u];
|
|
185
|
+
info.rowU = vec4<f32>(
|
|
186
|
+
bitcast<f32>(textures[at + 8u]), bitcast<f32>(textures[at + 9u]), bitcast<f32>(textures[at + 10u]), 0.0);
|
|
187
|
+
info.rowV = vec4<f32>(
|
|
188
|
+
bitcast<f32>(textures[at + 12u]), bitcast<f32>(textures[at + 13u]), bitcast<f32>(textures[at + 14u]), 0.0);
|
|
189
|
+
|
|
190
|
+
return info;
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
//# sourceMappingURL=common.wgsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/common.wgsl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEtE,6EAA6E;AAC7E,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;EACpC,YAAY,CAAC,IAAI;;;;;;;;;;EAUjB,eAAe,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BAiFO,aAAa,CAAC,QAAQ;6BACtB,aAAa,CAAC,QAAQ;4BACvB,aAAa,CAAC,OAAO;0BACvB,aAAa,CAAC,KAAK;yBACpB,aAAa,CAAC,IAAI;0BACjB,aAAa,CAAC,KAAK;2BAClB,aAAa,CAAC,MAAM;0BACrB,aAAa,CAAC,KAAK;2BAClB,aAAa,CAAC,MAAM;2BACpB,aAAa,CAAC,MAAM;2BACpB,aAAa,CAAC,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsF9C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
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 declare const ENVIRONMENT_WGSL = "\nstruct EnvironmentSample {\n direction: vec3<f32>,\n // Density per solid angle, before the probability of picking the environment at all.\n pdf: f32,\n};\n\n/** From world space into the image's own frame, which is what the rotation setting turns. */\nfn environmentRotate(direction: vec3<f32>) -> vec3<f32> {\n let c = cos(frame.environmentRotation);\n let s = sin(frame.environmentRotation);\n\n return vec3<f32>(direction.x * c - direction.z * s, direction.y, direction.x * s + direction.z * c);\n}\n\nfn environmentUnrotate(direction: vec3<f32>) -> vec3<f32> {\n let c = cos(frame.environmentRotation);\n let s = sin(frame.environmentRotation);\n\n return vec3<f32>(direction.x * c + direction.z * s, direction.y, -direction.x * s + direction.z * c);\n}\n\n/** Where a direction lands on an equirectangular image: longitude across, colatitude down from the zenith. */\nfn environmentUv(direction: vec3<f32>) -> vec2<f32> {\n return vec2<f32>(\n atan2(direction.z, direction.x) / (2.0 * PI) + 0.5,\n acos(clamp(direction.y, -1.0, 1.0)) / PI);\n}\n\n/** And back again. */\nfn environmentDirection(uv: vec2<f32>) -> vec3<f32> {\n let phi = (uv.x - 0.5) * 2.0 * PI;\n let theta = uv.y * PI;\n\n return vec3<f32>(sin(theta) * cos(phi), cos(theta), sin(theta) * sin(phi));\n}\n\n/** What arrives from a direction: the image if there is one, and the gradient otherwise. */\nfn environmentRadiance(direction: vec3<f32>) -> vec3<f32> {\n if (frame.environmentTexture == NO_TEXTURE) {\n let height = pow(clamp(direction.y * 0.5 + 0.5, 0.0, 1.0), frame.environmentTop.w);\n\n return mix(frame.environmentBottom.rgb, frame.environmentTop.rgb, height) * frame.environmentBottom.w;\n }\n\n let uv = environmentUv(environmentRotate(direction));\n\n return sampleTextureAt(frame.environmentTexture, uv).rgb * frame.environmentBottom.w;\n}\n\nfn environmentRow(y: u32) -> f32 {\n return bitcast<f32>(textures[frame.environmentCdf + y]);\n}\n\nfn environmentColumn(index: u32) -> f32 {\n return bitcast<f32>(textures[frame.environmentCdf + frame.environmentCdfSize.y + index]);\n}\n\nfn environmentTexelLuminance(index: u32) -> f32 {\n let base = frame.environmentCdf + frame.environmentCdfSize.y\n + frame.environmentCdfSize.x * frame.environmentCdfSize.y;\n\n return bitcast<f32>(textures[base + index]);\n}\n\n/** The first entry of a cumulative range whose value passes u. */\nfn searchCdf(start: u32, count: u32, u: f32, rows: bool) -> u32 {\n var low = 0u;\n var high = count - 1u;\n\n loop {\n if (low >= high) {\n break;\n }\n\n let middle = (low + high) / 2u;\n let value = select(environmentColumn(start + middle), environmentRow(middle), rows);\n\n if (u < value) {\n high = middle;\n } else {\n low = middle + 1u;\n }\n }\n\n return low;\n}\n\n/**\n * A direction drawn by the image's own luminance. The remainder of each random number places the sample inside the\n * texel it chose, so two dimensions do the work of four.\n */\nfn sampleEnvironmentDirection(u: vec2<f32>) -> EnvironmentSample {\n var result: EnvironmentSample;\n result.pdf = 0.0;\n result.direction = vec3<f32>(0.0, 1.0, 0.0);\n\n if (frame.environmentTotal <= 0.0) {\n return result;\n }\n\n let width = frame.environmentCdfSize.x;\n let height = frame.environmentCdfSize.y;\n let y = searchCdf(0u, height, u.x, true);\n let rowStart = select(environmentRow(y - 1u), 0.0, y == 0u);\n let rowSpan = environmentRow(y) - rowStart;\n let x = searchCdf(y * width, width, u.y, false);\n let columnStart = select(environmentColumn(y * width + x - 1u), 0.0, x == 0u);\n let columnSpan = environmentColumn(y * width + x) - columnStart;\n let jitterY = select(0.5, clamp((u.x - rowStart) / rowSpan, 0.0, 0.999999), rowSpan > 0.0);\n let jitterX = select(0.5, clamp((u.y - columnStart) / columnSpan, 0.0, 0.999999), columnSpan > 0.0);\n let uv = vec2<f32>((f32(x) + jitterX) / f32(width), (f32(y) + jitterY) / f32(height));\n\n result.direction = normalize(environmentUnrotate(environmentDirection(uv)));\n result.pdf = environmentTexelLuminance(y * width + x) * f32(width) * f32(height)\n / (2.0 * PI * PI * frame.environmentTotal);\n\n return result;\n}\n\n/** The density with which that sampling would have produced this direction, for the power heuristic. */\nfn environmentPdf(direction: vec3<f32>) -> f32 {\n if (frame.environmentTotal <= 0.0) {\n return 0.0;\n }\n\n let width = frame.environmentCdfSize.x;\n let height = frame.environmentCdfSize.y;\n let uv = environmentUv(environmentRotate(direction));\n let x = min(u32(max(floor(uv.x * f32(width)), 0.0)), width - 1u);\n let y = min(u32(max(floor(uv.y * f32(height)), 0.0)), height - 1u);\n\n return environmentTexelLuminance(y * width + x) * f32(width) * f32(height)\n / (2.0 * PI * PI * frame.environmentTotal);\n}\n";
|
|
10
|
+
//# sourceMappingURL=environment.wgsl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/environment.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,gwJAuI5B,CAAC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
`;
|
|
145
|
+
//# sourceMappingURL=environment.wgsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/environment.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuI1C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picking a light and aiming a shadow ray at it. Emissive triangles and Three's analytic lights answer the same
|
|
3
|
+
* question, so the integrator asks it once: given two random numbers, where does light come from, how much of it
|
|
4
|
+
* arrives, and how likely was this direction to be found this way.
|
|
5
|
+
*
|
|
6
|
+
* The falloff, the spot cone and the rectangle's projected area follow `material.js` in the CPU renderer, so direct
|
|
7
|
+
* light from an analytic light agrees with it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const LIGHTS_WGSL = "\nstruct LightSample {\n // Unit vector from the shaded point towards the light.\n direction: vec3<f32>,\n // How far an occlusion ray may travel before it is past the light.\n distance: f32,\n // What the light delivers, already divided by the density it was sampled with.\n value: vec3<f32>,\n // Solid angle density of this direction, for the power heuristic. Zero when no bounce ray could find this light,\n // which is every light that occupies no geometry, and then the light strategy keeps the whole contribution.\n misPdf: f32,\n valid: bool,\n occlude: bool,\n kind: u32,\n};\n\nfn noLight() -> LightSample {\n var sample: LightSample;\n sample.valid = false;\n\n return sample;\n}\n\n/** The light whose cumulative probability range contains u. */\nfn lightByProbability(u: f32) -> Light {\n var low = 0u;\n var high = frame.lightCount - 1u;\n\n loop {\n if (low >= high) {\n break;\n }\n\n let middle = (low + high) / 2u;\n\n if (lights[middle].cdf > u) {\n high = middle;\n } else {\n low = middle + 1u;\n }\n }\n\n return lights[low];\n}\n\n/**\n * The area density with which direct sampling would have picked a point on this triangle, or zero if it never would.\n * Only the leading entries are emissive triangles, and they are in triangle order, so one binary search answers it.\n */\nfn lightPdfOfTriangle(triangle: u32) -> f32 {\n var low = 0u;\n var high = frame.areaLightCount;\n\n loop {\n if (low >= high) {\n break;\n }\n\n let middle = (low + high) / 2u;\n let candidate = lights[middle].triangle;\n\n if (candidate == triangle) {\n return lights[middle].pdf;\n }\n\n if (candidate < triangle) {\n low = middle + 1u;\n } else {\n high = middle;\n }\n }\n\n return 0.0;\n}\n\n/** The power heuristic: how much of a contribution the strategy that produced it keeps, against the other one. */\nfn misWeight(pdf: f32, other: f32) -> f32 {\n let a = pdf * pdf;\n let b = other * other;\n\n return select(a / (a + b), 0.0, a + b <= 0.0);\n}\n\n/**\n * Spreads a direction over a cone, which is how a light of some apparent size softens its own shadow. The settings give\n * the angular diameter; a rasterizer would blur the shadow map by the same amount.\n */\nfn spreadDirection(direction: vec3<f32>, u: vec2<f32>) -> vec3<f32> {\n if (frame.shadowSpread <= 0.0) {\n return direction;\n }\n\n let basis = orthonormalBasis(direction);\n let radius = sqrt(u.x) * frame.shadowSpread;\n let angle = 2.0 * PI * u.y;\n\n return normalize(direction + basis[0] * (radius * cos(angle)) + basis[1] * (radius * sin(angle)));\n}\n\n/** Three's getDistanceAttenuation: the inverse power law, cut off smoothly at the light's range. */\nfn distanceFalloff(light: Light, distance: f32) -> f32 {\n var falloff = 1.0 / max(pow(distance, light.decay), 0.01);\n\n if (light.cutoff > 0.0) {\n let edge = clamp(1.0 - pow(distance / light.cutoff, 4.0), 0.0, 1.0);\n\n falloff = falloff * edge * edge;\n }\n\n return falloff;\n}\n\n/** A point uniformly distributed over a rectangular light, and the axis it emits along. */\nfn rectanglePoint(light: Light, u: vec2<f32>) -> vec3<f32> {\n return light.position + light.tangent * (u.x * 2.0 - 1.0) + light.bitangent * (u.y * 2.0 - 1.0);\n}\n\n/**\n * Where the light that pick selects arrives from, and how much of it. u places the point on a surface light or spreads a\n * point light's shadow, so the same two dimensions serve every kind.\n */\nfn sampleLight(position: vec3<f32>, pick: f32, u: vec2<f32>) -> LightSample {\n if (frame.lightCount == 0u) {\n return noLight();\n }\n\n return sampleLightEntry(lightByProbability(pick), position, u);\n}\n\n/** Three's spotLightMap: inside the projection's frustum the texture tints the light; outside it leaves it alone. */\nfn spotLightTint(light: Light, position: vec3<f32>) -> vec3<f32> {\n if ((light.flags & LIGHT_HAS_MAP) == 0u) {\n return vec3<f32>(1.0);\n }\n\n let at = light.triangle;\n let map = textures[at];\n let matrix = mat4x4<f32>(extraVec4(at + 1u), extraVec4(at + 5u), extraVec4(at + 9u), extraVec4(at + 13u));\n let coordinate = matrix * vec4<f32>(position, 1.0);\n\n if (coordinate.w <= 0.0) {\n return vec3<f32>(1.0);\n }\n\n let projected = coordinate.xyz / coordinate.w;\n\n if (any(projected <= vec3<f32>(0.0)) || any(projected >= vec3<f32>(1.0))) {\n return vec3<f32>(1.0);\n }\n\n return sampleTextureRaw(map, projected.xy).rgb;\n}\n\nfn sampleLightEntry(light: Light, position: vec3<f32>, u: vec2<f32>) -> LightSample {\n var sample: LightSample;\n sample.valid = true;\n sample.kind = light.kind;\n // Emissive geometry is always tested: a panel that lit the room through its own wall would not be a path tracer.\n sample.occlude = light.kind == LIGHT_AREA\n || ((light.flags & LIGHT_CASTS_SHADOW) != 0u && frame.shadowsEnabled != 0u);\n sample.misPdf = 0.0;\n\n if (light.kind == LIGHT_ENVIRONMENT) {\n let drawn = sampleEnvironmentDirection(u);\n\n if (drawn.pdf <= 0.0) {\n return noLight();\n }\n\n let density = light.pdf * drawn.pdf;\n\n sample.direction = drawn.direction;\n sample.distance = 1e30;\n sample.value = environmentRadiance(drawn.direction) / density;\n // A bounce ray that escapes finds the same light, so the two strategies share it.\n sample.misPdf = density;\n\n return sample;\n }\n\n if (light.kind == LIGHT_DIRECTIONAL) {\n sample.direction = spreadDirection(light.direction, u);\n sample.distance = 1e30;\n // The colour already carries the intensity, which for a directional light is the irradiance it delivers.\n sample.value = light.color / light.pdf;\n\n return sample;\n }\n\n if (light.kind == LIGHT_AREA || light.kind == LIGHT_RECT) {\n let area = light.kind == LIGHT_AREA;\n let bary = triangleBarycentric(u);\n let point = select(rectanglePoint(light, u), trianglePointAt(light.triangle, bary), area);\n let axis = select(normalize(cross(light.tangent, light.bitangent)), triangleNormal(light.triangle), area);\n let offset = point - position;\n let distanceSquared = dot(offset, offset);\n let distance = sqrt(distanceSquared);\n\n if (distance <= RAY_EPSILON) {\n return noLight();\n }\n\n sample.direction = offset / distance;\n // An emissive triangle radiates from both sides in this milestone; a rectangular light only from the side it faces.\n let cosine = dot(axis, sample.direction);\n let cosLight = select(max(cosine, 0.0), abs(cosine), area);\n\n if (cosLight <= 1e-6) {\n return noLight();\n }\n\n // Stop just short of the light so its own surface is not reported as the blocker.\n sample.distance = distance * (1.0 - 1e-3);\n let density = light.pdf * distanceSquared / cosLight;\n // The emissive map is read at the very point that was sampled, so this agrees with what a bounce ray would find.\n let emission = select(light.color, emissionAt(light.triangle, bary), area);\n\n sample.value = emission / density;\n // Only a triangle of the scene's geometry can also be found by a bounce ray, so only it needs the heuristic.\n sample.misPdf = select(0.0, density, area);\n\n return sample;\n }\n\n // A point or a spot light: both fall off with distance and are sampled at their centre.\n let offset = light.position - position;\n let distance = length(offset);\n\n if (distance <= RAY_EPSILON) {\n return noLight();\n }\n\n let towards = offset / distance;\n var power = distanceFalloff(light, distance);\n\n if (light.kind == LIGHT_SPOT) {\n let cosine = dot(light.direction, towards);\n\n if (light.innerCos <= light.outerCos) {\n power = power * select(0.0, 1.0, cosine >= light.outerCos);\n } else {\n let t = clamp((cosine - light.outerCos) / (light.innerCos - light.outerCos), 0.0, 1.0);\n\n power = power * t * t * (3.0 - 2.0 * t);\n }\n\n if (power <= 0.0) {\n return noLight();\n }\n }\n\n sample.direction = spreadDirection(towards, u);\n sample.distance = distance;\n sample.value = light.color * (power / light.pdf);\n\n if (light.kind == LIGHT_SPOT) {\n sample.value = sample.value * spotLightTint(light, position);\n }\n\n return sample;\n}\n\n/**\n * Whether a material stops every shadow ray that meets it, wherever: opaque, no cutout, no transmission, no clipping\n * or wireframe, no shader that might discard, and normal blending. Only then can the first triangle found decide.\n */\nfn blocksAll(m: u32) -> bool {\n let flags = materials[m].flags;\n let blending = materials[m].blending;\n\n return (flags & (MATERIAL_TRANSPARENT | MATERIAL_ALPHA_HASH | MATERIAL_WIREFRAME)) == 0u\n && materials[m].alphaTest <= 0.0\n && materials[m].transmission <= 0.0\n && materials[m].clipCount == 0u\n && materials[m].program == NO_PROGRAM\n && materials[m].kind != KIND_SHADOW\n && (blending == BLEND_NORMAL || blending == BLEND_NONE);\n}\n\n/** How many surfaces a shadow ray may pass through before it gives up and calls the light blocked. */\nconst MAX_SHADOW_SKIPS: u32 = 8u;\n\n/** No material to skip: every blocker counts. */\nconst NO_MATERIAL: u32 = 0xffffffffu;\n\n/**\n * What a shadow ray keeps of a thin transmissive wall it meets: the share Three lets through, tinted by the base colour,\n * less what the interface reflects. It is the expectation of the straight crossing in sampleDielectric, so a light\n * seen through a window is found the same by both estimators. A volume bends the ray it lets through, so it blocks.\n */\nfn thinCrossing(triangle: u32, bary: vec2<f32>, direction: vec3<f32>) -> vec3<f32> {\n let m = materialIndexOf(triangle);\n\n if (materials[m].transmission <= 0.0) {\n return vec3<f32>(0.0);\n }\n\n let metalness = materials[m].metalness * sampleMap(materials[m].metalnessMap, triangle, bary).b;\n let transmission = transmissionAt(m, triangle, bary, metalness);\n let thickness = materials[m].thickness * sampleMap(materials[m].thicknessMap, triangle, bary).g;\n\n if (transmission <= 0.0 || thickness > 0.0) {\n return vec3<f32>(0.0);\n }\n\n let albedo = materials[m].color.rgb * sampleMap(materials[m].map, triangle, bary).rgb;\n let reflectance = fresnelDielectric(abs(dot(authoredNormal(triangle, bary), direction)), 1.0 / max(materials[m].ior, 1.0));\n\n return albedo * (transmission * (1.0 - reflectance));\n}\n\n/** The colour a blended surface filters what is behind it by: its base colour, with the map and vertex colours. */\nfn blendColor(triangle: u32, bary: vec2<f32>) -> vec3<f32> {\n let m = materialIndexOf(triangle);\n var color = materials[m].color.rgb * sampleMap(materials[m].map, triangle, bary).rgb;\n\n if ((materials[m].flags & MATERIAL_VERTEX_COLORS) != 0u) {\n color = color * triangleColor(triangle, bary).rgb;\n }\n\n return color;\n}\n\n/**\n * How much of the light this sample came from reaches the shaded point. A surface that only partly covers its triangle\n * blocks the ray only that often, which is what makes a cutout leaf cast a cutout shadow; a thin transmissive wall\n * lets its own share through, tinted.\n */\nfn lightTransmittance(\n position: vec3<f32>, chosen: LightSample, rng: ptr<function, PathSampler>, skipMaterial: u32) -> vec3<f32> {\n if (!chosen.occlude) {\n return vec3<f32>(1.0);\n }\n\n // Most blockers are plainly opaque, and for those any triangle on the way settles it: the search stops at the first\n // one found instead of looking for the closest. Anything that lets light through goes the long way below.\n let first = traceAny(position, chosen.direction, chosen.distance);\n\n if (first == NO_HIT) {\n return vec3<f32>(1.0);\n }\n\n let firstMaterial = materialIndexOf(first);\n\n if (firstMaterial != skipMaterial && blocksAll(firstMaterial)) {\n return vec3<f32>(0.0);\n }\n\n var origin = position;\n var remaining = chosen.distance;\n var through = vec3<f32>(1.0);\n\n for (var skip = 0u; skip < MAX_SHADOW_SKIPS; skip = skip + 1u) {\n let blocker = traceScene(origin, chosen.direction, remaining);\n\n if (blocker.triangle == NO_HIT) {\n return through;\n }\n\n let bary = vec2<f32>(blocker.u, blocker.v);\n let m = materialIndexOf(blocker.triangle);\n let blending = materials[m].blending;\n\n // A shadow catcher catches shadows rather than casting them, and a surface that adds light blocks none.\n if (m != skipMaterial && materials[m].kind != KIND_SHADOW && blending != BLEND_ADDITIVE) {\n let coverage = coverageAt(blocker.triangle, bary, chosen.direction);\n\n if (blending == BLEND_MULTIPLY) {\n through = through * mix(vec3<f32>(1.0), blendColor(blocker.triangle, bary), coverage);\n } else if (blending == BLEND_SUBTRACTIVE) {\n through = through * (vec3<f32>(1.0) - blendColor(blocker.triangle, bary) * coverage);\n } else if (coverage >= 1.0 || sample2D(rng).x < coverage) {\n through = through * thinCrossing(blocker.triangle, bary, chosen.direction);\n }\n\n if (all(through <= vec3<f32>(0.0))) {\n return vec3<f32>(0.0);\n }\n }\n\n // The ray carries on from just past the surface it saw through.\n origin = origin + chosen.direction * (blocker.distance + RAY_EPSILON);\n remaining = remaining - blocker.distance - RAY_EPSILON;\n\n if (remaining <= RAY_EPSILON) {\n return through;\n }\n }\n\n return vec3<f32>(0.0);\n}\n\n/**\n * How shadowed a point is, as Three's ShadowMaterial reads it: for every light that casts a shadow, whether something\n * stands between it and the point, the darkest of them winning as in the CPU renderer. A rectangular light casts no\n * shadow in Three, so it takes no part.\n */\nfn shadowMaskAt(position: vec3<f32>, rng: ptr<function, PathSampler>) -> f32 {\n if (frame.shadowsEnabled == 0u) {\n return 0.0;\n }\n\n var shadow = 0.0;\n\n for (var i = frame.areaLightCount; i < frame.lightCount; i = i + 1u) {\n let light = lights[i];\n\n if (light.kind == LIGHT_ENVIRONMENT || light.kind == LIGHT_RECT || (light.flags & LIGHT_CASTS_SHADOW) == 0u) {\n continue;\n }\n\n let chosen = sampleLightEntry(light, position, sample2D(rng));\n\n if (!chosen.valid) {\n continue;\n }\n\n let visible = luminanceOf(lightTransmittance(position, chosen, rng, NO_MATERIAL));\n\n shadow = max(shadow, 1.0 - clamp(visible, 0.0, 1.0));\n }\n\n return shadow;\n}\n";
|
|
10
|
+
//# sourceMappingURL=lights.wgsl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lights.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/lights.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,6pcAwavB,CAAC"}
|