当前位置:   article > 正文

Three.js - JS三维模型库在Vue2中的基础教程_vue threejs

vue threejs

 threejs 官网 :https://threejs.org/

 threejs 案例: https://threejs.org/examples/#webgl_animation_keyframes

threejs API:https://threejs.org/docs/index.html#manual/zh/introduction/Creating-a-scene  

开始使用

npm install --save three;

全局引用

import * as THREE from 'three';

按需引用 ,demo均使用全局

import {class} from 'three';

package-lock.json

  1. "three": "^0.151.3",
  2. "three-orbitcontrols": "^2.110.3",
  3. "vue": "^2.6.14",

一、基础使用

三大核心

  1. new THREE.WebGLRenderer()// 创建渲染器
  2. new THREE.Scene()  // 实例化场景
  3. new THREE.PerspectiveCamera()// 实例化相机

创建四个文件方便场景管理

渲染控制器ThreeController.js 作为 3d渲染的主要入口文件

ThreeController.js

  1. import * as THREE from 'three';
  2. export const renderer = new THREE.WebGLRenderer()  // 创建渲染器
  3. export const scene = new THREE.Scene()  // 实例化场景
  4. export const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000) 相机
  5. export class ThreeController {
  6.    Model = null;
  7.    scene = null;
  8.    constructor(Model) {  //构造器函数
  9.     Model.appendChild(renderer.domElement) //容器
  10.     renderer.setSize(Model.offsetWidth, Model.offsetHeight, true)
  11.     this.Model = Model
  12.     this.scene = scene
  13.     camera.position.set(100, 100, 100) // 设置相机位置
  14.     camera.lookAt(new THREE.Vector3(0, 0, 0))  // 设置相机看先中心点
  15.     camera.up = new THREE.Vector3(0, 1, 0)  // 设置相机自身的方向
  16.     renderer.shadowMap.enabled = true;
  17.     renderer.render(scene, camera);
  18. }
  19. // 外部访问将模型添加到场景中
  20.     addObject(...object) {
  21.       object.forEach(elem => {
  22.         this.scene.add(elem)
  23.       })
  24.     }

}

HomeView.vue

  1.  
  2. <template>
  3.   <div>
  4.     <div class="three-canvas" ref="threeTarget"></div>
  5.   </div>
  6. </template>
  7. import {ThreeController,} from "@/components/ThreeController";
  8. import { ModelListConfig} from"@/components/ModelListConfig";
  9. import { LightList } from "@/components/LightListConfig";
  10. import { allHelper } from "@/components/AxesHelperConfig";
  11. return { ThreeController: null,
  12. }
  13. mounted() {
  14. this.init(); 
  15. },
  16. methods: {
  17.   init() {
  18.       this.ThreeController = newThreeController(this.$refs.threeTarget);
  19.       this.ThreeController.addObject(...ModelListConfig);
  20.       this.ThreeController.addObject(...LightList);
  21.       this.ThreeController.addObject(...allHelper);
  22.     },

此时场景中一片漆黑

接下来添加模型  ModelListConfig.js

  1. import * as THREE from 'three';
  2. 具体模型类型参考Api
  3. export const ModelListConfig = []  存储模型数组,也可某个模型单独导出
  4. const sky = new THREE.TextureLoader().load(Require('sky.jpg'))
  5. 创建材质贴图
  6. export const MeshModel = new THREE.Mesh( 创建几何体
  7.   new THREE.BoxGeometry(20, 20, 20), 正方体 size 20
  8.   new THREE.MeshStandardMaterial({  材质纹理
  9.     color: 'rgb(36, 172, 242)',
  10.     // roughness: 0 , 光滑度 0最光滑
  11.     // metalness: 0, 金属度 1最像金属
  12.     map: sky
  13.   })
  14. )
  15. ModelListConfig.push(MeshModel) 将模型添加到数组中
  16. // 多人开发用来存储数据
  17. MeshModel.userData = {
  18.   name: 'MeshModel',
  19.   user: '我是正方体模型'
  20. }

 可以看到场景依旧是漆黑,所以要“开灯”即添加光线

 接下来添加光线 LightListConfig.js

  1. import * as THREE from 'three';
  2. export const LightList = []
  3. // // 添加环境光(自然光),设置自然光的颜色,设置自然光的强度(0 最暗, 1 最强)
  4. const hemiLight = new THREE.HemisphereLight("#A09E9E", 0.5);
  5. hemiLight.position.set(0, 40, 15);
  6. LightList.push(hemiLight)
  7. const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5);
  8. LightList.push(hemiLighthelper)
  9. export const pointLight = new THREE.PointLight(
  10. 'rgb(255,255,255)',
  11. 0.7,
  12. 600,
  13. 0.2
  14. )
  15. pointLight.position.set(0, 1, 50) // 设置点光源位置 (x,y,z)
  16. LightList.push(pointLight)

这时模型就可以正常显示了

创建辅助线 AxesHelperConfig.js

  1. import { AxesHelper ,GridHelper } from 'three'
  2. /**
  3. * 场景中添加辅助线
  4. * @param {allHelper.push(new AxesHelper())}
  5. * 添加栅格
  6. * @param {allHelper.push(new GridHelper())}
  7. */
  8. export const allHelper = []
  9. // 坐标辅助
  10. export const axesHelper = new AxesHelper(200) // 创建坐标辅助 (500 为辅助线的长度)
  11. export const gridHelper = new GridHelper(500, 20, 'green', 'rgb(255, 255, 255)')
  12. allHelper.push(gridHelper,axesHelper) // 添加到辅助列表
  13. /*
  14. gridHelper Config
  15. size -- 坐标格尺寸. 默认为 10. 这就是网格组成的大正方形最大是多少
  16. divisions -- 坐标格细分次数. 默认为 10. 组成的最大的正方向平均分多少份
  17. colorCenterLine -- 中线颜色. 值可以为 Color 类型, 16进制 和 CSS 颜色名. 默认为 0x444444。这个是指网格和坐标的 x轴 z 轴重合线的颜色。
  18. colorGrid -- 坐标格网格线颜色. 值可以为 Color 类型, 16进制 和 CSS 颜色名. 默认为 0x888888
  19. */

 效果

二、射线控制器 

相机视角拖拽 ThreeController.js 需要射线控制器 OrbitControls 因为渲染成是三维之后,我们点击的是相机呈现的二维浏览器画面,距离模型到页面上是有一定距离的,简单来说就像我们站在一个物体面前,用手机拍照时,点击的照片中的物体,实际上我们点击的并不是物体,而是相机渲染给我们的一个二维照片。OrbitControls,会穿透整个三维场并返回一个list,第[0]项就是我们想要点击模型。

ThreeController.js

  1. import { OrbitControls }from'three/examples/jsm/controls/OrbitControls'
  2. import { TransformControls } from 'three/examples/jsm/controls/TransformControls'
  3. const mouse = new THREE.Vector2() // 初始化鼠标位置
  4. const raycaster = new THREE.Raycaster()//初始化射线发射器
  5. // 屏幕鼠标x,屏幕鼠标y 视图宽度,视图高度
  6. let x = 0; let y = 0; let width = 0; let height = 0
  7. constructor(Model) {
  8. EventInjection(camera) //要在渲染器之前
  9. renderer.render(scene, camera);
  10. }
  11. export const EventInjection=()=>{
  12. // 鼠标移动事件
  13. const transformControls = new TransformControls(camera, renderer.domElement)
  14. renderer.domElement.addEventListener("mousemove", event => {
  15. x = event.offsetX
  16. y = event.offsetY
  17. width = renderer.domElement.offsetWidth
  18. height = renderer.domElement.offsetHeight
  19. mouse.x = x / width * 2 - 1
  20. mouse.y = -y * 2 / height + 1
  21. })
  22. let transing = false
  23. transformControls.addEventListener("mouseDown", event => {
  24. transing = true
  25. return event
  26. })
  27. // 鼠标点击事件
  28. renderer.domElement.addEventListener("click", event => {
  29. if (transing) {
  30. transing = false
  31. return
  32. }
  33. scene.remove(transformControls) // 移除变换控制器
  34. transformControls.enabled = false // 停用变换控制器
  35. raycaster.setFromCamera(mouse, camera) // 配置射线发射器,传递鼠标和相机对象
  36. const intersection = raycaster.intersectObjects(scene.children) // 获取射线发射器捕获的模型列表,传进去场景中所以模型,穿透的会返回我们
  37. if (intersection.length) {
  38. const object = intersection[0].object // 获取第一个模型
  39. console.log(object)
  40. scene.add(transformControls) // 添加变换控制器
  41. transformControls.enabled = true // 启用变换控制器
  42. transformControls.attach(object)
  43. }
  44. return event
  45. })
  46. // 监听变换控制器模式更改
  47. document.addEventListener("keyup", event => {
  48. if (transformControls.enabled) { // 变换控制器为启用状态执行
  49. if (event.key === 'e') { // 鼠标按下e键,模式改为缩放
  50. transformControls.mode = 'scale'
  51. return false
  52. }
  53. if (event.key === 'r') { // 鼠标按下r键,模式改为旋转
  54. transformControls.mode = 'rotate'
  55. return false
  56. }
  57. if (event.key === 't') { // 鼠标按下t键,模式改为平移
  58. transformControls.mode = 'translate'
  59. return false
  60. }
  61. }
  62. return event
  63. })
  64. // three.js自带的方法
  65. const orbitControls = new OrbitControls(camera, renderer.domElement)
  66. // console.log(MOUSE)//查看MOUSE中的配置项
  67. orbitControls.mouseButtons = { // 设置鼠标功能键(轨道控制器)
  68. LEFT: null, // 左键无事件
  69. MIDDLE: THREE.MOUSE.DOLLY, // 中键缩放
  70. RIGHT: THREE.MOUSE.ROTATE// 右键旋转
  71. }
  72. scene.add(transformControls)
  73. }

这时我们在log里可以看到之前添加的 userdata

 同时也可以做拽查看

 三、进阶使用

1、外部导入模型  obj、 gltf、 json、 glb等。

开源模型地址 https://github.com/mrdoob/three.js/blob/master

开源的模型 

 可以自己改成其他格式,使用别的引用方法尝试

 ThreeController.js 示例演示,导入glb文件

  1. import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
  2. export const LoadingGLTFMethod=(GltfModel)=> {
  3. loader.load(`${process.env.BASE_URL}model/${GltfModel}`, (gltf) => {
  4. gltf.scene.scale.set(15, 15, 15)
  5. gltf.scene.position.set(0, 0, 0)
  6. gltf.scene.userData={
  7. name:"LoadingGLTFModel",
  8. data:"123",
  9. id:"1212121212",
  10. title:"人物模型"
  11. }
  12. let axis = new THREE.Vector3(0, 1, 0);//向量axis
  13. gltf.scene.rotateOnAxis(axis, Math.PI);
  14. gltf.scene.traverse(function (object) {
  15. if (object.isMesh) {
  16. object.castShadow = true; //阴影
  17. object.receiveShadow = true; //接受别人投的阴影
  18. }
  19. })
  20. scene.Model = gltf.scene;
  21. scene.add(gltf.scene) //公用访问时使用常量向场景中添加引入的模型
  22. return gltf.scene
  23. })
  24. }
  25. LoadingGLTFMethod("Soldier.glb");//看一看模型有没有出现

可以看到已经成功导入

创建地板  ModelListConfig.js

  1. export const Ground = new THREE.Mesh(new THREE.PlaneGeometry(300, 300), new THREE.MeshPhongMaterial({
  2. color: 0x888888, depthWrite: true,
  3. }));
  4. ModelListConfig.push(Ground)

 

 当然你也可以自己做一个材质,来作为地板的纹理

  1. const Require = (src) => {
  2. return require(`../assets/${src}`)
  3. }
  4. const GroundTexture = new THREE.TextureLoader().load(Require('RC.jpg'))
  5. export const Ground = new THREE.Mesh(new THREE.PlaneGeometry(300, 300), new THREE.MeshPhongMaterial({
  6. color: 0x888888, depthWrite: true,
  7. map: GroundTexture
  8. }));
  9. Ground.rotation.x = - Math.PI / 2;
  10. Ground.receiveShadow = true;
  11. ModelListConfig.push(Ground) // 添加到模型数组

贴图效果:

上述我们在导入文件时,设定了导入的物体是接受投影的,也添加过自然光,这时发现并没有影子。在方向光的作用下,物体会形成阴影投影效果,Three.js物体投影模拟计算主要设置三部分,一个是设置产生投影的模型对象,一个是设置接收投影效果的模型,最后一个是光源对象本身的设置,光源如何产生投影。

LightListConfig.js 添加平行光  

  1. const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
  2. directionalLight.position.set(0, 35, 20);// 设置光源位置
  3. directionalLight.castShadow = true; // 设置用于计算阴影的光源对象
  4. // 设置计算阴影的区域,最好刚好紧密包围在对象周围
  5. // 计算阴影的区域过大:模糊 过小:看不到或显示不完整
  6. directionalLight.shadow.camera.near =0.5;
  7. directionalLight.shadow.camera.far = 50;
  8. directionalLight.shadow.camera.left = -10;
  9. directionalLight.shadow.camera.right = 10;
  10. directionalLight.shadow.camera.top = 100;
  11. directionalLight.shadow.camera.bottom = -10;
  12. // 设置mapSize属性可以使阴影更清晰,不那么模糊
  13. // directionalLight.shadow.mapSize.set(1024,1024)
  14. LightList.push(directionalLight)

 

 这时候我们加上外部控制  HomeView.vue

  1. <template>
  2. <div>
  3. <ul>
  4. <li><button @click="LoadingMethod()" >LoadingMethod</button></li>
  5. <li><button @click="logs()">logs</button></li>
  6. <li><button @click="LoadingTrack()">LoadingTrack</button></li>
  7. <li><button @click="Rorate()">Rorate</button></li>
  8. <li><button@click="LoadingSceneMaterials()">LoadingSceneMaterials
  9. </button></li>
  10. <li><button @click="run()">Run</button></li>
  11. </ul>
  12. <div class="log-content ">
  13. <h6>name:{{userData.name}}</h6>
  14. <h6>data:{{userData.data}}</h6>
  15. <h6>id:{{userData.id}}</h6>
  16. <h6>title:{{userData.title}}</h6>
  17. </div>
  18. <div class="three-canvas" ref="threeTarget"></div>
  19. </div>
  20. </template>
  21. import {
  22. ThreeController,
  23. LoadingGLTFMethod,
  24. scene,
  25. }
  26. from "@/components/ThreeController"; //中央渲染控制
  27. return {
  28. userData: {},
  29. test: {},
  30. };
  31. methods: {
  32. LoadingMethod() {
  33. LoadingGLTFMethod("Soldier.glb");
  34. },
  35. logs() {
  36. this.test = this.ThreeController.scene.children.find((item) => {
  37. if(item.userData.name){
  38. return item.userData.name == "LoadingGLTFModel";
  39. }
  40. });
  41. this.userData=this.test.userData
  42. },
  43. }
  44. <style lang="scss" scoped>
  45. .three-canvas {
  46. width: 100vw;
  47. height: 100vh;
  48. }
  49. ul{
  50. position: absolute;
  51. right: 10px;
  52. top: 10px;
  53. }
  54. .log-content {
  55. width: 200px;
  56. height: 60px;
  57. position: absolute;
  58. margin: 10px;
  59. background-color: skyblue;
  60. }

调用 LoadingMethod() 模型加载后,我们再调用logs() 查看我们加上的usedata

 2. requestAnimationFrame 请求动画帧

 它是一个浏览器的宏任务, requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行,它会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,如果系统绘制率是 60Hz,那么回调函数就会16.7ms再 被执行一次,如果绘制频率是75Hz,那么这个间隔时间就变成了 1000/75=13.3ms。换句话说就是,requestAnimationFrame的执行步伐跟着系统的绘制频率走。它能保证回调函数在屏幕每一次的绘制间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。

ThreeController.js  使用requestAnimationFrame() 让模型“run”起来

  1. export const clock = new THREE.Clock();
  2. export let mixer = null
  3. export class ThreeController {
  4. constructor(Model) {
  5. animate() //执行做好的动画帧
  6. }
  7. }
  8. export const animate = () => { requestAnimationFrame(animate);
  9. if (mixer) { mixer.update(clock.getDelta());}
  10. renderer.render(scene, camera);
  11. }
  12. export const startAnimation=(skinnedMesh, animations, animationName)=>{
  13. const m_mixer = new THREE.AnimationMixer(skinnedMesh);
  14. const clip = THREE.AnimationClip.findByName(animations,animationName);
  15. if (clip) {
  16. const action = m_mixer.clipAction(clip);
  17. action.play();
  18. }
  19. return m_mixer;
  20. }
  21. export const LoadingGLTFMethod=(GltfModel,type)=> {//这里新加了一个type
  22. mixer = startAnimation(
  23. gltf.scene,
  24. gltf.animations,
  25. gltf.animations[type].name // animationName,1 是"Run"
  26. );
  27. }
  28. LoadingGLTFMethod("Soldier.glb",1);

效果  (不会做动图:)) 

此时模型仅仅是原地run ,这时我们加上手动控制

模型中有position属性,代表物体在空间中x、y、z轴的坐标

HomeView.vue

  1. import {
  2. renderer,
  3. camera,
  4. } from "@/components/ThreeController";
  5. methods: {
  6. Rorate(){
  7. this.test = this.ThreeController.scene.children.find((item) => {
  8. return item.userData.name == "LoadingGLTFModel";
  9. });
  10. this.animates();
  11. },
  12. animates() {
  13. requestAnimationFrame(this.animates);
  14. //this.test.position.x += 0.01;
  15. //this.test.position.y += 0.01;
  16. this.test.position.z += 0.1;
  17. renderer.render(scene, camera);
  18. },
  19. }加载完模型后,执行Rorate(),这时它就是真正的往前run了

3、轨道以及轨迹思想

Line类是一种线形状几何体,物体运动的轨迹我们可以看成一条线,让模型围轨道运动

ModelListConfig.js

  1. const curveArr = [0, 0, 0, 350, 0, 0, 0, 0, 350];
  2. const curve = [];
  3. for (let i = 0; i < curveArr.length; i += 3) { //每三个点生成一个坐标
  4. curve.push(new THREE.Vector3(curveArr[i], curveArr[i + 1], curveArr[i + 2]));
  5. }
  6. const RoutePoints = new THREE.CatmullRomCurve3(curve, true)
  7. const sphereCurve = RoutePoints.clone()
  8. export const pathPoints = sphereCurve.getPoints(200)//取200个点
  9. const line = new THREE.Line(
  10. new THREE.BufferGeometry().setFromPoints(pathPoints),
  11. new THREE.LineBasicMaterial({
  12. color: "red",
  13. linewidth: 1,
  14. })
  15. )
  16. ModelListConfig.push(line)

为了更直观一些,我们还可以把取到的点也渲染上 

  1. const addMesh = () => {
  2. let list = []
  3. for (let point of pathPoints) {
  4. const sphere = new THREE.BoxGeometry(3, 1, 1)
  5. const sphereMaterial = new THREE.MeshBasicMaterial({ map: RouteTexture })
  6. const sphereMesh = new THREE.Mesh(sphere, sphereMaterial)
  7. sphereMesh.position.set(point.x, point.y, point.z)
  8. ModelListConfig.push(sphereMesh)
  9. list.push(sphereMesh)
  10. }
  11. list.push(line)
  12. return list
  13. }
  14. // 加载轨道
  15. export const LoadingTrack = () => {
  16. return addMesh()
  17. }

让物体围绕着生成的轨道运动  HomeView.vue

  1. Return{ … num:0}
  2. LoadingTrack(){ //加载出轨道
  3. this.ThreeController.addObject(...LoadingTrack())
  4. },
  5. run(){
  6. this.test = this.ThreeController.scene.children.find((item) => {
  7. return item.userData.name == "LoadingGLTFModel";
  8. });
  9. this.runanimates();
  10. },
  11. runanimates(){
  12. if(this.num<=pathPoints.length-2){
  13. this.num+=1
  14. }else{
  15. this.num=0
  16. }
  17. requestAnimationFrame(this.runanimates);
  18. this.test.position.x = pathPoints[ this.num].x ;
  19. this.test.position.y = pathPoints[ this.num].y;
  20. this.test.position.z = pathPoints[ this.num].z
  21. renderer.render(scene, camera);
  22. },

 也可以用来实现相机漫游,实时光影 只不过就是运动的物体从模型,变成相机或者光线

加载场景材质,ThreeController.js  天空盒效果

  1. export const SceneMapMaterial=(list)=>{
  2. const map=new THREE.CubeTextureLoader()
  3. .setPath( `${process.env.BASE_URL}model/` )
  4. .load(list );
  5. return map
  6. }

HomeView.vue

  1. SceneList:[ 'px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg','nz.jpg' ]
  2. LoadingSceneMaterials(){
  3. scene.background = SceneMapMaterial(this.SceneList)//max px 1024*1024
  4. },
  5. 名字有对应的作用,且不能超过最大限制

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/96134
推荐阅读
相关标签
  

闽ICP备14008679号