Three.js中自定義UV坐標(biāo)貼圖舉例超詳細(xì)講解
一、背景
1.1 使用場(chǎng)景 / 什么時(shí)候需要這樣處理
1、當(dāng)原始幾何體沒有 UV 信息
- 比如從 GeoJSON 或自定義頂點(diǎn)數(shù)據(jù)生成的幾何體,通常不包含默認(rèn)的 uv 屬性。
- 沒有 uv 信息,紋理就無法正確貼在模型表面。
2、當(dāng)默認(rèn) UV 無法滿足紋理顯示需求
- 比如自動(dòng)生成的 UV 是按照立方體或球體映射方式,但你希望紋理按照 XY 平面鋪開。
- 或你想自定義紋理拉伸、縮放、裁剪方式。
3、貼圖范圍較大時(shí)
- 如將紋理貼在一個(gè)很大的面(如整個(gè)省份或城市)上,需要?dú)w一化 UV才能完整顯示整張圖,否則紋理可能看起來被擠壓或重復(fù)。
4、材質(zhì)設(shè)置使用了 map(紋理貼圖)
- UV 坐標(biāo)直接決定了紋理如何映射到頂點(diǎn)上,沒有正確的 UV,貼圖就是黑的或者混亂的。
1.2 這樣處理的好處
| 好處 | 描述 |
|---|---|
| ? 自定義貼圖范圍 | 按照 XY 平面的實(shí)際邊界(bounding box)進(jìn)行歸一化貼圖,紋理完整而且可控。 |
| ? 保證不同面統(tǒng)一貼圖邏輯 | 使所有面在同一坐標(biāo)系下統(tǒng)一使用紋理坐標(biāo),適用于多個(gè) Mesh 的統(tǒng)一貼圖。 |
| ? 更好地適應(yīng)實(shí)際地理數(shù)據(jù) | 尤其適合地圖類、建筑投影、城市地塊等從經(jīng)緯度生成的模型。 |
| ? 提高美觀性 | 避免貼圖拉伸、模糊、重復(fù)等問題。讓紋理在面上看起來更自然。 |
1.3 舉個(gè)例子
假如你從 GeoJSON 創(chuàng)建了一個(gè)類似湖北省的面模型,你貼上一張高清地圖 top.png 作為貼圖,如果不設(shè)置 UV:
紋理可能根本看不到。
或者整個(gè)紋理只出現(xiàn)在幾何體的一個(gè)小角落(默認(rèn) UV 不匹配)。
甚至多個(gè)面公用同一張紋理,但位置都不對(duì)。
二、紋理貼圖與 UV 的基礎(chǔ)知識(shí)
在 3D 渲染中,為了將 2D 圖片(紋理)映射到 3D 幾何體上,需要用到 UV 坐標(biāo):
| 概念 | 說明 |
|---|---|
| 紋理貼圖(Texture Mapping) | 是將 2D 圖像(比如一張地圖、照片等)映射到 3D 模型表面的過程。 |
| UV 坐標(biāo) | 是二維坐標(biāo)系 [u, v],對(duì)應(yīng)圖片的水平方向(U)和垂直方向(V),取值范圍通常是 [0, 1]。 |
| 目的 | 指定幾何體的哪個(gè)頂點(diǎn)對(duì)應(yīng)紋理圖的哪個(gè)像素點(diǎn)。沒有 UV,Three.js 無法知道貼圖怎么“貼”上去。 |
例子:
uv = [0,0]表示貼圖左下角,[1,1]表示右上角。- 如果某三角面頂點(diǎn) uv 是
[0,0],[1,0],[0,1],那紋理就會(huì)映射為一個(gè)右角三角區(qū)域。
三、代碼逐步解析
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. 計(jì)算 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. 根據(jù) 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. 設(shè)置新的 UV
child.geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uv, 2));
child.geometry.attributes.uv.needsUpdate = true;
}
})
使用你上面的邏輯:
- 會(huì)將湖北省這個(gè)面在 XY 平面上的投影范圍歸一化為 [0,1] × [0,1],然后貼圖正好完整覆蓋整個(gè)省份模型頂面。
- 并且每個(gè)三角面上紋理也會(huì)均勻分布,不會(huì)拉伸、重復(fù)或扭曲。
代碼每部分解析
const group = topPolygonMesh.object3d;
if (!group) return;
group.traverse(child => {
if (child.isMesh && child.geometry && child.geometry.attributes.position) {
?? 找到場(chǎng)景中的所有 Mesh,確保其包含幾何體和位置(頂點(diǎn))數(shù)據(jù)。
第一步:計(jì)算 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 平面上的包圍盒
第二步:計(jì)算新的 UV 坐標(biāo)
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 坐標(biāo)歸一化為 UV 區(qū)間
第三步:應(yīng)用 UV 到幾何體
child.geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uv, 2));
child.geometry.attributes.uv.needsUpdate = true;
?? 設(shè)置 UV 屬性并通知渲染系統(tǒng)更新
四、目的總結(jié)
該段代碼的最終目標(biāo)是:
? 使 XY 平面上的任意不規(guī)則幾何體,都能準(zhǔn)確、完整地顯示一張貼圖(通常是一張地圖或圖案)
五、為什么默認(rèn) UV 不適用?
| 場(chǎng)景 | 是否需要手動(dòng)設(shè)置 UV |
|---|---|
| 自定義頂點(diǎn)數(shù)據(jù)構(gòu)建幾何體 | ? 是的,需要自定義 UV |
| 從 GeoJSON/TopoJSON 轉(zhuǎn)換成幾何體 | ? 是的,沒有默認(rèn) UV |
使用 ExtrudeGeometry, ShapeGeometry | ? 有自動(dòng)生成 UV,但經(jīng)常不滿足 XY 貼圖的需要 |
| 紋理貼圖變形嚴(yán)重或重復(fù) | ? 需要重設(shè) UV 保證平鋪、清晰 |
總結(jié)
| 項(xiàng)目 | 內(nèi)容 |
|---|---|
| ?? 目的 | 將紋理按照 XY 平面貼滿整個(gè)面 |
| ?? 原理 | 將 XY 坐標(biāo)歸一化為 [0,1] 區(qū)間作為 UV |
| ?? 效果 | 保證紋理不變形、無縫、完整地顯示 |
| ?? 場(chǎng)景 | 地圖貼圖、建筑貼圖、非規(guī)則面紋理貼圖 |
| ?? 重點(diǎn) | UV 反映的是“紋理坐標(biāo)”,不是世界坐標(biāo) |
到此這篇關(guān)于Three.js中自定義UV坐標(biāo)貼圖的文章就介紹到這了,更多相關(guān)Three.js自定義UV坐標(biāo)貼圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于json字符串與實(shí)體之間的嚴(yán)格驗(yàn)證代碼
在一個(gè)項(xiàng)目中要求嚴(yán)格驗(yàn)證傳入的json字符串與定義的 類匹配,否則不記錄。后來查了好多資料才弄明白,下面小編給大家分享下關(guān)于json字符串與實(shí)體之間的嚴(yán)格驗(yàn)證,感興趣的朋友一起看看吧2016-11-11
用一段js程序來實(shí)現(xiàn)動(dòng)畫功能
用一段js程序來實(shí)現(xiàn)動(dòng)畫功能...2007-03-03
js 能實(shí)現(xiàn)監(jiān)聽F5頁面刷新子iframe 而父頁面不刷新的方法
下面小編就為大家?guī)硪黄猨s 能實(shí)現(xiàn)監(jiān)聽F5頁面刷新子iframe 而父頁面不刷新的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11
php對(duì)mongodb的擴(kuò)展(初識(shí)如故)
mongodb的數(shù)據(jù)存儲(chǔ)格式是一種由于MongoDB的文檔結(jié)構(gòu)為BJSON格式(BJSON全稱:Binary JSON),而BJSON格式本身就支持保存二進(jìn)制格式的數(shù)據(jù),因此可以把文件的二進(jìn)制格式的數(shù)據(jù)直接保存到MongoDB的文檔結(jié)構(gòu)中2012-11-11
javascript事件的傳播基礎(chǔ)實(shí)例講解(35)
這篇文章主要為大家詳細(xì)介紹了javascript事件的傳播基礎(chǔ)實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02
Javascript判斷文件是否存在(客戶端/服務(wù)器端)
這篇文章主要介紹了Javascript判斷文件是否存在的方法適用于客戶端、服務(wù)器端,遠(yuǎn)程文件,示例代碼如下,需要的朋友可以參考下2014-09-09
JavaScript 未結(jié)束的字符串常量常見解決方法
做JavaScript的時(shí)候,發(fā)現(xiàn)老是出現(xiàn)錯(cuò)誤:“未結(jié)束的字符串常量”. 自己找了下應(yīng)該是傳參數(shù)的時(shí)候,有特殊字符引起的.網(wǎng)上也找了下,也有好多出現(xiàn)這種情況.做下總結(jié),以方便以后查閱.2010-01-01

