信息发布→ 登录 注册 退出

基于Three.js制作一个3D中国地图

发布时间:2026-01-11

点击量:
目录
  • 1.使用geoJson绘制3d地图
    • 1.1 创建场景相关
    • 1.2 根据json绘制地图
  • 2.增加光照
    • 3.增加阴影模糊
      • 4.增加鼠标事件
        • 5.渲染
          • 6.动画效果

            不想看繁琐步骤的,可以直接去github下载项目,如果可以顺便来个star哈哈

            本项目使用vue-cli创建,但不影响使用,主要绘制都已封装成类

            1.使用geoJson绘制3d地图

            1.1 创建场景相关

            // 创建webGL渲染器
            this.renderer = new THREE.WebGLRenderer( { antialias: true,alpha: true} );
            this.renderer.shadowMap.enabled = true; // 开启阴影
            this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
            this.renderer.toneMappingExposure = 1.25;   
            
            // 根据自己的需要调整颜色模式
            // this.renderer.outputEncoding = THREE.sRGBEncoding;  
            
            this.renderer.outputEncoding = THREE.sHSVEncoding;
            this.renderer.setPixelRatio( window.devicePixelRatio );
            // 清除背景色,透明背景
            this.renderer.setClearColor(0xffffff, 0);
            this.renderer.setSize(this.width, this.height);
            
            // 场景
            this.scene = new THREE.Scene();
            this.scene.background = null
            // 相机 透视相机
            this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 0.1, 5000);
            this.camera.position.set(0, -40, 70);
            this.camera.lookAt(0, 0, 0);

            1.2 根据json绘制地图

            利用THREE.Shape绘制地图的平面边数据,再用THREE.ExtrudeGeometry将一个面拉高成3d模型,3d饼图同理也可以这么制作

            let jsonData = require('./json/china.json')
            this.initMap(jsonData);
            
            // initMap 方法主要部分
            initMap(chinaJson) {
                /* ...省略
                    ...
                */
                chinaJson.features.forEach((elem, index) => {
                    // 定一个省份3D对象
                    const province = new THREE.Object3D();
                    // 每个的 坐标 数组
                    const { coordinates } = elem.geometry;
                    const color = COLOR_ARR[index % COLOR_ARR.length]
                    // 循环坐标数组
                    coordinates.forEach(multiPolygon => {
                        
                        multiPolygon.forEach((polygon) => {
                            const shape = new THREE.Shape();
                            
                            for (let i = 0; i < polygon.length; i++) {
                                let [x, y] = projection(polygon[i]);
                                
                                if (i === 0) {
                                    shape.moveTo(x, -y);
                                }
                                shape.lineTo(x, -y);
                            }
                
                            const extrudeSettings = {
                                depth: 4,
                                bevelEnabled: true,
                                bevelSegments: 1,
                                bevelThickness: 0.2
                            };
                
                            const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
                           
                            // 平面部分材质
                            const material = new THREE.MeshStandardMaterial( {
                                metalness: 1,
                                color: color,
                                
                            } );
                            // 拉高部分材质
                            const material1 = new THREE.MeshStandardMaterial( {
                                metalness: 1,
                                roughness: 1,
                                color: color,
                                
                            } );
            
                            const mesh = new THREE.Mesh(geometry, [
                                material,
                                material1
                            ]);
                            // 设置高度将省区分开来
                            if (index % 2 === 0) {
                                mesh.scale.set(1, 1, 1.2);
                            }
                            // 给mesh开启阴影
                            mesh.castShadow = true
                            mesh.receiveShadow = true
                            mesh._color = color
                            province.add(mesh);
            
                        })
                
                    })
                
                    _this.map.add(province);
                    
                })
            }

            geoJson的坐标需要进行墨卡托投影转换才能转换成平面坐标,这里需要用到d3

            // 墨卡托投影转换
            const projection = d3.geoMercator().center([104.0, 37.5]).scale(80).translate([0, 0]);

            2.增加光照

            我们把各种光都打上,环境光,半球光,点光,平行光。以平行光为例,增加投影,调整投影分辨率,避免投影出现马赛克

            const light = new THREE.DirectionalLight( 0xffffff, 0.5 ); 
            light.position.set( 20, -50, 20 );
            
            light.castShadow = true;
            light.shadow.mapSize.width = 1024;
            light.shadow.mapSize.height = 1024;
            
            this.scene.add(light);

            castShadow = true表示开启投影

            3.增加阴影模糊

            默认的阴影没有模糊效果,看起来像白炽灯照射的样子,没有柔和感。使用官方示例中的csm来增加阴影模糊

            import { CSM } from 'three/examples/jsm/csm/CSM.js';
            
            this.csm = new CSM( {
                maxFar: params.far,
                cascades: 4,
                mode: params.mode,
                parent: this.scene,
                shadowMapSize: 1024,
                lightDirection: new THREE.Vector3( params.lightX, params.lightY, params.lightZ ).normalize(),
                camera: this.camera
            } );

            4.增加鼠标事件

            3d空间中,鼠标事件主要通过射线来获取鼠标所在位置,可以想象成鼠标放出一道射线,照射到的第一个物体就是鼠标所在位置。此时用的threejsRaycaster,通过Raycaster给对应的省份增加鼠标移入高亮效果和省份民悬浮展示效果

            this.raycaster = new THREE.Raycaster();
            // 传入需要检测的对象 group,group下的所有对象都会被检测到,如果被射线照到,则intersects有值,表示鼠标当前在这些物体上   
            const intersects = this.raycaster.intersectObject( this.group, true );
            // 代码太多就不贴了,见 GitHub源码

            5.渲染

            threejs的渲染一般调用原生的requestAnimationFrame,主要做的事就是调用rendererrender方法,当然因为我们做了阴影模糊处理,所以还有别的需要做的:

            this.camera.updateMatrixWorld();
            this.csm.update();
            this.renderer.render(this.scene, this.camera);

            6.动画效果

            地图上如果有一些动画效果,可以使用TWEEN.js,github地址,比如地图标注的出现动画:

            最后再奉上项目地址

            在线客服
            服务热线

            服务热线

            4008888355

            微信咨询
            二维码
            返回顶部
            ×二维码

            截屏,微信识别二维码

            打开微信

            微信号已复制,请打开微信添加咨询详情!