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.
@@ -0,0 +1,46 @@
1
+ import { SAH } from 'three-mesh-bvh';
2
+ import { GenerateMeshBVHWorker } from 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js';
3
+ import { mergeMeshes } from '../utils/GeometryPreparationUtils.js';
4
+
5
+ export class PathTracingSceneGenerator {
6
+
7
+ constructor() {
8
+
9
+ this.bvhGenerator = new GenerateMeshBVHWorker();
10
+
11
+ }
12
+
13
+ async generate( scene, options = {} ) {
14
+
15
+ const { bvhGenerator } = this;
16
+ const meshes = [];
17
+
18
+ scene.traverse( c => {
19
+
20
+ if ( c.isMesh ) {
21
+
22
+ meshes.push( c );
23
+
24
+ }
25
+
26
+ } );
27
+
28
+ const { geometry, materials, textures } = mergeMeshes( meshes, { attributes: [ 'position', 'normal', 'tangent', 'uv' ] } );
29
+ const bvhPromise = bvhGenerator.generate( geometry, { strategy: SAH, ...options, maxLeafTris: 1 } );
30
+
31
+ return {
32
+ scene,
33
+ materials,
34
+ textures,
35
+ bvh: await bvhPromise,
36
+ };
37
+
38
+ }
39
+
40
+ dispose() {
41
+
42
+ this.bvhGenerator.terminate();
43
+
44
+ }
45
+
46
+ }
package/src/index.js ADDED
@@ -0,0 +1,21 @@
1
+ // core
2
+ export * from './core/PathTracingRenderer.js';
3
+ export * from './core/PathTracingSceneGenerator.js';
4
+ export * from './core/MaterialReducer.js';
5
+
6
+ // uniforms
7
+ export * from './uniforms/MaterialStructArrayUniform.js';
8
+ export * from './uniforms/MaterialStructUniform.js';
9
+ export * from './uniforms/RenderTarget2DArray.js';
10
+
11
+ // utils
12
+ export * from './utils/GeometryPreparationUtils.js';
13
+
14
+ // materials
15
+ export * from './materials/MaterialBase.js';
16
+ export * from './materials/PhysicalPathTracingMaterial.js';
17
+
18
+ // shaders
19
+ export * from './shader/shaderMaterialSampling.js';
20
+ export * from './shader/shaderUtils.js';
21
+ export * from './shader/shaderStructs.js';
@@ -0,0 +1,197 @@
1
+ import { TangentSpaceNormalMap, Vector2 } from 'three';
2
+ import { MaterialBase } from './MaterialBase.js';
3
+ import { MeshBVHUniformStruct, shaderStructs, shaderIntersectFunction } from 'three-mesh-bvh';
4
+ import { shaderMaterialStructs } from '../shader/shaderStructs.js';
5
+ import { shaderUtils } from '../shader/shaderUtils.js';
6
+
7
+ export class AmbientOcclusionMaterial extends MaterialBase {
8
+
9
+ get normalMap() {
10
+
11
+ return this.uniforms.normalMap.value;
12
+
13
+ }
14
+
15
+ set normalMap( v ) {
16
+
17
+ this.uniforms.normalMap.value = v;
18
+ this.setDefine( 'USE_NORMALMAP', v ? null : '' );
19
+
20
+ }
21
+
22
+ get normalMapType() {
23
+
24
+ return TangentSpaceNormalMap;
25
+
26
+ }
27
+
28
+ set normalMapType( v ) {
29
+
30
+ if ( v !== TangentSpaceNormalMap ) {
31
+
32
+ throw new Error( 'AmbientOcclusionMaterial: Only tangent space normal map are supported' );
33
+
34
+ }
35
+
36
+ }
37
+
38
+ constructor( parameters ) {
39
+
40
+ super( {
41
+
42
+ defines: {
43
+ SAMPLES: 10,
44
+ },
45
+
46
+ uniforms: {
47
+ bvh: { value: new MeshBVHUniformStruct() },
48
+ radius: { value: 1.0 },
49
+ seed: { value: 0 },
50
+
51
+ normalMap: { value: null },
52
+ normalScale: { value: new Vector2( 1, 1 ) },
53
+ },
54
+
55
+ vertexShader: /* glsl */`
56
+
57
+ varying vec3 vNorm;
58
+ varying vec3 vPos;
59
+
60
+ #if defined(USE_NORMALMAP) && defined(USE_TANGENT)
61
+
62
+ varying vec2 vUv;
63
+ varying vec4 vTan;
64
+
65
+ #endif
66
+
67
+ void main() {
68
+
69
+ vec4 mvPosition = vec4( position, 1.0 );
70
+ mvPosition = modelViewMatrix * mvPosition;
71
+ gl_Position = projectionMatrix * mvPosition;
72
+
73
+ mat3 modelNormalMatrix = transpose( inverse( mat3( modelMatrix ) ) );
74
+ vNorm = normalize( modelNormalMatrix * normal );
75
+ vPos = ( modelMatrix * vec4( position, 1.0 ) ).xyz;
76
+
77
+ #if defined(USE_NORMALMAP) && defined(USE_TANGENT)
78
+
79
+ vUv = uv;
80
+ vTan = tangent;
81
+
82
+ #endif
83
+
84
+ }
85
+
86
+ `,
87
+
88
+ fragmentShader: /* glsl */`
89
+ #define RAY_OFFSET 1e-5
90
+
91
+ precision highp isampler2D;
92
+ precision highp usampler2D;
93
+ precision highp sampler2DArray;
94
+ #include <common>
95
+ #include <cube_uv_reflection_fragment>
96
+
97
+ ${ shaderStructs }
98
+ ${ shaderIntersectFunction }
99
+ ${ shaderMaterialStructs }
100
+ ${ shaderUtils }
101
+
102
+ uniform BVH bvh;
103
+ uniform int seed;
104
+ uniform float radius;
105
+
106
+ varying vec3 vNorm;
107
+ varying vec3 vPos;
108
+
109
+ #if defined(USE_NORMALMAP) && defined(USE_TANGENT)
110
+
111
+ uniform sampler2D normalMap;
112
+ uniform vec2 normalScale;
113
+ varying vec2 vUv;
114
+ varying vec4 vTan;
115
+
116
+ #endif
117
+
118
+ void main() {
119
+
120
+ rng_initialize( gl_FragCoord.xy, seed );
121
+
122
+ // compute the flat face surface normal
123
+ vec3 fdx = vec3( dFdx( vPos.x ), dFdx( vPos.y ), dFdx( vPos.z ) );
124
+ vec3 fdy = vec3( dFdy( vPos.x ), dFdy( vPos.y ), dFdy( vPos.z ) );
125
+ vec3 faceNormal = normalize( cross( fdx, fdy ) );
126
+
127
+ // find the max component to scale the offset to account for floating point error
128
+ vec3 absPoint = abs( vPos );
129
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
130
+ vec3 normal = vNorm;
131
+
132
+ #if defined(USE_NORMALMAP) && defined(USE_TANGENT)
133
+
134
+ // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
135
+ // resulting in NaNs and slow path tracing.
136
+ if ( length( vTan.xyz ) > 0.0 ) {
137
+
138
+ vec2 uv = vUv;
139
+ vec3 tangent = normalize( vTan.xyz );
140
+ vec3 bitangent = normalize( cross( normal, tangent ) * vTan.w );
141
+ mat3 vTBN = mat3( tangent, bitangent, normal );
142
+
143
+ vec3 texNormal = texture2D( normalMap, uv ).xyz * 2.0 - 1.0;
144
+ texNormal.xy *= normalScale;
145
+ normal = vTBN * texNormal;
146
+
147
+ }
148
+
149
+ #endif
150
+
151
+ normal *= gl_FrontFacing ? 1.0 : - 1.0;
152
+
153
+ vec3 rayOrigin = vPos + faceNormal * ( maxPoint + 1.0 ) * RAY_OFFSET;
154
+ float accumulated = 0.0;
155
+ for ( int i = 0; i < SAMPLES; i ++ ) {
156
+
157
+ // sample the cosine weighted hemisphere and discard the sample if it's below
158
+ // the geometric surface
159
+ vec3 rayDirection = getHemisphereSample( normalize( normal ), rand4().xy );
160
+
161
+ // check if we hit the mesh and its within the specified radius
162
+ float side = 1.0;
163
+ float dist = 0.0;
164
+ vec3 barycoord = vec3( 0.0 );
165
+ vec3 outNormal = vec3( 0.0 );
166
+ uvec4 faceIndices = uvec4( 0u );
167
+
168
+ // if the ray is above the geometry surface, and it doesn't hit another surface within the specified radius then
169
+ // we consider it lit
170
+ if (
171
+ dot( rayDirection, faceNormal ) > 0.0 &&
172
+ (
173
+ ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, outNormal, barycoord, side, dist ) ||
174
+ dist > radius
175
+ )
176
+ ) {
177
+
178
+ accumulated += 1.0;
179
+
180
+ }
181
+
182
+ }
183
+
184
+ gl_FragColor.rgb = vec3( accumulated / float( SAMPLES ) );
185
+ gl_FragColor.a = 1.0;
186
+
187
+ }
188
+
189
+ `
190
+
191
+ } );
192
+
193
+ this.setValues( parameters );
194
+
195
+ }
196
+
197
+ }
@@ -0,0 +1,285 @@
1
+ import { Matrix4, Color } from 'three';
2
+ import { MaterialBase } from './MaterialBase.js';
3
+ import {
4
+ MeshBVHUniformStruct, FloatVertexAttributeTexture, UIntVertexAttributeTexture,
5
+ shaderStructs, shaderIntersectFunction,
6
+ } from 'three-mesh-bvh';
7
+ import { shaderMaterialStructs } from '../shader/shaderStructs.js';
8
+ import { shaderUtils } from '../shader/shaderUtils.js';
9
+ import { MaterialStructArrayUniform } from '../uniforms/MaterialStructArrayUniform.js';
10
+ import { RenderTarget2DArray } from '../uniforms/RenderTarget2DArray.js';
11
+
12
+ export class LambertPathTracingMaterial extends MaterialBase {
13
+
14
+ // three.js relies on this field to add env map functions and defines
15
+ get envMap() {
16
+
17
+ return this.environmentMap;
18
+
19
+ }
20
+
21
+ constructor( parameters ) {
22
+
23
+ super( {
24
+
25
+ transparent: true,
26
+ depthWrite: false,
27
+
28
+ defines: {
29
+ BOUNCES: 3,
30
+ MATERIAL_LENGTH: 0,
31
+ GRADIENT_BG: 0,
32
+ DISPLAY_FLOOR: 1,
33
+ },
34
+
35
+ uniforms: {
36
+ bvh: { value: new MeshBVHUniformStruct() },
37
+ normalAttribute: { value: new FloatVertexAttributeTexture() },
38
+ tangentAttribute: { value: new FloatVertexAttributeTexture() },
39
+ uvAttribute: { value: new FloatVertexAttributeTexture() },
40
+ materialIndexAttribute: { value: new UIntVertexAttributeTexture() },
41
+ materials: { value: new MaterialStructArrayUniform() },
42
+ textures: { value: new RenderTarget2DArray().texture },
43
+ cameraWorldMatrix: { value: new Matrix4() },
44
+ invProjectionMatrix: { value: new Matrix4() },
45
+ environmentBlur: { value: 0.2 },
46
+ environmentIntensity: { value: 2.0 },
47
+ environmentMap: { value: null },
48
+ seed: { value: 0 },
49
+ opacity: { value: 1 },
50
+
51
+ gradientTop: { value: new Color( 0xbfd8ff ) },
52
+ gradientBottom: { value: new Color( 0xffffff ) },
53
+
54
+ bgGradientTop: { value: new Color( 0x111111 ) },
55
+ bgGradientBottom: { value: new Color( 0x000000 ) },
56
+
57
+ },
58
+
59
+ vertexShader: /* glsl */`
60
+
61
+ varying vec2 vUv;
62
+ void main() {
63
+
64
+ vec4 mvPosition = vec4( position, 1.0 );
65
+ mvPosition = modelViewMatrix * mvPosition;
66
+ gl_Position = projectionMatrix * mvPosition;
67
+
68
+ vUv = uv;
69
+
70
+ }
71
+
72
+ `,
73
+
74
+ fragmentShader: /* glsl */`
75
+ #define RAY_OFFSET 1e-5
76
+
77
+ precision highp isampler2D;
78
+ precision highp usampler2D;
79
+ precision highp sampler2DArray;
80
+ vec4 envMapTexelToLinear( vec4 a ) { return a; }
81
+ #include <common>
82
+ #include <cube_uv_reflection_fragment>
83
+
84
+ ${ shaderStructs }
85
+ ${ shaderIntersectFunction }
86
+ ${ shaderMaterialStructs }
87
+ ${ shaderUtils }
88
+
89
+ #ifdef USE_ENVMAP
90
+
91
+ uniform float environmentBlur;
92
+ uniform sampler2D environmentMap;
93
+
94
+ #else
95
+
96
+ uniform vec3 gradientTop;
97
+ uniform vec3 gradientBottom;
98
+
99
+ #endif
100
+
101
+ #if GRADIENT_BG
102
+
103
+ uniform vec3 bgGradientTop;
104
+ uniform vec3 bgGradientBottom;
105
+
106
+ #endif
107
+
108
+ uniform mat4 cameraWorldMatrix;
109
+ uniform mat4 invProjectionMatrix;
110
+ uniform sampler2D normalAttribute;
111
+ uniform sampler2D tangentAttribute;
112
+ uniform sampler2D uvAttribute;
113
+ uniform usampler2D materialIndexAttribute;
114
+ uniform BVH bvh;
115
+ uniform float environmentIntensity;
116
+ uniform int seed;
117
+ uniform float opacity;
118
+ uniform Material materials[ MATERIAL_LENGTH ];
119
+ uniform sampler2DArray textures;
120
+ varying vec2 vUv;
121
+
122
+ void main() {
123
+
124
+ rng_initialize( gl_FragCoord.xy, seed );
125
+
126
+ // get [-1, 1] normalized device coordinates
127
+ vec2 ndc = 2.0 * vUv - vec2( 1.0 );
128
+ vec3 rayOrigin, rayDirection;
129
+ ndcToCameraRay( ndc, cameraWorldMatrix, invProjectionMatrix, rayOrigin, rayDirection );
130
+
131
+ // Lambertian render
132
+ gl_FragColor = vec4( 0.0 );
133
+
134
+ vec3 throughputColor = vec3( 1.0 );
135
+
136
+ // hit results
137
+ uvec4 faceIndices = uvec4( 0u );
138
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
139
+ vec3 barycoord = vec3( 0.0 );
140
+ float side = 1.0;
141
+ float dist = 0.0;
142
+ int i;
143
+ for ( i = 0; i < BOUNCES; i ++ ) {
144
+
145
+ if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
146
+
147
+ #if GRADIENT_BG
148
+
149
+ if ( i == 0 ) {
150
+
151
+ rayDirection = normalize( rayDirection );
152
+ float value = ( rayDirection.y + 1.0 ) / 2.0;
153
+
154
+ value = pow( value, 2.0 );
155
+
156
+ gl_FragColor = vec4( mix( bgGradientBottom, bgGradientTop, value ), 1.0 );
157
+ break;
158
+
159
+ }
160
+
161
+ #endif
162
+
163
+ #ifdef USE_ENVMAP
164
+
165
+ vec3 skyColor = textureCubeUV( environmentMap, rayDirection, environmentBlur ).rgb;
166
+
167
+ #else
168
+
169
+ rayDirection = normalize( rayDirection );
170
+ float value = ( rayDirection.y + 1.0 ) / 2.0;
171
+ vec3 skyColor = mix( gradientBottom, gradientTop, value );
172
+
173
+ #endif
174
+
175
+ gl_FragColor += vec4( skyColor * throughputColor * environmentIntensity, 1.0 );
176
+
177
+ break;
178
+
179
+ }
180
+
181
+
182
+ uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
183
+ Material material = materials[ materialIndex ];
184
+
185
+ if ( material.opacity < rand() ) {
186
+
187
+ vec3 point = rayOrigin + rayDirection * dist;
188
+ rayOrigin += rayDirection * dist - faceNormal * RAY_OFFSET;
189
+ throughputColor *= mix( vec3( 1.0 ), material.color, 0.5 * material.opacity );
190
+
191
+ i --;
192
+ continue;
193
+
194
+ }
195
+
196
+ // fetch the interpolated smooth normal
197
+ vec3 normal = normalize( textureSampleBarycoord(
198
+ normalAttribute,
199
+ barycoord,
200
+ faceIndices.xyz
201
+ ).xyz );
202
+
203
+ vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
204
+
205
+ // emission
206
+ vec3 emission = material.emissiveIntensity * material.emissive;
207
+ if ( material.emissiveMap != - 1 ) {
208
+
209
+ emission *= texture2D( textures, vec3( uv, material.emissiveMap ) ).xyz;
210
+
211
+ }
212
+
213
+ gl_FragColor.rgb += throughputColor * emission * max( side, 0.0 );
214
+
215
+ // 1 / PI attenuation for physically correct lambert model
216
+ // https://www.rorydriscoll.com/2009/01/25/energy-conservation-in-games/
217
+ throughputColor *= 1.0 / PI;
218
+
219
+ // albedo
220
+ throughputColor *= material.color;
221
+ if ( material.map != - 1 ) {
222
+
223
+ throughputColor *= texture2D( textures, vec3( uv, material.map ) ).xyz;
224
+
225
+ }
226
+
227
+ // normal
228
+ if ( material.normalMap != - 1 ) {
229
+
230
+ vec4 tangentSample = textureSampleBarycoord(
231
+ tangentAttribute,
232
+ barycoord,
233
+ faceIndices.xyz
234
+ );
235
+
236
+ // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
237
+ // resulting in NaNs and slow path tracing.
238
+ if ( length( tangentSample.xyz ) > 0.0 ) {
239
+
240
+ vec3 tangent = normalize( tangentSample.xyz );
241
+ vec3 bitangent = normalize( cross( normal, tangent ) * tangentSample.w );
242
+ mat3 vTBN = mat3( tangent, bitangent, normal );
243
+
244
+ vec3 texNormal = texture2D( textures, vec3( uv, material.normalMap ) ).xyz * 2.0 - 1.0;
245
+ texNormal.xy *= material.normalScale;
246
+ normal = vTBN * texNormal;
247
+
248
+ }
249
+
250
+ }
251
+
252
+ normal *= side;
253
+
254
+ // adjust the hit point by the surface normal by a factor of some offset and the
255
+ // maximum component-wise value of the current point to accommodate floating point
256
+ // error as values increase.
257
+ vec3 point = rayOrigin + rayDirection * dist;
258
+ vec3 absPoint = abs( point );
259
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
260
+ rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * RAY_OFFSET;
261
+ rayDirection = getHemisphereSample( normal, rand2() );
262
+
263
+ // if the surface normal is skewed such that the outgoing vector can wind up underneath
264
+ // the triangle surface then just consider it absorbed.
265
+ if ( dot( rayDirection, faceNormal ) < 0.0 ) {
266
+
267
+ break;
268
+
269
+ }
270
+
271
+ }
272
+
273
+ gl_FragColor.a = opacity;
274
+
275
+ }
276
+
277
+ `
278
+
279
+ } );
280
+
281
+ this.setValues( parameters );
282
+
283
+ }
284
+
285
+ }
@@ -0,0 +1,56 @@
1
+ import { ShaderMaterial } from 'three';
2
+
3
+ export class MaterialBase extends ShaderMaterial {
4
+
5
+ constructor( shader ) {
6
+
7
+ super( shader );
8
+
9
+ for ( const key in this.uniforms ) {
10
+
11
+ Object.defineProperty( this, key, {
12
+
13
+ get() {
14
+
15
+ return this.uniforms[ key ].value;
16
+
17
+ },
18
+
19
+ set( v ) {
20
+
21
+ this.uniforms[ key ].value = v;
22
+
23
+ }
24
+
25
+ } );
26
+
27
+ }
28
+
29
+ }
30
+
31
+ // sets the given named define value and sets "needsUpdate" to true if it's different
32
+ setDefine( name, value = undefined ) {
33
+
34
+ if ( value === undefined || value === null ) {
35
+
36
+ if ( name in this.defines ) {
37
+
38
+ delete this.defines[ name ];
39
+ this.needsUpdate = true;
40
+
41
+ }
42
+
43
+ } else {
44
+
45
+ if ( this.defines[ name ] !== value ) {
46
+
47
+ this.defines[ name ] = value;
48
+ this.needsUpdate = true;
49
+
50
+ }
51
+
52
+ }
53
+
54
+ }
55
+
56
+ }