关于前端:关于从入门threejs到做出3d地球这件事第五篇-以点成面矢量地球有图解

9次阅读

共计 5057 个字符,预计需要花费 13 分钟才能阅读完成。

对于从入门 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 弧度 是对应弧长为半径的角度, 已知一个圆的周长是 2πr
  2. 能够得出结论, 一个圆总共有 2πr ÷ r 个弧度, 也就是 弧度为一个圆。
  3. 一个圆有 360°角, 1°的角度对应的弧度就是 ÷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

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

正文完
 0