Skip to main content

WebGL 进阶与核心考点

针对中高级前端/图形向面试,仅仅知道「如何画一个三角形」是不够的。本篇提炼了 WebGL 进阶的核心考点,涵盖坐标变换、纹理、离屏渲染及性能优化。

1. 核心数学:MVP 矩阵与坐标变换

在三维空间中,一个顶点要最终显示在二维屏幕上,必须经历MVP 矩阵变换(Model-View-Projection)。

// 典型的顶点着色器核心逻辑
uniform mat4 u_ProjectionMatrix; // 投影矩阵
uniform mat4 u_ViewMatrix; // 视图(摄像机)矩阵
uniform mat4 u_ModelMatrix; // 模型矩阵
attribute vec3 a_Position;

void main() {
// 乘法顺序从右向左:模型 -> 视图 -> 投影
gl_Position = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * vec4(a_Position, 1.0);
}
  • Model Matrix(模型矩阵):将顶点从局部空间(模型自带坐标系)转换到世界空间。包含平移、旋转、缩放。
  • View Matrix(视图矩阵):将世界坐标转换到观察空间(以摄像机为原点)。等同于摄像机的逆变换。
  • Projection Matrix(投影矩阵):将观察空间转换到裁剪空间(Clip Space)。分两种:
    • 正交投影(Orthographic):没有近大远小,常用于 UI、2D 游戏、工程制图。
    • 透视投影(Perspective):产生近大远小效果,符合人眼视觉。
  • 法线变换陷阱:法线不能直接乘以模型矩阵(如果模型存在非等比缩放,法线会不再垂直于表面)。必须乘以模型矩阵逆矩阵的转置(Inverse Transpose)

2. 纹理系统 (Texture)

纹理不仅是“贴图”,本质上是 GPU 里的多维数据查找表。

核心概念

  • UV 坐标:归一化的纹理坐标,左下角为 (0,0),右上角为 (1,1)
  • 寻址模式 (Wrap Mode):当 UV 超出 [0,1] 时的处理。
    • gl.CLAMP_TO_EDGE:边缘拉伸。
    • gl.REPEAT:平铺重复(要求纹理宽高必须是 2 的幂次方,WebGL2 放宽了限制)。
  • 过滤模式 (Filter Mode):纹理缩放时的采样策略。
    • gl.NEAREST:最近邻插值(像素风,马赛克)。
    • gl.LINEAR:双线性插值(平滑过渡)。
  • Mipmap(多级渐远纹理):为解决远处纹理闪烁(摩尔纹)和提高缓存命中率,提前生成一系列 1/2、1/4 大小的纹理图集。配合 gl.LINEAR_MIPMAP_LINEAR 实现三线性过滤。
  • 各向异性过滤 (Anisotropic Filtering):解决斜视角度下 Mipmap 导致纹理过度模糊的问题(需开启扩展 EXT_texture_filter_anisotropic)。

3. 离屏渲染:FBO (帧缓冲对象)

FBO (Framebuffer Object) 是实现高级渲染效果(后处理、阴影映射、镜面反射)的基石。

面试题:怎么用 WebGL 实现一个高斯模糊? :不能直接在屏幕上画,必须用到 FBO。

  1. 创建 FBO 并绑定一个纹理(Color Attachment)作为输出目标。
  2. 将场景渲染到这个 FBO,此时屏幕上什么都没有,图像保存在该纹理中。
  3. 解绑 FBO(恢复到默认屏幕画布)。
  4. 使用刚才渲染生成的纹理,画一个覆盖全屏的矩形,在片元着色器中对周围像素采样进行加权平均(高斯模糊),最终输出到屏幕。

FBO 三件套

  • gl.createFramebuffer():核心对象。
  • gl.createTexture():作为颜色附件(存 RGB)。
  • gl.createRenderbuffer():作为深度/模板附件(存 Z 值),因为通常不需要读取深度图,用 Renderbuffer 比 Texture 性能更好。

4. WebGL 性能优化指南

“我的 WebGL 场景卡顿了,怎么优化?”这是图形岗必考题。

  1. 降低 Draw Call (合批 Batching)
    • 原因:每次调用 drawArrays/drawElements,CPU 都要做大量的状态检查并向 GPU 提交指令,CPU 容易成为瓶颈。
    • 对策:将相同材质的多个模型合并成一个大模型(静态合批);或者使用纹理图集(Texture Atlas)合并贴图,减少材质切换。
  2. 实例化绘制 (Instanced Drawing)
    • 场景:画 10000 棵不同的树、草地、粒子系统。
    • 原理:用 gl.drawElementsInstanced,只传一份树的几何数据,通过 Instanced Buffer 传入 10000 个不同的矩阵(位置/缩放),一笔画完。
  3. 减少状态切换 (State Sorting)
    • WebGL 是状态机。频繁的 gl.useProgramgl.bindTexture 开销极大。
    • 对策:渲染前按 Shader -> Material -> Mesh 的层级进行排序。先画所有用 Shader A 的,再画 Shader B 的。
  4. 剔除 (Culling)
    • 视锥体剔除 (Frustum Culling):在 CPU 端计算,不在摄像机范围内的物体直接不提交 Draw Call(Three.js 默认开启)。
    • 背面剔除 (Backface Culling)gl.enable(gl.CULL_FACE),GPU 自动丢弃背对摄像机的三角形,节省 50% 片元着色器开销。

5. WebGL2 的关键跃升

虽然 WebGPU 是未来,但当前 WebGL2 已经全面普及(基于 OpenGL ES 3.0)。相比 WebGL1 的核心优势:

  • VAO (Vertex Array Object) 原生支持:记录顶点绑定的所有状态,切换模型只需 gl.bindVertexArray 一次,省去大量 vertexAttribPointer 调用。
  • UBO (Uniform Buffer Object):多个 Shader 共享同一套 Uniform 数据(如摄像机矩阵),不用每次切换 Shader 都重新传一遍。
  • MRT (多渲染目标):一个片元着色器可以同时输出到多个纹理。延迟渲染 (Deferred Shading) 的基础。
  • 3D 纹理 (Texture3D):常用于体积云、体素渲染、LUT 颜色查找表。

6. 调试利器

  • Spector.js:微软开源的 WebGL 调试插件,能截取一帧,逐条查看所有 WebGL API 调用、Draw Call、Buffer 状态和 Shader 源码。
  • RenderDoc:更底层的 GPU 抓帧工具(通过启动特殊配置的浏览器抓取)。
  • 查错套路:WebGL 默认不报错(黑屏),开发时应封装 gl.getError() 或使用 WebGLInspector