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

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.lryc.cn/news/574181.html

相关文章:

  • Ntfs!_LFCB结构如何构建出来的--从Ntfs!NtfsMountVolume到Ntfs!LfsAllocateLfcb
  • CentOS 上安装snmp
  • 如何在 Python 中连接 Elasticsearch 并使用 Qwen3 来实现 RAG
  • AI编程再突破,文心快码发布行业首个多模态、多智能体协同AI IDE
  • 【深度学习加速探秘】Winograd 卷积算法:让计算效率 “飞” 起来
  • SpringCloud系列(33)--使用Hystrix进行通配服务降级
  • 前缀树进阶-经典案例详解
  • Ubuntu20.04安装录屏工具OBS
  • 【Leetcode】有效的括号、用栈实现队列、用队列实现栈
  • Spring Boot + Logback MDC 深度解析:实现全链路日志追踪
  • 从数据到洞察:UI前端如何利用大数据优化用户体验
  • 用Fiddler抓包工具优化API联调流程:与Postman、Wireshark协作实践分享
  • Zynq + FreeRTOS + YAFFS2 + SQLite3 集成指南
  • 在Ubuntu上设置Firefox自动化测试环境:指定Marionette端口号
  • SpringBoot+Vue自习室座位预约系统
  • Lamp和友点CMS一键部署脚本(Rocky linux)
  • 技术干货 | 深度解读GB/T 45086.1-2024 EMC部分关键项
  • Excel学习03
  • 如何在 Vue 应用中嵌入 ONLYOFFICE 编辑器
  • 零基础学习RabbitMQ(2)--Linux安装RabbitMQ
  • 16.数据聚合
  • 文章以及好用网站分享
  • [QMT量化交易小白入门]-六十六、加入评分阈值后,历史回测收益率达到74%
  • Matlab自学笔记六十:符号表达式的缩写和简化
  • <tauri><threejs><rust><GUI>基于tauri和threejs,实现一个3D图形浏览程序
  • WPF中MVVM和MVVMLight模式
  • 技术逐梦之旅:从C语言到Vue的成长之路
  • 【附源码】考试报名系统设计与实现+SpringBoot + Vue (前后端分离)
  • Java底层原理:深入理解类加载机制与反射
  • 开始读Learning PostgresSQL第二版