现在位置:首页 >
发表在 2020年10月 的所有文章
-
使用ThreeJs搭建BIM模型浏览器-第十步 加载优化
最近在尝试了新的传输方式。不过对QModel而言,影响不是很大,因为QModel这个产品只有首次加载是需要从服务器下载模型数据的。 首先,把原本的模型文件拆分成多份了。原本只有一个zip数据压缩包,现在改为在服务端拆解为N份,根据构件数量每500个压缩为一个包,同时把数据转换为utf8array.然后生成一个索引文件A。 前端首先请求索引文件A。得到数据包的数量,然后进入本文重点。 主线程根据解析索引文件,知道了一共有N个数据包,然后开始启用worker下载。如下 主线程代码: var worker = new Worker("worker.js"); worker.postMessage({m: 模型N}); //向worker发送数据 worker.onmessage = function(evt) { //接收worker传过来的数据函数 var resulti = JSON.parse(evt.data); //开始解析resulti } 然后创建一个worker.js,多线程进行传输并解压。 onmessage = function(evt) { JSZipUtils.getBinaryContent( "数据包N.zip", { callback: function(err, data) { var zipdata = new JSZip(data); var filei = zipdata.file("数据包N.json"); postMessage(filei.asText()); } }); } 实际效果如下图 总结: 1、由于个人服务器原因,带宽就那么大,一个线程跑满和10个线程同时下载,完全没有效率的差异。所以下载起来是一样的。需要硬件支持才体现出传输的优化。 2、数据解析放到线程里面,确实会提升一些效率。 3、下一步优化,可以考虑在worker里面直接把数据转换成Utf8Array或者ArrayBuffer,PostMessage是可以移交这一类对象的控制权的。 原文https://blog.csdn.net/ztz87 -
使用ThreeJs搭建BIM模型浏览器,第九步-性能优化(2)
感谢网友给的建议。 我之前一直是使用threejs的102dev版本,以为geometry共享了就行了,但是这并不是threejs里面所说的Instance。在新的threejs版本中,新增加了几个很有意思的Instance类,这里重点挑InstancedMesh来说。InstancedMesh与使用一个geometry共享创建出Mesh是不一样的,InstancedMesh最终达到的效果是一次Drawcall,而共享geometry创建出来的Mesh并无此效果,效率跟不共享创建Mesh渲染性能上没有太大区别,只是可能会省点内存。 InstancedMesh是R110之后出现 官网简介: A special version of Mesh with instanced rendering support. Use InstancedMesh if you have to render a large number of objects with the same geometry and material but with different world transformations. The usage of InstancedMesh will help you to reduce the number of draw calls and thus improve the overall rendering performance in your application. 大量相同的几何,会减少绘图调用的次数,大量提高页面性能; 参考文档: http://www.qmodel.cn/threejs/three/docs/index.html#api/en/objects/InstancedMesh 查看示例: http://www.qmodel.cn/threejs/three/examples/?q=instanc#webgl_instancing_raycast 用法: var geometry = new THREE.SphereBufferGeometry( 0.2 ); var material = new THREE.MeshPhongMaterial( { flatShading: true } ); var amount=80 var count=amount*amount*amount; var mesh = new THREE.InstancedMesh( geometry, material, count ); var i = 0; var offset = ( amount - 1 ) / 2; var transform = new THREE.Object3D(); for ( var x = 0; x < amount; x ++ ) { for ( var y = 0; y < amount; y ++ ) { for ( var z = 0; z < amount; z ++ ) { transform.position.set( offset - x, offset - y, offset - z ); transform.updateMatrix(); mesh.setMatrixAt( i ++, transform.matrix ); } } } scene.add( mesh ); 点击获取InstancedId,官方示例 // move function onMouseMove( event ) { event.preventDefault(); mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1; mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; } // render function render() { raycaster.setFromCamera( mouse, camera ); var intersection = raycaster.intersectObject( mesh ); if ( intersection.length > 0 ) { // 获取当前实例的ID 索引 var instanceId = intersection[ 0 ].instanceId; // 根据当前的索引 获取当前的矩阵 mesh.getMatrixAt( instanceId, instanceMatrix ); // 更新矩阵 matrix.multiplyMatrices( instanceMatrix, rotationMatrix ); // 设置拾取当前几何的矩阵 mesh.setMatrixAt( instanceId, matrix ); // 更新矩阵 mesh.instanceMatrix.needsUpdate = true; } renderer.render( scene, camera ); } 实际测试性能效率,当使用InstancedMesh时,如上述代码。如果使用的是Mesh,我的渣渣电脑已经渲染不出来了。 如何解决使用InstancedMesh后,构件选择的问题?通过射线相交,可以取到Instance实例的索引ID,可使用ID来找到对应的构件。大家多看官方示例代码就可以了。 补充一点思路,如果网友有更好的方案也欢迎留言。 隐藏构件,把矩阵设置为0矩阵隐藏某个instanceId,0矩阵为[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,] 选择构件:可以考虑把InstancedMesh的geometry和被Pick到的Matrix构造一个新的InstancedMesh放在对应的位置,材质替换为高亮选中的材质,且把原来的Matrix设置为0矩阵,则实现隐藏。实际上目前120版本,Mesh和InstancedMesh还是有不少BUG的,建议全部使用InstancedMesh。 透明度和颜色设置:与选择构件同理,不过考虑到还原操作,对数据的管理会更复杂一些。这里会特征一些内存,效率上也有所降低,最终效果如何,全看编码人员的控制力了。 Merge之后也可以实现Drawcall的减少,且可以实现不同材质的合并。我就尝试过把一整个建筑模型合并成一个geometry,没错,性能提升巨大,且内存锐减一半!merge可以实现不同材质的几何直接合并,但是因为group的处理,还是会拆分为多次drawcall,没有太多作用。关于merge的总结可以看一下后面的介绍。 对比geometry.merge和Instance实例化、普通Mesh。以一个5万构件的全专业(建筑、结构、给排水、暖通、电气)的住宅塔楼为例(复用率还比较高的)。 Instance实例化几何体 Merge合并几何体 普通Mesh Material 相同 相同 可不同 Geometry 相同 可不同 可不同 单个控制 使用索引可实现 难以实现 容易实际 生成时间 快速 Merge需要计算消耗 一般。量大 渲染性能 较优 更优,极致减少drawcall 差 内存占用 极少 较少 较多 Drawcall次数 约1万次 1次 5万次 2020-9-11 关于merge又有了新的发现。 我们来试一下常规情况,直接使用mesh加入12万个构件。渲染有多么困难,降到fps=6 无视材质的merge,看一下对比,geometry合并到8个里面,有所提升,fps=18,drawcall一次没少。 1、为什么drawcall一次没少呢?因为group没有把同材质的合并起来。由些判断drawcall跟group有关。 2、但是为什么性能会有提升?因为视锥剔除不需要计算了啊! 再进一步,把材质全都合并起来,一个geometry就一个material,会达到什么效果呢?看下图。怎么样?fps109,是不是起飞了?当然,它的不易之处在于,首先要按材质先分组,不同material的也就别合并了,技术上可行,但是合并没好处。 -
使用ThreeJs搭建BIM模型浏览器,第九步-内存优化(1)
添加到场景的mesh,是通过geometry+materail生成的。如果场景内大量重复的构件,或者大量的构件的材质都是相同的,threejs提供这种方案节省内存:共享geometry 和materail。举例说明: 创建300个一样的圆,一般情况可能会写成下面这样 for (let i = 0; i < 300; i++) { let geometry = new THREE.BoxGeometry(10, 10, 10); let material = new THREE.MeshLambertMaterial({color: 0x00ffff}); let mesh = new THREE.Mesh(geometry, material); mesh.position.set(THREE.Math.randFloatSpread(200), THREE.Math.randFloatSpread(200), THREE.Math.randFloatSpread(200)); group.add(mesh); } 优化的方法是这样写: let geometry = new THREE.BoxGeometry(10, 10, 10); let material = new THREE.MeshLambertMaterial({color: 0x00ffff}); for (let i = 0; i < 300; i++) { let mesh = new THREE.Mesh(geometry, material); mesh.position.set(THREE.Math.randFloatSpread(200), THREE.Math.randFloatSpread(200), THREE.Math.randFloatSpread(200)); group.add(mesh); } 注意了,大佬的博客都点到为止,不会告诉你这样的弊端,你一试就知道在BIM浏览器这样做会存在的问题! 因为所有的构件都共享了materail,你怎么实现点击选中,点击选中之后给构件赋予颜色,结果所有同类构件都被渲染成相同的颜色!因为materail是共享的,对materail改个颜色会感觉被选中了一大片。 解决思路很简单,简单到我都没代码可分享:针对选中的构件(或者要赋予颜色的构件),临时替换一个materail即可(曲线救国)! 以前每个构件的纹理材质单独创建时,可以直接赋予颜色,如下图: 现在赋予颜色可以新建一个material给它。当然material新建之后也要管理好,以免内存膨胀;旧的材质纹理要缓存起来,用于恢复默认。相当于给构件弄了个新的包装盒,旧的也别丢掉,还要用的。 Revit自带的这个模型叫做Arch Link Model.rvt,它的材质有点多,有42种,即使这样也明显节省了20%的内存。如果是机电专业,管道设备几乎不在意材质,都是通用的材质纹理,一共就不需要创建几个material,估计会有惊喜。 这是第一步内存优化,针对材质纹理的。后面还会针对Revit导出的几何体进行分析,计算相同形状的构件,以实现文件的压缩和前端的提效。 -
使用ThreeJs搭建BIM模型浏览器,第八步-边缘线
这个辅助线条,一般称为辅助线,类似草图里的草稿线条。下面分别是有线条和没有线条的对比。 在Threejs对面的边缘添加线条,其实很简单。可以从官方示例中找到。 var geometry = new THREE.BoxBufferGeometry( 100, 100, 100 ); var edges = new THREE.EdgesGeometry( geometry ,89);//大于度才添加线条 var line = new THREE.LineSegments( edges, new THREE.LineBasicMaterial( { color: 0xffffff } ) ); scene.add( line ); https://threejs.org/docs/index.html#api/en/geometries/EdgesGeometry 注意:如果以三角面的方式去添加边缘线,往往会出现很多问题,需要依赖正确的法向量,只针对夹角大于等于90度的面添加边缘线效果会好一些,否则线条过多,对性能负担重,效果也很乱。 -
使用ThreeJs搭建BIM模型浏览器,第七步-测量
前面说到构件选择,实现了点击时与界面记录的焦点。《使用ThreeJs搭建BIM模型浏览器,第二步-构件选择》 主要的实现思路是:通过一个全局标记,记录前一次点击(作为起点)和后一次点击(作为终点),求两点之前的距离。 然后在终点附近插件一个标签。插件标签的方法前面也提到了。如意门:《使用ThreeJs搭建BIM模型浏览器 第三步 浮标》 1,点击,当然要加起点终点的全局变量记录一下。 mouseUp: function(event) { var vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5); vector = vector.unproject(this.camera); var raycaster = new THREE.Raycaster(this.camera.position, vector.sub(this.camera.position).normalize()); var intersects = raycaster.intersectObjects(this.scene.children); console.log(intersects[0].point);//这就是焦点。 } 2,在点击始末位置画个小球: function sphere(x, y, z, color, opacity, r) { var sphereGeo = new THREE.SphereGeometry(r, 10, 10); //创建球体 var sphereMat = new THREE.MeshLambertMaterial({ //创建材料 color: color, wireframe: false, transparent: true, side: THREE.DoubleSide, opacity: opacity }); var dwq = new THREE.Mesh(sphereGeo, sphereMat); //创建球体网格模型 dwq.position.set(x, y, z); //设置球的坐标 // this.scene.add(dwq); //将球体添加到场景 //this.mGroup.add(dwq); return dwq; } sphere(point.x, point.y, point.z, 0xFF0000, 1, 0.2) 3,在两点之间画条线: var material = new THREE.LineBasicMaterial({ color: 0x0000ff }); var geometry = new THREE.Geometry(); geometry.vertices.push(this.mp1); geometry.vertices.push(sel.point); var line = new THREE.Line(geometry, material); // scene.add(line); //mGroup.add(line); //建议用group来放小球和线 4,画个标签,看之前的文章,此处略。 5,看结果 6,优化的目标,可以在MouseOver的时候尝试把点自动的定位到面的边缘,会好用很多! -
使用ThreeJs搭建BIM模型浏览器 第六步 纹理贴图(材质)
上效果图 解决思路: Threejs上没啥好说的。看郭老师的博客:http://www.yanhuangxueyuan.com/Three.js_course/texture.html Revit上如何导出呢,不好意思,这次真不能贴核心代码了。。 -
使用ThreeJs搭建BIM模型浏览器 第五步 漫游
基础的键盘操作漫游其实很好解决。Threejs有一个FlyControl这个控制器,就是完成键盘操作的。 废话不说,上图 关键代码 <script src="js/controls/FlyControls.js"></script> //用FlyControl代替常用的OrbitControls controls =new THREE.FlyControls(this.camera); controls.movementSpeed = 100; //设置移动的速度 controls.rollSpeed = Math.PI / 6; //设置旋转速度 controls.autoForward = false; controls.dragToLook = true; -
使用ThreeJs搭建BIM模型浏览器 第四步 剖切
技术方案:threejs的剖切,是由renderer.clippingPlanes实现的。 this.clipHelpers = new THREE.Group(); this.clipHelpers.add(new THREE.AxesHelper(20)); this.globalPlanes = new THREE.Plane(new THREE.Vector3(1, 0, 0), 0); this.clipHelpers.add(new THREE.PlaneHelper( this.globalPlanes, 20, 0xff0000)); this.clipHelpers.visible = false; this.scene.add(this.clipHelpers); //创建一个剖切面 this.renderer.clippingPlanes = this.globalPlanes; // 显示剖面 this.renderer.localClippingEnabled = true; this.globalPlanes.constant = 5;//这个数值的变化将引起剖面的移动 效果如下: -
使用ThreeJs搭建BIM模型浏览器 第三步 浮标
实现效果如下。不用纠结UI为什么很面熟,因为我从某大品牌抄过来的,哈哈。 实现原理呢, 第一步,获取鼠标点击。 第二步,计算交插点。(这里要注意,如果是剖切之后,被剖切部分要舍弃,否则标记在隐藏构件上。 第三步,三维点转二维点。 第四部,在二维点中画一个div,样式控制为标记。 核心代码: 1、获取点击。请上看一遍文章。 2、三维转二维: //三维座转二维的计算。 toScreenPositionOfVector(x, y, z) { var vector = new THREE.Vector3(x, y, z); //calculate screen half size var widthHalf = 0.5 * this.renderer.context.canvas.width; var heightHalf = 0.5 * this.renderer.context.canvas.height; 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 }; } 3、增加标记的div AddRedMark(x, y, z) { var position = this.toScreenPositionOfVector(x, y, z); var adiv = document.createElement('div'); adiv.classList.add("redmark"); adiv.style.position = "absolute"; document.body.appendChild(adiv); adiv.innerHTML = '<img src="css/led_red.png" class="zoom" style="position: absolute;">'; adiv.style.display = ""; adiv.style.left = (position.x - 16) + 'px'; adiv.style.top = (position.y - 32) + 'px'; var markObj = { position: [x, y, z], mark: adiv }; this.redMarkDivs.push(markObj); } -
使用ThreeJs搭建BIM模型浏览器,第二步-构件选择
构件选择其实是鼠标点选的二维坐标与Canvas上的视角方向做射线投影,所穿过的所有构件,第1个即为点选。 以下为代码逻辑。部分变量可以根据您的需要,修改成你的变量。点击后把构件设置为半透明。 如果有需要,还可以在点击位置放一个标记。如isAddMark示意。 this.components 为所有构件的数组。 this.selComps 用来保存被点击的构件。 mouseUp(event) { var vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5); vector = vector.unproject(this.camera); var raycaster = new THREE.Raycaster(this.camera.position, vector.sub(this.camera.position).normalize()); var intersects = raycaster.intersectObjects(this.components); console.log(intersects) if (this.selComps.length > 0) { this.selComps[0].object.material.transparent = false; this.selComps[0].object.material.opacity = 1; this.selComps = []; } if (intersects.length > 0) { // console.log(intersects[0]); intersects[0].object.material.transparent = true; intersects[0].object.material.opacity = 0.5; this.showLog('点击' + intersects[0].object.uuid); //点击到的位置:intersects[0].point; if (this.isAddMark) { //todo 对标记进行监管。 viewer.sphere(intersects[0].point.x, intersects[0].point.y, intersects[0].point.z,0xFF0000,0.7); } this.selComps.push(intersects[0]); this.showProperty(intersects[0].object.rid); } } 如下图,点选一个门 在点击的焦点处放一个红色的气泡(请专家们不要纠结气泡太丑,没有时间去做浮标)。在BIM协同的时候,肯定会有类似的需求。