three-gpu-pathtracer 0.0.8 → 0.0.10

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-gpu-pathtracer",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "Path tracing renderer and utilities for three.js built on top of three-mesh-bvh.",
5
5
  "module": "src/index.js",
6
6
  "main": "build/index.umd.cjs",
@@ -40,7 +40,7 @@
40
40
  "puppeteer": "^15.4.0",
41
41
  "rollup": "^2.70.0",
42
42
  "simple-git": "^3.10.0",
43
- "three": "^0.145.0",
43
+ "three": "^0.147.0",
44
44
  "yargs": "^17.5.1"
45
45
  },
46
46
  "peerDependencies": {
@@ -1,6 +1,7 @@
1
1
  import { RGBAFormat, FloatType, Color, Vector2, WebGLRenderTarget, NoBlending, NormalBlending } from 'three';
2
2
  import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
3
3
  import { BlendMaterial } from '../materials/BlendMaterial.js';
4
+ import { SobolNumberMapGenerator } from '../utils/SobolNumberMapGenerator.js';
4
5
 
5
6
  function* renderTask() {
6
7
 
@@ -10,6 +11,7 @@ function* renderTask() {
10
11
  _blendQuad,
11
12
  _primaryTarget,
12
13
  _blendTargets,
14
+ _sobolTarget,
13
15
  alpha,
14
16
  camera,
15
17
  material,
@@ -36,6 +38,7 @@ function* renderTask() {
36
38
  const w = _primaryTarget.width;
37
39
  const h = _primaryTarget.height;
38
40
  material.resolution.set( w, h );
41
+ material.sobolTexture = _sobolTarget.texture;
39
42
  material.seed ++;
40
43
 
41
44
  const tilesX = this.tiles.x || 1;
@@ -184,6 +187,7 @@ export class PathTracingRenderer {
184
187
  this._task = null;
185
188
  this._currentTile = 0;
186
189
 
190
+ this._sobolTarget = new SobolNumberMapGenerator().generate( renderer );
187
191
  this._primaryTarget = new WebGLRenderTarget( 1, 1, {
188
192
  format: RGBAFormat,
189
193
  type: FloatType,
@@ -215,6 +219,7 @@ export class PathTracingRenderer {
215
219
  this._primaryTarget.dispose();
216
220
  this._blendTargets[ 0 ].dispose();
217
221
  this._blendTargets[ 1 ].dispose();
222
+ this._sobolTarget.dispose();
218
223
 
219
224
  this._fsQuad.dispose();
220
225
  this._blendQuad.dispose();
@@ -3,6 +3,7 @@ import { MaterialBase } from './MaterialBase.js';
3
3
  import { MeshBVHUniformStruct, shaderStructs, shaderIntersectFunction } from 'three-mesh-bvh';
4
4
  import { shaderMaterialStructs } from '../shader/shaderStructs.js';
5
5
  import { shaderUtils } from '../shader/shaderUtils.js';
6
+ import { shaderRandFunctions } from '../shader/shaderRandFunctions.js';
6
7
 
7
8
  export class AmbientOcclusionMaterial extends MaterialBase {
8
9
 
@@ -97,6 +98,7 @@ export class AmbientOcclusionMaterial extends MaterialBase {
97
98
  ${ shaderStructs }
98
99
  ${ shaderIntersectFunction }
99
100
  ${ shaderMaterialStructs }
101
+ ${ shaderRandFunctions }
100
102
  ${ shaderUtils }
101
103
 
102
104
  uniform BVH bvh;
@@ -10,8 +10,10 @@ import { RenderTarget2DArray } from '../uniforms/RenderTarget2DArray.js';
10
10
  import { shaderMaterialSampling } from '../shader/shaderMaterialSampling.js';
11
11
  import { shaderEnvMapSampling } from '../shader/shaderEnvMapSampling.js';
12
12
  import { shaderLightSampling } from '../shader/shaderLightSampling.js';
13
+ import { shaderSobolCommon, shaderSobolSampling } from '../shader/shaderSobolSampling.js';
13
14
  import { shaderUtils } from '../shader/shaderUtils.js';
14
15
  import { shaderLayerTexelFetchFunctions } from '../shader/shaderLayerTexelFetchFunctions.js';
16
+ import { shaderRandFunctions } from '../shader/shaderRandFunctions.js';
15
17
  import { PhysicalCameraUniform } from '../uniforms/PhysicalCameraUniform.js';
16
18
  import { EquirectHdrInfoUniform } from '../uniforms/EquirectHdrInfoUniform.js';
17
19
  import { LightsInfoUniformStruct } from '../uniforms/LightsInfoUniformStruct.js';
@@ -76,6 +78,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
76
78
  filterGlossyFactor: { value: 0.0 },
77
79
 
78
80
  backgroundAlpha: { value: 1.0 },
81
+ sobolTexture: { value: null },
79
82
  },
80
83
 
81
84
  vertexShader: /* glsl */`
@@ -102,6 +105,9 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
102
105
  vec4 envMapTexelToLinear( vec4 a ) { return a; }
103
106
  #include <common>
104
107
 
108
+ ${ shaderRandFunctions }
109
+ ${ shaderSobolCommon }
110
+ ${ shaderSobolSampling }
105
111
  ${ shaderStructs }
106
112
  ${ shaderIntersectFunction }
107
113
  ${ shaderMaterialStructs }
@@ -162,9 +168,9 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
162
168
 
163
169
  }
164
170
 
165
- vec3 sampleBackground( vec3 direction ) {
171
+ vec3 sampleBackground( vec3 direction, vec2 uv ) {
166
172
 
167
- vec3 sampleDir = normalize( direction + getHemisphereSample( direction, rand2() ) * 0.5 * backgroundBlur );
173
+ vec3 sampleDir = normalize( direction + getHemisphereSample( direction, uv ) * 0.5 * backgroundBlur );
168
174
 
169
175
  #if FEATURE_BACKGROUND_MAP
170
176
 
@@ -190,6 +196,8 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
190
196
 
191
197
  color = vec3( 1.0 );
192
198
 
199
+ // TODO: we should be using sobol sampling here instead of rand but the sobol bounce and path indices need to be incremented
200
+ // and then reset.
193
201
  for ( int i = 0; i < traversals; i ++ ) {
194
202
 
195
203
  if ( bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
@@ -316,14 +324,6 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
316
324
 
317
325
  }
318
326
 
319
- // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
320
- // erichlof/THREE.js-PathTracing-Renderer/
321
- float tentFilter( float x ) {
322
-
323
- return x < 0.5 ? sqrt( 2.0 * x ) - 1.0 : 1.0 - sqrt( 2.0 - ( 2.0 * x ) );
324
-
325
- }
326
-
327
327
  vec3 ndcToRayOrigin( vec2 coord ) {
328
328
 
329
329
  vec4 rayOrigin4 = cameraWorldMatrix * invProjectionMatrix * vec4( coord, - 1.0, 1.0 );
@@ -335,13 +335,13 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
335
335
  vec2 ssd = vec2( 1.0 ) / resolution;
336
336
 
337
337
  // Jitter the camera ray by finding a uv coordinate at a random sample
338
- // around this pixel's UV coordinate
339
- vec2 jitteredUv = vUv + vec2( tentFilter( rand() ) * ssd.x, tentFilter( rand() ) * ssd.y );
338
+ // around this pixel's UV coordinate for AA
339
+ vec2 ruv = sobol2( 0 );
340
+ vec2 jitteredUv = vUv + vec2( tentFilter( ruv.x ) * ssd.x, tentFilter( ruv.y ) * ssd.y );
340
341
 
341
342
  #if CAMERA_TYPE == 2
342
343
 
343
344
  // Equirectangular projection
344
-
345
345
  vec4 rayDirection4 = vec4( equirectUvToDirection( jitteredUv ), 0.0 );
346
346
  vec4 rayOrigin4 = vec4( 0.0, 0.0, 0.0, 1.0 );
347
347
 
@@ -355,20 +355,17 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
355
355
 
356
356
  // get [- 1, 1] normalized device coordinates
357
357
  vec2 ndc = 2.0 * jitteredUv - vec2( 1.0 );
358
-
359
358
  rayOrigin = ndcToRayOrigin( ndc );
360
359
 
361
360
  #if CAMERA_TYPE == 1
362
361
 
363
362
  // Orthographic projection
364
-
365
363
  rayDirection = ( cameraWorldMatrix * vec4( 0.0, 0.0, - 1.0, 0.0 ) ).xyz;
366
364
  rayDirection = normalize( rayDirection );
367
365
 
368
366
  #else
369
367
 
370
368
  // Perspective projection
371
-
372
369
  rayDirection = normalize( mat3(cameraWorldMatrix) * ( invProjectionMatrix * vec4( ndc, 0.0, 1.0 ) ).xyz );
373
370
 
374
371
  #endif
@@ -382,17 +379,17 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
382
379
  vec3 focalPoint = rayOrigin + normalize( rayDirection ) * physicalCamera.focusDistance;
383
380
 
384
381
  // get the aperture sample
385
- vec2 apertureSample = sampleAperture( physicalCamera.apertureBlades ) * physicalCamera.bokehSize * 0.5 * 1e-3;
382
+ // if blades === 0 then we assume a circle
383
+ vec3 shapeUVW= sobol3( 1 );
384
+ int blades = physicalCamera.apertureBlades;
385
+ float anamorphicRatio = physicalCamera.anamorphicRatio;
386
+ vec2 apertureSample = blades == 0 ? sampleCircle( shapeUVW.xy ) : sampleRegularNGon( blades, shapeUVW );
387
+ apertureSample *= physicalCamera.bokehSize * 0.5 * 1e-3;
386
388
 
387
389
  // rotate the aperture shape
388
- float ac = cos( physicalCamera.apertureRotation );
389
- float as = sin( physicalCamera.apertureRotation );
390
- apertureSample = vec2(
391
- apertureSample.x * ac - apertureSample.y * as,
392
- apertureSample.x * as + apertureSample.y * ac
393
- );
394
- apertureSample.x *= saturate( physicalCamera.anamorphicRatio );
395
- apertureSample.y *= saturate( 1.0 / physicalCamera.anamorphicRatio );
390
+ apertureSample =
391
+ rotateVector( apertureSample, physicalCamera.apertureRotation ) *
392
+ saturate( vec2( anamorphicRatio, 1.0 / anamorphicRatio ) );
396
393
 
397
394
  // create the new ray
398
395
  rayOrigin += ( cameraWorldMatrix * vec4( apertureSample, 0.0, 0.0 ) ).xyz;
@@ -408,6 +405,8 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
408
405
  void main() {
409
406
 
410
407
  rng_initialize( gl_FragCoord.xy, seed );
408
+ sobolPixelIndex = ( uint( gl_FragCoord.x ) << 16 ) | ( uint( gl_FragCoord.y ) );
409
+ sobolPathIndex = uint( seed );
411
410
 
412
411
  vec3 rayDirection;
413
412
  vec3 rayOrigin;
@@ -441,6 +440,8 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
441
440
 
442
441
  for ( i = 0; i < bounces; i ++ ) {
443
442
 
443
+ sobolBounceIndex ++;
444
+
444
445
  bool hit = bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
445
446
 
446
447
  LightSampleRec lightHit = lightsClosestHit( lights.tex, lights.count, rayOrigin, rayDirection );
@@ -455,9 +456,8 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
455
456
 
456
457
  #if FEATURE_MIS
457
458
 
458
- // NOTE: we skip MIS for spotlights since we haven't fixed the forward
459
- // path tracing code path, yet
460
- if ( lightHit.type == SPOT_LIGHT_TYPE ) {
459
+ // NOTE: we skip MIS for punctual lights since they are not supported in forward PT case
460
+ if ( lightHit.type == SPOT_LIGHT_TYPE || lightHit.type == DIR_LIGHT_TYPE || lightHit.type == POINT_LIGHT_TYPE ) {
461
461
 
462
462
  gl_FragColor.rgb += lightHit.emission * throughputColor;
463
463
 
@@ -484,7 +484,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
484
484
 
485
485
  if ( i == 0 || transmissiveRay ) {
486
486
 
487
- gl_FragColor.rgb += sampleBackground( envRotation3x3 * rayDirection ) * throughputColor;
487
+ gl_FragColor.rgb += sampleBackground( envRotation3x3 * rayDirection, sobol2( 2 ) ) * throughputColor;
488
488
  gl_FragColor.a = backgroundAlpha;
489
489
 
490
490
  } else {
@@ -493,7 +493,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
493
493
 
494
494
  // get the PDF of the hit envmap point
495
495
  vec3 envColor;
496
- float envPdf = envMapSample( envRotation3x3 * rayDirection, envMapInfo, envColor );
496
+ float envPdf = sampleEnvMap( envMapInfo, envRotation3x3 * rayDirection, envColor );
497
497
  envPdf /= float( lights.count + 1u );
498
498
 
499
499
  // and weight the contribution
@@ -578,7 +578,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
578
578
  || useAlphaTest && albedo.a < alphaTest
579
579
 
580
580
  // opacity
581
- || material.transparent && ! useAlphaTest && albedo.a < rand()
581
+ || material.transparent && ! useAlphaTest && albedo.a < sobol( 3 )
582
582
  ) {
583
583
 
584
584
  vec3 point = rayOrigin + rayDirection * dist;
@@ -639,6 +639,15 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
639
639
  }
640
640
 
641
641
  // normal
642
+ if ( material.flatShading ) {
643
+
644
+ // if we're rendering a flat shaded object then use the face normals - the face normal
645
+ // is provided based on the side the ray hits the mesh so flip it to align with the
646
+ // interpolated vertex normals.
647
+ normal = faceNormal * side;
648
+
649
+ }
650
+
642
651
  vec3 baseNormal = normal;
643
652
  if ( material.normalMap != - 1 ) {
644
653
 
@@ -682,7 +691,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
682
691
  if ( material.clearcoatRoughnessMap != - 1 ) {
683
692
 
684
693
  vec3 uvPrime = material.clearcoatRoughnessMapTransform * vec3( uv, 1 );
685
- clearcoat *= texture2D( textures, vec3( uvPrime.xy, material.clearcoatRoughnessMap ) ).g;
694
+ clearcoatRoughness *= texture2D( textures, vec3( uvPrime.xy, material.clearcoatRoughnessMap ) ).g;
686
695
 
687
696
  }
688
697
 
@@ -821,7 +830,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
821
830
  vec3 clearcoatOutgoing = - normalize( clearcoatInvBasis * rayDirection );
822
831
  sampleRec = bsdfSample( outgoing, clearcoatOutgoing, normalBasis, invBasis, clearcoatNormalBasis, clearcoatInvBasis, surfaceRec );
823
832
 
824
- isShadowRay = sampleRec.specularPdf < rand();
833
+ isShadowRay = sampleRec.specularPdf < sobol( 4 );
825
834
 
826
835
  // adjust the hit point by the surface normal by a factor of some offset and the
827
836
  // maximum component-wise value of the current point to accommodate floating point
@@ -838,10 +847,10 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
838
847
  #if FEATURE_MIS
839
848
 
840
849
  // uniformly pick a light or environment map
841
- if( rand() > 1.0 / float( lights.count + 1u ) ) {
850
+ if( sobol( 5 ) > 1.0 / float( lights.count + 1u ) ) {
842
851
 
843
852
  // sample a light or environment
844
- LightSampleRec lightSampleRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin );
853
+ LightSampleRec lightSampleRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, sobol3( 6 ) );
845
854
 
846
855
  bool isSampleBelowSurface = dot( faceNormal, lightSampleRec.direction ) < 0.0;
847
856
  if ( isSampleBelowSurface ) {
@@ -865,7 +874,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
865
874
 
866
875
  // weight the direct light contribution
867
876
  float lightPdf = lightSampleRec.pdf / float( lights.count + 1u );
868
- float misWeight = misHeuristic( lightPdf, lightMaterialPdf );
877
+ float misWeight = lightSampleRec.type == SPOT_LIGHT_TYPE || lightSampleRec.type == DIR_LIGHT_TYPE || lightSampleRec.type == POINT_LIGHT_TYPE ? 1.0 : misHeuristic( lightPdf, lightMaterialPdf );
869
878
  gl_FragColor.rgb += lightSampleRec.emission * throughputColor * sampleColor * misWeight / lightPdf;
870
879
 
871
880
  }
@@ -876,7 +885,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
876
885
 
877
886
  // find a sample in the environment map to include in the contribution
878
887
  vec3 envColor, envDirection;
879
- float envPdf = randomEnvMapSample( envMapInfo, envColor, envDirection );
888
+ float envPdf = sampleEnvMapProbability( envMapInfo, sobol2( 7 ), envColor, envDirection );
880
889
  envDirection = invEnvRotation3x3 * envDirection;
881
890
 
882
891
  // this env sampling is not set up for transmissive sampling and yields overly bright
@@ -21,13 +21,13 @@ float envMapDirectionPdf( vec3 direction ) {
21
21
 
22
22
  }
23
23
 
24
- float envMapSample( vec3 direction, EquirectHdrInfo info, out vec3 color ) {
24
+ float sampleEnvMap( EquirectHdrInfo info, vec3 direction, out vec3 color ) {
25
25
 
26
26
  vec2 uv = equirectDirectionToUv( direction );
27
27
  color = texture2D( info.map, uv ).rgb;
28
28
 
29
29
  float totalSum = info.totalSumWhole + info.totalSumDecimal;
30
- float lum = colorToLuminance( color );
30
+ float lum = luminance( color );
31
31
  ivec2 resolution = textureSize( info.map, 0 );
32
32
  float pdf = lum / totalSum;
33
33
 
@@ -35,10 +35,9 @@ float envMapSample( vec3 direction, EquirectHdrInfo info, out vec3 color ) {
35
35
 
36
36
  }
37
37
 
38
- float randomEnvMapSample( EquirectHdrInfo info, out vec3 color, out vec3 direction ) {
38
+ float sampleEnvMapProbability( EquirectHdrInfo info, vec2 r, out vec3 color, out vec3 direction ) {
39
39
 
40
40
  // sample env map cdf
41
- vec2 r = rand2();
42
41
  float v = texture2D( info.marginalWeights, vec2( r.x, 0.0 ) ).x;
43
42
  float u = texture2D( info.conditionalWeights, vec2( r.y, v ) ).x;
44
43
  vec2 uv = vec2( u, v );
@@ -48,7 +47,7 @@ float randomEnvMapSample( EquirectHdrInfo info, out vec3 color, out vec3 directi
48
47
  color = texture2D( info.map, uv ).rgb;
49
48
 
50
49
  float totalSum = info.totalSumWhole + info.totalSumDecimal;
51
- float lum = colorToLuminance( color );
50
+ float lum = luminance( color );
52
51
  ivec2 resolution = textureSize( info.map, 0 );
53
52
  float pdf = lum / totalSum;
54
53
 
@@ -8,14 +8,14 @@ export const shaderGGXFunctions = /* glsl */`
8
8
 
9
9
  // trowbridge-reitz === GGX === GTR
10
10
 
11
- vec3 ggxDirection( vec3 incidentDir, float roughnessX, float roughnessY, float random1, float random2 ) {
11
+ vec3 ggxDirection( vec3 incidentDir, vec2 roughness, vec2 uv ) {
12
12
 
13
13
  // TODO: try GGXVNDF implementation from reference [2], here. Needs to update ggxDistribution
14
14
  // function below, as well
15
15
 
16
16
  // Implementation from reference [1]
17
17
  // stretch view
18
- vec3 V = normalize( vec3( roughnessX * incidentDir.x, roughnessY * incidentDir.y, incidentDir.z ) );
18
+ vec3 V = normalize( vec3( roughness * incidentDir.xy, incidentDir.z ) );
19
19
 
20
20
  // orthonormal basis
21
21
  vec3 T1 = ( V.z < 0.9999 ) ? normalize( cross( V, vec3( 0.0, 0.0, 1.0 ) ) ) : vec3( 1.0, 0.0, 0.0 );
@@ -23,16 +23,16 @@ vec3 ggxDirection( vec3 incidentDir, float roughnessX, float roughnessY, float r
23
23
 
24
24
  // sample point with polar coordinates (r, phi)
25
25
  float a = 1.0 / ( 1.0 + V.z );
26
- float r = sqrt( random1 );
27
- float phi = ( random2 < a ) ? random2 / a * PI : PI + ( random2 - a ) / ( 1.0 - a ) * PI;
26
+ float r = sqrt( uv.x );
27
+ float phi = ( uv.y < a ) ? uv.y / a * PI : PI + ( uv.y - a ) / ( 1.0 - a ) * PI;
28
28
  float P1 = r * cos( phi );
29
- float P2 = r * sin( phi ) * ( ( random2 < a ) ? 1.0 : V.z );
29
+ float P2 = r * sin( phi ) * ( ( uv.y < a ) ? 1.0 : V.z );
30
30
 
31
31
  // compute normal
32
32
  vec3 N = P1 * T1 + P2 * T2 + V * sqrt( max( 0.0, 1.0 - P1 * P1 - P2 * P2 ) );
33
33
 
34
34
  // unstretch
35
- N = normalize( vec3( roughnessX * N.x, roughnessY * N.y, max( 0.0, N.z ) ) );
35
+ N = normalize( vec3( roughness * N.xy, max( 0.0, N.z ) ) );
36
36
 
37
37
  return N;
38
38
 
@@ -67,6 +67,7 @@ LightSampleRec lightsClosestHit( sampler2D lights, uint lightCount, vec3 rayOrig
67
67
 
68
68
  float dist;
69
69
 
70
+ // MIS / light intersection is not supported for punctual lights.
70
71
  if(
71
72
  ( light.type == RECT_AREA_LIGHT_TYPE && intersectsRectangle( light.position, normal, u, v, rayOrigin, rayDirection, dist ) ) ||
72
73
  ( light.type == CIRC_AREA_LIGHT_TYPE && intersectsCircle( light.position, normal, u, v, rayOrigin, rayDirection, dist ) )
@@ -85,38 +86,6 @@ LightSampleRec lightsClosestHit( sampler2D lights, uint lightCount, vec3 rayOrig
85
86
 
86
87
  }
87
88
 
88
- } else if ( light.type == SPOT_LIGHT_TYPE ) {
89
-
90
- // TODO: forward path tracing sampling needs to be made consistent with direct light sampling logic
91
- // float radius = light.radius;
92
- // vec3 lightNormal = normalize( cross( light.u, light.v ) );
93
- // float angle = acos( light.coneCos );
94
- // float angleTan = tan( angle );
95
- // float startDistance = radius / max( angleTan, EPSILON );
96
-
97
- // u = light.u / radius;
98
- // v = light.v / radius;
99
-
100
- // if (
101
- // intersectsCircle( light.position - normal * startDistance, normal, u, v, rayOrigin, rayDirection, dist ) &&
102
- // ( dist < lightSampleRec.dist || ! lightSampleRec.hit )
103
- // ) {
104
-
105
- // float cosTheta = dot( rayDirection, normal );
106
- // float spotAttenuation = light.iesProfile != - 1 ?
107
- // getPhotometricAttenuation( iesProfiles, light.iesProfile, rayDirection, normal, u, v )
108
- // : getSpotAttenuation( light.coneCos, light.penumbraCos, cosTheta );
109
-
110
- // float distanceAttenuation = getDistanceAttenuation( dist, light.distance, light.decay );
111
-
112
- // lightSampleRec.hit = true;
113
- // lightSampleRec.dist = dist;
114
- // lightSampleRec.direction = rayDirection;
115
- // lightSampleRec.emission = light.color * light.intensity * distanceAttenuation * spotAttenuation;
116
- // lightSampleRec.pdf = ( dist * dist ) / ( light.area * cosTheta );
117
-
118
- // }
119
-
120
89
  }
121
90
 
122
91
  }
@@ -125,7 +94,7 @@ LightSampleRec lightsClosestHit( sampler2D lights, uint lightCount, vec3 rayOrig
125
94
 
126
95
  }
127
96
 
128
- LightSampleRec randomAreaLightSample( Light light, vec3 rayOrigin ) {
97
+ LightSampleRec randomAreaLightSample( Light light, vec3 rayOrigin, vec2 ruv ) {
129
98
 
130
99
  LightSampleRec lightSampleRec;
131
100
  lightSampleRec.hit = true;
@@ -137,13 +106,13 @@ LightSampleRec randomAreaLightSample( Light light, vec3 rayOrigin ) {
137
106
  if( light.type == RECT_AREA_LIGHT_TYPE ) {
138
107
 
139
108
  // rectangular area light
140
- randomPos = light.position + light.u * ( rand() - 0.5 ) + light.v * ( rand() - 0.5 );
109
+ randomPos = light.position + light.u * ( ruv.x - 0.5 ) + light.v * ( ruv.y - 0.5 );
141
110
 
142
- } else if( light.type == 1 ) {
111
+ } else if( light.type == CIRC_AREA_LIGHT_TYPE ) {
143
112
 
144
113
  // circular area light
145
- float r = 0.5 * sqrt( rand() );
146
- float theta = rand() * 2.0 * PI;
114
+ float r = 0.5 * sqrt( ruv.x );
115
+ float theta = ruv.y * 2.0 * PI;
147
116
  float x = r * cos( theta );
148
117
  float y = r * sin( theta );
149
118
 
@@ -165,10 +134,10 @@ LightSampleRec randomAreaLightSample( Light light, vec3 rayOrigin ) {
165
134
 
166
135
  }
167
136
 
168
- LightSampleRec randomSpotLightSample( Light light, sampler2DArray iesProfiles, vec3 rayOrigin ) {
137
+ LightSampleRec randomSpotLightSample( Light light, sampler2DArray iesProfiles, vec3 rayOrigin, vec2 ruv ) {
169
138
 
170
- float radius = light.radius * sqrt( rand() );
171
- float theta = rand() * 2.0 * PI;
139
+ float radius = light.radius * sqrt( ruv.x );
140
+ float theta = ruv.y * 2.0 * PI;
172
141
  float x = radius * cos( theta );
173
142
  float y = radius * sin( theta );
174
143
 
@@ -189,8 +158,8 @@ LightSampleRec randomSpotLightSample( Light light, sampler2DArray iesProfiles, v
189
158
  float cosTheta = dot( direction, normal );
190
159
 
191
160
  float spotAttenuation = light.iesProfile != - 1 ?
192
- getPhotometricAttenuation( iesProfiles, light.iesProfile, direction, normal, u, v )
193
- : getSpotAttenuation( light.coneCos, light.penumbraCos, cosTheta );
161
+ getPhotometricAttenuation( iesProfiles, light.iesProfile, direction, normal, u, v ) :
162
+ getSpotAttenuation( light.coneCos, light.penumbraCos, cosTheta );
194
163
 
195
164
  float distanceAttenuation = getDistanceAttenuation( dist, light.distance, light.decay );
196
165
  LightSampleRec lightSampleRec;
@@ -199,30 +168,59 @@ LightSampleRec randomSpotLightSample( Light light, sampler2DArray iesProfiles, v
199
168
  lightSampleRec.dist = dist;
200
169
  lightSampleRec.direction = direction;
201
170
  lightSampleRec.emission = light.color * light.intensity * distanceAttenuation * spotAttenuation;
202
-
203
- // TODO: this makes the result consistent between MIS and non MIS paths but at radius 0 the pdf is infinite
204
- // and the intensity of the light is not correct
205
171
  lightSampleRec.pdf = 1.0;
206
- // lightSampleRec.pdf = lightDistSq / ( light.area * cosTheta );
207
172
 
208
173
  return lightSampleRec;
209
174
 
210
175
  }
211
176
 
212
- LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles, uint lightCount, vec3 rayOrigin ) {
177
+ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles, uint lightCount, vec3 rayOrigin, vec3 ruv ) {
213
178
 
214
179
  // pick a random light
215
- uint l = uint( rand() * float( lightCount ) );
180
+ uint l = uint( ruv.x * float( lightCount ) );
216
181
  Light light = readLightInfo( lights, l );
217
182
 
218
183
  if ( light.type == SPOT_LIGHT_TYPE ) {
219
184
 
220
- return randomSpotLightSample( light, iesProfiles, rayOrigin );
185
+ return randomSpotLightSample( light, iesProfiles, rayOrigin, ruv.yz );
186
+
187
+ } else if ( light.type == POINT_LIGHT_TYPE ) {
188
+
189
+ vec3 lightRay = light.u - rayOrigin;
190
+ float lightDist = length( lightRay );
191
+ float cutoffDistance = light.distance;
192
+ float distanceFalloff = 1.0 / max( pow( lightDist, light.decay ), 0.01 );
193
+ if ( cutoffDistance > 0.0 ) {
194
+
195
+ distanceFalloff *= pow2( saturate( 1.0 - pow4( lightDist / cutoffDistance ) ) );
196
+
197
+ }
198
+
199
+ LightSampleRec rec;
200
+ rec.hit = true;
201
+ rec.direction = normalize( lightRay );
202
+ rec.dist = length( lightRay );
203
+ rec.pdf = 1.0;
204
+ rec.emission = light.color * light.intensity * distanceFalloff;
205
+ rec.type = light.type;
206
+ return rec;
207
+
208
+ } else if ( light.type == DIR_LIGHT_TYPE ) {
209
+
210
+ LightSampleRec rec;
211
+ rec.hit = true;
212
+ rec.dist = 1e10;
213
+ rec.direction = light.u;
214
+ rec.pdf = 1.0;
215
+ rec.emission = light.color * light.intensity;
216
+ rec.type = light.type;
217
+
218
+ return rec;
221
219
 
222
220
  } else {
223
221
 
224
222
  // sample the light
225
- return randomAreaLightSample( light, rayOrigin );
223
+ return randomAreaLightSample( light, rayOrigin, ruv.yz );
226
224
 
227
225
  }
228
226
 
@@ -93,7 +93,7 @@ float diffuseEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRec surf, out vec3 color )
93
93
 
94
94
  vec3 diffuseDirection( vec3 wo, SurfaceRec surf ) {
95
95
 
96
- vec3 lightDirection = randDirection();
96
+ vec3 lightDirection = sampleSphere( sobol2( 11 ) );
97
97
  lightDirection.z += 1.0;
98
98
  lightDirection = normalize( lightDirection );
99
99
 
@@ -147,10 +147,8 @@ vec3 specularDirection( vec3 wo, SurfaceRec surf ) {
147
147
  float filteredRoughness = surf.filteredRoughness;
148
148
  vec3 halfVector = ggxDirection(
149
149
  wo,
150
- filteredRoughness,
151
- filteredRoughness,
152
- rand(),
153
- rand()
150
+ vec2( filteredRoughness ),
151
+ sobol2( 12 )
154
152
  );
155
153
 
156
154
  // apply to new ray by reflecting off the new normal
@@ -187,10 +185,8 @@ vec3 transmissionDirection( vec3 wo, SurfaceRec surf ) {
187
185
  // sample ggx vndf distribution which gives a new normal
188
186
  vec3 halfVector = ggxDirection(
189
187
  wo,
190
- filteredRoughness,
191
- filteredRoughness,
192
- rand(),
193
- rand()
188
+ vec2( filteredRoughness ),
189
+ sobol2( 13 )
194
190
  );
195
191
 
196
192
 
@@ -230,7 +226,7 @@ vec3 transmissionDirection( vec3 wo, SurfaceRec surf ) {
230
226
 
231
227
  float roughness = surf.roughness;
232
228
  float eta = surf.eta;
233
- vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + randDirection() * roughness );
229
+ vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere( sobol2( 13 ) ) * roughness );
234
230
  vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
235
231
 
236
232
  if ( surf.thinFilm ) {
@@ -278,10 +274,8 @@ vec3 clearcoatDirection( vec3 wo, SurfaceRec surf ) {
278
274
  float filteredClearcoatRoughness = surf.filteredClearcoatRoughness;
279
275
  vec3 halfVector = ggxDirection(
280
276
  wo,
281
- filteredClearcoatRoughness,
282
- filteredClearcoatRoughness,
283
- rand(),
284
- rand()
277
+ vec2( filteredClearcoatRoughness ),
278
+ sobol2( 14 )
285
279
  );
286
280
 
287
281
  // apply to new ray by reflecting off the new normal
@@ -470,7 +464,7 @@ SampleRec bsdfSample( vec3 wo, vec3 clearcoatWo, mat3 normalBasis, mat3 invBasis
470
464
  vec3 wi;
471
465
  vec3 clearcoatWi;
472
466
 
473
- float r = rand();
467
+ float r = sobol( 15 );
474
468
  if ( r <= cdf[0] ) { // diffuse
475
469
 
476
470
  wi = diffuseDirection( wo, surf );
@@ -0,0 +1,57 @@
1
+ export const shaderRandFunctions = /* glsl */`
2
+
3
+ // https://www.shadertoy.com/view/wltcRS
4
+ uvec4 WHITE_NOISE_SEED;
5
+
6
+ void rng_initialize( vec2 p, int frame ) {
7
+
8
+ // white noise seed
9
+ WHITE_NOISE_SEED = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
10
+
11
+ }
12
+
13
+ // https://www.pcg-random.org/
14
+ void pcg4d( inout uvec4 v ) {
15
+
16
+ v = v * 1664525u + 1013904223u;
17
+ v.x += v.y * v.w;
18
+ v.y += v.z * v.x;
19
+ v.z += v.x * v.y;
20
+ v.w += v.y * v.z;
21
+ v = v ^ ( v >> 16u );
22
+ v.x += v.y*v.w;
23
+ v.y += v.z*v.x;
24
+ v.z += v.x*v.y;
25
+ v.w += v.y*v.z;
26
+
27
+ }
28
+
29
+ // returns [ 0, 1 ]
30
+ float rand() {
31
+
32
+ pcg4d( WHITE_NOISE_SEED );
33
+ return float( WHITE_NOISE_SEED.x ) / float( 0xffffffffu );
34
+
35
+ }
36
+
37
+ vec2 rand2() {
38
+
39
+ pcg4d( WHITE_NOISE_SEED );
40
+ return vec2( WHITE_NOISE_SEED.xy ) / float(0xffffffffu);
41
+
42
+ }
43
+
44
+ vec3 rand3() {
45
+
46
+ pcg4d( WHITE_NOISE_SEED );
47
+ return vec3( WHITE_NOISE_SEED.xyz ) / float( 0xffffffffu );
48
+
49
+ }
50
+
51
+ vec4 rand4() {
52
+
53
+ pcg4d( WHITE_NOISE_SEED );
54
+ return vec4( WHITE_NOISE_SEED ) / float( 0xffffffffu );
55
+
56
+ }
57
+ `;