Webgl & Three.js中的物体拾取

IT科技2025-11-04 00:00:06569

 

1、物体引子:

在传统的物体web开发中,由于存在DOM树以及事件捕获冒泡等机制,物体我们可以很方便的物体在某个DOM节点上注册事件,并且执行父元素事件代理等一系列操作。物体但是物体在webgl的三维世界里,用户使用鼠标或者touch事件,物体事件接收方是物体canvas容器,如何将这种点击行为映射到三维世界,物体就需要借助三维世界的物体能力了,并且建立从canvas平面容器到三维世界的物体桥梁,进行所谓的物体物体拾取

2、基础知识:

DOM与NDC坐标转换,物体相机射线,物体观察者模式。物体熟悉这部分知识的同学,可以直接跳过到下面的代码实现。

a、DOM坐标和NDC坐标转换:在这里,我们知道DOM坐标的(0,0)点在容器左上角,香港云服务器NDC坐标的(0,0)点在容器中心,需要进行坐标转换。具体定义可以参考下面两个链接

三维坐标变换 屏幕坐标定义

b、相机射线: 根据Three.js的官方定义,射线就是用来做物体拾取的机制。相比较于传统的颜色拾取,射线拾取可以识别多个物体,得到先后顺序,在使用上更加方便并且符合人的直觉。? 下面看一个官方的code example?

javascript

const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); function onMouseMove( event ) {     // 这里实现了DOM坐标系到NDC坐标系的转换     mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;     mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; } function render() {     // 从相机位置,发出一条射线     raycaster.setFromCamera( mouse, camera );     // 检测相机发出射线相交的obj列表     const intersects = raycaster.intersectObjects( scene.children );     for ( let i = 0; i < intersects.length; i ++ ) {         // 将相交物体的材质颜色设置为红色         intersects[ i ].object.material.color.set( 0xff0000 );     }     renderer.render( scene, camera ); } window.addEventListener( mousemove, onMouseMove, false ); window.requestAnimationFrame(render); 

c、观察者模式:与dom事件机制类似,观察者模式非常是适合做这种注册-触发机制的。

3、具体实现:

考虑到每次遍历intersects数组非常的不方便,特别是当场景中有实际上百个Object3D的源码库时候。所以这里我们定义一个全局的对象存储注册事件,然后修改Object3D的原型链,增加on和on和on和off方法,来实现类似于DOM元素的事件注册和销毁。

const globalEvent = {click: {}} Object.assign(Object3D.prototype, {     $on(eventType, cb) {        if(globalEvent.hasOwnProperty(eventType)) {             globalEvent[eventType][this.id] = {                 object3d: this,                 callback: cb             };        } else { // error warn}     }     $off(eventType) {         if (!eventType) throw new Error()         if(globalEvent.hasOwnProperty(eventType)) {             delete globalEvent[eventType][this.id]         } else {             throw new Error()         }     } }) init(camera) function init(camera, container) {     let intersectPoint, obj, mouseX, mouseY, clicked;     const targetObj = globalEvent.click     const rayCaster = new Raycaster();     function down(e) {         obj = null;         e.preventDefault();         mouseX = event.clientX;         mouseY = event.clientY;         if (!globalEvent.click) return;         rayCaster.setFromCamera(             new Vector2(                 (mouseX / window.innerWidth) * 2 - 1,                 -(mouseY / window.innerHeight) * 2 + 1             ),             camera         );          let intersects = rayCaster.intersectsObjects(getVisibleList(targetObj));         if (intersects.length > 0) {             if (clicked) {                 obj = null;                 return;             }             clicked = true;             obj = intersects[0].object;             intersectPoint = intersects[0].point;                                 } else {             clicked = false;         }     }     function move(e) {         event.preventDefault();         // 这里针对移动端做一些优化      }     function up(e) {         event.preventDefault();         if (clicked && !!obj && obj.callback) {             obj.callback(obj.object3d, intersectPoint);         }         clicked = false     }    const eventOption = {      passive: false    };    container.addEventListener(mousedown, down, {passive: false});    container.addEventListener(mousemove, move, {passive: false});    container.addEventListener(mouseup, up, {passive: false});    container.addEventListener(touchstart, down, {passive: false});    container.addEventListener(touchmove, move, {passive: false});    container.addEventListener(touchend, up, {passive: false});} function getVisibleList(targetObj) {     const list = []     for (const key in targetObj) {         const target = targetObj[key].object3d;         if (target.visible) list.push(target);     }     return list } /*     使用方式:直接在mesh上注册事件     target: 命中物体     point: 命中点的三维坐标 */ mesh.$on(click, (target, point)) { } 

4、总结

本文通过修改three.js中Object3D的原型链,基于观察者模式,增加了点击事件绑定,支持用户点击canvas容器,拾取场景中的物体。在具体的生产环境使用中,还需要考虑如何实现事件冒泡,渲染层级,鼠标拖拽和长按等操作,这些不在主流程中,不再赘述,大家可以在debug的过程中,逐渐完善上面的代码。

作者:huoyunxieshen0007

链接:https://juejin.cn/post/6951226769529634830

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。云南idc服务商

本文地址:http://www.bzve.cn/news/661b65898680.html
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

全站热门

电脑耳机如何连接音箱?(一步步教你轻松实现音箱连接)

电脑开机错误代码0164的解决方法(探究电脑出现0164错误代码的原因与解决方案)

小米笔记本的性能与用户体验如何?(揭秘小米笔记本的优点与不足,让你更好地选择)

荣耀7i导航体验详解(荣耀7i导航功能及使用感受)

拯救者BIOS降级教程(降级BIOS,让拯救者重获新生)

三星S7港版性能怎么样?(全面评析三星S7港版的性能表现,为您解读手机性能的真正实力)

用雅思入门单反摄影教程电脑记录美丽瞬间(探索数字摄影的奇妙世界,提高摄影技巧)

电脑安装系统出现错误的解决方法(解决电脑安装系统时遇到错误的有效办法)

友情链接

滇ICP备2023006006号-39