深入理解Three.js(WebGL)貼圖(紋理映射)和UV映射

本文將詳細描述如何使用Three.js給3D對象添加貼圖(Texture Map,也譯作紋理映射,“貼圖”的翻譯要更直觀,而“紋理映射”更準確。)。為了能夠查看在線演示效果,你需要有一個兼容WebGL的現代瀏覽器(最好是Chrome/FireFox/Safari/Edge/IE11+)。

本文的在線演示結果和代碼請點擊這里:Three.js貼圖實例。

什么是貼圖(Texture Mapping)

貼圖是通過將圖像應用到對象的一個或多個面,來為3D對象添加細節的一種方法。

這使我們能夠添加表面細節,而無需將這些細節建模到我們的3D對象中,從而大大精簡3D模型的多邊形邊數,提高模型渲染性能。

開始吧

這里方便起見,我們使用踏得網在線開發工具來一步步邊學邊操作。

請點擊新建作品,在第三方庫中選擇Three.js 80版本,這將自動加載對應版本的Three.js開發庫(注:你也可以直接把<script src="http://wow.techbrood.com/libs/three.r73.js"></script>拷貝到HTML代碼面板中去)。

首先我們創建一個立方體,在JavaScript面板中編寫代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
var camera;
var scene;
var renderer;
var mesh;
??
init();
animate();
??
function init() {
??
????scene = new THREE.Scene();
????camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000);
??
????var light = new THREE.DirectionalLight( 0xffffff );
????light.position.set( 0, 1, 1 ).normalize();
????scene.add(light);
??
????var geometry = new THREE.CubeGeometry( 10, 10, 10);
????var material = new THREE.MeshPhongMaterial( { ambient: 0x050505, color: 0x0033ff, specular: 0x555555, shininess: 30 } );
??
????mesh = new THREE.Mesh(geometry, material );
????mesh.position.z = -50;
????scene.add( mesh );
??
????renderer = new THREE.WebGLRenderer();
????renderer.setSize( window.innerWidth, window.innerHeight );
????document.body.appendChild( renderer.domElement );
??
????window.addEventListener( 'resize', onWindowResize, false );
??
????render();
}
??
function animate() {
????mesh.rotation.x += .04;
????mesh.rotation.y += .02;
??
????render();
????requestAnimationFrame( animate );
}
??
function render() {
????renderer.render( scene, camera );
}
??
function onWindowResize() {
????camera.aspect = window.innerWidth / window.innerHeight;
????camera.updateProjectionMatrix();
????renderer.setSize( window.innerWidth, window.innerHeight );
????render();
}

點擊菜單欄中的[運行]菜單(blob.png),或者按快捷鍵:CTRL+R,來運行該代碼,你將看到一個旋轉的藍色立方體:

我們接下來要做的就是把這個立方體變成一個游戲里常見的木箱子,如下圖所示:

為此我們需要一張箱子表面的圖像,并用這張圖像映射到立方體對象的材料中去,

這里我們直接使用在線圖片http://wow.techbrood.com/uploads/1702/crate.jpg.

JS代碼中修改之前的材料(material)創建代碼:

1
var material = new THREE.MeshPhongMaterial( { ambient: 0x050505, color: 0x0033ff, specular: 0x555555, shininess: 30 } );

為使用貼圖:

1
var material = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('http://wow.techbrood.com/uploads/1702/crate.jpg') } );

再運行下(按[運行]菜單或CTRL+R快捷鍵),你會看到一個旋轉的板條箱,而不是一個普通的藍色立方體。

在構造我們的材質時,我們指定了texture屬性并將其值設置為木箱圖像,Three.js然后會加載紋理圖像并映射到立方體各個面上。

那么,問題是如果我們想給不同的面添加不同的紋理貼圖,該怎么辦呢?

一種方法是使用材料數組,我們創建6個新材料,每一個使用不同的紋理貼圖:bricks.jpg,clouds.jpg,stone-wall.jpg,water.jpg,wood-floor.jpg以及上面的crate.jpg。

相應的,我們把材料構造代碼修改為:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var?material1?=?new?THREE.MeshPhongMaterial(?{?
map:?THREE.ImageUtils.loadTexture('/uploads/1702/crate.jpg')?}?);
var?material2?=?new?THREE.MeshPhongMaterial(?{?
map:?THREE.ImageUtils.loadTexture('/uploads/1702/bricks.jpg')?}?);
var?material3?=?new?THREE.MeshPhongMaterial(?{?
map:?THREE.ImageUtils.loadTexture('/uploads/1702/clouds.jpg')?}?);
var?material4?=?new?THREE.MeshPhongMaterial(?{?
map:?THREE.ImageUtils.loadTexture('/uploads/1702/stone-wall.jpg')?}?);
var?material5?=?new?THREE.MeshPhongMaterial(?{?
map:?THREE.ImageUtils.loadTexture('/uploads/1702/water.jpg')?}?);
var?material6?=?new?THREE.MeshPhongMaterial(?{?
map:?THREE.ImageUtils.loadTexture('/uploads/1702/wood-floor.jpg')?}?);
var?materials?=?[material1,?material2,?material3,?material4,?material5,?material6];
var?meshFaceMaterial?=?new?THREE.MeshFaceMaterial(?materials?);

上述代碼,我們先分別創建了6個材料,組成了一個材料數組,并使用這個數組創建一個MeshFaceMaterial對象。

最后,我們需要告訴我們的3D模型來使用這個新的組合“面材料”,修改下面的代碼:

1
mesh = new THREE.Mesh(geometry, material );

為:

1
mesh = new THREE.Mesh(geometry,? meshFaceMaterial);

再運行下(按[運行]菜單或CTRL+R快捷鍵),你就將看到立方體的各個表面使用了不同的貼圖。

這很酷,Three.js會自動把數組中的這些材料應用到不同的面上去。

但問題又來了,隨著3D模型的面的增長,為每個面創建貼圖是不現實的。

這就是為什么我們需要另外一種更為普遍的解決方法:UV映射的原因。

UV映射(UV Mapping)

UV映射最典型的例子就是把一張地圖映射到3D球體的地球儀上去。其本質上就是把平面圖像的不同區塊映射到3D模型的不同面上去。我們把之前的6張圖拼裝成如下的一張圖:http://wow.techbrood.com/uploads/160801/texture-atlas.jpg.

修改如下代碼:

1
2
3
4
5
6
var material1 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/crate.jpg') } );
var material2 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/bricks.jpg') } );
var material3 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/clouds.jpg') } );
var material4 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/stone-wall.jpg') } );
var material5 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/water.jpg') } );
var material6 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/wood-floor.jpg') } );

為:

1
var material = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/texture-atlas.jpg') } );

我們又把代碼給改回來使用一張貼圖了,接下來我們需要把貼圖的不同位置映射到立方體不同的面上去。

首先我們創建貼圖的6個子圖,在創建完材料的代碼后面添加如下幾行:

1
2
3
4
5
6
var bricks = [new THREE.Vector2(0, .666), new THREE.Vector2(.5, .666), new THREE.Vector2(.5, 1), new THREE.Vector2(0, 1)];
var clouds = [new THREE.Vector2(.5, .666), new THREE.Vector2(1, .666), new THREE.Vector2(1, 1), new THREE.Vector2(.5, 1)];
var crate = [new THREE.Vector2(0, .333), new THREE.Vector2(.5, .333), new THREE.Vector2(.5, .666), new THREE.Vector2(0, .666)];
var stone = [new THREE.Vector2(.5, .333), new THREE.Vector2(1, .333), new THREE.Vector2(1, .666), new THREE.Vector2(.5, .666)];
var water = [new THREE.Vector2(0, 0), new THREE.Vector2(.5, 0), new THREE.Vector2(.5, .333), new THREE.Vector2(0, .333)];
var wood = [new THREE.Vector2(.5, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, .333), new THREE.Vector2(.5, .333)];

上面的代碼創建了六個數組,每一個對應于紋理貼圖中的每個子圖像。每個數組包含4個點,定義子圖像的邊界。坐標的范圍值是0到1,(0,0)表示左下角,(1,1)表示右上角。

子圖像的坐標是根據貼圖中百分比來定義。比如下面這個磚頭子圖像:

1
2
3
4
5
6
var bricks = [
new THREE.Vector2(0, .666),
new THREE.Vector2(.5, .666),
new THREE.Vector2(.5, 1),
new THREE.Vector2(0, 1)
];

在貼圖中的位置在左上角(占據橫向1/2,豎向1/3的位置),以逆時針方向來定義頂點坐標,從該子圖像較低的左下角開始。

左下角:
0 - 最左邊
.666 - 底部向上2/3處

右下角:
.5 - 中間線
.666 - 底部向上2/3處

右上角:
.5 - 中間線
1 - 頂邊

右上角:
0 - 最左邊
1 - 頂邊

定義好子圖像后,我們現在需要把它們映射到立方體的各個面上去。首先添加如下代碼:

1
geometry.faceVertexUvs[0] = [];

上述代碼清除現有的UV映射,接著我們添加如下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
geometry.faceVertexUvs[0][0] = [ bricks[0], bricks[1], bricks[3] ];
geometry.faceVertexUvs[0][1] = [ bricks[1], bricks[2], bricks[3] ];
??
geometry.faceVertexUvs[0][2] = [ clouds[0], clouds[1], clouds[3] ];
geometry.faceVertexUvs[0][3] = [ clouds[1], clouds[2], clouds[3] ];
??
geometry.faceVertexUvs[0][4] = [ crate[0], crate[1], crate[3] ];
geometry.faceVertexUvs[0][5] = [ crate[1], crate[2], crate[3] ];
??
geometry.faceVertexUvs[0][6] = [ stone[0], stone[1], stone[3] ];
geometry.faceVertexUvs[0][7] = [ stone[1], stone[2], stone[3] ];
??
geometry.faceVertexUvs[0][8] = [ water[0], water[1], water[3] ];
geometry.faceVertexUvs[0][9] = [ water[1], water[2], water[3] ];
??
geometry.faceVertexUvs[0][10] = [ wood[0], wood[1], wood[3] ];
geometry.faceVertexUvs[0][11] = [ wood[1], wood[2], wood[3] ];

geometry對象的faceVertexUvs屬性包含該geometry各個面的坐標映射。既然我們映射到一個多維數據集,你可能會疑惑為什么數組中有12個面。原因是在ThreeJS模型中,立方體的每個面實際上是由2個三角形組成的。所以我們必須單獨映射每個三角形。上述場景中,ThreeJS將為我們加載單一材料貼圖,自動分拆成三角形并映射到每個面。

這里要注意每個面的頂點坐標的定義順序必須遵循逆時針方向。為了映射底部三角形,我們需要使用的頂點指數0,1和3,而要映射頂部三角形,我們需要使用索引1,2,和頂點的3。

最后,我們替換如下代碼:

1
2
var meshFaceMaterial = new THREE.MeshFaceMaterial( materials );
mesh = new THREE.Mesh(geometry,? meshFaceMaterial);

為:

1
mesh = new THREE.Mesh(geometry,? material);

我們再運行下代碼(按[運行]菜單或CTRL+R快捷鍵),將看到各個面使用不同貼圖的旋轉立方體。

當然對于復雜的對象,我們還可以在建模的時候建立好模型貼圖,并導出為ThreeJS所支持的模型格式,然后在場景中直接加載。

這個超出本文范圍,請自行搜索本站Three.js在線實例。

?

參考:?http://solutiondesign.com/blog/-/blogs/webgl-and-three-js-texture-mappi-1/

編注:原文在線演示和源代碼鏈接不可用,已重新建立在WOW上。

轉載于:https://www.cnblogs.com/yanan-boke/p/7815018.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/286664.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/286664.shtml
英文地址,請注明出處:http://en.pswp.cn/news/286664.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android之glide加載圓形圖片地址異常監聽

1 問題 glide加載圖片地址的時候&#xff0c;可能這個地址沒有圖片&#xff0c;那么我們需要對這種異常情況進行處理&#xff0c;當然我們也需要把這個圖片進行圓形化 2 代碼解決 Glide.with(mContext).load(iconPath).error(new ColorDrawable()).listener(new RequestListen…

在蘭州吃牛肉面時親眼目睹的一幕,我感動了

今天中午去吃占國牛肉面&#xff0c;人比較多&#xff0c;很熱鬧。旁邊坐了一群身穿校服的小學生&#xff0c;身后坐著一位小學老師&#xff0c;當孩子們發現老師在后面的時候&#xff0c;都異口同聲地叫老師過去跟他們一起吃&#xff0c;老師嘿的一笑&#xff0c;沒同意&#…

如何跨 Namespace 同步 Secret 和 ConfigMap?

Secret 和 ConfigMap 資源對象是命名空間級別的。它們只能被同一命名空間中的 Pod 引用。所以有時候不得不手動為每個命名空間創建它們。但有很多場景&#xff0c;我們想讓它們是全局的&#xff0c;至少可以是跨命名空間共享的 Secret 和 ConfigMap&#xff0c;例如這些場景&am…

OS X 10.11 安裝Cocoapods

sudo gem install cocoapods報如下錯誤&#xff1a; ERROR: While executing gem ... (Errno::EPERM) Operation not permitted - /usr/bin/xcodeproj 解決的辦法是&#xff1a;sudo gem install -n /usr/local/bin cocoapods gem影像改成&#xff1a;https://ruby.taobao.org/…

一文總結學習 Python 的 14 張思維導圖

本文主要涵蓋了 Python 編程的核心知識&#xff08;暫不包括標準庫及第三方庫&#xff0c;后續會發布相應專題的文章&#xff09;。 首先&#xff0c;按順序依次展示了以下內容的一系列思維導圖&#xff1a;基礎知識&#xff0c;數據類型&#xff08;數字&#xff0c;字符串&am…

GEE學習筆記

掩膜 ? 在遙感圖像處理中&#xff0c;"掩膜"是指一種用于隱藏或保留圖像特定部分的技術。掩膜通常是一個二進制圖像&#xff0c;其中的像素值為0或1&#xff0c;分別表示遮蔽或保留。 ? 在去除云的情境中&#xff0c;掩膜通常用于隱藏圖像中被云覆蓋的部分&#…

十一、飛機大戰(IVX 快速開發教程)

十一、飛機大戰 制作微信小游戲大致流程與微信小程序、Web類似&#xff0c;不同的在于是組件的使用。 文章目錄十一、飛機大戰11.1.1 完成游戲角色制作11.1.2 完成物理世界添加11.1.3 完成子彈對象反重力運動11.1.4 使用對象組創建子彈11.1.5 子彈優化11.1.6 設置敵機11.1.7 優…

中國版LinkedIn呼之欲出

迄今為止&#xff0c;發展勢頭強勁的Facebook仍未上市&#xff0c;而美國職業社交網站LinkedIn 卻率先登錄紐約證券交易所&#xff0c;股價在第一天的交易中飆升&#xff0c;從45美元的發行價上漲了一倍&#xff0c;然后超過100美元&#xff0c;最終以94.25美元收盤。 LinkedIn…

Android之實現RecyclerView拖拽效果和固定部分元素不進行交換位置(包含源代碼下載地址)

1、效果爆照 啟動的效果 拖動過程中的效果 拖動后的效果 2、需求和問題 需求:RecyclerView實現拖拽效果,但是部分固定位置不能進行拖拽也不能在拖拽的過程中交換順序

各主流瀏覽器內核介紹

所謂的“瀏覽器內核”無非指的是一個瀏覽器最核心的部分——“Rendering Engine”&#xff0c;直譯這個詞匯叫做“渲染引擎”&#xff0c;不過我們也常稱其為“排版引擎”、“解釋引擎”。這個引擎的作用是幫助瀏覽器來渲染網頁的內容&#xff0c;將頁面內 容和排版代碼轉換為用…

測繪地理信息標準(國家、行業、地方)大全來了:測繪地理信息標準化服務平臺

測繪地理信息標準化服務平臺&#xff0c;這里有你需要的所有標準&#xff0c;趕快來圍觀吧&#xff01;

WPF效果第一百八十七篇之再玩ListBox

大周末的接著上一篇又玩了ListBox,這不又來再次去玩耍ListBox;畢竟是我的最愛,沒辦法就喜歡玩耍他;閑話也不多扯了,直接看最終效果:2、ItemsPanel還是老樣子:<ItemsPanelTemplate x:Key"CommonItemsPanelTemplate"><WrapPanel Orientation"Horizontal&…

Nginx支持比Apache高并發的原因

1.先從各自使用的多路復用IO模型說起&#xff1a; select模型&#xff1a;&#xff08;apache使用&#xff0c;由于受模塊等限制&#xff0c;用的不多&#xff09; 單個進程能夠 監視的文件描述符的數量存在最大限制select()所維護的 存儲大量文件描述符的數據結構 &#xf…

利用pdf.js開發嵌入pdf顯示,以及利用jquery-ui左右分欄顯示

原來考慮用pdf.js的viewer.html頁面&#xff0c;但怎么用都不方便。因此直接用pdf.js在左側連續顯示pdf所有內容&#xff0c;右側顯示其它相關內容&#xff0c;并且左右寬度可以任意拖動&#xff0c;最終實現效果如圖&#xff1a; 代碼&#xff1a;<!DOCTYPE html><ht…

十三、制作 iVX音樂分享小程序

功能介紹 通過前幾節的學習&#xff0c;我們對完成一個應用已經有了一些自己的心得。在此再次再制作一個小的音樂小程序應用。該應用一共分為首頁、榜單頁、音樂分享頁和音樂搜索頁。 首頁&#xff1a; 榜單內容頁&#xff1a; 音樂分享頁&#xff1a; 音樂搜索頁&#xff1a…

01_反射_02_反射類的構造方法

【工程截圖】 【Person.java】 //將要被反射的類 package com.Higgin.reflect; import java.util.List;public class Person {private String name"NULL";private int age0;public Person(){System.out.println("構造方法&#xff1a;Person()");}public P…

西北師范大學地理與環境科學學院考研真題匯總(自然地理學)持續更新。。。

西北師范大學地理與環境學科學院研究生入學考試的所有專業(地圖學與地理信息系統、自然地理學、人文地理學、環境科學、環境工程)的專業課均為自然地理學,本文持續收集整理歷年自然地理學考研入學考試真題。 1998年 一、名詞 1. 焚風 2. 徑流模數 3. 趨同適應 4. 墨卡托…

Android之提示type checking has run into a recrusive problem. Easiest workaround: specify types of your

1 問題 寫kotlin的時候錯誤提示如下 type checking has run into a recrusive problem. Easiest workaround: specify types of your declarations explicitly 2 分析 我寫得是遞歸函數如下&#xff0c;錯誤提示就是上面&#xff0c;是因為我們寫返回值&#xff0c;才導致 s…

私有云搭建 OpenStack(centos7.3, centos-release-openstack-ocata)

OpenStack&#xff08;centos7.3,centos-release-openstack-ocata&#xff09;nova&#xff1a;計算節點queue&#xff1a;消息隊列&#xff0c;系統瓶頸所在scheduler&#xff1a;調度機制conductor&#xff1a;更新數據庫cert&#xff08;objectstore&#xff09;&#xff1a…

C# 類繼承中的私有字段都去了哪里?

最近在看 C 類繼承中的字段內存布局&#xff0c;我就很好奇 C# 中的繼承鏈那些 private 字段都哪里去了? 在內存中是如何布局的&#xff0c;畢竟在子類中是無法訪問的。一&#xff1a;舉例說明 為了方便講述&#xff0c;先上一個例子&#xff1a;internal class Program{stati…