在网上找了很久都没有找到使用Three.js开发3d的免费文章或者免费视频,自己花了一点时间做了一个纯前端的项目demo。
模型都是在网上免费下载的,没有那么精细美观,请见谅。
此文章只用于想学习three.js的小伙伴做学习用途
技术栈都是最新的:vue3+vite+typeScript+Three+antv G2
有地面版本
无地面版本
开发各种框架的版本 开发工具为vscode
引入three,先初始化场景,相机,渲染器,光线,轨道控制器
先打印一下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坐标轴了,接下来我们开始导入模型和天空图盒子
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); });
//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后,使用监听鼠标的方法点击了网页哪里的方法触发事件
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动态光圈出来
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()})
让几合体(光圈)动起来
这个动态方法要放在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);}
这样光圈就开始动起来了 3d部分就讲完了
接下来的图表和页面样式
App.vue
<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的代码
<!-- --><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引入组件
<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>
素材
图片放在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);
原文链接:
https://juejin.cn/post/7293463921729372201