5月28日 02:34

WebGL 渲染管线的工作流程是什么?

WebGL 渲染管线是将 3D 顶点数据转化为屏幕像素的一系列处理阶段,分为应用程序阶段(CPU)GPU 管线阶段两大部分。其中 GPU 管线又包含可编程阶段固定功能阶段,面试中常围绕"哪些阶段可编程、各阶段输入输出是什么"展开追问。

管线总览

shell
CPU 应用程序阶段 │ 提交绘制命令、设置状态 顶点着色器(可编程) 图元装配 + 裁剪(固定) 光栅化(固定) 片段着色器(可编程) 逐片段测试与混合(固定) 帧缓冲区

关键点:整条管线中只有顶点着色器和片段着色器是可编程的,其余阶段由 GPU 硬件固定执行。WebGL 2.0 新增了变换反馈(Transform Feedback),可以将顶点着色器的输出回收到缓冲区,实现 GPU 端的粒子计算等效果。

一、应用程序阶段(CPU 端)

这是开发者通过 JavaScript 控制的阶段,主要负责:

  • 可见性判断:视锥体剔除、遮挡剔除,只提交可见物体给 GPU
  • 准备几何数据:将顶点位置、法线、UV、颜色等属性写入缓冲区
  • 设置渲染状态:绑定着色器程序、设置 uniform 变量、切换纹理
  • 发起绘制调用gl.drawArrays()gl.drawElements()

这一阶段的性能瓶颈通常在 draw call 数量,合并网格和使用实例化渲染(gl.drawArraysInstanced)是核心优化手段。

二、顶点着色器(可编程阶段)

顶点着色器对每个顶点执行一次,是管线的第一个可编程阶段。

输入

  • 顶点属性(attribute):位置、法线、UV、颜色
  • 全局变量(uniform):变换矩阵、光照参数

核心处理 — MVP 矩阵变换

glsl
attribute vec3 a_position; attribute vec2 a_texCoord; uniform mat4 u_model; // 模型矩阵:模型空间 → 世界空间 uniform mat4 u_view; // 视图矩阵:世界空间 → 观察空间 uniform mat4 u_projection; // 投影矩阵:观察空间 → 裁剪空间 varying vec2 v_texCoord; void main() { vec4 worldPos = u_model * vec4(a_position, 1.0); vec4 viewPos = u_view * worldPos; gl_Position = u_projection * viewPos; v_texCoord = a_texCoord; }

输出:裁剪空间坐标(Clip Space),gl_Position 的四个分量 (x, y, z, w) 中 w 用于后续透视除法。

面试追问:为什么用四维齐次坐标?——因为透视投影需要 w 分量来做透视除法,将裁剪空间转为 NDC;平移变换也需要齐次坐标才能用矩阵乘法表示。

三、图元装配与裁剪(固定阶段)

图元装配:将顶点按绘制模式(gl.TRIANGLESgl.LINESgl.POINTS)组装成图元。

裁剪:丢弃完全在视锥体外的图元,裁剪部分在视锥体内的图元(可能产生新顶点)。

透视除法:将裁剪坐标除以 w 分量,得到标准化设备坐标(NDC),x/y/z 范围均为 [-1, 1]。

视口变换:将 NDC 坐标映射到屏幕坐标,由 gl.viewport(x, y, width, height) 控制。

四、光栅化(固定阶段)

光栅化是将几何图元转换为片段(Fragment)的过程:

  1. 三角形遍历:检查哪些像素被三角形覆盖
  2. 插值计算:顶点属性(颜色、UV、法线)在片段间线性插值,透视校正插值由硬件自动完成
  3. 生成片段:每个被覆盖的像素生成一个片段,携带插值后的属性和深度值

片段不同于像素——片段是候选像素,还需要通过后续测试才能写入帧缓冲。

五、片段着色器(可编程阶段)

片段着色器对每个片段执行一次,是管线的第二个可编程阶段。

输入:插值后的顶点属性(varying)、纹理采样器(uniform sampler2D

处理:纹理采样、光照计算、颜色混合

输出:最终颜色值,写入 gl_FragColor

glsl
precision mediump float; varying vec2 v_texCoord; uniform sampler2D u_texture; void main() { gl_FragColor = texture2D(u_texture, v_texCoord); }

六、逐片段操作(固定阶段)

片段着色器输出的颜色需要通过一系列测试才能写入帧缓冲:

操作作用
模板测试用模板缓冲区做掩码,限制绘制区域
深度测试比较片段深度与深度缓冲区,丢弃被遮挡的片段
混合将片段颜色与帧缓冲已有颜色按 alpha 值混合,实现半透明
抖动用有限色深模拟更多颜色,减少色带

注意:深度测试默认关闭,需 gl.enable(gl.DEPTH_TEST) 开启。混合也需要 gl.enable(gl.BLEND) 并设置混合函数。

七、帧缓冲输出

通过所有测试的片段颜色被写入帧缓冲区。当一帧所有绘制完成后,前后缓冲区交换(双缓冲),画面显示到屏幕。

性能优化要点

  • 减少 draw call:合并静态网格为一次绘制;使用实例化渲染(gl.drawArraysInstanced)绘制大量相同几何体
  • 优化顶点着色器:MVP 矩阵在 CPU 端预计算 projection * view * model,不要在着色器中逐顶点相乘
  • 减少过度绘制:不透明物体从前到后绘制,利用深度测试提前丢弃被遮挡片段;半透明物体从后到前绘制
  • 控制片段着色器复杂度:移动端 GPU 是 tile-based 架构,片段着色器是性能瓶颈,避免 discard、复杂分支和过多纹理采样
  • 纹理压缩:使用 ASTC/ETC2 等压缩格式减少显存带宽

WebGL 1.0 与 2.0 管线差异

特性WebGL 1.0WebGL 2.0
GLSL 版本100 (GLSL ES 1.0)300 es (GLSL ES 3.0)
变换反馈不支持支持,顶点着色器输出可回收到缓冲区
多重渲染目标需扩展原生支持 MRT
3D 纹理需扩展原生支持
实例化渲染需扩展原生支持
顶点数组对象需扩展原生支持 VAO

面试追问

Q: WebGL 管线中哪些阶段可编程? 顶点着色器和片段着色器。WebGL 2.0 新增变换反馈但不算独立阶段,几何着色器 WebGL 不支持。

Q: 为什么需要透视除法? 裁剪空间的齐次坐标 (x, y, z, w) 除以 w 后得到 NDC,使不同深度的物体正确投影到屏幕上。没有透视除法,远处的物体不会变小。

Q: WebGL 和 OpenGL 管线的主要区别? WebGL 基于 OpenGL ES,去掉了几何着色器、曲面细分等着色器;运行在浏览器沙箱中,通过 JavaScript API 调用;着色器编译由浏览器驱动完成,不同浏览器可能有性能差异。

标签:WebGL