three-gpu-pathtracer 0.0.5 → 0.0.7

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 (44) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +815 -728
  3. package/build/index.module.js +5474 -3706
  4. package/build/index.module.js.map +1 -1
  5. package/build/index.umd.cjs +5479 -3704
  6. package/build/index.umd.cjs.map +1 -1
  7. package/package.json +68 -60
  8. package/src/core/DynamicPathTracingSceneGenerator.js +119 -111
  9. package/src/core/MaterialReducer.js +256 -256
  10. package/src/core/PathTracingRenderer.js +270 -255
  11. package/src/core/PathTracingSceneGenerator.js +4 -3
  12. package/src/index.js +35 -26
  13. package/src/materials/AlphaDisplayMaterial.js +48 -48
  14. package/src/materials/AmbientOcclusionMaterial.js +197 -197
  15. package/src/materials/BlendMaterial.js +67 -67
  16. package/src/materials/DenoiseMaterial.js +142 -0
  17. package/src/materials/GraphMaterial.js +243 -0
  18. package/src/materials/LambertPathTracingMaterial.js +285 -285
  19. package/src/materials/MaterialBase.js +56 -56
  20. package/src/materials/PhysicalPathTracingMaterial.js +970 -848
  21. package/src/{core → objects}/EquirectCamera.js +13 -13
  22. package/src/{core → objects}/PhysicalCamera.js +28 -28
  23. package/src/objects/PhysicalSpotLight.js +14 -0
  24. package/src/objects/ShapedAreaLight.js +12 -0
  25. package/src/shader/shaderEnvMapSampling.js +59 -59
  26. package/src/shader/shaderGGXFunctions.js +100 -108
  27. package/src/shader/shaderIridescenceFunctions.js +130 -0
  28. package/src/shader/shaderLightSampling.js +231 -87
  29. package/src/shader/shaderMaterialSampling.js +542 -501
  30. package/src/shader/shaderSheenFunctions.js +98 -0
  31. package/src/shader/shaderStructs.js +321 -191
  32. package/src/shader/shaderUtils.js +364 -287
  33. package/src/uniforms/EquirectHdrInfoUniform.js +259 -263
  34. package/src/uniforms/IESProfilesTexture.js +100 -0
  35. package/src/uniforms/LightsInfoUniformStruct.js +162 -0
  36. package/src/uniforms/MaterialsTexture.js +426 -319
  37. package/src/uniforms/PhysicalCameraUniform.js +36 -36
  38. package/src/uniforms/RenderTarget2DArray.js +93 -93
  39. package/src/utils/BlurredEnvMapGenerator.js +113 -113
  40. package/src/utils/GeometryPreparationUtils.js +21 -1
  41. package/src/utils/IESLoader.js +325 -0
  42. package/src/utils/UVUnwrapper.js +101 -101
  43. package/src/workers/PathTracingSceneWorker.js +42 -41
  44. package/src/uniforms/LightsTexture.js +0 -83
@@ -1,848 +1,970 @@
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, shaderLightStruct } 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 { shaderLightSampling } from '../shader/shaderLightSampling.js';
13
- import { shaderUtils } from '../shader/shaderUtils.js';
14
- import { PhysicalCameraUniform } from '../uniforms/PhysicalCameraUniform.js';
15
- import { EquirectHdrInfoUniform } from '../uniforms/EquirectHdrInfoUniform.js';
16
- import { LightsTexture } from '../uniforms/LightsTexture.js';
17
-
18
- export class PhysicalPathTracingMaterial extends MaterialBase {
19
-
20
- onBeforeRender() {
21
-
22
- this.setDefine( 'FEATURE_DOF', this.physicalCamera.bokehSize === 0 ? 0 : 1 );
23
-
24
- }
25
-
26
- constructor( parameters ) {
27
-
28
- super( {
29
-
30
- transparent: true,
31
- depthWrite: false,
32
-
33
- defines: {
34
- FEATURE_MIS: 1,
35
- FEATURE_DOF: 1,
36
- FEATURE_GRADIENT_BG: 0,
37
- TRANSPARENT_TRAVERSALS: 5,
38
- // 0 = Perspective
39
- // 1 = Orthographic
40
- // 2 = Equirectangular
41
- CAMERA_TYPE: 0,
42
- },
43
-
44
- uniforms: {
45
- resolution: { value: new Vector2() },
46
-
47
- bounces: { value: 3 },
48
- physicalCamera: { value: new PhysicalCameraUniform() },
49
-
50
- bvh: { value: new MeshBVHUniformStruct() },
51
- normalAttribute: { value: new FloatVertexAttributeTexture() },
52
- tangentAttribute: { value: new FloatVertexAttributeTexture() },
53
- uvAttribute: { value: new FloatVertexAttributeTexture() },
54
- materialIndexAttribute: { value: new UIntVertexAttributeTexture() },
55
- materials: { value: new MaterialsTexture() },
56
- textures: { value: new RenderTarget2DArray().texture },
57
- lights: { value: new LightsTexture() },
58
- lightCount: { value: 0 },
59
- cameraWorldMatrix: { value: new Matrix4() },
60
- invProjectionMatrix: { value: new Matrix4() },
61
- backgroundBlur: { value: 0.0 },
62
- environmentIntensity: { value: 2.0 },
63
- environmentRotation: { value: new Matrix3() },
64
- envMapInfo: { value: new EquirectHdrInfoUniform() },
65
-
66
- seed: { value: 0 },
67
- opacity: { value: 1 },
68
- filterGlossyFactor: { value: 0.0 },
69
-
70
- bgGradientTop: { value: new Color( 0x111111 ) },
71
- bgGradientBottom: { value: new Color( 0x000000 ) },
72
- backgroundAlpha: { value: 1.0 },
73
- },
74
-
75
- vertexShader: /* glsl */`
76
-
77
- varying vec2 vUv;
78
- void main() {
79
-
80
- vec4 mvPosition = vec4( position, 1.0 );
81
- mvPosition = modelViewMatrix * mvPosition;
82
- gl_Position = projectionMatrix * mvPosition;
83
-
84
- vUv = uv;
85
-
86
- }
87
-
88
- `,
89
-
90
- fragmentShader: /* glsl */`
91
- #define RAY_OFFSET 1e-4
92
-
93
- precision highp isampler2D;
94
- precision highp usampler2D;
95
- precision highp sampler2DArray;
96
- vec4 envMapTexelToLinear( vec4 a ) { return a; }
97
- #include <common>
98
-
99
- ${ shaderStructs }
100
- ${ shaderIntersectFunction }
101
- ${ shaderMaterialStructs }
102
- ${ shaderLightStruct }
103
-
104
- ${ shaderUtils }
105
- ${ shaderMaterialSampling }
106
- ${ shaderEnvMapSampling }
107
- ${ shaderLightSampling }
108
-
109
- uniform mat3 environmentRotation;
110
- uniform float backgroundBlur;
111
- uniform float backgroundAlpha;
112
-
113
- #if FEATURE_GRADIENT_BG
114
-
115
- uniform vec3 bgGradientTop;
116
- uniform vec3 bgGradientBottom;
117
-
118
- #endif
119
-
120
- #if FEATURE_DOF
121
-
122
- uniform PhysicalCamera physicalCamera;
123
-
124
- #endif
125
-
126
- uniform vec2 resolution;
127
- uniform int bounces;
128
- uniform mat4 cameraWorldMatrix;
129
- uniform mat4 invProjectionMatrix;
130
- uniform sampler2D normalAttribute;
131
- uniform sampler2D tangentAttribute;
132
- uniform sampler2D uvAttribute;
133
- uniform usampler2D materialIndexAttribute;
134
- uniform BVH bvh;
135
- uniform float environmentIntensity;
136
- uniform float filterGlossyFactor;
137
- uniform int seed;
138
- uniform float opacity;
139
- uniform sampler2D materials;
140
- uniform sampler2D lights;
141
- uniform uint lightCount;
142
-
143
- uniform EquirectHdrInfo envMapInfo;
144
-
145
- uniform sampler2DArray textures;
146
- varying vec2 vUv;
147
-
148
- vec3 sampleBackground( vec3 direction ) {
149
-
150
- #if FEATURE_GRADIENT_BG
151
-
152
- direction = normalize( direction + randDirection() * 0.05 );
153
-
154
- float value = ( direction.y + 1.0 ) / 2.0;
155
- value = pow( value, 2.0 );
156
-
157
- return mix( bgGradientBottom, bgGradientTop, value );
158
-
159
- #else
160
-
161
- vec3 sampleDir = normalize( direction + getHemisphereSample( direction, rand2() ) * 0.5 * backgroundBlur );
162
- return environmentIntensity * sampleEquirectEnvMapColor( sampleDir, envMapInfo.map );
163
-
164
- #endif
165
-
166
- }
167
-
168
- // step through multiple surface hits and accumulate color attenuation based on transmissive surfaces
169
- bool attenuateHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, int traversals, bool isShadowRay, out vec3 color ) {
170
-
171
- // hit results
172
- uvec4 faceIndices = uvec4( 0u );
173
- vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
174
- vec3 barycoord = vec3( 0.0 );
175
- float side = 1.0;
176
- float dist = 0.0;
177
-
178
- color = vec3( 1.0 );
179
-
180
- for ( int i = 0; i < traversals; i ++ ) {
181
-
182
- if ( bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
183
-
184
- // TODO: attenuate the contribution based on the PDF of the resulting ray including refraction values
185
- // Should be able to work using the material BSDF functions which will take into account specularity, etc.
186
- // TODO: should we account for emissive surfaces here?
187
-
188
- vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
189
- uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
190
- Material material = readMaterialInfo( materials, materialIndex );
191
-
192
- // adjust the ray to the new surface
193
- bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
194
- vec3 point = rayOrigin + rayDirection * dist;
195
- vec3 absPoint = abs( point );
196
- float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
197
- rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
198
-
199
- if ( ! material.castShadow && isShadowRay ) {
200
-
201
- continue;
202
-
203
- }
204
-
205
- // Opacity Test
206
-
207
- // albedo
208
- vec4 albedo = vec4( material.color, material.opacity );
209
- if ( material.map != - 1 ) {
210
-
211
- vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
212
- albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
213
-
214
- }
215
-
216
- // alphaMap
217
- if ( material.alphaMap != -1 ) {
218
-
219
- albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
220
-
221
- }
222
-
223
- // transmission
224
- float transmission = material.transmission;
225
- if ( material.transmissionMap != - 1 ) {
226
-
227
- vec3 uvPrime = material.transmissionMapTransform * vec3( uv, 1 );
228
- transmission *= texture2D( textures, vec3( uvPrime.xy, material.transmissionMap ) ).r;
229
-
230
- }
231
-
232
- // metalness
233
- float metalness = material.metalness;
234
- if ( material.metalnessMap != - 1 ) {
235
-
236
- vec3 uvPrime = material.metalnessMapTransform * vec3( uv, 1 );
237
- metalness *= texture2D( textures, vec3( uvPrime.xy, material.metalnessMap ) ).b;
238
-
239
- }
240
-
241
- float alphaTest = material.alphaTest;
242
- bool useAlphaTest = alphaTest != 0.0;
243
- float transmissionFactor = ( 1.0 - metalness ) * transmission;
244
- if (
245
- transmissionFactor < rand() && ! (
246
- // material sidedness
247
- material.side != 0.0 && side == material.side
248
-
249
- // alpha test
250
- || useAlphaTest && albedo.a < alphaTest
251
-
252
- // opacity
253
- || ! useAlphaTest && albedo.a < rand()
254
- )
255
- ) {
256
-
257
- return true;
258
-
259
- }
260
-
261
- // only attenuate on the way in
262
- if ( isBelowSurface ) {
263
-
264
- color *= mix( vec3( 1.0 ), albedo.rgb, transmissionFactor );
265
-
266
- }
267
-
268
- } else {
269
-
270
- return false;
271
-
272
- }
273
-
274
- }
275
-
276
- return true;
277
-
278
- }
279
-
280
- // returns whether the ray hit anything before a certain distance, not just the first surface. Could be optimized to not check the full hierarchy.
281
- bool anyCloserHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, float maxDist ) {
282
-
283
- uvec4 faceIndices = uvec4( 0u );
284
- vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
285
- vec3 barycoord = vec3( 0.0 );
286
- float side = 1.0;
287
- float dist = 0.0;
288
- bool hit = bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
289
- return hit && dist < maxDist;
290
-
291
- }
292
-
293
- // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
294
- // erichlof/THREE.js-PathTracing-Renderer/
295
- float tentFilter( float x ) {
296
-
297
- return x < 0.5 ? sqrt( 2.0 * x ) - 1.0 : 1.0 - sqrt( 2.0 - ( 2.0 * x ) );
298
-
299
- }
300
-
301
- vec3 ndcToRayOrigin( vec2 coord ) {
302
-
303
- vec4 rayOrigin4 = cameraWorldMatrix * invProjectionMatrix * vec4( coord, - 1.0, 1.0 );
304
- return rayOrigin4.xyz / rayOrigin4.w;
305
- }
306
-
307
- void getCameraRay( out vec3 rayDirection, out vec3 rayOrigin ) {
308
-
309
- vec2 ssd = vec2( 1.0 ) / resolution;
310
-
311
- // Jitter the camera ray by finding a uv coordinate at a random sample
312
- // around this pixel's UV coordinate
313
- vec2 jitteredUv = vUv + vec2( tentFilter( rand() ) * ssd.x, tentFilter( rand() ) * ssd.y );
314
-
315
- #if CAMERA_TYPE == 2
316
-
317
- // Equirectangular projection
318
-
319
- vec4 rayDirection4 = vec4( equirectUvToDirection( jitteredUv ), 0.0 );
320
- vec4 rayOrigin4 = vec4( 0.0, 0.0, 0.0, 1.0 );
321
-
322
- rayDirection4 = cameraWorldMatrix * rayDirection4;
323
- rayOrigin4 = cameraWorldMatrix * rayOrigin4;
324
-
325
- rayDirection = normalize( rayDirection4.xyz );
326
- rayOrigin = rayOrigin4.xyz / rayOrigin4.w;
327
-
328
- #else
329
-
330
- // get [-1, 1] normalized device coordinates
331
- vec2 ndc = 2.0 * jitteredUv - vec2( 1.0 );
332
-
333
- rayOrigin = ndcToRayOrigin( ndc );
334
-
335
- #if CAMERA_TYPE == 1
336
-
337
- // Orthographic projection
338
-
339
- rayDirection = ( cameraWorldMatrix * vec4( 0.0, 0.0, -1.0, 0.0 ) ).xyz;
340
- rayDirection = normalize( rayDirection );
341
-
342
- #else
343
-
344
- // Perspective projection
345
-
346
- rayDirection = normalize( mat3(cameraWorldMatrix) * ( invProjectionMatrix * vec4( ndc, 0.0, 1.0 ) ).xyz );
347
-
348
- #endif
349
-
350
- #endif
351
-
352
- #if FEATURE_DOF
353
- {
354
-
355
- // depth of field
356
- vec3 focalPoint = rayOrigin + normalize( rayDirection ) * physicalCamera.focusDistance;
357
-
358
- // get the aperture sample
359
- vec2 apertureSample = sampleAperture( physicalCamera.apertureBlades ) * physicalCamera.bokehSize * 0.5 * 1e-3;
360
-
361
- // rotate the aperture shape
362
- float ac = cos( physicalCamera.apertureRotation );
363
- float as = sin( physicalCamera.apertureRotation );
364
- apertureSample = vec2(
365
- apertureSample.x * ac - apertureSample.y * as,
366
- apertureSample.x * as + apertureSample.y * ac
367
- );
368
- apertureSample.x *= saturate( physicalCamera.anamorphicRatio );
369
- apertureSample.y *= saturate( 1.0 / physicalCamera.anamorphicRatio );
370
-
371
- // create the new ray
372
- rayOrigin += ( cameraWorldMatrix * vec4( apertureSample, 0.0, 0.0 ) ).xyz;
373
- rayDirection = focalPoint - rayOrigin;
374
-
375
- }
376
- #endif
377
-
378
- rayDirection = normalize( rayDirection );
379
-
380
- }
381
-
382
- void main() {
383
-
384
- rng_initialize( gl_FragCoord.xy, seed );
385
-
386
- vec3 rayDirection;
387
- vec3 rayOrigin;
388
-
389
- getCameraRay( rayDirection, rayOrigin );
390
-
391
- // inverse environment rotation
392
- mat3 invEnvironmentRotation = inverse( environmentRotation );
393
-
394
- // final color
395
- gl_FragColor = vec4( 0.0 );
396
- gl_FragColor.a = 1.0;
397
-
398
- // hit results
399
- uvec4 faceIndices = uvec4( 0u );
400
- vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
401
- vec3 barycoord = vec3( 0.0 );
402
- float side = 1.0;
403
- float dist = 0.0;
404
-
405
- // path tracing state
406
- float accumulatedRoughness = 0.0;
407
- float accumulatedClearcoatRoughness = 0.0;
408
- bool transmissiveRay = true;
409
- int transparentTraversals = TRANSPARENT_TRAVERSALS;
410
- vec3 throughputColor = vec3( 1.0 );
411
- SampleRec sampleRec;
412
- int i;
413
- bool isShadowRay = false;
414
-
415
- for ( i = 0; i < bounces; i ++ ) {
416
-
417
- bool hit = bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
418
-
419
- LightSampleRec lightHit = lightsClosestHit( lights, lightCount, rayOrigin, rayDirection );
420
-
421
- if ( lightHit.hit && ( lightHit.dist < dist || !hit ) ) {
422
-
423
- if ( i == 0 || transmissiveRay ) {
424
-
425
- gl_FragColor.rgb += lightHit.emission * throughputColor;
426
-
427
- } else {
428
-
429
- #if FEATURE_MIS
430
-
431
- // weight the contribution
432
- float misWeight = misHeuristic( sampleRec.pdf, lightHit.pdf / float( lightCount + 1u ) );
433
- gl_FragColor.rgb += lightHit.emission * throughputColor * misWeight;
434
-
435
- #else
436
-
437
- gl_FragColor.rgb +=
438
- lightHit.emission *
439
- throughputColor;
440
-
441
- #endif
442
-
443
- }
444
- break;
445
-
446
- }
447
-
448
- if ( ! hit ) {
449
-
450
- if ( i == 0 || transmissiveRay ) {
451
-
452
- gl_FragColor.rgb += sampleBackground( environmentRotation * rayDirection ) * throughputColor;
453
- gl_FragColor.a = backgroundAlpha;
454
-
455
- } else {
456
-
457
- #if FEATURE_MIS
458
-
459
- // get the PDF of the hit envmap point
460
- vec3 envColor;
461
- float envPdf = envMapSample( environmentRotation * rayDirection, envMapInfo, envColor );
462
- envPdf /= float( lightCount + 1u );
463
-
464
- // and weight the contribution
465
- float misWeight = misHeuristic( sampleRec.pdf, envPdf );
466
- gl_FragColor.rgb += environmentIntensity * envColor * throughputColor * misWeight;
467
-
468
- #else
469
-
470
- gl_FragColor.rgb +=
471
- environmentIntensity *
472
- sampleEquirectEnvMapColor( environmentRotation * rayDirection, envMapInfo.map ) *
473
- throughputColor;
474
-
475
- #endif
476
-
477
- }
478
- break;
479
-
480
- }
481
-
482
- uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
483
- Material material = readMaterialInfo( materials, materialIndex );
484
-
485
- if ( material.matte && i == 0 ) {
486
-
487
- gl_FragColor = vec4( 0.0 );
488
- break;
489
-
490
- }
491
-
492
- // if we've determined that this is a shadow ray and we've hit an item with no shadow casting
493
- // then skip it
494
- if ( ! material.castShadow && isShadowRay ) {
495
-
496
- vec3 point = rayOrigin + rayDirection * dist;
497
- vec3 absPoint = abs( point );
498
- float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
499
- rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
500
-
501
- continue;
502
-
503
- }
504
-
505
- vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
506
- // albedo
507
- vec4 albedo = vec4( material.color, material.opacity );
508
- if ( material.map != - 1 ) {
509
-
510
- vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
511
- albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
512
- }
513
-
514
- // alphaMap
515
- if ( material.alphaMap != -1 ) {
516
-
517
- albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
518
-
519
- }
520
-
521
- // possibly skip this sample if it's transparent, alpha test is enabled, or we hit the wrong material side
522
- // and it's single sided.
523
- // - alpha test is disabled when it === 0
524
- // - the material sidedness test is complicated because we want light to pass through the back side but still
525
- // be able to see the front side. This boolean checks if the side we hit is the front side on the first ray
526
- // and we're rendering the other then we skip it. Do the opposite on subsequent bounces to get incoming light.
527
- float alphaTest = material.alphaTest;
528
- bool useAlphaTest = alphaTest != 0.0;
529
- bool isFirstHit = i == 0;
530
- if (
531
- // material sidedness
532
- material.side != 0.0 && ( side != material.side ) == isFirstHit
533
-
534
- // alpha test
535
- || useAlphaTest && albedo.a < alphaTest
536
-
537
- // opacity
538
- || ! useAlphaTest && albedo.a < rand()
539
- ) {
540
-
541
- vec3 point = rayOrigin + rayDirection * dist;
542
- vec3 absPoint = abs( point );
543
- float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
544
- rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
545
-
546
- // only allow a limited number of transparency discards otherwise we could
547
- // crash the context with too long a loop.
548
- i -= sign( transparentTraversals );
549
- transparentTraversals -= sign( transparentTraversals );
550
- continue;
551
-
552
- }
553
-
554
- // fetch the interpolated smooth normal
555
- vec3 normal = normalize( textureSampleBarycoord(
556
- normalAttribute,
557
- barycoord,
558
- faceIndices.xyz
559
- ).xyz );
560
-
561
- // roughness
562
- float roughness = material.roughness;
563
- if ( material.roughnessMap != - 1 ) {
564
-
565
- vec3 uvPrime = material.roughnessMapTransform * vec3( uv, 1 );
566
- roughness *= texture2D( textures, vec3( uvPrime.xy, material.roughnessMap ) ).g;
567
-
568
- }
569
-
570
- // metalness
571
- float metalness = material.metalness;
572
- if ( material.metalnessMap != - 1 ) {
573
-
574
- vec3 uvPrime = material.metalnessMapTransform * vec3( uv, 1 );
575
- metalness *= texture2D( textures, vec3( uvPrime.xy, material.metalnessMap ) ).b;
576
-
577
- }
578
-
579
- // emission
580
- vec3 emission = material.emissiveIntensity * material.emissive;
581
- if ( material.emissiveMap != - 1 ) {
582
-
583
- vec3 uvPrime = material.emissiveMapTransform * vec3( uv, 1 );
584
- emission *= texture2D( textures, vec3( uvPrime.xy, material.emissiveMap ) ).xyz;
585
-
586
- }
587
-
588
- // transmission
589
- float transmission = material.transmission;
590
- if ( material.transmissionMap != - 1 ) {
591
-
592
- vec3 uvPrime = material.transmissionMapTransform * vec3( uv, 1 );
593
- transmission *= texture2D( textures, vec3( uvPrime.xy, material.transmissionMap ) ).r;
594
-
595
- }
596
-
597
- // normal
598
- vec3 baseNormal = normal;
599
- if ( material.normalMap != - 1 ) {
600
-
601
- vec4 tangentSample = textureSampleBarycoord(
602
- tangentAttribute,
603
- barycoord,
604
- faceIndices.xyz
605
- );
606
-
607
- // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
608
- // resulting in NaNs and slow path tracing.
609
- if ( length( tangentSample.xyz ) > 0.0 ) {
610
-
611
- vec3 tangent = normalize( tangentSample.xyz );
612
- vec3 bitangent = normalize( cross( normal, tangent ) * tangentSample.w );
613
- mat3 vTBN = mat3( tangent, bitangent, normal );
614
-
615
- vec3 uvPrime = material.normalMapTransform * vec3( uv, 1 );
616
- vec3 texNormal = texture2D( textures, vec3( uvPrime.xy, material.normalMap ) ).xyz * 2.0 - 1.0;
617
- texNormal.xy *= material.normalScale;
618
- normal = vTBN * texNormal;
619
-
620
- }
621
-
622
- }
623
-
624
- normal *= side;
625
-
626
- // clearcoat
627
- float clearcoat = material.clearcoat;
628
- if ( material.clearcoatMap != - 1 ) {
629
-
630
- vec3 uvPrime = material.clearcoatMapTransform * vec3( uv, 1 );
631
- clearcoat *= texture2D( textures, vec3( uvPrime.xy, material.clearcoatMap ) ).r;
632
-
633
- }
634
-
635
- // clearcoat
636
- float clearcoatRoughness = material.clearcoatRoughness;
637
- if ( material.clearcoatRoughnessMap != - 1 ) {
638
-
639
- vec3 uvPrime = material.clearcoatRoughnessMapTransform * vec3( uv, 1 );
640
- clearcoat *= texture2D( textures, vec3( uvPrime.xy, material.clearcoatRoughnessMap ) ).g;
641
-
642
- }
643
-
644
- // clearcoatNormal
645
- vec3 clearcoatNormal = baseNormal;
646
- if ( material.clearcoatNormalMap != - 1 ) {
647
-
648
- vec4 tangentSample = textureSampleBarycoord(
649
- tangentAttribute,
650
- barycoord,
651
- faceIndices.xyz
652
- );
653
-
654
- // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
655
- // resulting in NaNs and slow path tracing.
656
- if ( length( tangentSample.xyz ) > 0.0 ) {
657
-
658
- vec3 tangent = normalize( tangentSample.xyz );
659
- vec3 bitangent = normalize( cross( clearcoatNormal, tangent ) * tangentSample.w );
660
- mat3 vTBN = mat3( tangent, bitangent, clearcoatNormal );
661
-
662
- vec3 uvPrime = material.clearcoatNormalMapTransform * vec3( uv, 1 );
663
- vec3 texNormal = texture2D( textures, vec3( uvPrime.xy, material.clearcoatNormalMap ) ).xyz * 2.0 - 1.0;
664
- texNormal.xy *= material.clearcoatNormalScale;
665
- clearcoatNormal = vTBN * texNormal;
666
-
667
- }
668
-
669
- }
670
-
671
- clearcoatNormal *= side;
672
-
673
- SurfaceRec surfaceRec;
674
- surfaceRec.normal = normal;
675
- surfaceRec.faceNormal = faceNormal;
676
- surfaceRec.transmission = transmission;
677
- surfaceRec.ior = material.ior;
678
- surfaceRec.emission = emission;
679
- surfaceRec.metalness = metalness;
680
- surfaceRec.color = albedo.rgb;
681
- surfaceRec.roughness = roughness;
682
- surfaceRec.clearcoat = clearcoat;
683
- surfaceRec.clearcoatRoughness = clearcoatRoughness;
684
-
685
- // frontFace is used to determine transmissive properties and PDF. If no transmission is used
686
- // then we can just always assume this is a front face.
687
- surfaceRec.frontFace = side == 1.0 || transmission == 0.0;
688
-
689
- // Compute the filtered roughness value to use during specular reflection computations.
690
- // The accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
691
- // If we're exiting something transmissive then scale the factor down significantly so we can retain
692
- // sharp internal reflections
693
- surfaceRec.filteredRoughness = clamp( max( surfaceRec.roughness, accumulatedRoughness * filterGlossyFactor * 5.0 ), 0.0, 1.0 );
694
- surfaceRec.filteredClearcoatRoughness = clamp( max( surfaceRec.clearcoatRoughness, accumulatedClearcoatRoughness * filterGlossyFactor * 5.0 ), 0.0, 1.0 );
695
-
696
- mat3 normalBasis = getBasisFromNormal( surfaceRec.normal );
697
- mat3 invBasis = inverse( normalBasis );
698
-
699
- mat3 clearcoatNormalBasis = getBasisFromNormal( clearcoatNormal );
700
- mat3 clearcoatInvBasis = inverse( clearcoatNormalBasis );
701
-
702
- vec3 outgoing = - normalize( invBasis * rayDirection );
703
- vec3 clearcoatOutgoing = - normalize( clearcoatInvBasis * rayDirection );
704
- sampleRec = bsdfSample( outgoing, clearcoatOutgoing, normalBasis, invBasis, clearcoatNormalBasis, clearcoatInvBasis, surfaceRec );
705
-
706
- isShadowRay = sampleRec.specularPdf < rand();
707
-
708
- // adjust the hit point by the surface normal by a factor of some offset and the
709
- // maximum component-wise value of the current point to accommodate floating point
710
- // error as values increase.
711
- vec3 point = rayOrigin + rayDirection * dist;
712
- vec3 absPoint = abs( point );
713
- float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
714
- rayDirection = normalize( normalBasis * sampleRec.direction );
715
-
716
- bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
717
- rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
718
-
719
- // direct env map sampling
720
- #if FEATURE_MIS
721
-
722
- // uniformly pick a light or environment map
723
- if( rand() > 1.0 / float( lightCount + 1u ) ) {
724
-
725
- // sample a light or environment
726
- LightSampleRec lightSampleRec = randomLightSample( lights, lightCount, rayOrigin );
727
-
728
- bool isSampleBelowSurface = dot( faceNormal, lightSampleRec.direction ) < 0.0;
729
- if ( isSampleBelowSurface ) {
730
-
731
- lightSampleRec.pdf = 0.0;
732
-
733
- }
734
-
735
- // check if a ray could even reach the light area
736
- if (
737
- lightSampleRec.pdf > 0.0 &&
738
- isDirectionValid( lightSampleRec.direction, normal, faceNormal ) &&
739
- ! anyCloserHit( bvh, rayOrigin, lightSampleRec.direction, lightSampleRec.dist )
740
- ) {
741
-
742
- // get the material pdf
743
- vec3 sampleColor;
744
- float lightMaterialPdf = bsdfResult( outgoing, clearcoatOutgoing, normalize( invBasis * lightSampleRec.direction ), normalize( clearcoatInvBasis * lightSampleRec.direction ), surfaceRec, sampleColor );
745
- bool isValidSampleColor = all( greaterThanEqual( sampleColor, vec3( 0.0 ) ) );
746
- if ( lightMaterialPdf > 0.0 && isValidSampleColor ) {
747
-
748
- // weight the direct light contribution
749
- float lightPdf = lightSampleRec.pdf / float( lightCount + 1u );
750
- float misWeight = misHeuristic( lightPdf, lightMaterialPdf );
751
- gl_FragColor.rgb += lightSampleRec.emission * throughputColor * sampleColor * misWeight / lightPdf;
752
-
753
- }
754
-
755
- }
756
-
757
- } else {
758
-
759
- // find a sample in the environment map to include in the contribution
760
- vec3 envColor, envDirection;
761
- float envPdf = randomEnvMapSample( envMapInfo, envColor, envDirection );
762
- envDirection = invEnvironmentRotation * envDirection;
763
-
764
- // this env sampling is not set up for transmissive sampling and yields overly bright
765
- // results so we ignore the sample in this case.
766
- // TODO: this should be improved but how? The env samples could traverse a few layers?
767
- bool isSampleBelowSurface = dot( faceNormal, envDirection ) < 0.0;
768
- if ( isSampleBelowSurface ) {
769
-
770
- envPdf = 0.0;
771
-
772
- }
773
-
774
- // check if a ray could even reach the surface
775
- vec3 attenuatedColor;
776
- if (
777
- envPdf > 0.0 &&
778
- isDirectionValid( envDirection, normal, faceNormal ) &&
779
- ! attenuateHit( bvh, rayOrigin, envDirection, bounces - i, isShadowRay, attenuatedColor )
780
- ) {
781
-
782
- // get the material pdf
783
- vec3 sampleColor;
784
- float envMaterialPdf = bsdfResult( outgoing, clearcoatOutgoing, normalize( invBasis * envDirection ), normalize( clearcoatInvBasis * envDirection ), surfaceRec, sampleColor );
785
- bool isValidSampleColor = all( greaterThanEqual( sampleColor, vec3( 0.0 ) ) );
786
- if ( envMaterialPdf > 0.0 && isValidSampleColor ) {
787
-
788
- // weight the direct light contribution
789
- envPdf /= float( lightCount + 1u );
790
- float misWeight = misHeuristic( envPdf, envMaterialPdf );
791
- gl_FragColor.rgb += attenuatedColor * environmentIntensity * envColor * throughputColor * sampleColor * misWeight / envPdf;
792
-
793
- }
794
-
795
- }
796
-
797
- }
798
- #endif
799
-
800
- // accumulate a roughness value to offset diffuse, specular, diffuse rays that have high contribution
801
- // to a single pixel resulting in fireflies
802
- if ( ! isBelowSurface ) {
803
-
804
- // determine if this is a rough normal or not by checking how far off straight up it is
805
- vec3 halfVector = normalize( outgoing + sampleRec.direction );
806
- accumulatedRoughness += sin( acosApprox( halfVector.z ) );
807
-
808
- vec3 clearcoatHalfVector = normalize( clearcoatOutgoing + sampleRec.clearcoatDirection );
809
- accumulatedClearcoatRoughness += sin( acosApprox( clearcoatHalfVector.z ) );
810
-
811
- transmissiveRay = false;
812
-
813
- }
814
-
815
- // accumulate color
816
- gl_FragColor.rgb += ( emission * throughputColor );
817
-
818
- // skip the sample if our PDF or ray is impossible
819
- if ( sampleRec.pdf <= 0.0 || ! isDirectionValid( rayDirection, normal, faceNormal) ) {
820
-
821
- break;
822
-
823
- }
824
-
825
- throughputColor *= sampleRec.color / sampleRec.pdf;
826
-
827
- // discard the sample if there are any NaNs
828
- if ( any( isnan( throughputColor ) ) || any( isinf( throughputColor ) ) ) {
829
-
830
- break;
831
-
832
- }
833
-
834
- }
835
-
836
- gl_FragColor.a *= opacity;
837
-
838
- }
839
-
840
- `
841
-
842
- } );
843
-
844
- this.setValues( parameters );
845
-
846
- }
847
-
848
- }
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, shaderLightStruct } 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 { shaderLightSampling } from '../shader/shaderLightSampling.js';
13
+ import { shaderUtils } from '../shader/shaderUtils.js';
14
+ import { PhysicalCameraUniform } from '../uniforms/PhysicalCameraUniform.js';
15
+ import { EquirectHdrInfoUniform } from '../uniforms/EquirectHdrInfoUniform.js';
16
+ import { LightsInfoUniformStruct } from '../uniforms/LightsInfoUniformStruct.js';
17
+ import { IESProfilesTexture } from '../uniforms/IESProfilesTexture.js';
18
+
19
+ export class PhysicalPathTracingMaterial extends MaterialBase {
20
+
21
+ onBeforeRender() {
22
+
23
+ this.setDefine( 'FEATURE_DOF', this.physicalCamera.bokehSize === 0 ? 0 : 1 );
24
+
25
+ }
26
+
27
+ constructor( parameters ) {
28
+
29
+ super( {
30
+
31
+ transparent: true,
32
+ depthWrite: false,
33
+
34
+ defines: {
35
+ FEATURE_MIS: 1,
36
+ FEATURE_DOF: 1,
37
+ FEATURE_GRADIENT_BG: 0,
38
+ TRANSPARENT_TRAVERSALS: 5,
39
+ // 0 = Perspective
40
+ // 1 = Orthographic
41
+ // 2 = Equirectangular
42
+ CAMERA_TYPE: 0,
43
+ },
44
+
45
+ uniforms: {
46
+ resolution: { value: new Vector2() },
47
+
48
+ bounces: { value: 3 },
49
+ physicalCamera: { value: new PhysicalCameraUniform() },
50
+
51
+ bvh: { value: new MeshBVHUniformStruct() },
52
+ normalAttribute: { value: new FloatVertexAttributeTexture() },
53
+ tangentAttribute: { value: new FloatVertexAttributeTexture() },
54
+ uvAttribute: { value: new FloatVertexAttributeTexture() },
55
+ colorAttribute: { value: new FloatVertexAttributeTexture() },
56
+ materialIndexAttribute: { value: new UIntVertexAttributeTexture() },
57
+ materials: { value: new MaterialsTexture() },
58
+ textures: { value: new RenderTarget2DArray().texture },
59
+ lights: { value: new LightsInfoUniformStruct() },
60
+ iesProfiles: { value: new IESProfilesTexture().texture },
61
+ cameraWorldMatrix: { value: new Matrix4() },
62
+ invProjectionMatrix: { value: new Matrix4() },
63
+ backgroundBlur: { value: 0.0 },
64
+ environmentIntensity: { value: 1.0 },
65
+ environmentRotation: { value: new Matrix3() },
66
+ envMapInfo: { value: new EquirectHdrInfoUniform() },
67
+
68
+ seed: { value: 0 },
69
+ opacity: { value: 1 },
70
+ filterGlossyFactor: { value: 0.0 },
71
+
72
+ bgGradientTop: { value: new Color( 0x111111 ) },
73
+ bgGradientBottom: { value: new Color( 0x000000 ) },
74
+ backgroundAlpha: { value: 1.0 },
75
+ },
76
+
77
+ vertexShader: /* glsl */`
78
+
79
+ varying vec2 vUv;
80
+ void main() {
81
+
82
+ vec4 mvPosition = vec4( position, 1.0 );
83
+ mvPosition = modelViewMatrix * mvPosition;
84
+ gl_Position = projectionMatrix * mvPosition;
85
+
86
+ vUv = uv;
87
+
88
+ }
89
+
90
+ `,
91
+
92
+ fragmentShader: /* glsl */`
93
+ #define RAY_OFFSET 1e-4
94
+
95
+ precision highp isampler2D;
96
+ precision highp usampler2D;
97
+ precision highp sampler2DArray;
98
+ vec4 envMapTexelToLinear( vec4 a ) { return a; }
99
+ #include <common>
100
+
101
+ ${ shaderStructs }
102
+ ${ shaderIntersectFunction }
103
+ ${ shaderMaterialStructs }
104
+ ${ shaderLightStruct }
105
+
106
+ ${ shaderUtils }
107
+ ${ shaderMaterialSampling }
108
+ ${ shaderEnvMapSampling }
109
+
110
+ uniform mat3 environmentRotation;
111
+ uniform float backgroundBlur;
112
+ uniform float backgroundAlpha;
113
+
114
+ #if FEATURE_GRADIENT_BG
115
+
116
+ uniform vec3 bgGradientTop;
117
+ uniform vec3 bgGradientBottom;
118
+
119
+ #endif
120
+
121
+ #if FEATURE_DOF
122
+
123
+ uniform PhysicalCamera physicalCamera;
124
+
125
+ #endif
126
+
127
+ uniform vec2 resolution;
128
+ uniform int bounces;
129
+ uniform mat4 cameraWorldMatrix;
130
+ uniform mat4 invProjectionMatrix;
131
+ uniform sampler2D normalAttribute;
132
+ uniform sampler2D tangentAttribute;
133
+ uniform sampler2D uvAttribute;
134
+ uniform sampler2D colorAttribute;
135
+ uniform usampler2D materialIndexAttribute;
136
+ uniform BVH bvh;
137
+ uniform float environmentIntensity;
138
+ uniform float filterGlossyFactor;
139
+ uniform int seed;
140
+ uniform float opacity;
141
+ uniform sampler2D materials;
142
+ uniform LightsInfo lights;
143
+ uniform sampler2DArray iesProfiles;
144
+
145
+ ${ shaderLightSampling }
146
+
147
+ uniform EquirectHdrInfo envMapInfo;
148
+
149
+ uniform sampler2DArray textures;
150
+ varying vec2 vUv;
151
+
152
+ float applyFilteredGlossy( float roughness, float accumulatedRoughness ) {
153
+
154
+ return clamp(
155
+ max(
156
+ roughness,
157
+ accumulatedRoughness * filterGlossyFactor * 5.0 ),
158
+ 0.0,
159
+ 1.0
160
+ );
161
+
162
+ }
163
+
164
+ vec3 sampleBackground( vec3 direction ) {
165
+
166
+ #if FEATURE_GRADIENT_BG
167
+
168
+ direction = normalize( direction + randDirection() * 0.05 );
169
+
170
+ float value = ( direction.y + 1.0 ) / 2.0;
171
+ value = pow( value, 2.0 );
172
+
173
+ return mix( bgGradientBottom, bgGradientTop, value );
174
+
175
+ #else
176
+
177
+ vec3 sampleDir = normalize( direction + getHemisphereSample( direction, rand2() ) * 0.5 * backgroundBlur );
178
+ return environmentIntensity * sampleEquirectEnvMapColor( sampleDir, envMapInfo.map );
179
+
180
+ #endif
181
+
182
+ }
183
+
184
+ // step through multiple surface hits and accumulate color attenuation based on transmissive surfaces
185
+ bool attenuateHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, int traversals, bool isShadowRay, out vec3 color ) {
186
+
187
+ // hit results
188
+ uvec4 faceIndices = uvec4( 0u );
189
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
190
+ vec3 barycoord = vec3( 0.0 );
191
+ float side = 1.0;
192
+ float dist = 0.0;
193
+
194
+ color = vec3( 1.0 );
195
+
196
+ for ( int i = 0; i < traversals; i ++ ) {
197
+
198
+ if ( bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
199
+
200
+ // TODO: attenuate the contribution based on the PDF of the resulting ray including refraction values
201
+ // Should be able to work using the material BSDF functions which will take into account specularity, etc.
202
+ // TODO: should we account for emissive surfaces here?
203
+
204
+ vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
205
+ vec4 vertexColor = textureSampleBarycoord( colorAttribute, barycoord, faceIndices.xyz );
206
+ uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
207
+ Material material = readMaterialInfo( materials, materialIndex );
208
+
209
+ // adjust the ray to the new surface
210
+ bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
211
+ vec3 point = rayOrigin + rayDirection * dist;
212
+ vec3 absPoint = abs( point );
213
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
214
+ rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
215
+
216
+ if ( ! material.castShadow && isShadowRay ) {
217
+
218
+ continue;
219
+
220
+ }
221
+
222
+ // Opacity Test
223
+
224
+ // albedo
225
+ vec4 albedo = vec4( material.color, material.opacity );
226
+ if ( material.map != - 1 ) {
227
+
228
+ vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
229
+ albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
230
+
231
+ }
232
+
233
+ if ( material.vertexColors ) {
234
+
235
+ albedo *= vertexColor;
236
+
237
+ }
238
+
239
+ // alphaMap
240
+ if ( material.alphaMap != - 1 ) {
241
+
242
+ albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
243
+
244
+ }
245
+
246
+ // transmission
247
+ float transmission = material.transmission;
248
+ if ( material.transmissionMap != - 1 ) {
249
+
250
+ vec3 uvPrime = material.transmissionMapTransform * vec3( uv, 1 );
251
+ transmission *= texture2D( textures, vec3( uvPrime.xy, material.transmissionMap ) ).r;
252
+
253
+ }
254
+
255
+ // metalness
256
+ float metalness = material.metalness;
257
+ if ( material.metalnessMap != - 1 ) {
258
+
259
+ vec3 uvPrime = material.metalnessMapTransform * vec3( uv, 1 );
260
+ metalness *= texture2D( textures, vec3( uvPrime.xy, material.metalnessMap ) ).b;
261
+
262
+ }
263
+
264
+ float alphaTest = material.alphaTest;
265
+ bool useAlphaTest = alphaTest != 0.0;
266
+ float transmissionFactor = ( 1.0 - metalness ) * transmission;
267
+ if (
268
+ transmissionFactor < rand() && ! (
269
+ // material sidedness
270
+ material.side != 0.0 && side == material.side
271
+
272
+ // alpha test
273
+ || useAlphaTest && albedo.a < alphaTest
274
+
275
+ // opacity
276
+ || material.transparent && ! useAlphaTest && albedo.a < rand()
277
+ )
278
+ ) {
279
+
280
+ return true;
281
+
282
+ }
283
+
284
+ if ( side == 1.0 && isBelowSurface ) {
285
+
286
+ // only attenuate by surface color on the way in
287
+ color *= mix( vec3( 1.0 ), albedo.rgb, transmissionFactor );
288
+
289
+ } else if ( side == - 1.0 ) {
290
+
291
+ // attenuate by medium once we hit the opposite side of the model
292
+ color *= transmissionAttenuation( dist, material.attenuationColor, material.attenuationDistance );
293
+
294
+ }
295
+
296
+ } else {
297
+
298
+ return false;
299
+
300
+ }
301
+
302
+ }
303
+
304
+ return true;
305
+
306
+ }
307
+
308
+ // returns whether the ray hit anything before a certain distance, not just the first surface. Could be optimized to not check the full hierarchy.
309
+ bool anyCloserHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, float maxDist ) {
310
+
311
+ uvec4 faceIndices = uvec4( 0u );
312
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
313
+ vec3 barycoord = vec3( 0.0 );
314
+ float side = 1.0;
315
+ float dist = 0.0;
316
+ bool hit = bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
317
+ return hit && dist < maxDist;
318
+
319
+ }
320
+
321
+ // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
322
+ // erichlof/THREE.js-PathTracing-Renderer/
323
+ float tentFilter( float x ) {
324
+
325
+ return x < 0.5 ? sqrt( 2.0 * x ) - 1.0 : 1.0 - sqrt( 2.0 - ( 2.0 * x ) );
326
+
327
+ }
328
+
329
+ vec3 ndcToRayOrigin( vec2 coord ) {
330
+
331
+ vec4 rayOrigin4 = cameraWorldMatrix * invProjectionMatrix * vec4( coord, - 1.0, 1.0 );
332
+ return rayOrigin4.xyz / rayOrigin4.w;
333
+ }
334
+
335
+ void getCameraRay( out vec3 rayDirection, out vec3 rayOrigin ) {
336
+
337
+ vec2 ssd = vec2( 1.0 ) / resolution;
338
+
339
+ // Jitter the camera ray by finding a uv coordinate at a random sample
340
+ // around this pixel's UV coordinate
341
+ vec2 jitteredUv = vUv + vec2( tentFilter( rand() ) * ssd.x, tentFilter( rand() ) * ssd.y );
342
+
343
+ #if CAMERA_TYPE == 2
344
+
345
+ // Equirectangular projection
346
+
347
+ vec4 rayDirection4 = vec4( equirectUvToDirection( jitteredUv ), 0.0 );
348
+ vec4 rayOrigin4 = vec4( 0.0, 0.0, 0.0, 1.0 );
349
+
350
+ rayDirection4 = cameraWorldMatrix * rayDirection4;
351
+ rayOrigin4 = cameraWorldMatrix * rayOrigin4;
352
+
353
+ rayDirection = normalize( rayDirection4.xyz );
354
+ rayOrigin = rayOrigin4.xyz / rayOrigin4.w;
355
+
356
+ #else
357
+
358
+ // get [- 1, 1] normalized device coordinates
359
+ vec2 ndc = 2.0 * jitteredUv - vec2( 1.0 );
360
+
361
+ rayOrigin = ndcToRayOrigin( ndc );
362
+
363
+ #if CAMERA_TYPE == 1
364
+
365
+ // Orthographic projection
366
+
367
+ rayDirection = ( cameraWorldMatrix * vec4( 0.0, 0.0, - 1.0, 0.0 ) ).xyz;
368
+ rayDirection = normalize( rayDirection );
369
+
370
+ #else
371
+
372
+ // Perspective projection
373
+
374
+ rayDirection = normalize( mat3(cameraWorldMatrix) * ( invProjectionMatrix * vec4( ndc, 0.0, 1.0 ) ).xyz );
375
+
376
+ #endif
377
+
378
+ #endif
379
+
380
+ #if FEATURE_DOF
381
+ {
382
+
383
+ // depth of field
384
+ vec3 focalPoint = rayOrigin + normalize( rayDirection ) * physicalCamera.focusDistance;
385
+
386
+ // get the aperture sample
387
+ vec2 apertureSample = sampleAperture( physicalCamera.apertureBlades ) * physicalCamera.bokehSize * 0.5 * 1e-3;
388
+
389
+ // rotate the aperture shape
390
+ float ac = cos( physicalCamera.apertureRotation );
391
+ float as = sin( physicalCamera.apertureRotation );
392
+ apertureSample = vec2(
393
+ apertureSample.x * ac - apertureSample.y * as,
394
+ apertureSample.x * as + apertureSample.y * ac
395
+ );
396
+ apertureSample.x *= saturate( physicalCamera.anamorphicRatio );
397
+ apertureSample.y *= saturate( 1.0 / physicalCamera.anamorphicRatio );
398
+
399
+ // create the new ray
400
+ rayOrigin += ( cameraWorldMatrix * vec4( apertureSample, 0.0, 0.0 ) ).xyz;
401
+ rayDirection = focalPoint - rayOrigin;
402
+
403
+ }
404
+ #endif
405
+
406
+ rayDirection = normalize( rayDirection );
407
+
408
+ }
409
+
410
+ void main() {
411
+
412
+ rng_initialize( gl_FragCoord.xy, seed );
413
+
414
+ vec3 rayDirection;
415
+ vec3 rayOrigin;
416
+
417
+ getCameraRay( rayDirection, rayOrigin );
418
+
419
+ // inverse environment rotation
420
+ mat3 invEnvironmentRotation = inverse( environmentRotation );
421
+
422
+ // final color
423
+ gl_FragColor = vec4( 0.0 );
424
+ gl_FragColor.a = 1.0;
425
+
426
+ // hit results
427
+ uvec4 faceIndices = uvec4( 0u );
428
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
429
+ vec3 barycoord = vec3( 0.0 );
430
+ float side = 1.0;
431
+ float dist = 0.0;
432
+
433
+ // path tracing state
434
+ float accumulatedRoughness = 0.0;
435
+ float accumulatedClearcoatRoughness = 0.0;
436
+ bool transmissiveRay = true;
437
+ int transparentTraversals = TRANSPARENT_TRAVERSALS;
438
+ vec3 throughputColor = vec3( 1.0 );
439
+ SampleRec sampleRec;
440
+ int i;
441
+ bool isShadowRay = false;
442
+
443
+ for ( i = 0; i < bounces; i ++ ) {
444
+
445
+ bool hit = bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
446
+
447
+ LightSampleRec lightHit = lightsClosestHit( lights.tex, lights.count, rayOrigin, rayDirection );
448
+
449
+ if ( lightHit.hit && ( lightHit.dist < dist || !hit ) ) {
450
+
451
+ if ( i == 0 || transmissiveRay ) {
452
+
453
+ gl_FragColor.rgb += lightHit.emission * throughputColor;
454
+
455
+ } else {
456
+
457
+ #if FEATURE_MIS
458
+
459
+ // NOTE: we skip MIS for spotlights since we haven't fixed the forward
460
+ // path tracing code path, yet
461
+ if ( lightHit.type == SPOT_LIGHT_TYPE ) {
462
+
463
+ gl_FragColor.rgb += lightHit.emission * throughputColor;
464
+
465
+ } else {
466
+
467
+ // weight the contribution
468
+ float misWeight = misHeuristic( sampleRec.pdf, lightHit.pdf / float( lights.count + 1u ) );
469
+ gl_FragColor.rgb += lightHit.emission * throughputColor * misWeight;
470
+
471
+ }
472
+
473
+ #else
474
+
475
+ gl_FragColor.rgb += lightHit.emission * throughputColor;
476
+
477
+ #endif
478
+
479
+ }
480
+ break;
481
+
482
+ }
483
+
484
+ if ( ! hit ) {
485
+
486
+ if ( i == 0 || transmissiveRay ) {
487
+
488
+ gl_FragColor.rgb += sampleBackground( environmentRotation * rayDirection ) * throughputColor;
489
+ gl_FragColor.a = backgroundAlpha;
490
+
491
+ } else {
492
+
493
+ #if FEATURE_MIS
494
+
495
+ // get the PDF of the hit envmap point
496
+ vec3 envColor;
497
+ float envPdf = envMapSample( environmentRotation * rayDirection, envMapInfo, envColor );
498
+ envPdf /= float( lights.count + 1u );
499
+
500
+ // and weight the contribution
501
+ float misWeight = misHeuristic( sampleRec.pdf, envPdf );
502
+ gl_FragColor.rgb += environmentIntensity * envColor * throughputColor * misWeight;
503
+
504
+ #else
505
+
506
+ gl_FragColor.rgb +=
507
+ environmentIntensity *
508
+ sampleEquirectEnvMapColor( environmentRotation * rayDirection, envMapInfo.map ) *
509
+ throughputColor;
510
+
511
+ #endif
512
+
513
+ }
514
+ break;
515
+
516
+ }
517
+
518
+ uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
519
+ Material material = readMaterialInfo( materials, materialIndex );
520
+
521
+ if ( material.matte && i == 0 ) {
522
+
523
+ gl_FragColor = vec4( 0.0 );
524
+ break;
525
+
526
+ }
527
+
528
+ // if we've determined that this is a shadow ray and we've hit an item with no shadow casting
529
+ // then skip it
530
+ if ( ! material.castShadow && isShadowRay ) {
531
+
532
+ vec3 point = rayOrigin + rayDirection * dist;
533
+ vec3 absPoint = abs( point );
534
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
535
+ rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
536
+
537
+ continue;
538
+
539
+ }
540
+
541
+ // uv coord for textures
542
+ vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
543
+ vec4 vertexColor = textureSampleBarycoord( colorAttribute, barycoord, faceIndices.xyz );
544
+
545
+ // albedo
546
+ vec4 albedo = vec4( material.color, material.opacity );
547
+ if ( material.map != - 1 ) {
548
+
549
+ vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
550
+ albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
551
+ }
552
+
553
+ if ( material.vertexColors ) {
554
+
555
+ albedo *= vertexColor;
556
+
557
+ }
558
+
559
+ // alphaMap
560
+ if ( material.alphaMap != - 1 ) {
561
+
562
+ albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
563
+
564
+ }
565
+
566
+ // possibly skip this sample if it's transparent, alpha test is enabled, or we hit the wrong material side
567
+ // and it's single sided.
568
+ // - alpha test is disabled when it === 0
569
+ // - the material sidedness test is complicated because we want light to pass through the back side but still
570
+ // be able to see the front side. This boolean checks if the side we hit is the front side on the first ray
571
+ // and we're rendering the other then we skip it. Do the opposite on subsequent bounces to get incoming light.
572
+ float alphaTest = material.alphaTest;
573
+ bool useAlphaTest = alphaTest != 0.0;
574
+ if (
575
+ // material sidedness
576
+ material.side != 0.0 && side != material.side
577
+
578
+ // alpha test
579
+ || useAlphaTest && albedo.a < alphaTest
580
+
581
+ // opacity
582
+ || material.transparent && ! useAlphaTest && albedo.a < rand()
583
+ ) {
584
+
585
+ vec3 point = rayOrigin + rayDirection * dist;
586
+ vec3 absPoint = abs( point );
587
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
588
+ rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
589
+
590
+ // only allow a limited number of transparency discards otherwise we could
591
+ // crash the context with too long a loop.
592
+ i -= sign( transparentTraversals );
593
+ transparentTraversals -= sign( transparentTraversals );
594
+ continue;
595
+
596
+ }
597
+
598
+ // fetch the interpolated smooth normal
599
+ vec3 normal = normalize( textureSampleBarycoord(
600
+ normalAttribute,
601
+ barycoord,
602
+ faceIndices.xyz
603
+ ).xyz );
604
+
605
+ // roughness
606
+ float roughness = material.roughness;
607
+ if ( material.roughnessMap != - 1 ) {
608
+
609
+ vec3 uvPrime = material.roughnessMapTransform * vec3( uv, 1 );
610
+ roughness *= texture2D( textures, vec3( uvPrime.xy, material.roughnessMap ) ).g;
611
+
612
+ }
613
+
614
+ // metalness
615
+ float metalness = material.metalness;
616
+ if ( material.metalnessMap != - 1 ) {
617
+
618
+ vec3 uvPrime = material.metalnessMapTransform * vec3( uv, 1 );
619
+ metalness *= texture2D( textures, vec3( uvPrime.xy, material.metalnessMap ) ).b;
620
+
621
+ }
622
+
623
+ // emission
624
+ vec3 emission = material.emissiveIntensity * material.emissive;
625
+ if ( material.emissiveMap != - 1 ) {
626
+
627
+ vec3 uvPrime = material.emissiveMapTransform * vec3( uv, 1 );
628
+ emission *= texture2D( textures, vec3( uvPrime.xy, material.emissiveMap ) ).xyz;
629
+
630
+ }
631
+
632
+ // transmission
633
+ float transmission = material.transmission;
634
+ if ( material.transmissionMap != - 1 ) {
635
+
636
+ vec3 uvPrime = material.transmissionMapTransform * vec3( uv, 1 );
637
+ transmission *= texture2D( textures, vec3( uvPrime.xy, material.transmissionMap ) ).r;
638
+
639
+ }
640
+
641
+ // normal
642
+ vec3 baseNormal = normal;
643
+ if ( material.normalMap != - 1 ) {
644
+
645
+ vec4 tangentSample = textureSampleBarycoord(
646
+ tangentAttribute,
647
+ barycoord,
648
+ faceIndices.xyz
649
+ );
650
+
651
+ // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
652
+ // resulting in NaNs and slow path tracing.
653
+ if ( length( tangentSample.xyz ) > 0.0 ) {
654
+
655
+ vec3 tangent = normalize( tangentSample.xyz );
656
+ vec3 bitangent = normalize( cross( normal, tangent ) * tangentSample.w );
657
+ mat3 vTBN = mat3( tangent, bitangent, normal );
658
+
659
+ vec3 uvPrime = material.normalMapTransform * vec3( uv, 1 );
660
+ vec3 texNormal = texture2D( textures, vec3( uvPrime.xy, material.normalMap ) ).xyz * 2.0 - 1.0;
661
+ texNormal.xy *= material.normalScale;
662
+ normal = vTBN * texNormal;
663
+
664
+ }
665
+
666
+ }
667
+
668
+ normal *= side;
669
+
670
+ // clearcoat
671
+ float clearcoat = material.clearcoat;
672
+ if ( material.clearcoatMap != - 1 ) {
673
+
674
+ vec3 uvPrime = material.clearcoatMapTransform * vec3( uv, 1 );
675
+ clearcoat *= texture2D( textures, vec3( uvPrime.xy, material.clearcoatMap ) ).r;
676
+
677
+ }
678
+
679
+ // clearcoatRoughness
680
+ float clearcoatRoughness = material.clearcoatRoughness;
681
+ if ( material.clearcoatRoughnessMap != - 1 ) {
682
+
683
+ vec3 uvPrime = material.clearcoatRoughnessMapTransform * vec3( uv, 1 );
684
+ clearcoat *= texture2D( textures, vec3( uvPrime.xy, material.clearcoatRoughnessMap ) ).g;
685
+
686
+ }
687
+
688
+ // clearcoatNormal
689
+ vec3 clearcoatNormal = baseNormal;
690
+ if ( material.clearcoatNormalMap != - 1 ) {
691
+
692
+ vec4 tangentSample = textureSampleBarycoord(
693
+ tangentAttribute,
694
+ barycoord,
695
+ faceIndices.xyz
696
+ );
697
+
698
+ // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
699
+ // resulting in NaNs and slow path tracing.
700
+ if ( length( tangentSample.xyz ) > 0.0 ) {
701
+
702
+ vec3 tangent = normalize( tangentSample.xyz );
703
+ vec3 bitangent = normalize( cross( clearcoatNormal, tangent ) * tangentSample.w );
704
+ mat3 vTBN = mat3( tangent, bitangent, clearcoatNormal );
705
+
706
+ vec3 uvPrime = material.clearcoatNormalMapTransform * vec3( uv, 1 );
707
+ vec3 texNormal = texture2D( textures, vec3( uvPrime.xy, material.clearcoatNormalMap ) ).xyz * 2.0 - 1.0;
708
+ texNormal.xy *= material.clearcoatNormalScale;
709
+ clearcoatNormal = vTBN * texNormal;
710
+
711
+ }
712
+
713
+ }
714
+
715
+ clearcoatNormal *= side;
716
+
717
+ // sheenColor
718
+ vec3 sheenColor = material.sheenColor;
719
+ if ( material.sheenColorMap != - 1 ) {
720
+
721
+ vec3 uvPrime = material.sheenColorMapTransform * vec3( uv, 1 );
722
+ sheenColor *= texture2D( textures, vec3( uvPrime.xy, material.sheenColorMap ) ).rgb;
723
+
724
+ }
725
+
726
+ // sheenRoughness
727
+ float sheenRoughness = material.sheenRoughness;
728
+ if ( material.sheenRoughnessMap != - 1 ) {
729
+
730
+ vec3 uvPrime = material.sheenRoughnessMapTransform * vec3( uv, 1 );
731
+ sheenRoughness *= texture2D( textures, vec3( uvPrime.xy, material.sheenRoughnessMap ) ).a;
732
+
733
+ }
734
+
735
+ // iridescence
736
+ float iridescence = material.iridescence;
737
+ if ( material.iridescenceMap != - 1 ) {
738
+
739
+ vec3 uvPrime = material.iridescenceMapTransform * vec3( uv, 1 );
740
+ iridescence *= texture2D( textures, vec3( uvPrime.xy, material.iridescenceMap ) ).r;
741
+
742
+ }
743
+
744
+ // iridescence thickness
745
+ float iridescenceThickness = material.iridescenceThicknessMaximum;
746
+ if ( material.iridescenceThicknessMap != - 1 ) {
747
+
748
+ vec3 uvPrime = material.iridescenceThicknessMapTransform * vec3( uv, 1 );
749
+ float iridescenceThicknessSampled = texture2D( textures, vec3( uvPrime.xy, material.iridescenceThicknessMap ) ).g;
750
+ iridescenceThickness = mix( material.iridescenceThicknessMinimum, material.iridescenceThicknessMaximum, iridescenceThicknessSampled );
751
+
752
+ }
753
+
754
+ iridescence = iridescenceThickness == 0.0 ? 0.0 : iridescence;
755
+
756
+ // specular color
757
+ vec3 specularColor = material.specularColor;
758
+ if ( material.specularColorMap != - 1 ) {
759
+
760
+ vec3 uvPrime = material.specularColorMapTransform * vec3( uv, 1 );
761
+ specularColor *= texture2D( textures, vec3( uvPrime.xy, material.specularColorMap ) ).rgb;
762
+
763
+ }
764
+
765
+ // specular intensity
766
+ float specularIntensity = material.specularIntensity;
767
+ if ( material.specularIntensityMap != - 1 ) {
768
+
769
+ vec3 uvPrime = material.specularIntensityMapTransform * vec3( uv, 1 );
770
+ specularIntensity *= texture2D( textures, vec3( uvPrime.xy, material.specularIntensityMap ) ).a;
771
+
772
+ }
773
+
774
+ SurfaceRec surfaceRec;
775
+ surfaceRec.normal = normal;
776
+ surfaceRec.faceNormal = faceNormal;
777
+ surfaceRec.transmission = transmission;
778
+ surfaceRec.ior = material.ior;
779
+ surfaceRec.emission = emission;
780
+ surfaceRec.metalness = metalness;
781
+ surfaceRec.color = albedo.rgb;
782
+ surfaceRec.clearcoat = clearcoat;
783
+ surfaceRec.sheenColor = sheenColor;
784
+ surfaceRec.iridescence = iridescence;
785
+ surfaceRec.iridescenceIor = material.iridescenceIor;
786
+ surfaceRec.iridescenceThickness = iridescenceThickness;
787
+ surfaceRec.specularColor = specularColor;
788
+ surfaceRec.specularIntensity = specularIntensity;
789
+ surfaceRec.attenuationColor = material.attenuationColor;
790
+ surfaceRec.attenuationDistance = material.attenuationDistance;
791
+
792
+ // apply perceptual roughness factor from gltf
793
+ // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#microfacet-surfaces
794
+ surfaceRec.roughness = roughness * roughness;
795
+ surfaceRec.clearcoatRoughness = clearcoatRoughness * clearcoatRoughness;
796
+ surfaceRec.sheenRoughness = sheenRoughness * sheenRoughness;
797
+
798
+ // frontFace is used to determine transmissive properties and PDF. If no transmission is used
799
+ // then we can just always assume this is a front face.
800
+ surfaceRec.frontFace = side == 1.0 || transmission == 0.0;
801
+ surfaceRec.iorRatio = material.thinFilm || surfaceRec.frontFace ? 1.0 / material.ior : material.ior;
802
+ surfaceRec.thinFilm = material.thinFilm;
803
+
804
+ // Compute the filtered roughness value to use during specular reflection computations.
805
+ // The accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
806
+ // If we're exiting something transmissive then scale the factor down significantly so we can retain
807
+ // sharp internal reflections
808
+ surfaceRec.filteredRoughness = applyFilteredGlossy( surfaceRec.roughness, accumulatedRoughness );
809
+ surfaceRec.filteredClearcoatRoughness = applyFilteredGlossy( surfaceRec.clearcoatRoughness, accumulatedClearcoatRoughness );
810
+
811
+ mat3 normalBasis = getBasisFromNormal( surfaceRec.normal );
812
+ mat3 invBasis = inverse( normalBasis );
813
+
814
+ mat3 clearcoatNormalBasis = getBasisFromNormal( clearcoatNormal );
815
+ mat3 clearcoatInvBasis = inverse( clearcoatNormalBasis );
816
+
817
+ vec3 outgoing = - normalize( invBasis * rayDirection );
818
+ vec3 clearcoatOutgoing = - normalize( clearcoatInvBasis * rayDirection );
819
+ sampleRec = bsdfSample( outgoing, clearcoatOutgoing, normalBasis, invBasis, clearcoatNormalBasis, clearcoatInvBasis, surfaceRec );
820
+
821
+ isShadowRay = sampleRec.specularPdf < rand();
822
+
823
+ // adjust the hit point by the surface normal by a factor of some offset and the
824
+ // maximum component-wise value of the current point to accommodate floating point
825
+ // error as values increase.
826
+ vec3 point = rayOrigin + rayDirection * dist;
827
+ vec3 absPoint = abs( point );
828
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
829
+ rayDirection = normalize( normalBasis * sampleRec.direction );
830
+
831
+ bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
832
+ rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
833
+
834
+ // direct env map sampling
835
+ #if FEATURE_MIS
836
+
837
+ // uniformly pick a light or environment map
838
+ if( rand() > 1.0 / float( lights.count + 1u ) ) {
839
+
840
+ // sample a light or environment
841
+ LightSampleRec lightSampleRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin );
842
+
843
+ bool isSampleBelowSurface = dot( faceNormal, lightSampleRec.direction ) < 0.0;
844
+ if ( isSampleBelowSurface ) {
845
+
846
+ lightSampleRec.pdf = 0.0;
847
+
848
+ }
849
+
850
+ // check if a ray could even reach the light area
851
+ if (
852
+ lightSampleRec.pdf > 0.0 &&
853
+ isDirectionValid( lightSampleRec.direction, normal, faceNormal ) &&
854
+ ! anyCloserHit( bvh, rayOrigin, lightSampleRec.direction, lightSampleRec.dist )
855
+ ) {
856
+
857
+ // get the material pdf
858
+ vec3 sampleColor;
859
+ float lightMaterialPdf = bsdfResult( outgoing, clearcoatOutgoing, normalize( invBasis * lightSampleRec.direction ), normalize( clearcoatInvBasis * lightSampleRec.direction ), surfaceRec, sampleColor );
860
+ bool isValidSampleColor = all( greaterThanEqual( sampleColor, vec3( 0.0 ) ) );
861
+ if ( lightMaterialPdf > 0.0 && isValidSampleColor ) {
862
+
863
+ // weight the direct light contribution
864
+ float lightPdf = lightSampleRec.pdf / float( lights.count + 1u );
865
+ float misWeight = misHeuristic( lightPdf, lightMaterialPdf );
866
+ gl_FragColor.rgb += lightSampleRec.emission * throughputColor * sampleColor * misWeight / lightPdf;
867
+
868
+ }
869
+
870
+ }
871
+
872
+ } else {
873
+
874
+ // find a sample in the environment map to include in the contribution
875
+ vec3 envColor, envDirection;
876
+ float envPdf = randomEnvMapSample( envMapInfo, envColor, envDirection );
877
+ envDirection = invEnvironmentRotation * envDirection;
878
+
879
+ // this env sampling is not set up for transmissive sampling and yields overly bright
880
+ // results so we ignore the sample in this case.
881
+ // TODO: this should be improved but how? The env samples could traverse a few layers?
882
+ bool isSampleBelowSurface = dot( faceNormal, envDirection ) < 0.0;
883
+ if ( isSampleBelowSurface ) {
884
+
885
+ envPdf = 0.0;
886
+
887
+ }
888
+
889
+ // check if a ray could even reach the surface
890
+ vec3 attenuatedColor;
891
+ if (
892
+ envPdf > 0.0 &&
893
+ isDirectionValid( envDirection, normal, faceNormal ) &&
894
+ ! attenuateHit( bvh, rayOrigin, envDirection, bounces - i, isShadowRay, attenuatedColor )
895
+ ) {
896
+
897
+ // get the material pdf
898
+ vec3 sampleColor;
899
+ float envMaterialPdf = bsdfResult( outgoing, clearcoatOutgoing, normalize( invBasis * envDirection ), normalize( clearcoatInvBasis * envDirection ), surfaceRec, sampleColor );
900
+ bool isValidSampleColor = all( greaterThanEqual( sampleColor, vec3( 0.0 ) ) );
901
+ if ( envMaterialPdf > 0.0 && isValidSampleColor ) {
902
+
903
+ // weight the direct light contribution
904
+ envPdf /= float( lights.count + 1u );
905
+ float misWeight = misHeuristic( envPdf, envMaterialPdf );
906
+ gl_FragColor.rgb += attenuatedColor * environmentIntensity * envColor * throughputColor * sampleColor * misWeight / envPdf;
907
+
908
+ }
909
+
910
+ }
911
+
912
+ }
913
+ #endif
914
+
915
+ // accumulate a roughness value to offset diffuse, specular, diffuse rays that have high contribution
916
+ // to a single pixel resulting in fireflies
917
+ if ( ! isBelowSurface ) {
918
+
919
+ // determine if this is a rough normal or not by checking how far off straight up it is
920
+ vec3 halfVector = normalize( outgoing + sampleRec.direction );
921
+ accumulatedRoughness += sin( acosApprox( halfVector.z ) );
922
+
923
+ vec3 clearcoatHalfVector = normalize( clearcoatOutgoing + sampleRec.clearcoatDirection );
924
+ accumulatedClearcoatRoughness += sin( acosApprox( clearcoatHalfVector.z ) );
925
+
926
+ transmissiveRay = false;
927
+
928
+ }
929
+
930
+ // accumulate color
931
+ gl_FragColor.rgb += ( emission * throughputColor );
932
+
933
+ // skip the sample if our PDF or ray is impossible
934
+ if ( sampleRec.pdf <= 0.0 || ! isDirectionValid( rayDirection, normal, faceNormal) ) {
935
+
936
+ break;
937
+
938
+ }
939
+
940
+ throughputColor *= sampleRec.color / sampleRec.pdf;
941
+
942
+ // attenuate the throughput color by the medium color
943
+ if ( side == - 1.0 ) {
944
+
945
+ throughputColor *= transmissionAttenuation( dist, surfaceRec.attenuationColor, surfaceRec.attenuationDistance );
946
+
947
+ }
948
+
949
+ // discard the sample if there are any NaNs
950
+ if ( any( isnan( throughputColor ) ) || any( isinf( throughputColor ) ) ) {
951
+
952
+ break;
953
+
954
+ }
955
+
956
+ }
957
+
958
+ gl_FragColor.a *= opacity;
959
+
960
+ }
961
+
962
+ `
963
+
964
+ } );
965
+
966
+ this.setValues( parameters );
967
+
968
+ }
969
+
970
+ }