three-gpu-pathtracer 0.0.2 → 0.0.3
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 +107 -44
- package/build/index.module.js +2077 -1141
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +1922 -984
- package/build/index.umd.cjs.map +1 -1
- package/package.json +60 -61
- package/src/core/DynamicPathTracingSceneGenerator.js +106 -105
- package/src/core/PathTracingRenderer.js +233 -140
- package/src/index.js +4 -2
- package/src/materials/AlphaDisplayMaterial.js +48 -0
- package/src/materials/BlendMaterial.js +67 -0
- package/src/materials/PhysicalPathTracingMaterial.js +287 -75
- package/src/shader/shaderEnvMapSampling.js +67 -0
- package/src/shader/shaderMaterialSampling.js +7 -0
- package/src/shader/shaderStructs.js +92 -42
- package/src/shader/shaderUtils.js +37 -0
- package/src/uniforms/EquirectHdrInfoUniform.js +263 -0
- package/src/uniforms/MaterialsTexture.js +173 -0
- package/src/uniforms/RenderTarget2DArray.js +80 -80
- package/src/utils/BlurredEnvMapGenerator.js +113 -0
- package/src/utils/GeometryPreparationUtils.js +194 -200
- package/src/uniforms/EquirectPdfUniform.js +0 -132
- package/src/uniforms/MaterialStructArrayUniform.js +0 -18
- package/src/uniforms/MaterialStructUniform.js +0 -114
|
@@ -1,42 +1,92 @@
|
|
|
1
|
-
export const shaderMaterialStructs = /* glsl */ `
|
|
2
|
-
|
|
3
|
-
struct PhysicalCamera {
|
|
4
|
-
|
|
5
|
-
float focusDistance;
|
|
6
|
-
float anamorphicRatio;
|
|
7
|
-
float bokehSize;
|
|
8
|
-
int apertureBlades;
|
|
9
|
-
float apertureRotation;
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
struct
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
1
|
+
export const shaderMaterialStructs = /* glsl */ `
|
|
2
|
+
|
|
3
|
+
struct PhysicalCamera {
|
|
4
|
+
|
|
5
|
+
float focusDistance;
|
|
6
|
+
float anamorphicRatio;
|
|
7
|
+
float bokehSize;
|
|
8
|
+
int apertureBlades;
|
|
9
|
+
float apertureRotation;
|
|
10
|
+
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
struct EquirectHdrInfo {
|
|
14
|
+
|
|
15
|
+
sampler2D marginalWeights;
|
|
16
|
+
sampler2D conditionalWeights;
|
|
17
|
+
sampler2D map;
|
|
18
|
+
sampler2D totalSum;
|
|
19
|
+
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
struct Material {
|
|
23
|
+
|
|
24
|
+
vec3 color;
|
|
25
|
+
int map;
|
|
26
|
+
|
|
27
|
+
float metalness;
|
|
28
|
+
int metalnessMap;
|
|
29
|
+
|
|
30
|
+
float roughness;
|
|
31
|
+
int roughnessMap;
|
|
32
|
+
|
|
33
|
+
float ior;
|
|
34
|
+
float transmission;
|
|
35
|
+
int transmissionMap;
|
|
36
|
+
|
|
37
|
+
float emissiveIntensity;
|
|
38
|
+
vec3 emissive;
|
|
39
|
+
int emissiveMap;
|
|
40
|
+
|
|
41
|
+
int normalMap;
|
|
42
|
+
vec2 normalScale;
|
|
43
|
+
|
|
44
|
+
float opacity;
|
|
45
|
+
float alphaTest;
|
|
46
|
+
|
|
47
|
+
float side;
|
|
48
|
+
bool matte;
|
|
49
|
+
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
Material readMaterialInfo( sampler2D tex, uint index ) {
|
|
53
|
+
|
|
54
|
+
uint i = index * 6u;
|
|
55
|
+
|
|
56
|
+
vec4 s0 = texelFetch1D( tex, i + 0u );
|
|
57
|
+
vec4 s1 = texelFetch1D( tex, i + 1u );
|
|
58
|
+
vec4 s2 = texelFetch1D( tex, i + 2u );
|
|
59
|
+
vec4 s3 = texelFetch1D( tex, i + 3u );
|
|
60
|
+
vec4 s4 = texelFetch1D( tex, i + 4u );
|
|
61
|
+
vec4 s5 = texelFetch1D( tex, i + 5u );
|
|
62
|
+
|
|
63
|
+
Material m;
|
|
64
|
+
m.color = s0.rgb;
|
|
65
|
+
m.map = floatBitsToInt( s0.a );
|
|
66
|
+
|
|
67
|
+
m.metalness = s1.r;
|
|
68
|
+
m.metalnessMap = floatBitsToInt( s1.g );
|
|
69
|
+
m.roughness = s1.b;
|
|
70
|
+
m.roughnessMap = floatBitsToInt( s1.a );
|
|
71
|
+
|
|
72
|
+
m.ior = s2.r;
|
|
73
|
+
m.transmission = s2.g;
|
|
74
|
+
m.transmissionMap = floatBitsToInt( s2.b );
|
|
75
|
+
m.emissiveIntensity = s2.a;
|
|
76
|
+
|
|
77
|
+
m.emissive = s3.rgb;
|
|
78
|
+
m.emissiveMap = floatBitsToInt( s3.a );
|
|
79
|
+
|
|
80
|
+
m.normalMap = floatBitsToInt( s4.r );
|
|
81
|
+
m.normalScale = s4.gb;
|
|
82
|
+
|
|
83
|
+
m.opacity = s5.r;
|
|
84
|
+
m.alphaTest = s5.g;
|
|
85
|
+
m.side = s5.b;
|
|
86
|
+
m.matte = bool( s5.a );
|
|
87
|
+
|
|
88
|
+
return m;
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
`;
|
|
@@ -188,4 +188,41 @@ export const shaderUtils = /* glsl */`
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
}
|
|
191
|
+
|
|
192
|
+
float colorToLuminance( vec3 color ) {
|
|
193
|
+
|
|
194
|
+
// https://en.wikipedia.org/wiki/Relative_luminance
|
|
195
|
+
return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
|
|
196
|
+
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ray sampling x and z are swapped to align with expected background view
|
|
200
|
+
vec2 equirectDirectionToUv( vec3 direction ) {
|
|
201
|
+
|
|
202
|
+
// from Spherical.setFromCartesianCoords
|
|
203
|
+
vec2 uv = vec2( atan( direction.z, direction.x ), acos( direction.y ) );
|
|
204
|
+
uv /= vec2( 2.0 * PI, PI );
|
|
205
|
+
|
|
206
|
+
// apply adjustments to get values in range [0, 1] and y right side up
|
|
207
|
+
uv.x += 0.5;
|
|
208
|
+
uv.y = 1.0 - uv.y;
|
|
209
|
+
return uv;
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
vec3 equirectUvToDirection( vec2 uv ) {
|
|
214
|
+
|
|
215
|
+
// undo above adjustments
|
|
216
|
+
uv.x -= 0.5;
|
|
217
|
+
uv.y = 1.0 - uv.y;
|
|
218
|
+
|
|
219
|
+
// from Vector3.setFromSphericalCoords
|
|
220
|
+
float theta = uv.x * 2.0 * PI;
|
|
221
|
+
float phi = uv.y * PI;
|
|
222
|
+
|
|
223
|
+
float sinPhi = sin( phi );
|
|
224
|
+
|
|
225
|
+
return vec3( sinPhi * cos( theta ), cos( phi ), sinPhi * sin( theta ) );
|
|
226
|
+
|
|
227
|
+
}
|
|
191
228
|
`;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { DataTexture, FloatType, RedFormat, LinearFilter, DataUtils, HalfFloatType, Source, RepeatWrapping } from 'three';
|
|
2
|
+
|
|
3
|
+
function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
|
|
4
|
+
|
|
5
|
+
let lower = 0;
|
|
6
|
+
let upper = count;
|
|
7
|
+
while ( lower < upper ) {
|
|
8
|
+
|
|
9
|
+
const mid = ~ ~ ( 0.5 * upper + 0.5 * lower );
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// check if the middle array value is above or below the target and shift
|
|
13
|
+
// which half of the array we're looking at
|
|
14
|
+
if ( array[ offset + mid ] < targetValue ) {
|
|
15
|
+
|
|
16
|
+
lower = mid + 1;
|
|
17
|
+
|
|
18
|
+
} else {
|
|
19
|
+
|
|
20
|
+
upper = mid;
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return lower;
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function colorToLuminance( r, g, b ) {
|
|
31
|
+
|
|
32
|
+
// https://en.wikipedia.org/wiki/Relative_luminance
|
|
33
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ensures the data is all floating point values and flipY is false
|
|
38
|
+
function preprocessEnvMap( envMap ) {
|
|
39
|
+
|
|
40
|
+
const map = envMap.clone();
|
|
41
|
+
map.source = new Source( { ...map.image } );
|
|
42
|
+
const { width, height, data } = map.image;
|
|
43
|
+
|
|
44
|
+
// TODO: is there a simple way to avoid cloning and adjusting the env map data here?
|
|
45
|
+
// convert the data from half float uint 16 arrays to float arrays for cdf computation
|
|
46
|
+
let newData = data;
|
|
47
|
+
if ( map.type === HalfFloatType ) {
|
|
48
|
+
|
|
49
|
+
newData = new Float32Array( data.length );
|
|
50
|
+
for ( const i in data ) {
|
|
51
|
+
|
|
52
|
+
newData[ i ] = DataUtils.fromHalfFloat( data[ i ] );
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
map.image.data = newData;
|
|
57
|
+
map.type = FloatType;
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// remove any y flipping for cdf computation
|
|
62
|
+
if ( map.flipY ) {
|
|
63
|
+
|
|
64
|
+
const ogData = newData;
|
|
65
|
+
newData = newData.slice();
|
|
66
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
67
|
+
|
|
68
|
+
for ( let x = 0; x < width; x ++ ) {
|
|
69
|
+
|
|
70
|
+
const newY = height - y - 1;
|
|
71
|
+
const ogIndex = 4 * ( y * width + x );
|
|
72
|
+
const newIndex = 4 * ( newY * width + x );
|
|
73
|
+
|
|
74
|
+
newData[ newIndex + 0 ] = ogData[ ogIndex + 0 ];
|
|
75
|
+
newData[ newIndex + 1 ] = ogData[ ogIndex + 1 ];
|
|
76
|
+
newData[ newIndex + 2 ] = ogData[ ogIndex + 2 ];
|
|
77
|
+
newData[ newIndex + 3 ] = ogData[ ogIndex + 3 ];
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
map.flipY = false;
|
|
84
|
+
map.image.data = newData;
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return map;
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class EquirectHdrInfoUniform {
|
|
93
|
+
|
|
94
|
+
constructor() {
|
|
95
|
+
|
|
96
|
+
// Stores a map of [0, 1] value -> cumulative importance row & pdf
|
|
97
|
+
// used to sampling a random value to a relevant row to sample from
|
|
98
|
+
const marginalWeights = new DataTexture();
|
|
99
|
+
marginalWeights.type = FloatType;
|
|
100
|
+
marginalWeights.format = RedFormat;
|
|
101
|
+
marginalWeights.minFilter = LinearFilter;
|
|
102
|
+
marginalWeights.magFilter = LinearFilter;
|
|
103
|
+
marginalWeights.generateMipmaps = false;
|
|
104
|
+
|
|
105
|
+
// Stores a map of [0, 1] value -> cumulative importance column & pdf
|
|
106
|
+
// used to sampling a random value to a relevant pixel to sample from
|
|
107
|
+
const conditionalWeights = new DataTexture();
|
|
108
|
+
conditionalWeights.type = FloatType;
|
|
109
|
+
conditionalWeights.format = RedFormat;
|
|
110
|
+
conditionalWeights.minFilter = LinearFilter;
|
|
111
|
+
conditionalWeights.magFilter = LinearFilter;
|
|
112
|
+
conditionalWeights.generateMipmaps = false;
|
|
113
|
+
|
|
114
|
+
// store the total sum in a 1x1 tex since some android mobile devices have issues
|
|
115
|
+
// storing large values in structs.
|
|
116
|
+
const totalSumTex = new DataTexture();
|
|
117
|
+
totalSumTex.type = FloatType;
|
|
118
|
+
totalSumTex.format = RedFormat;
|
|
119
|
+
totalSumTex.minFilter = LinearFilter;
|
|
120
|
+
totalSumTex.magFilter = LinearFilter;
|
|
121
|
+
totalSumTex.generateMipmaps = false;
|
|
122
|
+
|
|
123
|
+
this.marginalWeights = marginalWeights;
|
|
124
|
+
this.conditionalWeights = conditionalWeights;
|
|
125
|
+
this.totalSum = totalSumTex;
|
|
126
|
+
this.map = null;
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
dispose() {
|
|
131
|
+
|
|
132
|
+
this.marginalWeights.dispose();
|
|
133
|
+
this.conditionalWeights.dispose();
|
|
134
|
+
this.totalSum.dispose();
|
|
135
|
+
if ( this.map ) this.map.dispose();
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
updateFrom( hdr ) {
|
|
140
|
+
|
|
141
|
+
// https://github.com/knightcrawler25/GLSL-PathTracer/blob/3c6fd9b6b3da47cd50c527eeb45845eef06c55c3/src/loaders/hdrloader.cpp
|
|
142
|
+
// https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
|
|
143
|
+
const map = preprocessEnvMap( hdr );
|
|
144
|
+
map.wrapS = RepeatWrapping;
|
|
145
|
+
map.wrapT = RepeatWrapping;
|
|
146
|
+
|
|
147
|
+
const { width, height, data } = map.image;
|
|
148
|
+
|
|
149
|
+
// "conditional" = "pixel relative to row pixels sum"
|
|
150
|
+
// "marginal" = "row relative to row sum"
|
|
151
|
+
|
|
152
|
+
// track the importance of any given pixel in the image by tracking its weight relative to other pixels in the image
|
|
153
|
+
const pdfConditional = new Float32Array( width * height );
|
|
154
|
+
const cdfConditional = new Float32Array( width * height );
|
|
155
|
+
|
|
156
|
+
const pdfMarginal = new Float32Array( height );
|
|
157
|
+
const cdfMarginal = new Float32Array( height );
|
|
158
|
+
|
|
159
|
+
let totalSumValue = 0.0;
|
|
160
|
+
let cumulativeWeightMarginal = 0.0;
|
|
161
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
162
|
+
|
|
163
|
+
let cumulativeRowWeight = 0.0;
|
|
164
|
+
for ( let x = 0; x < width; x ++ ) {
|
|
165
|
+
|
|
166
|
+
const i = y * width + x;
|
|
167
|
+
const r = data[ 4 * i + 0 ];
|
|
168
|
+
const g = data[ 4 * i + 1 ];
|
|
169
|
+
const b = data[ 4 * i + 2 ];
|
|
170
|
+
|
|
171
|
+
// the probability of the pixel being selected in this row is the
|
|
172
|
+
// scale of the luminance relative to the rest of the pixels.
|
|
173
|
+
// TODO: this should also account for the solid angle of the pixel when sampling
|
|
174
|
+
const weight = colorToLuminance( r, g, b );
|
|
175
|
+
cumulativeRowWeight += weight;
|
|
176
|
+
totalSumValue += weight;
|
|
177
|
+
|
|
178
|
+
pdfConditional[ i ] = weight;
|
|
179
|
+
cdfConditional[ i ] = cumulativeRowWeight;
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// can happen if the row is all black
|
|
184
|
+
if ( cumulativeRowWeight !== 0 ) {
|
|
185
|
+
|
|
186
|
+
// scale the pdf and cdf to [0.0, 1.0]
|
|
187
|
+
for ( let i = y * width, l = y * width + width; i < l; i ++ ) {
|
|
188
|
+
|
|
189
|
+
pdfConditional[ i ] /= cumulativeRowWeight;
|
|
190
|
+
cdfConditional[ i ] /= cumulativeRowWeight;
|
|
191
|
+
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
cumulativeWeightMarginal += cumulativeRowWeight;
|
|
197
|
+
|
|
198
|
+
// compute the marginal pdf and cdf along the height of the map.
|
|
199
|
+
pdfMarginal[ y ] = cumulativeRowWeight;
|
|
200
|
+
cdfMarginal[ y ] = cumulativeWeightMarginal;
|
|
201
|
+
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// can happen if the texture is all black
|
|
205
|
+
if ( cumulativeWeightMarginal !== 0 ) {
|
|
206
|
+
|
|
207
|
+
// scale the marginal pdf and cdf to [0.0, 1.0]
|
|
208
|
+
for ( let i = 0, l = pdfMarginal.length; i < l; i ++ ) {
|
|
209
|
+
|
|
210
|
+
pdfMarginal[ i ] /= cumulativeWeightMarginal;
|
|
211
|
+
cdfMarginal[ i ] /= cumulativeWeightMarginal;
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// compute a sorted index of distributions and the probabilities along them for both
|
|
218
|
+
// the marginal and conditional data. These will be used to sample with a random number
|
|
219
|
+
// to retrieve a uv value to sample in the environment map.
|
|
220
|
+
// These values continually increase so it's okay to interpolate between them.
|
|
221
|
+
const marginalDataArray = new Float32Array( height );
|
|
222
|
+
const conditionalDataArray = new Float32Array( width * height );
|
|
223
|
+
|
|
224
|
+
for ( let i = 0; i < height; i ++ ) {
|
|
225
|
+
|
|
226
|
+
const dist = ( i + 1 ) / height;
|
|
227
|
+
const row = binarySearchFindClosestIndexOf( cdfMarginal, dist );
|
|
228
|
+
|
|
229
|
+
marginalDataArray[ i ] = row / height;
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
234
|
+
|
|
235
|
+
for ( let x = 0; x < width; x ++ ) {
|
|
236
|
+
|
|
237
|
+
const i = y * width + x;
|
|
238
|
+
const dist = ( x + 1 ) / width;
|
|
239
|
+
const col = binarySearchFindClosestIndexOf( cdfConditional, dist, y * width, width );
|
|
240
|
+
|
|
241
|
+
conditionalDataArray[ i ] = col / width;
|
|
242
|
+
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
this.dispose();
|
|
248
|
+
|
|
249
|
+
const { marginalWeights, conditionalWeights, totalSum } = this;
|
|
250
|
+
marginalWeights.image = { width: height, height: 1, data: marginalDataArray };
|
|
251
|
+
marginalWeights.needsUpdate = true;
|
|
252
|
+
|
|
253
|
+
conditionalWeights.image = { width, height, data: conditionalDataArray };
|
|
254
|
+
conditionalWeights.needsUpdate = true;
|
|
255
|
+
|
|
256
|
+
totalSum.image = { width: 1, height: 1, data: new Float32Array( [ totalSumValue ] ) };
|
|
257
|
+
totalSum.needsUpdate = true;
|
|
258
|
+
|
|
259
|
+
this.map = map;
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { DataTexture, RGBAFormat, ClampToEdgeWrapping, FloatType, FrontSide, BackSide, DoubleSide } from 'three';
|
|
2
|
+
|
|
3
|
+
const MATERIAL_PIXELS = 6;
|
|
4
|
+
const MATERIAL_STRIDE = 6 * 4;
|
|
5
|
+
|
|
6
|
+
export class MaterialsTexture extends DataTexture {
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
|
|
10
|
+
super( new Float32Array( 4 ), 1, 1 );
|
|
11
|
+
|
|
12
|
+
this.format = RGBAFormat;
|
|
13
|
+
this.type = FloatType;
|
|
14
|
+
this.wrapS = ClampToEdgeWrapping;
|
|
15
|
+
this.wrapT = ClampToEdgeWrapping;
|
|
16
|
+
this.generateMipmaps = false;
|
|
17
|
+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setSide( materialIndex, side ) {
|
|
21
|
+
|
|
22
|
+
const array = this.image.data;
|
|
23
|
+
const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 2;
|
|
24
|
+
switch ( side ) {
|
|
25
|
+
|
|
26
|
+
case FrontSide:
|
|
27
|
+
array[ index ] = 1;
|
|
28
|
+
break;
|
|
29
|
+
case BackSide:
|
|
30
|
+
array[ index ] = - 1;
|
|
31
|
+
break;
|
|
32
|
+
case DoubleSide:
|
|
33
|
+
array[ index ] = 0;
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getSide( materialIndex ) {
|
|
41
|
+
|
|
42
|
+
const array = this.image.data;
|
|
43
|
+
const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 2;
|
|
44
|
+
switch ( array[ index ] ) {
|
|
45
|
+
|
|
46
|
+
case 0:
|
|
47
|
+
return DoubleSide;
|
|
48
|
+
case 1:
|
|
49
|
+
return FrontSide;
|
|
50
|
+
case - 1:
|
|
51
|
+
return BackSide;
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return 0;
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setMatte( materialIndex, matte ) {
|
|
60
|
+
|
|
61
|
+
const array = this.image.data;
|
|
62
|
+
const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 3;
|
|
63
|
+
array[ index ] = matte ? 1 : 0;
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getMatte( materialIndex ) {
|
|
68
|
+
|
|
69
|
+
const array = this.image.data;
|
|
70
|
+
const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 3;
|
|
71
|
+
return Boolean( array[ index ] );
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updateFrom( materials, textures ) {
|
|
76
|
+
|
|
77
|
+
function getTexture( material, key, def = - 1 ) {
|
|
78
|
+
|
|
79
|
+
return key in material ? textures.indexOf( material[ key ] ) : def;
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getField( material, key, def ) {
|
|
84
|
+
|
|
85
|
+
return key in material ? material[ key ] : def;
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let index = 0;
|
|
90
|
+
const pixelCount = materials.length * MATERIAL_PIXELS;
|
|
91
|
+
const dimension = Math.ceil( Math.sqrt( pixelCount ) );
|
|
92
|
+
|
|
93
|
+
if ( this.image.width !== dimension ) {
|
|
94
|
+
|
|
95
|
+
this.dispose();
|
|
96
|
+
|
|
97
|
+
this.image.data = new Float32Array( dimension * dimension * 4 );
|
|
98
|
+
this.image.width = dimension;
|
|
99
|
+
this.image.height = dimension;
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const floatArray = this.image.data;
|
|
104
|
+
const intArray = new Int32Array( floatArray.buffer );
|
|
105
|
+
|
|
106
|
+
for ( let i = 0, l = materials.length; i < l; i ++ ) {
|
|
107
|
+
|
|
108
|
+
const m = materials[ i ];
|
|
109
|
+
|
|
110
|
+
// color
|
|
111
|
+
floatArray[ index ++ ] = m.color.r;
|
|
112
|
+
floatArray[ index ++ ] = m.color.g;
|
|
113
|
+
floatArray[ index ++ ] = m.color.b;
|
|
114
|
+
intArray[ index ++ ] = getTexture( m, 'map' );
|
|
115
|
+
|
|
116
|
+
// metalness & roughness
|
|
117
|
+
floatArray[ index ++ ] = getField( m, 'metalness', 0.0 );
|
|
118
|
+
intArray[ index ++ ] = textures.indexOf( m.metalnessMap );
|
|
119
|
+
floatArray[ index ++ ] = getField( m, 'roughness', 0.0 );
|
|
120
|
+
intArray[ index ++ ] = textures.indexOf( m.roughnessMap );
|
|
121
|
+
|
|
122
|
+
// transmission & emissiveIntensity
|
|
123
|
+
floatArray[ index ++ ] = getField( m, 'ior', 1.0 );
|
|
124
|
+
floatArray[ index ++ ] = getField( m, 'transmission', 0.0 );
|
|
125
|
+
intArray[ index ++ ] = getTexture( m, 'transmissionMap' );
|
|
126
|
+
floatArray[ index ++ ] = getField( m, 'emissiveIntensity', 0.0 );
|
|
127
|
+
|
|
128
|
+
// emission
|
|
129
|
+
if ( 'emissive' in m ) {
|
|
130
|
+
|
|
131
|
+
floatArray[ index ++ ] = m.emissive.r;
|
|
132
|
+
floatArray[ index ++ ] = m.emissive.g;
|
|
133
|
+
floatArray[ index ++ ] = m.emissive.b;
|
|
134
|
+
|
|
135
|
+
} else {
|
|
136
|
+
|
|
137
|
+
floatArray[ index ++ ] = 0.0;
|
|
138
|
+
floatArray[ index ++ ] = 0.0;
|
|
139
|
+
floatArray[ index ++ ] = 0.0;
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
intArray[ index ++ ] = getTexture( m, 'emissiveMap' );
|
|
144
|
+
|
|
145
|
+
// normals
|
|
146
|
+
intArray[ index ++ ] = getTexture( m, 'normalMap' );
|
|
147
|
+
if ( 'normalScale' in m ) {
|
|
148
|
+
|
|
149
|
+
floatArray[ index ++ ] = m.normalScale.x;
|
|
150
|
+
floatArray[ index ++ ] = m.normalScale.y;
|
|
151
|
+
|
|
152
|
+
} else {
|
|
153
|
+
|
|
154
|
+
floatArray[ index ++ ] = 1;
|
|
155
|
+
floatArray[ index ++ ] = 1;
|
|
156
|
+
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
index ++;
|
|
160
|
+
|
|
161
|
+
// side & matte
|
|
162
|
+
floatArray[ index ++ ] = m.opacity;
|
|
163
|
+
floatArray[ index ++ ] = m.alphaTest;
|
|
164
|
+
index ++; // side
|
|
165
|
+
index ++; // matte
|
|
166
|
+
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.needsUpdate = true;
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
}
|