🚔「OBJ 模型」 以汽车展示案例带大家入门 Three.js

2,473 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情

前言

随着前端的不断发展,人们已经不满足在网上浏览文字图片以及视频,而是对 3D 以及互动有了更高的追求,本文今天带大家入门 three.js。

three.js 是基于原生WebGL封装运行的三维引擎,它可以满足人们对 3D 的追求:物联网3D可视化、产品720在线预览、数据可视化以及科研领域等。

今天带大家从一个案例来理解 three.js 的基本概念及使用方式。

概念理解

threejs9threejs.png

我们首先要知道在 three.js 中是有场景、相机以及灯光等概念。

场景怎么理解呢?大家可以类比一下 canvas ,我们要用 canvas 的 api 来画画,首先是要创建画布,也就是所谓的 容器 。而 three.js 作用于 3D,因此它对应的“画布”就是场景 Scene

灯光怎么理解呢?我们想想我们周围的光有哪些:首先是自然光,它是平行照射的;舞台上的聚光灯,它沿着特定方向会逐渐发散的光源,照射范围在三维空间中构成一个圆锥体;白炽灯,就是一个点光源,光线沿着发光核心向外发散,同一平面的不同位置与点光源光线入射角是不同的,点光源照射下,同一个平面不同区域是呈现出不同的明暗效果。

而相机就像人的双眼,我们从某个特定的角度看,只能看到物体的一个角度的样子。

引入 three.js 引擎

概念介绍完,接下来我们就着手进行开发,首先我们要用到 three.js,那肯定要先进行引入了。

<script src="https://cdn.bootcss.com/three.js/r83/three.min.js"></script>

这里我们为了快速上手,直接通过 CDN 引入。

创建场景 Scene

接着我们创建场景,有了场景才能装我们的物件。

var scene = new THREE.Scene();

加载主角

本文的模型是 OBJ 格式的,这个格式的模型文件进行导出时,会导出 .obj 以及 .mtl 两个类型的文件,.obj 文件包含的信息一样都是几何体顶点相关数据,材质文件 .mtl 包含的是模型的材质信息,比如颜色、贴图路径等。

引入obj模型加载库OBJLoader.js

我们要加载 OBJ 格式的模型,就要用到 OBJLoader 加载器来加载,因此我们先引入该加载器文件。

<script src="http://www.yanhuangxueyuan.com/threejs/examples/js/loaders/OBJLoader.js"></script>

加载模型

引入之后就可以通过 new THREE.OBJLoader() 创建一个加载器实例,然后通过该实例上的 load 方法去加载指定路径下的 模型文件

var objLoader = new THREE.OBJLoader(); //obj加载器
objLoader.load("file.obj", function (obj) {
  scene.add(obj);
});

创建相机

接下来我们要创建一个相机,指定其对着物体的方向。

/**
* 相机设置
*/
var width = window.innerWidth; //窗口宽度
var height = window.innerHeight; //窗口高度
var k = width / height; //窗口宽高比
var s = 100; //三维场景显示范围控制系数,系数越大,显示的范围越大
//创建相机对象
var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
camera.position.set(100, 100, 100); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)

创建渲染器对象

我们需要通过 new THREE.WebGLRenderer() 来创建渲染器对象,用于将我们的整体场景渲染出来。

var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height); //设置渲染区域尺寸
renderer.setClearColor(0xccbaaa, 1); //设置背景颜色
document.body.appendChild(renderer.domElement); //body元素中插入canvas对象
//执行渲染操作  指定场景、相机作为参数
function render() {
    renderer.render(scene, camera); //执行渲染操作
    requestAnimationFrame(render); //请求再次执行渲染函数render
}
render();

注意这里用到了 requestAnimationFrame 这个 api,且进行了嵌套调用。

看看成品

微信截图_20221010123051.png

我们可以发现模型乌漆嘛黑的,这是为什么呢?

如果没有指定灯光的,那么场景中默认是无光环境的,没有光源的话,模型就是黑的,因此我们接下来要给场景添加光源。

objLoader.load("file.obj", function (obj) {
  scene.add(obj);

  //环境光
  var ambient = new THREE.AmbientLight(0x777777);
  scene.add(ambient);

  var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
  // 设置光源的方向:通过光源position属性和目标指向对象的position属性计算
  directionalLight.position.set(200, 100, 200);
  directionalLight.target = obj;
  scene.add(directionalLight);
});

添加好后,我们再来看看效果:

微信截图_20221010122133.png

嗯?我们发现汽车模型是白色的,跟没贴膜一样 ,这是为什么呢?

我们回到刚刚加载模型的地方:

var objLoader = new THREE.OBJLoader(); //obj加载器
objLoader.load("file.obj", function (obj) {
  scene.add(obj);
});

可以发现我们是加载了模型没错,但是材质文件好像没有用上。

引入obj模型材质加载库MTLLoader.js

加载材质也是需要加载器的,因此我们先引入 MTL 加载器。

<script src="http://www.yanhuangxueyuan.com/threejs/examples/js/loaders/MTLLoader.js"></script>

加载材质

这里我们需要理清一个先后关系:首先要通过 MTLLoader 加载材质,然后将加载的材质装载到 OBJLoader 上,这样通过 OBJLoader 加载出的模型就是贴上了材质之后的样子。因此,这里加载是一个先一个后,我们就要用嵌套来做了。

var mtlLoader = new THREE.MTLLoader(); //材质文件加载器
mtlLoader.setCrossOrigin("Anonymous");
mtlLoader.setPath("./static/5/");
mtlLoader.load("file.mtl", function (materials) {
    materials.preload();
    var objLoader = new THREE.OBJLoader(); //obj加载器
    // 返回一个包含材质的对象MaterialCreator
    objLoader.setMaterials(materials);
    objLoader.setPath("./static/5/");
    objLoader.load("file.obj", function (obj) {
      scene.add(obj);
    });
});

看看效果:

微信截图_20221010122801.png

这会有点样子了,我们再给它做个自动旋转效果。

function render() {
    renderer.render(scene, camera); //执行渲染操作
    scene.rotateY(0.01); //每次绕y轴旋转0.01弧度
    requestAnimationFrame(render); //请求再次执行渲染函数render
  }

大功告成,收工下班!

20221010_123435.gif

源码地址

项目中用到的素材及源码,我都放在了 github,大家有需要自行获取。

juejin-demo/car-demo at main · catwatermelon/juejin-demo (github.com)

结束语

这个案例的制作过程中碰上了不少坑,也浪费了不少时间,本文通过最小代码,带大家体验了基本 api 的使用方法,希望大家能有所收获。

如果小伙伴们有别的想法,欢迎留言,让我们共同学习进步💪💪。

如果文中有不对的地方,或是大家有不同的见解,欢迎指出🙏🙏。

如果大家觉得所有收获,欢迎一键三连💕💕。