一.前言
在网上找了很久都没有找到使用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);