3d 地图进场特效以及地图边缘动画
代码仓库:
地图边缘动画核心代码:
const initBorderPoint = () => {
// 获取地图边界的左边(通过https://datav.aliyun.com/portal/school/atlas/area_generator#2.51/104.299012/33.447963 获取)
let borderPoints3Array = borderMap.features[0].geometry.coordinates
let borderPoints = []
borderPoints3Array.forEach((coordinate) => {
coordinate.forEach((rows) => {
rows.forEach(v => {
// 将坐标转化为 d3的坐标
const [x_XYZ, y_XYZ] = d3.geoMercator().center([109, 34.5]).scale(1000).translate([0, 0])(v)
borderPoints.push({x: x_XYZ, y: -y_XYZ})
// 将转换后的坐标放入到一个数组存储起来(重点!!!)
linePoints.push([x_XYZ, -y_XYZ, 32])
})
});
});
opacityGeometry = new THREE.BufferGeometry();
// 将数组转化为一维数组
positions = new Float32Array(linePoints.flat(1));
// 然后3个一组
opacityGeometry.setAttribute("position", new BufferAttribute(positions, 3));
opacity = new Float32Array(positions.length).map(() => 0);
opacityGeometry.setAttribute("aOpacity", new BufferAttribute(opacity, 1));
// 控制 颜色和粒子大小
const params = {
pointSize: 5.0,
pointColor: '#37ffff'
}
// 看不懂写死
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;
}
`
// 创建着色器
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: true, // 设置透明
uniforms: {
uSize: {
value: params.pointSize
},
uColor: {
value: new THREE.Color(params.pointColor)
}
}
})
// 创建一个点
opacityPoints = new THREE.Points(opacityGeometry, material)
// 将这个点放入到场景中
showScene.add(opacityPoints);
}
const render = () => {
if (activeProvince === '四川省') {
renderer.render(ScScene, camera)
let info = document.querySelector('#info')
info.style.display = 'none'
showScene = ScScene
} else {
renderer.render(showScene, camera)
}
if (!activeProvince) {
showScene = scene
renderer.render(showScene, camera)
}
// 如果有地图边界所有坐标
if (linePoints && opacity) {
// 5234怎么来的:linePoints打印出来我这边有7134个 但是我这边有900个边界点我这边不想显示(也就是我视频下面那一坨很杂碎的地方)所以我截取了5234个
//currentPos 也就是数组下标 只去前面5234个点
if (currentPos > 5234) {
// 移除边缘动画线
// showScene.remove(opacityPoints)
currentPos = 0;
}
// 20 就是运动的速度 值越大 移动越快
currentPos += 20;
// 忘记这是干嘛的
for (let i = 0; i < 10; i++) {
opacity[(currentPos - i) % linePoints.length] = 0;
}
// // 忘记这是干嘛的
for (let i = 0; i < 100; i++) {
opacity[(currentPos + i) % linePoints.length] = i / 50 > 2 ? 2 : i / 50;
}
// 让点 动起来
if (opacityGeometry) {
opacityGeometry.attributes.aOpacity.needsUpdate = true;
}
}
// 渲染下一帧的时候会调用render函数
requestAnimationFrame(render)
tween.update()
controls.update()
}
进场动画核心代码:
const tween = new Tween({x: 1000, y: 1000, z: 2000}) // 起始位置
.to(camera.position, 2000) // 最终位置
.easing(Easing.Quadratic.In) // 过度动画
.onUpdate((object) => { // 改变摄像机位置
camera.position.x = object.x
camera.position.y = object.y
camera.position.z = object.z
})
.start();
主要代码解析:
1、获取边缘地图的所有坐标 转换为 d3坐标
2、绘制着色器材质(ShaderMaterial)
3、将着色器材质按照地图边界的坐标跑动
4、使用Tween动画库或者 gsap 动画库实现摄像机进场动画
下一篇: