three-gpu-pathtracer 0.0.1 → 0.0.2
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/README.md +229 -18
- package/build/index.module.js +528 -198
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +534 -198
- package/build/index.umd.cjs.map +1 -1
- package/package.json +7 -3
- package/src/core/DynamicPathTracingSceneGenerator.js +105 -0
- package/src/core/PathTracingSceneGenerator.js +27 -21
- package/src/core/PhysicalCamera.js +28 -0
- package/src/index.js +2 -0
- package/src/materials/PhysicalPathTracingMaterial.js +56 -6
- package/src/shader/shaderStructs.js +12 -0
- package/src/shader/shaderUtils.js +51 -0
- package/src/uniforms/MaterialStructUniform.js +21 -1
- package/src/uniforms/PhysicalCameraUniform.js +36 -0
- package/src/utils/GeometryPreparationUtils.js +200 -172
- package/src/workers/PathTracingSceneWorker.js +40 -0
- package/src/viewers/PathTracingViewer.js +0 -259
package/build/index.module.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Color, Vector2, WebGLRenderTarget, RGBAFormat, FloatType, BufferAttribute, WebGLArrayRenderTarget, UnsignedByteType, LinearFilter, RepeatWrapping, MeshBasicMaterial, NoToneMapping, ShaderMaterial, Matrix4, Matrix3 } from 'three';
|
|
1
|
+
import { Color, Vector2, WebGLRenderTarget, RGBAFormat, FloatType, BufferAttribute, Mesh, BufferGeometry, PerspectiveCamera, BackSide, FrontSide, DoubleSide, WebGLArrayRenderTarget, UnsignedByteType, LinearFilter, RepeatWrapping, MeshBasicMaterial, NoToneMapping, ShaderMaterial, Matrix4, Matrix3 } from 'three';
|
|
2
2
|
import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
|
|
3
|
-
import { SAH, MeshBVHUniformStruct, FloatVertexAttributeTexture, UIntVertexAttributeTexture, shaderStructs, shaderIntersectFunction } from 'three-mesh-bvh';
|
|
4
|
-
import { GenerateMeshBVHWorker } from 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js';
|
|
3
|
+
import { StaticGeometryGenerator, SAH, MeshBVH, MeshBVHUniformStruct, FloatVertexAttributeTexture, UIntVertexAttributeTexture, shaderStructs, shaderIntersectFunction } from 'three-mesh-bvh';
|
|
5
4
|
import { mergeVertices, mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
6
5
|
|
|
7
6
|
function* renderTask() {
|
|
@@ -142,193 +141,226 @@ class PathTracingRenderer {
|
|
|
142
141
|
|
|
143
142
|
}
|
|
144
143
|
|
|
145
|
-
function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
|
|
146
|
-
|
|
147
|
-
if ( ! Array.isArray( materials ) ) {
|
|
148
|
-
|
|
149
|
-
materials = [ materials ];
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
144
|
+
function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
|
|
145
|
+
|
|
146
|
+
if ( ! Array.isArray( materials ) ) {
|
|
147
|
+
|
|
148
|
+
materials = [ materials ];
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const indexAttr = geometry.index;
|
|
153
|
+
const posAttr = geometry.attributes.position;
|
|
154
|
+
const vertCount = posAttr.count;
|
|
155
|
+
const materialArray = new Uint8Array( vertCount );
|
|
156
|
+
const totalCount = indexAttr ? indexAttr.count : vertCount;
|
|
157
|
+
let groups = geometry.groups;
|
|
158
|
+
if ( groups.length === 0 ) {
|
|
159
|
+
|
|
160
|
+
groups = [ { count: totalCount, start: 0, materialIndex: 0 } ];
|
|
161
|
+
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for ( let i = 0; i < groups.length; i ++ ) {
|
|
165
|
+
|
|
166
|
+
const group = groups[ i ];
|
|
167
|
+
const start = group.start;
|
|
168
|
+
const count = group.count;
|
|
169
|
+
const endCount = Math.min( count, totalCount - start );
|
|
170
|
+
|
|
171
|
+
const mat = materials[ group.materialIndex ];
|
|
172
|
+
const materialIndex = allMaterials.indexOf( mat );
|
|
173
|
+
|
|
174
|
+
for ( let j = 0; j < endCount; j ++ ) {
|
|
175
|
+
|
|
176
|
+
let index = start + j;
|
|
177
|
+
if ( indexAttr ) {
|
|
178
|
+
|
|
179
|
+
index = indexAttr.getX( index );
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
materialArray[ index ] = materialIndex;
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return new BufferAttribute( materialArray, 1, false );
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function trimToAttributes( geometry, attributes ) {
|
|
194
|
+
|
|
195
|
+
// trim any unneeded attributes
|
|
196
|
+
if ( attributes ) {
|
|
197
|
+
|
|
198
|
+
for ( const key in geometry.attributes ) {
|
|
199
|
+
|
|
200
|
+
if ( ! attributes.includes( key ) ) {
|
|
201
|
+
|
|
202
|
+
geometry.deleteAttribute( key );
|
|
203
|
+
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function setCommonAttributes( geometry, options ) {
|
|
213
|
+
|
|
214
|
+
const { attributes = [], normalMapRequired = false } = options;
|
|
215
|
+
|
|
216
|
+
if ( ! geometry.attributes.normal && ( attributes && attributes.includes( 'normal' ) ) ) {
|
|
217
|
+
|
|
218
|
+
geometry.computeVertexNormals();
|
|
219
|
+
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if ( ! geometry.attributes.uv && ( attributes && attributes.includes( 'uv' ) ) ) {
|
|
223
|
+
|
|
224
|
+
const vertCount = geometry.attributes.position.count;
|
|
225
|
+
geometry.setAttribute( 'uv', new BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if ( ! geometry.attributes.tangent && ( attributes && attributes.includes( 'tangent' ) ) ) {
|
|
230
|
+
|
|
231
|
+
if ( normalMapRequired ) {
|
|
232
|
+
|
|
233
|
+
// computeTangents requires an index buffer
|
|
234
|
+
if ( geometry.index === null ) {
|
|
235
|
+
|
|
236
|
+
geometry = mergeVertices( geometry );
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
geometry.computeTangents();
|
|
241
|
+
|
|
242
|
+
} else {
|
|
243
|
+
|
|
244
|
+
const vertCount = geometry.attributes.position.count;
|
|
245
|
+
geometry.setAttribute( 'tangent', new BufferAttribute( new Float32Array( vertCount * 4 ), 4, false ) );
|
|
246
|
+
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if ( ! geometry.index ) {
|
|
252
|
+
|
|
253
|
+
// TODO: compute a typed array
|
|
254
|
+
const indexCount = geometry.attributes.position.count;
|
|
255
|
+
const array = new Array( indexCount );
|
|
256
|
+
for ( let i = 0; i < indexCount; i ++ ) {
|
|
257
|
+
|
|
258
|
+
array[ i ] = i;
|
|
259
|
+
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
geometry.setIndex( array );
|
|
263
|
+
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function mergeMeshes( meshes, options = {} ) {
|
|
269
|
+
|
|
270
|
+
options = { attributes: null, cloneGeometry: true, ...options };
|
|
271
|
+
|
|
272
|
+
const transformedGeometry = [];
|
|
273
|
+
const materialSet = new Set();
|
|
274
|
+
for ( let i = 0, l = meshes.length; i < l; i ++ ) {
|
|
275
|
+
|
|
276
|
+
// save any materials
|
|
277
|
+
const mesh = meshes[ i ];
|
|
278
|
+
if ( mesh.visible === false ) continue;
|
|
279
|
+
|
|
280
|
+
if ( Array.isArray( mesh.material ) ) {
|
|
281
|
+
|
|
282
|
+
mesh.material.forEach( m => materialSet.add( m ) );
|
|
283
|
+
|
|
284
|
+
} else {
|
|
285
|
+
|
|
286
|
+
materialSet.add( mesh.material );
|
|
287
|
+
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const materials = Array.from( materialSet );
|
|
293
|
+
for ( let i = 0, l = meshes.length; i < l; i ++ ) {
|
|
294
|
+
|
|
295
|
+
// ensure the matrix world is up to date
|
|
296
|
+
const mesh = meshes[ i ];
|
|
297
|
+
if ( mesh.visible === false ) continue;
|
|
298
|
+
|
|
299
|
+
mesh.updateMatrixWorld();
|
|
300
|
+
|
|
301
|
+
// apply the matrix world to the geometry
|
|
302
|
+
const originalGeometry = meshes[ i ].geometry;
|
|
303
|
+
let geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
|
|
304
|
+
geometry.applyMatrix4( mesh.matrixWorld );
|
|
305
|
+
|
|
306
|
+
// ensure our geometry has common attributes
|
|
307
|
+
setCommonAttributes( geometry, {
|
|
308
|
+
attributes: options.attributes,
|
|
309
|
+
normalMapRequired: ! ! mesh.material.normalMap,
|
|
310
|
+
} );
|
|
311
|
+
trimToAttributes( geometry, options.attributes );
|
|
312
|
+
|
|
313
|
+
// create the material index attribute
|
|
314
|
+
const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
|
|
315
|
+
geometry.setAttribute( 'materialIndex', materialIndexAttribute );
|
|
316
|
+
|
|
317
|
+
transformedGeometry.push( geometry );
|
|
318
|
+
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const textureSet = new Set();
|
|
322
|
+
materials.forEach( material => {
|
|
323
|
+
|
|
324
|
+
for ( const key in material ) {
|
|
325
|
+
|
|
326
|
+
const value = material[ key ];
|
|
327
|
+
if ( value && value.isTexture ) {
|
|
328
|
+
|
|
329
|
+
textureSet.add( value );
|
|
330
|
+
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
} );
|
|
336
|
+
|
|
337
|
+
const geometry = mergeBufferGeometries( transformedGeometry, false );
|
|
338
|
+
const textures = Array.from( textureSet );
|
|
339
|
+
return { geometry, materials, textures };
|
|
340
|
+
|
|
314
341
|
}
|
|
315
342
|
|
|
316
343
|
class PathTracingSceneGenerator {
|
|
317
344
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
this.bvhGenerator = new GenerateMeshBVHWorker();
|
|
321
|
-
|
|
322
|
-
}
|
|
345
|
+
prepScene( scene ) {
|
|
323
346
|
|
|
324
|
-
async generate( scene, options = {} ) {
|
|
325
|
-
|
|
326
|
-
const { bvhGenerator } = this;
|
|
327
347
|
const meshes = [];
|
|
328
|
-
|
|
329
348
|
scene.traverse( c => {
|
|
330
349
|
|
|
331
|
-
if ( c.isMesh ) {
|
|
350
|
+
if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
|
|
351
|
+
|
|
352
|
+
const generator = new StaticGeometryGenerator( c );
|
|
353
|
+
generator.applyWorldTransforms = false;
|
|
354
|
+
const mesh = new Mesh(
|
|
355
|
+
generator.generate(),
|
|
356
|
+
c.material,
|
|
357
|
+
);
|
|
358
|
+
mesh.matrixWorld.copy( c.matrixWorld );
|
|
359
|
+
mesh.matrix.copy( c.matrixWorld );
|
|
360
|
+
mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
|
|
361
|
+
meshes.push( mesh );
|
|
362
|
+
|
|
363
|
+
} else if ( c.isMesh ) {
|
|
332
364
|
|
|
333
365
|
meshes.push( c );
|
|
334
366
|
|
|
@@ -336,24 +368,127 @@ class PathTracingSceneGenerator {
|
|
|
336
368
|
|
|
337
369
|
} );
|
|
338
370
|
|
|
339
|
-
|
|
340
|
-
|
|
371
|
+
return mergeMeshes( meshes, {
|
|
372
|
+
attributes: [ 'position', 'normal', 'tangent', 'uv' ],
|
|
373
|
+
} );
|
|
341
374
|
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
generate( scene, options = {} ) {
|
|
378
|
+
|
|
379
|
+
const { materials, textures, geometry } = this.prepScene( scene );
|
|
380
|
+
const bvhOptions = { strategy: SAH, ...options, maxLeafTris: 1 };
|
|
342
381
|
return {
|
|
343
382
|
scene,
|
|
344
383
|
materials,
|
|
345
384
|
textures,
|
|
346
|
-
bvh:
|
|
385
|
+
bvh: new MeshBVH( geometry, bvhOptions ),
|
|
347
386
|
};
|
|
348
387
|
|
|
349
388
|
}
|
|
350
389
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
class DynamicPathTracingSceneGenerator {
|
|
393
|
+
|
|
394
|
+
get initialized() {
|
|
395
|
+
|
|
396
|
+
return Boolean( this.bvh );
|
|
397
|
+
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
constructor( scene ) {
|
|
401
|
+
|
|
402
|
+
this.scene = scene;
|
|
403
|
+
this.bvh = null;
|
|
404
|
+
this.geometry = new BufferGeometry();
|
|
405
|
+
this.materials = null;
|
|
406
|
+
this.textures = null;
|
|
407
|
+
this.staticGeometryGenerator = new StaticGeometryGenerator( scene );
|
|
408
|
+
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
reset() {
|
|
412
|
+
|
|
413
|
+
this.geometry.dispose();
|
|
414
|
+
this.geometry = new BufferGeometry();
|
|
415
|
+
this.materials = null;
|
|
416
|
+
this.textures = null;
|
|
417
|
+
this.staticGeometryGenerator = new StaticGeometryGenerator( this.scene );
|
|
418
|
+
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
dispose() {}
|
|
422
|
+
|
|
423
|
+
generate() {
|
|
424
|
+
|
|
425
|
+
const { scene, staticGeometryGenerator, geometry } = this;
|
|
426
|
+
if ( this.bvh === null ) {
|
|
427
|
+
|
|
428
|
+
const attributes = [ 'position', 'normal', 'tangent', 'uv' ];
|
|
429
|
+
scene.traverse( c => {
|
|
430
|
+
|
|
431
|
+
if ( c.isMesh ) {
|
|
432
|
+
|
|
433
|
+
const normalMapRequired = ! ! c.material.normalMap;
|
|
434
|
+
setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
|
|
435
|
+
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
} );
|
|
439
|
+
|
|
440
|
+
const textureSet = new Set();
|
|
441
|
+
const materials = staticGeometryGenerator.getMaterials();
|
|
442
|
+
materials.forEach( material => {
|
|
443
|
+
|
|
444
|
+
for ( const key in material ) {
|
|
445
|
+
|
|
446
|
+
const value = material[ key ];
|
|
447
|
+
if ( value && value.isTexture ) {
|
|
448
|
+
|
|
449
|
+
textureSet.add( value );
|
|
450
|
+
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
} );
|
|
456
|
+
|
|
457
|
+
staticGeometryGenerator.attributes = attributes;
|
|
458
|
+
staticGeometryGenerator.generate( geometry );
|
|
459
|
+
|
|
460
|
+
const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, materials, materials );
|
|
461
|
+
geometry.setAttribute( 'materialIndex', materialIndexAttribute );
|
|
462
|
+
geometry.clearGroups();
|
|
463
|
+
|
|
464
|
+
this.bvh = new MeshBVH( geometry );
|
|
465
|
+
this.materials = materials;
|
|
466
|
+
this.textures = Array.from( textureSet );
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
bvh: this.bvh,
|
|
470
|
+
materials: this.materials,
|
|
471
|
+
textures: this.textures,
|
|
472
|
+
scene,
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
} else {
|
|
476
|
+
|
|
477
|
+
const { bvh } = this;
|
|
478
|
+
staticGeometryGenerator.generate( geometry );
|
|
479
|
+
bvh.refit();
|
|
480
|
+
return {
|
|
481
|
+
bvh: this.bvh,
|
|
482
|
+
materials: this.materials,
|
|
483
|
+
textures: this.textures,
|
|
484
|
+
scene,
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
|
|
357
492
|
}
|
|
358
493
|
|
|
359
494
|
// https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
|
|
@@ -613,6 +748,33 @@ class MaterialReducer {
|
|
|
613
748
|
|
|
614
749
|
}
|
|
615
750
|
|
|
751
|
+
class PhysicalCamera extends PerspectiveCamera {
|
|
752
|
+
|
|
753
|
+
set bokehSize( size ) {
|
|
754
|
+
|
|
755
|
+
this.fStop = this.getFocalLength() / size;
|
|
756
|
+
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
get bokehSize() {
|
|
760
|
+
|
|
761
|
+
return this.getFocalLength() / this.fStop;
|
|
762
|
+
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
constructor( ...args ) {
|
|
766
|
+
|
|
767
|
+
super( ...args );
|
|
768
|
+
this.fStop = 1.4;
|
|
769
|
+
this.apertureBlades = 0;
|
|
770
|
+
this.apertureRotation = 0;
|
|
771
|
+
this.focusDistance = 25;
|
|
772
|
+
this.anamorphicRatio = 1;
|
|
773
|
+
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
}
|
|
777
|
+
|
|
616
778
|
class MaterialStructUniform {
|
|
617
779
|
|
|
618
780
|
constructor() {
|
|
@@ -646,6 +808,8 @@ class MaterialStructUniform {
|
|
|
646
808
|
this.opacity = 1.0;
|
|
647
809
|
this.alphaTest = 0.0;
|
|
648
810
|
|
|
811
|
+
this.side = 0;
|
|
812
|
+
|
|
649
813
|
// TODO: Clearcoat
|
|
650
814
|
|
|
651
815
|
// TODO: Sheen
|
|
@@ -705,6 +869,24 @@ class MaterialStructUniform {
|
|
|
705
869
|
|
|
706
870
|
}
|
|
707
871
|
|
|
872
|
+
setSide( side ) {
|
|
873
|
+
|
|
874
|
+
switch ( side ) {
|
|
875
|
+
|
|
876
|
+
case DoubleSide:
|
|
877
|
+
this.side = 0;
|
|
878
|
+
break;
|
|
879
|
+
case FrontSide:
|
|
880
|
+
this.side = 1;
|
|
881
|
+
break;
|
|
882
|
+
case BackSide:
|
|
883
|
+
this.side = - 1;
|
|
884
|
+
break;
|
|
885
|
+
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
}
|
|
889
|
+
|
|
708
890
|
}
|
|
709
891
|
|
|
710
892
|
class MaterialStructArrayUniform extends Array {
|
|
@@ -850,6 +1032,16 @@ class MaterialBase extends ShaderMaterial {
|
|
|
850
1032
|
|
|
851
1033
|
const shaderMaterialStructs = /* glsl */ `
|
|
852
1034
|
|
|
1035
|
+
struct PhysicalCamera {
|
|
1036
|
+
|
|
1037
|
+
float focusDistance;
|
|
1038
|
+
float anamorphicRatio;
|
|
1039
|
+
float bokehSize;
|
|
1040
|
+
int apertureBlades;
|
|
1041
|
+
float apertureRotation;
|
|
1042
|
+
|
|
1043
|
+
};
|
|
1044
|
+
|
|
853
1045
|
struct Material {
|
|
854
1046
|
|
|
855
1047
|
vec3 color;
|
|
@@ -875,6 +1067,8 @@ const shaderMaterialStructs = /* glsl */ `
|
|
|
875
1067
|
float opacity;
|
|
876
1068
|
float alphaTest;
|
|
877
1069
|
|
|
1070
|
+
float side;
|
|
1071
|
+
|
|
878
1072
|
};
|
|
879
1073
|
|
|
880
1074
|
`;
|
|
@@ -1419,6 +1613,7 @@ const shaderUtils = /* glsl */`
|
|
|
1419
1613
|
|
|
1420
1614
|
}
|
|
1421
1615
|
|
|
1616
|
+
// returns [ 0, 1 ]
|
|
1422
1617
|
float rand() {
|
|
1423
1618
|
|
|
1424
1619
|
pcg4d(s0);
|
|
@@ -1458,8 +1653,94 @@ const shaderUtils = /* glsl */`
|
|
|
1458
1653
|
return vec3( f * cos( t ), f * sin( t ), u );
|
|
1459
1654
|
|
|
1460
1655
|
}
|
|
1656
|
+
|
|
1657
|
+
vec2 triangleSample( vec2 a, vec2 b, vec2 c ) {
|
|
1658
|
+
|
|
1659
|
+
// get the edges of the triangle and the diagonal across the
|
|
1660
|
+
// center of the parallelogram
|
|
1661
|
+
vec2 e1 = a - b;
|
|
1662
|
+
vec2 e2 = c - b;
|
|
1663
|
+
vec2 diag = normalize( e1 + e2 );
|
|
1664
|
+
|
|
1665
|
+
// pick a random point in the parallelogram
|
|
1666
|
+
vec2 r = rand2();
|
|
1667
|
+
if ( r.x + r.y > 1.0 ) {
|
|
1668
|
+
|
|
1669
|
+
r = vec2( 1.0 ) - r;
|
|
1670
|
+
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
return e1 * r.x + e2 * r.y;
|
|
1674
|
+
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// samples an aperture shape with the given number of sides. 0 means circle
|
|
1678
|
+
vec2 sampleAperture( int blades ) {
|
|
1679
|
+
|
|
1680
|
+
if ( blades == 0 ) {
|
|
1681
|
+
|
|
1682
|
+
vec2 r = rand2();
|
|
1683
|
+
float angle = 2.0 * PI * r.x;
|
|
1684
|
+
float radius = sqrt( rand() );
|
|
1685
|
+
return vec2( cos( angle ), sin( angle ) ) * radius;
|
|
1686
|
+
|
|
1687
|
+
} else {
|
|
1688
|
+
|
|
1689
|
+
blades = max( blades, 3 );
|
|
1690
|
+
|
|
1691
|
+
vec3 r = rand3();
|
|
1692
|
+
float anglePerSegment = 2.0 * PI / float( blades );
|
|
1693
|
+
float segment = floor( float( blades ) * r.x );
|
|
1694
|
+
|
|
1695
|
+
float angle1 = anglePerSegment * segment;
|
|
1696
|
+
float angle2 = angle1 + anglePerSegment;
|
|
1697
|
+
vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
|
|
1698
|
+
vec2 b = vec2( 0.0, 0.0 );
|
|
1699
|
+
vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
|
|
1700
|
+
|
|
1701
|
+
return triangleSample( a, b, c );
|
|
1702
|
+
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
}
|
|
1461
1706
|
`;
|
|
1462
1707
|
|
|
1708
|
+
class PhysicalCameraUniform {
|
|
1709
|
+
|
|
1710
|
+
constructor() {
|
|
1711
|
+
|
|
1712
|
+
this.bokehSize = 0;
|
|
1713
|
+
this.apertureBlades = 0;
|
|
1714
|
+
this.apertureRotation = 0;
|
|
1715
|
+
this.focusDistance = 10;
|
|
1716
|
+
this.anamorphicRatio = 1;
|
|
1717
|
+
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
updateFrom( camera ) {
|
|
1721
|
+
|
|
1722
|
+
if ( camera instanceof PhysicalCamera ) {
|
|
1723
|
+
|
|
1724
|
+
this.bokehSize = camera.bokehSize;
|
|
1725
|
+
this.apertureBlades = camera.apertureBlades;
|
|
1726
|
+
this.apertureRotation = camera.apertureRotation;
|
|
1727
|
+
this.focusDistance = camera.focusDistance;
|
|
1728
|
+
this.anamorphicRatio = camera.anamorphicRatio;
|
|
1729
|
+
|
|
1730
|
+
} else {
|
|
1731
|
+
|
|
1732
|
+
this.bokehSize = 0;
|
|
1733
|
+
this.apertureRotation = 0;
|
|
1734
|
+
this.apertureBlades = 0;
|
|
1735
|
+
this.focusDistance = 10;
|
|
1736
|
+
this.anamorphicRatio = 1;
|
|
1737
|
+
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1463
1744
|
class PhysicalPathTracingMaterial extends MaterialBase {
|
|
1464
1745
|
|
|
1465
1746
|
// three.js relies on this field to add env map functions and defines
|
|
@@ -1477,13 +1758,16 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
1477
1758
|
depthWrite: false,
|
|
1478
1759
|
|
|
1479
1760
|
defines: {
|
|
1480
|
-
|
|
1761
|
+
DOF_SUPPORT: 1,
|
|
1481
1762
|
TRANSPARENT_TRAVERSALS: 5,
|
|
1482
1763
|
MATERIAL_LENGTH: 0,
|
|
1483
1764
|
GRADIENT_BG: 0,
|
|
1484
1765
|
},
|
|
1485
1766
|
|
|
1486
1767
|
uniforms: {
|
|
1768
|
+
bounces: { value: 3 },
|
|
1769
|
+
physicalCamera: { value: new PhysicalCameraUniform() },
|
|
1770
|
+
|
|
1487
1771
|
bvh: { value: new MeshBVHUniformStruct() },
|
|
1488
1772
|
normalAttribute: { value: new FloatVertexAttributeTexture() },
|
|
1489
1773
|
tangentAttribute: { value: new FloatVertexAttributeTexture() },
|
|
@@ -1560,6 +1844,14 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
1560
1844
|
|
|
1561
1845
|
#endif
|
|
1562
1846
|
|
|
1847
|
+
#if DOF_SUPPORT
|
|
1848
|
+
|
|
1849
|
+
uniform PhysicalCamera physicalCamera;
|
|
1850
|
+
|
|
1851
|
+
#endif
|
|
1852
|
+
|
|
1853
|
+
uniform int bounces;
|
|
1854
|
+
|
|
1563
1855
|
uniform mat4 cameraWorldMatrix;
|
|
1564
1856
|
uniform mat4 invProjectionMatrix;
|
|
1565
1857
|
uniform sampler2D normalAttribute;
|
|
@@ -1584,6 +1876,30 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
1584
1876
|
vec3 rayOrigin, rayDirection;
|
|
1585
1877
|
ndcToCameraRay( ndc, cameraWorldMatrix, invProjectionMatrix, rayOrigin, rayDirection );
|
|
1586
1878
|
|
|
1879
|
+
#if DOF_SUPPORT
|
|
1880
|
+
|
|
1881
|
+
// depth of field
|
|
1882
|
+
vec3 focalPoint = rayOrigin + normalize( rayDirection ) * physicalCamera.focusDistance;
|
|
1883
|
+
|
|
1884
|
+
// get the aperture sample
|
|
1885
|
+
vec2 apertureSample = sampleAperture( physicalCamera.apertureBlades ) * physicalCamera.bokehSize * 0.5 * 1e-3;
|
|
1886
|
+
|
|
1887
|
+
// rotate the aperture shape
|
|
1888
|
+
float ac = cos( physicalCamera.apertureRotation );
|
|
1889
|
+
float as = sin( physicalCamera.apertureRotation );
|
|
1890
|
+
apertureSample = vec2(
|
|
1891
|
+
apertureSample.x * ac - apertureSample.y * as,
|
|
1892
|
+
apertureSample.x * as + apertureSample.y * ac
|
|
1893
|
+
);
|
|
1894
|
+
apertureSample.x *= saturate( physicalCamera.anamorphicRatio );
|
|
1895
|
+
apertureSample.y *= saturate( 1.0 / physicalCamera.anamorphicRatio );
|
|
1896
|
+
|
|
1897
|
+
#endif
|
|
1898
|
+
|
|
1899
|
+
// create the new ray
|
|
1900
|
+
rayOrigin += ( cameraWorldMatrix * vec4( apertureSample, 0.0, 0.0 ) ).xyz;
|
|
1901
|
+
rayDirection = focalPoint - rayOrigin;
|
|
1902
|
+
|
|
1587
1903
|
// Lambertian render
|
|
1588
1904
|
gl_FragColor = vec4( 0.0 );
|
|
1589
1905
|
|
|
@@ -1598,7 +1914,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
1598
1914
|
float accumulatedRoughness = 0.0;
|
|
1599
1915
|
int i;
|
|
1600
1916
|
int transparentTraversals = TRANSPARENT_TRAVERSALS;
|
|
1601
|
-
for ( i = 0; i <
|
|
1917
|
+
for ( i = 0; i < bounces; i ++ ) {
|
|
1602
1918
|
|
|
1603
1919
|
if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
|
|
1604
1920
|
|
|
@@ -1649,12 +1965,23 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
1649
1965
|
|
|
1650
1966
|
}
|
|
1651
1967
|
|
|
1652
|
-
// possibly skip this sample if it's transparent
|
|
1653
|
-
//
|
|
1968
|
+
// possibly skip this sample if it's transparent, alpha test is enabled, or we hit the wrong material side
|
|
1969
|
+
// and it's single sided.
|
|
1970
|
+
// - alpha test is disabled when it === 0
|
|
1971
|
+
// - the material sidedness test is complicated because we want light to pass through the back side but still
|
|
1972
|
+
// be able to see the front side. This boolean checks if the side we hit is the front side on the first ray
|
|
1973
|
+
// and we're rendering the other then we skip it. Do the opposite on subsequent bounces to get incoming light.
|
|
1654
1974
|
float alphaTest = material.alphaTest;
|
|
1655
1975
|
bool useAlphaTest = alphaTest != 0.0;
|
|
1976
|
+
bool isFirstHit = i == 0;
|
|
1656
1977
|
if (
|
|
1657
|
-
|
|
1978
|
+
// material sidedness
|
|
1979
|
+
material.side != 0.0 && ( side != material.side ) == isFirstHit
|
|
1980
|
+
|
|
1981
|
+
// alpha test
|
|
1982
|
+
|| useAlphaTest && albedo.a < alphaTest
|
|
1983
|
+
|
|
1984
|
+
// opacity
|
|
1658
1985
|
|| ! useAlphaTest && albedo.a < rand()
|
|
1659
1986
|
) {
|
|
1660
1987
|
|
|
@@ -1738,7 +2065,6 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
1738
2065
|
SurfaceRec surfaceRec;
|
|
1739
2066
|
surfaceRec.normal = normal;
|
|
1740
2067
|
surfaceRec.faceNormal = faceNormal;
|
|
1741
|
-
surfaceRec.frontFace = side == 1.0;
|
|
1742
2068
|
surfaceRec.transmission = transmission;
|
|
1743
2069
|
surfaceRec.ior = material.ior;
|
|
1744
2070
|
surfaceRec.emission = emission;
|
|
@@ -1746,6 +2072,10 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
1746
2072
|
surfaceRec.color = albedo.rgb;
|
|
1747
2073
|
surfaceRec.roughness = roughness;
|
|
1748
2074
|
|
|
2075
|
+
// frontFace is used to determine transmissive properties and PDF. If no transmission is used
|
|
2076
|
+
// then we can just always assume this is a front face.
|
|
2077
|
+
surfaceRec.frontFace = side == 1.0 || transmission == 0.0;
|
|
2078
|
+
|
|
1749
2079
|
// Compute the filtered roughness value to use during specular reflection computations. A minimum
|
|
1750
2080
|
// value of 1e-6 is needed because the GGX functions do not work with a roughness value of 0 and
|
|
1751
2081
|
// the accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
|
|
@@ -1821,5 +2151,5 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
1821
2151
|
|
|
1822
2152
|
// core
|
|
1823
2153
|
|
|
1824
|
-
export { MaterialBase, MaterialReducer, MaterialStructArrayUniform, MaterialStructUniform, PathTracingRenderer, PathTracingSceneGenerator, PhysicalPathTracingMaterial, RenderTarget2DArray, mergeMeshes, shaderMaterialSampling, shaderMaterialStructs, shaderUtils };
|
|
2154
|
+
export { DynamicPathTracingSceneGenerator, MaterialBase, MaterialReducer, MaterialStructArrayUniform, MaterialStructUniform, PathTracingRenderer, PathTracingSceneGenerator, PhysicalCamera, PhysicalPathTracingMaterial, RenderTarget2DArray, getGroupMaterialIndicesAttribute, mergeMeshes, setCommonAttributes, shaderMaterialSampling, shaderMaterialStructs, shaderUtils, trimToAttributes };
|
|
1825
2155
|
//# sourceMappingURL=index.module.js.map
|