基础概念

坐标系

我们的手机屏幕是二维的,但是我们展示物体的世界是三维的,当我们在构建一个物体的时候我们是以一个三维世界既是世界坐标来构建,而转化为屏幕坐标展示在我们眼前,则需要经历多道矩阵变化,中间webGL替我们操作了许多事情。

clipboard.png

  • 世界坐标系:在webGL中,世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的。你面对屏幕,你的右边是x正轴,上面是y正轴,屏幕指向你的为z正轴。长度单位这样来定:窗口范围按此单位恰好是(-1,-1)到(1,1),即屏幕左下角坐标为(-1,-1),右上角
    坐标为(1,1)。

  • 屏幕坐标系:
    webGL的重要功能之一就是将三维的世界坐标经过变换、投影等计算,最终算出它在显示设备上对应的位置,这个位置就称为设备坐标。在屏幕、打印机等设备上的坐标是二维坐标。

  • 视点坐标系:
    是以视点(照相机)为原点,以视线的方向为Z+轴正方向的坐标系中的方向。webGL会将世界坐标先变换到视点坐标,然后进行裁剪,只有在视线范围(视见体)之内的场景才会进入下一阶段的计算。

Raycaster

Raycaster threeJs官方文档

这个类设计用于鼠标去获取在3D世界被鼠标选中的一些物体


Raycaster( origin, direction, near, far ) 

origin — 射线的起点向量。
direction — 射线的方向向量,应该归一标准化。
near — 所有返回的结果应该比 near 远。Near不能为负,默认值为0。
far — 所有返回的结果应该比 far 近。Far 不能小于 near,默认值为无穷大。

找到点击物体的大致思路

clipboard.png

鼠标在屏幕上点击的时候,得到二维坐标p(x, y),再加上深度坐标的范围(0, 1), 就可以形成两个三位坐标A(x1, y1, 0), B(x2, y, 1), 由于它们的Z轴坐标是0和1,则转变到投影坐标系的话,一定分别是前剪切平面上的点和后剪切平面上的点,也就是说,在投影坐标系中,A点一定在能看见的所有模型的最前面,B点一定在能看见的所有的模型的最后边,将AB点连成线,AB线穿过的物体就是被点击的物体。而 Three.js提供一个射线类Raycasting来拾取场景里面的物体。更方便的使用鼠标来操作3D场景。(不过在实际代码中我们组成射线的两个点是摄像机所在视点与屏幕上点击的点连接而成的射线)

来一个Raycasting的官方实例

代码实现

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)
    }


}

代码释义

clipboard.png

  //得到
 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

参考资料

Three.js中的拾取
OpenGL中各种坐标系的理解
threejs对象拾取
《计算机图形学》
前端填坑指南

你可能感兴趣的文章

ray7551 · 2017年11月29日

"世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的。" 这句话有问题,不知道出处是哪里,上下文是什么样的?
顶点的 world coordinate 还要经过 view matrix 转换成以摄像机为中心的 eye space coordinate,再乘以投影矩阵变成 gl_Position,再自动转换成屏幕坐标,中间任何一个矩阵发生变化都会影响最后的屏幕坐标。你说的“世界坐标系是以屏幕中心为原点(0, 0, 0)”,至少有以下几个前提:

  1. 摄影机一直处在固定位置,没有旋转等变化
  2. 摄影机的朝向是固定的(look at world coordinate [0, 0, 0])
  3. 世界坐标 [0, 0, 0] 在摄影机的可视范围之内
  4. 投影矩阵不变

请参考下面这篇文章中的图示:
http://antongerdelan.net/open...

回复

只会说的环保人士 · 2017年12月09日

这里的世界坐标系是NDC吧,一般世界坐标指的是最初的三维世界的坐标

回复

奥特曼居士 · 2月23日

获取屏幕坐标很麻烦

回复

载入中...
Planets
mooonx mooonx

269 声望

发布于专栏

moonx

可以有代码,有设计,还有生活吗哈哈哈哈

12 人关注