THREE.js单个Mesh多UV/多纹理映射实现问题咨询
解决Three.js中2×2 UV范围的多纹理映射问题
我来帮你搞定这个多纹理映射的问题——你的OBJ模型UV坐标是0到2的范围,四个象限分别对应四张纹理,现在只需要把每个UV区间的面分配给对应的材质,再把UV归一到0-1的标准范围,就能让所有网格都正确显示啦。
下面是具体的实现步骤,分两种几何体类型(旧版Geometry和新版BufferGeometry)来讲解:
第一步:加载所有纹理并配置
首先我们要加载四张目标纹理,同时设置纹理的包裹方式为ClampToEdgeWrapping,避免UV超出范围时出现重复或拉伸的问题:
// 初始化纹理加载器 const textureLoader = new THREE.TextureLoader(); // 加载四张纹理 const textures = { u1v1: textureLoader.load('tex_u1_v1.png'), u1v2: textureLoader.load('tex_u1_v2.png'), u2v1: textureLoader.load('tex_u2_v1.png'), u2v2: textureLoader.load('tex_u2_v2.png') }; // 配置所有纹理的包裹规则,防止UV溢出导致的异常显示 Object.values(textures).forEach(texture => { texture.wrapS = THREE.ClampToEdgeWrapping; texture.wrapT = THREE.ClampToEdgeWrapping; texture.needsUpdate = true; });
第二步:处理几何体并按UV分组
接下来需要遍历几何体的所有面,根据UV坐标的归属区间给面分组,同时把每个组的UV调整到0-1的标准范围(因为纹理本身的UV是0-1)。
方式1:针对旧版THREE.Geometry
如果你的几何体是旧版的Geometry类型,可以这样处理:
const geometry = object.geometry; geometry.clearGroups(); // 清除现有分组 // 辅助函数:调整UV到0-1范围 const adjustUV = (faceUVs, uOffset, vOffset) => { faceUVs.forEach(uv => { uv.x -= uOffset; uv.y -= vOffset; }); }; // 遍历每个面,判断归属的纹理分组 for (let i = 0; i < geometry.faces.length; i++) { const faceUVs = geometry.faceVertexUvs[0][i]; // 取三个顶点UV的平均值来判断归属区间(避免单个顶点UV异常影响分组) const avgU = (faceUVs[0].x + faceUVs[1].x + faceUVs[2].x) / 3; const avgV = (faceUVs[0].y + faceUVs[1].y + faceUVs[2].y) / 3; let groupIndex = 0; let uOffset = 0, vOffset = 0; // 根据平均UV判断所属纹理区域 if (avgU < 1 && avgV < 1) { groupIndex = 0; // 对应tex_u1_v1.png } else if (avgU < 1 && avgV >= 1) { groupIndex = 1; // 对应tex_u1_v2.png vOffset = 1; } else if (avgU >= 1 && avgV < 1) { groupIndex = 2; // 对应tex_u2_v1.png uOffset = 1; } else { groupIndex = 3; // 对应tex_u2_v2.png uOffset = 1; vOffset = 1; } // 调整当前面的UV到0-1范围 adjustUV(faceUVs, uOffset, vOffset); // 给当前面添加分组 geometry.addGroup(i, 1, groupIndex); } // 标记几何体需要更新 geometry.uvsNeedUpdate = true; geometry.groupsNeedUpdate = true;
方式2:针对新版THREE.BufferGeometry
现在Three.js默认使用BufferGeometry,处理方式略有不同,需要直接操作UV数组:
const geometry = object.geometry; const uvAttribute = geometry.getAttribute('uv'); const uvs = uvAttribute.array; const groups = []; // 遍历每个面(每个面包含3个顶点,每个顶点2个UV值) for (let i = 0; i < uvs.length; i += 6) { // 获取当前面三个顶点的UV值 const u1 = uvs[i], v1 = uvs[i+1]; const u2 = uvs[i+2], v2 = uvs[i+3]; const u3 = uvs[i+4], v3 = uvs[i+5]; // 计算平均UV判断归属区间 const avgU = (u1 + u2 + u3) / 3; const avgV = (v1 + v2 + v3) / 3; let groupIndex = 0; let uOffset = 0, vOffset = 0; if (avgU < 1 && avgV < 1) { groupIndex = 0; } else if (avgU < 1 && avgV >= 1) { groupIndex = 1; vOffset = 1; } else if (avgU >= 1 && avgV < 1) { groupIndex = 2; uOffset = 1; } else { groupIndex = 3; uOffset = 1; vOffset = 1; } // 调整当前面的所有UV到0-1范围 uvs[i] -= uOffset; uvs[i+1] -= vOffset; uvs[i+2] -= uOffset; uvs[i+3] -= vOffset; uvs[i+4] -= uOffset; uvs[i+5] -= vOffset; // 添加分组:起始顶点索引、顶点数量、材质索引 groups.push({ start: i / 2, count: 3, materialIndex: groupIndex }); } // 更新UV属性和分组 uvAttribute.needsUpdate = true; geometry.groups = groups; geometry.groupsNeedUpdate = true;
第三步:创建材质数组并赋值给Mesh
最后我们创建四个材质(对应四张纹理),并把材质数组赋值给你的Mesh:
// 创建材质数组(这里用MeshBasicMaterial,你可以根据需求替换成MeshStandardMaterial等) const materials = [ new THREE.MeshBasicMaterial({ map: textures.u1v1, side: THREE.DoubleSide }), new THREE.MeshBasicMaterial({ map: textures.u1v2, side: THREE.DoubleSide }), new THREE.MeshBasicMaterial({ map: textures.u2v1, side: THREE.DoubleSide }), new THREE.MeshBasicMaterial({ map: textures.u2v2, side: THREE.DoubleSide }) ]; // 给Mesh设置材质数组 object.material = materials; // 如果你用的是非常旧的Three.js版本,可能需要用MeshFaceMaterial: // object.material = new THREE.MeshFaceMaterial(materials);
这样处理后,四个UV区间的网格就会分别对应四张纹理,整个模型就能正确显示啦。
内容的提问来源于stack exchange,提问作者The Obscure Question
相关产品推荐
相关产品推荐

