Three.js学习五——让模型沿着轨迹移动

news2025/1/20 1:59:07

目录

  • 流程
  • 搭建场景环境
  • 添加模型
  • 增加运动轨迹
  • 让模型沿轨迹运动
  • 完整代码和效果

流程

基本流程

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单页代码组成

<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8">
	<title>My first three.js app</title>
	<style>
		body {
			margin: 0;
		}
	</style>
</head>

<body>
	<script type="importmap">
			{
				"imports": {
					"three": "./three.js-master/build/three.module.js"
				}
			}
		</script>
	<script type="module">
		// 下文JS代码位置
		// ...
	</script>
</body>

</html>

参照官网例子: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学习四——模型导入》中有相对详细的介绍

let model = null;
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; //接受别人投的阴影
				}
			});
			scene.add(gltf.scene);
			model = gltf.scene;

		}, function (res) {
			// console.log(res.total, res.loaded)
		});
}

增加运动轨迹

用到了Three.js提供的CatmullRomCurve3:使用Catmull-Rom算法, 从一系列的点创建一条平滑的三维样条曲线。

let curve = null;
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)
}

让模型沿轨迹运动

let progress = 0; // 物体运动时在运动路径的初始位置,范围0~1
const velocity = 0.001; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
// 物体沿线移动方法
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);
};

完整代码和效果

完整代码

<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8">
	<title>My first three.js app</title>
	<style>
		body {
			margin: 0;
		}
	</style>
</head>

<body>
	<script type="importmap">
			{
				"imports": {
					"three": "./three.js-master/build/three.module.js"
				}
			}
		</script>
	<script type="module">
		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 progress = 0; // 物体运动时在运动路径的初始位置,范围0~1
		const velocity = 0.001; // 影响运动速率的一个值,范围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", gltf)
					gltf.scene.scale.set(1, 1, 1)
					gltf.scene.traverse(function (object) {
						if (object.isMesh) {
							object.castShadow = true; //阴影
							object.receiveShadow = true; //接受别人投的阴影
						}
					});
					scene.add(gltf.scene);
					model = gltf.scene;

				}, function (res) {
					// console.log(res.total, res.loaded)
				});

		}

		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; //设置线的张力,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;
				}
			}

		};

		function animate() {
			requestAnimationFrame(animate);
			moveOnCurve();
			renderer.render(scene, camera);
		};

		init();
		loadModel();
		makeCurve();
		animate();
	</script>
</body>

</html>

效果:

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/408859.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Edge DEV 侧边栏没有Chat的解决办法。

最近陆陆续续又有很多人通过了new bing 的申请&#xff0c;体验时又发现了自己的侧边栏的bing没有chat&#xff0c;现在解决这个问题有一个比较成熟的方案。一.安装mitmproxyWindowsWindows安装建议用官方提供的客户端访问mitmproxy官方网址点击访问mitmproxy - an interactive…

持久化 pinia 状态

pinia的状态与vuex一样把数据存在内存中&#xff0c;在刷新页面后会清理内存&#xff0c;数据会丢失。 要解决这个问题非常简单&#xff0c;在状态改变时将其同步到浏览器的存储中&#xff0c;如 cookie、localStorage、sessionStorage 。 可以搭配 pinia-persistedstate-plu…

uniapp中table表格设置宽度无效的原因及解决办法

table表格设置标题无效解决办法及原因探索 此属性并不只限于uniapp同时适用于普通表格设置文章目录table表格设置标题无效解决办法及原因探索前言一、示例二、原因三、拓展总结前言 本篇文章讲解了&#xff0c;实际开发中发现表格设置的宽度没有生效&#xff0c;无论是设置行…

css元素定位:通过元素的标签或者元素的id、class属性定位

前言 大部分人在使用selenium定位元素时&#xff0c;用的是xpath元素定位方式&#xff0c;因为xpath元素定位方式基本能解决定位的需求。xpath元素定位方式更直观&#xff0c;更好理解一些。 css元素定位方式往往被忽略掉了&#xff0c;其实css元素定位方式也有它的价值&…

微信公众号获取openId——开发阶段

1、注册测试号 微信公众平台 2、理解获取逻辑 获得微信的openid&#xff0c;需要先访问微信提供的一个网址来获取code。 再访问微信提供的另一网址从而获取openId。 两个链接分别为&#xff1a; https://open.weixin.qq.com/connect/oauth2/authorize?appidAPPID&redire…

JavaScript 基础语法

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录JavaScript 的组成第一个程序JavaScript 的书写形式:注释输出JS 变量理解动态类型基本数据类型运算符数组创建数组访问数组元素…

前端跨域问题的解决方案Access to XMLHttpRequest at ‘http..’ from origin ‘null‘ has been blocked by CORS policy

前言&#xff1a; 在前端发出Ajax请求的时候&#xff0c;有时候会产生跨域问题&#xff0c;报错如下: Access to XMLHttpRequest at http://127.0.0.1/api/post from origin null has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the…

06_HTML_表单提交的细节(submit提交按钮的使用细节)

目录一、submit提交按钮的细节二、关于输入类型(input标签)type属性中file类型(文件上传)和submit类型(提交按钮)的关系三、关于输入类型(input标签)type属性中hidden类型(隐藏域)和submit类型(提交按钮)的关系一、submit提交按钮的细节 form标签是表单标签action属性设置提交…

【js】多分支语句练习(2)

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大一在校生&#xff0c;web前端开发专业 &#x1f921; 个人主页&#xff1a;python学不会123 &#x1f43c;座右铭&#xff1a;懒惰受到的惩罚不仅仅是自己的失败&#xff0c;还有别人的成功。 &#x1f385;**学习…

媒体查询@media

media可以针对不同的媒体类型(包括显示器、便携设备、电视机&#xff0c;等等)设置不同的样式规则&#xff0c;CSS3 根据设置自适应显示。 1.使用方法: media 多媒体类型 and (条件) and (条件)... ①多媒体类型: all用于所有多媒体类型设备 print用于打印机 screen用于电脑…

2022前端笔试面试题

目录 以下为笔试题部分&#xff1a; 1.CSS盒子模型的构成是__,__,__,__. 2.二叉树的中序遍历顺序是badce,后续遍历顺序是bdeca&#xff0c;问前序遍历的顺序。 3.flex布局的父级元素中有哪些常用属性。 4.box-sizing:____;表示怪异盒模型&#xff08;IE盒模型&#xff09;…

JavaScript基础知识总结 14:学习JavaScript中的File API、Streams API、Web Cryptography API

目录一、Atomics和SharedArrayBuffer二、原子操作基础1、算术及位操作方法2、原子读和写3、原子交换4、原子Futex操作与加锁三、跨上下文消息四、Encoding API五、File API和Blob API1、File类型2、FileReader类型3、FileReaderSync类型4、Blob与部分读取六、Streams API1、应用…

uniapp和vue组件之间的传值方法(父子传值,兄弟传值,跨级传值,vuex)

前言 在做vue项目或者uniapp开发微信小程序时&#xff0c;经常会用到组件之间传值&#xff0c;因此想总结记录下。 一、父子传值 父向子传递&#xff1a;props子向父传递&#xff1a;通过 events&#xff08;$emit&#xff09;父组件想调用子组件的方法&#xff1a;通过 thi…

Get请求报错404出现原因及解决办法

ajax中get请求时报404背景环境项目结构问题成因解决办法1解决办法2背景环境 已学习java基础&#xff0c;html&#xff0c;css&#xff0c;js&#xff0c;jquery&#xff0c;bootstrap&#xff0c;layui&#xff0c;maven&#xff0c;servlet和jsp&#xff0c;刚进入spring的学…

前端下载文件的几种方式

前端下载文件的几种方式 前言 实习一个人负责一个管理系统的前端部分。其中&#xff0c;就有前端下载文件的需要。最终采用的是使用axios发送get请求的方式&#xff0c;因为需要携带token。但是&#xff0c;不应该只注重结果&#xff0c;也应该注重过程&#xff0c;不然可能一…

jQuery选择器(二)(基本过滤器,内容过滤器,可见过滤器)

写在前面 jQuery是一个快速、简洁的 JavaScript 框架&#xff0c;是继Prototype之后又一个优秀的 JavaScript 代码库。jQuery的设计宗旨是“WriteLess&#xff0c;DoMore”&#xff0c;即倡导写更少的代码&#xff0c;做 更多的事情。jQuery封装了 JavaScript 常用的功能代码&…

【Vue】具名插槽

要点&#xff1a; 具名插槽&#xff1a;即具有名字的插槽&#xff0c;在默认插槽基础上指定插槽的名字&#xff08;name " "&#xff09;。父组件指明放入子组件的哪个插槽 slot "footer"&#xff0c;如果是template可以写成 v-slot : footer。 父组件中…

html中关于侧边导航栏和导航栏的编写

侧边导航栏 <style>.box{width: 50px;height: 50px;background-color: #483957;transition: width .5s,background-color .2s;}.box:hover{background-color: #004FCB;width: 200px;cursor: pointer;}.a1{position: fixed;right: 40px;top: 200px;float: right;}</st…

如何搭建一个vue项目(完整步骤)

如何搭建一个vue项目(完整步骤) 一、环境准备 1、安装node.js 下载地址&#xff1a;https://nodejs.org/zh-cn/界面展示 2、检查node.js版本 查看版本的两种方式 1|node -v 2|node -version 出现版本号则说明安装成功&#xff08;最新的以官网为准&#xff09; 3、为了…

vue环境搭建

前言&#xff1a;1、首先安装nodejs2、其次安装vue-cli&#xff0c;配置vue环境变量3、再次安装webpack、webpack-cli一、NodeJs安装 1、nodejs下载地址&#xff1a;https://nodejs.org/ 2、验证是否安装成功&#xff08;安装时已经自动加入到环境变量的path中&#xff09; 以…