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/README.md +1 -2
- package/build/index.module.js +643 -227
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +639 -224
- package/build/index.umd.cjs.map +1 -1
- package/package.json +2 -2
- package/src/core/PathTracingRenderer.js +5 -0
- package/src/materials/AmbientOcclusionMaterial.js +2 -0
- package/src/materials/PhysicalPathTracingMaterial.js +46 -37
- package/src/shader/shaderEnvMapSampling.js +4 -5
- package/src/shader/shaderGGXFunctions.js +6 -6
- package/src/shader/shaderLightSampling.js +48 -50
- package/src/shader/shaderMaterialSampling.js +9 -15
- package/src/shader/shaderRandFunctions.js +57 -0
- package/src/shader/shaderSobolSampling.js +256 -0
- package/src/shader/shaderStructs.js +6 -2
- package/src/shader/shaderUtils.js +50 -92
- package/src/uniforms/EquirectHdrInfoUniform.js +4 -4
- package/src/uniforms/LightsInfoUniformStruct.js +47 -2
- package/src/uniforms/MaterialsTexture.js +12 -6
- package/src/uniforms/utils.js +11 -2
- package/src/utils/SobolNumberMapGenerator.js +80 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "three-gpu-pathtracer",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
|
459
|
-
|
|
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 =
|
|
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 <
|
|
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
|
-
|
|
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 <
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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,
|
|
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(
|
|
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(
|
|
27
|
-
float phi = (
|
|
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 ) * ( (
|
|
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(
|
|
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 * (
|
|
109
|
+
randomPos = light.position + light.u * ( ruv.x - 0.5 ) + light.v * ( ruv.y - 0.5 );
|
|
141
110
|
|
|
142
|
-
} else if( light.type ==
|
|
111
|
+
} else if( light.type == CIRC_AREA_LIGHT_TYPE ) {
|
|
143
112
|
|
|
144
113
|
// circular area light
|
|
145
|
-
float r = 0.5 * sqrt(
|
|
146
|
-
float theta =
|
|
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(
|
|
171
|
-
float theta =
|
|
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
|
-
|
|
193
|
-
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 ) +
|
|
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
|
-
|
|
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 =
|
|
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
|
+
`;
|