three-gpu-pathtracer 0.0.17 → 0.0.19
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/build/index.module.js +1013 -322
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +1010 -320
- package/build/index.umd.cjs.map +1 -1
- package/package.json +2 -2
- package/src/core/DynamicPathTracingSceneGenerator.js +80 -40
- package/src/core/PathTracingRenderer.js +28 -34
- package/src/core/PathTracingSceneGenerator.js +11 -60
- package/src/materials/pathtracing/LambertPathTracingMaterial.js +1 -1
- package/src/materials/pathtracing/PhysicalPathTracingMaterial.js +37 -12
- package/src/materials/pathtracing/glsl/attenuateHit.glsl.js +19 -9
- package/src/materials/pathtracing/glsl/cameraUtils.glsl.js +2 -2
- package/src/materials/pathtracing/glsl/directLightContribution.glsl.js +4 -4
- package/src/materials/pathtracing/glsl/getSurfaceRecord.glsl.js +2 -1
- package/src/materials/pathtracing/glsl/traceScene.glsl.js +1 -1
- package/src/shader/bsdf/bsdfSampling.glsl.js +14 -10
- package/src/shader/common/fresnel.glsl.js +15 -9
- package/src/shader/rand/pcg.glsl.js +4 -4
- package/src/shader/rand/stratifiedTexture.glsl.js +45 -0
- package/src/shader/sampling/equirectSampling.glsl.js +8 -1
- package/src/shader/structs/lightsStruct.glsl.js +5 -7
- package/src/textures/BlueNoiseTexture.js +87 -0
- package/src/textures/ProceduralEquirectTexture.js +7 -8
- package/src/textures/blueNoise/BlueNoiseGenerator.js +115 -0
- package/src/textures/blueNoise/BlueNoiseSamples.js +214 -0
- package/src/textures/blueNoise/utils.js +24 -0
- package/src/uniforms/EquirectHdrInfoUniform.js +45 -8
- package/src/uniforms/LightsInfoUniformStruct.js +11 -7
- package/src/uniforms/MaterialsTexture.js +1 -1
- package/src/uniforms/RenderTarget2DArray.js +50 -3
- package/src/uniforms/StratifiedSamplesTexture.js +49 -0
- package/src/uniforms/stratified/StratifiedSampler.js +73 -0
- package/src/uniforms/stratified/StratifiedSamplerCombined.js +59 -0
- package/src/uniforms/utils.js +1 -1
- package/src/utils/GeometryPreparationUtils.js +8 -101
- package/src/workers/PathTracingSceneWorker.js +18 -8
|
@@ -94,15 +94,17 @@ export const bsdfSamplingGLSL = /* glsl */`
|
|
|
94
94
|
|
|
95
95
|
// TODO: subsurface approx?
|
|
96
96
|
|
|
97
|
-
float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
97
|
+
// float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
98
|
+
float F = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
|
|
98
99
|
color = ( 1.0 - F ) * transFactor * metalFactor * wi.z * surf.color * ( retro + lambert ) / PI;
|
|
100
|
+
|
|
99
101
|
return wi.z / PI;
|
|
100
102
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
vec3 diffuseDirection( vec3 wo, SurfaceRecord surf ) {
|
|
104
106
|
|
|
105
|
-
vec3 lightDirection = sampleSphere(
|
|
107
|
+
vec3 lightDirection = sampleSphere( rand2( 11 ) );
|
|
106
108
|
lightDirection.z += 1.0;
|
|
107
109
|
lightDirection = normalize( lightDirection );
|
|
108
110
|
|
|
@@ -147,7 +149,7 @@ export const bsdfSamplingGLSL = /* glsl */`
|
|
|
147
149
|
vec3 halfVector = ggxDirection(
|
|
148
150
|
wo,
|
|
149
151
|
vec2( roughness ),
|
|
150
|
-
|
|
152
|
+
rand2( 12 )
|
|
151
153
|
);
|
|
152
154
|
|
|
153
155
|
// apply to new ray by reflecting off the new normal
|
|
@@ -184,7 +186,7 @@ export const bsdfSamplingGLSL = /* glsl */`
|
|
|
184
186
|
vec3 halfVector = ggxDirection(
|
|
185
187
|
wo,
|
|
186
188
|
vec2( filteredRoughness ),
|
|
187
|
-
|
|
189
|
+
rand2( 13 )
|
|
188
190
|
);
|
|
189
191
|
|
|
190
192
|
vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
|
|
@@ -206,7 +208,8 @@ export const bsdfSamplingGLSL = /* glsl */`
|
|
|
206
208
|
color = surf.transmission * surf.color;
|
|
207
209
|
|
|
208
210
|
// PDF
|
|
209
|
-
float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
211
|
+
// float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
212
|
+
float F = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
|
|
210
213
|
if ( F >= 1.0 ) {
|
|
211
214
|
|
|
212
215
|
return 0.0;
|
|
@@ -221,7 +224,7 @@ export const bsdfSamplingGLSL = /* glsl */`
|
|
|
221
224
|
|
|
222
225
|
float roughness = surf.filteredRoughness;
|
|
223
226
|
float eta = surf.eta;
|
|
224
|
-
vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere(
|
|
227
|
+
vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere( rand2( 13 ) ) * roughness );
|
|
225
228
|
vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
|
|
226
229
|
|
|
227
230
|
if ( surf.thinFilm ) {
|
|
@@ -262,7 +265,7 @@ export const bsdfSamplingGLSL = /* glsl */`
|
|
|
262
265
|
vec3 halfVector = ggxDirection(
|
|
263
266
|
wo,
|
|
264
267
|
vec2( roughness ),
|
|
265
|
-
|
|
268
|
+
rand2( 14 )
|
|
266
269
|
);
|
|
267
270
|
|
|
268
271
|
// apply to new ray by reflecting off the new normal
|
|
@@ -297,7 +300,8 @@ export const bsdfSamplingGLSL = /* glsl */`
|
|
|
297
300
|
|
|
298
301
|
float metalness = surf.metalness;
|
|
299
302
|
float transmission = surf.transmission;
|
|
300
|
-
float fEstimate = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
303
|
+
// float fEstimate = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
304
|
+
float fEstimate = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
|
|
301
305
|
|
|
302
306
|
float transSpecularProb = mix( max( 0.25, fEstimate ), 1.0, metalness );
|
|
303
307
|
float diffSpecularProb = 0.5 + 0.5 * metalness;
|
|
@@ -413,7 +417,7 @@ export const bsdfSamplingGLSL = /* glsl */`
|
|
|
413
417
|
ScatterRecord sampleRec;
|
|
414
418
|
sampleRec.specularPdf = 0.0;
|
|
415
419
|
sampleRec.pdf = 1.0 / ( 4.0 * PI );
|
|
416
|
-
sampleRec.direction = sampleSphere(
|
|
420
|
+
sampleRec.direction = sampleSphere( rand2( 16 ) );
|
|
417
421
|
sampleRec.color = surf.color / ( 4.0 * PI );
|
|
418
422
|
return sampleRec;
|
|
419
423
|
|
|
@@ -465,7 +469,7 @@ export const bsdfSamplingGLSL = /* glsl */`
|
|
|
465
469
|
vec3 wi;
|
|
466
470
|
vec3 clearcoatWi;
|
|
467
471
|
|
|
468
|
-
float r =
|
|
472
|
+
float r = rand( 15 );
|
|
469
473
|
if ( r <= cdf[0] ) { // diffuse
|
|
470
474
|
|
|
471
475
|
wi = diffuseDirection( wo, surf );
|
|
@@ -69,30 +69,36 @@ export const fresnelGLSL = /* glsl */`
|
|
|
69
69
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
// TODO: disney fresnel was removed and replaced with this fresnel function to better align with
|
|
73
|
+
// the glTF but is causing blown out pixels. Should be revisited
|
|
74
|
+
// float evaluateFresnelWeight( float cosTheta, float eta, float f0 ) {
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
// if ( totalInternalReflection( cosTheta, eta ) ) {
|
|
75
77
|
|
|
76
|
-
|
|
78
|
+
// return 1.0;
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
// }
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
// return schlickFresnel( cosTheta, f0 );
|
|
81
83
|
|
|
82
|
-
}
|
|
84
|
+
// }
|
|
83
85
|
|
|
84
|
-
/*
|
|
85
86
|
// https://schuttejoe.github.io/post/disneybsdf/
|
|
86
87
|
float disneyFresnel( vec3 wo, vec3 wi, vec3 wh, float f0, float eta, float metalness ) {
|
|
87
88
|
|
|
88
89
|
float dotHV = dot( wo, wh );
|
|
89
|
-
|
|
90
|
+
if ( totalInternalReflection( dotHV, eta ) ) {
|
|
90
91
|
|
|
92
|
+
return 1.0;
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
float dotHL = dot( wi, wh );
|
|
91
97
|
float dielectricFresnel = dielectricFresnel( abs( dotHV ), eta );
|
|
92
98
|
float metallicFresnel = schlickFresnel( dotHL, f0 );
|
|
93
99
|
|
|
94
100
|
return mix( dielectricFresnel, metallicFresnel, metalness );
|
|
95
101
|
|
|
96
102
|
}
|
|
97
|
-
|
|
103
|
+
|
|
98
104
|
`;
|
|
@@ -27,28 +27,28 @@ export const pcgGLSL = /* glsl */`
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// returns [ 0, 1 ]
|
|
30
|
-
float
|
|
30
|
+
float pcgRand() {
|
|
31
31
|
|
|
32
32
|
pcg4d( WHITE_NOISE_SEED );
|
|
33
33
|
return float( WHITE_NOISE_SEED.x ) / float( 0xffffffffu );
|
|
34
34
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
vec2
|
|
37
|
+
vec2 pcgRand2() {
|
|
38
38
|
|
|
39
39
|
pcg4d( WHITE_NOISE_SEED );
|
|
40
40
|
return vec2( WHITE_NOISE_SEED.xy ) / float(0xffffffffu);
|
|
41
41
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
vec3
|
|
44
|
+
vec3 pcgRand3() {
|
|
45
45
|
|
|
46
46
|
pcg4d( WHITE_NOISE_SEED );
|
|
47
47
|
return vec3( WHITE_NOISE_SEED.xyz ) / float( 0xffffffffu );
|
|
48
48
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
vec4
|
|
51
|
+
vec4 pcgRand4() {
|
|
52
52
|
|
|
53
53
|
pcg4d( WHITE_NOISE_SEED );
|
|
54
54
|
return vec4( WHITE_NOISE_SEED ) / float( 0xffffffffu );
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const stratifiedTextureGLSL = /* glsl */`
|
|
2
|
+
|
|
3
|
+
uniform sampler2D stratifiedTexture;
|
|
4
|
+
uniform sampler2D stratifiedOffsetTexture;
|
|
5
|
+
|
|
6
|
+
uint sobolPixelIndex = 0u;
|
|
7
|
+
uint sobolPathIndex = 0u;
|
|
8
|
+
uint sobolBounceIndex = 0u;
|
|
9
|
+
vec4 pixelSeed = vec4( 0 );
|
|
10
|
+
|
|
11
|
+
vec4 rand4( int v ) {
|
|
12
|
+
|
|
13
|
+
ivec2 uv = ivec2( v, sobolBounceIndex );
|
|
14
|
+
vec4 stratifiedSample = texelFetch( stratifiedTexture, uv, 0 );
|
|
15
|
+
return fract( stratifiedSample + pixelSeed.r ); // blue noise + stratified samples
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
vec3 rand3( int v ) {
|
|
20
|
+
|
|
21
|
+
return rand4( v ).xyz;
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
vec2 rand2( int v ) {
|
|
26
|
+
|
|
27
|
+
return rand4( v ).xy;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
float rand( int v ) {
|
|
32
|
+
|
|
33
|
+
return rand4( v ).x;
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void rng_initialize( vec2 screenCoord, int frame ) {
|
|
38
|
+
|
|
39
|
+
// tile the small noise texture across the entire screen
|
|
40
|
+
ivec2 noiseSize = ivec2( textureSize( stratifiedOffsetTexture, 0 ) );
|
|
41
|
+
pixelSeed = texelFetch( stratifiedOffsetTexture, ivec2( screenCoord.xy ) % noiseSize, 0 );
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
`;
|
|
@@ -26,10 +26,17 @@ export const equirectSamplingGLSL = /* glsl */`
|
|
|
26
26
|
// samples the color given env map with CDF and returns the pdf of the direction
|
|
27
27
|
float sampleEquirect( vec3 direction, inout vec3 color ) {
|
|
28
28
|
|
|
29
|
+
float totalSum = envMapInfo.totalSum;
|
|
30
|
+
if ( totalSum == 0.0 ) {
|
|
31
|
+
|
|
32
|
+
color = vec3( 0.0 );
|
|
33
|
+
return 1.0;
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
vec2 uv = equirectDirectionToUv( direction );
|
|
30
38
|
color = texture2D( envMapInfo.map, uv ).rgb;
|
|
31
39
|
|
|
32
|
-
float totalSum = envMapInfo.totalSum;
|
|
33
40
|
float lum = luminance( color );
|
|
34
41
|
ivec2 resolution = textureSize( envMapInfo.map, 0 );
|
|
35
42
|
float pdf = lum / totalSum;
|
|
@@ -61,18 +61,16 @@ export const lightsStructGLSL = /* glsl */`
|
|
|
61
61
|
vec4 s4 = texelFetch1D( tex, i + 4u );
|
|
62
62
|
vec4 s5 = texelFetch1D( tex, i + 5u );
|
|
63
63
|
l.radius = s4.r;
|
|
64
|
-
l.
|
|
65
|
-
l.
|
|
66
|
-
l.
|
|
64
|
+
l.decay = s4.g;
|
|
65
|
+
l.distance = s4.b;
|
|
66
|
+
l.coneCos = s4.a;
|
|
67
67
|
|
|
68
|
-
l.
|
|
69
|
-
l.
|
|
70
|
-
l.iesProfile = int( round( s5.b ) );
|
|
68
|
+
l.penumbraCos = s5.r;
|
|
69
|
+
l.iesProfile = int( round( s5.g ) );
|
|
71
70
|
|
|
72
71
|
} else {
|
|
73
72
|
|
|
74
73
|
l.radius = 0.0;
|
|
75
|
-
l.near = 0.0;
|
|
76
74
|
l.decay = 0.0;
|
|
77
75
|
l.distance = 0.0;
|
|
78
76
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DataTexture, FloatType, NearestFilter, RGBAFormat, RGFormat, RedFormat } from 'three';
|
|
2
|
+
import { BlueNoiseGenerator } from './blueNoise/BlueNoiseGenerator.js';
|
|
3
|
+
|
|
4
|
+
function getStride( channels ) {
|
|
5
|
+
|
|
6
|
+
if ( channels >= 3 ) {
|
|
7
|
+
|
|
8
|
+
return 4;
|
|
9
|
+
|
|
10
|
+
} else {
|
|
11
|
+
|
|
12
|
+
return channels;
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getFormat( channels ) {
|
|
19
|
+
|
|
20
|
+
switch ( channels ) {
|
|
21
|
+
|
|
22
|
+
case 1:
|
|
23
|
+
return RedFormat;
|
|
24
|
+
case 2:
|
|
25
|
+
return RGFormat;
|
|
26
|
+
default:
|
|
27
|
+
return RGBAFormat;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class BlueNoiseTexture extends DataTexture {
|
|
34
|
+
|
|
35
|
+
constructor( size = 64, channels = 1 ) {
|
|
36
|
+
|
|
37
|
+
super( new Float32Array( 4 ), 1, 1, RGBAFormat, FloatType );
|
|
38
|
+
this.minFilter = NearestFilter;
|
|
39
|
+
this.magFilter = NearestFilter;
|
|
40
|
+
|
|
41
|
+
this.size = size;
|
|
42
|
+
this.channels = channels;
|
|
43
|
+
this.update();
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
update() {
|
|
48
|
+
|
|
49
|
+
const channels = this.channels;
|
|
50
|
+
const size = this.size;
|
|
51
|
+
const generator = new BlueNoiseGenerator();
|
|
52
|
+
generator.channels = channels;
|
|
53
|
+
generator.size = size;
|
|
54
|
+
|
|
55
|
+
const stride = getStride( channels );
|
|
56
|
+
const format = getFormat( stride );
|
|
57
|
+
if ( this.image.width !== size || format !== this.format ) {
|
|
58
|
+
|
|
59
|
+
this.image.width = size;
|
|
60
|
+
this.image.height = size;
|
|
61
|
+
this.image.data = new Float32Array( ( size ** 2 ) * stride );
|
|
62
|
+
this.format = format;
|
|
63
|
+
this.dispose();
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const data = this.image.data;
|
|
68
|
+
for ( let i = 0, l = channels; i < l; i ++ ) {
|
|
69
|
+
|
|
70
|
+
const result = generator.generate();
|
|
71
|
+
const bin = result.data;
|
|
72
|
+
const maxValue = result.maxValue;
|
|
73
|
+
|
|
74
|
+
for ( let j = 0, l2 = bin.length; j < l2; j ++ ) {
|
|
75
|
+
|
|
76
|
+
const value = bin[ j ] / maxValue;
|
|
77
|
+
data[ j * stride + i ] = value;
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.needsUpdate = true;
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
}
|
|
@@ -2,14 +2,13 @@ import {
|
|
|
2
2
|
ClampToEdgeWrapping,
|
|
3
3
|
Color,
|
|
4
4
|
DataTexture,
|
|
5
|
-
DataUtils,
|
|
6
5
|
EquirectangularReflectionMapping,
|
|
7
|
-
HalfFloatType,
|
|
8
6
|
LinearFilter,
|
|
9
7
|
RepeatWrapping,
|
|
10
8
|
RGBAFormat,
|
|
11
9
|
Spherical,
|
|
12
10
|
Vector2,
|
|
11
|
+
FloatType
|
|
13
12
|
} from 'three';
|
|
14
13
|
|
|
15
14
|
const _uv = new Vector2();
|
|
@@ -21,8 +20,8 @@ export class ProceduralEquirectTexture extends DataTexture {
|
|
|
21
20
|
constructor( width = 512, height = 512 ) {
|
|
22
21
|
|
|
23
22
|
super(
|
|
24
|
-
new
|
|
25
|
-
width, height, RGBAFormat,
|
|
23
|
+
new Float32Array( width * height * 4 ),
|
|
24
|
+
width, height, RGBAFormat, FloatType, EquirectangularReflectionMapping,
|
|
26
25
|
RepeatWrapping, ClampToEdgeWrapping, LinearFilter, LinearFilter,
|
|
27
26
|
);
|
|
28
27
|
|
|
@@ -54,10 +53,10 @@ export class ProceduralEquirectTexture extends DataTexture {
|
|
|
54
53
|
|
|
55
54
|
const i = y * width + x;
|
|
56
55
|
const i4 = 4 * i;
|
|
57
|
-
data[ i4 + 0 ] =
|
|
58
|
-
data[ i4 + 1 ] =
|
|
59
|
-
data[ i4 + 2 ] =
|
|
60
|
-
data[ i4 + 3 ] =
|
|
56
|
+
data[ i4 + 0 ] = ( _color.r );
|
|
57
|
+
data[ i4 + 1 ] = ( _color.g );
|
|
58
|
+
data[ i4 + 2 ] = ( _color.b );
|
|
59
|
+
data[ i4 + 3 ] = ( 1.0 );
|
|
61
60
|
|
|
62
61
|
}
|
|
63
62
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { shuffleArray, fillWithOnes } from './utils.js';
|
|
2
|
+
import { BlueNoiseSamples } from './BlueNoiseSamples.js';
|
|
3
|
+
|
|
4
|
+
export class BlueNoiseGenerator {
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
|
|
8
|
+
this.random = Math.random;
|
|
9
|
+
this.sigma = 1.5;
|
|
10
|
+
this.size = 64;
|
|
11
|
+
this.majorityPointsRatio = 0.1;
|
|
12
|
+
|
|
13
|
+
this.samples = new BlueNoiseSamples( 1 );
|
|
14
|
+
this.savedSamples = new BlueNoiseSamples( 1 );
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
generate() {
|
|
19
|
+
|
|
20
|
+
// http://cv.ulichney.com/papers/1993-void-cluster.pdf
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
samples,
|
|
24
|
+
savedSamples,
|
|
25
|
+
sigma,
|
|
26
|
+
majorityPointsRatio,
|
|
27
|
+
size,
|
|
28
|
+
} = this;
|
|
29
|
+
|
|
30
|
+
samples.resize( size );
|
|
31
|
+
samples.setSigma( sigma );
|
|
32
|
+
|
|
33
|
+
// 1. Randomly place the minority points.
|
|
34
|
+
const pointCount = Math.floor( size * size * majorityPointsRatio );
|
|
35
|
+
const initialSamples = samples.binaryPattern;
|
|
36
|
+
|
|
37
|
+
fillWithOnes( initialSamples, pointCount );
|
|
38
|
+
shuffleArray( initialSamples, this.random );
|
|
39
|
+
|
|
40
|
+
for ( let i = 0, l = initialSamples.length; i < l; i ++ ) {
|
|
41
|
+
|
|
42
|
+
if ( initialSamples[ i ] === 1 ) {
|
|
43
|
+
|
|
44
|
+
samples.addPointIndex( i );
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 2. Remove minority point that is in densest cluster and place it in the largest void.
|
|
51
|
+
while ( true ) {
|
|
52
|
+
|
|
53
|
+
const clusterIndex = samples.findCluster();
|
|
54
|
+
samples.removePointIndex( clusterIndex );
|
|
55
|
+
|
|
56
|
+
const voidIndex = samples.findVoid();
|
|
57
|
+
if ( clusterIndex === voidIndex ) {
|
|
58
|
+
|
|
59
|
+
samples.addPointIndex( clusterIndex );
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
samples.addPointIndex( voidIndex );
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 3. PHASE I: Assign a rank to each progressively less dense cluster point and put it
|
|
69
|
+
// in the dither array.
|
|
70
|
+
const ditherArray = new Uint32Array( size * size );
|
|
71
|
+
savedSamples.copy( samples );
|
|
72
|
+
|
|
73
|
+
let rank;
|
|
74
|
+
rank = samples.count - 1;
|
|
75
|
+
while ( rank >= 0 ) {
|
|
76
|
+
|
|
77
|
+
const clusterIndex = samples.findCluster();
|
|
78
|
+
samples.removePointIndex( clusterIndex );
|
|
79
|
+
|
|
80
|
+
ditherArray[ clusterIndex ] = rank;
|
|
81
|
+
rank --;
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 4. PHASE II: Do the same thing for the largest voids up to half of the total pixels using
|
|
86
|
+
// the initial binary pattern.
|
|
87
|
+
const totalSize = size * size;
|
|
88
|
+
rank = savedSamples.count;
|
|
89
|
+
while ( rank < totalSize / 2 ) {
|
|
90
|
+
|
|
91
|
+
const voidIndex = savedSamples.findVoid();
|
|
92
|
+
savedSamples.addPointIndex( voidIndex );
|
|
93
|
+
ditherArray[ voidIndex ] = rank;
|
|
94
|
+
rank ++;
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 5. PHASE III: Invert the pattern and finish out by assigning a rank to the remaining
|
|
99
|
+
// and iteratively removing them.
|
|
100
|
+
savedSamples.invert();
|
|
101
|
+
|
|
102
|
+
while ( rank < totalSize ) {
|
|
103
|
+
|
|
104
|
+
const clusterIndex = savedSamples.findCluster();
|
|
105
|
+
savedSamples.removePointIndex( clusterIndex );
|
|
106
|
+
ditherArray[ clusterIndex ] = rank;
|
|
107
|
+
rank ++;
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { data: ditherArray, maxValue: totalSize };
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
}
|