three-gpu-pathtracer 0.0.1 → 0.0.4

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.
Files changed (36) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +678 -386
  3. package/build/index.module.js +3166 -1690
  4. package/build/index.module.js.map +1 -1
  5. package/build/index.umd.cjs +3176 -1692
  6. package/build/index.umd.cjs.map +1 -1
  7. package/package.json +60 -57
  8. package/src/core/DynamicPathTracingSceneGenerator.js +106 -0
  9. package/src/core/MaterialReducer.js +256 -256
  10. package/src/core/PathTracingRenderer.js +125 -28
  11. package/src/core/PathTracingSceneGenerator.js +52 -46
  12. package/src/core/PhysicalCamera.js +28 -0
  13. package/src/index.js +25 -21
  14. package/src/materials/AlphaDisplayMaterial.js +48 -0
  15. package/src/materials/AmbientOcclusionMaterial.js +197 -197
  16. package/src/materials/BlendMaterial.js +67 -0
  17. package/src/materials/LambertPathTracingMaterial.js +285 -285
  18. package/src/materials/MaterialBase.js +56 -56
  19. package/src/materials/PhysicalPathTracingMaterial.js +684 -370
  20. package/src/shader/shaderEnvMapSampling.js +67 -0
  21. package/src/shader/shaderGGXFunctions.js +108 -107
  22. package/src/shader/shaderMaterialSampling.js +345 -333
  23. package/src/shader/shaderStructs.js +131 -30
  24. package/src/shader/shaderUtils.js +246 -140
  25. package/src/uniforms/EquirectHdrInfoUniform.js +263 -0
  26. package/src/uniforms/MaterialsTexture.js +251 -0
  27. package/src/uniforms/PhysicalCameraUniform.js +36 -0
  28. package/src/uniforms/RenderTarget2DArray.js +93 -80
  29. package/src/utils/BlurredEnvMapGenerator.js +113 -0
  30. package/src/utils/GeometryPreparationUtils.js +194 -172
  31. package/src/utils/UVUnwrapper.js +101 -101
  32. package/src/workers/PathTracingSceneWorker.js +40 -0
  33. package/src/uniforms/EquirectPdfUniform.js +0 -132
  34. package/src/uniforms/MaterialStructArrayUniform.js +0 -18
  35. package/src/uniforms/MaterialStructUniform.js +0 -94
  36. package/src/viewers/PathTracingViewer.js +0 -259
@@ -1,370 +1,684 @@
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
- }
1
+ import { Matrix4, Matrix3, Color, Vector2 } 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 { MaterialsTexture } from '../uniforms/MaterialsTexture.js';
9
+ import { RenderTarget2DArray } from '../uniforms/RenderTarget2DArray.js';
10
+ import { shaderMaterialSampling } from '../shader/shaderMaterialSampling.js';
11
+ import { shaderEnvMapSampling } from '../shader/shaderEnvMapSampling.js';
12
+ import { shaderUtils } from '../shader/shaderUtils.js';
13
+ import { PhysicalCameraUniform } from '../uniforms/PhysicalCameraUniform.js';
14
+ import { EquirectHdrInfoUniform } from '../uniforms/EquirectHdrInfoUniform.js';
15
+
16
+ export class PhysicalPathTracingMaterial extends MaterialBase {
17
+
18
+ onBeforeRender() {
19
+
20
+ this.setDefine( 'FEATURE_DOF', this.physicalCamera.bokehSize === 0 ? 0 : 1 );
21
+
22
+ }
23
+
24
+ constructor( parameters ) {
25
+
26
+ super( {
27
+
28
+ transparent: true,
29
+ depthWrite: false,
30
+
31
+ defines: {
32
+ FEATURE_MIS: 1,
33
+ FEATURE_DOF: 1,
34
+ FEATURE_GRADIENT_BG: 0,
35
+ TRANSPARENT_TRAVERSALS: 5,
36
+ },
37
+
38
+ uniforms: {
39
+ resolution: { value: new Vector2() },
40
+
41
+ bounces: { value: 3 },
42
+ physicalCamera: { value: new PhysicalCameraUniform() },
43
+
44
+ bvh: { value: new MeshBVHUniformStruct() },
45
+ normalAttribute: { value: new FloatVertexAttributeTexture() },
46
+ tangentAttribute: { value: new FloatVertexAttributeTexture() },
47
+ uvAttribute: { value: new FloatVertexAttributeTexture() },
48
+ materialIndexAttribute: { value: new UIntVertexAttributeTexture() },
49
+ materials: { value: new MaterialsTexture() },
50
+ textures: { value: new RenderTarget2DArray().texture },
51
+ cameraWorldMatrix: { value: new Matrix4() },
52
+ invProjectionMatrix: { value: new Matrix4() },
53
+ isOrthographicCamera: { value: true },
54
+ backgroundBlur: { value: 0.0 },
55
+ environmentIntensity: { value: 2.0 },
56
+ environmentRotation: { value: new Matrix3() },
57
+ envMapInfo: { value: new EquirectHdrInfoUniform() },
58
+
59
+ seed: { value: 0 },
60
+ opacity: { value: 1 },
61
+ filterGlossyFactor: { value: 0.0 },
62
+
63
+ bgGradientTop: { value: new Color( 0x111111 ) },
64
+ bgGradientBottom: { value: new Color( 0x000000 ) },
65
+ backgroundAlpha: { value: 1.0 },
66
+ },
67
+
68
+ vertexShader: /* glsl */`
69
+
70
+ varying vec2 vUv;
71
+ void main() {
72
+
73
+ vec4 mvPosition = vec4( position, 1.0 );
74
+ mvPosition = modelViewMatrix * mvPosition;
75
+ gl_Position = projectionMatrix * mvPosition;
76
+
77
+ vUv = uv;
78
+
79
+ }
80
+
81
+ `,
82
+
83
+ fragmentShader: /* glsl */`
84
+ #define RAY_OFFSET 1e-4
85
+
86
+ precision highp isampler2D;
87
+ precision highp usampler2D;
88
+ precision highp sampler2DArray;
89
+ vec4 envMapTexelToLinear( vec4 a ) { return a; }
90
+ #include <common>
91
+
92
+ ${ shaderStructs }
93
+ ${ shaderIntersectFunction }
94
+ ${ shaderMaterialStructs }
95
+
96
+ ${ shaderUtils }
97
+ ${ shaderMaterialSampling }
98
+ ${ shaderEnvMapSampling }
99
+
100
+ uniform mat3 environmentRotation;
101
+ uniform float backgroundBlur;
102
+ uniform float backgroundAlpha;
103
+
104
+ #if FEATURE_GRADIENT_BG
105
+
106
+ uniform vec3 bgGradientTop;
107
+ uniform vec3 bgGradientBottom;
108
+
109
+ #endif
110
+
111
+ #if FEATURE_DOF
112
+
113
+ uniform PhysicalCamera physicalCamera;
114
+
115
+ #endif
116
+
117
+ uniform vec2 resolution;
118
+ uniform int bounces;
119
+ uniform mat4 cameraWorldMatrix;
120
+ uniform mat4 invProjectionMatrix;
121
+ uniform bool isOrthographicCamera;
122
+ uniform sampler2D normalAttribute;
123
+ uniform sampler2D tangentAttribute;
124
+ uniform sampler2D uvAttribute;
125
+ uniform usampler2D materialIndexAttribute;
126
+ uniform BVH bvh;
127
+ uniform float environmentIntensity;
128
+ uniform float filterGlossyFactor;
129
+ uniform int seed;
130
+ uniform float opacity;
131
+ uniform sampler2D materials;
132
+
133
+ uniform EquirectHdrInfo envMapInfo;
134
+
135
+ uniform sampler2DArray textures;
136
+ varying vec2 vUv;
137
+
138
+ vec3 sampleBackground( vec3 direction ) {
139
+
140
+ #if FEATURE_GRADIENT_BG
141
+
142
+ direction = normalize( direction + randDirection() * 0.05 );
143
+
144
+ float value = ( direction.y + 1.0 ) / 2.0;
145
+ value = pow( value, 2.0 );
146
+
147
+ return mix( bgGradientBottom, bgGradientTop, value );
148
+
149
+ #else
150
+
151
+ vec3 sampleDir = normalize( direction + getHemisphereSample( direction, rand2() ) * 0.5 * backgroundBlur );
152
+ return environmentIntensity * sampleEquirectEnvMapColor( sampleDir, envMapInfo.map );
153
+
154
+ #endif
155
+
156
+ }
157
+
158
+ // step through multiple surface hits and accumulate color attenuation based on transmissive surfaces
159
+ bool attenuateHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, int traversals, bool isShadowRay, out vec3 color ) {
160
+
161
+ // hit results
162
+ uvec4 faceIndices = uvec4( 0u );
163
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
164
+ vec3 barycoord = vec3( 0.0 );
165
+ float side = 1.0;
166
+ float dist = 0.0;
167
+
168
+ color = vec3( 1.0 );
169
+
170
+ for ( int i = 0; i < traversals; i ++ ) {
171
+
172
+ if ( bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
173
+
174
+ // TODO: attenuate the contribution based on the PDF of the resulting ray including refraction values
175
+ // Should be able to work using the material BSDF functions which will take into account specularity, etc.
176
+ // TODO: should we account for emissive surfaces here?
177
+
178
+ vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
179
+ uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
180
+ Material material = readMaterialInfo( materials, materialIndex );
181
+
182
+ // adjust the ray to the new surface
183
+ bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
184
+ vec3 point = rayOrigin + rayDirection * dist;
185
+ vec3 absPoint = abs( point );
186
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
187
+ rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
188
+
189
+ if ( ! material.castShadow && isShadowRay ) {
190
+
191
+ continue;
192
+
193
+ }
194
+
195
+ // Opacity Test
196
+
197
+ // albedo
198
+ vec4 albedo = vec4( material.color, material.opacity );
199
+ if ( material.map != - 1 ) {
200
+
201
+ vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
202
+ albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
203
+
204
+ }
205
+
206
+ // alphaMap
207
+ if ( material.alphaMap != -1 ) {
208
+
209
+ albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
210
+
211
+ }
212
+
213
+ // transmission
214
+ float transmission = material.transmission;
215
+ if ( material.transmissionMap != - 1 ) {
216
+
217
+ vec3 uvPrime = material.transmissionMapTransform * vec3( uv, 1 );
218
+ transmission *= texture2D( textures, vec3( uvPrime.xy, material.transmissionMap ) ).r;
219
+
220
+ }
221
+
222
+ // metalness
223
+ float metalness = material.metalness;
224
+ if ( material.metalnessMap != - 1 ) {
225
+
226
+ vec3 uvPrime = material.metalnessMapTransform * vec3( uv, 1 );
227
+ metalness *= texture2D( textures, vec3( uvPrime.xy, material.metalnessMap ) ).b;
228
+
229
+ }
230
+
231
+ float alphaTest = material.alphaTest;
232
+ bool useAlphaTest = alphaTest != 0.0;
233
+ float transmissionFactor = ( 1.0 - metalness ) * transmission;
234
+ if (
235
+ transmissionFactor < rand() && ! (
236
+ // material sidedness
237
+ material.side != 0.0 && side == material.side
238
+
239
+ // alpha test
240
+ || useAlphaTest && albedo.a < alphaTest
241
+
242
+ // opacity
243
+ || ! useAlphaTest && albedo.a < rand()
244
+ )
245
+ ) {
246
+
247
+ return true;
248
+
249
+ }
250
+
251
+ // only attenuate on the way in
252
+ if ( isBelowSurface ) {
253
+
254
+ color *= mix( vec3( 1.0 ), albedo.rgb, transmissionFactor );
255
+
256
+ }
257
+
258
+ } else {
259
+
260
+ return false;
261
+
262
+ }
263
+
264
+ }
265
+
266
+ return true;
267
+
268
+ }
269
+
270
+ // returns whether the ray hit anything, not just the first surface. Could be optimized to not check the full hierarchy.
271
+ bool anyHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection ) {
272
+
273
+ uvec4 faceIndices = uvec4( 0u );
274
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
275
+ vec3 barycoord = vec3( 0.0 );
276
+ float side = 1.0;
277
+ float dist = 0.0;
278
+ return bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
279
+
280
+ }
281
+
282
+ // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
283
+ // erichlof/THREE.js-PathTracing-Renderer/
284
+ float tentFilter( float x ) {
285
+
286
+ return x < 0.5 ? sqrt( 2.0 * x ) - 1.0 : 1.0 - sqrt( 2.0 - ( 2.0 * x ) );
287
+
288
+ }
289
+
290
+ vec3 ndcToRayOrigin( vec2 coord ) {
291
+
292
+ vec4 rayOrigin4 = cameraWorldMatrix * invProjectionMatrix * vec4( coord, - 1.0, 1.0 );
293
+ return rayOrigin4.xyz / rayOrigin4.w;
294
+ }
295
+
296
+ void main() {
297
+
298
+ rng_initialize( gl_FragCoord.xy, seed );
299
+
300
+ // get [-1, 1] normalized device coordinates
301
+ vec2 ndc = 2.0 * vUv - vec2( 1.0 );
302
+
303
+ vec3 ss00 = ndcToRayOrigin( vec2( - 1.0, - 1.0 ) );
304
+ vec3 ss01 = ndcToRayOrigin( vec2( - 1.0, 1.0 ) );
305
+ vec3 ss10 = ndcToRayOrigin( vec2( 1.0, - 1.0 ) );
306
+
307
+ vec3 ssdX = ( ss10 - ss00 ) / resolution.x;
308
+ vec3 ssdY = ( ss01 - ss00 ) / resolution.y;
309
+
310
+ // Jitter the camera ray by finding a new subpixel point to point to from the camera origin
311
+ // This is better than just jittering the camera position since it actually results in divergent
312
+ // rays providing better coverage for the pixel
313
+ vec3 rayOrigin = ndcToRayOrigin( ndc ) + tentFilter( rand() ) * ssdX + tentFilter( rand() ) * ssdY;
314
+
315
+ vec3 rayDirection;
316
+
317
+ if ( isOrthographicCamera ) {
318
+
319
+ rayDirection = ( cameraWorldMatrix * vec4( 0.0, 0.0, -1.0, 0.0 ) ).xyz;
320
+ rayDirection = normalize( rayDirection );
321
+
322
+ } else {
323
+
324
+ vec3 cameraOrigin = ( cameraWorldMatrix * vec4( 0.0, 0.0, 0.0, 1.0 ) ).xyz;
325
+ rayDirection = normalize( rayOrigin - cameraOrigin );
326
+
327
+ }
328
+
329
+ #if FEATURE_DOF
330
+ {
331
+
332
+ // depth of field
333
+ vec3 focalPoint = rayOrigin + normalize( rayDirection ) * physicalCamera.focusDistance;
334
+
335
+ // get the aperture sample
336
+ vec2 apertureSample = sampleAperture( physicalCamera.apertureBlades ) * physicalCamera.bokehSize * 0.5 * 1e-3;
337
+
338
+ // rotate the aperture shape
339
+ float ac = cos( physicalCamera.apertureRotation );
340
+ float as = sin( physicalCamera.apertureRotation );
341
+ apertureSample = vec2(
342
+ apertureSample.x * ac - apertureSample.y * as,
343
+ apertureSample.x * as + apertureSample.y * ac
344
+ );
345
+ apertureSample.x *= saturate( physicalCamera.anamorphicRatio );
346
+ apertureSample.y *= saturate( 1.0 / physicalCamera.anamorphicRatio );
347
+
348
+ // create the new ray
349
+ rayOrigin += ( cameraWorldMatrix * vec4( apertureSample, 0.0, 0.0 ) ).xyz;
350
+ rayDirection = focalPoint - rayOrigin;
351
+
352
+ }
353
+ #endif
354
+ rayDirection = normalize( rayDirection );
355
+
356
+ // inverse environment rotation
357
+ mat3 invEnvironmentRotation = inverse( environmentRotation );
358
+
359
+ // final color
360
+ gl_FragColor = vec4( 0.0 );
361
+ gl_FragColor.a = 1.0;
362
+
363
+ // hit results
364
+ uvec4 faceIndices = uvec4( 0u );
365
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
366
+ vec3 barycoord = vec3( 0.0 );
367
+ float side = 1.0;
368
+ float dist = 0.0;
369
+
370
+ // path tracing state
371
+ float accumulatedRoughness = 0.0;
372
+ bool transmissiveRay = true;
373
+ int transparentTraversals = TRANSPARENT_TRAVERSALS;
374
+ vec3 throughputColor = vec3( 1.0 );
375
+ SampleRec sampleRec;
376
+ int i;
377
+ bool isShadowRay = false;
378
+
379
+ for ( i = 0; i < bounces; i ++ ) {
380
+
381
+ if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
382
+
383
+ if ( i == 0 || transmissiveRay ) {
384
+
385
+ gl_FragColor.rgb += sampleBackground( environmentRotation * rayDirection ) * throughputColor;
386
+ gl_FragColor.a = backgroundAlpha;
387
+
388
+ } else {
389
+
390
+ #if FEATURE_MIS
391
+
392
+ // get the PDF of the hit envmap point
393
+ vec3 envColor;
394
+ float envPdf = envMapSample( environmentRotation * rayDirection, envMapInfo, envColor );
395
+
396
+ // and weight the contribution
397
+ float misWeight = misHeuristic( sampleRec.pdf, envPdf );
398
+ gl_FragColor.rgb += environmentIntensity * envColor * throughputColor * misWeight;
399
+
400
+ #else
401
+
402
+ gl_FragColor.rgb +=
403
+ environmentIntensity *
404
+ sampleEquirectEnvMapColor( environmentRotation * rayDirection, envMapInfo.map ) *
405
+ throughputColor;
406
+
407
+ #endif
408
+
409
+ }
410
+ break;
411
+
412
+ }
413
+
414
+ uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
415
+ Material material = readMaterialInfo( materials, materialIndex );
416
+
417
+ if ( material.matte && i == 0 ) {
418
+
419
+ gl_FragColor = vec4( 0.0 );
420
+ break;
421
+
422
+ }
423
+
424
+ // if we've determined that this is a shadow ray and we've hit an item with no shadow casting
425
+ // then skip it
426
+ if ( ! material.castShadow && isShadowRay ) {
427
+
428
+ vec3 point = rayOrigin + rayDirection * dist;
429
+ vec3 absPoint = abs( point );
430
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
431
+ rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
432
+
433
+ continue;
434
+
435
+ }
436
+
437
+ vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
438
+ // albedo
439
+ vec4 albedo = vec4( material.color, material.opacity );
440
+ if ( material.map != - 1 ) {
441
+
442
+ vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
443
+ albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
444
+ }
445
+
446
+ // alphaMap
447
+ if ( material.alphaMap != -1 ) {
448
+
449
+ albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
450
+
451
+ }
452
+
453
+ // possibly skip this sample if it's transparent, alpha test is enabled, or we hit the wrong material side
454
+ // and it's single sided.
455
+ // - alpha test is disabled when it === 0
456
+ // - the material sidedness test is complicated because we want light to pass through the back side but still
457
+ // be able to see the front side. This boolean checks if the side we hit is the front side on the first ray
458
+ // and we're rendering the other then we skip it. Do the opposite on subsequent bounces to get incoming light.
459
+ float alphaTest = material.alphaTest;
460
+ bool useAlphaTest = alphaTest != 0.0;
461
+ bool isFirstHit = i == 0;
462
+ if (
463
+ // material sidedness
464
+ material.side != 0.0 && ( side != material.side ) == isFirstHit
465
+
466
+ // alpha test
467
+ || useAlphaTest && albedo.a < alphaTest
468
+
469
+ // opacity
470
+ || ! useAlphaTest && albedo.a < rand()
471
+ ) {
472
+
473
+ vec3 point = rayOrigin + rayDirection * dist;
474
+ vec3 absPoint = abs( point );
475
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
476
+ rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
477
+
478
+ // only allow a limited number of transparency discards otherwise we could
479
+ // crash the context with too long a loop.
480
+ i -= sign( transparentTraversals );
481
+ transparentTraversals -= sign( transparentTraversals );
482
+ continue;
483
+
484
+ }
485
+
486
+ // fetch the interpolated smooth normal
487
+ vec3 normal = normalize( textureSampleBarycoord(
488
+ normalAttribute,
489
+ barycoord,
490
+ faceIndices.xyz
491
+ ).xyz );
492
+
493
+ // roughness
494
+ float roughness = material.roughness;
495
+ if ( material.roughnessMap != - 1 ) {
496
+
497
+ vec3 uvPrime = material.roughnessMapTransform * vec3( uv, 1 );
498
+ roughness *= texture2D( textures, vec3( uvPrime.xy, material.roughnessMap ) ).g;
499
+
500
+ }
501
+
502
+ // metalness
503
+ float metalness = material.metalness;
504
+ if ( material.metalnessMap != - 1 ) {
505
+
506
+ vec3 uvPrime = material.metalnessMapTransform * vec3( uv, 1 );
507
+ metalness *= texture2D( textures, vec3( uvPrime.xy, material.metalnessMap ) ).b;
508
+
509
+ }
510
+
511
+ // emission
512
+ vec3 emission = material.emissiveIntensity * material.emissive;
513
+ if ( material.emissiveMap != - 1 ) {
514
+
515
+ vec3 uvPrime = material.emissiveMapTransform * vec3( uv, 1 );
516
+ emission *= texture2D( textures, vec3( uvPrime.xy, material.emissiveMap ) ).xyz;
517
+
518
+ }
519
+
520
+ // transmission
521
+ float transmission = material.transmission;
522
+ if ( material.transmissionMap != - 1 ) {
523
+
524
+ vec3 uvPrime = material.transmissionMapTransform * vec3( uv, 1 );
525
+ transmission *= texture2D( textures, vec3( uvPrime.xy, material.transmissionMap ) ).r;
526
+
527
+ }
528
+
529
+ // normal
530
+ if ( material.normalMap != - 1 ) {
531
+
532
+ vec4 tangentSample = textureSampleBarycoord(
533
+ tangentAttribute,
534
+ barycoord,
535
+ faceIndices.xyz
536
+ );
537
+
538
+ // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
539
+ // resulting in NaNs and slow path tracing.
540
+ if ( length( tangentSample.xyz ) > 0.0 ) {
541
+
542
+ vec3 tangent = normalize( tangentSample.xyz );
543
+ vec3 bitangent = normalize( cross( normal, tangent ) * tangentSample.w );
544
+ mat3 vTBN = mat3( tangent, bitangent, normal );
545
+
546
+ vec3 uvPrime = material.normalMapTransform * vec3( uv, 1 );
547
+ vec3 texNormal = texture2D( textures, vec3( uvPrime.xy, material.normalMap ) ).xyz * 2.0 - 1.0;
548
+ texNormal.xy *= material.normalScale;
549
+ normal = vTBN * texNormal;
550
+
551
+ }
552
+
553
+ }
554
+
555
+ normal *= side;
556
+
557
+ SurfaceRec surfaceRec;
558
+ surfaceRec.normal = normal;
559
+ surfaceRec.faceNormal = faceNormal;
560
+ surfaceRec.transmission = transmission;
561
+ surfaceRec.ior = material.ior;
562
+ surfaceRec.emission = emission;
563
+ surfaceRec.metalness = metalness;
564
+ surfaceRec.color = albedo.rgb;
565
+ surfaceRec.roughness = roughness;
566
+
567
+ // frontFace is used to determine transmissive properties and PDF. If no transmission is used
568
+ // then we can just always assume this is a front face.
569
+ surfaceRec.frontFace = side == 1.0 || transmission == 0.0;
570
+
571
+ // Compute the filtered roughness value to use during specular reflection computations.
572
+ // The accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
573
+ // If we're exiting something transmissive then scale the factor down significantly so we can retain
574
+ // sharp internal reflections
575
+ surfaceRec.filteredRoughness = clamp( max( surfaceRec.roughness, accumulatedRoughness * filterGlossyFactor * 5.0 ), 0.0, 1.0 );
576
+
577
+ mat3 normalBasis = getBasisFromNormal( surfaceRec.normal );
578
+ mat3 invBasis = inverse( normalBasis );
579
+
580
+ vec3 outgoing = - normalize( invBasis * rayDirection );
581
+ sampleRec = bsdfSample( outgoing, surfaceRec );
582
+
583
+ float specRayPdf = specularPDF( outgoing, sampleRec.direction, surfaceRec );
584
+ isShadowRay = sampleRec.specularPdf < rand();
585
+
586
+ // adjust the hit point by the surface normal by a factor of some offset and the
587
+ // maximum component-wise value of the current point to accommodate floating point
588
+ // error as values increase.
589
+ vec3 point = rayOrigin + rayDirection * dist;
590
+ vec3 absPoint = abs( point );
591
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
592
+ rayDirection = normalize( normalBasis * sampleRec.direction );
593
+
594
+ bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
595
+ rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
596
+
597
+ // direct env map sampling
598
+ #if FEATURE_MIS
599
+ {
600
+
601
+ // find a sample in the environment map to include in the contribution
602
+ vec3 envColor, envDirection;
603
+ float envPdf = randomEnvMapSample( envMapInfo, envColor, envDirection );
604
+ envDirection = invEnvironmentRotation * envDirection;
605
+
606
+ // this env sampling is not set up for transmissive sampling and yields overly bright
607
+ // results so we ignore the sample in this case.
608
+ // TODO: this should be improved but how? The env samples could traverse a few layers?
609
+ bool isSampleBelowSurface = dot( faceNormal, envDirection ) < 0.0;
610
+ if ( isSampleBelowSurface ) {
611
+
612
+ envPdf = 0.0;
613
+
614
+ }
615
+
616
+ // check if a ray could even reach the surface
617
+ vec3 attenuatedColor;
618
+ if (
619
+ envPdf > 0.0 &&
620
+ isDirectionValid( envDirection, normal, faceNormal ) &&
621
+ ! attenuateHit( bvh, rayOrigin, envDirection, bounces - i, isShadowRay, attenuatedColor )
622
+ ) {
623
+
624
+ // get the material pdf
625
+ vec3 sampleColor;
626
+ float envMaterialPdf = bsdfResult( outgoing, normalize( invBasis * envDirection ), surfaceRec, sampleColor );
627
+ if ( envMaterialPdf > 0.0 ) {
628
+
629
+ // weight the direct light contribution
630
+ float misWeight = misHeuristic( envPdf, envMaterialPdf );
631
+ gl_FragColor.rgb += attenuatedColor * environmentIntensity * envColor * throughputColor * sampleColor * misWeight / envPdf;
632
+
633
+ }
634
+
635
+ }
636
+
637
+ }
638
+ #endif
639
+
640
+ // accumulate a roughness value to offset diffuse, specular, diffuse rays that have high contribution
641
+ // to a single pixel resulting in fireflies
642
+ if ( ! isBelowSurface ) {
643
+
644
+ // determine if this is a rough normal or not by checking how far off straight up it is
645
+ vec3 halfVector = normalize( outgoing + sampleRec.direction );
646
+ accumulatedRoughness += sin( acosApprox( halfVector.z ) );
647
+ transmissiveRay = false;
648
+
649
+ }
650
+
651
+ // accumulate color
652
+ gl_FragColor.rgb += ( emission * throughputColor );
653
+
654
+ // skip the sample if our PDF or ray is impossible
655
+ if ( sampleRec.pdf <= 0.0 || ! isDirectionValid( rayDirection, normal, faceNormal) ) {
656
+
657
+ break;
658
+
659
+ }
660
+
661
+ throughputColor *= sampleRec.color / sampleRec.pdf;
662
+
663
+ // discard the sample if there are any NaNs
664
+ if ( any( isnan( throughputColor ) ) || any( isinf( throughputColor ) ) ) {
665
+
666
+ break;
667
+
668
+ }
669
+
670
+ }
671
+
672
+ gl_FragColor.a *= opacity;
673
+
674
+ }
675
+
676
+ `
677
+
678
+ } );
679
+
680
+ this.setValues( parameters );
681
+
682
+ }
683
+
684
+ }