iT邦幫忙

2022 iThome 鐵人賽

DAY 3
1

在HTML我們有X與Y。

但在Three.js的座標系中是三維空間:不僅有XY,還有Z。一切都複雜起來。

我會介紹3D的樹狀結構,這個不只是Three.js,在3D建模也通用。

樹狀結構

3D的世界裡,一個物體底下可以有多個物體組成,形成樹狀結構。

如果你有用過Maya,應該有用過parenting的功能。它讓一個物件變成另一個物件的child。

maya的畫面

在Three.js也是一樣:你可以有Parent物件,內含多個Child物件,組成一個樹狀結構。(這裡的物件可以是群組、可以是Mesh。)

https://ithelp.ithome.com.tw/upload/images/20220918/20142505iIVGIusOfn.png
而parent本身可能自有3D的形狀(geometry),也可能沒有,比如group就沒有。

樹狀結構:最大的影響是空間

不就XYZ嗎?是這樣沒錯啦….但包含XYZ也成為樹狀結構。

每一個物件都有一個空間數值,這些空間數值都是相對於Parent的。最上層根部的空間,就是世界空間。

https://ithelp.ithome.com.tw/upload/images/20220918/20142505f1Tce2KcMJ.png

再畫嚴謹一點,每一個Mesh都有geometry,都位在local space,而Mesh本身位在parent的local space。最上面的space就是world space。

https://ithelp.ithome.com.tw/upload/images/20220918/20142505w9bMQOSMt2.png

所以說,你會遇到一個情形:parent空間位在世界空間的(10,0,0),child又活在parent空間的(5,0,0),但由於Child的位置置相對於parent的,所以它實際上位於(15,0,0)。

https://ithelp.ithome.com.tw/upload/images/20220918/201425056M89DDoakV.png

以程式碼示範

以程式碼示範:準備場景

直接用上一篇的場景:

import * as THREE from 'three';

const scene = new THREE.Scene();

// PerspectiveCamera 需設定四個參數,下面接著介紹
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// Camera 身為鏡頭,有位置屬性,設定在Z軸即可。
camera.position.set(0, 0 15)

// 實例化渲染器
const renderer = new THREE.WebGLRenderer();
// 渲染器負責投影畫面在螢幕上,會需要寬高
renderer.setSize(window.innerWidth, window.innerHeight);
// 渲染器會產生canvas物件,我們在html的body放置它
document.body.appendChild( renderer.domElement );

// 建立一個形狀,用來定義物體的形狀為長寬高為1的正方體
const geometry = new THREE.BoxGeometry(1,1,1)
// 建立一個材質,可想像成一個物體所穿的衣服,設定材質為藍色
const material = new THREE.MeshBasicMaterial({color: 0x0000ff})
// 依據前兩者,建立物體
const cube = new THREE.Mesh(geometry, material);
// 放到場景裡,預設位置會是(0,0,0)
scene.add(cube);

// 很像setInterval的函式。每一幀都會執行這個函式
function animate() {
	// 每一幀物體都會自轉
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
	// 它每一幀執行animate()
	requestAnimationFrame( animate );
	// 每一幀,場景物件都會被鏡頭捕捉
	renderer.render( scene, camera );
}
// 函式起始點
animate();

以程式碼示範:建立child → parent → scene樹狀結構

我把cube拿掉,然後新增了兩個物件:parent跟child

// 換成兩個物件
- const cube = new THREE.Mesh(geometry, material);
+ const parent = new THREE.Mesh(geometry, material);
+ const child = new THREE.Mesh(geometry, material);
function animate() {
  // 先停止自轉自轉
-   cube.rotation.x += 0.01;
-   cube.rotation.y += 0.01;
}

然後設定兩個物體的位置

// 接著,把parent加到世界裡,把child加到parent裡
- scene.add(cube);
+ scene.add(parent);
+ parent.add(child);
// 依據前面的說明,把parent位置改成10,child位置改成5
+ parent.position.x = 10
+ child.position.x = 5
// 移除旋轉
function animate() {
-  cube.rotation.x += 0.01;
-  cube.rotation.y += 0.01;
}

這時候,parent位在世界座標的(10,0,0),child又比parent往左邊5單位

https://ithelp.ithome.com.tw/upload/images/20220918/201425052pcoL0aqXz.png

為了看清楚點,我們把鏡頭往上移動一點點,然後再換一下材質

// 修改鏡頭位置
- camera.position.set(0, 0, 15)
+ camera.position.set(0, 5, 15)
// 換成MeshNormalMaterial
- const material = new THREE.MeshBasicMaterial({color: 0x0000ff})
+ const material = new THREE.MeshNormalMaterial({color: 0x0000ff})

MeshNormalMaterial幫助我們看清楚物件,具體功能之後說明。目前成品如下:

https://ithelp.ithome.com.tw/upload/images/20220918/20142505lDEybWEZ1D.png

現在我們的場景基本上關係長這樣

https://ithelp.ithome.com.tw/upload/images/20220918/20142505x4wdAb8JfQ.png

以程式碼示範:自轉與公轉

接下來我們設定parent自轉,你猜child會往哪裡轉?

function animate() {
+  parent.rotation.y += 0.01;
	requestAnimationFrame( animate );
	renderer.render( scene, camera );
}

gif圖檔

你會發現,parent在自轉,child在公轉。

為什麼Child在公轉?因為他的local是座標相對於parent的。parent的座標在選轉,child的世界也隨著他選轉。

以程式碼示範:旋轉咖啡杯為例

https://ithelp.ithome.com.tw/upload/images/20220918/20142505wIDxCePHLK.png

圖片版權:Studio Sarah Lou

圖中有茶壺跟茶杯。大家坐在茶杯自轉,茶壺又帶著大家旋轉。

Child是就像是茶杯,Parent就像是茶壺。茶壺是茶杯的座標中心,茶杯就會繞著茶壺公轉。

希望這樣能清楚解釋關聯。

CodePen

https://codepen.io/umas-sunavan/pen/YzLZvpM?editors=0010

有了以上概念之後,我們明天就可以往矩陣繼續解釋。但在繼續之前,先容我介紹設定空間的方法。

設定空間的方法?

設定空間的方法:先解釋目前漏掉沒講的

你可能上一篇就在疑問這一行旋轉的程式碼:

// 旋轉cube
cube.rotation.x += 0.01

你可能會問,如果cube有旋轉(cube.rotation),那是否還有位置(cube.position)?跟縮放(cube.scale)?

答案是有的。

// 指定位置
cube.position.x += 0.01
// 縮放
cube.scale.x += 0.01

那你可能又會問:如果都是有的,那之前code的這一行怎麼邏輯不太一樣?

// 跟前面的指定位置不太一樣
camera.position.set(0, 0, 15)

到底哪個才是正確的,有差別嗎?

這個問題,我會連著空間設置,一起解釋:

設定空間的方法:cube在parent的空間位置

cube是一個Mesh物件,這在我們建立cube就知道了。回顧一下:

// cube是一個Mesh
const cube = new THREE.Mesh(geometry, material);

Mesh是有面的3D物體,重要的屬性有:

  • Mesh.position 是三維向量Vector3
  • Mesh.scale 是三維向量Vector3
  • Mesh**.**rotation 是歐拉角(姑且將他當作三維向量Vector3理解)

以上皆可用.set()來設定該Mesh在parent的絕對位置、旋轉、跟縮放。.set()就像setter。

當然也可以直接取值,例如camera.position.x = 5

又或者,使用函式修改空間位置:

  • Mesh.translate() 可以位移Mesh.position。

    跟.set()不同,.set()設定絕對位置,而它則單純移動位置

https://ithelp.ithome.com.tw/upload/images/20220918/20142505bPXGevQIXe.png

以上這些,都是設定Mesh在parent空間中的位置。

設定空間的方法:cube在local的空間位置

cube 是有自己的形狀的(cube.geometry),自己的形狀活在自己的空間裡。

  • Mesh.geometry.translate 修改形狀在local空間的位置
  • Mesh.geometry.rotateXYZ 修改形狀在local空間的旋轉
  • Mesh.geometry.scale 修改形狀在local空間的縮放

https://ithelp.ithome.com.tw/upload/images/20220918/201425057L3FhB9hy9.png

以上就是設定空間的方法。明天將繼續解釋矩陣。


上一篇
Day2: ThreeJS、OpenGL、WebGL:誰是誰?我要怎麼開始?
下一篇
Day4: Three.js 什麼!空間被扭曲了?我願稱你為最強——矩陣
系列文
30天成為鍵盤麥可貝:前端視覺特效開發實戰31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言