最近在研究Geotoolkit过程中,发现很多三维的应用场景,用其实现起来比较复杂,就开展了利用Three.js实现海底管道流动的模拟。
推荐一个学习地址:Three.js教程,这这里的学习示例基本上H5+js,再多加以一点nodejs,对于vue+Three.js的开发方式比较少,包括官网的示例也是前者,因此就促使自己尝试用vue+Three.js的学习研究,这样后续和目前的前端集成起来就方便一些。
先说明一下,我的开发环境:vue-2.5.2,Three.js-0.142.0,开发工具webstorm2021.2.3,其他都默认。
示例:实现了海底管道的三维显示及三维液体流向的模拟,效果如下:
vue+Three.js,通过不断改变管道的Texture实现管道流动模拟,代码详见pipe4.vue。
核心代码如下:
<script>
import * as THREE from 'three';
// 这样引入一下灯光和材质 也可以使用 THREE.的方式
import {HemisphereLight,SphereGeometry} from 'three'
// 轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 此部分为了展示为hardcode
const pathArr = [
10.99, 23.38, -58.11,
10.47, 46.14, -58.36,
10.69, 46.14, -18.40,
-69.28, 46.14, -18.04,
-70.43, 46.14, 10.46,
-80.43, 46.14, 10.46,
-200.43, 46.14, -150.46
]
const radius = 3
const z=20;//管道下移数量
export default {
name: "pipe4",
data() {
return {
renderer: null,
// camera 相机
camera: null,
// Scene场景
scene: null,
// 灯光
light: null,
// 形状 球形
sphereGeometry: null,
// 材质
material: null,
// 网格
sphereMesh: null,
// 控制器
control: null,
effect: null
}
},
mounted() {
this.container = this.$refs.container;
this.init();
// this.initGrid();
this.animate();
},
methods: {
/**
* 初始化方法
*/
init() {
/*
* 1. 渲染器:WebGLRenderer
* 2. 相机:PerspectiveCamera
* 3. 场景和灯光:Scene,Light
* 4. Mesh:需要渲染的模型对象
* 4.1 几何形状或体,如SphereGeometry(球),CylinderGeometry(圆柱),TubeGeometry(管道)等
* 4.2 材质,MeshBasicMaterial,MeshPhongMaterial,需要设置颜色或者TextureLoader(指定材质图片的url)
* 5. 刷新:实现渲染器的不断刷新,详见renderScene,包括了requestAnimationFrame
* 6. 控制器:控制相机的运动,如OrbitControls
* */
// 1.antialias:true抗锯齿
this.renderer = new THREE.WebGLRenderer({antialias: true})
// 1.设置渲染器的大小和页面大小一样大
this.renderer.setSize(window.innerWidth, window.innerHeight)
// 切换背景颜色
this.renderer.setClearColor('rgba(255,255,255,0.54)')
// 将canvas装载在主体的模型上
// document.body.appendChild(this.renderer.domElement)
// 2.相机
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, window.innerWidth / window.innerHeight, 1000)
// 监听窗口大小
window.addEventListener('resize', () => {
this.renderer.setSize(window.innerWidth, window.innerHeight)
// 相机的宽高比
this.camera.aspect = window.innerWidth / window.innerHeight
// 设置了Aspect 之后必须更新相机的投影矩阵
this.camera.updateProjectionMatrix()
})
// 设置相机的位置
this.camera.position.set(0, 100, 200)
// 3.创建一个场景
this.scene = new THREE.Scene()
// 3.1创建一个纹理图片加载器加载图片
var textureLoader = new THREE.TextureLoader();
// 3.2加载背景图片
var texture = textureLoader.load('http://localhost:8082/static/data/background.png');
// 纹理对象Texture赋值给场景对象的背景属性.background
this.scene.background = texture
// 3.3灯光
this.light = new HemisphereLight('#2cef00', '#ef0038')
// 将灯光添加到场景中
this.scene.add(this.light)
//环境光
var ambient = new THREE.AmbientLight('#ef0038');
// 将灯光添加到场景中
this.scene.add(ambient)
//===============================================================
// 4.Mesh
this.createTube();
//================================================================
// 4.创建形状
this.sphereGeometry = new SphereGeometry(radius*3, 12, 12)
// 4.2创建材质
// this.material = new THREE.MeshPhongMaterial();//已加入图片材质,见下一行
let material3=new THREE.MeshBasicMaterial({map:new THREE.TextureLoader().load('/static/data/earth.jpg'),side:THREE.DoubleSide});
// 根据形状和材质创建网格
this.sphereMesh = new THREE.Mesh(this.sphereGeometry, material3)
this.sphereMesh.position.set(-25, -47, -135)//x,z,y
this.scene.add(this.sphereMesh)
// 5.渲染场景和相机
this.renderer.render(this.scene, this.camera)
// 6.控制器 第一个参数是相机第二个参数是渲染器
this.control = new OrbitControls(this.camera, this.renderer.domElement)
// 将canvas装载在主体的模型上
let myDiv = document.getElementById("myDiv")
myDiv.appendChild(this.renderer.domElement);
this.update()
},
// 4.Mesh 动态创建一个管道
createTube(){
let curveArr = []
// 三个一组取出curve数据
for(let i=0; i < pathArr.length; i+=3) {
curveArr.push(new THREE.Vector3(pathArr[i], pathArr[i+1], pathArr[i+2]))
}
var curve = new THREE.CatmullRomCurve3(curveArr);
/**
* TubeGeometry(path : Curve, tubularSegments : Integer, radius : Float, radialSegments : Integer, closed : Boolean)
*/
var tubeGeometry = new THREE.TubeGeometry(curve, 100, radius, 50, false);
var textureLoader = new THREE.TextureLoader();
var texture = textureLoader.load('http://localhost:8082/static/data/arrow1.jpg');
// 设置阵列模式 RepeatWrapping
// texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
// 设置x方向的重复数(沿着管道路径方向)
// 设置y方向的重复数(环绕管道方向)
texture.repeat.x = 10;
texture.repeat.y = 2;
// 设置管道纹理偏移数,便于对中
texture.offset.y = 0.5;
var tubeMaterial = new THREE.MeshPhongMaterial({
map: texture,
transparent: true
});
var mesh = new THREE.Mesh(tubeGeometry, tubeMaterial);
mesh.position.y = 2;
mesh.rotateZ(3.14);
mesh.scale.set(2, 2, 2);
// 使用加减法可以设置不同的运动方向
setInterval(() => {
texture.offset.x -= 0.0036
})
this.scene.add(mesh)
},
/**
* 定义一个刷新函数
*/
update() {
// 需要不断的进行渲染更新
this.renderer.render(this.scene, this.camera)
requestAnimationFrame(this.update)
},
animate() {
requestAnimationFrame( this.animate );
this.sphereMesh.rotation.y += 0.01;
this.render();
},
render() {
const timer = 0.001 * Date.now();
// this.camera.position.x += ( mouseX - this.camera.position.x ) * .05;
// this.camera.position.y += ( - mouseY - this.camera.position.y ) * .05;
this.camera.lookAt( this.scene.position );
for ( let i = 0, il = this.sphereGeometry.length; i < il; i ++ ) {
const sphere = this.sphereGeometry[ i ];
sphere.position.x = 5 * Math.cos( timer + i );
sphere.position.y = 5 * Math.sin( timer + i * 1.1 );
}
// this.effect.render( this.scene, this.camera );
},
//初始化网格
initGrid(){
let grid = new THREE.GridHelper( 300, 8, 0xffffff, 0xffffff );
grid.position.set(0,-100,-50);//xzy
this.scene.add( grid );
}
}
}
</script>