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