Three.js 重点
Three.js 重点
本文基于当前前端 3D 岗位的主流面试经验整理,按照「基础概念 → 相机控制 → 几何材质 → 光照阴影 → 模型加载 → 交互动画 → 性能优化 → 高级底层 → 工程实践」由浅入深排序,每题附带详细答案,覆盖初中高级岗位的高频考点。
一、基础概念篇
1. 什么是 Three.js?它和 WebGL 是什么关系?
答案:
Three.js 是基于 WebGL 的 JavaScript 3D 图形库,封装了 WebGL 复杂的底层 API,提供了一套简洁易用的高层接口,让开发者无需编写复杂的着色器和矩阵运算,就能快速在浏览器中创建 3D 场景、模型、动画和交互效果。
两者关系:
- WebGL 是浏览器提供的底层 3D 绘图标准,直接操作 GPU,开发门槛高、代码量大;
- Three.js 是 WebGL 的封装,内置了场景、相机、渲染器、几何体、材质、光照等完整体系,大幅降低 3D 开发成本;
- Three.js 最终还是通过 WebGL 上下文调用 GPU 进行渲染。
2. Three.js 最核心的三大组件是什么?分别有什么作用?
答案:
三大核心组件是场景(Scene)、相机(Camera)、渲染器(Renderer),三者配合才能把 3D 画面绘制到屏幕上。
- 场景 Scene:3D 世界的容器,所有物体、灯光、相机都要放在场景里,本质是树状的层级结构,支持父子节点变换继承。
- 相机 Camera:定义观察者的视角和可视范围,决定了场景中哪些部分会被渲染到屏幕上,常用透视相机和正交相机。
- 渲染器 Renderer:负责执行渲染指令,把相机拍到的场景画面,最终绘制到 Canvas 画布上,核心是
WebGLRenderer。
可以简单记为:场景装万物,相机定视角,渲染出画面。
3. Three.js 使用什么坐标系?坐标轴方向如何?
答案:
Three.js 使用右手笛卡尔坐标系,默认方向为:
- X 轴:水平向右为正;
- Y 轴:竖直向上为正;
- Z 轴:垂直屏幕向外(朝向观察者)为正。
Y
↑
|
Z ←—— O ——→ X
默认相机朝向负 Z 方向,因此所有物体默认从 -Z 方向被观察。可以通过 AxesHelper 坐标轴辅助线直观查看,红色为 X 轴、绿色为 Y 轴、蓝色为 Z 轴。理解坐标系对于物体变换、相机控制以及光照方向计算至关重要。
4. 什么是场景图(Scene Graph)?父子节点的变换有什么关系?
答案:
场景图是 Three.js 管理物体的层级树状结构,以 Scene 为根节点,所有物体都通过父子关系组织在一起。
变换关系:子节点会继承父节点的位置、旋转、缩放。父节点移动、旋转、缩放时,所有子节点会跟着一起变换,而子节点自身的变换是相对于父节点的局部变换。
典型应用:汽车的车轮作为子节点,车身移动时车轮跟着移动,车轮自身还可以旋转。
Group 的作用:THREE.Group 是常用的容器节点,本身不渲染,用于把多个物体作为一个整体进行变换管理,类似 Photoshop 的图层分组。
5. 简述 Object3D 是什么,它和 Mesh、Camera、Light 是什么关系?
答案:
Object3D 是 Three.js 中绝大多数 3D 对象的基类(父类),封装了位置、旋转、缩放、父子层级、矩阵变换等通用能力。
Mesh(网格)、Camera(相机)、Light(灯光)、Group(组)等都是 Object3D 的子类,都继承了它的位置变换、层级管理等能力,因此它们都可以设置 position、rotation、scale,也都可以作为父节点添加子物体。
6. 什么是 Mesh?它由哪两部分组成?
答案:
Mesh(网格)是 Three.js 中最基础的可见物体类,代表一个具体的 3D 物体,由两部分组成:
- 几何体 Geometry:定义物体的形状、顶点结构,即「长什么样」;
- 材质 Material:定义物体的表面外观,比如颜色、纹理、反光度、透明度,即「穿什么衣服」。
两者组合后,就能渲染出一个带外观的立体物体。
7. 什么是渲染循环?为什么要用 requestAnimationFrame?
答案:
渲染循环(动画循环)是指每隔一帧就重新渲染一次场景,从而形成连续动画的机制,Three.js 动画都依赖渲染循环。
为什么用 requestAnimationFrame:
- 它会跟随浏览器的刷新频率(通常 60Hz)执行,画面流畅且性能最优;
- 页面切换到后台标签页时会自动暂停,节省性能和电量;
- 相比
setInterval,它能保证渲染时机和浏览器重绘同步,避免掉帧和卡顿。
8. 什么是按需渲染?什么时候用它?
答案:
按需渲染是相对于「连续渲染循环」而言的:不是每帧都重绘画面,而是只有当场景发生变化时(用户操作、参数修改、资源加载完成)才重新渲染一帧。
适用场景:
- 静态 3D 展示页、模型查看器、3D 编辑器;
- 场景大部分时间静止,只有用户交互时才变化;
- 移动端、低性能设备,用来降低 GPU 占用、节省电量。
核心实现:移除 rAF 循环,初始化渲染一次,在控制器 change 事件、窗口 resize、参数修改时手动触发渲染。
二、相机与控制篇
9. 透视相机和正交相机有什么区别?分别适用什么场景?
答案:
透视相机 PerspectiveCamera:模拟人眼视觉,近大远小,有透视效果,符合真实视觉感受。
- 核心参数:视场角 fov、宽高比 aspect、近裁剪面 near、远裁剪面 far;
- 适用场景:绝大多数 3D 场景、游戏、产品展示、建筑可视化等需要真实立体感的场景。
正交相机 OrthographicCamera:物体大小和距离无关,没有透视变形,远近看起来一样大。
- 核心参数:上下左右四个边界、近远裁剪面;
- 适用场景:2D/3D 制图、建筑平面图、CAD 类工具、UI 层级、等距视角游戏等需要精确尺寸、无透视失真的场景。
PerspectiveCamera 是透视投影相机,符合人眼视觉规律,远近物体会产生缩放变化;OrthographicCamera 是正交投影相机,不存在透视效果,物体大小不随距离变化。在 Three.js 中前者用于真实感场景(如游戏、产品展示),后者用于工程制图或 UI 类 3D 场景。
10. 相机的 near/far 参数有什么影响?如何合理设置?
答案:
near 和 far 定义了相机的可视深度范围,只有在这个范围内的物体才会被渲染。
影响:
near过小:会导致近处物体被裁剪,同时会降低深度缓冲精度,远距离物体容易出现 Z-fighting;far过大:会过度稀释深度缓冲精度,同样导致 Z-fighting 问题;far过小:远处物体被裁剪看不到。
合理设置原则:
- 在能容纳所有物体的前提下,尽量缩小 near 和 far 的比值;
- 常用值:
near = 0.1,far = 1000(根据场景尺度调整); - 如果场景深度范围极大,考虑使用对数深度缓冲(
logarithmicDepthBuffer: true)。
11. OrbitControls 的原理是什么?
答案:
OrbitControls 本质是用"球坐标系"控制相机围绕 target 做轨道运动,并通过 lookAt 始终对准目标点。
数学本质:
相机位置不是直接 xyz,而是用球坐标表示:
- 半径 r(距离)
- 水平角 θ(azimuth 方位角)
- 垂直角 φ(polar 极角)
转换公式:
x = r · sinφ · cosθ
y = r · cosφ
z = r · sinφ · sinθ
控制逻辑:
- Rotate(旋转)→ 改 θ / φ
- Zoom(缩放)→ 改 r
- Pan(平移)→ 改 target
最终每帧:
camera.position = sphericalToCartesian()
camera.lookAt(target)
OrbitControls 的本质是基于球坐标系的相机控制器,它通过将相机位置转换为球坐标(半径、水平角、垂直角),再根据用户输入动态更新这些参数,实现相机围绕目标点旋转、缩放和平移的效果。每一帧都会重新计算相机的世界坐标,并通过 lookAt 保持对目标点的朝向,因此它本质是"球面轨道相机系统"。
12. Three.js 中常见的相机控制器有哪些?
答案:
| 控制器 | 特点 | 适用场景 |
|---|---|---|
| OrbitControls | 围绕目标点旋转、缩放、平移,最常用 | 产品展示、模型查看、大多数 3D 场景 |
| TrackballControls | 自由旋转,无"上下"限制,可翻转 | 自由视角探索、无重力场景 |
| FlyControls | 模拟飞行,键盘控制方向移动 | 飞行模拟、大场景漫游 |
| FirstPersonControls | 第一人称视角,类似 FPS 游戏 | 游戏场景、虚拟漫游 |
| PointerLockControls | 鼠标锁定,指针隐藏,全屏第一人称 | 沉浸式游戏、VR 场景 |
| MapControls | 类似地图操作,平移优先 | 地图应用、GIS 场景 |
三、几何体与材质篇
13. Three.js 中常见的几何体有哪些?
答案:
内置常用几何体分为几类:
- 基础立体:
BoxGeometry(立方体)、SphereGeometry(球体)、CylinderGeometry(圆柱体)、PlaneGeometry(平面)、TorusGeometry(圆环)、ConeGeometry(圆锥); - 二维图形:
CircleGeometry(圆形)、RingGeometry(圆环面)、ShapeGeometry(自定义形状); - 特殊几何体:
TubeGeometry(管道)、LatheGeometry(车削几何体)、ExtrudeGeometry(挤出几何体)、BufferGeometry(自定义缓冲几何体)。
14. Geometry 和 BufferGeometry 有什么区别?为什么推荐用 BufferGeometry?
答案:
- Geometry:早期的几何体格式,用 JavaScript 对象(Vector3、Color 等)存储顶点数据,可读性好、操作简单,但性能差、内存占用高,数据要转一遍才能传给 GPU,目前已被官方废弃。
- BufferGeometry:当前标准几何体格式,底层用
Float32Array等类型数组存储顶点数据,数据格式和 GPU 直接对齐,无需额外转换,内存占用小、渲染性能高。
BufferGeometry 的核心本质:
BufferGeometry = GPU-friendly typed arrays
数据结构:
position: Float32Array
normal: Float32Array
uv: Float32Array
优化点:
- 连续内存,GPU 直接读取
- 无 JS 对象开销
- 支持顶点数据动态更新,适合做变形动画、粒子效果
BufferGeometry 是 Three.js 中用于高性能渲染的几何结构,它使用 TypedArray(如 Float32Array)存储顶点数据,相比早期 Geometry 的对象数组结构,减少了内存开销并提升 GPU 访问效率。所有顶点属性(位置、法线、UV)都以连续缓冲区形式存储,并直接上传至 GPU。所有内置几何体底层都是 BufferGeometry,是当前唯一主推的格式。
15. 什么是 BufferAttribute?它起什么作用?
答案:
BufferAttribute 是 BufferGeometry 中存储单种顶点数据的类,比如位置、法线、UV、颜色各自对应一个 BufferAttribute。
它包装了一个类型数组(如 Float32Array),并指定「每个顶点占几个分量」,比如位置是 3 个分量(x/y/z),UV 是 2 个分量(u/v)。
作用:把顶点数据按规范组织好,高效传递给 GPU 的顶点着色器,同时支持标记动态更新、设置使用模式。
16. 什么是索引几何体(setIndex)?它有什么好处?
答案:
索引几何体就是给顶点数据增加一个索引数组,用顶点的编号来定义三角面,而不是每个三角面都重复写一遍顶点数据。
好处:
- 节省显存:去除重复顶点,顶点总数大幅减少,只增加少量整数索引,整体内存显著降低;
- 提升性能:GPU 可以缓存顶点计算结果,重复顶点不用重复计算,渲染更快;
- 修改方便:修改一个顶点数据,所有引用它的三角面都会同步生效。
17. 什么是材质?列举常用材质及特点
答案:
材质定义了物体表面的渲染特性,决定了物体如何反射光线、呈现颜色和纹理。
材质系统本质:Material 是对 GPU Shader 的封装层,不同材质对应不同的光照模型。
常用材质:
- MeshBasicMaterial:基础材质,不受光照影响,性能最好,没有立体感,常用于辅助线、背景、自发光物体。
- MeshLambertMaterial:朗伯材质,仅计算漫反射,没有高光,性能较好,适合粗糙、无光泽的物体。
- MeshPhongMaterial:冯氏材质,支持漫反射+高光,有光泽感,适合塑料、陶瓷类物体。
- MeshStandardMaterial:标准 PBR 材质,基于物理的渲染,支持金属度、粗糙度,效果最真实,是当前主流写实材质。
- MeshPhysicalMaterial:物理材质,PBR 增强版,支持清漆、透光、折射率等更高级的物理属性。
Three.js 的材质系统本质是对 GPU Shader 的封装层,不同材质对应不同的光照模型。其中 MeshBasicMaterial 不参与光照计算,适用于调试;MeshLambertMaterial 使用简化光照模型;MeshStandardMaterial 基于 PBR(物理渲染)模型,通过 metalness 和 roughness 控制材质的真实感,是现代 Three.js 的默认标准材质。
18. PBR 材质是什么?MeshStandardMaterial 的核心参数有哪些?
答案:
PBR(Physically Based Rendering)即基于物理的渲染,是一套基于物理光学原理的材质和光照计算模型,能更真实地模拟光线在物体表面的反射、折射,效果接近真实世界。
MeshStandardMaterial 是 Three.js 标准 PBR 材质,核心参数:
metalness金属度:0 为非金属,1 为纯金属,决定反射特性;roughness粗糙度:0 为光滑镜面,1 为完全粗糙,决定高光扩散程度;map颜色贴图、normalMap法线贴图、roughnessMap粗糙度贴图、metalnessMap金属度贴图;envMap环境贴图:提供环境反射,金属物体必备。
19. 什么是纹理(Texture)?常用的纹理贴图类型有哪些?
答案:
纹理就是贴在 3D 物体表面的图片数据,用来丰富物体表面细节,替代精细建模,提升真实感。
常用贴图类型:
- 颜色贴图(map):最基础,决定物体表面颜色图案;
- 法线贴图(normalMap):模拟表面凹凸细节,不增加顶点数就能提升细节;
- 粗糙度贴图(roughnessMap):控制表面不同区域的粗糙程度;
- 金属度贴图(metalnessMap):控制表面不同区域的金属属性;
- 环境光遮蔽贴图(aoMap):模拟缝隙、角落的暗部,增强立体感;
- 自发光贴图(emissiveMap):让物体局部自己发光。
20. 什么是 UV 坐标?它的作用是什么?
答案:
UV 坐标是 2D 纹理的坐标系统,U 代表水平方向,V 代表垂直方向,取值范围通常是 0~1。
作用:建立「3D 模型顶点」和「2D 纹理像素」的对应关系,告诉 GPU 纹理图片的哪个位置,应该贴到模型的哪个顶点上,从而把图片精准地包裹在 3D 模型表面。
可以理解为:UV 就是给模型贴包装纸的定位标记,UV 错了贴图就会拉伸、错位。
21. 什么是法线(Normal)?它有什么作用?
答案:
法线是垂直于物体表面、指向外侧的方向向量,每个顶点都有自己的法线。
核心作用:计算光照。GPU 根据法线方向和光线方向的夹角,来计算这个点的亮度——法线正对光线时最亮,法线背对光线时最暗。没有法线,受光材质就无法正确计算明暗,模型会全黑或没有立体感。
法线还决定了面的正反面,Three.js 默认只渲染法线朝外的正面。
22. 什么是 sRGB 和线性颜色空间?Three.js 中怎么处理?
答案:
- 线性空间 Linear:颜色数值和亮度成正比,适合做光照、混合等数学计算,结果准确。
- sRGB 空间:显示器使用的非线性空间,人眼对暗部更敏感,sRGB 用更多位存储暗部,显示效果更好。
渲染流程:纹理图片(sRGB)→ 解码为线性空间 → 进行光照计算 → 最终编码为 sRGB 输出到屏幕。
Three.js 处理方式(r152+ 默认开启颜色管理):
- 漫反射、自发光等颜色贴图要设置
texture.colorSpace = THREE.SRGBColorSpace; - 法线、粗糙度、金属度等数据贴图保持线性空间;
- 渲染器开启输出编码:
renderer.outputColorSpace = THREE.SRGBColorSpace。
23. 什么是背面剔除?怎么解决物体背面看不见的问题?
答案:
背面剔除是 GPU 的优化机制:根据顶点顺序和法线方向,判断面朝向相机还是背向相机,直接不渲染背向相机的面,从而减少一半的绘制量,提升性能。
问题:薄片、布料、镂空物体,从背面看会消失。
解决:将材质的 side 属性设置为 THREE.DoubleSide(双面渲染),正反两面都会被渲染。
注意:双面渲染性能略差,只给需要的物体开启,不要全局全开。
24. 什么是 Alpha 测试和 Alpha 混合?分别有什么特点?
答案:
两者都是处理透明/半透明的技术,但原理和效果不同:
- Alpha 测试(Alpha Test):设置一个阈值,像素透明度高于阈值就显示,低于就直接丢弃,没有半透明过渡。
- 特点:性能好,不需要排序;边缘硬,适合树叶、铁丝网、镂空文字等只有「显示/不显示」两种状态的透明物体。
- 对应材质属性:
alphaTest。
- Alpha 混合(Alpha Blending):根据透明度将当前像素和背景像素颜色混合,实现真正的半透明效果。
- 特点:效果柔和,但需要从后往前排序绘制,排序错误会出现显示异常;性能开销更大。
- 对应材质属性:
transparent: true。
四、光照与阴影篇
25. Three.js 常用的光源类型有哪些?分别有什么特点?
答案:
- AmbientLight 环境光:全局均匀照亮所有物体,没有方向和阴影,用来提亮整体暗部,不能单独作为主光。
- DirectionalLight 平行光:模拟太阳光,光线平行发射,有方向,能产生阴影,适合户外、大场景主光源。
- PointLight 点光源:从一个点向四面八方发射,比如灯泡、蜡烛,距离越远亮度越弱,支持阴影。
- SpotLight 聚光灯:从一点向锥形区域发射,比如手电筒、舞台射灯,有照射范围和衰减,支持阴影。
- HemisphereLight 半球光:天空色和地面色渐变,模拟环境漫反射,效果自然柔和,常配合平行光使用。
- RectAreaLight 面光源:模拟矩形发光面(如灯箱、屏幕),光照效果更真实,但不支持阴影。
26. 如何让 Three.js 中的物体产生和接收阴影?
答案:
需要三步同时配置,缺一不可:
渲染器开启阴影映射
renderer.shadowMap.enabled = true;光源开启投射阴影(只有平行光、点光源、聚光灯支持阴影)
light.castShadow = true;物体分别设置
- 产生阴影的物体:
mesh.castShadow = true - 接收阴影的物体:
mesh.receiveShadow = true
- 产生阴影的物体:
注意:环境光不能产生阴影;单面材质的背面不会投射阴影;阴影质量可以通过调整阴影贴图大小提升。
27. 阴影有哪些优化方法?
答案:
阴影非常消耗性能,常用优化手段:
- 减少阴影投射者:只有重要物体开
castShadow,地面、远处物体只接收不投射; - 降低阴影贴图分辨率:
light.shadow.mapSize按需设置,不要盲目开 2048+; - 缩小阴影范围:调整平行光阴影相机的远近左右边界,只在需要的范围内生成阴影,提升阴影精度和性能;
- 使用软阴影算法:根据需求选择,PCFSoftShadowMap 质量好但开销大,基础场景用默认即可;
- 静态场景烘焙阴影:完全静态的场景,把阴影烘焙成贴图,实时渲染只显示贴图,性能最好。
五、模型加载篇
28. 加载 3D 模型常用的格式有哪些?为什么推荐 glTF/GLB?
答案:
常用格式:glTF/GLB、OBJ、FBX、STL、DAE 等。
推荐 glTF/GLB 的原因:
- 专为传输设计:二进制存储,体积小,解析快,数据格式和 GPU 对齐,零开销直接渲染;
- 功能完整:自带模型、材质、贴图、骨骼动画、变形动画、场景层级,加载即用;
- 单文件可选:GLB 格式把所有资源打包成一个二进制文件,管理方便,不易出错;
- 官方推荐:Three.js 官方首推格式,生态完善,支持 Draco 几何压缩、KTX2 纹理压缩等优化;
- 跨平台通用:几乎所有 3D 引擎都支持,是网页 3D 的事实标准。
glTF 与 GLB 的本质区别:
| 项目 | glTF | GLB |
|---|---|---|
| 文件数 | 多(JSON + bin + textures) | 1(单文件) |
| 可读性 | 高(JSON 文本) | 低(二进制) |
| 性能 | 一般(需解析 JSON) | 更优(直接二进制读取) |
GLTF 是一种基于 JSON 的三维模型描述格式,模型数据通常与外部资源(如贴图、二进制顶点数据)分离;GLB 是 GLTF 的二进制打包版本,将 JSON、buffer 和纹理合并为单一文件,因此加载更快、更适合生产环境。在实际工程中通常优先使用 GLB。
29. 常用 3D 模型格式完整对比
答案:
| 对比维度 | glTF / GLB | OBJ | FBX | STL | DAE (Collada) |
|---|---|---|---|---|---|
| 格式全称 | GL Transmission Format(Khronos 官方标准) | Wavefront OBJ(传统几何交换格式) | Filmbox(Autodesk 工业格式) | Stereolithography(3D 打印标准格式) | Digital Asset Exchange(开放交换格式) |
| 存储形式 | .gltf:JSON 文本 + 外部二进制/贴图.glb:单二进制文件,所有资源打包 | 纯文本格式,材质依赖独立的 .mtl 文件,贴图为外部文件 | 二进制为主,也支持 ASCII 文本格式,贴图可内嵌 | 分纯文本/二进制两种,仅存几何面数据 | 纯 XML 文本格式,贴图为外部文件 |
| 材质支持 | ✅ 原生支持 PBR 物理材质,功能完整,效果最写实 | ⚠️ 依赖外部 MTL 文件,仅支持基础漫反射/高光,无 PBR,能力极弱 | ✅ 支持传统材质与 PBR 材质,功能全面 | ❌ 无材质、无颜色、无纹理概念,仅存三角面形状 | ✅ 支持完整材质定义,兼容多种着色模型 |
| 动画支持 | ✅ 原生支持骨骼动画、变形目标动画、相机动画 | ❌ 完全不支持任何动画 | ✅ 支持骨骼蒙皮、变形动画、相机动画、约束等,动画能力最强 | ❌ 完全不支持任何动画 | ✅ 支持骨骼动画、变形动画,动画能力完整 |
| 文件体积 | 小,二进制存储,配合 Draco 几何压缩可再减 60%~80% | 大,纯文本存储顶点,数据冗余高 | 中等偏大,内嵌资源时体积会显著增加 | 极小,仅存储三角面顶点数据 | 大,XML 标签冗余多,体积普遍大于 glTF |
| 解析加载性能 | 最优,二进制数据与 GPU 直接对齐,零解析开销,加载即渲染 | 差,文本解析慢,还要额外转换为二进制格式才能给 GPU 使用 | 一般,格式复杂,解析开销大,网页端加载偏慢 | 极快,格式极简,解析成本极低 | 差,XML 解析慢,数据转换开销高 |
| 层级结构 | ✅ 完整场景树(父子节点、对象层级),可单独操作部件 | ❌ 无层级结构,本质是面片集合,无法单独操作子部件 | ✅ 完整的场景层级、对象关系、骨骼结构 | ❌ 无任何层级,就是独立三角面的集合 | ✅ 完整场景层级与对象关系 |
| Three.js 加载器 | GLTFLoader(官方核心维护,生态最好)配合 DRACOLoader 解码压缩模型 | OBJLoader + MTLLoader(分别加载模型和材质) | FBXLoader(官方提供,依赖额外解析库) | STLLoader(官方提供) | ColladaLoader(官方提供,更新较少) |
| 核心适用场景 | 网页 3D 展示、产品交互、H5 游戏、AR/VR、数字孪生 | 简单静态模型交换、老模型兼容、建模软件间几何传递 | 游戏开发、影视动画、工业设计,适合复杂动画模型 | 3D 打印、工业原型展示、纯几何模型预览 | 多建模软件间资产交换、老项目兼容迁移 |
30. 模型加载后大小不对、看不见,通常怎么处理?
答案:
这是新手高频问题,标准排查步骤:
- 计算包围盒:用
Box3.setFromObject(model)得到模型尺寸和中心,判断是太大还是太小; - 自动适配相机:根据模型大小,计算相机需要后退的距离,让模型完整显示在视野内;
- 调整裁剪面:模型太大就调大 far,太小就调小 near,避免被裁剪掉;
- 检查位置:看模型是否在相机前方,有没有被其他物体挡住;
- 检查光照和材质:确认有灯光、材质不是纯黑、法线没有反转。
31. 模型加载方面有哪些优化手段?
答案:
- 格式选型:优先用 GLB 单文件,减少请求数;
- 几何压缩:开启 Draco 压缩,减少模型体积 60%+;
- 纹理压缩:用 KTX2 压缩纹理,减少下载体积和显存占用;
- 减面减贴图:移除不必要的细节,降低面数和贴图分辨率;
- 分级加载:先加载低模占位,再异步加载高模替换;
- LoadingManager 统一管理:做加载进度、错误处理、预加载、懒加载;
- 模型拆分:大场景分块加载,只加载视野内的区块。
32. 如何在 3D 场景中显示文字?有哪些方案?
答案:
常用方案各有优劣:
- Canvas 动态纹理:用 2D Canvas 绘制文字,生成纹理贴到平面上,灵活、支持中文、性能较好,是最常用方案。
- TextGeometry 文字几何体:将字体转为几何体,真正的 3D 立体文字,性能一般,适合少量标题文字。
- CSS2DRenderer / CSS3DRenderer:用 HTML 元素当标签,DOM 元素跟随 3D 位置,适合大量文字、表单、信息弹窗。
- Sprite 精灵文字:预生成文字图片当精灵,适合标签、标记点。
- MSDF 字体:通过着色器渲染字体,清晰、缩放不失真,适合大量文字场景。
33. OpenSCAD → OFF → GLB 的工程流程是什么?
答案:
从 CAD 参数建模到 GPU 可渲染网格的转换管线:
OpenSCAD (CSG 建模)
↓
OFF (mesh)
↓
Blender / MeshLab
↓
OBJ / FBX
↓
优化(UV / Decimate)
↓
GLB
↓
Three.js
OpenSCAD 使用基于 CSG(Constructive Solid Geometry)的参数化建模方式,生成几何体后可导出为 OFF 网格格式;该格式通常需要通过 MeshLab 或 Blender 进行转换和优化(如网格重建、UV 展开和面数简化),最终统一导出为 GLB 格式,以便在 Web3D(如 Three.js)中高效加载和渲染。这一流程本质是从 CAD 参数建模到 GPU 可渲染网格的转换管线。
六、交互动画篇
34. 什么是 Raycaster?如何实现点击交互?
答案:
Raycaster 是 Three.js 中用于交互检测的核心工具,它通过将屏幕鼠标坐标转换为标准设备坐标(NDC),再从相机位置生成一条射线,与场景中的物体进行几何相交计算,从而实现点击选中、悬停检测等交互功能。
核心流程:
屏幕坐标 → NDC → 世界空间射线 → 相交检测
本质:
Ray = camera → mouse direction
使用步骤:
- 获取鼠标点击位置的屏幕坐标;
- 将屏幕坐标转换为 NDC 标准设备坐标(-1 到 1);
- 用
raycaster.setFromCamera(ndc, camera)从相机位置发射射线; - 调用
raycaster.intersectObjects()检测射线与物体列表的交点; - 返回的数组中第一个元素就是距离最近的被选中物体。
35. Raycaster 为什么慢?如何优化?
答案:
慢的原因: Raycaster 需要遍历场景中所有 Mesh,对每个 Mesh 的每个三角形做相交计算。复杂场景中如果有 100000 个 Mesh,性能会爆炸。
Ray → 检测 → 所有 Mesh
优化方案:
- 使用 BVH(层次包围盒):引入
three-mesh-bvh库,将网格的三角形组织成层次包围盒树,射线只需检测相交的包围盒节点,性能提升数十倍; - 减少检测对象:只对需要交互的物体做检测,用
intersectObjects([targetObjects])而不是整个场景; - 使用低精度几何体:用简化后的包围盒代替精细模型做检测;
- GPU 拾取(Picking):用颜色编码渲染到离屏目标,读取像素判断点击对象,适合大量物体场景。
36. 如何实现拖拽 3D 物体移动?
答案:
本质上是:鼠标 → Raycaster 射线 → 找到选中的物体 → 将鼠标在屏幕上的移动转换成三维空间中的坐标变化 → 更新物体位置。
常用两种方案:
- 射线+平面相交法:
- 创建一个与地面平行的虚拟平面(屏幕:x,y,但是 Three.js:x,y,z,鼠标没有 z 轴,需要创建一个 z 轴垂直的平面);
- 拖拽时,从鼠标位置发射射线,求与平面的交点;
- 把交点坐标赋值给被拖拽物体,实现跟随鼠标移动。
- 使用官方插件:
DragControls,封装了拖拽逻辑,直接传入物体和相机、渲染器即可使用,适合简单拖拽场景。
37. Three.js 中播放骨骼动画的核心组件有哪些?
答案:
核心三个类:
- AnimationClip:动画剪辑,一段完整的动画数据(比如走路、跑步),通常从 glTF 模型中加载得到。
- AnimationMixer:动画混合器,一个模型对应一个实例,负责管理该模型的所有动画、计算时间、更新骨骼状态。
- AnimationAction:动画动作,对单个动画剪辑的播放控制实例,可以控制播放、暂停、循环、倍速、权重。
基本流程:创建混合器 → 获取动画剪辑 → 创建播放动作 → 播放 → 渲染循环中用 delta 时间更新混合器。
38. 什么是 Tween.js?在 Three.js 中怎么用?
答案:
Tween.js 是一个轻量级的补间动画库,用来快速实现物体位置、旋转、缩放、材质属性的平滑过渡动画,不用自己写缓动逻辑。
在 Three.js 中典型用法:
- 相机平滑移动到指定位置、视角平滑过渡;
- 物体弹出、淡入淡出、颜色渐变等简单动画;
- 配合用户交互做属性过渡。
使用流程:创建 Tween 实例,指定起始和目标值、时长、缓动函数,启动后在渲染循环中调用 TWEEN.update()。
39. 如何让物体沿着指定路径运动?
答案:
核心思路:用 Three.js 的曲线类生成路径,根据时间取曲线上的点,更新物体位置和朝向。
- 定义路径点数组,创建三维曲线,常用
CatmullRomCurve3(平滑曲线); - 动画循环中,根据时间计算进度值 t(0 ~ 1);
- 调用
curve.getPoint(t)获取当前位置,赋值给物体; - 调用
curve.getTangent(t)获取切线方向,让物体朝向运动方向。
40. 什么是 Billboard(广告牌)效果?怎么实现?
答案:
广告牌效果是指:一个平面物体始终朝向相机,无论相机怎么旋转移动,平面都正对着观察者。
常见应用:粒子、标签、树木精灵、UI 图标、血量条等始终面向屏幕的元素。
实现方式:
- 简单方式:每一帧让物体的旋转和相机保持一致,
billboard.lookAt(camera.position); - 内置方式:使用
Sprite和SpriteMaterial,天生就是广告牌效果,自动面向相机,性能更好。
41. 什么是 LOD(细节层次)?它的作用是什么?
答案:
LOD(Level of Detail)即细节层次技术,根据物体距离相机的远近,自动切换不同精度的模型。
- 距离近:显示高模,细节丰富;
- 距离远:显示低模,顶点数少。
作用:在不影响视觉效果的前提下,大幅减少远距离物体的顶点数和 Draw Call,提升整体渲染性能。
Three.js 中使用 THREE.LOD 类,添加多个层级的模型和对应距离阈值,自动切换。
42. 什么是天空盒?Three.js 中怎么实现?
答案:
天空盒是用来模拟环境背景的技术,用一个巨大的立方体包裹整个场景,六个面分别贴上天空、远山等环境贴图,营造沉浸式的空间感。
实现方式:
- CubeTexture 立方体贴图:加载 6 张对应方向的图片,创建立方体纹理,赋值给
scene.background; - 全景图 + 球体:用一张等距圆柱全景图,贴在大球体内侧,相机在球心,实现更简单,单张图即可;
- PMREM 环境贴图:同时作为场景背景和物体环境反射,PBR 场景常用。
七、性能优化篇
43. 什么是 Draw Call?为什么它是性能瓶颈?
答案:
Draw Call 就是 CPU 向 GPU 发送的一次绘制命令。每绘制一个独立的 Mesh(不同几何体/不同材质),就需要一次 Draw Call。
为什么是瓶颈:
- 每次 Draw Call 前,CPU 都要做状态切换、数据绑定、命令提交,开销很大;
- GPU 本身渲染能力很强,但 CPU 频繁发命令会导致 GPU 等待,流水线不满载;
- 当 Draw Call 达到几百上千次时,CPU 很容易成为瓶颈,导致帧率下降。
优化核心就是:尽可能减少 Draw Call 数量。
44. 列举至少 5 种减少 Draw Call 的方法
答案:
- 合并几何体:将多个静态物体的几何体合并成一个,多个 Mesh 变一个,Draw Call 直接降为 1,适合完全静态的物体。
- 实例化渲染 InstancedMesh:大量相同几何体、不同位置/旋转/缩放的物体,用一个 Draw Call 渲染全部,适合树木、石块、人群。
- 纹理图集(Texture Atlas):把多张小贴图拼成一张大图,多个物体共用同一张纹理和同一个材质,减少材质切换。
- 减少材质种类:尽量复用材质,通过顶点颜色、UV 区分不同效果,避免每个物体一个材质。
- 批处理:将相同材质、相同几何体的物体进行合批处理。
- 使用共享几何体:相同形状的物体复用同一个 Geometry 实例,不要重复创建。
45. 合并几何体(Merge)和实例化网格(InstancedMesh)有什么区别?
答案:
| 维度 | 合并几何体 | 实例化网格 InstancedMesh |
|---|---|---|
| 原理 | 多个几何体顶点合并成一个大几何体 | 同一个几何体复用 N 次,GPU 硬件实例化 |
| 单独变换 | ❌ 不能单独移动、旋转、修改 | ✅ 每个实例可以单独设置矩阵、颜色 |
| 性能 | 最高,数据量最小 | 略低于合并,但远高于多 Mesh |
| 动态更新 | 几乎不能改,改顶点开销极大 | 矩阵、颜色可动态更新,开销小 |
| 适用场景 | 完全静态的装饰、地形、建筑 | 大量重复、需要动画/单独控制的物体 |
选型口诀:完全不动用合并,要动要用实例化。
46. Three.js 中常见的内存泄漏原因有哪些?怎么释放资源?
答案:
常见原因:切换场景、删除物体时,只从场景移除,没有释放 GPU 资源(几何体、材质、纹理、渲染目标等),GPU 内存持续上涨,最终页面卡顿崩溃。
释放方法:
- 几何体:
geometry.dispose() - 材质:
material.dispose() - 纹理:
texture.dispose() - 渲染目标:
renderTarget.dispose() - 规范:销毁模型时,遍历所有子网格,依次释放几何体、材质以及材质上的所有贴图。
47. 纹理方面有哪些性能优化手段?
答案:
- 尺寸合理:不要盲目用 4K、8K 贴图,按需选择分辨率,优先 2 的幂次方尺寸;
- 纹理压缩:使用 KTX2 / Basis Universal 压缩纹理,显存占用减少 75% 左右;
- 纹理复用:相同贴图复用同一个 Texture 实例,不要重复加载;
- Mipmap:开启 Mipmap,远距离自动用低分辨率纹理,提升渲染质量和性能;
- 格式选择:漫反射用 JPG,带透明用 PNG,数据类贴图用压缩格式;
- 按需加载:远处物体先加载低清贴图,靠近后再切换高清。
48. 什么是视锥剔除?它是怎么优化性能的?
答案:
视锥剔除(Frustum Culling)是 Three.js 内置的优化机制:渲染前判断物体的包围盒是否完全在相机的可视锥体之外,如果完全在外面,就直接跳过不渲染。
作用:场景越大、物体越多,优化效果越明显,只渲染视野内的物体,大幅减少 Draw Call 和 GPU 渲染量。
注意:默认开启,通过
mesh.frustumCulled控制;如果物体自定义了顶点动画,需要手动更新包围盒,否则剔除会出错。
49. 大量粒子效果怎么优化性能?
答案:
- 使用 Points 而非 Mesh:粒子系统用
THREE.Points+PointsMaterial,一个 Draw Call 渲染所有粒子,性能远高于多个平面; - BufferGeometry 存数据:所有粒子位置、颜色存在一个 BufferGeometry 中,统一更新;
- Shader 动画:把粒子运动逻辑放到着色器中计算,GPU 并行计算,比 JS 计算快得多;
- 控制数量:合理控制粒子总数,避免无意义的超量粒子;
- 纹理合并:所有粒子共用一张纹理图集,减少材质切换。
50. 什么是离屏渲染(OffscreenCanvas)?有什么价值?
答案:
OffscreenCanvas 是浏览器提供的离屏画布能力,可以把 3D 渲染放到 Web Worker 后台线程中执行,不占用主线程。
价值:
- 主线程只处理 DOM 交互、事件转发,渲染计算不阻塞主线程,页面交互更流畅;
- 模型加载、几何解析也可以放到 Worker 中,避免大模型加载时页面卡顿;
- 适合复杂 3D 场景、页面同时有大量 DOM 交互的场景。
注意:Worker 不能访问 DOM,鼠标、键盘、尺寸等事件需要主线程监听后转发给 Worker。
八、高级底层篇
51. 简述从顶点数据到屏幕像素的完整渲染流水线
答案:
完整流程(MVP 变换 + 光栅化 + 片元处理):
CPU 阶段:
Scene → Camera → Culling → Matrix Transform → Shader → Rasterization → Pixel
- 更新物体状态
- 计算矩阵(Model、View、Projection)
GPU 阶段:
- 局部坐标 → 世界坐标:乘以模型矩阵(Model),将物体从自身坐标系变换到世界坐标系;
- 世界坐标 → 观察坐标:乘以视图矩阵(View),以相机为原点重新计算坐标;
- 观察坐标 → 裁剪坐标:乘以投影矩阵(Projection),将视锥体变换为立方体,超出范围的顶点被裁剪;
- 透视除法:坐标除以 w 分量,得到标准化设备坐标 NDC,范围 [-1, 1];
- 视口变换:NDC 坐标映射到屏幕像素坐标;
- 光栅化:将三角形覆盖的像素区域栅格化,生成片元;
- 片元着色:对每个片元执行片元着色器,计算颜色、纹理采样、光照;
- 深度测试、模板测试、混合:通过测试的像素最终写入帧缓冲,显示到屏幕。
Three.js 的渲染流程首先在 CPU 端更新场景中物体的位置、旋转与缩放,并计算模型矩阵、视图矩阵和投影矩阵;随后进入 GPU 渲染阶段,通过顶点着色器进行坐标变换、片元着色器进行光照计算,最后经过光栅化输出到 Canvas。整个流程本质是 CPU 构建场景数据,GPU 执行渲染管线。
52. 三大矩阵变换是什么?核心公式是什么?
答案:
Three.js 的坐标变换基于矩阵管线模型,包括三大矩阵:
- Model Matrix(模型矩阵):模型空间到世界空间
- View Matrix(视图矩阵):世界空间到相机空间
- Projection Matrix(投影矩阵):相机空间到裁剪空间
总公式:
Clip = P × V × M × position
Three.js 的坐标变换基于矩阵管线模型,包括 Model Matrix(模型空间到世界空间)、View Matrix(世界空间到相机空间)以及 Projection Matrix(相机空间到裁剪空间)。最终顶点位置通过 P × V × M 进行变换,实现从三维空间到二维屏幕空间的映射。
53. 什么是深度测试?它的作用是什么?
答案:
深度测试是 GPU 内置的机制,每个像素都存储一个深度值(距离相机的远近),绘制新像素时,比较新像素和已有像素的深度值。
- 新像素更近:通过测试,替换颜色和深度;
- 新像素更远:不通过测试,直接丢弃。
作用:自动实现物体前后遮挡关系,保证近处物体挡住远处物体,符合真实视觉。
对应 Three.js 材质属性:
depthTest开关测试,depthWrite开关深度写入。
54. 什么是 Z-fighting(深度冲突)?怎么解决?
答案:
Z-fighting 指两个距离非常近的面,深度值几乎相等,导致深度测试结果不稳定,两个面交替显示,出现闪烁的条纹。
产生原因:深度缓冲区精度有限,距离相机越远精度越低,两个面越近越容易冲突。
解决方法:
- 拉开两个面的物理距离,不要完全共面;
- 不要把远裁剪面 far 设置得过大,避免深度精度被过度稀释;
- 使用多边形偏移
polygonOffset,给其中一个面增加一点深度偏移; - 使用对数深度缓冲,提升远距离精度。
55. 自定义着色器(ShaderMaterial)是什么?什么时候用?
答案:
ShaderMaterial 允许开发者自己编写顶点着色器和片元着色器,完全控制顶点变换和像素颜色计算,实现内置材质做不到的特殊效果。
适用场景:
- 特殊的动画效果:波浪、溶解、扫描线、故障艺术;
- 自定义光照模型、非真实感渲染(卡通、描边);
- 高级粒子效果、后处理特效;
- 大量数据的 GPU 并行计算。
56. 顶点着色器和片元着色器分别负责什么?
答案:
顶点着色器(Vertex Shader):每个顶点执行一次,负责顶点的坐标变换、传递 varying 变量给片元着色器。
- 输入:attribute 顶点属性(位置、法线、UV 等);
- 输出:裁剪空间坐标
gl_Position,以及插值用的 varying 变量。
片元着色器(Fragment Shader):每个像素执行一次,负责计算该像素的最终颜色。
- 输入:插值后的 varying 变量、uniform 全局变量、纹理采样结果;
- 输出:像素颜色
gl_FragColor。
简单记:顶点着色器管形状位置,片元着色器管颜色外观。
57. attribute、uniform、varying 三种变量有什么区别?
答案:
这是 GLSL 中三种最核心的变量类型:
- attribute:顶点属性,每个顶点一个值,只能在顶点着色器中使用,用来传顶点位置、法线、UV、颜色等。
- uniform:全局统一变量,一帧内所有顶点/片元都一样,CPU 传给 GPU,比如时间、相机参数、灯光颜色、纹理。
- varying:插值变量,顶点着色器赋值,片元着色器接收,中间 GPU 会在三角形内做线性插值,比如 UV、顶点颜色。
58. 什么是渲染目标(RenderTarget)?有什么用?
答案:
渲染目标是一块离屏的帧缓冲,可以把 3D 场景先渲染到这个离屏缓冲上,生成一张动态纹理,再像普通纹理一样使用。
典型用途:
- 后处理特效:先把场景渲染到渲染目标,再用着色器做模糊、发光、调色等后期效果;
- 实时屏幕:3D 场景中的监控、电视屏幕,把另一个相机画面渲染成纹理贴上去;
- 拾取检测:用颜色编码渲染到离屏目标,读取像素判断点击对象;
- 截图、离屏生成图片。
59. 什么是后处理(Post Processing)?Three.js 中怎么实现?
答案:
后处理就是在场景渲染完成后,对最终画面再做一次图像处理,添加各种画面特效。
Three.js 中通过 EffectComposer 实现,流程:
- 创建效果组合器
EffectComposer; - 添加
RenderPass,先把场景正常渲染一遍; - 添加各种后处理通道,比如
UnrealBloomPass(泛光)、FXAAPass(抗锯齿)、ShaderPass(自定义着色器特效); - 渲染循环中用
composer.render()替代默认渲染。
常见后处理效果:bloom 发光、景深、运动模糊、色调映射、胶片颗粒、描边、复古滤镜。
60. 什么是模板测试(Stencil)?可以实现什么效果?
答案:
模板测试是和深度测试并行的 GPU 机制,模板缓冲区存储每个像素的标记值,绘制时可以根据规则更新和比较标记值,决定像素是否保留。
可以实现的效果:
- 描边效果:先画物体放大版写入模板,再画正常版本,模板外的放大像素就是描边;
- 裁剪遮罩:只在指定区域显示内容,比如后视镜、窗户、圆形视野;
- 镜面反射、平面倒影;
- 镂空、透视效果。
61. 物理引擎和 Three.js 是什么关系?常用方案有哪些?
答案:
Three.js 只负责渲染画面,本身没有物理计算能力,物理效果需要接入第三方物理引擎,两者配合工作:物理引擎负责计算物体的运动、碰撞、受力,每一帧把计算结果同步给 Three.js 的模型位置和旋转。
常用方案:
- cannon-es:纯 JS 轻量物理引擎,入门简单,适合中小型场景、简单碰撞掉落;
- Rapier:高性能 WASM 物理引擎,速度快、功能全,是当前主流推荐;
- Ammo.js:Bullet 引擎的 WASM 版本,功能最强,支持软体、载具、破碎,适合高精度仿真。
九、工程实践篇(补充重点)
62. 什么是色调映射(Tone Mapping)?为什么需要它?
答案:
色调映射是将高动态范围(HDR)的颜色值映射到低动态范围(LDR)显示器可显示范围(0~1)的过程。
为什么需要:
- PBR 渲染中,光照计算可能产生超过 1.0 的高亮度值(如强光、金属反射);
- 直接截断会导致高光区域纯白、细节丢失;
- 色调映射通过曲线压缩高光,保留更多细节,画面更真实。
Three.js 中的使用:
renderer.toneMapping = THREE.ACESFilmicToneMapping; // 电影级色调映射,最常用
renderer.toneMappingExposure = 1.0; // 曝光强度
常用色调映射模式:
NoToneMapping:不映射LinearToneMapping:线性映射ACESFilmicToneMapping:电影级 ACES 映射(推荐,效果最佳)CineonToneMapping:Cineon 映射ReinhardToneMapping:Reinhard 映射
63. 抗锯齿有哪些方法?分别有什么特点?
答案:
| 方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| MSAA | 多重采样抗锯齿,硬件支持 | 质量好,性能尚可 | 显存占用高,不适用于延迟渲染 |
| FXAA | 屏幕空间快速近似抗锯齿 | 性能好,兼容性强 | 画面略模糊 |
| SMAA | 增强型子像素形态抗锯齿 | 质量介于 MSAA 和 FXAA 之间 | 性能开销中等 |
| SSAA | 超采样抗锯齿 | 质量最高 | 性能开销极大 |
| TAA | 时间抗锯齿 | 质量好,性能好 | 快速移动物体有拖影 |
Three.js 中的使用:
// 方法1:渲染器原生 MSAA(推荐,性能最好)
const renderer = new THREE.WebGLRenderer({ antialias: true });
// 方法2:后处理 FXAA
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js';
const fxaaPass = new ShaderPass(FXAAShader);
// 方法3:后处理 SMAA
import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js';
const smaaPass = new SMAAPass();
注意:使用 EffectComposer 后处理时,渲染器的
antialias会失效,需要在 composer 中添加 FXAA 或 SMAA 通道。
64. WebGL 上下文丢失(Context Loss)怎么处理?
答案:
当 GPU 资源紧张或系统切换显卡时,浏览器可能丢失 WebGL 上下文,导致所有 GPU 资源(纹理、几何体、着色器)被清空。
处理方案:
// 1. 监听上下文丢失事件
canvas.addEventListener('webglcontextlost', (e) => {
e.preventDefault(); // 阻止默认行为
isContextLost = true;
console.log('WebGL 上下文丢失');
}, false);
// 2. 监听上下文恢复事件
canvas.addEventListener('webglcontextrestored', () => {
// 重新初始化所有资源
initScene();
initGeometries();
initMaterials();
initTextures();
isContextLost = false;
console.log('WebGL 上下文已恢复');
}, false);
// 3. 渲染循环中检查
function animate() {
if (!isContextLost) {
renderer.render(scene, camera);
}
requestAnimationFrame(animate);
}
预防措施:
- 避免创建过多 WebGL 上下文(多个 Canvas 同时渲染);
- 及时释放不用的资源,降低 GPU 内存压力;
- 复杂场景考虑使用
forceContextLoss()主动释放上下文。
65. Three.js 如何做响应式设计?
答案:
响应式设计的核心是:窗口大小变化时,同步更新相机宽高比和渲染器尺寸。
function onResize() {
const width = window.innerWidth;
const height = window.innerHeight;
// 1. 更新相机宽高比
camera.aspect = width / height;
camera.updateProjectionMatrix();
// 2. 更新渲染器尺寸
renderer.setSize(width, height);
// 3. 更新像素比(防止移动端模糊)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// 注意:pixelRatio 不要超过 2,否则性能开销过大
// 4. 如果用了后处理,也要更新 composer 尺寸
if (composer) composer.setSize(width, height);
}
window.addEventListener('resize', onResize);
注意事项:
setPixelRatio建议限制在 2 以内,移动端高 DPI 屏幕开 3 会导致性能问题;- 如果用了
EffectComposer,必须同步更新 composer 的尺寸; - ResizeObserver 比 window.resize 更精准,适合容器尺寸变化的场景。
66. 什么是雾效(Fog)?有什么作用?
答案:
雾效是模拟大气透视的效果,物体距离相机越远,越融入背景色,最终完全被雾色覆盖。
作用:
- 提升真实感:模拟真实大气效果,远处物体自然淡化;
- 优化性能:远处物体被雾遮挡,可以降低渲染精度或提前剔除;
- 掩盖场景边界:避免远处物体突然消失的突兀感。
Three.js 中的使用:
// 1. 线性雾:从 near 开始,到 far 完全雾化
scene.fog = new THREE.Fog(0x000000, 1, 50); // 颜色, near, far
// 2. 指数雾:随距离指数衰减,更自然
scene.fog = new THREE.FogExp2(0x000000, 0.02); // 颜色, 密度
// 3. 雾色应与背景色一致,效果最自然
scene.background = new THREE.Color(0x000000);
注意:只有
MeshStandardMaterial、MeshBasicMaterial等支持雾效,ShaderMaterial需要手动在着色器中实现 fog 计算。
67. Three.js r152+ 颜色管理有什么重大变化?
答案:
Three.js r152 版本开始默认开启颜色管理(Color Management),这是渲染质量的重要升级。
核心变化:
- 默认线性工作流:所有颜色计算在线性空间进行,结果更准确;
- 默认 sRGB 输出:
renderer.outputColorSpace默认为SRGBColorSpace; - 颜色输入自动转换:
THREE.Color默认按 sRGB 解释,自动转换为线性空间。
迁移注意事项:
// r152 之前(旧写法)
renderer.outputEncoding = THREE.sRGBEncoding;
texture.encoding = THREE.sRGBEncoding;
// r152 之后(新写法)
renderer.outputColorSpace = THREE.SRGBColorSpace;
texture.colorSpace = THREE.SRGBColorSpace;
常见问题:
- 升级后画面变亮/变暗:检查贴图的 colorSpace 设置是否正确;
- 颜色贴图(漫反射、自发光)应设为
SRGBColorSpace; - 数据贴图(法线、粗糙度、金属度)保持线性空间(默认值)。
68. 如何创建自定义几何体?
答案:
通过 BufferGeometry 手动构建顶点数据,可以创建任意形状的几何体。
// 创建一个自定义三角形
const geometry = new THREE.BufferGeometry();
// 1. 定义顶点位置(3个顶点,每个3个分量)
const vertices = new Float32Array([
0.0, 1.0, 0.0, // 顶点1
-1.0, -1.0, 0.0, // 顶点2
1.0, -1.0, 0.0 // 顶点3
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
// 2. 定义顶点颜色(可选)
const colors = new Float32Array([
1.0, 0.0, 0.0, // 红
0.0, 1.0, 0.0, // 绿
0.0, 0.0, 1.0 // 蓝
]);
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
// 3. 定义法线(可选,但受光材质必需)
const normals = new Float32Array([
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0
]);
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
// 4. 计算法线(如果没有手动定义)
// geometry.computeVertexNormals();
const material = new THREE.MeshBasicMaterial({ vertexColors: true });
const mesh = new THREE.Mesh(geometry, material);
注意事项:
- 顶点顺序决定面的正反面(逆时针为正面);
- 复杂几何体建议使用索引(
setIndex)减少顶点数据; - 修改顶点数据后需设置
attributes.position.needsUpdate = true。
总结
本文从基础概念、相机控制、几何材质、光照阴影、模型加载、交互动画、性能优化、高级底层、工程实践九个维度,系统梳理了 Three.js 面试的高频考点。核心要点回顾:
| 篇章 | 核心考点 |
|---|---|
| 基础概念 | WebGL 关系、三大组件、坐标系、场景图、Object3D |
| 相机控制 | 透视/正交相机、near/far、OrbitControls 球坐标原理 |
| 几何材质 | BufferGeometry、PBR 材质、纹理贴图、UV/法线、颜色空间 |
| 光照阴影 | 光源类型、阴影三步配置、阴影优化 |
| 模型加载 | glTF/GLB 优势、格式对比、加载优化、文字方案 |
| 交互动画 | Raycaster、BVH 优化、骨骼动画、LOD、天空盒 |
| 性能优化 | Draw Call、合并/实例化、内存释放、视锥剔除、粒子优化 |
| 高级底层 | 渲染流水线、MVP 矩阵、深度测试、着色器、后处理 |
| 工程实践 | 色调映射、抗锯齿、上下文丢失、响应式、雾效、颜色管理 |
掌握这些内容,可以覆盖初中高级 Three.js 岗位的核心面试要求。建议结合实际项目练习,加深对每个知识点的理解。