渲染管线与着色器
WebGL 渲染管线
渲染管线是图形学中用于将 3D 场景转换为 2D 图像的一系列处理步骤。WebGL 采用的是可编程渲染管线,允许开发者通过着色器控制渲染过程。
渲染管线流程
顶点数据 → 顶点着色器 → 图元装配 → 光栅化 → 片段着色器 → 输出合并
1. 顶点着色器(Vertex Shader)
顶点着色器是渲染管线的第一个可编程阶段,主要任务:
- 变换顶点位置(模型-视图-投影变换)
- 计算顶点颜色
- 传递属性到片段着色器
GLSL1// 典型顶点着色器结构 2attribute vec3 aPosition; // 顶点位置(模型空间) 3attribute vec2 aTexCoord; // 纹理坐标 4attribute vec3 aNormal; // 法线向量 5 6uniform mat4 uModelViewMatrix; // 模型视图矩阵 7uniform mat4 uProjectionMatrix;// 投影矩阵 8 9varying vec2 vTexCoord; // 传递给片段着色器 10varying vec3 vNormal; 11 12void main() { 13 gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0); 14 vTexCoord = aTexCoord; 15 vNormal = aNormal; 16}
2. 图元装配
顶点着色器输出的顶点被组装成图元:
gl.POINTS- 点gl.LINES/gl.LINE_STRIP/gl.LINE_LOOP- 线gl.TRIANGLES/gl.TRIANGLE_STRIP/gl.TRIANGLE_FAN- 三角形
3. 光栅化
将图元转换为片段(Fragments),每个片段对应屏幕上的一个像素。
4. 片段着色器(Fragment Shader)
片段着色器决定每个片段的最终颜色:
GLSL1precision mediump float; // 精度限定符 2 3varying vec2 vTexCoord; 4varying vec3 vNormal; 5 6uniform sampler2D uTexture; 7uniform vec3 uLightDirection; 8 9void main() { 10 // 纹理采样 11 vec4 texColor = texture2D(uTexture, vTexCoord); 12 13 // 简单光照计算 14 float light = max(dot(vNormal, uLightDirection), 0.0); 15 16 gl_FragColor = texColor * light; 17}
GLSL 基础
数据类型
| 类型 | 说明 | 示例 |
|---|---|---|
float | 浮点数 | float x = 1.0; |
vec2/3/4 | 向量 | vec3 position = vec3(1.0, 2.0, 3.0); |
mat2/3/4 | 矩阵 | mat4 m = mat4(1.0); |
sampler2D | 2D纹理 | uniform sampler2D uTexture; |
修饰符
GLSL1// attribute - 顶点属性,每个顶点不同 2attribute vec3 aPosition; 3 4// uniform - 全局变量,所有顶点/片段相同 5uniform mat4 uMatrix; 6uniform float uTime; 7 8// varying - 顶点着色器输出,片段着色器输入 9varying vec3 vNormal; 10varying vec2 vTexCoord;
变换矩阵
模型矩阵(Model Matrix)
将顶点从模型空间变换到世界空间。
视图矩阵(View Matrix)
将世界空间变换到相机空间。
投影矩阵(Projection Matrix)
将相机空间变换到裁剪空间:
透视投影:
JAVASCRIPT1function perspective(fov, aspect, near, far) { 2 const f = 1.0 / Math.tan(fov / 2); 3 const nf = 1 / (near - far); 4 5 return new Float32Array([ 6 f / aspect, 0, 0, 0, 7 0, f, 0, 0, 8 0, 0, (far + near) * nf, -1, 9 0, 0, 2 * far * near * nf, 0 10 ]); 11}
正交投影:
JAVASCRIPT1function ortho(left, right, bottom, top, near, far) { 2 const lr = 1 / (left - right); 3 const bt = 1 / (bottom - top); 4 const nf = 1 / (near - far); 5 6 return new Float32Array([ 7 -2 * lr, 0, 0, 0, 8 0, -2 * bt, 0, 0, 9 0, 0, 2 * nf, 0, 10 (left + right) * lr, (top + bottom) * bt, (far + near) * nf, 1 11 ]); 12}
绘制立方体示例
JAVASCRIPT1// 立方体顶点数据(36个顶点,每个面2个三角形) 2const vertices = new Float32Array([ 3 // 前面 (z = 0.5) 4 -0.5, -0.5, 0.5, 0.0, 0.0, // 顶点 + 纹理坐标 5 0.5, -0.5, 0.5, 1.0, 0.0, 6 0.5, 0.5, 0.5, 1.0, 1.0, 7 -0.5, -0.5, 0.5, 0.0, 0.0, 8 0.5, 0.5, 0.5, 1.0, 1.0, 9 -0.5, 0.5, 0.5, 0.0, 1.0, 10 // ... 其他五个面 11]); 12 13// 在渲染循环中 14function render(time) { 15 // 创建旋转矩阵 16 const angle = time * 0.001; 17 const cos = Math.cos(angle); 18 const sin = Math.sin(angle); 19 20 const modelMatrix = new Float32Array([ 21 cos, 0, sin, 0, 22 0, 1, 0, 0, 23 -sin, 0, cos, 0, 24 0, 0, 0, 1 25 ]); 26 27 // 上传矩阵到 uniform 28 gl.uniformMatrix4fv(uModelMatrix, false, modelMatrix); 29 30 // 绘制 31 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 32 gl.drawArrays(gl.TRIANGLES, 0, 36); 33 34 requestAnimationFrame(render); 35}
深度测试
启用深度测试以实现正确的遮挡关系:
JAVASCRIPT1// 启用深度测试 2gl.enable(gl.DEPTH_TEST); 3gl.depthFunc(gl.LEQUAL); 4 5// 清除深度缓冲 6gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
背面剔除
提高性能,不渲染不可见的背面:
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
gl.frontFace(gl.CCW); // 逆时针为正面
小结
本章学习了:
- 完整的 WebGL 渲染管线流程
- GLSL 着色器编程基础
- 矩阵变换原理
- 立方体绘制示例
下一章将学习纹理映射和光照模型。