Three.js ExtrudeGeometry 挤压几何体材质纹理不显示问题排查
Three.js ExtrudeGeometry 自定义形状挤压后纹理不渲染的修复方案
代码里的具体问题
- Shape绘制存在硬逻辑bug
初始化Shape时连续调用两次moveTo:第一次将画笔定位到walls[0].p1坐标后,未执行连线就再次调用moveTo定位到walls[0].p2,第一个点位完全被跳过,最终生成的闭合形状和预期墙体边界完全不符,挤压生成的几何体本身拓扑就是错误的。
对应修复:将第二次moveTo改为lineTo,确保从第一个点位连线到第二个点位,再循环追加后续墙体点位。 - ExtrudeGeometry默认UV尺度不匹配大坐标场景
ExtrudeGeometry生成上下两个端面的UV时,会直接将Shape平面上的顶点XY坐标值作为UV值,不会自动将UV归一化到0-1的标准区间。由于你的Shape是基于实际墙体的世界坐标绘制,尺度通常达到几十甚至上百单位,生成的UV值远大于1,哪怕开启了RepeatWrapping,纹理的平铺密度也和预期偏差极大,视觉效果和纹理完全不渲染一致。
对应修复:挤压生成几何体后,手动遍历几何体的UV属性,将UV值归一化到0-1区间,再配合设置的repeat参数调整平铺密度;也可以在绘制Shape时提前将所有点位平移到坐标原点附近,缩小Shape的整体包围盒尺度,让生成的UV值自然落在合理范围。 - 纹理旋转设置遗漏核心参数
给纹理设置90度旋转时,Three.js默认的纹理旋转中心是纹理坐标系的左上角,旋转后采样范围会直接偏移到有效像素外,导致采样结果为全黑或全透明。
对应修复:设置纹理旋转属性时,同步通过texture.center.set(0.5, 0.5)将旋转中心调整到纹理中心,避免采样偏移。 - 变量命名存在潜在冲突
代码中将纹理实例命名为Texture,和Three.js内置的THREE.Texture类名重名,容易出现作用域覆盖的问题,建议改为小写开头的实例名避免冲突。
核心修复逻辑参考代码
// 修正Shape绘制逻辑 const shape = new THREE.Shape(); shape.autoClose = true; shape.moveTo(walls[0].p1.x, -walls[0].p1.z); shape.lineTo(walls[0].p2.x, -walls[0].p2.z); // 替换原错误的moveTo调用 for (var j = 1; j < walls.length; j++) { shape.lineTo(walls[j].p1.x, -walls[j].p1.z); shape.lineTo(walls[j].p2.x, -walls[j].p2.z); } const extrudeSettings = { steps: 1, depth: 5, bevelEnabled: false }; // 初始化材质与纹理加载逻辑 const floor_material = new THREE.MeshPhongMaterial({ color: 0xaaaaaa }); const loader = new THREE.TextureLoader(); const grassTexture = loader.load("assets/textures/grassbw.jpg", texture => { // 纹理属性在加载成功后统一配置 texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.repeat.set(60, 60); texture.center.set(0.5, 0.5); // 修正旋转中心 texture.rotation = Math.PI / 2; floor_material.map = texture; floor_material.needsUpdate = true; }); // 生成挤压几何体 const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); // 归一化UV坐标 geometry.computeBoundingBox(); const bbox = geometry.boundingBox; const uvAttribute = geometry.attributes.uv; for (let i = 0; i < uvAttribute.count; i++) { const u = uvAttribute.getX(i); const v = uvAttribute.getY(i); // 基于几何体包围盒将UV缩放到0-1区间 const normalizedU = (u - bbox.min.x) / (bbox.max.x - bbox.min.x); const normalizedV = (v - bbox.min.y) / (bbox.max.y - bbox.min.y); uvAttribute.setXY(i, normalizedU, normalizedV); } uvAttribute.needsUpdate = true; geometry.computeFaceNormals(); const mesh = new THREE.Mesh(geometry, floor_material); mesh.receiveShadow = true;
补充提示:ExtrudeGeometry默认会生成两个材质组,索引0对应上下两个端面,索引1对应挤压形成的竖直侧面。如果需要给端面和侧面分别配置不同材质,可以在实例化Mesh时传入材质数组,无需修改几何体本身的UV逻辑。
内容的提问来源于stack exchange,提问作者daniel
相关产品推荐
相关产品推荐

