在HTML我們有X與Y。
但在Three.js的座標系中是三維空間:不僅有XY,還有Z。一切都複雜起來。
我會介紹3D的樹狀結構,這個不只是Three.js,在3D建模也通用。
3D的世界裡,一個物體底下可以有多個物體組成,形成樹狀結構。
如果你有用過Maya,應該有用過parenting的功能。它讓一個物件變成另一個物件的child。
在Three.js也是一樣:你可以有Parent物件,內含多個Child物件,組成一個樹狀結構。(這裡的物件可以是群組、可以是Mesh。)
而parent本身可能自有3D的形狀(geometry),也可能沒有,比如group就沒有。
不就XYZ嗎?是這樣沒錯啦….但包含XYZ也成為樹狀結構。
每一個物件都有一個空間數值,這些空間數值都是相對於Parent的。最上層根部的空間,就是世界空間。
再畫嚴謹一點,每一個Mesh都有geometry,都位在local space,而Mesh本身位在parent的local space。最上面的space就是world space。
所以說,你會遇到一個情形:parent空間位在世界空間的(10,0,0),child又活在parent空間的(5,0,0),但由於Child的位置置相對於parent的,所以它實際上位於(15,0,0)。
直接用上一篇的場景:
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();
我把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單位
為了看清楚點,我們把鏡頭往上移動一點點,然後再換一下材質
// 修改鏡頭位置
- 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
幫助我們看清楚物件,具體功能之後說明。目前成品如下:
現在我們的場景基本上關係長這樣
接下來我們設定parent自轉,你猜child會往哪裡轉?
function animate() {
+ parent.rotation.y += 0.01;
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
你會發現,parent在自轉,child在公轉。
為什麼Child在公轉?因為他的local是座標相對於parent的。parent的座標在選轉,child的世界也隨著他選轉。
圖片版權:Studio Sarah Lou
圖中有茶壺跟茶杯。大家坐在茶杯自轉,茶壺又帶著大家旋轉。
Child是就像是茶杯,Parent就像是茶壺。茶壺是茶杯的座標中心,茶杯就會繞著茶壺公轉。
希望這樣能清楚解釋關聯。
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
是三維向量Vector3Mesh.scale
是三維向量Vector3Mesh**.**rotation
是歐拉角(姑且將他當作三維向量Vector3理解)以上皆可用.set()
來設定該Mesh在parent的絕對位置、旋轉、跟縮放。.set()
就像setter。
當然也可以直接取值,例如camera.position.x = 5
又或者,使用函式修改空間位置:
Mesh.translate()
可以位移Mesh.position。
跟.set()不同,.set()設定絕對位置,而它則單純移動位置
以上這些,都是設定Mesh在parent空間中的位置。
cube
在local的空間位置cube
是有自己的形狀的(cube.geometry
),自己的形狀活在自己的空間裡。
Mesh.geometry.translate
修改形狀在local空間的位置Mesh.geometry.rotateXYZ
修改形狀在local空間的旋轉Mesh.geometry.scale
修改形狀在local空間的縮放以上就是設定空間的方法。明天將繼續解釋矩陣。