基礎概念
坐標系
我們的手機屏幕是二維的,但是我們展示物體的世界是三維的,當我們在構建一個物體的時候我們是以一個三維世界既是世界坐標來構建,而轉化為屏幕坐標展示在我們眼前,則需要經歷多道矩陣變化,中間webGL替我們操作了許多事情。
世界坐標系:在webGL中,世界坐標系是以屏幕中心為原點(0, 0, 0),且是始終不變的。你面對屏幕,你的右邊是x正軸,上面是y正軸,屏幕指向你的為z正軸。長度單位這樣來定:窗口范圍按此單位恰好是(-1,-1,-1)到(1,1,1)。
- 屏幕坐標系:
webGL的重要功能之一就是將三維的世界坐標經過變換、投影等計算,最終算出它在顯示設備上對應的位置,這個位置就稱為設備坐標。在屏幕、打印機等設備上的坐標是二維坐標。
- 視點坐標系:
是以視點(照相機)為原點,以視線的方向為Z+軸正方向的坐標系中的方向。webGL會將世界坐標先變換到視點坐標,然后進行裁剪,只有在視線范圍(視見體)之內的場景才會進入下一階段的計算。
Raycaster
這個類設計用於鼠標去獲取在3D世界被鼠標選中的一些物體
Raycaster( origin, direction, near, far )
origin — 射線的起點向量。
direction — 射線的方向向量,應該歸一標准化。
near — 所有返回的結果應該比 near 遠。Near不能為負,默認值為0。
far — 所有返回的結果應該比 far 近。Far 不能小於 near,默認值為無窮大。
找到點擊物體的大致思路
鼠標在屏幕上點擊的時候,得到二維坐標p(x, y),再加上深度坐標的范圍(0, 1), 就可以形成兩個三位坐標A(x1, y1, 0), B(x2, y, 1), 由於它們的Z軸坐標是0和1,則轉變到投影坐標系的話,一定分別是前剪切平面上的點和后剪切平面上的點,也就是說,在投影坐標系中,A點一定在能看見的所有模型的最前面,B點一定在能看見的所有的模型的最后邊,將AB點連成線,AB線穿過的物體就是被點擊的物體。而 Three.js提供一個射線類Raycasting來拾取場景里面的物體。更方便的使用鼠標來操作3D場景。(不過在實際代碼中我們組成射線的兩個點是攝像機所在視點與屏幕上點擊的點連接而成的射線)
代碼實現
function onDocumentMouseDown(e) { e.preventDefault(); //將鼠標點擊位置的屏幕坐標轉成threejs中的標准坐標,具體解釋見代碼釋義 mouse.x = (e.clientX / window.innerWidth) * 2 - 1; mouse.y = -(e.clientY / window.innerHeight) * 2 + 1; //新建一個三維單位向量 假設z方向就是0.5 //根據照相機,把這個向量轉換到視點坐標系 var vector = new THREE.Vector3(mouse.x, mouse.y,0.5).unproject(camera); //在視點坐標系中形成射線,射線的起點向量是照相機, 射線的方向向量是照相機到點擊的點,這個向量應該歸一標准化。 var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize()); //射線和模型求交,選中一系列直線 var intersects = raycaster.intersectObjects(objects); console.log('imtersrcts=' + intersects) if (intersects.length > 0) { //選中第一個射線相交的物體 SELECTED = intersects[0].object; var intersected = intersects[0].object; console.log(intersects[0].object) } }
代碼釋義
//得到 mouse.x = (e.clientX / window.innerWidth) * 2 - 1; mouse.y = -(e.clientY / window.innerHeight) * 2 + 1; 推導過程: 設A點為點擊點(x1,y1),x1=e.clintX, y1=e.clientY 設A點在世界坐標中的坐標值為B(x2,y2); 由於A點的坐標值的原點是以屏幕左上角為(0,0); 我們可以計算可得以屏幕中心為原點的B'值 x2' = x1 - innerWidth/2 y2' = innerHeight/2 - y1 又由於在世界坐標的范圍是[-1,1],要得到正確的B值我們必須要將坐標標准化 x2 = (x1 -innerWidth/2)/(innerwidth/2) = (x1/innerWidth)*2-1 同理得 y2 = -(y1/innerHeight)*2 +1
參考資料
Three.js中的拾取
OpenGL中各種坐標系的理解
threejs對象拾取
前端填坑指南