@tinypoly/gpu-renderer 0.2.4 → 0.3.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 +48 -10
- package/README.md +2 -0
- package/dist/accumulator.d.ts.map +1 -1
- package/dist/accumulator.js +1 -0
- package/dist/accumulator.js.map +1 -1
- package/dist/denoise.js +2 -2
- package/dist/denoise.js.map +1 -1
- package/dist/device.d.ts.map +1 -1
- package/dist/device.js +9 -0
- package/dist/device.js.map +1 -1
- package/dist/frameLayout.d.ts.map +1 -1
- package/dist/frameLayout.js +5 -0
- package/dist/frameLayout.js.map +1 -1
- package/dist/geometry.d.ts +48 -8
- package/dist/geometry.d.ts.map +1 -1
- package/dist/geometry.js +409 -134
- package/dist/geometry.js.map +1 -1
- package/dist/grass.d.ts.map +1 -1
- package/dist/grass.js +2 -1
- package/dist/grass.js.map +1 -1
- package/dist/imageResolve.js +2 -2
- package/dist/imageResolve.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/instancing.d.ts +100 -0
- package/dist/instancing.d.ts.map +1 -0
- package/dist/instancing.js +286 -0
- package/dist/instancing.js.map +1 -0
- package/dist/lensPass.d.ts +2 -0
- package/dist/lensPass.d.ts.map +1 -1
- package/dist/lensPass.js +7 -4
- package/dist/lensPass.js.map +1 -1
- package/dist/lights.d.ts.map +1 -1
- package/dist/lights.js +8 -5
- package/dist/lights.js.map +1 -1
- package/dist/memoryProbe.d.ts +76 -0
- package/dist/memoryProbe.d.ts.map +1 -0
- package/dist/memoryProbe.js +197 -0
- package/dist/memoryProbe.js.map +1 -0
- package/dist/oidnPass.d.ts +6 -0
- package/dist/oidnPass.d.ts.map +1 -1
- package/dist/oidnPass.js +18 -1
- package/dist/oidnPass.js.map +1 -1
- package/dist/pathTracePass.d.ts +15 -2
- package/dist/pathTracePass.d.ts.map +1 -1
- package/dist/pathTracePass.js +38 -16
- package/dist/pathTracePass.js.map +1 -1
- package/dist/renderer.d.ts +13 -0
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +248 -41
- package/dist/renderer.js.map +1 -1
- package/dist/sceneUpload.d.ts +26 -8
- package/dist/sceneUpload.d.ts.map +1 -1
- package/dist/sceneUpload.js +97 -27
- package/dist/sceneUpload.js.map +1 -1
- package/dist/shaderModule.d.ts.map +1 -1
- package/dist/shaderModule.js +2 -1
- package/dist/shaderModule.js.map +1 -1
- package/dist/skyBake.d.ts.map +1 -1
- package/dist/skyBake.js +5 -3
- package/dist/skyBake.js.map +1 -1
- package/dist/texturePack.d.ts +43 -3
- package/dist/texturePack.d.ts.map +1 -1
- package/dist/texturePack.js +209 -37
- package/dist/texturePack.js.map +1 -1
- package/dist/wgsl/bvh.wgsl.d.ts +5 -1
- package/dist/wgsl/bvh.wgsl.d.ts.map +1 -1
- package/dist/wgsl/bvh.wgsl.js +105 -15
- package/dist/wgsl/bvh.wgsl.js.map +1 -1
- package/dist/wgsl/common.wgsl.d.ts.map +1 -1
- package/dist/wgsl/common.wgsl.js +92 -11
- package/dist/wgsl/common.wgsl.js.map +1 -1
- package/dist/wgsl/lens.wgsl.d.ts +1 -1
- package/dist/wgsl/lens.wgsl.d.ts.map +1 -1
- package/dist/wgsl/lens.wgsl.js +14 -2
- package/dist/wgsl/lens.wgsl.js.map +1 -1
- package/dist/wgsl/surface.wgsl.d.ts +1 -1
- package/dist/wgsl/surface.wgsl.d.ts.map +1 -1
- package/dist/wgsl/surface.wgsl.js +24 -14
- package/dist/wgsl/surface.wgsl.js.map +1 -1
- package/dist/wgsl/texture.wgsl.d.ts +1 -1
- package/dist/wgsl/texture.wgsl.d.ts.map +1 -1
- package/dist/wgsl/texture.wgsl.js +11 -0
- package/dist/wgsl/texture.wgsl.js.map +1 -1
- package/package.json +1 -1
- package/src/accumulator.ts +1 -0
- package/src/denoise.ts +2 -2
- package/src/device.ts +12 -0
- package/src/frameLayout.ts +5 -0
- package/src/geometry.ts +527 -133
- package/src/grass.ts +2 -1
- package/src/imageResolve.ts +2 -2
- package/src/index.ts +1 -0
- package/src/instancing.ts +415 -0
- package/src/lensPass.ts +9 -4
- package/src/lights.ts +9 -5
- package/src/memoryProbe.ts +275 -0
- package/src/oidnPass.ts +21 -1
- package/src/pathTracePass.ts +42 -18
- package/src/renderer.ts +308 -47
- package/src/sceneUpload.ts +139 -36
- package/src/shaderModule.ts +2 -1
- package/src/skyBake.ts +5 -3
- package/src/texturePack.ts +274 -43
- package/src/wgsl/bvh.wgsl.ts +105 -15
- package/src/wgsl/common.wgsl.ts +92 -11
- package/src/wgsl/lens.wgsl.ts +14 -2
- package/src/wgsl/surface.wgsl.ts +24 -14
- package/src/wgsl/texture.wgsl.ts +11 -0
package/dist/wgsl/bvh.wgsl.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* The positions are uploaded in the hierarchy's leaf order, so a leaf is a run of triangles read straight off the
|
|
5
5
|
* buffer; the scene's own triangle number is looked up once, for the hit a trace returns. Every other reader goes the
|
|
6
6
|
* other way, from a triangle number to where its corners sit.
|
|
7
|
+
*
|
|
8
|
+
* With INSTANCES compiled in, a leaf with a count of -1 is an instance: the ray is taken into its object space and
|
|
9
|
+
* the traversal goes on in its prototype's hierarchy, with a marker on the stack that brings it back to world space.
|
|
10
|
+
* The distance along the ray is the same in both spaces, since the direction is transformed and not normalized.
|
|
7
11
|
*/
|
|
8
12
|
export const BVH_WGSL = /* wgsl */ `
|
|
9
13
|
/** A corner of the triangle at this position of the hierarchy's leaf order. */
|
|
@@ -18,8 +22,37 @@ fn leafOf(triangle: u32) -> u32 {
|
|
|
18
22
|
return indices[frame.leafOffset + triangle];
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
/** A corner of a triangle a hit named, in world space. */
|
|
21
26
|
fn triangleVertex(triangle: u32, corner: u32) -> vec3<f32> {
|
|
22
|
-
|
|
27
|
+
let instance = instanceOf(triangle);
|
|
28
|
+
let p = leafVertex(leafOf(storedTriangle(triangle, instance)), corner);
|
|
29
|
+
|
|
30
|
+
if (instance != NO_INSTANCE) {
|
|
31
|
+
return instancePoint(instance, p);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return p;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The stack entry that leaves an instance: the traversal goes back to world space. */
|
|
38
|
+
const RETURN_ENTRY: u32 = 0xffffffffu;
|
|
39
|
+
|
|
40
|
+
/** A huge finite reciprocal instead of an infinite one: an axis aligned slab would otherwise produce 0 * inf. */
|
|
41
|
+
fn rayInverse(direction: vec3<f32>) -> vec3<f32> {
|
|
42
|
+
let signs = select(vec3<f32>(-1.0), vec3<f32>(1.0), direction >= vec3<f32>(0.0));
|
|
43
|
+
|
|
44
|
+
return signs / max(abs(direction), vec3<f32>(1e-20));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The number a hit goes by, from where it sits in the leaf order and the instance it was found in. */
|
|
48
|
+
fn namedTriangle(at: u32, instance: u32) -> u32 {
|
|
49
|
+
let stored = orderedTriangle(at);
|
|
50
|
+
|
|
51
|
+
if (INSTANCES && instance != NO_INSTANCE) {
|
|
52
|
+
return instanceWord(instance, 26u) + stored - instanceWord(instance, 25u);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return stored;
|
|
23
56
|
}
|
|
24
57
|
|
|
25
58
|
/** Moller-Trumbore. Returns the distance in x and the barycentric coordinates in yz, or a negative distance. */
|
|
@@ -127,7 +160,7 @@ fn waitingLeaf(entry: u32) -> vec2<i32> {
|
|
|
127
160
|
* A leaf is tested at the top of the next step, the one place the loop tests triangles, which keeps the kernel small.
|
|
128
161
|
* This visits what the binary tree's traversal visited, in the same order, so it finds the same hit.
|
|
129
162
|
*/
|
|
130
|
-
fn traceScene(
|
|
163
|
+
fn traceScene(worldOrigin: vec3<f32>, worldDirection: vec3<f32>, maxDistance: f32) -> Hit {
|
|
131
164
|
var hit: Hit;
|
|
132
165
|
hit.distance = maxDistance;
|
|
133
166
|
hit.triangle = NO_HIT;
|
|
@@ -138,12 +171,15 @@ fn traceScene(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> Hit
|
|
|
138
171
|
return hit;
|
|
139
172
|
}
|
|
140
173
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
174
|
+
var origin = worldOrigin;
|
|
175
|
+
var direction = worldDirection;
|
|
176
|
+
var inverse = rayInverse(direction);
|
|
177
|
+
// The instance the traversal is inside, and the one the closest hit so far was found in.
|
|
178
|
+
var instance = NO_INSTANCE;
|
|
179
|
+
var hitInstance = NO_INSTANCE;
|
|
144
180
|
|
|
145
|
-
var stack: array<u32,
|
|
146
|
-
var enters: array<f32,
|
|
181
|
+
var stack: array<u32, BVH_STACK>;
|
|
182
|
+
var enters: array<f32, BVH_STACK>;
|
|
147
183
|
var depth = 0u;
|
|
148
184
|
var current = 0u;
|
|
149
185
|
// A leaf to test before anything else: its link, and its triangle count, zero when there is none.
|
|
@@ -151,10 +187,32 @@ fn traceScene(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> Hit
|
|
|
151
187
|
|
|
152
188
|
loop {
|
|
153
189
|
if (leaf.y > 0) {
|
|
190
|
+
let before = hit.distance;
|
|
191
|
+
|
|
154
192
|
intersectLeaf(leaf.x, leaf.y, origin, direction, &hit);
|
|
193
|
+
|
|
194
|
+
if (INSTANCES && hit.distance < before) {
|
|
195
|
+
hitInstance = instance;
|
|
196
|
+
}
|
|
197
|
+
|
|
155
198
|
leaf.y = 0;
|
|
156
199
|
}
|
|
157
200
|
|
|
201
|
+
if (INSTANCES && leaf.y < 0) {
|
|
202
|
+
leaf.y = 0;
|
|
203
|
+
|
|
204
|
+
if (depth < BVH_STACK) {
|
|
205
|
+
instance = u32(-1 - leaf.x);
|
|
206
|
+
stack[depth] = RETURN_ENTRY;
|
|
207
|
+
enters[depth] = -1.0;
|
|
208
|
+
depth = depth + 1u;
|
|
209
|
+
origin = instanceToObject(instance, worldOrigin, 1.0);
|
|
210
|
+
direction = instanceToObject(instance, worldDirection, 0.0);
|
|
211
|
+
inverse = rayInverse(direction);
|
|
212
|
+
current = instanceWord(instance, 24u);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
158
216
|
if (current == NO_NODE) {
|
|
159
217
|
if (depth == 0u) {
|
|
160
218
|
break;
|
|
@@ -169,6 +227,14 @@ fn traceScene(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> Hit
|
|
|
169
227
|
|
|
170
228
|
let entry = stack[depth];
|
|
171
229
|
|
|
230
|
+
if (INSTANCES && entry == RETURN_ENTRY) {
|
|
231
|
+
instance = NO_INSTANCE;
|
|
232
|
+
origin = worldOrigin;
|
|
233
|
+
direction = worldDirection;
|
|
234
|
+
inverse = rayInverse(direction);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
172
238
|
if ((entry & LEAF_ENTRY) != 0u) {
|
|
173
239
|
leaf = waitingLeaf(entry);
|
|
174
240
|
continue;
|
|
@@ -198,7 +264,7 @@ fn traceScene(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> Hit
|
|
|
198
264
|
}
|
|
199
265
|
|
|
200
266
|
// The builder caps its depth so the far children always fit; the guard keeps a malformed tree inside the stack.
|
|
201
|
-
if (farEnter < 1e30 && depth <
|
|
267
|
+
if (farEnter < 1e30 && depth < BVH_STACK) {
|
|
202
268
|
stack[depth] = select(u32(farLink), leafEntry(parent, leftFirst), farLink < 0);
|
|
203
269
|
enters[depth] = farEnter;
|
|
204
270
|
depth = depth + 1u;
|
|
@@ -206,7 +272,7 @@ fn traceScene(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> Hit
|
|
|
206
272
|
}
|
|
207
273
|
|
|
208
274
|
if (hit.triangle != NO_HIT) {
|
|
209
|
-
hit.triangle =
|
|
275
|
+
hit.triangle = namedTriangle(hit.triangle, hitInstance);
|
|
210
276
|
}
|
|
211
277
|
|
|
212
278
|
return hit;
|
|
@@ -217,15 +283,17 @@ fn traceScene(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> Hit
|
|
|
217
283
|
* closest: what a shadow ray asks when every surface it could meet blocks it. Returns that triangle, or NO_HIT. The
|
|
218
284
|
* right child goes first and the left one waits, the order the binary tree's traversal took.
|
|
219
285
|
*/
|
|
220
|
-
fn traceAny(
|
|
286
|
+
fn traceAny(worldOrigin: vec3<f32>, worldDirection: vec3<f32>, maxDistance: f32) -> u32 {
|
|
221
287
|
if (frame.triangleCount == 0u) {
|
|
222
288
|
return NO_HIT;
|
|
223
289
|
}
|
|
224
290
|
|
|
225
|
-
|
|
226
|
-
|
|
291
|
+
var origin = worldOrigin;
|
|
292
|
+
var direction = worldDirection;
|
|
293
|
+
var inverse = rayInverse(direction);
|
|
294
|
+
var instance = NO_INSTANCE;
|
|
227
295
|
|
|
228
|
-
var stack: array<u32,
|
|
296
|
+
var stack: array<u32, BVH_STACK>;
|
|
229
297
|
var depth = 0u;
|
|
230
298
|
var current = 0u;
|
|
231
299
|
var leaf = vec2<i32>(0, 0);
|
|
@@ -235,12 +303,26 @@ fn traceAny(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> u32 {
|
|
|
235
303
|
let found = anyInLeaf(leaf.x, leaf.y, origin, direction, maxDistance);
|
|
236
304
|
|
|
237
305
|
if (found != NO_HIT) {
|
|
238
|
-
return
|
|
306
|
+
return namedTriangle(found, instance);
|
|
239
307
|
}
|
|
240
308
|
|
|
241
309
|
leaf.y = 0;
|
|
242
310
|
}
|
|
243
311
|
|
|
312
|
+
if (INSTANCES && leaf.y < 0) {
|
|
313
|
+
leaf.y = 0;
|
|
314
|
+
|
|
315
|
+
if (depth < BVH_STACK) {
|
|
316
|
+
instance = u32(-1 - leaf.x);
|
|
317
|
+
stack[depth] = RETURN_ENTRY;
|
|
318
|
+
depth = depth + 1u;
|
|
319
|
+
origin = instanceToObject(instance, worldOrigin, 1.0);
|
|
320
|
+
direction = instanceToObject(instance, worldDirection, 0.0);
|
|
321
|
+
inverse = rayInverse(direction);
|
|
322
|
+
current = instanceWord(instance, 24u);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
244
326
|
if (current == NO_NODE) {
|
|
245
327
|
if (depth == 0u) {
|
|
246
328
|
break;
|
|
@@ -250,6 +332,14 @@ fn traceAny(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> u32 {
|
|
|
250
332
|
|
|
251
333
|
let entry = stack[depth];
|
|
252
334
|
|
|
335
|
+
if (INSTANCES && entry == RETURN_ENTRY) {
|
|
336
|
+
instance = NO_INSTANCE;
|
|
337
|
+
origin = worldOrigin;
|
|
338
|
+
direction = worldDirection;
|
|
339
|
+
inverse = rayInverse(direction);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
|
|
253
343
|
if ((entry & LEAF_ENTRY) != 0u) {
|
|
254
344
|
leaf = waitingLeaf(entry);
|
|
255
345
|
continue;
|
|
@@ -277,7 +367,7 @@ fn traceAny(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> u32 {
|
|
|
277
367
|
}
|
|
278
368
|
}
|
|
279
369
|
|
|
280
|
-
if (rightHit && leftHit && depth <
|
|
370
|
+
if (rightHit && leftHit && depth < BVH_STACK) {
|
|
281
371
|
stack[depth] = select(u32(node.leftLink), leafEntry(parent, false), node.leftLink < 0);
|
|
282
372
|
depth = depth + 1u;
|
|
283
373
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bvh.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/bvh.wgsl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bvh.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/bvh.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8WlC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/common.wgsl.ts"],"names":[],"mappings":"AAIA,6EAA6E;AAC7E,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"common.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/common.wgsl.ts"],"names":[],"mappings":"AAIA,6EAA6E;AAC7E,eAAO,MAAM,WAAW,QA6RvB,CAAC"}
|
package/dist/wgsl/common.wgsl.js
CHANGED
|
@@ -136,13 +136,16 @@ const TEXTURE_TABLE: u32 = ${TEXTURE_TABLE}u;
|
|
|
136
136
|
// A WebGPU device only has to offer eight storage buffers per stage, so arrays of the same kind share one binding and
|
|
137
137
|
// the uniform says where the second one starts. See sceneUpload.ts, which joins them.
|
|
138
138
|
@group(0) @binding(0) var<uniform> frame: Frame;
|
|
139
|
-
// Nine coordinates per triangle, in the hierarchy's leaf order: see leafOf in bvh.wgsl.ts.
|
|
139
|
+
// Nine coordinates per stored triangle, in the hierarchy's leaf order: see leafOf in bvh.wgsl.ts. A prototype's are
|
|
140
|
+
// in object space.
|
|
140
141
|
@group(0) @binding(1) var<storage, read> positions: array<f32>;
|
|
141
|
-
//
|
|
142
|
-
// frame.varyingOffset.
|
|
142
|
+
// Per vertex: the UV sets from frame.uvOffset and vertex colours from frame.colorOffset; then shader varyings, per
|
|
143
|
+
// corner, from frame.varyingOffset.
|
|
143
144
|
@group(0) @binding(2) var<storage, read> attributes: array<f32>;
|
|
144
|
-
// One material index per triangle, then the hierarchy's triangle order from frame.orderOffset on (the
|
|
145
|
-
// position of the leaf order), then from frame.leafOffset on the other way (the position of
|
|
145
|
+
// One material index per triangle a hit can name, then the hierarchy's triangle order from frame.orderOffset on (the
|
|
146
|
+
// stored triangle at each position of the leaf order), then from frame.leafOffset on the other way (the position of
|
|
147
|
+
// each stored triangle), the instance of every instanced triangle from frame.instanceOffset, the three vertices of each
|
|
148
|
+
// stored triangle from frame.cornerOffset and every vertex's folded normal from frame.normalOffset.
|
|
146
149
|
@group(0) @binding(3) var<storage, read> indices: array<u32>;
|
|
147
150
|
@group(0) @binding(4) var<storage, read> materials: array<Material>;
|
|
148
151
|
@group(0) @binding(5) var<storage, read> nodes: array<BvhNode>;
|
|
@@ -162,24 +165,102 @@ fn orderedTriangle(at: u32) -> u32 {
|
|
|
162
165
|
return indices[frame.orderOffset + at];
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
/**
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
/** What a triangle of the scene's own belongs to: no instance. */
|
|
169
|
+
const NO_INSTANCE: u32 = 0xffffffffu;
|
|
170
|
+
|
|
171
|
+
/** The instance a triangle a hit named belongs to, or NO_INSTANCE for one of the scene's own. */
|
|
172
|
+
fn instanceOf(triangle: u32) -> u32 {
|
|
173
|
+
if (!INSTANCES || triangle < frame.sceneTriangles) {
|
|
174
|
+
return NO_INSTANCE;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return indices[frame.instanceOffset + triangle - frame.sceneTriangles];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** One word of an instance's record: see INSTANCE_WORDS in instancing.ts. */
|
|
181
|
+
fn instanceWord(instance: u32, word: u32) -> u32 {
|
|
182
|
+
return textures[frame.instanceData + instance * 28u + word];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** A row of an instance's transforms: 0 to 2 take object space to world, 3 to 5 back. */
|
|
186
|
+
fn instanceRow(instance: u32, row: u32) -> vec4<f32> {
|
|
187
|
+
let at = frame.instanceData + instance * 28u + row * 4u;
|
|
188
|
+
|
|
189
|
+
return vec4<f32>(bitcast<f32>(textures[at]), bitcast<f32>(textures[at + 1u]), bitcast<f32>(textures[at + 2u]),
|
|
190
|
+
bitcast<f32>(textures[at + 3u]));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Where a named triangle is stored: itself, or for an instance's, the prototype's triangle it repeats. */
|
|
194
|
+
fn storedTriangle(triangle: u32, instance: u32) -> u32 {
|
|
195
|
+
if (instance == NO_INSTANCE) {
|
|
196
|
+
return triangle;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return instanceWord(instance, 25u) + triangle - instanceWord(instance, 26u);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** A point of an instance's prototype, in world space. */
|
|
203
|
+
fn instancePoint(instance: u32, p: vec3<f32>) -> vec3<f32> {
|
|
204
|
+
let q = vec4<f32>(p, 1.0);
|
|
205
|
+
|
|
206
|
+
return vec3<f32>(dot(instanceRow(instance, 0u), q), dot(instanceRow(instance, 1u), q), dot(instanceRow(instance, 2u), q));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** A world space point (w 1) or direction (w 0), in an instance's object space. */
|
|
210
|
+
fn instanceToObject(instance: u32, v: vec3<f32>, w: f32) -> vec3<f32> {
|
|
211
|
+
let q = vec4<f32>(v, w);
|
|
212
|
+
|
|
213
|
+
return vec3<f32>(dot(instanceRow(instance, 3u), q), dot(instanceRow(instance, 4u), q), dot(instanceRow(instance, 5u), q));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** An object space normal in world space, by the inverse transpose; not normalized. */
|
|
217
|
+
fn instanceNormal(instance: u32, n: vec3<f32>) -> vec3<f32> {
|
|
218
|
+
return n.x * instanceRow(instance, 3u).xyz + n.y * instanceRow(instance, 4u).xyz + n.z * instanceRow(instance, 5u).xyz;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** The three vertices of a triangle a hit named. */
|
|
222
|
+
fn triangleCorners(triangle: u32) -> vec3<u32> {
|
|
223
|
+
let at = frame.cornerOffset + storedTriangle(triangle, instanceOf(triangle)) * 3u;
|
|
224
|
+
|
|
225
|
+
return vec3<u32>(indices[at], indices[at + 1u], indices[at + 2u]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** One UV set of a vertex: four components, two per set. */
|
|
229
|
+
fn vertexUv(vertex: u32, channel: u32) -> vec2<f32> {
|
|
230
|
+
let at = frame.uvOffset + vertex * 4u + channel * 2u;
|
|
168
231
|
|
|
169
232
|
return vec2<f32>(attributes[at], attributes[at + 1u]);
|
|
170
233
|
}
|
|
171
234
|
|
|
172
|
-
/** A
|
|
173
|
-
fn
|
|
235
|
+
/** A vertex's colour, or white when the scene has none. */
|
|
236
|
+
fn vertexColor(vertex: u32) -> vec4<f32> {
|
|
174
237
|
if (frame.hasColors == 0u) {
|
|
175
238
|
return vec4<f32>(1.0);
|
|
176
239
|
}
|
|
177
240
|
|
|
178
|
-
let at = frame.colorOffset +
|
|
241
|
+
let at = frame.colorOffset + vertex * 4u;
|
|
179
242
|
|
|
180
243
|
return vec4<f32>(attributes[at], attributes[at + 1u], attributes[at + 2u], attributes[at + 3u]);
|
|
181
244
|
}
|
|
182
245
|
|
|
246
|
+
/** A normal folded onto an octahedron in two signed 16 bit halves, as sceneUpload.ts folds it; zero for NO_NORMAL. */
|
|
247
|
+
fn vertexNormal(vertex: u32) -> vec3<f32> {
|
|
248
|
+
let code = indices[frame.normalOffset + vertex];
|
|
249
|
+
|
|
250
|
+
if (code == 0x80008000u) {
|
|
251
|
+
return vec3<f32>(0.0);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let e = unpack2x16snorm(code);
|
|
255
|
+
var n = vec3<f32>(e.x, e.y, 1.0 - abs(e.x) - abs(e.y));
|
|
256
|
+
let t = max(-n.z, 0.0);
|
|
257
|
+
|
|
258
|
+
n.x = n.x + select(t, -t, n.x >= 0.0);
|
|
259
|
+
n.y = n.y + select(t, -t, n.y >= 0.0);
|
|
260
|
+
|
|
261
|
+
return normalize(n);
|
|
262
|
+
}
|
|
263
|
+
|
|
183
264
|
fn extraFloat(at: u32) -> f32 {
|
|
184
265
|
return bitcast<f32>(textures[at]);
|
|
185
266
|
}
|
|
@@ -1 +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;AACtE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE3E,6EAA6E;AAC7E,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;EACpC,YAAY,CAAC,IAAI;;;;;;;;;;;;;;;EAejB,eAAe,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BAqFO,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;;;;;;;;;;;;;;;;2BAgBpB,WAAW;0BACZ,UAAU;6BACP,aAAa
|
|
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;AACtE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE3E,6EAA6E;AAC7E,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;EACpC,YAAY,CAAC,IAAI;;;;;;;;;;;;;;;EAejB,eAAe,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BAqFO,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;;;;;;;;;;;;;;;;2BAgBpB,WAAW;0BACZ,UAAU;6BACP,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4JzC,CAAC"}
|
package/dist/wgsl/lens.wgsl.d.ts
CHANGED
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
* texel takes the light of the texels behind it along every ray's direction, fading with the distance, with weights
|
|
14
14
|
* that again sum to one.
|
|
15
15
|
*/
|
|
16
|
-
export declare const LENS_WGSL = "\nstruct Lens {\n resolution: vec2<u32>,\n // Whether the colour comes from the denoiser's output rather than straight from the accumulation.\n denoised: u32,\n // Upsampling only: the level read from sits in the downsampled pyramid (the smallest one) instead of the folded one.\n fromDown: u32,\n sourceSize: vec2<u32>,\n // Where the level read from starts in its pyramid; a size of the full resolution reads the image itself.\n sourceOffset: u32,\n // The first downsample reads the image itself, and averages its taps by luminance against fireflies.\n fromImage: u32,\n targetSize: vec2<u32>,\n targetOffset: u32,\n // Where the largest level of the folded pyramid starts, which the composite reads the halo from.\n haloOffset: u32,\n haloSize: vec2<u32>,\n // How much of the light the halo takes, and how much each level takes from the one below it.\n intensity: f32,\n radius: f32,\n // How far red and blue move apart at the corners, as a share of the distance from the centre.\n aberration: f32,\n // How much of the light the aperture's rays take, how many there are, and how far they reach in streak texels.\n glare: f32,\n rays: u32,\n glareLength: f32,\n // The white balance, a linear sRGB matrix, row by row.\n white0: vec4<f32>,\n white1: vec4<f32>,\n white2: vec4<f32>,\n // The size of the level the rays are gathered at, and the angle of the first ray, in radians.\n streakSize: vec2<u32>,\n glareAngle: f32,\n // Whether the camera meters the image and exposes it to middle grey, which the meter pass works out.\n autoExposure: f32,\n // The share of a dark scene's darkness the meter keeps.\n darkAdaptation: f32,\n};\n\nconst PI: f32 = 3.14159265358979;\n\n/** Taps along each of the aperture's rays. */\nconst STREAK_TAPS: i32 = 24;\n\n@group(0) @binding(0) var<uniform> lens: Lens;\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@group(0) @binding(3) var<storage, read_write> down: array<vec4<f32>>;\n@group(0) @binding(4) var<storage, read_write> up: array<vec4<f32>>;\n@group(0) @binding(5) var<storage, read_write> graded: array<vec4<f32>>;\n@group(0) @binding(6) var<storage, read_write> streaked: array<vec4<f32>>;\n// What the camera's meter read: the exposure that brings the image's average to middle grey, in x.\n@group(0) @binding(7) var<storage, read_write> metered: array<vec4<f32>>;\n\n/** What a light meter calibrates to: an average scene reflects about eighteen percent. */\nconst MIDDLE_GREY: f32 = 0.18;\nconst METER_GRID: vec2<u32> = vec2<u32>(64u, 36u);\nconst METER_THREADS: u32 = 256u;\n\n/**\n * How far above middle grey the meter lets the bright end of the image land: three stops, where AgX still shows\n * detail, three and a half under where it reaches white.\n */\nconst HIGHLIGHT_CEILING: f32 = 8.0;\n/** The share of the metered points that counts as the bright end: the light through a window, not a lamp's own pixels. */\nconst HIGHLIGHT_SHARE: f32 = 0.03;\n/**\n * The average below which a scene counts as dark: about three stops under middle grey, a room lit by a window on a\n * grey day. Above it the meter exposes fully; below it, only for the stops the dark adaptation lets go.\n */\nconst DIM_AVERAGE: f32 = 0.02;\n/** Half stops of luminance, from 2^-16 to 2^16, which the bright end is found in. */\nconst METER_BINS: u32 = 64u;\n\nvar<workgroup> meterSums: array<vec2<f32>, 256>;\nvar<workgroup> meterHistogram: array<atomic<u32>, 64>;\n\n/**\n * The camera's meter: the image's average luminance over a grid of points, taken as the mean of the logarithms so that\n * a bright window or a patch of sky cannot darken everything else, weighed towards the centre as a camera's\n * centre-weighted metering is. What no path has reached yet, or is black, does not count. One workgroup reads the\n * grid and sums it in shared memory.\n *\n * Then it guards the highlights, as a camera's matrix metering does: the average alone takes a dim attic with a shaft\n * of sun in it to middle grey and burns the shaft out, where a photographer keeps the room dim. The brightest few\n * percent of the points, found in a histogram of half stops, may land no more than {@link HIGHLIGHT_CEILING} times\n * brighter; an even scene, whose bright end is close to its average, is left as the average says.\n */\n@compute @workgroup_size(256, 1, 1)\nfn meter(@builtin(local_invocation_index) thread: u32) {\n var sum = vec2<f32>(0.0);\n let count = METER_GRID.x * METER_GRID.y;\n\n if (thread < METER_BINS) {\n atomicStore(&meterHistogram[thread], 0u);\n }\n\n workgroupBarrier();\n\n for (var k = thread; k < count; k = k + METER_THREADS) {\n let cell = vec2<f32>(f32(k % METER_GRID.x), f32(k / METER_GRID.x)) + 0.5;\n let uv = cell / vec2<f32>(METER_GRID);\n let luminance = dot(imageTexel(i32(uv.x * f32(lens.resolution.x)), i32(uv.y * f32(lens.resolution.y))),\n vec3<f32>(0.2126, 0.7152, 0.0722));\n let away = (uv - 0.5) * 2.0;\n let weight = select(0.0, 1.0 - 0.6 * min(dot(away, away) * 0.5, 1.0), luminance > 0.0);\n\n sum = sum + vec2<f32>(log(max(luminance, 1e-6)) * weight, weight);\n\n if (luminance > 0.0) {\n let bin = u32(clamp((log2(luminance) + 16.0) * 2.0, 0.0, f32(METER_BINS - 1u)));\n\n atomicAdd(&meterHistogram[bin], 1u);\n }\n }\n\n meterSums[thread] = sum;\n workgroupBarrier();\n\n for (var stride = METER_THREADS / 2u; stride > 0u; stride = stride / 2u) {\n if (thread < stride) {\n meterSums[thread] = meterSums[thread] + meterSums[thread + stride];\n }\n\n workgroupBarrier();\n }\n\n if (thread == 0u) {\n let total = meterSums[0];\n let average = select(MIDDLE_GREY, exp(total.x / max(total.y, 1e-6)), total.y > 0.0);\n var exposure = MIDDLE_GREY / average;\n\n // A dark scene keeps a share of its missing stops: a night stays a night instead of turning into dusk.\n if (average < DIM_AVERAGE) {\n exposure = exposure * pow(average / DIM_AVERAGE, lens.darkAdaptation);\n }\n\n // From the top down, the first half stop by which the bright share is reached; its upper edge is that end.\n var points = 0u;\n\n for (var bin = 0u; bin < METER_BINS; bin = bin + 1u) {\n points = points + atomicLoad(&meterHistogram[bin]);\n }\n\n var above = 0u;\n var bright = 0.0;\n\n for (var bin = i32(METER_BINS) - 1; bin >= 0 && points > 0u; bin = bin - 1) {\n above = above + atomicLoad(&meterHistogram[u32(bin)]);\n\n if (f32(above) >= HIGHLIGHT_SHARE * f32(points)) {\n bright = exp2(f32(bin + 1) * 0.5 - 16.0);\n break;\n }\n }\n\n if (bright > 0.0) {\n exposure = min(exposure, HIGHLIGHT_CEILING * MIDDLE_GREY / bright);\n }\n\n metered[0] = vec4<f32>(clamp(exposure, 1e-4, 1e4), average, bright, 0.0);\n }\n}\n\n/** The image's own texel: its mean so far, or nothing where no path has landed yet. */\nfn imageTexel(x: i32, y: i32) -> vec3<f32> {\n let size = vec2<i32>(lens.resolution);\n let index = u32(clamp(y, 0, size.y - 1) * size.x + clamp(x, 0, size.x - 1));\n\n if (lens.denoised != 0u) {\n return denoised[index].rgb;\n }\n\n let stored = accumulation[index];\n\n return select(stored.rgb / stored.w, vec3<f32>(0.0), stored.w <= 0.0);\n}\n\nfn downTexel(offset: u32, size: vec2<u32>, x: i32, y: i32) -> vec3<f32> {\n let s = vec2<i32>(size);\n\n return down[offset + u32(clamp(y, 0, s.y - 1) * s.x + clamp(x, 0, s.x - 1))].rgb;\n}\n\nfn upTexel(offset: u32, size: vec2<u32>, x: i32, y: i32) -> vec3<f32> {\n let s = vec2<i32>(size);\n\n return up[offset + u32(clamp(y, 0, s.y - 1) * s.x + clamp(x, 0, s.x - 1))].rgb;\n}\n\n/** Bilinear weights and the texel below and left of a point given in texels, edges clamped. */\nfn bilinearAt(position: vec2<f32>) -> vec4<f32> {\n let p = position - 0.5;\n let base = floor(p);\n\n return vec4<f32>(base, p - base);\n}\n\nfn imageBilinear(position: vec2<f32>) -> vec3<f32> {\n let b = bilinearAt(position);\n let x = i32(b.x);\n let y = i32(b.y);\n\n return mix(\n mix(imageTexel(x, y), imageTexel(x + 1, y), b.z),\n mix(imageTexel(x, y + 1), imageTexel(x + 1, y + 1), b.z), b.w);\n}\n\nfn downBilinear(offset: u32, size: vec2<u32>, position: vec2<f32>) -> vec3<f32> {\n let b = bilinearAt(position);\n let x = i32(b.x);\n let y = i32(b.y);\n\n return mix(\n mix(downTexel(offset, size, x, y), downTexel(offset, size, x + 1, y), b.z),\n mix(downTexel(offset, size, x, y + 1), downTexel(offset, size, x + 1, y + 1), b.z), b.w);\n}\n\nfn upBilinear(offset: u32, size: vec2<u32>, position: vec2<f32>) -> vec3<f32> {\n let b = bilinearAt(position);\n let x = i32(b.x);\n let y = i32(b.y);\n\n return mix(\n mix(upTexel(offset, size, x, y), upTexel(offset, size, x + 1, y), b.z),\n mix(upTexel(offset, size, x, y + 1), upTexel(offset, size, x + 1, y + 1), b.z), b.w);\n}\n\n/** The level being filtered, read bilinearly at a point given in its own texels. */\nfn sourceAt(position: vec2<f32>) -> vec3<f32> {\n if (lens.fromImage != 0u) {\n return imageBilinear(position);\n }\n\n return downBilinear(lens.sourceOffset, lens.sourceSize, position);\n}\n\nfn luminance(color: vec3<f32>) -> f32 {\n return dot(color, vec3<f32>(0.2126, 0.7152, 0.0722));\n}\n\n/** Karis' average: a group of taps weighed by one over one plus its luminance, so a lone firefly cannot flare. */\nfn karis(sum: vec3<f32>) -> vec4<f32> {\n let weight = 1.0 / (1.0 + luminance(sum));\n\n return vec4<f32>(sum * weight, weight);\n}\n\n/** One level of the pyramid from the one above it: Jimenez's 13 taps, four overlapping boxes and a centre one. */\n@compute @workgroup_size(8, 8, 1)\nfn downsample(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= lens.targetSize.x || id.y >= lens.targetSize.y) {\n return;\n }\n\n let scale = vec2<f32>(lens.sourceSize) / vec2<f32>(lens.targetSize);\n let centre = (vec2<f32>(id.xy) + 0.5) * scale;\n let a = sourceAt(centre + vec2<f32>(-2.0, -2.0));\n let b = sourceAt(centre + vec2<f32>(0.0, -2.0));\n let c = sourceAt(centre + vec2<f32>(2.0, -2.0));\n let d = sourceAt(centre + vec2<f32>(-2.0, 0.0));\n let e = sourceAt(centre);\n let f = sourceAt(centre + vec2<f32>(2.0, 0.0));\n let g = sourceAt(centre + vec2<f32>(-2.0, 2.0));\n let h = sourceAt(centre + vec2<f32>(0.0, 2.0));\n let i = sourceAt(centre + vec2<f32>(2.0, 2.0));\n let j = sourceAt(centre + vec2<f32>(-1.0, -1.0));\n let k = sourceAt(centre + vec2<f32>(1.0, -1.0));\n let l = sourceAt(centre + vec2<f32>(-1.0, 1.0));\n let m = sourceAt(centre + vec2<f32>(1.0, 1.0));\n var result: vec3<f32>;\n\n if (lens.fromImage != 0u) {\n let groups = karis((j + k + l + m) * 0.25) * 0.5\n + karis((a + b + d + e) * 0.25) * 0.125\n + karis((b + c + e + f) * 0.25) * 0.125\n + karis((d + e + g + h) * 0.25) * 0.125\n + karis((e + f + h + i) * 0.25) * 0.125;\n\n result = groups.rgb / max(groups.w, 1e-6);\n } else {\n result = e * 0.125 + (a + c + g + i) * 0.03125 + (b + d + f + h) * 0.0625 + (j + k + l + m) * 0.125;\n }\n\n down[lens.targetOffset + id.y * lens.targetSize.x + id.x] = vec4<f32>(max(result, vec3<f32>(0.0)), 1.0);\n}\n\n/** A 3 x 3 tent over the smaller level, read at a point given in its texels. */\nfn tent(position: vec2<f32>) -> vec3<f32> {\n var sum = vec3<f32>(0.0);\n\n for (var dy = -1; dy <= 1; dy = dy + 1) {\n for (var dx = -1; dx <= 1; dx = dx + 1) {\n let weight = f32((2 - abs(dx)) * (2 - abs(dy))) / 16.0;\n let at = position + vec2<f32>(f32(dx), f32(dy));\n\n if (lens.fromDown != 0u) {\n sum = sum + downBilinear(lens.sourceOffset, lens.sourceSize, at) * weight;\n } else {\n sum = sum + upBilinear(lens.sourceOffset, lens.sourceSize, at) * weight;\n }\n }\n }\n\n return sum;\n}\n\n/** One level folded back: its own light, mixed by the radius with the wider spread of every level below it. */\n@compute @workgroup_size(8, 8, 1)\nfn upsample(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= lens.targetSize.x || id.y >= lens.targetSize.y) {\n return;\n }\n\n let scale = vec2<f32>(lens.sourceSize) / vec2<f32>(lens.targetSize);\n let wider = tent((vec2<f32>(id.xy) + 0.5) * scale);\n let index = lens.targetOffset + id.y * lens.targetSize.x + id.x;\n\n up[index] = vec4<f32>(mix(down[index].rgb, wider, lens.radius), 1.0);\n}\n\n/** The aperture's rays at one texel of the streak level: the light behind it along each ray, fading with distance. */\n@compute @workgroup_size(8, 8, 1)\nfn streaks(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= lens.targetSize.x || id.y >= lens.targetSize.y) {\n return;\n }\n\n let centre = vec2<f32>(id.xy) + 0.5;\n var sum = vec3<f32>(0.0);\n var total = 0.0;\n\n for (var r = 0u; r < lens.rays; r = r + 1u) {\n let angle = lens.glareAngle + f32(r) * 2.0 * PI / f32(lens.rays);\n let along = vec2<f32>(cos(angle), sin(angle));\n\n for (var j = 1; j <= STREAK_TAPS; j = j + 1) {\n let t = f32(j) / f32(STREAK_TAPS);\n let weight = exp(-4.0 * t);\n\n sum = sum + downBilinear(lens.sourceOffset, lens.sourceSize, centre - along * (t * lens.glareLength)) * weight;\n total = total + weight;\n }\n }\n\n streaked[id.y * lens.targetSize.x + id.x] = vec4<f32>(sum / max(total, 1e-6), 1.0);\n}\n\nfn streakTexel(x: i32, y: i32) -> vec3<f32> {\n let s = vec2<i32>(lens.streakSize);\n\n return streaked[u32(clamp(y, 0, s.y - 1) * s.x + clamp(x, 0, s.x - 1))].rgb;\n}\n\nfn streakBilinear(position: vec2<f32>) -> vec3<f32> {\n let b = bilinearAt(position);\n let x = i32(b.x);\n let y = i32(b.y);\n\n return mix(mix(streakTexel(x, y), streakTexel(x + 1, y), b.z), mix(streakTexel(x, y + 1), streakTexel(x + 1, y + 1), b.z), b.w);\n}\n\n/** The image as the lens delivers it: fringed towards the corners, with its halo, balanced for the camera's white. */\n@compute @workgroup_size(8, 8, 1)\nfn composite(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= lens.resolution.x || id.y >= lens.resolution.y) {\n return;\n }\n\n let size = vec2<f32>(lens.resolution);\n let position = vec2<f32>(id.xy) + 0.5;\n var color = imageTexel(i32(id.x), i32(id.y));\n\n // Lateral chromatic aberration: each colour is magnified a little differently, red outwards and blue inwards,\n // in proportion to the distance from the centre, which is how a lens's dispersion moves them.\n if (lens.aberration > 0.0) {\n let centre = size * 0.5;\n let away = position - centre;\n let red = imageBilinear(centre + away * (1.0 - lens.aberration));\n let blue = imageBilinear(centre + away * (1.0 + lens.aberration));\n\n color = vec3<f32>(red.r, color.g, blue.b);\n }\n\n if (lens.intensity > 0.0) {\n // The largest folded level is already a tent over the levels below it, so one bilinear read of it is smooth.\n let halo = upBilinear(lens.haloOffset, lens.haloSize, position * vec2<f32>(lens.haloSize) / size);\n\n color = mix(color, halo, lens.intensity);\n }\n\n if (lens.glare > 0.0) {\n color = mix(color, streakBilinear(position * vec2<f32>(lens.streakSize) / size), lens.glare);\n }\n\n if (lens.autoExposure > 0.0) {\n color = color * metered[0].x;\n }\n\n let balanced = vec3<f32>(dot(lens.white0.xyz, color), dot(lens.white1.xyz, color), dot(lens.white2.xyz, color));\n\n graded[id.y * lens.resolution.x + id.x] = vec4<f32>(max(balanced, vec3<f32>(0.0)), 1.0);\n}\n";
|
|
16
|
+
export declare const LENS_WGSL = "\nstruct Lens {\n resolution: vec2<u32>,\n // Whether the colour comes from the denoiser's output rather than straight from the accumulation.\n denoised: u32,\n // Upsampling only: the level read from sits in the downsampled pyramid (the smallest one) instead of the folded one.\n fromDown: u32,\n sourceSize: vec2<u32>,\n // Where the level read from starts in its pyramid; a size of the full resolution reads the image itself.\n sourceOffset: u32,\n // The first downsample reads the image itself, and averages its taps by luminance against fireflies.\n fromImage: u32,\n targetSize: vec2<u32>,\n targetOffset: u32,\n // Where the largest level of the folded pyramid starts, which the composite reads the halo from.\n haloOffset: u32,\n haloSize: vec2<u32>,\n // How much of the light the halo takes, and how much each level takes from the one below it.\n intensity: f32,\n radius: f32,\n // How far red and blue move apart at the corners, as a share of the distance from the centre.\n aberration: f32,\n // How much of the light the aperture's rays take, how many there are, and how far they reach in streak texels.\n glare: f32,\n rays: u32,\n glareLength: f32,\n // The white balance, a linear sRGB matrix, row by row.\n white0: vec4<f32>,\n white1: vec4<f32>,\n white2: vec4<f32>,\n // The size of the level the rays are gathered at, and the angle of the first ray, in radians.\n streakSize: vec2<u32>,\n glareAngle: f32,\n // Whether the camera meters the image and exposes it to middle grey, which the meter pass works out.\n autoExposure: f32,\n // The share of a dark scene's darkness the meter keeps.\n darkAdaptation: f32,\n // The exposure applied after the lens, the meter's aside, which the rays are limited against.\n exposure: f32,\n};\n\nconst PI: f32 = 3.14159265358979;\n\n/** Taps along each of the aperture's rays. */\nconst STREAK_TAPS: i32 = 24;\n\n/**\n * The brightest a tap of the rays counts as, in multiples of the exposed white. A lamp is thousands of times white,\n * which would carry its rays across the frame at full length; past this they stay as short as a camera shows them.\n */\nconst GLARE_CEILING: f32 = 4.0;\n\n@group(0) @binding(0) var<uniform> lens: Lens;\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@group(0) @binding(3) var<storage, read_write> down: array<vec4<f32>>;\n@group(0) @binding(4) var<storage, read_write> up: array<vec4<f32>>;\n@group(0) @binding(5) var<storage, read_write> graded: array<vec4<f32>>;\n@group(0) @binding(6) var<storage, read_write> streaked: array<vec4<f32>>;\n// What the camera's meter read: the exposure that brings the image's average to middle grey, in x.\n@group(0) @binding(7) var<storage, read_write> metered: array<vec4<f32>>;\n\n/** What a light meter calibrates to: an average scene reflects about eighteen percent. */\nconst MIDDLE_GREY: f32 = 0.18;\nconst METER_GRID: vec2<u32> = vec2<u32>(64u, 36u);\nconst METER_THREADS: u32 = 256u;\n\n/**\n * How far above middle grey the meter lets the bright end of the image land: three stops, where AgX still shows\n * detail, three and a half under where it reaches white.\n */\nconst HIGHLIGHT_CEILING: f32 = 8.0;\n/** The share of the metered points that counts as the bright end: the light through a window, not a lamp's own pixels. */\nconst HIGHLIGHT_SHARE: f32 = 0.03;\n/**\n * The average below which a scene counts as dark: about three stops under middle grey, a room lit by a window on a\n * grey day. Above it the meter exposes fully; below it, only for the stops the dark adaptation lets go.\n */\nconst DIM_AVERAGE: f32 = 0.02;\n/** Half stops of luminance, from 2^-16 to 2^16, which the bright end is found in. */\nconst METER_BINS: u32 = 64u;\n\nvar<workgroup> meterSums: array<vec2<f32>, 256>;\nvar<workgroup> meterHistogram: array<atomic<u32>, 64>;\n\n/**\n * The camera's meter: the image's average luminance over a grid of points, taken as the mean of the logarithms so that\n * a bright window or a patch of sky cannot darken everything else, weighed towards the centre as a camera's\n * centre-weighted metering is. What no path has reached yet, or is black, does not count. One workgroup reads the\n * grid and sums it in shared memory.\n *\n * Then it guards the highlights, as a camera's matrix metering does: the average alone takes a dim attic with a shaft\n * of sun in it to middle grey and burns the shaft out, where a photographer keeps the room dim. The brightest few\n * percent of the points, found in a histogram of half stops, may land no more than {@link HIGHLIGHT_CEILING} times\n * brighter; an even scene, whose bright end is close to its average, is left as the average says.\n */\n@compute @workgroup_size(256, 1, 1)\nfn meter(@builtin(local_invocation_index) thread: u32) {\n var sum = vec2<f32>(0.0);\n let count = METER_GRID.x * METER_GRID.y;\n\n if (thread < METER_BINS) {\n atomicStore(&meterHistogram[thread], 0u);\n }\n\n workgroupBarrier();\n\n for (var k = thread; k < count; k = k + METER_THREADS) {\n let cell = vec2<f32>(f32(k % METER_GRID.x), f32(k / METER_GRID.x)) + 0.5;\n let uv = cell / vec2<f32>(METER_GRID);\n let luminance = dot(imageTexel(i32(uv.x * f32(lens.resolution.x)), i32(uv.y * f32(lens.resolution.y))),\n vec3<f32>(0.2126, 0.7152, 0.0722));\n let away = (uv - 0.5) * 2.0;\n let weight = select(0.0, 1.0 - 0.6 * min(dot(away, away) * 0.5, 1.0), luminance > 0.0);\n\n sum = sum + vec2<f32>(log(max(luminance, 1e-6)) * weight, weight);\n\n if (luminance > 0.0) {\n let bin = u32(clamp((log2(luminance) + 16.0) * 2.0, 0.0, f32(METER_BINS - 1u)));\n\n atomicAdd(&meterHistogram[bin], 1u);\n }\n }\n\n meterSums[thread] = sum;\n workgroupBarrier();\n\n for (var stride = METER_THREADS / 2u; stride > 0u; stride = stride / 2u) {\n if (thread < stride) {\n meterSums[thread] = meterSums[thread] + meterSums[thread + stride];\n }\n\n workgroupBarrier();\n }\n\n if (thread == 0u) {\n let total = meterSums[0];\n let average = select(MIDDLE_GREY, exp(total.x / max(total.y, 1e-6)), total.y > 0.0);\n var exposure = MIDDLE_GREY / average;\n\n // A dark scene keeps a share of its missing stops: a night stays a night instead of turning into dusk.\n if (average < DIM_AVERAGE) {\n exposure = exposure * pow(average / DIM_AVERAGE, lens.darkAdaptation);\n }\n\n // From the top down, the first half stop by which the bright share is reached; its upper edge is that end.\n var points = 0u;\n\n for (var bin = 0u; bin < METER_BINS; bin = bin + 1u) {\n points = points + atomicLoad(&meterHistogram[bin]);\n }\n\n var above = 0u;\n var bright = 0.0;\n\n for (var bin = i32(METER_BINS) - 1; bin >= 0 && points > 0u; bin = bin - 1) {\n above = above + atomicLoad(&meterHistogram[u32(bin)]);\n\n if (f32(above) >= HIGHLIGHT_SHARE * f32(points)) {\n bright = exp2(f32(bin + 1) * 0.5 - 16.0);\n break;\n }\n }\n\n if (bright > 0.0) {\n exposure = min(exposure, HIGHLIGHT_CEILING * MIDDLE_GREY / bright);\n }\n\n metered[0] = vec4<f32>(clamp(exposure, 1e-4, 1e4), average, bright, 0.0);\n }\n}\n\n/** The image's own texel: its mean so far, or nothing where no path has landed yet. */\nfn imageTexel(x: i32, y: i32) -> vec3<f32> {\n let size = vec2<i32>(lens.resolution);\n let index = u32(clamp(y, 0, size.y - 1) * size.x + clamp(x, 0, size.x - 1));\n\n if (lens.denoised != 0u) {\n return denoised[index].rgb;\n }\n\n let stored = accumulation[index];\n\n return select(stored.rgb / stored.w, vec3<f32>(0.0), stored.w <= 0.0);\n}\n\nfn downTexel(offset: u32, size: vec2<u32>, x: i32, y: i32) -> vec3<f32> {\n let s = vec2<i32>(size);\n\n return down[offset + u32(clamp(y, 0, s.y - 1) * s.x + clamp(x, 0, s.x - 1))].rgb;\n}\n\nfn upTexel(offset: u32, size: vec2<u32>, x: i32, y: i32) -> vec3<f32> {\n let s = vec2<i32>(size);\n\n return up[offset + u32(clamp(y, 0, s.y - 1) * s.x + clamp(x, 0, s.x - 1))].rgb;\n}\n\n/** Bilinear weights and the texel below and left of a point given in texels, edges clamped. */\nfn bilinearAt(position: vec2<f32>) -> vec4<f32> {\n let p = position - 0.5;\n let base = floor(p);\n\n return vec4<f32>(base, p - base);\n}\n\nfn imageBilinear(position: vec2<f32>) -> vec3<f32> {\n let b = bilinearAt(position);\n let x = i32(b.x);\n let y = i32(b.y);\n\n return mix(\n mix(imageTexel(x, y), imageTexel(x + 1, y), b.z),\n mix(imageTexel(x, y + 1), imageTexel(x + 1, y + 1), b.z), b.w);\n}\n\nfn downBilinear(offset: u32, size: vec2<u32>, position: vec2<f32>) -> vec3<f32> {\n let b = bilinearAt(position);\n let x = i32(b.x);\n let y = i32(b.y);\n\n return mix(\n mix(downTexel(offset, size, x, y), downTexel(offset, size, x + 1, y), b.z),\n mix(downTexel(offset, size, x, y + 1), downTexel(offset, size, x + 1, y + 1), b.z), b.w);\n}\n\nfn upBilinear(offset: u32, size: vec2<u32>, position: vec2<f32>) -> vec3<f32> {\n let b = bilinearAt(position);\n let x = i32(b.x);\n let y = i32(b.y);\n\n return mix(\n mix(upTexel(offset, size, x, y), upTexel(offset, size, x + 1, y), b.z),\n mix(upTexel(offset, size, x, y + 1), upTexel(offset, size, x + 1, y + 1), b.z), b.w);\n}\n\n/** The level being filtered, read bilinearly at a point given in its own texels. */\nfn sourceAt(position: vec2<f32>) -> vec3<f32> {\n if (lens.fromImage != 0u) {\n return imageBilinear(position);\n }\n\n return downBilinear(lens.sourceOffset, lens.sourceSize, position);\n}\n\nfn luminance(color: vec3<f32>) -> f32 {\n return dot(color, vec3<f32>(0.2126, 0.7152, 0.0722));\n}\n\n/** Karis' average: a group of taps weighed by one over one plus its luminance, so a lone firefly cannot flare. */\nfn karis(sum: vec3<f32>) -> vec4<f32> {\n let weight = 1.0 / (1.0 + luminance(sum));\n\n return vec4<f32>(sum * weight, weight);\n}\n\n/** One level of the pyramid from the one above it: Jimenez's 13 taps, four overlapping boxes and a centre one. */\n@compute @workgroup_size(8, 8, 1)\nfn downsample(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= lens.targetSize.x || id.y >= lens.targetSize.y) {\n return;\n }\n\n let scale = vec2<f32>(lens.sourceSize) / vec2<f32>(lens.targetSize);\n let centre = (vec2<f32>(id.xy) + 0.5) * scale;\n let a = sourceAt(centre + vec2<f32>(-2.0, -2.0));\n let b = sourceAt(centre + vec2<f32>(0.0, -2.0));\n let c = sourceAt(centre + vec2<f32>(2.0, -2.0));\n let d = sourceAt(centre + vec2<f32>(-2.0, 0.0));\n let e = sourceAt(centre);\n let f = sourceAt(centre + vec2<f32>(2.0, 0.0));\n let g = sourceAt(centre + vec2<f32>(-2.0, 2.0));\n let h = sourceAt(centre + vec2<f32>(0.0, 2.0));\n let i = sourceAt(centre + vec2<f32>(2.0, 2.0));\n let j = sourceAt(centre + vec2<f32>(-1.0, -1.0));\n let k = sourceAt(centre + vec2<f32>(1.0, -1.0));\n let l = sourceAt(centre + vec2<f32>(-1.0, 1.0));\n let m = sourceAt(centre + vec2<f32>(1.0, 1.0));\n var result: vec3<f32>;\n\n if (lens.fromImage != 0u) {\n let groups = karis((j + k + l + m) * 0.25) * 0.5\n + karis((a + b + d + e) * 0.25) * 0.125\n + karis((b + c + e + f) * 0.25) * 0.125\n + karis((d + e + g + h) * 0.25) * 0.125\n + karis((e + f + h + i) * 0.25) * 0.125;\n\n result = groups.rgb / max(groups.w, 1e-6);\n } else {\n result = e * 0.125 + (a + c + g + i) * 0.03125 + (b + d + f + h) * 0.0625 + (j + k + l + m) * 0.125;\n }\n\n down[lens.targetOffset + id.y * lens.targetSize.x + id.x] = vec4<f32>(max(result, vec3<f32>(0.0)), 1.0);\n}\n\n/** A 3 x 3 tent over the smaller level, read at a point given in its texels. */\nfn tent(position: vec2<f32>) -> vec3<f32> {\n var sum = vec3<f32>(0.0);\n\n for (var dy = -1; dy <= 1; dy = dy + 1) {\n for (var dx = -1; dx <= 1; dx = dx + 1) {\n let weight = f32((2 - abs(dx)) * (2 - abs(dy))) / 16.0;\n let at = position + vec2<f32>(f32(dx), f32(dy));\n\n if (lens.fromDown != 0u) {\n sum = sum + downBilinear(lens.sourceOffset, lens.sourceSize, at) * weight;\n } else {\n sum = sum + upBilinear(lens.sourceOffset, lens.sourceSize, at) * weight;\n }\n }\n }\n\n return sum;\n}\n\n/** One level folded back: its own light, mixed by the radius with the wider spread of every level below it. */\n@compute @workgroup_size(8, 8, 1)\nfn upsample(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= lens.targetSize.x || id.y >= lens.targetSize.y) {\n return;\n }\n\n let scale = vec2<f32>(lens.sourceSize) / vec2<f32>(lens.targetSize);\n let wider = tent((vec2<f32>(id.xy) + 0.5) * scale);\n let index = lens.targetOffset + id.y * lens.targetSize.x + id.x;\n\n up[index] = vec4<f32>(mix(down[index].rgb, wider, lens.radius), 1.0);\n}\n\n/** The aperture's rays at one texel of the streak level: the light behind it along each ray, fading with distance. */\n@compute @workgroup_size(8, 8, 1)\nfn streaks(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= lens.targetSize.x || id.y >= lens.targetSize.y) {\n return;\n }\n\n let centre = vec2<f32>(id.xy) + 0.5;\n // The exposure the image will have: the setting's, and the meter's when it meters.\n let exposure = lens.exposure * select(1.0, metered[0].x, lens.autoExposure > 0.0);\n var sum = vec3<f32>(0.0);\n var total = 0.0;\n\n for (var r = 0u; r < lens.rays; r = r + 1u) {\n let angle = lens.glareAngle + f32(r) * 2.0 * PI / f32(lens.rays);\n let along = vec2<f32>(cos(angle), sin(angle));\n\n for (var j = 1; j <= STREAK_TAPS; j = j + 1) {\n let t = f32(j) / f32(STREAK_TAPS);\n let weight = exp(-6.0 * t);\n\n let light = downBilinear(lens.sourceOffset, lens.sourceSize, centre - along * (t * lens.glareLength));\n\n sum = sum + light * (weight / max(1.0, luminance(light) * exposure / GLARE_CEILING));\n total = total + weight;\n }\n }\n\n streaked[id.y * lens.targetSize.x + id.x] = vec4<f32>(sum / max(total, 1e-6), 1.0);\n}\n\nfn streakTexel(x: i32, y: i32) -> vec3<f32> {\n let s = vec2<i32>(lens.streakSize);\n\n return streaked[u32(clamp(y, 0, s.y - 1) * s.x + clamp(x, 0, s.x - 1))].rgb;\n}\n\nfn streakBilinear(position: vec2<f32>) -> vec3<f32> {\n let b = bilinearAt(position);\n let x = i32(b.x);\n let y = i32(b.y);\n\n return mix(mix(streakTexel(x, y), streakTexel(x + 1, y), b.z), mix(streakTexel(x, y + 1), streakTexel(x + 1, y + 1), b.z), b.w);\n}\n\n/** The image as the lens delivers it: fringed towards the corners, with its halo, balanced for the camera's white. */\n@compute @workgroup_size(8, 8, 1)\nfn composite(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= lens.resolution.x || id.y >= lens.resolution.y) {\n return;\n }\n\n let size = vec2<f32>(lens.resolution);\n let position = vec2<f32>(id.xy) + 0.5;\n var color = imageTexel(i32(id.x), i32(id.y));\n\n // Lateral chromatic aberration: each colour is magnified a little differently, red outwards and blue inwards,\n // in proportion to the distance from the centre, which is how a lens's dispersion moves them.\n if (lens.aberration > 0.0) {\n let centre = size * 0.5;\n let away = position - centre;\n let red = imageBilinear(centre + away * (1.0 - lens.aberration));\n let blue = imageBilinear(centre + away * (1.0 + lens.aberration));\n\n color = vec3<f32>(red.r, color.g, blue.b);\n }\n\n if (lens.intensity > 0.0) {\n // The largest folded level is already a tent over the levels below it, so one bilinear read of it is smooth.\n let halo = upBilinear(lens.haloOffset, lens.haloSize, position * vec2<f32>(lens.haloSize) / size);\n\n color = mix(color, halo, lens.intensity);\n }\n\n if (lens.glare > 0.0) {\n color = mix(color, streakBilinear(position * vec2<f32>(lens.streakSize) / size), lens.glare);\n }\n\n if (lens.autoExposure > 0.0) {\n color = color * metered[0].x;\n }\n\n let balanced = vec3<f32>(dot(lens.white0.xyz, color), dot(lens.white1.xyz, color), dot(lens.white2.xyz, color));\n\n graded[id.y * lens.resolution.x + id.x] = vec4<f32>(max(balanced, vec3<f32>(0.0)), 1.0);\n}\n";
|
|
17
17
|
//# sourceMappingURL=lens.wgsl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lens.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/lens.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"lens.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/lens.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,kyfA+ZrB,CAAC"}
|
package/dist/wgsl/lens.wgsl.js
CHANGED
|
@@ -50,6 +50,8 @@ struct Lens {
|
|
|
50
50
|
autoExposure: f32,
|
|
51
51
|
// The share of a dark scene's darkness the meter keeps.
|
|
52
52
|
darkAdaptation: f32,
|
|
53
|
+
// The exposure applied after the lens, the meter's aside, which the rays are limited against.
|
|
54
|
+
exposure: f32,
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
const PI: f32 = 3.14159265358979;
|
|
@@ -57,6 +59,12 @@ const PI: f32 = 3.14159265358979;
|
|
|
57
59
|
/** Taps along each of the aperture's rays. */
|
|
58
60
|
const STREAK_TAPS: i32 = 24;
|
|
59
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The brightest a tap of the rays counts as, in multiples of the exposed white. A lamp is thousands of times white,
|
|
64
|
+
* which would carry its rays across the frame at full length; past this they stay as short as a camera shows them.
|
|
65
|
+
*/
|
|
66
|
+
const GLARE_CEILING: f32 = 4.0;
|
|
67
|
+
|
|
60
68
|
@group(0) @binding(0) var<uniform> lens: Lens;
|
|
61
69
|
@group(0) @binding(1) var<storage, read> accumulation: array<vec4<f32>>;
|
|
62
70
|
@group(0) @binding(2) var<storage, read> denoised: array<vec4<f32>>;
|
|
@@ -342,6 +350,8 @@ fn streaks(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
342
350
|
}
|
|
343
351
|
|
|
344
352
|
let centre = vec2<f32>(id.xy) + 0.5;
|
|
353
|
+
// The exposure the image will have: the setting's, and the meter's when it meters.
|
|
354
|
+
let exposure = lens.exposure * select(1.0, metered[0].x, lens.autoExposure > 0.0);
|
|
345
355
|
var sum = vec3<f32>(0.0);
|
|
346
356
|
var total = 0.0;
|
|
347
357
|
|
|
@@ -351,9 +361,11 @@ fn streaks(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
351
361
|
|
|
352
362
|
for (var j = 1; j <= STREAK_TAPS; j = j + 1) {
|
|
353
363
|
let t = f32(j) / f32(STREAK_TAPS);
|
|
354
|
-
let weight = exp(-
|
|
364
|
+
let weight = exp(-6.0 * t);
|
|
365
|
+
|
|
366
|
+
let light = downBilinear(lens.sourceOffset, lens.sourceSize, centre - along * (t * lens.glareLength));
|
|
355
367
|
|
|
356
|
-
sum = sum +
|
|
368
|
+
sum = sum + light * (weight / max(1.0, luminance(light) * exposure / GLARE_CEILING));
|
|
357
369
|
total = total + weight;
|
|
358
370
|
}
|
|
359
371
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lens.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/lens.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,UAAU,CAAC
|
|
1
|
+
{"version":3,"file":"lens.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/lens.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+ZnC,CAAC"}
|