Threejs3D地球标记中国地图位置
先看效果
地球预览视频效果
用到的库
TweenJS
(动画库)用来做相机转场的动画Jquery
(这里只用到一个 each 循环方法,可以使用 js 去写)ThreeJS
(3D 地球制作)100000.json
(全国城市经纬度)d3.v6.js
用来设置平面转3D效果(本来考虑做成3D的中国地图板块,最后因效果看起来比较美观还是考虑用线条嵌入球体的方式去实现,这里有小伙伴考虑制作3D的地图板块可以下载这个库)
适用范围
用于获取地图的位置以及到下一个目的地的总路程,可以将实际路程转成自己配置的路程,以及正在路上的标识,可以用头像表示,经过的地方可以嵌入链接点击进行跳转
设置基础场景
html
<div id="map">
<canvas id="c3d" class="c2d"></canvas>
</div>
<div id="demo"></div>
js
const Dom = document.querySelector("#c3d");
const width = Dom.clientWidth;
const height = Dom.clientHeight;
如果是 Vue 写的话需要从onMounted
生命周期中获取 Dom 元素
js
// 纹理加载器
const loader = new THREE.TextureLoader();
// 渲染器
let renderer;
// 相机
let camera;
// 场景
let scene;
// 灯光
let light;
// 相机控制
let controls;
// 动画
let tween;
// 其他
let earthMesh,
stars,
radius,
labelRenderer,
label,
labels,
labelsable,
labelimg;
js
/**
* 初始化渲染器
* */
function initRenderer() {
// antialias: true, alpha: true 抗锯齿设置
renderer = new THREE.WebGLRenderer({
canvas: Dom,
antialias: true,
alpha: true,
});
// window.devicePixelRatio 设备像素比
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
labelRenderer = new CSS2DRenderer();
labelRenderer.domElement.style.position = "absolute";
labelRenderer.domElement.style.top = "0px";
labelRenderer.domElement.style.pointerEvents = "none";
labelRenderer.setSize(width, height);
document.getElementById("map").appendChild(labelRenderer.domElement);
}
/**
* 初始化相机
*/
function initCamera() {
camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
camera.position.set(0, 0, 10);
camera.lookAt(0, 0, 0);
window.camera = camera;
}
/**
* 初始化场景
*/
function initScene() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x1c3262);
// 雾
// scene.fog = new THREE.Fog(0x020924, 200, 1000)
window.scene = scene;
}
/**
* 初始化 相机控制
*/
function initControls() {
controls = new OrbitControls(camera, renderer.domElement);
// 阻尼惯性
controls.enableDamping = true;
controls.dampingFactor = 0.1;
controls.enableZoom = true;
controls.autoRotate = false;
controls.rotateSpeed = 0.1;
controls.autoRotateSpeed = 1;
controls.enablePan = true;
controls.addEventListener("change", function () {
//相机位置与目标观察点距离
const dis = controls.getDistance();
console.log(camera.position);
});
}
/**
* 初始化光
*/
function initLight() {
// 环境光
const ambientLight = new THREE.AmbientLight(0xcccccc, 0.2);
scene.add(ambientLight);
// 平行光
let directionalLight = new THREE.DirectionalLight(0xffffff, 0.2);
directionalLight.position.set(1, 0.1, 0).normalize();
// 平行光2
let directionalLight2 = new THREE.DirectionalLight(0xff2ffff, 0.2);
directionalLight2.position.set(1, 0.1, 0.1).normalize();
scene.add(directionalLight);
scene.add(directionalLight2);
// 半球光
let hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1);
hemiLight.position.set(0, 1, 0);
scene.add(hemiLight);
// 平行光3
let directionalLight3 = new THREE.DirectionalLight(0xffffff);
directionalLight3.position.set(1, -200, -20);
let directionalLight4 = new THREE.DirectionalLight(0xffffff);
directionalLight4.position.set(0, 500, 500);
// 开启阴影
directionalLight3.castShadow = true;
// 设置光边界
directionalLight3.shadow.camera.top = 18;
directionalLight3.shadow.camera.bottom = -10;
directionalLight3.shadow.camera.left = -52;
directionalLight3.shadow.camera.right = 12;
scene.add(directionalLight3);
}
js
// 旋转队列
const rotateSlowArr = [];
// 放大并透明 队列
const bigByOpacityArr = [];
// 移动 队列
const moveArr = [];
// 边界 绘制点集合
const lines = [];
// 炫光粒子 几何体
const geometryLz = new THREE.BufferGeometry();
// 炫光粒子 透明度
let opacitys = [];
/**
* 渲染函数
* */
function renders(time) {
time *= 0.0;
// 3D对象 旋转
// _y 初始坐标 _s 旋转速度
rotateSlowArr.forEach((obj) => {
obj.rotation.y = obj._y + time * obj._s;
});
bigByOpacityArr.forEach(function (mesh) {
// 目标 圆环放大 并 透明
mesh._s += 0.01;
mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s);
if (mesh._s <= 2) {
mesh.material.opacity = 2 - mesh._s;
} else {
mesh._s = 1;
}
});
moveArr.forEach(function (mesh) {
mesh._s += 0.01;
let tankPosition = new THREE.Vector3();
tankPosition = mesh.curve.getPointAt(mesh._s % 1);
mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z);
});
if (geometryLz.attributes.position) {
geometryLz.currentPos += geometryLz.pointSpeed;
for (let i = 0; i < geometryLz.pointSpeed; i++) {
opacitys[(geometryLz.currentPos - i) % lines.length] = 0;
}
for (let i = 0; i < 200; i++) {
opacitys[(geometryLz.currentPos + i) % lines.length] =
i / 50 > 2 ? 2 : i / 50;
}
geometryLz.attributes.aOpacity.needsUpdate = true;
}
renderer.clear();
labelRenderer.render(scene, camera);
renderer.render(scene, camera);
// requestAnimationFrame(renders)
// earthMesh.rotation.y += 0.01
stars.rotation.y += 0.001;
}
动画渲染函数
用于制作开始场景镜头动画(由远到近并附带略微旋转)
js
let p1 = { x: 100, y: 200, z: 200 };
function initTWEEN() {
let tweena = cameraCon({ x: 100, y: 200, z: 200 }, 1000);
let tweenb = cameraCon({ x: 0, y: 0, z: 10 }, 4000);
tweena.chain(tweenb);
// tweenb.chain(tweenc);
tweenb.onComplete(function () {
console.log("结束");
drawChart();
//相机位置与观察目标点最小值
controls.minDistance = 7;
//相机位置与观察目标点最大值
controls.maxDistance = 50;
// 上下旋转范围
/* controls.minPolarAngle = -Math.PI /6;
controls.maxPolarAngle = Math.PI /4; */
// 左右旋转范围
controls.minAzimuthAngle = -Math.PI / 6;
controls.maxAzimuthAngle = Math.PI / 6;
});
tweena.start();
}
function cameraCon(p2 = { x: p1.x, y: p1.y, z: p1.z }, time = 5000) {
var tween1 = new TWEEN.Tween(p1)
.to(p2, time)
.easing(TWEEN.Easing.Sinusoidal.InOut);
var update = function () {
camera.position.set(p1.x, p1.y, p1.z);
};
tween1.onUpdate(update);
return tween1;
}
function cameraCon2(
p2 = { x: camera.position.x, y: camera.position.y, z: camera.position.z },
time = 2000
) {
var tween1 = new TWEEN.Tween(camera.position)
.to(p2, time)
.easing(TWEEN.Easing.Sinusoidal.Out);
var update = function () {
camera.position.set(
camera.position.x,
camera.position.y,
camera.position.z
);
};
tween1.onUpdate(update);
return tween1;
}
function animate() {
window.requestAnimationFrame((time) => {
if (controls) controls.update();
TWEEN.update();
renders(time);
animate();
});
}
cameraCon2
这个动画在后面会用到,是根据滚动下方内容进行左右镜头旋转的动画效果
星空背景
js
/**
* 创建 方形纹理
* */
function generateSprite() {
const canvas = document.createElement("canvas");
canvas.width = 16;
canvas.height = 16;
const context = canvas.getContext("2d");
// 创建颜色渐变
const gradient = context.createRadialGradient(
canvas.width / 2,
canvas.height / 2,
0,
canvas.width / 2,
canvas.height / 2,
canvas.width / 2
);
gradient.addColorStop(0, "rgba(255,255,255,1)");
gradient.addColorStop(0.2, "rgba(0,255,255,1)");
gradient.addColorStop(0.4, "rgba(0,0,64,1)");
gradient.addColorStop(1, "rgba(0,0,0,1)");
// 绘制方形
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
// 转为纹理
const texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
return texture;
}
/**
* 背景绘制
* */
function bg() {
const positions = [];
const colors = [];
// 创建 几何体
const geometry = new THREE.BufferGeometry();
for (let i = 0; i < 10000; i++) {
let vertex = new THREE.Vector3();
vertex.x = Math.random() * 2 - 1;
vertex.y = Math.random() * 2 - 1;
vertex.z = Math.random() * 2 - 1;
positions.push(vertex.x, vertex.y, vertex.z);
}
// 对几何体 设置 坐标 和 颜色
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3)
);
// 默认球体
geometry.computeBoundingSphere();
// ------------- 1 ----------
// 星星资源图片
// ParticleBasicMaterial 点基础材质
var starsMaterial = new THREE.ParticleBasicMaterial({
map: generateSprite(),
size: 2,
transparent: true,
opacity: 1,
//true:且该几何体的colors属性有值,则该粒子会舍弃第一个属性--color,而应用该几何体的colors属性的颜色
// vertexColors: true,
blending: THREE.AdditiveBlending,
sizeAttenuation: true,
});
// 粒子系统 网格
stars = new THREE.ParticleSystem(geometry, starsMaterial);
stars.scale.set(300, 300, 300);
scene.add(stars);
}
此时星空就已经搭建好了,可以左右旋转试试效果
搭建 3D 地球
其实也就是创建一个球,然后贴个准确的贴图放在圆上调整一些光感即可
js
// 地球,月亮 3D层
const landOrbitObject = new THREE.Object3D();
// 地球3D层
const earthObject = new THREE.Object3D();
// 月亮3D层
const moonObject = new THREE.Object3D();
// 地球半径
const globeRadius = 5;
/**
* 球相关加载
* */
function earth() {
radius = globeRadius;
const widthSegments = 100;
const heightSegments = 100;
const sphereGeometry = new THREE.SphereGeometry(
radius,
widthSegments,
heightSegments
);
function shine() {
var texture = loader.load("./images/blue.png");
var spriteMaterial = new THREE.SpriteMaterial({
map: texture,
transparent: true,
opacity: 0.5,
depthWrite: false,
});
var sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(radius * 3, radius * 3, 1);
sprite.rotation.set(-Math.PI / 2, 0, 0);
scene.add(sprite);
}
shine();
// 地球
const earthTexture = loader.load("./images/微信图片_20230711093004 (1).jpg");
const earthMaterial = new THREE.MeshStandardMaterial({
map: earthTexture,
});
earthMesh = new THREE.Mesh(sphereGeometry, earthMaterial);
// 月球
const moonTexture = loader.load("./images/yueqiu.jpg");
const moonMaterial = new THREE.MeshPhongMaterial({ map: moonTexture });
const moonMesh = new THREE.Mesh(sphereGeometry, moonMaterial);
moonMesh.scale.set(0.1, 0.1, 0.1);
moonMesh.position.x = 10;
moonObject.add(moonMesh);
// 加入动画队列
moonObject._y = 0;
moonObject._s = 1;
rotateSlowArr.push(moonObject);
// 地球加入 地球3D层
earthObject.add(earthMesh);
earthObject.rotation.set(0.5, 2.9, 0.1);
earthObject._y = 2.0;
earthObject._s = 0.1;
// 加入动画队列
// rotateSlowArr.push(earthObject)
// 加入 地球3D层
landOrbitObject.add(earthObject);
// 加入 月亮3D层
landOrbitObject.add(moonObject);
scene.add(landOrbitObject);
}
/**
* 经维度 转换坐标
* THREE.Spherical 球类坐标
* lng:经度
* lat:维度
* radius:地球半径
*/
function lglt2xyz(lng, lat, radius) {
// 以z轴正方向为起点的水平方向弧度值
const theta = (90 + lng) * (Math.PI / 180);
// 以y轴正方向为起点的垂直方向弧度值
const phi = (90 - lat) * (Math.PI / 180);
return new THREE.Vector3().setFromSpherical(
new THREE.Spherical(radius, phi, theta)
);
}
绘制红色圆点和点与点之间的飞线效果
js
/**
* 绘制 目标点
* */
function spotCircle(spot) {
// 圆
const geometry1 = new THREE.CircleGeometry(0.02, 100);
const material1 = new THREE.MeshBasicMaterial({
color: 0xff0000,
side: THREE.DoubleSide,
});
const circle = new THREE.Mesh(geometry1, material1);
circle.position.set(spot[0], spot[1], spot[2]);
// mesh在球面上的法线方向(球心和球面坐标构成的方向向量)
var coordVec3 = new THREE.Vector3(spot[0], spot[1], spot[2]).normalize();
// mesh默认在XOY平面上,法线方向沿着z轴new THREE.Vector3(0, 0, 1)
var meshNormal = new THREE.Vector3(0, 0, 1);
// 四元数属性.quaternion表示mesh的角度状态
//.setFromUnitVectors();计算两个向量之间构成的四元数值
circle.quaternion.setFromUnitVectors(meshNormal, coordVec3);
earthObject.add(circle);
// 圆环
const geometry2 = new THREE.RingGeometry(0.03, 0.04, 100);
// transparent 设置 true 开启透明
const material2 = new THREE.MeshBasicMaterial({
color: 0xff0000,
side: THREE.DoubleSide,
transparent: true,
});
const circleY = new THREE.Mesh(geometry2, material2);
circleY.position.set(spot[0], spot[1], spot[2]);
// 指向圆心
circleY.lookAt(new THREE.Vector3(0, 0, 0));
earthObject.add(circleY);
label.position.set(spot[0] - 0.15, spot[1] + 0.04, spot[2]);
// 加入动画队列
bigByOpacityArr.push(circleY);
}
js
/**
* 绘制 两个目标点并连线
* */
function lineConnect(posStart, posEnd) {
const v0 = lglt2xyz(posStart[0], posStart[1], globeRadius);
const v3 = lglt2xyz(posEnd[0], posEnd[1], globeRadius);
// angleTo() 计算向量的夹角
const angle = v0.angleTo(v3);
let vtop = v0.clone().add(v3);
// multiplyScalar 将该向量与所传入的 标量进行相乘
vtop = vtop.normalize().multiplyScalar(globeRadius);
let n;
if (angle <= 1) {
n = (globeRadius / 3) * angle;
} else if (angle > 1 && angle < 2) {
n = (globeRadius / 3) * Math.pow(angle, 2);
} else {
n = (globeRadius / 3) * Math.pow(angle, 1.5);
}
const v1 = v0
.clone()
.add(vtop)
.normalize()
.multiplyScalar(globeRadius + n);
const v2 = v3
.clone()
.add(vtop)
.normalize()
.multiplyScalar(globeRadius + n);
// 三维三次贝塞尔曲线(v0起点,v1第一个控制点,v2第二个控制点,v3终点)
const curve = new THREE.CubicBezierCurve3(v0, v1, v2, v3);
// 绘制 目标位置
spotCircle([v0.x, v0.y, v0.z]);
spotCircle([v3.x, v3.y, v3.z]);
// 线上移动物体
moveSpot(curve);
const lineGeometry = new THREE.BufferGeometry();
// 获取曲线 上的50个点
var points = curve.getPoints(50);
var positions = [];
var colors = [];
var color = new THREE.Color();
// 给每个顶点设置演示 实现渐变
for (var j = 0; j < points.length; j++) {
if (j < 25 || j == 25) {
color.set(0xffffff); // 粉色
} else if (j < 50 && j > 25) {
color.set(0xfffdaa); // 粉色
}
colors.push(color.r, color.g, color.b);
positions.push(points[j].x, points[j].y, points[j].z);
}
// 放入顶点 和 设置顶点颜色
lineGeometry.addAttribute(
"position",
new THREE.BufferAttribute(new Float32Array(positions), 3, true)
);
lineGeometry.addAttribute(
"color",
new THREE.BufferAttribute(new Float32Array(colors), 3, true)
);
const material = new THREE.LineBasicMaterial({
vertexColors: true,
side: THREE.DoubleSide,
});
const line = new THREE.Line(lineGeometry, material);
earthObject.add(line);
}
js
/**
* 线上移动物体
* */
function moveSpot(curve) {
// 线上的移动物体
const aGeo = new THREE.SphereGeometry(0.04, 0.04, 0.04);
const aMater = new THREE.MeshPhongMaterial({
color: 0xff0000,
side: THREE.DoubleSide,
});
const aMesh = new THREE.Mesh(aGeo, aMater);
// 保存曲线实例
aMesh.curve = curve;
aMesh._s = 0;
moveArr.push(aMesh);
earthObject.add(aMesh);
}
用画布渲染城市信息以及每个点到点的路程的总距离
js
/**
* 画图
* */
function drawChart() {
const loader = new THREE.FileLoader();
let centers;
loader.load("./js/100000_full.json", (data) => {
// 点与点
let objName = [
{ name: "黑龙江省", url: "https://www.baidu.com" },
{ name: "内蒙古自治区" },
{ name: "四川省" },
{ name: "" },
];
// 线与线
let city = [{ to: "黑龙江省" }, { to: "内蒙古自治区" }, { to: "四川省" }];
// 当前所在地
let location = [{ type: 0 }];
const jsondata = JSON.parse(data);
let transformedData = [];
// 循环
$.each(jsondata.features, function (index, item) {
const { centroid, center, name } = item.properties;
/* const point = centroid || center || [0, 0];
const depth = Math.random() * 0.3 + 0.3; */
let proName = item.properties.name;
let proName1 = item.properties.name;
centers = item.properties.center;
objName.forEach((v) => {
if (v.name == proName) {
labels = createLabel(name, v.url);
earthObject.add(labels);
if (centers != undefined) {
let markPos = lglt2xyz(centers[0], centers[1], 5);
spotCircle([markPos.x, markPos.y, markPos.z]);
}
// lineConnect(item.properties.center,[150,100,100])
}
});
let lastIndex = city.length - 2;
city.map((v, index) => {
if (v.to == proName1) {
v.tojwd = item.properties.center;
let indexNum = index + 1;
if (indexNum < city.length) {
let formCity = city[index];
let toCity = city[index + 1];
setTimeout(() => {
let combinedCity = {
to: formCity.to,
tojwd: v.tojwd,
form: toCity.to,
formjwd: toCity.tojwd,
};
if (combinedCity.formjwd != []) {
let distance = getDistance(
combinedCity.tojwd[0],
combinedCity.tojwd[1],
combinedCity.formjwd[0],
combinedCity.formjwd[1]
);
combinedCity.dist = distance;
transformedData.push(combinedCity);
const coordinates = kilometersToCoordinates(
distance,
combinedCity.tojwd[1],
combinedCity.tojwd[0]
);
console.log(
"从" +
combinedCity.to +
"到" +
combinedCity.form +
"的距离为" +
distance +
"公里"
); // 输出两个坐标之间的距离,单位为公里
// 示例用法
if (index == lastIndex) {
sessionStorage.setItem("last", JSON.stringify(combinedCity));
let par = JSON.parse(sessionStorage.getItem("last"));
} else {
lineConnect(combinedCity.tojwd, combinedCity.formjwd);
}
// lineConnect(transformedData[0].tojwd, transformedData[0].formjwd)
}
}, 100);
}
}
});
location.forEach((i) => {
if (i.type == item.properties.subFeatureIndex) {
setTimeout(() => {
let last = JSON.parse(sessionStorage.getItem("last"));
console.log(last.dist);
// 更换自定义路线(公里)
last.dist = 300;
// 将经纬度转换为三维坐标
const point1 = last.tojwd;
const point2 = last.formjwd;
// 个人的总步数(公里)
let lucheng = 100;
let bfb = lucheng / last.dist;
let s = point1[0] + (point2[0] - point1[0]) * bfb * 1;
let d = point1[1] + (point2[1] - point1[1]) * bfb * 1;
const div = document.createElement("div");
div.style.color = "#fff";
div.style.fontSize = "14px";
div.style.textShadow = "1px 1px 2px #047cd6";
div.innerHTML = `<img style="width:30px;border-radius:50%" src="./images/head.jpg" />`;
labelimg = new CSS2DObject(div);
labelimg.scale.set(0.01, 0.01, 0.01);
let markPos = lglt2xyz(s, d, 5);
labelimg.position.set(markPos.x - 0.1, markPos.y + 0.04, markPos.z);
earthObject.add(labelimg);
// 定义距离为1000公里
setTimeout(() => {
lineConnect(point1, [s, d]);
}, 101);
}, 200);
}
});
});
});
}
function getDistance(lat1, lon1, lat2, lon2) {
const R = 6371; // 地球半径,单位为公里
const rLat1 = toRadians(lat1);
const rLat2 = toRadians(lat2);
const deltaLat = toRadians(lat2 - lat1);
const deltaLon = toRadians(lon2 - lon1);
const a =
Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
Math.cos(rLat1) *
Math.cos(rLat2) *
Math.sin(deltaLon / 2) *
Math.sin(deltaLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c;
return distance;
}
function kilometersToCoordinates(distance, latitude, longitude) {
const earthRadius = 6371.393; // 地球半径,单位为公里
// 计算纬度差值
const latDiff = distance / earthRadius;
// 根据纬度计算经度差值
const lonDiff =
distance / (earthRadius * Math.cos((Math.PI * latitude) / 180));
// 计算新的经度和纬度
const newLatitude = latitude + latDiff * (180 / Math.PI);
const newLongitude = longitude + lonDiff * (180 / Math.PI);
// 返回经度和纬度
return { latitude: newLatitude, longitude: newLongitude };
}
js
// 将角度转换为弧度
function toRadians(degree) {
return degree * (Math.PI / 180);
}
创建可点击跳转的 url
js
const createLabel = (name, url) => {
const div = document.createElement("div");
div.style.color = "#fff";
div.style.fontSize = "10px";
div.style.textShadow = "1px 1px 2px #047cd6";
div.textContent = name;
div.style.pointerEvents = "auto";
div.style.cursor = "pointer";
label = new CSS2DObject(div);
div.addEventListener("click", function (event) {
if (url != undefined) {
window.location.href = url;
} else {
return;
}
});
label.scale.set(0.01, 0.01, 0.01);
return label;
};
创建描边炫光路径
js
const vertexShader = `
attribute float aOpacity;
uniform float uSize;
varying float vOpacity;
void main(){
gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
gl_PointSize = uSize;
vOpacity=aOpacity;
}
`;
const fragmentShader = `
varying float vOpacity;
uniform vec3 uColor;
float invert(float n){
return 1.-n;
}
void main(){
if(vOpacity <=0.2){
discard;
}
vec2 uv=vec2(gl_PointCoord.x,invert(gl_PointCoord.y));
vec2 cUv=2.*uv-1.;
vec4 color=vec4(1./length(cUv));
color*=vOpacity;
color.rgb*=uColor;
gl_FragColor=color;
}
`;
/**
* 边界炫光路径
* */
function dazzleLight() {
const loader = new THREE.FileLoader();
loader.load("./js/100000.json", (data) => {
const jsondata = JSON.parse(data);
console.log(jsondata);
// 中国边界
const feature = jsondata.features[0];
const province = new THREE.Object3D();
province.properties = feature.properties.name;
// 点数据
const coordinates = feature.geometry.coordinates;
coordinates.forEach((coordinate) => {
// coordinate 多边形数据
coordinate.forEach((rows) => {
// 绘制线
// const line = lineDraw(rows, 0xaa381e)
const line = lineDraw(rows, 0xaa381e);
province.add(line);
});
});
// 添加地图边界
earthObject.add(province);
// 拉平 为一维数组
const positions = new Float32Array(lines.flat(1));
// 设置顶点
geometryLz.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3)
);
// 设置 粒子透明度为 0
opacitys = new Float32Array(positions.length).map(() => 0);
geometryLz.setAttribute("aOpacity", new THREE.BufferAttribute(opacitys, 1));
geometryLz.currentPos = 0;
// 炫光移动速度
geometryLz.pointSpeed = 10;
// 控制 颜色和粒子大小
const params = {
pointSize: 2.0,
pointColor: "#4ec0e9",
};
// 创建着色器材质
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: true, // 设置透明
uniforms: {
uSize: {
value: params.pointSize,
},
uColor: {
value: new THREE.Color(params.pointColor),
},
},
});
const points = new THREE.Points(geometryLz, material);
earthObject.add(points);
});
}
/**
* 边框 图形绘制
* @param polygon 多边形 点数组
* @param color 材质颜色
* */
let indexBol = true;
function lineDraw(polygon, color) {
const lineGeometry = new THREE.BufferGeometry();
const pointsArray = new Array();
polygon.forEach((row) => {
// 转换坐标
const xyz = lglt2xyz(row[0], row[1], globeRadius);
// 创建三维点
pointsArray.push(xyz);
if (indexBol) {
// 为了好看 这里只要内陆边界
lines.push([xyz.x, xyz.y, xyz.z]);
}
});
indexBol = false;
// 放入多个点
lineGeometry.setFromPoints(pointsArray);
const lineMaterial = new THREE.LineBasicMaterial({
color: color,
});
return new THREE.Line(lineGeometry, lineMaterial);
}
最后当 ul 中的 li 发生滚动时调用前面所执行的相机转场动画的效果
js
let { scrollY } = window;
let currentSection = 0;
window.addEventListener("scroll", () => {
scrollY = window.scrollY;
const newSection = Math.round(scrollY / height);
console.log(newSection);
console.log(camera.position);
if (newSection !== currentSection) {
currentSection = newSection;
console.log(currentSection);
if (currentSection == 0) {
currentSection = newSection;
// console.log('changed', currentSection)
let tweena = cameraCon2(camera.position, 1000);
console.log(camera.position);
let tweenb = cameraCon2({ x: 0, y: 1, z: 10 }, 1000);
tweena.chain(tweenb);
tweena.start();
} else {
currentSection = newSection;
// console.log('changed', currentSection)
let tweena = cameraCon2(camera.position, 1000);
console.log(camera.position);
let tweenb = cameraCon2({ x: 0, y: -1, z: 10 }, 1000);
tweena.chain(tweenb);
tweena.start();
}
}
});
全局初始化
js
window.onload = () => {
// 初始化
initTWEEN();
initRenderer();
initCamera();
initScene();
initLight();
initControls();
// 绘制
bg();
earth();
dazzleLight();
// 渲染
animate();
};
完整代码地址 earthling ,此项目仅用于交流学习