WebGL之物體選擇

Jeff.Zhong發表於2019-05-29

原文地址: WebGL之物體選擇
使用WebGL將圖形繪製到畫布後,如何與外部進行互動?這其中最關鍵的就是如何實現物體的選擇。比如滑鼠點選後判斷是否選中了某個圖形或圖形的某個部分。

本節實現的效果: WebGL選中物體
WebGL選中物體

如何實現選中物體

顏色區分法

《WebGL程式設計指南》中提出了一個原理很簡單的解決方案,步驟如下:

  1. 滑鼠按下時物體重繪為紅色或其他能區分的顏色

  2. 讀取滑鼠點選處畫素的顏色

    gl.readPixels(x,y,width,height,format,type,pixels)
  3. 使用物體原來的顏色進行重繪,以恢復物體本來顏色

  4. 判斷第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圖形是由三角形構成的,如果進一步判斷滑鼠位置是否在構成該圖形的三角形面當中,那就會更加精確了,這個功能留給讀者去實現。

相關文章