顶点

几何体本质是由顶点构成的。

立方体网格模型Mesh是由立方体几何体geometry和材质material两部分构成,立方体几何体BoxGeometry本质上就是一系列的顶点构成,只是Three.js的APIBoxGeometry把顶点的生成细节封装了,用户可以直接使用。比如一个立方体网格模型,有6个面,每个面至少两个三角形拼成一个矩形平面,每个三角形三个顶点构成,对于球体网格模型而言,同样是通过三角形拼出来一个球面三角形数量越多网格模型表面越接近于球形。

自定义几何体

下面代码通过Threejs引擎的BufferGeometryBufferAttribute两个API自定义了一个具有六个顶点数据的几何体。

var geometry = new THREE.BufferGeometry(); //创建一个Buffer类型几何体对象
    //类型数组创建顶点数据
var vertices = new Float32Array([
    0, 0, 0, //顶点1坐标
    50, 0, 0, //顶点2坐标
    0, 100, 0, //顶点3坐标
    0, 0, 10, //顶点4坐标
    0, 0, 100, //顶点5坐标
    50, 0, 10, //顶点6坐标
]);
// 创建属性缓冲区对象
var attribue = new THREE.BufferAttribute(vertices, 3); //3个数字为一组,表示一个顶点的xyz坐标
// 设置几何体attributes属性的位置属性
geometry.attributes.position = attribue;
var material = new THREE.MeshBasicMaterial({
    color: 0x0000ff, //三角面颜色
    side: THREE.DoubleSide //两面可见
}); //材质对象
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
scene.add(mesh)

自定义”几何体“
自定义”几何体“

如图所示,正如上述所言,three.js的几何体的面是由三角面组成的,所以该几何体根据给定的顶点,三个为一组地绘制三角形(只不过这个给定的点没有在一起,所以看起来像两个独立的三角面)。

three.js封装好的几何体构造函数,其实就是封装了这个顶点的构造过程。

如果将其中两个点的坐标更改为另外两个点重合,并添加三个顶点,形成新的一面:

var vertices = new Float32Array([
        0, 0, 0, //顶点1坐标
        50, 0, 0, //顶点2坐标
        0, 100, 0, //顶点3坐标
        0, 0, 0, //顶点4坐标
        0, 0, 100, //顶点5坐标
        50, 0, 0, //顶点6
        50, 0, 0,
        0, 100, 0,
        0, 0, 100
]);

上方侧面视角
上方侧面视角

底部仰视
底部仰视

点模型Points

可以把几何体geometry作为点模型Points而不是网格模型Mesh的参数,你会发现上面的六个点坐标会渲染为六个方形的点区域,可以用下面代码代替上面的网格模型部分代码测试效果。

对于网格模型Mesh而言,几何体geometry三个顶点为一组渲染出来一个三角形,对于点模型Points而言,几何体的每个顶点对应位置都会渲染出来一个方形的点区域,该区域可以设置大小。

// 点渲染模式
var material = new THREE.PointsMaterial({
  color: 0xff0000,
  size: 10.0 //点对象像素尺寸
}); //材质对象
var points = new THREE.Points(geometry, material); //点模型对象
scene.add(points); //点对象添加到场景中

point
point

线模型Line

上面两个案例适用点模型和网格模型去渲染几何体的顶点数据,下面代码是把几何体作为线模型Line参数,你会发现渲染效果是从第一个点开始到最后一个点,依次连成线

// 线条渲染模式
var material=new THREE.LineBasicMaterial({
    color:0xff0000 //线条颜色
});//材质对象
var line=new THREE.Line(geometry,material);//线条模型对象
scene.add(line);//线条对象添加到场景中

point
point

顶点法向量

如果你有初高中物理的光学基础,应该会有漫反射、镜面反射的概念。比如太阳光照在一个物体表面,物体表面与光线夹角位置不同的区域明暗程度不同,WebGL中为了计算光线与物体表面入射角,你首先要计算物体表面每个位置的法线方向,在Threejs中表示物体的网格模型Mesh的曲面是由一个一个三角形构成,所以为了表示物体表面各个位置的法线方向,可以给几何体的每个顶点定义一个方向向量

立方体
立方体

不设置顶点法向量数据

下面代码仅仅定义了几何体BufferGeometry的顶点位置数据,没有定义顶点法向量数据。没有法向量数据,点光源、平行光等带有方向性的光源不会起作用,三角形平面整个渲染效果相对暗淡,而且两个三角形分界位置没有棱角感。

img
img

var geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
//类型数组创建顶点位置position数据
var vertices = new Float32Array([
  0, 0, 0, //顶点1坐标
  50, 0, 0, //顶点2坐标
  0, 100, 0, //顶点3坐标

  0, 0, 0, //顶点4坐标
  0, 0, 100, //顶点5坐标
  50, 0, 0, //顶点6坐标

]);
// 创建属性缓冲区对象
var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = attribue

定义几何体顶点法向量数据

在上面顶点位置数据基础上定义顶点法向量数据,这时候除了环境光以外,点光源也会参与光照计算,三角形整个表面比较明亮,同时两个三角形表面法线不同,即使光线方向相同,明暗自然不同,在分界位置有棱角感。

img
img

var normals = new Float32Array([
  0, 0, 1, //顶点1法向量
  0, 0, 1, //顶点2法向量
  0, 0, 1, //顶点3法向量

  0, 1, 0, //顶点4法向量
  0, 1, 0, //顶点5法向量
  0, 1, 0, //顶点6法向量
]);
// 设置几何体attributes属性的位置normal属性
geometry.attributes.normal = new THREE.BufferAttribute(normals, 3); //3个为一组,表示一个顶点的法向量数据

顶点索引

通过几何体BufferGeometry的顶点索引属性BufferGeometry.index可以设置几何体顶点索引数据。

比如绘制一个矩形网格模型,至少需要两个三角形拼接而成,两个三角形,每个三角形有三个顶点,也就是说需要定义6个顶点位置数据。对于矩形网格模型而言,两个三角形有两个顶点位置是重合的。也就是说可以重复的位置可以只定义一次,然后通过通过顶点数组的这个定义的索引值获取这些顶点位置数据。

index
index

不使用顶点索引

下面通过几何体六个顶点定义了两个三角形,几何体的顶点位置数据、顶点法向量数据都是6个

var geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
//类型数组创建顶点位置position数据
var vertices = new Float32Array([
  0, 0, 0, //顶点1坐标
  80, 0, 0, //顶点2坐标
  80, 80, 0, //顶点3坐标

  0, 0, 0, //顶点4坐标   和顶点1位置相同
  80, 80, 0, //顶点5坐标  和顶点3位置相同
  0, 80, 0, //顶点6坐标
]);
// 创建属性缓冲区对象
var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = attribue
var normals = new Float32Array([
  0, 0, 1, //顶点1法向量
  0, 0, 1, //顶点2法向量
  0, 0, 1, //顶点3法向量

  0, 0, 1, //顶点4法向量
  0, 0, 1, //顶点5法向量
  0, 0, 1, //顶点6法向量
]);
// 设置几何体attributes属性的位置normal属性
geometry.attributes.normal = new THREE.BufferAttribute(normals, 3); //3个为一组,表示一个顶点的xyz坐标

顶点索引 .index

下面代码通过几何体BufferGeometry的顶点索引BufferGeometry.index定义了一个矩形。通过顶点索引组织网格模型三角形的绘制,因为矩形的两个三角形有两个顶点位置重复,所以顶点位置数据、顶点法向量数据都只需要定义4个就可以。

var geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
//类型数组创建顶点位置position数据
var vertices = new Float32Array([
  0, 0, 0, //顶点1坐标
  80, 0, 0, //顶点2坐标
  80, 80, 0, //顶点3坐标
  0, 80, 0, //顶点4坐标
]);
// 创建属性缓冲区对象
var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = attribue
var normals = new Float32Array([
  0, 0, 1, //顶点1法向量
  0, 0, 1, //顶点2法向量
  0, 0, 1, //顶点3法向量
  0, 0, 1, //顶点4法向量
]);
// 设置几何体attributes属性的位置normal属性
geometry.attributes.normal = new THREE.BufferAttribute(normals, 3); //3个为一组,表示一个顶点的xyz坐标

通过顶点索引组织顶点数据,顶点索引数组indexes通过索引值指向顶点位置geometry.attributes.position、顶点法向量geometry.attributes.normal中顶面数组。

// Uint16Array类型数组创建顶点索引数据
var indexes = new Uint16Array([
  // 0对应第1个顶点位置数据、第1个顶点法向量数据
  // 1对应第2个顶点位置数据、第2个顶点法向量数据
  // 索引值3个为一组,表示一个三角形的3个顶点
  0, 1, 2,
  0, 2, 3,
])
// 索引数据赋值给几何体的index属性
geometry.index = new THREE.BufferAttribute(indexes, 1); //1个为一组

创建顶点索引数组的时候,可以根据顶点的数量选择类型数组Uint8ArrayUint16ArrayUint32Array。对于顶点索引而言选择整型类型数组,对于非索引的顶点数据,需要使用浮点类型数组Float32Array等。

设置Geometry

几何体Geometry和缓冲类型几何体BufferGeometry表达的含义相同,只是对象的结构不同。

Threejs渲染的时候会先把Geometry转化为BufferGeometry再解析几何体顶点数据进行渲染。

Vector3定义位置坐标

Vector3是threejs的三维向量对象,可以通过Vector3对象表示一个顶点的xyz坐标,顶点的法线向量。

几何体Geometry的顶点位置属性geometry.vertices和缓冲类型几何体BufferGeometry顶点位置属性BufferGeometry.attributes.position是对应的。

var geometry = new THREE.Geometry(); //声明一个几何体对象Geometry
var p1 = new THREE.Vector3(50, 0, 0); //顶点1坐标
var p2 = new THREE.Vector3(0, 70, 0); //顶点2坐标
var p3 = new THREE.Vector3(80, 70, 0); //顶点3坐标
//顶点坐标添加到geometry对象
geometry.vertices.push(p1, p2, p3);

Face3对象定义Geometry的三角形面

几何体Geometry的三角面属性geometry.faces和缓冲类型几何体BufferGeometry顶点索引属性BufferGeometry.index类似:都是顶点位置数据的索引值,用来组织网格模型三角形的绘制。

下面代码自定义了一个由两个三角形构成的几何体,两个三角形有两个顶点坐标位置是重合的。

var geometry = new THREE.Geometry(); //声明一个几何体对象Geometry

var p1 = new THREE.Vector3(0, 0, 0); //顶点1坐标
var p2 = new THREE.Vector3(0, 100, 0); //顶点2坐标
var p3 = new THREE.Vector3(50, 0, 0); //顶点3坐标
var p4 = new THREE.Vector3(0, 0, 100); //顶点4坐标
//顶点坐标添加到geometry对象
geometry.vertices.push(p1, p2, p3, p4);

// Face3构造函数创建一个三角面
var face1 = new THREE.Face3(0, 1, 2);
//三角面每个顶点的法向量
var n1 = new THREE.Vector3(0, 0, -1); //三角面Face1顶点1的法向量
var n2 = new THREE.Vector3(0, 0, -1); //三角面2Face2顶点2的法向量
var n3 = new THREE.Vector3(0, 0, -1); //三角面3Face3顶点3的法向量
// 设置三角面Face3三个顶点的法向量
face1.vertexNormals.push(n1,n2,n3);

// 三角面2
var face2 = new THREE.Face3(0, 2, 3);
// 设置三角面法向量
face2.normal=new THREE.Vector3(0, -1, 0);

//三角面face1、face2添加到几何体中
geometry.faces.push(face1,face2);

Face3构建三角形

threejs提供了Face3对象构建三角形,通过Face3构建一个三角形,不需要设置顶点位置坐标数据,只需要通过数组索引值geometry.vertices数组中获得顶点位置坐标数据。

geometry.vertices数组索引0, 1, 2对应的顶点位置坐标数据表示三角形1的三个顶点坐标,索引0, 2, 3对应的顶点位置坐标数据表示三角形2的三个顶点坐标。

// Face3构造函数创建一个三角面
var face1 = new THREE.Face3(0, 1, 2);
// 三角面2
var face2 = new THREE.Face3(0, 2, 3);

三角形法线设置

前面课程将结果网格模型Mesh的几何体Geometry本质上都是一个一个三角形拼接而成,所以可以通过设置三角形的法线方向向量来表示几何体表面各个位置的法线方向向量。

设置三角形法线方向向量有两种方式,一种是直接定义三角形面的法线方向,另一个是定义三角形三个顶点的法线方向数据来表示三角形面法线方向。

  • 使用三维向量THREE.Vector3表示三角形法线方向数值,然后赋值给三角形对象Face3的法线属性Face3.normal
// 三角面2
var face2 = new THREE.Face3(0, 2, 3);
// 设置三角面法向量
face2.normal=new THREE.Vector3(0, -1, 0);
  • 换另一种方式,通过三角形面Face3Face3.vertexNormals属性给三角形的三个顶点分别设置一个顶点法线方向数据。
// Face3构造函数创建一个三角面
var face1 = new THREE.Face3(0, 1, 2);
//三角面每个顶点的法向量
var n1 = new THREE.Vector3(0, 0, -1); //三角面Face1顶点1的法向量
var n2 = new THREE.Vector3(0, 0, -1); //三角面2Face2顶点2的法向量
var n3 = new THREE.Vector3(0, 0, -1); //三角面3Face3顶点3的法向量
// 设置三角面Face3三个顶点的法向量
face1.vertexNormals.push(n1,n2,n3);

访问几何体对象的数据

BufferGeometry的属性
BufferGeometry的属性

Geometry的属性
Geometry的属性

可以通过控制台输出来尝试访问这些数据:

BoxGeometryPlaneGeometrySphereGeometry等几何体类的基类是Geometry

var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
console.log(geometry);
console.log('几何体顶点位置数据',geometry.vertices);
console.log('三角行面数据',geometry.faces);

输出
输出

PlaneBufferGeometry表示一个矩形平面几何体。

BoxBufferGeometryPlaneBufferGeometrySphereBufferGeometry等几何体类的基类是BufferGeometry

//创建一个矩形平面几何体
var geometry = new THREE.PlaneBufferGeometry(100, 100);
console.log(geometry);
console.log('几何体顶点位置数据',geometry.attributes.position);
console.log('几何体索引数据',geometry.index);

image-20201206145419638
image-20201206145419638

几何体的几何变换

几何体Geometry对象有一系列的顶点属性,也封装了一系列的方法,通过.scale().translate().rotateX()等方法可以对几何体本身进行缩放、平移、旋转等几何变换。

注意:这些本质上都是改变结合体顶点位置坐标数据。

var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
// 几何体xyz三个方向都放大2倍
geometry.scale(2, 2, 2);
// 几何体沿着x轴平移50
geometry.translate(50, 0, 0);
// 几何体绕着x轴旋转45度
geometry.rotateX(Math.PI / 4);
// 居中:偏移的几何体居中
geometry.center();
console.log(geometry.vertices);
[Vector3, Vector3, Vector3, Vector3, Vector3, Vector3, Vector3, Vector3]
0: Vector3 {x: 100, y: 1.4210854715202004e-14, z: 141.4213562373095}
1: Vector3 {x: 100, y: 141.4213562373095, z: -1.4210854715202004e-14}
2: Vector3 {x: 100, y: -141.4213562373095, z: 1.4210854715202004e-14}
3: Vector3 {x: 100, y: -1.4210854715202004e-14, z: -141.4213562373095}
4: Vector3 {x: -100, y: 141.4213562373095, z: -1.4210854715202004e-14}
5: Vector3 {x: -100, y: 1.4210854715202004e-14, z: 141.4213562373095}
6: Vector3 {x: -100, y: -1.4210854715202004e-14, z: -141.4213562373095}
7: Vector3 {x: -100, y: -141.4213562373095, z: 1.4210854715202004e-14}
length: 8

BufferGeometry和几何体Geometry 一样具有.scale().rotateZ().rotateX()等几何体变换的方法。

注意

注意网格模型Mesh进行缩放旋转平移变换和几何体Geometry可以实现相同的渲染效果,但是网格模型Mesh进行这些变换不会影响几何体的顶点位置坐标,网格模型缩放/旋转/平移等变换 改变的是模型的本地矩阵、世界矩阵。

可以执行下面代码测试。

// 几何体xyz方向分别缩放0.5,1.5,2倍
geometry.scale(0.5, 1.5, 2);
// 网格模型xyz方向分别缩放0.5,1.5,2倍
mesh.scale.set(0.5, 1.5, 2)