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,370 @@
1
+ import { Matrix4, Matrix3, 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 { MaterialStructArrayUniform } from '../uniforms/MaterialStructArrayUniform.js';
9
+ import { RenderTarget2DArray } from '../uniforms/RenderTarget2DArray.js';
10
+ import { shaderMaterialSampling } from '../shader/shaderMaterialSampling.js';
11
+ import { shaderUtils } from '../shader/shaderUtils.js';
12
+
13
+ export class PhysicalPathTracingMaterial extends MaterialBase {
14
+
15
+ // three.js relies on this field to add env map functions and defines
16
+ get envMap() {
17
+
18
+ return this.environmentMap;
19
+
20
+ }
21
+
22
+ constructor( parameters ) {
23
+
24
+ super( {
25
+
26
+ transparent: true,
27
+ depthWrite: false,
28
+
29
+ defines: {
30
+ BOUNCES: 3,
31
+ TRANSPARENT_TRAVERSALS: 5,
32
+ MATERIAL_LENGTH: 0,
33
+ GRADIENT_BG: 0,
34
+ },
35
+
36
+ uniforms: {
37
+ bvh: { value: new MeshBVHUniformStruct() },
38
+ normalAttribute: { value: new FloatVertexAttributeTexture() },
39
+ tangentAttribute: { value: new FloatVertexAttributeTexture() },
40
+ uvAttribute: { value: new FloatVertexAttributeTexture() },
41
+ materialIndexAttribute: { value: new UIntVertexAttributeTexture() },
42
+ materials: { value: new MaterialStructArrayUniform() },
43
+ textures: { value: new RenderTarget2DArray().texture },
44
+ cameraWorldMatrix: { value: new Matrix4() },
45
+ invProjectionMatrix: { value: new Matrix4() },
46
+ environmentBlur: { value: 0.2 },
47
+ environmentIntensity: { value: 2.0 },
48
+ environmentMap: { value: null },
49
+ environmentRotation: { value: new Matrix3() },
50
+ seed: { value: 0 },
51
+ opacity: { value: 1 },
52
+ filterGlossyFactor: { value: 0.0 },
53
+
54
+ gradientTop: { value: new Color( 0xbfd8ff ) },
55
+ gradientBottom: { value: new Color( 0xffffff ) },
56
+
57
+ bgGradientTop: { value: new Color( 0x111111 ) },
58
+ bgGradientBottom: { value: new Color( 0x000000 ) },
59
+ },
60
+
61
+ vertexShader: /* glsl */`
62
+
63
+ varying vec2 vUv;
64
+ void main() {
65
+
66
+ vec4 mvPosition = vec4( position, 1.0 );
67
+ mvPosition = modelViewMatrix * mvPosition;
68
+ gl_Position = projectionMatrix * mvPosition;
69
+
70
+ vUv = uv;
71
+
72
+ }
73
+
74
+ `,
75
+
76
+ fragmentShader: /* glsl */`
77
+ #define RAY_OFFSET 1e-5
78
+
79
+ precision highp isampler2D;
80
+ precision highp usampler2D;
81
+ precision highp sampler2DArray;
82
+ vec4 envMapTexelToLinear( vec4 a ) { return a; }
83
+ #include <common>
84
+ #include <cube_uv_reflection_fragment>
85
+
86
+ ${ shaderStructs }
87
+ ${ shaderIntersectFunction }
88
+ ${ shaderMaterialStructs }
89
+
90
+ ${ shaderUtils }
91
+ ${ shaderMaterialSampling }
92
+
93
+ #ifdef USE_ENVMAP
94
+
95
+ uniform float environmentBlur;
96
+ uniform sampler2D environmentMap;
97
+ uniform mat3 environmentRotation;
98
+
99
+ #else
100
+
101
+ uniform vec3 gradientTop;
102
+ uniform vec3 gradientBottom;
103
+
104
+ #endif
105
+
106
+ #if GRADIENT_BG
107
+
108
+ uniform vec3 bgGradientTop;
109
+ uniform vec3 bgGradientBottom;
110
+
111
+ #endif
112
+
113
+ uniform mat4 cameraWorldMatrix;
114
+ uniform mat4 invProjectionMatrix;
115
+ uniform sampler2D normalAttribute;
116
+ uniform sampler2D tangentAttribute;
117
+ uniform sampler2D uvAttribute;
118
+ uniform usampler2D materialIndexAttribute;
119
+ uniform BVH bvh;
120
+ uniform float environmentIntensity;
121
+ uniform float filterGlossyFactor;
122
+ uniform int seed;
123
+ uniform float opacity;
124
+ uniform Material materials[ MATERIAL_LENGTH ];
125
+ uniform sampler2DArray textures;
126
+ varying vec2 vUv;
127
+
128
+ void main() {
129
+
130
+ rng_initialize( gl_FragCoord.xy, seed );
131
+
132
+ // get [-1, 1] normalized device coordinates
133
+ vec2 ndc = 2.0 * vUv - vec2( 1.0 );
134
+ vec3 rayOrigin, rayDirection;
135
+ ndcToCameraRay( ndc, cameraWorldMatrix, invProjectionMatrix, rayOrigin, rayDirection );
136
+
137
+ // Lambertian render
138
+ gl_FragColor = vec4( 0.0 );
139
+
140
+ vec3 throughputColor = vec3( 1.0 );
141
+
142
+ // hit results
143
+ uvec4 faceIndices = uvec4( 0u );
144
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
145
+ vec3 barycoord = vec3( 0.0 );
146
+ float side = 1.0;
147
+ float dist = 0.0;
148
+ float accumulatedRoughness = 0.0;
149
+ int i;
150
+ int transparentTraversals = TRANSPARENT_TRAVERSALS;
151
+ for ( i = 0; i < BOUNCES; i ++ ) {
152
+
153
+ if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
154
+
155
+ #if GRADIENT_BG
156
+
157
+ if ( i == 0 ) {
158
+
159
+ rayDirection = normalize( rayDirection + randDirection() * 0.05 );
160
+ float value = ( rayDirection.y + 1.0 ) / 2.0;
161
+
162
+ value = pow( value, 2.0 );
163
+
164
+ gl_FragColor = vec4( mix( bgGradientBottom, bgGradientTop, value ), 1.0 );
165
+ break;
166
+
167
+ }
168
+
169
+ #endif
170
+
171
+ #ifdef USE_ENVMAP
172
+
173
+ vec3 skyColor = textureCubeUV( environmentMap, environmentRotation * rayDirection, environmentBlur ).rgb;
174
+
175
+ #else
176
+
177
+ rayDirection = normalize( rayDirection );
178
+ float value = ( rayDirection.y + 1.0 ) / 2.0;
179
+ vec3 skyColor = mix( gradientBottom, gradientTop, value );
180
+
181
+ #endif
182
+
183
+ gl_FragColor += vec4( skyColor * throughputColor * environmentIntensity, 1.0 );
184
+
185
+ break;
186
+
187
+ }
188
+
189
+ uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
190
+ Material material = materials[ materialIndex ];
191
+
192
+ vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
193
+
194
+ // albedo
195
+ vec4 albedo = vec4( material.color, material.opacity );
196
+ if ( material.map != - 1 ) {
197
+
198
+ albedo *= texture2D( textures, vec3( uv, material.map ) );
199
+
200
+ }
201
+
202
+ // possibly skip this sample if it's transparent or alpha test is enabled
203
+ // alpha test is disabled when it === 0
204
+ float alphaTest = material.alphaTest;
205
+ bool useAlphaTest = alphaTest != 0.0;
206
+ if (
207
+ useAlphaTest && albedo.a < alphaTest
208
+ || ! useAlphaTest && albedo.a < rand()
209
+ ) {
210
+
211
+ vec3 point = rayOrigin + rayDirection * dist;
212
+ rayOrigin += rayDirection * dist - faceNormal * RAY_OFFSET;
213
+
214
+ // only allow a limited number of transparency discards otherwise we could
215
+ // crash the context with too long a loop.
216
+ i -= sign( transparentTraversals );
217
+ transparentTraversals -= sign( transparentTraversals );
218
+ continue;
219
+
220
+ }
221
+
222
+ // fetch the interpolated smooth normal
223
+ vec3 normal = normalize( textureSampleBarycoord(
224
+ normalAttribute,
225
+ barycoord,
226
+ faceIndices.xyz
227
+ ).xyz );
228
+
229
+ // roughness
230
+ float roughness = material.roughness;
231
+ if ( material.roughnessMap != - 1 ) {
232
+
233
+ roughness *= texture2D( textures, vec3( uv, material.roughnessMap ) ).g;
234
+
235
+ }
236
+
237
+ // metalness
238
+ float metalness = material.metalness;
239
+ if ( material.metalnessMap != - 1 ) {
240
+
241
+ metalness *= texture2D( textures, vec3( uv, material.metalnessMap ) ).b;
242
+
243
+ }
244
+
245
+ // emission
246
+ vec3 emission = material.emissiveIntensity * material.emissive;
247
+ if ( material.emissiveMap != - 1 ) {
248
+
249
+ emission *= texture2D( textures, vec3( uv, material.emissiveMap ) ).xyz;
250
+
251
+ }
252
+
253
+ // transmission
254
+ float transmission = material.transmission;
255
+ if ( material.transmissionMap != - 1 ) {
256
+
257
+ transmission *= texture2D( textures, vec3( uv, material.transmissionMap ) ).r;
258
+
259
+ }
260
+
261
+ // normal
262
+ if ( material.normalMap != - 1 ) {
263
+
264
+ vec4 tangentSample = textureSampleBarycoord(
265
+ tangentAttribute,
266
+ barycoord,
267
+ faceIndices.xyz
268
+ );
269
+
270
+ // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
271
+ // resulting in NaNs and slow path tracing.
272
+ if ( length( tangentSample.xyz ) > 0.0 ) {
273
+
274
+ vec3 tangent = normalize( tangentSample.xyz );
275
+ vec3 bitangent = normalize( cross( normal, tangent ) * tangentSample.w );
276
+ mat3 vTBN = mat3( tangent, bitangent, normal );
277
+
278
+ vec3 texNormal = texture2D( textures, vec3( uv, material.normalMap ) ).xyz * 2.0 - 1.0;
279
+ texNormal.xy *= material.normalScale;
280
+ normal = vTBN * texNormal;
281
+
282
+ }
283
+
284
+ }
285
+
286
+ normal *= side;
287
+
288
+ SurfaceRec surfaceRec;
289
+ surfaceRec.normal = normal;
290
+ surfaceRec.faceNormal = faceNormal;
291
+ surfaceRec.frontFace = side == 1.0;
292
+ surfaceRec.transmission = transmission;
293
+ surfaceRec.ior = material.ior;
294
+ surfaceRec.emission = emission;
295
+ surfaceRec.metalness = metalness;
296
+ surfaceRec.color = albedo.rgb;
297
+ surfaceRec.roughness = roughness;
298
+
299
+ // Compute the filtered roughness value to use during specular reflection computations. A minimum
300
+ // value of 1e-6 is needed because the GGX functions do not work with a roughness value of 0 and
301
+ // the accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
302
+ // If we're exiting something transmissive then scale the factor down significantly so we can retain
303
+ // sharp internal reflections
304
+ surfaceRec.filteredRoughness = clamp(
305
+ max( surfaceRec.roughness, accumulatedRoughness * filterGlossyFactor * 5.0 ),
306
+ 1e-3,
307
+ 1.0
308
+ );
309
+
310
+ mat3 normalBasis = getBasisFromNormal( surfaceRec.normal );
311
+ mat3 invBasis = inverse( normalBasis );
312
+
313
+ vec3 outgoing = - normalize( invBasis * rayDirection );
314
+ SampleRec sampleRec = bsdfSample( outgoing, surfaceRec );
315
+
316
+ // adjust the hit point by the surface normal by a factor of some offset and the
317
+ // maximum component-wise value of the current point to accommodate floating point
318
+ // error as values increase.
319
+ vec3 point = rayOrigin + rayDirection * dist;
320
+ vec3 absPoint = abs( point );
321
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
322
+ rayDirection = normalize( normalBasis * sampleRec.direction );
323
+
324
+ bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
325
+ rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
326
+
327
+ // accumulate a roughness value to offset diffuse, specular, diffuse rays that have high contribution
328
+ // to a single pixel resulting in fireflies
329
+ if ( ! isBelowSurface ) {
330
+
331
+ // determine if this is a rough normal or not by checking how far off straight up it is
332
+ vec3 halfVector = normalize( outgoing + sampleRec.direction );
333
+ accumulatedRoughness += sin( acos( halfVector.z ) );
334
+
335
+ }
336
+
337
+ // accumulate color
338
+ gl_FragColor.rgb += ( emission * throughputColor );
339
+
340
+ // skip the sample if our PDF or ray is impossible
341
+ if ( sampleRec.pdf <= 0.0 || ! isDirectionValid( rayDirection, normal, faceNormal) ) {
342
+
343
+ break;
344
+
345
+ }
346
+
347
+ throughputColor *= sampleRec.color / sampleRec.pdf;
348
+
349
+ // discard the sample if there are any NaNs
350
+ if ( any( isnan( throughputColor ) ) || any( isinf( throughputColor ) ) ) {
351
+
352
+ break;
353
+
354
+ }
355
+
356
+ }
357
+
358
+ gl_FragColor.a = opacity;
359
+
360
+ }
361
+
362
+ `
363
+
364
+ } );
365
+
366
+ this.setValues( parameters );
367
+
368
+ }
369
+
370
+ }
@@ -0,0 +1,107 @@
1
+ export const shaderGGXFunctions = /* glsl */`
2
+ // The GGX functions provide sampling and distribution information for normals as output so
3
+ // in order to get probability of scatter direction the half vector must be computed and provided.
4
+ // [0] https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
5
+ // [1] https://hal.archives-ouvertes.fr/hal-01509746/document
6
+ // [2] http://jcgt.org/published/0007/04/01/
7
+ // [4] http://jcgt.org/published/0003/02/03/
8
+
9
+ // trowbridge-reitz === GGX === GTR
10
+
11
+ vec3 ggxDirection( vec3 incidentDir, float roughnessX, float roughnessY, float random1, float random2 ) {
12
+
13
+ // TODO: try GGXVNDF implementation from reference [2], here. Needs to update ggxDistribution
14
+ // function below, as well
15
+
16
+ // Implementation from reference [1]
17
+ // stretch view
18
+ vec3 V = normalize( vec3( roughnessX * incidentDir.x, roughnessY * incidentDir.y, incidentDir.z ) );
19
+
20
+ // orthonormal basis
21
+ vec3 T1 = ( V.z < 0.9999 ) ? normalize( cross( V, vec3( 0.0, 0.0, 1.0 ) ) ) : vec3( 1.0, 0.0, 0.0 );
22
+ vec3 T2 = cross( T1, V );
23
+
24
+ // sample point with polar coordinates (r, phi)
25
+ float a = 1.0 / ( 1.0 + V.z );
26
+ float r = sqrt( random1 );
27
+ float phi = ( random2 < a ) ? random2 / a * PI : PI + ( random2 - a ) / ( 1.0 - a ) * PI;
28
+ float P1 = r * cos( phi );
29
+ float P2 = r * sin( phi ) * ( ( random2 < a ) ? 1.0 : V.z );
30
+
31
+ // compute normal
32
+ vec3 N = P1 * T1 + P2 * T2 + V * sqrt( max( 0.0, 1.0 - P1 * P1 - P2 * P2 ) );
33
+
34
+ // unstretch
35
+ N = normalize( vec3( roughnessX * N.x, roughnessY * N.y, max( 0.0, N.z ) ) );
36
+
37
+ return N;
38
+
39
+ }
40
+
41
+ // Below are PDF and related functions for use in a Monte Carlo path tracer
42
+ // as specified in Appendix B of the following paper
43
+ // See equation (2) from reference [2]
44
+ float ggxLamda( float theta, float roughness ) {
45
+
46
+ float tanTheta = tan( theta );
47
+ float tanTheta2 = tanTheta * tanTheta;
48
+ float alpha2 = roughness * roughness;
49
+
50
+ float numerator = - 1.0 + sqrt( 1.0 + alpha2 * tanTheta2 );
51
+ return numerator / 2.0;
52
+
53
+ }
54
+
55
+ // See equation (2) from reference [2]
56
+ float ggxShadowMaskG1( float theta, float roughness ) {
57
+
58
+ return 1.0 / ( 1.0 + ggxLamda( theta, roughness ) );
59
+
60
+ }
61
+
62
+ // See equation (125) from reference [4]
63
+ float ggxShadowMaskG2( vec3 wi, vec3 wo, float roughness ) {
64
+
65
+ float incidentTheta = acos( wi.z );
66
+ float scatterTheta = acos( wo.z );
67
+ return 1.0 / ( 1.0 + ggxLamda( incidentTheta, roughness ) + ggxLamda( scatterTheta, roughness ) );
68
+
69
+ }
70
+
71
+ float ggxDistribution( vec3 halfVector, float roughness ) {
72
+
73
+ // See equation (33) from reference [0]
74
+ float a2 = roughness * roughness;
75
+ float cosTheta = halfVector.z;
76
+ float cosTheta4 = pow( cosTheta, 4.0 );
77
+
78
+ if ( cosTheta == 0.0 ) return 0.0;
79
+
80
+ float theta = acos( halfVector.z );
81
+ float tanTheta = tan( theta );
82
+ float tanTheta2 = pow( tanTheta, 2.0 );
83
+
84
+ float denom = PI * cosTheta4 * pow( a2 + tanTheta2, 2.0 );
85
+ return a2 / denom;
86
+
87
+ // See equation (1) from reference [2]
88
+ // const { x, y, z } = halfVector;
89
+ // const a2 = roughness * roughness;
90
+ // const mult = x * x / a2 + y * y / a2 + z * z;
91
+ // const mult2 = mult * mult;
92
+
93
+ // return 1.0 / Math.PI * a2 * mult2;
94
+
95
+ }
96
+
97
+ // See equation (3) from reference [2]
98
+ float ggxPDF( vec3 wi, vec3 halfVector, float roughness ) {
99
+
100
+ float incidentTheta = acos( wi.z );
101
+ float D = ggxDistribution( halfVector, roughness );
102
+ float G1 = ggxShadowMaskG1( incidentTheta, roughness );
103
+
104
+ return D * G1 * max( 0.0, dot( wi, halfVector ) ) / wi.z;
105
+
106
+ }
107
+ `;