vue 使用 threejs 加载第三方模型

news2024/12/23 8:33:12

threejs 加载第三方模型

接专栏的上一篇博文,这是加载第三方模型相关的。这篇博文拖了很久了哈,简单说一下吧,本来不想写了的,觉得相对来说比较简单,但是还是稍微一扯。为啥要加载第三方呢,上一篇我们绘制的小立方体很简单啊,但是有一些模型可能比较复杂,比如一辆小汽车,一个大楼,我们用代码一行一行的写的话,一是费劲,二是性能不好,所以说我们单独制作一个模型或者是从第三方模型网站下载一个差不多项目能用的模型,我们直接添加进来就可以了,减少了很多自己代码绘制的麻烦。【该博文为本人学习记录,仅供参考,切勿尽信】

相关文档

threejs 官方文档

前言

第三方模型我们可以去一些第三方网站下载,也可以自己制作,从第三方网站下载的话有一个通病,我相信我不说也知道,那就是收费。给大家推荐一个网站,叫做 Sketchfab ,在里面我们可以下载一些需要的模型,当然,有收费的,但是免费的也不少,自己玩的话够用了。因为网站是国外的,国内大环境是吧,肯定收费,这个时候就得去国外网站转转了,由于这个网站是国外的,所以说初次进入可能比较慢,进去之后搜索自己想要的模型就可以了,记住,最好使用英文搜索,使用中文搜索的话,搜索到的模型很少甚至搜不到,如果不会中文怎么办呢,没关系,我给大家推荐一个好用的网站,我已经和 百度翻译 达成了战略合作协议,大家只要打开百度翻译官网,输入自己想要翻译的中文,他会帮助大家转换成对应的英文结果,然后复制到模型网站搜索就可以了。

具体的使用方式,参考 这篇博客 的【添加模型】部分内容,这里就不一次一次的赘述了。

模型获取

好,上面说了一个下载模型的网站,这里呢,我们下载下来模型备用就可以了。然后我这里就不下载了,我自己做一个简单的吧。因为下载的模型很复杂,后边有个地方讲解起来不方便,我自己做一个方便写博客哈,但是模型用法都是一样的东西。

模型很简单,就是一个平面,平面上面一个立方体。

在这里插入图片描述
然后我把模型导出来一下,暂时命名 csdn 吧。

在这里插入图片描述

导出来的结构就是这个死样子的,我们加载的就是 gltf 格式的模型,其他格式的也类似哈。

vue 使用 threejs 加载 gltf 模型

官方文档点这里【任意门】

其实加载模型不是很费劲,官网写的很明白。

既然写了,就稍微多说一点儿,首先我们需要一个空的页面载入 threejs,其他的什么也没有,然后初始化渲染器和轨道控制器就行了,什么其他的都不要加。如果不会就去参考 本专栏的第一篇博文,这里就不重复赘述了哈。

创建完,就很简单了。

在这里插入图片描述

没错,就是一个纯黑的页面。

然后加载我们第三方的模型。我们把下载下来或者自己制作的模型,放在项目 public - models 下,当然 models 文件夹自己随便起的,反正放到 public 下面就成。

在这里插入图片描述

然后我们引入一下这个模型。创建一个方法,就叫 addModel 吧,然后在构造器函数中调用一下让其执行。

方法很简单,就是加载 gltf 模型。

	addModel() {
		// 创建一个加载器
		const loader = new GLTFLoader();
		// 使用加载器加载模型
		loader.load('/models/css/cs.gltf', (gltf) => {
			// 将模型添加到场景
			this.scene.add(gltf.scene)
		});
	}

这样,我们的第三方模型就加载到场景里面去了。完成!就这么简单!

我们可以打印一下 gltf ,看一下里面都是些什么东西。

在这里插入图片描述

我们加载的模型,都在 scene 里面,所以我们是 this.scene.add(gltf.scene)

OK,我们看一下效果:

在这里插入图片描述

哇,黑的。啥也看不到。因为没光线是吧,我制作的模型默认是 MeshStandardMaterial 材质的,需要光线照明。

简单那就添加一个简单的光线吧再。那就添加一个自然光。

	// 添加点光源
	addLight() {
		let ambientLight = new AmbientLight(0xDFDFDF, 0.7);
		this.scene.add(ambientLight);
	}

添加完成之后,我们看一下效果:

在这里插入图片描述

OK。模型加载出来了。就这么简单。

模型加载材质丢失问题

很多人加载完有些模型之后啊,就是我们刚开始说的,画面一团黑,这是什么原因造成的呢,原因很多,最有可能的是两个可能,如果遇到,先排除这两个可能性:

  1. 材质资源丢失
  2. 材质类型需要灯光点亮

第二个原因就和我们刚刚遇到的问题一样哈,就是大部分模型,默认的材质是 MeshStandardMaterial 类型的,这种材质呢需要灯光进行点亮,所以说我们需要点一盏灯就看见了。

在这里插入图片描述

看,我制作的这个模型默认就是这个材质,所以最开始是一团黑,通过灯光点亮就可以看见模型了。

还有一种原因是模型材质资源丢失,比如上面案例有一个地板的资源,是一张图片进行贴图的,如果图片没有了,那么就可能找不到了。

模型透明度设置

我们上面的模型加载进来是不透明的,我们想要给某个模型设置一下透明度怎么办?可以的,比如说,我想给那个蓝色的方块设置一下透明度,我们首先在加载模型的时候得到这个小方块的模型吧?

在这里插入图片描述

看,有两个模型,我们需要获取小方块的,我们可以根据名字判断。

在这里插入图片描述

我在创建模型的时候,小方块的名字叫做 Cube ,所以说我们获取名字是 Cube 的模型就是小方块。

所以我们首先得所有模型,注意一点,我这个模型比较简单,我们下载的第三方模型,可能超级复杂,结构一层套一层,我这个直接遍历 children 就可以了,但是有的模型不行, threejs 提供了一个遍历模型的方式,下面代码:

		// 使用加载器加载模型
		loader.load('/models/csdn/csdn.gltf', (gltf) => {
			gltf.scene.traverse((child) => {
				console.log(child.name)
			})
			// 将模型添加到场景
			this.scene.add(gltf.scene)
		});

使用 traverse 可以递归遍历出所有对象。我们打印一下名字,获取到了所有对象的名字。

在这里插入图片描述

我们根据类型过滤一下,我们可以只获取 Mesh 多边形网格的模型,因为其他的我们不需要。

在这里插入图片描述

然后我们获取 Cube 模型对象设置一下透明度就可以了。

		// 使用加载器加载模型
		loader.load('/models/csdn/csdn.gltf', (gltf) => {
			gltf.scene.traverse((child) => {
				if (child.isMesh) {
					if(child.name === 'Cube') {
						// 允许材质设置透明度
						child.material.transparent = true
						// 材质透明度的值   1 不透明   0 透明
						child.material.opacity = 0.3
					}
				}
			})
			// 将模型添加到场景
			this.scene.add(gltf.scene)
		});

在这里插入图片描述

设置完成,小方块的透明度被改掉了,完成!

通过此方式举一反三,可以设置某些模型的材质样式问题。

蓝色小方块模型移动

上一篇博文已经说了通过变换控制器实现模型移动,这里再说一下,肯定有区别,我们按照上一篇博文说的给项目中添加 射线发射器 和 变换控制器 ,首先我们点击鼠标获取一下点击的对象。

主要代码,下面是主要代码哈!主要代码的意思是直接粘走用,报错,需要自己改造成符合自己的代码,有些库需要引入,变量需要声明啥的,我做案例写的比较随意,别粘过去改都不改一看报错直接骂我。我遇到过好几次了,评论骂我的。

		// 初始化变换控制器
		this.transformControls = new TransformControls(camera, this.renderer.domElement)
		// 判断此次鼠标事件是否是变换事件
		let transing = false
		this.transformControls.addEventListener("mouseDown", event => {
			transing = true
		})
		this.raycaster = new Raycaster() // 初始化射线发射器
		this.mouse = new Vector2()
		let x = 0;
		let y = 0;
		let width = 0;
		let height = 0

		this.renderer.domElement.addEventListener("click", event => {
			if (transing) {
				transing = false
				return
			}
			x = event.offsetX
			y = event.offsetY
			width = this.renderer.domElement.offsetWidth
			height = this.renderer.domElement.offsetHeight
			this.mouse.x = x / width * 2 - 1
			this.mouse.y = -y * 2 / height + 1
			this.raycaster.setFromCamera(this.mouse, camera) // 配置射线发射器
			this.scene.remove(this.transformControls) // 移除变换控制器
			const intersection = this.raycaster.intersectObjects(this.scene.children)
			this.transformControls.detach()
			if (intersection.length) {
				const object = intersection[0].object
				console.log(object)
			}
		})

我们看一下:

在这里插入图片描述

然后我们继续给他添加变换控制器,先仅实现拖拉拽哈。所以我们把模型和变换控制器绑定一下子。

if (intersection.length) {
	const object = intersection[0].object
	this.scene.add(this.transformControls) // 添加变换控制器
	this.transformControls.attach(object)  // 变换控制器绑定模型
}

然后看一下效果,看看我们能不能拖拉拽模型。

在这里插入图片描述
OK,是可以的,起码针对于我自己创建的模型是可以的吧。

模型移动问题

上面一节我们实现了模型移动,在实际开发过程中呢,有一个理论上很简单,但是实现起来会发现遇到很多问题的功能。比如说这样一个场景:

初始化导入第三方模型,小方块加载出来了,我现在移动小方块,我怎么获取小方块移动后的位置信息?

理论上很简单啊,我移动完成之后,打印一下小方块的信息不就可以了吗?OK,我们试一下,我们写一个按钮,移动完之后,打印一下小方块的数据看看,比如页面有一个按钮,我点击按钮,从场景中获取蓝色方块的模型,打印名字和位置:

    // 保存
    save() {
      // 获取蓝色方块模型
      let cube = this.ThreeEngine.scene.getObjectByName('Cube')
      // 打印蓝色方块名称和位置信息
      console.log(cube.name, '----> ', cube.position)
    }

OK,为了方便看,我们加上坐标辅助线哈。

	// 添加辅助线
	addHelpLine() {
		const axesHelper = new AxesHelper(100)
		this.scene.add(axesHelper)
	}

然后我们操作一下,默认小方块加载原点,我们拖拽之后看效果:

在这里插入图片描述

首先点击了小方块,拖拽之后,点击保存按钮打印小方块数据,OK,非常好,数据打印出来了,现在小方块的位置就是打印出来的位置啊!完美,就这样获取移动后的小方块位置。当然了,旋转、缩放亦是如此。

BUT | 但是

OK,接下来哈,说一种特殊情况,仔细看咯!下面的是重点。

我不加载这个模型了,我换一个模型加载。上面我不是加载一个蓝色的小模型吗?对吧,OK,我换一个。换成下面这个样子的模型:

在这里插入图片描述
我加载这样一个模型,和上一个模型的区别是什么呢?每个面设置了一个材质,每个面的颜色不一样,好的,导出模型,重新加载一下。

在这里插入图片描述

OK,模型加载出来了,我们还是拖拉拽一下模型,看一下会出现什么效果,好玩的来了哈!

在这里插入图片描述

看到了吗?我们点击其中一个面,进行拖拉拽的时候,这个面直接给拖走了!每个面独立了!!

为什么会出现这个问题?

我们分析一下:

第一次我们加载的时候,六个面都是统一的材质,都是一样的蓝色。我们加载的时候打印一下加载的模型数据。

在这里插入图片描述

gltf 下面的 scene 下面有个 children ,这是里面是具体展示的模型,里面有两个,其中我展开的这个就是蓝色小方块,名字叫做 Cube,他的 children 是没有东西的,他的类型是 Mesh 类型的。

接下来我们看一下我后来那个彩色小方块结构是啥样的。

在这里插入图片描述
不一样了!仔细看,我稍微一说就哈,好好理解。那个 Cube 小方块,他现在 children 里面,有五个对象,而且名字为 Cube 的模型他的类型变成 Group 了,不是 Mesh 了,反而是他的 children 类型变成 Mesh 了。为什么呢?跟我制作的模型有关系。我把那个小方块的四个面分别设置了自己颜色,上下面完全一样都是蓝色,所以小方块被我分成了 5 部分,每个部分都是一个模型对象,他们都属于 Cube 小方块,这五部分,拼起来了这个彩色的小方块,小方块依旧叫 Cube,他的每部分自动命名为: Cube_1 ~ Cube_5。

在这里插入图片描述

所以说,我们变化控制器那边,用的不对了就。

在这里插入图片描述

我们的 object 获取到的就是 Mesh 类型的,所以说最开始的蓝色小方块是一个整体,object 就是小方块对象。但是换成彩色的,我们点击拿到的这个 object 是彩色小方块的其中一个面!所以说我们移动的话就只能移动其中的一个面!这就是为什么会出现上面的面被移动的效果。

在这里插入图片描述

怎么解决这个问题呢?

其实也很简单,我们就判断一下,如果这个模型包含在组 Group 里,就说明我们点击的这个部分和其他内容包含在同一个组里面,是某个模型的一部分,我们需要对整个组移动,而不是这一部分移动就可以了!

if (intersection.length) {
	const object = intersection[0].object
	this.scene.add(this.transformControls) // 添加变换控制器
	// 变化控制器绑定模型(模型判断,如果父亲是Group就关联父亲,如果不是就关联自己)
	this.transformControls.attach(object.parent instanceof Group ? object.parent : object) // 变换控制器绑定模型
}

然后看一下效果:

在这里插入图片描述

这样就可以统一移动了。而且点击保存按钮,获取到的在场景中的位置依旧准确。

注意一点:我做的模型相对来说简单一些,有的模型一层套一层,所以说我们最终就是要找到属于点击这部分的分组,然后对分组统一操作。

【补充】

有一个地方不想说了,但是还是说一下吧,其实自己试一下也知道的东西。

就是我们先不找分组,直接拖拉面,我们托拉面之后,他的位置数据是怎么样的。我们看一下:

在这里插入图片描述

刚加载出来的时候彩色小方块的位置就是默认的 0,1,0

在这里插入图片描述

而他的 children 里面的各部分位置都是 0,0,0,那我们移动之后呢?

在这里插入图片描述

移动其中一个面之后,对于彩色小方块的位置而言,小方块的位置没有变化,但是对于 children 里面的移动的某个面而言,位置相比之前是有移动的。

在这里插入图片描述
所以说如果不以组的形式统一移动,我们的移动是操作在面上而不是整个模型上面。

在实际开发过程中,还可能遇到一些别的问题,但是按照这个思路,一般可以解决。因为我也懒得在找复杂的模型进行举例了。

双击屏幕实现全屏

这个比较简单了就,直接上代码了就

		window.addEventListener("dblclick", () => {
			// 双击控制进入/退出全屏
			const fullScreenElement = document.fullscreenElement;
			if (fullScreenElement) {
				// 退出全屏,使用document对象
				document.exitFullscreen() // 退出全屏
			} else {
				// 让画布对象全屏
				renderer.domElement.requestFullscreen() // 请求全屏
			}
		})

threejs 自适应页面

这个也很简单,监听一下处理就行了,直接上代码:

		// 监听页面变化
		window.addEventListener("resize", () => {
			// 更新摄像头
			camera.aspect = window.innerWidth / window.innerHeight;
			// 更新摄像机的投影矩阵
			camera.updateProjectionMatrix()
			// 更新渲染器
			renderer.setSize(window.innerWidth, window.innerHeight)
			// 设置渲染器像素比
			renderer.setPixelRatio(window.devicePixelRatio)
		})
	}

好了,今天先到这里吧,下一篇博文稍微说一下,关于鼠标移入弹出提示框的操作。
其实也不知道啥时候才能写,最近好懒!

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

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

相关文章

人工智能:技术的进步与未来展望

一、引言 1.人工智能的定义 人工智能(Artificial Intelligence,简称AI)是指由人类创造的具有某种程度上模拟、延伸或超越人类智能的技术。AI技术使计算机能够从数据中学习、推理、适应并执行类似人类大脑所进行的任务。这些任务包括图像识别、…

【Linux命令行与Shell脚本编程】三,Linux文件系统

Linux命令行与Shell脚本编程 第三章 Linux文件系统 文章目录 Linux命令行与Shell脚本编程三.Linux文件系统3.1,查看文件3.1.1,ls 命令 选项和参数3.1.2,过滤输出列表 3.2, 处理文件3.2.1,touch 创建文件3.2.2,cp 复制文件cp -i 覆盖询问cp -R 递归cp命令中使用通配符 3.2.3,ta…

NFS网络文件共享服务

NFS网络文件共享服务 NFS(network file system)网络文件系统 可以把对方主机资源直接挂载到自己电脑上,比FTP更加方便 明文传输 没有认证机制 安全性很差 只在局域网使用 依赖RPC(远程过程调用) 需要安装nfs-utils(提供NFS服务)…

对话西门子Mendix:低代码与亚马逊云科技Serverless的底层融合,助力企业提效降本...

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 3月30日 亚马逊云科技举办了主题为“全面拥抱Serverless时代”的创新大会,分享了亚马逊云科技17年引领Serverless发展的技术创新、应用场景以及全球客户的创新实践。 会上,亚马逊云科技大中华区产品部…

RocketMQ高级概念

一 RocketMQ核心概念 1.消息模型(Message Model) RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer 负责⽣产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应⼀台…

[MLIR] 转换流程详解(以Toy接入为例)

参考资料: [MLIR] 转换流程详解(以Toy接入为例) - 知乎 (zhihu.com) 在本文中我们使用 toy 语言接入 MLIR,最终转化为 LLVM IR (或目标代码)为例,来讲解 MLIR 的转换流程。具体的流程如下: .toy 源文件 → AST → MLIRGen(遍历AST…

【SSM】整合开发

文章目录 1.ssm整合过程1.1步骤1.2 Spring整合SpringMVC的问题 2.准备工作2.1 添加依赖2.2 创建数据库 3.相关配置3.1 整合Spring和Mybatis3.2 引入SpringMVC3.3 spring整合入web项目 4.测试整合效果 1.ssm整合过程 1.1步骤 (1)Spring整合MyBatis 通过…

PHP数组的功能及实现案例

目录 前言 一、什么是数组 二、创建关联数组 1.1运行流程(思想) 1.2代码段 1.3运行截图 三、创建索引数组 1.1运行流程(思想) 1.2代码段 1.3运行截图 前言 1.若有选择,可实现在目录里进行快速查找&#xff…

golang-GC垃圾回收

参考:https://juejin.cn/post/7040737998014513183#comment 垃圾回收(Garbage Collection,缩写为GC),是一种自动内存管理机制。 相关术语 赋值器:说白了就是你写的程序代码,在程序的执行过程中&#xff0c…

《架构设计》-08-分布式系统和Rpc架构

文章目录 1. 分布式系统1.1 横向拆分1.2 分布式服务框架优缺点1.3 功能/非功能需求 2. RPC架构2.1 概述2.2 网络通信2.3 序列化2.3.1 概述2.3.2 传输协议 2.4 服务调用2.4.1 概述2.4.2 同步调用2.4.3 异步调用(Future模式为例)1)Future-Get模…

day2 OSI七层体系结构

目录 网络体系结构的形成 协议与划分层次 OSI七层体系结构 网络体系结构的形成 两台计算机要互相传送文件需解决很多问题; (1) 必须有一条传送数据的通路。 (2) 发起方必须激活通路。 (3) 要告诉网络如何识别接收方。 (4) 发起方要清楚对方是否已开机&#…

绿色节约型校园电力能耗监控系统的设计与应用方案

摘 要:校园中能源的消耗与浪费占用了校园总费用支出的很大比例,而电能的消耗又是能源消耗的重中之重,重点阐述了校园能耗监控系统方案设计、关键技术。以北方某高校为例应用该方案,并结合具体的耗能特点对节能措施进行研究。 关…

养老保障金查询系统【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设!!! 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址:https://download.csdn.net/download/qq_50954361/87700421 更多系统资源库…

Linux中的DNS域名解析配置及原理

Linux中的DNS域名解析配置及原理 DNS系统的作用1、DNS系统的分布式数据结构2、DNS域名解析方式3、通过BIND做DNS解析部署 DNS系统的作用 DNS域名系统是因特网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互…

2023前端面试上岸手册——JavaScript 部分

目录 JavaScript 有哪些数据类型,它们的区别?数据类型检测的方式有哪些null 和undefined 区别如何获取安全的 undefined 值?Object.is() 与比较操作符 “两等” 、“三等” 的区别?什么是 JavaScript 中的包装类型?为什…

华为OD机试真题(Java),最远足迹(100%通过+复盘思路)

一、题目描述 某探险队负责对地下洞穴进行探险。探险队成员在进行探险任务时,随身携带的记录器会不定期地记录自身的坐标,但在记录的间隙中也会记录其他数据。探索工作结束后,探险队需要获取到某成员在探险过程中相对于探险队总部的最远的足…

2-01 在Nginx中配置静态资源防盗链

2-01 在Nginx中配置静态资源防盗链 IQ1AK-1682304821705)]

基于Spring+SpringMVC+MyBatis框架的Java在线考试系统

项目介绍 基于SpringSpringMVCMyBatis框架的Java在线考试系统 功能模块 |用户功能模块|用户注册登陆|用户可以通过用户名邮箱注册网站,并且通过注册的用户登陆网站。|随机练习|从题库中随机取出指定数量的题目供学员练习。|强化练习|按照学员知识分布情况&#xff…

SpringBoot【运维实用篇】---- 配置高级

SpringBoot【运维实用篇】---- 配置高级 1. 临时属性设置属性加载优先级开发环境中使用临时变量 2. 配置文件分类3. 自定义配置文件 关于配置在基础篇讲过一部分,基础篇的配置总体上来说就是让各位小伙伴掌握配置的格式。比如配置文件如何写啊,写好的数据…

HCIP之路VLAN,三层交换机,STP---生成树协议,MSTP

VLAN---虚拟局域网 垃圾流量问题 网络安全问题 VLAN特点 一个vlan就是一个广播域,不同vlan内部的数据无法进行跨广播域通讯 vlan的划分不受地域限制 vlan的实现 主机的网卡一般只能发送和接收无标记帧(Untagged Frame)。Tagged Frame --- 标…