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,94 @@
|
|
|
1
|
+
import { Color, Vector2 } from 'three';
|
|
2
|
+
export class MaterialStructUniform {
|
|
3
|
+
|
|
4
|
+
constructor() {
|
|
5
|
+
|
|
6
|
+
this.init();
|
|
7
|
+
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
init() {
|
|
11
|
+
|
|
12
|
+
this.color = new Color( 0xffffff );
|
|
13
|
+
this.map = - 1;
|
|
14
|
+
|
|
15
|
+
this.metalness = 1.0;
|
|
16
|
+
this.metalnessMap = - 1;
|
|
17
|
+
|
|
18
|
+
this.roughness = 1.0;
|
|
19
|
+
this.roughnessMap = - 1;
|
|
20
|
+
|
|
21
|
+
this.ior = 1.0;
|
|
22
|
+
this.transmission = 0.0;
|
|
23
|
+
this.transmissionMap = - 1;
|
|
24
|
+
|
|
25
|
+
this.emissive = new Color( 0 );
|
|
26
|
+
this.emissiveIntensity = 1.0;
|
|
27
|
+
this.emissiveMap = - 1;
|
|
28
|
+
|
|
29
|
+
this.normalMap = - 1;
|
|
30
|
+
this.normalScale = new Vector2( 1, 1 );
|
|
31
|
+
|
|
32
|
+
this.opacity = 1.0;
|
|
33
|
+
this.alphaTest = 0.0;
|
|
34
|
+
|
|
35
|
+
// TODO: Clearcoat
|
|
36
|
+
|
|
37
|
+
// TODO: Sheen
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
updateFrom( material, textures = [] ) {
|
|
42
|
+
|
|
43
|
+
this.init();
|
|
44
|
+
|
|
45
|
+
// color
|
|
46
|
+
if ( 'color' in material ) this.color.copy( material.color );
|
|
47
|
+
else material.color.set( 0xffffff );
|
|
48
|
+
|
|
49
|
+
this.map = textures.indexOf( material.map );
|
|
50
|
+
|
|
51
|
+
// metalness
|
|
52
|
+
if ( 'metalness' in material ) this.metalness = material.metalness;
|
|
53
|
+
else this.metalness = 1.0;
|
|
54
|
+
|
|
55
|
+
this.metalnessMap = textures.indexOf( material.metalnessMap );
|
|
56
|
+
|
|
57
|
+
// roughness
|
|
58
|
+
if ( 'roughness' in material ) this.roughness = material.roughness;
|
|
59
|
+
else this.roughness = 1.0;
|
|
60
|
+
|
|
61
|
+
this.roughnessMap = textures.indexOf( material.roughnessMap );
|
|
62
|
+
|
|
63
|
+
// transmission
|
|
64
|
+
if ( 'ior' in material ) this.ior = material.ior;
|
|
65
|
+
else this.ior = 1.0;
|
|
66
|
+
|
|
67
|
+
if ( 'transmission' in material ) this.transmission = material.transmission;
|
|
68
|
+
else this.transmission = 0.0;
|
|
69
|
+
|
|
70
|
+
if ( 'transmissionMap' in material ) this.transmissionMap = textures.indexOf( material.transmissionMap );
|
|
71
|
+
|
|
72
|
+
// emission
|
|
73
|
+
if ( 'emissive' in material ) this.emissive.copy( material.emissive );
|
|
74
|
+
else this.emissive.set( 0 );
|
|
75
|
+
|
|
76
|
+
if ( 'emissiveIntensity' in material ) this.emissiveIntensity = material.emissiveIntensity;
|
|
77
|
+
else this.emissiveIntensity = 1.0;
|
|
78
|
+
|
|
79
|
+
this.emissiveMap = textures.indexOf( material.emissiveMap );
|
|
80
|
+
|
|
81
|
+
// normals
|
|
82
|
+
this.normalMap = textures.indexOf( material.normalMap );
|
|
83
|
+
if ( 'normalScale' in material ) this.normalScale.copy( material.normalScale );
|
|
84
|
+
else this.normalScale.set( 1, 1 );
|
|
85
|
+
|
|
86
|
+
// opacity
|
|
87
|
+
this.opacity = material.opacity;
|
|
88
|
+
|
|
89
|
+
// alpha test
|
|
90
|
+
this.alphaTest = material.alphaTest;
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WebGLArrayRenderTarget,
|
|
3
|
+
RGBAFormat,
|
|
4
|
+
UnsignedByteType,
|
|
5
|
+
MeshBasicMaterial,
|
|
6
|
+
Color,
|
|
7
|
+
RepeatWrapping,
|
|
8
|
+
LinearFilter,
|
|
9
|
+
NoToneMapping,
|
|
10
|
+
} from 'three';
|
|
11
|
+
import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
|
|
12
|
+
|
|
13
|
+
const prevColor = new Color();
|
|
14
|
+
export class RenderTarget2DArray extends WebGLArrayRenderTarget {
|
|
15
|
+
|
|
16
|
+
constructor( ...args ) {
|
|
17
|
+
|
|
18
|
+
super( ...args );
|
|
19
|
+
|
|
20
|
+
const tex = this.texture;
|
|
21
|
+
tex.format = RGBAFormat;
|
|
22
|
+
tex.type = UnsignedByteType;
|
|
23
|
+
tex.minFilter = LinearFilter;
|
|
24
|
+
tex.magFilter = LinearFilter;
|
|
25
|
+
tex.wrapS = RepeatWrapping;
|
|
26
|
+
tex.wrapT = RepeatWrapping;
|
|
27
|
+
tex.setTextures = ( ...args ) => {
|
|
28
|
+
|
|
29
|
+
this.setTextures( ...args );
|
|
30
|
+
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const fsQuad = new FullScreenQuad( new MeshBasicMaterial() );
|
|
34
|
+
this.fsQuad = fsQuad;
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setTextures( renderer, width, height, textures ) {
|
|
39
|
+
|
|
40
|
+
// save previous renderer state
|
|
41
|
+
const prevRenderTarget = renderer.getRenderTarget();
|
|
42
|
+
const prevToneMapping = renderer.toneMapping;
|
|
43
|
+
const prevAlpha = renderer.getClearAlpha();
|
|
44
|
+
renderer.getClearColor( prevColor );
|
|
45
|
+
|
|
46
|
+
// resize the render target
|
|
47
|
+
const depth = textures.length;
|
|
48
|
+
this.setSize( width, height, depth );
|
|
49
|
+
renderer.setClearColor( 0, 0 );
|
|
50
|
+
renderer.toneMapping = NoToneMapping;
|
|
51
|
+
|
|
52
|
+
// render each texture into each layer of the target
|
|
53
|
+
const fsQuad = this.fsQuad;
|
|
54
|
+
for ( let i = 0, l = depth; i < l; i ++ ) {
|
|
55
|
+
|
|
56
|
+
const texture = textures[ i ];
|
|
57
|
+
fsQuad.material.map = texture;
|
|
58
|
+
fsQuad.material.transparent = true;
|
|
59
|
+
|
|
60
|
+
renderer.setRenderTarget( this, i );
|
|
61
|
+
fsQuad.render( renderer );
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// reset the renderer
|
|
66
|
+
fsQuad.material.map = null;
|
|
67
|
+
renderer.setClearColor( prevColor, prevAlpha );
|
|
68
|
+
renderer.setRenderTarget( prevRenderTarget );
|
|
69
|
+
renderer.toneMapping = prevToneMapping;
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
dispose() {
|
|
74
|
+
|
|
75
|
+
super.dispose();
|
|
76
|
+
this.fsQuad.dispose();
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { BufferAttribute } from 'three';
|
|
2
|
+
import { mergeBufferGeometries, mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
3
|
+
function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
|
|
4
|
+
|
|
5
|
+
if ( ! Array.isArray( materials ) ) {
|
|
6
|
+
|
|
7
|
+
materials = [ materials ];
|
|
8
|
+
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const vertCount = geometry.attributes.position.count;
|
|
12
|
+
const materialArray = new Uint8Array( vertCount );
|
|
13
|
+
let groups = geometry.groups;
|
|
14
|
+
if ( groups.length === 0 ) {
|
|
15
|
+
|
|
16
|
+
groups = [ { count: vertCount, start: 0, materialIndex: 0 } ];
|
|
17
|
+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for ( let i = 0; i < groups.length; i ++ ) {
|
|
21
|
+
|
|
22
|
+
const group = groups[ i ];
|
|
23
|
+
const { count, start } = group;
|
|
24
|
+
const endCount = Math.min( count, vertCount - start );
|
|
25
|
+
const mat = materials[ group.materialIndex ];
|
|
26
|
+
const materialIndex = allMaterials.indexOf( mat );
|
|
27
|
+
|
|
28
|
+
for ( let j = 0; j < endCount; j ++ ) {
|
|
29
|
+
|
|
30
|
+
materialArray[ start + j ] = materialIndex;
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return new BufferAttribute( materialArray, 1, false );
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function mergeMeshes( meshes, options = {} ) {
|
|
41
|
+
|
|
42
|
+
options = { attributes: null, cloneGeometry: true, ...options };
|
|
43
|
+
|
|
44
|
+
const transformedGeometry = [];
|
|
45
|
+
const materialSet = new Set();
|
|
46
|
+
for ( let i = 0, l = meshes.length; i < l; i ++ ) {
|
|
47
|
+
|
|
48
|
+
// save any materials
|
|
49
|
+
const mesh = meshes[ i ];
|
|
50
|
+
if ( mesh.visible === false ) continue;
|
|
51
|
+
|
|
52
|
+
if ( Array.isArray( mesh.material ) ) {
|
|
53
|
+
|
|
54
|
+
mesh.material.forEach( m => materialSet.add( m ) );
|
|
55
|
+
|
|
56
|
+
} else {
|
|
57
|
+
|
|
58
|
+
materialSet.add( mesh.material );
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const materials = Array.from( materialSet );
|
|
65
|
+
for ( let i = 0, l = meshes.length; i < l; i ++ ) {
|
|
66
|
+
|
|
67
|
+
// ensure the matrix world is up to date
|
|
68
|
+
const mesh = meshes[ i ];
|
|
69
|
+
if ( mesh.visible === false ) continue;
|
|
70
|
+
|
|
71
|
+
mesh.updateMatrixWorld();
|
|
72
|
+
|
|
73
|
+
// apply the matrix world to the geometry
|
|
74
|
+
const originalGeometry = meshes[ i ].geometry;
|
|
75
|
+
let geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
|
|
76
|
+
geometry.applyMatrix4( mesh.matrixWorld );
|
|
77
|
+
|
|
78
|
+
const attrs = options.attributes;
|
|
79
|
+
if ( ! geometry.attributes.normal && ( attrs && attrs.includes( 'normal' ) ) ) {
|
|
80
|
+
|
|
81
|
+
geometry.computeVertexNormals();
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if ( ! geometry.attributes.uv && ( attrs && attrs.includes( 'uv' ) ) ) {
|
|
86
|
+
|
|
87
|
+
const vertCount = geometry.attributes.position.count;
|
|
88
|
+
geometry.setAttribute( 'uv', new BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if ( ! geometry.attributes.tangent && ( attrs && attrs.includes( 'tangent' ) ) ) {
|
|
93
|
+
|
|
94
|
+
if ( mesh.material.normalMap ) {
|
|
95
|
+
|
|
96
|
+
// computeTangents requires an index buffer
|
|
97
|
+
if ( geometry.index === null ) {
|
|
98
|
+
|
|
99
|
+
geometry = mergeVertices( geometry );
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
geometry.computeTangents();
|
|
104
|
+
|
|
105
|
+
} else {
|
|
106
|
+
|
|
107
|
+
const vertCount = geometry.attributes.position.count;
|
|
108
|
+
geometry.setAttribute( 'tangent', new BufferAttribute( new Float32Array( vertCount * 4 ), 4, false ) );
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if ( ! geometry.index ) {
|
|
115
|
+
|
|
116
|
+
// TODO: compute a typed array
|
|
117
|
+
const indexCount = geometry.attributes.position.count;
|
|
118
|
+
const array = new Array( indexCount );
|
|
119
|
+
for ( let i = 0; i < indexCount; i ++ ) {
|
|
120
|
+
|
|
121
|
+
array[ i ] = i;
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
geometry.setIndex( array );
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// trim any unneeded attributes
|
|
130
|
+
if ( options.attributes ) {
|
|
131
|
+
|
|
132
|
+
for ( const key in geometry.attributes ) {
|
|
133
|
+
|
|
134
|
+
if ( ! options.attributes.includes( key ) ) {
|
|
135
|
+
|
|
136
|
+
geometry.deleteAttribute( key );
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// create the material index attribute
|
|
145
|
+
const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
|
|
146
|
+
geometry.setAttribute( 'materialIndex', materialIndexAttribute );
|
|
147
|
+
|
|
148
|
+
transformedGeometry.push( geometry );
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const textureSet = new Set();
|
|
153
|
+
materials.forEach( material => {
|
|
154
|
+
|
|
155
|
+
for ( const key in material ) {
|
|
156
|
+
|
|
157
|
+
const value = material[ key ];
|
|
158
|
+
if ( value && value.isTexture ) {
|
|
159
|
+
|
|
160
|
+
textureSet.add( value );
|
|
161
|
+
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
} );
|
|
167
|
+
|
|
168
|
+
const geometry = mergeBufferGeometries( transformedGeometry, false );
|
|
169
|
+
const textures = Array.from( textureSet );
|
|
170
|
+
return { geometry, materials, textures };
|
|
171
|
+
|
|
172
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// https://github.com/mozilla/Spoke/commit/9701d647020e09d584885bd457eb225e9995c12f
|
|
2
|
+
import XAtlas from 'xatlas-web';
|
|
3
|
+
import { BufferGeometry, Float32BufferAttribute, Uint32BufferAttribute } from 'three';
|
|
4
|
+
|
|
5
|
+
export class UVUnwrapper {
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
|
|
9
|
+
this._module = null;
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async load() {
|
|
14
|
+
|
|
15
|
+
const wasmurl = new URL( '../../node_modules/xatlas-web/dist/xatlas-web.wasm', import.meta.url );
|
|
16
|
+
this._module = XAtlas( {
|
|
17
|
+
|
|
18
|
+
locateFile( path ) {
|
|
19
|
+
|
|
20
|
+
if ( path.endsWith( '.wasm' ) ) {
|
|
21
|
+
|
|
22
|
+
return wasmurl.toString();
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return path;
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
} );
|
|
31
|
+
return this._module.ready;
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
generate( geometry ) {
|
|
36
|
+
|
|
37
|
+
const xatlas = this._module;
|
|
38
|
+
const originalVertexCount = geometry.attributes.position.count;
|
|
39
|
+
const originalIndexCount = geometry.index.count;
|
|
40
|
+
|
|
41
|
+
xatlas.createAtlas();
|
|
42
|
+
|
|
43
|
+
const meshInfo = xatlas.createMesh( originalVertexCount, originalIndexCount, true, true );
|
|
44
|
+
xatlas.HEAPU16.set( geometry.index.array, meshInfo.indexOffset / Uint16Array.BYTES_PER_ELEMENT );
|
|
45
|
+
xatlas.HEAPF32.set( geometry.attributes.position.array, meshInfo.positionOffset / Float32Array.BYTES_PER_ELEMENT );
|
|
46
|
+
xatlas.HEAPF32.set( geometry.attributes.normal.array, meshInfo.normalOffset / Float32Array.BYTES_PER_ELEMENT );
|
|
47
|
+
xatlas.HEAPF32.set( geometry.attributes.uv.array, meshInfo.uvOffset / Float32Array.BYTES_PER_ELEMENT );
|
|
48
|
+
|
|
49
|
+
const statusCode = xatlas.addMesh();
|
|
50
|
+
if ( statusCode !== AddMeshStatus.Success ) {
|
|
51
|
+
|
|
52
|
+
throw new Error( `UVUnwrapper: Error adding mesh. Status code ${ statusCode }` );
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
xatlas.generateAtlas();
|
|
57
|
+
|
|
58
|
+
const meshData = xatlas.getMeshData( meshInfo.meshId );
|
|
59
|
+
const oldPositionArray = geometry.attributes.position.array;
|
|
60
|
+
const oldNormalArray = geometry.attributes.normal.array;
|
|
61
|
+
const oldUvArray = geometry.attributes.uv.array;
|
|
62
|
+
|
|
63
|
+
const newPositionArray = new Float32Array( meshData.newVertexCount * 3 );
|
|
64
|
+
const newNormalArray = new Float32Array( meshData.newVertexCount * 3 );
|
|
65
|
+
const newUvArray = new Float32Array( meshData.newVertexCount * 2 );
|
|
66
|
+
const newUv2Array = new Float32Array( xatlas.HEAPF32.buffer, meshData.uvOffset, meshData.newVertexCount * 2 );
|
|
67
|
+
const newIndexArray = new Uint32Array( xatlas.HEAPU32.buffer, meshData.indexOffset, meshData.newIndexCount );
|
|
68
|
+
const originalIndexArray = new Uint32Array(
|
|
69
|
+
xatlas.HEAPU32.buffer,
|
|
70
|
+
meshData.originalIndexOffset,
|
|
71
|
+
meshData.newVertexCount
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
for ( let i = 0; i < meshData.newVertexCount; i ++ ) {
|
|
75
|
+
|
|
76
|
+
const originalIndex = originalIndexArray[ i ];
|
|
77
|
+
newPositionArray[ i * 3 ] = oldPositionArray[ originalIndex * 3 ];
|
|
78
|
+
newPositionArray[ i * 3 + 1 ] = oldPositionArray[ originalIndex * 3 + 1 ];
|
|
79
|
+
newPositionArray[ i * 3 + 2 ] = oldPositionArray[ originalIndex * 3 + 2 ];
|
|
80
|
+
newNormalArray[ i * 3 ] = oldNormalArray[ originalIndex * 3 ];
|
|
81
|
+
newNormalArray[ i * 3 + 1 ] = oldNormalArray[ originalIndex * 3 + 1 ];
|
|
82
|
+
newNormalArray[ i * 3 + 2 ] = oldNormalArray[ originalIndex * 3 + 2 ];
|
|
83
|
+
newUvArray[ i * 2 ] = oldUvArray[ originalIndex * 2 ];
|
|
84
|
+
newUvArray[ i * 2 + 1 ] = oldUvArray[ originalIndex * 2 + 1 ];
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const newGeometry = new BufferGeometry();
|
|
89
|
+
newGeometry.addAttribute( 'position', new Float32BufferAttribute( newPositionArray, 3 ) );
|
|
90
|
+
newGeometry.addAttribute( 'normal', new Float32BufferAttribute( newNormalArray, 3 ) );
|
|
91
|
+
newGeometry.addAttribute( 'uv', new Float32BufferAttribute( newUvArray, 2 ) );
|
|
92
|
+
newGeometry.addAttribute( 'uv2', new Float32BufferAttribute( newUv2Array, 2 ) );
|
|
93
|
+
newGeometry.setIndex( new Uint32BufferAttribute( newIndexArray, 1 ) );
|
|
94
|
+
|
|
95
|
+
mesh.geometry = newGeometry;
|
|
96
|
+
|
|
97
|
+
xatlas.destroyAtlas();
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { Scene, WebGLRenderer, MeshBasicMaterial, Vector2, Mesh, PerspectiveCamera, sRGBEncoding } from 'three';
|
|
2
|
+
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
3
|
+
import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
|
|
4
|
+
import { SAH } from 'three-mesh-bvh';
|
|
5
|
+
import { GenerateMeshBVHWorker } from 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js';
|
|
6
|
+
import { PathTracingRenderer } from '../core/PathTracingRenderer.js';
|
|
7
|
+
import { mergeMeshes } from '../utils/GeometryPreparationUtils.js';
|
|
8
|
+
import { PhysicalPathTracingMaterial } from '../materials/PhysicalPathTracingMaterial.js';
|
|
9
|
+
import { MaterialReducer } from '../core/MaterialReducer.js';
|
|
10
|
+
|
|
11
|
+
export class PathTracingViewer {
|
|
12
|
+
|
|
13
|
+
get domElement() {
|
|
14
|
+
|
|
15
|
+
return this._container;
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
|
|
21
|
+
this.scene = new Scene();
|
|
22
|
+
this.camera = new PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.025, 500 );
|
|
23
|
+
this.camera.position.set( - 1, 0.25, 1 );
|
|
24
|
+
|
|
25
|
+
this.renderer = new WebGLRenderer( { antialias: true } );
|
|
26
|
+
this.fsQuad = new FullScreenQuad( new MeshBasicMaterial( { transparent: true } ) );
|
|
27
|
+
this.ptRenderer = new PathTracingRenderer( this.renderer );
|
|
28
|
+
this.ptModel = null;
|
|
29
|
+
this.ptMaterials = null;
|
|
30
|
+
this.ptTextures = null;
|
|
31
|
+
this.model = null;
|
|
32
|
+
this.bvhGenerator = new GenerateMeshBVHWorker();
|
|
33
|
+
this.onRender = null;
|
|
34
|
+
this.enablePathTracing = true;
|
|
35
|
+
this.pausePathTracing = false;
|
|
36
|
+
this.samplesPerFrame = 1;
|
|
37
|
+
this._scale = 1;
|
|
38
|
+
this._nextObject = null;
|
|
39
|
+
this._needsSizeUpdate = false;
|
|
40
|
+
this._newSize = new Vector2();
|
|
41
|
+
this._resizeObserver = new ResizeObserver( entries => {
|
|
42
|
+
|
|
43
|
+
const { contentRect } = entries[ 0 ];
|
|
44
|
+
this._newSize.set( contentRect.width, contentRect.height );
|
|
45
|
+
this._needsSizeUpdate = true;
|
|
46
|
+
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
const container = document.createElement( 'div' );
|
|
50
|
+
container.style.overflow = 'hidden';
|
|
51
|
+
container.appendChild( this.renderer.domElement );
|
|
52
|
+
this._container = container;
|
|
53
|
+
|
|
54
|
+
this.ptRenderer.camera = this.camera;
|
|
55
|
+
this.ptRenderer.material = new PhysicalPathTracingMaterial();
|
|
56
|
+
this.renderer.outputEncoding = sRGBEncoding;
|
|
57
|
+
this._resizeObserver.observe( container );
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_updateSize() {
|
|
62
|
+
|
|
63
|
+
const dpr = window.devicePixelRatio;
|
|
64
|
+
const scale = this._scale;
|
|
65
|
+
const size = this._newSize;
|
|
66
|
+
this.renderer.setPixelRatio( dpr );
|
|
67
|
+
|
|
68
|
+
this.renderer.domElement.style.aspectRatio = `${ size.width } / ${ size.height }`;
|
|
69
|
+
this.renderer.domElement.style.width = `100%`;
|
|
70
|
+
this.renderer.setSize( scale * size.width, scale * size.height, false );
|
|
71
|
+
this.ptRenderer.target.setSize( size.width * scale * dpr, size.height * scale * dpr );
|
|
72
|
+
this.camera.aspect = size.width / size.height;
|
|
73
|
+
this.camera.updateProjectionMatrix();
|
|
74
|
+
this._needsSizeUpdate = false;
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setScale( scale ) {
|
|
79
|
+
|
|
80
|
+
this._scale = scale;
|
|
81
|
+
this._needsSizeUpdate = true;
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setModel( object, bvhOptions = {} ) {
|
|
86
|
+
|
|
87
|
+
if ( this.bvhGenerator.running ) {
|
|
88
|
+
|
|
89
|
+
this._nextObject = object;
|
|
90
|
+
return;
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
object.updateMatrixWorld( true );
|
|
95
|
+
|
|
96
|
+
const materialReducer = new MaterialReducer();
|
|
97
|
+
materialReducer.process( object );
|
|
98
|
+
|
|
99
|
+
const meshes = [];
|
|
100
|
+
object.traverse( c => {
|
|
101
|
+
|
|
102
|
+
if ( c.isMesh ) {
|
|
103
|
+
|
|
104
|
+
meshes.push( c );
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
} );
|
|
109
|
+
|
|
110
|
+
const { geometry, materials, textures } = mergeMeshes( meshes, { attributes: [ 'position', 'normal', 'tangent', 'uv' ] } );
|
|
111
|
+
return this
|
|
112
|
+
.bvhGenerator
|
|
113
|
+
.generate( geometry, { strategy: SAH, maxLeafTris: 1, ...bvhOptions } )
|
|
114
|
+
.then( bvh => {
|
|
115
|
+
|
|
116
|
+
if ( this._nextObject ) {
|
|
117
|
+
|
|
118
|
+
this.setModel( this._nextObject );
|
|
119
|
+
this._nextObject = null;
|
|
120
|
+
return;
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if ( this.model ) {
|
|
125
|
+
|
|
126
|
+
this.scene.remove( this.model );
|
|
127
|
+
this.ptTextures.forEach( tex => tex.dispose() );
|
|
128
|
+
this.ptMaterials.forEach( mat => mat.dispose() );
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const mesh = new Mesh( geometry );
|
|
133
|
+
this.scene.add( object );
|
|
134
|
+
this.ptModel = mesh;
|
|
135
|
+
this.ptMaterials = materials;
|
|
136
|
+
this.ptTextures = textures;
|
|
137
|
+
this.model = object;
|
|
138
|
+
|
|
139
|
+
const { ptRenderer } = this;
|
|
140
|
+
const { material } = ptRenderer;
|
|
141
|
+
|
|
142
|
+
// dispose of textures because they cannot be updated in later version of three.js
|
|
143
|
+
material.bvh.dispose();
|
|
144
|
+
material.normalAttribute.dispose();
|
|
145
|
+
material.tangentAttribute.dispose();
|
|
146
|
+
material.uvAttribute.dispose();
|
|
147
|
+
material.materialIndexAttribute.dispose();
|
|
148
|
+
material.textures.dispose();
|
|
149
|
+
|
|
150
|
+
material.bvh.updateFrom( bvh );
|
|
151
|
+
material.normalAttribute.updateFrom( geometry.attributes.normal );
|
|
152
|
+
material.tangentAttribute.updateFrom( geometry.attributes.tangent );
|
|
153
|
+
material.uvAttribute.updateFrom( geometry.attributes.uv );
|
|
154
|
+
material.materialIndexAttribute.updateFrom( geometry.attributes.materialIndex );
|
|
155
|
+
material.textures.setTextures( this.renderer, 2048, 2048, textures );
|
|
156
|
+
material.materials.updateFrom( materials, textures );
|
|
157
|
+
material.setDefine( 'MATERIAL_LENGTH', materials.length );
|
|
158
|
+
ptRenderer.reset();
|
|
159
|
+
|
|
160
|
+
} );
|
|
161
|
+
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
setEnvironment( envMap ) {
|
|
165
|
+
|
|
166
|
+
this.scene.environment = envMap;
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
init() {
|
|
171
|
+
|
|
172
|
+
const { ptRenderer, renderer, fsQuad, camera } = this;
|
|
173
|
+
|
|
174
|
+
const controls = new OrbitControls( camera, renderer.domElement );
|
|
175
|
+
let delaySamples = 0;
|
|
176
|
+
controls.addEventListener( 'change', () => {
|
|
177
|
+
|
|
178
|
+
const tiles = this.ptRenderer.tiles;
|
|
179
|
+
if ( tiles.x * tiles.y !== 1 ) {
|
|
180
|
+
|
|
181
|
+
delaySamples = 1;
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
ptRenderer.reset();
|
|
186
|
+
|
|
187
|
+
} );
|
|
188
|
+
|
|
189
|
+
renderer.setAnimationLoop( () => {
|
|
190
|
+
|
|
191
|
+
if ( this.pausePathTracing ) {
|
|
192
|
+
|
|
193
|
+
return;
|
|
194
|
+
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if ( this._needsSizeUpdate ) {
|
|
198
|
+
|
|
199
|
+
this._updateSize();
|
|
200
|
+
this.ptRenderer.reset();
|
|
201
|
+
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if ( this.model ) {
|
|
205
|
+
|
|
206
|
+
// ensure materials are updated
|
|
207
|
+
const { ptRenderer, ptMaterials, ptTextures } = this;
|
|
208
|
+
ptRenderer.material.materials.updateFrom( ptMaterials, ptTextures );
|
|
209
|
+
|
|
210
|
+
if ( this.enablePathTracing && delaySamples === 0 ) {
|
|
211
|
+
|
|
212
|
+
camera.updateMatrixWorld();
|
|
213
|
+
|
|
214
|
+
for ( let i = 0, l = this.samplesPerFrame; i < l; i ++ ) {
|
|
215
|
+
|
|
216
|
+
ptRenderer.update();
|
|
217
|
+
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if ( ptRenderer.samples < 1 ) {
|
|
221
|
+
|
|
222
|
+
renderer.render( this.scene, this.camera );
|
|
223
|
+
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
renderer.autoClear = false;
|
|
227
|
+
fsQuad.material.map = ptRenderer.target.texture;
|
|
228
|
+
fsQuad.render( renderer );
|
|
229
|
+
renderer.autoClear = true;
|
|
230
|
+
|
|
231
|
+
} else {
|
|
232
|
+
|
|
233
|
+
if ( delaySamples > 0 ) {
|
|
234
|
+
|
|
235
|
+
delaySamples --;
|
|
236
|
+
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
renderer.render( this.scene, this.camera );
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
} else {
|
|
244
|
+
|
|
245
|
+
renderer.clear();
|
|
246
|
+
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if ( this.onRender ) {
|
|
250
|
+
|
|
251
|
+
this.onRender();
|
|
252
|
+
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
} );
|
|
256
|
+
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
}
|