???? 一直以来我们的地球背景都是黑黑的, 这次我们就要让星空的图片作为地球的背景。
???? 这个星空背景也要是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);
},
new THREE.SphereGeometry(1000, 50, 50)
第一个参数是球体的半径, 这个半径一定不要大于我们的远视点
, 否则会看不见这个球了。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);
},
transparent: true
才可以设置透明度。envConifg.r - 1
是因为怕它遮盖我们国家的连线。this.InsideSphere.material.color.set(this.bgColor)
方法。看下两种效果吧:
精灵
)???? 这种特殊的材质它会始终面向相机
, 也就是我们不管转到什么角度去看这个模型, 他都是正对着我们的屏幕, 精灵不会投射任何阴影。
???? new THREE.Sprite
与new 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);
},
THREE.SpriteMaterial
与THREE.Sprite
来赋予纹理贴图。sprite.scale
因为精灵图普遍很小所以需要等比增大一下, 这个数不一定是π
, 根据自己的实景情况输入。???? 比如精灵图可以用在射击类游戏的瞄准星
, 还比如3d游戏
里游戏人物会头顶自己的名称
, 这个名字如果不面向我们的屏幕那肯定看不清楚了。
???? 如果感觉我的光晕
不好看可以自己动手打开ps
修改一下:
替换颜色
???? 平时我们需要在地球上做一些标记, 并且这些标记有大有小, 颜色各异, 形状各异, 重要的是这个图形需要与地球中心点射出的半径线, 保持平行才能完全展示的地球表面。
???? 准备一张打点图, 最好是白色的方便我们以后为其赋予其他颜色:
???? ???? 我们在地球组件里面添加一个markSpot
方法, 此方法支持多个与单个对象的处理, 接收数组或是对象, 外部使用ref
的方式调用这个方法:
markSpot(obj) {
if (obj instanceof Array) {
obj.forEach((item) => {
this.object.add(spot(item));
});
} else {
this.object.add(spot(obj));
}
},
this.object
是一个new THREE.Object3D
生成的容器, 可以储存多个Mesh
为一组形成一个整体。spot
使我们接下来要实现的打点方法。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;
}
x, y, z
值。THREE.PlaneBufferGeometry(1, 1)
生成一个平面的几何体, 里面的参数是宽与高, 之所以不用这里的宽高来操作图片的大小, 是因为这里设置不如scale
里灵活。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);
CylinderGeometry
第一个参数上圆半径
也就是圆锥的顶端, 设置为0就是一个尖尖的锥子。CylinderGeometry
第一个参数下圆半径
也就是圆锥的底座。/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
}
???? 下一篇我们要聊聊如何区分各个国家, 鼠标悬停时可以出现当前国家的提示框, 这里面虽然会有不少数学概念, 但不要畏惧, 我会详细的把我从0开始理解这些概念的过程都描绘出来,希望与你一起进步。
注释1:上图整个大背景是这个网页的全部尺寸,中间的小框才是浏览器中的可见尺寸...
data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,可以...
John Au-Yeung 来源:medium 译者:前端小智 有梦想,有干货,微信搜索 【大迁世...
Redis 官方在 2020 年 5 月正式推出 6.0 版本,提供很多振奋人心的新特性,所以...
1.HTML5的内容类型 内容类型 描述 内嵌 向文档中添加其他类型的内容,例如audio...
复制代码 代码如下: !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional...
content属性一般用于::before、::after伪元素中,用于呈现伪元素的内容。平时con...
解决方法如下: 第一种 使用iframe,但是目前使用iframe的人已经越来越少了,而...
简介: 企业上云多账号架构中,如何做到从上到下管理的同时,处理好员工的权限边...
先点赞再看,养成好习惯 前言 这两天在另一个社区看到了一个关于 Tomcat 的提问...