WebGL学习笔记 🎮
什么是 WebGL? 🤔
WebGL (Web Graphics Library) 是一种基于 OpenGL ES 的 JavaScript API,用于在网页浏览器中渲染 2D 和 3D 图形。它能够直接调用 GPU 进行图形渲染,提供了强大的图形处理能力。
着色器语言 GLSL ES 基础 ⚡
GLSL ES (OpenGL ES Shading Language) 是 WebGL 中使用的着色器语言。与普通的 JavaScript、C、Java 等在 CPU 上执行的语言不同,GLSL ES 直接在 GPU 上执行,这使得它能够高效地处理图形计算。
基础数据类型 📊
| 类型 | 说明 | 示例 |
|---|---|---|
| float | 浮点型 | float a = 1.0; |
| int | 整型 | int a = 1; |
| bool | 布尔型 | bool a = true; |
| vec2 | 2维向量 | vec2 a = vec2(1.0, 0.0); |
| vec3 | 3维向量 | vec3 a = vec3(1.0, 0.0, 0.0); |
| vec4 | 4维向量 | vec4 a = vec4(1.0, 0.0, 0.0, 1.0); |
常量声明 🔒
glsl
const float PI = 3.14159265359;
const int MAX_POINTS = 100;函数声明 ⚙️
glsl
// 有返回值的函数
float calculateDistance(vec2 point1, vec2 point2) {
return distance(point1, point2);
}
// 无返回值的函数
void main() {
// 着色器入口函数
}向量操作 📐
glsl
// 三维向量
vec3 position = vec3(1.0, 2.0, 3.0);
// 四维向量(齐次坐标)
vec4 homogeneous = vec4(position, 1.0);
// 向量分量访问
vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
float red = color.r; // 或 color.x
float alpha = color.a; // 或 color.w重要的内置变量 🔍
| 变量名 | 着色器类型 | 说明 |
|---|---|---|
| gl_Position | 顶点着色器 | 顶点位置(vec4) |
| gl_PointSize | 顶点着色器 | 点的大小(float) |
| gl_FragColor | 片元着色器 | 片元颜色(vec4) |
| gl_FragCoord | 片元着色器 | 片元坐标(vec4) |
WebGL 渲染管线 🔄
基本渲染流程
- 顶点着色器处理 (Vertex Processing)
- 图元装配 (Primitive Assembly)
- 光栅化 (Rasterization)
- 片元着色器处理 (Fragment Processing)
- 帧缓冲操作 (Framebuffer Operations)
第一个 WebGL 程序示例 💻
javascript
// 获取 WebGL 上下文
const canvas = document.getElementById('webgl');
const gl = canvas.getContext('webgl');
// 顶点着色器
const vertexShaderSource = `
void main() {
// 设置点的大小
gl_PointSize = 20.0;
// 设置顶点位置
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
// 片元着色器
const fragmentShaderSource = `
void main() {
// 设置像素颜色为红色
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`;
// 初始化着色器程序
const program = initShader(gl, vertexShaderSource, fragmentShaderSource);
// 绘制一个点
gl.drawArrays(gl.POINTS, 0, 1);进阶主题 🚀
1. 纹理映射
- 图片纹理加载
- UV 坐标系统
- 多重纹理
2. 变换矩阵
- 平移变换
- 旋转变换
- 缩放变换
- 投影矩阵
3. 光照模型
- 环境光
- 漫反射
- 镜面反射
- Phong 光照模型
性能优化建议 ⚡
- 使用 VAO (Vertex Array Object) 管理顶点数据
- 合理使用 instancing 绘制重复对象
- 避免频繁切换着色器程序
- 正确使用 WebGL 状态缓存
调试技巧 🔧
- 使用 WebGL Inspector
- 检查 shader 编译错误
- 使用 performance 工具分析性能
- 善用 console 输出调试信息
注意事项 ⚠️
- GLSL ES 语句必须以分号结尾
- 数据类型要严格匹配
- 注意浮点数精度问题
- WebGL 坐标系与屏幕坐标系的区别
纹理映射详解 🖼️
纹理基础
javascript
// 创建纹理对象
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// 加载图片
const image = new Image();
image.src = 'texture.png';
image.onload = () => {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
// 生成多级渐远纹理
gl.generateMipmap(gl.TEXTURE_2D);
};UV 坐标系统详解 📐
UV 坐标是二维纹理坐标系统,范围在 [0,1] 之间:
- U: 水平方向坐标
- V: 垂直方向坐标
glsl
// 顶点着色器中的纹理坐标
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
v_texCoord = a_texCoord;
// ...
}
// 片元着色器中的纹理采样
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
}多重纹理技术 🎨
javascript
// 激活并绑定多个纹理单元
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture1);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, texture2);
// 在着色器中使用
const fragmentShaderSource = `
uniform sampler2D u_texture1;
uniform sampler2D u_texture2;
varying vec2 v_texCoord;
void main() {
vec4 color1 = texture2D(u_texture1, v_texCoord);
vec4 color2 = texture2D(u_texture2, v_texCoord);
gl_FragColor = mix(color1, color2, 0.5);
}
`;变换矩阵详解 🔄
矩阵运算基础
glsl
// 在 GLSL 中定义矩阵
mat4 modelMatrix;
mat4 viewMatrix;
mat4 projectionMatrix;
// 矩阵乘法
vec4 position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertex, 1.0);常用变换矩阵示例 📊
javascript
// 平移矩阵
function createTranslationMatrix(tx, ty, tz) {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
tx, ty, tz, 1
]);
}
// 旋转矩阵 (绕 Z 轴)
function createRotationMatrix(angleInRadians) {
const c = Math.cos(angleInRadians);
const s = Math.sin(angleInRadians);
return new Float32Array([
c, -s, 0, 0,
s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}光照模型实现 💡
Phong 光照模型
glsl
// 顶点着色器
attribute vec3 a_position;
attribute vec3 a_normal;
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
varying vec3 v_normal;
varying vec3 v_position;
void main() {
v_normal = normalize(a_normal);
v_position = vec3(u_modelMatrix * vec4(a_position, 1.0));
gl_Position = u_projectionMatrix * u_viewMatrix * vec4(v_position, 1.0);
}
// 片元着色器
precision mediump float;
uniform vec3 u_lightPosition;
uniform vec3 u_viewPosition;
uniform vec3 u_lightColor;
varying vec3 v_normal;
varying vec3 v_position;
void main() {
// 环境光
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * u_lightColor;
// 漫反射
vec3 norm = normalize(v_normal);
vec3 lightDir = normalize(u_lightPosition - v_position);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * u_lightColor;
// 镜面反射
float specularStrength = 0.5;
vec3 viewDir = normalize(u_viewPosition - v_position);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
vec3 specular = specularStrength * spec * u_lightColor;
vec3 result = ambient + diffuse + specular;
gl_FragColor = vec4(result, 1.0);
}高级技巧与优化 🚀
实例化渲染 (Instancing)
javascript
// 启用实例化数组
const instanceMatrix = new Float32Array(instances * 16); // 每个实例一个 4x4 矩阵
const instanceMatrixBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, instanceMatrixBuffer);
gl.bufferData(gl.ARRAY_BUFFER, instanceMatrix, gl.STATIC_DRAW);
// 在顶点着色器中使用
attribute mat4 a_instanceMatrix;
void main() {
gl_Position = u_projectionMatrix * u_viewMatrix * a_instanceMatrix * vec4(position, 1.0);
}帧缓冲对象 (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.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);常见问题与解决方案 🔧
1. 纹理上下翻转
javascript
// 在加载纹理前翻转 Y 轴
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);[待续...]