@tinypoly/gpu-renderer 0.1.0 → 0.2.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 +31 -3
- package/README.md +13 -4
- package/dist/denoise.d.ts +10 -1
- package/dist/denoise.d.ts.map +1 -1
- package/dist/denoise.js +4 -0
- package/dist/denoise.js.map +1 -1
- package/dist/device.d.ts.map +1 -1
- package/dist/device.js +8 -0
- package/dist/device.js.map +1 -1
- package/dist/frameLayout.d.ts.map +1 -1
- package/dist/frameLayout.js +6 -1
- package/dist/frameLayout.js.map +1 -1
- package/dist/imageResolve.d.ts +2 -1
- package/dist/imageResolve.d.ts.map +1 -1
- package/dist/imageResolve.js +1 -0
- package/dist/imageResolve.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lensPass.d.ts +4 -0
- package/dist/lensPass.d.ts.map +1 -1
- package/dist/lensPass.js +14 -1
- package/dist/lensPass.js.map +1 -1
- package/dist/lightTree.d.ts +10 -3
- package/dist/lightTree.d.ts.map +1 -1
- package/dist/lightTree.js +56 -17
- package/dist/lightTree.js.map +1 -1
- package/dist/lights.d.ts +20 -0
- package/dist/lights.d.ts.map +1 -1
- package/dist/lights.js +48 -1
- package/dist/lights.js.map +1 -1
- package/dist/oidnPass.d.ts +48 -0
- package/dist/oidnPass.d.ts.map +1 -0
- package/dist/oidnPass.js +255 -0
- package/dist/oidnPass.js.map +1 -0
- package/dist/pathTracePass.d.ts +2 -2
- package/dist/pathTracePass.d.ts.map +1 -1
- package/dist/pathTracePass.js +9 -9
- package/dist/pathTracePass.js.map +1 -1
- package/dist/presentPass.d.ts +2 -0
- package/dist/presentPass.d.ts.map +1 -1
- package/dist/presentPass.js +1 -0
- package/dist/presentPass.js.map +1 -1
- package/dist/renderer.d.ts +20 -0
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +138 -19
- 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 +29 -1
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +15 -6
- package/dist/settings.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 +110 -17
- package/dist/wgsl/lens.wgsl.js.map +1 -1
- 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 +197 -33
- 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 +173 -131
- package/dist/wgsl/pathTrace.wgsl.js.map +1 -1
- package/dist/wgsl/present.wgsl.d.ts +1 -1
- package/dist/wgsl/present.wgsl.d.ts.map +1 -1
- package/dist/wgsl/present.wgsl.js +11 -1
- package/dist/wgsl/present.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/package.json +4 -2
- package/src/denoise.ts +15 -1
- package/src/device.ts +9 -0
- package/src/frameLayout.ts +6 -1
- package/src/imageResolve.ts +2 -0
- package/src/index.ts +2 -1
- package/src/lensPass.ts +17 -1
- package/src/lightTree.ts +67 -21
- package/src/lights.ts +65 -1
- package/src/oidnPass.ts +284 -0
- package/src/oidnWebgpu.d.ts +5 -0
- package/src/pathTracePass.ts +11 -9
- package/src/presentPass.ts +3 -0
- package/src/renderer.ts +161 -20
- package/src/sceneUpload.ts +9 -0
- package/src/settings.ts +40 -6
- package/src/wgsl/lens.wgsl.ts +110 -17
- package/src/wgsl/lights.wgsl.ts +197 -33
- package/src/wgsl/pathTrace.wgsl.ts +173 -131
- package/src/wgsl/present.wgsl.ts +11 -1
- package/src/wgsl/random.wgsl.ts +28 -0
- package/weights/LICENSE.txt +202 -0
- package/weights/rt_hdr_alb_nrm.tza +0 -0
package/src/wgsl/lights.wgsl.ts
CHANGED
|
@@ -55,6 +55,7 @@ fn lightByProbability(u: f32) -> Light {
|
|
|
55
55
|
const TREE_NODE_WORDS: u32 = 16u;
|
|
56
56
|
const TREE_LEAF: u32 = 0x80000000u;
|
|
57
57
|
const TREE_ROOT_PARENT: u32 = 0xffffffffu;
|
|
58
|
+
const TREE_ENTRY_WORDS: u32 = 4u;
|
|
58
59
|
const NO_LIGHT: u32 = 0xffffffffu;
|
|
59
60
|
|
|
60
61
|
/**
|
|
@@ -63,55 +64,74 @@ const NO_LIGHT: u32 = 0xffffffffu;
|
|
|
63
64
|
* angle the node's box takes from the point (Conty Estevez and Kulla's importance). A point inside the box rules
|
|
64
65
|
* nothing out. A zero normal is a receiver that takes light from everywhere, such as a point in a volume.
|
|
65
66
|
*/
|
|
66
|
-
fn lightNodeImportance(node: u32, position: vec3<f32>, normal: vec3<f32>) -> f32 {
|
|
67
|
-
let at =
|
|
67
|
+
fn lightNodeImportance(tree: u32, node: u32, position: vec3<f32>, normal: vec3<f32>) -> f32 {
|
|
68
|
+
let at = tree + node * TREE_NODE_WORDS;
|
|
68
69
|
let low = extraVec4(at);
|
|
69
70
|
let high = extraVec4(at + 4u);
|
|
70
|
-
let cone = extraVec4(at + 8u);
|
|
71
71
|
|
|
72
72
|
if (low.w <= 0.0) {
|
|
73
73
|
return 0.0;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
let centre = (low.xyz + high.xyz) * 0.5;
|
|
77
|
-
let
|
|
77
|
+
let radius2 = dot(high.xyz - low.xyz, high.xyz - low.xyz) * 0.25;
|
|
78
78
|
let offset = position - centre;
|
|
79
79
|
let distance2 = dot(offset, offset);
|
|
80
80
|
|
|
81
|
-
if (distance2 <=
|
|
82
|
-
return low.w / max(
|
|
81
|
+
if (distance2 <= radius2) {
|
|
82
|
+
return low.w / max(radius2, 1e-12);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
let
|
|
86
|
-
|
|
85
|
+
let away = offset * inverseSqrt(distance2);
|
|
86
|
+
// The angle the box takes from the point, as its sine and cosine: the angles below are widened by it without a
|
|
87
|
+
// single inverse trigonometric function, by the sum and difference formulas.
|
|
88
|
+
let sinU2 = radius2 / distance2;
|
|
89
|
+
let cosU = sqrt(1.0 - sinU2);
|
|
90
|
+
let sinU = sqrt(sinU2);
|
|
87
91
|
var orientation = 1.0;
|
|
88
92
|
|
|
89
93
|
// A cone of minus one is the whole sphere: such lights shine towards every point alike.
|
|
90
94
|
if (high.w > -1.0) {
|
|
91
|
-
let
|
|
92
|
-
let
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
let cone = extraVec4(at + 8u);
|
|
96
|
+
let cosO = high.w;
|
|
97
|
+
let sinO = sqrt(max(0.0, 1.0 - cosO * cosO));
|
|
98
|
+
// The cone widened by the box: cos and sin of theta_o + theta_u, the whole sphere once that passes pi.
|
|
99
|
+
let cosWide = cosO * cosU - sinO * sinU;
|
|
100
|
+
let sinWide = sinO * cosU + cosO * sinU;
|
|
101
|
+
let cosTheta = dot(cone.xyz, away);
|
|
102
|
+
|
|
103
|
+
// cos(max(0, theta - theta_o - theta_u)), which is one while the point lies within the widened cone.
|
|
104
|
+
if (sinWide >= 0.0 && cosTheta < cosWide) {
|
|
105
|
+
let sinTheta = sqrt(max(0.0, 1.0 - cosTheta * cosTheta));
|
|
106
|
+
let cosPast = cosTheta * cosWide + sinTheta * sinWide;
|
|
107
|
+
|
|
108
|
+
orientation = select(0.0, cosPast, cosPast >= cone.w);
|
|
109
|
+
}
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
var facing = 1.0;
|
|
98
113
|
|
|
99
114
|
if (any(normal != vec3<f32>(0.0))) {
|
|
100
|
-
let
|
|
115
|
+
let cosN = -dot(normal, away);
|
|
116
|
+
|
|
117
|
+
// cos(max(0, theta_n - theta_u)), and nothing once that reaches a right angle.
|
|
118
|
+
if (cosN < cosU) {
|
|
119
|
+
let sinN = sqrt(max(0.0, 1.0 - cosN * cosN));
|
|
101
120
|
|
|
102
|
-
|
|
121
|
+
facing = max(0.0, cosN * cosU + sinN * sinU);
|
|
122
|
+
}
|
|
103
123
|
}
|
|
104
124
|
|
|
105
125
|
return low.w * orientation * facing / distance2;
|
|
106
126
|
}
|
|
107
127
|
|
|
108
|
-
/** The chance of stepping from a node of
|
|
109
|
-
fn lightChildOdds(left: u32, right: u32, position: vec3<f32>, normal: vec3<f32>) -> vec2<f32> {
|
|
128
|
+
/** The chance of stepping from a node of a tree (the lights' or the windows') to each of its children, from here. */
|
|
129
|
+
fn lightChildOdds(tree: u32, left: u32, right: u32, position: vec3<f32>, normal: vec3<f32>) -> vec2<f32> {
|
|
110
130
|
var importance = vec2<f32>(0.0);
|
|
111
131
|
|
|
112
132
|
// One loop over the two children, so the importance is written into the kernel once per walk.
|
|
113
133
|
for (var k = 0u; k < 2u; k = k + 1u) {
|
|
114
|
-
importance[k] = lightNodeImportance(select(right, left, k == 0u), position, normal);
|
|
134
|
+
importance[k] = lightNodeImportance(tree, select(right, left, k == 0u), position, normal);
|
|
115
135
|
}
|
|
116
136
|
|
|
117
137
|
let total = importance.x + importance.y;
|
|
@@ -119,26 +139,41 @@ fn lightChildOdds(left: u32, right: u32, position: vec3<f32>, normal: vec3<f32>)
|
|
|
119
139
|
return select(importance / total, vec2<f32>(0.0), total <= 0.0);
|
|
120
140
|
}
|
|
121
141
|
|
|
122
|
-
/**
|
|
123
|
-
|
|
142
|
+
/**
|
|
143
|
+
* An entry picked down a tree by importance from this point, with the chance of picking it; NO_LIGHT for none. The
|
|
144
|
+
* tree is the lights' or the windows', given by where its nodes and its leaves' list start.
|
|
145
|
+
*/
|
|
146
|
+
fn sampleLightTree(tree: u32, list: u32, position: vec3<f32>, normal: vec3<f32>, u: f32, pdf: ptr<function, f32>,
|
|
124
147
|
density: ptr<function, f32>) -> u32 {
|
|
125
148
|
var node = 0u;
|
|
126
149
|
var chance = 1.0;
|
|
127
150
|
var x = u;
|
|
128
151
|
|
|
129
152
|
for (var depth = 0u; depth < 64u; depth = depth + 1u) {
|
|
130
|
-
let at =
|
|
153
|
+
let at = tree + node * TREE_NODE_WORDS;
|
|
131
154
|
let first = textures[at + 12u];
|
|
132
155
|
|
|
156
|
+
// A leaf lists its lights, too close together to tell apart from here: one is picked by its share of the power.
|
|
133
157
|
if ((first & TREE_LEAF) != 0u) {
|
|
134
|
-
|
|
135
|
-
|
|
158
|
+
let start = list + (first & ~TREE_LEAF) * TREE_ENTRY_WORDS;
|
|
159
|
+
let count = textures[at + 13u];
|
|
160
|
+
var slot = start + (count - 1u) * TREE_ENTRY_WORDS;
|
|
161
|
+
|
|
162
|
+
for (var j = 0u; j + 1u < count; j = j + 1u) {
|
|
163
|
+
if (x < extraFloat(start + j * TREE_ENTRY_WORDS + 3u)) {
|
|
164
|
+
slot = start + j * TREE_ENTRY_WORDS;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
136
168
|
|
|
137
|
-
|
|
169
|
+
*pdf = chance * extraFloat(slot + 1u);
|
|
170
|
+
*density = extraFloat(slot + 2u);
|
|
171
|
+
|
|
172
|
+
return textures[slot];
|
|
138
173
|
}
|
|
139
174
|
|
|
140
175
|
let second = textures[at + 13u];
|
|
141
|
-
let odds = lightChildOdds(first, second, position, normal);
|
|
176
|
+
let odds = lightChildOdds(tree, first, second, position, normal);
|
|
142
177
|
|
|
143
178
|
if (odds.x + odds.y <= 0.0) {
|
|
144
179
|
return NO_LIGHT;
|
|
@@ -162,8 +197,10 @@ fn sampleLightTree(position: vec3<f32>, normal: vec3<f32>, u: f32, pdf: ptr<func
|
|
|
162
197
|
|
|
163
198
|
/** The chance the tree would have picked the local light at this index from this point, walked up from its leaf. */
|
|
164
199
|
fn lightTreePdf(index: u32, position: vec3<f32>, normal: vec3<f32>) -> f32 {
|
|
165
|
-
var node = textures[frame.lightLeaves + index];
|
|
166
|
-
|
|
200
|
+
var node = textures[frame.lightLeaves + index * 2u];
|
|
201
|
+
let slot = frame.lightList + textures[frame.lightLeaves + index * 2u + 1u] * TREE_ENTRY_WORDS;
|
|
202
|
+
// Its share within its leaf, and the area density of a point on it.
|
|
203
|
+
var chance = extraFloat(slot + 1u) * extraFloat(slot + 2u);
|
|
167
204
|
|
|
168
205
|
for (var depth = 0u; depth < 64u; depth = depth + 1u) {
|
|
169
206
|
let parent = textures[frame.lightTree + node * TREE_NODE_WORDS + 14u];
|
|
@@ -174,7 +211,7 @@ fn lightTreePdf(index: u32, position: vec3<f32>, normal: vec3<f32>) -> f32 {
|
|
|
174
211
|
|
|
175
212
|
let at = frame.lightTree + parent * TREE_NODE_WORDS;
|
|
176
213
|
let first = textures[at + 12u];
|
|
177
|
-
let odds = lightChildOdds(first, textures[at + 13u], position, normal);
|
|
214
|
+
let odds = lightChildOdds(frame.lightTree, first, textures[at + 13u], position, normal);
|
|
178
215
|
|
|
179
216
|
chance = chance * select(odds.y, odds.x, node == first);
|
|
180
217
|
node = parent;
|
|
@@ -218,6 +255,119 @@ fn lightPdfOfTriangle(triangle: u32, position: vec3<f32>, normal: vec3<f32>) ->
|
|
|
218
255
|
return 0.0;
|
|
219
256
|
}
|
|
220
257
|
|
|
258
|
+
/** Share of the sky's samples aimed through the windows, from a point inside a room. */
|
|
259
|
+
const PORTAL_SHARE: f32 = 0.5;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* How often the sky is aimed at through the windows from this point: from inside a room, where something stands above
|
|
263
|
+
* it, half the time; outside never, where the open sky is better found by its brightness. A function of the point alone,
|
|
264
|
+
* so the heuristic can ask it again when a bounce ray escapes.
|
|
265
|
+
*/
|
|
266
|
+
fn portalChance(position: vec3<f32>) -> f32 {
|
|
267
|
+
if (!PORTALS || frame.portalCount == 0u) {
|
|
268
|
+
return 0.0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return select(0.0, PORTAL_SHARE, traceAny(position, vec3<f32>(0.0, 1.0, 0.0), 1e30) != NO_HIT);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Window subtrees a ray's walk through the windows' tree keeps waiting. */
|
|
275
|
+
const PORTAL_STACK: u32 = 32u;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The solid angle density with which aiming through the windows produces this direction: over every window triangle
|
|
279
|
+
* it crosses, the chance the tree picks that window from here times the area density of a point on it, over the area
|
|
280
|
+
* it shows from here. The ray walks the tree as it would a hierarchy, by the nodes' boxes, carrying the chance of
|
|
281
|
+
* reaching each node, so the windows it misses cost a box test and the ones it crosses no walk back up.
|
|
282
|
+
*/
|
|
283
|
+
fn portalPdf(position: vec3<f32>, direction: vec3<f32>) -> f32 {
|
|
284
|
+
var nodes: array<u32, PORTAL_STACK>;
|
|
285
|
+
var chances: array<f32, PORTAL_STACK>;
|
|
286
|
+
var top = 1u;
|
|
287
|
+
let inverse = 1.0 / direction;
|
|
288
|
+
var pdf = 0.0;
|
|
289
|
+
|
|
290
|
+
nodes[0] = 0u;
|
|
291
|
+
chances[0] = 1.0;
|
|
292
|
+
|
|
293
|
+
loop {
|
|
294
|
+
if (top == 0u) {
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
top = top - 1u;
|
|
299
|
+
|
|
300
|
+
let node = nodes[top];
|
|
301
|
+
let chance = chances[top];
|
|
302
|
+
let at = frame.portals + node * TREE_NODE_WORDS;
|
|
303
|
+
|
|
304
|
+
if (enterBox(extraVec4(at).xyz, extraVec4(at + 4u).xyz, position, inverse, 1e30) >= 1e30) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let first = textures[at + 12u];
|
|
309
|
+
let second = textures[at + 13u];
|
|
310
|
+
|
|
311
|
+
if ((first & TREE_LEAF) != 0u) {
|
|
312
|
+
let start = frame.portalList + (first & ~TREE_LEAF) * TREE_ENTRY_WORDS;
|
|
313
|
+
|
|
314
|
+
for (var j = 0u; j < second; j = j + 1u) {
|
|
315
|
+
let slot = start + j * TREE_ENTRY_WORDS;
|
|
316
|
+
let triangle = textures[slot];
|
|
317
|
+
let t = intersectTriangle(leafOf(triangle), position, direction).x;
|
|
318
|
+
|
|
319
|
+
if (t > 0.0) {
|
|
320
|
+
let cosine = abs(dot(triangleNormal(triangle), direction));
|
|
321
|
+
|
|
322
|
+
pdf = pdf + chance * extraFloat(slot + 1u) * extraFloat(slot + 2u) * t * t / max(cosine, 1e-6);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let odds = lightChildOdds(frame.portals, first, second, position, vec3<f32>(0.0));
|
|
330
|
+
|
|
331
|
+
for (var k = 0u; k < 2u; k = k + 1u) {
|
|
332
|
+
if (odds[k] > 0.0 && top < PORTAL_STACK) {
|
|
333
|
+
nodes[top] = select(second, first, k == 0u);
|
|
334
|
+
chances[top] = chance * odds[k];
|
|
335
|
+
top = top + 1u;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return pdf;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** A direction through a point on a window the tree picks from here, or none from a point on the window itself. */
|
|
344
|
+
fn samplePortal(position: vec3<f32>, u: vec2<f32>) -> vec3<f32> {
|
|
345
|
+
var chance = 0.0;
|
|
346
|
+
var density = 0.0;
|
|
347
|
+
let triangle = sampleLightTree(frame.portals, frame.portalList, position, vec3<f32>(0.0), u.x, &chance, &density);
|
|
348
|
+
|
|
349
|
+
if (triangle == NO_LIGHT) {
|
|
350
|
+
return vec3<f32>(0.0);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// The tree spent the sample walking down; a fresh one places the point, which the pick does not depend on.
|
|
354
|
+
let point = trianglePointAt(triangle, triangleBarycentric(vec2<f32>(fract(u.x * 4096.0), u.y)));
|
|
355
|
+
let offset = point - position;
|
|
356
|
+
|
|
357
|
+
return select(vec3<f32>(0.0), normalize(offset), dot(offset, offset) > 1e-12);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** The density of the sky's sampling from this point for this direction: by brightness, through windows, or both. */
|
|
361
|
+
fn skyPdf(position: vec3<f32>, direction: vec3<f32>, share: f32) -> f32 {
|
|
362
|
+
var pdf = (1.0 - share) * environmentPdf(direction);
|
|
363
|
+
|
|
364
|
+
if (share > 0.0) {
|
|
365
|
+
pdf = pdf + share * portalPdf(position, direction);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return pdf;
|
|
369
|
+
}
|
|
370
|
+
|
|
221
371
|
/** The power heuristic: how much of a contribution the strategy that produced it keeps, against the other one. */
|
|
222
372
|
fn misWeight(pdf: f32, other: f32) -> f32 {
|
|
223
373
|
let a = pdf * pdf;
|
|
@@ -273,7 +423,7 @@ fn sampleLight(position: vec3<f32>, normal: vec3<f32>, pick: f32, u: vec2<f32>)
|
|
|
273
423
|
if (frame.lightTree != 0u && pick < frame.localLightPick) {
|
|
274
424
|
var chance = 0.0;
|
|
275
425
|
var density = 1.0;
|
|
276
|
-
let index = sampleLightTree(position, normal, pick / frame.localLightPick, &chance, &density);
|
|
426
|
+
let index = sampleLightTree(frame.lightTree, frame.lightList, position, normal, pick / frame.localLightPick, &chance, &density);
|
|
277
427
|
|
|
278
428
|
if (index == NO_LIGHT || chance <= 0.0) {
|
|
279
429
|
return noLight();
|
|
@@ -322,17 +472,31 @@ fn sampleLightEntry(light: Light, pdf: f32, position: vec3<f32>, u: vec2<f32>) -
|
|
|
322
472
|
sample.misPdf = 0.0;
|
|
323
473
|
|
|
324
474
|
if (light.kind == LIGHT_ENVIRONMENT) {
|
|
325
|
-
|
|
475
|
+
// From inside a room, part of the time through a window: daylight comes in there or not at all.
|
|
476
|
+
let share = portalChance(position);
|
|
477
|
+
var direction = vec3<f32>(0.0);
|
|
478
|
+
|
|
479
|
+
if (u.x < share) {
|
|
480
|
+
direction = samplePortal(position, vec2<f32>(u.x / share, u.y));
|
|
481
|
+
} else {
|
|
482
|
+
let drawn = sampleEnvironmentDirection(vec2<f32>(min((u.x - share) / (1.0 - share), 0.99999994), u.y));
|
|
483
|
+
|
|
484
|
+
if (drawn.pdf > 0.0) {
|
|
485
|
+
direction = drawn.direction;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
let mixed = skyPdf(position, direction, share);
|
|
326
490
|
|
|
327
|
-
if (
|
|
491
|
+
if (all(direction == vec3<f32>(0.0)) || mixed <= 0.0) {
|
|
328
492
|
return noLight();
|
|
329
493
|
}
|
|
330
494
|
|
|
331
|
-
let density = pdf *
|
|
495
|
+
let density = pdf * mixed;
|
|
332
496
|
|
|
333
|
-
sample.direction =
|
|
497
|
+
sample.direction = direction;
|
|
334
498
|
sample.distance = 1e30;
|
|
335
|
-
sample.value = environmentRadiance(
|
|
499
|
+
sample.value = environmentRadiance(direction) / density;
|
|
336
500
|
// A bounce ray that escapes finds the same light, so the two strategies share it.
|
|
337
501
|
sample.misPdf = density;
|
|
338
502
|
|