Vue3 + Three.js + antvG2 实战智慧城市

点击上方 前端Q,关注公众号

回复加群,加入前端Q技术交流群

在网上找了很久都没有找到使用Three.js开发3d的免费文章或者免费视频,自己花了一点时间做了一个纯前端的项目demo。 模型[1]都是在网上免费下载的,没有那么精细、美观请见谅。技术栈都是最新的:vue3+vite+typeScript+Three+antv G2 项目源码[2]

此文章只用于想学习three.js的小伙伴做学习用途。

有地面版本
1e794c09148b8238cdc090652a5c4fba.png
无地面版本
f6467170ee16c3c841ec202c2a61ddf6.png
开发各种框架的版本
  • "vue": "^3.2.47"

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

  • "typescript": "^5.0.2"

  • "vite": "^4.3.0"

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

搭建three场景

引入three.js,先初始化场景,相机,渲染器,光线,轨道控制器。先打印一下three看一下有没有输出,然后再搭建场景等…

<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坐标轴了,接下来我们开始导入模型和天空图盒子

1213fa61a117253470310421879372b0.png

加载gltf模型

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);
  });
18b19f0c2d4163fe88d0eeeb12dc44b6.png

加载天空盒子

//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;
ef129dd35aa9c07b02476a4742e8ac41.png

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

加贴图文字

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

  • 1.写一个canvas文字

  • 2.canvas转成图片

  • 3.three纹理材质导入图片

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

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

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()
})
fcae38b4ff8ce27003c43ef0135525ae.png

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

做一个three动态光圈

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

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

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

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()
})
76be8e42897c11d02b9285566c133605.png

让几何体(光圈)动起来,这个动态方法要放在animate方法里面

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);
}
7d5370717f48c8cbc5b10898f87bd3ed.png

这样光圈就开始动起来了,3d部分就讲完了,接下来就是图表和页面样式

图标和头部

<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>

效果如下:

7d629619ddc5717a63f4d2f919f09df0.png

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

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

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

  • 3.创建一个地面

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

5f130206fde51bafcc536c5d4491a1bf.png

然后在app.vue引入组件

<div class="container" id="container">
    <header class="header">three学习</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>

创建一个地面

素材:

7516aed1e685114cf34e28ca09b7cc8b.png

图片放在public文件夹

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

  • 2.然后设置重复次数

  • 3.定位到模型的下面

  • 4.将地板添加到场景中

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

//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);

最终效果:

f9ec5f93a0072bc7d937b51d28df8b45.png

参考资料

[1]

模型提取码1234: https://pan.baidu.com/share/init?surl=07PKQEfDZfmsBN_aKbPcgw

[2]

项目源码: https://gitee.com/fantianyuan/wisdom-city

作者:范天缘

链接:https://juejin.cn/post/7293463921729372201

ee02b0b06f2878cb23e5435eb9c0f159.png

往期推荐

中美程序员不完全对比,太真实了。。。

a68c197c2ea238fb508c5140a69d6e6f.png

两行CSS让长列表性能渲染提升7倍!

b9ded1330d016b20187c6c0bc815b355.png

字节面试:如何实现准时的setTimeout

a73deeda3bb947175f5e1eafa6f55688.png


最后

  • 欢迎加我微信,拉你进技术群,长期交流学习...

  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人...

f1f6bdd27f17e02f3a3189bb636fcbc1.jpeg

63f3054c662b08b89f7ecc04f1c11d1b.png

点个在看支持我吧

8dbcbb98034c1b1e53f5449778996425.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值