Three.js
0x01 概述
(1)关于
- 相关网站
- 官网:https://threejs.org
- GitHub 仓库:https://github.com/mrdoob/three.js
- Three.js 是一个 JavaScript 3D 库,用于创建一个易于使用、轻量级、跨浏览器、通用的 3D 库
以下内容使用 Three@0.148.0
(2)安装
a. CDN
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@<version>/examples/jsm/"
}
}
</script>
b. Node.js
-
使用命令
npm install three@<version> --save
安装指定版本的 Three.js -
在入口 JavaScript 文件中导入 Three.js
import * as THREE from 'three';
c. Vue 3
-
使用命令
npm create vite@latest
并选择安装 Vue + JavaScript -
使用命令
npm install -S three@<version>
安装指定版本的 Three.js -
在页面中导入并使用 Three.js
<script setup> import * as THREE from "three"; </script> <template></template> <style scoped></style>
d. React 18
-
使用命令
npx create-react-app <project-name>
创建 React 应用 -
使用命令
npm install -S three@<version>
安装指定版本的 Three.js -
在页面中导入并使用 Three.js
import * as THREE from "three"; function App() { return <div></div>; } export default App;
0x02 基本概念
(1)场景 Scene
-
“场景能够让你在什么地方、摆放什么东西来交给three.js来渲染,这是你放置物体、灯光和摄像机的地方”
-
Three.js 提供场景相关的 API
构造器 含义 官方文档 Fog( color : Integer, near : Float, far : Float )
线性雾 https://threejs.org/docs/#api/zh/scenes/Fog FogExp2( color : Integer, density : Float )
指数雾 https://threejs.org/docs/#api/zh/scenes/FogExp2 Scene()
通用场景 https://threejs.org/docs/#api/zh/scenes/Scene -
创建场景
import * as THREE from 'three'; const scene = new THREE.Scene();
a. 几何体 Geometry
-
Three.js 提供几何体相关的 API
构造器 含义 官方文档 BoxGeometry(width : Float, height : Float, depth : Float, widthSegments : Integer, heightSegments : Integer, depthSegments : Integer)
长方体 https://threejs.org/docs/#api/zh/geometries/BoxGeometry CapsuleGeometry(radius : Float, length : Float, capSegments : Integer, radialSegments : Integer)
胶囊体 https://threejs.org/docs/#api/zh/geometries/CapsuleGeometry CircleGeometry(radius : Float, segments : Integer, thetaStart : Float, thetaLength : Float)
圆 https://threejs.org/docs/#api/zh/geometries/CircleGeometry ConeGeometry(radius : Float, height : Float, radialSegments : Integer, heightSegments : Integer, openEnded : Boolean, thetaStart : Float, thetaLength : Float)
圆锥体 https://threejs.org/docs/#api/zh/geometries/ConeGeometry CylinderGeometry(radiusTop : Float, radiusBottom : Float, height : Float, radialSegments : Integer, heightSegments : Integer, openEnded : Boolean, thetaStart : Float, thetaLength : Float)
圆柱体 https://threejs.org/docs/#api/zh/geometries/CylinderGeometry DodecahedronGeometry(radius : Float, detail : Integer)
十二面体 https://threejs.org/docs/#api/zh/geometries/DodecahedronGeometry EdgesGeometry( geometry : [BufferGeometry](https://threejs.org/docs/index.html#api/zh/core/BufferGeometry), thresholdAngle : Integer )
边缘 https://threejs.org/docs/#api/zh/geometries/EdgesGeometry ExtrudeGeometry(shapes : Array, options : Object)
挤压 https://threejs.org/docs/#api/zh/geometries/ExtrudeGeometry IcosahedronGeometry(radius : Float, detail : Integer)
二十面体 https://threejs.org/docs/#api/zh/geometries/IcosahedronGeometry LatheGeometry(points : Array, segments : Integer, phiStart : Float, phiLength : Float)
车床 https://threejs.org/docs/#api/zh/geometries/LatheGeometry OctahedronGeometry(radius : Float, detail : Integer)
八面体 https://threejs.org/docs/#api/zh/geometries/OctahedronGeometry PlaneGeometry(width : Float, height : Float, widthSegments : Integer, heightSegments : Integer)
平面 https://threejs.org/docs/#api/zh/geometries/PlaneGeometry PolyhedronGeometry(vertices : Array, indices : Array, radius : Float, detail : Integer)
多面体 https://threejs.org/docs/#api/zh/geometries/PolyhedronGeometry RingGeometry(innerRadius : Float, outerRadius : Float, thetaSegments : Integer, phiSegments : Integer, thetaStart : Float, thetaLength : Float)
圆环形 https://threejs.org/docs/#api/zh/geometries/RingGeometry ShapeGeometry(shapes : Array, curveSegments : Integer)
单面多边形 https://threejs.org/docs/#api/zh/geometries/ShapeGeometry SphereGeometry(radius : Float, widthSegments : Integer, heightSegments : Integer, phiStart : Float, phiLength : Float, thetaStart : Float, thetaLength : Float)
球体 https://threejs.org/docs/#api/zh/geometries/SphereGeometry TetrahedronGeometry(radius : Float, detail : Integer)
四面体 https://threejs.org/docs/#api/zh/geometries/TetrahedronGeometry TorusGeometry(radius : Float, tube : Float, radialSegments : Integer, tubularSegments : Integer, arc : Float)
圆环体 https://threejs.org/docs/#api/zh/geometries/TorusGeometry TorusKnotGeometry(radius : Float, tube : Float, tubularSegments : Integer, radialSegments : Integer, p : Integer, q : Integer)
圆环扭结体 https://threejs.org/docs/#api/zh/geometries/TorusKnotGeometry TubeGeometry(path : Curve, tubularSegments : Integer, radius : Float, radialSegments : Integer, closed : Boolean)
管道体 https://threejs.org/docs/#api/zh/geometries/TubeGeometry WireframeGeometry(geometry : BufferGeometry)
网格 https://threejs.org/docs/#api/zh/geometries/WireframeGeometry -
使用长方几何体
const geometry = new THREE.BoxGeometry(50, 50, 50);
b. 材质 Material
-
Three.js 提供材质相关的 API
-
使用基础网格材质
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
c. 物体 Objects
-
Three.js 提供物体相关的 API
构造器 含义 官方文档 BatchedMesh( maxInstanceCount : Integer, maxVertexCount : Integer, maxIndexCount : Integer, material : Material, )
批处理网格 https://threejs.org/docs/#api/zh/objects/BatchedMesh Bone( )
骨骼 https://threejs.org/docs/#api/zh/objects/Bone Group( )
组 https://threejs.org/docs/#api/zh/objects/Group InstancedMesh( geometry : BufferGeometry, material : Material, count : Integer )
实例化网格 https://threejs.org/docs/#api/zh/objects/InstancedMesh Line( geometry : BufferGeometry, material : Material )
线 https://threejs.org/docs/#api/zh/objects/Line LineLoop( geometry : BufferGeometry, material : Material )
环线 https://threejs.org/docs/#api/zh/objects/LineLoop LineSegments( geometry : BufferGeometry material : Material )
线段 https://threejs.org/docs/#api/zh/objects/LineSegments LOD( )
多细节层次 https://threejs.org/docs/#api/zh/objects/LOD Mesh( geometry : BufferGeometry, material : Material )
网格 https://threejs.org/docs/#api/zh/objects/Mesh Points( geometry : BufferGeometry, material : Material )
点集 https://threejs.org/docs/#api/zh/objects/Points Skeleton( bones : Array, boneInverses : Array )
骨架 https://threejs.org/docs/#api/zh/objects/Skeleton SkinnedMesh( geometry : BufferGeometry, material : Material )
蒙皮网格 https://threejs.org/docs/#api/zh/objects/SkinnedMesh Sprite( material : Material )
精灵 https://threejs.org/docs/#api/zh/objects/Sprite -
使用网格物体
const geometry = new THREE.BoxGeometry(50, 50, 50); const material = new THREE.MeshBasicMaterial({ color: 0xff0000 }); const mesh = new THREE.Mesh(geometry, material);
-
将物体添加到场景中
scene.add(mesh);
d. 三维坐标系 AxesHelper
-
Three.js 支持通过
AxesHelper
导入三维坐标系到页面中const axesHelper = new THREE.AxesHelper(150); scene.add(axesHelper);
-
设置物体的材质为半透明
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, // 允许透明 opacity: 0.5, // 透明度 });
-
-
导入的三维坐标系中,颜色与轴对应关系如下:
红色:X 轴
绿色:Y 轴
蓝色:Z 轴
e. 光源 Light
-
Three.js 提供的部分材质受光照影响,如:
graph LR 网格材质-->A[不受光照影响] & B[受光照影响] A-->MeshBasicMaterial B-->漫反射 & 高光 & 物理 漫反射-->MeshLambertMaterial 高光-->MeshPhongMaterial 物理-->MeshStandardMaterial & MeshPhysicalMaterial -
Three.js 提供光源相关的 API
构造器 含义 官方文档 AmbientLight( color : Color, intensity : Float )
环境光 https://threejs.org/docs/#api/zh/lights/AmbientLight DirectionalLight( color : Color, intensity : Float )
平行光 https://threejs.org/docs/#api/zh/lights/DirectionalLight HemisphereLight( skyColor : Integer, groundColor : Integer, intensity : Float )
半球光 https://threejs.org/docs/#api/zh/lights/HemisphereLight Light( color : Color, intensity : Float )
光源基类 https://threejs.org/docs/#api/zh/lights/Light LightProbe( sh : SphericalHarmonics3, intensity : Float )
光照探针 https://threejs.org/docs/#api/zh/lights/LightProbe PointLight( color : Color, intensity : Float, distance : Number, decay : Float )
点光源 https://threejs.org/docs/#api/zh/lights/PointLight RectAreaLight( color : Color, intensity : Float, width : Float, height : Float )
平面光光源 https://threejs.org/docs/#api/zh/lights/RectAreaLight SpotLight( color : Color, intensity : Float, distance : Float, angle : Radians, penumbra : Float, decay : Float )
聚光灯 https://threejs.org/docs/#api/zh/lights/SpotLight -
使用环境光(改变场景整体的明暗)
const ambient = new THREE.AmbientLight(0xffffff, 0.2); scene.add(ambient);
-
使用点光源结合
MeshLambertMaterial
漫反射材质const material = new THREE.MeshLambertMaterial({ color: 0xff0000, }); const pointLight = new THREE.PointLight(0xffffff); pointLight.intensity = 1.0; // 光照强度 pointLight.decay = 0.0; // 光照衰减 pointLight.position.set(150, 100, 50); // 光源位置 scene.add(pointLight); // 添加光源 // ...
-
通过使用
PointLightHelper
对点光源可视化const pointLightHelper = new THREE.PointLightHelper(pointLight, 10); scene.add(pointLightHelper);
-
-
使用平行光结合
MeshLambertMaterial
漫反射材质const directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(-50, -100, -150); // 光源位置 directionalLight.target = mesh; // 光源指向目标对象 scene.add(directionalLight);
-
通过使用
DirectionalLightHelper
对平行光源可视化const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 10); scene.add(directionalLightHelper);
-
(2)相机 Camera
-
Three.js 提供相机相关的 API
构造器 含义 官方文档 ArrayCamera( array : Array )
相机阵列 https://threejs.org/docs/#api/zh/cameras/ArrayCamera Camera()
通用相机 https://threejs.org/docs/#api/zh/cameras/Camera CubeCamera( near : Number, far : Number, renderTarget : WebGLCubeRenderTarget )
立方相机 https://threejs.org/docs/#api/zh/cameras/CubeCamera OrthographicCamera( left : Number, right : Number, top : Number, bottom : Number, near : Number, far : Number )
正交相机 https://threejs.org/docs/#api/zh/cameras/OrthographicCamera PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
透视相机 https://threejs.org/docs/#api/zh/cameras/PerspectiveCamera StereoCamera( )
立体相机 https://threejs.org/docs/#api/zh/cameras/StereoCamera -
使用透视相机
// 相机画布尺寸(px) const width = window.innerWidth; const height = window.innerHeight; document.body.style.margin = "0"; const camera = new THREE.PerspectiveCamera(30, width / height, 0.1, 3000); camera.position.set(200, 200, 200); // 位置 camera.lookAt(0, 0, 0); // 视线
- 透视相机的四个参数构成一个视锥体,仅视锥体内的物体可以被“看到”
a. 画布尺寸与布局
-
使用 Three.js 创建的图像作为
<canvas />
嵌入页面中 -
全浏览器显示
const width = window.innerWidth; const height = window.innerHeight; document.body.style.margin = "0"; document.body.style.overflow = "hidden"; // ... renderer.setSize(width, height);
-
动态填充
window.onresize = () => { renderer.setSize(window.innerWidth, window.innerHeight); // 重置画布尺寸 camera.aspect = window.innerWidth / window.innerHeight; // 重置相机观察范围比例 camera.updateProjectionMatrix(); // 更新相机投影矩阵 };
b. 轨道控制器 OrbitControls
-
通过 OrbitControls 可以使用鼠标控制相机
- 左键:切换视角
- 中键:调整远近
- 右键:改变位置
-
原生 HTML 中导入 OrbitControls
-
index.html
<script type="importmap"> { "imports": { "three": "./node_modules/three/build/three.module.js", "three/addons/": "./node_modules/three/examples/jsm/" } } </script>
-
main.js
// ... import { OrbitControls } from "three/addons/controls/OrbitControls.js"; const controls = new OrbitControls(camera, renderer.domElement); // 创建控制器对象 controls.addEventListener("change", () => renderer.render(scene, camera)); // 监听改变事件并重新渲染
-
(3)渲染器 Renderer
-
Three.js 提供渲染器相关的 API
构造器 含义 官方文档 WebGLRenderer( parameters : Object )
WebGL 渲染器 https://threejs.org/docs/#api/zh/renderers/WebGLRenderer WebGLRenderTarget(width : Number, height : Number, options : Object)
渲染缓冲 https://threejs.org/docs/#api/zh/renderers/WebGLRenderTarget WebGL3DRenderTarget( width : Number, height : Number, depth : Number, options : Object )
三维渲染缓冲 https://threejs.org/docs/#api/zh/renderers/WebGL3DRenderTarget WebGLArrayRenderTarget( width : Number, height : Number, depth : Number, options : Object )
纹理组渲染缓冲 https://threejs.org/docs/#api/zh/renderers/WebGLArrayRenderTarget WebGLCubeRenderTarget(size : Number, options : Object)
用于立方相机 https://threejs.org/docs/#api/zh/renderers/WebGLCubeRenderTarget -
使用渲染器
const renderer = new THREE.WebGLRenderer({ logarithmicDepthBuffer: true, // 开启深度缓存,优化深度冲突问题 }); renderer.setSize(width, height); // 设置画布尺寸 renderer.antialias = true; // 开启抗锯齿 renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比 renderer.setClearColor(0x00bbff); // 设置背景色 renderer.setClearAlpha(0.5); // 设置背景半透明 renderer.render(scene, camera); // 渲染 document.body.appendChild(renderer.domElement);
- 深度冲突指渲染器无法分辨两个重合模型的先后顺序,现象是模型闪烁
a. 动画渲染循环效果
-
通过 HTML5 的
requestAnimationFrame
方法实现 -
举例:自旋
// 周期性渲染,按屏幕刷新率执行 function render() { mesh.rotateY(0.01); // 沿 Y 轴旋转 0.01 rad renderer.render(scene, camera); // 渲染 requestAnimationFrame(render); } render();
b. 阵列
-
完成定义几何体及材质后,通过
for
循环批量创建物体,并分配其所在位置 -
举例:立方体阵列
import * as THREE from "three"; const scene = new THREE.Scene(); // 创建场景 const geometry = new THREE.BoxGeometry(10, 10, 10); // 创建立方几何体 const material = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.5, }); // 创建半透明材质 // for 循环构建阵列 for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { const mesh = new THREE.Mesh(geometry, material); mesh.position.set(i * 20, 0, j * 20); scene.add(mesh); } } // 全屏显示 const width = window.innerWidth; const height = window.innerHeight; document.body.style.margin = "0"; document.body.style.overflow = "hidden"; // 调整相机 const camera = new THREE.PerspectiveCamera(30, width / height, 0.1, 3000); camera.position.set(460, 200, 460); camera.lookAt(0, 0, 0); // 渲染 const renderer = new THREE.WebGLRenderer(); renderer.setSize(width, height); renderer.render(scene, camera); document.body.appendChild(renderer.domElement);
c. stats.js
-
stats.js 可以查看 Three.js 当前的渲染性能,即计算渲染帧率(Frames per Second,FPS)
- Three.js 每次执行 WebGL 渲染器的
render()
方法,就会得到一帧图像
- Three.js 每次执行 WebGL 渲染器的
-
导入并使用
//... import Stats from "three/addons/libs/stats.module.js"; const stats = Stats(); document.body.appendChild(stats.domElement); function render() { stats.update(); // 更新统计信息 mesh.rotateY(0.01); renderer.render(scene, camera); requestAnimationFrame(render); } render();
d. dat.gui
-
dat.gui 用于快速创建控制三维场景的 UI 交互界面
-
导入并使用
import * as THREE from "three"; import { GUI } from "three/addons/libs/lil-gui.module.min.js"; // 导入 dat.gui const scene = new THREE.Scene(); const geometry = new THREE.BoxGeometry(50, 50, 50); const material = new THREE.MeshBasicMaterial({ color: 0xff0000, }); const mesh = new THREE.Mesh(geometry, material); mesh.position.set(0, 0, 0); scene.add(mesh); const axesHelper = new THREE.AxesHelper(100); scene.add(axesHelper); const width = window.innerWidth; const height = window.innerHeight; document.body.style.margin = "0"; document.body.style.overflow = "hidden"; const camera = new THREE.PerspectiveCamera(30, width / height, 0.1, 3000); camera.position.set(200, 200, 200); camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer(); renderer.setSize(width, height); renderer.render(scene, camera); document.body.appendChild(renderer.domElement); // 使用 dat.gui const gui = new GUI(); gui.domElement.style.backgroundColor = "#0bf"; // 设置 GUI DOM 元素背景色 const positionFolder = gui.addFolder("位置"); // 创建子菜单 positionFolder.open(); // 默认打开菜单 // 添加控制项 positionFolder .add(mesh.position, "x", -50, 50) // add(目标对象, 属性名, 最小值, 最大值) 拖动条 .name("x 坐标"); // name() 设置名称 positionFolder .add(mesh.position, "y", [-50, -25, 0, 25, 50]) // add(目标对象, 属性名, 枚举值列表) 下拉菜单 .name("y 坐标") .step(2); // step() 设置步长 positionFolder .add(mesh.position, "z", { 小: -50, 中: -25, 大: 0, 大中: 25, 大大: 50, }) // add(目标对象, 属性名, 枚举对象) 下拉菜单(命名) .name("z 坐标") .onChange((value) => console.log(`Z 坐标:${value}`)); // onChange() 监听属性值改变 gui .addColor(material, "color") // 修改材质颜色 .name("颜色"); gui .add(mesh, "visible") // add(目标对象, 属性名) 复选框(布尔值) .name("是否可见"); // 渲染循环 function render() { renderer.render(scene, camera); requestAnimationFrame(render); } render();
0x03 顶点数据
(1)渲染顶点数据
-
缓冲类型几何体
BufferGeometry
用于定义顶点数据BoxGeometry
和SphereGeometry
等几何体均基于BufferGeometry
类构建
-
举例:
import * as THREE from "three"; const geometry = new THREE.BufferGeometry(); // 创建一个空几何体对象 const vertices = new Float32Array([ 0, 0, 0, // 第一个顶点 50, 0, 0, // 第二个顶点 0, 100, 0, // 第三个顶点 0, 0, 10, // 第四个顶点 0, 0, 100, // 第五个顶点 50, 0, 10, // 第六个顶点 ]); // 设置顶点坐标类型化数组 const attribute = new THREE.BufferAttribute(vertices, 3); // 创建顶点属性对象, 3 表示每三个元素为一组, 对应 xyz 三个属性 geometry.attributes.position = attribute; // 将顶点位置属性设置给几何体
a. 点模型渲染顶点数据
-
与
Mesh
模型类似,点模型对象Points
将几何体渲染为点,而Mesh
将几何体渲染为面 -
点模型对象的材质是点材质
PointsMaterial
-
将上一节的顶点数据通过点模型对象进行可视化渲染
import * as THREE from "three"; import { OrbitControls } from "three/addons/controls/OrbitControls.js"; const geometry = new THREE.BufferGeometry(); const vertices = new Float32Array([ 0, 0, 0, 50, 0, 0, 0, 100, 0, 0, 0, 10, 0, 0, 100, 50, 0, 10, ]); const attribute = new THREE.BufferAttribute(vertices, 3); geometry.attributes.position = attribute; const material = new THREE.PointsMaterial({ color: 0xff0000, size: 10.0, // 点的像素尺寸 }); const points = new THREE.Points(geometry, material); const axesHelper = new THREE.AxesHelper(150); const scene = new THREE.Scene(); scene.add(points); scene.add(axesHelper); const width = window.innerWidth; const height = window.innerHeight; document.body.style.margin = "0"; document.body.style.overflow = "hidden"; const camera = new THREE.PerspectiveCamera(30, width / height, 0.1, 3000); camera.position.set(200, 400, 200); camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer(); renderer.setSize(width, height); renderer.render(scene, camera); document.body.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.addEventListener("change", () => renderer.render(scene, camera));
b. 线模型渲染顶点数据
-
相比
Points
模型,线模型对象Line
将顶点连成线 -
使用线模型渲染上述顶点数据
const material = new THREE.LineBasicMaterial({ color: 0xff0000, size: 10.0, // 点的像素尺寸 }); const line = new THREE.Line(geometry, material); scene.add(line);
-
除了上述线模型,Three.js 还提供了其他线模型:
LineLoop
模型构成闭合线条LineSegments
模型构成非连续的线条
c. 网格模型渲染顶点数据
-
网格模型
Mesh
本质上是由多个三角形(面)拼接而成 -
使用网格模型渲染上述顶点数据
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh);
(2)构建平面
绘制平面需要使用 Mesh
模型,而 Mesh
模型根据顶点数据构建三角形面,因此需要通过三角顶点构建所需要的平面,如矩形平面
注意:必须将三角平面的正面(背面)统一
a. 构建矩形平面几何体
-
Mesh
模型构建矩形平面的顶点顺序如下:- 第一个三角平面
- 左下角
- 右下角
- 右上角
- 第二个三角平面
- 左下角
- 右上角
- 左上角
- 第一个三角平面
-
举例:在 \(XOY\) 平面,边长为 50 的正方形
const geometry = new THREE.BufferGeometry(); const vertices = new Float32Array([ 0, 0, 0, 50, 0, 0, 50, 50, 0, 0, 0, 0, 50, 50, 0, 0, 50, 0, ]); geometry.attributes.position = new THREE.BufferAttribute(vertices, 3); const material = new THREE.MeshBasicMaterial({ color: 0xff0000, }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh);
b. 顶点索引
-
顶点索引数据用于移除在构建平面时重复的顶点
- 上述案例中,如 \((0, 0, 0)\)、\((50, 50, 0)\) 两个顶点
-
使用顶点索引
const geometry = new THREE.BufferGeometry(); const indexes = new Uint16Array([0, 1, 2, 0, 2, 3]); const vertices = new Float32Array([ 0, 0, 0, 50, 0, 0, 50, 50, 0, 0, 50, 0 ]); geometry.attributes.position = new THREE.BufferAttribute(vertices, 3); geometry.index = new THREE.BufferAttribute(indexes, 1); // 导入索引并设置每个索引元素代表一个点
c. 顶点法线
-
Three.js 中的法线是广义上的法线,比数学上的法线定义更宽泛
-
对于需要受光照影响的材质,必须引入法线
- 如
MeshLambertMaterial
- 如
-
法线通过顶点定义,每个顶点都有一个法线数据
-
定义顶点法线
// 顶点法线数据与顶点一一对应,支持索引 const normals = new Float32Array([ 0, 0, 1, // 顶点一法线 0, 0, 1, // 顶点二法线 0, 0, 1, // 顶点三法线 0, 0, 1, // 顶点四法线 ]) geometry.attributes.normals = new THREE.BufferAttribute(normals, 3)
(3)几何体变换
-
BufferGeometry
通过一系列 Three.js 提供的 API,基于改变顶点数据,实现对几何体的变换-
缩放:
scale()
-
平移:
translate()
-
\(x\) 轴旋转:
rotateX()
\(y\) 轴旋转:
rotateY()
\(z\) 轴旋转:
rotateZ()
-
居中:
center()
-
-
将几何体 \(x\) 坐标放大 2 倍,\(y\) 坐标缩小一半,\(z\) 坐标不变
geometry.scale(2, 1 / 2, 0);
将几何体沿 \(z\) 轴平移 50
geometry.translate(0, 0, 50);
将几何体沿 \(y\) 轴旋转 45°
geometry.rotateY(Math.PI / 4);
将几何体居中
geometry.center();
0x04 模型对象
(1)三维向量 Vector3
-
Three.js 提供的模型对象,如
Points
、Line
、Mesh
,均继承于Object3D
,而Object3D
中坐标位置position
等属性,均采用Vector3
类型- 构造器:
Vector3( x : Float, y : Float, z : Float )
- 官方文档:https://threejs.org/docs/#api/zh/math/Vector3
- 构造器:
-
三维向量
Vector3
有x
、y
、z
三个分量,可以表示坐标等变量const vector3 = new THREE.Vector3(100, 200, 300); // 声明一个三维向量 console.log(vector3.x, vector3.y, vector3.z); // 获取三维向量的x,y,z分量 vector3.set(200, 300, 400); // 重新设置三维向量的x,y,z分量 console.log(vector3.x, vector3.y, vector3.z);
-
基于三维向量,可以对模型对象进行缩放或平移变换,如将立方体放大 2 倍:
const geometry = new THREE.BoxGeometry(50, 50, 50); const material = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.5, }); const mesh = new THREE.Mesh(geometry, material); mesh.scale.set(2, 2, 2);
(2)欧拉 Euler
-
模型对象的角度属性
rotation
采用Euler
类型- 构造器:
Euler( x : Float, y : Float, z : Float, order : String )
- 官方文档:https://threejs.org/docs/#api/zh/math/Euler
- 构造器:
-
特别地,
order
分量表示旋转顺序的字符串,默认为'XYZ'
(必须是大写) -
基于欧拉,可以对模型对象进行选择变换,如将立方体沿 \(x\) 轴旋转 45°
const mesh = new THREE.Mesh(geometry, material); mesh.rotateX(Math.PI / 4); // 或 // mesh.rotation.x += Math.PI / 4;
(3)颜色 Color
-
模型材质的颜色属性
color
采用Color
类型- 构造器:
Color( r : Color_Hex_or_String, g : Float, b : Float )
- 官方文档:https://threejs.org/docs/#api/zh/math/Color
- 构造器:
-
当分量
g
、b
被定义后,r
表示红色分量,否则为十六进制颜色值const red = new THREE.Color(255, 0, 0); const green = new THREE.Color(0x00ff00); const blue = new THREE.Color("#0000ff");
(4)模型材质属性
MeshBasicMaterial
等材质均继承于Material
- 透明属性:
- 透明开关:
transparent
(布尔类型) - 透明度:
opacity
(浮点类型,\(0\) 表示全透明,\(1\) 表示不透明)
- 透明开关:
- 面属性:
side
(整型 / 枚举)- 前面可见:
THREE.FrontSide
- 背面可见:
THREE.BackSide
- 双面可见:
THREE.DoubleSide
- 前面可见:
(5)克隆与复制
-
克隆
clone()
:获得调用该方法的对象的副本 -
复制
copy()
:将传入的参数覆盖到调用该方法的对象相应的属性 -
举例:
const mesh = new THREE.Mesh(geometry, material); const mesh2 = mesh.clone(); // 克隆 mesh 模型 mesh2.position.set(100, 0, 0); mesh.rotation.x += Math.PI / 4; mesh2.rotation.copy(mesh.rotation); // 复制角度属性 scene.add(mesh); scene.add(mesh2);
0x05 层级模型
(1)组对象 Group
-
组对象
Group
继承于Object3D
,用于将模型对象进行分组管理官方文档:https://threejs.org/docs/#api/zh/objects/Group
const group = new THREE.Group(); // 声明一个组对象 group.add(mesh); // 向组中添加模型对象 group.add(mesh2); scene.add(group); // 将组作为子场景加入场景中
-
Mesh
等模型对象也可以通过add()
方法添加子对象,如为子对象添加坐标系const axesHelper = new THREE.AxesHelper(50); mesh2.add(axesHelper);
-
相对地,
remove()
方法可以移除子对象,如移除group
中的mesh2
:group.remove(mesh2);
-
-
Object3D
提供children
属性来查看其模型树结构const scene = new THREE.Scene(); scene.add(group); scene.add(axesHelper); console.log(scene.children);
flowchart TB Scene-->Group & AxesHelper Group-->1[Mesh] & 2[Mesh]
(2)模型树结构
-
模型树结构由层级模型节点构成模型树,每个层级模型节点均可进行单独操作
- 类似 DOM 树
-
命名:
name
const group = new THREE.Group(); group.name = "立方体组"; mesh.name = "立方体 A"; mesh2.name = "立方体 B"; group.add(mesh); group.add(mesh2);
-
递归:
traverse()
scene.traverse((item) => console.log(item.name, item));
-
查找:
如按模型名称查找:getObjectByName()
console.log(scene.getObjectByName("立方体 A"));
(3)坐标
-
Three.js 中,坐标分为本地(局部)坐标、世界坐标
- 本地(局部)坐标:模型自身的
position
属性值 - 世界坐标:模型自身及其所有父模型的
position
属性值之和
- 本地(局部)坐标:模型自身的
-
现象:当改变对象的
position
属性值时,模型位置发生改变;当改变其父对象的position
属性值时,其位置也会改变 -
获取模型对象坐标
scene.position.set(50, 50, 50); scene.add(mesh); const worldPosition = new THREE.Vector3(); mesh.getWorldPosition(worldPosition); console.log("世界坐标", worldPosition.toArray()); // [50, 50, 50] console.log("本地坐标", mesh.position.toArray()); // [0, 0, 0]
(4)模型显隐
-
Object3D
封装了visible
属性,用于控制模型对象的显隐,其值为布尔类型mesh2.visible = false;
-
Material
等也支持通过visible
属性控制显隐
0x06 纹理贴图
(1)纹理贴图
-
纹理贴图:将一张图片作为纹理对象
Texture
导入到材质并映射到模型中 -
创建纹理贴图
const textureLoader = new THREE.TextureLoader(); // 声明一个纹理贴图加载器 const texture = textureLoader.load("./public/image.jpg"); // 加载图像并返回纹理对象 const geometry = new THREE.PlaneGeometry(192, 108); const material = new THREE.MeshBasicMaterial({ map: texture, // 导入纹理对象 });
(2)顶点 UV 坐标
-
顶点 UV 坐标用于从纹理贴图上提取像素映射到模型的几何体表面上
-
获取几何体的顶点 UV 坐标
console.log(geometry.attributes.uv);
-
-
顶点 UV 坐标的 \(u\)、\(v\) 取值范围均为 \([0, 1]\),其中左下角是 \((0, 0)\),右上角是 \((1, 1)\)
- 顶点 UV 坐标系相当于二维坐标系,\(u\) 是 \(x\) 轴,\(v\) 是 \(y\) 轴
-
自定义顶点 UV 坐标
const geometry = new THREE.PlaneGeometry(192, 108); const uvs = new Float32Array([ 0, 0, // 左下角 1, 0, // 右下角 1, 1, // 右上角 0, 1, // 左上角 ]); geometry.attributes.uv = new THREE.BufferAttribute(uvs, 2);
(3)圆形平面设置纹理贴图
-
用于将矩形图片裁剪为圆形并渲染
const textureLoader = new THREE.TextureLoader(); const texture = textureLoader.load("./public/image.jpg"); const geometry = new THREE.CircleGeometry(60, 100); const material = new THREE.MeshBasicMaterial({ map: texture, });
-
本质上,圆形平面的顶点 UV 坐标就是一个圆形,从而实现对图片进行圆形裁剪
(4)阵列
纹理对象 Texture
提供了阵列功能,用于在几何体内重复纹理,而非全填充
const texture = textureLoader.load("./public/image.jpg");
texture.wrapS = THREE.RepeatWrapping; // 水平方向重复
texture.wrapT = THREE.RepeatWrapping; // 垂直方向重复
texture.repeat.set(10, 5); // 水平方向重复 10 次, 垂直方向重复 5 次
(5)背景透明图片纹理
-
常用于场景标注
-
在导入透明 PNG 纹理贴图的同时开启透明
const material = new THREE.MeshBasicMaterial({ map: texture, transparent: true, });
(6)UV 动画
-
UV 动画基于纹理对象
Texture
的偏移属性offset
以及阵列功能,通过循环渲染实现texture.offset.x += 0.5; // U 轴正方向偏移 texture.offset.y += 0.5; // V 轴正方向偏移
-
举例:
// 导入贴图 const texture = textureLoader.load("./public/image.png"); // 设置阵列 texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; texture.repeat.set(10, 1); // ... // 循环渲染 function render() { texture.offset.x += 0.05; // 偏移 renderer.render(scene, camera); requestAnimationFrame(render); } render();
0x07 模型拾取
(1)坐标归一化
-
Three.js 的坐标系原点在屏幕正中央,而浏览器页面的坐标系原点在屏幕的左上角
-
由于原点不同,同一位置的坐标值存在差异,需要归一化
const pointer = new THREE.Vector2(); window.addEventListener("pointermove", (event) => { pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = -(event.clientY / window.innerHeight) * 2 + 1; });
(2)射线与光线投射
-
Three.js 提供射线
Ray
类型,需要指定原点和方向- 构造函数:
Ray( origin : Vector3, direction : Vector3 )
- 官方文档:https://threejs.org/docs/#api/zh/math/Ray
const ray = new THREE.Ray( new THREE.Vector3(0, 0, 0), new THREE.Vector3(1, 0, 0) );
- 构造函数:
-
光线投射
Raycaster
用于进行鼠标拾取,即在三维空间中计算出鼠标移过了什么物体// 坐标归一化 const raycaster = new THREE.Raycaster(); function render() { raycaster.setFromCamera(pointer, camera); // 通过摄像机和鼠标位置更新射线 const intersects = raycaster.intersectObjects(scene.children); // 计算物体和射线的交点 for (let i = 0; i < intersects.length; i++) { intersects[i].object.material.color.set(0xff0000); } renderer.render(scene, camera); requestAnimationFrame(render); } render();
(3)点击交互
-
坐标归一化
const pointer = new THREE.Vector2(); window.addEventListener("click", (event) => { pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = -(event.clientY / window.innerHeight) * 2 + 1; });
-
计算射线
const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(pointer, camera);
-
计算交点
const intersects = raycaster.intersectObjects(scene.children); if (intersects.length > 0) { intersects[0].object.material.color.set(0xff0000); }
0x08 导入外部三维模型
- 对于复杂的三维模型,仅通过 Three.js 提供的 API 很难实现,需要依赖其他专业三维建模工具实现,并导入到 Three.js 中渲染至 HTML 页面
- 常见三维建模软件工具包括:
(1)加载模型
-
Three.js 可以加载 GLTF 格式的三维模型数据
- GLTF 全称 GL Transmission Format,是基于 Web 端通用的 JSON 格式与二进制格式数据
- .bin 文件以二进制形式存储模型的顶点数据等
- .glb 文件是 GLTF 格式的二进制文件,包含模型和贴图信息
- GLTF 2.0 于 2017 年发布
- GLTF 全称 GL Transmission Format,是基于 Web 端通用的 JSON 格式与二进制格式数据
-
借助
GLTFLoader
实现 GLTF 格式模型导入import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; // ... const loader = new GLTFLoader(); loader.load("./public/scene.gltf", (gltf) => scene.add(gltf.scene));
- GLB 格式的模型也可以用上述方法导入
-
通过
OrbitControls
调整相机至合适的位置const controls = new OrbitControls(camera, renderer.domElement); controls.addEventListener("change", () => { console.log(camera.position); // 调整相机并输出位置参数 renderer.render(scene, camera); });
(2)处理模型
- 对于导入到 Three.js 的三维模型,均采用模型树结构,即可以通过第五章第二节的方法处理模型
- 导入的模型中,一些外观一致的模型一般会共享材质
- 美术:设置模型材质独占
- 开发:批量克隆材质
(3)进度可视
-
当导入的模型较大时,不可避免的需要一定时间加载
-
GLTFLoader
的load()
方法的第三个参数需要一个回调函数,来获取模型的加载信息,实现进度可视loader.load( "./public/scene.gltf", (gltf) => scene.add(gltf.scene), (xhr) => console.log(xhr.loaded / xhr.total) );