Three.js中自定义UV坐标贴图举例详解

UV映射是一种将二维纹理映射到三维模型表面的技术,这篇文章主要介绍了Three.js中自定义UV坐标贴图的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下。

一、背景

1.1 使用场景 / 什么时候需要这样处理

1、当原始几何体没有 UV 信息

  • 比如从 GeoJSON 或自定义顶点数据生成的几何体,通常不包含默认的 uv 属性。
  • 没有 uv 信息,纹理就无法正确贴在模型表面。

2、当默认 UV 无法满足纹理显示需求

  • 比如自动生成的 UV 是按照立方体或球体映射方式,但你希望纹理按照 XY 平面铺开。
  • 或你想自定义纹理拉伸、缩放、裁剪方式。

3、贴图范围较大时

  • 如将纹理贴在一个很大的面(如整个省份或城市)上,需要归一化 UV才能完整显示整张图,否则纹理可能看起来被挤压或重复。

4、材质设置使用了 map(纹理贴图)

  • UV 坐标直接决定了纹理如何映射到顶点上,没有正确的 UV,贴图就是黑的或者混乱的。

1.2 这样处理的好处

好处 描述
自定义贴图范围 按照 XY 平面的实际边界(bounding box)进行归一化贴图,纹理完整而且可控。
保证不同面统一贴图逻辑 使所有面在同一坐标系下统一使用纹理坐标,适用于多个 Mesh 的统一贴图。
更好地适应实际地理数据 尤其适合地图类、建筑投影、城市地块等从经纬度生成的模型。
提高美观性 避免贴图拉伸、模糊、重复等问题。让纹理在面上看起来更自然。

1.3 举个例子

假如你从 GeoJSON 创建了一个类似湖北省的面模型,你贴上一张高清地图 top.png 作为贴图,如果不设置 UV:

  • 纹理可能根本看不到。

  • 或者整个纹理只出现在几何体的一个小角落(默认 UV 不匹配)。

  • 甚至多个面公用同一张纹理,但位置都不对。

二、纹理贴图与 UV 的基础知识

在 3D 渲染中,为了将 2D 图片(纹理)映射到 3D 几何体上,需要用到 UV 坐标

概念 说明
纹理贴图(Texture Mapping) 是将 2D 图像(比如一张地图、照片等)映射到 3D 模型表面的过程。
UV 坐标 是二维坐标系 [u, v],对应图片的水平方向(U)和垂直方向(V),取值范围通常是 [0, 1]。
目的 指定几何体的哪个顶点对应纹理图的哪个像素点。没有 UV,Three.js 无法知道贴图怎么“贴”上去。

例子:

  • uv = [0,0] 表示贴图左下角,[1,1] 表示右上角。
  • 如果某三角面顶点 uv 是 [0,0], [1,0], [0,1],那纹理就会映射为一个右角三角区域。

三、代码逐步解析

const group = topPolygonMesh.object3d;
if (!group) return;
group.traverse(child => {
  if (child.isMesh && child.geometry && child.geometry.attributes.position) {
    const posAttr = child.geometry.attributes.position;
    const positions = posAttr.array;
    // 1. 计算 XY 投影平面的包围盒
    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
    for (let i = 0; i < positions.length; i += 3) {
      const x = positions[i];
      const y = positions[i + 1];
      if (x < minX) minX = x;
      if (y < minY) minY = y;
      if (x > maxX) maxX = x;
      if (y > maxY) maxY = y;
    };
    const width = maxX - minX;
    const height = maxY - minY;
    // 2. 根据 bbox 生成新的 UV(按 XY 贴图)
    const uv = [];
    for (let i = 0; i < positions.length; i += 3) {
      const x = positions[i];
      const y = positions[i + 1];
      const u = (x - minX) / width;
      const v = (y - minY) / height;
      uv.push(u, v);
    };
    // 3. 设置新的 UV
    child.geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uv, 2));
    child.geometry.attributes.uv.needsUpdate = true;
  }
})

使用你上面的逻辑:

  • 会将湖北省这个面在 XY 平面上的投影范围归一化为 [0,1] × [0,1],然后贴图正好完整覆盖整个省份模型顶面。
  • 并且每个三角面上纹理也会均匀分布,不会拉伸、重复或扭曲。

代码每部分解析

const group = topPolygonMesh.object3d;
if (!group) return;
group.traverse(child => {
  if (child.isMesh && child.geometry && child.geometry.attributes.position) {

找到场景中的所有 Mesh,确保其包含几何体和位置(顶点)数据。

第一步:计算 XY 平面的包围盒

const posAttr = child.geometry.attributes.position;
const positions = posAttr.array;
 
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
for (let i = 0; i < positions.length; i += 3) {
  const x = positions[i];
  const y = positions[i + 1];
  if (x < minX) minX = x;
  if (y < minY) minY = y;
  if (x > maxX) maxX = x;
  if (y > maxY) maxY = y;
}
const width = maxX - minX;
const height = maxY - minY;

获取几何体在 XY 平面上的包围盒

第二步:计算新的 UV 坐标

const uv = [];
for (let i = 0; i < positions.length; i += 3) {
  const x = positions[i];
  const y = positions[i + 1];
  const u = (x - minX) / width;
  const v = (y - minY) / height;
  uv.push(u, v);
}

将 XY 坐标归一化为 UV 区间

第三步:应用 UV 到几何体

child.geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uv, 2));
child.geometry.attributes.uv.needsUpdate = true;

设置 UV 属性并通知渲染系统更新

四、目的总结

该段代码的最终目标是:

使 XY 平面上的任意不规则几何体,都能准确、完整地显示一张贴图(通常是一张地图或图案)

五、为什么默认 UV 不适用?

场景 是否需要手动设置 UV
自定义顶点数据构建几何体 是的,需要自定义 UV
从 GeoJSON/TopoJSON 转换成几何体 是的,没有默认 UV
使用 ExtrudeGeometry, ShapeGeometry 有自动生成 UV,但经常不满足 XY 贴图的需要
纹理贴图变形严重或重复 需要重设 UV 保证平铺、清晰

总结

项目 内容
目的 将纹理按照 XY 平面贴满整个面
原理 将 XY 坐标归一化为 [0,1] 区间作为 UV
效果 保证纹理不变形、无缝、完整地显示
场景 地图贴图、建筑贴图、非规则面纹理贴图
重点 UV 反映的是“纹理坐标”,不是世界坐标

到此这篇关于Three.js中自定义UV坐标贴图的文章就介绍到这了,更多相关Three.js自定义UV坐标贴图内容请继续浏览下面的相关文章!