@tinypoly/gpu-renderer 0.0.5 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +59 -3
- package/README.md +53 -4
- package/dist/atmosphere.d.ts +21 -0
- package/dist/atmosphere.d.ts.map +1 -0
- package/dist/atmosphere.js +72 -0
- package/dist/atmosphere.js.map +1 -0
- package/dist/denoise.d.ts +11 -1
- package/dist/denoise.d.ts.map +1 -1
- package/dist/denoise.js +34 -6
- package/dist/denoise.js.map +1 -1
- package/dist/frameLayout.d.ts.map +1 -1
- package/dist/frameLayout.js +17 -0
- package/dist/frameLayout.js.map +1 -1
- package/dist/geometry.d.ts.map +1 -1
- package/dist/geometry.js +6 -3
- package/dist/geometry.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lensPass.d.ts +60 -0
- package/dist/lensPass.d.ts.map +1 -0
- package/dist/lensPass.js +235 -0
- package/dist/lensPass.js.map +1 -0
- package/dist/lightTree.d.ts +50 -0
- package/dist/lightTree.d.ts.map +1 -0
- package/dist/lightTree.js +146 -0
- package/dist/lightTree.js.map +1 -0
- package/dist/lights.d.ts +12 -0
- package/dist/lights.d.ts.map +1 -1
- package/dist/lights.js +56 -1
- package/dist/lights.js.map +1 -1
- package/dist/materialLayout.d.ts +24 -0
- package/dist/materialLayout.d.ts.map +1 -1
- package/dist/materialLayout.js +36 -1
- package/dist/materialLayout.js.map +1 -1
- package/dist/pathTracePass.d.ts +6 -3
- package/dist/pathTracePass.d.ts.map +1 -1
- package/dist/pathTracePass.js +16 -10
- package/dist/pathTracePass.js.map +1 -1
- package/dist/renderer.d.ts +27 -7
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +153 -16
- package/dist/renderer.js.map +1 -1
- package/dist/sceneUpload.d.ts +6 -0
- package/dist/sceneUpload.d.ts.map +1 -1
- package/dist/sceneUpload.js +5 -0
- package/dist/sceneUpload.js.map +1 -1
- package/dist/settings.d.ts +142 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +100 -0
- package/dist/settings.js.map +1 -0
- package/dist/subsurface.d.ts +76 -0
- package/dist/subsurface.d.ts.map +1 -0
- package/dist/subsurface.js +70 -0
- package/dist/subsurface.js.map +1 -0
- package/dist/volumeContainment.d.ts +17 -0
- package/dist/volumeContainment.d.ts.map +1 -0
- package/dist/volumeContainment.js +61 -0
- package/dist/volumeContainment.js.map +1 -0
- package/dist/wgsl/bsdf.wgsl.d.ts +1 -1
- package/dist/wgsl/bsdf.wgsl.d.ts.map +1 -1
- package/dist/wgsl/bsdf.wgsl.js +35 -3
- package/dist/wgsl/bsdf.wgsl.js.map +1 -1
- package/dist/wgsl/common.wgsl.d.ts.map +1 -1
- package/dist/wgsl/common.wgsl.js +4 -0
- package/dist/wgsl/common.wgsl.js.map +1 -1
- package/dist/wgsl/lens.wgsl.d.ts +17 -0
- package/dist/wgsl/lens.wgsl.d.ts.map +1 -0
- package/dist/wgsl/lens.wgsl.js +301 -0
- package/dist/wgsl/lens.wgsl.js.map +1 -0
- package/dist/wgsl/lights.wgsl.d.ts +1 -1
- package/dist/wgsl/lights.wgsl.d.ts.map +1 -1
- package/dist/wgsl/lights.wgsl.js +237 -31
- package/dist/wgsl/lights.wgsl.js.map +1 -1
- package/dist/wgsl/pathTrace.wgsl.d.ts +1 -1
- package/dist/wgsl/pathTrace.wgsl.d.ts.map +1 -1
- package/dist/wgsl/pathTrace.wgsl.js +406 -25
- package/dist/wgsl/pathTrace.wgsl.js.map +1 -1
- package/dist/wgsl/random.wgsl.d.ts +1 -1
- package/dist/wgsl/random.wgsl.d.ts.map +1 -1
- package/dist/wgsl/random.wgsl.js +28 -0
- package/dist/wgsl/random.wgsl.js.map +1 -1
- package/dist/wgsl/sampling.wgsl.d.ts +1 -1
- package/dist/wgsl/sampling.wgsl.d.ts.map +1 -1
- package/dist/wgsl/sampling.wgsl.js +25 -0
- package/dist/wgsl/sampling.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 +21 -1
- package/dist/wgsl/surface.wgsl.js.map +1 -1
- package/package.json +1 -1
- package/src/atmosphere.ts +95 -0
- package/src/denoise.ts +44 -6
- package/src/frameLayout.ts +17 -0
- package/src/geometry.ts +6 -2
- package/src/index.ts +13 -0
- package/src/lensPass.ts +299 -0
- package/src/lightTree.ts +218 -0
- package/src/lights.ts +75 -1
- package/src/materialLayout.ts +52 -1
- package/src/pathTracePass.ts +19 -10
- package/src/renderer.ts +206 -24
- package/src/sceneUpload.ts +11 -0
- package/src/settings.ts +240 -0
- package/src/subsurface.ts +153 -0
- package/src/volumeContainment.ts +81 -0
- package/src/wgsl/bsdf.wgsl.ts +35 -3
- package/src/wgsl/common.wgsl.ts +4 -0
- package/src/wgsl/lens.wgsl.ts +300 -0
- package/src/wgsl/lights.wgsl.ts +237 -31
- package/src/wgsl/pathTrace.wgsl.ts +406 -25
- package/src/wgsl/random.wgsl.ts +28 -0
- package/src/wgsl/sampling.wgsl.ts +25 -0
- package/src/wgsl/surface.wgsl.ts +21 -1
|
@@ -98,12 +98,14 @@ fn clampIndirect(contribution: vec3<f32>) -> vec3<f32> {
|
|
|
98
98
|
* How much of the emission a bounce ray found the renderer keeps. Direct sampling had its own chance at this triangle,
|
|
99
99
|
* and the heuristic splits the two so neither counts it twice.
|
|
100
100
|
*/
|
|
101
|
-
fn emissionWeight(hit: Hit, direction: vec3<f32>, bsdfPdf: f32, lightsOwnDirect: bool
|
|
101
|
+
fn emissionWeight(hit: Hit, direction: vec3<f32>, bsdfPdf: f32, lightsOwnDirect: bool, scatteredAt: vec3<f32>,
|
|
102
|
+
normal: vec3<f32>) -> f32 {
|
|
102
103
|
if (frame.areaLightCount == 0u) {
|
|
103
104
|
return 1.0;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
+
// From the vertex the bounce left, which is where direct sampling would have picked it from.
|
|
108
|
+
let areaPdf = lightPdfOfTriangle(hit.triangle, scatteredAt, normal);
|
|
107
109
|
|
|
108
110
|
if (areaPdf <= 0.0) {
|
|
109
111
|
return 1.0;
|
|
@@ -122,6 +124,18 @@ fn emissionWeight(hit: Hit, direction: vec3<f32>, bsdfPdf: f32, lightsOwnDirect:
|
|
|
122
124
|
return misWeight(bsdfPdf, areaPdf * hit.distance * hit.distance / cosLight);
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
/**
|
|
128
|
+
* The normal the light tree weighs lights by, or none where light from behind the surface counts too: a point in a
|
|
129
|
+
* volume, a toon ramp read all round, a clearcoat that may face what its base turns from, and transmission.
|
|
130
|
+
*/
|
|
131
|
+
fn treeNormal(surface: Surface) -> vec3<f32> {
|
|
132
|
+
if (surface.kind == KIND_VOLUME || surface.kind == KIND_TOON || surface.clearcoat > 0.0 || surface.transmission > 0.0) {
|
|
133
|
+
return vec3<f32>(0.0);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return surface.normal;
|
|
137
|
+
}
|
|
138
|
+
|
|
125
139
|
/** One shadow ray from the surface towards a light, weighted against the chance the bounce would have found it too. */
|
|
126
140
|
fn directLight(
|
|
127
141
|
surface: Surface,
|
|
@@ -130,10 +144,11 @@ fn directLight(
|
|
|
130
144
|
pick: f32,
|
|
131
145
|
u: vec2<f32>,
|
|
132
146
|
rng: ptr<function, PathSampler>,
|
|
133
|
-
diffusePart: ptr<function, vec3<f32
|
|
147
|
+
diffusePart: ptr<function, vec3<f32>>,
|
|
148
|
+
medium: f32) -> vec3<f32> {
|
|
134
149
|
*diffusePart = vec3<f32>(0.0);
|
|
135
150
|
|
|
136
|
-
let chosen = sampleLight(position, pick, u);
|
|
151
|
+
let chosen = sampleLight(position, treeNormal(surface), pick, u);
|
|
137
152
|
|
|
138
153
|
if (!chosen.valid) {
|
|
139
154
|
return vec3<f32>(0.0);
|
|
@@ -153,7 +168,7 @@ fn directLight(
|
|
|
153
168
|
let toon = surface.kind == KIND_TOON;
|
|
154
169
|
// Three's shadow map leaves a toon mesh's own far side unshadowed; tracing through the mesh would not.
|
|
155
170
|
let skip = select(NO_MATERIAL, surface.material, toon && dot(surface.normal, chosen.direction) < 0.1);
|
|
156
|
-
let through = lightTransmittance(position, chosen, rng, skip);
|
|
171
|
+
let through = lightTransmittance(position, chosen, rng, skip, medium);
|
|
157
172
|
|
|
158
173
|
if (all(through <= vec3<f32>(0.0))) {
|
|
159
174
|
return vec3<f32>(0.0);
|
|
@@ -191,6 +206,142 @@ fn diffuseGuide(surface: Surface) -> vec3<f32> {
|
|
|
191
206
|
return surface.albedo * (1.0 - surface.metalness) * (1.0 - clamp(surface.transmission, 0.0, 1.0));
|
|
192
207
|
}
|
|
193
208
|
|
|
209
|
+
/**
|
|
210
|
+
* How often a vertex on a material that scatters inside takes the walk into it instead of its surface lobes: by the
|
|
211
|
+
* share of the light each accounts for, as lobeWeights chooses between lobes. Never all of the time, so the specular
|
|
212
|
+
* layer on top stays reachable.
|
|
213
|
+
*/
|
|
214
|
+
fn subsurfaceChance(surface: Surface, view: vec3<f32>) -> f32 {
|
|
215
|
+
if (surface.subsurface <= 0.0 || (surface.flags & MATERIAL_PBR) == 0u) {
|
|
216
|
+
return 0.0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let diffuse = luminanceOf(surface.albedo * (1.0 - surface.metalness)) * coatTransmittance(surface, view);
|
|
220
|
+
let inside = diffuse * surface.subsurface;
|
|
221
|
+
let outside = diffuse * (1.0 - surface.subsurface) + luminanceOf(specularAlbedo(surface, view)) + 0.02;
|
|
222
|
+
|
|
223
|
+
return clamp(inside / max(inside + outside, 1e-6), 0.0, 0.95);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The single scattering albedo that gives this multiple scattering albedo in a semi-infinite medium: van de Hulst's
|
|
228
|
+
* relation, inverted as Cycles inverts it for its random walk. What the material's colour says it reflects in all
|
|
229
|
+
* is what light keeps at each scattering inside it.
|
|
230
|
+
*/
|
|
231
|
+
fn subsurfaceAlbedo(albedo: vec3<f32>) -> vec3<f32> {
|
|
232
|
+
let a = clamp(albedo, vec3<f32>(0.0), vec3<f32>(0.999));
|
|
233
|
+
let s = 4.09712 + 4.20863 * a - sqrt(9.59217 + 41.6808 * a + 17.7126 * a * a);
|
|
234
|
+
|
|
235
|
+
return vec3<f32>(1.0) - s * s;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* How far the walk goes before it next scatters, drawn for one colour picked in proportion to what the path still
|
|
240
|
+
* carries of it. choice keeps those odds, which the next step weighs its distance by over all three colours, so each
|
|
241
|
+
* colour keeps its own mean free path and the estimate stays exact (spectral MIS, as Cycles' random walk does it).
|
|
242
|
+
*/
|
|
243
|
+
fn walkDistance(throughput: vec3<f32>, sigma: vec3<f32>, u: vec2<f32>, choice: ptr<function, vec3<f32>>) -> f32 {
|
|
244
|
+
let weights = max(throughput, vec3<f32>(0.0));
|
|
245
|
+
let total = weights.x + weights.y + weights.z;
|
|
246
|
+
let odds = select(vec3<f32>(1.0 / 3.0), weights / total, total > 0.0);
|
|
247
|
+
let chosen = select(select(sigma.z, sigma.y, u.x < odds.x + odds.y), sigma.x, u.x < odds.x);
|
|
248
|
+
|
|
249
|
+
*choice = odds;
|
|
250
|
+
|
|
251
|
+
return -log(max(1.0 - u.y, 1e-12)) / chosen;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Where a walk leaves the material: a white Lambert surface facing out, as Cycles leaves its random walk. The colour
|
|
256
|
+
* was taken inside, scattering by scattering, so nothing tints the light again on the way out.
|
|
257
|
+
*/
|
|
258
|
+
fn subsurfaceExit(m: u32, triangle: u32, bary: vec2<f32>, direction: vec3<f32>) -> Surface {
|
|
259
|
+
var surface: Surface;
|
|
260
|
+
// Turned to face against the reversed ray: outwards, the side the walk is leaving through.
|
|
261
|
+
let out = interpolatedNormal(m, triangle, bary, -direction);
|
|
262
|
+
|
|
263
|
+
surface.normal = out;
|
|
264
|
+
surface.facing = out;
|
|
265
|
+
surface.albedo = vec3<f32>(1.0);
|
|
266
|
+
surface.flags = MATERIAL_LIT;
|
|
267
|
+
surface.kind = KIND_LAMBERT;
|
|
268
|
+
surface.opacity = 1.0;
|
|
269
|
+
surface.alpha = 1.0;
|
|
270
|
+
surface.blending = BLEND_NORMAL;
|
|
271
|
+
surface.material = m;
|
|
272
|
+
surface.roughness = 1.0;
|
|
273
|
+
surface.ior = 1.5;
|
|
274
|
+
surface.specularF90 = 1.0;
|
|
275
|
+
surface.envMap = NO_TEXTURE;
|
|
276
|
+
surface.envMapIntensity = 1.0;
|
|
277
|
+
surface.gradientMap = NO_TEXTURE;
|
|
278
|
+
|
|
279
|
+
return surface;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** A point inside a volume where the light scatters: the medium's phase function in place of a surface's lobes. */
|
|
283
|
+
fn volumePoint(direction: vec3<f32>, albedo: vec3<f32>, anisotropy: f32) -> Surface {
|
|
284
|
+
var surface: Surface;
|
|
285
|
+
|
|
286
|
+
surface.normal = -direction;
|
|
287
|
+
surface.facing = -direction;
|
|
288
|
+
surface.albedo = albedo;
|
|
289
|
+
surface.flags = MATERIAL_LIT;
|
|
290
|
+
surface.kind = KIND_VOLUME;
|
|
291
|
+
surface.anisotropy = anisotropy;
|
|
292
|
+
surface.opacity = 1.0;
|
|
293
|
+
surface.alpha = 1.0;
|
|
294
|
+
surface.blending = BLEND_NORMAL;
|
|
295
|
+
surface.material = NO_MATERIAL;
|
|
296
|
+
surface.roughness = 1.0;
|
|
297
|
+
surface.ior = 1.0;
|
|
298
|
+
surface.specularF90 = 1.0;
|
|
299
|
+
surface.envMap = NO_TEXTURE;
|
|
300
|
+
surface.envMapIntensity = 1.0;
|
|
301
|
+
surface.gradientMap = NO_TEXTURE;
|
|
302
|
+
|
|
303
|
+
return surface;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* The shading normal with the edges near it rounded, as Cycles' bevel node rounds them, by one probe a sample. A
|
|
308
|
+
* point is drawn on a disk of the bevel radius around the shaded point, across an axis picked along the normal half
|
|
309
|
+
* the time and along each tangent a quarter; a short ray along that axis through it finds the surface of the same
|
|
310
|
+
* material there, and that surface's normal turns this one by as much as it differs from the normal here. Over the
|
|
311
|
+
* samples a pixel takes, the normal blends from one face to the next within the radius of every edge, so a box reads
|
|
312
|
+
* as a machined one. A probe that finds nothing, or a normal turned away from the view, leaves the normal as it was.
|
|
313
|
+
*/
|
|
314
|
+
fn bevelNormal(m: u32, point: vec3<f32>, surface: Surface, view: vec3<f32>, radius: f32, u: vec2<f32>,
|
|
315
|
+
v: vec2<f32>) -> vec3<f32> {
|
|
316
|
+
let basis = orthonormalBasis(surface.facing);
|
|
317
|
+
var axis = basis[2];
|
|
318
|
+
var first = basis[0];
|
|
319
|
+
var second = basis[1];
|
|
320
|
+
|
|
321
|
+
if (v.x >= 0.5) {
|
|
322
|
+
axis = select(basis[1], basis[0], v.x < 0.75);
|
|
323
|
+
first = select(basis[2], basis[1], v.x < 0.75);
|
|
324
|
+
second = select(basis[0], basis[2], v.x < 0.75);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
let reach = radius * sqrt(u.x);
|
|
328
|
+
let angle = 2.0 * PI * u.y;
|
|
329
|
+
let across = point + first * (reach * cos(angle)) + second * (reach * sin(angle));
|
|
330
|
+
// Along the axis one way or the other at random, from the far edge of the radius across it.
|
|
331
|
+
let way = select(-1.0, 1.0, v.y < 0.5);
|
|
332
|
+
let hit = traceScene(across - axis * (radius * way), axis * way, 2.0 * radius);
|
|
333
|
+
|
|
334
|
+
if (hit.triangle == NO_HIT || materialIndexOf(hit.triangle) != m) {
|
|
335
|
+
return surface.normal;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// The authored normal there, turned the way this side's own is: the facing normal is the authored one, or its reverse.
|
|
339
|
+
let there = authoredNormal(hit.triangle, vec2<f32>(hit.u, hit.v)) * select(-1.0, 1.0, surface.front);
|
|
340
|
+
let turned = normalize(surface.normal + there - surface.facing);
|
|
341
|
+
|
|
342
|
+
return select(surface.normal, turned, dot(turned, view) > 0.0 && all(turned == turned));
|
|
343
|
+
}
|
|
344
|
+
|
|
194
345
|
struct PathResult {
|
|
195
346
|
radiance: vec3<f32>,
|
|
196
347
|
alpha: f32,
|
|
@@ -206,11 +357,16 @@ struct PathResult {
|
|
|
206
357
|
guideDistance: f32,
|
|
207
358
|
roughness: f32,
|
|
208
359
|
specularDistance: f32,
|
|
360
|
+
// How much of the first surface scatters light inside, which the denoiser relaxes its tests over.
|
|
361
|
+
subsurface: f32,
|
|
209
362
|
};
|
|
210
363
|
|
|
211
364
|
/** How many surfaces a path may pass through without scattering (cutouts, culled faces, glass) before it gives up. */
|
|
212
365
|
const MAX_STEPS: u32 = 64u;
|
|
213
366
|
|
|
367
|
+
/** Scatterings one walk inside a material may take; the roulette ends most walks well before. */
|
|
368
|
+
const MAX_WALK: u32 = 128u;
|
|
369
|
+
|
|
214
370
|
fn traceRadiance(
|
|
215
371
|
rayOrigin: vec3<f32>, rayDirection: vec3<f32>, rayLength: f32, rng: ptr<function, PathSampler>) -> PathResult {
|
|
216
372
|
var radiance = vec3<f32>(0.0);
|
|
@@ -230,6 +386,9 @@ fn traceRadiance(
|
|
|
230
386
|
var bounce = 0u;
|
|
231
387
|
// The vertex the path last scattered at took all of its direct light by sampling lights: a toon surface.
|
|
232
388
|
var lightsOwnDirect = false;
|
|
389
|
+
// Where the path last scattered and the normal the light tree weighed lights by there, for the heuristic's other half.
|
|
390
|
+
var lastPosition = rayOrigin;
|
|
391
|
+
var lastNormal = vec3<f32>(0.0);
|
|
233
392
|
// That vertex's own environment map, which a ray escaping from it sees instead of the scene's.
|
|
234
393
|
var escapeMap = NO_TEXTURE;
|
|
235
394
|
var escapeIntensity = 1.0;
|
|
@@ -240,6 +399,12 @@ fn traceRadiance(
|
|
|
240
399
|
var fogAmount = 0.0;
|
|
241
400
|
var fogStart = vec3<f32>(0.0);
|
|
242
401
|
var fogThroughput = vec3<f32>(0.0);
|
|
402
|
+
// The air between the camera and the first surface it sees: aerial perspective.
|
|
403
|
+
var hazed = false;
|
|
404
|
+
var hazeDistance = 0.0;
|
|
405
|
+
var hazeStart = vec3<f32>(0.0);
|
|
406
|
+
var hazeThroughput = vec3<f32>(0.0);
|
|
407
|
+
var hazeDirection = vec3<f32>(0.0);
|
|
243
408
|
// A built-in material's hooks after <opaque_fragment> edit the colour the camera sees of it, which is everything the
|
|
244
409
|
// path gathers from that surface on.
|
|
245
410
|
var afterMaterial = NO_MATERIAL;
|
|
@@ -257,6 +422,7 @@ fn traceRadiance(
|
|
|
257
422
|
var guideNormal = vec3<f32>(0.0);
|
|
258
423
|
var guideDistance = 1e6;
|
|
259
424
|
var guideRoughness = 1.0;
|
|
425
|
+
var guideSubsurface = 0.0;
|
|
260
426
|
// Where the path first scattered, which decides which of the two signals the rest of it feeds.
|
|
261
427
|
var split = false;
|
|
262
428
|
var splitBase = vec3<f32>(0.0);
|
|
@@ -265,17 +431,111 @@ fn traceRadiance(
|
|
|
265
431
|
var splitDiffuse = true;
|
|
266
432
|
var waitingForSpecular = false;
|
|
267
433
|
var specularDistance = 1e6;
|
|
434
|
+
// Inside a material that scatters light, between two scatterings: its extinction and single scattering albedo per
|
|
435
|
+
// colour, its anisotropy, the odds each colour had when the current step's distance was drawn, and the steps so far.
|
|
436
|
+
var inMedium = false;
|
|
437
|
+
var sigma = vec3<f32>(0.0);
|
|
438
|
+
var walkAlbedo = vec3<f32>(0.0);
|
|
439
|
+
var walkG = 0.0;
|
|
440
|
+
var walkOdds = vec3<f32>(0.0);
|
|
441
|
+
var walkSteps = 0u;
|
|
442
|
+
// Inside a volume: its density per unit of distance, what it keeps at each scattering and its anisotropy. A camera
|
|
443
|
+
// inside one starts there.
|
|
444
|
+
var inVolume = false;
|
|
445
|
+
var volumeDensity = 0.0;
|
|
446
|
+
var volumeAlbedo = vec3<f32>(0.0);
|
|
447
|
+
var volumeG = 0.0;
|
|
448
|
+
|
|
449
|
+
if (VOLUMES && frame.cameraVolume != NO_MATERIAL && frame.volumesEnabled != 0u) {
|
|
450
|
+
inVolume = true;
|
|
451
|
+
volumeDensity = materials[frame.cameraVolume].volume.w;
|
|
452
|
+
volumeAlbedo = materials[frame.cameraVolume].volume.rgb;
|
|
453
|
+
volumeG = materials[frame.cameraVolume].volumeAnisotropy;
|
|
454
|
+
}
|
|
268
455
|
|
|
269
|
-
for (var step = 0u; step < MAX_STEPS; step = step + 1u) {
|
|
456
|
+
for (var step = 0u; step < MAX_STEPS + MAX_WALK; step = step + 1u) {
|
|
270
457
|
let hit = traceScene(origin, direction, limit);
|
|
271
458
|
|
|
459
|
+
// A step of the walk inside a material that ends in the medium: the light scatters there, keeping each colour's
|
|
460
|
+
// share of it. It needs three numbers, which a plain hash gives for a fraction of what the sequence costs.
|
|
461
|
+
if (SUBSURFACE_WALK && inMedium && hit.triangle == NO_HIT) {
|
|
462
|
+
let travelled = exp(-sigma * limit);
|
|
463
|
+
let density = dot(walkOdds, sigma * travelled);
|
|
464
|
+
// A new direction, the next distance (a colour and how far), and the roulette's chance.
|
|
465
|
+
let turn = hashSample2D(rng);
|
|
466
|
+
let next = hashSample2D(rng);
|
|
467
|
+
let chance = hashSample2D(rng).x;
|
|
468
|
+
|
|
469
|
+
throughput = throughput * sigma * walkAlbedo * travelled / max(density, 1e-20);
|
|
470
|
+
walkSteps = walkSteps + 1u;
|
|
471
|
+
|
|
472
|
+
let survival = max3(throughput);
|
|
473
|
+
|
|
474
|
+
if (walkSteps >= MAX_WALK || survival <= 0.0) {
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (survival < 0.2) {
|
|
479
|
+
if (chance > survival) {
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
throughput = throughput / survival;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
origin = origin + direction * limit;
|
|
487
|
+
direction = sampleHenyeyGreenstein(direction, walkG, turn);
|
|
488
|
+
limit = walkDistance(throughput, sigma, next, &walkOdds);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Every vertex draws the same dimensions in the same order, whatever the path then does with them: from the
|
|
493
|
+
// sequence for the first vertices, from a hash past SEQUENCE_DEPTH bounces.
|
|
494
|
+
let deep = bounce >= SEQUENCE_DEPTH;
|
|
495
|
+
let coverage = pathSample2D(rng, deep);
|
|
496
|
+
let pick = pathSample2D(rng, deep);
|
|
497
|
+
let lightSample = pathSample2D(rng, deep);
|
|
498
|
+
let lobe = pathSample2D(rng, deep);
|
|
499
|
+
let bounceSample = pathSample2D(rng, deep);
|
|
500
|
+
var mediumSample = vec2<f32>(0.0);
|
|
501
|
+
|
|
502
|
+
if (VOLUMES) {
|
|
503
|
+
mediumSample = pathSample2D(rng, deep);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// The walk left the material here, and the surface below is its way out rather than what the material shows.
|
|
507
|
+
var exiting = false;
|
|
508
|
+
|
|
509
|
+
if (SUBSURFACE_WALK && inMedium) {
|
|
510
|
+
// The walk reached the surface before scattering again: the chance of travelling that far, per colour.
|
|
511
|
+
let travelled = exp(-sigma * hit.distance);
|
|
512
|
+
|
|
513
|
+
throughput = throughput * travelled / max(dot(walkOdds, travelled), 1e-20);
|
|
514
|
+
inMedium = false;
|
|
515
|
+
exiting = true;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Inside a volume, the medium may stop the ray before whatever it hit. The distance is drawn now that the segment's
|
|
519
|
+
// length is known, which a homogeneous medium allows; one grey density makes the weight of either outcome one.
|
|
520
|
+
var scatterAt = -1.0;
|
|
521
|
+
|
|
522
|
+
if (VOLUMES && inVolume && !exiting) {
|
|
523
|
+
let free = -log(max(1.0 - mediumSample.x, 1e-12)) / max(volumeDensity, 1e-12);
|
|
524
|
+
|
|
525
|
+
if (free < select(hit.distance, limit, hit.triangle == NO_HIT)) {
|
|
526
|
+
scatterAt = free;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
let scattering = scatterAt >= 0.0;
|
|
531
|
+
|
|
272
532
|
// The specular continuation's first hit: how far the reflection the denoiser filters actually is.
|
|
273
533
|
if (waitingForSpecular) {
|
|
274
534
|
specularDistance = select(min(hit.distance, 1e6), 1e6, hit.triangle == NO_HIT);
|
|
275
535
|
waitingForSpecular = false;
|
|
276
536
|
}
|
|
277
537
|
|
|
278
|
-
if (hit.triangle == NO_HIT) {
|
|
538
|
+
if (hit.triangle == NO_HIT && !scattering) {
|
|
279
539
|
if (primary) {
|
|
280
540
|
// A transparent background leaves the pixel empty, which its alpha says.
|
|
281
541
|
if (frame.background.w != 2.0) {
|
|
@@ -299,18 +559,45 @@ fn traceRadiance(
|
|
|
299
559
|
break;
|
|
300
560
|
}
|
|
301
561
|
|
|
302
|
-
|
|
562
|
+
let travel = select(hit.distance, scatterAt, scattering);
|
|
563
|
+
|
|
564
|
+
throughput = throughput * exp(-absorption * travel);
|
|
303
565
|
|
|
304
566
|
let bary = vec2<f32>(hit.u, hit.v);
|
|
305
|
-
let point = origin + direction *
|
|
306
|
-
|
|
567
|
+
let point = origin + direction * travel;
|
|
568
|
+
var m = 0u;
|
|
569
|
+
|
|
570
|
+
if (!scattering) {
|
|
571
|
+
m = materialIndexOf(hit.triangle);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
var surface: Surface;
|
|
575
|
+
|
|
576
|
+
if (SUBSURFACE_WALK && exiting) {
|
|
577
|
+
surface = subsurfaceExit(m, hit.triangle, bary, direction);
|
|
578
|
+
// Seen from outside, along its normal: the side the light leaves through.
|
|
579
|
+
direction = -surface.normal;
|
|
580
|
+
} else if (VOLUMES && scattering) {
|
|
581
|
+
surface = volumePoint(direction, volumeAlbedo, volumeG);
|
|
582
|
+
} else {
|
|
583
|
+
|
|
584
|
+
// A volume's boundary is not a surface: the ray goes in or out, and the medium is what it meets.
|
|
585
|
+
if (VOLUMES && (materials[m].flags & MATERIAL_VOLUME) != 0u) {
|
|
586
|
+
if (dot(authoredNormal(hit.triangle, bary), direction) < 0.0) {
|
|
587
|
+
if (frame.volumesEnabled != 0u) {
|
|
588
|
+
inVolume = true;
|
|
589
|
+
volumeDensity = materials[m].volume.w;
|
|
590
|
+
volumeAlbedo = materials[m].volume.rgb;
|
|
591
|
+
volumeG = materials[m].volumeAnisotropy;
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
594
|
+
inVolume = false;
|
|
595
|
+
}
|
|
307
596
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
let lobe = sample2D(rng);
|
|
313
|
-
let bounceSample = sample2D(rng);
|
|
597
|
+
origin = point + direction * RAY_EPSILON;
|
|
598
|
+
limit = select(1e30, limit - hit.distance, straight);
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
314
601
|
|
|
315
602
|
// Three culls the side a material does not draw. Only the camera's rays are culled: a mesh still casts shadows and
|
|
316
603
|
// holds light in from both sides.
|
|
@@ -344,7 +631,20 @@ fn traceRadiance(
|
|
|
344
631
|
}
|
|
345
632
|
}
|
|
346
633
|
|
|
347
|
-
|
|
634
|
+
surface = resolveSurfaceFrom(hit.triangle, bary, direction, base);
|
|
635
|
+
|
|
636
|
+
// Deep in the path, light reflects off a material that scatters inside as off a diffuse one of the same colour.
|
|
637
|
+
if (SUBSURFACE_WALK && bounce >= frame.subsurfaceDepth) {
|
|
638
|
+
surface.subsurface = 0.0;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (BEVEL && frame.bevelEnabled != 0u && materials[m].bevel > 0.0) {
|
|
642
|
+
let disk = hashSample2D(rng);
|
|
643
|
+
let axis = hashSample2D(rng);
|
|
644
|
+
|
|
645
|
+
surface.normal = bevelNormal(m, point, surface, -direction, materials[m].bevel, disk, axis);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
348
648
|
|
|
349
649
|
// Blending other than normal: the surface adds its light to, or filters, whatever is behind it, and the path goes on.
|
|
350
650
|
if (surface.blending == BLEND_ADDITIVE || surface.blending == BLEND_MULTIPLY || surface.blending == BLEND_SUBTRACTIVE) {
|
|
@@ -358,7 +658,7 @@ fn traceRadiance(
|
|
|
358
658
|
if ((surface.flags & MATERIAL_LIT) != 0u) {
|
|
359
659
|
var unused: vec3<f32>;
|
|
360
660
|
added = added + directLight(surface, point + surface.normal * RAY_EPSILON, -direction, pick.x, lightSample, rng,
|
|
361
|
-
&unused);
|
|
661
|
+
&unused, select(0.0, volumeDensity, inVolume));
|
|
362
662
|
}
|
|
363
663
|
|
|
364
664
|
radiance = radiance + throughput * added * strength;
|
|
@@ -410,6 +710,7 @@ fn traceRadiance(
|
|
|
410
710
|
guideNormal = surface.normal;
|
|
411
711
|
guideDistance = length(point - rayOrigin);
|
|
412
712
|
guideRoughness = surface.roughness;
|
|
713
|
+
guideSubsurface = select(0.0, surface.subsurface, SUBSURFACE_WALK);
|
|
413
714
|
|
|
414
715
|
if (surface.kind == KIND_PHONG) {
|
|
415
716
|
// Phong's exponent as the roughness of the GGX lobe that is about as wide.
|
|
@@ -420,7 +721,7 @@ fn traceRadiance(
|
|
|
420
721
|
if (primary) {
|
|
421
722
|
alpha = 1.0;
|
|
422
723
|
|
|
423
|
-
if (!fogged && afterMaterial == NO_MATERIAL && materials[m].afterProgram != NO_PROGRAM) {
|
|
724
|
+
if (!scattering && !fogged && afterMaterial == NO_MATERIAL && materials[m].afterProgram != NO_PROGRAM) {
|
|
424
725
|
afterMaterial = m;
|
|
425
726
|
afterTriangle = hit.triangle;
|
|
426
727
|
afterBary = bary;
|
|
@@ -444,11 +745,20 @@ fn traceRadiance(
|
|
|
444
745
|
}
|
|
445
746
|
}
|
|
446
747
|
|
|
748
|
+
if (!hazed && frame.atmosphereColor.w > 0.0) {
|
|
749
|
+
hazed = true;
|
|
750
|
+
// From the eye, where the camera ray only starts on the near plane; an orthographic view has no eye.
|
|
751
|
+
hazeDistance = length(point - select(frame.cameraToWorld[3].xyz, rayOrigin, frame.cameraKind == 1u));
|
|
752
|
+
hazeStart = radiance;
|
|
753
|
+
hazeThroughput = throughput;
|
|
754
|
+
hazeDirection = direction;
|
|
755
|
+
}
|
|
756
|
+
|
|
447
757
|
radiance = radiance + throughput * surface.lightMap;
|
|
448
758
|
}
|
|
449
759
|
|
|
450
760
|
if (any(surface.emissive > vec3<f32>(0.0))) {
|
|
451
|
-
let contribution = throughput * surface.emissive * emissionWeight(hit, direction, bsdfPdf, lightsOwnDirect);
|
|
761
|
+
let contribution = throughput * surface.emissive * emissionWeight(hit, direction, bsdfPdf, lightsOwnDirect, lastPosition, lastNormal);
|
|
452
762
|
|
|
453
763
|
// Seen by the camera or one bounce away this is direct light and stays exact; deeper it may be clamped.
|
|
454
764
|
radiance = radiance + select(contribution, clampIndirect(contribution), bounce > 1u);
|
|
@@ -535,15 +845,56 @@ fn traceRadiance(
|
|
|
535
845
|
bounce = bounce + 1u;
|
|
536
846
|
}
|
|
537
847
|
} else {
|
|
848
|
+
// Light that enters the material: a random walk inside it from here, in place of the share of the diffuse
|
|
849
|
+
// layer that scatters inside. Chosen by chance, so neither branch is weighted against the other.
|
|
850
|
+
var chance = 0.0;
|
|
851
|
+
|
|
852
|
+
if (SUBSURFACE_WALK) {
|
|
853
|
+
chance = subsurfaceChance(surface, view);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (SUBSURFACE_WALK && lobe.x < chance) {
|
|
857
|
+
let medium = materials[surface.material].subsurface;
|
|
858
|
+
|
|
859
|
+
throughput = throughput
|
|
860
|
+
* ((1.0 - surface.metalness) * surface.subsurface * coatTransmittance(surface, view) / chance);
|
|
861
|
+
sigma = vec3<f32>(1.0) / max(medium.rgb, vec3<f32>(1e-6));
|
|
862
|
+
walkAlbedo = subsurfaceAlbedo(surface.albedo);
|
|
863
|
+
walkG = materials[surface.material].subsurfaceAnisotropy;
|
|
864
|
+
walkSteps = 0u;
|
|
865
|
+
|
|
866
|
+
// Light that scattered inside is diffuse light, which is the signal it feeds.
|
|
867
|
+
if (!split) {
|
|
868
|
+
split = true;
|
|
869
|
+
splitBase = radiance;
|
|
870
|
+
splitDiffuse = true;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
inMedium = true;
|
|
874
|
+
primary = false;
|
|
875
|
+
straight = false;
|
|
876
|
+
bsdfPdf = 0.0;
|
|
877
|
+
lightsOwnDirect = false;
|
|
878
|
+
escapeMap = NO_TEXTURE;
|
|
879
|
+
bounce = bounce + 1u;
|
|
880
|
+
direction = cosineDirection(-surface.facing, bounceSample);
|
|
881
|
+
origin = point - surface.facing * RAY_EPSILON;
|
|
882
|
+
limit = walkDistance(throughput, sigma, pick, &walkOdds);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
throughput = throughput / (1.0 - chance);
|
|
887
|
+
|
|
538
888
|
let position = point + surface.normal * RAY_EPSILON;
|
|
539
889
|
var directDiffuse: vec3<f32>;
|
|
540
|
-
let direct = throughput * directLight(surface, position, view, pick.x, lightSample, rng, &directDiffuse
|
|
890
|
+
let direct = throughput * directLight(surface, position, view, pick.x, lightSample, rng, &directDiffuse,
|
|
891
|
+
select(0.0, volumeDensity, inVolume));
|
|
541
892
|
let added = select(direct, clampIndirect(direct), bounce > 0u);
|
|
542
893
|
let before = radiance;
|
|
543
894
|
|
|
544
895
|
radiance = radiance + added;
|
|
545
896
|
|
|
546
|
-
let scattered = sampleBsdf(surface, view, lobe.x, bounceSample);
|
|
897
|
+
let scattered = sampleBsdf(surface, view, (lobe.x - chance) / (1.0 - chance), bounceSample);
|
|
547
898
|
|
|
548
899
|
// The first scatter splits the path: its direct light by lobe, and everything after it by the lobe it chose.
|
|
549
900
|
if (!split) {
|
|
@@ -562,6 +913,8 @@ fn traceRadiance(
|
|
|
562
913
|
|
|
563
914
|
throughput = throughput * scattered.weight;
|
|
564
915
|
bsdfPdf = scattered.pdf;
|
|
916
|
+
lastPosition = position;
|
|
917
|
+
lastNormal = treeNormal(surface);
|
|
565
918
|
lightsOwnDirect = surface.kind == KIND_TOON;
|
|
566
919
|
escapeMap = surface.envMap;
|
|
567
920
|
escapeIntensity = surface.envMapIntensity;
|
|
@@ -579,7 +932,7 @@ fn traceRadiance(
|
|
|
579
932
|
break;
|
|
580
933
|
}
|
|
581
934
|
|
|
582
|
-
if (survival <
|
|
935
|
+
if (survival < frame.rouletteThreshold && bounce >= frame.rouletteStart) {
|
|
583
936
|
if (pick.y > survival) {
|
|
584
937
|
break;
|
|
585
938
|
}
|
|
@@ -623,6 +976,21 @@ fn traceRadiance(
|
|
|
623
976
|
diffuse = fogStart + (diffuse - fogStart) * (1.0 - fogAmount) + fog;
|
|
624
977
|
}
|
|
625
978
|
|
|
979
|
+
// The air takes its share of what lies behind it and adds the light it scatters towards the camera; the background,
|
|
980
|
+
// which already is the sky, is left as it is.
|
|
981
|
+
if (hazed) {
|
|
982
|
+
let through = exp(-frame.atmosphereExtinction.rgb * hazeDistance);
|
|
983
|
+
// The sun lights the air by the phase function of what fills it: Rayleigh's for clean air, a forward lobe for haze
|
|
984
|
+
// (Henyey-Greenstein at 0.76), which is what makes the air glow towards the sun.
|
|
985
|
+
let turn = dot(hazeDirection, frame.atmosphereSun.xyz);
|
|
986
|
+
let phase = mix(3.0 / (16.0 * PI) * (1.0 + turn * turn), henyeyGreenstein(turn, 0.76), frame.atmosphereExtinction.w);
|
|
987
|
+
let air = frame.atmosphereColor.rgb + frame.atmosphereSunColor.rgb * phase;
|
|
988
|
+
let glow = air * hazeThroughput * (vec3<f32>(1.0) - through);
|
|
989
|
+
|
|
990
|
+
radiance = hazeStart + (radiance - hazeStart) * through + glow;
|
|
991
|
+
diffuse = hazeStart + (diffuse - hazeStart) * through + glow;
|
|
992
|
+
}
|
|
993
|
+
|
|
626
994
|
var result: PathResult;
|
|
627
995
|
result.radiance = radiance;
|
|
628
996
|
result.alpha = alpha;
|
|
@@ -634,6 +1002,7 @@ fn traceRadiance(
|
|
|
634
1002
|
result.guideDistance = guideDistance;
|
|
635
1003
|
result.roughness = guideRoughness;
|
|
636
1004
|
result.specularDistance = specularDistance;
|
|
1005
|
+
result.subsurface = guideSubsurface;
|
|
637
1006
|
|
|
638
1007
|
return result;
|
|
639
1008
|
}
|
|
@@ -659,7 +1028,17 @@ fn lensPoint(u: vec2<f32>) -> vec2<f32> {
|
|
|
659
1028
|
*/
|
|
660
1029
|
fn cameraRay(pixel: vec2<u32>, jitter: vec2<f32>, lens: vec2<f32>) -> array<vec4<f32>, 2> {
|
|
661
1030
|
let uv = (vec2<f32>(pixel) + jitter) / vec2<f32>(frame.resolution);
|
|
662
|
-
|
|
1031
|
+
var ndc = vec2<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
|
|
1032
|
+
|
|
1033
|
+
// A lens's radial distortion: the pixel sees the scene at a radius scaled by the square of its own, which is one at
|
|
1034
|
+
// the corners. Positive reaches further out at the edges (barrel), negative less far (pincushion).
|
|
1035
|
+
if (frame.lensDistortion != 0.0) {
|
|
1036
|
+
let aspect = f32(frame.resolution.x) / f32(frame.resolution.y);
|
|
1037
|
+
let radius2 = (ndc.x * ndc.x * aspect * aspect + ndc.y * ndc.y) / (aspect * aspect + 1.0);
|
|
1038
|
+
|
|
1039
|
+
ndc = ndc * (1.0 + frame.lensDistortion * radius2);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
663
1042
|
let ray = ndcRay(ndc);
|
|
664
1043
|
|
|
665
1044
|
if (frame.lens.x <= 0.0 || frame.cameraKind == 1u) {
|
|
@@ -979,6 +1358,7 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
979
1358
|
var distanceSum = 0.0;
|
|
980
1359
|
var roughnessSum = 0.0;
|
|
981
1360
|
var specularDistanceSum = 0.0;
|
|
1361
|
+
var subsurfaceSum = 0.0;
|
|
982
1362
|
|
|
983
1363
|
for (var s = 0u; s < frame.sampleCount; s = s + 1u) {
|
|
984
1364
|
var rng = makeSampler(pixel, frame.sampleIndex + s);
|
|
@@ -1012,6 +1392,7 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
1012
1392
|
distanceSum = distanceSum + result.guideDistance;
|
|
1013
1393
|
roughnessSum = roughnessSum + result.roughness;
|
|
1014
1394
|
specularDistanceSum = specularDistanceSum + result.specularDistance;
|
|
1395
|
+
subsurfaceSum = subsurfaceSum + result.subsurface;
|
|
1015
1396
|
|
|
1016
1397
|
alpha = alpha + sampleAlpha;
|
|
1017
1398
|
depthSum = depthSum + windowDepth(ray[0].xyz + ray[1].xyz * min(result.depth, 1e20), result.depth);
|
|
@@ -1026,7 +1407,7 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
1026
1407
|
accumulation[2u * pc + index] = accumulation[2u * pc + index] + vec4<f32>(diffuseAlbedoSum, roughnessSum);
|
|
1027
1408
|
accumulation[3u * pc + index] = accumulation[3u * pc + index] + vec4<f32>(normalSum, distanceSum);
|
|
1028
1409
|
accumulation[4u * pc + index] = accumulation[4u * pc + index] + vec4<f32>(diffuseSum, specularDistanceSum);
|
|
1029
|
-
accumulation[5u * pc + index] = accumulation[5u * pc + index] + vec4<f32>(specularAlbedoSum,
|
|
1410
|
+
accumulation[5u * pc + index] = accumulation[5u * pc + index] + vec4<f32>(specularAlbedoSum, subsurfaceSum);
|
|
1030
1411
|
// This row still has work: every pixel that traced writes the same value, so the race is harmless.
|
|
1031
1412
|
accumulation[6u * pc + pixel.y] = vec4<f32>(1.0);
|
|
1032
1413
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathTrace.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/pathTrace.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC
|
|
1
|
+
{"version":3,"file":"pathTrace.wgsl.js","sourceRoot":"","sources":["../../src/wgsl/pathTrace.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAy3CzC,CAAC"}
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* random numbers. Only the sample index, the pixel and a dimension counter feed the generator, so a pixel draws the same
|
|
6
6
|
* numbers whichever dispatch happens to cover it and the image does not depend on the tiling.
|
|
7
7
|
*/
|
|
8
|
-
export declare const RANDOM_WGSL = "\nfn pcgHash(value: u32) -> u32 {\n let state = value * 747796405u + 2891336453u;\n let word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\n\n return (word >> 22u) ^ word;\n}\n\nstruct PathSampler {\n seed: u32,\n index: u32,\n dimension: u32,\n};\n\nfn makeSampler(pixel: vec2<u32>, index: u32) -> PathSampler {\n var rng: PathSampler;\n rng.seed = pcgHash(pixel.x * 1973u + pixel.y * 9277u + 1u);\n rng.index = index;\n rng.dimension = 0u;\n\n return rng;\n}\n\n/**\n * The Laine-Karras hash, which permutes the high bits in a way that nests: what Owen scrambling asks for. Every\n * multiplier has to be even, or a bit stops depending on itself and the map is no longer a permutation.\n */\nfn laineKarras(value: u32, seed: u32) -> u32 {\n var x = value + seed;\n x = x ^ (x * 0x6c50b47cu);\n x = x ^ (x * 0xb82f1e52u);\n x = x ^ (x * 0xc7afe784u);\n x = x ^ (x * 0x8d22f6e6u);\n\n return x;\n}\n\nfn nestedScramble(value: u32, seed: u32) -> u32 {\n return reverseBits(laineKarras(reverseBits(value), seed));\n}\n\n/**\n * The first two Sobol dimensions, as fixed point fractions. The second is the index times Pascal's triangle mod 2,\n * which is linear in the bits of the index, so it is read one byte at a time from the tables texturePack.ts writes\n * instead of folded in one bit at a time: four reads where the scrambled index made it thirty two steps.\n */\nfn sobol2(index: u32) -> vec2<u32> {\n let second = textures[SOBOL_TABLE + (index & 0xffu)]\n ^ textures[SOBOL_TABLE + 256u + ((index >> 8u) & 0xffu)]\n ^ textures[SOBOL_TABLE + 512u + ((index >> 16u) & 0xffu)]\n ^ textures[SOBOL_TABLE + 768u + (index >> 24u)];\n\n return vec2<u32>(reverseBits(index), second);\n}\n\nfn toUnit(value: u32) -> f32 {\n return min(f32(value) * 2.3283064365386963e-10, 0.99999994);\n}\n\n/**\n * The next pair of dimensions, in [0, 1). The sample index is shuffled per pair as well, which pads the two dimensional\n * sequence out to as many dimensions as a path needs without correlating them.\n */\nfn sample2D(rng: ptr<function, PathSampler>) -> vec2<f32> {\n let pair = (*rng).seed + (*rng).dimension * 2654435761u;\n (*rng).dimension = (*rng).dimension + 1u;\n\n let shuffled = nestedScramble((*rng).index, pcgHash(pair));\n let point = sobol2(shuffled);\n\n return vec2<f32>(\n toUnit(nestedScramble(point.x, pcgHash(pair + 0x9e3779b9u))),\n toUnit(nestedScramble(point.y, pcgHash(pair + 0x3c6ef372u))));\n}\n";
|
|
8
|
+
export declare const RANDOM_WGSL = "\nfn pcgHash(value: u32) -> u32 {\n let state = value * 747796405u + 2891336453u;\n let word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\n\n return (word >> 22u) ^ word;\n}\n\nstruct PathSampler {\n seed: u32,\n index: u32,\n dimension: u32,\n};\n\nfn makeSampler(pixel: vec2<u32>, index: u32) -> PathSampler {\n var rng: PathSampler;\n rng.seed = pcgHash(pixel.x * 1973u + pixel.y * 9277u + 1u);\n rng.index = index;\n rng.dimension = 0u;\n\n return rng;\n}\n\n/**\n * The Laine-Karras hash, which permutes the high bits in a way that nests: what Owen scrambling asks for. Every\n * multiplier has to be even, or a bit stops depending on itself and the map is no longer a permutation.\n */\nfn laineKarras(value: u32, seed: u32) -> u32 {\n var x = value + seed;\n x = x ^ (x * 0x6c50b47cu);\n x = x ^ (x * 0xb82f1e52u);\n x = x ^ (x * 0xc7afe784u);\n x = x ^ (x * 0x8d22f6e6u);\n\n return x;\n}\n\nfn nestedScramble(value: u32, seed: u32) -> u32 {\n return reverseBits(laineKarras(reverseBits(value), seed));\n}\n\n/**\n * The first two Sobol dimensions, as fixed point fractions. The second is the index times Pascal's triangle mod 2,\n * which is linear in the bits of the index, so it is read one byte at a time from the tables texturePack.ts writes\n * instead of folded in one bit at a time: four reads where the scrambled index made it thirty two steps.\n */\nfn sobol2(index: u32) -> vec2<u32> {\n let second = textures[SOBOL_TABLE + (index & 0xffu)]\n ^ textures[SOBOL_TABLE + 256u + ((index >> 8u) & 0xffu)]\n ^ textures[SOBOL_TABLE + 512u + ((index >> 16u) & 0xffu)]\n ^ textures[SOBOL_TABLE + 768u + (index >> 24u)];\n\n return vec2<u32>(reverseBits(index), second);\n}\n\nfn toUnit(value: u32) -> f32 {\n return min(f32(value) * 2.3283064365386963e-10, 0.99999994);\n}\n\n/**\n * The next pair of dimensions from a plain hash instead of the scrambled sequence: for the deep dimensions of a random\n * walk, where stratification buys nothing and the sequence's table reads and scrambles cost a step more than its ray.\n * It takes a dimension of its own, so the sequence's draws after it stay where they would have been.\n */\nfn hashSample2D(rng: ptr<function, PathSampler>) -> vec2<f32> {\n let first = pcgHash((*rng).seed ^ pcgHash((*rng).index * 2654435761u + (*rng).dimension * 0x9e3779b9u));\n (*rng).dimension = (*rng).dimension + 1u;\n\n return vec2<f32>(toUnit(first), toUnit(pcgHash(first)));\n}\n\n/**\n * How many vertices of a path draw from the scrambled sequence. Its stratification shows on the camera ray and the\n * first bounces, where the light a pixel sees is decided; past them the light has been averaged by every bounce\n * before, and a plain hash gives numbers as good for a fraction of the cost.\n */\nconst SEQUENCE_DEPTH: u32 = 3u;\n\n/** The next pair of dimensions of a path's vertex: from the sequence while the path is shallow, from a hash after. */\nfn pathSample2D(rng: ptr<function, PathSampler>, deep: bool) -> vec2<f32> {\n if (deep) {\n return hashSample2D(rng);\n }\n\n return sample2D(rng);\n}\n\n/**\n * The next pair of dimensions, in [0, 1). The sample index is shuffled per pair as well, which pads the two dimensional\n * sequence out to as many dimensions as a path needs without correlating them.\n */\nfn sample2D(rng: ptr<function, PathSampler>) -> vec2<f32> {\n let pair = (*rng).seed + (*rng).dimension * 2654435761u;\n (*rng).dimension = (*rng).dimension + 1u;\n\n let shuffled = nestedScramble((*rng).index, pcgHash(pair));\n let point = sobol2(shuffled);\n\n return vec2<f32>(\n toUnit(nestedScramble(point.x, pcgHash(pair + 0x9e3779b9u))),\n toUnit(nestedScramble(point.y, pcgHash(pair + 0x3c6ef372u))));\n}\n";
|
|
9
9
|
//# sourceMappingURL=random.wgsl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"random.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/random.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"random.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/random.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,mqHAsGvB,CAAC"}
|