全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-03-19_threejs开发可视化数字城市效果

您的位置:首页 >> 新闻 >> 行业资讯

threejs开发可视化数字城市效果 灵感图现在随着城市的发展,越来越多的智慧摄像头,都被互联网公司布到城市的各个角落,举一个例子,一个大楼上上下下都被布置了智能摄像头,用于监控火势,人员进出,工装工牌佩戴等监控,这时候为了美化项目,大公司都会将城市的区域作为对象,进行3d可视化交互,接下来的内容,就是基于以上元素,开发的一款城市数据可视化的demo,包含楼宇特效,飞线,特定视角,动画等交互,希望可以给大家带来一 neinei 的帮助,话不多说,开整 本文比较长,每个阶段代码都提供tag,可以分步骤查看,请耐心品尝 用到的技术栈 vite + typescript + threejs 白模下载白模模型下载网站 [上海模型](City- Shanghai-Sandboxie - Download Free 3D model by Michael Zhang (@beyond.zht) [3eab443] (sketchfab.com)) 搜索关键词:city 压缩包包含的内容 模型加载模型下载的是gltf格式,所以要用到threejs 提供的 # GLTFLoader,下面是具体代码 exportfunctionloadGltf(url:string){ returnnewPromiseObject((resolve,reject)={ gltfLoader.load(url,function(gltf){ console.log('gltf',gltf) resolve(gltf) }); }) } 处理模型模型上有一些咱们用不到的模型,进行删除,还有一些用的到的模型,但是名称不友好,所以进行整理 loadGltf('./models/scene.gltf').then((gltf:any)={ constgroup=gltf.scene constscale=10 group.scale.set(scale,scale,scale) //删除多余模型 constmesh1=group.getObjectByName('Text_test-base_0') if(mesh1mesh1.parent)mesh1.parent.remove(mesh1) constmesh2=group.getObjectByName('Text_text_0') if(mesh2mesh2.parent)mesh2.parent.remove(mesh2) //重命名模型 //环球金融中心 consthqjrzx=group.getObjectByName('02-huanqiujinrongzhongxin_huanqiujinrongzhongxin_0') if(hqjrzx)hqjrzx.name='hqjrzx' //上海中心 constshzx=group.getObjectByName('01-shanghaizhongxindasha_shanghaizhongxindasha_0') if(shzx)shzx.name='shzx' //金茂大厦 constjmds=group.getObjectByName('03-jinmaodasha_jjinmaodasha_0') if(jmds)jmds.name='jmds' //东方明珠塔 constdfmzt=group.getObjectByName('04-dongfangmingzhu_dongfangmingzhu_0') if(dfmzt)dfmzt.name='dfmzt' T.scene.add(group) T.toSceneCenter(group) T.ray(group.children,(meshList)={ console.log('meshList',meshList); }) T.animate() }) T是场景的构建函数,具体可以查看 gitee中的文件,这里就不赘述了,主要创建了场景,镜头,控制器,灯光等基础信息,并且监听控制器变化时修改灯光位置 在使用第三方模型的时候,总有一些不尽人意的地方,比如模型加载后,模型中心并不在3d世界的中心位置,所以就需要调整一下模型整体的位置,toSceneCenter 方法是自定义的一个让模型居中的方法,通过# Box3获取到模型的包围盒,获取到模型的中心点坐标信息,再取反,就会得到模型中心点在3d世界的位置信息 //获取包围盒 getBoxInfo(mesh){ constbox3=newTHREE.Box3() box3.expandByObject(mesh) constsize=newTHREE.Vector3() constcenter=newTHREE.Vector3() //获取包围盒的中心点和尺寸 box3.getCenter(center) box3.getSize(size) return{ size,center } } toSceneCenter(mesh){ const{center,size}=this.getBoxInfo(mesh) //将Y轴置为0 mesh.position.copy(center.negate().setY(0)) } 阶段代码以上代码地址 城市加载白模 v2.0.1 飞线收集飞线的点没有3d设计师的支持,所有的数据都来自于模型,所以利用现有条件,收集飞线经过的点位,原理就是使用到的鼠标射线,点击模型上的某个位置并记录下来,提供给后期使用 众所周知,click的调用过程是忽略mousedown的,mouseup时候就会调用,如果单纯的想要改变视角,鼠标抬起时候也会调用click事件,所以要加一个鼠标是否移动的判断,利用控制器监听start和end时的镜头位置变化来区分鼠标是否移动 控制器部分代码: this.controls.addEventListener('start',()={ this.controlsStartPos.copy(this.camera.position) }) this.controls.addEventListener('end',()={ this.controlsMoveFlag=this.controlsStartPos.distanceToSquared(this.camera.position)===0 }) 控制器开始变化的时候记录camera位置,跟结束时的camera的位置相减,如果为0,则表示鼠标没晃动,单纯的点击,如果不为0,说明镜头位置变化了,这时,鼠标的click回调将不会调用 射线部分代码: ray(children:THREE.Object3D[],callback:(mesh:THREE.IntersectionTHREE.Object3DTHREE.Event[])=void){ letmouse=newTHREE.Vector2();//鼠标位置 varraycaster=newTHREE.Raycaster(); window.addEventListener("click",(event)={ mouse.x=(event.clientX/document.body.offsetWidth)*2-1; mouse.y=-(event.clientY/document.body.offsetHeight)*2+1; raycaster.setFromCamera(mouse,this.camera); constrallyist=raycaster.intersectObjects(children); if(this.controlsMoveFlag){ callbackcallback(rallyist) } } 射线的回调: letarr=[] T.ray(group.children,(meshList)={ console.log('meshList',meshList); arr.push(...meshList[0].point.toArray()) console.log(JSON.stringify(arr)); }) 收集后的顶点信息: 这部分的工作和之前写 # threejs 打造 world.ipanda.com 同款3D首页时候收集熊猫基地的点位是一样的,只不过判断鼠标是否移动的部分不一样而已。 细化顶点有了飞线具体经过的点位时候,要将这些点位细化,这时就要讲飞线的大致原理了,两点确定一条线段,获取线段上的100个点,每条飞线占用20个点位,每个点位创建一个着色器,用于绘制飞线的组成部分,当更新时候,飞线的首个点向下一个点前进,一次往后20个点都往前前进一次,循环往复一直到飞线的最后一个组成部分到达线段的最后一个点,飞线占用的点位数量决定飞线的长度,将线段分为多少个顶点,决定飞线的疏密程度,像图中这样的疏密度,就是单个线段的点位分少了,这个可以优化的,vector3.distanceTo(vector3)即可判断两个线段的长度,通过不同的长度,决定细化线段的点,当然,线段的顶点信息越多,对gpu的消耗越大 flyLineData.forEach((data:number[])={ constpoints:THREE.Vector2[]=[] for(leti=0;idata.length/3;i++){ constx=data[i*3] constz=data[i*3+2] constpoint=newTHREE.Vector2(x,z) points.push(point) } constcurve=newTHREE.SplineCurve(points); //此处决定飞线每个点的疏密程度,数值越大,对gpu的压力越大 constcurvePoints=curve.getPoints(100); constflyPoints=curvePoints.map((curveP:THREE.Vector2)=newTHREE.Vector3(curveP.x,0,curveP.y)) //constl=points.length-1 constflyGroup=T._Fly.setFly({ index:Math.random()0.5?50:20, num:20, points:flyPoints, spaced:50,//要将曲线划分为的分段数。默认是 5 starColor:newTHREE.Color(Math.random()*0xffffff), endColor:newTHREE.Color(Math.random()*0xffffff), size:0.5 }) flyLineGroup.add(flyGroup) }) setFly参数 interfaceSetFly{ index:number,//截取起点 num:number,//截取长度//要小于length points:Vector3[], spaced:number//要将曲线划分为的分段数。默认是 5 starColor:Color, endColor:Color, size:number } endColor和starColor目前不好用,做不出渐变,不知道是不是长度不够,暂时先放放 flyLine创建flyLine做成了一个类,开箱即用,也可以加入自己的想法,调整内容, 创建flyLine之后要在render中调用 render(){ this.controls.update() this.renderer.render(this.scene,this.camera); this._Flythis._Fly.upDate() } new Fly() 方法详见飞线fly.ts 可配置参数有尺寸,透明度,颜色等 varcolor1=params.starColor;//轨迹线颜色青色 varcolor2=params.endColor;//黄色 varcolor=color1.lerp(color2,i/newPoints2.length) colorArr.push(color.r,color.g,color.b); 这里是引用渐变色的位置,需要再调整一下 阶段代码以上代码地址 城市飞线 v2.0.2 线稿将模型绘制出线稿,并添加到原有模型上,这里用到LineBasicMaterial基础线条材质,和MeshLambertMaterial基础网格材质,调节颜色和不透明度。 材质代码: //建筑材质 exportconstotherBuildingMaterial=(color:THREE.Color,opacity=1)={ returnnewTHREE.MeshLambertMaterial({ color, transparent:true, opacity } //建筑线条材质 exportconstotherBuildingLineMaterial=(color:THREE.Color,opacity=1)={ returnnewTHREE.LineBasicMaterial( { color, depthTest:true, transparent:true, opacity } ) } 以下代码是之前对模型改造时写的对模型重命名的方法,现在我们来改造一下 //重命名模型 //环球金融中心 consthqjrzx=group.getObjectByName('02-huanqiujinrongzhongxin_huanqiujinrongzhongxin_0') if(hqjrzx){ hqjrzx.name='hqjrzx' changeModelMaterial(hqjrzx,otherBuildingMaterial(buildColor,buildOpacity),otherBuildingLineMaterial(buildLineColor,buildLineOpacity),buildLineDeg) } //上海中心 constshzx=group.getObjectByName('01-shanghaizhongxindasha_shanghaizhongxindasha_0') if(shzx){ shzx.name='shzx' changeModelMaterial(shzx,otherBuildingMaterial(buildColor,buildOpacity),otherBuildingLineMaterial(buildLineColor,buildLineOpacity),buildLineDeg) } //金茂大厦 constjmds=group.getObjectByName('03-jinmaodasha_jjinmaodasha_0') if(jmds){ jmds.name='jmds' changeModelMaterial(jmds,otherBuildingMaterial(buildColor,buildOpacity),otherBuildingLineMaterial(buildLineColor,buildLineOpacity),buildLineDeg) } //东方明珠塔 constdfmzt=group.getObjectByName('04-dongfangmingzhu_dongfangmingzhu_0') if(dfmzt){ dfmzt.name='dfmzt' changeModelMaterial(dfmzt,otherBuildingMaterial(buildColor,buildOpacity),otherBuildingLineMaterial(buildLineColor,buildLineOpacity),buildLineDeg) } T.scene.add(group) T.toSceneCenter(group) group.traverse((mesh:any)={ meshasTHREE.Mesh if(mesh.isMesh(mesh.name.indexOf('Shanghai')!==-1||mesh.name.indexOf('Object')!==-1)){ if(mesh.name.indexOf('Floor')!==-1){ mesh.material=floorMaterial }elseif(mesh.name.indexOf('River')!==-1){ }else{ changeModelMaterial(mesh,otherBuildingMaterial(otherBuildColor,0.8),otherBuildingLineMaterial(otherBuildLineColor,0.4),buildLineDeg) } } }) changeModelMaterial这个方法就是创建模型相对应的线条的方法,获取到模型的geometry,这里存着模型所有的顶点信息,索引和法向量,以此创建一个# 边缘几何体(EdgesGeometry);通过边缘几何体的信息创建 # 线段(LineSegments);并将创建出来的线段添加到原有模型中,因为我们的线段不需要单独处理,所以这里写的方法会比之前在# threejs渲染高级感可视化涡轮模型 一文中写的简化的很多,如果需要单独对线段处理的同学,可以采用这篇文章里的 changeModelMaterial 方法 /** * *@paramobject模型 *@paramlineGroup线组 *@parammeshMaterial模型材质 *@paramlineMaterial线材质 */ exportconstchangeModelMaterial=(mesh:THREE.Mesh,meshMaterial:THREE.MeshBasicMaterial,lineMaterial:THREE.LineBasicMaterial,deg=1):any={ if(mesh.isMesh){ if(meshMaterial)mesh.material=meshMaterial //以模型顶点信息创建线条 constline=getLine(mesh,deg,lineMaterial) constname=mesh.name+'_line' line.name=name mesh.add(line) } } //通过模型创建线条 exportconstgetLine=(object:THREE.Mesh,thresholdAngle=1,lineMaterial:THREE.LineBasicMaterial):THREE.LineSegments={ //创建线条,参数为几何体模型,相邻面的法线之间的角度, varedges=newTHREE.EdgesGeometry(object.geometry,thresholdAngle); varline=newTHREE.LineSegments(edges); if(lineMaterial)line.material=lineMaterial returnline; } 关于颜色对于我这种野生前端开发,没有UI和UE的支持,只能在网上找案例,那么就需要图片中的颜色,这里不得不提到一个工具色輪、調色盤產生器 | Adobe Color 色彩这里可以根据一个颜色,调出互补色、相似色、单色等色彩信息 取色这个工具也可以根据一张图片,提取出主题色,包含主色、辅助色等信息 阶段代码以上代码地址 城市线稿轮廓 预设镜头位置 预埋点位预埋的点位坐标信息获取和飞线点位获取一样的方法,标记采用的是# CSS2DRenderer,将创建的element节点渲染到3d世界,3drender和2drender不在同一个图层内,所以需要新建一个dom节点,专门存放css2d的dom信息, divid="css2dRender"/div 加载css2drendercreateScene 文件 +renderCss2D:CSS2DRenderer createRenderer(){ ... this.renderCss2D=newCSS2DRenderer({element:this.css2dDom this.renderCss2D.setSize(this.width,this.height); ... } render(){ ... this.renderCss2D.render(this.scene,this.camera); ... } 根据数据创建dom节点exportinterfaceCameraPosInfo{ pos:number[],//预设摄像机位置信息 target:number[],//控制器目标位置 name:string,//预埋标记点或其他信息 tagPos?:number[],//预埋标记点的位置信息 } 接下来就是要根据信息创建节点,遍历这些信息,并创建节点,这里有一个点需要提一下,2d图层和3d图层的关系,这里要是不介绍清楚,后面没法进行 从图中可以看出,2d图层始终保持在3d图层的上层,然而我们在创建控制器的时候,第二个参数使用的是3d的图层,this.controls = new OrbitControls(this.camera, this.renderer.domElement),因为这一层被覆盖了,所以控制器失效了。 有两种解决方案,第一种是 new OrbitControls时,将第二个参数改为this.renderCss2D.domElement,还有一种方式,也就是本文采用的方式,将2d图层的css属性改变一下,忽略这个图层的任何事件。 #css2dRender{ /*一定要加这个属性,不然2D内容点击没效果*/ pointer-events:none; } 由于pointer-events属性是可以继承的,2d图层内所有的元素都不响应事件,所以要将咱们创建的建筑tag的样式改一下 .build_tag{ /*一定要加这个属性,不然2D内容点击没效果*/ pointer-events: } 第一次写这方面的代码的时候,也是头疼了好久,慢慢摸索才摸索出来的。 //创建建筑标记 functioncreateTag(){ constbuildTagGroup=newTHREE.Group() T.scene.add(buildTagGroup) presetsCameraPos.forEach((cameraPos:CameraPosInfo,i:number)={ if(cameraPos.tagPos){ //渲染2d文字 constelement=document.createElement('li'); //将信息存入dom节点中,如果是react或者vue写的,不用这么存,直接存data或者state element.setAttribute('data-cameraPosInfo',JSON.stringify(cameraPos)) element.classList.add('build_tag') element.innerText=`${i+1}` //将初始化好的dom节点渲染成CSS2DObject,并在scene场景中渲染 consttag=newCSS2DObject(element); consttagPos=newTHREE.Vector3().fromArray(cameraPos.tagPos) tag.position.copy(tagPos) buildTagGroup.add(tag) } }) } 镜头动画这里通过事件代理,点击到相应的建筑tag,从dom节点上获取到data-cameraPosInfo属性,然后通过tween动画处理器修改控制器的taget和镜头的position。事件代理是js基础内容,这里就不赘述了 if(css2dDom){ css2dDom.addEventListener('click',function(e){ if(e.target){ if(e.target.nodeName==='LI'){ console.dir(e); constcameraPosInfo=e.target.getAttribute('data-cameraPosInfo') if(cameraPosInfo){ const{pos,target}=JSON.parse(cameraPosInfo) T.controls.target.set(...target) T.handleCameraPos(pos) } } } } handleCameraPos的代码 handleCameraPos(end:number[]){ //结束时候相机位置 constendV3=newTHREE.Vector3().fromArray(end) //目前相机到目标位置的距离,根据不同的位置判断运动的时间长度,从而保证速度不变 constlength=this.camera.position.distanceTo(endV3) //如果位置相同,不运行动画 if(length===0)return newthis._TWEEN.Tween(this.camera.position) .to(endV3,Math.sqrt(length)*400) .start() //.onUpdate((value)={ //console.log(value) //}) .onComplete(()={ //动画结束的回调,可以展示建筑信息或其他操作 }) } 阶段代码以上代码地址 建筑镜头动画 v2.0.4 场景背景渲染scene的场景不仅支持颜色和texture纹理,还支持canvas,上面的黑色背景太单调了,所以利用canvas绘制一个圆渐变填充到scene.background createScene(){ ... constdrawingCanvas=document.createElement('canvas'); constcontext=drawingCanvas.getContext('2d'); if(context){ //设置canvas的尺寸 drawingCanvas.width=this.width; drawingCanvas.height=this.height; //创建渐变 constgradient=context.createRadialGradient(this.width/2,this.height,0,this.width/2,this.height/2,Math.max(this.width,this.height)); //为渐变添加颜色 gradient.addColorStop(0,'#0b171f'); gradient.addColorStop(0.6,'#000000'); //使用渐变填充矩形 context.fillStyle=gradient; context.fillRect(0,0,drawingCanvas.width,drawingCanvas.height); this.scene.background=newTHREE.CanvasTexture(drawingCanvas) ... } 其他风格 完整代码地址 历史文章# threejs渲染高级感可视化涡轮模型# 写一个高德地图巡航功能的小DEMO # three.js 打造游戏小场景(拾取武器、领取任务、刷怪) # threejs 打造 world.ipanda.com 同款3D首页 # three.js——物理引擎 # three.js——镜头跟踪 # threejs 笔记 03 —— 轨道控制器 # Javascript基础之有趣的文字效果 # Javascript基础之写一个好玩的点击效果 # Javascript基础之鼠标拖拉拽 阅读原文

上一篇:2019-10-17_赠书:Sutton老爷子经典之作,《强化学习》中文第2版 下一篇:2025-03-23_地平线提出AlphaDrive,首个基于GRPO强化学习和规划推理实现自动驾驶大模型

TAG标签:

16
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价