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,1840 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three'), require('three/examples/jsm/postprocessing/Pass.js'), require('three-mesh-bvh'), require('three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js'), require('three/examples/jsm/utils/BufferGeometryUtils.js')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'three', 'three/examples/jsm/postprocessing/Pass.js', 'three-mesh-bvh', 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js', 'three/examples/jsm/utils/BufferGeometryUtils.js'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ThreePathTracer = global.ThreePathTracer || {}, global.THREE, global.THREE, global.MeshBVHLib, global.MeshBVHLib, global.THREE));
|
|
5
|
+
})(this, (function (exports, three, Pass_js, threeMeshBvh, GenerateMeshBVHWorker_js, BufferGeometryUtils_js) { 'use strict';
|
|
6
|
+
|
|
7
|
+
function* renderTask() {
|
|
8
|
+
|
|
9
|
+
const { _fsQuad, _renderer, target, camera, material } = this;
|
|
10
|
+
while ( true ) {
|
|
11
|
+
|
|
12
|
+
material.opacity = 1 / ( this.samples + 1 );
|
|
13
|
+
material.seed ++;
|
|
14
|
+
|
|
15
|
+
const w = target.width;
|
|
16
|
+
const h = target.height;
|
|
17
|
+
camera.setViewOffset(
|
|
18
|
+
w, h,
|
|
19
|
+
Math.random() - 0.5, Math.random() - 0.5,
|
|
20
|
+
w, h,
|
|
21
|
+
);
|
|
22
|
+
camera.updateProjectionMatrix();
|
|
23
|
+
|
|
24
|
+
const tx = this.tiles.x || 1;
|
|
25
|
+
const ty = this.tiles.y || 1;
|
|
26
|
+
const totalTiles = tx * ty;
|
|
27
|
+
const dprInv = ( 1 / _renderer.getPixelRatio() );
|
|
28
|
+
for ( let y = 0; y < ty; y ++ ) {
|
|
29
|
+
|
|
30
|
+
for ( let x = 0; x < tx; x ++ ) {
|
|
31
|
+
|
|
32
|
+
material.cameraWorldMatrix.copy( camera.matrixWorld );
|
|
33
|
+
material.invProjectionMatrix.copy( camera.projectionMatrixInverse );
|
|
34
|
+
|
|
35
|
+
const ogRenderTarget = _renderer.getRenderTarget();
|
|
36
|
+
const ogAutoClear = _renderer.autoClear;
|
|
37
|
+
|
|
38
|
+
// three.js renderer takes values relative to the current pixel ratio
|
|
39
|
+
_renderer.setRenderTarget( target );
|
|
40
|
+
_renderer.setScissorTest( true );
|
|
41
|
+
_renderer.setScissor(
|
|
42
|
+
dprInv * Math.ceil( x * w / tx ),
|
|
43
|
+
dprInv * Math.ceil( ( ty - y - 1 ) * h / ty ),
|
|
44
|
+
dprInv * Math.ceil( w / tx ),
|
|
45
|
+
dprInv * Math.ceil( h / ty ) );
|
|
46
|
+
_renderer.autoClear = false;
|
|
47
|
+
_fsQuad.render( _renderer );
|
|
48
|
+
|
|
49
|
+
_renderer.setScissorTest( false );
|
|
50
|
+
_renderer.setRenderTarget( ogRenderTarget );
|
|
51
|
+
_renderer.autoClear = ogAutoClear;
|
|
52
|
+
|
|
53
|
+
this.samples += ( 1 / totalTiles );
|
|
54
|
+
|
|
55
|
+
yield;
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.samples = Math.round( this.samples );
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const ogClearColor = new three.Color();
|
|
68
|
+
class PathTracingRenderer {
|
|
69
|
+
|
|
70
|
+
get material() {
|
|
71
|
+
|
|
72
|
+
return this._fsQuad.material;
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
set material( v ) {
|
|
77
|
+
|
|
78
|
+
this._fsQuad.material = v;
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
constructor( renderer ) {
|
|
83
|
+
|
|
84
|
+
this.camera = null;
|
|
85
|
+
this.tiles = new three.Vector2( 1, 1 );
|
|
86
|
+
this.target = new three.WebGLRenderTarget( 1, 1, {
|
|
87
|
+
format: three.RGBAFormat,
|
|
88
|
+
type: three.FloatType,
|
|
89
|
+
} );
|
|
90
|
+
this.samples = 0;
|
|
91
|
+
this.stableNoise = false;
|
|
92
|
+
this._renderer = renderer;
|
|
93
|
+
this._fsQuad = new Pass_js.FullScreenQuad( null );
|
|
94
|
+
this._task = null;
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
setSize( w, h ) {
|
|
99
|
+
|
|
100
|
+
this.target.setSize( w, h );
|
|
101
|
+
this.reset();
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
reset() {
|
|
106
|
+
|
|
107
|
+
const renderer = this._renderer;
|
|
108
|
+
const target = this.target;
|
|
109
|
+
const ogRenderTarget = renderer.getRenderTarget();
|
|
110
|
+
const ogClearAlpha = renderer.getClearAlpha();
|
|
111
|
+
renderer.getClearColor( ogClearColor );
|
|
112
|
+
|
|
113
|
+
renderer.setRenderTarget( target );
|
|
114
|
+
renderer.setClearColor( 0, 0 );
|
|
115
|
+
renderer.clearColor();
|
|
116
|
+
|
|
117
|
+
renderer.setClearColor( ogClearColor, ogClearAlpha );
|
|
118
|
+
renderer.setRenderTarget( ogRenderTarget );
|
|
119
|
+
|
|
120
|
+
this.samples = 0;
|
|
121
|
+
this._task = null;
|
|
122
|
+
|
|
123
|
+
if ( this.stableNoise ) {
|
|
124
|
+
|
|
125
|
+
this.material.seed = 0;
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
update() {
|
|
132
|
+
|
|
133
|
+
if ( ! this._task ) {
|
|
134
|
+
|
|
135
|
+
this._task = renderTask.call( this );
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this._task.next();
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
|
|
146
|
+
|
|
147
|
+
if ( ! Array.isArray( materials ) ) {
|
|
148
|
+
|
|
149
|
+
materials = [ materials ];
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const vertCount = geometry.attributes.position.count;
|
|
154
|
+
const materialArray = new Uint8Array( vertCount );
|
|
155
|
+
let groups = geometry.groups;
|
|
156
|
+
if ( groups.length === 0 ) {
|
|
157
|
+
|
|
158
|
+
groups = [ { count: vertCount, start: 0, materialIndex: 0 } ];
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for ( let i = 0; i < groups.length; i ++ ) {
|
|
163
|
+
|
|
164
|
+
const group = groups[ i ];
|
|
165
|
+
const { count, start } = group;
|
|
166
|
+
const endCount = Math.min( count, vertCount - start );
|
|
167
|
+
const mat = materials[ group.materialIndex ];
|
|
168
|
+
const materialIndex = allMaterials.indexOf( mat );
|
|
169
|
+
|
|
170
|
+
for ( let j = 0; j < endCount; j ++ ) {
|
|
171
|
+
|
|
172
|
+
materialArray[ start + j ] = materialIndex;
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return new three.BufferAttribute( materialArray, 1, false );
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function mergeMeshes( meshes, options = {} ) {
|
|
183
|
+
|
|
184
|
+
options = { attributes: null, cloneGeometry: true, ...options };
|
|
185
|
+
|
|
186
|
+
const transformedGeometry = [];
|
|
187
|
+
const materialSet = new Set();
|
|
188
|
+
for ( let i = 0, l = meshes.length; i < l; i ++ ) {
|
|
189
|
+
|
|
190
|
+
// save any materials
|
|
191
|
+
const mesh = meshes[ i ];
|
|
192
|
+
if ( mesh.visible === false ) continue;
|
|
193
|
+
|
|
194
|
+
if ( Array.isArray( mesh.material ) ) {
|
|
195
|
+
|
|
196
|
+
mesh.material.forEach( m => materialSet.add( m ) );
|
|
197
|
+
|
|
198
|
+
} else {
|
|
199
|
+
|
|
200
|
+
materialSet.add( mesh.material );
|
|
201
|
+
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const materials = Array.from( materialSet );
|
|
207
|
+
for ( let i = 0, l = meshes.length; i < l; i ++ ) {
|
|
208
|
+
|
|
209
|
+
// ensure the matrix world is up to date
|
|
210
|
+
const mesh = meshes[ i ];
|
|
211
|
+
if ( mesh.visible === false ) continue;
|
|
212
|
+
|
|
213
|
+
mesh.updateMatrixWorld();
|
|
214
|
+
|
|
215
|
+
// apply the matrix world to the geometry
|
|
216
|
+
const originalGeometry = meshes[ i ].geometry;
|
|
217
|
+
let geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
|
|
218
|
+
geometry.applyMatrix4( mesh.matrixWorld );
|
|
219
|
+
|
|
220
|
+
const attrs = options.attributes;
|
|
221
|
+
if ( ! geometry.attributes.normal && ( attrs && attrs.includes( 'normal' ) ) ) {
|
|
222
|
+
|
|
223
|
+
geometry.computeVertexNormals();
|
|
224
|
+
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if ( ! geometry.attributes.uv && ( attrs && attrs.includes( 'uv' ) ) ) {
|
|
228
|
+
|
|
229
|
+
const vertCount = geometry.attributes.position.count;
|
|
230
|
+
geometry.setAttribute( 'uv', new three.BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
|
|
231
|
+
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if ( ! geometry.attributes.tangent && ( attrs && attrs.includes( 'tangent' ) ) ) {
|
|
235
|
+
|
|
236
|
+
if ( mesh.material.normalMap ) {
|
|
237
|
+
|
|
238
|
+
// computeTangents requires an index buffer
|
|
239
|
+
if ( geometry.index === null ) {
|
|
240
|
+
|
|
241
|
+
geometry = BufferGeometryUtils_js.mergeVertices( geometry );
|
|
242
|
+
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
geometry.computeTangents();
|
|
246
|
+
|
|
247
|
+
} else {
|
|
248
|
+
|
|
249
|
+
const vertCount = geometry.attributes.position.count;
|
|
250
|
+
geometry.setAttribute( 'tangent', new three.BufferAttribute( new Float32Array( vertCount * 4 ), 4, false ) );
|
|
251
|
+
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if ( ! geometry.index ) {
|
|
257
|
+
|
|
258
|
+
// TODO: compute a typed array
|
|
259
|
+
const indexCount = geometry.attributes.position.count;
|
|
260
|
+
const array = new Array( indexCount );
|
|
261
|
+
for ( let i = 0; i < indexCount; i ++ ) {
|
|
262
|
+
|
|
263
|
+
array[ i ] = i;
|
|
264
|
+
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
geometry.setIndex( array );
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// trim any unneeded attributes
|
|
272
|
+
if ( options.attributes ) {
|
|
273
|
+
|
|
274
|
+
for ( const key in geometry.attributes ) {
|
|
275
|
+
|
|
276
|
+
if ( ! options.attributes.includes( key ) ) {
|
|
277
|
+
|
|
278
|
+
geometry.deleteAttribute( key );
|
|
279
|
+
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// create the material index attribute
|
|
287
|
+
const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
|
|
288
|
+
geometry.setAttribute( 'materialIndex', materialIndexAttribute );
|
|
289
|
+
|
|
290
|
+
transformedGeometry.push( geometry );
|
|
291
|
+
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const textureSet = new Set();
|
|
295
|
+
materials.forEach( material => {
|
|
296
|
+
|
|
297
|
+
for ( const key in material ) {
|
|
298
|
+
|
|
299
|
+
const value = material[ key ];
|
|
300
|
+
if ( value && value.isTexture ) {
|
|
301
|
+
|
|
302
|
+
textureSet.add( value );
|
|
303
|
+
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
} );
|
|
309
|
+
|
|
310
|
+
const geometry = BufferGeometryUtils_js.mergeBufferGeometries( transformedGeometry, false );
|
|
311
|
+
const textures = Array.from( textureSet );
|
|
312
|
+
return { geometry, materials, textures };
|
|
313
|
+
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
class PathTracingSceneGenerator {
|
|
317
|
+
|
|
318
|
+
constructor() {
|
|
319
|
+
|
|
320
|
+
this.bvhGenerator = new GenerateMeshBVHWorker_js.GenerateMeshBVHWorker();
|
|
321
|
+
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async generate( scene, options = {} ) {
|
|
325
|
+
|
|
326
|
+
const { bvhGenerator } = this;
|
|
327
|
+
const meshes = [];
|
|
328
|
+
|
|
329
|
+
scene.traverse( c => {
|
|
330
|
+
|
|
331
|
+
if ( c.isMesh ) {
|
|
332
|
+
|
|
333
|
+
meshes.push( c );
|
|
334
|
+
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
} );
|
|
338
|
+
|
|
339
|
+
const { geometry, materials, textures } = mergeMeshes( meshes, { attributes: [ 'position', 'normal', 'tangent', 'uv' ] } );
|
|
340
|
+
const bvhPromise = bvhGenerator.generate( geometry, { strategy: threeMeshBvh.SAH, ...options, maxLeafTris: 1 } );
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
scene,
|
|
344
|
+
materials,
|
|
345
|
+
textures,
|
|
346
|
+
bvh: await bvhPromise,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
dispose() {
|
|
352
|
+
|
|
353
|
+
this.bvhGenerator.terminate();
|
|
354
|
+
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
|
|
360
|
+
|
|
361
|
+
function isTypedArray( arr ) {
|
|
362
|
+
|
|
363
|
+
return arr.buffer instanceof ArrayBuffer && 'BYTES_PER_ELEMENT' in arr;
|
|
364
|
+
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
class MaterialReducer {
|
|
368
|
+
|
|
369
|
+
constructor() {
|
|
370
|
+
|
|
371
|
+
const ignoreKeys = new Set();
|
|
372
|
+
ignoreKeys.add( 'uuid' );
|
|
373
|
+
|
|
374
|
+
this.ignoreKeys = ignoreKeys;
|
|
375
|
+
this.shareTextures = true;
|
|
376
|
+
this.textures = [];
|
|
377
|
+
this.materials = [];
|
|
378
|
+
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
areEqual( objectA, objectB ) {
|
|
382
|
+
|
|
383
|
+
const keySet = new Set();
|
|
384
|
+
const traverseSet = new Set();
|
|
385
|
+
const ignoreKeys = this.ignoreKeys;
|
|
386
|
+
|
|
387
|
+
const traverse = ( a, b ) => {
|
|
388
|
+
|
|
389
|
+
if ( a === b ) {
|
|
390
|
+
|
|
391
|
+
return true;
|
|
392
|
+
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if ( a && b && a instanceof Object && b instanceof Object ) {
|
|
396
|
+
|
|
397
|
+
if ( traverseSet.has( a ) || traverseSet.has( b ) ) {
|
|
398
|
+
|
|
399
|
+
throw new Error( 'MaterialReducer: Material is recursive.' );
|
|
400
|
+
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const aIsElement = a instanceof Element;
|
|
404
|
+
const bIsElement = b instanceof Element;
|
|
405
|
+
if ( aIsElement || bIsElement ) {
|
|
406
|
+
|
|
407
|
+
if ( aIsElement !== bIsElement || ! ( a instanceof Image ) || ! ( b instanceof Image ) ) {
|
|
408
|
+
|
|
409
|
+
return false;
|
|
410
|
+
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return a.src === b.src;
|
|
414
|
+
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const aIsImageBitmap = a instanceof ImageBitmap;
|
|
418
|
+
const bIsImageBitmap = b instanceof ImageBitmap;
|
|
419
|
+
if ( aIsImageBitmap || bIsImageBitmap ) {
|
|
420
|
+
|
|
421
|
+
return false;
|
|
422
|
+
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if ( a.equals ) {
|
|
426
|
+
|
|
427
|
+
return a.equals( b );
|
|
428
|
+
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const aIsTypedArray = isTypedArray( a );
|
|
432
|
+
const bIsTypedArray = isTypedArray( b );
|
|
433
|
+
if ( aIsTypedArray || bIsTypedArray ) {
|
|
434
|
+
|
|
435
|
+
if ( aIsTypedArray !== bIsTypedArray || a.constructor !== b.constructor || a.length !== b.length ) {
|
|
436
|
+
|
|
437
|
+
return false;
|
|
438
|
+
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
for ( let i = 0, l = a.length; i < l; i ++ ) {
|
|
442
|
+
|
|
443
|
+
if ( a[ i ] !== b[ i ] ) return false;
|
|
444
|
+
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return true;
|
|
448
|
+
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
traverseSet.add( a );
|
|
452
|
+
traverseSet.add( b );
|
|
453
|
+
|
|
454
|
+
keySet.clear();
|
|
455
|
+
for ( const key in a ) {
|
|
456
|
+
|
|
457
|
+
if ( ! a.hasOwnProperty( key ) || a[ key ] instanceof Function || ignoreKeys.has( key ) ) {
|
|
458
|
+
|
|
459
|
+
continue;
|
|
460
|
+
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
keySet.add( key );
|
|
464
|
+
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
for ( const key in b ) {
|
|
468
|
+
|
|
469
|
+
if ( ! b.hasOwnProperty( key ) || b[ key ] instanceof Function || ignoreKeys.has( key ) ) {
|
|
470
|
+
|
|
471
|
+
continue;
|
|
472
|
+
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
keySet.add( key );
|
|
476
|
+
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const keys = Array.from( keySet.values() );
|
|
480
|
+
let result = true;
|
|
481
|
+
for ( const i in keys ) {
|
|
482
|
+
|
|
483
|
+
const key = keys[ i ];
|
|
484
|
+
if ( ignoreKeys.has( key ) ) {
|
|
485
|
+
|
|
486
|
+
continue;
|
|
487
|
+
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
result = traverse( a[ key ], b[ key ] );
|
|
491
|
+
if ( ! result ) {
|
|
492
|
+
|
|
493
|
+
break;
|
|
494
|
+
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
traverseSet.delete( a );
|
|
500
|
+
traverseSet.delete( b );
|
|
501
|
+
return result;
|
|
502
|
+
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return false;
|
|
506
|
+
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
return traverse( objectA, objectB );
|
|
510
|
+
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
process( object ) {
|
|
514
|
+
|
|
515
|
+
const { textures, materials } = this;
|
|
516
|
+
let replaced = 0;
|
|
517
|
+
|
|
518
|
+
const processMaterial = material => {
|
|
519
|
+
|
|
520
|
+
// Check if another material matches this one
|
|
521
|
+
let foundMaterial = null;
|
|
522
|
+
for ( const i in materials ) {
|
|
523
|
+
|
|
524
|
+
const otherMaterial = materials[ i ];
|
|
525
|
+
if ( this.areEqual( material, otherMaterial ) ) {
|
|
526
|
+
|
|
527
|
+
foundMaterial = otherMaterial;
|
|
528
|
+
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if ( foundMaterial ) {
|
|
534
|
+
|
|
535
|
+
replaced ++;
|
|
536
|
+
return foundMaterial;
|
|
537
|
+
|
|
538
|
+
} else {
|
|
539
|
+
|
|
540
|
+
materials.push( material );
|
|
541
|
+
|
|
542
|
+
if ( this.shareTextures ) {
|
|
543
|
+
|
|
544
|
+
// See if there's another texture that matches the ones on this material
|
|
545
|
+
for ( const key in material ) {
|
|
546
|
+
|
|
547
|
+
if ( ! material.hasOwnProperty( key ) ) continue;
|
|
548
|
+
|
|
549
|
+
const value = material[ key ];
|
|
550
|
+
if ( value && value.isTexture && value.image instanceof Image ) {
|
|
551
|
+
|
|
552
|
+
let foundTexture = null;
|
|
553
|
+
for ( const i in textures ) {
|
|
554
|
+
|
|
555
|
+
const texture = textures[ i ];
|
|
556
|
+
if ( this.areEqual( texture, value ) ) {
|
|
557
|
+
|
|
558
|
+
foundTexture = texture;
|
|
559
|
+
break;
|
|
560
|
+
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if ( foundTexture ) {
|
|
566
|
+
|
|
567
|
+
material[ key ] = foundTexture;
|
|
568
|
+
|
|
569
|
+
} else {
|
|
570
|
+
|
|
571
|
+
textures.push( value );
|
|
572
|
+
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return material;
|
|
582
|
+
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
object.traverse( c => {
|
|
588
|
+
|
|
589
|
+
if ( c.isMesh && c.material ) {
|
|
590
|
+
|
|
591
|
+
const material = c.material;
|
|
592
|
+
if ( Array.isArray( material ) ) {
|
|
593
|
+
|
|
594
|
+
for ( let i = 0; i < material.length; i ++ ) {
|
|
595
|
+
|
|
596
|
+
material[ i ] = processMaterial( material[ i ] );
|
|
597
|
+
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
} else {
|
|
601
|
+
|
|
602
|
+
c.material = processMaterial( material );
|
|
603
|
+
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
} );
|
|
609
|
+
|
|
610
|
+
return { replaced, retained: materials.length };
|
|
611
|
+
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
class MaterialStructUniform {
|
|
617
|
+
|
|
618
|
+
constructor() {
|
|
619
|
+
|
|
620
|
+
this.init();
|
|
621
|
+
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
init() {
|
|
625
|
+
|
|
626
|
+
this.color = new three.Color( 0xffffff );
|
|
627
|
+
this.map = - 1;
|
|
628
|
+
|
|
629
|
+
this.metalness = 1.0;
|
|
630
|
+
this.metalnessMap = - 1;
|
|
631
|
+
|
|
632
|
+
this.roughness = 1.0;
|
|
633
|
+
this.roughnessMap = - 1;
|
|
634
|
+
|
|
635
|
+
this.ior = 1.0;
|
|
636
|
+
this.transmission = 0.0;
|
|
637
|
+
this.transmissionMap = - 1;
|
|
638
|
+
|
|
639
|
+
this.emissive = new three.Color( 0 );
|
|
640
|
+
this.emissiveIntensity = 1.0;
|
|
641
|
+
this.emissiveMap = - 1;
|
|
642
|
+
|
|
643
|
+
this.normalMap = - 1;
|
|
644
|
+
this.normalScale = new three.Vector2( 1, 1 );
|
|
645
|
+
|
|
646
|
+
this.opacity = 1.0;
|
|
647
|
+
this.alphaTest = 0.0;
|
|
648
|
+
|
|
649
|
+
// TODO: Clearcoat
|
|
650
|
+
|
|
651
|
+
// TODO: Sheen
|
|
652
|
+
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
updateFrom( material, textures = [] ) {
|
|
656
|
+
|
|
657
|
+
this.init();
|
|
658
|
+
|
|
659
|
+
// color
|
|
660
|
+
if ( 'color' in material ) this.color.copy( material.color );
|
|
661
|
+
else material.color.set( 0xffffff );
|
|
662
|
+
|
|
663
|
+
this.map = textures.indexOf( material.map );
|
|
664
|
+
|
|
665
|
+
// metalness
|
|
666
|
+
if ( 'metalness' in material ) this.metalness = material.metalness;
|
|
667
|
+
else this.metalness = 1.0;
|
|
668
|
+
|
|
669
|
+
this.metalnessMap = textures.indexOf( material.metalnessMap );
|
|
670
|
+
|
|
671
|
+
// roughness
|
|
672
|
+
if ( 'roughness' in material ) this.roughness = material.roughness;
|
|
673
|
+
else this.roughness = 1.0;
|
|
674
|
+
|
|
675
|
+
this.roughnessMap = textures.indexOf( material.roughnessMap );
|
|
676
|
+
|
|
677
|
+
// transmission
|
|
678
|
+
if ( 'ior' in material ) this.ior = material.ior;
|
|
679
|
+
else this.ior = 1.0;
|
|
680
|
+
|
|
681
|
+
if ( 'transmission' in material ) this.transmission = material.transmission;
|
|
682
|
+
else this.transmission = 0.0;
|
|
683
|
+
|
|
684
|
+
if ( 'transmissionMap' in material ) this.transmissionMap = textures.indexOf( material.transmissionMap );
|
|
685
|
+
|
|
686
|
+
// emission
|
|
687
|
+
if ( 'emissive' in material ) this.emissive.copy( material.emissive );
|
|
688
|
+
else this.emissive.set( 0 );
|
|
689
|
+
|
|
690
|
+
if ( 'emissiveIntensity' in material ) this.emissiveIntensity = material.emissiveIntensity;
|
|
691
|
+
else this.emissiveIntensity = 1.0;
|
|
692
|
+
|
|
693
|
+
this.emissiveMap = textures.indexOf( material.emissiveMap );
|
|
694
|
+
|
|
695
|
+
// normals
|
|
696
|
+
this.normalMap = textures.indexOf( material.normalMap );
|
|
697
|
+
if ( 'normalScale' in material ) this.normalScale.copy( material.normalScale );
|
|
698
|
+
else this.normalScale.set( 1, 1 );
|
|
699
|
+
|
|
700
|
+
// opacity
|
|
701
|
+
this.opacity = material.opacity;
|
|
702
|
+
|
|
703
|
+
// alpha test
|
|
704
|
+
this.alphaTest = material.alphaTest;
|
|
705
|
+
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
class MaterialStructArrayUniform extends Array {
|
|
711
|
+
|
|
712
|
+
updateFrom( materials, textures ) {
|
|
713
|
+
|
|
714
|
+
while ( this.length > materials.length ) this.pop();
|
|
715
|
+
while ( this.length < materials.length ) this.push( new MaterialStructUniform() );
|
|
716
|
+
|
|
717
|
+
for ( let i = 0, l = this.length; i < l; i ++ ) {
|
|
718
|
+
|
|
719
|
+
this[ i ].updateFrom( materials[ i ], textures );
|
|
720
|
+
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const prevColor = new three.Color();
|
|
728
|
+
class RenderTarget2DArray extends three.WebGLArrayRenderTarget {
|
|
729
|
+
|
|
730
|
+
constructor( ...args ) {
|
|
731
|
+
|
|
732
|
+
super( ...args );
|
|
733
|
+
|
|
734
|
+
const tex = this.texture;
|
|
735
|
+
tex.format = three.RGBAFormat;
|
|
736
|
+
tex.type = three.UnsignedByteType;
|
|
737
|
+
tex.minFilter = three.LinearFilter;
|
|
738
|
+
tex.magFilter = three.LinearFilter;
|
|
739
|
+
tex.wrapS = three.RepeatWrapping;
|
|
740
|
+
tex.wrapT = three.RepeatWrapping;
|
|
741
|
+
tex.setTextures = ( ...args ) => {
|
|
742
|
+
|
|
743
|
+
this.setTextures( ...args );
|
|
744
|
+
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
const fsQuad = new Pass_js.FullScreenQuad( new three.MeshBasicMaterial() );
|
|
748
|
+
this.fsQuad = fsQuad;
|
|
749
|
+
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
setTextures( renderer, width, height, textures ) {
|
|
753
|
+
|
|
754
|
+
// save previous renderer state
|
|
755
|
+
const prevRenderTarget = renderer.getRenderTarget();
|
|
756
|
+
const prevToneMapping = renderer.toneMapping;
|
|
757
|
+
const prevAlpha = renderer.getClearAlpha();
|
|
758
|
+
renderer.getClearColor( prevColor );
|
|
759
|
+
|
|
760
|
+
// resize the render target
|
|
761
|
+
const depth = textures.length;
|
|
762
|
+
this.setSize( width, height, depth );
|
|
763
|
+
renderer.setClearColor( 0, 0 );
|
|
764
|
+
renderer.toneMapping = three.NoToneMapping;
|
|
765
|
+
|
|
766
|
+
// render each texture into each layer of the target
|
|
767
|
+
const fsQuad = this.fsQuad;
|
|
768
|
+
for ( let i = 0, l = depth; i < l; i ++ ) {
|
|
769
|
+
|
|
770
|
+
const texture = textures[ i ];
|
|
771
|
+
fsQuad.material.map = texture;
|
|
772
|
+
fsQuad.material.transparent = true;
|
|
773
|
+
|
|
774
|
+
renderer.setRenderTarget( this, i );
|
|
775
|
+
fsQuad.render( renderer );
|
|
776
|
+
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// reset the renderer
|
|
780
|
+
fsQuad.material.map = null;
|
|
781
|
+
renderer.setClearColor( prevColor, prevAlpha );
|
|
782
|
+
renderer.setRenderTarget( prevRenderTarget );
|
|
783
|
+
renderer.toneMapping = prevToneMapping;
|
|
784
|
+
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
dispose() {
|
|
788
|
+
|
|
789
|
+
super.dispose();
|
|
790
|
+
this.fsQuad.dispose();
|
|
791
|
+
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
class MaterialBase extends three.ShaderMaterial {
|
|
797
|
+
|
|
798
|
+
constructor( shader ) {
|
|
799
|
+
|
|
800
|
+
super( shader );
|
|
801
|
+
|
|
802
|
+
for ( const key in this.uniforms ) {
|
|
803
|
+
|
|
804
|
+
Object.defineProperty( this, key, {
|
|
805
|
+
|
|
806
|
+
get() {
|
|
807
|
+
|
|
808
|
+
return this.uniforms[ key ].value;
|
|
809
|
+
|
|
810
|
+
},
|
|
811
|
+
|
|
812
|
+
set( v ) {
|
|
813
|
+
|
|
814
|
+
this.uniforms[ key ].value = v;
|
|
815
|
+
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
} );
|
|
819
|
+
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// sets the given named define value and sets "needsUpdate" to true if it's different
|
|
825
|
+
setDefine( name, value = undefined ) {
|
|
826
|
+
|
|
827
|
+
if ( value === undefined || value === null ) {
|
|
828
|
+
|
|
829
|
+
if ( name in this.defines ) {
|
|
830
|
+
|
|
831
|
+
delete this.defines[ name ];
|
|
832
|
+
this.needsUpdate = true;
|
|
833
|
+
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
} else {
|
|
837
|
+
|
|
838
|
+
if ( this.defines[ name ] !== value ) {
|
|
839
|
+
|
|
840
|
+
this.defines[ name ] = value;
|
|
841
|
+
this.needsUpdate = true;
|
|
842
|
+
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const shaderMaterialStructs = /* glsl */ `
|
|
852
|
+
|
|
853
|
+
struct Material {
|
|
854
|
+
|
|
855
|
+
vec3 color;
|
|
856
|
+
int map;
|
|
857
|
+
|
|
858
|
+
float metalness;
|
|
859
|
+
int metalnessMap;
|
|
860
|
+
|
|
861
|
+
float roughness;
|
|
862
|
+
int roughnessMap;
|
|
863
|
+
|
|
864
|
+
float ior;
|
|
865
|
+
float transmission;
|
|
866
|
+
int transmissionMap;
|
|
867
|
+
|
|
868
|
+
vec3 emissive;
|
|
869
|
+
float emissiveIntensity;
|
|
870
|
+
int emissiveMap;
|
|
871
|
+
|
|
872
|
+
int normalMap;
|
|
873
|
+
vec2 normalScale;
|
|
874
|
+
|
|
875
|
+
float opacity;
|
|
876
|
+
float alphaTest;
|
|
877
|
+
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
`;
|
|
881
|
+
|
|
882
|
+
const shaderGGXFunctions = /* glsl */`
|
|
883
|
+
// The GGX functions provide sampling and distribution information for normals as output so
|
|
884
|
+
// in order to get probability of scatter direction the half vector must be computed and provided.
|
|
885
|
+
// [0] https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
|
|
886
|
+
// [1] https://hal.archives-ouvertes.fr/hal-01509746/document
|
|
887
|
+
// [2] http://jcgt.org/published/0007/04/01/
|
|
888
|
+
// [4] http://jcgt.org/published/0003/02/03/
|
|
889
|
+
|
|
890
|
+
// trowbridge-reitz === GGX === GTR
|
|
891
|
+
|
|
892
|
+
vec3 ggxDirection( vec3 incidentDir, float roughnessX, float roughnessY, float random1, float random2 ) {
|
|
893
|
+
|
|
894
|
+
// TODO: try GGXVNDF implementation from reference [2], here. Needs to update ggxDistribution
|
|
895
|
+
// function below, as well
|
|
896
|
+
|
|
897
|
+
// Implementation from reference [1]
|
|
898
|
+
// stretch view
|
|
899
|
+
vec3 V = normalize( vec3( roughnessX * incidentDir.x, roughnessY * incidentDir.y, incidentDir.z ) );
|
|
900
|
+
|
|
901
|
+
// orthonormal basis
|
|
902
|
+
vec3 T1 = ( V.z < 0.9999 ) ? normalize( cross( V, vec3( 0.0, 0.0, 1.0 ) ) ) : vec3( 1.0, 0.0, 0.0 );
|
|
903
|
+
vec3 T2 = cross( T1, V );
|
|
904
|
+
|
|
905
|
+
// sample point with polar coordinates (r, phi)
|
|
906
|
+
float a = 1.0 / ( 1.0 + V.z );
|
|
907
|
+
float r = sqrt( random1 );
|
|
908
|
+
float phi = ( random2 < a ) ? random2 / a * PI : PI + ( random2 - a ) / ( 1.0 - a ) * PI;
|
|
909
|
+
float P1 = r * cos( phi );
|
|
910
|
+
float P2 = r * sin( phi ) * ( ( random2 < a ) ? 1.0 : V.z );
|
|
911
|
+
|
|
912
|
+
// compute normal
|
|
913
|
+
vec3 N = P1 * T1 + P2 * T2 + V * sqrt( max( 0.0, 1.0 - P1 * P1 - P2 * P2 ) );
|
|
914
|
+
|
|
915
|
+
// unstretch
|
|
916
|
+
N = normalize( vec3( roughnessX * N.x, roughnessY * N.y, max( 0.0, N.z ) ) );
|
|
917
|
+
|
|
918
|
+
return N;
|
|
919
|
+
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Below are PDF and related functions for use in a Monte Carlo path tracer
|
|
923
|
+
// as specified in Appendix B of the following paper
|
|
924
|
+
// See equation (2) from reference [2]
|
|
925
|
+
float ggxLamda( float theta, float roughness ) {
|
|
926
|
+
|
|
927
|
+
float tanTheta = tan( theta );
|
|
928
|
+
float tanTheta2 = tanTheta * tanTheta;
|
|
929
|
+
float alpha2 = roughness * roughness;
|
|
930
|
+
|
|
931
|
+
float numerator = - 1.0 + sqrt( 1.0 + alpha2 * tanTheta2 );
|
|
932
|
+
return numerator / 2.0;
|
|
933
|
+
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// See equation (2) from reference [2]
|
|
937
|
+
float ggxShadowMaskG1( float theta, float roughness ) {
|
|
938
|
+
|
|
939
|
+
return 1.0 / ( 1.0 + ggxLamda( theta, roughness ) );
|
|
940
|
+
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// See equation (125) from reference [4]
|
|
944
|
+
float ggxShadowMaskG2( vec3 wi, vec3 wo, float roughness ) {
|
|
945
|
+
|
|
946
|
+
float incidentTheta = acos( wi.z );
|
|
947
|
+
float scatterTheta = acos( wo.z );
|
|
948
|
+
return 1.0 / ( 1.0 + ggxLamda( incidentTheta, roughness ) + ggxLamda( scatterTheta, roughness ) );
|
|
949
|
+
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
float ggxDistribution( vec3 halfVector, float roughness ) {
|
|
953
|
+
|
|
954
|
+
// See equation (33) from reference [0]
|
|
955
|
+
float a2 = roughness * roughness;
|
|
956
|
+
float cosTheta = halfVector.z;
|
|
957
|
+
float cosTheta4 = pow( cosTheta, 4.0 );
|
|
958
|
+
|
|
959
|
+
if ( cosTheta == 0.0 ) return 0.0;
|
|
960
|
+
|
|
961
|
+
float theta = acos( halfVector.z );
|
|
962
|
+
float tanTheta = tan( theta );
|
|
963
|
+
float tanTheta2 = pow( tanTheta, 2.0 );
|
|
964
|
+
|
|
965
|
+
float denom = PI * cosTheta4 * pow( a2 + tanTheta2, 2.0 );
|
|
966
|
+
return a2 / denom;
|
|
967
|
+
|
|
968
|
+
// See equation (1) from reference [2]
|
|
969
|
+
// const { x, y, z } = halfVector;
|
|
970
|
+
// const a2 = roughness * roughness;
|
|
971
|
+
// const mult = x * x / a2 + y * y / a2 + z * z;
|
|
972
|
+
// const mult2 = mult * mult;
|
|
973
|
+
|
|
974
|
+
// return 1.0 / Math.PI * a2 * mult2;
|
|
975
|
+
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// See equation (3) from reference [2]
|
|
979
|
+
float ggxPDF( vec3 wi, vec3 halfVector, float roughness ) {
|
|
980
|
+
|
|
981
|
+
float incidentTheta = acos( wi.z );
|
|
982
|
+
float D = ggxDistribution( halfVector, roughness );
|
|
983
|
+
float G1 = ggxShadowMaskG1( incidentTheta, roughness );
|
|
984
|
+
|
|
985
|
+
return D * G1 * max( 0.0, dot( wi, halfVector ) ) / wi.z;
|
|
986
|
+
|
|
987
|
+
}
|
|
988
|
+
`;
|
|
989
|
+
|
|
990
|
+
const shaderMaterialSampling = /* glsl */`
|
|
991
|
+
|
|
992
|
+
struct SurfaceRec {
|
|
993
|
+
vec3 normal;
|
|
994
|
+
vec3 faceNormal;
|
|
995
|
+
bool frontFace;
|
|
996
|
+
float roughness;
|
|
997
|
+
float filteredRoughness;
|
|
998
|
+
float metalness;
|
|
999
|
+
vec3 color;
|
|
1000
|
+
vec3 emission;
|
|
1001
|
+
float transmission;
|
|
1002
|
+
float ior;
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
struct SampleRec {
|
|
1006
|
+
float pdf;
|
|
1007
|
+
vec3 direction;
|
|
1008
|
+
vec3 color;
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
${ shaderGGXFunctions }
|
|
1012
|
+
|
|
1013
|
+
// diffuse
|
|
1014
|
+
float diffusePDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
1015
|
+
|
|
1016
|
+
// https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html#lightscattering/thescatteringpdf
|
|
1017
|
+
float cosValue = wi.z;
|
|
1018
|
+
return cosValue / PI;
|
|
1019
|
+
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
vec3 diffuseDirection( vec3 wo, SurfaceRec surf ) {
|
|
1023
|
+
|
|
1024
|
+
vec3 lightDirection = randDirection();
|
|
1025
|
+
lightDirection.z += 1.0;
|
|
1026
|
+
lightDirection = normalize( lightDirection );
|
|
1027
|
+
|
|
1028
|
+
return lightDirection;
|
|
1029
|
+
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
vec3 diffuseColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
1033
|
+
|
|
1034
|
+
// TODO: scale by 1 - F here
|
|
1035
|
+
// note on division by PI
|
|
1036
|
+
// https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
|
|
1037
|
+
float metalFactor = ( 1.0 - surf.metalness ) * wi.z / ( PI * PI );
|
|
1038
|
+
float transmissionFactor = 1.0 - surf.transmission;
|
|
1039
|
+
return surf.color * metalFactor * transmissionFactor;
|
|
1040
|
+
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// specular
|
|
1044
|
+
float specularPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
1045
|
+
|
|
1046
|
+
// See equation (17) in http://jcgt.org/published/0003/02/03/
|
|
1047
|
+
float filteredRoughness = surf.filteredRoughness;
|
|
1048
|
+
vec3 halfVector = getHalfVector( wi, wo );
|
|
1049
|
+
return ggxPDF( wi, halfVector, filteredRoughness ) / ( 4.0 * dot( wi, halfVector ) );
|
|
1050
|
+
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
vec3 specularDirection( vec3 wo, SurfaceRec surf ) {
|
|
1054
|
+
|
|
1055
|
+
// sample ggx vndf distribution which gives a new normal
|
|
1056
|
+
float filteredRoughness = surf.filteredRoughness;
|
|
1057
|
+
vec3 halfVector = ggxDirection(
|
|
1058
|
+
wo,
|
|
1059
|
+
filteredRoughness,
|
|
1060
|
+
filteredRoughness,
|
|
1061
|
+
rand(),
|
|
1062
|
+
rand()
|
|
1063
|
+
);
|
|
1064
|
+
|
|
1065
|
+
// apply to new ray by reflecting off the new normal
|
|
1066
|
+
return - reflect( wo, halfVector );
|
|
1067
|
+
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
vec3 specularColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
1071
|
+
|
|
1072
|
+
// if roughness is set to 0 then D === NaN which results in black pixels
|
|
1073
|
+
float metalness = surf.metalness;
|
|
1074
|
+
float ior = surf.ior;
|
|
1075
|
+
bool frontFace = surf.frontFace;
|
|
1076
|
+
float filteredRoughness = surf.filteredRoughness;
|
|
1077
|
+
|
|
1078
|
+
vec3 halfVector = getHalfVector( wo, wi );
|
|
1079
|
+
float iorRatio = frontFace ? 1.0 / ior : ior;
|
|
1080
|
+
float G = ggxShadowMaskG2( wi, wo, filteredRoughness );
|
|
1081
|
+
float D = ggxDistribution( halfVector, filteredRoughness );
|
|
1082
|
+
|
|
1083
|
+
float F = schlickFresnelFromIor( dot( wi, halfVector ), iorRatio );
|
|
1084
|
+
float cosTheta = min( wo.z, 1.0 );
|
|
1085
|
+
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
|
|
1086
|
+
bool cannotRefract = iorRatio * sinTheta > 1.0;
|
|
1087
|
+
if ( cannotRefract ) {
|
|
1088
|
+
|
|
1089
|
+
F = 1.0;
|
|
1090
|
+
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
vec3 color = mix( vec3( 1.0 ), surf.color, metalness );
|
|
1094
|
+
color = mix( color, vec3( 1.0 ), F );
|
|
1095
|
+
color *= G * D / ( 4.0 * abs( wi.z * wo.z ) );
|
|
1096
|
+
color *= mix( F, 1.0, metalness );
|
|
1097
|
+
color *= wi.z; // scale the light by the direction the light is coming in from
|
|
1098
|
+
|
|
1099
|
+
return color;
|
|
1100
|
+
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/*
|
|
1104
|
+
// transmission
|
|
1105
|
+
function transmissionPDF( wo, wi, material, surf ) {
|
|
1106
|
+
|
|
1107
|
+
// See section 4.2 in https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
|
|
1108
|
+
|
|
1109
|
+
const { roughness, ior } = material;
|
|
1110
|
+
const { frontFace } = hit;
|
|
1111
|
+
const ratio = frontFace ? ior : 1 / ior;
|
|
1112
|
+
const minRoughness = Math.max( roughness, MIN_ROUGHNESS );
|
|
1113
|
+
|
|
1114
|
+
halfVector.set( 0, 0, 0 ).addScaledVector( wi, ratio ).addScaledVector( wo, 1.0 ).normalize().multiplyScalar( - 1 );
|
|
1115
|
+
|
|
1116
|
+
const denom = Math.pow( ratio * halfVector.dot( wi ) + 1.0 * halfVector.dot( wo ), 2.0 );
|
|
1117
|
+
return ggxPDF( wo, halfVector, minRoughness ) / denom;
|
|
1118
|
+
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function transmissionDirection( wo, hit, material, lightDirection ) {
|
|
1122
|
+
|
|
1123
|
+
const { roughness, ior } = material;
|
|
1124
|
+
const { frontFace } = hit;
|
|
1125
|
+
const ratio = frontFace ? 1 / ior : ior;
|
|
1126
|
+
const minRoughness = Math.max( roughness, MIN_ROUGHNESS );
|
|
1127
|
+
|
|
1128
|
+
// sample ggx vndf distribution which gives a new normal
|
|
1129
|
+
ggxDirection(
|
|
1130
|
+
wo,
|
|
1131
|
+
minRoughness,
|
|
1132
|
+
minRoughness,
|
|
1133
|
+
Math.random(),
|
|
1134
|
+
Math.random(),
|
|
1135
|
+
halfVector,
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
// apply to new ray by reflecting off the new normal
|
|
1139
|
+
tempDir.copy( wo ).multiplyScalar( - 1 );
|
|
1140
|
+
refract( tempDir, halfVector, ratio, lightDirection );
|
|
1141
|
+
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function transmissionColor( wo, wi, material, hit, colorTarget ) {
|
|
1145
|
+
|
|
1146
|
+
const { metalness, transmission } = material;
|
|
1147
|
+
colorTarget
|
|
1148
|
+
.copy( material.color )
|
|
1149
|
+
.multiplyScalar( ( 1.0 - metalness ) * wo.z )
|
|
1150
|
+
.multiplyScalar( transmission );
|
|
1151
|
+
|
|
1152
|
+
}
|
|
1153
|
+
*/
|
|
1154
|
+
|
|
1155
|
+
// TODO: This is just using a basic cosine-weighted specular distribution with an
|
|
1156
|
+
// incorrect PDF value at the moment. Update it to correctly use a GGX distribution
|
|
1157
|
+
float transmissionPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
1158
|
+
|
|
1159
|
+
float ior = surf.ior;
|
|
1160
|
+
bool frontFace = surf.frontFace;
|
|
1161
|
+
|
|
1162
|
+
float ratio = frontFace ? 1.0 / ior : ior;
|
|
1163
|
+
float cosTheta = min( wo.z, 1.0 );
|
|
1164
|
+
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
|
|
1165
|
+
float reflectance = schlickFresnelFromIor( cosTheta, ratio );
|
|
1166
|
+
bool cannotRefract = ratio * sinTheta > 1.0;
|
|
1167
|
+
if ( cannotRefract ) {
|
|
1168
|
+
|
|
1169
|
+
return 0.0;
|
|
1170
|
+
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
return 1.0 / ( 1.0 - reflectance );
|
|
1174
|
+
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
vec3 transmissionDirection( vec3 wo, SurfaceRec surf ) {
|
|
1178
|
+
|
|
1179
|
+
float roughness = surf.roughness;
|
|
1180
|
+
float ior = surf.ior;
|
|
1181
|
+
bool frontFace = surf.frontFace;
|
|
1182
|
+
float ratio = frontFace ? 1.0 / ior : ior;
|
|
1183
|
+
|
|
1184
|
+
vec3 lightDirection = refract( - wo, vec3( 0.0, 0.0, 1.0 ), ratio );
|
|
1185
|
+
lightDirection += randDirection() * roughness;
|
|
1186
|
+
return normalize( lightDirection );
|
|
1187
|
+
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
vec3 transmissionColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
1191
|
+
|
|
1192
|
+
float metalness = surf.metalness;
|
|
1193
|
+
float transmission = surf.transmission;
|
|
1194
|
+
|
|
1195
|
+
vec3 color = surf.color;
|
|
1196
|
+
color *= ( 1.0 - metalness );
|
|
1197
|
+
color *= transmission;
|
|
1198
|
+
|
|
1199
|
+
return color;
|
|
1200
|
+
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
float bsdfPdf( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
1204
|
+
|
|
1205
|
+
float ior = surf.ior;
|
|
1206
|
+
float metalness = surf.metalness;
|
|
1207
|
+
float transmission = surf.transmission;
|
|
1208
|
+
bool frontFace = surf.frontFace;
|
|
1209
|
+
|
|
1210
|
+
float ratio = frontFace ? 1.0 / ior : ior;
|
|
1211
|
+
float cosTheta = min( wo.z, 1.0 );
|
|
1212
|
+
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
|
|
1213
|
+
float reflectance = schlickFresnelFromIor( cosTheta, ratio );
|
|
1214
|
+
bool cannotRefract = ratio * sinTheta > 1.0;
|
|
1215
|
+
if ( cannotRefract ) {
|
|
1216
|
+
|
|
1217
|
+
reflectance = 1.0;
|
|
1218
|
+
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
float spdf = 0.0;
|
|
1222
|
+
float dpdf = 0.0;
|
|
1223
|
+
float tpdf = 0.0;
|
|
1224
|
+
|
|
1225
|
+
if ( wi.z < 0.0 ) {
|
|
1226
|
+
|
|
1227
|
+
tpdf = transmissionPDF( wo, wi, surf );
|
|
1228
|
+
|
|
1229
|
+
} else {
|
|
1230
|
+
|
|
1231
|
+
spdf = specularPDF( wo, wi, surf );
|
|
1232
|
+
dpdf = diffusePDF( wo, wi, surf );
|
|
1233
|
+
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
float transSpecularProb = mix( reflectance, 1.0, metalness );
|
|
1237
|
+
float diffSpecularProb = 0.5 + 0.5 * metalness;
|
|
1238
|
+
float pdf =
|
|
1239
|
+
spdf * transmission * transSpecularProb
|
|
1240
|
+
+ tpdf * transmission * ( 1.0 - transSpecularProb )
|
|
1241
|
+
+ spdf * ( 1.0 - transmission ) * diffSpecularProb
|
|
1242
|
+
+ dpdf * ( 1.0 - transmission ) * ( 1.0 - diffSpecularProb );
|
|
1243
|
+
|
|
1244
|
+
return pdf;
|
|
1245
|
+
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
vec3 bsdfColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
|
|
1249
|
+
|
|
1250
|
+
vec3 color = vec3( 0.0 );
|
|
1251
|
+
if ( wi.z < 0.0 ) {
|
|
1252
|
+
|
|
1253
|
+
color = transmissionColor( wo, wi, surf );
|
|
1254
|
+
|
|
1255
|
+
} else {
|
|
1256
|
+
|
|
1257
|
+
color = diffuseColor( wo, wi, surf );
|
|
1258
|
+
color *= 1.0 - surf.transmission;
|
|
1259
|
+
|
|
1260
|
+
color += specularColor( wo, wi, surf );
|
|
1261
|
+
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
return color;
|
|
1265
|
+
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
SampleRec bsdfSample( vec3 wo, SurfaceRec surf ) {
|
|
1269
|
+
|
|
1270
|
+
float ior = surf.ior;
|
|
1271
|
+
float metalness = surf.metalness;
|
|
1272
|
+
float transmission = surf.transmission;
|
|
1273
|
+
bool frontFace = surf.frontFace;
|
|
1274
|
+
|
|
1275
|
+
float ratio = frontFace ? 1.0 / ior : ior;
|
|
1276
|
+
float cosTheta = min( wo.z, 1.0 );
|
|
1277
|
+
float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
|
|
1278
|
+
float reflectance = schlickFresnelFromIor( cosTheta, ratio );
|
|
1279
|
+
bool cannotRefract = ratio * sinTheta > 1.0;
|
|
1280
|
+
if ( cannotRefract ) {
|
|
1281
|
+
|
|
1282
|
+
reflectance = 1.0;
|
|
1283
|
+
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
SampleRec result;
|
|
1287
|
+
if ( rand() < transmission ) {
|
|
1288
|
+
|
|
1289
|
+
float specularProb = mix( reflectance, 1.0, metalness );
|
|
1290
|
+
if ( rand() < specularProb ) {
|
|
1291
|
+
|
|
1292
|
+
result.direction = specularDirection( wo, surf );
|
|
1293
|
+
|
|
1294
|
+
} else {
|
|
1295
|
+
|
|
1296
|
+
result.direction = transmissionDirection( wo, surf );
|
|
1297
|
+
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
} else {
|
|
1301
|
+
|
|
1302
|
+
float specularProb = 0.5 + 0.5 * metalness;
|
|
1303
|
+
if ( rand() < specularProb ) {
|
|
1304
|
+
|
|
1305
|
+
result.direction = specularDirection( wo, surf );
|
|
1306
|
+
|
|
1307
|
+
} else {
|
|
1308
|
+
|
|
1309
|
+
result.direction = diffuseDirection( wo, surf );
|
|
1310
|
+
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
result.pdf = bsdfPdf( wo, result.direction, surf );
|
|
1316
|
+
result.color = bsdfColor( wo, result.direction, surf );
|
|
1317
|
+
return result;
|
|
1318
|
+
|
|
1319
|
+
}
|
|
1320
|
+
`;
|
|
1321
|
+
|
|
1322
|
+
const shaderUtils = /* glsl */`
|
|
1323
|
+
|
|
1324
|
+
// https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
|
|
1325
|
+
float schlickFresnel( float cosine, float f0 ) {
|
|
1326
|
+
|
|
1327
|
+
return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
|
|
1328
|
+
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
|
|
1332
|
+
float schlickFresnelFromIor( float cosine, float iorRatio ) {
|
|
1333
|
+
|
|
1334
|
+
// Schlick approximation
|
|
1335
|
+
float r_0 = pow( ( 1.0 - iorRatio ) / ( 1.0 + iorRatio ), 2.0 );
|
|
1336
|
+
return schlickFresnel( cosine, r_0 );
|
|
1337
|
+
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// forms a basis with the normal vector as Z
|
|
1341
|
+
mat3 getBasisFromNormal( vec3 normal ) {
|
|
1342
|
+
|
|
1343
|
+
vec3 other;
|
|
1344
|
+
if ( abs( normal.x ) > 0.5 ) {
|
|
1345
|
+
|
|
1346
|
+
other = vec3( 0.0, 1.0, 0.0 );
|
|
1347
|
+
|
|
1348
|
+
} else {
|
|
1349
|
+
|
|
1350
|
+
other = vec3( 1.0, 0.0, 0.0 );
|
|
1351
|
+
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
vec3 ortho = normalize( cross( normal, other ) );
|
|
1355
|
+
vec3 ortho2 = normalize( cross( normal, ortho ) );
|
|
1356
|
+
return mat3( ortho2, ortho, normal );
|
|
1357
|
+
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
vec3 getHalfVector( vec3 a, vec3 b ) {
|
|
1361
|
+
|
|
1362
|
+
return normalize( a + b );
|
|
1363
|
+
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
|
|
1367
|
+
// is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
|
|
1368
|
+
// we find a ray like that we ignore it to avoid artifacts.
|
|
1369
|
+
// This function returns if the direction is on the same side of both planes.
|
|
1370
|
+
bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
|
|
1371
|
+
|
|
1372
|
+
bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
|
|
1373
|
+
bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
|
|
1374
|
+
return aboveSurfaceNormal == aboveGeometryNormal;
|
|
1375
|
+
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
vec3 getHemisphereSample( vec3 n, vec2 uv ) {
|
|
1379
|
+
|
|
1380
|
+
// https://www.rorydriscoll.com/2009/01/07/better-sampling/
|
|
1381
|
+
// https://graphics.pixar.com/library/OrthonormalB/paper.pdf
|
|
1382
|
+
float sign = n.z == 0.0 ? 1.0 : sign( n.z );
|
|
1383
|
+
float a = - 1.0 / ( sign + n.z );
|
|
1384
|
+
float b = n.x * n.y * a;
|
|
1385
|
+
vec3 b1 = vec3( 1.0 + sign * n.x * n.x * a, sign * b, - sign * n.x );
|
|
1386
|
+
vec3 b2 = vec3( b, sign + n.y * n.y * a, - n.y );
|
|
1387
|
+
|
|
1388
|
+
float r = sqrt( uv.x );
|
|
1389
|
+
float theta = 2.0 * PI * uv.y;
|
|
1390
|
+
float x = r * cos( theta );
|
|
1391
|
+
float y = r * sin( theta );
|
|
1392
|
+
return x * b1 + y * b2 + sqrt( 1.0 - uv.x ) * n;
|
|
1393
|
+
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// https://www.shadertoy.com/view/wltcRS
|
|
1397
|
+
uvec4 s0;
|
|
1398
|
+
|
|
1399
|
+
void rng_initialize(vec2 p, int frame) {
|
|
1400
|
+
|
|
1401
|
+
// white noise seed
|
|
1402
|
+
s0 = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
|
|
1403
|
+
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// https://www.pcg-random.org/
|
|
1407
|
+
void pcg4d( inout uvec4 v ) {
|
|
1408
|
+
|
|
1409
|
+
v = v * 1664525u + 1013904223u;
|
|
1410
|
+
v.x += v.y * v.w;
|
|
1411
|
+
v.y += v.z * v.x;
|
|
1412
|
+
v.z += v.x * v.y;
|
|
1413
|
+
v.w += v.y * v.z;
|
|
1414
|
+
v = v ^ ( v >> 16u );
|
|
1415
|
+
v.x += v.y*v.w;
|
|
1416
|
+
v.y += v.z*v.x;
|
|
1417
|
+
v.z += v.x*v.y;
|
|
1418
|
+
v.w += v.y*v.z;
|
|
1419
|
+
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
float rand() {
|
|
1423
|
+
|
|
1424
|
+
pcg4d(s0);
|
|
1425
|
+
return float( s0.x ) / float( 0xffffffffu );
|
|
1426
|
+
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
vec2 rand2() {
|
|
1430
|
+
|
|
1431
|
+
pcg4d( s0 );
|
|
1432
|
+
return vec2( s0.xy ) / float(0xffffffffu);
|
|
1433
|
+
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
vec3 rand3() {
|
|
1437
|
+
|
|
1438
|
+
pcg4d(s0);
|
|
1439
|
+
return vec3( s0.xyz ) / float( 0xffffffffu );
|
|
1440
|
+
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
vec4 rand4() {
|
|
1444
|
+
|
|
1445
|
+
pcg4d(s0);
|
|
1446
|
+
return vec4(s0)/float(0xffffffffu);
|
|
1447
|
+
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js#L724
|
|
1451
|
+
vec3 randDirection() {
|
|
1452
|
+
|
|
1453
|
+
vec2 r = rand2();
|
|
1454
|
+
float u = ( r.x - 0.5 ) * 2.0;
|
|
1455
|
+
float t = r.y * PI * 2.0;
|
|
1456
|
+
float f = sqrt( 1.0 - u * u );
|
|
1457
|
+
|
|
1458
|
+
return vec3( f * cos( t ), f * sin( t ), u );
|
|
1459
|
+
|
|
1460
|
+
}
|
|
1461
|
+
`;
|
|
1462
|
+
|
|
1463
|
+
class PhysicalPathTracingMaterial extends MaterialBase {
|
|
1464
|
+
|
|
1465
|
+
// three.js relies on this field to add env map functions and defines
|
|
1466
|
+
get envMap() {
|
|
1467
|
+
|
|
1468
|
+
return this.environmentMap;
|
|
1469
|
+
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
constructor( parameters ) {
|
|
1473
|
+
|
|
1474
|
+
super( {
|
|
1475
|
+
|
|
1476
|
+
transparent: true,
|
|
1477
|
+
depthWrite: false,
|
|
1478
|
+
|
|
1479
|
+
defines: {
|
|
1480
|
+
BOUNCES: 3,
|
|
1481
|
+
TRANSPARENT_TRAVERSALS: 5,
|
|
1482
|
+
MATERIAL_LENGTH: 0,
|
|
1483
|
+
GRADIENT_BG: 0,
|
|
1484
|
+
},
|
|
1485
|
+
|
|
1486
|
+
uniforms: {
|
|
1487
|
+
bvh: { value: new threeMeshBvh.MeshBVHUniformStruct() },
|
|
1488
|
+
normalAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
|
|
1489
|
+
tangentAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
|
|
1490
|
+
uvAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
|
|
1491
|
+
materialIndexAttribute: { value: new threeMeshBvh.UIntVertexAttributeTexture() },
|
|
1492
|
+
materials: { value: new MaterialStructArrayUniform() },
|
|
1493
|
+
textures: { value: new RenderTarget2DArray().texture },
|
|
1494
|
+
cameraWorldMatrix: { value: new three.Matrix4() },
|
|
1495
|
+
invProjectionMatrix: { value: new three.Matrix4() },
|
|
1496
|
+
environmentBlur: { value: 0.2 },
|
|
1497
|
+
environmentIntensity: { value: 2.0 },
|
|
1498
|
+
environmentMap: { value: null },
|
|
1499
|
+
environmentRotation: { value: new three.Matrix3() },
|
|
1500
|
+
seed: { value: 0 },
|
|
1501
|
+
opacity: { value: 1 },
|
|
1502
|
+
filterGlossyFactor: { value: 0.0 },
|
|
1503
|
+
|
|
1504
|
+
gradientTop: { value: new three.Color( 0xbfd8ff ) },
|
|
1505
|
+
gradientBottom: { value: new three.Color( 0xffffff ) },
|
|
1506
|
+
|
|
1507
|
+
bgGradientTop: { value: new three.Color( 0x111111 ) },
|
|
1508
|
+
bgGradientBottom: { value: new three.Color( 0x000000 ) },
|
|
1509
|
+
},
|
|
1510
|
+
|
|
1511
|
+
vertexShader: /* glsl */`
|
|
1512
|
+
|
|
1513
|
+
varying vec2 vUv;
|
|
1514
|
+
void main() {
|
|
1515
|
+
|
|
1516
|
+
vec4 mvPosition = vec4( position, 1.0 );
|
|
1517
|
+
mvPosition = modelViewMatrix * mvPosition;
|
|
1518
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
1519
|
+
|
|
1520
|
+
vUv = uv;
|
|
1521
|
+
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
`,
|
|
1525
|
+
|
|
1526
|
+
fragmentShader: /* glsl */`
|
|
1527
|
+
#define RAY_OFFSET 1e-5
|
|
1528
|
+
|
|
1529
|
+
precision highp isampler2D;
|
|
1530
|
+
precision highp usampler2D;
|
|
1531
|
+
precision highp sampler2DArray;
|
|
1532
|
+
vec4 envMapTexelToLinear( vec4 a ) { return a; }
|
|
1533
|
+
#include <common>
|
|
1534
|
+
#include <cube_uv_reflection_fragment>
|
|
1535
|
+
|
|
1536
|
+
${ threeMeshBvh.shaderStructs }
|
|
1537
|
+
${ threeMeshBvh.shaderIntersectFunction }
|
|
1538
|
+
${ shaderMaterialStructs }
|
|
1539
|
+
|
|
1540
|
+
${ shaderUtils }
|
|
1541
|
+
${ shaderMaterialSampling }
|
|
1542
|
+
|
|
1543
|
+
#ifdef USE_ENVMAP
|
|
1544
|
+
|
|
1545
|
+
uniform float environmentBlur;
|
|
1546
|
+
uniform sampler2D environmentMap;
|
|
1547
|
+
uniform mat3 environmentRotation;
|
|
1548
|
+
|
|
1549
|
+
#else
|
|
1550
|
+
|
|
1551
|
+
uniform vec3 gradientTop;
|
|
1552
|
+
uniform vec3 gradientBottom;
|
|
1553
|
+
|
|
1554
|
+
#endif
|
|
1555
|
+
|
|
1556
|
+
#if GRADIENT_BG
|
|
1557
|
+
|
|
1558
|
+
uniform vec3 bgGradientTop;
|
|
1559
|
+
uniform vec3 bgGradientBottom;
|
|
1560
|
+
|
|
1561
|
+
#endif
|
|
1562
|
+
|
|
1563
|
+
uniform mat4 cameraWorldMatrix;
|
|
1564
|
+
uniform mat4 invProjectionMatrix;
|
|
1565
|
+
uniform sampler2D normalAttribute;
|
|
1566
|
+
uniform sampler2D tangentAttribute;
|
|
1567
|
+
uniform sampler2D uvAttribute;
|
|
1568
|
+
uniform usampler2D materialIndexAttribute;
|
|
1569
|
+
uniform BVH bvh;
|
|
1570
|
+
uniform float environmentIntensity;
|
|
1571
|
+
uniform float filterGlossyFactor;
|
|
1572
|
+
uniform int seed;
|
|
1573
|
+
uniform float opacity;
|
|
1574
|
+
uniform Material materials[ MATERIAL_LENGTH ];
|
|
1575
|
+
uniform sampler2DArray textures;
|
|
1576
|
+
varying vec2 vUv;
|
|
1577
|
+
|
|
1578
|
+
void main() {
|
|
1579
|
+
|
|
1580
|
+
rng_initialize( gl_FragCoord.xy, seed );
|
|
1581
|
+
|
|
1582
|
+
// get [-1, 1] normalized device coordinates
|
|
1583
|
+
vec2 ndc = 2.0 * vUv - vec2( 1.0 );
|
|
1584
|
+
vec3 rayOrigin, rayDirection;
|
|
1585
|
+
ndcToCameraRay( ndc, cameraWorldMatrix, invProjectionMatrix, rayOrigin, rayDirection );
|
|
1586
|
+
|
|
1587
|
+
// Lambertian render
|
|
1588
|
+
gl_FragColor = vec4( 0.0 );
|
|
1589
|
+
|
|
1590
|
+
vec3 throughputColor = vec3( 1.0 );
|
|
1591
|
+
|
|
1592
|
+
// hit results
|
|
1593
|
+
uvec4 faceIndices = uvec4( 0u );
|
|
1594
|
+
vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
|
|
1595
|
+
vec3 barycoord = vec3( 0.0 );
|
|
1596
|
+
float side = 1.0;
|
|
1597
|
+
float dist = 0.0;
|
|
1598
|
+
float accumulatedRoughness = 0.0;
|
|
1599
|
+
int i;
|
|
1600
|
+
int transparentTraversals = TRANSPARENT_TRAVERSALS;
|
|
1601
|
+
for ( i = 0; i < BOUNCES; i ++ ) {
|
|
1602
|
+
|
|
1603
|
+
if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
|
|
1604
|
+
|
|
1605
|
+
#if GRADIENT_BG
|
|
1606
|
+
|
|
1607
|
+
if ( i == 0 ) {
|
|
1608
|
+
|
|
1609
|
+
rayDirection = normalize( rayDirection + randDirection() * 0.05 );
|
|
1610
|
+
float value = ( rayDirection.y + 1.0 ) / 2.0;
|
|
1611
|
+
|
|
1612
|
+
value = pow( value, 2.0 );
|
|
1613
|
+
|
|
1614
|
+
gl_FragColor = vec4( mix( bgGradientBottom, bgGradientTop, value ), 1.0 );
|
|
1615
|
+
break;
|
|
1616
|
+
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
#endif
|
|
1620
|
+
|
|
1621
|
+
#ifdef USE_ENVMAP
|
|
1622
|
+
|
|
1623
|
+
vec3 skyColor = textureCubeUV( environmentMap, environmentRotation * rayDirection, environmentBlur ).rgb;
|
|
1624
|
+
|
|
1625
|
+
#else
|
|
1626
|
+
|
|
1627
|
+
rayDirection = normalize( rayDirection );
|
|
1628
|
+
float value = ( rayDirection.y + 1.0 ) / 2.0;
|
|
1629
|
+
vec3 skyColor = mix( gradientBottom, gradientTop, value );
|
|
1630
|
+
|
|
1631
|
+
#endif
|
|
1632
|
+
|
|
1633
|
+
gl_FragColor += vec4( skyColor * throughputColor * environmentIntensity, 1.0 );
|
|
1634
|
+
|
|
1635
|
+
break;
|
|
1636
|
+
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
|
|
1640
|
+
Material material = materials[ materialIndex ];
|
|
1641
|
+
|
|
1642
|
+
vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
|
|
1643
|
+
|
|
1644
|
+
// albedo
|
|
1645
|
+
vec4 albedo = vec4( material.color, material.opacity );
|
|
1646
|
+
if ( material.map != - 1 ) {
|
|
1647
|
+
|
|
1648
|
+
albedo *= texture2D( textures, vec3( uv, material.map ) );
|
|
1649
|
+
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// possibly skip this sample if it's transparent or alpha test is enabled
|
|
1653
|
+
// alpha test is disabled when it === 0
|
|
1654
|
+
float alphaTest = material.alphaTest;
|
|
1655
|
+
bool useAlphaTest = alphaTest != 0.0;
|
|
1656
|
+
if (
|
|
1657
|
+
useAlphaTest && albedo.a < alphaTest
|
|
1658
|
+
|| ! useAlphaTest && albedo.a < rand()
|
|
1659
|
+
) {
|
|
1660
|
+
|
|
1661
|
+
vec3 point = rayOrigin + rayDirection * dist;
|
|
1662
|
+
rayOrigin += rayDirection * dist - faceNormal * RAY_OFFSET;
|
|
1663
|
+
|
|
1664
|
+
// only allow a limited number of transparency discards otherwise we could
|
|
1665
|
+
// crash the context with too long a loop.
|
|
1666
|
+
i -= sign( transparentTraversals );
|
|
1667
|
+
transparentTraversals -= sign( transparentTraversals );
|
|
1668
|
+
continue;
|
|
1669
|
+
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// fetch the interpolated smooth normal
|
|
1673
|
+
vec3 normal = normalize( textureSampleBarycoord(
|
|
1674
|
+
normalAttribute,
|
|
1675
|
+
barycoord,
|
|
1676
|
+
faceIndices.xyz
|
|
1677
|
+
).xyz );
|
|
1678
|
+
|
|
1679
|
+
// roughness
|
|
1680
|
+
float roughness = material.roughness;
|
|
1681
|
+
if ( material.roughnessMap != - 1 ) {
|
|
1682
|
+
|
|
1683
|
+
roughness *= texture2D( textures, vec3( uv, material.roughnessMap ) ).g;
|
|
1684
|
+
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// metalness
|
|
1688
|
+
float metalness = material.metalness;
|
|
1689
|
+
if ( material.metalnessMap != - 1 ) {
|
|
1690
|
+
|
|
1691
|
+
metalness *= texture2D( textures, vec3( uv, material.metalnessMap ) ).b;
|
|
1692
|
+
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
// emission
|
|
1696
|
+
vec3 emission = material.emissiveIntensity * material.emissive;
|
|
1697
|
+
if ( material.emissiveMap != - 1 ) {
|
|
1698
|
+
|
|
1699
|
+
emission *= texture2D( textures, vec3( uv, material.emissiveMap ) ).xyz;
|
|
1700
|
+
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// transmission
|
|
1704
|
+
float transmission = material.transmission;
|
|
1705
|
+
if ( material.transmissionMap != - 1 ) {
|
|
1706
|
+
|
|
1707
|
+
transmission *= texture2D( textures, vec3( uv, material.transmissionMap ) ).r;
|
|
1708
|
+
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// normal
|
|
1712
|
+
if ( material.normalMap != - 1 ) {
|
|
1713
|
+
|
|
1714
|
+
vec4 tangentSample = textureSampleBarycoord(
|
|
1715
|
+
tangentAttribute,
|
|
1716
|
+
barycoord,
|
|
1717
|
+
faceIndices.xyz
|
|
1718
|
+
);
|
|
1719
|
+
|
|
1720
|
+
// some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
|
|
1721
|
+
// resulting in NaNs and slow path tracing.
|
|
1722
|
+
if ( length( tangentSample.xyz ) > 0.0 ) {
|
|
1723
|
+
|
|
1724
|
+
vec3 tangent = normalize( tangentSample.xyz );
|
|
1725
|
+
vec3 bitangent = normalize( cross( normal, tangent ) * tangentSample.w );
|
|
1726
|
+
mat3 vTBN = mat3( tangent, bitangent, normal );
|
|
1727
|
+
|
|
1728
|
+
vec3 texNormal = texture2D( textures, vec3( uv, material.normalMap ) ).xyz * 2.0 - 1.0;
|
|
1729
|
+
texNormal.xy *= material.normalScale;
|
|
1730
|
+
normal = vTBN * texNormal;
|
|
1731
|
+
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
normal *= side;
|
|
1737
|
+
|
|
1738
|
+
SurfaceRec surfaceRec;
|
|
1739
|
+
surfaceRec.normal = normal;
|
|
1740
|
+
surfaceRec.faceNormal = faceNormal;
|
|
1741
|
+
surfaceRec.frontFace = side == 1.0;
|
|
1742
|
+
surfaceRec.transmission = transmission;
|
|
1743
|
+
surfaceRec.ior = material.ior;
|
|
1744
|
+
surfaceRec.emission = emission;
|
|
1745
|
+
surfaceRec.metalness = metalness;
|
|
1746
|
+
surfaceRec.color = albedo.rgb;
|
|
1747
|
+
surfaceRec.roughness = roughness;
|
|
1748
|
+
|
|
1749
|
+
// Compute the filtered roughness value to use during specular reflection computations. A minimum
|
|
1750
|
+
// value of 1e-6 is needed because the GGX functions do not work with a roughness value of 0 and
|
|
1751
|
+
// the accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
|
|
1752
|
+
// If we're exiting something transmissive then scale the factor down significantly so we can retain
|
|
1753
|
+
// sharp internal reflections
|
|
1754
|
+
surfaceRec.filteredRoughness = clamp(
|
|
1755
|
+
max( surfaceRec.roughness, accumulatedRoughness * filterGlossyFactor * 5.0 ),
|
|
1756
|
+
1e-3,
|
|
1757
|
+
1.0
|
|
1758
|
+
);
|
|
1759
|
+
|
|
1760
|
+
mat3 normalBasis = getBasisFromNormal( surfaceRec.normal );
|
|
1761
|
+
mat3 invBasis = inverse( normalBasis );
|
|
1762
|
+
|
|
1763
|
+
vec3 outgoing = - normalize( invBasis * rayDirection );
|
|
1764
|
+
SampleRec sampleRec = bsdfSample( outgoing, surfaceRec );
|
|
1765
|
+
|
|
1766
|
+
// adjust the hit point by the surface normal by a factor of some offset and the
|
|
1767
|
+
// maximum component-wise value of the current point to accommodate floating point
|
|
1768
|
+
// error as values increase.
|
|
1769
|
+
vec3 point = rayOrigin + rayDirection * dist;
|
|
1770
|
+
vec3 absPoint = abs( point );
|
|
1771
|
+
float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
|
|
1772
|
+
rayDirection = normalize( normalBasis * sampleRec.direction );
|
|
1773
|
+
|
|
1774
|
+
bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
|
|
1775
|
+
rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
|
|
1776
|
+
|
|
1777
|
+
// accumulate a roughness value to offset diffuse, specular, diffuse rays that have high contribution
|
|
1778
|
+
// to a single pixel resulting in fireflies
|
|
1779
|
+
if ( ! isBelowSurface ) {
|
|
1780
|
+
|
|
1781
|
+
// determine if this is a rough normal or not by checking how far off straight up it is
|
|
1782
|
+
vec3 halfVector = normalize( outgoing + sampleRec.direction );
|
|
1783
|
+
accumulatedRoughness += sin( acos( halfVector.z ) );
|
|
1784
|
+
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// accumulate color
|
|
1788
|
+
gl_FragColor.rgb += ( emission * throughputColor );
|
|
1789
|
+
|
|
1790
|
+
// skip the sample if our PDF or ray is impossible
|
|
1791
|
+
if ( sampleRec.pdf <= 0.0 || ! isDirectionValid( rayDirection, normal, faceNormal) ) {
|
|
1792
|
+
|
|
1793
|
+
break;
|
|
1794
|
+
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
throughputColor *= sampleRec.color / sampleRec.pdf;
|
|
1798
|
+
|
|
1799
|
+
// discard the sample if there are any NaNs
|
|
1800
|
+
if ( any( isnan( throughputColor ) ) || any( isinf( throughputColor ) ) ) {
|
|
1801
|
+
|
|
1802
|
+
break;
|
|
1803
|
+
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
gl_FragColor.a = opacity;
|
|
1809
|
+
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
`
|
|
1813
|
+
|
|
1814
|
+
} );
|
|
1815
|
+
|
|
1816
|
+
this.setValues( parameters );
|
|
1817
|
+
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// core
|
|
1823
|
+
|
|
1824
|
+
exports.MaterialBase = MaterialBase;
|
|
1825
|
+
exports.MaterialReducer = MaterialReducer;
|
|
1826
|
+
exports.MaterialStructArrayUniform = MaterialStructArrayUniform;
|
|
1827
|
+
exports.MaterialStructUniform = MaterialStructUniform;
|
|
1828
|
+
exports.PathTracingRenderer = PathTracingRenderer;
|
|
1829
|
+
exports.PathTracingSceneGenerator = PathTracingSceneGenerator;
|
|
1830
|
+
exports.PhysicalPathTracingMaterial = PhysicalPathTracingMaterial;
|
|
1831
|
+
exports.RenderTarget2DArray = RenderTarget2DArray;
|
|
1832
|
+
exports.mergeMeshes = mergeMeshes;
|
|
1833
|
+
exports.shaderMaterialSampling = shaderMaterialSampling;
|
|
1834
|
+
exports.shaderMaterialStructs = shaderMaterialStructs;
|
|
1835
|
+
exports.shaderUtils = shaderUtils;
|
|
1836
|
+
|
|
1837
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
1838
|
+
|
|
1839
|
+
}));
|
|
1840
|
+
//# sourceMappingURL=index.umd.cjs.map
|