对于从入门three.js到做出3d地球这件事(第六篇: 装璜地球, 打点等操作)

本章让咱们一起为咱们孤独的地球线条增加点装璜 (前期会绘制太阳系)。

一. 星空背景

     始终以来咱们的地球背景都是黑黑的, 这次咱们就要让星空的图片作为地球的背景。

     这个星空背景也要是3d的, 并且是把地球包裹在其内的, 能够把这个星空设想成一个球体内侧的贴图, 咱们的'相机'就处于这个球体外部。

上网找一张星空的背景图, 图片要宽度比高度大一些的, 否则显示的不清晰:

/cc_map_3d_pro/src/config/earth.config.js

export default {    r: 80, // 半径    bg: require("../assets/images/星空.jpg"), // 背景图 (新增)    earthBg: require("../assets/images/地图加文字.png"), // 贴图门路}

在生命周期函数外面咱们新增初始化背景函数

mounted() {    // ...    this.initBg();  },

这里咱们要做的是绘制一个大大的圆球, 包裹着咱们的地球与相机, 并且将其纹理设置在外部。

    initBg() {      // 加载星空纹理      const texture = this.textureLoader.load(envConifg.bg);      // 生成球体      const sphereGeometry = new THREE.SphereGeometry(1000, 50, 50);      // 调转球体正反      sphereGeometry.scale(-1, 1, 1);      // 赋予纹理贴图      const sphereMaterial = new THREE.MeshBasicMaterial({ map: texture });      // 生成几何球体      this.sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);      // 放入场景内      this.scene.add(this.sphere);    },
注意事项
  1. new THREE.SphereGeometry(1000, 50, 50)第一个参数是球体的半径, 这个半径肯定不要大于咱们的远视点, 否则会看不见这个球了。
  2. 球体的半径不能太小, 否则会有一种星星间隔地球很近的样子, 并且之后咱们会尝试绘制太阳系这个球体太小就绘制不下了,上面我会展现一下设置过小的成果。
  3. sphereGeometry.scale(-1, 1, 1)你能够想想是把一个皮球从内而外的翻转过去。

失常的成果的不同角度:

外层球体的大小超过远视角:

外层球体的大小等于地球半径:

二. 地球透光性

     下面的效果图都有一个显示问题, 就是咱们的地图是中空的, 并且能够透过一边看到另一边, 咱们想要让这个地球看起来更像实体, 咱们当初就须要将一个球体放到地球外面, 通过管制这个球体的透明度来管制地球的透明度。

    initInside() {      const sphereGeometry = new THREE.SphereGeometry(envConifg.r - 1, 50, 50);      const sphereMaterial = new THREE.MeshBasicMaterial({        color: this.bgColor,        opacity: 0.9,        transparent: true,      });      this.InsideSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);      this.scene.add(this.InsideSphere);    },
  1. 要留神须要设置transparent: true才能够设置透明度。
  2. 之所以把内球的半径设置为envConifg.r - 1是因为怕它遮蔽咱们国家的连线。
  3. 如果要扭转内球的色彩能够应用this.InsideSphere.material.color.set(this.bgColor)办法。

看下两种成果吧:

三. 地球光晕(精灵)

     这种非凡的材质它会始终面向相机, 也就是咱们不论转到什么角度去看这个模型, 他都是正对着咱们的屏幕, 精灵不会投射任何暗影。

     new THREE.Spritenew THREE.BoxGeometry性质上差不多, 只是他会创立进去一个始终面向屏幕的精灵几何体。

     THREE.SpriteMaterial一种应用Sprite的材质, 与THREE.Sprite是一对, 能够调整色彩, 透明度等等。

先筹备一张相似上面的光晕图:

initSprite() {   const texture = this.textureLoader.load(envConifg.haloBg);   const spriteMaterial = new THREE.SpriteMaterial({     map: texture,     transparent: true,     opacity: 0.7,   });   const sprite = new THREE.Sprite(spriteMaterial);   sprite.scale.set(envConifg.r * Math.PI, envConifg.r * Math.PI, 1);   this.scene.add(sprite); },
  1. 判若两人的引入'纹理贴图'。
  2. 只是他要用THREE.SpriteMaterialTHREE.Sprite来赋予纹理贴图。
  3. 设置sprite.scale因为精灵图广泛很小所以须要等比增大一下, 这个数不肯定是, 依据本人的实景状况输出。

     比方精灵图能够用在射击类游戏的瞄准星, 还比方3d游戏里游戏人物会头顶本人的名称, 这个名字如果不面向咱们的屏幕那必定看不清楚了。

四. ps批改精灵图色彩

     如果感觉我的光晕不难看能够本人入手关上ps批改一下:

导入图片

点击替换色彩

抉择本人喜爱的色彩

五. 地球打点

     平时咱们须要在地球上做一些标记, 并且这些标记有大有小, 色彩各异, 形态各异, 重要的是这个图形须要与地球核心点射出的半径线, 放弃平行能力齐全展现的地球表面。

     筹备一张打点图, 最好是红色的不便咱们当前为其赋予其余色彩:

          咱们在地球组件外面增加一个markSpot办法, 此办法反对多个与单个对象的解决, 接管数组或是对象, 内部应用ref的形式调用这个办法:

    markSpot(obj) {      if (obj instanceof Array) {        obj.forEach((item) => {          this.object.add(spot(item));        });      } else {        this.object.add(spot(obj));      }    },
  1. this.object是一个new THREE.Object3D生成的容器, 能够贮存多个Mesh为一组造成一个整体。
  2. spot使咱们接下来要实现的打点办法。
  3. 接管的obj是个配置项, 外面包含打点的色彩、大小、透明度、形态等等的配置。

在应用组件时如此编写:

 <cc-map ref="map"></cc-map>// ...initMarks() {   const arr = [     {       longitude: 116.2,       latitude: 39.56,       color: "red",     },     {       longitude: 76.2,       latitude: 49.56,       color: "blue",     },    ];   this.$refs.map.markSpot(arr); },
开始编写打点办法

/cc_map_3d_pro/src/utils/sport.config.js打点的一些默认属性

const config = {    size: 7,    opacity: .8,    color: 'yellow',    url: require('../assets/images/打点.png')}export default (options) => {    return { ...config, ...options }}

/cc_map_3d_pro/src/utils/spot.js, 先要把配置项导进来并且设置默认值。

import * as THREE from "three";import envConifg from '../config/earth.config';import lon2xyz from './lon2xyz';import mergeConfig from './sport.config';const geometry = new THREE.PlaneBufferGeometry(1, 1);const textureLoader = new THREE.TextureLoader();export default function spot(options) {    const { longitude, latitude, color, opacity, size, url } = mergeConfig(options);    const texture = textureLoader.load(url);    const material = new THREE.MeshBasicMaterial({        color,        opacity,        map: texture,        transparent: true,    });    const mesh = new THREE.Mesh(geometry, material);    const coord = lon2xyz(envConifg.r * 1.01, longitude, latitude)    mesh.scale.set(size, size, size);    mesh.position.set(coord.x, coord.y, coord.z);    return mesh;}
  1. 咱们设置了默认的配置属性, 并与用户传进来的配置进行了合并。
  2. 将传入的经纬度转换成笛卡尔坐标系的 x, y, z 值。
  3. 大小通过放大与放大图形实现。
  4. THREE.PlaneBufferGeometry(1, 1)生成一个立体的几何体, 外面的参数是宽与高, 之所以不必这里的宽高来操作图片的大小, 是因为这里设置不如scale里灵便。
  5. 获取经纬度时envConifg.r * 1.01是因为怕它与地球上的线重合。

成果如下:

翻转打点

     地位尽管对了, 然而角度这样必定不行, 当初咱们要计算他须要旋转多少度能力与半径线垂直, 这里开始波及一些数学知识了。

第一步: normalize归一化

     比方现有从圆心登程的两条线段, 不论两条线段有多长它两个的夹角的度数是不会变动的, 所以在有些时候计算一些比例或是角度时, 如果数据的长度并不影响计算结果那么咱们会把它归一下解决后再进行计算。

     归一化做的事是把你的向量变成一条长度为1的向量, 比方你有一条三维向量长宽高为x, y, z, 解决过后就会变成x*x + y*y + z*z = 1

  console.log(new THREE.Vector3(10, 10, 10))  console.log(new THREE.Vector3(1, 1, 1).normalize())

如图所示, 红色为x, y, z值都为6的向量, 归一化后变为蓝色线段示意的向量。

第二步: XOY立体的法线
“法线(normal line),是指始终垂直于某立体的直线

     以后咱们的打点的立体默认是在XOY立体上的, 所以它的法线能够了解成中垂线是z轴, 比方是(0, 0, z)这条线, z是多少都没关系比方你能够写成new THREE.Vector3(0, 0, 999).normalize(), 但这里举荐间接写new THREE.Vector3(0, 0, 1)能够节约一些计算的性能。

第三步: 利用 四元数 翻转立体

     四元数这个数学概念我讲不好, 但能够带你简略了解, 它用于计算某个点, 绕某条向量旋转'c度'后所在的坐标, 他的概念与复数(i*i = -1)很相似, 四元数在几何学上可写作i*i = j*j = k*k = i*j*k = -1, 通过数学的计算能够得出旋转后的坐标的一套公式。

     利用四元数设置旋转角度quaternion.setFromUnitVectors('向量1', '向量2')的参数须要应用归一化解决的, 向量1旋转到方向向量2所需的旋转角度为n, 则使指标转转n度。

     方才咱们得悉咱们打点图形的法线(0, 0, 1), 从圆心登程达到该点的坐标是(x, y, z), 那么咱们管制其法线旋转, 使法线与(x, y, z)向量重合, 则(x, y, z)向量就会同样垂直与打点立体, 这样打点图形就会是与地球相切的成果。

    const coordVec3 = new THREE.Vector3(coord.x, coord.y, coord.z).normalize();    const meshNormal = new THREE.Vector3(0, 0, 1);    mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3);

全副代码
import * as THREE from "three";import envConifg from '../config/earth.config';import lon2xyz from './lon2xyz';import mergeConfig from './sport.config';const geometry = new THREE.PlaneBufferGeometry(1, 1);const textureLoader = new THREE.TextureLoader();export default function spot(options) {    const { longitude, latitude, color, opacity, size, url } = mergeConfig(options);    const texture = textureLoader.load(url);    const material = new THREE.MeshBasicMaterial({        color,        opacity,        map: texture,        transparent: true,    });    const mesh = new THREE.Mesh(geometry, material);    const coord = lon2xyz(envConifg.r * 1.01, longitude, latitude)    mesh.scale.set(size, size, size);    mesh.position.set(coord.x, coord.y, coord.z);    const coordVec3 = new THREE.Vector3(coord.x, coord.y, coord.z).normalize();    const meshNormal = new THREE.Vector3(0, 0, 1);    mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3);    return mesh;}
只有写个定时器, 一直扭转打点的大小与色彩, 就能够做出动静的打点成果了, 这个前面章节专门讲动画的时候再对立款式

六. 地球光柱

     有些时候咱们须要在地球上的某一坐标亮起一道光柱, 由光柱的高度来示意此处资产的密度, 或是商品的销量。

圆锥体 THREE.CylinderGeometry
  const geometry = new THREE.CylinderGeometry(1.5, 2, 5, 100, 100);  const material = new THREE.MeshBasicMaterial({      color: 'red'  })  const mesh = new THREE.Mesh(geometry, material);
  1. CylinderGeometry第一个参数上圆半径也就是圆锥的顶端, 设置为0就是一个尖尖的锥子。
  2. CylinderGeometry第一个参数下圆半径也就是圆锥的底座。
  3. 第三个个参数是圆锥的高度。
  4. 第四个圆柱侧面四周的分段数,默认为8。
  5. 第五个圆柱侧面沿着其高度的分段数,默认值为1。
  6. 留神圆柱默认中心线是在y轴上, 应用四元数翻转时有用。

封装成函数

/cc_map_3d_pro/src/utils/column.config.js

const config = {    size: 7,    opacity: .8,    color: 'yellow',}export default (options) => {    return { ...config, ...options }}

/cc_map_3d_pro/src/utils/column.js

import * as THREE from "three";import envConifg from '../config/earth.config';import lon2xyz from './lon2xyz';import mergeConfig from './column.config';export default function column(options) {    const { longitude, latitude, color, opacity, size, url } = mergeConfig(options);    const material = new THREE.MeshBasicMaterial({        color,        opacity,        transparent: true,        side: THREE.DoubleSide,    });    const coord = lon2xyz(envConifg.r * 1.01, longitude, latitude)    const coordVec3 = new THREE.Vector3(coord.x, coord.y, coord.z).normalize();    const geometry = new THREE.CylinderGeometry(0, 3, size);    const mesh = new THREE.Mesh(geometry, material);    return mesh}

再次利用四元数

     介于圆锥的个性, 咱们须要让圆锥的核心高线与球面向量重合, 所以圆锥的归一化向量能够抉择(0, 1, 0):

mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), coordVec3);

效果图:

全副代码
import * as THREE from "three";import envConifg from '../config/earth.config';import lon2xyz from './lon2xyz';import mergeConfig from './column.config';export default function column(options) {    const { longitude, latitude, color, opacity, size, url } = mergeConfig(options);    const material = new THREE.MeshBasicMaterial({        color,        opacity,        transparent: true,        side: THREE.DoubleSide,    });    const coord = lon2xyz(envConifg.r * 1.01, longitude, latitude)    const coordVec3 = new THREE.Vector3(coord.x, coord.y, coord.z).normalize();    const geometry = new THREE.CylinderGeometry(0, 3, size);    const mesh = new THREE.Mesh(geometry, material);    mesh.position.set(coord.x, coord.y, coord.z);    mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), coordVec3);    return mesh}
只有写个定时器, 一直扭转柱子的色彩与高度, 就能够做出动静的锥闪成果了, 这个前面章节专门讲动画的时候再对立款式
地球飞线须要之后独自一张讲了, 因为波及的常识有点难度, 须要具体的布局一下。

end

     下一篇咱们要聊聊如何辨别各个国家, 鼠标悬停时能够呈现以后国家的提示框, 这外面尽管会有不少数学概念, 但不要畏惧, 我会具体的把我从0开始了解这些概念的过程都描绘出来,心愿与你一起提高。