three-gpu-pathtracer 0.0.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/LICENSE +21 -0
- package/README.md +386 -0
- package/build/index.module.js +1825 -0
- package/build/index.module.js.map +1 -0
- package/build/index.umd.cjs +1840 -0
- package/build/index.umd.cjs.map +1 -0
- package/package.json +57 -0
- package/src/core/MaterialReducer.js +256 -0
- package/src/core/PathTracingRenderer.js +140 -0
- package/src/core/PathTracingSceneGenerator.js +46 -0
- package/src/index.js +21 -0
- package/src/materials/AmbientOcclusionMaterial.js +197 -0
- package/src/materials/LambertPathTracingMaterial.js +285 -0
- package/src/materials/MaterialBase.js +56 -0
- package/src/materials/PhysicalPathTracingMaterial.js +370 -0
- package/src/shader/shaderGGXFunctions.js +107 -0
- package/src/shader/shaderMaterialSampling.js +333 -0
- package/src/shader/shaderStructs.js +30 -0
- package/src/shader/shaderUtils.js +140 -0
- package/src/uniforms/EquirectPdfUniform.js +132 -0
- package/src/uniforms/MaterialStructArrayUniform.js +18 -0
- package/src/uniforms/MaterialStructUniform.js +94 -0
- package/src/uniforms/RenderTarget2DArray.js +80 -0
- package/src/utils/GeometryPreparationUtils.js +172 -0
- package/src/utils/UVUnwrapper.js +101 -0
- package/src/viewers/PathTracingViewer.js +259 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { shaderGGXFunctions } from './shaderGGXFunctions.js';
|
|
2
|
+
|
|
3
|
+
export const shaderMaterialSampling = /* glsl */`
|
|
4
|
+
|
|
5
|
+
struct SurfaceRec {
|
|
6
|
+
vec3 normal;
|
|
7
|
+
vec3 faceNormal;
|
|
8
|
+
bool frontFace;
|
|
9
|
+
float roughness;
|
|
10
|
+
float filteredRoughness;
|
|
11
|
+
float metalness;
|
|
12
|
+
vec3 color;
|
|
13
|
+
vec3 emission;
|
|
14
|
+
float transmission;
|
|
15
|
+
float ior;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
struct SampleRec {
|
|
19
|
+
float pdf;
|
|
20
|
+
vec3 direction;
|
|
21
|
+
vec3 color;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
${ shaderGGXFunctions }
|
|
25
|
+
|
|
26
|
+
// diffuse
|
|
27
|
+
float diffusePDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
28
|
+
|
|
29
|
+
// https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html#lightscattering/thescatteringpdf
|
|
30
|
+
float cosValue = wi.z;
|
|
31
|
+
return cosValue / PI;
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
vec3 diffuseDirection( vec3 wo, SurfaceRec surf ) {
|
|
36
|
+
|
|
37
|
+
vec3 lightDirection = randDirection();
|
|
38
|
+
lightDirection.z += 1.0;
|
|
39
|
+
lightDirection = normalize( lightDirection );
|
|
40
|
+
|
|
41
|
+
return lightDirection;
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
vec3 diffuseColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
46
|
+
|
|
47
|
+
// TODO: scale by 1 - F here
|
|
48
|
+
// note on division by PI
|
|
49
|
+
// https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
|
|
50
|
+
float metalFactor = ( 1.0 - surf.metalness ) * wi.z / ( PI * PI );
|
|
51
|
+
float transmissionFactor = 1.0 - surf.transmission;
|
|
52
|
+
return surf.color * metalFactor * transmissionFactor;
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// specular
|
|
57
|
+
float specularPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
58
|
+
|
|
59
|
+
// See equation (17) in http://jcgt.org/published/0003/02/03/
|
|
60
|
+
float filteredRoughness = surf.filteredRoughness;
|
|
61
|
+
vec3 halfVector = getHalfVector( wi, wo );
|
|
62
|
+
return ggxPDF( wi, halfVector, filteredRoughness ) / ( 4.0 * dot( wi, halfVector ) );
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
vec3 specularDirection( vec3 wo, SurfaceRec surf ) {
|
|
67
|
+
|
|
68
|
+
// sample ggx vndf distribution which gives a new normal
|
|
69
|
+
float filteredRoughness = surf.filteredRoughness;
|
|
70
|
+
vec3 halfVector = ggxDirection(
|
|
71
|
+
wo,
|
|
72
|
+
filteredRoughness,
|
|
73
|
+
filteredRoughness,
|
|
74
|
+
rand(),
|
|
75
|
+
rand()
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// apply to new ray by reflecting off the new normal
|
|
79
|
+
return - reflect( wo, halfVector );
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
vec3 specularColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
84
|
+
|
|
85
|
+
// if roughness is set to 0 then D === NaN which results in black pixels
|
|
86
|
+
float metalness = surf.metalness;
|
|
87
|
+
float ior = surf.ior;
|
|
88
|
+
bool frontFace = surf.frontFace;
|
|
89
|
+
float filteredRoughness = surf.filteredRoughness;
|
|
90
|
+
|
|
91
|
+
vec3 halfVector = getHalfVector( wo, wi );
|
|
92
|
+
float iorRatio = frontFace ? 1.0 / ior : ior;
|
|
93
|
+
float G = ggxShadowMaskG2( wi, wo, filteredRoughness );
|
|
94
|
+
float D = ggxDistribution( halfVector, filteredRoughness );
|
|
95
|
+
|
|
96
|
+
float F = schlickFresnelFromIor( dot( wi, halfVector ), iorRatio );
|
|
97
|
+
float cosTheta = min( wo.z, 1.0 );
|
|
98
|
+
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
|
|
99
|
+
bool cannotRefract = iorRatio * sinTheta > 1.0;
|
|
100
|
+
if ( cannotRefract ) {
|
|
101
|
+
|
|
102
|
+
F = 1.0;
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
vec3 color = mix( vec3( 1.0 ), surf.color, metalness );
|
|
107
|
+
color = mix( color, vec3( 1.0 ), F );
|
|
108
|
+
color *= G * D / ( 4.0 * abs( wi.z * wo.z ) );
|
|
109
|
+
color *= mix( F, 1.0, metalness );
|
|
110
|
+
color *= wi.z; // scale the light by the direction the light is coming in from
|
|
111
|
+
|
|
112
|
+
return color;
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/*
|
|
117
|
+
// transmission
|
|
118
|
+
function transmissionPDF( wo, wi, material, surf ) {
|
|
119
|
+
|
|
120
|
+
// See section 4.2 in https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
|
|
121
|
+
|
|
122
|
+
const { roughness, ior } = material;
|
|
123
|
+
const { frontFace } = hit;
|
|
124
|
+
const ratio = frontFace ? ior : 1 / ior;
|
|
125
|
+
const minRoughness = Math.max( roughness, MIN_ROUGHNESS );
|
|
126
|
+
|
|
127
|
+
halfVector.set( 0, 0, 0 ).addScaledVector( wi, ratio ).addScaledVector( wo, 1.0 ).normalize().multiplyScalar( - 1 );
|
|
128
|
+
|
|
129
|
+
const denom = Math.pow( ratio * halfVector.dot( wi ) + 1.0 * halfVector.dot( wo ), 2.0 );
|
|
130
|
+
return ggxPDF( wo, halfVector, minRoughness ) / denom;
|
|
131
|
+
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function transmissionDirection( wo, hit, material, lightDirection ) {
|
|
135
|
+
|
|
136
|
+
const { roughness, ior } = material;
|
|
137
|
+
const { frontFace } = hit;
|
|
138
|
+
const ratio = frontFace ? 1 / ior : ior;
|
|
139
|
+
const minRoughness = Math.max( roughness, MIN_ROUGHNESS );
|
|
140
|
+
|
|
141
|
+
// sample ggx vndf distribution which gives a new normal
|
|
142
|
+
ggxDirection(
|
|
143
|
+
wo,
|
|
144
|
+
minRoughness,
|
|
145
|
+
minRoughness,
|
|
146
|
+
Math.random(),
|
|
147
|
+
Math.random(),
|
|
148
|
+
halfVector,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// apply to new ray by reflecting off the new normal
|
|
152
|
+
tempDir.copy( wo ).multiplyScalar( - 1 );
|
|
153
|
+
refract( tempDir, halfVector, ratio, lightDirection );
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function transmissionColor( wo, wi, material, hit, colorTarget ) {
|
|
158
|
+
|
|
159
|
+
const { metalness, transmission } = material;
|
|
160
|
+
colorTarget
|
|
161
|
+
.copy( material.color )
|
|
162
|
+
.multiplyScalar( ( 1.0 - metalness ) * wo.z )
|
|
163
|
+
.multiplyScalar( transmission );
|
|
164
|
+
|
|
165
|
+
}
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
// TODO: This is just using a basic cosine-weighted specular distribution with an
|
|
169
|
+
// incorrect PDF value at the moment. Update it to correctly use a GGX distribution
|
|
170
|
+
float transmissionPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
171
|
+
|
|
172
|
+
float ior = surf.ior;
|
|
173
|
+
bool frontFace = surf.frontFace;
|
|
174
|
+
|
|
175
|
+
float ratio = frontFace ? 1.0 / ior : ior;
|
|
176
|
+
float cosTheta = min( wo.z, 1.0 );
|
|
177
|
+
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
|
|
178
|
+
float reflectance = schlickFresnelFromIor( cosTheta, ratio );
|
|
179
|
+
bool cannotRefract = ratio * sinTheta > 1.0;
|
|
180
|
+
if ( cannotRefract ) {
|
|
181
|
+
|
|
182
|
+
return 0.0;
|
|
183
|
+
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return 1.0 / ( 1.0 - reflectance );
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
vec3 transmissionDirection( vec3 wo, SurfaceRec surf ) {
|
|
191
|
+
|
|
192
|
+
float roughness = surf.roughness;
|
|
193
|
+
float ior = surf.ior;
|
|
194
|
+
bool frontFace = surf.frontFace;
|
|
195
|
+
float ratio = frontFace ? 1.0 / ior : ior;
|
|
196
|
+
|
|
197
|
+
vec3 lightDirection = refract( - wo, vec3( 0.0, 0.0, 1.0 ), ratio );
|
|
198
|
+
lightDirection += randDirection() * roughness;
|
|
199
|
+
return normalize( lightDirection );
|
|
200
|
+
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
vec3 transmissionColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
204
|
+
|
|
205
|
+
float metalness = surf.metalness;
|
|
206
|
+
float transmission = surf.transmission;
|
|
207
|
+
|
|
208
|
+
vec3 color = surf.color;
|
|
209
|
+
color *= ( 1.0 - metalness );
|
|
210
|
+
color *= transmission;
|
|
211
|
+
|
|
212
|
+
return color;
|
|
213
|
+
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
float bsdfPdf( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
217
|
+
|
|
218
|
+
float ior = surf.ior;
|
|
219
|
+
float metalness = surf.metalness;
|
|
220
|
+
float transmission = surf.transmission;
|
|
221
|
+
bool frontFace = surf.frontFace;
|
|
222
|
+
|
|
223
|
+
float ratio = frontFace ? 1.0 / ior : ior;
|
|
224
|
+
float cosTheta = min( wo.z, 1.0 );
|
|
225
|
+
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
|
|
226
|
+
float reflectance = schlickFresnelFromIor( cosTheta, ratio );
|
|
227
|
+
bool cannotRefract = ratio * sinTheta > 1.0;
|
|
228
|
+
if ( cannotRefract ) {
|
|
229
|
+
|
|
230
|
+
reflectance = 1.0;
|
|
231
|
+
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
float spdf = 0.0;
|
|
235
|
+
float dpdf = 0.0;
|
|
236
|
+
float tpdf = 0.0;
|
|
237
|
+
|
|
238
|
+
if ( wi.z < 0.0 ) {
|
|
239
|
+
|
|
240
|
+
tpdf = transmissionPDF( wo, wi, surf );
|
|
241
|
+
|
|
242
|
+
} else {
|
|
243
|
+
|
|
244
|
+
spdf = specularPDF( wo, wi, surf );
|
|
245
|
+
dpdf = diffusePDF( wo, wi, surf );
|
|
246
|
+
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
float transSpecularProb = mix( reflectance, 1.0, metalness );
|
|
250
|
+
float diffSpecularProb = 0.5 + 0.5 * metalness;
|
|
251
|
+
float pdf =
|
|
252
|
+
spdf * transmission * transSpecularProb
|
|
253
|
+
+ tpdf * transmission * ( 1.0 - transSpecularProb )
|
|
254
|
+
+ spdf * ( 1.0 - transmission ) * diffSpecularProb
|
|
255
|
+
+ dpdf * ( 1.0 - transmission ) * ( 1.0 - diffSpecularProb );
|
|
256
|
+
|
|
257
|
+
return pdf;
|
|
258
|
+
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
vec3 bsdfColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
262
|
+
|
|
263
|
+
vec3 color = vec3( 0.0 );
|
|
264
|
+
if ( wi.z < 0.0 ) {
|
|
265
|
+
|
|
266
|
+
color = transmissionColor( wo, wi, surf );
|
|
267
|
+
|
|
268
|
+
} else {
|
|
269
|
+
|
|
270
|
+
color = diffuseColor( wo, wi, surf );
|
|
271
|
+
color *= 1.0 - surf.transmission;
|
|
272
|
+
|
|
273
|
+
color += specularColor( wo, wi, surf );
|
|
274
|
+
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return color;
|
|
278
|
+
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
SampleRec bsdfSample( vec3 wo, SurfaceRec surf ) {
|
|
282
|
+
|
|
283
|
+
float ior = surf.ior;
|
|
284
|
+
float metalness = surf.metalness;
|
|
285
|
+
float transmission = surf.transmission;
|
|
286
|
+
bool frontFace = surf.frontFace;
|
|
287
|
+
|
|
288
|
+
float ratio = frontFace ? 1.0 / ior : ior;
|
|
289
|
+
float cosTheta = min( wo.z, 1.0 );
|
|
290
|
+
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
|
|
291
|
+
float reflectance = schlickFresnelFromIor( cosTheta, ratio );
|
|
292
|
+
bool cannotRefract = ratio * sinTheta > 1.0;
|
|
293
|
+
if ( cannotRefract ) {
|
|
294
|
+
|
|
295
|
+
reflectance = 1.0;
|
|
296
|
+
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
SampleRec result;
|
|
300
|
+
if ( rand() < transmission ) {
|
|
301
|
+
|
|
302
|
+
float specularProb = mix( reflectance, 1.0, metalness );
|
|
303
|
+
if ( rand() < specularProb ) {
|
|
304
|
+
|
|
305
|
+
result.direction = specularDirection( wo, surf );
|
|
306
|
+
|
|
307
|
+
} else {
|
|
308
|
+
|
|
309
|
+
result.direction = transmissionDirection( wo, surf );
|
|
310
|
+
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
} else {
|
|
314
|
+
|
|
315
|
+
float specularProb = 0.5 + 0.5 * metalness;
|
|
316
|
+
if ( rand() < specularProb ) {
|
|
317
|
+
|
|
318
|
+
result.direction = specularDirection( wo, surf );
|
|
319
|
+
|
|
320
|
+
} else {
|
|
321
|
+
|
|
322
|
+
result.direction = diffuseDirection( wo, surf );
|
|
323
|
+
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
result.pdf = bsdfPdf( wo, result.direction, surf );
|
|
329
|
+
result.color = bsdfColor( wo, result.direction, surf );
|
|
330
|
+
return result;
|
|
331
|
+
|
|
332
|
+
}
|
|
333
|
+
`;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const shaderMaterialStructs = /* glsl */ `
|
|
2
|
+
|
|
3
|
+
struct Material {
|
|
4
|
+
|
|
5
|
+
vec3 color;
|
|
6
|
+
int map;
|
|
7
|
+
|
|
8
|
+
float metalness;
|
|
9
|
+
int metalnessMap;
|
|
10
|
+
|
|
11
|
+
float roughness;
|
|
12
|
+
int roughnessMap;
|
|
13
|
+
|
|
14
|
+
float ior;
|
|
15
|
+
float transmission;
|
|
16
|
+
int transmissionMap;
|
|
17
|
+
|
|
18
|
+
vec3 emissive;
|
|
19
|
+
float emissiveIntensity;
|
|
20
|
+
int emissiveMap;
|
|
21
|
+
|
|
22
|
+
int normalMap;
|
|
23
|
+
vec2 normalScale;
|
|
24
|
+
|
|
25
|
+
float opacity;
|
|
26
|
+
float alphaTest;
|
|
27
|
+
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
`;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export const shaderUtils = /* glsl */`
|
|
2
|
+
|
|
3
|
+
// https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
|
|
4
|
+
float schlickFresnel( float cosine, float f0 ) {
|
|
5
|
+
|
|
6
|
+
return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
|
|
7
|
+
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
|
|
11
|
+
float schlickFresnelFromIor( float cosine, float iorRatio ) {
|
|
12
|
+
|
|
13
|
+
// Schlick approximation
|
|
14
|
+
float r_0 = pow( ( 1.0 - iorRatio ) / ( 1.0 + iorRatio ), 2.0 );
|
|
15
|
+
return schlickFresnel( cosine, r_0 );
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// forms a basis with the normal vector as Z
|
|
20
|
+
mat3 getBasisFromNormal( vec3 normal ) {
|
|
21
|
+
|
|
22
|
+
vec3 other;
|
|
23
|
+
if ( abs( normal.x ) > 0.5 ) {
|
|
24
|
+
|
|
25
|
+
other = vec3( 0.0, 1.0, 0.0 );
|
|
26
|
+
|
|
27
|
+
} else {
|
|
28
|
+
|
|
29
|
+
other = vec3( 1.0, 0.0, 0.0 );
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
vec3 ortho = normalize( cross( normal, other ) );
|
|
34
|
+
vec3 ortho2 = normalize( cross( normal, ortho ) );
|
|
35
|
+
return mat3( ortho2, ortho, normal );
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
vec3 getHalfVector( vec3 a, vec3 b ) {
|
|
40
|
+
|
|
41
|
+
return normalize( a + b );
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
|
|
46
|
+
// is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
|
|
47
|
+
// we find a ray like that we ignore it to avoid artifacts.
|
|
48
|
+
// This function returns if the direction is on the same side of both planes.
|
|
49
|
+
bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
|
|
50
|
+
|
|
51
|
+
bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
|
|
52
|
+
bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
|
|
53
|
+
return aboveSurfaceNormal == aboveGeometryNormal;
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
vec3 getHemisphereSample( vec3 n, vec2 uv ) {
|
|
58
|
+
|
|
59
|
+
// https://www.rorydriscoll.com/2009/01/07/better-sampling/
|
|
60
|
+
// https://graphics.pixar.com/library/OrthonormalB/paper.pdf
|
|
61
|
+
float sign = n.z == 0.0 ? 1.0 : sign( n.z );
|
|
62
|
+
float a = - 1.0 / ( sign + n.z );
|
|
63
|
+
float b = n.x * n.y * a;
|
|
64
|
+
vec3 b1 = vec3( 1.0 + sign * n.x * n.x * a, sign * b, - sign * n.x );
|
|
65
|
+
vec3 b2 = vec3( b, sign + n.y * n.y * a, - n.y );
|
|
66
|
+
|
|
67
|
+
float r = sqrt( uv.x );
|
|
68
|
+
float theta = 2.0 * PI * uv.y;
|
|
69
|
+
float x = r * cos( theta );
|
|
70
|
+
float y = r * sin( theta );
|
|
71
|
+
return x * b1 + y * b2 + sqrt( 1.0 - uv.x ) * n;
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// https://www.shadertoy.com/view/wltcRS
|
|
76
|
+
uvec4 s0;
|
|
77
|
+
|
|
78
|
+
void rng_initialize(vec2 p, int frame) {
|
|
79
|
+
|
|
80
|
+
// white noise seed
|
|
81
|
+
s0 = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// https://www.pcg-random.org/
|
|
86
|
+
void pcg4d( inout uvec4 v ) {
|
|
87
|
+
|
|
88
|
+
v = v * 1664525u + 1013904223u;
|
|
89
|
+
v.x += v.y * v.w;
|
|
90
|
+
v.y += v.z * v.x;
|
|
91
|
+
v.z += v.x * v.y;
|
|
92
|
+
v.w += v.y * v.z;
|
|
93
|
+
v = v ^ ( v >> 16u );
|
|
94
|
+
v.x += v.y*v.w;
|
|
95
|
+
v.y += v.z*v.x;
|
|
96
|
+
v.z += v.x*v.y;
|
|
97
|
+
v.w += v.y*v.z;
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
float rand() {
|
|
102
|
+
|
|
103
|
+
pcg4d(s0);
|
|
104
|
+
return float( s0.x ) / float( 0xffffffffu );
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
vec2 rand2() {
|
|
109
|
+
|
|
110
|
+
pcg4d( s0 );
|
|
111
|
+
return vec2( s0.xy ) / float(0xffffffffu);
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
vec3 rand3() {
|
|
116
|
+
|
|
117
|
+
pcg4d(s0);
|
|
118
|
+
return vec3( s0.xyz ) / float( 0xffffffffu );
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
vec4 rand4() {
|
|
123
|
+
|
|
124
|
+
pcg4d(s0);
|
|
125
|
+
return vec4(s0)/float(0xffffffffu);
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js#L724
|
|
130
|
+
vec3 randDirection() {
|
|
131
|
+
|
|
132
|
+
vec2 r = rand2();
|
|
133
|
+
float u = ( r.x - 0.5 ) * 2.0;
|
|
134
|
+
float t = r.y * PI * 2.0;
|
|
135
|
+
float f = sqrt( 1.0 - u * u );
|
|
136
|
+
|
|
137
|
+
return vec3( f * cos( t ), f * sin( t ), u );
|
|
138
|
+
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Color, DataTexture, FloatType, RGFormat, LinearFilter } from 'three';
|
|
2
|
+
|
|
3
|
+
function RGBEToLinear( r, g, b, e, target ) {
|
|
4
|
+
|
|
5
|
+
const exp = e * 255.0 - 128.0;
|
|
6
|
+
target.set( r, g, b ).multiplyScalar( Math.pow( exp ) );
|
|
7
|
+
return target;
|
|
8
|
+
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class EquirectPdfUniform {
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
|
|
15
|
+
const marginalData = new DataTexture();
|
|
16
|
+
marginalData.type = FloatType;
|
|
17
|
+
marginalData.format = RGFormat;
|
|
18
|
+
marginalData.minFilter = LinearFilter;
|
|
19
|
+
marginalData.maxFilter = LinearFilter;
|
|
20
|
+
marginalData.generateMipmaps = false;
|
|
21
|
+
|
|
22
|
+
const conditionalData = new DataTexture();
|
|
23
|
+
conditionalData.type = FloatType;
|
|
24
|
+
conditionalData.format = RGFormat;
|
|
25
|
+
conditionalData.minFilter = LinearFilter;
|
|
26
|
+
conditionalData.maxFilter = LinearFilter;
|
|
27
|
+
conditionalData.generateMipmaps = false;
|
|
28
|
+
|
|
29
|
+
this.marginalData = marginalData;
|
|
30
|
+
this.conditionalData = conditionalData;
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
updateFrom( hdr ) {
|
|
35
|
+
|
|
36
|
+
// TODO: another reference implementation with a different approach:
|
|
37
|
+
// https://github.com/nvpro-samples/vk_mini_samples/blob/main/hdr_sampling/src/hdr_env.cpp#L243
|
|
38
|
+
|
|
39
|
+
// https://github.com/knightcrawler25/GLSL-PathTracer/blob/master/src/loaders/hdrloader.cpp
|
|
40
|
+
const { width, height, data } = hdr.image;
|
|
41
|
+
const color = new Color();
|
|
42
|
+
const hsl = {};
|
|
43
|
+
|
|
44
|
+
// track the importance of any given pixel in the image by tracking its weight relative to other pixels in the image
|
|
45
|
+
const pdfConditional = new Float32Array( width * height );
|
|
46
|
+
const cdfConditional = new Float32Array( width * height );
|
|
47
|
+
|
|
48
|
+
const pdfMarginal = new Float32Array( height );
|
|
49
|
+
const cdfMarginal = new Float32Array( height );
|
|
50
|
+
|
|
51
|
+
let cumulativeWeightMarginal = 0.0;
|
|
52
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
53
|
+
|
|
54
|
+
let cumulativeWeight = 0.0;
|
|
55
|
+
for ( let x = 0; x < width; x ++ ) {
|
|
56
|
+
|
|
57
|
+
const i = y * width + x;
|
|
58
|
+
const r = data[ 4 * i + 0 ];
|
|
59
|
+
const g = data[ 4 * i + 1 ];
|
|
60
|
+
const b = data[ 4 * i + 2 ];
|
|
61
|
+
const e = data[ 4 * i + 3 ];
|
|
62
|
+
|
|
63
|
+
// the probability of the pixel being selected in this row is the
|
|
64
|
+
// scale of the luminance relative to the rest of the pixels.
|
|
65
|
+
// TODO: this should also account for the solid angle of the pixel when sampling
|
|
66
|
+
const weight = RGBEToLinear( r, g, b, e, color ).getHSL( hsl ).l;
|
|
67
|
+
cumulativeWeight += weight;
|
|
68
|
+
|
|
69
|
+
pdfConditional[ i ] = weight;
|
|
70
|
+
cdfConditional[ i ] = cumulativeWeight;
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// scale the pdf and cdf to [0.0, 1.0]
|
|
75
|
+
for ( let i = 0, l = pdfConditional.length; i < l; i ++ ) {
|
|
76
|
+
|
|
77
|
+
pdfConditional[ i ] /= cumulativeWeight;
|
|
78
|
+
cdfConditional[ i ] /= cumulativeWeight;
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
cumulativeWeightMarginal += cumulativeWeight;
|
|
83
|
+
|
|
84
|
+
// compute the marginal pdf and cdf along the height of the map.
|
|
85
|
+
pdfMarginal[ y ] = cumulativeWeight;
|
|
86
|
+
cdfMarginal[ y ] = cumulativeWeightMarginal;
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// scale the marginal pdf and cdf to [0.0, 1.0]
|
|
91
|
+
for ( let i = 0, l = pdfMarginal.length; i < l; i ++ ) {
|
|
92
|
+
|
|
93
|
+
pdfMarginal[ i ] /= cumulativeWeightMarginal;
|
|
94
|
+
cdfMarginal[ i ] /= cumulativeWeightMarginal;
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// compute a sorted index of distributions and the probabilities along them for both
|
|
99
|
+
// the marginal and conditional data. These will be used to sample with a random number
|
|
100
|
+
// to retrieve a uv value to sample in the environment map.
|
|
101
|
+
const marginalDataArray = new Float32Array( height * 2 );
|
|
102
|
+
const conditionalDataArray = new Float32Array( width * height * 2 );
|
|
103
|
+
|
|
104
|
+
for ( let i = 0; i < height; i ++ ) {
|
|
105
|
+
|
|
106
|
+
//const dist = ( i + 1 ) / height;
|
|
107
|
+
const row = 0; // TODO: find the row that lies at the given cumulative distribution value above
|
|
108
|
+
marginalDataArray[ 2 * i + 0 ] = row / height;
|
|
109
|
+
marginalDataArray[ 2 * i + 1 ] = pdfMarginal[ i ];
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
114
|
+
|
|
115
|
+
for ( let x = 0; x < width; x ++ ) {
|
|
116
|
+
|
|
117
|
+
const i = y * width + x;
|
|
118
|
+
//const dist = ( x + 1 ) / width;
|
|
119
|
+
const col = 0; // TODO: find the column in the given row that lies at the cumulative dist value above
|
|
120
|
+
conditionalDataArray[ 2 * i + 0 ] = col / width;
|
|
121
|
+
conditionalDataArray[ 2 * i + 1 ] = pdfConditional[ i ];
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.marginalData.image = { width, height, data: marginalDataArray };
|
|
128
|
+
this.conditionalData.image = { width, height, data: conditionalDataArray };
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MaterialStructUniform } from './MaterialStructUniform.js';
|
|
2
|
+
|
|
3
|
+
export class MaterialStructArrayUniform extends Array {
|
|
4
|
+
|
|
5
|
+
updateFrom( materials, textures ) {
|
|
6
|
+
|
|
7
|
+
while ( this.length > materials.length ) this.pop();
|
|
8
|
+
while ( this.length < materials.length ) this.push( new MaterialStructUniform() );
|
|
9
|
+
|
|
10
|
+
for ( let i = 0, l = this.length; i < l; i ++ ) {
|
|
11
|
+
|
|
12
|
+
this[ i ].updateFrom( materials[ i ], textures );
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
}
|