WebGL可以用来做3D效果的全景图呈现,例如故宫的全景图。但有时候我们不仅仅只是呈现全景图,还需要增加互动。故宫里边可以又分了很多区域,例如外朝中路、外朝西路、外朝东路等等。我们需要在3D图上做一些标记表示某个小的区域。当点击这个标记时,界面切换到对应标记区域的全景图。下图是实现此功能的一个小DEMO:

如何实现这样的功能?通过本篇的介绍,我们可以了解到以上交互过程的代码实现方式。这里我先提出几个问题

1).如何获取3D全景图某个地址的3D坐标?

2).如何将获取的地址的3D坐标转换为屏幕上的2D坐标?

3).在旋转3D全景图时,如何让3D坐标对应的2D屏幕坐标跟着移动?

4).如何确认一个标记点是在相机的可视区域?

搞清楚以上问题,全景图的标记功能也就轻而易举了。接下来我们就围绕每个问题来实现功能。

如何获取3D全景图的某个地址的3D坐标?

一般获取景区上某个地址的标记,都是通过手动获取的。因为这些标记是无规律可寻的。所以我们就得考虑如何通过手动去获取3D图上的某个地址。人机交互时通过鼠标来操作,但鼠标是2D坐标,需要转换到对应的3D坐标上。Three.js为我们提供了Raycaster对象,我们可以很轻松的获取到一个2D点对应的3D坐标。先声明几个对象:

var raycasterCubeMesh;
var raycaster = new THREE.Raycaster();
var mouseVector = new THREE.Vector3();var tags = [];

这里需要在document上注册mousemove事件,实时获取鼠标对应的3D坐标。事件代码如下:

function onMouseMove(event){mouseVector.x = 2 * (event.clientX / window.innerHeight) - 1;mouseVector.y = - 2 * (event.clientY / window.innerHeight) + 1;raycaster.setFromCamera(mouseVector.clone(), camera);var intersects = raycaster.intersectObjects([cubeMesh]);if(raycasterCubeMesh){scene.remove(raycasterCubeMesh);}activePoint = null;if(intersects.length > 0){var points = [];points.push(new THREE.Vector3(0, 0, 0));points.push(intersects[0].point);var mat = new THREE.MeshBasicMaterial({color: 0xff0000, transparent: true, opacity: 0.5});var sphereGeometry = new THREE.SphereGeometry(100);raycasterCubeMesh = new THREE.Mesh(sphereGeometry, mat);raycasterCubeMesh.position.copy(intersects[0].point);scene.add(raycasterCubeMesh);activePoint = intersects[0].point;}}

代码中的大部分我已经在“如何实现对象交互”有介绍。这里只介绍和当前功能相关代码。intersects包含了鼠标当前位置下拾取到的3D对象集合。如果长度大于0,表示已经拾取到3D对象了。由于我们给intersectObjects函数只传递了cubeMesh对象(即全景图),所以intersects的长度肯定为1。intersects[0].point表示鼠标投射到cubeMesh对象表面上的坐标。这个坐标正是我们需要的3D标记点。所以我把这个点存储在activePoint。raycasterCubeMesh直接用交互点作为中心画的一个球体,鼠标移动这个球体也就跟着移动。

鼠标移动时,能够获取到3D坐标了。如何确认这个坐标就是我们需要的?这里还得 给docuent注册一个mousedown事件。通过右键点击确认。注册事件如下:

function onMouseDown(event){if(event.buttons === 2 &&  activePoint){var tagMesh = new THREE.Mesh(new THREE.SphereGeometry(1), new THREE.MeshBasicMaterial({color: 0xffff00}));tagMesh.position.copy(activePoint);tagObject.add(tagMesh);var tagElement = document.createElement("div");tagElement.innerHTML = "<span>标记" + (tags.length + 1) + "</span>";tagElement.style.background = "#00ff00";tagElement.style.position  = "absolute";tagElement.addEventListener("click", function(evt){alert(tagElement.innerText);});tagMesh.updateTag = function(){if(isOffScreen(tagMesh, camera)){tagElement.style.display = "none";}else{tagElement.style.display = "block";var position = toScreenPosition(tagMesh, camera);tagElement.style.left = position.x + "px";tagElement.style.top = position.y + "px";}}tagMesh.updateTag();document.getElementById("WebGL-output").appendChild(tagElement);tags.push(tagMesh);}}

代码第一行有if判断,只有鼠标右键触发,并且activePoint不为空,才执行下面的代码。首先创建一个球体tagMesh并且设置坐标为activePoint,然后把它添加到tagObject对象中。tagObject是一个Object3D对象,用来存放所有的tagMesh,便于统一管理。

接着代码创建了一个tagElement元素,设置样式和内容。并且附加到WebGL容器中。tagMesh自定义了updateTag函数,里边调用了两个特别重要的函数:toScreenPosition和isOffScreen。这里先不忙介绍updateTag函数。接下来通过介绍这两个函数来回答剩下的问题。

如何将获取的地址的3D坐标转换为屏幕上的2D坐标?

如果熟悉GIS的同学,应该知道什么叫做投影。我们将3D坐标映射到2D坐标的过程就叫做投影。toScreenPosition正是使用投影功能做的转换。函数代码如下:

function toScreenPosition(obj, camera){var vector = new THREE.Vector3();var widthHalf = 0.5 * renderer.context.canvas.width;var heightHalf = 0.5 * renderer.context.canvas.height;obj.updateMatrixWorld();vector.setFromMatrixPosition(obj.matrixWorld);vector.project(camera);vector.x = (vector.x * widthHalf) + widthHalf;vector.y = -(vector.y * heightHalf) + heightHalf;return {x: vector.x,y: vector.y};}

widthHalf和heightHalf分别表示canvas容器的宽度和高度的一半。接着更新obj对象的全局坐标。然后把vector的位置指向obj的全局坐标,之后调用viector.project(camera)将vector以相机为参考,转换为2D坐标。但此时的2D坐标是笛卡尔坐标。原点在中间位置,需要转换为屏幕坐标(原点在左上角)。最后返回的即是我们需要的2D坐标了。

在旋转3D全景图时,如何让3D坐标对应的2D屏幕坐标跟着移动?

之前没有介绍tagMesh的updateTag函数,这里我们再看下该函数:

tagMesh.updateTag = function(){if(isOffScreen(tagMesh, camera)){tagElement.style.display = "none";}else{tagElement.style.display = "block";var position = toScreenPosition(tagMesh, camera);tagElement.style.left = position.x + "px";tagElement.style.top = position.y + "px";}}

这里只看else中代码,设置元素的display为block,让其可见。然后调用toScreenPosition(tagMesh, camera)获取tagMesh 3D对象投影在屏幕上的坐标,所有我们直接设置给tagElement样式的left和top。这只是第一步。如果全景图旋转了,tagElement和tagMesh位置又对应不上了。所有在每次渲染时还得调用该函数去执行更新2D坐标。

function render(){controls.update();tags.forEach(function(tagMesh){tagMesh.updateTag();});renderer.render(scene, camera);requestAnimationFrame(render);}

上面代码遍历了所有的标记集合,每次渲染都更新一次。以上两个步骤就实现了3D坐标和2D屏幕坐标的联动。

如何只按照以上的介绍来实现功能,会发现一个问题。每添加一个标记,我们在旋转全景图时发现相机的前后都会显示这个标记。这因为2D坐标没有z方向,所以空间上会有两个对称点投影到相同的2D平面上。如何解决?看最后一个问题。

如何确认一个标记点是在相机的可视区域?

我们知道相机有可视区域,如果一个3D坐标在可视区域内,那么它投影到屏幕上的坐标需要显示。而如果该3D坐标不在相机的可视区域,那么我们就不应该把该点投影到屏幕上。Three.js提供了Frustum对象解决这类问题。我们通过调用isOffScreen函数,判断3D对象是否是离屏的。代码如下:

function isOffScreen(obj, camera){var frustum = new THREE.Frustum(); //Frustum用来确定相机的可视区域var cameraViewProjectionMatrix = new THREE.Matrix4();cameraViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); //获取相机的法线frustum.setFromMatrix(cameraViewProjectionMatrix); //设置frustum沿着相机法线方向return !frustum.intersectsObject(obj);}

首先创建Frustum对象,然后创建一个4 * 4矩阵对象。接下来的一行代码把cameraViewProjectMatrix转换为相机的法线矩阵。直接把它设置到frustum对象上。

接着调用frustum.intersectObject函数判断obj是否在frustum的可视区域内。至于内部的实现逻辑,大家可查看Three.js的源代码了解。

以上即是实现全景图标记的核心代码。至于全景图如何创建,可以从我的github上下载源代码查看。地址:

https://github.com/heavis/threejs-demo

转载于:https://www.cnblogs.com/w-wanglei/p/6853716.html

如何在WebGL全景图上做标记相关推荐

  1. php怎么实现在图片上做标记,如何给图片添加标记(notes)?

    最近几天没有更新文章,有点小忙!呵呵,不说废话,直接进入正题. 今天主要说的是,如何在图片上添加标记(一个或多个).从网上搜到很多的例子,总体来说,感觉这个很不错!! 我就简单说一下这个标记是怎么存储 ...

  2. opencv在图片上做标记使用putText函数

    opencv图片上标记做出信息显示 cv2.puttext(img, '添加信息', (50, 50), 字体, 大小, (255, 0, 0), 2) font = cv2.FONT_HERSHEY ...

  3. 如何进行服务器备份操作系统,如何在Linux服务器上做备份和恢复

    ZDNetChina服务器站 10月26日操作系统技巧进行一般的备份应该是负责任的系统管理者的最高优先考虑.虽然Linux 是一个高度可靠的作业系统,系统失效仍然可能发生.可能因为硬体故障,电源中断, ...

  4. apple id 强制激活_如何在Apple TV上强制退出应用

    apple id 强制激活 Just like apps can misbehave on your phone and tablet, apps can misbehave on the Apple ...

  5. canvas在图片上绘制标记,可拖拽、缩放,基于ZRender

    如下图所示,在图片上做标记,如圆形.矩形等. 该demo实现画布在页面布局中缩放后居中显示,可拖拽.缩放.做标记说明. 项目下载地址:https://gitlab.com/zhangcw66/draw ...

  6. android标记 pdf,PDF标记篇二:安卓手机怎样在pdf文件中做标记

    原标题:PDF标记篇二:安卓手机怎样在pdf文件中做标记 在日常办公中,常常需要给pdf文件添加一些标记,为的是给文本做些说明或者让重点更加明显.电脑上做标记非常简单,不用小编多说!这里小编要说的是在 ...

  7. 如何在Windows上做Python开发?微软出了官方教程(附链接)

    来源:机器之心 本文附教程,建议阅读5分钟. 本文为你分享微软最近发布的关于在Windows上做Python开发的一系列官方教程. 在Windows上做Python开发太痛苦?微软最近发布了一系列官方 ...

  8. asp.net web开发步骤_如何在Windows上做Python开发?微软出了官方教程

    机器之心报道 参与:路 在 Windows 上做 Python 开发太痛苦?微软最近发布了一系列官方教程,终于-- 教程地址:https://docs.microsoft.com/zh-cn/wind ...

  9. 2022年跨境电商卖家如何在Facebook上做广告【完整指南】

    关键词:跨境电商卖家.Facebook广告 了解如何在 Facebook 上做广告对于大多数跨境电商卖家来说是一项非常重要的技能,因为您在 Facebook 上做广告,您的广告可以覆盖21.7 亿人- ...

最新文章

  1. 一个与生命起源有关的悖论终于得到了解决
  2. OpenCASCADE:使用扩展数据交换 XDE之读写 STEP 或 IGES
  3. 数据库-检索部分-查找-更新表记录
  4. Linux中创建用户并且配置sudo权限,百分百简单有效
  5. kaili camera
  6. 冲锋衣和羽绒服哪个保暖?
  7. ubunut16.04 更新python3.6
  8. 电脑有网但是有的软件显示无网络连接服务器,有网络连接,但浏览器不能上网怎么办?电脑高手教你怎么解决...
  9. Freescale PowerPC系列架构 处理器 种类介绍 型号发展
  10. u盘 计算机管理 没有就绪,U盘无法识别先别着急扔!这五步操作还能挽救一下...
  11. 生僻字看不懂很尴尬?知道手机中的这个功能,瞬间发现:太简单
  12. python 3 爬取12306余票:升级版-----图形化界面
  13. R可视乎|创建乐高版马赛克图
  14. 被告知孩子学校偷钱后
  15. item_get - 获得搜好货商品详情
  16. error: cannot lock ref ‘refs/remotes/origin/master‘
  17. 关于-32768的补码!
  18. 技术人生的职场众生相
  19. UEFI 基础教程 (十七) - SMM的简单使用
  20. 优效文件助手-【深度】都2021年了,你还只会用文件夹管理电脑文件?

热门文章

  1. spring实战(第4版读书笔记)
  2. 理解主从设备模式(Master-Slave)
  3. 剑指offer面试题[21]包含min函数的栈
  4. 何时适合进行自动化测试?(上)
  5. 程序人生:我最真实的10年软件测试感悟...【建议收藏】
  6. python scikit learn 关闭开源_Python机器学习工具:Scikit-Learn介绍与实践
  7. jupyter中python3如何导入文件_无法读取Azure Jupyter笔记本(Python 2和3)中的“.parquet”文件...
  8. 【java】打印一个序列,第一项和第二项都是1,以后的每一项都是前面两项的和。
  9. ai的弹窗点了都不响应_怎么吃都不胖?这8点增重看了就能跟着做
  10. B站项目资源过于敏感,稍后删,抓紧保存!!!