{T}

WebGL 核心概念

渲染管线

WebGL 渲染管线(Rendering Pipeline)是将 3D 场景转换为 2D 图像的一系列处理步骤。理解渲染管线是学习 WebGL 的基础。

管线流程图

code
┌─────────────────────────────────────────────────────────┐
│                    应用程序阶段                          │
│  JavaScript 代码、顶点数据、纹理数据                     │
└────────────────────────┬────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────────┐
│                  顶点着色器(可编程)                    │
│  • 顶点变换                                             │
│  • 法线变换                                             │
│  • 纹理坐标生成                                         │
│  • 光照计算                                             │
└────────────────────────┬────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────────┐
│                  图元装配(固定功能)                    │
│  • 图元类型:点、线、三角形                             │
│  • 裁剪                                                 │
│  • 剔除                                                 │
└────────────────────────┬────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────────┐
│                    光栅化(固定功能)                    │
│  • 将图元转换为片段                                     │
│  • 插值顶点属性                                         │
└────────────────────────┬────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────────┐
│                 片段着色器(可编程)                     │
│  • 计算片段颜色                                         │
│  • 纹理采样                                             │
│  • 逐片段光照                                           │
└────────────────────────┬────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────────┐
│                 逐片段操作(固定功能)                   │
│  • 像素所属测试                                         │
│  • 裁剪测试                                             │
│  • 模板测试                                             │
│  • 深度测试                                             │
│  • 混合                                                 │
│  • 抖动                                                 │
└────────────────────────┬────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────────┐
│                       帧缓冲                             │
│  • 颜色缓冲                                             │
│  • 深度缓冲                                             │
│  • 模板缓冲                                             │
└─────────────────────────────────────────────────────────┘

管线阶段详解

阶段类型输入输出主要任务
顶点着色器可编程顶点属性变换后的顶点坐标变换、属性传递
图元装配固定顶点图元组装图元、裁剪剔除
光栅化固定图元片段生成片段、属性插值
片段着色器可编程片段属性颜色颜色计算、纹理采样
逐片段操作固定片段颜色像素测试、混合
帧缓冲存储像素图像存储最终图像

可编程阶段

WebGL 提供两个可编程阶段:

阶段着色器类型主要任务GLSL 变量
顶点处理顶点着色器坐标变换、属性传递gl_Position, gl_PointSize
片段处理片段着色器颜色计算、纹理采样gl_FragColor, gl_FragDepth

顶点着色器

顶点着色器(Vertex Shader)对每个顶点执行一次,负责顶点的几何变换和属性计算

基本结构

glsl
// 顶点着色器
attribute vec3 a_Position;    // 顶点位置
attribute vec3 a_Color;       // 顶点颜色
uniform mat4 u_MVPMatrix;     // 模型视图投影矩阵
varying vec3 v_Color;         // 传递给片段着色器

void main() {
  // 计算变换后的顶点位置
  gl_Position = u_MVPMatrix * vec4(a_Position, 1.0);
  
  // 传递颜色到片段着色器
  v_Color = a_Color;
  
  // 设置点的大小(仅用于点绘制模式)
  gl_PointSize = 10.0;
}

内置变量

变量类型说明
gl_Positionvec4顶点在裁剪空间的位置(必须设置)
gl_PointSizefloat点的大小(像素),仅在 gl.POINTS 模式有效
gl_ClipVertexvec4用户裁剪平面坐标

顶点属性

顶点属性是每个顶点独有的数据:

javascript
// JavaScript 中设置顶点属性
const positions = new Float32Array([
  // x, y, z
  0.0,  0.5, 0.0,   // 顶点 0
 -0.5, -0.5, 0.0,   // 顶点 1
  0.5, -0.5, 0.0    // 顶点 2
]);

// 创建缓冲区
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);

// 获取属性位置
const a_Position = gl.getAttribLocation(program, 'a_Position');

// 配置属性指针
gl.vertexAttribPointer(
  a_Position,    // 属性位置
  3,             // 每个顶点的分量数(x, y, z)
  gl.FLOAT,      // 数据类型
  false,         // 是否归一化
  0,             // 步长(0 表示紧密排列)
  0              // 偏移量
);

// 启用属性
gl.enableVertexAttribArray(a_Position);

实际应用场景

顶点动画

glsl
// 顶点波动动画
uniform float u_Time;
uniform float u_Amplitude;

void main() {
  vec3 pos = a_Position;
  
  // 正弦波动画
  pos.y += sin(pos.x * 3.0 + u_Time) * u_Amplitude;
  
  gl_Position = u_MVPMatrix * vec4(pos, 1.0);
}

顶点位移贴图

glsl
// 根据纹理位移顶点
uniform sampler2D u_HeightMap;
uniform float u_HeightScale;

void main() {
  // 采样高度图
  float height = texture2D(u_HeightMap, a_TexCoord).r;
  
  // 沿法线方向位移
  vec3 pos = a_Position + a_Normal * height * u_HeightScale;
  
  gl_Position = u_MVPMatrix * vec4(pos, 1.0);
}

片段着色器

片段着色器(Fragment Shader)对每个片段(像素)执行一次,负责计算片段的最终颜色。

基本结构

glsl
// 片段着色器
precision mediump float;      // 设置浮点精度

varying vec3 v_Color;         // 从顶点着色器插值得到
uniform sampler2D u_Texture;  // 纹理采样器

void main() {
  // 计算片段颜色
  gl_FragColor = vec4(v_Color, 1.0);
}

精度限定符

WebGL 要求在片段着色器中声明浮点精度:

glsl
precision highp float;    // 高精度(可选)
precision mediump float;  // 中等精度(推荐)
precision lowp float;     // 低精度
精度范围精度适用场景
highp±2^6216位位置计算、深度
mediump±2^1410位纹理坐标、颜色
lowp±2^88位颜色、法线

内置变量

变量类型说明
gl_FragColorvec4片段的最终颜色(必须设置)
gl_FragDepthfloat片段的深度值
gl_FragCoordvec4片段在窗口坐标系中的位置
gl_FrontFacingbool片段是否属于正面

实际应用场景

渐变背景

glsl
uniform vec2 u_Resolution;

void main() {
  // 计算归一化坐标
  vec2 uv = gl_FragCoord.xy / u_Resolution;
  
  // 渐变颜色
  vec3 color1 = vec3(0.1, 0.1, 0.3);
  vec3 color2 = vec3(0.5, 0.3, 0.8);
  vec3 color = mix(color1, color2, uv.y);
  
  gl_FragColor = vec4(color, 1.0);
}

圆形绘制

glsl
void main() {
  // 计算到中心的距离
  float dist = distance(gl_PointCoord, vec2(0.5));
  
  // 圆形裁剪
  if (dist > 0.5) discard;
  
  // 平滑边缘
  float alpha = 1.0 - smoothstep(0.4, 0.5, dist);
  
  gl_FragColor = vec4(1.0, 0.0, 0.0, alpha);
}

缓冲区对象

缓冲区对象(Buffer Object)用于存储顶点数据、索引数据等,是 WebGL 中数据传输的核心机制。

缓冲区类型

类型常量用途
顶点缓冲区gl.ARRAY_BUFFER存储顶点属性数据
索引缓冲区gl.ELEMENT_ARRAY_BUFFER存储顶点索引

创建和使用缓冲区

javascript
// 1. 创建缓冲区
const buffer = gl.createBuffer();

// 2. 绑定缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

// 3. 写入数据
const data = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

// 4. 配置属性指针
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

// 5. 启用属性
gl.enableVertexAttribArray(a_Position);

// 6. 解绑缓冲区(可选)
gl.bindBuffer(gl.ARRAY_BUFFER, null);

缓冲区使用提示

gl.bufferData() 的第三个参数指定数据使用模式:

模式说明适用场景
gl.STATIC_DRAW一次写入,多次绘制静态模型
gl.DYNAMIC_DRAW多次写入,多次绘制动态模型
gl.STREAM_DRAW一次写入,少量绘制粒子系统

索引缓冲区

使用索引可以重用顶点,减少数据量:

javascript
// 顶点数据(4个顶点)
const vertices = new Float32Array([
 -0.5,  0.5,  // 顶点 0
 -0.5, -0.5,  // 顶点 1
  0.5, -0.5,  // 顶点 2
  0.5,  0.5   // 顶点 3
]);

// 索引数据(绘制两个三角形)
const indices = new Uint16Array([
  0, 1, 2,  // 第一个三角形
  0, 2, 3   // 第二个三角形
]);

// 创建顶点缓冲区
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

// 创建索引缓冲区
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

// 使用索引绘制
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

着色器程序

着色器程序(Shader Program)是顶点着色器和片段着色器的链接组合。

创建流程

code
创建着色器 → 编译着色器 → 创建程序 → 链接着色器 → 使用程序

完整示例

javascript
// 创建着色器
function createShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  
  // 检查编译状态
  const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }
  
  // 输出错误信息
  console.error('着色器编译失败:', gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
  return null;
}

// 创建程序
function createProgram(gl, vertexShader, fragmentShader) {
  const program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  
  // 检查链接状态
  const success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }
  
  // 输出错误信息
  console.error('程序链接失败:', gl.getProgramInfoLog(program));
  gl.deleteProgram(program);
  return null;
}

// 使用示例
const vertexShaderSource = `...`;
const fragmentShaderSource = `...`;

const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);

// 使用程序
gl.useProgram(program);

变量类型

WebGL 着色器程序中的变量分为三类:

类型存储限定符说明示例
属性变量attribute顶点属性,每个顶点不同位置、颜色、纹理坐标
一致变量uniform全局变量,所有顶点/片段共享变换矩阵、光照参数
可变变量varying从顶点着色器传递到片段着色器插值的颜色、纹理坐标
glsl
// 顶点着色器
attribute vec3 a_Position;    // 每个顶点不同
uniform mat4 u_ModelMatrix;   // 所有顶点相同
varying vec3 v_Color;         // 传递给片段着色器

// 片段着色器
varying vec3 v_Color;         // 接收插值后的值
uniform sampler2D u_Texture;  // 纹理采样器

渲染缓冲

渲染缓冲(Renderbuffer)是用于离屏渲染的缓冲区,可以存储颜色、深度或模板数据。

帧缓冲对象

帧缓冲对象(Framebuffer Object, FBO)是一组缓冲区的容器,支持离屏渲染:

javascript
// 创建帧缓冲
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

// 创建纹理作为颜色附件
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

// 将纹理附加到帧缓冲
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);

// 创建渲染缓冲作为深度附件
const renderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);

// 将渲染缓冲附加到帧缓冲
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);

// 检查帧缓冲状态
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
  console.error('帧缓冲不完整');
}

// 渲染到帧缓冲
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.viewport(0, 0, width, height);
// ... 渲染操作 ...

// 恢复默认帧缓冲
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, canvas.width, canvas.height);

帧缓冲应用场景

应用说明
后处理将场景渲染到纹理,再应用特效
阴影贴图从光源视角渲染深度
延迟渲染分离几何和光照阶段
镜面反射渲染反射视图

深度测试

深度测试(Depth Test)用于确定片段的前后关系,确保正确的遮挡效果。

开启深度测试

javascript
// 开启深度测试
gl.enable(gl.DEPTH_TEST);

// 设置深度测试函数
gl.depthFunc(gl.LESS);  // 默认值:小于时通过

// 清除深度缓冲
gl.clearDepth(1.0);     // 默认值:1.0(最远)
gl.clear(gl.DEPTH_BUFFER_BIT);

深度测试函数

函数说明
gl.NEVER永不通过
gl.LESS小于时通过(默认)
gl.EQUAL等于时通过
gl.LEQUAL小于等于时通过
gl.GREATER大于时通过
gl.NOTEQUAL不等于时通过
gl.GEQUAL大于等于时通过
gl.ALWAYS总是通过

深度冲突

当两个面距离过近时,会出现深度冲突(Z-fighting):

glsl
// 在顶点着色器中添加深度偏移
gl_Position = u_MVPMatrix * vec4(a_Position, 1.0);
gl_Position.z -= 0.001;  // 微小偏移

或者在 JavaScript 中设置多边形偏移:

javascript
gl.enable(gl.POLYGON_OFFSET_FILL);
gl.polygonOffset(1.0, 1.0);  // 因子和单位

混合模式

混合(Blending)用于实现半透明效果,将片段颜色与帧缓冲中的颜色混合。

开启混合

javascript
// 开启混合
gl.enable(gl.BLEND);

// 设置混合函数
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

混合方程

code
结果 = 源颜色 × 源因子 + 目标颜色 × 目标因子

混合因子

因子说明
gl.ZERO0
gl.ONE1
gl.SRC_COLOR源颜色
gl.ONE_MINUS_SRC_COLOR1 - 源颜色
gl.DST_COLOR目标颜色
gl.ONE_MINUS_DST_COLOR1 - 目标颜色
gl.SRC_ALPHA源透明度
gl.ONE_MINUS_SRC_ALPHA1 - 源透明度
gl.DST_ALPHA目标透明度
gl.ONE_MINUS_DST_ALPHA1 - 目标透明度

常见混合模式

javascript
// 标准透明度混合
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

// 加法混合(发光效果)
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);

// 乘法混合(阴影效果)
gl.blendFunc(gl.DST_COLOR, gl.ZERO);

// 预乘透明度
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

裁剪与剔除

背面剔除

剔除不面向相机的三角形,提升性能:

javascript
// 开启剔除
gl.enable(gl.CULL_FACE);

// 设置剔除面(默认:BACK)
gl.cullFace(gl.BACK);   // 剔除背面
gl.cullFace(gl.FRONT);  // 剔除正面
gl.cullFace(gl.FRONT_AND_BACK);  // 剔除两面

// 设置正面方向(默认:CCW)
gl.frontFace(gl.CCW);   // 逆时针为正面
gl.frontFace(gl.CW);    // 顺时针为正面

裁剪测试

只渲染指定矩形区域:

javascript
// 开启裁剪测试
gl.enable(gl.SCISSOR_TEST);

// 设置裁剪区域(x, y, width, height)
gl.scissor(100, 100, 200, 200);

// 关闭裁剪测试
gl.disable(gl.SCISSOR_TEST);

WebGL 状态机

WebGL 是一个状态机,理解状态管理对于高效开发至关重要。

状态类型

状态类型说明示例
全局状态影响整个渲染管线深度测试、混合模式
资源绑定状态当前绑定的资源缓冲区、纹理、帧缓冲
着色器状态当前使用的程序uniform 变量

状态查询

javascript
// 查询当前状态
const currentProgram = gl.getParameter(gl.CURRENT_PROGRAM);
const viewport = gl.getParameter(gl.VIEWPORT);
const clearColor = gl.getParameter(gl.COLOR_CLEAR_VALUE);

// 查询能力
const depthTest = gl.isEnabled(gl.DEPTH_TEST);
const blend = gl.isEnabled(gl.BLEND);

状态缓存最佳实践

javascript
class WebGLStateCache {
  constructor(gl) {
    this.gl = gl;
    this.state = {
      program: null,
      textures: new Array(16).fill(null),
      arrayBuffer: null,
      elementArrayBuffer: null,
      depthTest: false,
      blend: false
    };
  }
  
  useProgram(program) {
    if (this.state.program !== program) {
      this.gl.useProgram(program);
      this.state.program = program;
    }
  }
  
  bindTexture(unit, texture) {
    if (this.state.textures[unit] !== texture) {
      this.gl.activeTexture(this.gl.TEXTURE0 + unit);
      this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
      this.state.textures[unit] = texture;
    }
  }
}

小结

核心概念总结

概念作用关键 API
渲染管线图形处理流程-
顶点着色器顶点变换gl_Position, gl_PointSize
片段着色器颜色计算gl_FragColor, 精度限定符
缓冲区对象数据存储gl.createBuffer(), gl.bufferData()
着色器程序着色器链接gl.createProgram(), gl.linkProgram()
深度测试遮挡处理gl.enable(gl.DEPTH_TEST)
混合模式半透明效果gl.enable(gl.BLEND)

学习建议

  1. 理解管线:掌握渲染管线的每个阶段
  2. 实践着色器:多写 GLSL 代码,理解顶点和片段着色器的协作
  3. 调试技巧:学会使用浏览器开发工具和 WebGL 调试扩展
  4. 性能意识:了解状态切换、缓冲区使用对性能的影响

下一步

继续学习着色器语言,深入掌握 GLSL 编程。