vue3搭配threejs画地图

1.下载threejs
npm i three
2.创建一个js文件 创建一个类

import * as THREE from 'three'
//5-1---------引入屏幕后处理
import {EffectComposer} from 'three/addons/postprocessing/EffectComposer.js'  //屏幕后处理 来处理发光
export default class Base{
    constructor(){
        //创建场景
        this.scene=new THREE.Scene()
        //创建一个透视相机
        this.camera=new THREE.PerspectiveCamera(
            75,//角度
            window.innerWidth / window.innerHeight,//宽高比
            0.1,//近端距离
            1000,//远端距离
        )
        //创建一个渲染器
        this.renderer=new THREE.WebGLRenderer({antialias:true}) //抗锯齿,边缘丝滑
        this.renderer.setSize(window.innerWidth,window.innerHeight) //渲染器尺寸,宽高
        //使物体更加清晰
        this.renderer.setPixelRatio(window.devicePixelRatio)//像素比设置为设备像素比
        //挂载渲染器到子页面容器上  vue文件里id命名的容器
        document.getElementById("containers").appendChild(this.renderer.domElement)

        //屏幕后处理 发光
        this.composer= new EffectComposer(this.renderer)
        this.composer.setSize(window.innerWidth,window.innerHeight)

    }

    //更新的时候要把渲染器重新渲染一下
    update(){
        this.renderer.render(this.scene,this.camera)
    }
    
    //屏幕后处理
    updateComposer(){
        this.composer.render()
    }

    //自适应的方法,当屏幕宽高改变的时候把相机的宽高比也改变一下
    resize(){
        this.camera.aspect=window.innerWidth / window.innerHeight
        //更新投影矩阵
        this.camera.updateProjectionMatrix()
        //更新渲染器尺寸
        this.renderer.setSize(window.innerWidth,window.innerHeight)
    }

    resizeComposer(){
        this.composer.setSize(window.innerWidth,window.innerHeight)
    }

    //添加环境光
    addAmbientLight(intensity=1,color=0xffffff){ //强度,颜色
        //官网拿的
        const light = new THREE.AmbientLight(color,intensity ); // soft white light
        this.scene.add( light );
    }
}
  1. 创建一个vue文件,在文件里创建一个容器 ,使用id选择器,(在js文件里获取这个容器来展示场景)
<!--  -->
<template>
    <div>
      <div class="threeJSBox">
        <div id="containers">
            <div class="label" ref="label">
                <span>{{ provincename }}</span>
            </div>
        </div>
      </div>
    </div>
</template>

<script setup>
import { onMounted, ref } from 'vue'
import Base from './Base0.js'
import * as THREE from 'three'
import * as d3 from 'd3'  //球形坐标转化为 二维坐标
// 2-1------加载标记点数据
import {flylineAddress,airplaneAddress,centerAddress} from './constant.js'
import { OrbitControls} from 'three/addons/controls/OrbitControls.js' //引入轨道控制器
//5-1-------使用屏幕后处理高亮
//5-2-------添加渲染通道
import {RenderPass} from 'three/addons/postprocessing/RenderPass.js'
//5-2-------添加发光通道
import {UnrealBloomPass} from 'three/addons/postprocessing/UnrealBloomPass.js'
let base,controls;
const label=ref(null)
const provincename=ref('')
onMounted(async ()=>{ //组件挂载完毕之后执行
    base=new Base()
    // base.camera.position.z=5 //把摄像机拉远一点测试box
    base.camera.position.z=60 //把摄像机拉远一点测试box
    base.camera.updateProjectionMatrix() //更新投影矩阵
    base.camera.layers.enableAll();//摄像头能照到所有层级
    base.addAmbientLight(0.55)

    //1-1-------控制器控制轨道
    controls=new OrbitControls(base.camera,base.renderer.domElement)
    // //测试box
    // createBox()
    addPass() 
    //加载地图的方法
    await loadMap()
    update()
    //把resize绑定到window
    window.addEventListener('resize',resize);    
    //5-4---------监听鼠标移动
    document.addEventListener('mousemove',onPointerMove) //鼠标移动执行
})


//射线
const raycaster = new THREE.Raycaster();
raycaster.layers.set(1); //射线层级为1
const pointer = new THREE.Vector2();
const depthMat = new THREE.MeshStandardMaterial({ //高光材质
  color: 0x00ffff,
  transparent: true,
  blending: THREE.AdditiveBlending,
  depthTest: false,
  depthWrite: false,
});
function onPointerMove(){
    // 5-5------将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
    // 5-6-------通过摄像机和鼠标位置更新射线
    raycaster.setFromCamera(pointer, base.camera);
    // 5-6-------计算物体和射线的焦点
    const intersects = raycaster.intersectObjects(base.scene.children);
    if (intersects.length > 0) { //检测到物体个数大于0
        label.value.style.top = event.clientY - label.value.clientHeight - 5 + "px";
        label.value.style.left = event.clientX - label.value.clientWidth / 2 + "px";
        //6------label
        label.value.style.display = "block"; //显示label
        provincename.value = intersects[0].object.name; //省份名字就是射线检测到的名字

        let arr = Object.entries(extrudeGeos);
        arr.forEach((val) => {
        if (val[0] == intersects[0].object.name) { //检测到的物体和省份数组元素对此
            val[1].traverse((obj) => {
                obj.material = depthMat; //5-7------悬停相应省份高亮
            });
        } else {
            val[1].traverse((obj) => {
                obj.material = extrudeMats; //否则还是之前暗的挤压体
            });
        }
        });
    }else{
        label.value.style.display = "none"; //不显示label
    }
}
// //测试box
// function createBox(){
//     const geometry = new THREE.BoxGeometry( 1, 1, 1 );
//     const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
//     const cube = new THREE.Mesh( geometry, material );
//     base.scene.add( cube );
// }


//5-2--------渲染  发光
function addPass(){
    let renderpass=new RenderPass(base.scene,base.camera)  //渲染通道把场景,相机包裹起来
    base.composer.addPass(renderpass)  //把通道添加到屏幕后处理
    let bloompass=new UnrealBloomPass( //发光通道
        new THREE.Vector2(window.innerWidth,window.innerHeight), //分辨率
        0.8, //发光强度
        0.1, //半径(扩散范围)
        0.0  //起始亮度值
    )
    base.composer.addPass(bloompass)  //发光通道添加到屏幕后处理里面
}

//1-3----加载地图
async function loadMap(){
    const fileloader=new THREE.FileLoader()
    
    let res=await Promise.all([
        // 1-2------加载地图数据   从阿里云下载的数据
        fileloader.loadAsync('/mapjson/china.json'),
        fileloader.loadAsync('/mapjson/chinaedg.json')
    ])
    if(res instanceof Array){
        // console.log(JSON.parse(res[0]))
        //1-4--------创建地图
        createMap(res[0])
        //地图流光
        createMoveLight(res[1]);
    }
}

//创建球形墨卡托投影
const projection = d3
    .geoMercator() //地图投影方式(用于绘制地球墨卡托投影)
    .center([108.5525,34.3277])//地图中心点经纬度坐标
    .scale(84) //缩放
    .translate([0,0]) //移动地图位置

//1-4------创建3D地图
let chinaObj=new THREE.Object3D()
const extrudeGeos = {};//存储省份
function createMap(res){
    res=JSON.parse(res)
    let centerCoord=null
    res.features.forEach((province)=>{
        let provinceObj=new THREE.Object3D()
        console.log(province.geometry.type);//多边形/多个多边形

        if(province.geometry.type=='MultiPolygon'){

            province.geometry.coordinates.forEach((multipolygon)=>{

                multipolygon.forEach((polygon)=>{
                    let shape=new THREE.Shape()
                    let arr=[]
                    polygon.forEach((coord,index)=>{
                        //corrd是球坐标,要转换成二维坐标
                       
                        let [x,y]=projection(coord)
                        if(index==0){
                            shape.moveTo(x,-y) //把原点设置在解构出来的x,y
                        }else{
                            shape.lineTo(x,-y)
                        }
                        arr.push(x,-y,1) //画线    
                    })
                    //1-5------绘制形状
                    let mesh=createPolygon(shape,arr, province)
                    provinceObj.add(mesh)

                })

            })

        }else if(province.geometry.type=='Polygon'){

            province.geometry.coordinates.forEach((polygon)=>{
                let shape=new THREE.Shape()
                let arr=[]
                polygon.forEach((coord,index)=>{
                    let [x,y]=projection(coord)
                    if(index==0){
                        shape.moveTo(x,-y) //把原点设置在解构出来的x,y
                    }else{
                        shape.lineTo(x,-y)
                    }
                    arr.push(x,-y,1) //画线 
                })
                //创建多边形
                let mesh=createPolygon(shape,arr, province)
                provinceObj.add(mesh)

            })

        }

        // 2---2显示中心光点
        if(province.properties.name==centerAddress){ //等于中心位置
            centerCoord = createSprite(province,'/sprites/光圈.png',true)
        }
        if (province.properties.name) {
            extrudeGeos[province.properties.name] = provinceObj;
        }
        chinaObj.add(provinceObj)
    })

    //中心点坐标不为空
    if(centerCoord){
        res.features.forEach((province)=>{
            //3-1-----飞线
            if(flylineAddress.indexOf(province.properties.name)!=-1){
                let address=createSprite(province,'/sprites/circle.png',false)
                // addressLines(centerCoord,address)
                FlyLine(centerCoord,address)
            }
            //4-1------飞机
            if(airplaneAddress.indexOf(province.properties.name)!=-1){
                let address=createSprite(province,"/sprites/circle.png",false)
                createSprite(province,"/sprites/圆圈.png",true,3)
                // addressLines(centerCoord,address)
                AirLines(centerCoord, address);
            }
        })
    }
    base.scene.add(chinaObj)
}



//挤压材质
const extrudeMats=[
    new THREE.MeshStandardMaterial({ //标准材质
        color:0x0000ff,
        transparent:true,
        opacity:0.85,
        blending:THREE.AdditiveBlending,//混合模式:叠加
    }),
    new THREE.MeshStandardMaterial({ //标准材质
        color:0x0000ff,
        transparent:true,
        opacity:0.35,
        blending:THREE.AdditiveBlending,//混合模式:叠加
    }),
]
//边缘材质
const edgMat=new THREE.LineBasicMaterial({//基础边缘材质
    color:0xffffff,
    // blending:THREE.AdditiveBlending, //边缘也发光会模糊
})

//1-5------绘制形状
function createPolygon(shape,arr,province){
    //二维变成三维物体  使用挤压缓冲几何体
    //创建一个挤压几何体
    let geo=new THREE.ExtrudeGeometry(shape)
    let mesh=new THREE.Mesh(geo,extrudeMats) //添加到省份的obj里
    mesh.layers.set(1);
    if(province.properties.name){//类似于 河北省保定市
        mesh.name=province.properties.name
    }
    //创建画线
    let buffer=new THREE.BufferGeometry()
    buffer.setAttribute(
        'position',
        new THREE.BufferAttribute(new Float32Array(arr),3)//3代表 x,y.z
    )
    //创建一个线段
    let line=new THREE.Line(buffer,edgMat)
    chinaObj.add(line)

    return mesh;

}

const textureloader=new THREE.TextureLoader()  //加载图片
const animCircle=[]  //接收需要动画的物体

const PointsCount=100  //曲线分成几段
const addressMat=new THREE.LineBasicMaterial({  //材质
    color:0xff0000,
})
//创建两点之间的固定曲线
function addressLines(start,end){
    //三维二次贝塞尔曲线
    const curve=new THREE.QuadraticBezierCurve3(
        new THREE.Vector3(...start), //起始点位
        new THREE.Vector3( (start[0]+end[0])/2 , (start[1]+end[1])/2 , 9 ),  //中间点
        new THREE.Vector3(...end), //结束点位
    )

    const points = curve.getPoints(PointsCount)  //把曲线上的点拿下来
    const buffer = new THREE.BufferGeometry().setFromPoints(points)  //根据点位信息来创建BufferGeometry
    //生成线段
    const line=new THREE.Line(buffer,addressMat)
    chinaObj.add(line)
    return {points,curve}

}
//创建精灵图片
function createSprite(
    province,  //省份信息
    imageurl,  //图片路径
    needanim,  //是否需要动画
    scaleparam = 2.5, //缩放参数
    opacityparam =10.0, //透明度参数
    speed = 0.015   //速度
){
  let [x,y] = projection(province.properties.center) //省份中心点坐标
  const map=textureloader.load(imageurl)
  let material=new THREE.MeshStandardMaterial({//生成基础材质
    map,
    transparent:true,
    depthWrite:false, //深度写入
    depthTest:false,  //深度测试
    blending: THREE.AdditiveBlending, //混合叠加
  })

  //创建精灵图片
  let sprite=new THREE.Sprite(material)
  if(needanim){
    animCircle.push({
        circle:sprite,
        offset:0, //偏移
        speed,
        scaleparam,
        opacityparam,
    })
  }
  sprite.position.set(x,-y,1)
  chinaObj.add(sprite)
  return [x,-y,1]
}



//3-2-----飞线效果
const movePointsCount=35
const flyMat=new THREE.ShaderMaterial({
    transparent:true,
    vertexShader:
    `
    attribute float mopacity,
    varying float vopacity,
    void main(){
        vopacity=mopacity;
        //当前位置=投影矩阵*模型矩阵*传入的position
        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0)

    }`,
    fragmentShader:
    `
    varying float vopacity,
    void main(){
        gl_fragColor=vec4(0.0,vopacity,0.0,vopacity)

    }`
})
//飞线
const flylines=[]
function FlyLine(start,end){
    let {points}=addressLines(start,end)
    let slicepoint=points.slice(0,movePointsCount)
    let linegeo=new THREE.BufferGeometry().setFromPoints(slicepoint)
    let arr=[movePointsCount]
    for (let i = 0; i < movePointsCount; i++) {
        arr[i] = i * (1 / movePointsCount);
    }
    linegeo.setAttribute(
        'mopacity',
        new THREE.BufferAttribute(new Float32Array(arr), 1)
    ) //设置透明度
    let line=new THREE.Line(linegeo,flyMat)
    chinaObj.add(line)
    flylines.push({
        linegeo,
        points,
        speed:2,
        offset:0
    })    
}

//4-2------飞机
const airMat = new THREE.MeshBasicMaterial({
  map: textureloader.load("/sprites/飞机.png"),
  transparent: true,
  depthTest: false,
  depthWrite: false,
  side: THREE.DoubleSide,
});
const airSprites = [];
function AirLines(start, end) {
  let { points, curve } = addressLines(start, end);
  let sprite = new THREE.Sprite(airMat);
  sprite.scale.set(3, 3, 3);
  let tangent = curve.getTangent(0.5); //切线
  let r = tangent.angleTo(new THREE.Vector3(0, 1, 0)); //改变飞机朝向切线
  if (tangent.x < 0) sprite.rotateZ(r);
  else sprite.rotateZ(-r);
  // sprite.position.set(...points[points.length - 1]);
  chinaObj.add(sprite);
  airSprites.push({
    sprite,
    points,
    speed: 1,
    offset: 0,
  });
}

//地图流光
let lightPoints = []; //存储地图边界位置数组
//流光的Geo
let lightGeo = new THREE.BufferGeometry();
let lightCount = 70; //流光长度
let lightSpeed = 10; //流光速度
let lightOffset = 0; //偏移
//材质
const lightMat = new THREE.PointsMaterial({
  color: 0xffffff,
  size: 0.25,
});
//流光物体
let lightpoint;
function createMoveLight(res) {
  res = JSON.parse(res);
  res.features.forEach((province) => {
    if (province.geometry.type == "MultiPolygon") {
      province.geometry.coordinates.forEach((multipolygon) => {
        multipolygon.forEach((polygon) => {
          polygon.forEach((coord) => {
            let [x, y] = projection(coord);
            lightPoints.push([x, -y, 1]);
          });
        });
      });
    }
  });
  lightpoint = new THREE.Points(lightGeo, lightMat);
  chinaObj.add(lightpoint);
}
let lightArr = [lightCount];

//更新
function update(){
    //每帧调用
    requestAnimationFrame(update)
    animCircle.forEach((val)=>{
        val.circle.material.opacity = val.opacityparam*Math.pow((1-val.offset),2)  //透明度
        val.circle.scale.set(  //缩放
            val.scaleparam * val.offset,
            val.scaleparam * val.offset,
            val.scaleparam * val.offset
        )
        val.offset+=val.speed  //偏移量+=速度
        val.offset %=1 //偏移量取余等于1
    })
    flylines.forEach((val) => {
        let slicepoint = val.points.slice(val.offset, val.offset + movePointsCount);
        val.linegeo.setFromPoints(slicepoint);
        val.offset += val.speed;
        val.offset %= val.points.length;
    });
    airSprites.forEach((val) => {
        val.sprite.position.set(...val.points[val.offset]);
        val.offset += val.speed;
        val.offset %= val.points.length;
    });

    for (let i = 0; i < lightCount; i++) {
        lightArr[i] = lightPoints[(lightOffset + i) % lightPoints.length];
    }
        lightOffset += lightSpeed;
    if ((lightOffset % lightPoints.length) + lightCount < lightPoints.length) {
        lightOffset %= lightPoints.length;
    }
    lightGeo.setAttribute(
        "position",
        new THREE.BufferAttribute(new Float32Array(lightArr.flat(1)), 3)
    );
    lightpoint.geometry.attributes.position.needsUpdate = true;

    base.update();
    controls.update();

    base.updateComposer()
}

function resize(){
    //屏幕宽高比发生变化执行
    base.resize()

    base.resizeComposer()
}


</script>
<style lang='less' scoped>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  
}

div{
    height: 100%;
    width: 100%;
    .threeJSBox{
        width: 100%;
        height: 100%;
        border: 1px solid #f00;
        #containers{
            width: 100%;
            height: 100%;
            border: 1px solid #f00;
            .label{
                position: absolute;
                top:0;
                left: 0;
                background-color: rgba(0,0,0,0.5);
                z-index:99;
                text-align: center;
                width: 100px;
                height: 30px;
                line-height: 30px;
                padding: 0 10px;
                span {
                    opacity: 1;
                    color: #ffffff;
                }

            }
        }

    }
    
   
}
</style>

4.标记点数据 放在一个js文件里

//飞线坐标
export const flylineAddress = [
  "湖北省",
  "甘肃省",
  "内蒙古自治区",
  "山东省",
  "河南省",
  "陕西省",
  "青海省",
  "辽宁省",
  "山西省",
];

//飞机坐标
export const airplaneAddress = [
  "新疆维吾尔自治区",
  "西藏自治区",
  "黑龙江省",
  "广东省",
  "云南省",
];

//中心点位
export const centerAddress = "北京市";
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
使用Vue3和Three.js渲染室内地图有几个主要步骤。 首先,你需要准备好室内地图的数据。这可以是一个包含房间、墙壁、家具等元素的3D模型文件,如OBJ或GLTF格式。你还可以考虑将地图数据转换为JSON格式,并使用自定义脚本生成3D对象。 接下来,在Vue3中创建一个Three.js的场景(Scene)。你可以使用Vue Composition API来创建一个自定义的Vue组件,该组件将负责Three.js的初始化和场景的绘制。在这个组件中,你将使用Three.js的PerspectiveCamera来设置透视投影相机,并使用OrbitControls插件来实现用户交互控制。 然后,你需要加载地图数据并将其转换为Three.js的3D对象。你可以使用Three.js提供的加载器(Loader)来加载3D模型文件。完成加载后,你可以将模型添加到场景中,并设置其位置、旋转等属性。 最后,你可以根据需要添加光源、阴影效果、材质等来提高渲染效果。你可以通过创建Three.js的光源对象,如DirectionalLight或SpotLight,来模拟现实世界的光照。你可以使用Three.js的材质(Material)来定义模型的外观和反射属性,如颜色、贴图等。 通过以上步骤,你可以在Vue3应用中使用Three.js渲染室内地图。不过,请注意,使用Three.js进行复杂的渲染可能对性能有一定要求,你可能需要优化性能,如使用LOD(多级细分)模型、合并几何体等技术来提高性能并避免卡顿。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值