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。模型加载出来了。就这么简单。
模型加载材质丢失问题
很多人加载完有些模型之后啊,就是我们刚开始说的,画面一团黑,这是什么原因造成的呢,原因很多,最有可能的是两个可能,如果遇到,先排除这两个可能性:
- 材质资源丢失
- 材质类型需要灯光点亮
第二个原因就和我们刚刚遇到的问题一样哈,就是大部分模型,默认的材质是 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)
})
}
好了,今天先到这里吧,下一篇博文稍微说一下,关于鼠标移入弹出提示框的操作。
其实也不知道啥时候才能写,最近好懒!