效果描述
在播放导入的模型动画同时,让模型沿着预定路径轨迹移动。例如导入一个会跑步动作的模型,让它沿着一条类似跑道的路径跑步移动。
实现流程
基本流程
1、搭建场景
2、添加模型和播放动画
3、添加路径和模型移动
工程文件
工程文件结构如下图:
static:存放静态资源文件
three.js-master:为官网下载的代码包,包含所有需要用到的资源包,链接:https://github.com/mrdoob/three.js/archive/master.zip
index.html:页面代码
模型使用的是官方示例中的Soldier模型,文件位置:three.js-master\examples\models\gltf\Soldier.glb
为了方便操作我们将文件拷出来放在上图static\3dmod\gltf文件夹下,static与three.js-master同级
index.html单页代码组成
-
-
-
-
- <meta charset="utf-8">
- <title>My first three.js app</title>
-
-
-
-
- <script>
- {
- "imports": {
- "three": "./three.js-master/build/three.module.js"
- }
- }
- </script>
- <script>
- // 下文JS代码位置
- // ...
- </script>
-
-
-
-
-
复制代码
参照官网例子:https://threejs.org/examples/#webgl_animation_skinning_blending中的场景和模型
搭建场景
搭建场景环境
- import * as THREE from "three";
- import { OrbitControls } from "./three.js-master/examples/jsm/controls/OrbitControls.js";
-
- let scene, camera, renderer;
-
- // 渲染器开启阴影渲染:renderer.shadowMapEnabled = true;
- // 灯光需要开启“引起阴影”:light.castShadow = true;
- // 物体需要开启“引起阴影”和“接收阴影”:mesh.castShadow = mesh.receiveShadow = true;
-
- function init() {
- scene = new THREE.Scene();
- camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
- renderer = new THREE.WebGLRenderer();
- // position and point the camera to the center of the scene
- camera.position.set(5, 5, 5);
- camera.lookAt(scene.position);
-
- // 增加坐标系红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
- // 添加坐标系到场景中
- const axes = new THREE.AxesHelper(20);
- scene.add(axes);
-
- // 调整背景颜色,边界雾化
- scene.background = new THREE.Color(0xa0a0a0);
- scene.fog = new THREE.Fog(0xa0a0a0, 10, 30);
-
- // 半球形光源
- const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
- hemiLight.position.set(0, 10, 0);
- scene.add(hemiLight);
-
- // 创建一个虚拟的球形网格 Mesh 的辅助对象来模拟 半球形光源 HemisphereLight.
- const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5);
- scene.add(hemiLighthelper);
-
- // 地面
- const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }));
- mesh.rotation.x = - Math.PI / 2;
- mesh.receiveShadow = true;
- scene.add(mesh);
-
- // 平行光
- const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
- directionalLight.castShadow = true;
- directionalLight.shadow.camera.near = 0.5;
- directionalLight.shadow.camera.far = 50;
- directionalLight.shadow.camera.left = -10;
- directionalLight.shadow.camera.right = 10;
- directionalLight.shadow.camera.top = 10;
- directionalLight.shadow.camera.bottom = -10;
- directionalLight.position.set(0, 5, 5);
- scene.add(directionalLight);
-
- // 用于模拟场景中平行光 DirectionalLight 的辅助对象. 其中包含了表示光位置的平面和表示光方向的线段.
- const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
- scene.add(directionalLightHelper);
-
- renderer.shadowMap.enabled = true;
- renderer.setSize(window.innerWidth, window.innerHeight);
- document.body.appendChild(renderer.domElement);
-
- // 控制器
- const controls = new OrbitControls(camera, renderer.domElement);
- }
- // 渲染
- function animate() {
- requestAnimationFrame(animate);
- renderer.render(scene, camera);
- };
-
-
复制代码
添加模型和播放动画
导入模型,在《Three.js学习四——模型导入》中有相对详细的介绍。
动画实现基本流程:
1、使用加载器导入模型后,在加载成功后调用的函数中设置动画
2、新建一个AnimationMixer(动画混合器)
3、获取AnimationClip(动画)实例列表
4、设置播放动画,并在每一帧中更新mixer
- let model = null; //用于模型移动
- let clock = new THREE.Clock(); // 用于clock.getDelta()
- let mixer;
- // 加载器加载完成后添加了动画设置
- function loadModel() {
- // 加载模型并开启阴影和接受阴影
- const gltfLoader = new GLTFLoader();
- gltfLoader.setPath('./static/3dmod/gltf/')
- .load('Soldier.glb', function (gltf) {
- gltf.scene.rotation.y = Math.PI;
- console.log("gltf", gltf)
- gltf.scene.scale.set(1, 1, 1)
- gltf.scene.traverse(function (object) {
- if (object.isMesh) {
- object.castShadow = true; //阴影
- object.receiveShadow = true; //接受别人投的阴影
- }
- });
-
- // 使用动画混合器及配置
- mixer = startAnimation(
- gltf.scene,
- gltf.animations,
- gltf.animations[1].name // animationName,这里是"Run"
- );
-
- scene.add(gltf.scene);
- model = gltf.scene;
-
- }, function (res) {
- // console.log(res.total, res.loaded)
- });
-
- };
-
- /**
- * 启动特定网格对象的动画。在三维模型的动画数组中按名称查找动画
- * @param skinnedMesh {THREE.SkinnedMesh} 要设置动画的网格
- * @param animations {Array} 数组,包含此模型的所有动画
- * @param animationName {string} 要启动的动画的名称
- * @return {THREE.AnimationMixer} 要在渲染循环中使用的混合器
- */
- function startAnimation(skinnedMesh, animations, animationName) {
- const m_mixer = new THREE.AnimationMixer(skinnedMesh);
- const clip = THREE.AnimationClip.findByName(animations, animationName);
- if (clip) {
- const action = m_mixer.clipAction(clip);
- action.play();
- }
- return m_mixer;
- };
-
- function animate() {
- requestAnimationFrame(animate);
- // 更新动画帧
- if(mixer){
- mixer.update(clock.getDelta());
- }
-
- renderer.render(scene, camera);
- };
-
-
复制代码
添加路径和模型移动
路径:用到了Three.js提供的CatmullRomCurve3:使用Catmull-Rom算法, 从一系列的点创建一条平滑的三维样条曲线。
移动:在每一帧中按照一定步长更新模型位置。
- let curve = null; // 存放路径对象
- let progress = 0; // 物体运动时在运动路径的初始位置,范围0~1
- const velocity = 0.002; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
-
- function makeCurve() {
- //Create a closed wavey loop
- curve = new THREE.CatmullRomCurve3([
- new THREE.Vector3(0, 0, 0),
- new THREE.Vector3(5, 0, 0),
- new THREE.Vector3(0, 0, 5)
- ]);
- curve.curveType = "catmullrom";
- curve.closed = true;//设置是否闭环
- curve.tension = 0.5![请添加图片描述](https://img-blog.csdnimg.cn/12a2fa45062d44a58bb7cbf719e4b20f.gif)
- ; //设置线的张力,0为无弧度折线
-
- // 为曲线添加材质在场景中显示出来,不显示也不会影响运动轨迹,相当于一个Helper
- const points = curve.getPoints(50);
- const geometry = new THREE.BufferGeometry().setFromPoints(points);
- const material = new THREE.LineBasicMaterial({ color: 0x000000 });
-
- // Create the final object to add to the scene
- const curveObject = new THREE.Line(geometry, material);
- scene.add(curveObject)
- }
-
- // 物体沿线移动方法
- function moveOnCurve() {
- if (curve == null || model == null) {
- console.log("Loading")
- } else {
- if (progress <= 1 - velocity) {
- const point = curve.getPointAt(progress); //获取样条曲线指定点坐标
- const pointBox = curve.getPointAt(progress + velocity); //获取样条曲线指定点坐标
-
- if (point && pointBox) {
- model.position.set(point.x, point.y, point.z);
- // model.lookAt(pointBox.x, pointBox.y, pointBox.z); //因为这个模型加载进来默认面部是正对Z轴负方向的,所以直接lookAt会导致出现倒着跑的现象,这里用重新设置朝向的方法来解决。
-
- var targetPos = pointBox //目标位置点
- var offsetAngle = 0 //目标移动时的朝向偏移
-
- // //以下代码在多段路径时可重复执行
- var mtx = new THREE.Matrix4() //创建一个4维矩阵
- // .lookAt ( eye : Vector3, target : Vector3, up : Vector3 ) : this,构造一个旋转矩阵,从eye 指向 target,由向量 up 定向。
- mtx.lookAt(model.position, targetPos, model.up) //设置朝向
- mtx.multiply(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(0, offsetAngle, 0)))
- var toRot = new THREE.Quaternion().setFromRotationMatrix(mtx) //计算出需要进行旋转的四元数值
- model.quaternion.slerp(toRot, 0.2)
- }
-
- progress += velocity;
- } else {
- progress = 0;
- }
- }
-
- };
- // moveOnCurve()需要在渲染中一直调用更新,以达到物体移动效果
- function animate() {
- requestAnimationFrame(animate);
- moveOnCurve();
- renderer.render(scene, camera);
- };
-
-
复制代码
完整代码和实现效果
完整代码
-
-
-
-
- <meta charset="utf-8">
- <title>My first three.js app</title>
-
-
-
-
- <script>
- {
- "imports": {
- "three": "./three.js-master/build/three.module.js"
- }
- }
- </script>
- <script>
- import * as THREE from "three";
- import { OrbitControls } from "./three.js-master/examples/jsm/controls/OrbitControls.js";
- import { GLTFLoader } from "./three.js-master/examples/jsm/loaders/GLTFLoader.js";
-
- let scene, camera, renderer;
- let curve = null, model = null;
- let clock = new THREE.Clock();
- let mixer;
- let progress = 0; // 物体运动时在运动路径的初始位置,范围0~1
- const velocity = 0.002; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
-
- // 渲染器开启阴影渲染:renderer.shadowMapEnabled = true;
- // 灯光需要开启“引起阴影”:light.castShadow = true;
- // 物体需要开启“引起阴影”和“接收阴影”:mesh.castShadow = mesh.receiveShadow = true;
-
- function init() {
- scene = new THREE.Scene();
- camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
- renderer = new THREE.WebGLRenderer();
- // position and point the camera to the center of the scene
- camera.position.set(5, 5, 5);
- camera.lookAt(scene.position);
-
- // 增加坐标系红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
- // 添加坐标系到场景中
- const axes = new THREE.AxesHelper(20);
- scene.add(axes);
-
- // 调整背景颜色,边界雾化
- scene.background = new THREE.Color(0xa0a0a0);
- scene.fog = new THREE.Fog(0xa0a0a0, 10, 30);
-
- // 半球形光源
- const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
- hemiLight.position.set(0, 10, 0);
- scene.add(hemiLight);
-
- // 创建一个虚拟的球形网格 Mesh 的辅助对象来模拟 半球形光源 HemisphereLight.
- const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5);
- scene.add(hemiLighthelper);
-
- // 地面
- const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }));
- mesh.rotation.x = - Math.PI / 2;
- mesh.receiveShadow = true;
- scene.add(mesh);
-
- // 平行光
- const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
- directionalLight.castShadow = true;
- directionalLight.shadow.camera.near = 0.5;
- directionalLight.shadow.camera.far = 50;
- directionalLight.shadow.camera.left = -10;
- directionalLight.shadow.camera.right = 10;
- directionalLight.shadow.camera.top = 10;
- directionalLight.shadow.camera.bottom = -10;
- directionalLight.position.set(0, 5, 5);
- scene.add(directionalLight);
-
- // 用于模拟场景中平行光 DirectionalLight 的辅助对象. 其中包含了表示光位置的平面和表示光方向的线段.
- const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
- scene.add(directionalLightHelper);
-
- renderer.shadowMap.enabled = true;
- renderer.setSize(window.innerWidth, window.innerHeight);
- document.body.appendChild(renderer.domElement);
-
- // 控制器
- const controls = new OrbitControls(camera, renderer.domElement);
- };
-
- function loadModel() {
- // 加载模型并开启阴影和接受阴影
- const gltfLoader = new GLTFLoader();
- gltfLoader.setPath('./static/3dmod/gltf/')
- .load('Soldier.glb', function (gltf) {
- gltf.scene.rotation.y = Math.PI;
- console.log("gltf\ngltf", gltf)
- gltf.scene.scale.set(1, 1, 1)
- gltf.scene.traverse(function (object) {
- if (object.isMesh) {
- object.castShadow = true; //阴影
- object.receiveShadow = true; //接受别人投的阴影
- }
- });
-
- // 使用动画混合器及配置
- mixer = startAnimation(
- gltf.scene,
- gltf.animations,
- gltf.animations[1].name // animationName,这里是"Run"
- );
-
- scene.add(gltf.scene);
- model = gltf.scene;
-
- }, function (res) {
- // console.log(res.total, res.loaded)
- });
-
- };
-
- function makeCurve() {
- // 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
- // Create a closed wavey loop
- curve = new THREE.CatmullRomCurve3([
- new THREE.Vector3(0, 0, 0),
- new THREE.Vector3(5, 0, 0),
- new THREE.Vector3(0, 0, 5)
-
- ]);
- curve.curveType = "catmullrom";
- curve.closed = true;//设置是否闭环
- curve.tension = 0.5; //设置线的张力,0为无弧度折线
-
- // 为曲线添加材质在场景中显示出来,不添加到场景显示也不会影响运动轨迹,相当于一个Helper
- const points = curve.getPoints(50);
- const geometry = new THREE.BufferGeometry().setFromPoints(points);
- const material = new THREE.LineBasicMaterial({ color: 0x000000 });
-
- // Create the final object to add to the scene
- const curveObject = new THREE.Line(geometry, material);
- scene.add(curveObject)
- };
-
- // 物体沿线移动方法
- function moveOnCurve() {
- if (curve == null || model == null) {
- console.log("Loading")
- } else {
- if (progress <= 1 - velocity) {
- const point = curve.getPointAt(progress); //获取样条曲线指定点坐标
- const pointBox = curve.getPointAt(progress + velocity); //获取样条曲线指定点坐标
-
- if (point && pointBox) {
- model.position.set(point.x, point.y, point.z);
- // model.lookAt(pointBox.x, pointBox.y, pointBox.z);//因为这个模型加载进来默认面部是正对Z轴负方向的,所以直接lookAt会导致出现倒着跑的现象,这里用重新设置朝向的方法来解决。
-
- var targetPos = pointBox //目标位置点
- var offsetAngle = 0 //目标移动时的朝向偏移
-
- // //以下代码在多段路径时可重复执行
- var mtx = new THREE.Matrix4() //创建一个4维矩阵
- // .lookAt ( eye : Vector3, target : Vector3, up : Vector3 ) : this,构造一个旋转矩阵,从eye 指向 target,由向量 up 定向。
- mtx.lookAt(model.position, targetPos, model.up) //设置朝向
- mtx.multiply(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(0, offsetAngle, 0)))
- var toRot = new THREE.Quaternion().setFromRotationMatrix(mtx) //计算出需要进行旋转的四元数值
- model.quaternion.slerp(toRot, 0.2)
- }
-
- progress += velocity;
- } else {
- progress = 0;
- }
- }
-
- };
-
- /**
- * 启动特定网格对象的动画。在三维模型的动画数组中按名称查找动画
- * @param skinnedMesh {THREE.SkinnedMesh} 要设置动画的网格
- * @param animations {Array} 数组,包含此模型的所有动画
- * @param animationName {string} 要启动的动画的名称
- * @return {THREE.AnimationMixer} 要在渲染循环中使用的混合器
- */
- function startAnimation(skinnedMesh, animations, animationName) {
- const m_mixer = new THREE.AnimationMixer(skinnedMesh);
- const clip = THREE.AnimationClip.findByName(animations, animationName);
-
- if (clip) {
- const action = m_mixer.clipAction(clip);
- action.play();
- }
-
- return m_mixer;
- };
-
- function animate() {
- requestAnimationFrame(animate);
- // 更新动画帧
- if(mixer){
- mixer.update(clock.getDelta());
- }
- moveOnCurve();
-
- renderer.render(scene, camera);
- };
-
- init();
- loadModel();
- makeCurve();
- animate();
- </script>
-
-
-
复制代码
实现效果
来源:SketchUpBBS
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
|