本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
经过前面几大章节的学习,我们基本对 WebGL 有一定的认识了。但长期的底层知识学习,不仅让人感觉十分的枯燥乏味,也使我们与应用层渐行渐远了,学到现在甚至不知道可以用 WebGL 来做什么…因此这一章开始,我打算暂停对底层基础知识的学习了,转而开始做一个小实战,通过实战的方式来接着探索不一样的 WebGL!
本章的实战内容呢,我打算以业界中比较成熟的应用层框架 three.js
为开端,实战一个“3D看车”。然后会从上层往底层走,在后续章节中用原生 WebGL 一步步实现这种效果。一路走来我们都仅仅在学习底层的知识,这一次我们反过来,从上层往下走!
OBJ模型文件
回顾整个 WebGL 的学习历程,我们绘制过三角形、简单的多边形,甚至还有立方体等图形…但我们都是通过显示地自定义数据点、表面颜色等图形数据再将其绘制出来的,因此你也许会感觉到非常地吃力。就好比我在绘制立方体的时候,我会习惯性地先建立一个三维直角坐标系,然后一个一个地描点算坐标…
OBJ文件解析
在绘制一个如此简单的立方体时都要耗费大量的时间精力在非代码层面,更别说你要绘制一些复杂的图形了。于是,三维模型就出现在我们的眼前了~当然,三维模型的文件类型有很多,我的建议是掌握其中一个,剩下的用到再去关注即可。
so,本文我们只需要关注 obj 的文件即可。那什么是 obj 文件呢?
.obj
文件是Alias|Wavefront公司为3D建模和动画软件”Advanced Visualizer”开发的一种标准3D模型文件格式,大部分3D软件都支持导入、导出obj格式的模型文件。
看似非常抽象,其实我们可以将其当作一个数据存储文件,它里面包含了三维模型所需要的数据信息,如顶点坐标、纹理颜色等等…(也就是我们之前学习过程中手动定义的那一对数据信息)
接下来,我带大家简单的了解下 obj 文件中的一些“语法”知识:
前缀 | 描述 |
---|---|
# | 注释 |
mtllib | 引用外部 mtl 材质文件(注意文件路径) |
o | 模型名称 |
v | 顶点坐标 (x, y, z, [w]) ,其中w 可选 |
vt | uv贴图坐标(x,y) |
vn | 法向量 |
usemtl | 使用 mtl 文件中的材质 |
这里我们简单看一下就好,不过大家可以先留意一下 mtllib
这个前缀,它是模型文件的“外衣”,后面实战环节中会用到!
一个真实的 OBJ 模型文件大概长这样,大家先看看实物,如下图所示(估计你也不会有啥耐心看):
大概瞄一眼就能里面一点数字,全是就是顶点啊、法向量啊等等的数据信息,都是我们在之前的学习中经常使用的,并没有什么新东西。
发现模型
那在即将实战之前,我们是不是还缺点什么?诶,车呢?对,我们虽然已经有了 three.js 这个渲染器,但是我们还差个被观察的物体——车。车这玩意上哪里去找?好问题!
其实 3D 模型的来源还真不少,我们既可以自己找软件制作,也可以去扒拉一个现成的模型来用。在网上随便搜一下,能搜到很多网站,还有很多免费的模型。而我就在 free.3d 网站上找了个轻量一点的车模型:
因为我打算在码上掘金中直接实现这个功能给大家直接体验,所以选了一个文件体积较小的模型文件,至于好不好看!帅不帅气~这个大家就不要太纠结了。学完了,大家就可以在自己去玩的时候找个帅一点的车车了。
当然了,模型的网站有很多,大家自行去发掘,有好的又可以白嫖的可以告诉无知的笔者,给我也有多一个薅羊毛的渠道!参考知乎一些大牛的推荐,也能找到很多 3D 模型相关的网站~
注:此处模型仅用于个人学习用途,如有侵权,联系我删除。
实战 3D 看车
其实最早在刚学习 WebGL 的开始,就想自己实现一个渲染引擎,以实现可以 360° 观察物体的这么一个功能。因为我觉得这是往 WebGL 方向学习的必经之路,毕竟平时我们能接触到的一些 3D 相关的应用都有这种能力,好比 3D 看房这种。那么事不宜迟,我就开始我的第一个 3D 看车应用了!
废话不多说,下面我将直接上手 three.js
和加载前文提及的模型文件,跟大家一起探究怎么用 three.js
来实现一个简单的 3D 看车。(关于 WebGL 的框架选择,并不局限于 three,大家继续往后看吧。关于框架的选择我会在文章的结尾再唠唠一会)
直接上效果(可以直接用鼠标拖拽以 360° 观察该汽车模型):
功能点解析
我觉得既然是学 WebGL 就得先看到效果,才有学习下去的动力,所以这里我就先把整个看车的效果放上来给大家先体验了!接下来,我将会解析这个3D看车应用中涉及到的原生知识点,并且会跟之前学习的基础知识联系起来。首先我们把功能拆解如下:
- 加载模型文件。前面刚刚才说完,文件里存放着汽车的数据信息。
- 相机。360°看车靠的就是切换相机的位置,从不同的角度观察物体。而其中涉及到的具体知识点我们一起回顾下:
- 视点、目标点、上方向。确定观察者和目标的关系。
- 视图矩阵。平移、旋转观察物体的底层便是视图矩阵的变换。
- MVP变换中的V。
- 透视投影。 现象:物体近大远小,比起正交投影的效果则更贴近生活。
- 用
fov
、aspect
、near
、far
来表示透视投影。 - MVP变换中的P。
- 用
- 光照。有没有发现我们渲染出来的效果跟模型时的截图颜色有所不同?
拆解完知识点后发现,我们之前所学的 WebGL 基础知识已经足够让我们实现一个 3D 看车的效果了。不过我们先不急着直接动手原生代码,先看看 WebGL 的学习标杆 three.js
到底做了什么,再自行通过原生代码实现!我们接着往下看。
Three.js
的代码解析
其实有大概了解过 three.js
的同学应该对以下几个词语都不陌生:场景(scene)、相机(camera)、渲染器(renderer)。没错,官方文档的起步教学中就有提到,所以我们大概可以知道,这几个词对于我们这个实战来说是必备的,那我们也从这几个词开启 three.js 的使用讲解!
1. 初始化准备
俗话说磨刀不误砍柴工,这里我们首先分两块进行初始化的准备。
- 模型文件及加载。
- three 代码层面的初始化。
模型文件怎么来的前文有提到,大家就自行准备吧。在码上掘金的代码演示中,我把文件放到了 github 上,当然如果是自己的项目,放到 public 目录下也行了…反正这玩意自行准备好后将其放到web服务器上就好(不管你用什么手段,只要能加载到就行了)
这里多说一句题外话,首先要确保模型文件的可用性,毕竟像我这种穷人去网上随便扒拉的OBJ文件可能它本身就是不可用的(或者说有缺陷的)。这也是为什么我先用
three.js
来开实战的头,而不是上来就原生实战。毕竟以我的实力,万一是模型文件自身有问题导致开发上不符合预期,我就要掉坑里了!
那紧接着就是 three.js
的初始化准备了!首先我们就得就先初始化了上述的三个关键词!
// 透视投影相机
camera = new THREE.PerspectiveCamera(60, canvasRef.value.offsetWidth / canvasRef.value.offsetHeight, 1, 100);
// 场景
scene = new THREE.Scene();
// 渲染器
renderer = new THREE.WebGLRenderer();
简单来看,上述代码中:
- 初始化了透视投影的相机
PerspectiveCamera
。这里顺带跟大家回顾一下具体的参数含义。- fov:垂直方向视角,角度越大,视野越广。
- aspect:近裁剪面的宽高比。
width / height
。 - near:近裁剪面位置。
- far:远裁剪面位置。
- 具体可以看下图加深印象!
- 初始化了场景
Scene
。 - 初始化了渲染器
WebGLRenderer
。
2. 基础配置
初始化工作完成后,我们接着来到基础的配置环节。这里会涉及环境光、点光源、相机位置等的配置代码讲解。
// 环境光
ambientLight = new THREE.AmbientLight(0xffffff)
// 点光源
let pointLight = new THREE.PointLight(0xffffff, 0.6);
// 设置相机位置
camera.position.set(0, 1, 6);
// 将环境光添加到场景
scene.add(ambientLight);
// 点光源添加到相机
camera.add(pointLight);
// 设置渲染器的宽高为我们画布容器的宽高
renderer.setSize(canvasRef.value.offsetWidth, canvasRef.value.offsetHeight);
// 插入渲染器到 dom 上
canvasRef.value.appendChild( renderer.domElement );
上述代码作用已经在注释中标明,这里做个简要的总结。这里我们设置了白光的环境光 AmbientLight
,还在相机中添加了点光源PointLight
(中间直射位置亮,向四周递减)。跟环境光不同的是,这里设置了点光源的光照强度(第二个参数)为 0.6
(这个大家可以在码上掘金中自己调整看看效果)。并且我将环境光加到了场景中,点光源加入到相机中!最后,设置了渲染器的宽高和将渲染器的 dom 插入到 html 中。
那么,如果到这一步,我们想看看渲染效果可以吗?当然可以,我们只需要调用 renderer.render
方法即可。看看threejs官网对其的介绍:
介绍具体的大家自己看吧,我们这里关注怎么用!可以看到这个方法接受两个参数,一个 scene 一个 camera。这些我们在初始化环节就已经准备好了的,直接传进去并执行看看效果吧!
可以,居然是一块黑屏。不过这样也起码说明了前面的代码没什么问题,至少能出来个黑屏不是?那我们接着趁热打铁,将我们的车车放进去!
3. 加载模型
前文讲解 OBJ 文件语法的时候,大概介绍了 mtl
这么一个东西,有印象的小伙伴应该记得我说他是模型的“外衣”,并且它在 OBJ 文件中存在的形式只是一个文件地址。所以,这里我们加载模型的时候有两步,一是加载 mtl
的外衣,其次再是要加载车子的 OBJ
模型文件。
在 three.js 中,它针对不同的模型文件封装了不同的 Loader
(有需要的同学可以去 官网three-loader 中看一下),所以我们首先需要找到对应的 Loader 并初始化一下。
// 加载 OBJ 文件所用
const loader = new OBJLoader();
// 加载 MTL 外衣所以
const MTLloader = new MTLLoader();
紧接着就是加载模型文件了,因为我们要把 mtl
材质给到车模型去穿起来,所以我们是先加载 mtl
文件,再加载 OBJ
文件!放到代码里可以这样写:
MTLloader.load('utl', function (materials) {
// 给模型穿衣服
loader.setMaterials(materials);
loader.load('url', function () {
// todo
})
})
没错,当我们加载完 mtl
文件后,可以通过 setMaterials
方法给真正的 OBJ 模型穿上衣服。当我们的OBJ
文件也加载完成的时候,我们就可以将模型文件也加入到 scene
中,并且通过 renderer
将其渲染出来。也就是上面代码块的 todo
中,我们仅需要这样即可,如下代码块:
// 模型加入到 scene
scene.add(obj);
renderer.render(scene,camera);
那么此时!我们的车就出现在眼前了:
这里,我们可以顺便做个小实验,以验证 mtl
是“外衣”的这么一件事。当我们注释掉代码 loader.setMaterials(materials);
会变成什么样的一个现象呢?我们接着看下图:
好家伙,差点连是个车都看不出来了~所以,这里大家应该给家真实的感受到 mtl
的外衣作用了吧?那么,现在车也有了,就差最后一步就能做到 360° 观察了,我们再接着往下。
4. 轨道控制器
其实在 three.js
中要实现 360° 观察物体是很简单的一件事情,因为已经有了现成的封装了——OrbitControls 轨道控制器。我们看看它的简介:
轨道控制器允许相机绕目标环绕。
ok,这正是我们想要的效果,直接上!我们来看看代码:
// 初始化轨道控制器
controls = new OrbitControls(camera, renderer.domElement);
上述代码中,我们初始化轨道控制器,并传入了 相机、渲染器dom 两个参数!至于要传入相机,大家都应该知道为什么,因为我们是要改变相机的观察角度嘛(改变相机的 position
)。那传入 dom 是要干嘛?没错,是为了对 html元素的 事件监听!
所以我们也能大概猜到这个东西的用法了。它既然自己实现了对渲染器的事件监听,那我们应该只需要在一些时间节点中做“更新”的操作就行了。这个“更新”分成两个点,其中一个是相机位置的更新,还有一个是画面的更新。画面更新好办,我们重新调用 renderer.render
就好了,那相机位置的更新要怎么搞呢?是我们去 camera.position.set(x, y, z)
吗?哈哈哈,这倒不用,我们看到它有一个 update 的方法,直接调用它就得了。
那其实到这里,我们要做的事情也就很简单了,只需要在需要变化的节点改变相机位置、重新绘制图像即可。所以我们只需要在 mousemove
的时候加入以下代码即可:
// 更新相机位置以改变观察角度
controls.update();
// 重新绘制图像
renderer.render(scene, camera);
至于这个 OrbitControls
控制器的实例有很多配置项的,可以控制一些动画速度、是否缩放观察模型等等,我就不在这里一一例举了,这些大家用的时候再参照文档就好。
5. 加入点光源
记性好的小伙伴一定会说怎么好像点光源初始化了也一直没用,也一直没提到过,笔者是不是忘了?这不,我也有苦衷啊…毕竟点光源这玩意,不是十分显眼(并且我还设置成了0.6的光照强度),怕大家一时间看不出区别嘛~
之所以放在这里,是因为这一步大家已经可以 360° 观看小汽车了,这时候有点光源和没点光源就比较好看出来了,毕竟我们大部分人都不具备 ui小哥 才有像素眼~还记得前文我们把点光源 add
到了 camera
中吗?因此,要实现点光源只需要我们把拥有点光源的 camera
也加入到 scene
中即可。
scene.add(camera);
就这么一句,点光源效果就出来了。大家可以自己改变观察角度观察一下这个点光源的效果。我这里截了个图来给大家大概看看区别:
对比起来看,有点光源和没有点光源差距就很明显了!当然,大家自己去码上掘金,通过注释代码来对比一下效果,感受会更加明显~
写在最后
WebGL 的基础知识学习确实很枯燥,但是当其真正被拿上应用层做3D开发时,它所带来的实战效果却又有很强的吸引力,吸引你了解这个领域并对图形学有一定的求知欲。所以,从这一章节开始我打算从上层的实战应用往下套,通过 three.js
在实战中应用,并逐步往底层走了解原理、源码实现,这样既能学到 WebGL 的知识 又不全是枯燥乏味的内容~
原文链接:https://juejin.cn/post/7350181789531373619 作者:井柏然