这里主要用到的就是threejs的shader,至于其他知识点,可以参考json生成3d区域
下面的主要代码:
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import * as d3geo from 'd3-geo'
import guangzhouJSON from '../assets/json/guangzhou.json'export default (domId) => {/* ------------------------------初始化三件套--------------------------------- */const dom = document.getElementById(domId);const { innerHeight, innerWidth } = windowconst scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 2000);camera.position.set(0, 0, 10);camera.lookAt(scene.position);const renderer = new THREE.WebGLRenderer({antialias: true,// 抗锯齿alpha: false,// 透明度powerPreference: 'high-performance',// 性能logarithmicDepthBuffer: true,// 深度缓冲})// renderer.setClearColor(0x000000, 0);// 设置背景色// renderer.clear();// 清除渲染器renderer.shadowMap.enabled = true;// 开启阴影renderer.shadowMap.type = THREE.PCFSoftShadowMap;// 阴影类型renderer.outputEncoding = THREE.sRGBEncoding;// 输出编码renderer.toneMapping = THREE.ACESFilmicToneMapping;// 色调映射renderer.toneMappingExposure = 1;// 色调映射曝光renderer.physicallyCorrectLights = true;// 物理正确灯光renderer.setPixelRatio(devicePixelRatio);// 设置像素比renderer.setSize(innerWidth, innerHeight);// 设置渲染器大小dom.appendChild(renderer.domElement);// 重置大小window.addEventListener('resize', () => {const { innerHeight, innerWidth } = windowcamera.aspect = innerWidth / innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth, innerHeight);})/* ------------------------------初始化工具--------------------------------- */const controls = new OrbitControls(camera, renderer.domElement) // 相机轨道控制器controls.enableDamping = true // 是否开启阻尼controls.dampingFactor = 0.05// 阻尼系数controls.panSpeed = -1// 平移速度// const axesHelper = new THREE.AxesHelper(10);// scene.add(axesHelper);/* ------------------------------正题--------------------------------- */// 相机控制器配置const cameraControl = {autoCamera: true,// 是否自动旋转height: 10,// 相机高度width: 0.5,// 相机宽度depth: 1,// 相机深度cameraPosX: 10,// 相机位置xcameraPosY: 181,// 相机位置ycameraPosZ: 116,// 相机位置zautoRotate: false,// 是否自动旋转rotateSpeed: 2000// 旋转速度}let geoFun;// 地理投影函数let guangzhouData = [];// 地图数据let shape = null;// 地图形状let time = 1;// 时间const group = new THREE.Group();const edgeMaterial = new THREE.ShaderMaterial({side: THREE.DoubleSide,transparent: true,depthTest: false,uniforms: {time: { value: 0.0 },num: { value: 5.0 },color1: { value: new THREE.Color('#00FFFF') }},vertexShader: ` varying vec2 vUv;varying vec3 vNormal;void main() {vUv=uv;vNormal=normal; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );}`,fragmentShader: `uniform vec3 color1; uniform float time; uniform float num; varying vec2 vUv; varying vec3 vNormal;void main() {if(vNormal.z==1.0||vNormal.z==-1.0||vUv.y ==0.0){discard;}else{gl_FragColor = vec4(color1, 1.0-fract((vUv.y-time ) *num) ) ;} }`});// 初始化地理投影const initGeo = (size) => {geoFun = d3geo.geoMercator().scale(size || 100)}// 经纬度转像素坐标const latlng2px = (pos) => {if (pos[0] >= -180 && pos[0] <= 180 && pos[1] >= -90 && pos[1] <= 90) {return geoFun(pos);}return pos;};// 处理地图数据const processData = () => {guangzhouData = guangzhouJSON.features[0].geometry.coordinates[0][0];// 数据 从 经纬度-> 像素坐标-> 三维坐标guangzhouData = guangzhouData.map(item => {const two = latlng2px(item);const three = new THREE.Vector3(two[0], 0, two[1]);return three;})}// 创建地图块const createMap = () => {const extrudeSettings = {depth: 0.2,// 区块厚度bevelEnabled: false// 是否使用倒角};shape = new THREE.Shape();shape.moveTo(guangzhouData[0].x, guangzhouData[0].z);// 移动到第一个点for (let i = 1; i < guangzhouData.length; i++) {shape.lineTo(guangzhouData[i].x, guangzhouData[i].z);// 连线}shape.lineTo(guangzhouData[0].x, guangzhouData[0].z);// 闭合const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);const img = new URL('../assets/images/tex.png', import.meta.url).href;const text = new THREE.TextureLoader().load(img);text.wrapS = THREE.RepeatWrapping;// 水平方向text.wrapT = THREE.RepeatWrapping;// 垂直方向const material = new THREE.MeshBasicMaterial({map: text,color: new THREE.Color('#00FFFF')})const mesh = new THREE.Mesh(geometry, material);mesh.rotateX(Math.PI / 2);// 旋转90度mesh.position.y = extrudeSettings.depth * 0.5;group.add(mesh);}// 创建边缘线const createEdge = () => {const extrudeSettings = {depth: 1,// 厚度bevelEnabled: false// 是否使用倒角};const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);const mesh = new THREE.Mesh(geometry, edgeMaterial);mesh.rotation.x = Math.PI * 0.5;mesh.position.y = extrudeSettings.depth + 0.1;group.add(mesh);}// 设置模型的中心点const setModeCenter = (object, viewControl) => {// 如果对象不存在,则返回 if (!object) {return;}if (object.updateMatrixWorld) {object.updateMatrixWorld();// 更新模型矩阵}// 获得包围盒得min和maxlet box = new THREE.Box3().setFromObject(object);let objSize;// 获取包围盒的尺寸try {objSize = box.getSize();} catch (error) {objSize = new THREE.Vector3(Math.abs(box.max.x - box.min.x),Math.abs(box.max.y - box.min.y),Math.abs(box.max.z - box.min.z));}// 返回包围盒的中心点const center = box.getCenter(new THREE.Vector3());object.position.x += object.position.x - center.x;object.position.y += object.position.y - center.y;object.position.z += object.position.z - center.z;let width = objSize.x;let height = objSize.y;let depth = objSize.z;// 设置相机位置let centroid = new THREE.Vector3().copy(objSize);centroid.multiplyScalar(0.5);if (viewControl.autoCamera) {camera.position.x =centroid.x * (viewControl.centerX || 0) + width * (viewControl.width || 0);camera.position.y =centroid.y * (viewControl.centerY || 0) + height * (viewControl.height || 0);camera.position.z =centroid.z * (viewControl.centerZ || 0) + depth * (viewControl.depth || 0);} else {camera.position.set(viewControl.cameraPosX || 0,viewControl.cameraPosY || 0,viewControl.cameraPosZ || 0);}camera.lookAt(0, 0, 0);}// 初始化const init = () => {initGeo(180)// 初始化地理投影processData()// 处理地图数据createMap()// 创建地图块createEdge()// 创建边缘线scene.add(group);// 添加到场景中setModeCenter(group, cameraControl)// 设置模型的中心点}init();/* ------------------------------动画函数--------------------------------- */const animation = () => {if (edgeMaterial) {if (time >= 1.0) {time = 0.0;}time += 0.005;edgeMaterial.uniforms.time.value = time;}controls.update();// 如果不调用,就会很卡renderer.render(scene, camera);requestAnimationFrame(animation);}animation();
}