@tinypoly/gpu-renderer 0.0.5 → 0.1.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 +59 -3
- package/README.md +52 -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 +15 -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 +43 -0
- package/dist/lightTree.d.ts.map +1 -0
- package/dist/lightTree.js +107 -0
- package/dist/lightTree.js.map +1 -0
- package/dist/lights.d.ts +10 -0
- package/dist/lights.d.ts.map +1 -1
- package/dist/lights.js +54 -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 +19 -7
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +118 -14
- package/dist/renderer.js.map +1 -1
- package/dist/sceneUpload.d.ts +5 -0
- package/dist/sceneUpload.d.ts.map +1 -1
- package/dist/sceneUpload.js +4 -0
- package/dist/sceneUpload.js.map +1 -1
- package/dist/settings.d.ts +136 -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 +315 -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 +203 -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 +396 -25
- package/dist/wgsl/pathTrace.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 +15 -0
- package/src/geometry.ts +6 -2
- package/src/index.ts +13 -0
- package/src/lensPass.ts +299 -0
- package/src/lightTree.ts +172 -0
- package/src/lights.ts +71 -1
- package/src/materialLayout.ts +52 -1
- package/src/pathTracePass.ts +19 -10
- package/src/renderer.ts +166 -22
- package/src/sceneUpload.ts +9 -0
- package/src/settings.ts +234 -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 +314 -0
- package/src/wgsl/lights.wgsl.ts +203 -31
- package/src/wgsl/pathTrace.wgsl.ts +396 -25
- 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,109 @@ 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
|
+
// Every vertex draws the same dimensions in the same order, whatever the path then does with them.
|
|
460
|
+
let coverage = sample2D(rng);
|
|
461
|
+
let pick = sample2D(rng);
|
|
462
|
+
let lightSample = sample2D(rng);
|
|
463
|
+
let lobe = sample2D(rng);
|
|
464
|
+
let bounceSample = sample2D(rng);
|
|
465
|
+
var mediumSample = vec2<f32>(0.0);
|
|
466
|
+
var bevelSample = vec4<f32>(0.0);
|
|
467
|
+
|
|
468
|
+
if (VOLUMES) {
|
|
469
|
+
mediumSample = sample2D(rng);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (BEVEL) {
|
|
473
|
+
bevelSample = vec4<f32>(sample2D(rng), sample2D(rng));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// The walk left the material here, and the surface below is its way out rather than what the material shows.
|
|
477
|
+
var exiting = false;
|
|
478
|
+
|
|
479
|
+
if (SUBSURFACE_WALK && inMedium) {
|
|
480
|
+
if (hit.triangle == NO_HIT) {
|
|
481
|
+
// Nothing within the drawn distance: the light scatters there, keeping each colour's share of the medium.
|
|
482
|
+
let travelled = exp(-sigma * limit);
|
|
483
|
+
let density = dot(walkOdds, sigma * travelled);
|
|
484
|
+
|
|
485
|
+
throughput = throughput * sigma * walkAlbedo * travelled / max(density, 1e-20);
|
|
486
|
+
walkSteps = walkSteps + 1u;
|
|
487
|
+
|
|
488
|
+
let survival = max3(throughput);
|
|
489
|
+
|
|
490
|
+
if (walkSteps >= MAX_WALK || survival <= 0.0) {
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (survival < 0.2) {
|
|
495
|
+
if (coverage.x > survival) {
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
throughput = throughput / survival;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
origin = origin + direction * limit;
|
|
503
|
+
direction = sampleHenyeyGreenstein(direction, walkG, lobe);
|
|
504
|
+
limit = walkDistance(throughput, sigma, pick, &walkOdds);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// The walk reached the surface before scattering again: the chance of travelling that far, per colour.
|
|
509
|
+
let travelled = exp(-sigma * hit.distance);
|
|
510
|
+
|
|
511
|
+
throughput = throughput * travelled / max(dot(walkOdds, travelled), 1e-20);
|
|
512
|
+
inMedium = false;
|
|
513
|
+
exiting = true;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Inside a volume, the medium may stop the ray before whatever it hit. The distance is drawn now that the segment's
|
|
517
|
+
// length is known, which a homogeneous medium allows; one grey density makes the weight of either outcome one.
|
|
518
|
+
var scatterAt = -1.0;
|
|
519
|
+
|
|
520
|
+
if (VOLUMES && inVolume && !exiting) {
|
|
521
|
+
let free = -log(max(1.0 - mediumSample.x, 1e-12)) / max(volumeDensity, 1e-12);
|
|
522
|
+
|
|
523
|
+
if (free < select(hit.distance, limit, hit.triangle == NO_HIT)) {
|
|
524
|
+
scatterAt = free;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
let scattering = scatterAt >= 0.0;
|
|
529
|
+
|
|
272
530
|
// The specular continuation's first hit: how far the reflection the denoiser filters actually is.
|
|
273
531
|
if (waitingForSpecular) {
|
|
274
532
|
specularDistance = select(min(hit.distance, 1e6), 1e6, hit.triangle == NO_HIT);
|
|
275
533
|
waitingForSpecular = false;
|
|
276
534
|
}
|
|
277
535
|
|
|
278
|
-
if (hit.triangle == NO_HIT) {
|
|
536
|
+
if (hit.triangle == NO_HIT && !scattering) {
|
|
279
537
|
if (primary) {
|
|
280
538
|
// A transparent background leaves the pixel empty, which its alpha says.
|
|
281
539
|
if (frame.background.w != 2.0) {
|
|
@@ -299,18 +557,45 @@ fn traceRadiance(
|
|
|
299
557
|
break;
|
|
300
558
|
}
|
|
301
559
|
|
|
302
|
-
|
|
560
|
+
let travel = select(hit.distance, scatterAt, scattering);
|
|
561
|
+
|
|
562
|
+
throughput = throughput * exp(-absorption * travel);
|
|
303
563
|
|
|
304
564
|
let bary = vec2<f32>(hit.u, hit.v);
|
|
305
|
-
let point = origin + direction *
|
|
306
|
-
|
|
565
|
+
let point = origin + direction * travel;
|
|
566
|
+
var m = 0u;
|
|
307
567
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
568
|
+
if (!scattering) {
|
|
569
|
+
m = materialIndexOf(hit.triangle);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
var surface: Surface;
|
|
573
|
+
|
|
574
|
+
if (SUBSURFACE_WALK && exiting) {
|
|
575
|
+
surface = subsurfaceExit(m, hit.triangle, bary, direction);
|
|
576
|
+
// Seen from outside, along its normal: the side the light leaves through.
|
|
577
|
+
direction = -surface.normal;
|
|
578
|
+
} else if (VOLUMES && scattering) {
|
|
579
|
+
surface = volumePoint(direction, volumeAlbedo, volumeG);
|
|
580
|
+
} else {
|
|
581
|
+
|
|
582
|
+
// A volume's boundary is not a surface: the ray goes in or out, and the medium is what it meets.
|
|
583
|
+
if (VOLUMES && (materials[m].flags & MATERIAL_VOLUME) != 0u) {
|
|
584
|
+
if (dot(authoredNormal(hit.triangle, bary), direction) < 0.0) {
|
|
585
|
+
if (frame.volumesEnabled != 0u) {
|
|
586
|
+
inVolume = true;
|
|
587
|
+
volumeDensity = materials[m].volume.w;
|
|
588
|
+
volumeAlbedo = materials[m].volume.rgb;
|
|
589
|
+
volumeG = materials[m].volumeAnisotropy;
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
inVolume = false;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
origin = point + direction * RAY_EPSILON;
|
|
596
|
+
limit = select(1e30, limit - hit.distance, straight);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
314
599
|
|
|
315
600
|
// Three culls the side a material does not draw. Only the camera's rays are culled: a mesh still casts shadows and
|
|
316
601
|
// holds light in from both sides.
|
|
@@ -344,7 +629,12 @@ fn traceRadiance(
|
|
|
344
629
|
}
|
|
345
630
|
}
|
|
346
631
|
|
|
347
|
-
|
|
632
|
+
surface = resolveSurfaceFrom(hit.triangle, bary, direction, base);
|
|
633
|
+
|
|
634
|
+
if (BEVEL && frame.bevelEnabled != 0u && materials[m].bevel > 0.0) {
|
|
635
|
+
surface.normal = bevelNormal(m, point, surface, -direction, materials[m].bevel, bevelSample.xy, bevelSample.zw);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
348
638
|
|
|
349
639
|
// Blending other than normal: the surface adds its light to, or filters, whatever is behind it, and the path goes on.
|
|
350
640
|
if (surface.blending == BLEND_ADDITIVE || surface.blending == BLEND_MULTIPLY || surface.blending == BLEND_SUBTRACTIVE) {
|
|
@@ -358,7 +648,7 @@ fn traceRadiance(
|
|
|
358
648
|
if ((surface.flags & MATERIAL_LIT) != 0u) {
|
|
359
649
|
var unused: vec3<f32>;
|
|
360
650
|
added = added + directLight(surface, point + surface.normal * RAY_EPSILON, -direction, pick.x, lightSample, rng,
|
|
361
|
-
&unused);
|
|
651
|
+
&unused, select(0.0, volumeDensity, inVolume));
|
|
362
652
|
}
|
|
363
653
|
|
|
364
654
|
radiance = radiance + throughput * added * strength;
|
|
@@ -410,6 +700,7 @@ fn traceRadiance(
|
|
|
410
700
|
guideNormal = surface.normal;
|
|
411
701
|
guideDistance = length(point - rayOrigin);
|
|
412
702
|
guideRoughness = surface.roughness;
|
|
703
|
+
guideSubsurface = select(0.0, surface.subsurface, SUBSURFACE_WALK);
|
|
413
704
|
|
|
414
705
|
if (surface.kind == KIND_PHONG) {
|
|
415
706
|
// Phong's exponent as the roughness of the GGX lobe that is about as wide.
|
|
@@ -420,7 +711,7 @@ fn traceRadiance(
|
|
|
420
711
|
if (primary) {
|
|
421
712
|
alpha = 1.0;
|
|
422
713
|
|
|
423
|
-
if (!fogged && afterMaterial == NO_MATERIAL && materials[m].afterProgram != NO_PROGRAM) {
|
|
714
|
+
if (!scattering && !fogged && afterMaterial == NO_MATERIAL && materials[m].afterProgram != NO_PROGRAM) {
|
|
424
715
|
afterMaterial = m;
|
|
425
716
|
afterTriangle = hit.triangle;
|
|
426
717
|
afterBary = bary;
|
|
@@ -444,11 +735,20 @@ fn traceRadiance(
|
|
|
444
735
|
}
|
|
445
736
|
}
|
|
446
737
|
|
|
738
|
+
if (!hazed && frame.atmosphereColor.w > 0.0) {
|
|
739
|
+
hazed = true;
|
|
740
|
+
// From the eye, where the camera ray only starts on the near plane; an orthographic view has no eye.
|
|
741
|
+
hazeDistance = length(point - select(frame.cameraToWorld[3].xyz, rayOrigin, frame.cameraKind == 1u));
|
|
742
|
+
hazeStart = radiance;
|
|
743
|
+
hazeThroughput = throughput;
|
|
744
|
+
hazeDirection = direction;
|
|
745
|
+
}
|
|
746
|
+
|
|
447
747
|
radiance = radiance + throughput * surface.lightMap;
|
|
448
748
|
}
|
|
449
749
|
|
|
450
750
|
if (any(surface.emissive > vec3<f32>(0.0))) {
|
|
451
|
-
let contribution = throughput * surface.emissive * emissionWeight(hit, direction, bsdfPdf, lightsOwnDirect);
|
|
751
|
+
let contribution = throughput * surface.emissive * emissionWeight(hit, direction, bsdfPdf, lightsOwnDirect, lastPosition, lastNormal);
|
|
452
752
|
|
|
453
753
|
// Seen by the camera or one bounce away this is direct light and stays exact; deeper it may be clamped.
|
|
454
754
|
radiance = radiance + select(contribution, clampIndirect(contribution), bounce > 1u);
|
|
@@ -535,15 +835,56 @@ fn traceRadiance(
|
|
|
535
835
|
bounce = bounce + 1u;
|
|
536
836
|
}
|
|
537
837
|
} else {
|
|
838
|
+
// Light that enters the material: a random walk inside it from here, in place of the share of the diffuse
|
|
839
|
+
// layer that scatters inside. Chosen by chance, so neither branch is weighted against the other.
|
|
840
|
+
var chance = 0.0;
|
|
841
|
+
|
|
842
|
+
if (SUBSURFACE_WALK) {
|
|
843
|
+
chance = subsurfaceChance(surface, view);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (SUBSURFACE_WALK && lobe.x < chance) {
|
|
847
|
+
let medium = materials[surface.material].subsurface;
|
|
848
|
+
|
|
849
|
+
throughput = throughput
|
|
850
|
+
* ((1.0 - surface.metalness) * surface.subsurface * coatTransmittance(surface, view) / chance);
|
|
851
|
+
sigma = vec3<f32>(1.0) / max(medium.rgb, vec3<f32>(1e-6));
|
|
852
|
+
walkAlbedo = subsurfaceAlbedo(surface.albedo);
|
|
853
|
+
walkG = materials[surface.material].subsurfaceAnisotropy;
|
|
854
|
+
walkSteps = 0u;
|
|
855
|
+
|
|
856
|
+
// Light that scattered inside is diffuse light, which is the signal it feeds.
|
|
857
|
+
if (!split) {
|
|
858
|
+
split = true;
|
|
859
|
+
splitBase = radiance;
|
|
860
|
+
splitDiffuse = true;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
inMedium = true;
|
|
864
|
+
primary = false;
|
|
865
|
+
straight = false;
|
|
866
|
+
bsdfPdf = 0.0;
|
|
867
|
+
lightsOwnDirect = false;
|
|
868
|
+
escapeMap = NO_TEXTURE;
|
|
869
|
+
bounce = bounce + 1u;
|
|
870
|
+
direction = cosineDirection(-surface.facing, bounceSample);
|
|
871
|
+
origin = point - surface.facing * RAY_EPSILON;
|
|
872
|
+
limit = walkDistance(throughput, sigma, pick, &walkOdds);
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
throughput = throughput / (1.0 - chance);
|
|
877
|
+
|
|
538
878
|
let position = point + surface.normal * RAY_EPSILON;
|
|
539
879
|
var directDiffuse: vec3<f32>;
|
|
540
|
-
let direct = throughput * directLight(surface, position, view, pick.x, lightSample, rng, &directDiffuse
|
|
880
|
+
let direct = throughput * directLight(surface, position, view, pick.x, lightSample, rng, &directDiffuse,
|
|
881
|
+
select(0.0, volumeDensity, inVolume));
|
|
541
882
|
let added = select(direct, clampIndirect(direct), bounce > 0u);
|
|
542
883
|
let before = radiance;
|
|
543
884
|
|
|
544
885
|
radiance = radiance + added;
|
|
545
886
|
|
|
546
|
-
let scattered = sampleBsdf(surface, view, lobe.x, bounceSample);
|
|
887
|
+
let scattered = sampleBsdf(surface, view, (lobe.x - chance) / (1.0 - chance), bounceSample);
|
|
547
888
|
|
|
548
889
|
// The first scatter splits the path: its direct light by lobe, and everything after it by the lobe it chose.
|
|
549
890
|
if (!split) {
|
|
@@ -562,6 +903,8 @@ fn traceRadiance(
|
|
|
562
903
|
|
|
563
904
|
throughput = throughput * scattered.weight;
|
|
564
905
|
bsdfPdf = scattered.pdf;
|
|
906
|
+
lastPosition = position;
|
|
907
|
+
lastNormal = treeNormal(surface);
|
|
565
908
|
lightsOwnDirect = surface.kind == KIND_TOON;
|
|
566
909
|
escapeMap = surface.envMap;
|
|
567
910
|
escapeIntensity = surface.envMapIntensity;
|
|
@@ -579,7 +922,7 @@ fn traceRadiance(
|
|
|
579
922
|
break;
|
|
580
923
|
}
|
|
581
924
|
|
|
582
|
-
if (survival <
|
|
925
|
+
if (survival < frame.rouletteThreshold && bounce >= frame.rouletteStart) {
|
|
583
926
|
if (pick.y > survival) {
|
|
584
927
|
break;
|
|
585
928
|
}
|
|
@@ -623,6 +966,21 @@ fn traceRadiance(
|
|
|
623
966
|
diffuse = fogStart + (diffuse - fogStart) * (1.0 - fogAmount) + fog;
|
|
624
967
|
}
|
|
625
968
|
|
|
969
|
+
// The air takes its share of what lies behind it and adds the light it scatters towards the camera; the background,
|
|
970
|
+
// which already is the sky, is left as it is.
|
|
971
|
+
if (hazed) {
|
|
972
|
+
let through = exp(-frame.atmosphereExtinction.rgb * hazeDistance);
|
|
973
|
+
// The sun lights the air by the phase function of what fills it: Rayleigh's for clean air, a forward lobe for haze
|
|
974
|
+
// (Henyey-Greenstein at 0.76), which is what makes the air glow towards the sun.
|
|
975
|
+
let turn = dot(hazeDirection, frame.atmosphereSun.xyz);
|
|
976
|
+
let phase = mix(3.0 / (16.0 * PI) * (1.0 + turn * turn), henyeyGreenstein(turn, 0.76), frame.atmosphereExtinction.w);
|
|
977
|
+
let air = frame.atmosphereColor.rgb + frame.atmosphereSunColor.rgb * phase;
|
|
978
|
+
let glow = air * hazeThroughput * (vec3<f32>(1.0) - through);
|
|
979
|
+
|
|
980
|
+
radiance = hazeStart + (radiance - hazeStart) * through + glow;
|
|
981
|
+
diffuse = hazeStart + (diffuse - hazeStart) * through + glow;
|
|
982
|
+
}
|
|
983
|
+
|
|
626
984
|
var result: PathResult;
|
|
627
985
|
result.radiance = radiance;
|
|
628
986
|
result.alpha = alpha;
|
|
@@ -634,6 +992,7 @@ fn traceRadiance(
|
|
|
634
992
|
result.guideDistance = guideDistance;
|
|
635
993
|
result.roughness = guideRoughness;
|
|
636
994
|
result.specularDistance = specularDistance;
|
|
995
|
+
result.subsurface = guideSubsurface;
|
|
637
996
|
|
|
638
997
|
return result;
|
|
639
998
|
}
|
|
@@ -659,7 +1018,17 @@ fn lensPoint(u: vec2<f32>) -> vec2<f32> {
|
|
|
659
1018
|
*/
|
|
660
1019
|
fn cameraRay(pixel: vec2<u32>, jitter: vec2<f32>, lens: vec2<f32>) -> array<vec4<f32>, 2> {
|
|
661
1020
|
let uv = (vec2<f32>(pixel) + jitter) / vec2<f32>(frame.resolution);
|
|
662
|
-
|
|
1021
|
+
var ndc = vec2<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
|
|
1022
|
+
|
|
1023
|
+
// A lens's radial distortion: the pixel sees the scene at a radius scaled by the square of its own, which is one at
|
|
1024
|
+
// the corners. Positive reaches further out at the edges (barrel), negative less far (pincushion).
|
|
1025
|
+
if (frame.lensDistortion != 0.0) {
|
|
1026
|
+
let aspect = f32(frame.resolution.x) / f32(frame.resolution.y);
|
|
1027
|
+
let radius2 = (ndc.x * ndc.x * aspect * aspect + ndc.y * ndc.y) / (aspect * aspect + 1.0);
|
|
1028
|
+
|
|
1029
|
+
ndc = ndc * (1.0 + frame.lensDistortion * radius2);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
663
1032
|
let ray = ndcRay(ndc);
|
|
664
1033
|
|
|
665
1034
|
if (frame.lens.x <= 0.0 || frame.cameraKind == 1u) {
|
|
@@ -979,6 +1348,7 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
979
1348
|
var distanceSum = 0.0;
|
|
980
1349
|
var roughnessSum = 0.0;
|
|
981
1350
|
var specularDistanceSum = 0.0;
|
|
1351
|
+
var subsurfaceSum = 0.0;
|
|
982
1352
|
|
|
983
1353
|
for (var s = 0u; s < frame.sampleCount; s = s + 1u) {
|
|
984
1354
|
var rng = makeSampler(pixel, frame.sampleIndex + s);
|
|
@@ -1012,6 +1382,7 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
1012
1382
|
distanceSum = distanceSum + result.guideDistance;
|
|
1013
1383
|
roughnessSum = roughnessSum + result.roughness;
|
|
1014
1384
|
specularDistanceSum = specularDistanceSum + result.specularDistance;
|
|
1385
|
+
subsurfaceSum = subsurfaceSum + result.subsurface;
|
|
1015
1386
|
|
|
1016
1387
|
alpha = alpha + sampleAlpha;
|
|
1017
1388
|
depthSum = depthSum + windowDepth(ray[0].xyz + ray[1].xyz * min(result.depth, 1e20), result.depth);
|
|
@@ -1026,7 +1397,7 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
|
1026
1397
|
accumulation[2u * pc + index] = accumulation[2u * pc + index] + vec4<f32>(diffuseAlbedoSum, roughnessSum);
|
|
1027
1398
|
accumulation[3u * pc + index] = accumulation[3u * pc + index] + vec4<f32>(normalSum, distanceSum);
|
|
1028
1399
|
accumulation[4u * pc + index] = accumulation[4u * pc + index] + vec4<f32>(diffuseSum, specularDistanceSum);
|
|
1029
|
-
accumulation[5u * pc + index] = accumulation[5u * pc + index] + vec4<f32>(specularAlbedoSum,
|
|
1400
|
+
accumulation[5u * pc + index] = accumulation[5u * pc + index] + vec4<f32>(specularAlbedoSum, subsurfaceSum);
|
|
1030
1401
|
// This row still has work: every pixel that traced writes the same value, so the race is harmless.
|
|
1031
1402
|
accumulation[6u * pc + pixel.y] = vec4<f32>(1.0);
|
|
1032
1403
|
}
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+2CzC,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** The geometric primitives the lobes and the light sampling are built from. */
|
|
2
|
-
export declare const SAMPLING_WGSL = "\n/** Branchless orthonormal basis around a unit normal (Duff and others, 2017). */\nfn orthonormalBasis(n: vec3<f32>) -> mat3x3<f32> {\n let s = select(-1.0, 1.0, n.z >= 0.0);\n let a = -1.0 / (s + n.z);\n let b = n.x * n.y * a;\n\n return mat3x3<f32>(\n vec3<f32>(1.0 + s * n.x * n.x * a, s * b, -s * n.x),\n vec3<f32>(b, s + n.y * n.y * a, -n.y),\n n);\n}\n\n/** Cosine weighted direction in the hemisphere around the normal; the weight cancels the Lambert cosine. */\nfn cosineDirection(normal: vec3<f32>, u: vec2<f32>) -> vec3<f32> {\n let radius = sqrt(u.x);\n let angle = 2.0 * PI * u.y;\n let local = vec3<f32>(radius * cos(angle), radius * sin(angle), sqrt(max(0.0, 1.0 - u.x)));\n\n return normalize(orthonormalBasis(normal) * local);\n}\n\n/** The unit normal of the triangle's plane; which side it faces does not matter, every emitter is two sided. */\nfn triangleNormal(triangle: u32) -> vec3<f32> {\n let a = triangleVertex(triangle, 0u);\n\n return normalize(cross(triangleVertex(triangle, 1u) - a, triangleVertex(triangle, 2u) - a));\n}\n";
|
|
2
|
+
export declare const SAMPLING_WGSL = "\n/** Branchless orthonormal basis around a unit normal (Duff and others, 2017). */\nfn orthonormalBasis(n: vec3<f32>) -> mat3x3<f32> {\n let s = select(-1.0, 1.0, n.z >= 0.0);\n let a = -1.0 / (s + n.z);\n let b = n.x * n.y * a;\n\n return mat3x3<f32>(\n vec3<f32>(1.0 + s * n.x * n.x * a, s * b, -s * n.x),\n vec3<f32>(b, s + n.y * n.y * a, -n.y),\n n);\n}\n\n/** Cosine weighted direction in the hemisphere around the normal; the weight cancels the Lambert cosine. */\nfn cosineDirection(normal: vec3<f32>, u: vec2<f32>) -> vec3<f32> {\n let radius = sqrt(u.x);\n let angle = 2.0 * PI * u.y;\n let local = vec3<f32>(radius * cos(angle), radius * sin(angle), sqrt(max(0.0, 1.0 - u.x)));\n\n return normalize(orthonormalBasis(normal) * local);\n}\n\n/** A direction scattered from forward by the Henyey-Greenstein phase function, which its own sampling cancels. */\nfn sampleHenyeyGreenstein(forward: vec3<f32>, g: f32, u: vec2<f32>) -> vec3<f32> {\n var cosine = 1.0 - 2.0 * u.x;\n\n if (abs(g) > 1e-3) {\n let s = (1.0 - g * g) / (1.0 - g + 2.0 * g * u.x);\n cosine = clamp((1.0 + g * g - s * s) / (2.0 * g), -1.0, 1.0);\n }\n\n let sine = sqrt(max(0.0, 1.0 - cosine * cosine));\n let angle = 2.0 * PI * u.y;\n\n return normalize(orthonormalBasis(forward) * vec3<f32>(sine * cos(angle), sine * sin(angle), cosine));\n}\n\n/**\n * The Henyey-Greenstein phase function, per steradian, for a turn whose cosine is given from the direction of travel:\n * the density sampleHenyeyGreenstein draws with.\n */\nfn henyeyGreenstein(cosine: f32, g: f32) -> f32 {\n let denominator = 1.0 + g * g - 2.0 * g * cosine;\n\n return (1.0 - g * g) / (4.0 * PI * denominator * sqrt(max(denominator, 1e-12)));\n}\n\n/** The unit normal of the triangle's plane; which side it faces does not matter, every emitter is two sided. */\nfn triangleNormal(triangle: u32) -> vec3<f32> {\n let a = triangleVertex(triangle, 0u);\n\n return normalize(cross(triangleVertex(triangle, 1u) - a, triangleVertex(triangle, 2u) - a));\n}\n";
|
|
3
3
|
//# sourceMappingURL=sampling.wgsl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sampling.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/sampling.wgsl.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"sampling.wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/sampling.wgsl.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,aAAa,w/DAqDzB,CAAC"}
|
|
@@ -21,6 +21,31 @@ fn cosineDirection(normal: vec3<f32>, u: vec2<f32>) -> vec3<f32> {
|
|
|
21
21
|
return normalize(orthonormalBasis(normal) * local);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** A direction scattered from forward by the Henyey-Greenstein phase function, which its own sampling cancels. */
|
|
25
|
+
fn sampleHenyeyGreenstein(forward: vec3<f32>, g: f32, u: vec2<f32>) -> vec3<f32> {
|
|
26
|
+
var cosine = 1.0 - 2.0 * u.x;
|
|
27
|
+
|
|
28
|
+
if (abs(g) > 1e-3) {
|
|
29
|
+
let s = (1.0 - g * g) / (1.0 - g + 2.0 * g * u.x);
|
|
30
|
+
cosine = clamp((1.0 + g * g - s * s) / (2.0 * g), -1.0, 1.0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let sine = sqrt(max(0.0, 1.0 - cosine * cosine));
|
|
34
|
+
let angle = 2.0 * PI * u.y;
|
|
35
|
+
|
|
36
|
+
return normalize(orthonormalBasis(forward) * vec3<f32>(sine * cos(angle), sine * sin(angle), cosine));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The Henyey-Greenstein phase function, per steradian, for a turn whose cosine is given from the direction of travel:
|
|
41
|
+
* the density sampleHenyeyGreenstein draws with.
|
|
42
|
+
*/
|
|
43
|
+
fn henyeyGreenstein(cosine: f32, g: f32) -> f32 {
|
|
44
|
+
let denominator = 1.0 + g * g - 2.0 * g * cosine;
|
|
45
|
+
|
|
46
|
+
return (1.0 - g * g) / (4.0 * PI * denominator * sqrt(max(denominator, 1e-12)));
|
|
47
|
+
}
|
|
48
|
+
|
|
24
49
|
/** The unit normal of the triangle's plane; which side it faces does not matter, every emitter is two sided. */
|
|
25
50
|
fn triangleNormal(triangle: u32) -> vec3<f32> {
|
|
26
51
|
let a = triangleVertex(triangle, 0u);
|