农家乐网站设计,免费视频素材下载的网站,老河口网站定制,做seo网站 公司three.js内置了Raycaster类实现鼠标的碰撞检测#xff0c;用它可以实现3D物体的鼠标点击#xff0c;移入移出#xff0c;触屏检测一类的业务功能。
该功能虽然强大#xff0c;但同事们普遍反映不是那么好用#xff0c;因为它不像其它配套了可视编辑的3D引擎一样#xff…three.js内置了Raycaster类实现鼠标的碰撞检测用它可以实现3D物体的鼠标点击移入移出触屏检测一类的业务功能。
该功能虽然强大但同事们普遍反映不是那么好用因为它不像其它配套了可视编辑的3D引擎一样直接把这些交互事件挂载到3D物体上。
不好用没关系只要研发团队有懂three.js的人或者有人把写好的实现代码封装一下给到业务开发去用就可以了。
蛋疼的是这个东西还有一些小bug比如它不认相机裁剪。以下为测试用例代码
!DOCTYPE html
html
headmeta charsetUTF-8titlethree_cameraNear/titlestylebody {margin: 0;overflow: hidden;}/stylescript srcthree/build/three.js/scriptscript srcthree/examples/js/controls/OrbitControls.js/scriptscript srcthree/examples/js/libs/dat.gui.min.js/script
/headbodyscriptvar scene new THREE.Scene();var geometry new THREE.SphereGeometry(50, 100, 100);var srcColor 0xFF6600;var material new THREE.MeshLambertMaterial({color: srcColor});var mesh new THREE.Mesh(geometry, material);scene.add(mesh);var light new THREE.DirectionalLight({color: 0xFFFFFF, intensity: 0.5});light.position.set(-500, 500, 500);scene.add(light);var ambLight new THREE.AmbientLight({color: 0xFFFFFF, intensity: 0.3});scene.add(ambLight);var width window.innerWidth; var height window.innerHeight; var camera new THREE.PerspectiveCamera(60, width / height, 100, 20000);camera.position.set(0, 0, 150); var renderer new THREE.WebGLRenderer();renderer.setSize(width, height);renderer.setClearColor(0x000000, 1); document.body.appendChild(renderer.domElement); var gui new dat.GUI(),folderCamera gui.addFolder(相机),propsCamera {get 裁剪() {return camera.near;},set 裁剪( v ) {camera.near v;camera.updateProjectionMatrix();},};folderCamera.add( propsCamera, 裁剪, 100, 150 ); folderCamera.open();function render() {renderer.render(scene, camera);requestAnimationFrame(render);}render();var controls new THREE.OrbitControls(camera,renderer.domElement);controls.addEventListener(change, render);var raycaster new THREE.Raycaster(); function onMouseMove(e){ //这里是屏幕坐标到ndc的转换不懂的可以自行上webgl中文网学习var x ((e.clientX - width * 0.5) / width * 2);var y (-(e.clientY - height * 0.5) / height * 2);raycaster.setFromCamera(new THREE.Vector2(x, y), camera);var intersects raycaster.intersectObject(scene, true);material.color new THREE.Color().set(srcColor);for(let intersect of intersects){intersect.object.material.color new THREE.Color().set(0x999900);}}window.addEventListener(mousemove, onMouseMove);/script
/body
/html
场景上的球体在场景不被裁剪的时候工作得非常好但是一旦用上了裁剪设置camera的near/far就会发现Raycaster完全无视这一属性。 解决问题的时候笔者还是先尝试去找现成的api看是不是有设置项。果不其然Raycaster就自带了near和far属性。
//在onMouseMove方法的intersects调用前加上这两行同步相机的裁剪属性
raycaster.near camera.near;
raycaster.far camera.far;这不是脱裤子放屁嘛为什么就不直接在Raycaster内部去读取camera的这两条属性呢非要业务层多此一举
先不纠结这问题我们看看这样写是否就能把问题解决掉。 有了显著改善但是边缘处并不是那么准确并且鼠标位置离画布中心越远误差就越大。
笔者在解决这个问题之前有大致了解过射线检测的原理所以结合源码里的实现逻辑盲猜到把相机改成正交就会立马变得非常准确。
var k width / height;
var s 100;
var camera new THREE.OrthographicCamera(-s * k, s * k, s, -s, 100, 20000); 也就是说near和far的同步适用于正交相机透视相机用它不靠谱这大概也就可以解释为什么three.js不直接在底层同步这两项属性了。
现在笔者就来跟大家解释下为什么两种相机得到的结果会有所差异。
透视相机的特征是近大远小实际上玩得花的可以搞成近小远大其可视区域是下图中近裁剪面和远裁剪面及其顶点连线所围成的一个棱台称作视锥体。 做基于射线检测的鼠标碰撞时人眼的直观感受是从鼠标位置发射一条垂直于屏幕向里的射线最先跟啥相交就算碰到谁。近裁剪面和远裁剪面在屏幕上将会被缩放到相同的大小从而形成近大远小的效果那么垂直于屏幕向里的射线也将据此进行的倾斜变换如上图的红绿蓝3条射线图中PA和A1在画面上是重叠的P蓝点和蓝箭头的头部也一样可以看到上图的3条射线跟近裁剪面交点和相机的距离不一样绿点屏幕中心最近红点次之蓝点最远。
但是射线检测源码用的却是相机位置到交点的距离因此对于透视相机而言离屏幕中心越远误差就越大它用球面代替了平面来判断。 而正交相机则不存在正大远小的说法它的视锥体是一个长方体所以不管从哪里发出来的射线都是两两平行的。 下面给出解决方案代码很简单只有两行。
const proj _intersectionPointWorld.clone().project(raycaster.camera);
if(proj.z 1 || proj.z -1) return null;
这段代码加到Mesh.js上 每个显示对象都有自己的raycast属性其它对象的修改方法类似不再赘述。
虽然只有两行但这里的学问大着呢。
无论是OpenGLWebGL是其子集还是DirectX这些GPU渲染底层使用的都是标准设备坐标系Normal Device CoordinateNDC其xyz3轴的范围均为-1到1不懂的小伙伴可以搜索NDC进一步学习。上述代码中的方法就是实现从屏幕像素坐标到NDC坐标的转换它在three.js内已经封装好了。
NDC超出-1到1范围的物体将不予显示因此该做法在视觉上是最准确的。但有同事不建议笔者这样改他们认为能不动源码就不动后续想要更新引擎也方便。但笔者当时坚持修改源码因为这就是引擎的一个bug。
写本文的时候笔者重新审视了一下改源码也有它的不合理之处。因为它并不适用于所有的业务场景。比如线框材质它的背面也是可见的这时候笔者的这一修改就反倒不正确。 由此可见three.js不直接在raycaster里读取camera的near和far也是有他的道理。再者如果哪天有人突发奇想搞个哇哈哈效果那样的曲面相机那机制又可能不是这么一回事了。
这也就使得three.js比其它引擎更加灵活同时也变得没那么好用了。如果读者们有注意到笔者上面的滑块源码就会发现在camera的near修改了之后笔者还加了句updateProjectionMatrix触发刷新这些都是灵活性高封装性不强的表现对于萌新们来说上手和查问题都会变得困难。这时候笔者这一专栏的价值就体现出来了。
废话说完来小结一下
1 默认情况下射线检测无视相机裁剪
2 正交相机非线框材质的情况下把camera的near和far属性同步到raycaster能完美解决问题
3 透视相机非线框材质的情况下用THREE.Vector3的project方法算出来的结果比用near和far要准确得多同时此法也适用于正交相机
4 如果要纠结线框材质那么可以通过修改Mesh.js源码来实现兼容 本文重点是project方法事实上这个方法也有坑后面还会提到敬请期待