前言

在浏览器中浏览三维图形的时候,有时想要与三维图形之间做一些点击事件和交互操作,其中比较常用的一个解决方案就是使用Raycaster对象来实现(射线拾取)。

基础知识

  1. 世界坐标系:webGL中,世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的。面对屏幕时,右边是x正轴,上面是y正轴,由屏幕内指向屏幕外的是z正轴。
  2. 屏幕坐标系:webGL的重要功能之一就是将三维的世界坐标经过变换、投影等计算,最终计算出它在显示设备上对应的位置,这个位置就称为设备坐标,也就是二维坐标。
  3. 视点坐标系: 是以视点(照相机)为原点,以视线的方向为Z轴正方向的坐标系。

原理

利用webGL,既可以将三维坐标换算为二维坐标,也可以将二维坐标换算成三维坐标。webGL会将世界坐标先换算到视点坐标,然后进行裁剪,只有在视线范围之内的场景才会进入下一阶段的计算。

效果图1

  1. 图中圆柱体是演示拾取的三维图形;
  2. 图中绿、红、蓝三条轴线分别代表三维坐标中的Y正轴、X正轴和Z正轴;
  3. 图中蓝色五角形,其实是由视点(照相机)坐标出发,以鼠标点击位置为正方向,绘制的一条箭头图形,代表绘制的射线,五角形中心即鼠标点击位

当鼠标点击圆柱体边缘以外时,圆柱体材质颜色没有发生变化,说明此时鼠标点击位置并没有拾取到圆柱体。

效果图2

当鼠标点击圆柱体边缘以外时,圆柱体材质颜色发生了变化,说明此时鼠标点击位置已经拾取到了圆柱体,并在控制台打印出了圆柱体的mesh对象,并可以对它进行操作。

演示代码

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>钉钉分享</title> <!--threejs版本是71版本-->
<script src="three.js"></script>
</head> <body style="margin: 0;overflow: hidden;"> <!-- 输出容器 -->
<div id="webgl-output"></div> <!-- 操作代码 -->
<script> // 声名Threejs对象(场景、相机、渲染器)
var scene, camera, renderer; // 初始化
function init() { // 添加浏览器窗口大小监听事件(画布自适应方法onResize)
window.addEventListener('resize', onResize, false); // 鼠标点击拾取
document.addEventListener('click', initRay, false); // 创建场景
scene = new THREE.Scene(); // 创建相机(设置相机位置,设置相机朝向)
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(10, 50, 50);
camera.lookAt(scene.position); // 创建渲染器
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xEEEEEE);
renderer.setSize(window.innerWidth, window.innerHeight); // 创建辅助轴线
var axis = new THREE.AxisHelper(100);
scene.add(axis); // 添加图形---圆柱体
let _radius = 5;
var cylinderGeometry = new THREE.CylinderGeometry(_radius, _radius, 20, 40, 40);
var cylinderMaterial = new THREE.MeshBasicMaterial({ color: 0x765432 });
var cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
scene.add(cylinder); // 将图形输出到页面
document.getElementById('webgl-output').appendChild(renderer.domElement); // 逐帧绘制方法
function renderScene() {
requestAnimationFrame(renderScene);
renderer.render(scene, camera);
} // 开启渲染
renderScene();
} // 画布自适应
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
} // 射线拾取
function initRay(event) { // 获取画布
let mainCanvas = document.querySelector("#webgl-output canvas"); // 将屏幕坐标转为标准设备坐标(支持画布非全屏的情况)
let x = ((event.clientX - mainCanvas.getBoundingClientRect().left) / mainCanvas.offsetWidth) * 2 - 1; // 设备横坐标
let y = -((event.clientY - mainCanvas.getBoundingClientRect().top) / mainCanvas.offsetHeight) * 2 + 1; // 设备纵坐标
let standardVector = new THREE.Vector3(x, y, 1); // 设备坐标 // 标准设备坐标转为世界坐标
let worldVector = standardVector.unproject(camera); // 射线投射方向单位向量(worldVector坐标减相机位置坐标)
let ray = worldVector.sub(camera.position).normalize(); // 绘制射线
drawRay(scene, camera.position, ray); // 创建射线投射器对象
let rayCaster = new THREE.Raycaster(camera.position, ray); // 返回射线选中的对象数组(第二个参数默认值是false,意为是否遍历图形内部的所有子图形)
let intersects = rayCaster.intersectObjects(scene.children, true);
if (intersects.length > 0) { // 射线拾取的首个对象
let currObj = intersects[0]; console.log(currObj); // 改变被拾取对象的材质颜色(随机)
let currcolor = `rgb(${Math.floor(Math.random() * 256).toString()},${Math.floor(Math.random() * 256).toString()},${Math.floor(Math.random() * 256).toString()})`
currObj.object.material.color.set(currcolor);
}
} // 绘制射线(箭头射线)
function drawRay(scene, start, dir) {
let prevRay = scene.getObjectByName("customRay");
if (prevRay) {
scene.remove(prevRay);
} let arrow = new THREE.ArrowHelper(dir, start, 1000, 0x0000ff);
arrow.name = "customRay";
scene.add(arrow);
} // 初始化
window.onload = init();
</script>
</body> </html>

参考原文地址: https://segmentfault.com/a/1190000010490845

threeJS射线拾取机制及案例的更多相关文章

  1. UE4 射线拾取&三维画线

    虽然有人建议UE4使用C++创建VR项目,能避免一些坑爹的错误,但是我用C++创建,竟然问题更多,还存在创建不了的情况,也不知道是不是我的操作问题,快疯了. 于是我还是选择了蓝图创建VR项目,但是.. ...

  2. WebGL射线拾取模型——八叉树优化

    经过前面2篇WebGL射线拾取模型的文章,相信大家对射线和模型面片相交的原理已经有所了解,那么今天我们再深入探究关于射线拾取的一个问题,那就是遍历场景中的所有与射线相交的模型的优化问题.首先我们来复习 ...

  3. 【转载】Java虚拟机类加载机制与案例分析

    出处:https://blog.csdn.net/u013256816/article/details/50829596 https://blog.csdn.net/u013256816/articl ...

  4. three.js raycaster射线碰撞的坑 (当canvas大小 不是屏幕大小是解决拾取物体的办法)

    这里只是记录一下坑,方便查阅,内容主要援引自:three.js Raycaster 射线拾取 canvas不占满整屏时射线拾取存在偏差 1. 世界坐标系: 世界坐标系位于屏幕的中心(0,0,0),往右 ...

  5. WebGL模型拾取——射线法二

    这篇文章是对射线法raycaster的补充,上一篇文章主要讲的是raycaster射线法拾取模型的原理,而这篇文章着重讲使用射线法要注意的地方.首先我们来看下图. 我来解释一下上图中的originTr ...

  6. Modern OpenGL用Shader拾取VBO内单一图元的思路和实现

    Modern OpenGL用Shader拾取VBO内单一图元的思路和实现 什么意思? 拾取 最简单的理解拾取的方式大概是到(http://www.yakergong.net/nehe/course/t ...

  7. ThreeJS中的点击与交互——Raycaster的用法

    基础概念 坐标系 我们的手机屏幕是二维的,但是我们展示物体的世界是三维的,当我们在构建一个物体的时候我们是以一个三维世界既是世界坐标来构建,而转化为屏幕坐标展示在我们眼前,则需要经历多道矩阵变化,中间 ...

  8. ThreeJs 基础入门

    本文来自网易云社区 作者:唐钊 Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它在 web 中创建各种三维场景,包括了摄影机.光影.材质等各种对象.使用它可以让我们更加直观的了解 we ...

  9. OpenGL 3D拾取文章(转)

    参考文章 深入探索3D拾取技术 OpenGL 3D拾取 射线和三角形的相交检测(ray triangle intersection test) 3D拾取的方法有两种 1.基于几何计算的射线-三角形相交 ...

随机推荐

  1. [CSP-S模拟测试110]题解

    也许是最后一篇了. A.最大或 不错的签到题. 对于二进制位来说,高位的一个1比低位的所有1的贡献总和还要大. 显然,$r$必选,因为$r$中所有1的相对考前.那么考虑如何构造另一个数. 首先$l$和 ...

  2. 2017工业软件top100

  3. 九个console命令调试JS

    下面九个console命令,可以帮助我们更方便地调试 常用的console命令,最常用的事console.log() 1 //常用的console命令,其中最常用的console.log() 2 co ...

  4. day40—JavaScript多物体运动框架

    转行学开发,代码100天——2018-04-25 今天继续学习JavaScript的运动实现——多物体运动框架的介绍及其应用. 首先来看一个简单的例子.如下图,要使图中3个红色盒子实现鼠标移入变宽,移 ...

  5. RSA - 原理、特点(加解密及签名验签)及公钥和私钥的生成

    Wiki - RSA加密演算法 Wiki - 欧拉函数 Wiki - 模反元素 ASN.1 格式标准 RSA算法原理(二) 注意: RSA 加密或签名后的结果是不可读的二进制,使用时经常会转为 BAS ...

  6. Oracle11g安装步骤

    plsql安装等:https://blog.csdn.net/li66934791/article/details/83856225      https://www.cnblogs.com/gaoz ...

  7. Using Tensorflow SavedModel Format to Save and Do Predictions

    We are now trying to deploy our Deep Learning model onto Google Cloud. It is required to use Google ...

  8. 使EasyUI的window、panel、dialog 不被拖出页面

    function easyuiPanelOnMove(left, top) {  var l = left;  var t = top;  if (l < 1) {    l = 1;  }  ...

  9. 如何在Python中使用Linux epoll

    如何在Python中使用Linux epoll 内容 介绍 阻塞套接字编程示例 异步套接字和Linux epoll的好处 epoll的异步套接字编程示例 性能考量 源代码 介绍 从2.6版开始,Pyt ...

  10. 禁止html复制文本

    <body class="content" oncontextmenu="return false" onselectstart="return ...