three-gpu-pathtracer 0.0.24 → 0.0.26
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 +53 -530
- package/build/index.module.js +262 -50
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +261 -48
- package/build/index.umd.cjs.map +1 -1
- package/package.json +29 -11
- package/src/core/WebGLPathTracer.js +8 -0
- package/src/core/utils/sceneUpdateUtils.js +8 -12
- package/src/detectors/PrecisionMaterial.js +9 -9
- package/src/index.js +1 -0
- package/src/objects/PhysicalSpotLight.js +2 -0
- package/src/shader/bsdf/bsdf_functions.glsl.js +0 -1
- package/src/textures/BlueNoiseTexture.js +6 -6
- package/src/textures/ProceduralEquirectTexture.js +8 -7
- package/src/textures/turquinMetal.png +0 -0
- package/src/uniforms/EquirectHdrInfoUniform.js +9 -4
- package/src/uniforms/FloatAttributeTextureArray.js +11 -11
- package/src/uniforms/MaterialsTexture.js +9 -9
- package/src/webgpu/API.md +762 -0
- package/src/webgpu/AtlasTexture.js +471 -0
- package/src/webgpu/BlurredEnvMapGenerator.js +129 -0
- package/src/webgpu/EquirectBackgroundInfo.js +97 -0
- package/src/webgpu/EquirectHdrInfoNode.js +151 -0
- package/src/webgpu/LightsInfoNode.js +224 -0
- package/src/webgpu/MegaKernelPathTracer.js +277 -0
- package/src/webgpu/PathTracerBackend.js +216 -0
- package/src/webgpu/TurquinTexture.js +169 -0
- package/src/webgpu/WaveFrontPathTracer.js +586 -0
- package/src/webgpu/WebGPUPathTracer.js +1302 -0
- package/src/webgpu/compute/ComputeKernel.js +92 -0
- package/src/webgpu/compute/CopyBufferKernel.js +40 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +470 -0
- package/src/webgpu/compute/SampleDebugKernel.js +51 -0
- package/src/webgpu/compute/TallySampleCountsKernel.js +111 -0
- package/src/webgpu/compute/ZeroOutBufferKernel.js +35 -0
- package/src/webgpu/compute/ZeroOutKernel.js +31 -0
- package/src/webgpu/compute/wavefront/LogicKernel.js +349 -0
- package/src/webgpu/compute/wavefront/MaterialKernel.js +371 -0
- package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +72 -0
- package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +38 -0
- package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +66 -0
- package/src/webgpu/compute/wavefront/TraceRayKernel.js +71 -0
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +67 -0
- package/src/webgpu/compute/wavefront/structs.js +155 -0
- package/src/webgpu/constants.js +62 -0
- package/src/webgpu/denoise/OIDNDenoiser.js +431 -0
- package/src/webgpu/index.d.ts +176 -0
- package/src/webgpu/index.js +11 -0
- package/src/webgpu/materials/GltfCompliantMaterial.js +608 -0
- package/src/webgpu/materials/GraphMaterial.js +331 -0
- package/src/webgpu/materials/PathtracingMaterial.js +89 -0
- package/src/webgpu/materials/RenderToScreenMaterial.js +150 -0
- package/src/webgpu/materials/debug/AtlasDebugMaterial.js +41 -0
- package/src/webgpu/materials/debug/SampleDensityMaterial.js +61 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +1037 -0
- package/src/webgpu/nodes/debugBounds.wgsl.js +228 -0
- package/src/webgpu/nodes/eon.wgsl.js +262 -0
- package/src/webgpu/nodes/ggx.wgsl.js +190 -0
- package/src/webgpu/nodes/lights.wgsl.js +203 -0
- package/src/webgpu/nodes/material.wgsl.js +990 -0
- package/src/webgpu/nodes/rand/bluedither.wgsl.js +92 -0
- package/src/webgpu/nodes/rand/pcg.wgsl.js +81 -0
- package/src/webgpu/nodes/rand/sobol.wgsl.js +287 -0
- package/src/webgpu/nodes/random.wgsl.js +20 -0
- package/src/webgpu/nodes/reset.wgsl.js +21 -0
- package/src/webgpu/nodes/sampling.wgsl.js +193 -0
- package/src/webgpu/nodes/sheen.wgsl.js +148 -0
- package/src/webgpu/nodes/structs.wgsl.js +272 -0
- package/src/webgpu/nodes/utils.wgsl.js +366 -0
- package/src/webgpu/shims/ArrayCameraShim.js +74 -0
- package/src/webgpu/shims/EquirectCameraShim.js +37 -0
- package/src/webgpu/shims/PhysicalCameraShim.js +151 -0
- package/src/webgpu/upscale/FSRUpscaler.js +108 -0
|
@@ -0,0 +1,1037 @@
|
|
|
1
|
+
import { BackSide, FrontSide, DoubleSide, BufferAttribute, BufferGeometry, StorageBufferAttribute, StructTypeNode, Vector4, SkinnedMesh, RepeatWrapping, ClampToEdgeWrapping, MirroredRepeatWrapping, NearestFilter } from 'three/webgpu';
|
|
2
|
+
import { BVHComputeData, intersectRayTriangle, bvhNodeBoundsStruct, bvhNodeStruct, rayStruct, rayIntersectionResultStruct as intersectionResultStruct, wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
3
|
+
import { storage, float, texture, uniformArray, uint } from 'three/tsl';
|
|
4
|
+
import { SkinnedMeshBVH, MeshBVH, SAH } from 'three-mesh-bvh';
|
|
5
|
+
import { materialStruct } from './structs.wgsl.js';
|
|
6
|
+
import { getTextureHash } from '../../core/utils/sceneUpdateUtils.js';
|
|
7
|
+
import { sampleTexelFunc } from './utils.wgsl.js';
|
|
8
|
+
import { getSurfaceRecordFunc } from './material.wgsl.js';
|
|
9
|
+
import { AtlasTexture } from '../AtlasTexture.js';
|
|
10
|
+
|
|
11
|
+
const _colorVec = new Vector4();
|
|
12
|
+
const transformStruct = new StructTypeNode( {
|
|
13
|
+
matrixWorld: 'mat4x4f',
|
|
14
|
+
inverseMatrixWorld: 'mat4x4f',
|
|
15
|
+
visible: 'uint',
|
|
16
|
+
materialIndex: 'uint',
|
|
17
|
+
_alignment0: 'uint',
|
|
18
|
+
_alignment1: 'uint',
|
|
19
|
+
color: 'vec4f',
|
|
20
|
+
}, 'TransformStruct' );
|
|
21
|
+
|
|
22
|
+
// Pathtracer-specific version of the BVHComputeData tht includes material mapping, property structs
|
|
23
|
+
export class PathtracerBVHComputeData extends BVHComputeData {
|
|
24
|
+
|
|
25
|
+
constructor( bvh, options = {} ) {
|
|
26
|
+
|
|
27
|
+
// TODO: once supported we should use the appropriately-sized member sizes
|
|
28
|
+
super( bvh, {
|
|
29
|
+
attributes: {
|
|
30
|
+
position: 'vec4f',
|
|
31
|
+
normal: 'vec4f',
|
|
32
|
+
tangent: 'vec4f',
|
|
33
|
+
color: 'vec4f',
|
|
34
|
+
uv: 'vec4f',
|
|
35
|
+
},
|
|
36
|
+
...options,
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
this.structs.transform = transformStruct;
|
|
40
|
+
this.structs.material = materialStruct;
|
|
41
|
+
this.storage.materials = null;
|
|
42
|
+
this.materialsMap = new Map();
|
|
43
|
+
this.materials = [];
|
|
44
|
+
this.bvhMap = new Map();
|
|
45
|
+
this.textureAtlas = new AtlasTexture();
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
updateUvAttributesFromScene() {
|
|
50
|
+
|
|
51
|
+
const { attributes } = this;
|
|
52
|
+
const keys = new Set( [ 'color' ] );
|
|
53
|
+
for ( let i = 0; i < 8; i ++ ) {
|
|
54
|
+
|
|
55
|
+
const key = i === 0 ? 'uv' : 'uv' + i;
|
|
56
|
+
delete attributes[ key ];
|
|
57
|
+
keys.add( key );
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.getRootObject().traverse( c => {
|
|
62
|
+
|
|
63
|
+
if ( c.geometry ) {
|
|
64
|
+
|
|
65
|
+
for ( const key in c.geometry.attributes ) {
|
|
66
|
+
|
|
67
|
+
if ( keys.has( key ) ) {
|
|
68
|
+
|
|
69
|
+
attributes[ key ] = 'vec4f';
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
} );
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
updateUvSampleFunction() {
|
|
82
|
+
|
|
83
|
+
// TODO: eventually it may be best to pack the uvs into an array in the
|
|
84
|
+
// attribute struct so they can be sampled via array index but TSL structs
|
|
85
|
+
// make this difficult at the moment. It may break down when a uv channel
|
|
86
|
+
// unused, as well.
|
|
87
|
+
const { structs, fns } = this;
|
|
88
|
+
|
|
89
|
+
// generate the switch cases for the uv channels
|
|
90
|
+
const cases = [];
|
|
91
|
+
let fallback = null;
|
|
92
|
+
structs.attributes.membersLayout.forEach( ( { name } ) => {
|
|
93
|
+
|
|
94
|
+
if ( /^uv/.test( name ) ) {
|
|
95
|
+
|
|
96
|
+
const channel = name === 'uv' ? 0 : Number( name.replace( /^uv/, '' ) );
|
|
97
|
+
cases.push( /* wgsl */`
|
|
98
|
+
case ${ channel }u: {
|
|
99
|
+
|
|
100
|
+
return vertexData.${ name }.xy;
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
` );
|
|
104
|
+
|
|
105
|
+
if ( fallback === null ) {
|
|
106
|
+
|
|
107
|
+
fallback = `vertexData.${ name }.xy`;
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
} );
|
|
114
|
+
|
|
115
|
+
fns.getUvFromChannel = wgslTagFn/* wgsl */`
|
|
116
|
+
fn getUvFromChannel( vertexData: ${ structs.attributes }, packed: i32 ) -> vec2f {
|
|
117
|
+
|
|
118
|
+
// the uv channel is packed into bits 23-25 of the "*Map" descriptor
|
|
119
|
+
let channel = u32( ( packed >> 23 ) & 0x7 );
|
|
120
|
+
switch ( channel ) {
|
|
121
|
+
|
|
122
|
+
${ cases.join( '\n' ) }
|
|
123
|
+
default: {
|
|
124
|
+
|
|
125
|
+
return ${ fallback ?? 'vec2f( 0.0 )' };
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
updateColorSampleFn() {
|
|
137
|
+
|
|
138
|
+
// create a function for retrieving the color from the vertex data instance. If the struct does not include
|
|
139
|
+
// color then return white.
|
|
140
|
+
const { structs, fns } = this;
|
|
141
|
+
const hasColor = Boolean( structs.attributes.membersLayout.find( ( { name } ) => name === 'color' ) );
|
|
142
|
+
if ( hasColor ) {
|
|
143
|
+
|
|
144
|
+
fns.getColor = wgslTagFn/* wgsl */`
|
|
145
|
+
fn getColor( vertexData: ${ structs.attributes } ) -> vec4f {
|
|
146
|
+
|
|
147
|
+
return vertexData.color;
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
} else {
|
|
153
|
+
|
|
154
|
+
fns.getColor = wgslTagFn/* wgsl */`
|
|
155
|
+
fn getColor( vertexData: ${ structs.attributes } ) -> vec4f {
|
|
156
|
+
|
|
157
|
+
return vec4f( 1.0 );
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
useTransparencyRaycastFn() {
|
|
167
|
+
|
|
168
|
+
const { textureAtlas, storage, fns } = this;
|
|
169
|
+
const textures = textureAtlas.texture;
|
|
170
|
+
const textureInfo = uniformArray( textureAtlas.textureInfo, 'uvec4' );
|
|
171
|
+
|
|
172
|
+
// build the single sampleTexel bound to this instance's textureInfo node
|
|
173
|
+
const sampleTexel = sampleTexelFunc( textureInfo, texture( textures ) );
|
|
174
|
+
|
|
175
|
+
// getSurfaceRecord shares the same sampleTexel, so the surface shading and
|
|
176
|
+
// the transparency raycast resolve to one textureInfo binding per pipeline
|
|
177
|
+
fns.getSurfaceRecord = getSurfaceRecordFunc( sampleTexel, fns.getUvFromChannel, fns.getColor );
|
|
178
|
+
|
|
179
|
+
// raycast first hit, bounded by the ray's "maxDist" - 0 means unbounded
|
|
180
|
+
const currentMaterialIndex = uint().toVar( 'bvh_materialIndex' );
|
|
181
|
+
const scratchRayScalar = float( 1.0 ).toVar( 'bvh_rayScalar' );
|
|
182
|
+
const baseOpacityScalar = float( 1.0 ).toVar( 'bvh_baseOpacity' );
|
|
183
|
+
|
|
184
|
+
fns.raycastFirstHit = this.getShapecastFn( {
|
|
185
|
+
name: 'raycastFirstHit',
|
|
186
|
+
shapeStruct: rayStruct,
|
|
187
|
+
resultStruct: intersectionResultStruct,
|
|
188
|
+
|
|
189
|
+
boundsOrderFn: wgslTagFn/* wgsl */`
|
|
190
|
+
fn getBoundsOrder( ray: ${ rayStruct }, splitAxis: u32, node: ${ bvhNodeStruct } ) -> bool {
|
|
191
|
+
|
|
192
|
+
return ray.direction[ splitAxis ] >= 0.0;
|
|
193
|
+
|
|
194
|
+
}
|
|
195
|
+
`,
|
|
196
|
+
intersectsBoundsFn: wgslTagFn/* wgsl */`
|
|
197
|
+
fn rayIntersectsBounds( ray: ${ rayStruct }, bounds: ${ bvhNodeBoundsStruct }, result: ptr<function, ${ intersectionResultStruct }> ) -> u32 {
|
|
198
|
+
|
|
199
|
+
// early-out if our object is completely transparent
|
|
200
|
+
if ( ${ baseOpacityScalar } == 0.0 ) {
|
|
201
|
+
|
|
202
|
+
return 0u;
|
|
203
|
+
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let boundsMin = vec3( bounds.min[0], bounds.min[1], bounds.min[2] );
|
|
207
|
+
let boundsMax = vec3( bounds.max[0], bounds.max[1], bounds.max[2] );
|
|
208
|
+
|
|
209
|
+
let invDir = 1.0 / ray.direction;
|
|
210
|
+
let tMinPlane = ( boundsMin - ray.origin ) * invDir;
|
|
211
|
+
let tMaxPlane = ( boundsMax - ray.origin ) * invDir;
|
|
212
|
+
|
|
213
|
+
let tMinHit = min( tMinPlane, tMaxPlane );
|
|
214
|
+
let tMaxHit = max( tMinPlane, tMaxPlane );
|
|
215
|
+
|
|
216
|
+
let t0 = max( max( tMinHit.x, tMinHit.y ), tMinHit.z );
|
|
217
|
+
let t1 = min( min( tMaxHit.x, tMaxHit.y ), tMaxHit.z );
|
|
218
|
+
|
|
219
|
+
let dist = max( t0, 0.0 );
|
|
220
|
+
if ( t1 < dist ) {
|
|
221
|
+
|
|
222
|
+
return 0u;
|
|
223
|
+
|
|
224
|
+
} else if ( ray.maxDist > 0.0 && dist * ${ scratchRayScalar } >= ray.maxDist ) {
|
|
225
|
+
|
|
226
|
+
return 0u;
|
|
227
|
+
|
|
228
|
+
} else if ( result.didHit && dist * ${ scratchRayScalar } >= result.dist ) {
|
|
229
|
+
|
|
230
|
+
return 0u;
|
|
231
|
+
|
|
232
|
+
} else {
|
|
233
|
+
|
|
234
|
+
return 1u;
|
|
235
|
+
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
`,
|
|
241
|
+
intersectRangeFn: wgslTagFn/* wgsl */`
|
|
242
|
+
fn intersectRange( ray: ${ rayStruct }, offset: u32, count: u32, result: ptr<function, ${ intersectionResultStruct }> ) -> bool {
|
|
243
|
+
|
|
244
|
+
var didHit = false;
|
|
245
|
+
for ( var ti = offset; ti < offset + count; ti = ti + 1u ) {
|
|
246
|
+
|
|
247
|
+
let i0 = ${ storage.index }[ ti * 3u ];
|
|
248
|
+
let i1 = ${ storage.index }[ ti * 3u + 1u ];
|
|
249
|
+
let i2 = ${ storage.index }[ ti * 3u + 2u ];
|
|
250
|
+
|
|
251
|
+
let a = ${ storage.attributes }[ i0 ].position.xyz;
|
|
252
|
+
let b = ${ storage.attributes }[ i1 ].position.xyz;
|
|
253
|
+
let c = ${ storage.attributes }[ i2 ].position.xyz;
|
|
254
|
+
|
|
255
|
+
var triResult = ${ intersectRayTriangle }( ray, a, b, c, 0.0 );
|
|
256
|
+
triResult.dist *= ${ scratchRayScalar };
|
|
257
|
+
if ( triResult.didHit && ( ray.maxDist <= 0.0 || triResult.dist < ray.maxDist ) && ( ! result.didHit || triResult.dist < result.dist ) ) {
|
|
258
|
+
|
|
259
|
+
let material = ${ storage.materials }[ ${ currentMaterialIndex } ];
|
|
260
|
+
|
|
261
|
+
// TODO: if material is a transmissive volume we may need to assume double-sidedness
|
|
262
|
+
if ( material.side != 0 && triResult.side != material.side ) {
|
|
263
|
+
|
|
264
|
+
continue;
|
|
265
|
+
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if ( material.transparent != 0 || material.alphaTest > 0.0 ) {
|
|
269
|
+
|
|
270
|
+
var opacity = ${ baseOpacityScalar };
|
|
271
|
+
|
|
272
|
+
// add support for vertex color opacity
|
|
273
|
+
if ( material.vertexColors == 1 ) {
|
|
274
|
+
|
|
275
|
+
let barycoord = triResult.barycoord;
|
|
276
|
+
let a = ${ fns.getColor }( ${ storage.attributes }[ i0 ] );
|
|
277
|
+
let b = ${ fns.getColor }( ${ storage.attributes }[ i1 ] );
|
|
278
|
+
let c = ${ fns.getColor }( ${ storage.attributes }[ i2 ] );
|
|
279
|
+
let col = barycoord.x * a + barycoord.y * b + barycoord.z * c;
|
|
280
|
+
|
|
281
|
+
opacity *= col.a;
|
|
282
|
+
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// account for alpha component of albedo map
|
|
286
|
+
if ( material.map != - 1 ) {
|
|
287
|
+
|
|
288
|
+
let barycoord = triResult.barycoord;
|
|
289
|
+
let a = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i0 ], material.map );
|
|
290
|
+
let b = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i1 ], material.map );
|
|
291
|
+
let c = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i2 ], material.map );
|
|
292
|
+
let uv = barycoord.x * a + barycoord.y * b + barycoord.z * c;
|
|
293
|
+
let uvPrime = material.mapTransform * vec3f( uv, 1 );
|
|
294
|
+
|
|
295
|
+
opacity *= ${ sampleTexel }( uvPrime.xy, material.map, 0 ).a;
|
|
296
|
+
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// account for green component of alpha map
|
|
300
|
+
if ( material.alphaMap != - 1 ) {
|
|
301
|
+
|
|
302
|
+
let barycoord = triResult.barycoord;
|
|
303
|
+
let a = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i0 ], material.alphaMap );
|
|
304
|
+
let b = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i1 ], material.alphaMap );
|
|
305
|
+
let c = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i2 ], material.alphaMap );
|
|
306
|
+
let uv = barycoord.x * a + barycoord.y * b + barycoord.z * c;
|
|
307
|
+
let uvPrime = material.alphaMapTransform * vec3f( uv, 1 );
|
|
308
|
+
|
|
309
|
+
opacity *= ${ sampleTexel }( uvPrime.xy, material.alphaMap, 0 ).g;
|
|
310
|
+
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Opacity is resolved at surface processing but skip full transparent hits here.
|
|
314
|
+
if ( material.transparent != 0 && opacity <= 0.0 ) {
|
|
315
|
+
|
|
316
|
+
continue;
|
|
317
|
+
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if ( opacity < material.alphaTest ) {
|
|
321
|
+
|
|
322
|
+
continue;
|
|
323
|
+
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
result.didHit = true;
|
|
329
|
+
result.dist = triResult.dist;
|
|
330
|
+
result.normal = triResult.normal;
|
|
331
|
+
result.side = triResult.side;
|
|
332
|
+
result.barycoord = triResult.barycoord;
|
|
333
|
+
result.indices = vec4u( i0, i1, i2, ti );
|
|
334
|
+
|
|
335
|
+
didHit = true;
|
|
336
|
+
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return didHit;
|
|
342
|
+
|
|
343
|
+
}
|
|
344
|
+
`,
|
|
345
|
+
transformShapeFn: wgslTagFn/* wgsl */`
|
|
346
|
+
fn transformRay( ray: ptr<function, ${ rayStruct }>, objectIndex: u32 ) -> void {
|
|
347
|
+
|
|
348
|
+
let toLocal = ${ storage.transforms }[ objectIndex ].inverseMatrixWorld;
|
|
349
|
+
ray.origin = ( toLocal * vec4f( ray.origin, 1.0 ) ).xyz;
|
|
350
|
+
ray.direction = ( toLocal * vec4f( ray.direction, 0.0 ) ).xyz;
|
|
351
|
+
|
|
352
|
+
let len = length( ray.direction );
|
|
353
|
+
ray.direction /= len;
|
|
354
|
+
${ scratchRayScalar } = 1.0 / len;
|
|
355
|
+
|
|
356
|
+
let object = ${ storage.transforms }[ objectIndex ];
|
|
357
|
+
${ currentMaterialIndex } = object.materialIndex;
|
|
358
|
+
|
|
359
|
+
let material = ${ storage.materials }[ object.materialIndex ];
|
|
360
|
+
if ( material.transparent == 1 ) {
|
|
361
|
+
|
|
362
|
+
${ baseOpacityScalar } = material.opacity * object.color.a;
|
|
363
|
+
|
|
364
|
+
} else {
|
|
365
|
+
|
|
366
|
+
${ baseOpacityScalar } = 1.0;
|
|
367
|
+
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
}
|
|
371
|
+
`,
|
|
372
|
+
transformResultFn: wgslTagFn/* wgsl */`
|
|
373
|
+
fn transformResult( hit: ptr<function, ${ intersectionResultStruct }>, objectIndex: u32 ) -> void {
|
|
374
|
+
|
|
375
|
+
let toLocal = ${ storage.transforms }[ objectIndex ].inverseMatrixWorld;
|
|
376
|
+
hit.normal = normalize( ( transpose( toLocal ) * vec4f( hit.normal, 0.0 ) ).xyz );
|
|
377
|
+
hit.objectIndex = objectIndex;
|
|
378
|
+
|
|
379
|
+
}
|
|
380
|
+
`,
|
|
381
|
+
resetShapeFn: wgslTagFn/* wgsl */`
|
|
382
|
+
fn resetRayScalar( objectIndex: u32 ) -> void {
|
|
383
|
+
|
|
384
|
+
${ scratchRayScalar } = 1.0;
|
|
385
|
+
${ baseOpacityScalar } = 1.0;
|
|
386
|
+
|
|
387
|
+
}
|
|
388
|
+
`,
|
|
389
|
+
} );
|
|
390
|
+
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
update() {
|
|
394
|
+
|
|
395
|
+
const { structs } = this;
|
|
396
|
+
const attr = new StorageBufferAttribute( new Uint8Array(), structs.material.getLength() );
|
|
397
|
+
this.storage.materials = storage( attr, structs.material ).toReadOnly().setName( 'bvh_materials' );
|
|
398
|
+
|
|
399
|
+
this.updateUvAttributesFromScene();
|
|
400
|
+
|
|
401
|
+
super.update();
|
|
402
|
+
|
|
403
|
+
// build the channel -> uv lookup now that the geometry struct (and its uv members) exist
|
|
404
|
+
this.updateUvSampleFunction();
|
|
405
|
+
this.updateColorSampleFn();
|
|
406
|
+
|
|
407
|
+
// build material storage
|
|
408
|
+
this.updateMaterials();
|
|
409
|
+
|
|
410
|
+
this.bvhMap.clear();
|
|
411
|
+
this.useTransparencyRaycastFn();
|
|
412
|
+
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
updateMaterialsMap() {
|
|
416
|
+
|
|
417
|
+
const { materials, materialsMap } = this;
|
|
418
|
+
materialsMap.clear();
|
|
419
|
+
materials.length = 0;
|
|
420
|
+
this.getRootObject().traverse( o => {
|
|
421
|
+
|
|
422
|
+
if ( o.material ) {
|
|
423
|
+
|
|
424
|
+
if ( Array.isArray( o.material ) ) {
|
|
425
|
+
|
|
426
|
+
o.material.forEach( m => add( m ) );
|
|
427
|
+
|
|
428
|
+
} else {
|
|
429
|
+
|
|
430
|
+
add( o.material );
|
|
431
|
+
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
} );
|
|
437
|
+
|
|
438
|
+
materials
|
|
439
|
+
.sort( ( a, b ) => {
|
|
440
|
+
|
|
441
|
+
return a.uuid < b.uuid ? 1 : - 1;
|
|
442
|
+
|
|
443
|
+
} )
|
|
444
|
+
.forEach( ( m, i ) => {
|
|
445
|
+
|
|
446
|
+
materialsMap.set( m, i );
|
|
447
|
+
|
|
448
|
+
} );
|
|
449
|
+
|
|
450
|
+
function add( mat ) {
|
|
451
|
+
|
|
452
|
+
if ( ! materialsMap.has( mat ) ) {
|
|
453
|
+
|
|
454
|
+
materials.push( mat );
|
|
455
|
+
materialsMap.set( mat, - 1 );
|
|
456
|
+
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
updateMaterials() {
|
|
464
|
+
|
|
465
|
+
this.updateMaterialsMap();
|
|
466
|
+
|
|
467
|
+
const { materials, storage, structs, bvh } = this;
|
|
468
|
+
const { materialData, textures } = this.writeMaterialsBuffer( materials );
|
|
469
|
+
|
|
470
|
+
const materialsStorage = storage.materials.proxyNode;
|
|
471
|
+
const transformsStorage = storage.transforms.proxyNode;
|
|
472
|
+
const count = materialData.length / structs.material.getLength();
|
|
473
|
+
if ( materialsStorage.value.count < count ) {
|
|
474
|
+
|
|
475
|
+
materialsStorage.value.dispose();
|
|
476
|
+
materialsStorage.value = new StorageBufferAttribute( materialData, structs.material.getLength() );
|
|
477
|
+
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// copy the material buffer content
|
|
481
|
+
materialsStorage.value.array.set( materialData );
|
|
482
|
+
materialsStorage.value.needsUpdate = true;
|
|
483
|
+
|
|
484
|
+
// update the transform content
|
|
485
|
+
this._getTransformMap( bvh ).forEach( info => {
|
|
486
|
+
|
|
487
|
+
this.writeMaterialData( info, info.slot, transformsStorage.value.array.buffer );
|
|
488
|
+
|
|
489
|
+
} );
|
|
490
|
+
transformsStorage.value.needsUpdate = true;
|
|
491
|
+
|
|
492
|
+
// save the textures
|
|
493
|
+
this.textures = textures;
|
|
494
|
+
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
writeMaterialsBuffer( materials ) {
|
|
498
|
+
|
|
499
|
+
function encodeTextureWrap( wrap ) {
|
|
500
|
+
|
|
501
|
+
switch ( wrap ) {
|
|
502
|
+
|
|
503
|
+
case RepeatWrapping:
|
|
504
|
+
return 0;
|
|
505
|
+
case ClampToEdgeWrapping:
|
|
506
|
+
return 1;
|
|
507
|
+
case MirroredRepeatWrapping:
|
|
508
|
+
return 2;
|
|
509
|
+
default:
|
|
510
|
+
return 0;
|
|
511
|
+
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function getTexture( material, key ) {
|
|
517
|
+
|
|
518
|
+
if ( key in material && material[ key ] ) {
|
|
519
|
+
|
|
520
|
+
const texture = material[ key ];
|
|
521
|
+
const hash = getTextureHash( texture );
|
|
522
|
+
|
|
523
|
+
if ( ! textureLookUp.has( hash ) ) {
|
|
524
|
+
|
|
525
|
+
textureLookUp.set( hash, textureLookUp.size );
|
|
526
|
+
textures.push( texture );
|
|
527
|
+
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const idx = textureLookUp.get( hash ); // 23 bits
|
|
531
|
+
const channel = texture.channel & 7; // 3 bits
|
|
532
|
+
const wrapS = encodeTextureWrap( texture.wrapS ); // 2 bits
|
|
533
|
+
const wrapT = encodeTextureWrap( texture.wrapT ); // 2 bits
|
|
534
|
+
const nearest = texture.magFilter === NearestFilter ? 1 : 0; // 1 bit
|
|
535
|
+
return ( nearest << 30 ) | ( wrapT << 28 ) | ( wrapS << 26 ) | ( channel << 23 ) | ( idx & 0x7fffff );
|
|
536
|
+
|
|
537
|
+
} else {
|
|
538
|
+
|
|
539
|
+
return - 1;
|
|
540
|
+
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function getField( material, key, def ) {
|
|
546
|
+
|
|
547
|
+
return key in material ? material[ key ] : def;
|
|
548
|
+
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function writeTextureMatrixToArray( material, textureKey, array, offset ) {
|
|
552
|
+
|
|
553
|
+
const texture = material[ textureKey ] && material[ textureKey ].isTexture ? material[ textureKey ] : null;
|
|
554
|
+
|
|
555
|
+
// check if texture exists
|
|
556
|
+
if ( texture ) {
|
|
557
|
+
|
|
558
|
+
if ( texture.matrixAutoUpdate ) {
|
|
559
|
+
|
|
560
|
+
texture.updateMatrix();
|
|
561
|
+
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const elements = texture.matrix.elements;
|
|
565
|
+
|
|
566
|
+
// Both wgsl struct and elements should be in column-major format
|
|
567
|
+
for ( let i = 0; i < 3; i ++ ) {
|
|
568
|
+
|
|
569
|
+
array[ offset + 4 * i + 0 ] = elements[ 3 * i + 0 ];
|
|
570
|
+
array[ offset + 4 * i + 1 ] = elements[ 3 * i + 1 ];
|
|
571
|
+
array[ offset + 4 * i + 2 ] = elements[ 3 * i + 2 ];
|
|
572
|
+
array[ offset + 4 * i + 3 ] = 0; // padding float
|
|
573
|
+
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return 12;
|
|
579
|
+
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
let index = 0;
|
|
583
|
+
|
|
584
|
+
// Collect and index the list of textures based on shareable source
|
|
585
|
+
const textureLookUp = new Map();
|
|
586
|
+
const textures = [];
|
|
587
|
+
|
|
588
|
+
// NOTE: make the minimum material buffer length 2 in order to avoid TSL converting it to a scalar
|
|
589
|
+
// TODO: remove this when fixed in three
|
|
590
|
+
const materialBufferLength = Math.max( materials.length, 2 );
|
|
591
|
+
const floatArray = new Float32Array( materialBufferLength * this.structs.material.getLength() );
|
|
592
|
+
const intArray = new Int32Array( floatArray.buffer );
|
|
593
|
+
|
|
594
|
+
// TODO: make features work
|
|
595
|
+
// features.reset();
|
|
596
|
+
for ( let i = 0, l = materials.length; i < l; i ++ ) {
|
|
597
|
+
|
|
598
|
+
const m = materials[ i ];
|
|
599
|
+
|
|
600
|
+
// if ( m.isFogVolumeMaterial ) {
|
|
601
|
+
//
|
|
602
|
+
// // features.setUsed( 'FOG' );
|
|
603
|
+
//
|
|
604
|
+
// for ( let j = 0; j < MATERIAL_STRIDE; j ++ ) {
|
|
605
|
+
//
|
|
606
|
+
// floatArray[ index + j ] = 0;
|
|
607
|
+
//
|
|
608
|
+
// }
|
|
609
|
+
//
|
|
610
|
+
// // sample 0 .rgb
|
|
611
|
+
// floatArray[ index + 0 * 4 + 0 ] = m.color.r;
|
|
612
|
+
// floatArray[ index + 0 * 4 + 1 ] = m.color.g;
|
|
613
|
+
// floatArray[ index + 0 * 4 + 2 ] = m.color.b;
|
|
614
|
+
//
|
|
615
|
+
// // sample 2 .a
|
|
616
|
+
// floatArray[ index + 2 * 4 + 3 ] = getField( m, 'emissiveIntensity', 0.0 );
|
|
617
|
+
//
|
|
618
|
+
// // sample 3 .rgb
|
|
619
|
+
// floatArray[ index + 3 * 4 + 0 ] = m.emissive.r;
|
|
620
|
+
// floatArray[ index + 3 * 4 + 1 ] = m.emissive.g;
|
|
621
|
+
// floatArray[ index + 3 * 4 + 2 ] = m.emissive.b;
|
|
622
|
+
//
|
|
623
|
+
// // sample 13 .g
|
|
624
|
+
// // reusing opacity field
|
|
625
|
+
// floatArray[ index + 13 * 4 + 1 ] = m.density;
|
|
626
|
+
//
|
|
627
|
+
// // side
|
|
628
|
+
// floatArray[ index + 13 * 4 + 3 ] = 0.0;
|
|
629
|
+
//
|
|
630
|
+
// // sample 14 .b
|
|
631
|
+
// floatArray[ index + 14 * 4 + 2 ] = 1 << 2;
|
|
632
|
+
//
|
|
633
|
+
// index += MATERIAL_STRIDE;
|
|
634
|
+
// continue;
|
|
635
|
+
//
|
|
636
|
+
// }
|
|
637
|
+
|
|
638
|
+
// color - offset 0
|
|
639
|
+
floatArray[ index ++ ] = m.color.r;
|
|
640
|
+
floatArray[ index ++ ] = m.color.g;
|
|
641
|
+
floatArray[ index ++ ] = m.color.b;
|
|
642
|
+
intArray[ index ++ ] = getTexture( m, 'map' );
|
|
643
|
+
|
|
644
|
+
// metalness & roughness - offset 4
|
|
645
|
+
floatArray[ index ++ ] = getField( m, 'metalness', 0.0 );
|
|
646
|
+
intArray[ index ++ ] = getTexture( m, 'metalnessMap' );
|
|
647
|
+
floatArray[ index ++ ] = getField( m, 'roughness', 0.0 );
|
|
648
|
+
intArray[ index ++ ] = getTexture( m, 'roughnessMap' );
|
|
649
|
+
|
|
650
|
+
// transmission & emissiveIntensity - offset 8
|
|
651
|
+
// three.js assumes a default f0 of 0.04 if no ior is provided which equates to an ior of 1.5
|
|
652
|
+
floatArray[ index ++ ] = getField( m, 'ior', 1.5 );
|
|
653
|
+
floatArray[ index ++ ] = getField( m, 'transmission', 0.0 );
|
|
654
|
+
intArray[ index ++ ] = getTexture( m, 'transmissionMap' );
|
|
655
|
+
floatArray[ index ++ ] = getField( m, 'emissiveIntensity', 0.0 );
|
|
656
|
+
|
|
657
|
+
// emission - offset 12
|
|
658
|
+
if ( 'emissive' in m ) {
|
|
659
|
+
|
|
660
|
+
floatArray[ index ++ ] = m.emissive.r;
|
|
661
|
+
floatArray[ index ++ ] = m.emissive.g;
|
|
662
|
+
floatArray[ index ++ ] = m.emissive.b;
|
|
663
|
+
|
|
664
|
+
} else {
|
|
665
|
+
|
|
666
|
+
floatArray[ index ++ ] = 0.0;
|
|
667
|
+
floatArray[ index ++ ] = 0.0;
|
|
668
|
+
floatArray[ index ++ ] = 0.0;
|
|
669
|
+
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
intArray[ index ++ ] = getTexture( m, 'emissiveMap' );
|
|
673
|
+
|
|
674
|
+
// normals - offset 16
|
|
675
|
+
intArray[ index ++ ] = getTexture( m, 'normalMap' );
|
|
676
|
+
index ++; // because of vec2 alignment
|
|
677
|
+
if ( 'normalScale' in m ) {
|
|
678
|
+
|
|
679
|
+
floatArray[ index ++ ] = m.normalScale.x;
|
|
680
|
+
floatArray[ index ++ ] = m.normalScale.y;
|
|
681
|
+
|
|
682
|
+
} else {
|
|
683
|
+
|
|
684
|
+
floatArray[ index ++ ] = 1;
|
|
685
|
+
floatArray[ index ++ ] = 1;
|
|
686
|
+
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// clearcoat - offset 20
|
|
690
|
+
floatArray[ index ++ ] = getField( m, 'clearcoat', 0.0 );
|
|
691
|
+
intArray[ index ++ ] = getTexture( m, 'clearcoatMap' );
|
|
692
|
+
intArray[ index ++ ] = getTexture( m, 'clearcoatNormalMap' );
|
|
693
|
+
index ++; // because of vec2 alignment
|
|
694
|
+
|
|
695
|
+
// offset 24
|
|
696
|
+
if ( 'clearcoatNormalScale' in m ) {
|
|
697
|
+
|
|
698
|
+
floatArray[ index ++ ] = m.clearcoatNormalScale.x;
|
|
699
|
+
floatArray[ index ++ ] = m.clearcoatNormalScale.y;
|
|
700
|
+
|
|
701
|
+
} else {
|
|
702
|
+
|
|
703
|
+
floatArray[ index ++ ] = 1;
|
|
704
|
+
floatArray[ index ++ ] = 1;
|
|
705
|
+
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
floatArray[ index ++ ] = getField( m, 'clearcoatRoughness', 0.0 );
|
|
709
|
+
intArray[ index ++ ] = getTexture( m, 'clearcoatRoughnessMap' );
|
|
710
|
+
|
|
711
|
+
// iridescence - offset 28
|
|
712
|
+
intArray[ index ++ ] = getTexture( m, 'iridescenceMap' );
|
|
713
|
+
intArray[ index ++ ] = getTexture( m, 'iridescenceThicknessMap' );
|
|
714
|
+
|
|
715
|
+
floatArray[ index ++ ] = getField( m, 'iridescence', 0.0 );
|
|
716
|
+
floatArray[ index ++ ] = getField( m, 'iridescenceIOR', 1.3 );
|
|
717
|
+
|
|
718
|
+
// offset 32
|
|
719
|
+
const iridescenceThicknessRange = getField( m, 'iridescenceThicknessRange', [ 100, 400 ] );
|
|
720
|
+
floatArray[ index ++ ] = iridescenceThicknessRange[ 0 ];
|
|
721
|
+
floatArray[ index ++ ] = iridescenceThicknessRange[ 1 ];
|
|
722
|
+
floatArray[ index ++ ] = getField( m, 'diffuseRoughness', 0.0 );
|
|
723
|
+
floatArray[ index ++ ] = getField( m, 'dispersion', 0.0 );
|
|
724
|
+
|
|
725
|
+
// specular color - offset 36
|
|
726
|
+
if ( 'specularColor' in m ) {
|
|
727
|
+
|
|
728
|
+
floatArray[ index ++ ] = m.specularColor.r;
|
|
729
|
+
floatArray[ index ++ ] = m.specularColor.g;
|
|
730
|
+
floatArray[ index ++ ] = m.specularColor.b;
|
|
731
|
+
|
|
732
|
+
} else {
|
|
733
|
+
|
|
734
|
+
floatArray[ index ++ ] = 1.0;
|
|
735
|
+
floatArray[ index ++ ] = 1.0;
|
|
736
|
+
floatArray[ index ++ ] = 1.0;
|
|
737
|
+
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
intArray[ index ++ ] = getTexture( m, 'specularColorMap' );
|
|
741
|
+
|
|
742
|
+
// specular intensity - offset 40
|
|
743
|
+
floatArray[ index ++ ] = getField( m, 'specularIntensity', 1.0 );
|
|
744
|
+
intArray[ index ++ ] = getTexture( m, 'specularIntensityMap' );
|
|
745
|
+
|
|
746
|
+
// isThinWall
|
|
747
|
+
const isThinWall = getField( m, 'thickness', 0.0 ) === 0.0 && getField( m, 'attenuationDistance', Infinity ) === Infinity;
|
|
748
|
+
intArray[ index ++ ] = Number( isThinWall );
|
|
749
|
+
index ++;
|
|
750
|
+
|
|
751
|
+
// attenuation - offset 44
|
|
752
|
+
if ( 'attenuationColor' in m ) {
|
|
753
|
+
|
|
754
|
+
floatArray[ index ++ ] = m.attenuationColor.r;
|
|
755
|
+
floatArray[ index ++ ] = m.attenuationColor.g;
|
|
756
|
+
floatArray[ index ++ ] = m.attenuationColor.b;
|
|
757
|
+
|
|
758
|
+
} else {
|
|
759
|
+
|
|
760
|
+
floatArray[ index ++ ] = 1.0;
|
|
761
|
+
floatArray[ index ++ ] = 1.0;
|
|
762
|
+
floatArray[ index ++ ] = 1.0;
|
|
763
|
+
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
floatArray[ index ++ ] = getField( m, 'attenuationDistance', Infinity );
|
|
767
|
+
|
|
768
|
+
// alphaMap - offset 48
|
|
769
|
+
intArray[ index ++ ] = getTexture( m, 'alphaMap' );
|
|
770
|
+
intArray[ index ++ ] = Number( getField( m, 'castShadow', true ) ); // shadow
|
|
771
|
+
floatArray[ index ++ ] = m.opacity;
|
|
772
|
+
floatArray[ index ++ ] = m.alphaTest;
|
|
773
|
+
|
|
774
|
+
// side & matte - offset 52
|
|
775
|
+
if ( ! isThinWall && m.transmission > 0.0 ) {
|
|
776
|
+
|
|
777
|
+
floatArray[ index ++ ] = 0;
|
|
778
|
+
|
|
779
|
+
} else {
|
|
780
|
+
|
|
781
|
+
switch ( m.side ) {
|
|
782
|
+
|
|
783
|
+
case FrontSide:
|
|
784
|
+
floatArray[ index ++ ] = 1;
|
|
785
|
+
break;
|
|
786
|
+
case BackSide:
|
|
787
|
+
floatArray[ index ++ ] = - 1;
|
|
788
|
+
break;
|
|
789
|
+
case DoubleSide:
|
|
790
|
+
floatArray[ index ++ ] = 0;
|
|
791
|
+
break;
|
|
792
|
+
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
intArray[ index ++ ] = Number( getField( m, 'matte', false ) ); // matte
|
|
798
|
+
floatArray[ index ++ ] = getField( m, 'sheen', 0.0 );
|
|
799
|
+
index ++; // vec3 alignment requirements
|
|
800
|
+
|
|
801
|
+
// sheenColor - offset 56
|
|
802
|
+
if ( 'sheenColor' in m ) {
|
|
803
|
+
|
|
804
|
+
floatArray[ index ++ ] = m.sheenColor.r;
|
|
805
|
+
floatArray[ index ++ ] = m.sheenColor.g;
|
|
806
|
+
floatArray[ index ++ ] = m.sheenColor.b;
|
|
807
|
+
|
|
808
|
+
} else {
|
|
809
|
+
|
|
810
|
+
floatArray[ index ++ ] = 0.0;
|
|
811
|
+
floatArray[ index ++ ] = 0.0;
|
|
812
|
+
floatArray[ index ++ ] = 0.0;
|
|
813
|
+
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
intArray[ index ++ ] = getTexture( m, 'sheenColorMap' );
|
|
817
|
+
|
|
818
|
+
// sheenRoughness, flags - offset 60
|
|
819
|
+
floatArray[ index ++ ] = getField( m, 'sheenRoughness', 0.0 );
|
|
820
|
+
intArray[ index ++ ] = getTexture( m, 'sheenRoughnessMap' );
|
|
821
|
+
|
|
822
|
+
intArray[ index ++ ] = Number( m.vertexColors );
|
|
823
|
+
intArray[ index ++ ] = Number( m.flatShading );
|
|
824
|
+
|
|
825
|
+
// transparent, fogVolume - offset 64
|
|
826
|
+
intArray[ index ++ ] = Number( m.transparent );
|
|
827
|
+
intArray[ index ++ ] = 0;
|
|
828
|
+
|
|
829
|
+
// anisotropy, anisotropyRotation, anisotropyMap - offset 66
|
|
830
|
+
floatArray[ index ++ ] = getField( m, 'anisotropy', 0.0 );
|
|
831
|
+
floatArray[ index ++ ] = getField( m, 'anisotropyRotation', 0.0 );
|
|
832
|
+
intArray[ index ++ ] = getTexture( m, 'anisotropyMap' );
|
|
833
|
+
index ++; // padding for mat3 alignment
|
|
834
|
+
index ++;
|
|
835
|
+
index ++;
|
|
836
|
+
|
|
837
|
+
// map transform - offset 72
|
|
838
|
+
index += writeTextureMatrixToArray( m, 'map', floatArray, index );
|
|
839
|
+
|
|
840
|
+
// metalnessMap transform - offset 84
|
|
841
|
+
index += writeTextureMatrixToArray( m, 'metalnessMap', floatArray, index );
|
|
842
|
+
|
|
843
|
+
// roughnessMap transform - offset 96
|
|
844
|
+
index += writeTextureMatrixToArray( m, 'roughnessMap', floatArray, index );
|
|
845
|
+
|
|
846
|
+
// transmissionMap transform - offset 108
|
|
847
|
+
index += writeTextureMatrixToArray( m, 'transmissionMap', floatArray, index );
|
|
848
|
+
|
|
849
|
+
// emissiveMap transform - offset 120
|
|
850
|
+
index += writeTextureMatrixToArray( m, 'emissiveMap', floatArray, index );
|
|
851
|
+
|
|
852
|
+
// normalMap transform - offset 132
|
|
853
|
+
index += writeTextureMatrixToArray( m, 'normalMap', floatArray, index );
|
|
854
|
+
|
|
855
|
+
// clearcoatMap transform - offset 144
|
|
856
|
+
index += writeTextureMatrixToArray( m, 'clearcoatMap', floatArray, index );
|
|
857
|
+
|
|
858
|
+
// clearcoatNormalMap transform - offset 156
|
|
859
|
+
index += writeTextureMatrixToArray( m, 'clearcoatNormalMap', floatArray, index );
|
|
860
|
+
|
|
861
|
+
// clearcoatRoughnessMap transform - offset 168
|
|
862
|
+
index += writeTextureMatrixToArray( m, 'clearcoatRoughnessMap', floatArray, index );
|
|
863
|
+
|
|
864
|
+
// sheenColorMap transform - offset 180
|
|
865
|
+
index += writeTextureMatrixToArray( m, 'sheenColorMap', floatArray, index );
|
|
866
|
+
|
|
867
|
+
// sheenRoughnessMap transform - offset 192
|
|
868
|
+
index += writeTextureMatrixToArray( m, 'sheenRoughnessMap', floatArray, index );
|
|
869
|
+
|
|
870
|
+
// iridescenceMap transform - offset 204
|
|
871
|
+
index += writeTextureMatrixToArray( m, 'iridescenceMap', floatArray, index );
|
|
872
|
+
|
|
873
|
+
// iridescenceThicknessMap transform - offset 216
|
|
874
|
+
index += writeTextureMatrixToArray( m, 'iridescenceThicknessMap', floatArray, index );
|
|
875
|
+
|
|
876
|
+
// specularColorMap transform - offset 228
|
|
877
|
+
index += writeTextureMatrixToArray( m, 'specularColorMap', floatArray, index );
|
|
878
|
+
|
|
879
|
+
// specularIntensityMap transform - offset 240
|
|
880
|
+
index += writeTextureMatrixToArray( m, 'specularIntensityMap', floatArray, index );
|
|
881
|
+
|
|
882
|
+
// alphaMap transform - offset 252
|
|
883
|
+
index += writeTextureMatrixToArray( m, 'alphaMap', floatArray, index );
|
|
884
|
+
|
|
885
|
+
// anisotropyMap transform - offset 264
|
|
886
|
+
index += writeTextureMatrixToArray( m, 'anisotropyMap', floatArray, index );
|
|
887
|
+
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
return { materialData: intArray, textures };
|
|
891
|
+
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
writeMaterialData( info, writeOffset, targetBuffer ) {
|
|
895
|
+
|
|
896
|
+
// write material data to the transforms
|
|
897
|
+
const { materialsMap } = this;
|
|
898
|
+
const { object, instanceId, root } = info;
|
|
899
|
+
|
|
900
|
+
// get the material associated with the bvh group
|
|
901
|
+
let material = object.material;
|
|
902
|
+
if ( Array.isArray( material ) ) {
|
|
903
|
+
|
|
904
|
+
const { materialIndex } = object.geometry.groups[ root ];
|
|
905
|
+
material = material[ materialIndex ];
|
|
906
|
+
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// save the index
|
|
910
|
+
const index = materialsMap.get( material ) || 0;
|
|
911
|
+
const transformBufferU32 = new Uint32Array( targetBuffer );
|
|
912
|
+
transformBufferU32[ writeOffset * transformStruct.getLength() + 33 ] = index;
|
|
913
|
+
|
|
914
|
+
// write color
|
|
915
|
+
// TODO: note that both BatchedMesh and InstancedMesh "getColorAt" functions throw
|
|
916
|
+
// if colors have not been defined.
|
|
917
|
+
if ( object.isInstancedMesh && object.instanceColor ) {
|
|
918
|
+
|
|
919
|
+
object.getColorAt( instanceId, _colorVec );
|
|
920
|
+
_colorVec.w = 1.0;
|
|
921
|
+
|
|
922
|
+
} else if ( object.isBatchedMesh && object._colorsTexture ) {
|
|
923
|
+
|
|
924
|
+
object.getColorAt( instanceId, _colorVec );
|
|
925
|
+
|
|
926
|
+
} else {
|
|
927
|
+
|
|
928
|
+
_colorVec.setScalar( 1 );
|
|
929
|
+
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const transformBufferF32 = new Float32Array( targetBuffer );
|
|
933
|
+
_colorVec.toArray( transformBufferF32, writeOffset * transformStruct.getLength() + 36 );
|
|
934
|
+
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
writeTransformData( info, premultiplyMatrix, writeOffset, targetBuffer ) {
|
|
938
|
+
|
|
939
|
+
super.writeTransformData( info, premultiplyMatrix, writeOffset, targetBuffer );
|
|
940
|
+
this.writeMaterialData( info, writeOffset, targetBuffer );
|
|
941
|
+
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
getBVH( object, instanceId, rangeTarget ) {
|
|
945
|
+
|
|
946
|
+
const { bvhMap } = this;
|
|
947
|
+
const bvh = super.getBVH( object, instanceId, rangeTarget );
|
|
948
|
+
if ( bvhMap.has( bvh ) ) {
|
|
949
|
+
|
|
950
|
+
const data = bvhMap.get( bvh );
|
|
951
|
+
Object.assign( rangeTarget, data.range );
|
|
952
|
+
|
|
953
|
+
// make sure the mesh and bvh are updated if it's being reused across updates
|
|
954
|
+
if ( bvh !== data.bvh && bvh instanceof SkinnedMeshBVH ) {
|
|
955
|
+
|
|
956
|
+
const sourceMesh = bvh.mesh;
|
|
957
|
+
const clonedMesh = data.bvh.mesh;
|
|
958
|
+
clonedMesh.matrixWorld
|
|
959
|
+
.copy( sourceMesh.matrixWorld )
|
|
960
|
+
.decompose( clonedMesh.position, clonedMesh.quaternion, clonedMesh.scale );
|
|
961
|
+
|
|
962
|
+
bvh.refit();
|
|
963
|
+
bvh.getBoundingBox( clonedMesh.boundingBox );
|
|
964
|
+
bvh.geometry.computeBoundingBox();
|
|
965
|
+
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
return data.bvh;
|
|
969
|
+
|
|
970
|
+
} else if ( bvh.indirect ) {
|
|
971
|
+
|
|
972
|
+
// "indirect" bvhs are not supported since they cannot be unpacked in a way tht will allow for coherent material indices
|
|
973
|
+
const proxyGeometry = new BufferGeometry();
|
|
974
|
+
proxyGeometry.attributes = bvh.geometry.attributes;
|
|
975
|
+
|
|
976
|
+
let array;
|
|
977
|
+
if ( bvh.geometry.index ) {
|
|
978
|
+
|
|
979
|
+
array = bvh.geometry.index.array.slice( rangeTarget.start, rangeTarget.count + rangeTarget.start );
|
|
980
|
+
|
|
981
|
+
} else {
|
|
982
|
+
|
|
983
|
+
const { start, count } = rangeTarget;
|
|
984
|
+
array = new Uint32Array( count );
|
|
985
|
+
for ( let i = 0, l = rangeTarget.count; i < l; i ++ ) {
|
|
986
|
+
|
|
987
|
+
array[ i ] = start + i;
|
|
988
|
+
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
proxyGeometry.index = new BufferAttribute( array, 1 );
|
|
994
|
+
rangeTarget.start = 0;
|
|
995
|
+
|
|
996
|
+
let newBVH;
|
|
997
|
+
if ( bvh instanceof SkinnedMeshBVH ) {
|
|
998
|
+
|
|
999
|
+
const sourceMesh = bvh.mesh;
|
|
1000
|
+
const clonedMesh = new SkinnedMesh( proxyGeometry );
|
|
1001
|
+
clonedMesh.copy( sourceMesh );
|
|
1002
|
+
clonedMesh.matrixWorld
|
|
1003
|
+
.copy( sourceMesh.matrixWorld )
|
|
1004
|
+
.decompose( clonedMesh.position, clonedMesh.quaternion, clonedMesh.scale );
|
|
1005
|
+
|
|
1006
|
+
newBVH = new SkinnedMeshBVH( clonedMesh, { strategy: SAH, targetLeafSize: 5 } );
|
|
1007
|
+
|
|
1008
|
+
} else {
|
|
1009
|
+
|
|
1010
|
+
newBVH = new MeshBVH( proxyGeometry, { strategy: SAH, targetLeafSize: 5 } );
|
|
1011
|
+
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
bvhMap.set( bvh, { bvh: newBVH, range: { ...rangeTarget } } );
|
|
1015
|
+
return newBVH;
|
|
1016
|
+
|
|
1017
|
+
} else {
|
|
1018
|
+
|
|
1019
|
+
return bvh;
|
|
1020
|
+
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
dispose() {
|
|
1026
|
+
|
|
1027
|
+
// TODO: This belongs in three-mesh-bvh
|
|
1028
|
+
const { storage } = this;
|
|
1029
|
+
for ( const key in storage ) {
|
|
1030
|
+
|
|
1031
|
+
storage[ key ].value?.dispose();
|
|
1032
|
+
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
}
|