对于从入门three.js到做出3d地球这件事(第五篇: 以点成面矢量地球)

本篇效果图:

注: 自己画工较差哈哈哈哈哈哈...

一. geojson基本概念

     本篇咱们要绘制一个矢量地球, 那咱们先要晓得矢量地球是由什么组成的, 比如说要绘制'中国', 那么咱们只有晓得中国边界上所有的点的坐标, 再逐个把这些点链接起来就是一个中国的轮廓了, 因为每个点相距很近所以尽管咱们是用直线链接但仍然能够造成圆滑的球面成果, 简略了解geojson就是这样一组数据, 它外面有绘制各个国家轮廓所需的所有的的信息, 深刻了解你会发现geojson外面还有各种分组信息, 但咱们本篇次要讲绘制最根本的国家轮廓就不展开讨论了, 让咱们先绘制一款立体地图。
     这是我之前写过的一篇具体介绍geojson的文章,有趣味的同学能够去理解下, 会有助于你更好的了解地图: 记一次前端"揭开绘制地图的神秘面纱"分享会。
     本章设计的数学知识都是高级的, 再往后会波及到矩阵之类的常识, 到时候我也会用最艰深的形式解释给你听, 绝不止于概念而是最艰深的形式不便你了解, 本篇前面会有具体的经纬度转xyz的解说与图解。

二. 经纬度

     这里的概念很根底也很重要, 如果不相熟的话要认真看哦。

经度

     经度是地球上一个地点离一根被称为本初子午线的南北方向走线以东或以西的度数。本初子午线的经度是0°,地球上其它地点的经度是向东到180°或向西到180°, 做为本初子午线的那条线是人选进去的, 每15°一个时区(时区引起的bug我在之前分享过: 时区相干bug)。
     如图所示, 在计算机外面是用正负数来区东经与西经, 东经为负数西经为正数, 度数范畴是[-180, 180]

纬度

     过椭球面上某点作法线,该点法线与赤道立体的线面角,其数值在0至90度之间。位于赤道以北的点的纬度叫北纬,记为N;位于赤道以南的点的纬度称南纬,记为S。
     如图所示, 在计算机外面是用正负数来区北纬与南纬, 北纬为负数南纬为正数, 度数范畴是[-90, 90]

扩大常识: 测量经纬度

     在地球上任何地点,只有有只表,有根竹竿,一根卷尺,就可晓得当地经纬度。但表必须与该国规范时校对, 具体方法在百度百科有趣味的能够做下试验。

三. 还记得三角函数么

     大郎不要怕咱们毕业这么久也不必背诵了, 只有晓得怎么用就行, 咱们一起来温习一下:

名称公式
sin(∠A)a/c
cos(∠A)b/c
tan(∠A)a/b
  • 因为geojson外面存储的数据是经纬度, 所以等下咱们要用他把经纬度转换成坐标, 当然geojson也能够间接贮存坐标。

四. 弧度

     即两条射线从圆心向圆周射出,造成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆的半径时,两条射线的夹角的弧度为1。

五. 前端代码里的实现

     如果你想当然认为间接在前端代码里写入Math.sin(30)就会输入0.5那你就错了。

     因为在咱们的Math运算外面, 须要输出的是弧度, 这就是为啥我下面要复述弧度的概念, 所以想求sin(30)咱们要这样写Math.sin(30 * Math.PI / 180)

为了不便大家都能了解我还是简略写下推导过程
  1. 1弧度是对应弧长为半径的角度, 已知一个圆的周长是 2r
  2. 能够得出结论, 一个圆总共有 2r ÷ r 个弧度, 也就是2弧度为一个圆。
  3. 一个圆有360°角, 1°的角度对应的弧度就是 2÷360, 也就是 /180
  4. 所以下面所说的sin(30°)就是Math.sin(30 * Math.PI / 180)

六. 绘制一个立体世界

     学到这里我默认你曾经理解了geojson的相干概念, 外面一个国家可能有多个轮廓并且相互不交界, 咱们要把它们解决成数组, 也就是这句country.geometry.type === "Polygon"
/cc_map_3d_pro/src/components/cc_map.vue

import worldGeo from "../assets/geojson/world.geo";.../    initEarth() {      const R = envConifg.r;      worldGeo.features.forEach((country) => {        if (country.geometry.type === "Polygon") {          country.geometry.coordinates = [country.geometry.coordinates];        }        var line = countryLine(R, country.geometry.coordinates);        this.scene.add(line);      });    },

世界的geojson能够在我的我的项目里找到github查看。

  • 下面咱们把半径, 点位传给了countryLine办法来解决, 并且把他们都退出到了环境外面, 前面会讲把图形对象都别离放入不同的组外面, 这里先这样不扩散知识点。

七. 用线

/cc_map_3d_pro/src/utils/countryLine.js这个办法里咱们专门绘制国家的轮廓。

import * as THREE from 'three';function countryLine(R, polygonArr) {  let group = new THREE.Group();  polygonArr.forEach(polygon => {    let pointArr = [];    polygon[0].forEach(elem => {      pointArr.push(elem[0], elem[1], 0)    });    group.add(line(pointArr));  });  return group;}
new THREE.Group()

     在three.js中文网接摘下来的原话, 它简直和Object3D是雷同的,其目标是使得组中对象在语法上的构造更加清晰。

     假如我当初生成两个正方体geometry, 别离命名为a 与 b, 那么我不必上面的写法

this.scene.add(a);this.scene.add(b);

而是能够创立一个组:

const group = new THREE.Group();group.add(a)group.add(b)this.scene.add(group);

再具体的咱们后续章节会具体聊

  • 下面咱们依赖了一个名为line的办法, 这个办法是这次的一个重要的知识点。

八 . 绘制线段不简略(line办法)

function line(pointArr) {  let geometry = new THREE.BufferGeometry();  let vertices = new Float32Array(pointArr);  let attribue = new THREE.BufferAttribute(vertices, 3);  geometry.attributes.position = attribue;  let material = new THREE.LineBasicMaterial({    color: 0x00aaaa //线条色彩  });  let line = new THREE.LineLoop(geometry, material);  return line;}
1. 传入的参数pointArr;

     由countryLine办法可知, 这里是[x1, y1, z1, x2, y2, z2, x3, y3, z3]这样的一系列坐标, 你可能感觉这种模式不太合乎js的思维, 然而它合乎webgl或是svg的思维, 对于webgl的常识后续会在解说着色器的时候会让你明确的, 当初不必太深钻研因为这里学识很深。

2. new THREE.BufferGeometry();

     应用BufferGeometry能够无效缩小向GPU传输上述数据所需的开销, 一个国家均匀有几百组, 所以再用一般的Geometry会变的很卡, 大家释怀后续在 优化 相干的篇幅外面我会对立讲一遍, 这里你能够临时了解为一种three.js转换的数据流

3. new Float32Array();

     js原生常识: Float32Array类型数组代表的是平台字节程序为32位的浮点数型数组(对应于 C 浮点数据类型), Float32Array在数据量较大时性更更好一些, 并且更合乎webgl的参数规范, 对于这类TypedArray是个大课题, 具体的我会在着色器章节好好聊聊 。

4. new THREE.BufferAttribute();

     这个类用于存储与BufferGeometry相关联的 attribute(例如顶点地位向量,面片索引,法向量,色彩值,UV坐标以及任何自定义 attribute), 利用 BufferAttribute能够更高效的向GPU传递数据。

  1. 第一个参数: 是数据源, 也就是下面解决好的坐标数组。
  2. 第二个参数: 数据被存储为任意长度的矢量, 这里传的是3, 能够了解为每三个数据为一组, 也就是x1, y1, z1一组, x2, y2, z2一组, 以此类推。
5. new THREE.LineBasicMaterial();

     根底线条材质, 也就是业余绘制线条的, 能够调节色彩与粗细, 以及线头的样子。

6. geometry.attributes.position = attribue

     这个写法看起来很粗鲁, 它的意思就是把图形的地位信息, 替换成咱们解决好的数组, 也就是为图形设置每个点的地位。

7. new THREE.LineLoop();

     环线也就是首尾相连的线, 就向咱们每次创立一个矩形一样, 这个办法创立了一条环线。

因为咱们把z轴的数值都传的0, 所以才会呈现下图这种立体地图

九. 经纬度转换到笛卡尔坐标系(实践)

     也有不少是间接做这种立体地球的, 但咱们的系列文章是要学习圆形地球的, 所以咱们要把经纬度坐标转换成球面坐标
     咱们就从x y z逐个开始钻研。

最简略的y轴

     y轴其实只与纬度无关, 最简略就能够求进去如下图所示:

  • 球体上的一个点, 这个点间隔圆心的间隔是圆的半径r, 当初咱们要求这个点间隔zy立体的间隔。

  • 这个点的x与z不肯定为0, 所以作垂线不肯定落在x轴上, 然而不论如何作垂线这条线段与xz立体的夹角是不会变的, 而这个夹角就是纬度, 所以由此可知咱们已知斜边的长度为r, 三角函数sin(纬度) = 对边 / 斜边, 对边就是y轴的数值, 咱们把应用左右都乘以r, 最终得出:

    sin(纬度) 乘 r = y
须要计算的x轴

     x轴须要点计算咱们一步一步来, 每步都有图解:


下面演示的是, 咱们能够把这个看成是一个立方体, 一个已知对角线长度为r的立方体, 接下来咱们就能够把这个立方体独自拿进去钻研, 能够脱离这个坐标系了。

现已知立方体对角线长度为r, 高为y轴坐标, 接下来应用纬度求出底面对角线。


咱们采纳与求y轴差不多的形式求出x1的长度:

cos(纬度) 乘 r = x1

由图可知经度是下方沿yz立体开展的对角线的角度, 咱们能够用sin的对边比斜边求出长度。

sin(经度) * x1 = x

把x1的公式带入进来:

x = sin(经度) 乘 cos(纬度)
与x对应的z轴


与x雷同的原理,只是这里咱们用cos的临边比斜边。

z = cos(经度) 乘 cos(纬度)

十. 经纬度转换到笛卡尔坐标系(代码)

实战的时候别忘了, 先把经纬度转成弧度

// 经纬度转坐标function lon2xyz(R, longitude, latitude) {    const lon = longitude * Math.PI / 180;    const lat = latitude * Math.PI / 180;    const x = R * Math.cos(lat) * Math.sin(lon);    const y = R * Math.sin(lat);    const z = R * Math.cos(lon) * Math.cos(lat);    return { x, y, z };}export default lon2xyz;
一些其余教程

     一些其余教程会要求 经度取反, 同时把x与z进行颠倒,不举荐那种写法, 咱们就按失常的思路来即可。

十一. 圆圆的地球

     通过不懈的致力咱们终于援救了圆球, 让咱们看看他还缺什么吧:

从上图咱们能够看出, 其实问题还是挺多的, 比方线条之间相互遮蔽, 咱们应该让这个地球不可透视, 以及临时这个地球不可点击, 并且真实度上做的不够, 真是技术路漫漫那。

end

     下一篇就要解说如何在地图上打点以及为地球增加光晕等等成果, 到此时这个系列文章还不到一半哦, 射线拾取国家以及三角抛分方面的常识也会陆续付出水面, 精彩乏味的常识还在前面, 心愿与你一起提高。