我们在很多场景需要使用类似HUD的效果,比如游戏的小地图啊,瞄准啊,标注啊,地图上的定点啊等等都是HUD层的效果。(神马,你还不知道啥叫HUD,好吧,哥来给你个链接,赶快去看看https://en.wikipedia.org/wiki/Head-up_display)这篇,我们来讲讲怎么制作一个包含3D场景的HUD显示效果。
废话不多说,先上一个显示效果图。
图上可以看出一个红色的警告标识会一直跟随着下面的方块一起运动。这个时候你肯定会说,神马,就这么简单一个效果?直接在3d场景中加入一个三位的object不就ok了么。其实仔细想想并不是这么简单,比如大家可以去看看百度上的标注图标文字,虽然会跟着地图的位置变动,可是大家发现了没有,它的大小并没有因为地图的缩放而产生改变。这就是hud显示的好处,无论你的场景缩放的多大多小,标注会一直不变。足够引起大家的重视。
解释完效果后,我们来讲讲如何实现。首先,对于这类显示效果在Threejs中有两种解决方案。第一种是在同一个场景中使用两个摄像机,一个是正交摄像机,只用来显示标识物,还有一个就是普通的透视摄像机,用来显示正常的3d场景设备。对于这种方式,稍微有点复杂,具体的步骤可以看这篇文章。
除此之外其实我们还有一种比较简单的办法,就是直接在html层中加入一个div,在里面操作dom,直接显示,这样div层的显示不会受到3d场景的缩放影响。但是这个解决方案需要解决一个核心的问题,如何将一个3d物体的三维坐标信息转换为屏幕显示的2d坐标。
function toScreenPosition(obj) { var vector = new THREE.Vector3(); //calculate screen half size var widthHalf = 0.5 * renderer.context.canvas.width; var heightHalf = 0.5 * renderer.context.canvas.height; //get 3d object position obj.updateMatrixWorld(); vector.setFromMatrixPosition(obj.matrixWorld); //translate to top (assume that position in the middle of the mesh) vector.y = vector.y * 2; vector.project(this.camera); //get 2d position on screen vector.x = (vector.x * widthHalf) + widthHalf; vector.y = -(vector.y * heightHalf) + heightHalf; return { x: vector.x, y: vector.y }; }
通过以上代码我们就可以将3d物体的坐标转换成2d的屏幕坐标,当然这里我们把3d的坐标高度增加了一些,从而使标识物显示在3d物体的头顶上。(同样的方法我们也可以显示在左侧,右侧等)
显示的代码非常简单直接指定left top就可以了
var position = toScreenPosition(obj); document.getElementById("alarm").style.left = (position.x - 16) + 'px'; document.getElementById("alarm").style.top = (position.y - 32) + 'px';
这里减掉的数值是图片的大小,为了将图片的中间显示在左边上。
还有一个性能优化的地方,就是我们最好不要在render的主循环中更新坐标,而是在场景发生变化后做坐标更新,这样计算量会小很多。这里我们只需监听OrbitControls的start和end方法就好,settimeout,cleartimeout一起使用,防止事件多次执行。
var controls = new THREE.OrbitControls(camera, renderer.domElement); controls.addEventListener('end', function () { refreshLedPositionTimer = setTimeout(doRefreshLedPosition, 800) }); controls.addEventListener('start', function () { clearTimeout(refreshLedPositionTimer); document.getElementById("alarm").style.display = "none"; });
这样我们在鼠标开始改变场景的时候隐藏标识,停止后重新计算坐标,显示标识。