news 2026/1/1 1:48:45

Three.js 点模型、线模型、精灵模型拾取实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Three.js 点模型、线模型、精灵模型拾取实现

一、点模型(Points)拾取实现

实现步骤:

  1. 创建点模型:使用THREE.Points和点材质
  2. 设置点大小:在材质中设置size属性
  3. Raycaster配置:设置Points的拾取阈值
  4. 拾取检测:使用intersectObjects检测相交

完整案例:

<template><divclass="container"ref="containerRef"></div></template><scriptsetup>import{onMounted,ref}from"vue";import*asTHREEfrom"three";import{OrbitControls}from"three/examples/jsm/controls/OrbitControls";constcontainerRef=ref(null);// 创建场景constscene=newTHREE.Scene();scene.background=newTHREE.Color(0xf0f0f0);// 创建相机constcamera=newTHREE.PerspectiveCamera(60,window.innerWidth/window.innerHeight,0.1,1000);camera.position.set(0,0,10);// 创建渲染器constrenderer=newTHREE.WebGLRenderer({antialias:true});renderer.setSize(window.innerWidth,window.innerHeight);// 创建点模型functioncreatePointCloud(){// 创建100个随机点constvertices=[];for(leti=0;i<100;i++){constx=(Math.random()-0.5)*10;consty=(Math.random()-0.5)*10;constz=(Math.random()-0.5)*10;vertices.push(x,y,z);}constgeometry=newTHREE.BufferGeometry();geometry.setAttribute('position',newTHREE.Float32BufferAttribute(vertices,3));// 创建点材质 - 关键:设置点大小constmaterial=newTHREE.PointsMaterial({color:0xff0000,size:0.2,// 点的大小sizeAttenuation:true// 点大小是否随距离衰减});// 创建点云对象constpoints=newTHREE.Points(geometry,material);points.name='pointCloud';scene.add(points);returnpoints;}// 创建点模型constpointCloud=createPointCloud();// 添加光源constambientLight=newTHREE.AmbientLight(0xffffff,0.5);scene.add(ambientLight);constdirectionalLight=newTHREE.DirectionalLight(0xffffff,0.8);directionalLight.position.set(10,10,5);scene.add(directionalLight);// 添加坐标轴辅助constaxesHelper=newTHREE.AxesHelper(5);scene.add(axesHelper);// 动画循环functionanimate(){requestAnimationFrame(animate);// 让点云缓慢旋转pointCloud.rotation.y+=0.005;renderer.render(scene,camera);}animate();// 点模型拾取函数functionpickPoints(event){// 1. 获取鼠标归一化设备坐标constmouse=newTHREE.Vector2();mouse.x=(event.clientX/window.innerWidth)*2-1;mouse.y=-(event.clientY/window.innerHeight)*2+1;// 2. 创建Raycasterconstraycaster=newTHREE.Raycaster();raycaster.setFromCamera(mouse,camera);// 3. 关键:设置点模型的拾取阈值// 这个值决定点击距离点多近才算选中,值越大越容易选中raycaster.params.Points={threshold:0.2};// 4. 检测相交constintersects=raycaster.intersectObjects([pointCloud]);// 5. 处理结果if(intersects.length>0){constintersect=intersects[0];console.log('选中了点:',intersect);// 获取选中的点的索引constpointIndex=intersect.index;// 创建高亮点(在该位置添加一个更大的点)consthighlightGeometry=newTHREE.BufferGeometry();constposition=intersect.object.geometry.attributes.position;constpointPosition=[position.getX(pointIndex),position.getY(pointIndex),position.getZ(pointIndex)];highlightGeometry.setAttribute('position',newTHREE.Float32BufferAttribute(pointPosition,3));consthighlightMaterial=newTHREE.PointsMaterial({color:0xffff00,size:0.5,sizeAttenuation:true});consthighlight=newTHREE.Points(highlightGeometry,highlightMaterial);highlight.name='highlightPoint';// 移除之前的高亮点constoldHighlight=scene.getObjectByName('highlightPoint');if(oldHighlight)scene.remove(oldHighlight);scene.add(highlight);// 显示信息console.log('点位置:',pointPosition);console.log('点索引:',pointIndex);}}// 添加点击事件window.addEventListener('click',pickPoints);// 挂载到DOMonMounted(()=>{constcontrols=newOrbitControls(camera,containerRef.value);controls.enableDamping=true;containerRef.value.appendChild(renderer.domElement);// 窗口大小变化处理window.addEventListener('resize',()=>{camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight);});});</script><style>.container{width:100%;height:100vh;}</style>

关键点说明:

  1. PointsMaterial.size:控制点的大小
  2. raycaster.params.Points.threshold:设置点拾取的敏感度
  3. intersect.index:获取选中点的索引
  4. intersect.point:获取选中点的具体位置

二、线模型(Line)拾取实现

实现步骤:

  1. 创建线模型:使用THREE.LineTHREE.LineSegments
  2. Raycaster配置:设置Line的拾取阈值
  3. 提高拾取精度:通过辅助方法增加拾取成功率
  4. 处理拾取结果:获取线段信息

完整案例:

<template><divclass="container"ref="containerRef"></div></template><scriptsetup>import{onMounted,ref}from"vue";import*asTHREEfrom"three";import{OrbitControls}from"three/examples/jsm/controls/OrbitControls";constcontainerRef=ref(null);// 创建场景constscene=newTHREE.Scene();scene.background=newTHREE.Color(0x222222);// 创建相机constcamera=newTHREE.PerspectiveCamera(60,window.innerWidth/window.innerHeight,0.1,1000);camera.position.set(5,5,5);camera.lookAt(0,0,0);// 创建渲染器constrenderer=newTHREE.WebGLRenderer({antialias:true});renderer.setSize(window.innerWidth,window.innerHeight);// 创建复杂的线模型functioncreateComplexLine(){// 创建曲线路径constcurve=newTHREE.CatmullRomCurve3([newTHREE.Vector3(-4,0,0),newTHREE.Vector3(-2,3,1),newTHREE.Vector3(0,0,2),newTHREE.Vector3(2,3,1),newTHREE.Vector3(4,0,0)]);// 获取曲线上的点constpoints=curve.getPoints(50);// 创建线几何体constgeometry=newTHREE.BufferGeometry().setFromPoints(points);// 创建线材质constmaterial=newTHREE.LineBasicMaterial({color:0x00aaff,linewidth:3// 注意:大多数浏览器不支持大于1的线宽});// 创建线对象constline=newTHREE.Line(geometry,material);line.name='complexLine';scene.add(line);returnline;}// 创建网格线(更容易拾取)functioncreateGridLines(){constgroup=newTHREE.Group();// 创建水平线for(leti=-5;i<=5;i++){constgeometry=newTHREE.BufferGeometry().setFromPoints([newTHREE.Vector3(-5,i,0),newTHREE.Vector3(5,i,0)]);constmaterial=newTHREE.LineBasicMaterial({color:0x666666,linewidth:2});constline=newTHREE.Line(geometry,material);line.userData.type='gridLine';line.userData.index=i;group.add(line);}// 创建垂直线for(leti=-5;i<=5;i++){constgeometry=newTHREE.BufferGeometry().setFromPoints([newTHREE.Vector3(i,-5,0),newTHREE.Vector3(i,5,0)]);constmaterial=newTHREE.LineBasicMaterial({color:0x666666,linewidth:2});constline=newTHREE.Line(geometry,material);line.userData.type='gridLine';line.userData.index=i;group.add(line);}scene.add(group);returngroup;}// 创建线模型constcomplexLine=createComplexLine();constgridLines=createGridLines();// 添加光源constambientLight=newTHREE.AmbientLight(0xffffff,0.6);scene.add(ambientLight);constdirectionalLight=newTHREE.DirectionalLight(0xffffff,0.8);directionalLight.position.set(10,10,5);scene.add(directionalLight);// 添加坐标轴辅助constaxesHelper=newTHREE.AxesHelper(5);scene.add(axesHelper);// 动画循环functionanimate(){requestAnimationFrame(animate);renderer.render(scene,camera);}animate();// 线模型拾取函数functionpickLines(event){// 获取鼠标位置constmouse=newTHREE.Vector2();mouse.x=(event.clientX/window.innerWidth)*2-1;mouse.y=-(event.clientY/window.innerHeight)*2+1;// 创建Raycasterconstraycaster=newTHREE.Raycaster();raycaster.setFromCamera(mouse,camera);// 关键:设置线模型的拾取阈值(增大以提高拾取成功率)// 这个值表示距离线多远的点击算选中(单位:世界单位)raycaster.params.Line={threshold:0.3};// 收集所有线对象constlineObjects=[];scene.traverse((object)=>{if(object.type==='Line'||object.type==='LineSegments'){lineObjects.push(object);}});// 检测相交constintersects=raycaster.intersectObjects(lineObjects);if(intersects.length>0){constintersect=intersects[0];constline=intersect.object;console.log('选中了线:',line);console.log('相交点:',intersect.point);console.log('距离:',intersect.distance);console.log('线段索引:',intersect.faceIndex);// 改变线的颜色line.material.color.set(Math.random()*0xffffff);// 在相交点添加标记addIntersectionMarker(intersect.point);// 如果是网格线,显示信息if(line.userData.type==='gridLine'){console.log(`网格线类型:${line.userData.type}, 索引:${line.userData.index}`);}}}// 添加相交点标记functionaddIntersectionMarker(position){// 移除旧的标记constoldMarker=scene.getObjectByName('intersectionMarker');if(oldMarker)scene.remove(oldMarker);// 创建标记几何体constmarkerGeometry=newTHREE.SphereGeometry(0.1,16,16);constmarkerMaterial=newTHREE.MeshBasicMaterial({color:0xff0000});constmarker=newTHREE.Mesh(markerGeometry,markerMaterial);marker.position.copy(position);marker.name='intersectionMarker';scene.add(marker);// 3秒后移除标记setTimeout(()=>{if(marker.parent)scene.remove(marker);},3000);}// 添加点击事件window.addEventListener('click',pickLines);// 挂载到DOMonMounted(()=>{constcontrols=newOrbitControls(camera,containerRef.value);controls.enableDamping=true;containerRef.value.appendChild(renderer.domElement);// 窗口大小变化处理window.addEventListener('resize',()=>{camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight);});});</script><style>.container{width:100%;height:100vh;}</style>

关键点说明:

  1. raycaster.params.Line.threshold:控制线拾取的敏感度
  2. 线宽限制:WebGL中大多不支持大于1的linewidth
  3. intersect.faceIndex:获取选中线段的索引
  4. 使用辅助标记:通过添加标记点显示拾取位置

三、精灵模型(Sprite)拾取实现

实现步骤:

  1. 创建精灵模型:使用THREE.Sprite和精灵材质
  2. 设置精灵大小:通过scale属性控制
  3. Raycaster配置:Sprite会自动被检测,无需特殊配置
  4. 处理拾取结果:获取精灵信息

完整案例:

<template><divclass="container"ref="containerRef"></div></template><scriptsetup>import{onMounted,ref}from"vue";import*asTHREEfrom"three";import{OrbitControls}from"three/examples/jsm/controls/OrbitControls";constcontainerRef=ref(null);// 创建场景constscene=newTHREE.Scene();scene.background=newTHREE.Color(0x111122);// 创建相机constcamera=newTHREE.PerspectiveCamera(60,window.innerWidth/window.innerHeight,0.1,1000);camera.position.set(0,0,15);// 创建渲染器constrenderer=newTHREE.WebGLRenderer({antialias:true});renderer.setSize(window.innerWidth,window.innerHeight);// 创建精灵材质(使用Canvas绘制纹理)functioncreateSpriteMaterial(text,color='#ff0000'){// 创建Canvasconstcanvas=document.createElement('canvas');canvas.width=256;canvas.height=256;constcontext=canvas.getContext('2d');// 绘制圆形背景context.beginPath();context.arc(128,128,120,0,2*Math.PI);context.fillStyle=color;context.fill();// 添加描边context.lineWidth=8;context.strokeStyle='#ffffff';context.stroke();// 添加文字context.font='bold 60px Arial';context.fillStyle='#ffffff';context.textAlign='center';context.textBaseline='middle';context.fillText(text,128,128);// 创建纹理consttexture=newTHREE.CanvasTexture(canvas);// 创建精灵材质constmaterial=newTHREE.SpriteMaterial({map:texture,transparent:true});returnmaterial;}// 创建多个精灵constsprites=[];constspriteGroup=newTHREE.Group();functioncreateSprites(){constpositions=[{x:-5,y:0,z:0,text:'A',color:'#ff0000'},{x:-2.5,y:3,z:-2,text:'B',color:'#00ff00'},{x:0,y:-2,z:2,text:'C',color:'#0000ff'},{x:2.5,y:3,z:-2,text:'D',color:'#ffff00'},{x:5,y:0,z:0,text:'E',color:'#ff00ff'},{x:0,y:5,z:0,text:'F',color:'#00ffff'}];positions.forEach((pos,index)=>{constmaterial=createSpriteMaterial(pos.text,pos.color);constsprite=newTHREE.Sprite(material);// 设置位置sprite.position.set(pos.x,pos.y,pos.z);// 设置大小 - 精灵的大小通过scale控制sprite.scale.set(2,2,1);// 添加自定义数据sprite.userData={type:'interactiveSprite',id:index,text:pos.text,originalColor:pos.color,originalScale:{x:2,y:2,z:1}};sprite.name=`sprite_${pos.text}`;sprites.push(sprite);spriteGroup.add(sprite);});scene.add(spriteGroup);}// 创建精灵createSprites();// 添加一个立方体作为参考constcubeGeometry=newTHREE.BoxGeometry(1,1,1);constcubeMaterial=newTHREE.MeshBasicMaterial({color:0x888888,wireframe:true});constcube=newTHREE.Mesh(cubeGeometry,cubeMaterial);scene.add(cube);// 添加光源constambientLight=newTHREE.AmbientLight(0xffffff,0.4);scene.add(ambientLight);constdirectionalLight=newTHREE.DirectionalLight(0xffffff,0.6);directionalLight.position.set(10,10,5);scene.add(directionalLight);// 添加坐标轴辅助constaxesHelper=newTHREE.AxesHelper(10);scene.add(axesHelper);// 动画循环functionanimate(){requestAnimationFrame(animate);// 让精灵组缓慢旋转spriteGroup.rotation.y+=0.005;renderer.render(scene,camera);}animate();// 精灵模型拾取函数functionpickSprites(event){// 获取鼠标位置constmouse=newTHREE.Vector2();mouse.x=(event.clientX/window.innerWidth)*2-1;mouse.y=-(event.clientY/window.innerHeight)*2+1;// 创建Raycasterconstraycaster=newTHREE.Raycaster();raycaster.setFromCamera(mouse,camera);// 注意:精灵模型会自动被Raycaster检测,无需特殊配置// 检测相交constintersects=raycaster.intersectObjects(sprites);if(intersects.length>0){constintersect=intersects[0];constsprite=intersect.object;console.log('选中了精灵:',sprite.name);console.log('精灵数据:',sprite.userData);console.log('相交点:',intersect.point);console.log('距离:',intersect.distance);// 高亮效果:放大精灵constoriginalScale=sprite.userData.originalScale;sprite.scale.set(originalScale.x*1.5,originalScale.y*1.5,originalScale.z);// 3秒后恢复原大小setTimeout(()=>{sprite.scale.set(originalScale.x,originalScale.y,originalScale.z);},300);// 显示选中信息showSelectionInfo(sprite.userData);}}// 显示选中信息functionshowSelectionInfo(spriteData){// 移除旧的信息显示constoldInfo=scene.getObjectByName('selectionInfo');if(oldInfo)scene.remove(oldInfo);// 创建信息精灵constcanvas=document.createElement('canvas');canvas.width=512;canvas.height=128;constcontext=canvas.getContext('2d');// 绘制背景context.fillStyle='rgba(0, 0, 0, 0.8)';context.fillRect(0,0,canvas.width,canvas.height);// 绘制文字context.font='bold 40px Arial';context.fillStyle='#ffffff';context.textAlign='center';context.textBaseline='middle';context.fillText(`选中: 精灵${spriteData.text}(ID:${spriteData.id})`,canvas.width/2,canvas.height/2);// 创建纹理和精灵consttexture=newTHREE.CanvasTexture(canvas);constmaterial=newTHREE.SpriteMaterial({map:texture});constinfoSprite=newTHREE.Sprite(material);// 设置位置(相机上方)infoSprite.position.set(0,8,0);infoSprite.scale.set(8,2,1);infoSprite.name='selectionInfo';scene.add(infoSprite);// 5秒后移除信息setTimeout(()=>{if(infoSprite.parent)scene.remove(infoSprite);},5000);}// 添加点击事件window.addEventListener('click',pickSprites);// 挂载到DOMonMounted(()=>{constcontrols=newOrbitControls(camera,containerRef.value);controls.enableDamping=true;containerRef.value.appendChild(renderer.domElement);// 窗口大小变化处理window.addEventListener('resize',()=>{camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight);});});</script><style>.container{width:100%;height:100vh;}</style>

关键点说明:

  1. 精灵创建:使用THREE.SpriteSpriteMaterial
  2. 大小控制:通过sprite.scale.set()控制精灵大小
  3. 朝向:精灵始终面向相机(这是Sprite的特性)
  4. 拾取:Sprite会自动被Raycaster检测,无需特殊配置
  5. 纹理创建:通常使用Canvas创建动态纹理

总结对比

模型类型关键配置特点拾取难度
点模型raycaster.params.Points.threshold需要设置阈值,可获取点索引中等
线模型raycaster.params.Line.threshold需要增大阈值,线宽有限制较高
精灵模型无需特殊配置始终面向相机,自动检测容易
网格模型无需特殊配置最常见的3D物体最容易

每个模型类型都有其特定的应用场景和拾取配置,根据实际需求选择合适的模型类型和拾取策略。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/24 22:37:51

MYSQL-窗口函数学习总结

窗口函数不是将数据汇总成单个结果&#xff0c;而是为每一行数据都返回一个结果。完整的窗口函数的定义&#xff1a;window_function over(partition by...-- 分区参数 order by... -- 排序参数 frame_clause -- 窗口大小 );以下代码所用数据集在之前的文章中可以找到。select…

作者头像 李华
网站建设 2025/12/24 18:22:43

前沿财经与管理国际期刊征稿!

Probe - Accounting, Auditing and Taxation (PAAT)(探究—会计、审计与税务) 是一本开放获取期刊&#xff0c;可快速发表会计、审计和税务所有领域的文章。该期刊的目标是通过发表与会计领域发展相关的论文来弥合学术研究人员和从业者之间的差距。期刊名称&#xff1a;Probe -…

作者头像 李华
网站建设 2025/12/29 19:34:30

如何快速部署CogAgent:GUI智能助手完整指南

如何快速部署CogAgent&#xff1a;GUI智能助手完整指南 【免费下载链接】CogAgent An open-sourced end-to-end VLM-based GUI Agent 项目地址: https://gitcode.com/GitHub_Trending/co/CogAgent 你是否曾想过让AI助手帮你操作电脑界面&#xff1f;CogAgent作为一款开源…

作者头像 李华
网站建设 2025/12/25 0:28:09

智能财税引擎-发票查验接口技术解析-发票OCR识别

在数字化财税管理时代&#xff0c;发票作为交易的核心凭证&#xff0c;其真实性验证是确保企业财务合规、防范税务风险的第一道防线。然而&#xff0c;面对从传统纸质票到全面数字化的电子发票&#xff08;全电票&#xff09;的演变&#xff0c;以及税务编码、校验规则的多变&a…

作者头像 李华
网站建设 2025/12/26 7:54:42

《requests vs httpx:Python 网络请求库的全面对比与实战指南》

《requests vs httpx&#xff1a;Python 网络请求库的全面对比与实战指南》 从同步到异步&#xff0c;从简单请求到高性能并发&#xff0c;选对工具&#xff0c;事半功倍。 一、写在前面&#xff1a;为什么我们需要重新审视 HTTP 客户端&#xff1f; 在 Python 的世界里&#…

作者头像 李华
网站建设 2025/12/30 3:20:59

终极指南:用cloc轻松统计代码行数的完整教程

终极指南&#xff1a;用cloc轻松统计代码行数的完整教程 【免费下载链接】cloc cloc counts blank lines, comment lines, and physical lines of source code in many programming languages. 项目地址: https://gitcode.com/gh_mirrors/cl/cloc 你还在为代码统计的准确…

作者头像 李华