🌓
搜索
 找回密码
 立即注册

Three.js学习七——播放模型动画时模型沿着轨迹移动

塞上雪狼 2022-10-9 11:05:26 90104
效果描述

在播放导入的模型动画同时,让模型沿着预定路径轨迹移动。例如导入一个会跑步动作的模型,让它沿着一条类似跑道的路径跑步移动。
实现流程

基本流程

   1、搭建场景
2、添加模型和播放动画
3、添加路径和模型移动
  工程文件

工程文件结构如下图:
static:存放静态资源文件
three.js-master:为官网下载的代码包,包含所有需要用到的资源包,链接:https://github.com/mrdoob/three.js/archive/master.zip
index.html:页面代码
224307bii6w896i8hwkiyw.png

模型使用的是官方示例中的Soldier模型,文件位置:three.js-master\examples\models\gltf\Soldier.glb
为了方便操作我们将文件拷出来放在上图static\3dmod\gltf文件夹下,static与three.js-master同级
index.html单页代码组成
  1.         <meta charset="utf-8">
  2.         <title>My first three.js app</title>
  3.        
  4.         <script>
  5.                         {
  6.                                 "imports": {
  7.                                         "three": "./three.js-master/build/three.module.js"
  8.                                 }
  9.                         }
  10.                 </script>
  11.         <script>
  12.                 // 下文JS代码位置
  13.                 // ...
  14.         </script>
复制代码
参照官网例子:https://threejs.org/examples/#webgl_animation_skinning_blending中的场景和模型
搭建场景

搭建场景环境
  1. import * as THREE from "three";
  2. import { OrbitControls } from "./three.js-master/examples/jsm/controls/OrbitControls.js";
  3. let scene, camera, renderer;
  4. // 渲染器开启阴影渲染:renderer.shadowMapEnabled = true;
  5. // 灯光需要开启“引起阴影”:light.castShadow = true;
  6. // 物体需要开启“引起阴影”和“接收阴影”:mesh.castShadow = mesh.receiveShadow = true;
  7. function init() {
  8.         scene = new THREE.Scene();
  9.         camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  10.         renderer = new THREE.WebGLRenderer();
  11.         // position and point the camera to the center of the scene
  12.         camera.position.set(5, 5, 5);
  13.         camera.lookAt(scene.position);
  14.        
  15.         // 增加坐标系红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
  16.         // 添加坐标系到场景中
  17.         const axes = new THREE.AxesHelper(20);
  18.         scene.add(axes);
  19.        
  20.         // 调整背景颜色,边界雾化
  21.         scene.background = new THREE.Color(0xa0a0a0);
  22.         scene.fog = new THREE.Fog(0xa0a0a0, 10, 30);
  23.        
  24.         // 半球形光源
  25.         const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
  26.         hemiLight.position.set(0, 10, 0);
  27.         scene.add(hemiLight);
  28.        
  29.         // 创建一个虚拟的球形网格 Mesh 的辅助对象来模拟 半球形光源 HemisphereLight.
  30.         const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5);
  31.         scene.add(hemiLighthelper);
  32.        
  33.         // 地面
  34.         const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }));
  35.         mesh.rotation.x = - Math.PI / 2;
  36.         mesh.receiveShadow = true;
  37.         scene.add(mesh);
  38.        
  39.         // 平行光
  40.         const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
  41.         directionalLight.castShadow = true;
  42.         directionalLight.shadow.camera.near = 0.5;
  43.         directionalLight.shadow.camera.far = 50;
  44.         directionalLight.shadow.camera.left = -10;
  45.         directionalLight.shadow.camera.right = 10;
  46.         directionalLight.shadow.camera.top = 10;
  47.         directionalLight.shadow.camera.bottom = -10;
  48.         directionalLight.position.set(0, 5, 5);
  49.         scene.add(directionalLight);
  50.        
  51.         // 用于模拟场景中平行光 DirectionalLight 的辅助对象. 其中包含了表示光位置的平面和表示光方向的线段.
  52.         const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
  53.         scene.add(directionalLightHelper);
  54.        
  55.         renderer.shadowMap.enabled = true;
  56.         renderer.setSize(window.innerWidth, window.innerHeight);
  57.         document.body.appendChild(renderer.domElement);
  58.        
  59.         // 控制器
  60.         const controls = new OrbitControls(camera, renderer.domElement);
  61. }
  62. // 渲染
  63. function animate() {
  64.         requestAnimationFrame(animate);
  65.         renderer.render(scene, camera);
  66. };
复制代码
添加模型和播放动画

导入模型,在《Three.js学习四——模型导入》中有相对详细的介绍。
动画实现基本流程:
   1、使用加载器导入模型后,在加载成功后调用的函数中设置动画
2、新建一个AnimationMixer(动画混合器)
3、获取AnimationClip(动画)实例列表
4、设置播放动画,并在每一帧中更新mixer
  1. let model = null;        //用于模型移动
  2. let clock = new THREE.Clock(); // 用于clock.getDelta()
  3. let mixer;
  4. // 加载器加载完成后添加了动画设置
  5. function loadModel() {
  6.         // 加载模型并开启阴影和接受阴影
  7.         const gltfLoader = new GLTFLoader();
  8.         gltfLoader.setPath('./static/3dmod/gltf/')
  9.                 .load('Soldier.glb', function (gltf) {
  10.                         gltf.scene.rotation.y = Math.PI;
  11.                         console.log("gltf", gltf)
  12.                         gltf.scene.scale.set(1, 1, 1)
  13.                         gltf.scene.traverse(function (object) {
  14.                                 if (object.isMesh) {
  15.                                         object.castShadow = true; //阴影
  16.                                         object.receiveShadow = true; //接受别人投的阴影
  17.                                 }
  18.                         });
  19.             // 使用动画混合器及配置
  20.                         mixer = startAnimation(
  21.                                 gltf.scene,
  22.                                 gltf.animations,
  23.                                 gltf.animations[1].name // animationName,这里是"Run"
  24.                         );
  25.                        
  26.                         scene.add(gltf.scene);
  27.                         model = gltf.scene;
  28.                 }, function (res) {
  29.                         // console.log(res.total, res.loaded)
  30.                 });
  31. };
  32. /**
  33. * 启动特定网格对象的动画。在三维模型的动画数组中按名称查找动画
  34. * @param skinnedMesh {THREE.SkinnedMesh} 要设置动画的网格
  35. * @param animations {Array} 数组,包含此模型的所有动画
  36. * @param animationName {string} 要启动的动画的名称
  37. * @return {THREE.AnimationMixer} 要在渲染循环中使用的混合器
  38. */
  39. function startAnimation(skinnedMesh, animations, animationName) {
  40.         const m_mixer = new THREE.AnimationMixer(skinnedMesh);         
  41.         const clip = THREE.AnimationClip.findByName(animations, animationName);
  42.         if (clip) {
  43.                 const action = m_mixer.clipAction(clip);
  44.                 action.play();
  45.         }
  46.         return m_mixer;
  47. };
  48. function animate() {
  49.         requestAnimationFrame(animate);
  50.         // 更新动画帧
  51.           if(mixer){
  52.               mixer.update(clock.getDelta());
  53.           }
  54.        
  55.         renderer.render(scene, camera);
  56. };
复制代码
添加路径和模型移动

路径:用到了Three.js提供的CatmullRomCurve3:使用Catmull-Rom算法, 从一系列的点创建一条平滑的三维样条曲线。
移动:在每一帧中按照一定步长更新模型位置。
  1. let curve = null; // 存放路径对象
  2. let progress = 0; // 物体运动时在运动路径的初始位置,范围0~1
  3. const velocity = 0.002; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
  4. function makeCurve() {
  5.         //Create a closed wavey loop
  6.         curve = new THREE.CatmullRomCurve3([
  7.                 new THREE.Vector3(0, 0, 0),
  8.                 new THREE.Vector3(5, 0, 0),
  9.                 new THREE.Vector3(0, 0, 5)
  10.         ]);
  11.         curve.curveType = "catmullrom";
  12.         curve.closed = true;//设置是否闭环
  13.         curve.tension = 0.5![请添加图片描述](https://img-blog.csdnimg.cn/12a2fa45062d44a58bb7cbf719e4b20f.gif)
  14. ; //设置线的张力,0为无弧度折线
  15.         // 为曲线添加材质在场景中显示出来,不显示也不会影响运动轨迹,相当于一个Helper
  16.         const points = curve.getPoints(50);
  17.         const geometry = new THREE.BufferGeometry().setFromPoints(points);
  18.         const material = new THREE.LineBasicMaterial({ color: 0x000000 });
  19.         // Create the final object to add to the scene
  20.         const curveObject = new THREE.Line(geometry, material);
  21.         scene.add(curveObject)
  22. }
  23. // 物体沿线移动方法
  24. function moveOnCurve() {
  25.         if (curve == null || model == null) {
  26.                 console.log("Loading")
  27.         } else {
  28.                 if (progress &lt;= 1 - velocity) {
  29.                         const point = curve.getPointAt(progress); //获取样条曲线指定点坐标
  30.                         const pointBox = curve.getPointAt(progress + velocity); //获取样条曲线指定点坐标
  31.                         if (point &amp;&amp; pointBox) {
  32.                                 model.position.set(point.x, point.y, point.z);
  33.                                 // model.lookAt(pointBox.x, pointBox.y, pointBox.z); //因为这个模型加载进来默认面部是正对Z轴负方向的,所以直接lookAt会导致出现倒着跑的现象,这里用重新设置朝向的方法来解决。
  34.                                 var targetPos = pointBox   //目标位置点
  35.                                 var offsetAngle = 0 //目标移动时的朝向偏移
  36.                                 // //以下代码在多段路径时可重复执行
  37.                                 var mtx = new THREE.Matrix4()  //创建一个4维矩阵
  38.                                 // .lookAt ( eye : Vector3, target : Vector3, up : Vector3 ) : this,构造一个旋转矩阵,从eye 指向 target,由向量 up 定向。
  39.                                 mtx.lookAt(model.position, targetPos, model.up) //设置朝向
  40.                                 mtx.multiply(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(0, offsetAngle, 0)))
  41.                                 var toRot = new THREE.Quaternion().setFromRotationMatrix(mtx)  //计算出需要进行旋转的四元数值
  42.                                 model.quaternion.slerp(toRot, 0.2)
  43.                         }
  44.                         progress += velocity;
  45.                 } else {
  46.                         progress = 0;
  47.                 }
  48.         }
  49. };
  50. // moveOnCurve()需要在渲染中一直调用更新,以达到物体移动效果
  51. function animate() {
  52.         requestAnimationFrame(animate);
  53.         moveOnCurve();
  54.         renderer.render(scene, camera);
  55. };
复制代码
完整代码和实现效果

完整代码
  1.         <meta charset="utf-8">
  2.         <title>My first three.js app</title>
  3.        
  4.         <script>
  5.                         {
  6.                                 "imports": {
  7.                                         "three": "./three.js-master/build/three.module.js"
  8.                                 }
  9.                         }
  10.                 </script>
  11.         <script>
  12.                 import * as THREE from "three";
  13.                 import { OrbitControls } from "./three.js-master/examples/jsm/controls/OrbitControls.js";
  14.                 import { GLTFLoader } from "./three.js-master/examples/jsm/loaders/GLTFLoader.js";
  15.                 let scene, camera, renderer;
  16.                 let curve = null, model = null;
  17.         let clock = new THREE.Clock();
  18.         let mixer;
  19.                 let progress = 0; // 物体运动时在运动路径的初始位置,范围0~1
  20.                 const velocity = 0.002; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
  21.                 // 渲染器开启阴影渲染:renderer.shadowMapEnabled = true;
  22.                 // 灯光需要开启“引起阴影”:light.castShadow = true;
  23.                 // 物体需要开启“引起阴影”和“接收阴影”:mesh.castShadow = mesh.receiveShadow = true;
  24.                 function init() {
  25.                         scene = new THREE.Scene();
  26.                         camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  27.                         renderer = new THREE.WebGLRenderer();
  28.                         // position and point the camera to the center of the scene
  29.                         camera.position.set(5, 5, 5);
  30.                         camera.lookAt(scene.position);
  31.                         // 增加坐标系红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
  32.                         // 添加坐标系到场景中
  33.                         const axes = new THREE.AxesHelper(20);
  34.                         scene.add(axes);
  35.                         // 调整背景颜色,边界雾化
  36.                         scene.background = new THREE.Color(0xa0a0a0);
  37.                         scene.fog = new THREE.Fog(0xa0a0a0, 10, 30);
  38.                         // 半球形光源
  39.                         const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
  40.                         hemiLight.position.set(0, 10, 0);
  41.                         scene.add(hemiLight);
  42.                         // 创建一个虚拟的球形网格 Mesh 的辅助对象来模拟 半球形光源 HemisphereLight.
  43.                         const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5);
  44.                         scene.add(hemiLighthelper);
  45.                         // 地面
  46.                         const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }));
  47.                         mesh.rotation.x = - Math.PI / 2;
  48.                         mesh.receiveShadow = true;
  49.                         scene.add(mesh);
  50.                         // 平行光
  51.                         const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
  52.                         directionalLight.castShadow = true;
  53.                         directionalLight.shadow.camera.near = 0.5;
  54.                         directionalLight.shadow.camera.far = 50;
  55.                         directionalLight.shadow.camera.left = -10;
  56.                         directionalLight.shadow.camera.right = 10;
  57.                         directionalLight.shadow.camera.top = 10;
  58.                         directionalLight.shadow.camera.bottom = -10;
  59.                         directionalLight.position.set(0, 5, 5);
  60.                         scene.add(directionalLight);
  61.                         // 用于模拟场景中平行光 DirectionalLight 的辅助对象. 其中包含了表示光位置的平面和表示光方向的线段.
  62.                         const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
  63.                         scene.add(directionalLightHelper);
  64.                         renderer.shadowMap.enabled = true;
  65.                         renderer.setSize(window.innerWidth, window.innerHeight);
  66.                         document.body.appendChild(renderer.domElement);
  67.                         // 控制器
  68.                         const controls = new OrbitControls(camera, renderer.domElement);
  69.                 };
  70.                 function loadModel() {
  71.                         // 加载模型并开启阴影和接受阴影
  72.                         const gltfLoader = new GLTFLoader();
  73.                         gltfLoader.setPath('./static/3dmod/gltf/')
  74.                                 .load('Soldier.glb', function (gltf) {
  75.                                         gltf.scene.rotation.y = Math.PI;
  76.                                         console.log("gltf\ngltf", gltf)
  77.                                         gltf.scene.scale.set(1, 1, 1)
  78.                                         gltf.scene.traverse(function (object) {
  79.                                                 if (object.isMesh) {
  80.                                                         object.castShadow = true; //阴影
  81.                                                         object.receiveShadow = true; //接受别人投的阴影
  82.                                                 }
  83.                                         });
  84.                     // 使用动画混合器及配置
  85.                                         mixer = startAnimation(
  86.                                                 gltf.scene,
  87.                                                 gltf.animations,
  88.                                                 gltf.animations[1].name // animationName,这里是"Run"
  89.                                         );
  90.                                        
  91.                                         scene.add(gltf.scene);
  92.                                         model = gltf.scene;
  93.                                 }, function (res) {
  94.                                         // console.log(res.total, res.loaded)
  95.                                 });
  96.                 };
  97.                 function makeCurve() {
  98.                         // 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
  99.                         // Create a closed wavey loop
  100.                         curve = new THREE.CatmullRomCurve3([
  101.                                 new THREE.Vector3(0, 0, 0),
  102.                                 new THREE.Vector3(5, 0, 0),
  103.                                 new THREE.Vector3(0, 0, 5)
  104.                                
  105.                         ]);
  106.                         curve.curveType = "catmullrom";
  107.                         curve.closed = true;//设置是否闭环
  108.                         curve.tension = 0.5; //设置线的张力,0为无弧度折线
  109.                         // 为曲线添加材质在场景中显示出来,不添加到场景显示也不会影响运动轨迹,相当于一个Helper
  110.                         const points = curve.getPoints(50);
  111.                         const geometry = new THREE.BufferGeometry().setFromPoints(points);
  112.                         const material = new THREE.LineBasicMaterial({ color: 0x000000 });
  113.                         // Create the final object to add to the scene
  114.                         const curveObject = new THREE.Line(geometry, material);
  115.                         scene.add(curveObject)
  116.                 };
  117.                 // 物体沿线移动方法
  118.                 function moveOnCurve() {
  119.                         if (curve == null || model == null) {
  120.                                 console.log("Loading")
  121.                         } else {
  122.                                 if (progress &lt;= 1 - velocity) {
  123.                                         const point = curve.getPointAt(progress); //获取样条曲线指定点坐标
  124.                                         const pointBox = curve.getPointAt(progress + velocity); //获取样条曲线指定点坐标
  125.                                         if (point &amp;&amp; pointBox) {
  126.                                                 model.position.set(point.x, point.y, point.z);
  127.                                                 // model.lookAt(pointBox.x, pointBox.y, pointBox.z);//因为这个模型加载进来默认面部是正对Z轴负方向的,所以直接lookAt会导致出现倒着跑的现象,这里用重新设置朝向的方法来解决。
  128.                                                 var targetPos = pointBox   //目标位置点
  129.                                                 var offsetAngle = 0 //目标移动时的朝向偏移
  130.                                                 // //以下代码在多段路径时可重复执行
  131.                                                 var mtx = new THREE.Matrix4()  //创建一个4维矩阵
  132.                                                 // .lookAt ( eye : Vector3, target : Vector3, up : Vector3 ) : this,构造一个旋转矩阵,从eye 指向 target,由向量 up 定向。
  133.                                                 mtx.lookAt(model.position, targetPos, model.up) //设置朝向
  134.                                                 mtx.multiply(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(0, offsetAngle, 0)))
  135.                                                 var toRot = new THREE.Quaternion().setFromRotationMatrix(mtx)  //计算出需要进行旋转的四元数值
  136.                                                 model.quaternion.slerp(toRot, 0.2)
  137.                                         }
  138.                                         progress += velocity;
  139.                                 } else {
  140.                                         progress = 0;
  141.                                 }
  142.                         }
  143.                 };
  144.                 /**
  145.                  * 启动特定网格对象的动画。在三维模型的动画数组中按名称查找动画
  146.                  * @param skinnedMesh {THREE.SkinnedMesh} 要设置动画的网格
  147.                  * @param animations {Array} 数组,包含此模型的所有动画
  148.                  * @param animationName {string} 要启动的动画的名称
  149.                  * @return {THREE.AnimationMixer} 要在渲染循环中使用的混合器
  150.                  */
  151.                 function startAnimation(skinnedMesh, animations, animationName) {
  152.                         const m_mixer = new THREE.AnimationMixer(skinnedMesh);         
  153.                         const clip = THREE.AnimationClip.findByName(animations, animationName);
  154.                         if (clip) {
  155.                                 const action = m_mixer.clipAction(clip);
  156.                                 action.play();
  157.                         }
  158.                         return m_mixer;
  159.                 };
  160.                 function animate() {
  161.                         requestAnimationFrame(animate);
  162.                         // 更新动画帧
  163.             if(mixer){
  164.                 mixer.update(clock.getDelta());
  165.             }
  166.                         moveOnCurve();
  167.                        
  168.                         renderer.render(scene, camera);
  169.                 };
  170.                 init();
  171.                 loadModel();
  172.                 makeCurve();
  173.                 animate();
  174.         </script>
复制代码
实现效果
224307g2606c0g25b0m2c6.gif


来源:SketchUpBBS
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

扫一扫

随机推荐

最新主题

8 回复

路过,学习下
前排支持下
拓展了知识面,谢谢!
前排支持下,谢谢分享
前排支持下
写的真的很不错
好好 学习了 确实不错
路过,支持一下啦
高级模式
游客
返回顶部