three.js效果之热力图和轨迹线
Posted 桔子桑
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了three.js效果之热力图和轨迹线相关的知识,希望对你有一定的参考价值。
1.热力图
开始的时候,是用一个网上找的canvas画渐变热点的demo,原理就是给定顶点坐标,然后画圆,颜色使用渐变色,根据权重决定渐变的层数(红色->橙色->绿色) 。
但是终究觉得这种方法不仅繁琐,而且画出来的效果不够自然。
后来发现有一个开源库heatmap效果很好,它是这样用的(官方demo地址链接):
var heatmapInstance = h337.create({ container: document.querySelector(\'.heatmap\') }); var data = { max: max, data: points }; heatmapInstance.setData(data);
max值为所有points中权重属性的最大值。
看到这里,那我们要怎么在three.js中去使用heatmap呢,他是用dom去实例化heatmap对象的啊。
不用担心,我们可以creatElement(\'div\'),然后在这个dom对象上实例化heatmap对象,并且
var canvas = heatmapdiv.getElementsByTagName(\'canvas\')[0];
获取绘制后的canvas对象。
let heatMapGeo = new THREE.PlaneGeometry(120,90); let heatMapTexture = new THREE.Texture(canvas); let heatMapMaterial = new THREE.MeshBasicMaterial({ map: heatMapTexture, transparent:true }); heatMapMaterial.map.needsUpdate = true; var heatMapPlane = new THREE.Mesh(heatMapGeo,heatMapMaterial); heatMapPlane.position.set(0,0.1,0); heatMapPlane.rotation.x = -Math.PI/2; this.scene.add(heatMapPlane);
这样,用heatmap绘制的热力图就添加到了three.js创建的场景中去了。
2.轨迹线
轨迹线不难想,利用three.js提供的曲线来绘制,但是会存在如下两个问题:
q1.three.js的曲线貌似只能一次性整条绘制出来,没有api显示可以按百分比绘制曲线,所以只好自己写shader实现
q2.webgl渲染器不支持线宽属性(three.MeshLine支持线宽,不过没有研究是否支持按百分比绘制);
q3.着色器里面可以针对点设置pointsize来实现点的大小(间接实现曲线的宽度控制),但是点是二维的,默认存在于x-y平面,所以在x-z平面看的时候,如果点的数量不够多那么就会出现断断续续的效果,但是采样的点数量足够多又会影响性能。
上述的问题不能解决的话,后续的曲线样式优化(渐变)就无从谈起。
期间我想过,既然点存在于x-y平面,那么我们就将x-z平面的轨迹放到x-y平面来绘制,最后将这条线绕x轴旋转90度,但是因为对点进行处理的时候,首先正方形的点->圆点->渐变(抗锯齿),最后,结果如下:
看着好像成功了,但是由于深度检测机制(现在想来,是不是可以设置取消这条线的深度检测机制)的存在,某些角度下,这条线的本质(n个大号的点拼接)就变得很明显了,你会明显地看到这条线是由进行抗锯齿处理后的无数个点组成。
哎,好像又遇到困难了啊。
后来一想,既然three.js中一条线很细,那么10条线,100条线在一起呢?只要间距足够小,它们看上去就是一根线,一根麻绳!!!
PS:
回过头来看我当时的这个处理思路,其实在性能上还是有很大的提升空间的,至少增加了内存消耗;
当然后续查看meshline源码,你会发现他是将顶点作偏移绘制三角面来实现有宽度的“线”,并且每个顶点还传入了顶点的索引值属性,可用于进度的控制;
照着这个思路, 写了一个FatLine类:
import * as THREE from \'three\' /** * Author:桔子桑 * Time:2019.10.12 */ const vs =` varying vec3 iPosition; void main(){ iPosition = vec3(position); gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x,0.2,position.z,1.0); } `; const fs = ` uniform float time; varying vec3 iPosition; uniform float alpha; void main( void ) { if(iPosition.y > time){ discard; }else{ gl_FragColor = vec4(0.813,0.124,0.334,alpha); } } `; function FatLine(vertices,width,scene){ this.width = width; this.vertices = vertices; this.start = 0; this.scene = scene; this.linearr = []; this.lines = []; } function createMaterial(vs, fs, start) { var attributes = {}; var uniforms = { time: {type: \'f\', value: start}, size:{type:\'f\',value:25.0}, }; var meshMaterial = new THREE.ShaderMaterial({ uniforms: uniforms, defaultAttributeValues : attributes, vertexShader: vs, fragmentShader: fs, transparent: true }); return meshMaterial; } FatLine.prototype.draw = function() { var size = this.vertices.length; var length = Math.floor(this.width/2); var vm = this; for(var j =0;j<length;j++){ var lineadd = []; var linereduce = []; for ( var i = 0; i <size; i ++ ) { var Vector3 = this.vertices[i], x = Vector3.x, y = Vector3.y, z = Vector3.z; var zadd = z+j*0.001; var zreduce = z-j*0.001; lineadd.push( new THREE.Vector3(x,y,zadd )); linereduce.push( new THREE.Vector3(x,y,zreduce )); } this.linearr.push(lineadd); this.linearr.push(linereduce); }; this.linearr.push(vm.vertices); var pointsize = this.vertices.length * 10; for(var k = 0,size=this.linearr.length;k<size;k++){ var vertices = this.linearr[k]; var alpha = (Math.floor(size/2) - Math.floor(k/2))/Math.floor(size/2); var curve = new THREE.CatmullRomCurve3(vertices); var geometry = new THREE.Geometry(); geometry.vertices = curve.getPoints(pointsize); var material = createMaterial(vs,fs,vm.start); material.uniforms.alpha = {type:\'f\',value:alpha}; var line = new THREE.Line(geometry, material); this.lines.push(line); this.scene.add( line ); } } FatLine.prototype.animate = function(speed,callback){ var time = this.lines[0].material.uniforms.time.value; for(var i = 0,length=this.lines.length;i<length;i++){ var line = this.lines[i]; line.material.uniforms.time.value +=speed||0.3; }; if(callback){ callback(time); } } export default FatLine;
你可以看到,着色器中还又一个uniform变量time,这个是用来在FatLine开启动画的时候,随着时间的进展来逐步绘制的。
ok,看到这你以为就完了?no!!!
刚开始的时候,按照常规当time++的时候,在x-z平面上轨迹点,我们判断x<time是否来控制曲线的绘制进度,但是一个问题出现了,人员轨迹点可能出现在一个房间兜圈子的情况(实际也是如此),这样就会存在第2个点和第200个点都满足x<2.0,那么总不能根据时间,第2秒的时候,直接把200秒时候的点也绘制出来了吧,这是不符合常理的。
在下班回家的路上,我想到了一个问题,在三维空间,一个点有(x,y,z)三个维度的坐标信息数据传进了着色器里面,但是我们的人员轨迹只会存在于场景的x-z平面,所以这个y坐标值在着色器里面是没有用到的,哈哈,那么这个y值可以充当时间维度值,第一个点y=1,第二个点y=2,第三个点y=3...,如此一来,当time++的时候,我们只要判断y<time就可以实现在时间维度上的控制了。
并且FatLine的animate函数还提供了一个回调函数,参数值是当前的time值,所以你可以用这个time值来绘制具体的点:
if(this.FatLine){ function addpoint(time){ for(var i = 0,length=vm.vertices.length;i<length;i++){ var point = vm.vertices[i]; if(Math.abs(point.y-time)< 0.1){ vm.addpoint(point.x,point.z); } } } this.FatLine.animate(0.2,addpoint); }
每一帧都会animate一下,也就是time++,并且判断进度是不是到了指定的某个点上,如果到了,那么就顺便把这个点也画出来,就像上述的动图一样。
最终可控制粗细和运动速度的曲线就完成了。
以上是关于three.js效果之热力图和轨迹线的主要内容,如果未能解决你的问题,请参考以下文章
Three.js地球开发—3D经纬度等比地图,3D飞行航线最终效果