Three.js 全栈开发深度面试指南 (精通版)

52 阅读28分钟

本文档旨在为高级图形开发工程师提供一份深度的面试指南。不同于基础的 API 问答,本指南侧重于底层原理图形学算法GPU 架构以及工程化最佳实践


一、 图形学基础与数学原理 (Q1-Q15)

Q1: 请从底层矩阵变换的角度,详述从“局部坐标”到“屏幕像素”的完整流水线。

答案要点:

  • MVP 变换
    1. Model Matrix (模型矩阵)Local Space -> World Space。包含平移、旋转、缩放。
    2. View Matrix (视图矩阵)World Space -> View/Camera Space。相机的逆变换,将世界转换到相机视野下。
    3. Projection Matrix (投影矩阵)View Space -> Clip Space (裁剪空间)。进行透视除法前的准备,定义视锥体。
  • 透视除法 (Perspective Divide):将 Clip Space 坐标 (x, y, z, w) 除以 w,得到 NDC (标准化设备坐标),范围 [-1, 1]
  • 视口变换 (Viewport Transform)NDC -> Screen Space。映射到 Canvas 的像素坐标 (x, y)
  • 光栅化 (Rasterization):GPU 将三角形离散化为片元 (Fragment),并进行插值。

Q2: 欧拉角 (Euler Angles) 与四元数 (Quaternion) 的底层区别及应用场景?

答案要点:

  • 欧拉角
    • 原理:按顺序绕 X、Y、Z 轴旋转。
    • 缺陷万向节死锁 (Gimbal Lock)。当中间轴旋转 90° 时,导致第一轴与第三轴重合,丢失一个自由度。
  • 四元数
    • 原理:使用 (x, y, z, w) 表示旋转,本质是复数在 4D 空间的扩展。
    • 优势:无死锁、Slerp (球形插值) 平滑、矩阵转换效率高。
  • 实践:Three.js 中 Object3D.rotation 是欧拉角,但底层 Object3D.quaternion 才是核心。骨骼动画、飞行模拟必须使用四元数。

Q3: 什么是法线矩阵 (Normal Matrix)?为什么计算法线不能直接使用模型矩阵?

答案要点:

  • 问题:如果模型矩阵包含非均匀缩放(如 x 轴放大 2 倍),直接使用模型矩阵变换法线,会导致法线不再垂直于表面。
  • 原理:法线矩阵是模型矩阵左上角 3x3 子矩阵的逆转置矩阵 ((M^-1)^T)。
  • Three.js:在 Shader 中通常通过 normalMatrix 传入,用于将法线从 View Space 正确变换。

Q4: 深度缓冲 (Z-Buffer) 的工作原理是什么?什么是 Z-Fighting?

答案要点:

  • 原理:GPU 维护一个深度纹理,存储每个像素当前的深度值 (0.0~1.0)。
  • 深度测试:新片元的深度值 vs 缓冲中的值。如果新值更近(通常 <),则通过测试并更新缓冲;否则丢弃。
  • Z-Fighting (深度冲突)
    • 现象:两个面距离极近,因浮点数精度限制,深度值交替占优,导致闪烁。
    • 解决
      1. 增大两面距离。
      2. 调整相机 nearfar 平面(near 越远,far 越近,精度越高)。
      3. 使用 对数深度缓冲 (Logarithmic Depth Buffer)
      4. 设置 polygonOffset

Q5: 视锥体剔除 (Frustum Culling) 的算法逻辑是怎样的?

答案要点:

  • 核心:判断物体的包围体(AABB 包围盒或包围球)是否与视锥体(由 6 个平面组成的截断金字塔)相交。
  • 流程
    1. 更新物体的世界矩阵。
    2. 计算物体的包围球 (BoundingSphere) 并应用世界变换。
    3. 遍历视锥体的 6 个平面,判断球体是否在所有平面的“内侧”。
  • 优化:Three.js 使用层级剔除,先剔除父节点,若父节点不可见,子节点直接跳过。

Q6: 什么是齐次坐标 (Homogeneous Coordinates)?为什么 w 分量很重要?

答案要点:

  • 定义:用 4 维向量 (x, y, z, w) 表示 3D 坐标。
  • 作用
    1. 统一变换:使得平移(Translation)可以用矩阵乘法表示(线性变换)。
    2. 透视投影w 分量存储了顶点的深度信息(距离相机的远近)。在透视除法中,x/w, y/w 实现了“近大远小”的效果。

Q7: 射线检测 (Raycasting) 的数学原理?如何优化大量物体的检测性能?

答案要点:

  • 原理
    1. Unproject:将屏幕坐标 (NDC) 逆变换回世界空间,得到射线原点和方向。
    2. Intersection:计算射线与包围球/盒的交点(快速),若相交再计算与三角形的交点(慢速,重心坐标插值)。
  • 优化
    1. BVH (Bounding Volume Hierarchy):使用 three-mesh-bvh 库构建空间索引,将复杂度从 O(N) 降至 O(logN)。
    2. 层级检测:只检测特定 Layers 的物体。
    3. 粗略检测:先检测包围球,命中后再检测几何体。

Q8: 什么是 Draw Call?为什么它是性能瓶颈?

答案要点:

  • 定义:CPU 通知 GPU 绘制一个图元列表的命令(如 glDrawElements)。
  • 瓶颈原因
    • CPU 开销:每次 Draw Call 前,CPU 需要设置渲染状态(Shader, Texture, Buffer 绑定),验证数据,提交命令。
    • 状态切换:频繁切换材质和几何体导致 GPU 流水线停顿。
  • 优化:Instancing(实例化)、Merging(合并)、Texture Atlas(纹理图集)。

Q9: 解释 UV 映射 (UV Mapping) 及其在纹理采样中的作用。

答案要点:

  • 定义:将 2D 纹理坐标 (u, v) (范围 0~1) 映射到 3D 模型的顶点上。
  • 采样:Fragment Shader 使用插值后的 UV 坐标,通过 texture2D(map, uv) 读取纹理颜色。
  • Wrap Mode:决定 UV 超出 0~1 范围时的行为(Repeat, ClampToEdge, MirroredRepeat)。

Q10: 什么是切线空间 (Tangent Space)?法线贴图为何是蓝紫色的?

答案要点:

  • 切线空间:基于模型表面的局部坐标系。由 Normal (法线), Tangent (切线), Bitangent (副切线) 组成。
  • 法线贴图:存储的是切线空间下的法线扰动向量。
    • 平坦表面的法线是 (0, 0, 1)(指向外)。
    • 映射到 RGB 空间即 (0.5, 0.5, 1.0),呈现浅蓝色
  • 优势:法线贴图可以复用于变形的网格(如骨骼动画),因为它是相对于表面的。

二、 Three.js 核心架构与资源管理 (Q11-Q25)

Q11: 深入解析 Three.js 的渲染循环 (Render Loop) 与 requestAnimationFrame。

答案要点:

  • rAF:浏览器提供的 API,通常以 60fps 运行,在后台标签页自动暂停以省电。
  • Three.js 流程
    1. renderer.render(scene, camera) 被调用。
    2. Update Matrix:遍历场景图,更新所有物体的 matrixWorld
    3. Frustum Culling:剔除视锥体外的物体。
    4. Sort:不透明物体按从近到远排序(减少 Overdraw),透明物体从远到近排序(混合正确)。
    5. Render:按顺序提交 Draw Call。

Q12: Three.js 的资源管理机制:dispose() 到底做了什么?

答案要点:

  • JS vs GPU:JS 的 GC 无法回收 GPU 显存。
  • dispose()
    • Geometry:删除 WebGLBuffer (gl.deleteBuffer)。
    • Texture:删除 WebGLTexture (gl.deleteTexture)。
    • Material:清理关联的 ShaderProgram 引用。
    • RenderTarget:删除 Framebuffer。
  • 最佳实践:切换场景时,必须递归遍历 Scene,手动 dispose 所有资源,否则会导致严重的内存泄漏

Q13: BufferGeometry 的底层数据结构:InterleavedBuffer 有什么优势?

答案要点:

  • 普通 BufferAttribute:Position, Normal, UV 分别存放在独立的 ArrayBuffer 中。
  • InterleavedBuffer (交错缓冲):所有数据紧凑存放在同一个 Buffer 中(如 P,P,P,N,N,N,U,V...)。
  • 优势:提高 GPU 缓存命中率 (Cache Locality)。当 GPU 读取顶点位置时,法线和 UV 数据已经在缓存行中,减少内存带宽消耗。

Q14: 什么是 UniformBufferObject (UBO)?Three.js 如何支持?

答案要点:

  • 问题:全局变量(如相机矩阵、光照信息)在每个 Shader 中都需要重复上传,开销大。
  • UBO:在 GPU 显存中开辟一块共享区域,所有 Shader 都可以读取。
  • 优势:只需上传一次数据,切换 Shader 时无需重新绑定这些 Uniforms,大幅降低 CPU 开销。
  • Three.js:在 WebGL 2.0 环境下自动使用 UBO 管理全局 Uniforms。

Q15: ShaderMaterial 与 RawShaderMaterial 的底层区别?

答案要点:

  • ShaderMaterial
    • 预处理:Three.js 会自动注入内置 Uniforms (modelViewMatrix, projectionMatrix) 和 Attributes。
    • Chunks:支持 #include <common> 等代码片段复用。
    • Defines:根据材质属性(如 map, fog)自动添加 #define USE_MAP
  • RawShaderMaterial
    • 纯净:不注入任何代码。开发者完全掌控 GLSL。
    • 用途:编写不依赖 Three.js 系统的独立 Shader,或移植其他引擎的 Shader。

Q16: onBeforeCompile 的作用及使用场景?

答案要点:

  • 痛点:想要微调标准材质(如 MeshStandardMaterial)的效果(如增加顶点波浪),但不想重写几千行 PBR Shader 代码。
  • 机制:在 Shader 编译前,以字符串形式拦截并修改 GLSL 代码。
  • 操作:使用 shader.vertexShader.replace('#include <begin_vertex>', myCustomCode) 注入自定义逻辑。

Q17: 什么是 WebGLRenderTarget?FBO 在后处理中的应用?

答案要点:

  • FBO (Frame Buffer Object):允许渲染到纹理而不是屏幕。
  • Ping-Pong 技术:在后处理(如高斯模糊)中,需要多次 Pass。通常使用两个 RenderTarget (Read/Write) 交替读写,避免读写同一纹理导致的未定义行为。
  • 应用:GPGPU 粒子系统、动态环境映射、阴影贴图生成。

Q18: 解释 InstancedMesh 的实现原理与限制。

答案要点:

  • 原理:利用 ANGLE_instanced_arrays 扩展(WebGL 2 原生)。
    • Geometry:只上传一份顶点数据。
    • Attributes:上传一份包含所有实例变换矩阵(位置/旋转/缩放)的 Buffer。
    • Draw:调用 glDrawElementsInstanced 一次性绘制。
  • 限制:所有实例必须共享同一个 Geometry 和 Material。
  • Frustum Culling:默认只检测整体包围球。若需单独剔除,需自行实现计算逻辑。

Q19: 什么是 MRT (Multiple Render Targets)?

答案要点:

  • 定义:一次 Draw Call 同时输出到多个纹理(Color Attachments)。
  • 应用延迟渲染 (Deferred Rendering)
    • 在 G-Buffer Pass 中,同时输出 Position, Normal, Albedo, Roughness 到不同的纹理。
    • 在 Lighting Pass 中,读取这些纹理进行光照计算。

Q20: 纹理压缩 (Texture Compression) 的格式选择策略?

答案要点:

  • 原理:GPU 硬件直接支持解码,无需 CPU 解压,显存占用通常是原始图片的 1/4 或 1/8。
  • 格式
    • ASTC:最新标准,质量高,支持移动端和桌面端(覆盖面广)。
    • ETC2:Android/iOS 通用。
    • BC7/DXT:桌面端 PC 专用。
  • Basis Universal (.basis/.ktx2):一种中间格式,运行时转码为当前 GPU 支持的格式,最佳跨平台方案。

三、 渲染管线与 PBR 材质 (Q21-Q35)

Q21: 详述 PBR (Physically Based Rendering) 的能量守恒与微表面理论。

答案要点:

  • 能量守恒:出射光能量 <= 入射光能量。Diffuse + Specular <= 1.0
  • 微表面理论:表面由无数微小的镜面组成。
    • Roughness (粗糙度):决定微表面的朝向离散程度。越粗糙,高光越扩散。
    • Fresnel (菲涅尔效应):视线与表面越平行,反射率越高(水面、玻璃侧面反光强)。
    • BRDF:双向反射分布函数,Three.js 使用 Cook-Torrance 模型。

Q22: Metalness (金属度) 工作流的本质区别是什么?

答案要点:

  • 非金属 (Dielectric)
    • 漫反射颜色 = Albedo。
    • 高光反射率 (F0) 固定为 0.04 (4%)。
  • 金属 (Conductor)
    • 没有漫反射 (Diffuse = 0)。所有光都被吸收或反射。
    • 高光反射率 = Albedo 颜色 (60%~90%)。
  • Shader 实现:通过 mix() 函数根据 metalness 在这两套逻辑间插值。

Q23: 什么是 Tone Mapping (色调映射)?为什么 HDR 渲染必不可少?

答案要点:

  • 问题:物理光照计算的结果范围是 [0, ∞) (HDR),而显示器只能显示 [0, 1] (LDR)。直接截断会导致亮部过曝死白。
  • 解决:使用映射曲线将高动态范围压缩到 [0, 1]
  • 算法
    • Reinhard:简单压缩,色彩较灰。
    • ACESFilmic:电影工业标准,对比度高,保留亮部细节和色彩饱和度(Three.js 推荐)。

Q24: Gamma 校正 (Gamma Correction) 的全流程是怎样的?

答案要点:

  • 输入:纹理图片通常是 sRGB (非线性) 存储的。
  • 计算:Shader 中必须将 sRGB 解码为 Linear 空间进行物理计算 (pow(color, 2.2))。
  • 输出:最终颜色输出到屏幕前,必须编码回 sRGB (pow(color, 1/2.2))。
  • Three.js:设置 renderer.outputColorSpace = THREE.SRGBColorSpace 自动处理输出编码。

Q25: 阴影贴图 (Shadow Map) 的 Bias 问题与 Peter Panning 现象。

答案要点:

  • Shadow Acne (痤疮):由于深度图精度不够,物体表面错误地遮挡了自己,产生条纹。
    • 解决:增加 bias,将阴影深度稍微推远。
  • Peter Panning (悬浮)bias 过大,导致阴影与物体根部脱离,像彼得潘一样悬浮。
    • 解决:使用 normalBias(沿法线方向偏移),比单纯的深度偏移效果更好。

Q26: 什么是 IBL (Image Based Lighting)?它如何提升真实感?

答案要点:

  • 原理:将环境贴图(HDR)视为一个巨大的球形光源。
  • 组成
    • Irradiance Map:漫反射环境光(模糊的环境图)。
    • Radiance Map:镜面反射环境光(不同粗糙度的多级 Mipmap)。
  • 作用:提供复杂的环境反射和漫反射,是 PBR 材质看起来“真实”的关键,尤其是金属材质。

Q27: 解释 AO (Ambient Occlusion) 环境光遮蔽的原理。

答案要点:

  • 目的:模拟角落、缝隙等光线难以到达区域的阴影,增强体积感。
  • 类型
    • Static AO:烘焙在纹理中 (aoMap)。
    • SSAO (Screen Space AO):基于屏幕深度缓冲的实时后处理算法,计算开销较大。

Q28: 透明物体渲染的排序问题 (Transparency Sorting)。

答案要点:

  • 机制:透明物体不写深度 (Depth Write Off),只进行深度测试。必须按从远到近的顺序渲染,才能正确混合颜色。
  • 问题:当透明物体相互穿插,或形状复杂(凹多面体)时,基于中心的排序会失效,导致渲染错误。
  • 解决
    1. Depth Peeling:多遍渲染剥离深度(开销大)。
    2. OIT (Order Independent Transparency):与顺序无关的透明技术(如 Weighted Blended)。
    3. Alpha Test:对于树叶等,直接 discard 透明像素,当作不透明物体渲染。

Q29: 什么是延迟渲染 (Deferred Rendering)?与前向渲染 (Forward) 的对比。

答案要点:

  • Forward:每个物体对每个光源计算一次光照。复杂度 O(NumObjects * NumLights)。光源多时性能崩塌。
  • Deferred
    1. G-Buffer Pass:渲染几何信息(位置、法线、颜色)到纹理。
    2. Lighting Pass:对屏幕每个像素计算光照。复杂度 O(ScreenWidth * Height * NumLights)
  • 优缺:延迟渲染支持成百上千光源,但难以处理透明物体和 MSAA。

Q30: 什么是 Light Probe (光照探针) 和 Lightmap (光照贴图)?

答案要点:

  • Lightmap:将静态光照(阴影、GI)烘焙到纹理上。效果最好,但物体不能动。
  • Light Probe:在空间中布置探针点,预计算球谐函数 (Spherical Harmonics)。动态物体经过时,插值周围探针的光照数据。性能极高,适合动态物体。

四、 性能优化与工程化 (Q31-Q45)

Q31: 如何系统性地分析 Three.js 的性能瓶颈?

答案要点:

  • CPU 瓶颈
    • 现象:帧率低,但 GPU 占用不高。
    • 原因:Draw Calls 太多、几何体更新频繁、物理计算过重。
    • 工具:Chrome Performance Tab。
  • GPU 瓶颈
    • 现象:分辨率降低后帧率显著提升。
    • 原因:Fragment Shader 过重(像素填充率)、纹理过大、后处理太多。
    • 工具:Spector.js, RenderDoc。

Q32: 减少 Draw Calls 的终极方案:Merge vs Instancing vs Batching。

答案要点:

  • Merge:合并几何体。适合完全静态、材质相同的物体。缺点是内存占用增加,无法单独剔除。
  • Instancing:实例化。适合网格相同、材质相同但变换不同的物体(草地、树木)。性能最佳。
  • Batching (BatchedMesh):Three.js 新特性。允许合并不同几何体,且能动态更新每个子物体的矩阵。比 Merge 更灵活,比 Instancing 适用范围更广。

Q33: 纹理优化的最佳实践:尺寸、格式与 Mipmap。

答案要点:

  • POT (Power of Two):宽高为 2 的幂次方(如 1024),是启用 Mipmap 的前提(WebGL 1)。
  • Mipmap:以 33% 的额外显存换取渲染性能和画质(减少摩尔纹)。
  • Atlas (图集):将多张小纹理合并为一张大图,减少材质切换和 Draw Calls。
  • 各向异性过滤:只在地面等大角度倾斜表面开启,消耗 GPU 性能。

Q34: 几何体优化:LOD (Level of Detail) 与 Draco 压缩。

答案要点:

  • LOD:根据距离动态切换高/中/低模。减少远处的顶点着色器开销。
  • Draco:Google 的几何体压缩算法。
    • 优点:GLB 体积减少 50%~90%。
    • 代价:加载时需要 WASM 解码,增加 CPU 初始化时间。适合网络带宽瓶颈场景。

Q35: 什么是 OffscreenCanvas?如何在 WebWorker 中渲染?

答案要点:

  • 原理:将 Canvas 的控制权转移给 WebWorker。
  • 优势:渲染循环与主线程(UI、DOM 操作)分离。即使主线程卡顿(如 React 复杂更新),3D 动画依然流畅。
  • 限制:DOM 事件(鼠标点击)需要从主线程转发给 Worker。

Q36: 内存泄漏排查:如何彻底销毁一个 Three.js 场景?

答案要点:

  • 递归遍历scene.traverse()
  • 清理资源
    • geometry.dispose()
    • material.dispose()
    • material.map.dispose() (以及 normalMap, roughnessMap 等所有贴图)
  • 移除引用scene.remove(object),并将变量置为 null
  • 渲染器renderer.dispose()renderer.forceContextLoss()

Q37: 移动端 H5 3D 项目的特定优化策略。

答案要点:

  • DPR 限制renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))。避免在 3x 屏上渲染过高分辨率。
  • 精度:Shader 中使用 mediumplowp
  • 后处理:尽量少用或不用 Bloom、DOF 等昂贵特效。
  • 阴影:使用烘焙贴图代替实时阴影。

Q38: 什么是 Shader 编译卡顿 (Shader Compilation Jitter)?如何解决?

答案要点:

  • 现象:游戏开始或新物体出现时,画面卡顿一下。
  • 原因:GPU 首次遇到新 Shader 需要编译。
  • 解决预编译 (Pre-compiling)
    • 在 Loading 阶段,将所有材质强制渲染一次(哪怕是渲染到 1x1 的不可见 RenderTarget),强制 GPU 提前编译 Shader。
    • 使用 renderer.compile(scene, camera) API。

Q39: 什么是 Object Pooling (对象池)?

答案要点:

  • 场景:粒子发射、子弹射击等高频创建/销毁。
  • 问题:频繁 new 和 GC 会导致帧率波动。
  • 实现:预先创建数组 [bullet1, bullet2...]。使用时标记 active=true,用完后标记 active=false 并重置位置,而不是销毁。

Q40: 如何优化大型场景的加载速度?

答案要点:

  • 增量加载:先加载低模或包围盒,用户走近时流式加载高模。
  • 二进制格式:使用 GLB 代替 GLTF/OBJ。
  • 并行加载:使用 Promise.all 并发请求资源。
  • Service Worker:缓存静态资源。

五、 WebGPU 与前沿技术 (Q41-Q50)

Q41: WebGPU 相比 WebGL 的核心架构变革是什么?

答案要点:

  • 无状态 (Stateless):WebGL 是状态机(全局状态容易错乱),WebGPU 使用 Pipeline State Object (PSO),预先定义好所有状态,切换开销极低。
  • 低开销:更薄的驱动层,直接映射 Vulkan/Metal/DX12。
  • 多线程:支持在 Worker 线程录制 CommandEncoder,主线程只负责提交。

Q42: Compute Shader (计算着色器) 为什么是革命性的?

答案要点:

  • 通用计算:不再局限于画三角形。可以处理任意数据。
  • 并行性:利用 GPU 的数千个核心进行物理模拟(布料、流体)、群组行为(Boids)、剔除计算。
  • Storage Buffer:允许读写巨大的结构化数据,打破了 WebGL 纹理尺寸的限制。

Q43: Three.js 的 TSL (Three Shading Language) 解决了什么问题?

答案要点:

  • 跨后端:开发者用 JS 写节点逻辑,Three.js 自动编译为 WGSL (WebGPU) 或 GLSL (WebGL)。
  • 模块化:基于节点的 Shader 编写,比字符串拼接更易维护、复用和 Tree-shaking。
  • 未来:是 Three.js 全面转向 WebGPU 的基石。

Q44: 什么是混合渲染 (Hybrid Rendering)?

答案要点:

  • 结合 光栅化 (Rasterization)光线追踪 (Ray Tracing)
  • 利用光栅化快速渲染几何体,利用光线追踪计算精确的反射、折射和软阴影。
  • WebGPU 目前暂未正式开放光追扩展,但未来可期。

Q45: 什么是 Gaussian Splatting (高斯泼溅)?

答案要点:

  • 原理:用数百万个带颜色的 3D 高斯椭球体表示场景。
  • 优势:从照片重建 3D 场景的速度和质量远超传统摄影测量,且渲染速度极快(实时)。
  • Three.js:已有第三方加载器支持 .splat 文件渲染。

六、 进阶数学与算法 (Q46-Q60)

Q46: 如何判断点在三角形内部?(重心坐标)

答案要点:

  • 重心坐标 (Barycentric Coordinates):三角形内任意一点 P 可表示为 P = uA + vB + wC,其中 u+v+w=1u,v,w >= 0
  • 应用:射线检测交点计算、属性插值(颜色、法线)。

Q47: 什么是 SDF (Signed Distance Field) 符号距离场?

答案要点:

  • 定义:空间中任意一点到最近物体表面的距离。内部为负,外部为正。
  • 应用
    • Raymarching:渲染体积云、分形几何。
    • 字体渲染:MSDF (Multi-channel SDF) 实现无限放大不失真的文本。

Q48: 什么是 OBB (Oriented Bounding Box)?如何进行碰撞检测?

答案要点:

  • OBB:有向包围盒,随物体旋转,比 AABB 更紧凑。
  • 分离轴定理 (SAT):如果能找到一个轴,使得两个凸多面体在该轴上的投影不重叠,则它们不相交。检测 OBB 需要测试 15 个轴。

Q49: 四元数的 Slerp (球面线性插值) 原理。

答案要点:

  • Lerp:线性插值,路径是弦,会导致旋转速度不均匀。
  • Slerp:沿 4D 球面的大圆弧插值。保证角速度恒定,是 3D 旋转动画的标准插值方法。

Q50: 什么是噪声算法 (Perlin Noise / Simplex Noise)?

答案要点:

  • 特点:伪随机、连续、平滑。
  • 应用:程序化生成地形(高度图)、云层纹理、水面波纹、溶解特效。

Q51: 什么是八叉树 (Octree)?在 3D 中的应用?

答案要点:

  • 结构:将 3D 空间递归划分为 8 个子立方体。
  • 应用
    • 视锥体剔除:快速剔除大片不可见区域。
    • 碰撞检测:只检测物体所在区域的三角形,大幅提升物理引擎性能。

Q52: 什么是骨骼动画的蒙皮 (Skinning) 算法?

答案要点:

  • 矩阵调色板:将所有骨骼的变换矩阵传入 Shader。
  • 顶点计算FinalPos = sum(BoneMatrix[i] * InitialPos * Weight[i])
  • 限制:通常限制每个顶点最多受 4 根骨骼影响(Attributes 限制)。

Q53: 什么是切线空间法线贴图 (Tangent Space Normal Mapping)?

答案要点:

  • TBN 矩阵:由 Tangent, Bitangent, Normal 组成的矩阵,用于将切线空间的法线变换到世界空间。
  • 计算:在 Fragment Shader 中构建 TBN 矩阵,解码法线贴图颜色,应用变换。

Q54: 什么是 Shadow Mapping 的 PCF (Percentage-Closer Filtering)?

答案要点:

  • 原理:不只采样阴影图中对应的一个点,而是采样周围(如 3x3 或 5x5)区域。
  • 结果:计算平均遮挡值,产生边缘柔和的阴影,消除锯齿。

Q55: 什么是 BRDF (双向反射分布函数)?

答案要点:

  • 定义:描述光线射入表面后,向各个方向反射的比例。f(l, v)
  • PBR:Cook-Torrance BRDF 包含漫反射项和镜面反射项(DFG 项:分布、菲涅尔、几何遮蔽)。

Q56: 什么是 HDR 的 IBL (Image Based Lighting) 卷积?

答案要点:

  • 漫反射卷积:预计算 Irradiance Map,解决漫反射积分。
  • 镜面反射卷积:预计算 Prefiltered Environment Map (Split Sum Approximation),解决不同粗糙度的镜面反射。

Q57: 什么是 SSAO (Screen Space Ambient Occlusion)?

答案要点:

  • 原理:在屏幕空间,根据深度缓冲,采样像素周围的随机点。如果周围点大多深度更浅(被遮挡),则该像素变暗。
  • 优点:与场景复杂度无关,实时性好。

Q58: 什么是 FXAA (Fast Approximate Anti-Aliasing)?

答案要点:

  • 原理:后处理滤镜。检测图像中的边缘(高对比度区域),沿边缘方向进行模糊混合。
  • 优缺:极快,但会导致画面整体轻微模糊,无法处理子像素移动闪烁。

Q59: 什么是 TAA (Temporal Anti-Aliasing)?

答案要点:

  • 原理:利用上一帧的渲染结果。通过抖动投影矩阵,每帧采样像素内的不同位置,混合历史帧。
  • 优缺:抗锯齿效果极佳,但会有“鬼影” (Ghosting) 现象,需要运动向量 (Motion Vectors) 进行重投影修正。

Q60: 什么是 Bloom (辉光) 的实现流程?

答案要点:

  • 阈值提取:提取画面中亮度超过阈值的像素。
  • 高斯模糊:对提取的亮部进行多次降采样和高斯模糊。
  • 混合:将模糊后的亮部叠加回原图。

七、 综合实战与架构设计 (Q61-Q75)

Q61: 设计一个 3D 地图系统的架构(如智慧城市)。

答案要点:

  • 瓦片化 (Tiling):将地图切分为金字塔瓦片(类似 Google Maps),按需加载。
  • 坐标精度:解决浮点数抖动(RTC - Relative To Center),将渲染原点移动到相机附近。
  • 数据压缩:建筑物使用 ExtrudeGeometry 合并,或 GPU Instancing。
  • 流式加载:LRU 缓存策略,卸载不可见瓦片。

Q62: 如何实现一个高性能的粒子系统(如 100 万粒子)?

答案要点:

  • CPU 方案:不可行。
  • WebGL 方案:GPGPU。使用 DataTexture 存储位置/速度,Fragment Shader 更新物理状态,Vertex Shader 读取纹理渲染。
  • WebGPU 方案:Compute Shader 更新 Storage Buffer,Render Pipeline 直接绘制。

Q63: 如何实现 3D 模型的拆解/爆炸效果?

答案要点:

  • 预处理:确保模型由独立的 Mesh 或独立的 Face 组成。
  • 动画:计算每个部件的包围盒中心,沿中心向外插值位置。
  • Shader:在 Vertex Shader 中根据 attribute centeruniform progress 偏移顶点。

Q64: 如何实现“传送门”效果?

答案要点:

  • 渲染到纹理:在传送门位置放置一个摄像机,渲染目标场景到 RenderTarget。
  • 投影纹理:将 RenderTarget 作为纹理贴在传送门 Mesh 上,使用屏幕空间坐标采样。
  • 递归:如果传送门互相可见,需要递归渲染(通常限制次数)。

Q65: 如何实现体积云 (Volumetric Clouds)?

答案要点:

  • Raymarching:在 Fragment Shader 中沿视线步进。
  • 噪声采样:采样 3D 噪声纹理(Perlin/Worley)模拟密度。
  • 光照计算:Beer's Law (比尔定律) 计算光线穿透密度的衰减。

Q66: 如何优化骨骼动画的 CPU 开销?

答案要点:

  • GPU Skinning:Three.js 默认已做。
  • 动画烘焙 (Baking):将骨骼变换预计算并存储到浮点纹理中 (VAT - Vertex Animation Texture)。Vertex Shader 直接读取纹理,移除骨骼计算逻辑。

Q67: 如何实现贴花 (Decal) 系统(如弹孔)?

答案要点:

  • 投影几何体:裁剪目标物体与贴花包围盒相交的三角形,投影 UV。
  • 深度偏移polygonOffset 防止 Z-Fighting。
  • 延迟贴花:在延迟渲染管线中,通过投影矩阵直接修改 G-Buffer。

Q68: 如何实现无限大地形?

答案要点:

  • 网格复用:使用环形缓冲区 (Toroidal Array) 复用 Grid Mesh。
  • 顶点位移:Vertex Shader 中根据世界坐标采样高度图/噪声。
  • LOD:近处高密度网格,远处低密度。

Q69: 如何实现 3D 拾取(不使用 Raycaster)?

答案要点:

  • GPU Picking
    1. 为每个物体分配唯一的颜色 ID。
    2. 在离屏 RenderTarget 中渲染 ID 颜色。
    3. 读取鼠标位置像素的颜色,解码为 ID。
  • 优势:性能与物体数量无关,适合海量物体。

Q70: 如何处理透明物体的复杂排序(如头发)?

答案要点:

  • Alpha Test:不透明部分直接写入深度。
  • 多Pass:先渲染背面,再渲染正面。
  • OIT:加权混合 OIT (Weighted Blended OIT),不依赖排序的近似算法。

Q71: 什么是 DrawIndirect (间接绘制)?

答案要点:

  • 原理:绘制参数(顶点数、实例数)存储在 GPU Buffer 中,而不是由 CPU 指定。
  • 应用:GPU 剔除 (GPU Culling)。Compute Shader 计算可见性,修改 Draw 参数,渲染管线直接执行。实现零 CPU 剔除开销。

Q72: 如何实现动态切割模型?

答案要点:

  • CSG (Constructive Solid Geometry):布尔运算库(如 three-bvh-csg)。
  • Stencil:利用模板缓冲遮罩实现视觉上的切割(模型未真正改变)。
  • Shader Discard:根据世界坐标 discard 像素(空心效果)。

Q73: 什么是 Virtual Texture (虚拟纹理) / SVT?

答案要点:

  • 原理:将超大纹理切片,只加载当前视野可见的切片到显存。
  • 应用:超精细地形、巨型场景。

Q74: 如何实现即时全局光照 (Real-time GI)?

答案要点:

  • Light Propagation Volumes (LPV)
  • VXGI (Voxel Cone Tracing)
  • SSGI (Screen Space GI):基于屏幕空间的近似,Three.js 扩展示例中有实现。

Q75: 什么是 Cluster Lighting (集群光照)?

答案要点:

  • 问题:Forward Rendering 限制光源数量。
  • 解决:将视锥体划分为 3D 网格 (Clusters)。Compute Shader 计算每个 Cluster 受哪些光源影响。Fragment Shader 只计算当前 Cluster 内的光源。支持成千上万光源。

八、 补充深度问题 (Q76-Q100)

Q76: 解释 Projection Matrix 中的 Near 和 Far 对深度精度的影响。

答案要点:

  • 非线性深度:深度缓冲是非线性的,大部分精度集中在 Near 平面附近。
  • 影响:Near 太小或 Far 太大,会导致远处物体精度极低,产生 Z-Fighting。
  • 建议:Near 尽可能大,Far 尽可能小,或使用反向 Z-Buffer (Reversed Z)。

Q77: 什么是 Early-Z 和 Z-Prepass?

答案要点:

  • Early-Z:在 Fragment Shader 执行前进行深度测试,若失败则跳过 Shader,节省性能。
  • Z-Prepass:先只渲染深度(不计算颜色),生成完善的深度缓冲。再进行颜色渲染,最大化利用 Early-Z。

Q78: 什么是 Mipmap 的 Bias?

答案要点:

  • 控制采样 Mipmap 的层级。负值会让纹理更清晰(但可能闪烁),正值更模糊。

Q79: 什么是 Texture Atlas 的 Bleeding (出血) 问题?

答案要点:

  • 现象:采样时读取到了相邻子图的像素,导致边缘出现杂色。
  • 解决:子图之间保留 Padding(间隙)。

Q80: 什么是 Equirectangular Projection?

答案要点:

  • 将球体表面展开为 2:1 矩形的投影方式。常用于 360 度全景图。

Q81: 什么是 MorphTarget 的 Normal 变换问题?

答案要点:

  • 变形时,顶点位置变了,法线也需要随之插值,否则光照会错误。Three.js 自动处理,但需确保导出时包含 MorphNormals。

Q82: 什么是 SkinnedMesh 的 Bind Pose?

答案要点:

  • 骨骼绑定时的初始姿态。所有骨骼变换都是相对于 Bind Pose 的逆矩阵计算的。

Q83: 什么是 Animation 的 Blending Mode (Additive vs Override)?

答案要点:

  • Override:完全覆盖(如 走路 -> 跑步)。
  • Additive:叠加(如 跑步 + 射击动作)。

Q84: 什么是 Quaternion 的 Slerp vs Nlerp?

答案要点:

  • Slerp:球面插值,角速度恒定,计算重。
  • Nlerp:线性插值后归一化,角速度不匀,但计算快。小角度可用 Nlerp 近似。

Q85: 什么是 AABB 的合并算法?

答案要点:

  • min = min(box1.min, box2.min), max = max(box1.max, box2.max)。用于构建 BVH。

Q86: 什么是 Plane 的 Hessian Normal Form?

答案要点:

  • Ax + By + Cz + D = 0,其中 (A,B,C) 是法线。用于视锥体剔除计算点到平面的距离。

Q87: 什么是 Gamma Space 和 Linear Space 的混合错误?

答案要点:

  • 如果在 sRGB 空间做光照混合(如 0.5 + 0.5),结果物理不正确,亮度偏暗。必须在 Linear 空间计算。

Q88: 什么是 Premultiplied Alpha 的混合公式?

答案要点:

  • Blend = Src + Dst * (1 - SrcAlpha)。无需乘 SrcAlpha,因为已经乘过了。

Q89: 什么是 WebGL 的 Context Loss (上下文丢失)?

答案要点:

  • 原因:GPU 崩溃、驱动更新、占用过高。
  • 处理:监听 webglcontextlost 事件,取消动画循环;监听 webglcontextrestored,重新创建所有 WebGL 资源。

Q90: 什么是 High-DPI (Retina) 渲染的性能陷阱?

答案要点:

  • 像素数是 4 倍。Fragment Shader 开销激增。
  • 策略:对于复杂 3D 场景,限制 pixelRatio 为 1.5 或 2,或者动态调整。

Q91: WebGPU 的 BindGroup 是什么?

答案要点:

  • 将一组资源(Buffer, Texture, Sampler)打包绑定。
  • 减少 CPU 提交绑定的频率,类似 Vulkan 的 Descriptor Set。

Q92: WebGPU 的 WGSL 语言特点?

答案要点:

  • Rust 风格语法。
  • 强类型。
  • 内置矩阵、向量运算。

Q93: 什么是 Render Bundle (WebGPU)?

答案要点:

  • 预录制的渲染命令包。
  • 可以在每一帧重复执行,无需重新遍历场景图录制命令,极大降低 CPU 开销。

Q94: 什么是 Timestamp Query (WebGPU)?

答案要点:

  • 精确测量 GPU 执行命令的时间(纳秒级)。用于性能分析。

Q95: 什么是 Reverse Z (反向 Z)?

答案要点:

  • Near=1.0, Far=0.0。配合浮点深度缓冲。
  • 大幅提升远处的深度精度,彻底解决 Z-Fighting。WebGPU 推荐做法。

Q96: 什么是 Mesh Shader (未来技术)?

答案要点:

  • 取代传统的 Vertex/Geometry Shader 管线。
  • 以 Meshlet(小网格块)为单位进行处理和剔除,几何处理能力呈指数级提升。

Q97: 什么是 Variable Rate Shading (VRS)?

答案要点:

  • 对画面不同区域使用不同的着色分辨率。
  • 例如:中心区域全分辨率,边缘区域 1/2 分辨率。提升性能且视觉损失小。

Q98: 什么是 Nanite (UE5) 的核心原理?Web 能实现吗?

答案要点:

  • 原理:虚拟几何体。实时流式加载 Meshlet,根据屏幕误差动态选择 LOD,软件光栅化微小三角形。
  • Web:WebGPU Compute Shader 可以实现简化版。

Q99: 什么是 Lumen (UE5) 的核心原理?

答案要点:

  • 原理:基于 SDF 的软件光线追踪 + 屏幕空间探针。
  • Web:硬件光追普及前,SDF Tracing 是 Web 端实现动态 GI 的可行路径。

Q100: 作为图形工程师,如何保持技术敏感度?

答案要点:

  • 关注 SIGGRAPH 论文。
  • 关注 Three.js / Babylon.js GitHub 提交记录。
  • 学习原生图形 API (Vulkan/DX12/WebGPU)。
  • ShaderToy 练习。