原文地址: 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;}}
}
复制代码

转载于:https://juejin.im/post/5cecfaa3e51d4510727c8010

WebGL之物体选择相关推荐

  1. Max Script|物体选择和拷贝

    本章记录Max脚本的中的简单操作-物体选择和拷贝 •对物体的简单操作 sphere() box() torus() plane() *******************创建物体,更多类型请根据max ...

  2. unity编辑器扩展——替换选择物体名字中的内容

    名字的特点: 1,有规律 2,有相同的内容 可以用这个工具,修改物体的名字,在菜单中生成的自定义目录"DCCTools/Rename" using System.Collectio ...

  3. 第二章 3ds Max物体的操作及选择

    系列文章 3DSMAX学习 第一章 3ds Max基础界面和操作​​​​​​​ 第二章 3ds Max物体的操作及选择 文章目录 目录 系列文章 3DSMAX学习 文章目录 一.3ds Max物体操作 ...

  4. webgl坐标转换_WebGL教程

    前言 通过WebGL做了很多项目,感觉有必要录制一套视频教程,所以在这里写一个录制大纲,大家也可以通过章节目录了解下WebGL的基本内容. 视频教程发布地址 Threejs引擎 Threejs是web ...

  5. 培育强人工智能的「ImageNet」:上海交大卢策吾组提出铰接物体知识库 AKB-48

    来源:前沿科技 编译:OGAI 编辑:陈彩娴 ImageNet 的出现极大推动了计算机视觉领域的发展.在通往强人工智能的路上,我们还需要考虑物体的外观.结构.物理性质.语义等因素.为此,上海交大卢策吾 ...

  6. 从maya中 导入物体 到Uniyt3D 规范 整理

    从maya中导入物体到Uniyt3D 我想,很多小白跟我一样,从maya或者3dmax中导入到u3d的物体不知道怎么用.我也是新手,共同分享一下今上午的经验. 我想大部分人看到我这个东西之前,已经看多 ...

  7. cad2016中选择全图字体怎么操作_cad教程分享CAD如何替换找不到的原文字体?

    Autocad教程公众号,专注于cad教程.cad教程视频的分享,欢迎关注,下载你所需的教程资源! 如你还未关注,请点击文章标题下方蓝色字体的"Autocad教程"进行关注. ca ...

  8. cad2016中选择全图字体怎么操作_cad教程分享CAD中如何删除顽固图层?

    Autocad教程公众号,专注于cad教程.cad教程视频的分享,欢迎关注,下载你所需的教程资源! 如你还未关注,请点击文章标题下方蓝色字体的"Autocad教程"进行关注. ca ...

  9. 【Unity3D】Unity 编辑器窗口布局 ( 创建 Unity3D 项目 | 添加物体 | 层级窗口 | 场景窗口 | 游戏窗口 | 属性窗口 | 项目窗口 | 控制台窗口 | 窗口位置修改 )

    文章目录 一.在 Unity Hub 中创建 Unity 2020 编辑器版本的 Unity3D 项目 二.Unity 编辑器窗口布局 1.添加物体 2.菜单栏和工具栏 3.Hierarchy 层级窗 ...

最新文章

  1. 迄今为止程序员写过的最大Bug:亏损30亿、致6人死亡,甚至差点毁灭世界
  2. python3 requests 动态网页post提交数据_Python爬虫三:动态网页爬虫
  3. 计算机硬盘满了怎么解决,使用SSD硬盘空间清理 C盘爆满怎么办
  4. Java | Assignment Statements Expressions
  5. java中start与loop_java for-loop问题
  6. mysql %3e -u -r_mysql基础
  7. Windows操作系统的各进程的作用
  8. ASP.NET Razor 视图引擎编程参考
  9. 区块链 智能合约中获取不了时间戳 随机数怎么办
  10. java 加载java文件_如何用JAVA实现加载一个文件?
  11. 市场对php的需求分析,PHP 留言板功能需求分析
  12. CAN总线协议是什么?CAN总线协议有哪些?
  13. IP-Guard清除记录
  14. 模拟网络延迟抖动测试
  15. 微信公众平台开发实例 php,php微信公众平台开发类实例,php公众平台实例_PHP教程...
  16. JavaWeb整合萤石云(一),VUE和小程序也适用
  17. 阿里云ECS学习资源
  18. 第四章:前缀和、差分(数列)
  19. 现代计算机发展经历的阶段,计算机的发展经历了那几个阶段
  20. 网吧机子dns服务器没有响应,网吧找不到的服务器dns地址

热门文章

  1. matlab考试试题,matlab-考试试题-
  2. ip地址合不合法怎么看_到底醇基燃料合不合法呢?
  3. php 异常错误信息用处,关于PHP中异常错误的处理详细介绍
  4. mysql简单的sql语句单表分组查询
  5. cc穿盾并发脚本_敢不敢模拟超过 5 万的并发用户?
  6. Pandas对象的层次化索引——【from_tuples()、from_arrays()、from_product()、swaplevel()、sort_index()、sort_values()】
  7. 简单暴力到dp的优化(入门篇)
  8. C++(23)--多态性与虚函数
  9. java mvc 菜鸟_【java框架】SpringMVC(1)--SpringMVC入门
  10. 遍历文件夹下所有文件和文件夹