1. 程式人生 > 實用技巧 >轉載【Raycaster入門】 | ThreeJS中的點選與互動——Raycaster的用法

轉載【Raycaster入門】 | ThreeJS中的點選與互動——Raycaster的用法

基礎概念

座標系

我們的手機螢幕是二維的,但是我們展示物體的世界是三維的,當我們在構建一個物體的時候我們是以一個三維世界既是世界座標來構建,而轉化為螢幕座標展示在我們眼前,則需要經歷多道矩陣變化,中間webGL替我們操作了許多事情。

  • 世界座標系:在webGL中,世界座標系是以螢幕中心為原點(0, 0, 0),且是始終不變的。你面對螢幕,你的右邊是x正軸,上面是y正軸,螢幕指向你的為z正軸。長度單位這樣來定:視窗範圍按此單位恰好是(-1,-1,-1)到(1,1,1)。
  • 螢幕座標系:

webGL的重要功能之一就是將三維的世界座標經過變換、投影等計算,最終算出它在顯示裝置上對應的位置,這個位置就稱為裝置座標。在螢幕、印表機等裝置上的座標是二維座標。

  • 視點座標系:

是以視點(照相機)為原點,以視線的方向為Z+軸正方向的座標系中的方向。webGL會將世界座標先變換到視點座標,然後進行裁剪,只有在視線範圍(視見體)之內的場景才會進入下一階段的計算。

Raycaster

Raycaster threeJs官方文件

這個類設計用於滑鼠去獲取在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場景。(不過在實際程式碼中我們組成射線的兩個點是攝像機所在視點與螢幕上點選的點連線而成的射線)

來一個Raycasting的官方例項

程式碼實現

function onDocumentMouseDown(e) {
    e.preventDefault();
<span class="hljs-comment">//將滑鼠點選位置的螢幕座標轉成threejs中的標準座標,具體解釋見程式碼釋義</span>
mouse.x = (e.clientX / <span class="hljs-built_in">window</span>.innerWidth) * <span class="hljs-number">2</span> - <span class="hljs-number">1</span>;
mouse.y = -(e.clientY / <span class="hljs-built_in">window</span>.innerHeight) * <span class="hljs-number">2</span> + <span class="hljs-number">1</span>;
<span class="hljs-comment">//新建一個三維單位向量 假設z方向就是0.5</span>
<span class="hljs-comment">//根據照相機,把這個向量轉換到視點座標系</span>
  <span class="hljs-keyword">var</span> vector = <span class="hljs-keyword">new</span> THREE.Vector3(mouse.x, mouse.y,<span class="hljs-number">0.5</span>).unproject(camera);

<span class="hljs-comment">//在視點座標系中形成射線,射線的起點向量是照相機, 射線的方向向量是照相機到點選的點,這個向量應該歸一標準化。</span>
<span class="hljs-keyword">var</span> raycaster = <span class="hljs-keyword">new</span> THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());

<span class="hljs-comment">//射線和模型求交,選中一系列直線</span>
<span class="hljs-keyword">var</span> intersects = raycaster.intersectObjects(objects);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'imtersrcts='</span> + intersects)

<span class="hljs-keyword">if</span> (intersects.length &gt; <span class="hljs-number">0</span>) {
    <span class="hljs-comment">//選中第一個射線相交的物體</span>
    SELECTED = intersects[<span class="hljs-number">0</span>].object;
    <span class="hljs-keyword">var</span> intersected = intersects[<span class="hljs-number">0</span>].object;
    <span class="hljs-built_in">console</span>.log(intersects[<span class="hljs-number">0</span>].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物件拾取
《計算機圖形學》
前端填坑指南


轉載自:https://segmentfault.com/a/1190000010490845
作者:beccaFu