Three.js 初体验之基础要点总结

28 Oct 2017

大概是六月底的某天,偶然打开了 three.js 的官网。随意点开了网站上的几个展示项目。"哇塞 ~ ~" 这炫酷的画风真是 interesting,感觉充满惊喜 ~

其实最早看到 three.js 这个词应该是2015年看张雯莉博客的时候,博主写了一本电子书《Three.js入门指南》。但是当时的我并没有仔细地去看看 three.js 是什么。

在搜集 three.js 资料的过程中,又特地去翻了一下她的博客,也把那本电子书看了一遍。忍不住赞美一下博主,这个和我年纪相仿的女生,身上有很多值得我学习的地方。每次隔了很久去她的博客,总会看到一些新颖的小创意。从一篇篇的博文里面,隔着屏幕都能感觉到博主不断地在进步。嗯,努力的人会发光。

前段时间,看了电子书以及网上的其他入门教程和资料。现在,补一下博文,把一些最基础的要点在这里摘录总结一下吧。最后会附上相关的参考和推荐资料。

Three.js 是什么?

这是一个 3D JavaScript库,通过 three.js, 能够写出在浏览器上流畅运行的3D程序。 Three.js 封装了底层的图形接口,让我们无需掌握复杂的图形学知识,就能用简单的代码实现三维场景的渲染。

OpenGL vs WebGL vs Three.js

  • OpenGL: 最常用的跨平台图形库。 
  • WebGL: 基于 OpenGL 设计的面向web的图形标准,将 OpenGL ES(OpenGL for Embedded Systems,OpenGL嵌入式版本,针对手机、游戏机等设备相对较轻量级的版本)移植到了网页平台,提供了一系列JavaScript API。
  • Three.js: 通过对 WebGL 接口的封装与简化而形成的一个易用的图形库。

开发准备

  • 下载

Three.js GitHub 地址,可以看到 three.js 和压缩过的 three.min.js两个文件。下载其中任意一个。

  • 引用

在 HTML 文件中引用文件:

<script type="text/javascript" src="three.js"></script>  

然后就能通过全局变量 THREE 访问到所有属性和方法了。

基础概念与用法

基本思路

Three.js 实现简单的三维场景渲染最基本的步骤如下:

  • 构建一个三维空间 ,即 Three.js 中的场景(Scene)。场景可以理解为现实世界中的舞台。
  • 选择一个观察点,并确定观察方向/角度等 ,即 Three.js 中的相机(Camera)。相机决定了场景中哪个角度的景象会显示出来。就好比舞台中的摄像机或人的眼睛一样,在不同位置,抬头或低头所看到的景象不同。对于同一个的场景,不同类型的相机以及不同的相机设置,会呈现不一样的效果。
  • 在场景中添加供观察的物体。就像舞台上需要演员、道具、灯光等等。
  • 最后,需要通过渲染器 Renderer 将观察到的场景渲染到屏幕上的指定区域。

所以总体上,一个典型的 Three.js 程序至少要包括场景(Scene)、照相机(Camera)、渲染器(Renderer)、以及在场景中创建的物体。

Scene

Three.js 中添加的物体都需要添加到场景中,因此它相当于一个大容器。一般来说,场景里没有很复杂的操作,在程序最开始的时候进行实例化,然后将物体添加到场景中即可。

var scene = new THREE.Scene();

Camera

首先,关于坐标系,WebGLThree.js 使用的坐标系都是右手坐标系。X 轴向右,Y 轴向上, Z 轴向屏幕外。

通过 Three.js 创建的三维场景如何显示到二维的显示屏上呢?照相机就是这样一个抽象,它定义了三维空间到二维屏幕的投影方式,而针对投影方式的不同,照相机又分为正交投影照相机 THREE.OrthographicCamera 与透视投影照相机 THREE.PerspectiveCamera。二者都是 THREE.Camera 的子类。

  • 正交投影
THREE.OrthographicCamera(left, right, top, bottom, near, far)

上面六个参数分别代表正交投影照相机拍摄到的空间的六个面的位置,这六个面围成一个长方体,即视景体(Frustum)。只有在视景体内部的物体才可能显示在屏幕上,而视景体外的物体会在显示之前被裁减掉。

使用正交投影照相机获得的结果就像数学课画几何图形的效果,对于三维空间内平行的线,投影到二维空间中也一定是平行的。一般对于制图、建模软件通常使用正交投影,这样不会因为投影而改变物体比例。

  • 透视投影
THREE.PerspectiveCamera(fov, aspect, near, far)

上面四个参数中,fov 是视景体竖直方向上的张角(是角度制而非弧度制);aspect 等于width / height,是照相机水平方向和竖直方向长度的比值,通常设为 Canvas 的横纵比例。 near 和 far 分别是照相机到视景体最近、最远的距离,均为正值,且far应大于near。

使用透视投影照相机获得的结果是类似人眼在真实世界中看到的有近大远小的效果。因此大多数情况采用透视投影展示 3D 效果。

Renderer

渲染器决定了渲染的结果应该画在页面的什么元素上面,并且以怎样的方式来绘制。

Renderer 绑定一个 canvas 对象,并可以设置大小,默认背景颜色等属性。调用Renderer 的 render 函数,传入 scenecamera,就可以把图像渲染到 canvas 中了。

  • 方式一:绑定已有 canvas 元素
var renderer = new THREE.WebGLRenderer({
  canvas: document.getElementById('container')
});
  • 方式二:生成 canvas 元素
var renderer = new THREE.WebGLRenderer(); 

//设置 canvas 宽400像素,高300像素 
renderer.setSize(400, 300);

//将渲染器对应的Canvas元素添加到 <body> 中
document.body.appendChild(renderer.domElement);

另外,设置背景色的写法如下:

renderer.setClearColor(0x000000);//设为黑色

Object

有了场景、相机和渲染器,相当于搭好舞台。接下来就需要在场景中加入具体的物体。

Three.js 中的物体有很多种,包括 MeshLinePoints等,它们都继承自 Object3D 类。其中,最常用的是网格(Mesh),网格是由顶点、边、面等组成的物体。

为什么叫网格呢?在计算机的世界里,一条弧线是由有限个点构成的有限条线段连接得到的。线段很多时,看起来就是一条平滑的弧线了。 三维模型也是类似的,普遍的做法是用三角形组成的网格来描述,即Mesh 模型。随着三角形网格数量的增多,所展示的物体表面越加平滑。

在创建物体时,需要传入两个参数,一个是几何形状 (Geometry),另一个是材质(Material)。 前者最主要的功能是储存了一个物体的顶点信息。后者是物体表面除了形状以外所有可视属性的集合,包括色彩、纹理、光滑度、透明度等等,是与渲染效果相关的属性。

Geometry

Three.js 提供了长方体、平面、球体、圆形、圆柱、圆台等许多基本形状。此外,还可以通过自己定义每个点的位置来构造形状。对于比较复杂的形状,还可以通过外部的模型文件导入。

以长方体为例,其构造函数如下:

THREE.BoxGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)

上面的前三个参数分别代表在 x、y、z方向上的长度;后面的三个参数分别是在三个方向上的分段数(缺省值为1,即不分段)。

Material

  • 基本材质(BasicMaterial)

使用这种材质的物体,渲染后物体的颜色始终为该材质的颜色,而不会由于光照产生明暗、阴影效果。如果没有指定材质的颜色,则颜色是随机的。

new THREE.MeshBasicMaterial(opt)

其中,opt 可以缺省,或者为包含各属性的值

* visible:是否可见,默认为true
* side:渲染面片正面或是反面,默认为正面THREE.FrontSide,可设置为反面THREE.BackSide,或双面THREE.DoubleSide
* wireframe:是否渲染线而非面,默认为false
* color:十六进制RGB颜色,如红色表示为0xff0000
* map:使用纹理贴图
  • 法向材质(MeshNormalMaterial)

法向材质可以将材质的颜色设置为其法向量的方向,材质的颜色与照相机与该物体的角度相关。 法向材质的设定很简单,甚至不用设置任何参数:

new THREE.MeshNormalMaterial()

以上两种材质都不受光照的影响,比较缺乏真实感,下面是两种更加真实的光照模型:Lambert 光照模型以及 Phong 光照模型。

  • Lambert 材质

这是在灰暗的或不光滑的表面产生均匀散射而形成的材质类型。比如一张纸就是Lambert表面。其主要特点是只考虑漫反射而不考虑镜面反射的效果,对于金属、镜子等需要镜面反射效果的物体就不适应,对于其他大部分物体的漫反射效果都是适用的。

new THREE.MeshLambertMaterial(opt)
  • Phong 材质

Lambert 不同的是,Phong 考虑了镜面反射的效果,因此对于金属、镜面的表现尤为适合。

new THREE.MeshPhongMaterial(opt)
  • 材质的纹理贴图

前面的几种材质都是单一颜色的,如果想要使用图像作为材质,就需要导入图像作为纹理贴图,并添加到相应的材质中。

new Three.TextureLoader();

Animation

最后,加入动画,就可以让 3D 场景动起来。动画的关键就是利用 requestAnimationFrame 来实现循环渲染。

// a render loop
function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}

之所以使用 requestAnimationFrame 而不是 setTimeout / setInterval,据说因为 requestAnimationFrame 是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。

后记

当然,要让场景更加真实炫酷,还需要添加光的效果(环境光、点光源、聚光灯等等)。这里就不展开了。

上面摘录整理的,属于 Three.js 中最简单基础的部分吧。学习的过程中,可以跟随文档写写简单的测试示例demo 代码地址, 但是距离一个比较完整的展示效果还是 long way to go 呀。

总体感觉,Three.js 基础入门(不对,应该只能算尝鲜 or 体验)还算比较简单。但是要真正掌握其实还是有一些难度的,也需要了解那里面更加高级和复杂的用法。

参考资料

其他推荐

Three.js 官网示例 里面展示了很多例子和相应的源码。虽然对于初学者而言,在没有掌握很多知识点的情况下,感觉很多例子过于复杂,但是也可以大致看看参考下思路。

END

🐱 就酱。