原文地址: 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) 组成的射线向量。然后我们就可以将物体坐标构成的面逐个与这个向量进行对比。这涉及到线性代数中的向量,点积,叉积,矩阵等概念,比较复杂。主要分两个步骤:

  1. 创建物体的包围盒,判断射线是否穿过该物体包围盒
  2. 判断射线是否穿过该物体的某个三角形面,如果经过即可判断选中了该物体

下面就分步实现光线投射算法的上面两个步骤

包围盒

包围盒算法原理如下:

首先用视图投影模型矩阵 (mvp) 对图形坐标进行变换,得到在屏幕中的绘制坐标[x,y,z]

遍历每个坐标得出一个由最大最小xy坐标 [xmax, xmin, ymax, ymin] 构成的二维包围盒

鼠标位置 (x, y) 与包围盒边界进行比较,如果坐标处于盒子边界之内,那么就可判断选中了该物体

核心代码如下:

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, zmax, zmin;//包围盒边界
//遍历顶点获取包围盒的边界
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];
zmax = s[2];
zmin = s[2];
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(s[2]>zmax) zmax = s[2];
if(s[2]<zmin) zmin = s[2];
}
// 射线处于包围盒内
if(pos.x >= xmin && pos.x <= xmax && pos.y >= ymin && pos.y <= ymax){
p.coord = [(xmax+xmin)/2,(ymax+ymin)/2,(zmax+zmin)/2];
ps.push(p);
}
});
if(!ps.length) return;
//获取最靠近视点的图形
const sel = ps.length == 1? ps[0]: ps.sort((a,b)=> a.coord[2] - b.coord[2])[0];
sel.select = true;
},false);

射线与三角形相交

但是包围盒算法判断地不是很精准,在物体形状不是很规则或物体间靠拢的比较紧时表现得尤其明显。

我们知道WebGL图形是由三角形构成的,那么进一步判断射线是否相交该物体某个三角形面就会非常精确了。

数学原理如下:

三角形内的任意一点都可以用它相对于三角形的顶点的位置来定义:

T(u,v) = (1 - u - v)V0 + uV1 + vV2

其中 u >= 0, v >= 0, u + v <= 1 ,称为重心坐标

射线可以用参数方程表示为:

T(t) = P + td

其中P为起始点,d为方向向量

因此计算直线与三角的交点的等式为:

P + td = (1-u-v)V0 + uV1 + vV2

整理后最终得到一个齐次线性方程组,其中[t u v] 为1 x 3 的矩阵,(t,u,v) 是它的解

[-d V1-V0 V2-V0] [t u v] = [P-V0]

根据克莱姆法则求解,其中T = P - V0, E1 = V1 - V0, E2 = V2 - V0,( [(T x E1) • E2] [(d x E2) • T] [(T x E1) • d] ) 为 3 x 3 矩阵,等式最终可以写成如下:

(t,u,v) = 1/((d x E2) • E1) ( [(T x E1) • E2] [(d x E2) • T] [(T x E1) • d] )

具体实现代码如下:

// 射线处于包围盒内
if(pos.x >= xmin && pos.x <= xmax && pos.y >= ymin && pos.y <= ymax){
p.coord = [(xmax+xmin)/2,(ymax+ymin)/2,(zmax+zmin)/2];
const P = [pos.x,pos.y,0.5];//射线起始点
const d = [0,0,1];//射线方向 for(let j = 0; j < p.position.length; j = j + 9){
//三角形顶点
const V0 = m4.transformPoint(matrix, p.position.slice(j,j+3));
const V1 = m4.transformPoint(matrix, p.position.slice(j+3,j+6));
const V2 = m4.transformPoint(matrix, p.position.slice(j+6,j+9)); const T = v3.subtract(P,V0);
const E1 = v3.subtract(V1,V0);
const E2 = v3.subtract(V2,V0);
const M = v3.cross(d,E2);
const det = v3.dot(M,E1); if(det == 0) continue;
const K = v3.cross(T,E1);
const t = v3.dot(K,E2)/det;
const u = v3.dot(M,T)/det;
const v = v3.dot(K,d)/det;
//射线与三角形相加
if(u >= 0 && v >= 0 && u+v<=1 ){
ps.push(p);
break;
}
}
}

WebGL之物体选择的更多相关文章

  1. 3DMax 物体选择方法

    全选: Ctrl + A, 取消选择:Ctrl +D 加选:ctrl+鼠标左键:减选:alt+鼠标 窗口与交叉:下面红框内的右边的按钮, 是切换两种模式: 选择模式一:只要选框碰到物体边缘, 就可选中 ...

  2. webgl自学笔记——深度监测与混合

    这一章中关于webgl中颜色的使用我们将深入研究.我们将从研究颜色在webgl和essl中如何被组装和获取开始.然后我们讨论在物体.光照和场景中颜色的使用.这之后我们将看到当一个物体在另一个物体前面是 ...

  3. 教你用webgl快速创建一个小世界

    收录待用,修改转载已取得腾讯云授权 作者:TAT.vorshen Webgl的魅力在于可以创造一个自己的3D世界,但相比较canvas2D来说,除了物体的移动旋转变换完全依赖矩阵增加了复杂度,就连生成 ...

  4. WebGL多模型光照综合实例

      原文地址:WebGL多模型光照综合实例   WebGL是一个非常的接近硬件底层的光栅化API, 从非常类似C/C++风格的API调用方式就可以看出来, 习惯了高级语言的我们会觉得很不友好,觉得特别 ...

  5. [学习笔记] Blender 常用工具栏,选择及游标

    Shift + A 创建物体 选择工具: 默认是框选 shift 鼠标左键 加选, 再次可减选 游标 默认情况下游标在世界中心.创建新物体时,会自动被创建在游标的位置.可以随意改变游标的位置,便于建模 ...

  6. Blender2.5快捷键

    General--通用 ESC Stops ongoing operation--停止当前操作 TAB Toggles Edit/Object mode--切换编辑/物体模式 ZKEY Toggles ...

  7. 3ds Max Shortcuts 快捷键大全

    主界面 [Q]选择循环改变方式 [W]移动 [E]旋转 [R]缩放循环改变方式 [7]物体面数 [8]Environment [9]Advanced lighting [0]Render to Tex ...

  8. Box2D淌坑日记: 关节(Joint)和旋转关节(b2RevoluteJoint)

    关节在Box2D的对象组织结构中,与b2Body(刚体)并列.因此两种对象都是由b2World创建并直接管理. 然而Joint有依赖于b2Body的地方,就是它的销毁:当关节所涉及到的刚体被销毁,关节 ...

  9. V-rep学习笔记:转动关节1

    V-REP(Virtual Robot Experimentation Platform),是全球领先的机器人及模拟自动化软件平台.V-REP让使用者可以模拟整个机器人系统或其子系统(如感测器或机械结 ...

随机推荐

  1. 【转】 Pro Android学习笔记(三二):Menu(3):Context菜单

    目录(?)[-] 什么是Context menu 注册View带有Context menu 填Context菜单内容 Context菜单点击触发 什么是Context menu 在桌面电脑,我们都很熟 ...

  2. LVS实战1

    (一).NAT模式:NAT模型:地址转换类型,主要是做地址转换,类似于iptables的DNAT类型,它通过多目标地址转换,来实现负载均衡:特点和要求: 1.LVS(Director)上面需要双网卡: ...

  3. netty支持的协议

    流经网络的数据总是具有相同的类型:字节.这些字节是如何流动的主要取决于我们所说的 网络传输--一个帮助我们抽象底层数据传输机制的概念.用户并不关心这些细节:他们只想确保他们的字节被可靠地发送和接收. ...

  4. IIS备份和还原

    当我们电脑系统有大量的站点和虚拟目录的时候,电脑因为种种原因需要重做系统,那么重装系统后这些站点我们是否只能一个一个的添加,如果有成百上千个站点呢,任务量可想而知,本文将介绍如何备份和还原window ...

  5. tomcat 自带jdk

    http://blog.csdn.net/b452608/article/details/70143466

  6. Activity--弹出底部窗口

    第一步 : 退出时候的布局文件exit_dialog_from_settings.xml <?xml version="1.0" encoding="UTF-8&q ...

  7. Prim算法:最小生成树---贪心算法的实现

    算法图解: http://baike.baidu.com/link?url=hGNkWIOLRJ_LDWMJRECxCPKUw7pI3s8AH5kj-944RwgeBSa9hGpTaIz5aWYsl_ ...

  8. java笔记--关于线程同步(5种同步方式)

    转自:http://www.2cto.com/kf/201408/324061.html 为何要使用同步?     java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改 ...

  9. iis应用程序池没有fromwork 4.0-----安装iis

    找到已经安装的目录  C:\Windows\Microsoft.NET\Framework\v4.0.30319  以管理员身份运行一下就ok 安装iis 控制面板-程序与功能-打开与关闭window ...

  10. PHP5 $this self parent static的区别

    PHP5 是一具备了大部分面向对象语言的特性的语言,比PHP4 有了很多的面向对象的特性,但是有部分概念也比较绕人,所以今天拿出来说说,说 的不好,请高手见谅. (阅读本文,需要了解PHP5 的面向对 ...