如何在Three.js中为加载的OBJ模型实现等尺寸棋盘格纹理统一显示
解决Three.js中OBJ模型棋盘格纹理尺寸不均的问题
这是个很常见的纹理映射问题,核心原因要么是OBJ模型自带的UV坐标没有统一规范,要么是纹理的重复/映射参数没适配模型的几何体特征。下面给你几个实用的解决思路,结合你的代码来调整:
1. 先调整纹理的基础参数
首先确保纹理支持重复映射,不然超出UV范围的部分会拉伸而不是重复。修改你的纹理加载代码:
texture = textureLoader.load(currentTextureUrl); // texture checker image // 开启重复包裹模式 texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; // 设置重复次数,先尝试一个适中的值,比如(5,5),后续根据效果调整 texture.repeat.set(5, 5); texture.needsUpdate = true; // 你之前写的onUpdate是错误属性,应该用needsUpdate
不过这个方法只适用于模型各部分缩放比例接近的情况,如果模型不同网格大小差异大,还是会出现棋盘格大小不一的问题,所以需要结合UV坐标的调整。
2. 基于几何体边界盒重新计算UV坐标
OBJ模型的每个网格可能有自己的尺寸,我们可以针对每个网格的边界盒(BoundingBox)来生成均匀的UV,让纹理在每个网格上的重复密度一致。修改你遍历模型的代码:
object.traverse(function(child) { if (child.type.toLowerCase() == "mesh" && child.material && child.geometry) { const geom = child.geometry; // 计算几何体的边界盒 geom.computeBoundingBox(); const bbox = geom.boundingBox; const bboxSize = bbox.getSize(new THREE.Vector3()); // 获取UV属性,重新计算每个顶点的UV值 const uvAttribute = geom.attributes.uv; for (let i = 0; i < uvAttribute.count; i++) { // 基于顶点在边界盒内的相对位置计算UV,乘以重复次数控制棋盘格大小 const u = (geom.attributes.position.getX(i) - bbox.min.x) / bboxSize.x * 5; const v = (geom.attributes.position.getY(i) - bbox.min.y) / bboxSize.y * 5; uvAttribute.setXY(i, u, v); } uvAttribute.needsUpdate = true; // 标记UV属性需要更新 // 应用纹理到材质 if (child.material.length) { for (var i=0; i < child.material.length; i++) { child.material[i].map = texture; child.material[i].needsUpdate = true; } } else { child.material.map = texture; child.material.needsUpdate = true; } } });
这个方法的核心是让每个网格的UV都基于自身的尺寸来映射,这样不管网格多大,棋盘格的大小都会保持相对一致。你可以调整代码里的*5这个乘数来控制棋盘格的大小——数值越大,棋盘格越小。
3. 检查模型本身的UV(根源解决)
如果上面的方法效果还是不好,可能是你导出的OBJ模型本身UV没有正确展开。比如在Blender这类3D软件中,模型的UV可能被拉伸或者没有均匀分布,导致纹理映射异常。你可以:
- 用Blender打开OBJ模型,切换到UV编辑器查看UV布局
- 对模型重新进行UV展开(比如用智能UV展开功能),确保UV在UV编辑器里均匀分布
- 重新导出OBJ模型,再导入Three.js中测试
修改后的完整加载流程示例
把上面的调整整合到你的代码里,大概是这样:
function loadModel() { // 这里无需再处理纹理,因为我们在loader回调里已经完成了处理 scene.add( object ); } manager = new THREE.LoadingManager( loadModel ); manager.onProgress = function ( item, loaded, total ) { console.log( item, loaded, total ); }; textureLoader = new THREE.TextureLoader( manager ); texture = textureLoader.load(currentTextureUrl); // 配置纹理参数 texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; texture.repeat.set(5, 5); texture.needsUpdate = true; function onProgress( xhr ) { if ( xhr.lengthComputable ) { var percentComplete = xhr.loaded / xhr.total * 100; console.log( 'model ' + Math.round( percentComplete, 2 ) + '% downloaded' ); } } function onError() {} var loader = new THREE.OBJLoader( manager ); loader.load(modals.currentModal.objectFile, function ( obj ) { object = obj; // 遍历模型处理每个网格的UV和纹理 object.traverse(function(child) { if (child.type.toLowerCase() == "mesh" && child.material && child.geometry) { const geom = child.geometry; geom.computeBoundingBox(); const bbox = geom.boundingBox; const bboxSize = bbox.getSize(new THREE.Vector3()); const uvAttribute = geom.attributes.uv; for (let i = 0; i < uvAttribute.count; i++) { const u = (geom.attributes.position.getX(i) - bbox.min.x) / bboxSize.x * 5; const v = (geom.attributes.position.getY(i) - bbox.min.y) / bboxSize.y * 5; uvAttribute.setXY(i, u, v); } uvAttribute.needsUpdate = true; if (child.material.length) { for (var i=0; i < child.material.length; i++) { child.material[i].map = texture; child.material[i].needsUpdate = true; } } else { child.material.map = texture; child.material.needsUpdate = true; } } }); }, onProgress, onError );
内容的提问来源于stack exchange,提问作者Mukesh Patel
相关产品推荐
相关产品推荐

