拾取模型的原理及其在THREE.JS中的代码实现

 

1. Three.js中的拾取 

 

 

 

1.1. 从模型转到屏幕上的过程说开

 

  因为图形显示的基本单位是三角形,那就先从一个三角形从世界坐标转到屏幕坐标提及,例如三角形abchtml

 

 

 

 

 

 

 

 

 

乘以模型视图矩阵就进入了视点坐标系,其实就是相机所在的坐标系,以下图:web

 

 

 

进入视点坐标系后,再乘以投影矩阵,就会变换到一个立方体内,以下图:微信

 

 

 

这个时候整个三角形就位于中心位于坐标系原点,边长为2的立方体内,在这个立方体内,三角形要计算光照,要裁剪,而后乘以视口矩阵,最后转到屏幕上。优化

 

 

 

 

 

转到屏幕上后,三角形的全部点的Z坐标就是深度坐标,必定在(0, 1)这个区间内,那么哪些点的Z坐标是0呢,在投影坐标系中,必定是投影视景体的前剪切平面上的点,而投影视景体的后剪切平面上的点的Z坐标就是1webgl

 

1.2. 思路来了

 

   根据以上三角形转换到屏幕坐标上的过程能够分析出,鼠标在屏幕上点击的时候,能够获得二维坐标p(x, y),再加上深度坐标的范围(0, 1), 就能够造成两个三位坐标A(x, y, 0), B(x, y, 1),  因为它们的Z轴坐标是01,则转变到投影坐标系的话,必定分别是前剪切平面上的点和后剪切平面上的点,也就是说,在投影坐标系中,A点必定在能看见的全部模型的最前面,B点必定在能看见的全部的模型的最后边,假设视口矩阵的逆矩帧,投影矩阵的逆矩阵,模型视图矩阵的逆矩阵为M, N, P,则 P * N * M * A = A1,  P * N * M * B = B1, 在世界坐标系中,点A1B1就能够造成一个射线,此射线和模型再求交,就能选中模型。以下图是在视点坐标系中的情形。注意,求交能够在视点坐标系或者世界坐标系计算均可以,但通常会在世界坐标坐标系中计算。spa

 

 

 

1.3. 拾取的优化,射线和AABB包围盒求交

 

    若是射线和全部的模型求交,显然不是一个好办法,通常状况下会进行一些优化,好比先和模型的包围盒求交,若是和模型的包围盒不相交的话,就放过去,不然就接着往下进行,和模型的全部三角面片求交。3d

 

      那么什么是包围盒呢?在计算机图形学与计算几何领域,一组物体的包围体就是将物体组合彻底包容起来的一个封闭空间。将复杂物体封装在简单的包围体中,就能够提升几何运算的效率。一般简单的物体比较容易检查相互之间的重叠。其中有一种包围盒叫作AABB, AABB的全称是axis aligned bounding box,就是咱们经常提到轴向包围盒,这个盒子的边是平行于x/y/z轴的。 全部的2d3d物体都是由点组成的,因此只要找出这些物体的最大值点和最小值点,那么就可使用这两个点表示该物体的AABB包围盒了。
       检测碰撞的时候咱们只须要检测这些物体的AABB(即他们的最大值点和最小值点)是否相交,就能够判断是否碰撞了。orm

 

 

 

 

 

 

 

1.4. 射线和三角形相交

 

     判断射线和包围盒是否求交后,就轮到判断是否和三角形求交了,最早想到的是 首先判断射线是否与三角形所在的平面相交,若是相交,再判断交点是否在三角形内。判断射线是否与平面相交, 判断点是否在三角形内.htm

 

1.5. THREE.JS中求交的代码实现

 

  three.js中的一个案例,名字叫webgl_interactive_lines.html,能够选中一根线,并显示一个小球。根据以上的思路,代码注释以下:blog

 

//鼠标点击的屏幕坐标转换到视点坐标系

 

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, 交流更多的技术问题

相关文章
相关标签/搜索