【threejs】从零到一的web3d时代
2021-02-06 06:18
标签:blank mes 数据 对象 script target 视觉 lse 摄像头 “未来早已到来,只是尚未流行。”——K.K 最近,由于业务的需求,笔者的团队终于迈进了3d时代。 其实,早在2017年,笔者便开始尝试前端的3d探索,因为当时主要的业务场景是运营活动,求新、创新便是活动的特性。不过,当时由于种种原因,最后未能落地,但未曾想到,会在3年后有了落地的时刻: 动态效果请查看这个demo: gis3dmodel 这也是笔者第一次在正式场景中做这种尝试,而且由于时效性特别强,中间过程伴随着各种各样的问题,尽管最终效果也没有尽善尽美,但也算是里程碑式的第一步,心想着记录下这次探索的积累,于是才有了此文。 其实,与当时相比,现在的业内的web3d环境已经有了很大的改观:前有U3D大量的商业案例成熟的开发模式,后有微软为babylon.js站台力挺,远不像当年只有3d领域three.js一家独奏的时代,不过比较下来,由于three.js海量的资料与文献,最终成为了笔者选择它作为团队撬开3d时代大门的钥匙(笔者这里直接略过了webgl,而直接选择了封装好一定功能上层库): 相比2d时代,除了原本的舞台(scene)、渲染器(renderer)之外,新加入了光源(light)、摄像头(camera),以用来在绘图区域,描述一个虚拟的3d场景。而原本在场景中的元素,也变得复杂了起来:有geometry来描述它们的几何形状,有material来描述他们的材质,然后共同作用与object3d及其子类上,最终成为虚拟3d空间中的2d或3d角色。 不过,要真正把three.js介绍完按笔者目前的熟悉程度,还是远远不够的,所以就以上做一个简单的介绍吧,然后回到笔者的实践中来。 在笔者的业务场景中,主要述求是需要在一个3d场景中基于GIS信息绘制2d地图对象,然后给它添加一些光柱、辉光等效果存在着一个和上图类似的基于GIS信息的2d地图对象,需要解决经纬度到虚拟三维空间坐标转换的问题,还有诸如渐变线条、镜头转动效果、辉光效果等等或模型上或交互上诸多问题的确认和解决,但整体来讲,其中最具有挑战性的却是一种新的工作流的建立:需要统一PM、UE/I、FE的想法,因为设计师提供的是一张静态的设计稿,而FE需要实现的则是包含比2d交互更多的3d交互效果(还有说镜头效果等)。 不过工作流的问题比较虚,还是简单整理点实际坑: 1.经纬度转换 其实拿到经纬度的时候,聪明的你一定会先有一个疑问,经纬度类似于球面坐标,你要在平面绘制,难道不需要先做墨卡托投影吗?笔者刚开始也有这个疑问,不过经过对数据源的了解,其实我们拿到的gis数据就是已经做过墨卡托投影的了,所以就不必画蛇添足呢。 另外,笔者需要将一连串经纬度数据,绘制到虚拟三维空间中去,而这些经纬度信息,由于本身的精度问题,直接绘制会导致整个地图在视觉上特别的小,那么如何解决呢?当然是做缩放咯。另外,笔者原以为需要对经纬度做球面坐标到平面坐标的转变,但是查阅资料后发现,并没有这个必要,于是就只需要进行缩放了,而缩放的逻辑也比较简单: 笔者先找到这些经纬度的最大值与最小值,在把他们按照原本的xy比例映射到x总长为100的区间上去,对原本都在31.XXX的维度进行放大,让他们能够比较清晰的呈现在画布上。 2.渐变线绘制 这个就更简单了,因为笔者使用了业界一个泛用性比较多的库——threejs,在它的官方demo中便提供了一种线性材质LineMaterial,更够让使用者自己定义Line的颜色: 值得注意的是,因为笔者的场景需要保证颜色的平滑过渡,所以笔者在前半段进行了颜色HSL值的递增,后半段进行递减,最终实现首尾闭合。同时,这个材质还有个特殊的地方是需要在RaF中进行逐帧更新: 3.光柱光晕辉光等效果 这个就是比较基础的贴材质的功夫了,边缘的辉光笔者使用了Tween函数和scale去动态改变sprite的大小,做到让两个辉光能够跟着不规则路径进行“匀速运动”。另一方面,对于光晕和光柱则更简单了: 只要给一个开口的圆柱体贴上材质,然后逐帧改变它的位置信息,就能够实现。 不过,在整个过程中,笔者也发现threejs的文档体系整体并不是很完善,对于很多属性和方法的描述也不是很清楚,都需要开发者基于个人经验去做出决策,算是个不大不小的缺点。同时,由于threejs api的过于原子性特点,也让笔者产生了希望基于threejs打造一个小而美的3d引擎的想法。 与此同时,笔者在完成了团队3d可视化能力从0到1的突破后,也需要将这些经验的方法输送给团队的其他同学,而想到此处,笔者在激动之余,更深深的觉得: “未来早已到来,只是尚未流行。” 写在最后 其实,本次的gis模型只是小试牛刀而已,紧接着就要去呈现数字城市了,其中笔者感觉又将扔掉多年的C捡了起来,开始拾掇GLSL,简单的来说,three.js提供的普通材质还不足以覆盖笔者面临的业务场景 图片来源于网络 更细致的纹理效果,很难依靠操作材质来实施,所以需要对纹理进行“定制”,而在web3d时代,webgl提供的定制材质的方式之一便是GLSL(OpenGL Shading Language),简单来说主要是使用着色器(shader),下面则是openGL中的两种着色器的工作流,主要就是从顶点着色器进行图元装配(primitive assembly),然后对它进行光栅化(Rasterization),之后再藉由片元着色器进行计算、混合、防抖,并进行一些必要裁剪(主要是超出显示区域的部分)。 最后,将数据推入帧缓冲区(frame buffer),再将它最终绘制到屏幕上。而GLSL正是能够通过C语言操作去顶点着色器和片元着色器,影响最终输出结果的语言,有了它才能够做出更精致的效果。 再写就扯远了,笔者团队对于web3d时代的探索才刚刚起步,希望有朝一日,也能够孵化出比肩业内优秀方案的web3d解决方案。 【threejs】从零到一的web3d时代 标签:blank mes 数据 对象 script target 视觉 lse 摄像头 原文地址:https://www.cnblogs.com/mfoonirlee/p/13052289.html// 经纬度的最小值和最大值需在外层完成取值
function zoomXY(coords) {
let per = (maxX - minX) / (maxY - minY);
return coords.map(({ x, y }) => ({
x: (x - minX) / (maxX - minX) * 100,
y: (y - minY) / (maxY - minY) * 100 * per
}))
}
for (let i = 0, len = coords.length; i
matLine.resolution.set(this._dom.clientWidth, this._dom.clientHeight) function getShiningCylinder(imgReousrce) {
const texture = new THREE.TextureLoader().load(imgReousrce);
texture.wrapS = THREE.RepeatWrapping;
texture.repeat.set(10, 1);
const geometry = new THREE.CylinderGeometry(radius, radius, height, 32, 1, true);
const material = new THREE.MeshBasicMaterial({
color: 0xffff00,
map: texture,
blending: THREE.AdditiveBlending,
side: THREE.DoubleSide,
transparent: true,
opacity: 1,
depthWrite: false,
})
// 因为时间仓猝,并未进行类的抽象,而是直接以闭包实现的丑陋代码还请见谅
const cylinder = new THREE.Mesh(geometry, material);
cylinder.rotation.x = Math.PI / 2;
cylinder.position.set(0, 0, height / 2);
cylinder.update = () => {
cylinder._counter += .5;
let angle = cylinder._counter * Math.PI / 180;
cylinder._texture.offset.x = angle;
if (cylinder.scale.y > 0) {
cylinder.scale.x += 0.06;
cylinder.scale.z += 0.06;
cylinder.scale.y -= 0.005;
cylinder.position.set(cylinder.position.x, cylinder.position.y, height / 2 * cylinder.scale.y);
} else {
cylinder.scale.set(1, 1, 1);
cylinder.position.set(cylinder.position.x, cylinder.position.y, cylinder._orginZ);
}
}
return cylinder;
}