three-gpu-pathtracer 0.0.16 → 0.0.18
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 +3 -1
- package/build/index.module.js +1202 -471
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +1200 -470
- package/build/index.umd.cjs.map +1 -1
- package/package.json +5 -6
- package/src/core/DynamicPathTracingSceneGenerator.js +80 -35
- package/src/core/PathTracingRenderer.js +36 -38
- package/src/core/PathTracingSceneGenerator.js +11 -55
- package/src/materials/debug/GraphMaterial.js +1 -1
- package/src/materials/fullscreen/AlphaDisplayMaterial.js +1 -1
- package/src/materials/fullscreen/DenoiseMaterial.js +1 -1
- package/src/materials/fullscreen/GradientMapMaterial.js +1 -1
- package/src/materials/pathtracing/LambertPathTracingMaterial.js +5 -4
- package/src/materials/pathtracing/PhysicalPathTracingMaterial.js +87 -50
- package/src/materials/pathtracing/glsl/attenuateHit.glsl.js +21 -20
- package/src/materials/pathtracing/glsl/cameraUtils.glsl.js +2 -2
- package/src/materials/pathtracing/glsl/directLightContribution.glsl.js +12 -8
- package/src/materials/pathtracing/glsl/getSurfaceRecord.glsl.js +3 -2
- package/src/materials/pathtracing/glsl/traceScene.glsl.js +11 -13
- package/src/materials/surface/AmbientOcclusionMaterial.js +4 -3
- package/src/shader/bsdf/bsdfSampling.glsl.js +21 -17
- package/src/shader/common/bvhAnyHit.glsl.js +2 -2
- package/src/shader/common/fresnel.glsl.js +15 -9
- package/src/shader/common/intersectShapes.glsl.js +2 -2
- package/src/shader/rand/pcg.glsl.js +4 -4
- package/src/shader/rand/sobol.glsl.js +3 -3
- package/src/shader/rand/stratifiedTexture.glsl.js +45 -0
- package/src/shader/sampling/equirectSampling.glsl.js +10 -10
- package/src/shader/sampling/lightSampling.glsl.js +30 -37
- package/src/shader/structs/fogMaterialBvh.glsl.js +2 -2
- package/src/shader/structs/lightsStruct.glsl.js +15 -6
- package/src/shader/structs/materialStruct.glsl.js +16 -15
- package/src/textures/BlueNoiseTexture.js +87 -0
- package/src/textures/ProceduralEquirectTexture.js +6 -6
- 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 +59 -21
- package/src/uniforms/IESProfilesTexture.js +2 -2
- package/src/uniforms/LightsInfoUniformStruct.js +15 -9
- package/src/uniforms/MaterialsTexture.js +3 -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/BlurredEnvMapGenerator.js +4 -4
- package/src/utils/GeometryPreparationUtils.js +8 -95
- package/src/utils/IESLoader.js +7 -5
- package/src/utils/TextureUtils.js +15 -0
- package/src/workers/PathTracingSceneWorker.js +18 -8
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
Color,
|
|
4
4
|
DataTexture,
|
|
5
5
|
EquirectangularReflectionMapping,
|
|
6
|
-
FloatType,
|
|
7
6
|
LinearFilter,
|
|
8
7
|
RepeatWrapping,
|
|
9
8
|
RGBAFormat,
|
|
10
9
|
Spherical,
|
|
11
10
|
Vector2,
|
|
11
|
+
FloatType
|
|
12
12
|
} from 'three';
|
|
13
13
|
|
|
14
14
|
const _uv = new Vector2();
|
|
@@ -17,7 +17,7 @@ const _polar = new Spherical();
|
|
|
17
17
|
const _color = new Color();
|
|
18
18
|
export class ProceduralEquirectTexture extends DataTexture {
|
|
19
19
|
|
|
20
|
-
constructor( width, height ) {
|
|
20
|
+
constructor( width = 512, height = 512 ) {
|
|
21
21
|
|
|
22
22
|
super(
|
|
23
23
|
new Float32Array( width * height * 4 ),
|
|
@@ -53,10 +53,10 @@ export class ProceduralEquirectTexture extends DataTexture {
|
|
|
53
53
|
|
|
54
54
|
const i = y * width + x;
|
|
55
55
|
const i4 = 4 * i;
|
|
56
|
-
data[ i4 + 0 ] = _color.r;
|
|
57
|
-
data[ i4 + 1 ] = _color.g;
|
|
58
|
-
data[ i4 + 2 ] = _color.b;
|
|
59
|
-
data[ i4 + 3 ] = 1.0;
|
|
56
|
+
data[ i4 + 0 ] = ( _color.r );
|
|
57
|
+
data[ i4 + 1 ] = ( _color.g );
|
|
58
|
+
data[ i4 + 2 ] = ( _color.b );
|
|
59
|
+
data[ i4 + 3 ] = ( 1.0 );
|
|
60
60
|
|
|
61
61
|
}
|
|
62
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
|
+
}
|
|
@@ -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,5 @@
|
|
|
1
|
-
import { DataTexture,
|
|
1
|
+
import { DataTexture, RedFormat, LinearFilter, DataUtils, HalfFloatType, Source, RepeatWrapping, RGBAFormat, FloatType, ClampToEdgeWrapping } from 'three';
|
|
2
|
+
import { toHalfFloatArray } from '../utils/TextureUtils.js';
|
|
2
3
|
|
|
3
4
|
function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
|
|
4
5
|
|
|
@@ -38,7 +39,7 @@ function colorToLuminance( r, g, b ) {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
// ensures the data is all floating point values and flipY is false
|
|
41
|
-
function preprocessEnvMap( envMap ) {
|
|
42
|
+
function preprocessEnvMap( envMap, targetType = HalfFloatType ) {
|
|
42
43
|
|
|
43
44
|
const map = envMap.clone();
|
|
44
45
|
map.source = new Source( { ...map.image } );
|
|
@@ -47,17 +48,54 @@ function preprocessEnvMap( envMap ) {
|
|
|
47
48
|
// TODO: is there a simple way to avoid cloning and adjusting the env map data here?
|
|
48
49
|
// convert the data from half float uint 16 arrays to float arrays for cdf computation
|
|
49
50
|
let newData = data;
|
|
50
|
-
if ( map.type
|
|
51
|
+
if ( map.type !== targetType ) {
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
for ( const i in data ) {
|
|
53
|
+
if ( targetType === HalfFloatType ) {
|
|
54
54
|
|
|
55
|
-
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
|
+
}
|
|
56
94
|
|
|
57
95
|
}
|
|
58
96
|
|
|
59
97
|
map.image.data = newData;
|
|
60
|
-
map.type =
|
|
98
|
+
map.type = targetType;
|
|
61
99
|
|
|
62
100
|
}
|
|
63
101
|
|
|
@@ -98,8 +136,8 @@ export class EquirectHdrInfoUniform {
|
|
|
98
136
|
|
|
99
137
|
// Default to a white texture and associated weights so we don't
|
|
100
138
|
// just render black initially.
|
|
101
|
-
const whiteTex = new DataTexture( new Float32Array( [ 1, 1, 1, 1 ] ), 1, 1 );
|
|
102
|
-
whiteTex.type =
|
|
139
|
+
const whiteTex = new DataTexture( toHalfFloatArray( new Float32Array( [ 1, 1, 1, 1 ] ) ), 1, 1 );
|
|
140
|
+
whiteTex.type = HalfFloatType;
|
|
103
141
|
whiteTex.format = RGBAFormat;
|
|
104
142
|
whiteTex.minFilter = LinearFilter;
|
|
105
143
|
whiteTex.magFilter = LinearFilter;
|
|
@@ -110,8 +148,8 @@ export class EquirectHdrInfoUniform {
|
|
|
110
148
|
|
|
111
149
|
// Stores a map of [0, 1] value -> cumulative importance row & pdf
|
|
112
150
|
// used to sampling a random value to a relevant row to sample from
|
|
113
|
-
const marginalWeights = new DataTexture( new Float32Array( [ 0, 1 ] ), 1, 2 );
|
|
114
|
-
marginalWeights.type =
|
|
151
|
+
const marginalWeights = new DataTexture( toHalfFloatArray( new Float32Array( [ 0, 1 ] ) ), 1, 2 );
|
|
152
|
+
marginalWeights.type = HalfFloatType;
|
|
115
153
|
marginalWeights.format = RedFormat;
|
|
116
154
|
marginalWeights.minFilter = LinearFilter;
|
|
117
155
|
marginalWeights.magFilter = LinearFilter;
|
|
@@ -120,8 +158,8 @@ export class EquirectHdrInfoUniform {
|
|
|
120
158
|
|
|
121
159
|
// Stores a map of [0, 1] value -> cumulative importance column & pdf
|
|
122
160
|
// used to sampling a random value to a relevant pixel to sample from
|
|
123
|
-
const conditionalWeights = new DataTexture( new Float32Array( [ 0, 0, 1, 1 ] ), 2, 2 );
|
|
124
|
-
conditionalWeights.type =
|
|
161
|
+
const conditionalWeights = new DataTexture( toHalfFloatArray( new Float32Array( [ 0, 0, 1, 1 ] ) ), 2, 2 );
|
|
162
|
+
conditionalWeights.type = HalfFloatType;
|
|
125
163
|
conditionalWeights.format = RedFormat;
|
|
126
164
|
conditionalWeights.minFilter = LinearFilter;
|
|
127
165
|
conditionalWeights.magFilter = LinearFilter;
|
|
@@ -149,7 +187,7 @@ export class EquirectHdrInfoUniform {
|
|
|
149
187
|
// https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
|
|
150
188
|
const map = preprocessEnvMap( hdr );
|
|
151
189
|
map.wrapS = RepeatWrapping;
|
|
152
|
-
map.wrapT =
|
|
190
|
+
map.wrapT = ClampToEdgeWrapping;
|
|
153
191
|
|
|
154
192
|
const { width, height, data } = map.image;
|
|
155
193
|
|
|
@@ -171,9 +209,9 @@ export class EquirectHdrInfoUniform {
|
|
|
171
209
|
for ( let x = 0; x < width; x ++ ) {
|
|
172
210
|
|
|
173
211
|
const i = y * width + x;
|
|
174
|
-
const r = data[ 4 * i + 0 ];
|
|
175
|
-
const g = data[ 4 * i + 1 ];
|
|
176
|
-
const b = data[ 4 * i + 2 ];
|
|
212
|
+
const r = DataUtils.fromHalfFloat( data[ 4 * i + 0 ] );
|
|
213
|
+
const g = DataUtils.fromHalfFloat( data[ 4 * i + 1 ] );
|
|
214
|
+
const b = DataUtils.fromHalfFloat( data[ 4 * i + 2 ] );
|
|
177
215
|
|
|
178
216
|
// the probability of the pixel being selected in this row is the
|
|
179
217
|
// scale of the luminance relative to the rest of the pixels.
|
|
@@ -225,8 +263,8 @@ export class EquirectHdrInfoUniform {
|
|
|
225
263
|
// the marginal and conditional data. These will be used to sample with a random number
|
|
226
264
|
// to retrieve a uv value to sample in the environment map.
|
|
227
265
|
// These values continually increase so it's okay to interpolate between them.
|
|
228
|
-
const marginalDataArray = new
|
|
229
|
-
const conditionalDataArray = new
|
|
266
|
+
const marginalDataArray = new Uint16Array( height );
|
|
267
|
+
const conditionalDataArray = new Uint16Array( width * height );
|
|
230
268
|
|
|
231
269
|
// we add a half texel offset so we're sampling the center of the pixel
|
|
232
270
|
for ( let i = 0; i < height; i ++ ) {
|
|
@@ -234,7 +272,7 @@ export class EquirectHdrInfoUniform {
|
|
|
234
272
|
const dist = ( i + 1 ) / height;
|
|
235
273
|
const row = binarySearchFindClosestIndexOf( cdfMarginal, dist );
|
|
236
274
|
|
|
237
|
-
marginalDataArray[ i ] = ( row + 0.5 ) / height;
|
|
275
|
+
marginalDataArray[ i ] = DataUtils.toHalfFloat( ( row + 0.5 ) / height );
|
|
238
276
|
|
|
239
277
|
}
|
|
240
278
|
|
|
@@ -246,7 +284,7 @@ export class EquirectHdrInfoUniform {
|
|
|
246
284
|
const dist = ( x + 1 ) / width;
|
|
247
285
|
const col = binarySearchFindClosestIndexOf( cdfConditional, dist, y * width, width );
|
|
248
286
|
|
|
249
|
-
conditionalDataArray[ i ] = ( col + 0.5 ) / width;
|
|
287
|
+
conditionalDataArray[ i ] = DataUtils.toHalfFloat( ( col + 0.5 ) / width );
|
|
250
288
|
|
|
251
289
|
}
|
|
252
290
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ClampToEdgeWrapping,
|
|
3
3
|
Color,
|
|
4
|
-
|
|
4
|
+
HalfFloatType,
|
|
5
5
|
LinearFilter,
|
|
6
6
|
MeshBasicMaterial,
|
|
7
7
|
NoToneMapping,
|
|
@@ -20,7 +20,7 @@ export class IESProfilesTexture extends WebGLArrayRenderTarget {
|
|
|
20
20
|
|
|
21
21
|
const tex = this.texture;
|
|
22
22
|
tex.format = RGBAFormat;
|
|
23
|
-
tex.type =
|
|
23
|
+
tex.type = HalfFloatType;
|
|
24
24
|
tex.minFilter = LinearFilter;
|
|
25
25
|
tex.magFilter = LinearFilter;
|
|
26
26
|
tex.wrapS = ClampToEdgeWrapping;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DataTexture, RGBAFormat, ClampToEdgeWrapping, FloatType, Vector3, Quaternion, Matrix4 } from 'three';
|
|
1
|
+
import { DataTexture, RGBAFormat, ClampToEdgeWrapping, FloatType, Vector3, Quaternion, Matrix4, NearestFilter } from 'three';
|
|
2
2
|
|
|
3
3
|
const LIGHT_PIXELS = 6;
|
|
4
4
|
const RECT_AREA_LIGHT = 0;
|
|
@@ -16,6 +16,8 @@ export class LightsInfoUniformStruct {
|
|
|
16
16
|
tex.wrapS = ClampToEdgeWrapping;
|
|
17
17
|
tex.wrapT = ClampToEdgeWrapping;
|
|
18
18
|
tex.generateMipmaps = false;
|
|
19
|
+
tex.minFilter = NearestFilter;
|
|
20
|
+
tex.magFilter = NearestFilter;
|
|
19
21
|
|
|
20
22
|
this.tex = tex;
|
|
21
23
|
this.count = 0;
|
|
@@ -46,7 +48,7 @@ export class LightsInfoUniformStruct {
|
|
|
46
48
|
const worldQuaternion = new Quaternion();
|
|
47
49
|
const eye = new Vector3();
|
|
48
50
|
const target = new Vector3();
|
|
49
|
-
const up = new Vector3();
|
|
51
|
+
const up = new Vector3( 0, 1, 0 );
|
|
50
52
|
|
|
51
53
|
for ( let i = 0, l = lights.length; i < l; i ++ ) {
|
|
52
54
|
|
|
@@ -55,6 +57,13 @@ export class LightsInfoUniformStruct {
|
|
|
55
57
|
const baseIndex = i * LIGHT_PIXELS * 4;
|
|
56
58
|
let index = 0;
|
|
57
59
|
|
|
60
|
+
// initialize to 0
|
|
61
|
+
for ( let p = 0; p < LIGHT_PIXELS; p ++ ) {
|
|
62
|
+
|
|
63
|
+
floatArray[ baseIndex + p ] = 0;
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
// sample 1
|
|
59
68
|
// position
|
|
60
69
|
l.getWorldPosition( v );
|
|
@@ -119,7 +128,7 @@ export class LightsInfoUniformStruct {
|
|
|
119
128
|
|
|
120
129
|
} else if ( l.isSpotLight ) {
|
|
121
130
|
|
|
122
|
-
const radius = l.radius;
|
|
131
|
+
const radius = l.radius || 0;
|
|
123
132
|
eye.setFromMatrixPosition( l.matrixWorld );
|
|
124
133
|
target.setFromMatrixPosition( l.target.matrixWorld );
|
|
125
134
|
m.lookAt( eye, target, up );
|
|
@@ -149,24 +158,21 @@ export class LightsInfoUniformStruct {
|
|
|
149
158
|
// radius
|
|
150
159
|
floatArray[ baseIndex + ( index ++ ) ] = radius;
|
|
151
160
|
|
|
152
|
-
// near
|
|
153
|
-
floatArray[ baseIndex + ( index ++ ) ] = l.shadow.camera.near;
|
|
154
|
-
|
|
155
161
|
// decay
|
|
156
162
|
floatArray[ baseIndex + ( index ++ ) ] = l.decay;
|
|
157
163
|
|
|
158
164
|
// distance
|
|
159
165
|
floatArray[ baseIndex + ( index ++ ) ] = l.distance;
|
|
160
166
|
|
|
161
|
-
// sample 6
|
|
162
167
|
// coneCos
|
|
163
168
|
floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle );
|
|
164
169
|
|
|
170
|
+
// sample 6
|
|
165
171
|
// penumbraCos
|
|
166
172
|
floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle * ( 1 - l.penumbra ) );
|
|
167
173
|
|
|
168
174
|
// iesProfile
|
|
169
|
-
floatArray[ baseIndex + ( index ++ ) ] = iesTextures.indexOf( l.iesTexture );
|
|
175
|
+
floatArray[ baseIndex + ( index ++ ) ] = l.iesTexture ? iesTextures.indexOf( l.iesTexture ) : - 1;
|
|
170
176
|
|
|
171
177
|
} else if ( l.isPointLight ) {
|
|
172
178
|
|
|
@@ -183,7 +189,7 @@ export class LightsInfoUniformStruct {
|
|
|
183
189
|
index += 4;
|
|
184
190
|
|
|
185
191
|
// sample 5
|
|
186
|
-
index +=
|
|
192
|
+
index += 1;
|
|
187
193
|
|
|
188
194
|
floatArray[ baseIndex + ( index ++ ) ] = l.decay;
|
|
189
195
|
floatArray[ baseIndex + ( index ++ ) ] = l.distance;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DataTexture, RGBAFormat, ClampToEdgeWrapping, FloatType, FrontSide, BackSide, DoubleSide } from 'three';
|
|
1
|
+
import { DataTexture, RGBAFormat, ClampToEdgeWrapping, FloatType, FrontSide, BackSide, DoubleSide, NearestFilter } from 'three';
|
|
2
2
|
import { reduceTexturesToUniqueSources, getTextureHash } from './utils.js';
|
|
3
3
|
|
|
4
4
|
const MATERIAL_PIXELS = 45;
|
|
@@ -53,6 +53,8 @@ export class MaterialsTexture extends DataTexture {
|
|
|
53
53
|
this.type = FloatType;
|
|
54
54
|
this.wrapS = ClampToEdgeWrapping;
|
|
55
55
|
this.wrapT = ClampToEdgeWrapping;
|
|
56
|
+
this.minFilter = NearestFilter;
|
|
57
|
+
this.magFilter = NearestFilter;
|
|
56
58
|
this.generateMipmaps = false;
|
|
57
59
|
this.threeCompatibilityTransforms = false;
|
|
58
60
|
this.features = new MaterialFeatures();
|