ThreeJS学习笔记 - 1. 几何体

97 阅读7分钟

几何体定义了模型的形状或结构,它描述了模型的顶点、边和面,也就是说几何体决定了模型的物理外观。


几何体的本质

几何体本质上就是一个一个三角形拼接而成的
说明:几何体所有顶点坐标三个为一组,构成一个三角形,多组顶点构成多个三角形,用来模拟物体的表面。

如:一个矩形平面,可以由两个三角形组合而成。一个矩形方块,可以由12个三角形组合而成等。 image-20241010110528-hwcmjq3.png

三角形的正反面
三个点可以构成一个三角形,从第一个点往第三个点连接

  • 正面:相机对着面,连接的顺序是逆时针

  • 反面:相机对着面,连接的顺序是顺时针


常用的几何体

基本几何体
  • BoxGeometry

    用于创建立方体或长方体。width​、height​ 和 depth​ 分别表示立方体的宽、高和深度。

    const geometry = new THREE.BoxGeometry(width, height, depth);
    
  • PlaneGeometry

    用于创建一个二维平面。width​ 和 height​ 分别表示平面的宽和高。

    const geometry = new THREE.PlaneGeometry(width, height);
    
  • CircleGeometry

    用于创建一个圆形。radius​ 表示圆的半径,segments​ 表示圆的分段数。

    const geometry = new THREE.CircleGeometry(radius, segments);
    
球体和多面体几何体
  • SphereGeometry

    用于创建一个球体。radius​ 表示球的半径,widthSegments​ 和 heightSegments​ 分别表示球在水平和垂直方向的分段数。

    const geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);
    
  • DodecahedronGeometry

    用于创建一个十二面体。radius​ 表示十二面体的半径。

    const geometry = new THREE.DodecahedronGeometry(radius);
    
  • IcosahedronGeometry

    用于创建一个二十面体。radius​ 表示二十面体的半径。

    const geometry = new THREE.IcosahedronGeometry(radius);
    
  • OctahedronGeometry

    用于创建一个八面体。radius​ 表示八面体的半径。

    const geometry = new THREE.OctahedronGeometry(radius);
    
  • TetrahedronGeometry

    用于创建一个四面体。radius​ 表示四面体的半径。

    const geometry = new THREE.TetrahedronGeometry(radius);
    
圆柱体和圆锥体几何体
  • CylinderGeometry

    用于创建一个圆柱体。radiusTop​ 和 radiusBottom​ 分别表示圆柱体顶部和底部的半径,height​ 表示圆柱体的高度,radialSegments​ 表示圆柱体的径向分段数。

    const geometry = new THREE.CylinderGeometry(radiusTop, radiusBottom, height, radialSegments);
    
  • ConeGeometry

    用于创建一个圆锥体。radius​ 表示圆锥体底部的半径,height​ 表示圆锥体的高度,radialSegments​ 表示圆锥体的径向分段数

    const geometry = new THREE.ConeGeometry(radius, height, radialSegments);
    
环形几何体
  • TorusGeometry

    用于创建一个圆环。radius​ 表示圆环的半径,tube​ 表示圆环管的半径,radialSegments​ 表示圆环的径向分段数,tubularSegments​ 表示圆环管的分段数。

    const geometry = new THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments);
    
  • TorusKnotGeometry

    用于创建一个环结。radius​ 表示环结的半径,tube​ 表示环结管的半径,radialSegments​ 表示环结的径向分段数,tubularSegments​ 表示环结管的分段数。

    const geometry = new THREE.TorusKnotGeometry(radius, tube, radialSegments, tubularSegments);
    
自定义几何体
  • BufferGeometry

    ThreeJS的长方体 BoxGeometry​ 、球体 SphereGeometry​ 等几何体都是基于BufferGeometry 类构建的,BufferGeometry是一个没有任何形状的空几何体,你可以通过BufferGeometry自定义任何几何形状,具体一点说就是定义顶点数据

    // 创建自定义几何体
    const geometry = new THREE.BufferGeometry();
    

几何体顶点位置数据

geometry.attributes.position​ 是 Three.js 中 BufferGeometry​ 对象的一个属性,用于存储几何体的顶点位置数据。

位置数据通常是一个 Float32Array​,每三个值表示一个顶点的 x、y、z 坐标。

  • 设置顶点位置

    const vertices = new Float32Array([
      0, 0, 0,  // 顶点1
      1, 0, 0,  // 顶点2
      0, 1, 0   // 顶点3
    ]);
    geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
    
  • 访问顶点位置数据。

    const positions = geometry.attributes.position.array;
    positions[0] = 1; // 修改第一个顶点的 x 坐标
    
  • 通知ThreeJS更新顶点位置

    修改顶点位置数据后,需要设置 needsUpdate​ 属性为 true​,以通知 Three.js 数据已更改

    geometry.attributes.position.needsUpdate = true;
    

几何体顶点索引数据

网格模型Mesh对应的几何体BufferGeometry,拆分为多个三角后,很多三角形重合的顶点位置坐标是相同的,

这时候如果你想减少顶点坐标数据量,可以借助几何体顶点索引 geometry.index​ 来实现。

如下:

每个三角形3个顶点坐标,矩形平面可以拆分为两个三角形,也就是6个顶点坐标。

const geometry = new THREE.BufferGeometry();
// 顶点位置数据
const 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坐标
]);

// 顶点数据赋值给几何体的attributes属性
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); //3个为一组

// 创建材质
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, side: THREE.DoubleSide });
// 创建网格模型
const mesh = new THREE.Mesh(geometry, material);
// 网格模型添加到场景中
scene.add(mesh);

如果几何体有顶点索引 geometry.index​,那么你可以把三角形重复的顶点位置坐标删除。

// ......
// 新的顶点位置数据
const vertices = new Float32Array([
  0, 0, 0, //顶点1坐标
  80, 0, 0, //顶点2坐标
  80, 80, 0, //顶点3坐标
  0, 80, 0, //顶点4坐标
]);

// Uint16Array类型数组创建顶点索引数据
const indexes = new Uint16Array([
  // 下面索引值对应顶点位置数据中的顶点坐标
  0, 1, 2, 0, 2, 3,
])

// 索引数据赋值给几何体的index属性
geometry.setIndex(new THREE.BufferAttribute(indexes, 1)); //1个为一组
// 顶点数据赋值给几何体的attributes属性
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); //3个为一组
// ......

image-20241010115426-lt1t4gb.png


顶点法线数据

先来理解一下数学上的法线概念,比如一个平面,法线的就是该平面的垂线,如果是光滑曲面,一点的法线就是该点切面的法线。

在ThreeJS中法线是一个向量,表示顶点或面的方向,通常用于光照计算。

ThreeJS使用 BufferGeometry​ 对象的一个属性:geometry.attributes.normal​来存储几何体的发现数据

法线数据通常是一个 Float32Array​,每三个值表示一个法线向量的 x、y、z 分量。

  • 设置法线

    const normals = new Float32Array([
      0, 0, 1,  // 顶点1的法线
      0, 0, 1,  // 顶点2的法线
      0, 0, 1   // 顶点3的法线
    ]);
    geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
    
  • 自动计算法线

    ThreeJS 提供了 computeVertexNormals​ 方法,可以自动计算顶点法线。

    geometry.computeVertexNormals();
    
  • 访问和修改法线

    const normals = geometry.attributes.normal.array; // 访问(获取)法线
    normals[0] = 1; // 修改第一个法线向量的 x 分量
    
  • 更新法线

    修改法线数据后,需要设置 needsUpdate​ 属性为 true​,以通知 Three.js 数据已更改。

    geometry.attributes.normal.needsUpdate = true;
    
// 创建一个几何体
const geometry = new THREE.BufferGeometry();

// 定义顶点坐标
const vertices = new Float32Array([
  0, 0, 0,  // 顶点1
  1, 0, 0,  // 顶点2
  0, 1, 0   // 顶点3
]);

// 定义法线
const normals = new Float32Array([
  0, 0, 1,  // 顶点1的法线
  0, 0, 1,  // 顶点2的法线
  0, 0, 1   // 顶点3的法线
]);

// 将顶点坐标和法线添加到几何体中
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));

// 创建一个材质
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });

// 创建一个网格
const mesh = new THREE.Mesh(geometry, material);

// 将网格添加到场景中
scene.add(mesh);

旋转、缩放、居中和平移几何体

BufferGeometry通过 .scale()​、.translate()​、.rotateX()​、.rotateY()​ 等方法可以对几何体本身进行缩放、平移、旋转,这些方法本质上都是改变几何体的顶点数据。

缩放 .scale()
// 几何体xyz三个方向都放大2倍
geometry.scale(2, 2, 2);

// BufferGeometry的旋转、缩放、平移等方法本质上就是改变顶点的位置坐标
console.log('顶点位置数据', geometry.attributes.position);
平移 .translate()
// 几何体沿着x轴平移50
geometry.translate(50, 0, 0);
旋转 .rotateX()​、.rotateY()​、.rotateZ()
// 几何体绕着x轴旋转45度
geometry.rotateX(Math.PI / 4);
居中 .center()
geometry.translate(50, 0, 0);//偏移
// 居中:已经偏移的几何体居中,执行.center(),你可以看到几何体重新与坐标原点重合
geometry.center();