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
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
export class BlueNoiseSamples {
|
|
2
|
+
|
|
3
|
+
constructor( size ) {
|
|
4
|
+
|
|
5
|
+
this.count = 0;
|
|
6
|
+
this.size = - 1;
|
|
7
|
+
this.sigma = - 1;
|
|
8
|
+
this.radius = - 1;
|
|
9
|
+
this.lookupTable = null;
|
|
10
|
+
this.score = null;
|
|
11
|
+
this.binaryPattern = null;
|
|
12
|
+
|
|
13
|
+
this.resize( size );
|
|
14
|
+
this.setSigma( 1.5 );
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
findVoid() {
|
|
19
|
+
|
|
20
|
+
const { score, binaryPattern } = this;
|
|
21
|
+
|
|
22
|
+
let currValue = Infinity;
|
|
23
|
+
let currIndex = - 1;
|
|
24
|
+
for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
|
|
25
|
+
|
|
26
|
+
if ( binaryPattern[ i ] !== 0 ) {
|
|
27
|
+
|
|
28
|
+
continue;
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pScore = score[ i ];
|
|
33
|
+
if ( pScore < currValue ) {
|
|
34
|
+
|
|
35
|
+
currValue = pScore;
|
|
36
|
+
currIndex = i;
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return currIndex;
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
findCluster() {
|
|
47
|
+
|
|
48
|
+
const { score, binaryPattern } = this;
|
|
49
|
+
|
|
50
|
+
let currValue = - Infinity;
|
|
51
|
+
let currIndex = - 1;
|
|
52
|
+
for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
|
|
53
|
+
|
|
54
|
+
if ( binaryPattern[ i ] !== 1 ) {
|
|
55
|
+
|
|
56
|
+
continue;
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const pScore = score[ i ];
|
|
61
|
+
if ( pScore > currValue ) {
|
|
62
|
+
|
|
63
|
+
currValue = pScore;
|
|
64
|
+
currIndex = i;
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return currIndex;
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
setSigma( sigma ) {
|
|
75
|
+
|
|
76
|
+
if ( sigma === this.sigma ) {
|
|
77
|
+
|
|
78
|
+
return;
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// generate a radius in which the score will be updated under the
|
|
83
|
+
// assumption that e^-10 is insignificant enough to be the border at
|
|
84
|
+
// which we drop off.
|
|
85
|
+
const radius = ~ ~ ( Math.sqrt( 10 * 2 * ( sigma ** 2 ) ) + 1 );
|
|
86
|
+
const lookupWidth = 2 * radius + 1;
|
|
87
|
+
const lookupTable = new Float32Array( lookupWidth * lookupWidth );
|
|
88
|
+
const sigma2 = sigma * sigma;
|
|
89
|
+
for ( let x = - radius; x <= radius; x ++ ) {
|
|
90
|
+
|
|
91
|
+
for ( let y = - radius; y <= radius; y ++ ) {
|
|
92
|
+
|
|
93
|
+
const index = ( radius + y ) * lookupWidth + x + radius;
|
|
94
|
+
const dist2 = x * x + y * y;
|
|
95
|
+
lookupTable[ index ] = Math.E ** ( - dist2 / ( 2 * sigma2 ) );
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.lookupTable = lookupTable;
|
|
102
|
+
this.sigma = sigma;
|
|
103
|
+
this.radius = radius;
|
|
104
|
+
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
resize( size ) {
|
|
108
|
+
|
|
109
|
+
if ( this.size !== size ) {
|
|
110
|
+
|
|
111
|
+
this.size = size;
|
|
112
|
+
this.score = new Float32Array( size * size );
|
|
113
|
+
this.binaryPattern = new Uint8Array( size * size );
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
invert() {
|
|
121
|
+
|
|
122
|
+
const { binaryPattern, score, size } = this;
|
|
123
|
+
|
|
124
|
+
score.fill( 0 );
|
|
125
|
+
|
|
126
|
+
for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
|
|
127
|
+
|
|
128
|
+
if ( binaryPattern[ i ] === 0 ) {
|
|
129
|
+
|
|
130
|
+
const y = ~ ~ ( i / size );
|
|
131
|
+
const x = i - y * size;
|
|
132
|
+
this.updateScore( x, y, 1 );
|
|
133
|
+
binaryPattern[ i ] = 1;
|
|
134
|
+
|
|
135
|
+
} else {
|
|
136
|
+
|
|
137
|
+
binaryPattern[ i ] = 0;
|
|
138
|
+
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
updateScore( x, y, multiplier ) {
|
|
146
|
+
|
|
147
|
+
// TODO: Is there a way to keep track of the highest and lowest scores here to avoid have to search over
|
|
148
|
+
// everything in the buffer?
|
|
149
|
+
const { size, score, lookupTable } = this;
|
|
150
|
+
|
|
151
|
+
// const sigma2 = sigma * sigma;
|
|
152
|
+
// const radius = Math.floor( size / 2 );
|
|
153
|
+
const radius = this.radius;
|
|
154
|
+
const lookupWidth = 2 * radius + 1;
|
|
155
|
+
for ( let px = - radius; px <= radius; px ++ ) {
|
|
156
|
+
|
|
157
|
+
for ( let py = - radius; py <= radius; py ++ ) {
|
|
158
|
+
|
|
159
|
+
// const dist2 = px * px + py * py;
|
|
160
|
+
// const value = Math.E ** ( - dist2 / ( 2 * sigma2 ) );
|
|
161
|
+
|
|
162
|
+
const lookupIndex = ( radius + py ) * lookupWidth + px + radius;
|
|
163
|
+
const value = lookupTable[ lookupIndex ];
|
|
164
|
+
|
|
165
|
+
let sx = ( x + px );
|
|
166
|
+
sx = sx < 0 ? size + sx : sx % size;
|
|
167
|
+
|
|
168
|
+
let sy = ( y + py );
|
|
169
|
+
sy = sy < 0 ? size + sy : sy % size;
|
|
170
|
+
|
|
171
|
+
const sindex = sy * size + sx;
|
|
172
|
+
score[ sindex ] += multiplier * value;
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
addPointIndex( index ) {
|
|
181
|
+
|
|
182
|
+
this.binaryPattern[ index ] = 1;
|
|
183
|
+
|
|
184
|
+
const size = this.size;
|
|
185
|
+
const y = ~ ~ ( index / size );
|
|
186
|
+
const x = index - y * size;
|
|
187
|
+
this.updateScore( x, y, 1 );
|
|
188
|
+
this.count ++;
|
|
189
|
+
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
removePointIndex( index ) {
|
|
193
|
+
|
|
194
|
+
this.binaryPattern[ index ] = 0;
|
|
195
|
+
|
|
196
|
+
const size = this.size;
|
|
197
|
+
const y = ~ ~ ( index / size );
|
|
198
|
+
const x = index - y * size;
|
|
199
|
+
this.updateScore( x, y, - 1 );
|
|
200
|
+
this.count --;
|
|
201
|
+
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
copy( source ) {
|
|
205
|
+
|
|
206
|
+
this.resize( source.size );
|
|
207
|
+
this.score.set( source.score );
|
|
208
|
+
this.binaryPattern.set( source.binaryPattern );
|
|
209
|
+
this.setSigma( source.sigma );
|
|
210
|
+
this.count = source.count;
|
|
211
|
+
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function shuffleArray( array, random = Math.random ) {
|
|
2
|
+
|
|
3
|
+
for ( let i = array.length - 1; i > 0; i -- ) {
|
|
4
|
+
|
|
5
|
+
const replaceIndex = ~ ~ ( ( random() - 1e-6 ) * i );
|
|
6
|
+
const tmp = array[ i ];
|
|
7
|
+
array[ i ] = array[ replaceIndex ];
|
|
8
|
+
array[ replaceIndex ] = tmp;
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function fillWithOnes( array, count ) {
|
|
15
|
+
|
|
16
|
+
array.fill( 0 );
|
|
17
|
+
|
|
18
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
19
|
+
|
|
20
|
+
array[ i ] = 1;
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DataTexture, RedFormat, LinearFilter, DataUtils, HalfFloatType, Source, RepeatWrapping, RGBAFormat } from 'three';
|
|
1
|
+
import { DataTexture, RedFormat, LinearFilter, DataUtils, HalfFloatType, Source, RepeatWrapping, RGBAFormat, FloatType, ClampToEdgeWrapping } from 'three';
|
|
2
2
|
import { toHalfFloatArray } from '../utils/TextureUtils.js';
|
|
3
3
|
|
|
4
4
|
function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
|
|
@@ -39,7 +39,7 @@ function colorToLuminance( r, g, b ) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// ensures the data is all floating point values and flipY is false
|
|
42
|
-
function preprocessEnvMap( envMap ) {
|
|
42
|
+
function preprocessEnvMap( envMap, targetType = HalfFloatType ) {
|
|
43
43
|
|
|
44
44
|
const map = envMap.clone();
|
|
45
45
|
map.source = new Source( { ...map.image } );
|
|
@@ -48,17 +48,54 @@ function preprocessEnvMap( envMap ) {
|
|
|
48
48
|
// TODO: is there a simple way to avoid cloning and adjusting the env map data here?
|
|
49
49
|
// convert the data from half float uint 16 arrays to float arrays for cdf computation
|
|
50
50
|
let newData = data;
|
|
51
|
-
if ( map.type
|
|
51
|
+
if ( map.type !== targetType ) {
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
for ( const i in data ) {
|
|
53
|
+
if ( targetType === HalfFloatType ) {
|
|
55
54
|
|
|
56
|
-
newData
|
|
55
|
+
newData = new Uint16Array( data.length );
|
|
56
|
+
|
|
57
|
+
} else {
|
|
58
|
+
|
|
59
|
+
newData = new Float32Array( data.length );
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let maxIntValue;
|
|
64
|
+
if ( data instanceof Int8Array || data instanceof Int16Array || data instanceof Int32Array ) {
|
|
65
|
+
|
|
66
|
+
maxIntValue = 2 ** ( 8 * data.BYTES_PER_ELEMENT - 1 ) - 1;
|
|
67
|
+
|
|
68
|
+
} else {
|
|
69
|
+
|
|
70
|
+
maxIntValue = 2 ** ( 8 * data.BYTES_PER_ELEMENT ) - 1;
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for ( let i = 0, l = data.length; i < l; i ++ ) {
|
|
75
|
+
|
|
76
|
+
let v = data[ i ];
|
|
77
|
+
if ( map.type === HalfFloatType ) {
|
|
78
|
+
|
|
79
|
+
v = DataUtils.fromHalfFloat( data[ i ] );
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if ( map.type !== FloatType && map.type !== HalfFloatType ) {
|
|
84
|
+
|
|
85
|
+
v /= maxIntValue;
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if ( targetType === HalfFloatType ) {
|
|
90
|
+
|
|
91
|
+
newData[ i ] = DataUtils.toHalfFloat( v );
|
|
92
|
+
|
|
93
|
+
}
|
|
57
94
|
|
|
58
95
|
}
|
|
59
96
|
|
|
60
97
|
map.image.data = newData;
|
|
61
|
-
map.type =
|
|
98
|
+
map.type = targetType;
|
|
62
99
|
|
|
63
100
|
}
|
|
64
101
|
|
|
@@ -150,7 +187,7 @@ export class EquirectHdrInfoUniform {
|
|
|
150
187
|
// https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
|
|
151
188
|
const map = preprocessEnvMap( hdr );
|
|
152
189
|
map.wrapS = RepeatWrapping;
|
|
153
|
-
map.wrapT =
|
|
190
|
+
map.wrapT = ClampToEdgeWrapping;
|
|
154
191
|
|
|
155
192
|
const { width, height, data } = map.image;
|
|
156
193
|
|
|
@@ -57,6 +57,13 @@ export class LightsInfoUniformStruct {
|
|
|
57
57
|
const baseIndex = i * LIGHT_PIXELS * 4;
|
|
58
58
|
let index = 0;
|
|
59
59
|
|
|
60
|
+
// initialize to 0
|
|
61
|
+
for ( let p = 0; p < LIGHT_PIXELS * 4; p ++ ) {
|
|
62
|
+
|
|
63
|
+
floatArray[ baseIndex + p ] = 0;
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
// sample 1
|
|
61
68
|
// position
|
|
62
69
|
l.getWorldPosition( v );
|
|
@@ -121,7 +128,7 @@ export class LightsInfoUniformStruct {
|
|
|
121
128
|
|
|
122
129
|
} else if ( l.isSpotLight ) {
|
|
123
130
|
|
|
124
|
-
const radius = l.radius;
|
|
131
|
+
const radius = l.radius || 0;
|
|
125
132
|
eye.setFromMatrixPosition( l.matrixWorld );
|
|
126
133
|
target.setFromMatrixPosition( l.target.matrixWorld );
|
|
127
134
|
m.lookAt( eye, target, up );
|
|
@@ -151,24 +158,21 @@ export class LightsInfoUniformStruct {
|
|
|
151
158
|
// radius
|
|
152
159
|
floatArray[ baseIndex + ( index ++ ) ] = radius;
|
|
153
160
|
|
|
154
|
-
// near
|
|
155
|
-
floatArray[ baseIndex + ( index ++ ) ] = l.shadow.camera.near;
|
|
156
|
-
|
|
157
161
|
// decay
|
|
158
162
|
floatArray[ baseIndex + ( index ++ ) ] = l.decay;
|
|
159
163
|
|
|
160
164
|
// distance
|
|
161
165
|
floatArray[ baseIndex + ( index ++ ) ] = l.distance;
|
|
162
166
|
|
|
163
|
-
// sample 6
|
|
164
167
|
// coneCos
|
|
165
168
|
floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle );
|
|
166
169
|
|
|
170
|
+
// sample 6
|
|
167
171
|
// penumbraCos
|
|
168
172
|
floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle * ( 1 - l.penumbra ) );
|
|
169
173
|
|
|
170
174
|
// iesProfile
|
|
171
|
-
floatArray[ baseIndex + ( index ++ ) ] = iesTextures.indexOf( l.iesTexture );
|
|
175
|
+
floatArray[ baseIndex + ( index ++ ) ] = l.iesTexture ? iesTextures.indexOf( l.iesTexture ) : - 1;
|
|
172
176
|
|
|
173
177
|
} else if ( l.isPointLight ) {
|
|
174
178
|
|
|
@@ -185,7 +189,7 @@ export class LightsInfoUniformStruct {
|
|
|
185
189
|
index += 4;
|
|
186
190
|
|
|
187
191
|
// sample 5
|
|
188
|
-
index +=
|
|
192
|
+
index += 1;
|
|
189
193
|
|
|
190
194
|
floatArray[ baseIndex + ( index ++ ) ] = l.decay;
|
|
191
195
|
floatArray[ baseIndex + ( index ++ ) ] = l.distance;
|
|
@@ -186,7 +186,7 @@ export class MaterialsTexture extends DataTexture {
|
|
|
186
186
|
|
|
187
187
|
let index = 0;
|
|
188
188
|
const pixelCount = materials.length * MATERIAL_PIXELS;
|
|
189
|
-
const dimension = Math.ceil( Math.sqrt( pixelCount ) );
|
|
189
|
+
const dimension = Math.ceil( Math.sqrt( pixelCount ) ) || 1;
|
|
190
190
|
const { threeCompatibilityTransforms, image, features } = this;
|
|
191
191
|
|
|
192
192
|
// get the list of textures with unique sources
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
WebGLArrayRenderTarget,
|
|
3
3
|
RGBAFormat,
|
|
4
4
|
UnsignedByteType,
|
|
5
|
-
MeshBasicMaterial,
|
|
6
5
|
Color,
|
|
7
6
|
RepeatWrapping,
|
|
8
7
|
LinearFilter,
|
|
9
8
|
NoToneMapping,
|
|
9
|
+
ShaderMaterial,
|
|
10
10
|
} from 'three';
|
|
11
11
|
import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
|
|
12
12
|
import { reduceTexturesToUniqueSources } from './utils.js';
|
|
@@ -31,7 +31,7 @@ export class RenderTarget2DArray extends WebGLArrayRenderTarget {
|
|
|
31
31
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
const fsQuad = new FullScreenQuad( new
|
|
34
|
+
const fsQuad = new FullScreenQuad( new CopyMaterial() );
|
|
35
35
|
this.fsQuad = fsQuad;
|
|
36
36
|
|
|
37
37
|
}
|
|
@@ -66,7 +66,6 @@ export class RenderTarget2DArray extends WebGLArrayRenderTarget {
|
|
|
66
66
|
texture.matrix.identity();
|
|
67
67
|
|
|
68
68
|
fsQuad.material.map = texture;
|
|
69
|
-
fsQuad.material.transparent = true;
|
|
70
69
|
|
|
71
70
|
renderer.setRenderTarget( this, i );
|
|
72
71
|
fsQuad.render( renderer );
|
|
@@ -95,3 +94,51 @@ export class RenderTarget2DArray extends WebGLArrayRenderTarget {
|
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
}
|
|
97
|
+
|
|
98
|
+
class CopyMaterial extends ShaderMaterial {
|
|
99
|
+
|
|
100
|
+
get map() {
|
|
101
|
+
|
|
102
|
+
return this.uniforms.map.value;
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
set map( v ) {
|
|
106
|
+
|
|
107
|
+
this.uniforms.map.value = v;
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
constructor() {
|
|
112
|
+
|
|
113
|
+
super( {
|
|
114
|
+
uniforms: {
|
|
115
|
+
|
|
116
|
+
map: { value: null },
|
|
117
|
+
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
vertexShader: /* glsl */`
|
|
121
|
+
varying vec2 vUv;
|
|
122
|
+
void main() {
|
|
123
|
+
|
|
124
|
+
vUv = uv;
|
|
125
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
`,
|
|
129
|
+
|
|
130
|
+
fragmentShader: /* glsl */`
|
|
131
|
+
uniform sampler2D map;
|
|
132
|
+
varying vec2 vUv;
|
|
133
|
+
void main() {
|
|
134
|
+
|
|
135
|
+
gl_FragColor = texture2D( map, vUv );
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
`
|
|
139
|
+
} );
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { DataTexture, FloatType, NearestFilter, RGBAFormat } from 'three';
|
|
2
|
+
import { StratifiedSamplerCombined } from './stratified/StratifiedSamplerCombined.js';
|
|
3
|
+
|
|
4
|
+
export class StratifiedSamplesTexture extends DataTexture {
|
|
5
|
+
|
|
6
|
+
constructor( count = 1, depth = 1, strata = 8 ) {
|
|
7
|
+
|
|
8
|
+
super( new Float32Array( 1 ), 1, 1, RGBAFormat, FloatType );
|
|
9
|
+
this.minFilter = NearestFilter;
|
|
10
|
+
this.magFilter = NearestFilter;
|
|
11
|
+
|
|
12
|
+
this.strata = strata;
|
|
13
|
+
this.sampler = null;
|
|
14
|
+
|
|
15
|
+
this.init( count, depth, strata );
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
init( count, depth, strata = this.strata ) {
|
|
20
|
+
|
|
21
|
+
const { image } = this;
|
|
22
|
+
if ( image.width === depth && image.height === count ) {
|
|
23
|
+
|
|
24
|
+
return;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const dimensions = new Array( count * depth ).fill( 4 );
|
|
29
|
+
const sampler = new StratifiedSamplerCombined( strata, dimensions );
|
|
30
|
+
|
|
31
|
+
image.width = depth;
|
|
32
|
+
image.height = count;
|
|
33
|
+
image.data = sampler.samples;
|
|
34
|
+
|
|
35
|
+
this.sampler = sampler;
|
|
36
|
+
|
|
37
|
+
this.dispose();
|
|
38
|
+
this.next();
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
next() {
|
|
43
|
+
|
|
44
|
+
this.sampler.next();
|
|
45
|
+
this.needsUpdate = true;
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Stratified Sampling based on implementation from hoverinc pathtracer
|
|
2
|
+
// - https://github.com/hoverinc/ray-tracing-renderer
|
|
3
|
+
// - http://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Stratified_Sampling.html
|
|
4
|
+
|
|
5
|
+
export function shuffle( arr ) {
|
|
6
|
+
|
|
7
|
+
for ( let i = arr.length - 1; i > 0; i -- ) {
|
|
8
|
+
|
|
9
|
+
const j = Math.floor( Math.random() * ( i + 1 ) );
|
|
10
|
+
const x = arr[ i ];
|
|
11
|
+
arr[ i ] = arr[ j ];
|
|
12
|
+
arr[ j ] = x;
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return arr;
|
|
17
|
+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// strataCount : The number of bins per dimension
|
|
21
|
+
// dimensions : The number of dimensions to generate stratified values for
|
|
22
|
+
export class StratifiedSampler {
|
|
23
|
+
|
|
24
|
+
constructor( strataCount, dimensions ) {
|
|
25
|
+
|
|
26
|
+
const l = strataCount ** dimensions;
|
|
27
|
+
const strata = new Uint16Array( l );
|
|
28
|
+
let index = l;
|
|
29
|
+
|
|
30
|
+
// each integer represents a statum bin
|
|
31
|
+
for ( let i = 0; i < l; i ++ ) {
|
|
32
|
+
|
|
33
|
+
strata[ i ] = i;
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.samples = new Float32Array( dimensions );
|
|
38
|
+
|
|
39
|
+
this.strataCount = strataCount;
|
|
40
|
+
|
|
41
|
+
this.restart = function () {
|
|
42
|
+
|
|
43
|
+
index = 0;
|
|
44
|
+
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
this.next = function () {
|
|
48
|
+
|
|
49
|
+
const { samples } = this;
|
|
50
|
+
|
|
51
|
+
if ( index >= strata.length ) {
|
|
52
|
+
|
|
53
|
+
shuffle( strata );
|
|
54
|
+
this.restart();
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let stratum = strata[ index ++ ];
|
|
59
|
+
|
|
60
|
+
for ( let i = 0; i < dimensions; i ++ ) {
|
|
61
|
+
|
|
62
|
+
samples[ i ] = ( stratum % strataCount + Math.random() ) / strataCount;
|
|
63
|
+
stratum = Math.floor( stratum / strataCount );
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return samples;
|
|
68
|
+
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Stratified Sampling based on implementation from hoverinc pathtracer
|
|
2
|
+
// - https://github.com/hoverinc/ray-tracing-renderer
|
|
3
|
+
// - http://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Stratified_Sampling.html
|
|
4
|
+
|
|
5
|
+
import { StratifiedSampler } from './StratifiedSampler.js';
|
|
6
|
+
|
|
7
|
+
// Stratified set of data with each tuple stratified separately and combined
|
|
8
|
+
export class StratifiedSamplerCombined {
|
|
9
|
+
|
|
10
|
+
constructor( strataCount, listOfDimensions ) {
|
|
11
|
+
|
|
12
|
+
let totalDim = 0;
|
|
13
|
+
for ( const dim of listOfDimensions ) {
|
|
14
|
+
|
|
15
|
+
totalDim += dim;
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const combined = new Float32Array( totalDim );
|
|
20
|
+
const strataObjs = [];
|
|
21
|
+
let offset = 0;
|
|
22
|
+
for ( const dim of listOfDimensions ) {
|
|
23
|
+
|
|
24
|
+
const sampler = new StratifiedSampler( strataCount, dim );
|
|
25
|
+
sampler.samples = new Float32Array( combined.buffer, offset, sampler.samples.length );
|
|
26
|
+
offset += sampler.samples.length * 4;
|
|
27
|
+
strataObjs.push( sampler );
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.samples = combined;
|
|
32
|
+
|
|
33
|
+
this.strataCount = strataCount;
|
|
34
|
+
|
|
35
|
+
this.next = function () {
|
|
36
|
+
|
|
37
|
+
for ( const strata of strataObjs ) {
|
|
38
|
+
|
|
39
|
+
strata.next();
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return combined;
|
|
44
|
+
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
this.restart = function () {
|
|
48
|
+
|
|
49
|
+
for ( const strata of strataObjs ) {
|
|
50
|
+
|
|
51
|
+
strata.restart();
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
package/src/uniforms/utils.js
CHANGED