three-gpu-pathtracer 0.0.3 → 0.0.6

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 (32) hide show
  1. package/README.md +136 -15
  2. package/build/index.module.js +2706 -529
  3. package/build/index.module.js.map +1 -1
  4. package/build/index.umd.cjs +2713 -529
  5. package/build/index.umd.cjs.map +1 -1
  6. package/package.json +68 -60
  7. package/src/core/DynamicPathTracingSceneGenerator.js +24 -11
  8. package/src/core/PathTracingRenderer.js +22 -0
  9. package/src/core/PathTracingSceneGenerator.js +36 -20
  10. package/src/index.js +9 -1
  11. package/src/materials/PhysicalPathTracingMaterial.js +350 -60
  12. package/src/objects/EquirectCamera.js +13 -0
  13. package/src/{core → objects}/PhysicalCamera.js +0 -0
  14. package/src/objects/PhysicalSpotLight.js +14 -0
  15. package/src/objects/ShapedAreaLight.js +12 -0
  16. package/src/shader/shaderEnvMapSampling.js +59 -67
  17. package/src/shader/shaderGGXFunctions.js +3 -2
  18. package/src/shader/shaderIridescenceFunctions.js +130 -0
  19. package/src/shader/shaderLightSampling.js +231 -0
  20. package/src/shader/shaderMaterialSampling.js +259 -53
  21. package/src/shader/shaderSheenFunctions.js +98 -0
  22. package/src/shader/shaderStructs.js +307 -92
  23. package/src/shader/shaderUtils.js +122 -0
  24. package/src/uniforms/EquirectHdrInfoUniform.js +10 -14
  25. package/src/uniforms/IESProfilesTexture.js +100 -0
  26. package/src/uniforms/LightsInfoUniformStruct.js +162 -0
  27. package/src/uniforms/MaterialsTexture.js +266 -33
  28. package/src/uniforms/PhysicalCameraUniform.js +1 -1
  29. package/src/uniforms/RenderTarget2DArray.js +93 -80
  30. package/src/utils/GeometryPreparationUtils.js +1 -1
  31. package/src/utils/IESLoader.js +325 -0
  32. package/src/workers/PathTracingSceneWorker.js +3 -1
@@ -146,6 +146,7 @@
146
146
 
147
147
  blendMaterial.opacity = 1 / ( this.samples + 1 );
148
148
  material.blending = three.NoBlending;
149
+ material.opacity = 1;
149
150
 
150
151
  } else {
151
152
 
@@ -170,6 +171,27 @@
170
171
  material.cameraWorldMatrix.copy( camera.matrixWorld );
171
172
  material.invProjectionMatrix.copy( camera.projectionMatrixInverse );
172
173
 
174
+ // Perspective camera (default)
175
+ let cameraType = 0;
176
+
177
+ // An orthographic projection matrix will always have the bottom right element == 1
178
+ // And a perspective projection matrix will always have the bottom right element == 0
179
+ if ( camera.projectionMatrix.elements[ 15 ] > 0 ) {
180
+
181
+ // Orthographic
182
+ cameraType = 1;
183
+
184
+ }
185
+
186
+ if ( camera.isEquirectCamera ) {
187
+
188
+ // Equirectangular
189
+ cameraType = 2;
190
+
191
+ }
192
+
193
+ material.setDefine( 'CAMERA_TYPE', cameraType );
194
+
173
195
  const ogRenderTarget = _renderer.getRenderTarget();
174
196
  const ogAutoClear = _renderer.autoClear;
175
197
 
@@ -507,7 +529,7 @@
507
529
 
508
530
  // apply the matrix world to the geometry
509
531
  const originalGeometry = meshes[ i ].geometry;
510
- let geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
532
+ const geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
511
533
  geometry.applyMatrix4( mesh.matrixWorld );
512
534
 
513
535
  // ensure our geometry has common attributes
@@ -551,44 +573,60 @@
551
573
 
552
574
  prepScene( scene ) {
553
575
 
576
+ scene = Array.isArray( scene ) ? scene : [ scene ];
577
+
554
578
  const meshes = [];
555
- scene.traverse( c => {
579
+ const lights = [];
580
+
581
+ for ( let i = 0, l = scene.length; i < l; i ++ ) {
556
582
 
557
- if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
583
+ scene[ i ].traverse( c => {
558
584
 
559
- const generator = new threeMeshBvh.StaticGeometryGenerator( c );
560
- generator.applyWorldTransforms = false;
561
- const mesh = new three.Mesh(
562
- generator.generate(),
563
- c.material,
564
- );
565
- mesh.matrixWorld.copy( c.matrixWorld );
566
- mesh.matrix.copy( c.matrixWorld );
567
- mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
568
- meshes.push( mesh );
585
+ if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
569
586
 
570
- } else if ( c.isMesh ) {
587
+ const generator = new threeMeshBvh.StaticGeometryGenerator( c );
588
+ generator.applyWorldTransforms = false;
589
+ const mesh = new three.Mesh(
590
+ generator.generate(),
591
+ c.material,
592
+ );
593
+ mesh.matrixWorld.copy( c.matrixWorld );
594
+ mesh.matrix.copy( c.matrixWorld );
595
+ mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
596
+ meshes.push( mesh );
571
597
 
572
- meshes.push( c );
598
+ } else if ( c.isMesh ) {
573
599
 
574
- }
600
+ meshes.push( c );
575
601
 
576
- } );
602
+ } else if ( c.isRectAreaLight || c.isSpotLight ) {
577
603
 
578
- return mergeMeshes( meshes, {
579
- attributes: [ 'position', 'normal', 'tangent', 'uv' ],
580
- } );
604
+ lights.push( c );
605
+
606
+ }
607
+
608
+ } );
609
+
610
+ }
611
+
612
+ return {
613
+ ...mergeMeshes( meshes, {
614
+ attributes: [ 'position', 'normal', 'tangent', 'uv' ],
615
+ } ),
616
+ lights,
617
+ };
581
618
 
582
619
  }
583
620
 
584
621
  generate( scene, options = {} ) {
585
622
 
586
- const { materials, textures, geometry } = this.prepScene( scene );
623
+ const { materials, textures, geometry, lights } = this.prepScene( scene );
587
624
  const bvhOptions = { strategy: threeMeshBvh.SAH, ...options, maxLeafTris: 1 };
588
625
  return {
589
626
  scene,
590
627
  materials,
591
628
  textures,
629
+ lights,
592
630
  bvh: new threeMeshBvh.MeshBVH( geometry, bvhOptions ),
593
631
  };
594
632
 
@@ -606,11 +644,12 @@
606
644
 
607
645
  constructor( scene ) {
608
646
 
609
- this.scene = scene;
647
+ this.objects = Array.isArray( scene ) ? scene : [ scene ];
610
648
  this.bvh = null;
611
649
  this.geometry = new three.BufferGeometry();
612
650
  this.materials = null;
613
651
  this.textures = null;
652
+ this.lights = [];
614
653
  this.staticGeometryGenerator = new threeMeshBvh.StaticGeometryGenerator( scene );
615
654
 
616
655
  }
@@ -622,7 +661,8 @@
622
661
  this.geometry = new three.BufferGeometry();
623
662
  this.materials = null;
624
663
  this.textures = null;
625
- this.staticGeometryGenerator = new threeMeshBvh.StaticGeometryGenerator( this.scene );
664
+ this.lights = [];
665
+ this.staticGeometryGenerator = new threeMeshBvh.StaticGeometryGenerator( this.objects );
626
666
 
627
667
  }
628
668
 
@@ -630,20 +670,29 @@
630
670
 
631
671
  generate() {
632
672
 
633
- const { scene, staticGeometryGenerator, geometry } = this;
673
+ const { objects, staticGeometryGenerator, geometry } = this;
634
674
  if ( this.bvh === null ) {
635
675
 
636
676
  const attributes = [ 'position', 'normal', 'tangent', 'uv' ];
637
- scene.traverse( c => {
638
677
 
639
- if ( c.isMesh ) {
678
+ for ( let i = 0, l = objects.length; i < l; i ++ ) {
640
679
 
641
- const normalMapRequired = ! ! c.material.normalMap;
642
- setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
680
+ objects[ i ].traverse( c => {
643
681
 
644
- }
682
+ if ( c.isMesh ) {
645
683
 
646
- } );
684
+ const normalMapRequired = ! ! c.material.normalMap;
685
+ setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
686
+
687
+ } else if ( c.isRectAreaLight || c.isSpotLight ) {
688
+
689
+ this.lights.push( c );
690
+
691
+ }
692
+
693
+ } );
694
+
695
+ }
647
696
 
648
697
  const textureSet = new Set();
649
698
  const materials = staticGeometryGenerator.getMaterials();
@@ -674,10 +723,11 @@
674
723
  this.textures = Array.from( textureSet );
675
724
 
676
725
  return {
726
+ lights: this.lights,
677
727
  bvh: this.bvh,
678
728
  materials: this.materials,
679
729
  textures: this.textures,
680
- scene,
730
+ objects,
681
731
  };
682
732
 
683
733
  } else {
@@ -686,10 +736,11 @@
686
736
  staticGeometryGenerator.generate( geometry );
687
737
  bvh.refit();
688
738
  return {
739
+ lights: this.lights,
689
740
  bvh: this.bvh,
690
741
  materials: this.materials,
691
742
  textures: this.textures,
692
- scene,
743
+ objects,
693
744
  };
694
745
 
695
746
  }
@@ -983,8 +1034,48 @@
983
1034
 
984
1035
  }
985
1036
 
986
- const MATERIAL_PIXELS = 6;
987
- const MATERIAL_STRIDE = 6 * 4;
1037
+ class EquirectCamera extends three.Camera {
1038
+
1039
+ constructor() {
1040
+
1041
+ super();
1042
+
1043
+ this.isEquirectCamera = true;
1044
+
1045
+ }
1046
+
1047
+ }
1048
+
1049
+ class PhysicalSpotLight extends three.SpotLight {
1050
+
1051
+ constructor( ...args ) {
1052
+
1053
+ super( ...args );
1054
+
1055
+ this.iesTexture = null;
1056
+ this.radius = 0;
1057
+
1058
+ }
1059
+
1060
+ }
1061
+
1062
+ class ShapedAreaLight extends three.RectAreaLight {
1063
+
1064
+ constructor( ...args ) {
1065
+
1066
+ super( ...args );
1067
+ this.isCircular = false;
1068
+
1069
+ }
1070
+
1071
+ }
1072
+
1073
+ const MATERIAL_PIXELS = 44;
1074
+ const MATERIAL_STRIDE = MATERIAL_PIXELS * 4;
1075
+
1076
+ const SIDE_OFFSET = 12 * 4 + 3; // s12.a
1077
+ const MATTE_OFFSET = 13 * 4 + 0; // s13.r
1078
+ const SHADOW_OFFSET = 13 * 4 + 1; // s13.g
988
1079
 
989
1080
  class MaterialsTexture extends three.DataTexture {
990
1081
 
@@ -997,24 +1088,42 @@
997
1088
  this.wrapS = three.ClampToEdgeWrapping;
998
1089
  this.wrapT = three.ClampToEdgeWrapping;
999
1090
  this.generateMipmaps = false;
1091
+ this.threeCompatibilityTransforms = false;
1092
+
1093
+ }
1094
+
1095
+ setCastShadow( materialIndex, cast ) {
1096
+
1097
+ // invert the shadow value so we default to "true" when initializing a material
1098
+ const array = this.image.data;
1099
+ const index = materialIndex * MATERIAL_STRIDE + SHADOW_OFFSET;
1100
+ array[ index ] = ! cast ? 1 : 0;
1101
+
1102
+ }
1103
+
1104
+ getCastShadow( materialIndex ) {
1105
+
1106
+ const array = this.image.data;
1107
+ const index = materialIndex * MATERIAL_STRIDE + SHADOW_OFFSET;
1108
+ return ! Boolean( array[ index ] );
1000
1109
 
1001
1110
  }
1002
1111
 
1003
1112
  setSide( materialIndex, side ) {
1004
1113
 
1005
1114
  const array = this.image.data;
1006
- const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 2;
1115
+ const index = materialIndex * MATERIAL_STRIDE + SIDE_OFFSET;
1007
1116
  switch ( side ) {
1008
1117
 
1009
- case three.FrontSide:
1010
- array[ index ] = 1;
1011
- break;
1012
- case three.BackSide:
1013
- array[ index ] = - 1;
1014
- break;
1015
- case three.DoubleSide:
1016
- array[ index ] = 0;
1017
- break;
1118
+ case three.FrontSide:
1119
+ array[ index ] = 1;
1120
+ break;
1121
+ case three.BackSide:
1122
+ array[ index ] = - 1;
1123
+ break;
1124
+ case three.DoubleSide:
1125
+ array[ index ] = 0;
1126
+ break;
1018
1127
 
1019
1128
  }
1020
1129
 
@@ -1023,15 +1132,15 @@
1023
1132
  getSide( materialIndex ) {
1024
1133
 
1025
1134
  const array = this.image.data;
1026
- const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 2;
1135
+ const index = materialIndex * MATERIAL_STRIDE + SIDE_OFFSET;
1027
1136
  switch ( array[ index ] ) {
1028
1137
 
1029
- case 0:
1030
- return three.DoubleSide;
1031
- case 1:
1032
- return three.FrontSide;
1033
- case - 1:
1034
- return three.BackSide;
1138
+ case 0:
1139
+ return three.DoubleSide;
1140
+ case 1:
1141
+ return three.FrontSide;
1142
+ case - 1:
1143
+ return three.BackSide;
1035
1144
 
1036
1145
  }
1037
1146
 
@@ -1042,7 +1151,7 @@
1042
1151
  setMatte( materialIndex, matte ) {
1043
1152
 
1044
1153
  const array = this.image.data;
1045
- const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 3;
1154
+ const index = materialIndex * MATERIAL_STRIDE + MATTE_OFFSET;
1046
1155
  array[ index ] = matte ? 1 : 0;
1047
1156
 
1048
1157
  }
@@ -1050,7 +1159,7 @@
1050
1159
  getMatte( materialIndex ) {
1051
1160
 
1052
1161
  const array = this.image.data;
1053
- const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 3;
1162
+ const index = materialIndex * MATERIAL_STRIDE + MATTE_OFFSET;
1054
1163
  return Boolean( array[ index ] );
1055
1164
 
1056
1165
  }
@@ -1069,45 +1178,120 @@
1069
1178
 
1070
1179
  }
1071
1180
 
1181
+ function getUVTransformTexture( material ) {
1182
+
1183
+ // https://github.com/mrdoob/three.js/blob/f3a832e637c98a404c64dae8174625958455e038/src/renderers/webgl/WebGLMaterials.js#L204-L306
1184
+ // https://threejs.org/docs/#api/en/textures/Texture.offset
1185
+ // fallback order of textures to use as a common uv transform
1186
+ return material.map ||
1187
+ material.specularMap ||
1188
+ material.displacementMap ||
1189
+ material.normalMap ||
1190
+ material.bumpMap ||
1191
+ material.roughnessMap ||
1192
+ material.metalnessMap ||
1193
+ material.alphaMap ||
1194
+ material.emissiveMap ||
1195
+ material.clearcoatMap ||
1196
+ material.clearcoatNormalMap ||
1197
+ material.clearcoatRoughnessMap ||
1198
+ material.iridescenceMap ||
1199
+ material.iridescenceThicknessMap ||
1200
+ material.specularIntensityMap ||
1201
+ material.specularColorMap ||
1202
+ material.transmissionMap ||
1203
+ material.thicknessMap ||
1204
+ material.sheenColorMap ||
1205
+ material.sheenRoughnessMap ||
1206
+ null;
1207
+
1208
+ }
1209
+
1210
+ function writeTextureMatrixToArray( material, textureKey, array, offset ) {
1211
+
1212
+ let texture;
1213
+ if ( threeCompatibilityTransforms ) {
1214
+
1215
+ texture = getUVTransformTexture( material );
1216
+
1217
+ } else {
1218
+
1219
+ texture = material[ textureKey ] && material[ textureKey ].isTexture ? material[ textureKey ] : null;
1220
+
1221
+ }
1222
+
1223
+ // check if texture exists
1224
+ if ( texture ) {
1225
+
1226
+ const elements = texture.matrix.elements;
1227
+
1228
+ let i = 0;
1229
+
1230
+ // first row
1231
+ array[ offset + i ++ ] = elements[ 0 ];
1232
+ array[ offset + i ++ ] = elements[ 3 ];
1233
+ array[ offset + i ++ ] = elements[ 6 ];
1234
+ i ++;
1235
+
1236
+ // second row
1237
+ array[ offset + i ++ ] = elements[ 1 ];
1238
+ array[ offset + i ++ ] = elements[ 4 ];
1239
+ array[ offset + i ++ ] = elements[ 7 ];
1240
+ i ++;
1241
+
1242
+ }
1243
+
1244
+ return 8;
1245
+
1246
+ }
1247
+
1072
1248
  let index = 0;
1073
1249
  const pixelCount = materials.length * MATERIAL_PIXELS;
1074
1250
  const dimension = Math.ceil( Math.sqrt( pixelCount ) );
1251
+ const { threeCompatibilityTransforms, image } = this;
1075
1252
 
1076
- if ( this.image.width !== dimension ) {
1253
+ if ( image.width !== dimension ) {
1077
1254
 
1078
1255
  this.dispose();
1079
1256
 
1080
- this.image.data = new Float32Array( dimension * dimension * 4 );
1081
- this.image.width = dimension;
1082
- this.image.height = dimension;
1257
+ image.data = new Float32Array( dimension * dimension * 4 );
1258
+ image.width = dimension;
1259
+ image.height = dimension;
1083
1260
 
1084
1261
  }
1085
1262
 
1086
- const floatArray = this.image.data;
1087
- const intArray = new Int32Array( floatArray.buffer );
1263
+ const floatArray = image.data;
1264
+
1265
+ // on some devices (Google Pixel 6) the "floatBitsToInt" function does not work correctly so we
1266
+ // can't encode texture ids that way.
1267
+ // const intArray = new Int32Array( floatArray.buffer );
1088
1268
 
1089
1269
  for ( let i = 0, l = materials.length; i < l; i ++ ) {
1090
1270
 
1091
1271
  const m = materials[ i ];
1092
1272
 
1273
+ // sample 0
1093
1274
  // color
1094
1275
  floatArray[ index ++ ] = m.color.r;
1095
1276
  floatArray[ index ++ ] = m.color.g;
1096
1277
  floatArray[ index ++ ] = m.color.b;
1097
- intArray[ index ++ ] = getTexture( m, 'map' );
1278
+ floatArray[ index ++ ] = getTexture( m, 'map' );
1098
1279
 
1280
+ // sample 1
1099
1281
  // metalness & roughness
1100
1282
  floatArray[ index ++ ] = getField( m, 'metalness', 0.0 );
1101
- intArray[ index ++ ] = textures.indexOf( m.metalnessMap );
1283
+ floatArray[ index ++ ] = textures.indexOf( m.metalnessMap );
1102
1284
  floatArray[ index ++ ] = getField( m, 'roughness', 0.0 );
1103
- intArray[ index ++ ] = textures.indexOf( m.roughnessMap );
1285
+ floatArray[ index ++ ] = textures.indexOf( m.roughnessMap );
1104
1286
 
1287
+ // sample 2
1105
1288
  // transmission & emissiveIntensity
1106
1289
  floatArray[ index ++ ] = getField( m, 'ior', 1.0 );
1107
1290
  floatArray[ index ++ ] = getField( m, 'transmission', 0.0 );
1108
- intArray[ index ++ ] = getTexture( m, 'transmissionMap' );
1291
+ floatArray[ index ++ ] = getTexture( m, 'transmissionMap' );
1109
1292
  floatArray[ index ++ ] = getField( m, 'emissiveIntensity', 0.0 );
1110
1293
 
1294
+ // sample 3
1111
1295
  // emission
1112
1296
  if ( 'emissive' in m ) {
1113
1297
 
@@ -1123,10 +1307,11 @@
1123
1307
 
1124
1308
  }
1125
1309
 
1126
- intArray[ index ++ ] = getTexture( m, 'emissiveMap' );
1310
+ floatArray[ index ++ ] = getTexture( m, 'emissiveMap' );
1127
1311
 
1312
+ // sample 4
1128
1313
  // normals
1129
- intArray[ index ++ ] = getTexture( m, 'normalMap' );
1314
+ floatArray[ index ++ ] = getTexture( m, 'normalMap' );
1130
1315
  if ( 'normalScale' in m ) {
1131
1316
 
1132
1317
  floatArray[ index ++ ] = m.normalScale.x;
@@ -1139,13 +1324,148 @@
1139
1324
 
1140
1325
  }
1141
1326
 
1327
+ // clearcoat
1328
+ floatArray[ index ++ ] = getField( m, 'clearcoat', 0.0 );
1329
+ floatArray[ index ++ ] = getTexture( m, 'clearcoatMap' ); // sample 5
1330
+
1331
+ floatArray[ index ++ ] = getField( m, 'clearcoatRoughness', 0.0 );
1332
+ floatArray[ index ++ ] = getTexture( m, 'clearcoatRoughnessMap' );
1333
+
1334
+ floatArray[ index ++ ] = getTexture( m, 'clearcoatNormalMap' );
1335
+
1336
+ // sample 6
1337
+ if ( 'clearcoatNormalScale' in m ) {
1338
+
1339
+ floatArray[ index ++ ] = m.clearcoatNormalScale.x;
1340
+ floatArray[ index ++ ] = m.clearcoatNormalScale.y;
1341
+
1342
+ } else {
1343
+
1344
+ floatArray[ index ++ ] = 1;
1345
+ floatArray[ index ++ ] = 1;
1346
+
1347
+ }
1348
+
1349
+ index ++;
1350
+ index ++;
1351
+
1352
+ // sample 7
1353
+ // sheen
1354
+ if ( 'sheenColor' in m ) {
1355
+
1356
+ floatArray[ index ++ ] = m.sheenColor.r;
1357
+ floatArray[ index ++ ] = m.sheenColor.g;
1358
+ floatArray[ index ++ ] = m.sheenColor.b;
1359
+
1360
+ } else {
1361
+
1362
+ floatArray[ index ++ ] = 0.0;
1363
+ floatArray[ index ++ ] = 0.0;
1364
+ floatArray[ index ++ ] = 0.0;
1365
+
1366
+ }
1367
+
1368
+ floatArray[ index ++ ] = getTexture( m, 'sheenColorMap' );
1369
+
1370
+ // sample 8
1371
+ floatArray[ index ++ ] = getField( m, 'sheenRoughness', 0.0 );
1372
+ floatArray[ index ++ ] = getTexture( m, 'sheenRoughnessMap' );
1373
+
1374
+ // iridescence
1375
+ floatArray[ index ++ ] = getTexture( m, 'iridescenceMap' );
1376
+ floatArray[ index ++ ] = getTexture( m, 'iridescenceThicknessMap' );
1377
+
1378
+ floatArray[ index ++ ] = getField( m, 'iridescence', 0.0 ); // sample 9
1379
+ floatArray[ index ++ ] = getField( m, 'iridescenceIOR', 1.3 );
1380
+
1381
+ const iridescenceThicknessRange = getField( m, 'iridescenceThicknessRange', [ 100, 400 ] );
1382
+ floatArray[ index ++ ] = iridescenceThicknessRange[ 0 ];
1383
+ floatArray[ index ++ ] = iridescenceThicknessRange[ 1 ];
1384
+
1385
+ // sample 10
1386
+ // specular color
1387
+ if ( 'specularColor' in m ) {
1388
+
1389
+ floatArray[ index ++ ] = m.specularColor.r;
1390
+ floatArray[ index ++ ] = m.specularColor.g;
1391
+ floatArray[ index ++ ] = m.specularColor.b;
1392
+
1393
+ } else {
1394
+
1395
+ floatArray[ index ++ ] = 0.0;
1396
+ floatArray[ index ++ ] = 0.0;
1397
+ floatArray[ index ++ ] = 0.0;
1398
+
1399
+ }
1400
+
1401
+ floatArray[ index ++ ] = getTexture( m, 'specularColorMap' );
1402
+
1403
+ // sample 11
1404
+ // specular intensity
1405
+ floatArray[ index ++ ] = getField( m, 'specularIntensity', 1.0 );
1406
+ floatArray[ index ++ ] = getTexture( m, 'specularIntensityMap' );
1142
1407
  index ++;
1408
+ index ++;
1409
+
1410
+ // sample 12
1411
+ // alphaMap
1412
+ floatArray[ index ++ ] = getTexture( m, 'alphaMap' );
1143
1413
 
1144
1414
  // side & matte
1145
1415
  floatArray[ index ++ ] = m.opacity;
1146
1416
  floatArray[ index ++ ] = m.alphaTest;
1147
1417
  index ++; // side
1418
+
1419
+ // sample 13
1148
1420
  index ++; // matte
1421
+ index ++; // shadow
1422
+ index ++;
1423
+ index ++;
1424
+
1425
+ // map transform 14
1426
+ index += writeTextureMatrixToArray( m, 'map', floatArray, index );
1427
+
1428
+ // metalnessMap transform 16
1429
+ index += writeTextureMatrixToArray( m, 'metalnessMap', floatArray, index );
1430
+
1431
+ // roughnessMap transform 18
1432
+ index += writeTextureMatrixToArray( m, 'roughnessMap', floatArray, index );
1433
+
1434
+ // transmissionMap transform 20
1435
+ index += writeTextureMatrixToArray( m, 'transmissionMap', floatArray, index );
1436
+
1437
+ // emissiveMap transform 22
1438
+ index += writeTextureMatrixToArray( m, 'emissiveMap', floatArray, index );
1439
+
1440
+ // normalMap transform 24
1441
+ index += writeTextureMatrixToArray( m, 'normalMap', floatArray, index );
1442
+
1443
+ // clearcoatMap transform 26
1444
+ index += writeTextureMatrixToArray( m, 'clearcoatMap', floatArray, index );
1445
+
1446
+ // clearcoatNormalMap transform 28
1447
+ index += writeTextureMatrixToArray( m, 'clearcoatNormalMap', floatArray, index );
1448
+
1449
+ // clearcoatRoughnessMap transform 30
1450
+ index += writeTextureMatrixToArray( m, 'clearcoatRoughnessMap', floatArray, index );
1451
+
1452
+ // sheenColorMap transform 32
1453
+ index += writeTextureMatrixToArray( m, 'sheenColorMap', floatArray, index );
1454
+
1455
+ // sheenRoughnessMap transform 34
1456
+ index += writeTextureMatrixToArray( m, 'sheenRoughnessMap', floatArray, index );
1457
+
1458
+ // iridescenceMap transform 36
1459
+ index += writeTextureMatrixToArray( m, 'iridescenceMap', floatArray, index );
1460
+
1461
+ // iridescenceThicknessMap transform 38
1462
+ index += writeTextureMatrixToArray( m, 'iridescenceThicknessMap', floatArray, index );
1463
+
1464
+ // specularColorMap transform 40
1465
+ index += writeTextureMatrixToArray( m, 'specularColorMap', floatArray, index );
1466
+
1467
+ // specularIntensityMap transform 42
1468
+ index += writeTextureMatrixToArray( m, 'specularIntensityMap', floatArray, index );
1149
1469
 
1150
1470
  }
1151
1471
 
@@ -1155,73 +1475,86 @@
1155
1475
 
1156
1476
  }
1157
1477
 
1158
- const prevColor = new three.Color();
1159
- class RenderTarget2DArray extends three.WebGLArrayRenderTarget {
1160
-
1161
- constructor( ...args ) {
1162
-
1163
- super( ...args );
1164
-
1165
- const tex = this.texture;
1166
- tex.format = three.RGBAFormat;
1167
- tex.type = three.UnsignedByteType;
1168
- tex.minFilter = three.LinearFilter;
1169
- tex.magFilter = three.LinearFilter;
1170
- tex.wrapS = three.RepeatWrapping;
1171
- tex.wrapT = three.RepeatWrapping;
1172
- tex.setTextures = ( ...args ) => {
1173
-
1174
- this.setTextures( ...args );
1175
-
1176
- };
1177
-
1178
- const fsQuad = new Pass_js.FullScreenQuad( new three.MeshBasicMaterial() );
1179
- this.fsQuad = fsQuad;
1180
-
1181
- }
1182
-
1183
- setTextures( renderer, width, height, textures ) {
1184
-
1185
- // save previous renderer state
1186
- const prevRenderTarget = renderer.getRenderTarget();
1187
- const prevToneMapping = renderer.toneMapping;
1188
- const prevAlpha = renderer.getClearAlpha();
1189
- renderer.getClearColor( prevColor );
1190
-
1191
- // resize the render target and ensure we don't have an empty texture
1192
- const depth = textures.length || 1;
1193
- this.setSize( width, height, depth );
1194
- renderer.setClearColor( 0, 0 );
1195
- renderer.toneMapping = three.NoToneMapping;
1196
-
1197
- // render each texture into each layer of the target
1198
- const fsQuad = this.fsQuad;
1199
- for ( let i = 0, l = depth; i < l; i ++ ) {
1200
-
1201
- const texture = textures[ i ];
1202
- fsQuad.material.map = texture;
1203
- fsQuad.material.transparent = true;
1204
-
1205
- renderer.setRenderTarget( this, i );
1206
- fsQuad.render( renderer );
1207
-
1208
- }
1209
-
1210
- // reset the renderer
1211
- fsQuad.material.map = null;
1212
- renderer.setClearColor( prevColor, prevAlpha );
1213
- renderer.setRenderTarget( prevRenderTarget );
1214
- renderer.toneMapping = prevToneMapping;
1215
-
1216
- }
1217
-
1218
- dispose() {
1219
-
1220
- super.dispose();
1221
- this.fsQuad.dispose();
1222
-
1223
- }
1224
-
1478
+ const prevColor$1 = new three.Color();
1479
+ class RenderTarget2DArray extends three.WebGLArrayRenderTarget {
1480
+
1481
+ constructor( ...args ) {
1482
+
1483
+ super( ...args );
1484
+
1485
+ const tex = this.texture;
1486
+ tex.format = three.RGBAFormat;
1487
+ tex.type = three.UnsignedByteType;
1488
+ tex.minFilter = three.LinearFilter;
1489
+ tex.magFilter = three.LinearFilter;
1490
+ tex.wrapS = three.RepeatWrapping;
1491
+ tex.wrapT = three.RepeatWrapping;
1492
+ tex.setTextures = ( ...args ) => {
1493
+
1494
+ this.setTextures( ...args );
1495
+
1496
+ };
1497
+
1498
+ const fsQuad = new Pass_js.FullScreenQuad( new three.MeshBasicMaterial() );
1499
+ this.fsQuad = fsQuad;
1500
+
1501
+ }
1502
+
1503
+ setTextures( renderer, width, height, textures ) {
1504
+
1505
+ // save previous renderer state
1506
+ const prevRenderTarget = renderer.getRenderTarget();
1507
+ const prevToneMapping = renderer.toneMapping;
1508
+ const prevAlpha = renderer.getClearAlpha();
1509
+ renderer.getClearColor( prevColor$1 );
1510
+
1511
+ // resize the render target and ensure we don't have an empty texture
1512
+ // render target depth must be >= 1 to avoid unbound texture error on android devices
1513
+ const depth = textures.length || 1;
1514
+ this.setSize( width, height, depth );
1515
+ renderer.setClearColor( 0, 0 );
1516
+ renderer.toneMapping = three.NoToneMapping;
1517
+
1518
+ // render each texture into each layer of the target
1519
+ const fsQuad = this.fsQuad;
1520
+ for ( let i = 0, l = depth; i < l; i ++ ) {
1521
+
1522
+ const texture = textures[ i ];
1523
+ if ( texture ) {
1524
+
1525
+ // revert to default texture transform before rendering
1526
+ texture.matrixAutoUpdate = false;
1527
+ texture.matrix.identity();
1528
+
1529
+ fsQuad.material.map = texture;
1530
+ fsQuad.material.transparent = true;
1531
+
1532
+ renderer.setRenderTarget( this, i );
1533
+ fsQuad.render( renderer );
1534
+
1535
+ // restore custom texture transform
1536
+ texture.updateMatrix();
1537
+ texture.matrixAutoUpdate = true;
1538
+
1539
+ }
1540
+
1541
+ }
1542
+
1543
+ // reset the renderer
1544
+ fsQuad.material.map = null;
1545
+ renderer.setClearColor( prevColor$1, prevAlpha );
1546
+ renderer.setRenderTarget( prevRenderTarget );
1547
+ renderer.toneMapping = prevToneMapping;
1548
+
1549
+ }
1550
+
1551
+ dispose() {
1552
+
1553
+ super.dispose();
1554
+ this.fsQuad.dispose();
1555
+
1556
+ }
1557
+
1225
1558
  }
1226
1559
 
1227
1560
  function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
@@ -1335,27 +1668,21 @@
1335
1668
  conditionalWeights.magFilter = three.LinearFilter;
1336
1669
  conditionalWeights.generateMipmaps = false;
1337
1670
 
1338
- // store the total sum in a 1x1 tex since some android mobile devices have issues
1339
- // storing large values in structs.
1340
- const totalSumTex = new three.DataTexture();
1341
- totalSumTex.type = three.FloatType;
1342
- totalSumTex.format = three.RedFormat;
1343
- totalSumTex.minFilter = three.LinearFilter;
1344
- totalSumTex.magFilter = three.LinearFilter;
1345
- totalSumTex.generateMipmaps = false;
1346
-
1347
1671
  this.marginalWeights = marginalWeights;
1348
1672
  this.conditionalWeights = conditionalWeights;
1349
- this.totalSum = totalSumTex;
1350
1673
  this.map = null;
1351
1674
 
1675
+ // the total sum value is separated into two values to work around low precision
1676
+ // storage of floating values in structs
1677
+ this.totalSumWhole = 0;
1678
+ this.totalSumDecimal = 0;
1679
+
1352
1680
  }
1353
1681
 
1354
1682
  dispose() {
1355
1683
 
1356
1684
  this.marginalWeights.dispose();
1357
1685
  this.conditionalWeights.dispose();
1358
- this.totalSum.dispose();
1359
1686
  if ( this.map ) this.map.dispose();
1360
1687
 
1361
1688
  }
@@ -1470,15 +1797,17 @@
1470
1797
 
1471
1798
  this.dispose();
1472
1799
 
1473
- const { marginalWeights, conditionalWeights, totalSum } = this;
1800
+ const { marginalWeights, conditionalWeights } = this;
1474
1801
  marginalWeights.image = { width: height, height: 1, data: marginalDataArray };
1475
1802
  marginalWeights.needsUpdate = true;
1476
1803
 
1477
1804
  conditionalWeights.image = { width, height, data: conditionalDataArray };
1478
1805
  conditionalWeights.needsUpdate = true;
1479
1806
 
1480
- totalSum.image = { width: 1, height: 1, data: new Float32Array( [ totalSumValue ] ) };
1481
- totalSum.needsUpdate = true;
1807
+ const totalSumWhole = ~ ~ totalSumValue;
1808
+ const totalSumDecimal = ( totalSumValue - totalSumWhole );
1809
+ this.totalSumWhole = totalSumWhole;
1810
+ this.totalSumDecimal = totalSumDecimal;
1482
1811
 
1483
1812
  this.map = map;
1484
1813
 
@@ -1522,63 +1851,634 @@
1522
1851
 
1523
1852
  }
1524
1853
 
1525
- const shaderUtils = /* glsl */`
1854
+ const LIGHT_PIXELS = 6;
1855
+ const RECT_AREA_LIGHT = 0;
1856
+ const CIRC_AREA_LIGHT = 1;
1857
+ const SPOT_LIGHT = 2;
1858
+ class LightsInfoUniformStruct {
1526
1859
 
1527
- // https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
1528
- float schlickFresnel( float cosine, float f0 ) {
1860
+ constructor() {
1529
1861
 
1530
- return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
1862
+ const tex = new three.DataTexture( new Float32Array( 4 ), 1, 1 );
1863
+ tex.format = three.RGBAFormat;
1864
+ tex.type = three.FloatType;
1865
+ tex.wrapS = three.ClampToEdgeWrapping;
1866
+ tex.wrapT = three.ClampToEdgeWrapping;
1867
+ tex.generateMipmaps = false;
1531
1868
 
1532
- }
1869
+ this.tex = tex;
1870
+ this.count = 0;
1533
1871
 
1534
- // https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
1535
- float schlickFresnelFromIor( float cosine, float iorRatio ) {
1872
+ }
1536
1873
 
1537
- // Schlick approximation
1538
- float r_0 = pow( ( 1.0 - iorRatio ) / ( 1.0 + iorRatio ), 2.0 );
1539
- return schlickFresnel( cosine, r_0 );
1874
+ updateFrom( lights, iesTextures = [] ) {
1540
1875
 
1541
- }
1876
+ const tex = this.tex;
1877
+ const pixelCount = Math.max( lights.length * LIGHT_PIXELS, 1 );
1878
+ const dimension = Math.ceil( Math.sqrt( pixelCount ) );
1542
1879
 
1543
- // forms a basis with the normal vector as Z
1544
- mat3 getBasisFromNormal( vec3 normal ) {
1880
+ if ( tex.image.width !== dimension ) {
1545
1881
 
1546
- vec3 other;
1547
- if ( abs( normal.x ) > 0.5 ) {
1882
+ tex.dispose();
1548
1883
 
1549
- other = vec3( 0.0, 1.0, 0.0 );
1884
+ tex.image.data = new Float32Array( dimension * dimension * 4 );
1885
+ tex.image.width = dimension;
1886
+ tex.image.height = dimension;
1550
1887
 
1551
- } else {
1888
+ }
1552
1889
 
1553
- other = vec3( 1.0, 0.0, 0.0 );
1890
+ const floatArray = tex.image.data;
1554
1891
 
1555
- }
1892
+ const u = new three.Vector3();
1893
+ const v = new three.Vector3();
1894
+ const m = new three.Matrix4();
1895
+ const worldQuaternion = new three.Quaternion();
1896
+ const eye = new three.Vector3();
1897
+ const target = new three.Vector3();
1898
+ const up = new three.Vector3();
1556
1899
 
1557
- vec3 ortho = normalize( cross( normal, other ) );
1558
- vec3 ortho2 = normalize( cross( normal, ortho ) );
1559
- return mat3( ortho2, ortho, normal );
1900
+ for ( let i = 0, l = lights.length; i < l; i ++ ) {
1560
1901
 
1561
- }
1902
+ const l = lights[ i ];
1562
1903
 
1563
- vec3 getHalfVector( vec3 a, vec3 b ) {
1904
+ const baseIndex = i * LIGHT_PIXELS * 4;
1905
+ let index = 0;
1564
1906
 
1565
- return normalize( a + b );
1907
+ // sample 1
1908
+ // position
1909
+ l.getWorldPosition( v );
1910
+ floatArray[ baseIndex + ( index ++ ) ] = v.x;
1911
+ floatArray[ baseIndex + ( index ++ ) ] = v.y;
1912
+ floatArray[ baseIndex + ( index ++ ) ] = v.z;
1566
1913
 
1567
- }
1914
+ // type
1915
+ let type = RECT_AREA_LIGHT;
1916
+ if ( l.isRectAreaLight && l.isCircular ) type = CIRC_AREA_LIGHT;
1917
+ else if ( l.isSpotLight ) type = SPOT_LIGHT;
1918
+ floatArray[ baseIndex + ( index ++ ) ] = type;
1568
1919
 
1569
- // The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
1570
- // is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
1571
- // we find a ray like that we ignore it to avoid artifacts.
1572
- // This function returns if the direction is on the same side of both planes.
1573
- bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
1920
+ // sample 2
1921
+ // color
1922
+ floatArray[ baseIndex + ( index ++ ) ] = l.color.r;
1923
+ floatArray[ baseIndex + ( index ++ ) ] = l.color.g;
1924
+ floatArray[ baseIndex + ( index ++ ) ] = l.color.b;
1574
1925
 
1575
- bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
1576
- bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
1577
- return aboveSurfaceNormal == aboveGeometryNormal;
1926
+ // intensity
1927
+ floatArray[ baseIndex + ( index ++ ) ] = l.intensity;
1578
1928
 
1579
- }
1929
+ l.getWorldQuaternion( worldQuaternion );
1580
1930
 
1581
- vec3 getHemisphereSample( vec3 n, vec2 uv ) {
1931
+ if ( l.isRectAreaLight ) {
1932
+
1933
+ // sample 3
1934
+ // u vector
1935
+ u.set( l.width, 0, 0 ).applyQuaternion( worldQuaternion );
1936
+
1937
+ floatArray[ baseIndex + ( index ++ ) ] = u.x;
1938
+ floatArray[ baseIndex + ( index ++ ) ] = u.y;
1939
+ floatArray[ baseIndex + ( index ++ ) ] = u.z;
1940
+ index ++;
1941
+
1942
+ // sample 4
1943
+ // v vector
1944
+ v.set( 0, l.height, 0 ).applyQuaternion( worldQuaternion );
1945
+
1946
+ floatArray[ baseIndex + ( index ++ ) ] = v.x;
1947
+ floatArray[ baseIndex + ( index ++ ) ] = v.y;
1948
+ floatArray[ baseIndex + ( index ++ ) ] = v.z;
1949
+
1950
+ // area
1951
+ floatArray[ baseIndex + ( index ++ ) ] = u.cross( v ).length() * ( l.isCircular ? ( Math.PI / 4.0 ) : 1.0 );
1952
+
1953
+ } else if ( l.isSpotLight ) {
1954
+
1955
+ const radius = l.radius;
1956
+ eye.setFromMatrixPosition( l.matrixWorld );
1957
+ target.setFromMatrixPosition( l.target.matrixWorld );
1958
+ m.lookAt( eye, target, up );
1959
+ worldQuaternion.setFromRotationMatrix( m );
1960
+
1961
+ // sample 3
1962
+ // u vector
1963
+ u.set( 1, 0, 0 ).applyQuaternion( worldQuaternion );
1964
+
1965
+ floatArray[ baseIndex + ( index ++ ) ] = u.x;
1966
+ floatArray[ baseIndex + ( index ++ ) ] = u.y;
1967
+ floatArray[ baseIndex + ( index ++ ) ] = u.z;
1968
+ index ++;
1969
+
1970
+ // sample 4
1971
+ // v vector
1972
+ v.set( 0, 1, 0 ).applyQuaternion( worldQuaternion );
1973
+
1974
+ floatArray[ baseIndex + ( index ++ ) ] = v.x;
1975
+ floatArray[ baseIndex + ( index ++ ) ] = v.y;
1976
+ floatArray[ baseIndex + ( index ++ ) ] = v.z;
1977
+
1978
+ // area
1979
+ floatArray[ baseIndex + ( index ++ ) ] = Math.PI * radius * radius;
1980
+
1981
+ // sample 5
1982
+ // radius
1983
+ floatArray[ baseIndex + ( index ++ ) ] = radius;
1984
+
1985
+ // near
1986
+ floatArray[ baseIndex + ( index ++ ) ] = l.shadow.camera.near;
1987
+
1988
+ // decay
1989
+ floatArray[ baseIndex + ( index ++ ) ] = l.decay;
1990
+
1991
+ // distance
1992
+ floatArray[ baseIndex + ( index ++ ) ] = l.distance;
1993
+
1994
+ // sample 6
1995
+ // coneCos
1996
+ floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle );
1997
+
1998
+ // penumbraCos
1999
+ floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle * ( 1 - l.penumbra ) );
2000
+
2001
+ // iesProfile
2002
+ floatArray[ baseIndex + ( index ++ ) ] = iesTextures.indexOf( l.iesTexture );
2003
+
2004
+ }
2005
+
2006
+ }
2007
+
2008
+ tex.needsUpdate = true;
2009
+ this.count = lights.length;
2010
+
2011
+ }
2012
+
2013
+ }
2014
+
2015
+ function IESLamp( text ) {
2016
+
2017
+ const _self = this;
2018
+
2019
+ const textArray = text.split( '\n' );
2020
+
2021
+ let lineNumber = 0;
2022
+ let line;
2023
+
2024
+ _self.verAngles = [ ];
2025
+ _self.horAngles = [ ];
2026
+
2027
+ _self.candelaValues = [ ];
2028
+
2029
+ _self.tiltData = { };
2030
+ _self.tiltData.angles = [ ];
2031
+ _self.tiltData.mulFactors = [ ];
2032
+
2033
+ function textToArray( text ) {
2034
+
2035
+ text = text.replace( /^\s+|\s+$/g, '' ); // remove leading or trailing spaces
2036
+ text = text.replace( /,/g, ' ' ); // replace commas with spaces
2037
+ text = text.replace( /\s\s+/g, ' ' ); // replace white space/tabs etc by single whitespace
2038
+
2039
+ const array = text.split( ' ' );
2040
+
2041
+ return array;
2042
+
2043
+ }
2044
+
2045
+ function readArray( count, array ) {
2046
+
2047
+ while ( true ) {
2048
+
2049
+ const line = textArray[ lineNumber ++ ];
2050
+ const lineData = textToArray( line );
2051
+
2052
+ for ( let i = 0; i < lineData.length; ++ i ) {
2053
+
2054
+ array.push( Number( lineData[ i ] ) );
2055
+
2056
+ }
2057
+
2058
+ if ( array.length === count )
2059
+ break;
2060
+
2061
+ }
2062
+
2063
+ }
2064
+
2065
+ function readTilt() {
2066
+
2067
+ let line = textArray[ lineNumber ++ ];
2068
+ let lineData = textToArray( line );
2069
+
2070
+ _self.tiltData.lampToLumGeometry = Number( lineData[ 0 ] );
2071
+
2072
+ line = textArray[ lineNumber ++ ];
2073
+ lineData = textToArray( line );
2074
+
2075
+ _self.tiltData.numAngles = Number( lineData[ 0 ] );
2076
+
2077
+ readArray( _self.tiltData.numAngles, _self.tiltData.angles );
2078
+ readArray( _self.tiltData.numAngles, _self.tiltData.mulFactors );
2079
+
2080
+ }
2081
+
2082
+ function readLampValues() {
2083
+
2084
+ const values = [ ];
2085
+ readArray( 10, values );
2086
+
2087
+ _self.count = Number( values[ 0 ] );
2088
+ _self.lumens = Number( values[ 1 ] );
2089
+ _self.multiplier = Number( values[ 2 ] );
2090
+ _self.numVerAngles = Number( values[ 3 ] );
2091
+ _self.numHorAngles = Number( values[ 4 ] );
2092
+ _self.gonioType = Number( values[ 5 ] );
2093
+ _self.units = Number( values[ 6 ] );
2094
+ _self.width = Number( values[ 7 ] );
2095
+ _self.length = Number( values[ 8 ] );
2096
+ _self.height = Number( values[ 9 ] );
2097
+
2098
+ }
2099
+
2100
+ function readLampFactors() {
2101
+
2102
+ const values = [ ];
2103
+ readArray( 3, values );
2104
+
2105
+ _self.ballFactor = Number( values[ 0 ] );
2106
+ _self.blpFactor = Number( values[ 1 ] );
2107
+ _self.inputWatts = Number( values[ 2 ] );
2108
+
2109
+ }
2110
+
2111
+ while ( true ) {
2112
+
2113
+ line = textArray[ lineNumber ++ ];
2114
+
2115
+ if ( line.includes( 'TILT' ) ) {
2116
+
2117
+ break;
2118
+
2119
+ }
2120
+
2121
+ }
2122
+
2123
+ if ( ! line.includes( 'NONE' ) ) {
2124
+
2125
+ if ( line.includes( 'INCLUDE' ) ) {
2126
+
2127
+ readTilt();
2128
+
2129
+ } else {
2130
+
2131
+ // TODO:: Read tilt data from a file
2132
+
2133
+ }
2134
+
2135
+ }
2136
+
2137
+ readLampValues();
2138
+
2139
+ readLampFactors();
2140
+
2141
+ // Initialize candela value array
2142
+ for ( let i = 0; i < _self.numHorAngles; ++ i ) {
2143
+
2144
+ _self.candelaValues.push( [ ] );
2145
+
2146
+ }
2147
+
2148
+ // Parse Angles
2149
+ readArray( _self.numVerAngles, _self.verAngles );
2150
+ readArray( _self.numHorAngles, _self.horAngles );
2151
+
2152
+ // Parse Candela values
2153
+ for ( let i = 0; i < _self.numHorAngles; ++ i ) {
2154
+
2155
+ readArray( _self.numVerAngles, _self.candelaValues[ i ] );
2156
+
2157
+ }
2158
+
2159
+ // Calculate actual candela values, and normalize.
2160
+ for ( let i = 0; i < _self.numHorAngles; ++ i ) {
2161
+
2162
+ for ( let j = 0; j < _self.numVerAngles; ++ j ) {
2163
+
2164
+ _self.candelaValues[ i ][ j ] *= _self.candelaValues[ i ][ j ] * _self.multiplier
2165
+ * _self.ballFactor * _self.blpFactor;
2166
+
2167
+ }
2168
+
2169
+ }
2170
+
2171
+ let maxVal = - 1;
2172
+ for ( let i = 0; i < _self.numHorAngles; ++ i ) {
2173
+
2174
+ for ( let j = 0; j < _self.numVerAngles; ++ j ) {
2175
+
2176
+ const value = _self.candelaValues[ i ][ j ];
2177
+ maxVal = maxVal < value ? value : maxVal;
2178
+
2179
+ }
2180
+
2181
+ }
2182
+
2183
+ const bNormalize = true;
2184
+ if ( bNormalize && maxVal > 0 ) {
2185
+
2186
+ for ( let i = 0; i < _self.numHorAngles; ++ i ) {
2187
+
2188
+ for ( let j = 0; j < _self.numVerAngles; ++ j ) {
2189
+
2190
+ _self.candelaValues[ i ][ j ] /= maxVal;
2191
+
2192
+ }
2193
+
2194
+ }
2195
+
2196
+ }
2197
+
2198
+ }
2199
+
2200
+ class IESLoader extends three.Loader {
2201
+
2202
+ _getIESValues( iesLamp ) {
2203
+
2204
+ const width = 360;
2205
+ const height = 180;
2206
+ const size = width * height;
2207
+
2208
+ const data = new Float32Array( size );
2209
+
2210
+ function interpolateCandelaValues( phi, theta ) {
2211
+
2212
+ let phiIndex = 0, thetaIndex = 0;
2213
+ let startTheta = 0, endTheta = 0, startPhi = 0, endPhi = 0;
2214
+
2215
+ for ( let i = 0; i < iesLamp.numHorAngles - 1; ++ i ) { // numHorAngles = horAngles.length-1 because of extra padding, so this wont cause an out of bounds error
2216
+
2217
+ if ( theta < iesLamp.horAngles[ i + 1 ] || i == iesLamp.numHorAngles - 2 ) {
2218
+
2219
+ thetaIndex = i;
2220
+ startTheta = iesLamp.horAngles[ i ];
2221
+ endTheta = iesLamp.horAngles[ i + 1 ];
2222
+
2223
+ break;
2224
+
2225
+ }
2226
+
2227
+ }
2228
+
2229
+ for ( let i = 0; i < iesLamp.numVerAngles - 1; ++ i ) {
2230
+
2231
+ if ( phi < iesLamp.verAngles[ i + 1 ] || i == iesLamp.numVerAngles - 2 ) {
2232
+
2233
+ phiIndex = i;
2234
+ startPhi = iesLamp.verAngles[ i ];
2235
+ endPhi = iesLamp.verAngles[ i + 1 ];
2236
+
2237
+ break;
2238
+
2239
+ }
2240
+
2241
+ }
2242
+
2243
+ const deltaTheta = endTheta - startTheta;
2244
+ const deltaPhi = endPhi - startPhi;
2245
+
2246
+ if ( deltaPhi === 0 ) // Outside range
2247
+ return 0;
2248
+
2249
+ const t1 = deltaTheta === 0 ? 0 : ( theta - startTheta ) / deltaTheta;
2250
+ const t2 = ( phi - startPhi ) / deltaPhi;
2251
+
2252
+ const nextThetaIndex = deltaTheta === 0 ? thetaIndex : thetaIndex + 1;
2253
+
2254
+ const v1 = three.MathUtils.lerp( iesLamp.candelaValues[ thetaIndex ][ phiIndex ], iesLamp.candelaValues[ nextThetaIndex ][ phiIndex ], t1 );
2255
+ const v2 = three.MathUtils.lerp( iesLamp.candelaValues[ thetaIndex ][ phiIndex + 1 ], iesLamp.candelaValues[ nextThetaIndex ][ phiIndex + 1 ], t1 );
2256
+ const v = three.MathUtils.lerp( v1, v2, t2 );
2257
+
2258
+ return v;
2259
+
2260
+ }
2261
+
2262
+ const startTheta = iesLamp.horAngles[ 0 ], endTheta = iesLamp.horAngles[ iesLamp.numHorAngles - 1 ];
2263
+ for ( let i = 0; i < size; ++ i ) {
2264
+
2265
+ let theta = i % width;
2266
+ const phi = Math.floor( i / width );
2267
+
2268
+ if ( endTheta - startTheta !== 0 && ( theta < startTheta || theta >= endTheta ) ) { // Handle symmetry for hor angles
2269
+
2270
+ theta %= endTheta * 2;
2271
+ if ( theta > endTheta )
2272
+ theta = endTheta * 2 - theta;
2273
+
2274
+ }
2275
+
2276
+ data[ i ] = interpolateCandelaValues( phi, theta );
2277
+
2278
+ }
2279
+
2280
+ return data;
2281
+
2282
+ }
2283
+
2284
+ load( url, onLoad, onProgress, onError ) {
2285
+
2286
+ const loader = new three.FileLoader( this.manager );
2287
+ loader.setResponseType( 'text' );
2288
+ loader.setCrossOrigin( this.crossOrigin );
2289
+ loader.setWithCredentials( this.withCredentials );
2290
+ loader.setPath( this.path );
2291
+ loader.setRequestHeader( this.requestHeader );
2292
+
2293
+ const texture = new three.DataTexture( null, 360, 180, three.RedFormat, three.FloatType );
2294
+ texture.minFilter = three.LinearFilter;
2295
+ texture.magFilter = three.LinearFilter;
2296
+
2297
+ loader.load( url, text => {
2298
+
2299
+ const iesLamp = new IESLamp( text );
2300
+
2301
+ texture.image.data = this._getIESValues( iesLamp );
2302
+ texture.needsUpdate = true;
2303
+
2304
+ if ( onLoad !== undefined ) {
2305
+
2306
+ onLoad( texture );
2307
+
2308
+ }
2309
+
2310
+ }, onProgress, onError );
2311
+
2312
+ return texture;
2313
+
2314
+ }
2315
+
2316
+ parse( text ) {
2317
+
2318
+ const iesLamp = new IESLamp( text );
2319
+ const texture = new three.DataTexture( null, 360, 180, three.RedFormat, three.FloatType );
2320
+ texture.minFilter = three.LinearFilter;
2321
+ texture.magFilter = three.LinearFilter;
2322
+ texture.image.data = this._getIESValues( iesLamp );
2323
+ texture.needsUpdate = true;
2324
+
2325
+ return texture;
2326
+
2327
+ }
2328
+
2329
+ }
2330
+
2331
+ const prevColor = new three.Color();
2332
+ class IESProfilesTexture extends three.WebGLArrayRenderTarget {
2333
+
2334
+ constructor( ...args ) {
2335
+
2336
+ super( ...args );
2337
+
2338
+ const tex = this.texture;
2339
+ tex.format = three.RGBAFormat;
2340
+ tex.type = three.FloatType;
2341
+ tex.minFilter = three.LinearFilter;
2342
+ tex.magFilter = three.LinearFilter;
2343
+ tex.wrapS = three.ClampToEdgeWrapping;
2344
+ tex.wrapT = three.ClampToEdgeWrapping;
2345
+ tex.generateMipmaps = false;
2346
+
2347
+ tex.updateFrom = ( ...args ) => {
2348
+
2349
+ this.updateFrom( ...args );
2350
+
2351
+ };
2352
+
2353
+ const fsQuad = new Pass_js.FullScreenQuad( new three.MeshBasicMaterial() );
2354
+ this.fsQuad = fsQuad;
2355
+
2356
+ this.iesLoader = new IESLoader();
2357
+
2358
+ }
2359
+
2360
+ async updateFrom( renderer, textures ) {
2361
+
2362
+ // save previous renderer state
2363
+ const prevRenderTarget = renderer.getRenderTarget();
2364
+ const prevToneMapping = renderer.toneMapping;
2365
+ const prevAlpha = renderer.getClearAlpha();
2366
+ renderer.getClearColor( prevColor );
2367
+
2368
+ // resize the render target and ensure we don't have an empty texture
2369
+ // render target depth must be >= 1 to avoid unbound texture error on android devices
2370
+ const depth = textures.length || 1;
2371
+ this.setSize( 360, 180, depth );
2372
+ renderer.setClearColor( 0, 0 );
2373
+ renderer.toneMapping = three.NoToneMapping;
2374
+
2375
+ // render each texture into each layer of the target
2376
+ const fsQuad = this.fsQuad;
2377
+ for ( let i = 0, l = depth; i < l; i ++ ) {
2378
+
2379
+ const texture = textures[ i ];
2380
+ if ( texture ) {
2381
+
2382
+ // revert to default texture transform before rendering
2383
+ texture.matrixAutoUpdate = false;
2384
+ texture.matrix.identity();
2385
+
2386
+ fsQuad.material.map = texture;
2387
+ fsQuad.material.transparent = true;
2388
+
2389
+ renderer.setRenderTarget( this, i );
2390
+ fsQuad.render( renderer );
2391
+
2392
+ // restore custom texture transform
2393
+ texture.updateMatrix();
2394
+ texture.matrixAutoUpdate = true;
2395
+
2396
+ }
2397
+
2398
+ }
2399
+
2400
+ // reset the renderer
2401
+ fsQuad.material.map = null;
2402
+ renderer.setClearColor( prevColor, prevAlpha );
2403
+ renderer.setRenderTarget( prevRenderTarget );
2404
+ renderer.toneMapping = prevToneMapping;
2405
+
2406
+ fsQuad.dispose();
2407
+
2408
+ }
2409
+
2410
+ dispose() {
2411
+
2412
+ super.dispose();
2413
+ this.fsQuad.dispose();
2414
+
2415
+ }
2416
+
2417
+ }
2418
+
2419
+ const shaderUtils = /* glsl */`
2420
+
2421
+ // https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
2422
+ float schlickFresnel( float cosine, float f0 ) {
2423
+
2424
+ return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
2425
+
2426
+ }
2427
+
2428
+ vec3 schlickFresnel( float cosine, vec3 f0 ) {
2429
+
2430
+ return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
2431
+
2432
+ }
2433
+
2434
+ // https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
2435
+ float schlickFresnelFromIor( float cosine, float iorRatio ) {
2436
+
2437
+ // Schlick approximation
2438
+ float r_0 = pow( ( 1.0 - iorRatio ) / ( 1.0 + iorRatio ), 2.0 );
2439
+ return schlickFresnel( cosine, r_0 );
2440
+
2441
+ }
2442
+
2443
+ // forms a basis with the normal vector as Z
2444
+ mat3 getBasisFromNormal( vec3 normal ) {
2445
+
2446
+ vec3 other;
2447
+ if ( abs( normal.x ) > 0.5 ) {
2448
+
2449
+ other = vec3( 0.0, 1.0, 0.0 );
2450
+
2451
+ } else {
2452
+
2453
+ other = vec3( 1.0, 0.0, 0.0 );
2454
+
2455
+ }
2456
+
2457
+ vec3 ortho = normalize( cross( normal, other ) );
2458
+ vec3 ortho2 = normalize( cross( normal, ortho ) );
2459
+ return mat3( ortho2, ortho, normal );
2460
+
2461
+ }
2462
+
2463
+ vec3 getHalfVector( vec3 a, vec3 b ) {
2464
+
2465
+ return normalize( a + b );
2466
+
2467
+ }
2468
+
2469
+ // The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
2470
+ // is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
2471
+ // we find a ray like that we ignore it to avoid artifacts.
2472
+ // This function returns if the direction is on the same side of both planes.
2473
+ bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
2474
+
2475
+ bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
2476
+ bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
2477
+ return aboveSurfaceNormal == aboveGeometryNormal;
2478
+
2479
+ }
2480
+
2481
+ vec3 getHemisphereSample( vec3 n, vec2 uv ) {
1582
2482
 
1583
2483
  // https://www.rorydriscoll.com/2009/01/07/better-sampling/
1584
2484
  // https://graphics.pixar.com/library/OrthonormalB/paper.pdf
@@ -1671,84 +2571,200 @@
1671
2571
  vec2 e2 = c - b;
1672
2572
  vec2 diag = normalize( e1 + e2 );
1673
2573
 
1674
- // pick a random point in the parallelogram
1675
- vec2 r = rand2();
1676
- if ( r.x + r.y > 1.0 ) {
2574
+ // pick a random point in the parallelogram
2575
+ vec2 r = rand2();
2576
+ if ( r.x + r.y > 1.0 ) {
2577
+
2578
+ r = vec2( 1.0 ) - r;
2579
+
2580
+ }
2581
+
2582
+ return e1 * r.x + e2 * r.y;
2583
+
2584
+ }
2585
+
2586
+ // samples an aperture shape with the given number of sides. 0 means circle
2587
+ vec2 sampleAperture( int blades ) {
2588
+
2589
+ if ( blades == 0 ) {
2590
+
2591
+ vec2 r = rand2();
2592
+ float angle = 2.0 * PI * r.x;
2593
+ float radius = sqrt( rand() );
2594
+ return vec2( cos( angle ), sin( angle ) ) * radius;
2595
+
2596
+ } else {
2597
+
2598
+ blades = max( blades, 3 );
2599
+
2600
+ vec3 r = rand3();
2601
+ float anglePerSegment = 2.0 * PI / float( blades );
2602
+ float segment = floor( float( blades ) * r.x );
2603
+
2604
+ float angle1 = anglePerSegment * segment;
2605
+ float angle2 = angle1 + anglePerSegment;
2606
+ vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
2607
+ vec2 b = vec2( 0.0, 0.0 );
2608
+ vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
2609
+
2610
+ return triangleSample( a, b, c );
2611
+
2612
+ }
2613
+
2614
+ }
2615
+
2616
+ float colorToLuminance( vec3 color ) {
2617
+
2618
+ // https://en.wikipedia.org/wiki/Relative_luminance
2619
+ return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
2620
+
2621
+ }
2622
+
2623
+ // ray sampling x and z are swapped to align with expected background view
2624
+ vec2 equirectDirectionToUv( vec3 direction ) {
2625
+
2626
+ // from Spherical.setFromCartesianCoords
2627
+ vec2 uv = vec2( atan( direction.z, direction.x ), acos( direction.y ) );
2628
+ uv /= vec2( 2.0 * PI, PI );
2629
+
2630
+ // apply adjustments to get values in range [0, 1] and y right side up
2631
+ uv.x += 0.5;
2632
+ uv.y = 1.0 - uv.y;
2633
+ return uv;
2634
+
2635
+ }
2636
+
2637
+ vec3 equirectUvToDirection( vec2 uv ) {
2638
+
2639
+ // undo above adjustments
2640
+ uv.x -= 0.5;
2641
+ uv.y = 1.0 - uv.y;
2642
+
2643
+ // from Vector3.setFromSphericalCoords
2644
+ float theta = uv.x * 2.0 * PI;
2645
+ float phi = uv.y * PI;
2646
+
2647
+ float sinPhi = sin( phi );
2648
+
2649
+ return vec3( sinPhi * cos( theta ), cos( phi ), sinPhi * sin( theta ) );
2650
+
2651
+ }
2652
+
2653
+ // Fast arccos approximation used to remove banding artifacts caused by numerical errors in acos.
2654
+ // This is a cubic Lagrange interpolating polynomial for x = [-1, -1/2, 0, 1/2, 1].
2655
+ // For more information see: https://github.com/gkjohnson/three-gpu-pathtracer/pull/171#issuecomment-1152275248
2656
+ float acosApprox( float x ) {
2657
+
2658
+ x = clamp( x, -1.0, 1.0 );
2659
+ return ( - 0.69813170079773212 * x * x - 0.87266462599716477 ) * x + 1.5707963267948966;
2660
+
2661
+ }
2662
+
2663
+ // An acos with input values bound to the range [-1, 1].
2664
+ float acosSafe( float x ) {
2665
+
2666
+ return acos( clamp( x, -1.0, 1.0 ) );
2667
+
2668
+ }
2669
+
2670
+ float saturateCos( float val ) {
2671
+
2672
+ return clamp( val, 0.001, 1.0 );
2673
+
2674
+ }
2675
+
2676
+ float square( float t ) {
2677
+
2678
+ return t * t;
2679
+
2680
+ }
2681
+
2682
+ vec2 square( vec2 t) {
2683
+
2684
+ return t * t;
2685
+
2686
+ }
2687
+
2688
+ vec3 square( vec3 t ) {
1677
2689
 
1678
- r = vec2( 1.0 ) - r;
2690
+ return t * t;
1679
2691
 
1680
- }
2692
+ }
1681
2693
 
1682
- return e1 * r.x + e2 * r.y;
2694
+ vec4 square( vec4 t ) {
2695
+
2696
+ return t * t;
1683
2697
 
1684
2698
  }
1685
2699
 
1686
- // samples an aperture shape with the given number of sides. 0 means circle
1687
- vec2 sampleAperture( int blades ) {
2700
+ // Finds the point where the ray intersects the plane defined by u and v and checks if this point
2701
+ // falls in the bounds of the rectangle on that same plane.
2702
+ // Plane intersection: https://lousodrome.net/blog/light/2020/07/03/intersection-of-a-ray-and-a-plane/
2703
+ bool intersectsRectangle( vec3 center, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
1688
2704
 
1689
- if ( blades == 0 ) {
2705
+ float t = dot( center - rayOrigin, normal ) / dot( rayDirection, normal );
1690
2706
 
1691
- vec2 r = rand2();
1692
- float angle = 2.0 * PI * r.x;
1693
- float radius = sqrt( rand() );
1694
- return vec2( cos( angle ), sin( angle ) ) * radius;
2707
+ if ( t > EPSILON ) {
1695
2708
 
1696
- } else {
2709
+ vec3 p = rayOrigin + rayDirection * t;
2710
+ vec3 vi = p - center;
1697
2711
 
1698
- blades = max( blades, 3 );
2712
+ // check if p falls inside the rectangle
2713
+ float a1 = dot( u, vi );
2714
+ if ( abs( a1 ) <= 0.5 ) {
1699
2715
 
1700
- vec3 r = rand3();
1701
- float anglePerSegment = 2.0 * PI / float( blades );
1702
- float segment = floor( float( blades ) * r.x );
2716
+ float a2 = dot( v, vi );
2717
+ if ( abs( a2 ) <= 0.5 ) {
1703
2718
 
1704
- float angle1 = anglePerSegment * segment;
1705
- float angle2 = angle1 + anglePerSegment;
1706
- vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
1707
- vec2 b = vec2( 0.0, 0.0 );
1708
- vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
2719
+ dist = t;
2720
+ return true;
1709
2721
 
1710
- return triangleSample( a, b, c );
2722
+ }
2723
+
2724
+ }
1711
2725
 
1712
2726
  }
1713
2727
 
2728
+ return false;
2729
+
1714
2730
  }
1715
2731
 
1716
- float colorToLuminance( vec3 color ) {
2732
+ // Finds the point where the ray intersects the plane defined by u and v and checks if this point
2733
+ // falls in the bounds of the circle on that same plane. See above URL for a description of the plane intersection algorithm.
2734
+ bool intersectsCircle( vec3 position, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
1717
2735
 
1718
- // https://en.wikipedia.org/wiki/Relative_luminance
1719
- return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
2736
+ float t = dot( position - rayOrigin, normal ) / dot( rayDirection, normal );
1720
2737
 
1721
- }
2738
+ if ( t > EPSILON ) {
1722
2739
 
1723
- // ray sampling x and z are swapped to align with expected background view
1724
- vec2 equirectDirectionToUv( vec3 direction ) {
2740
+ vec3 hit = rayOrigin + rayDirection * t;
2741
+ vec3 vi = hit - position;
1725
2742
 
1726
- // from Spherical.setFromCartesianCoords
1727
- vec2 uv = vec2( atan( direction.z, direction.x ), acos( direction.y ) );
1728
- uv /= vec2( 2.0 * PI, PI );
2743
+ float a1 = dot( u, vi );
2744
+ float a2 = dot( v, vi );
1729
2745
 
1730
- // apply adjustments to get values in range [0, 1] and y right side up
1731
- uv.x += 0.5;
1732
- uv.y = 1.0 - uv.y;
1733
- return uv;
2746
+ if( length( vec2( a1, a2 ) ) <= 0.5 ) {
1734
2747
 
1735
- }
2748
+ dist = t;
2749
+ return true;
1736
2750
 
1737
- vec3 equirectUvToDirection( vec2 uv ) {
2751
+ }
1738
2752
 
1739
- // undo above adjustments
1740
- uv.x -= 0.5;
1741
- uv.y = 1.0 - uv.y;
2753
+ }
1742
2754
 
1743
- // from Vector3.setFromSphericalCoords
1744
- float theta = uv.x * 2.0 * PI;
1745
- float phi = uv.y * PI;
2755
+ return false;
1746
2756
 
1747
- float sinPhi = sin( phi );
2757
+ }
1748
2758
 
1749
- return vec3( sinPhi * cos( theta ), cos( phi ), sinPhi * sin( theta ) );
2759
+ // power heuristic for multiple importance sampling
2760
+ float misHeuristic( float a, float b ) {
2761
+
2762
+ float aa = a * a;
2763
+ float bb = b * b;
2764
+ return aa / ( aa + bb );
1750
2765
 
1751
2766
  }
2767
+
1752
2768
  `;
1753
2769
 
1754
2770
  class PMREMCopyMaterial extends MaterialBase {
@@ -1860,97 +2876,312 @@
1860
2876
 
1861
2877
  }
1862
2878
 
1863
- const shaderMaterialStructs = /* glsl */ `
1864
-
1865
- struct PhysicalCamera {
1866
-
1867
- float focusDistance;
1868
- float anamorphicRatio;
1869
- float bokehSize;
1870
- int apertureBlades;
1871
- float apertureRotation;
1872
-
1873
- };
1874
-
1875
- struct EquirectHdrInfo {
1876
-
1877
- sampler2D marginalWeights;
1878
- sampler2D conditionalWeights;
1879
- sampler2D map;
1880
- sampler2D totalSum;
1881
-
1882
- };
1883
-
1884
- struct Material {
1885
-
1886
- vec3 color;
1887
- int map;
1888
-
1889
- float metalness;
1890
- int metalnessMap;
1891
-
1892
- float roughness;
1893
- int roughnessMap;
1894
-
1895
- float ior;
1896
- float transmission;
1897
- int transmissionMap;
1898
-
1899
- float emissiveIntensity;
1900
- vec3 emissive;
1901
- int emissiveMap;
1902
-
1903
- int normalMap;
1904
- vec2 normalScale;
1905
-
1906
- float opacity;
1907
- float alphaTest;
1908
-
1909
- float side;
1910
- bool matte;
1911
-
1912
- };
1913
-
1914
- Material readMaterialInfo( sampler2D tex, uint index ) {
1915
-
1916
- uint i = index * 6u;
1917
-
1918
- vec4 s0 = texelFetch1D( tex, i + 0u );
1919
- vec4 s1 = texelFetch1D( tex, i + 1u );
1920
- vec4 s2 = texelFetch1D( tex, i + 2u );
1921
- vec4 s3 = texelFetch1D( tex, i + 3u );
1922
- vec4 s4 = texelFetch1D( tex, i + 4u );
1923
- vec4 s5 = texelFetch1D( tex, i + 5u );
1924
-
1925
- Material m;
1926
- m.color = s0.rgb;
1927
- m.map = floatBitsToInt( s0.a );
1928
-
1929
- m.metalness = s1.r;
1930
- m.metalnessMap = floatBitsToInt( s1.g );
1931
- m.roughness = s1.b;
1932
- m.roughnessMap = floatBitsToInt( s1.a );
1933
-
1934
- m.ior = s2.r;
1935
- m.transmission = s2.g;
1936
- m.transmissionMap = floatBitsToInt( s2.b );
1937
- m.emissiveIntensity = s2.a;
1938
-
1939
- m.emissive = s3.rgb;
1940
- m.emissiveMap = floatBitsToInt( s3.a );
1941
-
1942
- m.normalMap = floatBitsToInt( s4.r );
1943
- m.normalScale = s4.gb;
1944
-
1945
- m.opacity = s5.r;
1946
- m.alphaTest = s5.g;
1947
- m.side = s5.b;
1948
- m.matte = bool( s5.a );
1949
-
1950
- return m;
1951
-
1952
- }
1953
-
2879
+ const shaderMaterialStructs = /* glsl */ `
2880
+
2881
+ struct PhysicalCamera {
2882
+
2883
+ float focusDistance;
2884
+ float anamorphicRatio;
2885
+ float bokehSize;
2886
+ int apertureBlades;
2887
+ float apertureRotation;
2888
+
2889
+ };
2890
+
2891
+ struct EquirectHdrInfo {
2892
+
2893
+ sampler2D marginalWeights;
2894
+ sampler2D conditionalWeights;
2895
+ sampler2D map;
2896
+
2897
+ float totalSumWhole;
2898
+ float totalSumDecimal;
2899
+
2900
+ };
2901
+
2902
+ struct Material {
2903
+
2904
+ vec3 color;
2905
+ int map;
2906
+
2907
+ float metalness;
2908
+ int metalnessMap;
2909
+
2910
+ float roughness;
2911
+ int roughnessMap;
2912
+
2913
+ float ior;
2914
+ float transmission;
2915
+ int transmissionMap;
2916
+
2917
+ float emissiveIntensity;
2918
+ vec3 emissive;
2919
+ int emissiveMap;
2920
+
2921
+ int normalMap;
2922
+ vec2 normalScale;
2923
+
2924
+ float clearcoat;
2925
+ int clearcoatMap;
2926
+ int clearcoatNormalMap;
2927
+ vec2 clearcoatNormalScale;
2928
+ float clearcoatRoughness;
2929
+ int clearcoatRoughnessMap;
2930
+
2931
+ int iridescenceMap;
2932
+ int iridescenceThicknessMap;
2933
+ float iridescence;
2934
+ float iridescenceIor;
2935
+ float iridescenceThicknessMinimum;
2936
+ float iridescenceThicknessMaximum;
2937
+
2938
+ vec3 specularColor;
2939
+ int specularColorMap;
2940
+
2941
+ float specularIntensity;
2942
+ int specularIntensityMap;
2943
+
2944
+ int alphaMap;
2945
+
2946
+ bool castShadow;
2947
+ float opacity;
2948
+ float alphaTest;
2949
+
2950
+ float side;
2951
+ bool matte;
2952
+
2953
+ vec3 sheenColor;
2954
+ int sheenColorMap;
2955
+ float sheenRoughness;
2956
+ int sheenRoughnessMap;
2957
+
2958
+ mat3 mapTransform;
2959
+ mat3 metalnessMapTransform;
2960
+ mat3 roughnessMapTransform;
2961
+ mat3 transmissionMapTransform;
2962
+ mat3 emissiveMapTransform;
2963
+ mat3 normalMapTransform;
2964
+ mat3 clearcoatMapTransform;
2965
+ mat3 clearcoatNormalMapTransform;
2966
+ mat3 clearcoatRoughnessMapTransform;
2967
+ mat3 sheenColorMapTransform;
2968
+ mat3 sheenRoughnessMapTransform;
2969
+ mat3 iridescenceMapTransform;
2970
+ mat3 iridescenceThicknessMapTransform;
2971
+ mat3 specularColorMapTransform;
2972
+ mat3 specularIntensityMapTransform;
2973
+
2974
+ };
2975
+
2976
+ mat3 readTextureTransform( sampler2D tex, uint index ) {
2977
+
2978
+ mat3 textureTransform;
2979
+
2980
+ vec4 row1 = texelFetch1D( tex, index );
2981
+ vec4 row2 = texelFetch1D( tex, index + 1u );
2982
+
2983
+ textureTransform[0] = vec3(row1.r, row2.r, 0.0);
2984
+ textureTransform[1] = vec3(row1.g, row2.g, 0.0);
2985
+ textureTransform[2] = vec3(row1.b, row2.b, 1.0);
2986
+
2987
+ return textureTransform;
2988
+
2989
+ }
2990
+
2991
+ Material readMaterialInfo( sampler2D tex, uint index ) {
2992
+
2993
+ uint i = index * 44u;
2994
+
2995
+ vec4 s0 = texelFetch1D( tex, i + 0u );
2996
+ vec4 s1 = texelFetch1D( tex, i + 1u );
2997
+ vec4 s2 = texelFetch1D( tex, i + 2u );
2998
+ vec4 s3 = texelFetch1D( tex, i + 3u );
2999
+ vec4 s4 = texelFetch1D( tex, i + 4u );
3000
+ vec4 s5 = texelFetch1D( tex, i + 5u );
3001
+ vec4 s6 = texelFetch1D( tex, i + 6u );
3002
+ vec4 s7 = texelFetch1D( tex, i + 7u );
3003
+ vec4 s8 = texelFetch1D( tex, i + 8u );
3004
+ vec4 s9 = texelFetch1D( tex, i + 9u );
3005
+ vec4 s10 = texelFetch1D( tex, i + 10u );
3006
+ vec4 s11 = texelFetch1D( tex, i + 11u );
3007
+ vec4 s12 = texelFetch1D( tex, i + 12u );
3008
+ vec4 s13 = texelFetch1D( tex, i + 13u );
3009
+
3010
+ Material m;
3011
+ m.color = s0.rgb;
3012
+ m.map = int( round( s0.a ) );
3013
+
3014
+ m.metalness = s1.r;
3015
+ m.metalnessMap = int( round( s1.g ) );
3016
+ m.roughness = s1.b;
3017
+ m.roughnessMap = int( round( s1.a ) );
3018
+
3019
+ m.ior = s2.r;
3020
+ m.transmission = s2.g;
3021
+ m.transmissionMap = int( round( s2.b ) );
3022
+ m.emissiveIntensity = s2.a;
3023
+
3024
+ m.emissive = s3.rgb;
3025
+ m.emissiveMap = int( round( s3.a ) );
3026
+
3027
+ m.normalMap = int( round( s4.r ) );
3028
+ m.normalScale = s4.gb;
3029
+
3030
+ m.clearcoat = s4.a;
3031
+ m.clearcoatMap = int( round( s5.r ) );
3032
+ m.clearcoatRoughness = s5.g;
3033
+ m.clearcoatRoughnessMap = int( round( s5.b ) );
3034
+ m.clearcoatNormalMap = int( round( s5.a ) );
3035
+ m.clearcoatNormalScale = s6.rg;
3036
+
3037
+ m.sheenColor = s7.rgb;
3038
+ m.sheenColorMap = int( round( s7.a ) );
3039
+ m.sheenRoughness = s8.r;
3040
+ m.sheenRoughnessMap = int( round( s8.g ) );
3041
+
3042
+ m.iridescenceMap = int( round( s8.b ) );
3043
+ m.iridescenceThicknessMap = int( round( s8.a ) );
3044
+ m.iridescence = s9.r;
3045
+ m.iridescenceIor = s9.g;
3046
+ m.iridescenceThicknessMinimum = s9.b;
3047
+ m.iridescenceThicknessMaximum = s9.a;
3048
+
3049
+ m.specularColor = s10.rgb;
3050
+ m.specularColorMap = int( round( s10.a ) );
3051
+
3052
+ m.specularIntensity = s11.r;
3053
+ m.specularIntensityMap = int( round( s11.g ) );
3054
+
3055
+ m.alphaMap = int( round( s12.r ) );
3056
+
3057
+ m.opacity = s12.g;
3058
+ m.alphaTest = s12.b;
3059
+ m.side = s12.a;
3060
+
3061
+ m.matte = bool( s13.r );
3062
+ m.castShadow = ! bool( s13.g );
3063
+
3064
+ uint firstTextureTransformIdx = i + 14u;
3065
+
3066
+ m.mapTransform = m.map == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx );
3067
+ m.metalnessMapTransform = m.metalnessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 2u );
3068
+ m.roughnessMapTransform = m.roughnessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 4u );
3069
+ m.transmissionMapTransform = m.transmissionMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 6u );
3070
+ m.emissiveMapTransform = m.emissiveMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 8u );
3071
+ m.normalMapTransform = m.normalMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 10u );
3072
+ m.clearcoatMapTransform = m.clearcoatMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 12u );
3073
+ m.clearcoatNormalMapTransform = m.clearcoatNormalMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 14u );
3074
+ m.clearcoatRoughnessMapTransform = m.clearcoatRoughnessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 16u );
3075
+ m.sheenColorMapTransform = m.sheenColorMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 18u );
3076
+ m.sheenRoughnessMapTransform = m.sheenRoughnessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 20u );
3077
+ m.iridescenceMapTransform = m.iridescenceMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 22u );
3078
+ m.iridescenceThicknessMapTransform = m.iridescenceThicknessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 24u );
3079
+ m.specularColorMapTransform = m.specularColorMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 26u );
3080
+ m.specularIntensityMapTransform = m.specularIntensityMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 28u );
3081
+
3082
+ return m;
3083
+
3084
+ }
3085
+
3086
+ `;
3087
+
3088
+ const shaderLightStruct = /* glsl */ `
3089
+
3090
+ #define RECT_AREA_LIGHT_TYPE 0
3091
+ #define CIRC_AREA_LIGHT_TYPE 1
3092
+ #define SPOT_LIGHT_TYPE 2
3093
+
3094
+ struct LightsInfo {
3095
+
3096
+ sampler2D tex;
3097
+ uint count;
3098
+
3099
+ };
3100
+
3101
+ struct Light {
3102
+
3103
+ vec3 position;
3104
+ int type;
3105
+
3106
+ vec3 color;
3107
+ float intensity;
3108
+
3109
+ vec3 u;
3110
+ vec3 v;
3111
+ float area;
3112
+
3113
+ // spot light fields
3114
+ float radius;
3115
+ float near;
3116
+ float decay;
3117
+ float distance;
3118
+ float coneCos;
3119
+ float penumbraCos;
3120
+ int iesProfile;
3121
+
3122
+ };
3123
+
3124
+ Light readLightInfo( sampler2D tex, uint index ) {
3125
+
3126
+ uint i = index * 6u;
3127
+
3128
+ vec4 s0 = texelFetch1D( tex, i + 0u );
3129
+ vec4 s1 = texelFetch1D( tex, i + 1u );
3130
+ vec4 s2 = texelFetch1D( tex, i + 2u );
3131
+ vec4 s3 = texelFetch1D( tex, i + 3u );
3132
+
3133
+ Light l;
3134
+ l.position = s0.rgb;
3135
+ l.type = int( round( s0.a ) );
3136
+
3137
+ l.color = s1.rgb;
3138
+ l.intensity = s1.a;
3139
+
3140
+ l.u = s2.rgb;
3141
+ l.v = s3.rgb;
3142
+ l.area = s3.a;
3143
+
3144
+ if ( l.type == SPOT_LIGHT_TYPE ) {
3145
+
3146
+ vec4 s4 = texelFetch1D( tex, i + 4u );
3147
+ vec4 s5 = texelFetch1D( tex, i + 5u );
3148
+ l.radius = s4.r;
3149
+ l.near = s4.g;
3150
+ l.decay = s4.b;
3151
+ l.distance = s4.a;
3152
+
3153
+ l.coneCos = s5.r;
3154
+ l.penumbraCos = s5.g;
3155
+ l.iesProfile = int( round ( s5.b ) );
3156
+
3157
+ }
3158
+
3159
+ return l;
3160
+
3161
+ }
3162
+
3163
+ struct SpotLight {
3164
+
3165
+ vec3 position;
3166
+ int type;
3167
+
3168
+ vec3 color;
3169
+ float intensity;
3170
+
3171
+ vec3 u;
3172
+ vec3 v;
3173
+ float area;
3174
+
3175
+ float radius;
3176
+ float near;
3177
+ float decay;
3178
+ float distance;
3179
+ float coneCos;
3180
+ float penumbraCos;
3181
+ int iesProfile;
3182
+
3183
+ };
3184
+
1954
3185
  `;
1955
3186
 
1956
3187
  const shaderGGXFunctions = /* glsl */`
@@ -2027,17 +3258,18 @@ float ggxDistribution( vec3 halfVector, float roughness ) {
2027
3258
 
2028
3259
  // See equation (33) from reference [0]
2029
3260
  float a2 = roughness * roughness;
3261
+ a2 = max( EPSILON, a2 );
2030
3262
  float cosTheta = halfVector.z;
2031
3263
  float cosTheta4 = pow( cosTheta, 4.0 );
2032
3264
 
2033
3265
  if ( cosTheta == 0.0 ) return 0.0;
2034
3266
 
2035
- float theta = acos( halfVector.z );
3267
+ float theta = acosSafe( halfVector.z );
2036
3268
  float tanTheta = tan( theta );
2037
3269
  float tanTheta2 = pow( tanTheta, 2.0 );
2038
3270
 
2039
3271
  float denom = PI * cosTheta4 * pow( a2 + tanTheta2, 2.0 );
2040
- return a2 / denom;
3272
+ return ( a2 / denom );
2041
3273
 
2042
3274
  // See equation (1) from reference [2]
2043
3275
  // const { x, y, z } = halfVector;
@@ -2059,6 +3291,236 @@ float ggxPDF( vec3 wi, vec3 halfVector, float roughness ) {
2059
3291
  return D * G1 * max( 0.0, dot( wi, halfVector ) ) / wi.z;
2060
3292
 
2061
3293
  }
3294
+ `;
3295
+
3296
+ const shaderSheenFunctions = /* glsl */`
3297
+
3298
+ // See equation (2) in http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf
3299
+ float velvetD( float cosThetaH, float roughness ) {
3300
+
3301
+ float alpha = max( roughness, 0.07 );
3302
+ alpha = alpha * alpha;
3303
+
3304
+ float invAlpha = 1.0 / alpha;
3305
+
3306
+ float sqrCosThetaH = cosThetaH * cosThetaH;
3307
+ float sinThetaH = max( 1.0 - sqrCosThetaH, 0.001 );
3308
+
3309
+ return ( 2.0 + invAlpha ) * pow( sinThetaH, 0.5 * invAlpha ) / ( 2.0 * PI );
3310
+
3311
+ }
3312
+
3313
+ float velvetParamsInterpolate( int i, float oneMinusAlphaSquared ) {
3314
+
3315
+ const float p0[5] = float[5]( 25.3245, 3.32435, 0.16801, -1.27393, -4.85967 );
3316
+ const float p1[5] = float[5]( 21.5473, 3.82987, 0.19823, -1.97760, -4.32054 );
3317
+
3318
+ return mix( p1[i], p0[i], oneMinusAlphaSquared );
3319
+
3320
+ }
3321
+
3322
+ float velvetL( float x, float alpha ) {
3323
+
3324
+ float oneMinusAlpha = 1.0 - alpha;
3325
+ float oneMinusAlphaSquared = oneMinusAlpha * oneMinusAlpha;
3326
+
3327
+ float a = velvetParamsInterpolate( 0, oneMinusAlphaSquared );
3328
+ float b = velvetParamsInterpolate( 1, oneMinusAlphaSquared );
3329
+ float c = velvetParamsInterpolate( 2, oneMinusAlphaSquared );
3330
+ float d = velvetParamsInterpolate( 3, oneMinusAlphaSquared );
3331
+ float e = velvetParamsInterpolate( 4, oneMinusAlphaSquared );
3332
+
3333
+ return a / ( 1.0 + b * pow( abs( x ), c ) ) + d * x + e;
3334
+
3335
+ }
3336
+
3337
+ // See equation (3) in http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf
3338
+ float velvetLambda( float cosTheta, float alpha ) {
3339
+
3340
+ return abs( cosTheta ) < 0.5 ? exp( velvetL( cosTheta, alpha ) ) : exp( 2.0 * velvetL( 0.5, alpha ) - velvetL( 1.0 - cosTheta, alpha ) );
3341
+
3342
+ }
3343
+
3344
+ // See Section 3, Shadowing Term, in http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf
3345
+ float velvetG( float cosThetaO, float cosThetaI, float roughness ) {
3346
+
3347
+ float alpha = max( roughness, 0.07 );
3348
+ alpha = alpha * alpha;
3349
+
3350
+ return 1.0 / ( 1.0 + velvetLambda( cosThetaO, alpha ) + velvetLambda( cosThetaI, alpha ) );
3351
+
3352
+ }
3353
+
3354
+ float directionalAlbedoSheen( float cosTheta, float alpha ) {
3355
+
3356
+ cosTheta = saturate( cosTheta );
3357
+
3358
+ float c = 1.0 - cosTheta;
3359
+ float c3 = c * c * c;
3360
+
3361
+ return 0.65584461 * c3 + 1.0 / ( 4.16526551 + exp( -7.97291361 * sqrt( alpha ) + 6.33516894 ) );
3362
+
3363
+ }
3364
+
3365
+ float sheenAlbedoScaling( vec3 wo, vec3 wi, SurfaceRec surf ) {
3366
+
3367
+ float alpha = max( surf.sheenRoughness, 0.07 );
3368
+ alpha = alpha * alpha;
3369
+
3370
+ float maxSheenColor = max( max( surf.sheenColor.r, surf.sheenColor.g ), surf.sheenColor.b );
3371
+
3372
+ float eWo = directionalAlbedoSheen( saturateCos( wo.z ), alpha );
3373
+ float eWi = directionalAlbedoSheen( saturateCos( wi.z ), alpha );
3374
+
3375
+ return min( 1.0 - maxSheenColor * eWo, 1.0 - maxSheenColor * eWi );
3376
+
3377
+ }
3378
+
3379
+ // See Section 5, Layering, in http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf
3380
+ float sheenAlbedoScaling( vec3 wo, SurfaceRec surf ) {
3381
+
3382
+ float alpha = max( surf.sheenRoughness, 0.07 );
3383
+ alpha = alpha * alpha;
3384
+
3385
+ float maxSheenColor = max( max( surf.sheenColor.r, surf.sheenColor.g ), surf.sheenColor.b );
3386
+
3387
+ float eWo = directionalAlbedoSheen( saturateCos( wo.z ), alpha );
3388
+
3389
+ return 1.0 - maxSheenColor * eWo;
3390
+
3391
+ }
3392
+
3393
+ `;
3394
+
3395
+ const shaderIridescenceFunctions = /* glsl */`
3396
+
3397
+ // XYZ to sRGB color space
3398
+ const mat3 XYZ_TO_REC709 = mat3(
3399
+ 3.2404542, -0.9692660, 0.0556434,
3400
+ -1.5371385, 1.8760108, -0.2040259,
3401
+ -0.4985314, 0.0415560, 1.0572252
3402
+ );
3403
+
3404
+ vec3 fresnel0ToIor( vec3 fresnel0 ) {
3405
+
3406
+ vec3 sqrtF0 = sqrt( fresnel0 );
3407
+ return ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );
3408
+
3409
+ }
3410
+
3411
+ // Conversion FO/IOR
3412
+ vec3 iorToFresnel0( vec3 transmittedIor, float incidentIor ) {
3413
+
3414
+ return square( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );
3415
+
3416
+ }
3417
+
3418
+ // ior is a value between 1.0 and 3.0. 1.0 is air interface
3419
+ float iorToFresnel0( float transmittedIor, float incidentIor ) {
3420
+
3421
+ return square( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ) );
3422
+
3423
+ }
3424
+
3425
+ // Fresnel equations for dielectric/dielectric interfaces. See https://belcour.github.io/blog/research/2017/05/01/brdf-thin-film.html
3426
+ vec3 evalSensitivity( float OPD, vec3 shift ) {
3427
+
3428
+ float phase = 2.0 * PI * OPD * 1.0e-9;
3429
+
3430
+ vec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );
3431
+ vec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );
3432
+ vec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );
3433
+
3434
+ vec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - square( phase ) * var );
3435
+ xyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * square( phase ) );
3436
+ xyz /= 1.0685e-7;
3437
+
3438
+ vec3 srgb = XYZ_TO_REC709 * xyz;
3439
+ return srgb;
3440
+
3441
+ }
3442
+
3443
+ // See Section 4. Analytic Spectral Integration, A Practical Extension to Microfacet Theory for the Modeling of Varying Iridescence, https://hal.archives-ouvertes.fr/hal-01518344/document
3444
+ vec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {
3445
+
3446
+ vec3 I;
3447
+
3448
+ // Force iridescenceIor -> outsideIOR when thinFilmThickness -> 0.0
3449
+ float iridescenceIor = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );
3450
+
3451
+ // Evaluate the cosTheta on the base layer (Snell law)
3452
+ float sinTheta2Sq = square( outsideIOR / iridescenceIor ) * ( 1.0 - square( cosTheta1 ) );
3453
+
3454
+ // Handle TIR:
3455
+ float cosTheta2Sq = 1.0 - sinTheta2Sq;
3456
+ if ( cosTheta2Sq < 0.0 ) {
3457
+
3458
+ return vec3( 1.0 );
3459
+
3460
+ }
3461
+
3462
+ float cosTheta2 = sqrt( cosTheta2Sq );
3463
+
3464
+ // First interface
3465
+ float R0 = iorToFresnel0( iridescenceIor, outsideIOR );
3466
+ float R12 = schlickFresnel( cosTheta1, R0 );
3467
+ float R21 = R12;
3468
+ float T121 = 1.0 - R12;
3469
+ float phi12 = 0.0;
3470
+ if ( iridescenceIor < outsideIOR ) {
3471
+
3472
+ phi12 = PI;
3473
+
3474
+ }
3475
+ float phi21 = PI - phi12;
3476
+
3477
+ // Second interface
3478
+ vec3 baseIOR = fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) ); // guard against 1.0
3479
+ vec3 R1 = iorToFresnel0( baseIOR, iridescenceIor );
3480
+ vec3 R23 = schlickFresnel( cosTheta2, R1 );
3481
+ vec3 phi23 = vec3( 0.0 );
3482
+ if ( baseIOR[0] < iridescenceIor ) {
3483
+
3484
+ phi23[ 0 ] = PI;
3485
+
3486
+ }
3487
+ if ( baseIOR[1] < iridescenceIor ) {
3488
+
3489
+ phi23[ 1 ] = PI;
3490
+
3491
+ }
3492
+ if ( baseIOR[2] < iridescenceIor ) {
3493
+
3494
+ phi23[ 2 ] = PI;
3495
+
3496
+ }
3497
+
3498
+ // Phase shift
3499
+ float OPD = 2.0 * iridescenceIor * thinFilmThickness * cosTheta2;
3500
+ vec3 phi = vec3( phi21 ) + phi23;
3501
+
3502
+ // Compound terms
3503
+ vec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );
3504
+ vec3 r123 = sqrt( R123 );
3505
+ vec3 Rs = square( T121 ) * R23 / ( vec3( 1.0 ) - R123 );
3506
+
3507
+ // Reflectance term for m = 0 (DC term amplitude)
3508
+ vec3 C0 = R12 + Rs;
3509
+ I = C0;
3510
+
3511
+ // Reflectance term for m > 0 (pairs of diracs)
3512
+ vec3 Cm = Rs - T121;
3513
+ for ( int m = 1; m <= 2; ++ m )
3514
+ {
3515
+ Cm *= r123;
3516
+ vec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );
3517
+ I += Cm * Sm;
3518
+ }
3519
+
3520
+ // Since out of gamut colors might be produced, negative color values are clamped to 0.
3521
+ return max( I, vec3( 0.0 ) );
3522
+ }
3523
+
2062
3524
  `;
2063
3525
 
2064
3526
  const shaderMaterialSampling = /* glsl */`
@@ -2074,15 +3536,29 @@ struct SurfaceRec {
2074
3536
  vec3 emission;
2075
3537
  float transmission;
2076
3538
  float ior;
3539
+ float clearcoat;
3540
+ float clearcoatRoughness;
3541
+ float filteredClearcoatRoughness;
3542
+ vec3 sheenColor;
3543
+ float sheenRoughness;
3544
+ float iridescence;
3545
+ float iridescenceIor;
3546
+ float iridescenceThickness;
3547
+ vec3 specularColor;
3548
+ float specularIntensity;
2077
3549
  };
2078
3550
 
2079
3551
  struct SampleRec {
3552
+ float specularPdf;
2080
3553
  float pdf;
2081
3554
  vec3 direction;
3555
+ vec3 clearcoatDirection;
2082
3556
  vec3 color;
2083
3557
  };
2084
3558
 
2085
3559
  ${ shaderGGXFunctions }
3560
+ ${ shaderSheenFunctions }
3561
+ ${ shaderIridescenceFunctions }
2086
3562
 
2087
3563
  // diffuse
2088
3564
  float diffusePDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
@@ -2117,10 +3593,15 @@ vec3 diffuseColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
2117
3593
  // specular
2118
3594
  float specularPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
2119
3595
 
2120
- // See equation (17) in http://jcgt.org/published/0003/02/03/
3596
+ // See 14.1.1 Microfacet BxDFs in https://www.pbr-book.org/
2121
3597
  float filteredRoughness = surf.filteredRoughness;
2122
3598
  vec3 halfVector = getHalfVector( wi, wo );
2123
- return ggxPDF( wi, halfVector, filteredRoughness ) / ( 4.0 * dot( wi, halfVector ) );
3599
+
3600
+ float incidentTheta = acos( wo.z );
3601
+ float D = ggxDistribution( halfVector, filteredRoughness );
3602
+ float G1 = ggxShadowMaskG1( incidentTheta, filteredRoughness );
3603
+ float ggxPdf = D * G1 * max( 0.0, abs( dot( wo, halfVector ) ) ) / abs ( wo.z );
3604
+ return ggxPdf / ( 4.0 * dot( wo, halfVector ) );
2124
3605
 
2125
3606
  }
2126
3607
 
@@ -2153,21 +3634,25 @@ vec3 specularColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
2153
3634
  float iorRatio = frontFace ? 1.0 / ior : ior;
2154
3635
  float G = ggxShadowMaskG2( wi, wo, filteredRoughness );
2155
3636
  float D = ggxDistribution( halfVector, filteredRoughness );
3637
+ vec3 F = vec3( schlickFresnelFromIor( dot( wi, halfVector ), iorRatio ) ) * surf.specularColor * surf.specularIntensity;
2156
3638
 
2157
- float F = schlickFresnelFromIor( dot( wi, halfVector ), iorRatio );
2158
3639
  float cosTheta = min( wo.z, 1.0 );
2159
3640
  float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
2160
3641
  bool cannotRefract = iorRatio * sinTheta > 1.0;
2161
3642
  if ( cannotRefract ) {
2162
3643
 
2163
- F = 1.0;
3644
+ F = vec3( 1.0 );
2164
3645
 
2165
3646
  }
2166
3647
 
3648
+ float f0 = pow( ( iorRatio - 1.0 ) / ( iorRatio + 1.0 ), 2.0 );
3649
+ vec3 iridescenceFresnel = evalIridescence( 1.0, surf.iridescenceIor, dot( wi, halfVector ), surf.iridescenceThickness, vec3( f0 ) );
3650
+ F = mix( F, iridescenceFresnel, surf.iridescence );
3651
+
2167
3652
  vec3 color = mix( vec3( 1.0 ), surf.color, metalness );
2168
3653
  color = mix( color, vec3( 1.0 ), F );
2169
3654
  color *= G * D / ( 4.0 * abs( wi.z * wo.z ) );
2170
- color *= mix( F, 1.0, metalness );
3655
+ color *= mix( F, vec3( 1.0 ), metalness );
2171
3656
  color *= wi.z; // scale the light by the direction the light is coming in from
2172
3657
 
2173
3658
  return color;
@@ -2274,198 +3759,603 @@ vec3 transmissionColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
2274
3759
 
2275
3760
  }
2276
3761
 
2277
- float bsdfPdf( vec3 wo, vec3 wi, SurfaceRec surf ) {
3762
+ // clearcoat
3763
+ float clearcoatPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
3764
+
3765
+ // See equation (27) in http://jcgt.org/published/0003/02/03/
3766
+ float filteredClearcoatRoughness = surf.filteredClearcoatRoughness;
3767
+ vec3 halfVector = getHalfVector( wi, wo );
3768
+ return ggxPDF( wo, halfVector, filteredClearcoatRoughness ) / ( 4.0 * dot( wi, halfVector ) );
3769
+
3770
+ }
3771
+
3772
+ vec3 clearcoatDirection( vec3 wo, SurfaceRec surf ) {
3773
+
3774
+ // sample ggx vndf distribution which gives a new normal
3775
+ float filteredClearcoatRoughness = surf.filteredClearcoatRoughness;
3776
+ vec3 halfVector = ggxDirection(
3777
+ wo,
3778
+ filteredClearcoatRoughness,
3779
+ filteredClearcoatRoughness,
3780
+ rand(),
3781
+ rand()
3782
+ );
3783
+
3784
+ // apply to new ray by reflecting off the new normal
3785
+ return - reflect( wo, halfVector );
3786
+
3787
+ }
3788
+
3789
+ void clearcoatColor( inout vec3 color, vec3 wo, vec3 wi, SurfaceRec surf ) {
3790
+
3791
+ float ior = 1.5;
3792
+ bool frontFace = surf.frontFace;
3793
+ float filteredClearcoatRoughness = surf.filteredClearcoatRoughness;
3794
+
3795
+ vec3 halfVector = getHalfVector( wo, wi );
3796
+ float iorRatio = frontFace ? 1.0 / ior : ior;
3797
+ float G = ggxShadowMaskG2( wi, wo, filteredClearcoatRoughness );
3798
+ float D = ggxDistribution( halfVector, filteredClearcoatRoughness );
3799
+
3800
+ float F = schlickFresnelFromIor( dot( wi, halfVector ), ior );
3801
+ float cosTheta = min( wo.z, 1.0 );
3802
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
3803
+ bool cannotRefract = iorRatio * sinTheta > 1.0;
3804
+ if ( cannotRefract ) {
3805
+
3806
+ F = 1.0;
3807
+
3808
+ }
3809
+
3810
+ float fClearcoat = F * D * G / ( 4.0 * abs( wi.z * wo.z ) );
3811
+
3812
+ color = color * ( 1.0 - surf.clearcoat * F ) + fClearcoat * surf.clearcoat * wi.z;
3813
+
3814
+ }
3815
+
3816
+ // sheen
3817
+ vec3 sheenColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
3818
+
3819
+ vec3 halfVector = getHalfVector( wo, wi );
3820
+
3821
+ float cosThetaO = saturateCos( wo.z );
3822
+ float cosThetaI = saturateCos( wi.z );
3823
+ float cosThetaH = halfVector.z;
3824
+
3825
+ float D = velvetD( cosThetaH, surf.sheenRoughness );
3826
+ float G = velvetG( cosThetaO, cosThetaI, surf.sheenRoughness );
3827
+
3828
+ // See equation (1) in http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf
3829
+ vec3 color = surf.sheenColor;
3830
+ color *= D * G / ( 4.0 * abs( cosThetaO * cosThetaI ) );
3831
+ color *= wi.z;
3832
+
3833
+ return color;
3834
+
3835
+ }
3836
+
3837
+ // bsdf
3838
+ void getLobeWeights( vec3 wo, vec3 clearcoatWo, SurfaceRec surf, out float diffuseWeight, out float specularWeight, out float transmissionWeight, out float clearcoatWeight ) {
3839
+
3840
+ float ior = surf.ior;
3841
+ float metalness = surf.metalness;
3842
+ float transmission = surf.transmission;
3843
+ bool frontFace = surf.frontFace;
3844
+
3845
+ float ratio = frontFace ? 1.0 / ior : ior;
3846
+ float cosTheta = min( wo.z, 1.0 );
3847
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
3848
+ float reflectance = schlickFresnelFromIor( cosTheta, ratio );
3849
+ bool cannotRefract = ratio * sinTheta > 1.0;
3850
+ if ( cannotRefract ) {
3851
+
3852
+ reflectance = 1.0;
3853
+
3854
+ }
3855
+
3856
+ float transSpecularProb = mix( reflectance, 1.0, metalness );
3857
+ float diffSpecularProb = 0.5 + 0.5 * metalness;
3858
+
3859
+ clearcoatWeight = surf.clearcoat * schlickFresnel( clearcoatWo.z, 0.04 );
3860
+ diffuseWeight = ( 1.0 - transmission ) * ( 1.0 - diffSpecularProb ) * ( 1.0 - clearcoatWeight );
3861
+ specularWeight = transmission * transSpecularProb + ( 1.0 - transmission ) * diffSpecularProb * ( 1.0 - clearcoatWeight );
3862
+ transmissionWeight = transmission * ( 1.0 - transSpecularProb ) * ( 1.0 - clearcoatWeight );
3863
+
3864
+ float totalWeight = diffuseWeight + specularWeight + transmissionWeight + clearcoatWeight;
3865
+ float invTotalWeight = 1.0 / totalWeight;
3866
+
3867
+ diffuseWeight *= invTotalWeight;
3868
+ specularWeight *= invTotalWeight;
3869
+ transmissionWeight *= invTotalWeight;
3870
+ clearcoatWeight *= invTotalWeight;
3871
+
3872
+ }
3873
+
3874
+ float bsdfPdf( vec3 wo, vec3 clearcoatWo, vec3 wi, vec3 clearcoatWi, SurfaceRec surf, out float specularPdf, float diffuseWeight, float specularWeight, float transmissionWeight, float clearcoatWeight ) {
3875
+
3876
+ float ior = surf.ior;
3877
+ float metalness = surf.metalness;
3878
+ float transmission = surf.transmission;
3879
+ bool frontFace = surf.frontFace;
3880
+
3881
+ float ratio = frontFace ? 1.0 / ior : ior;
3882
+ float cosTheta = min( wo.z, 1.0 );
3883
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
3884
+ float reflectance = schlickFresnelFromIor( cosTheta, ratio );
3885
+ bool cannotRefract = ratio * sinTheta > 1.0;
3886
+ if ( cannotRefract ) {
3887
+
3888
+ reflectance = 1.0;
3889
+
3890
+ }
3891
+
3892
+ float spdf = 0.0;
3893
+ float dpdf = 0.0;
3894
+ float tpdf = 0.0;
3895
+ float cpdf = 0.0;
3896
+
3897
+ if ( wi.z < 0.0 ) {
3898
+
3899
+ if( transmissionWeight > 0.0 ) {
3900
+
3901
+ tpdf = transmissionPDF( wo, wi, surf );
3902
+
3903
+ }
3904
+
3905
+ } else {
3906
+
3907
+ if( diffuseWeight > 0.0 ) {
3908
+
3909
+ dpdf = diffusePDF( wo, wi, surf );
3910
+
3911
+ }
3912
+
3913
+ if( specularWeight > 0.0 ) {
3914
+
3915
+ spdf = specularPDF( wo, wi, surf );
3916
+
3917
+ }
3918
+
3919
+ }
3920
+
3921
+ if( clearcoatWi.z >= 0.0 && clearcoatWeight > 0.0 ) {
3922
+
3923
+ cpdf = clearcoatPDF( clearcoatWo, clearcoatWi, surf );
3924
+
3925
+ }
3926
+
3927
+ float pdf =
3928
+ dpdf * diffuseWeight
3929
+ + spdf * specularWeight
3930
+ + tpdf * transmissionWeight
3931
+ + cpdf * clearcoatWeight;
3932
+
3933
+ // retrieve specular rays for the shadows flag
3934
+ specularPdf = spdf * specularWeight + cpdf * clearcoatWeight;
3935
+
3936
+ return pdf;
3937
+
3938
+ }
3939
+
3940
+ vec3 bsdfColor( vec3 wo, vec3 clearcoatWo, vec3 wi, vec3 clearcoatWi, SurfaceRec surf, float diffuseWeight, float specularWeight, float transmissionWeight, float clearcoatWeight ) {
3941
+
3942
+ vec3 color = vec3( 0.0 );
3943
+ if ( wi.z < 0.0 ) {
3944
+
3945
+ if( transmissionWeight > 0.0 ) {
3946
+
3947
+ color = transmissionColor( wo, wi, surf );
3948
+
3949
+ }
3950
+
3951
+ } else {
3952
+
3953
+ if( diffuseWeight > 0.0 ) {
3954
+
3955
+ color = diffuseColor( wo, wi, surf );
3956
+ color *= 1.0 - surf.transmission;
3957
+
3958
+ }
3959
+
3960
+ if( specularWeight > 0.0 ) {
3961
+
3962
+ color += specularColor( wo, wi, surf );
3963
+
3964
+ }
3965
+
3966
+ color *= sheenAlbedoScaling( wo, wi, surf );
3967
+ color += sheenColor( wo, wi, surf );
3968
+
3969
+ }
3970
+
3971
+ if( clearcoatWi.z >= 0.0 && clearcoatWeight > 0.0 ) {
3972
+
3973
+ clearcoatColor( color, clearcoatWo, clearcoatWi, surf );
3974
+
3975
+ }
3976
+
3977
+ return color;
3978
+
3979
+ }
3980
+
3981
+ float bsdfResult( vec3 wo, vec3 clearcoatWo, vec3 wi, vec3 clearcoatWi, SurfaceRec surf, out vec3 color ) {
3982
+
3983
+ float diffuseWeight;
3984
+ float specularWeight;
3985
+ float transmissionWeight;
3986
+ float clearcoatWeight;
3987
+ getLobeWeights( wo, clearcoatWo, surf, diffuseWeight, specularWeight, transmissionWeight, clearcoatWeight );
3988
+
3989
+ float specularPdf;
3990
+ color = bsdfColor( wo, clearcoatWo, wi, clearcoatWi, surf, diffuseWeight, specularWeight, transmissionWeight, clearcoatWeight );
3991
+ return bsdfPdf( wo, clearcoatWo, wi, clearcoatWi, surf, specularPdf, diffuseWeight, specularWeight, transmissionWeight, clearcoatWeight );
3992
+
3993
+ }
3994
+
3995
+ SampleRec bsdfSample( vec3 wo, vec3 clearcoatWo, mat3 normalBasis, mat3 invBasis, mat3 clearcoatNormalBasis, mat3 clearcoatInvBasis, SurfaceRec surf ) {
3996
+
3997
+ float diffuseWeight;
3998
+ float specularWeight;
3999
+ float transmissionWeight;
4000
+ float clearcoatWeight;
4001
+ getLobeWeights( wo, clearcoatWo, surf, diffuseWeight, specularWeight, transmissionWeight, clearcoatWeight );
4002
+
4003
+ float pdf[4];
4004
+ pdf[0] = diffuseWeight;
4005
+ pdf[1] = specularWeight;
4006
+ pdf[2] = transmissionWeight;
4007
+ pdf[3] = clearcoatWeight;
4008
+
4009
+ float cdf[4];
4010
+ cdf[0] = pdf[0];
4011
+ cdf[1] = pdf[1] + cdf[0];
4012
+ cdf[2] = pdf[2] + cdf[1];
4013
+ cdf[3] = pdf[3] + cdf[2];
4014
+
4015
+ if( cdf[3] != 0.0 ) {
4016
+
4017
+ float invMaxCdf = 1.0 / cdf[3];
4018
+ cdf[0] *= invMaxCdf;
4019
+ cdf[1] *= invMaxCdf;
4020
+ cdf[2] *= invMaxCdf;
4021
+ cdf[3] *= invMaxCdf;
4022
+
4023
+ } else {
4024
+
4025
+ cdf[0] = 1.0;
4026
+ cdf[1] = 0.0;
4027
+ cdf[2] = 0.0;
4028
+ cdf[3] = 0.0;
4029
+
4030
+ }
4031
+
4032
+ vec3 wi;
4033
+ vec3 clearcoatWi;
4034
+
4035
+ float r = rand();
4036
+ if ( r <= cdf[0] ) {
4037
+
4038
+ wi = diffuseDirection( wo, surf );
4039
+ clearcoatWi = normalize( clearcoatInvBasis * normalize( normalBasis * wi ) );
4040
+
4041
+ } else if ( r <= cdf[1] ) {
4042
+
4043
+ wi = specularDirection( wo, surf );
4044
+ clearcoatWi = normalize( clearcoatInvBasis * normalize( normalBasis * wi ) );
4045
+
4046
+ } else if ( r <= cdf[2] ) {
4047
+
4048
+ wi = transmissionDirection( wo, surf );
4049
+ clearcoatWi = normalize( clearcoatInvBasis * normalize( normalBasis * wi ) );
4050
+
4051
+ } else if ( r <= cdf[3] ) {
4052
+
4053
+ clearcoatWi = clearcoatDirection( clearcoatWo, surf );
4054
+ wi = normalize( invBasis * normalize( clearcoatNormalBasis * clearcoatWi ) );
4055
+
4056
+ }
4057
+
4058
+ SampleRec result;
4059
+ result.pdf = bsdfPdf( wo, clearcoatWo, wi, clearcoatWi, surf, result.specularPdf, diffuseWeight, specularWeight, transmissionWeight, clearcoatWeight );
4060
+ result.color = bsdfColor( wo, clearcoatWo, wi, clearcoatWi, surf, diffuseWeight, specularWeight, transmissionWeight, clearcoatWeight );
4061
+ result.direction = wi;
4062
+ result.clearcoatDirection = clearcoatWi;
4063
+
4064
+ return result;
4065
+
4066
+ }
4067
+ `;
4068
+
4069
+ const shaderEnvMapSampling = /* glsl */`
4070
+
4071
+ vec3 sampleEquirectEnvMapColor( vec3 direction, sampler2D map ) {
4072
+
4073
+ return texture2D( map, equirectDirectionToUv( direction ) ).rgb;
4074
+
4075
+ }
4076
+
4077
+ float envMapDirectionPdf( vec3 direction ) {
4078
+
4079
+ vec2 uv = equirectDirectionToUv( direction );
4080
+ float theta = uv.y * PI;
4081
+ float sinTheta = sin( theta );
4082
+ if ( sinTheta == 0.0 ) {
4083
+
4084
+ return 0.0;
4085
+
4086
+ }
4087
+
4088
+ return 1.0 / ( 2.0 * PI * PI * sinTheta );
4089
+
4090
+ }
4091
+
4092
+ float envMapSample( vec3 direction, EquirectHdrInfo info, out vec3 color ) {
4093
+
4094
+ vec2 uv = equirectDirectionToUv( direction );
4095
+ color = texture2D( info.map, uv ).rgb;
4096
+
4097
+ float totalSum = info.totalSumWhole + info.totalSumDecimal;
4098
+ float lum = colorToLuminance( color );
4099
+ ivec2 resolution = textureSize( info.map, 0 );
4100
+ float pdf = lum / totalSum;
4101
+
4102
+ return float( resolution.x * resolution.y ) * pdf * envMapDirectionPdf( direction );
4103
+
4104
+ }
4105
+
4106
+ float randomEnvMapSample( EquirectHdrInfo info, out vec3 color, out vec3 direction ) {
4107
+
4108
+ // sample env map cdf
4109
+ vec2 r = rand2();
4110
+ float v = texture2D( info.marginalWeights, vec2( r.x, 0.0 ) ).x;
4111
+ float u = texture2D( info.conditionalWeights, vec2( r.y, v ) ).x;
4112
+ vec2 uv = vec2( u, v );
4113
+
4114
+ vec3 derivedDirection = equirectUvToDirection( uv );
4115
+ direction = derivedDirection;
4116
+ color = texture2D( info.map, uv ).rgb;
4117
+
4118
+ float totalSum = info.totalSumWhole + info.totalSumDecimal;
4119
+ float lum = colorToLuminance( color );
4120
+ ivec2 resolution = textureSize( info.map, 0 );
4121
+ float pdf = lum / totalSum;
4122
+
4123
+ return float( resolution.x * resolution.y ) * pdf * envMapDirectionPdf( direction );
4124
+
4125
+ }
4126
+
4127
+ `;
4128
+
4129
+ const shaderLightSampling = /* glsl */`
4130
+
4131
+ float getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {
4132
+
4133
+ return smoothstep( coneCosine, penumbraCosine, angleCosine );
4134
+
4135
+ }
4136
+
4137
+ float getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {
4138
+
4139
+ // based upon Frostbite 3 Moving to Physically-based Rendering
4140
+ // page 32, equation 26: E[window1]
4141
+ // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
4142
+ float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), EPSILON );
4143
+
4144
+ if ( cutoffDistance > 0.0 ) {
4145
+
4146
+ distanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );
4147
+
4148
+ }
4149
+
4150
+ return distanceFalloff;
4151
+
4152
+ }
4153
+
4154
+ float getPhotometricAttenuation( sampler2DArray iesProfiles, int iesProfile, vec3 posToLight, vec3 lightDir, vec3 u, vec3 v ) {
4155
+
4156
+ float cosTheta = dot( posToLight, lightDir );
4157
+ float angle = acos( cosTheta ) * ( 1.0 / PI );
4158
+
4159
+ return texture2D( iesProfiles, vec3( 0.0, angle, iesProfile ) ).r;
4160
+
4161
+ }
4162
+
4163
+ struct LightSampleRec {
4164
+
4165
+ bool hit;
4166
+ float dist;
4167
+ vec3 direction;
4168
+ float pdf;
4169
+ vec3 emission;
4170
+ int type;
4171
+
4172
+ };
4173
+
4174
+ LightSampleRec lightsClosestHit( sampler2D lights, uint lightCount, vec3 rayOrigin, vec3 rayDirection ) {
4175
+
4176
+ LightSampleRec lightSampleRec;
4177
+ lightSampleRec.hit = false;
4178
+
4179
+ uint l;
4180
+ for ( l = 0u; l < lightCount; l ++ ) {
4181
+
4182
+ Light light = readLightInfo( lights, l );
4183
+
4184
+ vec3 u = light.u;
4185
+ vec3 v = light.v;
4186
+
4187
+ // check for backface
4188
+ vec3 normal = normalize( cross( u, v ) );
4189
+ if ( dot( normal, rayDirection ) < 0.0 ) {
4190
+ continue;
4191
+ }
4192
+
4193
+ u *= 1.0 / dot( u, u );
4194
+ v *= 1.0 / dot( v, v );
4195
+
4196
+ float dist;
4197
+
4198
+ if(
4199
+ ( light.type == RECT_AREA_LIGHT_TYPE && intersectsRectangle( light.position, normal, u, v, rayOrigin, rayDirection, dist ) ) ||
4200
+ ( light.type == CIRC_AREA_LIGHT_TYPE && intersectsCircle( light.position, normal, u, v, rayOrigin, rayDirection, dist ) )
4201
+ ) {
4202
+
4203
+ if ( dist < lightSampleRec.dist || ! lightSampleRec.hit ) {
4204
+
4205
+ float cosTheta = dot( rayDirection, normal );
4206
+
4207
+ lightSampleRec.hit = true;
4208
+ lightSampleRec.dist = dist;
4209
+ lightSampleRec.pdf = ( dist * dist ) / ( light.area * cosTheta );
4210
+ lightSampleRec.emission = light.color * light.intensity;
4211
+ lightSampleRec.direction = rayDirection;
4212
+ lightSampleRec.type = light.type;
4213
+
4214
+ }
2278
4215
 
2279
- float ior = surf.ior;
2280
- float metalness = surf.metalness;
2281
- float transmission = surf.transmission;
2282
- bool frontFace = surf.frontFace;
4216
+ } else if ( light.type == SPOT_LIGHT_TYPE ) {
2283
4217
 
2284
- float ratio = frontFace ? 1.0 / ior : ior;
2285
- float cosTheta = min( wo.z, 1.0 );
2286
- float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
2287
- float reflectance = schlickFresnelFromIor( cosTheta, ratio );
2288
- bool cannotRefract = ratio * sinTheta > 1.0;
2289
- if ( cannotRefract ) {
4218
+ // TODO: forward path tracing sampling needs to be made consistent with direct light sampling logic
4219
+ // float radius = light.radius;
4220
+ // vec3 lightNormal = normalize( cross( light.u, light.v ) );
4221
+ // float angle = acos( light.coneCos );
4222
+ // float angleTan = tan( angle );
4223
+ // float startDistance = radius / max( angleTan, EPSILON );
2290
4224
 
2291
- reflectance = 1.0;
4225
+ // u = light.u / radius;
4226
+ // v = light.v / radius;
2292
4227
 
2293
- }
4228
+ // if (
4229
+ // intersectsCircle( light.position - normal * startDistance, normal, u, v, rayOrigin, rayDirection, dist ) &&
4230
+ // ( dist < lightSampleRec.dist || ! lightSampleRec.hit )
4231
+ // ) {
2294
4232
 
2295
- float spdf = 0.0;
2296
- float dpdf = 0.0;
2297
- float tpdf = 0.0;
4233
+ // float cosTheta = dot( rayDirection, normal );
4234
+ // float spotAttenuation = light.iesProfile != - 1 ?
4235
+ // getPhotometricAttenuation( iesProfiles, light.iesProfile, rayDirection, normal, u, v )
4236
+ // : getSpotAttenuation( light.coneCos, light.penumbraCos, cosTheta );
2298
4237
 
2299
- if ( wi.z < 0.0 ) {
4238
+ // float distanceAttenuation = getDistanceAttenuation( dist, light.distance, light.decay );
2300
4239
 
2301
- tpdf = transmissionPDF( wo, wi, surf );
4240
+ // lightSampleRec.hit = true;
4241
+ // lightSampleRec.dist = dist;
4242
+ // lightSampleRec.direction = rayDirection;
4243
+ // lightSampleRec.emission = light.color * light.intensity * distanceAttenuation * spotAttenuation;
4244
+ // lightSampleRec.pdf = ( dist * dist ) / ( light.area * cosTheta );
2302
4245
 
2303
- } else {
4246
+ // }
2304
4247
 
2305
- spdf = specularPDF( wo, wi, surf );
2306
- dpdf = diffusePDF( wo, wi, surf );
4248
+ }
2307
4249
 
2308
4250
  }
2309
4251
 
2310
- float transSpecularProb = mix( reflectance, 1.0, metalness );
2311
- float diffSpecularProb = 0.5 + 0.5 * metalness;
2312
- float pdf =
2313
- spdf * transmission * transSpecularProb
2314
- + tpdf * transmission * ( 1.0 - transSpecularProb )
2315
- + spdf * ( 1.0 - transmission ) * diffSpecularProb
2316
- + dpdf * ( 1.0 - transmission ) * ( 1.0 - diffSpecularProb );
2317
-
2318
- return pdf;
4252
+ return lightSampleRec;
2319
4253
 
2320
4254
  }
2321
4255
 
2322
- vec3 bsdfColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
4256
+ LightSampleRec randomAreaLightSample( Light light, vec3 rayOrigin ) {
2323
4257
 
2324
- vec3 color = vec3( 0.0 );
2325
- if ( wi.z < 0.0 ) {
4258
+ LightSampleRec lightSampleRec;
4259
+ lightSampleRec.hit = true;
4260
+ lightSampleRec.type = light.type;
2326
4261
 
2327
- color = transmissionColor( wo, wi, surf );
4262
+ lightSampleRec.emission = light.color * light.intensity;
2328
4263
 
2329
- } else {
4264
+ vec3 randomPos;
4265
+ if( light.type == RECT_AREA_LIGHT_TYPE ) {
4266
+
4267
+ // rectangular area light
4268
+ randomPos = light.position + light.u * ( rand() - 0.5 ) + light.v * ( rand() - 0.5 );
2330
4269
 
2331
- color = diffuseColor( wo, wi, surf );
2332
- color *= 1.0 - surf.transmission;
4270
+ } else if( light.type == 1 ) {
2333
4271
 
2334
- color += specularColor( wo, wi, surf );
4272
+ // circular area light
4273
+ float r = 0.5 * sqrt( rand() );
4274
+ float theta = rand() * 2.0 * PI;
4275
+ float x = r * cos( theta );
4276
+ float y = r * sin( theta );
4277
+
4278
+ randomPos = light.position + light.u * x + light.v * y;
2335
4279
 
2336
4280
  }
2337
4281
 
2338
- return color;
4282
+ vec3 toLight = randomPos - rayOrigin;
4283
+ float lightDistSq = dot( toLight, toLight );
4284
+ lightSampleRec.dist = sqrt( lightDistSq );
2339
4285
 
2340
- }
4286
+ vec3 direction = toLight / lightSampleRec.dist;
4287
+ lightSampleRec.direction = direction;
2341
4288
 
2342
- float bsdfResult( vec3 wo, vec3 wi, SurfaceRec surf, out vec3 color ) {
4289
+ vec3 lightNormal = normalize( cross( light.u, light.v ) );
4290
+ lightSampleRec.pdf = lightDistSq / ( light.area * dot( direction, lightNormal ) );
2343
4291
 
2344
- color = bsdfColor( wo, wi, surf );
2345
- return bsdfPdf( wo, wi, surf );
4292
+ return lightSampleRec;
2346
4293
 
2347
4294
  }
2348
4295
 
2349
- SampleRec bsdfSample( vec3 wo, SurfaceRec surf ) {
4296
+ LightSampleRec randomSpotLightSample( Light light, sampler2DArray iesProfiles, vec3 rayOrigin ) {
2350
4297
 
2351
- float ior = surf.ior;
2352
- float metalness = surf.metalness;
2353
- float transmission = surf.transmission;
2354
- bool frontFace = surf.frontFace;
4298
+ float radius = light.radius * sqrt( rand() );
4299
+ float theta = rand() * 2.0 * PI;
4300
+ float x = radius * cos( theta );
4301
+ float y = radius * sin( theta );
2355
4302
 
2356
- float ratio = frontFace ? 1.0 / ior : ior;
2357
- float cosTheta = min( wo.z, 1.0 );
2358
- float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
2359
- float reflectance = schlickFresnelFromIor( cosTheta, ratio );
2360
- bool cannotRefract = ratio * sinTheta > 1.0;
2361
- if ( cannotRefract ) {
4303
+ vec3 u = light.u;
4304
+ vec3 v = light.v;
4305
+ vec3 normal = normalize( cross( u, v ) );
2362
4306
 
2363
- reflectance = 1.0;
4307
+ float angle = acos( light.coneCos );
4308
+ float angleTan = tan( angle );
4309
+ float startDistance = light.radius / max( angleTan, EPSILON );
2364
4310
 
2365
- }
4311
+ vec3 randomPos = light.position - normal * startDistance + u * x + v * y;
4312
+ vec3 toLight = randomPos - rayOrigin;
4313
+ float lightDistSq = dot( toLight, toLight );
4314
+ float dist = sqrt( lightDistSq );
2366
4315
 
2367
- SampleRec result;
2368
- if ( rand() < transmission ) {
4316
+ vec3 direction = toLight / max( dist, EPSILON );
4317
+ float cosTheta = dot( direction, normal );
2369
4318
 
2370
- float specularProb = mix( reflectance, 1.0, metalness );
2371
- if ( rand() < specularProb ) {
4319
+ float spotAttenuation = light.iesProfile != - 1 ?
4320
+ getPhotometricAttenuation( iesProfiles, light.iesProfile, direction, normal, u, v )
4321
+ : getSpotAttenuation( light.coneCos, light.penumbraCos, cosTheta );
2372
4322
 
2373
- result.direction = specularDirection( wo, surf );
4323
+ float distanceAttenuation = getDistanceAttenuation( dist, light.distance, light.decay );
4324
+ LightSampleRec lightSampleRec;
4325
+ lightSampleRec.hit = true;
4326
+ lightSampleRec.type = light.type;
4327
+ lightSampleRec.dist = dist;
4328
+ lightSampleRec.direction = direction;
4329
+ lightSampleRec.emission = light.color * light.intensity * distanceAttenuation * spotAttenuation;
2374
4330
 
2375
- } else {
4331
+ // TODO: this makes the result consistent between MIS and non MIS paths but at radius 0 the pdf is infinite
4332
+ // and the intensity of the light is not correct
4333
+ lightSampleRec.pdf = 1.0;
4334
+ // lightSampleRec.pdf = lightDistSq / ( light.area * cosTheta );
2376
4335
 
2377
- result.direction = transmissionDirection( wo, surf );
4336
+ return lightSampleRec;
2378
4337
 
2379
- }
4338
+ }
2380
4339
 
2381
- } else {
4340
+ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles, uint lightCount, vec3 rayOrigin ) {
2382
4341
 
2383
- float specularProb = 0.5 + 0.5 * metalness;
2384
- if ( rand() < specularProb ) {
4342
+ // pick a random light
4343
+ uint l = uint( rand() * float( lightCount ) );
4344
+ Light light = readLightInfo( lights, l );
2385
4345
 
2386
- result.direction = specularDirection( wo, surf );
4346
+ if ( light.type == SPOT_LIGHT_TYPE ) {
2387
4347
 
2388
- } else {
4348
+ return randomSpotLightSample( light, iesProfiles, rayOrigin );
2389
4349
 
2390
- result.direction = diffuseDirection( wo, surf );
4350
+ } else {
2391
4351
 
2392
- }
4352
+ // sample the light
4353
+ return randomAreaLightSample( light, rayOrigin );
2393
4354
 
2394
4355
  }
2395
4356
 
2396
- result.pdf = bsdfPdf( wo, result.direction, surf );
2397
- result.color = bsdfColor( wo, result.direction, surf );
2398
- return result;
2399
-
2400
4357
  }
2401
- `;
2402
-
2403
- const shaderEnvMapSampling = /* glsl */`
2404
-
2405
- vec3 sampleEquirectEnvMapColor( vec3 direction, sampler2D map ) {
2406
-
2407
- return texture2D( map, equirectDirectionToUv( direction ) ).rgb;
2408
-
2409
- }
2410
-
2411
- float envMapDirectionPdf( vec3 direction ) {
2412
-
2413
- vec2 uv = equirectDirectionToUv( direction );
2414
- float theta = uv.y * PI;
2415
- float sinTheta = sin( theta );
2416
- if ( sinTheta == 0.0 ) {
2417
-
2418
- return 0.0;
2419
-
2420
- }
2421
-
2422
- return 1.0 / ( 2.0 * PI * PI * sinTheta );
2423
-
2424
- }
2425
-
2426
- float envMapSample( vec3 direction, EquirectHdrInfo info, out vec3 color ) {
2427
-
2428
- vec2 uv = equirectDirectionToUv( direction );
2429
- color = texture2D( info.map, uv ).rgb;
2430
-
2431
- float totalSum = texture2D( info.totalSum, vec2( 0.0 ) ).r;
2432
- float lum = colorToLuminance( color );
2433
- ivec2 resolution = textureSize( info.map, 0 );
2434
- float pdf = lum / totalSum;
2435
-
2436
- return float( resolution.x * resolution.y ) * pdf * envMapDirectionPdf( direction );
2437
-
2438
- }
2439
-
2440
- float randomEnvMapSample( EquirectHdrInfo info, out vec3 color, out vec3 direction ) {
2441
-
2442
- // sample env map cdf
2443
- vec2 r = rand2();
2444
- float v = texture2D( info.marginalWeights, vec2( r.x, 0.0 ) ).x;
2445
- float u = texture2D( info.conditionalWeights, vec2( r.y, v ) ).x;
2446
- vec2 uv = vec2( u, v );
2447
-
2448
- vec3 derivedDirection = equirectUvToDirection( uv );
2449
- direction = derivedDirection;
2450
- color = texture2D( info.map, uv ).rgb;
2451
-
2452
- float totalSum = texture2D( info.totalSum, vec2( 0.0 ) ).r;
2453
- float lum = colorToLuminance( color );
2454
- ivec2 resolution = textureSize( info.map, 0 );
2455
- float pdf = lum / totalSum;
2456
-
2457
- return float( resolution.x * resolution.y ) * pdf * envMapDirectionPdf( direction );
2458
-
2459
- }
2460
-
2461
- float misHeuristic( float a, float b ) {
2462
-
2463
- float aa = a * a;
2464
- float bb = a * b;
2465
- return aa / ( bb + aa );
2466
-
2467
- }
2468
-
4358
+
2469
4359
  `;
2470
4360
 
2471
4361
  class PhysicalPathTracingMaterial extends MaterialBase {
@@ -2488,6 +4378,10 @@ float misHeuristic( float a, float b ) {
2488
4378
  FEATURE_DOF: 1,
2489
4379
  FEATURE_GRADIENT_BG: 0,
2490
4380
  TRANSPARENT_TRAVERSALS: 5,
4381
+ // 0 = Perspective
4382
+ // 1 = Orthographic
4383
+ // 2 = Equirectangular
4384
+ CAMERA_TYPE: 0,
2491
4385
  },
2492
4386
 
2493
4387
  uniforms: {
@@ -2503,10 +4397,12 @@ float misHeuristic( float a, float b ) {
2503
4397
  materialIndexAttribute: { value: new threeMeshBvh.UIntVertexAttributeTexture() },
2504
4398
  materials: { value: new MaterialsTexture() },
2505
4399
  textures: { value: new RenderTarget2DArray().texture },
4400
+ lights: { value: new LightsInfoUniformStruct() },
4401
+ iesProfiles: { value: new IESProfilesTexture().texture },
2506
4402
  cameraWorldMatrix: { value: new three.Matrix4() },
2507
4403
  invProjectionMatrix: { value: new three.Matrix4() },
2508
4404
  backgroundBlur: { value: 0.0 },
2509
- environmentIntensity: { value: 2.0 },
4405
+ environmentIntensity: { value: 1.0 },
2510
4406
  environmentRotation: { value: new three.Matrix3() },
2511
4407
  envMapInfo: { value: new EquirectHdrInfoUniform() },
2512
4408
 
@@ -2546,6 +4442,7 @@ float misHeuristic( float a, float b ) {
2546
4442
  ${ threeMeshBvh.shaderStructs }
2547
4443
  ${ threeMeshBvh.shaderIntersectFunction }
2548
4444
  ${ shaderMaterialStructs }
4445
+ ${ shaderLightStruct }
2549
4446
 
2550
4447
  ${ shaderUtils }
2551
4448
  ${ shaderMaterialSampling }
@@ -2582,6 +4479,10 @@ float misHeuristic( float a, float b ) {
2582
4479
  uniform int seed;
2583
4480
  uniform float opacity;
2584
4481
  uniform sampler2D materials;
4482
+ uniform LightsInfo lights;
4483
+ uniform sampler2DArray iesProfiles;
4484
+
4485
+ ${ shaderLightSampling }
2585
4486
 
2586
4487
  uniform EquirectHdrInfo envMapInfo;
2587
4488
 
@@ -2609,7 +4510,7 @@ float misHeuristic( float a, float b ) {
2609
4510
  }
2610
4511
 
2611
4512
  // step through multiple surface hits and accumulate color attenuation based on transmissive surfaces
2612
- bool attenuateHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, int traversals, out vec3 color ) {
4513
+ bool attenuateHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, int traversals, bool isShadowRay, out vec3 color ) {
2613
4514
 
2614
4515
  // hit results
2615
4516
  uvec4 faceIndices = uvec4( 0u );
@@ -2632,13 +4533,34 @@ float misHeuristic( float a, float b ) {
2632
4533
  uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
2633
4534
  Material material = readMaterialInfo( materials, materialIndex );
2634
4535
 
4536
+ // adjust the ray to the new surface
4537
+ bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
4538
+ vec3 point = rayOrigin + rayDirection * dist;
4539
+ vec3 absPoint = abs( point );
4540
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
4541
+ rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
4542
+
4543
+ if ( ! material.castShadow && isShadowRay ) {
4544
+
4545
+ continue;
4546
+
4547
+ }
4548
+
2635
4549
  // Opacity Test
2636
4550
 
2637
4551
  // albedo
2638
4552
  vec4 albedo = vec4( material.color, material.opacity );
2639
4553
  if ( material.map != - 1 ) {
2640
4554
 
2641
- albedo *= texture2D( textures, vec3( uv, material.map ) );
4555
+ vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
4556
+ albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
4557
+
4558
+ }
4559
+
4560
+ // alphaMap
4561
+ if ( material.alphaMap != -1 ) {
4562
+
4563
+ albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
2642
4564
 
2643
4565
  }
2644
4566
 
@@ -2646,7 +4568,8 @@ float misHeuristic( float a, float b ) {
2646
4568
  float transmission = material.transmission;
2647
4569
  if ( material.transmissionMap != - 1 ) {
2648
4570
 
2649
- transmission *= texture2D( textures, vec3( uv, material.transmissionMap ) ).r;
4571
+ vec3 uvPrime = material.transmissionMapTransform * vec3( uv, 1 );
4572
+ transmission *= texture2D( textures, vec3( uvPrime.xy, material.transmissionMap ) ).r;
2650
4573
 
2651
4574
  }
2652
4575
 
@@ -2654,7 +4577,8 @@ float misHeuristic( float a, float b ) {
2654
4577
  float metalness = material.metalness;
2655
4578
  if ( material.metalnessMap != - 1 ) {
2656
4579
 
2657
- metalness *= texture2D( textures, vec3( uv, material.metalnessMap ) ).b;
4580
+ vec3 uvPrime = material.metalnessMapTransform * vec3( uv, 1 );
4581
+ metalness *= texture2D( textures, vec3( uvPrime.xy, material.metalnessMap ) ).b;
2658
4582
 
2659
4583
  }
2660
4584
 
@@ -2679,19 +4603,12 @@ float misHeuristic( float a, float b ) {
2679
4603
  }
2680
4604
 
2681
4605
  // only attenuate on the way in
2682
- bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
2683
4606
  if ( isBelowSurface ) {
2684
4607
 
2685
- color *= albedo.rgb;
4608
+ color *= mix( vec3( 1.0 ), albedo.rgb, transmissionFactor );
2686
4609
 
2687
4610
  }
2688
4611
 
2689
- // adjust the ray to the new surface
2690
- vec3 point = rayOrigin + rayDirection * dist;
2691
- vec3 absPoint = abs( point );
2692
- float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
2693
- rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
2694
-
2695
4612
  } else {
2696
4613
 
2697
4614
  return false;
@@ -2704,15 +4621,16 @@ float misHeuristic( float a, float b ) {
2704
4621
 
2705
4622
  }
2706
4623
 
2707
- // returns whether the ray hit anything, not just the first surface. Could be optimized to not check the full hierarchy.
2708
- bool anyHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection ) {
4624
+ // returns whether the ray hit anything before a certain distance, not just the first surface. Could be optimized to not check the full hierarchy.
4625
+ bool anyCloserHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, float maxDist ) {
2709
4626
 
2710
4627
  uvec4 faceIndices = uvec4( 0u );
2711
4628
  vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
2712
4629
  vec3 barycoord = vec3( 0.0 );
2713
4630
  float side = 1.0;
2714
4631
  float dist = 0.0;
2715
- return bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
4632
+ bool hit = bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
4633
+ return hit && dist < maxDist;
2716
4634
 
2717
4635
  }
2718
4636
 
@@ -2724,33 +4642,56 @@ float misHeuristic( float a, float b ) {
2724
4642
 
2725
4643
  }
2726
4644
 
2727
- void main() {
4645
+ vec3 ndcToRayOrigin( vec2 coord ) {
2728
4646
 
2729
- rng_initialize( gl_FragCoord.xy, seed );
4647
+ vec4 rayOrigin4 = cameraWorldMatrix * invProjectionMatrix * vec4( coord, - 1.0, 1.0 );
4648
+ return rayOrigin4.xyz / rayOrigin4.w;
4649
+ }
2730
4650
 
2731
- // get [-1, 1] normalized device coordinates
2732
- vec2 ndc = 2.0 * vUv - vec2( 1.0 );
2733
- vec3 rayOrigin, rayDirection;
2734
- ndcToCameraRay( ndc, cameraWorldMatrix, invProjectionMatrix, rayOrigin, rayDirection );
4651
+ void getCameraRay( out vec3 rayDirection, out vec3 rayOrigin ) {
2735
4652
 
2736
- // Jitter the camera ray by finding a new subpixel point to point to from the camera origin
2737
- // This is better than just jittering the camera position since it actually results in divergent
2738
- // rays providing better coverage for the pixel
2739
- {
4653
+ vec2 ssd = vec2( 1.0 ) / resolution;
2740
4654
 
2741
- // TODO: the complexity here could be improved
2742
- vec3 cameraOrigin = ( cameraWorldMatrix * vec4( 0.0, 0.0, 0.0, 1.0 ) ).xyz;
2743
- vec3 ss00, ss01, ss10, temp;
2744
- ndcToCameraRay( vec2( - 1.0, - 1.0 ), cameraWorldMatrix, invProjectionMatrix, ss00, temp );
2745
- ndcToCameraRay( vec2( - 1.0, 1.0 ), cameraWorldMatrix, invProjectionMatrix, ss01, temp );
2746
- ndcToCameraRay( vec2( 1.0, - 1.0 ), cameraWorldMatrix, invProjectionMatrix, ss10, temp );
4655
+ // Jitter the camera ray by finding a uv coordinate at a random sample
4656
+ // around this pixel's UV coordinate
4657
+ vec2 jitteredUv = vUv + vec2( tentFilter( rand() ) * ssd.x, tentFilter( rand() ) * ssd.y );
2747
4658
 
2748
- vec3 ssdX = ( ss10 - ss00 ) / resolution.x;
2749
- vec3 ssdY = ( ss01 - ss00 ) / resolution.y;
2750
- rayOrigin += tentFilter( rand() ) * ssdX + tentFilter( rand() ) * ssdY;
2751
- rayDirection = normalize( rayOrigin - cameraOrigin );
4659
+ #if CAMERA_TYPE == 2
2752
4660
 
2753
- }
4661
+ // Equirectangular projection
4662
+
4663
+ vec4 rayDirection4 = vec4( equirectUvToDirection( jitteredUv ), 0.0 );
4664
+ vec4 rayOrigin4 = vec4( 0.0, 0.0, 0.0, 1.0 );
4665
+
4666
+ rayDirection4 = cameraWorldMatrix * rayDirection4;
4667
+ rayOrigin4 = cameraWorldMatrix * rayOrigin4;
4668
+
4669
+ rayDirection = normalize( rayDirection4.xyz );
4670
+ rayOrigin = rayOrigin4.xyz / rayOrigin4.w;
4671
+
4672
+ #else
4673
+
4674
+ // get [-1, 1] normalized device coordinates
4675
+ vec2 ndc = 2.0 * jitteredUv - vec2( 1.0 );
4676
+
4677
+ rayOrigin = ndcToRayOrigin( ndc );
4678
+
4679
+ #if CAMERA_TYPE == 1
4680
+
4681
+ // Orthographic projection
4682
+
4683
+ rayDirection = ( cameraWorldMatrix * vec4( 0.0, 0.0, -1.0, 0.0 ) ).xyz;
4684
+ rayDirection = normalize( rayDirection );
4685
+
4686
+ #else
4687
+
4688
+ // Perspective projection
4689
+
4690
+ rayDirection = normalize( mat3(cameraWorldMatrix) * ( invProjectionMatrix * vec4( ndc, 0.0, 1.0 ) ).xyz );
4691
+
4692
+ #endif
4693
+
4694
+ #endif
2754
4695
 
2755
4696
  #if FEATURE_DOF
2756
4697
  {
@@ -2777,8 +4718,20 @@ float misHeuristic( float a, float b ) {
2777
4718
 
2778
4719
  }
2779
4720
  #endif
4721
+
2780
4722
  rayDirection = normalize( rayDirection );
2781
4723
 
4724
+ }
4725
+
4726
+ void main() {
4727
+
4728
+ rng_initialize( gl_FragCoord.xy, seed );
4729
+
4730
+ vec3 rayDirection;
4731
+ vec3 rayOrigin;
4732
+
4733
+ getCameraRay( rayDirection, rayOrigin );
4734
+
2782
4735
  // inverse environment rotation
2783
4736
  mat3 invEnvironmentRotation = inverse( environmentRotation );
2784
4737
 
@@ -2795,15 +4748,56 @@ float misHeuristic( float a, float b ) {
2795
4748
 
2796
4749
  // path tracing state
2797
4750
  float accumulatedRoughness = 0.0;
4751
+ float accumulatedClearcoatRoughness = 0.0;
2798
4752
  bool transmissiveRay = true;
2799
4753
  int transparentTraversals = TRANSPARENT_TRAVERSALS;
2800
4754
  vec3 throughputColor = vec3( 1.0 );
2801
4755
  SampleRec sampleRec;
2802
4756
  int i;
4757
+ bool isShadowRay = false;
2803
4758
 
2804
4759
  for ( i = 0; i < bounces; i ++ ) {
2805
4760
 
2806
- if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
4761
+ bool hit = bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
4762
+
4763
+ LightSampleRec lightHit = lightsClosestHit( lights.tex, lights.count, rayOrigin, rayDirection );
4764
+
4765
+ if ( lightHit.hit && ( lightHit.dist < dist || !hit ) ) {
4766
+
4767
+ if ( i == 0 || transmissiveRay ) {
4768
+
4769
+ gl_FragColor.rgb += lightHit.emission * throughputColor;
4770
+
4771
+ } else {
4772
+
4773
+ #if FEATURE_MIS
4774
+
4775
+ // NOTE: we skip MIS for spotlights since we haven't fixed the forward
4776
+ // path tracing code path, yet
4777
+ if ( lightHit.type == SPOT_LIGHT_TYPE ) {
4778
+
4779
+ gl_FragColor.rgb += lightHit.emission * throughputColor;
4780
+
4781
+ } else {
4782
+
4783
+ // weight the contribution
4784
+ float misWeight = misHeuristic( sampleRec.pdf, lightHit.pdf / float( lights.count + 1u ) );
4785
+ gl_FragColor.rgb += lightHit.emission * throughputColor * misWeight;
4786
+
4787
+ }
4788
+
4789
+ #else
4790
+
4791
+ gl_FragColor.rgb += lightHit.emission * throughputColor;
4792
+
4793
+ #endif
4794
+
4795
+ }
4796
+ break;
4797
+
4798
+ }
4799
+
4800
+ if ( ! hit ) {
2807
4801
 
2808
4802
  if ( i == 0 || transmissiveRay ) {
2809
4803
 
@@ -2817,6 +4811,7 @@ float misHeuristic( float a, float b ) {
2817
4811
  // get the PDF of the hit envmap point
2818
4812
  vec3 envColor;
2819
4813
  float envPdf = envMapSample( environmentRotation * rayDirection, envMapInfo, envColor );
4814
+ envPdf /= float( lights.count + 1u );
2820
4815
 
2821
4816
  // and weight the contribution
2822
4817
  float misWeight = misHeuristic( sampleRec.pdf, envPdf );
@@ -2846,13 +4841,32 @@ float misHeuristic( float a, float b ) {
2846
4841
 
2847
4842
  }
2848
4843
 
2849
- vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
4844
+ // if we've determined that this is a shadow ray and we've hit an item with no shadow casting
4845
+ // then skip it
4846
+ if ( ! material.castShadow && isShadowRay ) {
4847
+
4848
+ vec3 point = rayOrigin + rayDirection * dist;
4849
+ vec3 absPoint = abs( point );
4850
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
4851
+ rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
4852
+
4853
+ continue;
2850
4854
 
4855
+ }
4856
+
4857
+ vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
2851
4858
  // albedo
2852
4859
  vec4 albedo = vec4( material.color, material.opacity );
2853
4860
  if ( material.map != - 1 ) {
2854
4861
 
2855
- albedo *= texture2D( textures, vec3( uv, material.map ) );
4862
+ vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
4863
+ albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
4864
+ }
4865
+
4866
+ // alphaMap
4867
+ if ( material.alphaMap != -1 ) {
4868
+
4869
+ albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
2856
4870
 
2857
4871
  }
2858
4872
 
@@ -2900,7 +4914,8 @@ float misHeuristic( float a, float b ) {
2900
4914
  float roughness = material.roughness;
2901
4915
  if ( material.roughnessMap != - 1 ) {
2902
4916
 
2903
- roughness *= texture2D( textures, vec3( uv, material.roughnessMap ) ).g;
4917
+ vec3 uvPrime = material.roughnessMapTransform * vec3( uv, 1 );
4918
+ roughness *= texture2D( textures, vec3( uvPrime.xy, material.roughnessMap ) ).g;
2904
4919
 
2905
4920
  }
2906
4921
 
@@ -2908,7 +4923,8 @@ float misHeuristic( float a, float b ) {
2908
4923
  float metalness = material.metalness;
2909
4924
  if ( material.metalnessMap != - 1 ) {
2910
4925
 
2911
- metalness *= texture2D( textures, vec3( uv, material.metalnessMap ) ).b;
4926
+ vec3 uvPrime = material.metalnessMapTransform * vec3( uv, 1 );
4927
+ metalness *= texture2D( textures, vec3( uvPrime.xy, material.metalnessMap ) ).b;
2912
4928
 
2913
4929
  }
2914
4930
 
@@ -2916,7 +4932,8 @@ float misHeuristic( float a, float b ) {
2916
4932
  vec3 emission = material.emissiveIntensity * material.emissive;
2917
4933
  if ( material.emissiveMap != - 1 ) {
2918
4934
 
2919
- emission *= texture2D( textures, vec3( uv, material.emissiveMap ) ).xyz;
4935
+ vec3 uvPrime = material.emissiveMapTransform * vec3( uv, 1 );
4936
+ emission *= texture2D( textures, vec3( uvPrime.xy, material.emissiveMap ) ).xyz;
2920
4937
 
2921
4938
  }
2922
4939
 
@@ -2924,11 +4941,13 @@ float misHeuristic( float a, float b ) {
2924
4941
  float transmission = material.transmission;
2925
4942
  if ( material.transmissionMap != - 1 ) {
2926
4943
 
2927
- transmission *= texture2D( textures, vec3( uv, material.transmissionMap ) ).r;
4944
+ vec3 uvPrime = material.transmissionMapTransform * vec3( uv, 1 );
4945
+ transmission *= texture2D( textures, vec3( uvPrime.xy, material.transmissionMap ) ).r;
2928
4946
 
2929
4947
  }
2930
4948
 
2931
4949
  // normal
4950
+ vec3 baseNormal = normal;
2932
4951
  if ( material.normalMap != - 1 ) {
2933
4952
 
2934
4953
  vec4 tangentSample = textureSampleBarycoord(
@@ -2945,7 +4964,8 @@ float misHeuristic( float a, float b ) {
2945
4964
  vec3 bitangent = normalize( cross( normal, tangent ) * tangentSample.w );
2946
4965
  mat3 vTBN = mat3( tangent, bitangent, normal );
2947
4966
 
2948
- vec3 texNormal = texture2D( textures, vec3( uv, material.normalMap ) ).xyz * 2.0 - 1.0;
4967
+ vec3 uvPrime = material.normalMapTransform * vec3( uv, 1 );
4968
+ vec3 texNormal = texture2D( textures, vec3( uvPrime.xy, material.normalMap ) ).xyz * 2.0 - 1.0;
2949
4969
  texNormal.xy *= material.normalScale;
2950
4970
  normal = vTBN * texNormal;
2951
4971
 
@@ -2955,6 +4975,110 @@ float misHeuristic( float a, float b ) {
2955
4975
 
2956
4976
  normal *= side;
2957
4977
 
4978
+ // clearcoat
4979
+ float clearcoat = material.clearcoat;
4980
+ if ( material.clearcoatMap != - 1 ) {
4981
+
4982
+ vec3 uvPrime = material.clearcoatMapTransform * vec3( uv, 1 );
4983
+ clearcoat *= texture2D( textures, vec3( uvPrime.xy, material.clearcoatMap ) ).r;
4984
+
4985
+ }
4986
+
4987
+ // clearcoatRoughness
4988
+ float clearcoatRoughness = material.clearcoatRoughness;
4989
+ if ( material.clearcoatRoughnessMap != - 1 ) {
4990
+
4991
+ vec3 uvPrime = material.clearcoatRoughnessMapTransform * vec3( uv, 1 );
4992
+ clearcoat *= texture2D( textures, vec3( uvPrime.xy, material.clearcoatRoughnessMap ) ).g;
4993
+
4994
+ }
4995
+
4996
+ // clearcoatNormal
4997
+ vec3 clearcoatNormal = baseNormal;
4998
+ if ( material.clearcoatNormalMap != - 1 ) {
4999
+
5000
+ vec4 tangentSample = textureSampleBarycoord(
5001
+ tangentAttribute,
5002
+ barycoord,
5003
+ faceIndices.xyz
5004
+ );
5005
+
5006
+ // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
5007
+ // resulting in NaNs and slow path tracing.
5008
+ if ( length( tangentSample.xyz ) > 0.0 ) {
5009
+
5010
+ vec3 tangent = normalize( tangentSample.xyz );
5011
+ vec3 bitangent = normalize( cross( clearcoatNormal, tangent ) * tangentSample.w );
5012
+ mat3 vTBN = mat3( tangent, bitangent, clearcoatNormal );
5013
+
5014
+ vec3 uvPrime = material.clearcoatNormalMapTransform * vec3( uv, 1 );
5015
+ vec3 texNormal = texture2D( textures, vec3( uvPrime.xy, material.clearcoatNormalMap ) ).xyz * 2.0 - 1.0;
5016
+ texNormal.xy *= material.clearcoatNormalScale;
5017
+ clearcoatNormal = vTBN * texNormal;
5018
+
5019
+ }
5020
+
5021
+ }
5022
+
5023
+ clearcoatNormal *= side;
5024
+
5025
+ // sheenColor
5026
+ vec3 sheenColor = material.sheenColor;
5027
+ if ( material.sheenColorMap != - 1 ) {
5028
+
5029
+ vec3 uvPrime = material.sheenColorMapTransform * vec3( uv, 1 );
5030
+ sheenColor *= texture2D( textures, vec3( uvPrime.xy, material.sheenColorMap ) ).rgb;
5031
+
5032
+ }
5033
+
5034
+ // sheenRoughness
5035
+ float sheenRoughness = material.sheenRoughness;
5036
+ if ( material.sheenRoughnessMap != - 1 ) {
5037
+
5038
+ vec3 uvPrime = material.sheenRoughnessMapTransform * vec3( uv, 1 );
5039
+ sheenRoughness *= texture2D( textures, vec3( uvPrime.xy, material.sheenRoughnessMap ) ).a;
5040
+
5041
+ }
5042
+
5043
+ // iridescence
5044
+ float iridescence = material.iridescence;
5045
+ if ( material.iridescenceMap != - 1 ) {
5046
+
5047
+ vec3 uvPrime = material.iridescenceMapTransform * vec3( uv, 1 );
5048
+ iridescence *= texture2D( textures, vec3( uvPrime.xy, material.iridescenceMap ) ).r;
5049
+
5050
+ }
5051
+
5052
+ // iridescence thickness
5053
+ float iridescenceThickness = material.iridescenceThicknessMaximum;
5054
+ if ( material.iridescenceThicknessMap != - 1 ) {
5055
+
5056
+ vec3 uvPrime = material.iridescenceThicknessMapTransform * vec3( uv, 1 );
5057
+ float iridescenceThicknessSampled = texture2D( textures, vec3( uvPrime.xy, material.iridescenceThicknessMap ) ).g;
5058
+ iridescenceThickness = mix( material.iridescenceThicknessMinimum, material.iridescenceThicknessMaximum, iridescenceThicknessSampled );
5059
+
5060
+ }
5061
+
5062
+ iridescence = iridescenceThickness == 0.0 ? 0.0 : iridescence;
5063
+
5064
+ // specular color
5065
+ vec3 specularColor = material.specularColor;
5066
+ if ( material.specularColorMap != - 1 ) {
5067
+
5068
+ vec3 uvPrime = material.specularColorMapTransform * vec3( uv, 1 );
5069
+ specularColor *= texture2D( textures, vec3( uvPrime.xy, material.specularColorMap ) ).rgb;
5070
+
5071
+ }
5072
+
5073
+ // specular intensity
5074
+ float specularIntensity = material.specularIntensity;
5075
+ if ( material.specularIntensityMap != - 1 ) {
5076
+
5077
+ vec3 uvPrime = material.specularIntensityMapTransform * vec3( uv, 1 );
5078
+ specularIntensity *= texture2D( textures, vec3( uvPrime.xy, material.specularIntensityMap ) ).a;
5079
+
5080
+ }
5081
+
2958
5082
  SurfaceRec surfaceRec;
2959
5083
  surfaceRec.normal = normal;
2960
5084
  surfaceRec.faceNormal = faceNormal;
@@ -2964,27 +5088,38 @@ float misHeuristic( float a, float b ) {
2964
5088
  surfaceRec.metalness = metalness;
2965
5089
  surfaceRec.color = albedo.rgb;
2966
5090
  surfaceRec.roughness = roughness;
5091
+ surfaceRec.clearcoat = clearcoat;
5092
+ surfaceRec.clearcoatRoughness = clearcoatRoughness;
5093
+ surfaceRec.sheenColor = sheenColor;
5094
+ surfaceRec.sheenRoughness = sheenRoughness;
5095
+ surfaceRec.iridescence = iridescence;
5096
+ surfaceRec.iridescenceIor = material.iridescenceIor;
5097
+ surfaceRec.iridescenceThickness = iridescenceThickness;
5098
+ surfaceRec.specularColor = specularColor;
5099
+ surfaceRec.specularIntensity = specularIntensity;
2967
5100
 
2968
5101
  // frontFace is used to determine transmissive properties and PDF. If no transmission is used
2969
5102
  // then we can just always assume this is a front face.
2970
5103
  surfaceRec.frontFace = side == 1.0 || transmission == 0.0;
2971
5104
 
2972
- // Compute the filtered roughness value to use during specular reflection computations. A minimum
2973
- // value of 1e-6 is needed because the GGX functions do not work with a roughness value of 0 and
2974
- // the accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
5105
+ // Compute the filtered roughness value to use during specular reflection computations.
5106
+ // The accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
2975
5107
  // If we're exiting something transmissive then scale the factor down significantly so we can retain
2976
5108
  // sharp internal reflections
2977
- surfaceRec.filteredRoughness = clamp(
2978
- max( surfaceRec.roughness, accumulatedRoughness * filterGlossyFactor * 5.0 ),
2979
- 1e-3,
2980
- 1.0
2981
- );
5109
+ surfaceRec.filteredRoughness = clamp( max( surfaceRec.roughness, accumulatedRoughness * filterGlossyFactor * 5.0 ), 0.0, 1.0 );
5110
+ surfaceRec.filteredClearcoatRoughness = clamp( max( surfaceRec.clearcoatRoughness, accumulatedClearcoatRoughness * filterGlossyFactor * 5.0 ), 0.0, 1.0 );
2982
5111
 
2983
5112
  mat3 normalBasis = getBasisFromNormal( surfaceRec.normal );
2984
5113
  mat3 invBasis = inverse( normalBasis );
2985
5114
 
5115
+ mat3 clearcoatNormalBasis = getBasisFromNormal( clearcoatNormal );
5116
+ mat3 clearcoatInvBasis = inverse( clearcoatNormalBasis );
5117
+
2986
5118
  vec3 outgoing = - normalize( invBasis * rayDirection );
2987
- sampleRec = bsdfSample( outgoing, surfaceRec );
5119
+ vec3 clearcoatOutgoing = - normalize( clearcoatInvBasis * rayDirection );
5120
+ sampleRec = bsdfSample( outgoing, clearcoatOutgoing, normalBasis, invBasis, clearcoatNormalBasis, clearcoatInvBasis, surfaceRec );
5121
+
5122
+ isShadowRay = sampleRec.specularPdf < rand();
2988
5123
 
2989
5124
  // adjust the hit point by the surface normal by a factor of some offset and the
2990
5125
  // maximum component-wise value of the current point to accommodate floating point
@@ -2999,7 +5134,43 @@ float misHeuristic( float a, float b ) {
2999
5134
 
3000
5135
  // direct env map sampling
3001
5136
  #if FEATURE_MIS
3002
- {
5137
+
5138
+ // uniformly pick a light or environment map
5139
+ if( rand() > 1.0 / float( lights.count + 1u ) ) {
5140
+
5141
+ // sample a light or environment
5142
+ LightSampleRec lightSampleRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin );
5143
+
5144
+ bool isSampleBelowSurface = dot( faceNormal, lightSampleRec.direction ) < 0.0;
5145
+ if ( isSampleBelowSurface ) {
5146
+
5147
+ lightSampleRec.pdf = 0.0;
5148
+
5149
+ }
5150
+
5151
+ // check if a ray could even reach the light area
5152
+ if (
5153
+ lightSampleRec.pdf > 0.0 &&
5154
+ isDirectionValid( lightSampleRec.direction, normal, faceNormal ) &&
5155
+ ! anyCloserHit( bvh, rayOrigin, lightSampleRec.direction, lightSampleRec.dist )
5156
+ ) {
5157
+
5158
+ // get the material pdf
5159
+ vec3 sampleColor;
5160
+ float lightMaterialPdf = bsdfResult( outgoing, clearcoatOutgoing, normalize( invBasis * lightSampleRec.direction ), normalize( clearcoatInvBasis * lightSampleRec.direction ), surfaceRec, sampleColor );
5161
+ bool isValidSampleColor = all( greaterThanEqual( sampleColor, vec3( 0.0 ) ) );
5162
+ if ( lightMaterialPdf > 0.0 && isValidSampleColor ) {
5163
+
5164
+ // weight the direct light contribution
5165
+ float lightPdf = lightSampleRec.pdf / float( lights.count + 1u );
5166
+ float misWeight = misHeuristic( lightPdf, lightMaterialPdf );
5167
+ gl_FragColor.rgb += lightSampleRec.emission * throughputColor * sampleColor * misWeight / lightPdf;
5168
+
5169
+ }
5170
+
5171
+ }
5172
+
5173
+ } else {
3003
5174
 
3004
5175
  // find a sample in the environment map to include in the contribution
3005
5176
  vec3 envColor, envDirection;
@@ -3021,15 +5192,17 @@ float misHeuristic( float a, float b ) {
3021
5192
  if (
3022
5193
  envPdf > 0.0 &&
3023
5194
  isDirectionValid( envDirection, normal, faceNormal ) &&
3024
- ! attenuateHit( bvh, rayOrigin, envDirection, bounces - i, attenuatedColor )
5195
+ ! attenuateHit( bvh, rayOrigin, envDirection, bounces - i, isShadowRay, attenuatedColor )
3025
5196
  ) {
3026
5197
 
3027
5198
  // get the material pdf
3028
5199
  vec3 sampleColor;
3029
- float envMaterialPdf = bsdfResult( outgoing, normalize( invBasis * envDirection ), surfaceRec, sampleColor );
3030
- if ( envMaterialPdf > 0.0 ) {
5200
+ float envMaterialPdf = bsdfResult( outgoing, clearcoatOutgoing, normalize( invBasis * envDirection ), normalize( clearcoatInvBasis * envDirection ), surfaceRec, sampleColor );
5201
+ bool isValidSampleColor = all( greaterThanEqual( sampleColor, vec3( 0.0 ) ) );
5202
+ if ( envMaterialPdf > 0.0 && isValidSampleColor ) {
3031
5203
 
3032
5204
  // weight the direct light contribution
5205
+ envPdf /= float( lights.count + 1u );
3033
5206
  float misWeight = misHeuristic( envPdf, envMaterialPdf );
3034
5207
  gl_FragColor.rgb += attenuatedColor * environmentIntensity * envColor * throughputColor * sampleColor * misWeight / envPdf;
3035
5208
 
@@ -3046,7 +5219,11 @@ float misHeuristic( float a, float b ) {
3046
5219
 
3047
5220
  // determine if this is a rough normal or not by checking how far off straight up it is
3048
5221
  vec3 halfVector = normalize( outgoing + sampleRec.direction );
3049
- accumulatedRoughness += sin( acos( halfVector.z ) );
5222
+ accumulatedRoughness += sin( acosApprox( halfVector.z ) );
5223
+
5224
+ vec3 clearcoatHalfVector = normalize( clearcoatOutgoing + sampleRec.clearcoatDirection );
5225
+ accumulatedClearcoatRoughness += sin( acosApprox( clearcoatHalfVector.z ) );
5226
+
3050
5227
  transmissiveRay = false;
3051
5228
 
3052
5229
  }
@@ -3090,7 +5267,11 @@ float misHeuristic( float a, float b ) {
3090
5267
 
3091
5268
  exports.BlurredEnvMapGenerator = BlurredEnvMapGenerator;
3092
5269
  exports.DynamicPathTracingSceneGenerator = DynamicPathTracingSceneGenerator;
5270
+ exports.EquirectCamera = EquirectCamera;
3093
5271
  exports.EquirectHdrInfoUniform = EquirectHdrInfoUniform;
5272
+ exports.IESLoader = IESLoader;
5273
+ exports.IESProfilesTexture = IESProfilesTexture;
5274
+ exports.LightsInfoUniformStruct = LightsInfoUniformStruct;
3094
5275
  exports.MaterialBase = MaterialBase;
3095
5276
  exports.MaterialReducer = MaterialReducer;
3096
5277
  exports.MaterialsTexture = MaterialsTexture;
@@ -3099,10 +5280,13 @@ float misHeuristic( float a, float b ) {
3099
5280
  exports.PhysicalCamera = PhysicalCamera;
3100
5281
  exports.PhysicalCameraUniform = PhysicalCameraUniform;
3101
5282
  exports.PhysicalPathTracingMaterial = PhysicalPathTracingMaterial;
5283
+ exports.PhysicalSpotLight = PhysicalSpotLight;
3102
5284
  exports.RenderTarget2DArray = RenderTarget2DArray;
5285
+ exports.ShapedAreaLight = ShapedAreaLight;
3103
5286
  exports.getGroupMaterialIndicesAttribute = getGroupMaterialIndicesAttribute;
3104
5287
  exports.mergeMeshes = mergeMeshes;
3105
5288
  exports.setCommonAttributes = setCommonAttributes;
5289
+ exports.shaderLightStruct = shaderLightStruct;
3106
5290
  exports.shaderMaterialSampling = shaderMaterialSampling;
3107
5291
  exports.shaderMaterialStructs = shaderMaterialStructs;
3108
5292
  exports.shaderUtils = shaderUtils;