未验证 提交 65a6875d 编写于 作者: M Mr.doob 提交者: GitHub

Merge pull request #13707 from takahirox/GLTFLoaderMultiMaterial

GLTFLoader: MultiMaterial support
......@@ -188,7 +188,7 @@ THREE.BufferGeometryUtils = {
* @param {Array<THREE.BufferGeometry>} geometries
* @return {THREE.BufferGeometry}
*/
mergeBufferGeometries: function ( geometries ) {
mergeBufferGeometries: function ( geometries, useGroups ) {
var isIndexed = geometries[ 0 ].index !== null;
......@@ -200,6 +200,8 @@ THREE.BufferGeometryUtils = {
var mergedGeometry = new THREE.BufferGeometry();
var offset = 0;
for ( var i = 0; i < geometries.length; ++ i ) {
var geometry = geometries[ i ];
......@@ -242,6 +244,30 @@ THREE.BufferGeometryUtils = {
}
if ( useGroups ) {
var count;
if ( isIndexed ) {
count = geometry.index.count;
} else if ( geometry.attributes.position !== undefined ) {
count = geometry.attributes.position.count;
} else {
return null;
}
mergedGeometry.addGroup( offset, count, i );
offset += count;
}
}
// merge indices
......
......@@ -1214,15 +1214,11 @@ THREE.GLTFLoader = ( function () {
/**
* Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
*
* @param {THREE.Mesh} mesh
* @param {GLTF.Mesh} meshDef
* @param {GLTF.Primitive} primitiveDef
* @param {THREE.Geometry} geometry
* @param {Array<GLTF.Target>} targets
* @param {Array<THREE.BufferAttribute>} accessors
*/
function addMorphTargets( mesh, meshDef, primitiveDef, accessors ) {
var geometry = mesh.geometry;
var targets = primitiveDef.targets;
function addMorphTargets( geometry, targets, accessors ) {
var hasMorphPosition = false;
var hasMorphNormal = false;
......@@ -1330,6 +1326,14 @@ THREE.GLTFLoader = ( function () {
if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions;
if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals;
}
/**
* @param {THREE.Mesh} mesh
* @param {GLTF.Mesh} meshDef
*/
function updateMorphTargets( mesh, meshDef ) {
mesh.updateMorphTargets();
if ( meshDef.weights !== undefined ) {
......@@ -1375,26 +1379,31 @@ THREE.GLTFLoader = ( function () {
}
var attribA = a.attributes || {};
var attribB = b.attributes || {};
var keysA = Object.keys( attribA );
var keysB = Object.keys( attribB );
return isObjectEqual( a.attributes, b.attributes );
if ( keysA.length !== keysB.length ) {
}
return false;
function isObjectEqual( a, b ) {
if ( Object.keys( a ).length !== Object.keys( b ).length ) return false;
for ( var key in a ) {
if ( a[ key ] !== b[ key ] ) return false;
}
for ( var i = 0, il = keysA.length; i < il; i ++ ) {
return true;
var key = keysA[ i ];
}
if ( attribA[ key ] !== attribB[ key ] ) {
function isArrayEqual( a, b ) {
return false;
if ( a.length !== b.length ) return false;
}
for ( var i = 0, il = a.length; i < il; i ++ ) {
if ( a[ i ] !== b[ i ] ) return false;
}
......@@ -1408,11 +1417,35 @@ THREE.GLTFLoader = ( function () {
var cached = cache[ i ];
if ( isPrimitiveEqual( cached.primitive, newPrimitive ) ) {
if ( isPrimitiveEqual( cached.primitive, newPrimitive ) ) return cached.promise;
return cached.promise;
}
}
return null;
}
function getCachedCombinedGeometry( cache, geometries ) {
for ( var i = 0, il = cache.length; i < il; i ++ ) {
var cached = cache[ i ];
if ( isArrayEqual( geometries, cached.baseGeometries ) ) return cached.geometry;
}
return null;
}
function getCachedMultiPassGeometry( cache, geometry, primitives ) {
for ( var i = 0, il = cache.length; i < il; i ++ ) {
var cached = cache[ i ];
if ( geometry === cached.baseGeometry && isArrayEqual( primitives, cached.primitives ) ) return cached.geometry;
}
......@@ -1445,6 +1478,47 @@ THREE.GLTFLoader = ( function () {
}
/**
* Checks if we can build a single Mesh with MultiMaterial from multiple primitives.
* Returns true if all primitives use the same attributes/morphAttributes/mode
* and also have index. Otherwise returns false.
*
* @param {Array<GLTF.Primitive>} primitives
* @return {Boolean}
*/
function isMultiPassGeometry( primitives ) {
if ( primitives.length < 2 ) return false;
var primitive0 = primitives[ 0 ];
var targets0 = primitive0.targets || [];
if ( primitive0.indices === undefined ) return false;
for ( var i = 1, il = primitives.length; i < il; i ++ ) {
var primitive = primitives[ i ];
if ( primitive0.mode !== primitive.mode ) return false;
if ( primitive.indices === undefined ) return false;
if ( ! isObjectEqual( primitive0.attributes, primitive.attributes ) ) return false;
var targets = primitive.targets || [];
if ( targets0.length !== targets.length ) return false;
for ( var j = 0, jl = targets0.length; j < jl; j ++ ) {
if ( ! isObjectEqual( targets0[ j ], targets[ j ] ) ) return false;
}
}
return true;
}
/* GLTF PARSER */
function GLTFParser( json, extensions, options ) {
......@@ -1458,6 +1532,8 @@ THREE.GLTFLoader = ( function () {
// BufferGeometry caching
this.primitiveCache = [];
this.multiplePrimitivesCache = [];
this.multiPassGeometryCache = []
this.textureLoader = new THREE.TextureLoader( this.options.manager );
this.textureLoader.setCrossOrigin( this.options.crossOrigin );
......@@ -2183,7 +2259,7 @@ THREE.GLTFLoader = ( function () {
* @param {GLTF.Primitive} primitiveDef
* @param {Array<THREE.BufferAttribute>} accessors
*/
function addPrimitiveAttributes ( geometry, primitiveDef, accessors ) {
function addPrimitiveAttributes( geometry, primitiveDef, accessors ) {
var attributes = primitiveDef.attributes;
......@@ -2200,16 +2276,33 @@ THREE.GLTFLoader = ( function () {
}
if ( primitiveDef.indices !== undefined && !geometry.index ) {
if ( primitiveDef.indices !== undefined && ! geometry.index ) {
geometry.setIndex( accessors[ primitiveDef.indices ] );
}
if ( primitiveDef.targets !== undefined ) {
addMorphTargets( geometry, primitiveDef.targets, accessors );
}
if ( primitiveDef.extras !== undefined ) {
geometry.userData = primitiveDef.extras;
}
}
/**
* Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
*
* Creates BufferGeometries from primitives.
* If we can build a single BufferGeometry with .groups from multiple primitives, returns one BufferGeometry.
* Otherwise, returns BufferGeometries without .groups as many as primitives.
*
* @param {Array<Object>} primitives
* @return {Promise<Array<THREE.BufferGeometry>>}
*/
......@@ -2219,6 +2312,22 @@ THREE.GLTFLoader = ( function () {
var extensions = this.extensions;
var cache = this.primitiveCache;
var isMultiPass = isMultiPassGeometry( primitives );
var originalPrimitives;
if ( isMultiPass ) {
originalPrimitives = primitives; // save original primitives and use later
// We build a single BufferGeometry with .groups from multiple primitives
// because all primitives share the same attributes/morph/mode and have indices.
primitives = [ primitives[ 0 ] ];
// Sets .groups and combined indices to a geometry later in this method.
}
return this.getDependencies( 'accessor' ).then( function ( accessors ) {
var pending = [];
......@@ -2262,12 +2371,7 @@ THREE.GLTFLoader = ( function () {
var geometryPromise = Promise.resolve( geometry );
// Cache this geometry
cache.push( {
primitive: primitive,
promise: geometryPromise
} );
cache.push( { primitive: primitive, promise: geometryPromise } );
pending.push( geometryPromise );
......@@ -2275,7 +2379,83 @@ THREE.GLTFLoader = ( function () {
}
return Promise.all( pending );
return Promise.all( pending ).then( function ( geometries ) {
if ( isMultiPass ) {
var baseGeometry = geometries[ 0 ];
// See if we've already created this combined geometry
var cache = parser.multiPassGeometryCache;
var cached = getCachedMultiPassGeometry( cache, baseGeometry, originalPrimitives );
if ( cached !== null ) return [ cached.geometry ];
// Cloning geometry because of index override.
// Attributes can be reused so cloning by myself here.
var geometry = new THREE.BufferGeometry();
geometry.name = baseGeometry.name;
geometry.userData = baseGeometry.userData;
for ( var key in baseGeometry.attributes ) geometry.addAttribute( key, baseGeometry.attributes[ key ] );
for ( var key in baseGeometry.morphAttributes ) geometry.morphAttributes[ key ] = baseGeometry.morphAttributes[ key ];
var indices = [];
var offset = 0;
for ( var i = 0, il = originalPrimitives.length; i < il; i ++ ) {
var accessor = accessors[ originalPrimitives[ i ].indices ];
for ( var j = 0, jl = accessor.count; j < jl; j ++ ) indices.push( accessor.array[ j ] );
geometry.addGroup( offset, accessor.count, i );
offset += accessor.count;
}
geometry.setIndex( indices );
cache.push( { geometry: geometry, baseGeometry: baseGeometry, primitives: originalPrimitives } );
return [ geometry ];
} else if ( geometries.length > 1 && THREE.BufferGeometryUtils !== undefined ) {
// Tries to merge geometries with BufferGeometryUtils if possible
for ( var i = 1, il = primitives.length; i < il; i ++ ) {
// can't merge if draw mode is different
if ( primitives[ 0 ].mode !== primitives[ i ].mode ) return geometries;
}
// See if we've already created this combined geometry
var cache = parser.multiplePrimitivesCache;
var cached = getCachedCombinedGeometry( cache, geometries );
if ( cached ) {
if ( cached.geometry !== null ) return [ cached.geometry ];
} else {
var geometry = THREE.BufferGeometryUtils.mergeBufferGeometries( geometries, true );
cache.push( { geometry: geometry, baseGeometries: geometries } );
if ( geometry !== null ) return [ geometry ];
}
}
return geometries;
} );
} );
......@@ -2301,188 +2481,220 @@ THREE.GLTFLoader = ( function () {
] ).then( function ( dependencies ) {
var group = new THREE.Group();
var primitives = meshDef.primitives;
var originalMaterials = [];
for ( var i = 0, il = primitives.length; i < il; i ++ ) {
originalMaterials[ i ] = primitives[ i ].material === undefined
? createDefaultMaterial()
: dependencies.materials[ primitives[ i ].material ];
}
return scope.loadGeometries( primitives ).then( function ( geometries ) {
for ( var i = 0, il = primitives.length; i < il; i ++ ) {
var isMultiMaterial = geometries.length === 1 && geometries[ 0 ].groups.length > 0;
var primitive = primitives[ i ];
var geometry = geometries[ i ];
var meshes = [];
var material = primitive.material === undefined
? createDefaultMaterial()
: dependencies.materials[ primitive.material ];
for ( var i = 0, il = geometries.length; i < il; i ++ ) {
if ( material.aoMap
&& geometry.attributes.uv2 === undefined
&& geometry.attributes.uv !== undefined ) {
var geometry = geometries[ i ];
var primitive = primitives[ i ];
console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' );
geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) );
// 1. create Mesh
}
var mesh;
// If the material will be modified later on, clone it now.
var useVertexColors = geometry.attributes.color !== undefined;
var useFlatShading = geometry.attributes.normal === undefined;
var useSkinning = meshDef.isSkinnedMesh === true;
var useMorphTargets = primitive.targets !== undefined;
var material = isMultiMaterial ? originalMaterials : originalMaterials[ i ]
if ( useVertexColors || useFlatShading || useSkinning || useMorphTargets ) {
if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
primitive.mode === undefined ) {
if ( material.isGLTFSpecularGlossinessMaterial ) {
// .isSkinnedMesh isn't in glTF spec. See .markDefs()
mesh = meshDef.isSkinnedMesh === true
? new THREE.SkinnedMesh( geometry, material )
: new THREE.Mesh( geometry, material );
var specGlossExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ];
material = specGlossExtension.cloneMaterial( material );
if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {
} else {
mesh.drawMode = THREE.TriangleStripDrawMode;
material = material.clone();
} else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {
mesh.drawMode = THREE.TriangleFanDrawMode;
}
}
} else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) {
mesh = new THREE.LineSegments( geometry, material );
} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) {
mesh = new THREE.Line( geometry, material );
} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) {
mesh = new THREE.LineLoop( geometry, material );
} else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) {
mesh = new THREE.Points( geometry, material );
if ( useVertexColors ) {
} else {
material.vertexColors = THREE.VertexColors;
material.needsUpdate = true;
throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode );
}
if ( useFlatShading ) {
if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) {
material.flatShading = true;
updateMorphTargets( mesh, meshDef );
}
var mesh;
mesh.name = meshDef.name || ( 'mesh_' + meshIndex );
if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
primitive.mode === undefined ) {
if ( geometries.length > 1 ) mesh.name += '_' + i;
if ( useSkinning ) {
if ( meshDef.extras !== undefined ) mesh.userData = meshDef.extras;
mesh = new THREE.SkinnedMesh( geometry, material );
material.skinning = true;
meshes.push( mesh );
} else {
// 2. update Material depending on Mesh and BufferGeometry
mesh = new THREE.Mesh( geometry, material );
var materials = isMultiMaterial ? mesh.material : [ mesh.material ];
}
var useVertexColors = geometry.attributes.color !== undefined;
var useFlatShading = geometry.attributes.normal === undefined;
var useSkinning = mesh.isSkinnedMesh === true;
var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0;
var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined;
if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {
for ( var j = 0, jl = materials.length; j < jl; j ++ ) {
mesh.drawMode = THREE.TriangleStripDrawMode;
var material = materials[ j ];
} else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {
if ( mesh.isPoints ) {
mesh.drawMode = THREE.TriangleFanDrawMode;
var cacheKey = 'PointsMaterial:' + material.uuid;
}
var pointsMaterial = scope.cache.get( cacheKey );
} else if ( primitive.mode === WEBGL_CONSTANTS.LINES ||
primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ||
primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) {
if ( ! pointsMaterial ) {
var cacheKey = 'LineBasicMaterial:' + material.uuid;
pointsMaterial = new THREE.PointsMaterial();
THREE.Material.prototype.copy.call( pointsMaterial, material );
pointsMaterial.color.copy( material.color );
pointsMaterial.map = material.map;
pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet
var lineMaterial = scope.cache.get( cacheKey );
scope.cache.add( cacheKey, pointsMaterial );
if ( ! lineMaterial ) {
}
lineMaterial = new THREE.LineBasicMaterial();
THREE.Material.prototype.copy.call( lineMaterial, material );
lineMaterial.color.copy( material.color );
lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet
material = pointsMaterial;
scope.cache.add( cacheKey, lineMaterial );
} else if ( mesh.isLine ) {
}
var cacheKey = 'LineBasicMaterial:' + material.uuid;
material = lineMaterial;
var lineMaterial = scope.cache.get( cacheKey );
if ( primitive.mode === WEBGL_CONSTANTS.LINES ) {
if ( ! lineMaterial ) {
mesh = new THREE.LineSegments( geometry, material );
lineMaterial = new THREE.LineBasicMaterial();
THREE.Material.prototype.copy.call( lineMaterial, material );
lineMaterial.color.copy( material.color );
lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet
} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) {
scope.cache.add( cacheKey, lineMaterial );
mesh = new THREE.Line( geometry, material );
}
} else {
material = lineMaterial;
}
// If the material will be modified later on, clone it now.
if ( useVertexColors || useFlatShading || useSkinning || useMorphTargets ) {
mesh = new THREE.LineLoop( geometry, material );
material = material.isGLTFSpecularGlossinessMaterial
? extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].cloneMaterial( material )
: material.clone();
}
} else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) {
if ( useSkinning ) {
var cacheKey = 'PointsMaterial:' + material.uuid;
material.skinning = true;
var pointsMaterial = scope.cache.get( cacheKey );
}
if ( ! pointsMaterial ) {
if ( useVertexColors ) {
material.vertexColors = THREE.VertexColors;
material.needsUpdate = true;
}
pointsMaterial = new THREE.PointsMaterial();
THREE.Material.prototype.copy.call( pointsMaterial, material );
pointsMaterial.color.copy( material.color );
pointsMaterial.map = material.map;
pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet
if ( useFlatShading ) {
scope.cache.add( cacheKey, pointsMaterial );
material.flatShading = true;
}
material = pointsMaterial;
if ( useMorphTargets ) {
mesh = new THREE.Points( geometry, material );
material.morphTargets = true;
} else {
}
throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode );
if ( useMorphNormals ) {
}
material.morphNormals = true;
mesh.name = meshDef.name || ( 'mesh_' + meshIndex );
}
if ( useMorphTargets ) {
materials[ j ] = material;
addMorphTargets( mesh, meshDef, primitive, dependencies.accessors );
// workarounds for mesh and geometry
material.morphTargets = true;
if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) {
if ( mesh.geometry.morphAttributes.normal !== undefined ) material.morphNormals = true;
console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' );
geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) );
}
}
if ( meshDef.extras !== undefined ) mesh.userData = meshDef.extras;
if ( primitive.extras !== undefined ) mesh.geometry.userData = primitive.extras;
if ( material.isGLTFSpecularGlossinessMaterial ) {
// for Specular-Glossiness.
if ( material.isGLTFSpecularGlossinessMaterial === true ) {
// for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update
mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms;
mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms;
}
}
if ( primitives.length > 1 ) {
mesh.material = isMultiMaterial ? materials : materials[ 0 ];
mesh.name += '_' + i;
}
group.add( mesh );
if ( meshes.length === 1 ) {
} else {
return meshes[ 0 ];
return mesh;
}
}
var group = new THREE.Group();
for ( var i = 0, il = meshes.length; i < il; i ++ ) {
group.add( meshes[ i ] );
}
......@@ -2726,6 +2938,7 @@ THREE.GLTFLoader = ( function () {
var node;
// .isBone isn't in glTF spec. See .markDefs
if ( nodeDef.isBone === true ) {
node = new THREE.Bone();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册