转载来源:https://segmentfault.com/a/1190000010490845
坐标系
我们的手机屏幕是二维的,但是我们展示物体的世界是三维的,当我们在构建一个物体的时候我们是以一个三维世界既是世界坐标来构建,而转化为屏幕坐标展示在我们眼前,则需要经历多道矩阵变化,中间webGL替我们操作了许多事情。
世界坐标系:
在webGL中,世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的。你面对屏幕,你的右边是x正轴,上面是y正轴,屏幕指向你的为z正轴。长度单位这样来定:窗口范围按此单位恰好是(-1,-1,-1)到(1,1,1)。
记清楚了,横向:从原点往右是 0 到 1,从原点往左是0 到 -1;纵向同理,向上到最顶端是1,向下最底端是 -1,待会儿需要 通过屏幕坐标,计算出世界虚拟坐标,然后根据虚拟坐标计算出Raycaster函数第一个参数 origin — 射线的起点向量(就是虚拟坐标值的比例)。
屏幕坐标系:
webGL的重要功能之一就是将三维的世界坐标经过变换、投影等计算,最终算出它在显示设备上对应的位置,这个位置就称为设备坐标。在屏幕、打印机等设备上的坐标是二维坐标。
视点坐标系:
是以视点(照相机)为原点,以视线的方向为Z+轴正方向的坐标系中的方向。webGL会将世界坐标先变换到视点坐标,然后进行裁剪,只有在视线范围(视见体)之内的场景才会进入下一阶段的计算。
Raycaster
Raycaster( origin, direction, near, far )
origin — 射线的起点向量。
direction — 射线的方向向量,应该归一标准化。
near — 所有返回的结果应该比 near 远。Near不能为负,默认值为0。
far — 所有返回的结果应该比 far 近。Far 不能小于 near,默认值为无穷大。
找到点击物体的大致思路
鼠标在屏幕上点击的时候,得到二维坐标p(x, y),再加上深度坐标的范围(0, 1), 就可以形成两个三位坐标A(x1, y1, 0), B(x2, y, 1), 由于它们的Z轴坐标是0和1,则转变到投影坐标系的话,一定分别是前剪切平面上的点和后剪切平面上的点,也就是说,在投影坐标系中,A点一定在能看见的所有模型的最前面,B点一定在能看见的所有的模型的最后边,将AB点连成线,AB线穿过的物体就是被点击的物体。而 Three.js提供一个射线类Raycasting来拾取场景里面的物体。更方便的使用鼠标来操作3D场景。(不过在实际代码中我们组成射线的两个点是摄像机所在视点与屏幕上点击的点连接而成的射线)
代码实现
function onDocumentMouseDown(e) {
e.preventDefault();
//将鼠标点击位置的屏幕坐标转成threejs中的标准坐标,具体解释见代码释义
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
//新建一个三维单位向量 假设z方向就是0.5
//根据照相机,把这个向量转换到视点坐标系
var vector = new THREE.Vector3(mouse.x, mouse.y,0.5).unproject(camera);
//在视点坐标系中形成射线,射线的起点向量是照相机, 射线的方向向量是照相机到点击的点,这个向量应该归一标准化。
var raycaster = new THREE.Raycaster(camera.position,
vector.sub(camera.position).normalize());
//射线和模型求交,选中一系列直线
var intersects = raycaster.intersectObjects(objects);
console.log('imtersrcts=' + intersects)
if (intersects.length > 0) {
//选中第一个射线相交的物体
SELECTED = intersects[0].object;
var intersected = intersects[0].object;
console.log(intersects[0].object)
}
}
代码释义
//得到
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
推导过程:
设A点为点击点(x1,y1),x1=e.clintX, y1=e.clientY
设A点在世界坐标中的坐标值为B(x2,y2);
由于A点的坐标值的原点是以屏幕左上角为(0,0);
我们可以计算可得以屏幕中心为原点的B'值
x2' = x1 - innerWidth/2
y2' = innerHeight/2 - y1
又由于在世界坐标的范围是[-1,1],要得到正确的B值我们必须要将坐标标准化
x2 = (x1 -innerWidth/2)/(innerwidth/2) = (x1/innerWidth)*2-1
同理得 y2 = -(y1/innerHeight)*2 +1
遇到的问题
最后加一点遇到的问题,canvas上面有上下两个box,点击上面的box,但是射线拾取的确是下面的box对象,找了很久,终于发现了问题,原来我的canvas不是全屏的,因为body下面有个div,所以它占了一定的高度,导致canvas的top是有偏移的。
解决办法:
mouse.set( ( (event.clientX - document.querySelector('canvas').offsetLeft)/ window.innerWidth ) * 2 - 1, - ( (event.clientY - document.querySelector('canvas').offsetTop) / window.innerHeight ) * 2 + 1 );