当前位置: 首页 > ai >正文

Three.js 中自定义 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. 设置新的 UVchild.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 反映的是“纹理坐标”,不是世界坐标
http://www.xdnf.cn/news/14584.html

相关文章:

  • Java数据结构第二十四期:探秘 AVL 树,当二叉搜索树学会 “自我调节”
  • 华为云 Flexus+DeepSeek 征文|增值税发票智能提取小工具:基于大模型的自动化信息解析实践
  • 计算机操作系统(十六)进程同步
  • 安全版V4.5密码加密算法由SM3改为MD5
  • 使用Windows自带的WSL安装Ubuntu Linux系统
  • SQLite FTS4全文搜索实战指南:从入门到优化
  • Java基础(三):逻辑运算符详解
  • 【技术分享】XR技术体系浅析:VR、AR与MR的区别、联系与应用实践
  • 从语言到生态:编程语言在各行业的应用格局与未来演进
  • 考研408《计算机组成原理》复习笔记,第三章(1)——存储系统概念
  • CMCC RAX3000M nand版 OpenWrt 可用空间变小的恢复方法
  • redis相关面试题
  • 使用模板创建uniapp提示未关联uniCloud问题
  • vscode+react+ESLint解决不引入组件,vscode不会报错的问题
  • 小孙学变频学习笔记(四)变频器的逆变器件—IGBT管(下)
  • linux 远程终端执行qt应用显示到接入的物理显示器上
  • 如何仅用AI开发完整的小程序<5>—让AI制作开始页面
  • C++ Programming Language —— 第2章:数据类型
  • C#.NET HttpClient 使用教程
  • 【Dicom标准】dicom数据中pixelData显示处理流程详细介绍
  • Linux 服务器运维:磁盘管理与网络配置
  • 一个免费的视频、音频、文本、图片多媒体处理工具
  • ICM-20948 Wake on Motion功能开发全过程(8)
  • Python 的内置函数 hash
  • python模块常用语法sys、traceback、QApplication
  • 操作系统内核态和用户态--2-系统调用是什么?
  • 决策树:化繁为简的智能决策利器
  • GO语言---数组
  • 【Docker基础】Docker镜像管理:docker rmi、prune详解
  • 经典:在浏览器地址栏输入信息到最终看到网页的全过程,涉及网络协议以及前后端技术