上次我们在黑漆漆的屏幕上画了三条座标轴,是不是很有成就感呢?但是对 Three.js 这么牛逼的绘图工具来说,只画线似乎太小儿科了。敢不敢画点立 体的东西?

Mesh 对象

笔记1 里,我们画线的时候用到了 Line 对象,它用一个包含顶点数 组的 Geometry 对象来描述自己的几何形状。

实际上在 Three.js 里,具有“几何形状”的对象只有4种: LineMeshPointCloudSprite . 基本上,除了线条以外,所有的3D模型(包括3D 空间中的各种平面)都是由 Mesh 对象以及它的变种来描述的,所以我们在这 里也只讨论 Mesh 对象。至于另外两种, PointCloud 和 Sprite , 我们在 以后会了解到的,不用着急 :)

回到 Mesh 对象上,从文档里可以看到,它的构造函数和 Line 很像:

1
var mesh = new THREE.Mesh(geometry, material)

也就是说,要定义一个 Mesh ,我们也需要提供几何结构和材质信息。

Mesh 的几何结构

和 Line 一样, Mesh 的几何结构是由 Geometry 对象定义的;但是和 Line 不同的是,仅仅一个顶点数组并不足以描述 Mesh 的完整结构,因为 Mesh 由 面组成,而一个面至少包含3个顶点,一维数组不能描述顶点间的这种二维关系。

所以, Three.js 定义了 Face3 类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var geometry = new THREE.Geometry();
geometry.vertices.push(
    new THREE.Vector3(-10,  10, 0),
    new THREE.Vector3(-10, -10, 0),
    new THREE.Vector3( 10, -10, 0)
);
geometry.faces.push(new THREE.Face3( 0, 1, 2 ));

var material = new THREE.MeshBasicMaterial({color: 0xff0000});

var mesh = new THREE.Mesh(geometry, material);

上面 Face3 构造函数的三个参数分别是该平面三个顶点在 Geometry.vertices 数组中的 索引 ;由此可见, Face3 对象是不能独立于 Geometry 对象存 在的,要画出一个面,就要通过 Geometry 的 vertices 和 faces 属性去定义它。 这样定义好 Mesh 的各个面,再提供渲染这些面所用的材质,就能将这个 Mesh 绘制出来了。

简而言之 , Mesh 比 Line 多出一个描述 Face3 的步骤。

但是……为什么 Three.js 只定义了 Face3 ,而没有 Face4 、 Face5 、 FaceN 呢? 因为3顶点和4顶点组成的面具有许多方便程序处理的性质,图形学研究基本上集中在 这两种面上,而这里面又以3顶点的面处理起来最为简单快捷而且显示效果更好。

另外,在 OpenGL 里, 构成面的顶点顺序决定了面的方向 :顶点按逆时针方向排列 的那一面是正面,反之则是背面。以上面的例子画出的三角形为例,顺着Z轴负方向 看过去,看到的就是三角形的正面,因为定义这个面的三个顶点在这个方向上正是 按逆时针排列的。

除了“GL”之名以外, WebGL 也继承了这个约定。而且为了节省资源,WebGL 也是默 认不渲染背面的;所以当我们发现本该出现3D模型的地方空空如也的时候,就要检查 一下是不是把面给画反了……

生成随机地形

为了演示通过程序一步步生成一个有意义的 Mesh ,我们来生成个随机的山地 地形吧。方法很简单:首先通过简化的 Loop Subdivision 算法 将一个正 方形平面细分为足够小的三角形,然后随机一下细分后每个顶点的Y轴坐标。

以下是执行结果,源码嘛当然就在本页面源文件里了。