1. Three.js中的拾取
1.1. 從模型轉到屏幕上的過程說開
由於圖形顯示的基本單位是三角形,那就先從一個三角形從世界坐標轉到屏幕坐標說起,例如三角形abc
乘以模型視圖矩陣就進入了視點坐標系,其實就是相機所在的坐標系,如下圖:
進入視點坐標系后,再乘以投影矩陣,就會變換到一個立方體內,如下圖:
這個時候整個三角形就位於中心位於坐標系原點,邊長為2的立方體內,在這個立方體內,三角形要計算光照,要裁剪,然后乘以視口矩陣,最后轉到屏幕上。
轉到屏幕上后,三角形的所有點的Z坐標就是深度坐標,一定在(0, 1)這個區間內,那么哪些點的Z坐標是0呢,在投影坐標系中,一定是投影視景體的前剪切平面上的點,而投影視景體的后剪切平面上的點的Z坐標就是1。
1.2. 思路來了
根據以上三角形轉換到屏幕坐標上的過程可以分析出,鼠標在屏幕上點擊的時候,可以得到二維坐標p(x, y),再加上深度坐標的范圍(0, 1), 就可以形成兩個三位坐標A(x, y, 0), B(x, y, 1), 由於它們的Z軸坐標是0和1,則轉變到投影坐標系的話,一定分別是前剪切平面上的點和后剪切平面上的點,也就是說,在投影坐標系中,A點一定在能看見的所有模型的最前面,B點一定在能看見的所有的模型的最后邊,假設視口矩陣的逆矩幀,投影矩陣的逆矩陣,模型視圖矩陣的逆矩陣為M, N, P,則 P * N * M * A = A1, P * N * M * B = B1, 在世界坐標系中,點A1B1就可以形成一個射線,此射線和模型再求交,就能選中模型。如下圖是在視點坐標系中的情形。注意,求交可以在視點坐標系或者世界坐標系計算都可以,但一般會在世界坐標坐標系中計算。
1.3. 拾取的優化,射線和AABB包圍盒求交
如果射線和所有的模型求交,顯然不是一個好辦法,一般情況下會進行一些優化,比如先和模型的包圍盒求交,如果和模型的包圍盒不相交的話,就放過去,否則就接着往下進行,和模型的所有三角面片求交。
那么什么是包圍盒呢?在計算機圖形學與計算幾何領域,一組物體的包圍體就是將物體組合完全包容起來的一個封閉空間。將復雜物體封裝在簡單的包圍體中,就可以提高幾何運算的效率。通常簡單的物體比較容易檢查相互之間的重疊。其中有一種包圍盒叫做AABB, AABB的全稱是axis aligned bounding box,就是我們常常提到軸向包圍盒,這個盒子的邊是平行於x/y/z軸的。 所有的2d和3d物體都是由點組成的,所以只要找出這些物體的最大值點和最小值點,那么就可以使用這兩個點表示該物體的AABB包圍盒了。
檢測碰撞的時候我們只需要檢測這些物體的AABB(即他們的最大值點和最小值點)是否相交,就可以判斷是否碰撞了。
1.4. 射線和三角形相交
判斷射線和包圍盒是否求交后,就輪到判斷是否和三角形求交了,最先想到的是 首先判斷射線是否與三角形所在的平面相交,如果相交,再判斷交點是否在三角形內。判斷射線是否與平面相交, 判斷點是否在三角形內.
1.5. THREE.JS中求交的代碼實現
three.js中的一個案例,名字叫webgl_interactive_lines.html,可以選中一根線,並顯示一個小球。根據以上的思路,代碼注釋如下:
//鼠標點擊的屏幕坐標轉換到視點坐標系
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 ).unproject( camera );
//在視點坐標系中形成射線
raycaster.set( camera.position,vector.sub( camera.position ).normalize() );
//射線和模型求交,選中一系列直線
var intersects = raycaster.intersectObjects( parentTransform.children, true);
if ( intersects.length > 0 ) {
if ( currentIntersected !== undefined )
{
currentIntersected.material.linewidth = 1;
}
//第一個直線
currentIntersected = intersects[ 0 ].object;
currentIntersected.material.linewidth = 5;
//把球設為可見,並且位置移到鼠標點擊的屏幕位置
sphereInter.visible = true;
sphereInter.position.copy( intersects[ 0 ].point );
}
歡迎加微信 nuonuodi_1, 交流更多的技術問題