用Three.js做一个智慧城市-纯前端(Vue3+Three.js+antvG2)

一.前言

在网上找了很久都没有找到使用Three.js开发智慧城市的免费文章或者免费视频,自己花了一点时间做了一个纯前端的智慧城市项目。

技术栈都是最新的:vue3+vite+typeScript+Three+antv G2

源码分享 源码

模型,天空图盒子链接分享(不想下载源码可以只用下这个)提取码1234 ------------------------------------------------

有地面版本

无地面版本

开发各种框架的版本 开发工具为vscode

"vue": "^3.2.47"

"@antv/g2plot": "^2.4.29",

"typescript": "^5.0.2",

"vite": "^4.3.0",

"@types/three": "^0.150.2",

一.搭建three场景

引入three,先初始化场景,相机,渲染器,光线,轨道控制器

先打印一下three 看一下有没有输出,然后在搭建场景等...

xml 复制代码
<template>
  <div class="container" id="container"></div>
</tempalte>
<script lang="ts" setup>
let scene = null as any,//场景
camera = null as any,//相机
renderer = null as any,//渲染器
controls = null as any//轨道控制器
import {onMounted, reactive } from 'vue';
import * as THREE from 'three';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
//设置three的方法
const render = async () =>{
  //1.创建场景
  scene = new THREE.Scene();
  //2.创建相机
  camera = new THREE.PerspectiveCamera(105,window.innerWidth/window.innerHeight,0.1,1000);
  //3.设置相机位置
  camera.position.set(0,0,4);
  scene.add(camera);
  //4.建立3个坐标轴
  const axesHelper = new THREE.AxesHelper(5);
  scene.add(axesHelper);
  
  //6.设置环境光,要不然模型没有颜色
  let ambientLight = new THREE.AmbientLight(); //设置环境光
  scene.add(ambientLight); //将环境光添加到场景中
  let pointLight = new THREE.PointLight();
  pointLight.position.set(200, 200, 200); //设置点光源位置
  scene.add(pointLight); //将点光源添加至场景


  //7.初始化渲染器
  //渲染器透明
  renderer = new THREE.WebGLRenderer({
    alpha:true,//渲染器透明
    antialias:true,//抗锯齿
    precision:'highp',//着色器开启高精度
  });
  
  //开启HiDPI设置
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  //设置渲染器尺寸大小
  renderer.setClearColor(0x228b22,0.1);
  renderer.setSize(window.innerWidth,window.innerHeight);
  //将webgl渲染的canvas内容添加到div
  let container = document.getElementById('container') as any;
  container.appendChild(renderer.domElement);
  //使用渲染器 通过相机将场景渲染出来
  renderer.render(scene,camera);
  controls = new OrbitControls(camera,renderer.domElement);
}
const animate = () =>{
   requestAnimationFrame(animate);
   renderer.render(scene,camera);
}
onMounted(()=>{
  render()
  animate()
})
</script>
<style scoped>
.container{
  width:100vw;
  height: 100vh;
  overflow: hidden;
}
</style>

现在我们就看到了three坐标轴了,接下来我们开始导入模型和天空图盒子

二.加载gltf模型

typescript 复制代码
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
//5.导入gltf模型
  const gltfLoader = new GLTFLoader();
  
  gltfLoader.load('./model/scene.gltf',function(object){
    console.log(object)
    scene.add(object.scene);
  });

三.加载天空盒子

arduino 复制代码
//1.1 创建天空盒子
  const textureCubeLoader = new THREE.CubeTextureLoader();
  const textureCube = textureCubeLoader.load([
    "../public/img/right.jpg",//右
    "../public/img/left.jpg",//左
    "../public/img/top.jpg",//上
    "../public/img/bottom.jpg",//下
    "../public/img/front.jpg",//前
    "../public/img/back.jpg",//后
  ])
  scene.background = textureCube;
  scene.environment = textureCube;

现在我们可以看到模型和天空盒子了,接下来我们讲如何给three加文字进去 以及触发文字事件

四.加贴图文字

这里我们使用canvas写文字然后转成图片 最后使用three的纹理材质导入到three里面

1.写一个canvas文字

2.canvas转成图片

3.three纹理材质导入图片

4.定位到想要显示的地方

文字显示到three后,使用监听鼠标的方法点击了网页哪里的方法触发事件

typescript 复制代码
let canvas = null as any //文字
//创建three文字
const threeText = () => {
  //用canvas生成图片
  canvas = document.getElementById('canvas');
  canvas.width = 300
  canvas.height = 300
  let ctx = canvas.getContext('2d')
  //制作矩形
  ctx.fillStyle = "rgba(6,7,80,0.8)";
  ctx.fillRect(0,0,80,20);
  //设置文字
  ctx.fillStyle = "#fff";
  ctx.font = 'normal 10pt "楷体"'
  ctx.fillText('东方明珠', 12.5, 15)
  //生成图片
  let url = canvas.toDataURL('image/png');
  //将图片放到纹理中
  let geoMetry1 = new THREE.PlaneGeometry(30,30);
  let texture = new THREE.TextureLoader().load(url);
  let material1 = new THREE.MeshBasicMaterial({
    map:texture,
    side:THREE.DoubleSide,
    opacity:1,
    transparent:true
  })
  let rect = new THREE.Mesh(geoMetry1,material1)
  rect.position.set(10,1,-13)
  scene.add(rect)
}
//触发东方明珠点击事件
const threeTextClick = () =>{
  window.addEventListener('click',(event)=>{
    console.log(event.clientX)
    if(event.clientX > 855 && event.clientX < 1022){
      alert("触发了点击事件")
    }else{return}
  })
}
onMounted(()=>{  
  threeText()
  threeTextClick()
})

我们接下来做一个three动态光圈出来

五.做一个three动态光圈

1.先创建一个three的圆柱几何体

2.给几何体加载一个合适的纹理

3.然后让他缓慢变大,重复运动

php 复制代码
let cylinderGeometry = null as any//光圈
//创建光圈
const aperture = () =>{
  //创建圆柱
  let gemetry = new THREE.CylinderGeometry(1,1,0.2,64);
  //加载纹理
  let texture = new THREE.TextureLoader().load('../public/img/cheng.png');
  texture.wrapS = texture.wrapT = THREE.RepeatWrapping;//每个都重复
  texture.repeat.set(1,1);
  texture.needsUpdate = true;

  let material = [
    //圆柱侧面材质,使用纹理贴图
    new THREE.MeshBasicMaterial({
      map:texture,
      side:THREE.DoubleSide,
      transparent:true
    }),
    //圆柱顶材质
    new THREE.MeshBasicMaterial({
      transparent:true,
      opacity:0,
      side:THREE.DoubleSide
    }),
    //圆柱顶材质
    new THREE.MeshBasicMaterial({
      transparent:true,
      opacity:0,
      side:THREE.DoubleSide
    })
  ];
  cylinderGeometry = new THREE.Mesh(gemetry,material);
  cylinderGeometry.position.set(0,-0.2,1);
  scene.add(cylinderGeometry);
}
onMounted(()=>{
  aperture()
})

让几合体(光圈)动起来

这个动态方法要放在animate方法里面

ini 复制代码
let cylinderRadius = 0;
let cylinderOpacity = 1;
//圆柱光圈扩散动画
const cylinderAnimate = () => {
  cylinderRadius += 0.01;
  cylinderOpacity -= 0.003;
  if (cylinderRadius > 1.6) {
    cylinderRadius = 0;
    cylinderOpacity = 1;
  }
  if (cylinderGeometry) {
    cylinderGeometry.scale.set(1 + cylinderRadius, 1, 1 + cylinderRadius); //圆柱半径增大
    cylinderGeometry.material[0].opacity = cylinderOpacity; //圆柱可见度减小
  }
}
const animate = () =>{
   cylinderAnimate()
   requestAnimationFrame(animate);
   renderer.render(scene,camera);
}

这样光圈就开始动起来了 3d部分就讲完了

接下来的图表和页面样式

六.图标和头部

App.vue

xml 复制代码
<template>
  <div class="container" id="container">
    <header class="header">智慧上海驾驶舱</header>
    <section class="leftTop"></section>
    <section class="leftCenter"></section>
    <section class="leftFooter"></section>
    <section class="rightTop"></section>
    <section class="rightCenter"></section>
    <section class="rightFooter"></section>
 </div>
</template>
<style>
.container{
  width:100vw;
  height: 100vh;
  overflow: hidden;
}
.header{
  width: 100vw;
  height: 80px;
  position: fixed;
  top: 0;
  text-align: center;
  font-size: 28px;
  letter-spacing: 4px;
  line-height: 65px;
  color:#fff;
  background-image: url("../public/img/23.png");
  background-size: 100% 100%;
  background-repeat: no-repeat;
}
.leftTop{
  width: 400px;
  height: 310px;
  position: fixed;
  z-index: 9999999;
  top: 40px;
  left:20px;
  border-radius: 10px;
  background-color: rgba(6,7,80,0.6);
}
.leftCenter{
  width: 400px;
  height: 310px;
  position: fixed;
  z-index: 9999999;
  top: 370px;
  left:20px;
  border-radius: 10px;
  background-color: rgba(6,7,80,0.6);
}
.leftFooter{
  width: 400px;
  height: 210px;
  position: fixed;
  z-index: 9999999;
  top: 700px;
  left:20px;
  border-radius: 10px;
  background-color: rgba(6,7,80,0.6);
}
.rightTop{
  width: 400px;
  height: 310px;
  position: fixed;
  z-index: 9999999;
  top: 40px;
  right:20px;
  border-radius: 10px;
  background-color: rgba(6,7,80,0.6);
}
.rightCenter{
  width: 400px;
  height: 310px;
  position: fixed;
  z-index: 9999999;
  top: 370px;
  right:20px;
  border-radius: 10px;
  background-color: rgba(6,7,80,0.6);
}
.rightFooter{
  width: 400px;
  height: 210px;
  position: fixed;
  z-index: 9999999;
  top: 700px;
  right:20px;
  border-radius: 10px;
  background-color: rgba(6,7,80,0.6);
}
</style>

效果如下

大致结构我们搭建好了 接下来的步骤

1.我们做几个antv的组件 柱状图 条形图 折线图的组件

2.然后引入到我们刚刚创建好的app.vue 的 div 里面去

在views文件夹里面创建如下图的文件夹

leftCenter文件夹->index.vue的代码

xml 复制代码
<!--  -->
<template>
  <div class="leftTopModule">
    <header class="head">
        <titleModule :title="state.titleName"></titleModule>
    </header>
    <section class="main" id="main"></section>
  </div>
</template>

<script setup>
import { onMounted, reactive } from 'vue'
import titleModule from '../../components/title/index.vue'
import { Bar } from '@antv/g2plot';
const state = reactive({
    titleName:'各区GDP'
})
//创建饼图
const createBar = () =>{
    //1.创建数据源
    const data = [
        { year: '浦东新区', sales: 15353 },
        { year: '徐汇区', sales: 2176 },
        { year: '长宁区', sales: 1561 },
        { year: '黄埔区', sales: 2616 },
        { year: '普陀区', sales: 1226 },
    ]
    //2.创建bar对象
    const barPlot = new Bar('main',{
        data,
        xField:'sales',
        yField:'year',
        colorField:'year',
        color:(d)=>{
            console.log(d)
            if(d.year == '浦东新区') return '#5AD8A6';
            if(d.year == '徐汇区') return '#F6BD16';
            if(d.year == '长宁区') return '#E86452';
            if(d.year == '黄埔区') return '#6DC8EC';
            if(d.year == '普陀区') return '#945FB9';
        },
        xAxis:{
            label:{
                visible:false,
                style:{
                    fontSize:17,
                }
            },
            tickLine:{
                visible:false
            }
        },
        yAxis:{
            label:{
                style:{
                    fontSize:17,
                }
            },
            grid:{
                visible:false
            }
        },
    })
    //3.渲染
    barPlot.render();
}

onMounted(()=>{
    createBar()
})
</script>
<style  scoped>
.leftTopModule{
    width: 100%;
    height: 100%;
}
.head{
    width: 100%;
    height: 15%;
    display: flex;
    align-items: center;
    justify-content: center;
}
.main{
    width: 95%;
    height: 82%;
    margin: 0 auto;
}
</style>

因为六个文件夹的代码太多了 这里我只做一个示例 其他五个文件夹的代码 可以复制leftCenter的

2.然后在app.vue引入组件

xml 复制代码
<div class="container" id="container">
    <header class="header">智慧上海驾驶舱</header>
    <section class="leftTop">
      <LeftTop />
    </section>
    <section class="leftCenter"></section> 
    <section class="leftFooter"></section>
    <section class="rightTop"></section>
    <section class="rightCenter"></section>
    <section class="rightFooter"></section>
</div>
<script lang="ts" setup>
import LeftTop from './views/leftTop/index.vue'
</script>

七.补充--如何做一个地面出来

素材

图片放在public文件夹

----------------------------手动分割

1.创建一个three的纹理贴图并把草地加载进来

2.然后设置重复次数

3.定位到模型的下面

4.将地板添加到场景中

以下请加在加载gltf模型的前面

vbnet 复制代码
 //4.创建地面  const groundTexture = new THREE.TextureLoader().load("./2.png");  groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping;  groundTexture.repeat.set(100, 100);  const ground = new THREE.CircleGeometry(500, 100);  const groundMaterial = new THREE.MeshLambertMaterial({    side: THREE.DoubleSide,    map: groundTexture,  });  const groundMesh = new THREE.Mesh(ground, groundMaterial);  groundMesh.name = "地面";  groundMesh.rotateX(-Math.PI / 2);  groundMesh.position.set(0, -0.345, 1);  scene.add(groundMesh);
相关推荐
KaiSonng13 小时前
【前端利器】这款轻量级图片标注库让你的Web应用瞬间提升交互体验
前端
二十雨辰13 小时前
vite性能优化
前端·vue.js
明月与玄武13 小时前
浅谈 富文本编辑器
前端·javascript·vue.js
paodan14 小时前
如何使用ORM 工具,Prisma
前端
布列瑟农的星空14 小时前
重学React——memo能防止Context的额外渲染吗
前端
FuckPatience14 小时前
Vue 与.Net Core WebApi交互时路由初探
前端·javascript·vue.js
小小前端_我自坚强14 小时前
前端踩坑指南 - 避免这些常见陷阱
前端·程序员·代码规范
lichenyang45314 小时前
从零实现JSON与图片文件上传功能
前端
WebGirl14 小时前
动态生成多层表头表格算法
前端·javascript
hywel15 小时前
一开始只是想整理下书签,结果做成了一个 AI 插件 😂
前端