原文地址: WebGL之物體選擇
使用WebGL將圖形繪製到畫布後,如何與外部進行互動?這其中最關鍵的就是如何實現物體的選擇。比如滑鼠點選後判斷是否選中了某個圖形或圖形的某個部分。
本節實現的效果: WebGL選中物體
如何實現選中物體
顏色區分法
《WebGL程式設計指南》中提出了一個原理很簡單的解決方案,步驟如下:
滑鼠按下時物體重繪為紅色或其他能區分的顏色
讀取滑鼠點選處畫素的顏色
gl.readPixels(x,y,width,height,format,type,pixels)
使用物體原來的顏色進行重繪,以恢復物體本來顏色
判斷第2步讀取到的顏色是否與預設的顏色值相等,相等則表示點選中物體
可以說這是個非常容易實現的方案,不過要為每個物體分別設定不同的區分顏色卻是個隱患,同時也不夠友好。
光線投射法
這是使用最廣泛也最精確的一種方案了,Three.js 中的光線投射器 (Raycaster) 就實現了這種方案,可以看裡面的原始碼。
它的基本原理: 從視點出發的光線首先投射到近截面,最後投射到遠截面,結合滑鼠點選的位置 (x, y) 和檢視投影矩陣 (viewProjection)。可以得出由近截面座標 (x1, y1, z1) 和遠截面座標 (x2, y2, z2) 組成的光線向量。然後我們就可以將物體座標構成的面逐個與這個光線向量進行對比。首先對比盒子邊界,再對比三角形面,這中間涉及到法向量,點積,叉積的計算,那叫一個複雜。
投影座標判斷法
目前對光線投射的具體實現理解地不是很透徹,那我就只能通過自己的理解來實現個簡單版的方案。基本原理就是,用檢視投影模型矩陣 (mvp) 對圖形座標進行變換,得到在螢幕中的繪製座標(xyz)。然後遍歷每個座標得出一個由最大最小xy座標 (xmax, xmin, ymax, ymin) 構成的二維平面盒子。最後與滑鼠位置 (x, y) 進行比較,如果滑鼠xy座標處於盒子邊界之內,那麼就可判斷選中了該物體。核心程式碼如下:
canvas.addEventListener('mousemove', function(e) {
//座標轉換為webgl表示區間
const pos = util.windowToWebgl(tCanvas,e.clientX,e.clientY);
const ps = [];
Polygons.forEach((p,i)=>{
//重置狀態
p.select = false;
//mvp矩陣
const matrix = m4.translate(viewProjection, p.pos);
let xmax, ymax, xmin, ymin;//盒子的邊界
//遍歷頂點獲取盒子的邊界
for(let j = 0; j < p.position.length; j = j+3){
//對座標進行矩陣轉換
const s = m4.transformPoint(matrix, p.position.slice(j,j+3));
if(j == 0){
xmax = s[0];
xmin = s[0];
ymax = s[1];
ymin = s[1];
continue;
}
if(s[0]>xmax) xmax = s[0];
if(s[0]<xmin) xmin = s[0];
if(s[1]>ymax) ymax = s[1];
if(s[1]<ymin) ymin = s[1];
}
// 位於盒子邊界內
if(pos.x >= xmin && pos.x <= xmax && pos.y >= ymin && pos.y <= ymax){
ps.push(p);
}
});
if(!ps.length) return;
let sel;
//獲取最靠近視點的圖形
if(ps.length == 1) {
sel = ps[0];
} else {
sel = ps.sort((a,b)=> {
const az = m4.transformPoint(a.matrix,[0,0,0])[2];
const bz = m4.transformPoint(b.matrix,[0,0,0])[2];
return az - bz;
})[0];
}
//設定該圖形為選中
Polygons[sel.index].select = true;
},false);
目前實現的功能在 選擇不規則的物體時,判斷地不是很精準,畢竟不是所有的圖形都是類似矩形。
那麼解決方案就是:我們知道WebGL圖形是由三角形構成的,如果進一步判斷滑鼠位置是否在構成該圖形的三角形面當中,那就會更加精確了,這個功能留給讀者去實現。