类型数组
介绍
在 JavaScript 中,数组分为两类,一种是我们经常使用到的普通数组(Array),它支持存储任意类型的数据,因此具有较好的灵活性和较差的运算效率;另一种则是限定了数据类型的类型数组(TypedArray),它灵活性更差,但运算效率更高,适合用于大规模的数据处理(如矩阵计算)。
// 普通数组
const arr = [1, 'str', false, {}, []];
// 类型数组
// 如下语句表示创建一个存储了5个数据(4、1、4、4、1),
// 且每个数据占用的内存固定为16位/2字节
const typedArr = new Int16Array([4, 1, 4, 4, 1]);
类型数组有多种类型,以下是各种类型数组对应的构造函数、数据位数和数据类型:
Uint8Array 8 无符号8位整数
Uint16Array 16 无符号16位整数
Uint32Array 32 无符号32位整数
Int8Array 8 有符号8位整数
Int16Array 16 有符号16位整数
Int32Array 32 有符号32位整数
Float32Array 32 32位浮点数
Float64Array 64 64位浮点数
BigInt64Array 64 无符号64位整数
BigUint64Array 64 有符号64位整数
属性和方法
类型数组拥有如下属性和方法:
// 创建
const arr = new Int16Array([4, 1, 4, 4, 1]);
// 属性
arr.BYTES_PER_ELEMENT; // 单个数据占字节数,返回2
arr.byteLength; // 数组占字节数,返回10
arr.length; // 数据个数,返回5
// 方法
arr.get(1); // 获取index为1的数据,返回4
arr.set(1, 5); // 将index为1的数据设置为5
几何体
介绍
点动成线,线动成面,面动成体,且只需 3 个顶点就能确定一个平面(三角面),所以顶点是构成几何体的基本元素,三角面是构成几何体的直接元素。将几何体的材质的wireframe属性设置为true,以只显示线框轮廓,可以看出,即使是表面看似圆滑的球体,实际上也是由许多个三角面构成的:
const mesh = new Three.Mesh(
new Three.SphereGeometry(100),
new Three.MeshBasicMaterial({
color: 'red',
wireframe: true, // 仅显示线框轮廓
})
);
顶点属性:位置
以正方体为例,对其构成进行分解:1 个正方体由 6 个正方形构成,1 个正方形由 2 个三角面构成,1 个三角面由 3 个顶点构成,所以 1 个正方体可以被分解成6 * 2 * 3 = 36个顶点。我们试着基于最底层的顶点构建一个正方形:
// 创建一个空的(无顶点的)缓冲几何体
const geometry = new Three.BufferGeometry();
// 1个正方形由2个三角面,即6个顶点构成,
// 每个顶点的位置用[x,y,z]表示
const vertice1 = [0, 0, 0];
const vertice2 = [50, 0, 0];
const vertice3 = [0, 50, 0];
const vertice4 = [50, 0, 0];
const vertice5 = [0, 50, 0];
const vertice6 = [50, 50, 0];
// 将顶点的位置数据包装为类型数组
const vertices = new Float32Array([
...vertice1,
...vertice2,
...vertice3,
...vertice4,
...vertice5,
...vertice6,
]);
// 基于vertices创建缓冲属性,
// 传参数3表示3个数字为一组(x,y,z),
// 创建完成后赋值给几何体的位置属性
const position = new Three.BufferAttribute(vertices, 3);
geometry.attributes.position = position;
// 创建材质
const material = new Three.MeshBasicMaterial({
color: 'red',
side: Three.DoubleSide,
wireframe: true,
});
// 基于几何体和材质创建网格模型
const mesh = new Three.Mesh(geometry, material);
顶点属性:颜色和法线
顶点不仅具有位置属性(geometry.attributes.position),还具有颜色属性(geometry.attributes.color)和法线属性(geometry.attributes.normal)。
// 创建一个缓冲几何体
const geometry = new Three.BufferGeometry();
// 下文为了方便阅览,利用解构赋值创建类型数组
// 位置属性,格式:[x,y,z]
geometry.attributes.position = new Three.BufferAttribute(
new Float32Array([
...[0, 0, 0],
...[0, 50, 0],
...[50, 0, 0],
...[50, 0, 0],
...[0, 50, 0],
...[50, 50, 0],
]),
3
);
// 颜色属性,格式:[r,g,b],
// 顶点之间的颜色将采用插值处理
geometry.attributes.color = new Three.BufferAttribute(
new Float32Array([
...[1, 0, 0],
...[0, 1, 0],
...[0, 0, 1],
...[1, 1, 0],
...[0, 1, 1],
...[1, 0, 1],
]),
3
);
// 法线(方向)属性,格式:[x,y,z],
// 法线用于计算光线与网格模型表面的入射角和出射角
geometry.attributes.normal = new Three.BufferAttribute(
new Float32Array([
...[0, 0, 1],
...[0, 0, 1],
...[0, 0, 1],
...[0, 0, 1],
...[0, 0, 1],
...[0, 0, 1],
]),
3
);
// 顶点的位置数据、颜色数据和法线数据一一对应,
// 如第2个顶点的属性为:
// 位置 - [0,50,0],
// 颜色 - [0,1,0],
// 法线 - [0,0,1]
// 创建材质和网格模型
const material = new Three.MeshBasicMaterial({
vertexColors: Three.VertexColors, // 以顶点颜色为准
side: Three.DoubleSide,
});
const mesh = new Three.Mesh(geometry, material);
顶点索引
可以发现,确定 1 个正方形,实际上只需要 4 个顶点而非 6 个顶点,因为可以利用索引复用其中的 2 个顶点:
const geometry = new Three.BufferGeometry();
geometry.attributes.position = new Three.BufferAttribute(
new Float32Array([
...[0, 0, 0],
...[0, 50, 0],
...[50, 0, 0],
...[50, 50, 0],
]),
3
);
// 数字表示顶点的索引,如数字1出现了2次,
// 表示索引为1的顶点,即第2个顶点,被复用了,
// 数字2同理(表示索引为2的顶点,即第3个顶点)
geometry.index = new Three.BufferAttribute(
new Uint16Array([0, 1, 2, 2, 1, 3]),
1
);
封装
几何体Geometry是对缓冲几何体BufferGeometry的封装,其基于Three.Vector3和Three.Color存储几何体相关的属性,简化了许多操作:
// 创建几何体
const geometry = new Three.Geometry();
// 创建和添加顶点位置数据
const vertice1 = new Three.Vector3(50, 50, 0);
const vertice2 = new Three.Vector3(0, 50, 50);
const vertice3 = new Three.Vector3(50, 0, 50);
geometry.vertices.push(vertice1, vertice2, vertice3);
// 创建和添加顶点颜色数据
const color1 = new Three.Color('red');
const color2 = new Three.Color('green');
const color3 = new Three.Color('blue');
geometry.colors.push(color1, color2, color3);
// 创建和添加顶点法线数据
const normal1 = new Three.Vector3(0, 0, 50);
const normal2 = new Three.Vector3(50, 0, 0);
const normal3 = new Three.Vector3(0, 50, 0);
geometry.normals.push(normal1, normal2, normal3);
可通过设置几何体的face属性来组织几何体:
const geometry = new Three.Geometry();
const vertice1 = new Three.Vector3(0, 0, 0);
const vertice2 = new Three.Vector3(0, 50, 0);
const vertice3 = new Three.Vector3(50, 0, 0);
const vertice4 = new Three.Vector3(0, 0, 50);
geometry.vertices.push(vertice1, vertice2, vertice3, vertice4);
// 创建三角面1,该三角面包含索引为0、1、2的顶点,
// 可以直接设置该三角面的颜色属性和法线属性,face2同理
const face1 = new Three.Face3(0, 1, 2);
face1.color = new Three.Color('red');
face1.normal = new Three.Vector3(0, 0, 1);
// 创建三角面2,该三角面包含索引为0、2、3的顶点
const face2 = new Three.Face3(0, 2, 3);
face2.color = new Three.Color('green');
face2.normal = new Three.Vector3(0, 1, 0);
// 将2个三角面添加到几何体中
geometry.faces.push(face1, face2);
几何体的类型繁多,如立方几何体BoxGeometry和球体几何体SphereGeometry,它们都基于几何体Geometry封装而来。详情请见 three.js 的 API 文档。
属性和操作
缓冲几何体和几何体分别拥有如下属性:
// BufferGeometry
bufferGeometry
.attributes
.position : Three.BufferAttribute
.color : Three.BufferAttribute
.normal : Three.BufferAttribute
.uv # 纹理贴图UV坐标
.uv2
.index : Three.BufferAttribute
// Geometry
geometry
.vertices : [Three.Vector3]
.colors : [Three.Color]
.normals : [Three.Vector3]
.faces : [Three.Face3]
.a : Number # 第1个顶点的索引
.b : Number # 第2个顶点的索引
.c : Number # 第3个顶点的索引
.color : Three.Color # 三角面的颜色
.vertexColors : [Three.Color] # 各顶点的颜色
.normal : Three.Vector3 # 三角面的法线
.vertexNormals : [Three.Vector3] # 各顶点的法线
缓冲几何体和几何体均拥有如下操作:
bufferGeometry/geometry
.scale(2, 2, 2); // 缩放
.translate(50, 0, 0); // 平移
.rotateX(Math.PI / 4); // 旋转(绕X轴)
.rotateY(Math.PI / 4); // 旋转(绕Y轴)
.rotateZ(Math.PI / 4); // 旋转(绕Z轴)
.center(); // 偏移
材质
介绍
几何体决定网格模型的形状,而材质决定网格模型的表面纹理特征。
类型
材质有以下几种类型:
// 1.点材质
// 1.1 PointsMaterial
const point = new Three.Points(
geometry,
new Three.PointsMaterial({
color: 0x0000ff, // 点的颜色
size: 3, // 点的尺寸
})
);
// 2.线材质
// 2.1 LineBasicMaterial:实线
const line = new Three.Line(
geometry,
new Three.LineBasicMaterial({
color: 0x0000ff, // 线的颜色
})
);
// 2.2 LineDashedMaterial:虚线
const line = new Three.Line(
geometry,
new Three.LineDashedMaterial({
color: 0x0000ff, // 线的颜色
dashSize: 10, // 线的尺寸
gapSize: 5, // 线隙的尺寸
})
);
// 3.网格材质
// 3.1 MeshBasicMaterial:不参与光源相关的计算
const mesh = new Three.Mesh(
geometry,
new Three.MeshBasicMaterial({
color: 0x0000ff, // 网格的颜色
})
);
// 3.2 MeshLambertMaterial:参与光源的漫反射计算
const mesh = new Three.Mesh(
geometry,
new Three.MeshLambertMaterial({
color: 0x00ff00, // 网格的颜色
})
);
// 3.3 MeshPhongMaterial:参与光源的镜面反射和漫反射计算
const mesh = new Three.Mesh(
geometry,
new Three.MeshPhongMaterial({
color: 0xff0000, // 网格的颜色
specular: 0x444444, // 高光的颜色
shininess: 20, // 高光的亮度
})
);
// 4.精灵材质
// 5.自定义材质
属性
材质拥有以下公有属性:
// 1.side,表示渲染哪一面,取值可能为:
// Three.FrontSide - 显示前面,
// Three.BackSide - 显示背面,
// Three.DoubleSize - 显示双面
const material = new Three.MeshBasicMaterial({
side: Three.DoubleSide,
});
// 2.opacity,表示不透明度,取值范围0~1,
// 在transparent为true时才有效
const material = new Three.MeshBasicMaterial({
transparent: true,
side: 0.5,
});
网格模型
介绍
网格模型基于几何体和材质创建而成,它决定了顶点联结和渲染的方式。
类型
网格模型有以下几种类型:
// 1.点模型:每个顶点都被渲染成一个方形
// 1.1 Points
const point = new Three.Points(geometry, material);
// 2.线模型:顶点两两连接,被渲染成线条
// 2.1 Line:1→2→3→4
const line = new Three.Line(geometry, material);
// 2.2 LineLoop:1→2→3→4→1
const line = new Three.LineLoop(geometry, material);
// 2.3 LineSegments:1→2,3→4
const line = new Three.LineSegements(geometry, material);
// 3.网格模型:每三个顶点被渲染成一个三角面,三角面拼接形成网格
const mesh = new Three.Mesh(geometry, material);
属性和操作
// 缩放
mesh.scale.set(0.5, 1.5, 2);
mesh.scale.x = 0.5;
// 移位
mesh.position.set(80, 20, 100);
mesh.position.y = 20;
// 平移
const axis = new Three.Vector3(1, 1, 1);
mesh.translateOnAxis(axis.normalize(), 50);
mesh.translateX(30);
// 旋转
mesh.rotation.set(1, 2, 3);
const axis = new Three.Vector3(1, 1, 1);
mesh.rotateOnAxis(axis.normalize(), 50);
mesh.rotateX(Math.PI / 4);
光源
介绍
光源是对自然界光照的模拟。为了更好的渲染场景,往往需要配置一定的光源,如光源的类型、位置、角度和强度等。
类型
光源有以下几种类型:
// 1.环境光:没有特定方向的光源,用于均匀改变模型表面的明暗效果
const ambientLight = new Three.AmbientLight('white', 1);
// 2.点光源:位于某一位置,光线向四周均匀发散的光源
const pointLight = new Three.PointLight('white', 1);
pointLight.position.set(100, 100, 100);
// 3.聚光源:位于某一位置,从某一方向向另一方向,发散照射的光源
const spotLight = new Three.SpotLight('white', 1);
spotLight.position.set(80, 100, 50);
spotLight.target = mesh;
spotLight.angle = Math.PI / 6;
// 4.平行光:位于无限远处,从某一方向向另一方向,平行照射的光源
const directionalLight = new Three.DirectionalLight('white', 1);
directionalLight.position.set(80, 100, 50);
directionalLight.target = mesh;
辅助对象
类似于坐标辅助对象AxisHelper,光源也有自己的辅助对象,用于显示光源的位置和方向。光源辅助对象包含PointLightHelper、DirectionalLightHelper和SpotLightHelper等几类。
const pointLightHelper = new Three.PointLightHelper(pointLight, 100);
阴影
// 场景
const scene = new Three.Scene();
// 模型
// 立方体
const boxMesh = new Three.Mesh(
new Three.BoxGeometry(90, 90, 90),
new Three.MeshLambertMaterial({
color: 'red',
})
);
boxMesh.position.set(0, 45, 0);
boxMesh.castShadow = true; // 产生投影
// 平面
const planeMesh = new Three.Mesh(
new Three.PlaneGeometry(200, 200),
new Three.MeshLambertMaterial({
color: 'green',
})
);
planeMesh.rotation.set(-Math.PI / 2, 0, 0);
planeMesh.receiveShadow = true; // 接收投影
scene.add(boxMesh, planeMesh);
// 光源
const light = new Three.PointLight('white', 2);
light.position.set(90, 135, -90);
light.castShadow = true;
scene.add(light);
// 相机
const { innerWidth, innerHeight } = window;
const camera = new Three.PerspectiveCamera(45, innerWidth / innerHeight);
camera.position.set(200, 200, 200);
camera.lookAt(scene.position);
// 轨道控制器
const orbitControl = new Three.OrbitControls(camera);
// 渲染器
const renderer = new Three.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor('lightblue', 1);
renderer.shadowMap.enabled = true; // 开启投影
function render() {
renderer.render(scene, camera);
boxMesh.rotateY(0.01);
requestAnimationFrame(render);
}
render();
// 将渲染结果添加到页面中
document.querySelector('#container').appendChild(renderer.domElement);
相机
介绍
投影有透视投影和正交投影2 类,若有两个一样大小的盒子,在透视投影中,远处的盒子会小于近处的盒子,类似人眼,有近大远小的效果;而在正交投影中,远处的盒子和近处的盒子一样大,类似数学课上黑板上的立方体模型,各棱等长。而在 three.js 中,对应不同类型的投影,有着对应类型的相机。
类型
相机有以下几种类型:
// 获取页面宽高和宽高比
const { innerWidth, innerHeight } = window;
const s = innerWidth / innerHeight;
// 1.PerspectiveCamera:透视投影相机
const camera = new Three.PerspectiveCamera(
60, // 视角范围
s, // 渲染长宽比
1, // 最近渲染距离
1000 // 最远渲染距离
);
// 2.OrthographicCamera:正交投影相机
const k = 250; // 显示范围的控制系数,系数越大,显示的范围越大
const camera = new Three.OrthographicCamera(
-k * s, // 渲染空间的左边界
k * s, // 渲染空间的右边界
k, // 渲染空间的上边界
-k, // 渲染空间的下边界
1, // 最近渲染距离
1000 //最远渲染距离
);
// 设置相机
camera.position.set(500, 500, 500);
camera.lookAt(scene.position);