WebGL
WebGL(Web Graphics Library)是一种 JavaScript API,用于在任何兼容的网页浏览器中不使用插件渲染2D和3D图形。它是基于OpenGL ES的规范,旨在在Web平台上提供OpenGL的性能和功能。通过 WebGL,开发者可以为网页应用程序创建复杂的可视化效果、游戏、可视化数据和各种交互式图形体验。

查看更多相关内容
WebGL 渲染管线的工作流程是什么?WebGL 渲染管线(Rendering Pipeline)是一系列将 3D 顶点数据转换为 2D 屏幕像素的处理阶段。理解渲染管线对于优化 WebGL 应用性能至关重要。
## 渲染管线的各个阶段
### 1. 顶点处理阶段(Vertex Processing)
#### 顶点着色器(Vertex Shader)
* **输入**:顶点位置、颜色、纹理坐标、法线等属性
* **处理**:
* 坐标变换(模型矩阵、视图矩阵、投影矩阵)
* 顶点光照计算
* 纹理坐标变换
* **输出**:裁剪空间坐标(Clip Space Coordinates)
```glsl
// 顶点着色器示例
attribute vec3 a_position;
attribute vec2 a_texCoord;
uniform mat4 u_modelViewProjectionMatrix;
varying vec2 v_texCoord;
void main() {
gl_Position = u_modelViewProjectionMatrix * vec4(a_position, 1.0);
v_texCoord = a_texCoord;
}
```
### 2. 图元装配阶段(Primitive Assembly)
* 将顶点组装成图元(点、线、三角形)
* 执行**裁剪**(Clipping):移除视锥体外的图元
* 执行**透视除法**(Perspective Division):将裁剪坐标转换为标准化设备坐标(NDC)
### 3. 光栅化阶段(Rasterization)
* 将图元转换为片段(Fragments)
* 片段是潜在的像素,包含颜色、深度等信息
* 确定哪些像素被图元覆盖
* 进行**插值**计算:颜色、纹理坐标、法线等属性在片段间的插值
### 4. 片段处理阶段(Fragment Processing)
#### 片段着色器(Fragment Shader)
* **输入**:插值后的顶点属性
* **处理**:
* 纹理采样
* 光照计算
* 颜色混合
* **输出**:最终像素颜色
```glsl
// 片段着色器示例
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
}
```
### 5. 逐片段操作阶段(Per-Fragment Operations)
#### 深度测试(Depth Testing)
* 比较片段深度值与深度缓冲区
* 决定是否丢弃片段
#### 模板测试(Stencil Testing)
* 使用模板缓冲区进行掩码操作
#### 混合(Blending)
* 将片段颜色与帧缓冲区已有颜色混合
* 实现透明效果
#### 抖动(Dithering)
* 减少颜色量化带来的色带
## WebGL 渲染流程图
```
顶点数据 → 顶点着色器 → 图元装配 → 裁剪 → 透视除法 → 视口变换
↓
帧缓冲区 ← 逐片段操作 ← 片段着色器 ← 光栅化/插值 ← 屏幕映射
```
## 性能优化要点
1. **减少绘制调用**:合并网格,使用实例化渲染
2. **优化顶点着色器**:避免复杂计算
3. **减少片段着色器复杂度**:特别是移动端
4. **合理使用深度测试**:从前到后绘制不透明物体
5. **避免过度绘制**:使用遮挡查询
## WebGL 1.0 vs WebGL 2.0 管线差异
| 特性 | WebGL 1.0 | WebGL 2.0 |
| ------ | --------- | --------- |
| 变换反馈 | 不支持 | 支持 |
| 多重渲染目标 | 需要扩展 | 原生支持 |
| 3D 纹理 | 需要扩展 | 原生支持 |
| 实例化渲染 | 需要扩展 | 原生支持 |
服务端 · 3月7日 19:38
WebGL 中的立方体贴图(Cubemap)是什么?有哪些应用场景?## WebGL 立方体贴图概述
立方体贴图(Cubemap)是一种特殊的纹理,由 6 张独立的 2D 纹理组成,分别对应立方体的 6 个面。它使用 3D 方向向量进行采样,常用于实现天空盒、环境反射和折射等效果。
## 立方体贴图的结构
立方体贴图由 6 个面组成:
```
┌─────────┐
│ +Y │ (Top)
┌──────┼─────────┼──────┬─────────┐
│ -X │ +Z │ +X │ -Z │
│ Left │ Front │ Right│ Back │
└──────┴─────────┴──────┴─────────┘
│ -Y │ (Bottom)
└─────────┘
```
## 创建立方体贴图
### 基本创建流程
```javascript
// 创建立方体贴图
const cubemap = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubemap);
// 6 个面的图片 URL
const faceImages = [
'px.jpg', // +X (Right)
'nx.jpg', // -X (Left)
'py.jpg', // +Y (Top)
'ny.jpg', // -Y (Bottom)
'pz.jpg', // +Z (Front)
'nz.jpg' // -Z (Back)
];
// 加载 6 个面的图片
const targets = [
gl.TEXTURE_CUBE_MAP_POSITIVE_X,
gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
];
let loadedCount = 0;
faceImages.forEach((src, index) => {
const image = new Image();
image.onload = () => {
gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubemap);
gl.texImage2D(
targets[index],
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
image
);
loadedCount++;
if (loadedCount === 6) {
// 所有面加载完成,生成 mipmap
gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
}
};
image.src = src;
});
// 设置纹理参数
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
```
### 程序化生成立方体贴图
```javascript
// 创建纯色立方体贴图
function createSolidColorCubemap(gl, color) {
const cubemap = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubemap);
const targets = [
gl.TEXTURE_CUBE_MAP_POSITIVE_X,
gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
];
const size = 1;
const data = new Uint8Array([
color[0] * 255,
color[1] * 255,
color[2] * 255,
(color[3] || 1) * 255
]);
targets.forEach(target => {
gl.texImage2D(
target,
0,
gl.RGBA,
size,
size,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
data
);
});
return cubemap;
}
```
## 在着色器中使用立方体贴图
### 顶点着色器
```glsl
attribute vec3 a_position;
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
varying vec3 v_worldPos;
void main() {
vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0);
v_worldPos = worldPos.xyz;
gl_Position = u_projectionMatrix * u_viewMatrix * worldPos;
}
```
### 片段着色器
```glsl
precision mediump float;
varying vec3 v_worldPos;
uniform vec3 u_cameraPos;
uniform samplerCube u_cubemap;
void main() {
// 计算从相机指向片段的方向向量
vec3 direction = normalize(v_worldPos - u_cameraPos);
// 使用方向向量采样立方体贴图
vec4 color = textureCube(u_cubemap, direction);
gl_FragColor = color;
}
```
## 主要应用场景
### 1. 天空盒(Skybox)
天空盒用于渲染远处的环境背景,给人一种无限大的感觉。
```glsl
// 天空盒顶点着色器
attribute vec3 a_position;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
varying vec3 v_texCoord;
void main() {
// 移除平移分量,只保留旋转
mat4 viewRotation = mat4(mat3(u_viewMatrix));
vec4 pos = u_projectionMatrix * viewRotation * vec4(a_position, 1.0);
// 确保天空盒在远裁剪面
gl_Position = pos.xyww;
// 使用位置作为纹理坐标
v_texCoord = a_position;
}
// 天空盒片段着色器
precision mediump float;
varying vec3 v_texCoord;
uniform samplerCube u_skybox;
void main() {
gl_FragColor = textureCube(u_skybox, v_texCoord);
}
```
```javascript
// 渲染天空盒
function drawSkybox(gl, skyboxProgram, cubemap) {
// 禁用深度写入
gl.depthMask(false);
gl.useProgram(skyboxProgram);
// 绑定立方体贴图
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubemap);
gl.uniform1i(gl.getUniformLocation(skyboxProgram, 'u_skybox'), 0);
// 绘制立方体
drawCube(gl);
// 恢复深度写入
gl.depthMask(true);
}
```
### 2. 环境反射(Environment Reflection)
使用立方体贴图模拟光滑表面的反射效果。
```glsl
// 反射顶点着色器
attribute vec3 a_position;
attribute vec3 a_normal;
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
varying vec3 v_worldPos;
varying vec3 v_normal;
void main() {
vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0);
v_worldPos = worldPos.xyz;
v_normal = mat3(u_modelMatrix) * a_normal;
gl_Position = u_projectionMatrix * u_viewMatrix * worldPos;
}
// 反射片段着色器
precision mediump float;
varying vec3 v_worldPos;
varying vec3 v_normal;
uniform vec3 u_cameraPos;
uniform samplerCube u_environmentMap;
uniform float u_reflectivity;
void main() {
vec3 normal = normalize(v_normal);
vec3 viewDir = normalize(u_cameraPos - v_worldPos);
// 计算反射向量
vec3 reflectDir = reflect(-viewDir, normal);
// 采样环境贴图
vec4 reflectionColor = textureCube(u_environmentMap, reflectDir);
// 可以结合基础颜色和反射
vec3 baseColor = vec3(0.8, 0.8, 0.8);
vec3 finalColor = mix(baseColor, reflectionColor.rgb, u_reflectivity);
gl_FragColor = vec4(finalColor, 1.0);
}
```
### 3. 环境折射(Environment Refraction)
模拟光线穿过透明物体的折射效果。
```glsl
// 折射片段着色器
precision mediump float;
varying vec3 v_worldPos;
varying vec3 v_normal;
uniform vec3 u_cameraPos;
uniform samplerCube u_environmentMap;
uniform float u_refractiveIndex; // 折射率,如 1.52 表示玻璃
void main() {
vec3 normal = normalize(v_normal);
vec3 viewDir = normalize(u_cameraPos - v_worldPos);
// 计算折射向量
// refract(I, N, eta) 其中 eta = 入射介质折射率 / 折射介质折射率
vec3 refractDir = refract(-viewDir, normal, 1.0 / u_refractiveIndex);
// 采样环境贴图
vec4 refractionColor = textureCube(u_environmentMap, refractDir);
gl_FragColor = refractionColor;
}
```
### 4. 菲涅尔反射(Fresnel Reflection)
模拟真实世界中反射强度随视角变化的效应。
```glsl
// 菲涅尔反射片段着色器
precision mediump float;
varying vec3 v_worldPos;
varying vec3 v_normal;
uniform vec3 u_cameraPos;
uniform samplerCube u_environmentMap;
uniform float u_fresnelPower;
void main() {
vec3 normal = normalize(v_normal);
vec3 viewDir = normalize(u_cameraPos - v_worldPos);
// 计算反射向量
vec3 reflectDir = reflect(-viewDir, normal);
vec4 reflectionColor = textureCube(u_environmentMap, reflectDir);
// 计算菲涅尔因子
// 视线与法线夹角越大,反射越强
float fresnel = pow(1.0 - max(dot(viewDir, normal), 0.0), u_fresnelPower);
// 基础颜色
vec3 baseColor = vec3(0.1, 0.2, 0.3);
// 混合基础颜色和反射
vec3 finalColor = mix(baseColor, reflectionColor.rgb, fresnel);
gl_FragColor = vec4(finalColor, 1.0);
}
```
### 5. 动态环境贴图(Dynamic Environment Mapping)
实时生成立方体贴图用于反射。
```javascript
// 动态生成环境贴图
function generateEnvironmentMap(gl, scene, centerPos, size = 256) {
// 创建立方体贴图
const cubemap = gl.createTexture();
gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubemap);
// 创建帧缓冲区
const framebuffer = gl.createFramebuffer();
// 6 个面的相机方向
const directions = [
{ target: gl.TEXTURE_CUBE_MAP_POSITIVE_X, eye: [1, 0, 0], up: [0, -1, 0] },
{ target: gl.TEXTURE_CUBE_MAP_NEGATIVE_X, eye: [-1, 0, 0], up: [0, -1, 0] },
{ target: gl.TEXTURE_CUBE_MAP_POSITIVE_Y, eye: [0, 1, 0], up: [0, 0, 1] },
{ target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, eye: [0, -1, 0], up: [0, 0, -1] },
{ target: gl.TEXTURE_CUBE_MAP_POSITIVE_Z, eye: [0, 0, 1], up: [0, -1, 0] },
{ target: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, eye: [0, 0, -1], up: [0, -1, 0] }
];
// 设置投影矩阵(90度视野)
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, Math.PI / 2, 1, 0.1, 1000);
// 渲染 6 个面
directions.forEach(dir => {
// 设置视图矩阵
const viewMatrix = mat4.create();
mat4.lookAt(viewMatrix, centerPos,
[centerPos[0] + dir.eye[0], centerPos[1] + dir.eye[1], centerPos[2] + dir.eye[2]],
dir.up
);
// 渲染场景到帧缓冲区
renderSceneToCubemapFace(gl, scene, framebuffer, cubemap, dir.target,
projectionMatrix, viewMatrix, size);
});
return cubemap;
}
```
## 立方体贴图采样原理
立方体贴图使用 3D 方向向量 `(x, y, z)` 进行采样,选择哪个面取决于哪个分量的绝对值最大:
```
如果 |x| 最大:
x > 0: 使用 +X 面,坐标 ( -z/x, -y/x )
x < 0: 使用 -X 面,坐标 ( z/x, -y/x )
如果 |y| 最大:
y > 0: 使用 +Y 面,坐标 ( x/y, z/y )
y < 0: 使用 -Y 面,坐标 ( x/y, -z/y )
如果 |z| 最大:
z > 0: 使用 +Z 面,坐标 ( x/z, -y/z )
z < 0: 使用 -Z 面,坐标 ( -x/z, -y/z )
```
## 性能优化建议
1. **预过滤环境贴图**:
- 为不同粗糙度预计算模糊的立方体贴图
- 使用 mipmap 级别存储不同粗糙度的反射
2. **立方体贴图压缩**:
- 使用压缩纹理格式减少内存占用
- DXT、ETC、PVRTC 等格式
3. **动态环境贴图优化**:
- 降低分辨率(如 128x128)
- 减少更新频率(如每 10 帧更新一次)
- 只更新可见物体的反射
4. **LOD 系统**:
- 远处物体使用低分辨率立方体贴图
- 近处物体使用高分辨率立方体贴图
## 常见问题
### 接缝问题
立方体贴图面与面之间可能出现接缝。
**解决方案**:
```javascript
// 使用 CLAMP_TO_EDGE 环绕模式
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE);
```
### 图片方向问题
不同面的图片可能需要翻转。
**解决方案**:
- 使用图像编辑软件预先调整
- 或在着色器中翻转纹理坐标
```glsl
// 翻转 Y 坐标
vec2 flippedCoord = vec2(texCoord.x, 1.0 - texCoord.y);
```
服务端 · 3月7日 12:04
WebGL 中的缓冲区(Buffer)是什么?如何使用 VBO 和 VAO?## WebGL 缓冲区概述
缓冲区(Buffer)是 GPU 内存中的一块区域,用于存储顶点数据、索引数据等图形渲染所需的信息。使用缓冲区可以高效地将数据从 CPU 传输到 GPU。
## 缓冲区类型
### 1. 顶点缓冲区对象(VBO - Vertex Buffer Object)
存储顶点属性数据,如位置、颜色、法线、纹理坐标等。
### 2. 索引缓冲区对象(IBO/EBO - Index/Element Buffer Object)
存储顶点索引,用于定义图元的连接方式,减少重复顶点数据。
### 3. 顶点数组对象(VAO - Vertex Array Object)
存储顶点属性配置的状态,简化绘制调用前的设置。
## VBO(顶点缓冲区对象)详解
### 创建和使用 VBO
```javascript
// 1. 创建缓冲区
const vbo = gl.createBuffer();
// 2. 绑定缓冲区(指定当前操作的缓冲区类型)
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
// 3. 上传数据到 GPU
const vertices = new Float32Array([
// 位置(x, y, z) 颜色(r, g, b)
-0.5, -0.5, 0.0, 1.0, 0.0, 0.0, // 顶点1
0.5, -0.5, 0.0, 0.0, 1.0, 0.0, // 顶点2
0.0, 0.5, 0.0, 0.0, 0.0, 1.0 // 顶点3
]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 4. 配置顶点属性指针
gl.vertexAttribPointer(
0, // 属性位置(对应着色器中的 layout(location=0))
3, // 每个属性的分量数(这里是3:x, y, z)
gl.FLOAT, // 数据类型
false, // 是否归一化
6 * 4, // 步长(每个顶点的字节数:6个float * 4字节)
0 // 偏移量
);
gl.enableVertexAttribArray(0); // 启用位置属性
// 配置颜色属性
gl.vertexAttribPointer(
1, // 属性位置
3, // 分量数(r, g, b)
gl.FLOAT,
false,
6 * 4, // 步长
3 * 4 // 偏移量(跳过位置数据)
);
gl.enableVertexAttribArray(1); // 启用颜色属性
```
### 缓冲区使用模式
```javascript
// gl.STATIC_DRAW:数据不经常改变,多次绘制
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// gl.DYNAMIC_DRAW:数据经常改变,多次绘制
gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW);
// gl.STREAM_DRAW:数据每次绘制都改变
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STREAM_DRAW);
```
## IBO/EBO(索引缓冲区对象)详解
### 创建和使用 IBO
```javascript
// 顶点数据(4个顶点定义一个四边形)
const vertices = new Float32Array([
-0.5, 0.5, 0.0, // 左上 (0)
-0.5, -0.5, 0.0, // 左下 (1)
0.5, -0.5, 0.0, // 右下 (2)
0.5, 0.5, 0.0 // 右上 (3)
]);
// 索引数据(2个三角形 = 6个索引)
const indices = new Uint16Array([
0, 1, 2, // 第一个三角形
0, 2, 3 // 第二个三角形
]);
// 创建并绑定 VBO
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 创建并绑定 IBO
const ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
// 绘制(使用索引绘制)
gl.drawElements(
gl.TRIANGLES, // 绘制模式
6, // 索引数量
gl.UNSIGNED_SHORT, // 索引数据类型
0 // 偏移量
);
```
## VAO(顶点数组对象)详解
VAO 存储了所有顶点属性的配置状态,包括:
- 启用的顶点属性
- 每个属性的配置(大小、类型、步长、偏移)
- 绑定的 VBO
### WebGL 2.0 / WebGL 1.0 + OES_vertex_array_object 扩展
```javascript
// 创建 VAO
const vao = gl.createVertexArray(); // WebGL 2.0
// const vao = ext.createVertexArrayOES(); // 使用扩展
// 绑定 VAO(后续配置将存储在 VAO 中)
gl.bindVertexArray(vao);
// 配置顶点属性(这些配置会被 VAO 记录)
gl.bindBuffer(gl.ARRAY_BUFFER, positionVBO);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
gl.bindBuffer(gl.ARRAY_BUFFER, colorVBO);
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(1);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
// 解绑 VAO
gl.bindVertexArray(null);
// 绘制时只需绑定 VAO
gl.bindVertexArray(vao);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
```
## 完整示例代码
```javascript
class Mesh {
constructor(gl) {
this.gl = gl;
this.vertexCount = 0;
this.indexCount = 0;
// WebGL 2.0 创建 VAO
this.vao = gl.createVertexArray();
this.vbo = gl.createBuffer();
this.ibo = gl.createBuffer();
}
setVertices(vertices, attributes) {
const gl = this.gl;
gl.bindVertexArray(this.vao);
// 上传顶点数据
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 配置属性
let offset = 0;
const stride = attributes.reduce((sum, attr) => sum + attr.size, 0) * 4;
attributes.forEach((attr, index) => {
gl.vertexAttribPointer(
index,
attr.size,
gl.FLOAT,
false,
stride,
offset
);
gl.enableVertexAttribArray(index);
offset += attr.size * 4;
});
this.vertexCount = vertices.length / (stride / 4);
}
setIndices(indices) {
const gl = this.gl;
gl.bindVertexArray(this.vao);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
this.indexCount = indices.length;
}
draw() {
const gl = this.gl;
gl.bindVertexArray(this.vao);
if (this.indexCount > 0) {
gl.drawElements(gl.TRIANGLES, this.indexCount, gl.UNSIGNED_SHORT, 0);
} else {
gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount);
}
}
}
// 使用示例
const mesh = new Mesh(gl);
mesh.setVertices(
new Float32Array([/* 顶点数据 */]),
[
{ size: 3 }, // 位置
{ size: 3 }, // 颜色
{ size: 2 } // 纹理坐标
]
);
mesh.setIndices(new Uint16Array([/* 索引数据 */]));
mesh.draw();
```
## 性能优化建议
1. **减少状态切换**:使用 VAO 减少绘制前的配置开销
2. **合并缓冲区**:将多个小网格合并到一个大缓冲区
3. **使用索引绘制**:减少顶点数据重复
4. **选择合适的绘制模式**:
- `STATIC_DRAW`:静态几何体
- `DYNAMIC_DRAW`:频繁更新的数据
- `STREAM_DRAW`:每帧都更新的数据
5. **批量绘制**:使用实例化渲染(Instanced Rendering)绘制多个相同对象
服务端 · 3月7日 12:04
WebGL 中的雾效(Fog)是如何实现的?## WebGL 雾效概述
雾效(Fog)是一种模拟大气散射效果的渲染技术,使远处的物体逐渐融入到背景颜色中。雾效不仅能增加场景的真实感,还能隐藏远处的裁剪边界,优化性能。
## 雾效的基本原理
雾效的核心思想是根据物体与相机的距离,在物体颜色和雾颜色之间进行线性或指数插值:
```
最终颜色 = 物体颜色 × (1 - 雾因子) + 雾颜色 × 雾因子
```
## 雾的类型
### 1. 线性雾(Linear Fog)
雾的浓度随距离线性增加。
**公式**:
```
fogFactor = (end - distance) / (end - start)
```
- `start`:雾开始距离
- `end`:雾完全覆盖距离
- `distance`:片段到相机的距离
```glsl
// 顶点着色器
varying float v_fogDepth;
void main() {
vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0);
vec4 viewPos = u_viewMatrix * worldPos;
// 计算视图空间深度(正值)
v_fogDepth = -viewPos.z;
gl_Position = u_projectionMatrix * viewPos;
}
// 片段着色器
uniform vec3 u_fogColor;
uniform float u_fogStart;
uniform float u_fogEnd;
varying float v_fogDepth;
void main() {
vec4 objectColor = texture2D(u_texture, v_texCoord);
// 计算线性雾因子
float fogFactor = (u_fogEnd - v_fogDepth) / (u_fogEnd - u_fogStart);
fogFactor = clamp(fogFactor, 0.0, 1.0);
// 混合物体颜色和雾颜色
vec3 finalColor = mix(u_fogColor, objectColor.rgb, fogFactor);
gl_FragColor = vec4(finalColor, objectColor.a);
}
```
### 2. 指数雾(Exponential Fog)
雾的浓度随距离指数增加,效果更自然。
**公式**:
```
fogFactor = exp(-density × distance)
```
```glsl
uniform vec3 u_fogColor;
uniform float u_fogDensity;
varying float v_fogDepth;
void main() {
vec4 objectColor = texture2D(u_texture, v_texCoord);
// 计算指数雾因子
float fogFactor = exp(-u_fogDensity * v_fogDepth);
fogFactor = clamp(fogFactor, 0.0, 1.0);
vec3 finalColor = mix(u_fogColor, objectColor.rgb, fogFactor);
gl_FragColor = vec4(finalColor, objectColor.a);
}
```
### 3. 指数平方雾(Exp2 Fog)
雾的浓度随距离平方指数增加,效果更加柔和。
**公式**:
```
fogFactor = exp(-(density × distance)²)
```
```glsl
void main() {
vec4 objectColor = texture2D(u_texture, v_texCoord);
// 计算指数平方雾因子
float fogFactor = exp(-pow(u_fogDensity * v_fogDepth, 2.0));
fogFactor = clamp(fogFactor, 0.0, 1.0);
vec3 finalColor = mix(u_fogColor, objectColor.rgb, fogFactor);
gl_FragColor = vec4(finalColor, objectColor.a);
}
```
## 完整的雾效实现
### 顶点着色器
```glsl
attribute vec3 a_position;
attribute vec2 a_texCoord;
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
varying vec2 v_texCoord;
varying float v_fogDepth;
void main() {
vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0);
vec4 viewPos = u_viewMatrix * worldPos;
v_texCoord = a_texCoord;
v_fogDepth = length(viewPos.xyz); // 使用实际距离而非仅 Z 值
gl_Position = u_projectionMatrix * viewPos;
}
```
### 片段着色器
```glsl
precision mediump float;
uniform sampler2D u_texture;
uniform vec3 u_fogColor;
uniform float u_fogDensity;
uniform int u_fogType; // 0: 线性, 1: 指数, 2: 指数平方
uniform float u_fogStart;
uniform float u_fogEnd;
varying vec2 v_texCoord;
varying float v_fogDepth;
float calculateFogFactor() {
float fogFactor = 0.0;
if (u_fogType == 0) {
// 线性雾
fogFactor = (u_fogEnd - v_fogDepth) / (u_fogEnd - u_fogStart);
} else if (u_fogType == 1) {
// 指数雾
fogFactor = exp(-u_fogDensity * v_fogDepth);
} else if (u_fogType == 2) {
// 指数平方雾
fogFactor = exp(-pow(u_fogDensity * v_fogDepth, 2.0));
}
return clamp(fogFactor, 0.0, 1.0);
}
void main() {
vec4 objectColor = texture2D(u_texture, v_texCoord);
float fogFactor = calculateFogFactor();
// mix(fogColor, objectColor, fogFactor)
vec3 finalColor = mix(u_fogColor, objectColor.rgb, fogFactor);
gl_FragColor = vec4(finalColor, objectColor.a);
}
```
### JavaScript 控制
```javascript
class Fog {
constructor(gl, program) {
this.gl = gl;
this.program = program;
// 获取 uniform 位置
this.fogColorLoc = gl.getUniformLocation(program, 'u_fogColor');
this.fogDensityLoc = gl.getUniformLocation(program, 'u_fogDensity');
this.fogTypeLoc = gl.getUniformLocation(program, 'u_fogType');
this.fogStartLoc = gl.getUniformLocation(program, 'u_fogStart');
this.fogEndLoc = gl.getUniformLocation(program, 'u_fogEnd');
// 默认设置
this.color = [0.7, 0.8, 0.9]; // 淡蓝色雾
this.density = 0.02;
this.type = 2; // 指数平方雾
this.start = 10.0;
this.end = 50.0;
}
apply() {
const gl = this.gl;
gl.uniform3fv(this.fogColorLoc, this.color);
gl.uniform1f(this.fogDensityLoc, this.density);
gl.uniform1i(this.fogTypeLoc, this.type);
gl.uniform1f(this.fogStartLoc, this.start);
gl.uniform1f(this.fogEndLoc, this.end);
}
// 设置雾的类型
setLinear(start, end) {
this.type = 0;
this.start = start;
this.end = end;
}
setExponential(density) {
this.type = 1;
this.density = density;
}
setExponentialSquared(density) {
this.type = 2;
this.density = density;
}
}
// 使用示例
const fog = new Fog(gl, program);
fog.setExponentialSquared(0.015);
fog.apply();
```
## 高度雾(Height Fog)
模拟根据高度变化的雾效,如山谷中的雾。
```glsl
// 顶点着色器
varying vec3 v_worldPos;
void main() {
vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0);
v_worldPos = worldPos.xyz;
gl_Position = u_projectionMatrix * u_viewMatrix * worldPos;
}
// 片段着色器
uniform float u_fogHeight;
uniform float u_fogHeightFalloff;
varying vec3 v_worldPos;
float calculateHeightFog() {
// 基于高度的雾密度
float heightFactor = (u_fogHeight - v_worldPos.y) * u_fogHeightFalloff;
heightFactor = clamp(heightFactor, 0.0, 1.0);
// 结合距离雾
float distanceFactor = exp(-u_fogDensity * length(v_worldPos - u_cameraPos));
return distanceFactor * (1.0 - heightFactor);
}
```
## 雾效与光照结合
```glsl
void main() {
// 计算光照
vec3 ambient = ...;
vec3 diffuse = ...;
vec3 specular = ...;
vec3 lighting = ambient + diffuse + specular;
vec4 texColor = texture2D(u_texture, v_texCoord);
vec3 objectColor = lighting * texColor.rgb;
// 应用雾效
float fogFactor = calculateFogFactor();
vec3 finalColor = mix(u_fogColor, objectColor, fogFactor);
gl_FragColor = vec4(finalColor, texColor.a);
}
```
## 雾效的性能优化
### 1. 顶点级雾效计算
当顶点数较少时,可以在顶点着色器中计算雾因子:
```glsl
// 顶点着色器
varying float v_fogFactor;
void main() {
// ... 计算位置
float fogDepth = length(viewPos.xyz);
v_fogFactor = exp(-u_fogDensity * fogDepth);
v_fogFactor = clamp(v_fogFactor, 0.0, 1.0);
}
// 片段着色器
varying float v_fogFactor;
void main() {
vec3 finalColor = mix(u_fogColor, objectColor, v_fogFactor);
}
```
### 2. 使用深度纹理
在后处理阶段应用雾效:
```javascript
// 1. 渲染场景到颜色纹理和深度纹理
renderSceneToTextures();
// 2. 后处理阶段应用雾效
applyFogPostProcess();
```
```glsl
// 后处理雾效着色器
uniform sampler2D u_colorTexture;
uniform sampler2D u_depthTexture;
void main() {
vec3 color = texture2D(u_colorTexture, v_texCoord).rgb;
float depth = texture2D(u_depthTexture, v_texCoord).r;
// 将深度转换为世界空间距离
float linearDepth = linearizeDepth(depth);
// 计算雾因子
float fogFactor = exp(-u_fogDensity * linearDepth);
vec3 finalColor = mix(u_fogColor, color, fogFactor);
gl_FragColor = vec4(finalColor, 1.0);
}
```
## 不同类型的雾效对比
| 雾类型 | 公式 | 特点 |
|--------|------|------|
| 线性雾 | `(end - d) / (end - start)` | 简单,雾边界明显 |
| 指数雾 | `exp(-density × d)` | 自然,适合大多数场景 |
| 指数平方雾 | `exp(-(density × d)²)` | 更柔和,雾边界不明显 |
| 高度雾 | 结合 Y 轴 | 适合山谷、水面等场景 |
## 实际应用建议
1. **选择合适的雾类型**:
- 大多数场景使用指数或指数平方雾
- 需要精确控制雾边界时使用线性雾
2. **调整雾的颜色**:
- 通常与天空盒或背景色一致
- 可以随时间变化模拟昼夜效果
3. **性能考虑**:
- 移动端建议使用顶点级雾效
- 复杂场景可以使用后处理雾效
4. **与其他效果结合**:
- 雾效可以与体积光、大气散射结合
- 注意雾效对透明物体的影响
服务端 · 3月7日 12:04
WebGL 中的矩阵变换有哪些?MVP 矩阵是什么?## WebGL 中的矩阵变换
在 3D 图形渲染中,矩阵变换用于将顶点从一个坐标空间转换到另一个坐标空间。WebGL 使用 4×4 矩阵进行各种变换操作。
## 基本变换矩阵
### 1. 平移矩阵(Translation Matrix)
将物体沿 X、Y、Z 轴移动
```
| 1 0 0 tx |
| 0 1 0 ty |
| 0 0 1 tz |
| 0 0 0 1 |
```
```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
]);
}
```
### 2. 缩放矩阵(Scale Matrix)
沿各轴缩放物体大小
```
| sx 0 0 0 |
| 0 sy 0 0 |
| 0 0 sz 0 |
| 0 0 0 1 |
```
### 3. 旋转矩阵(Rotation Matrix)
#### 绕 X 轴旋转
```
| 1 0 0 0 |
| 0 cosθ -sinθ 0 |
| 0 sinθ cosθ 0 |
| 0 0 0 1 |
```
#### 绕 Y 轴旋转
```
| cosθ 0 sinθ 0 |
| 0 1 0 0 |
| -sinθ 0 cosθ 0 |
| 0 0 0 1 |
```
#### 绕 Z 轴旋转
```
| cosθ -sinθ 0 0 |
| sinθ cosθ 0 0 |
| 0 0 1 0 |
| 0 0 0 1 |
```
## MVP 矩阵详解
MVP 矩阵是三个矩阵的乘积,用于将顶点从**模型空间**转换到**裁剪空间**:
**MVP = P × V × M**
### M - 模型矩阵(Model Matrix)
**作用**:将顶点从**模型空间**(局部空间)转换到**世界空间**
```javascript
// 模型矩阵 = 平移 × 旋转 × 缩放
const modelMatrix = mat4.create();
mat4.translate(modelMatrix, modelMatrix, [x, y, z]);
mat4.rotateX(modelMatrix, modelMatrix, angleX);
mat4.rotateY(modelMatrix, modelMatrix, angleY);
mat4.scale(modelMatrix, modelMatrix, [sx, sy, sz]);
```
**应用场景**:
- 物体在世界中的位置
- 物体的旋转角度
- 物体的大小缩放
### V - 视图矩阵(View Matrix)
**作用**:将顶点从**世界空间**转换到**相机空间**(观察空间)
```javascript
// 使用 lookAt 函数创建视图矩阵
const viewMatrix = mat4.create();
mat4.lookAt(
viewMatrix,
[0, 0, 5], // 相机位置(眼睛)
[0, 0, 0], // 观察目标点
[0, 1, 0] // 上方向向量
);
```
**视图矩阵的本质**:
- 将世界坐标系原点移动到相机位置
- 旋转坐标系使相机朝向 -Z 方向
- 相机位于原点,看向 -Z 轴方向
### P - 投影矩阵(Projection Matrix)
**作用**:将顶点从**相机空间**转换到**裁剪空间**
#### 透视投影(Perspective Projection)
模拟人眼视觉效果,近大远小
```javascript
const projectionMatrix = mat4.create();
mat4.perspective(
projectionMatrix,
Math.PI / 4, // 视野角度(FOV)
canvas.width / canvas.height, // 宽高比
0.1, // 近平面
100.0 // 远平面
);
```
#### 正交投影(Orthographic Projection)
保持物体大小不变,常用于 2D 游戏或 CAD 软件
```javascript
mat4.ortho(
projectionMatrix,
-2, 2, // 左右
-2, 2, // 下上
0.1, 100 // 近远
);
```
## 坐标空间转换流程
```
模型空间(局部空间)
↓ [模型矩阵 M]
世界空间
↓ [视图矩阵 V]
相机空间(观察空间)
↓ [投影矩阵 P]
裁剪空间
↓ [透视除法]
标准化设备坐标(NDC)
↓ [视口变换]
屏幕空间
```
## 顶点着色器中的 MVP 应用
```glsl
// 顶点着色器
attribute vec3 a_position;
attribute vec3 a_color;
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
varying vec3 v_color;
void main() {
// 方法1:分别应用三个矩阵
// vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0);
// vec4 viewPos = u_viewMatrix * worldPos;
// gl_Position = u_projectionMatrix * viewPos;
// 方法2:使用预计算的 MVP 矩阵(推荐)
mat4 mvp = u_projectionMatrix * u_viewMatrix * u_modelMatrix;
gl_Position = mvp * vec4(a_position, 1.0);
v_color = a_color;
}
```
## JavaScript 中的矩阵计算
```javascript
// 使用 gl-matrix 库
import { mat4, vec3 } from 'gl-matrix';
class Camera {
constructor() {
this.projectionMatrix = mat4.create();
this.viewMatrix = mat4.create();
this.mvpMatrix = mat4.create();
}
setPerspective(fov, aspect, near, far) {
mat4.perspective(this.projectionMatrix, fov, aspect, near, far);
}
lookAt(eye, center, up) {
mat4.lookAt(this.viewMatrix, eye, center, up);
}
getMVPMatrix(modelMatrix) {
// MVP = P × V × M
mat4.multiply(this.mvpMatrix, this.viewMatrix, modelMatrix);
mat4.multiply(this.mvpMatrix, this.projectionMatrix, this.mvpMatrix);
return this.mvpMatrix;
}
}
// 使用示例
const camera = new Camera();
camera.setPerspective(Math.PI / 4, 16/9, 0.1, 100);
camera.lookAt([0, 0, 5], [0, 0, 0], [0, 1, 0]);
const modelMatrix = mat4.create();
mat4.translate(modelMatrix, modelMatrix, [1, 0, 0]);
const mvp = camera.getMVPMatrix(modelMatrix);
gl.uniformMatrix4fv(mvpLocation, false, mvp);
```
## 常见问题与注意事项
### 1. 矩阵乘法顺序
矩阵乘法不满足交换律,顺序非常重要:
- **正确**:`MVP = P × V × M`,应用于顶点:`MVP × 顶点`
- 先应用的变换在乘法链的右侧
### 2. 行主序 vs 列主序
- WebGL 使用**列主序**存储矩阵
- `gl.uniformMatrix4fv` 的第三个参数 `transpose` 必须为 `false`
### 3. 齐次坐标
使用 4D 向量 `(x, y, z, w)`:
- 顶点位置:`w = 1`
- 方向向量:`w = 0`(不受平移影响)
### 4. 性能优化
- 在 CPU 端预计算 MVP 矩阵,而不是在着色器中分别相乘
- 使用 uniform 传递预计算的矩阵
服务端 · 3月6日 21:58
WebGL 中的光照模型有哪些?如何实现 Phong 光照模型?## WebGL 光照模型概述
光照模型用于模拟光线与物体表面的交互,是 3D 渲染中实现真实感的关键技术。WebGL 中常用的光照模型包括:环境光(Ambient)、漫反射(Diffuse)和镜面反射(Specular)。
## 基本光照模型
### 1. 环境光(Ambient Lighting)
模拟场景中无处不在的间接光照,不考虑光源位置和方向。
```glsl
vec3 ambient = ambientStrength * lightColor;
```
### 2. 漫反射(Diffuse Lighting)
模拟光线照射到粗糙表面后向各个方向均匀反射的效果。遵循**朗伯余弦定律**。
```glsl
// 计算光线方向与法线的夹角
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
```
### 3. 镜面反射(Specular Lighting)
模拟光线在光滑表面的定向反射,产生高光效果。
```glsl
// Phong 模型
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess);
vec3 specular = specularStrength * spec * lightColor;
// Blinn-Phong 模型(更高效)
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess);
vec3 specular = specularStrength * spec * lightColor;
```
## Phong 光照模型详解
Phong 光照模型是三种光照成分的组合:
**最终颜色 = 环境光 + 漫反射 + 镜面反射**
### 顶点着色器
```glsl
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord;
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
uniform mat3 u_normalMatrix; // 法线矩阵(用于正确变换法线)
uniform vec3 u_lightPosition;
uniform vec3 u_cameraPosition;
varying vec3 v_normal;
varying vec3 v_lightDir;
varying vec3 v_viewDir;
varying vec2 v_texCoord;
void main() {
vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0);
gl_Position = u_projectionMatrix * u_viewMatrix * worldPos;
// 变换法线到世界空间
v_normal = normalize(u_normalMatrix * a_normal);
// 计算光线方向(从片段指向光源)
v_lightDir = normalize(u_lightPosition - worldPos.xyz);
// 计算视线方向(从片段指向相机)
v_viewDir = normalize(u_cameraPosition - worldPos.xyz);
v_texCoord = a_texCoord;
}
```
### 片段着色器(Phong 模型)
```glsl
precision mediump float;
varying vec3 v_normal;
varying vec3 v_lightDir;
varying vec3 v_viewDir;
varying vec2 v_texCoord;
uniform vec3 u_lightColor;
uniform vec3 u_ambientColor;
uniform sampler2D u_diffuseMap;
uniform float u_shininess;
uniform float u_ambientStrength;
uniform float u_specularStrength;
void main() {
vec3 normal = normalize(v_normal);
vec3 lightDir = normalize(v_lightDir);
vec3 viewDir = normalize(v_viewDir);
// 环境光
vec3 ambient = u_ambientStrength * u_ambientColor;
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * u_lightColor;
// 镜面反射(Phong)
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), u_shininess);
vec3 specular = u_specularStrength * spec * u_lightColor;
// 采样纹理
vec4 texColor = texture2D(u_diffuseMap, v_texCoord);
// 组合光照
vec3 lighting = ambient + diffuse + specular;
vec3 result = lighting * texColor.rgb;
gl_FragColor = vec4(result, texColor.a);
}
```
### 片段着色器(Blinn-Phong 模型)
Blinn-Phong 是 Phong 的改进版本,使用半角向量代替反射向量,计算更高效。
```glsl
void main() {
vec3 normal = normalize(v_normal);
vec3 lightDir = normalize(v_lightDir);
vec3 viewDir = normalize(v_viewDir);
// 环境光
vec3 ambient = u_ambientStrength * u_ambientColor;
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * u_lightColor;
// 镜面反射(Blinn-Phong)
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), u_shininess);
vec3 specular = u_specularStrength * spec * u_lightColor;
// 组合光照
vec3 lighting = ambient + diffuse + specular;
vec3 result = lighting * texture2D(u_diffuseMap, v_texCoord).rgb;
gl_FragColor = vec4(result, 1.0);
}
```
## 法线矩阵的计算
```javascript
// 法线矩阵是模型矩阵的逆转置矩阵的左上 3x3 部分
function createNormalMatrix(modelMatrix) {
const normalMatrix = mat4.create();
mat4.invert(normalMatrix, modelMatrix);
mat4.transpose(normalMatrix, normalMatrix);
// 提取 3x3 部分
return new Float32Array([
normalMatrix[0], normalMatrix[1], normalMatrix[2],
normalMatrix[4], normalMatrix[5], normalMatrix[6],
normalMatrix[8], normalMatrix[9], normalMatrix[10]
]);
}
// 或使用 gl-matrix 的 mat3
const normalMatrix = mat3.create();
mat3.fromMat4(normalMatrix, modelMatrix);
mat3.invert(normalMatrix, normalMatrix);
mat3.transpose(normalMatrix, normalMatrix);
```
## 多光源处理
```glsl
#define MAX_LIGHTS 4
struct Light {
vec3 position;
vec3 color;
float intensity;
};
uniform Light u_lights[MAX_LIGHTS];
uniform int u_numLights;
vec3 calculateLighting(vec3 normal, vec3 viewDir, vec3 fragPos) {
vec3 result = u_ambientColor * u_ambientStrength;
for (int i = 0; i < MAX_LIGHTS; i++) {
if (i >= u_numLights) break;
Light light = u_lights[i];
vec3 lightDir = normalize(light.position - fragPos);
// 漫反射
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * light.color * light.intensity;
// 镜面反射
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), u_shininess);
vec3 specular = u_specularStrength * spec * light.color * light.intensity;
// 衰减
float distance = length(light.position - fragPos);
float attenuation = 1.0 / (1.0 + 0.09 * distance + 0.032 * distance * distance);
result += (diffuse + specular) * attenuation;
}
return result;
}
```
## 不同光源类型
### 方向光(Directional Light)
```glsl
// 方向光只有方向,没有位置
uniform vec3 u_lightDirection; // 光线方向
vec3 lightDir = normalize(-u_lightDirection); // 指向光源
float diff = max(dot(normal, lightDir), 0.0);
```
### 点光源(Point Light)
```glsl
// 点光源有位置,向四面八方发射
uniform vec3 u_lightPosition;
uniform float u_constant;
uniform float u_linear;
uniform float u_quadratic;
vec3 lightDir = normalize(u_lightPosition - fragPos);
float distance = length(u_lightPosition - fragPos);
float attenuation = 1.0 / (u_constant + u_linear * distance + u_quadratic * distance * distance);
```
### 聚光灯(Spot Light)
```glsl
// 聚光灯有位置、方向和角度限制
uniform vec3 u_lightPosition;
uniform vec3 u_lightDirection;
uniform float u_cutOff; // 内切角余弦
uniform float u_outerCutOff; // 外切角余弦
vec3 lightDir = normalize(u_lightPosition - fragPos);
float theta = dot(lightDir, normalize(-u_lightDirection));
float epsilon = u_cutOff - u_outerCutOff;
float intensity = clamp((theta - u_outerCutOff) / epsilon, 0.0, 1.0);
```
## 材质属性
```glsl
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material u_material;
void main() {
vec3 ambient = u_light.ambient * u_material.ambient;
vec3 diffuse = u_light.diffuse * (diff * u_material.diffuse);
vec3 specular = u_light.specular * (spec * u_material.specular);
}
```
## Gouraud 着色 vs Phong 着色
| 特性 | Gouraud 着色 | Phong 着色 |
|------|-------------|------------|
| **计算位置** | 顶点着色器 | 片段着色器 |
| **质量** | 较低(插值) | 较高(逐像素) |
| **性能** | 更快 | 较慢 |
| **高光** | 可能不准确 | 准确 |
```glsl
// Gouraud 着色(顶点着色器中计算光照)
varying vec3 v_lighting;
void main() {
// 在顶点级别计算光照
vec3 ambient = ...;
vec3 diffuse = ...;
vec3 specular = ...;
v_lighting = ambient + diffuse + specular;
}
// 片段着色器
void main() {
gl_FragColor = vec4(v_lighting * texColor, 1.0);
}
```
## 性能优化建议
1. **顶点着色器 vs 片段着色器**:
- 顶点数 < 片段数时,在顶点着色器计算
- 需要高质量光照时,在片段着色器计算
2. **使用 Blinn-Phong**:
- 比 Phong 更高效(避免计算 reflect)
- 视觉效果相似
3. **限制光源数量**:
- 移动端建议 1-2 个光源
- 桌面端建议 4-8 个光源
4. **使用延迟渲染**:
- 大量光源时使用
- 避免对每个光源遍历所有片段
服务端 · 3月6日 21:57
WebGL 中的后期处理(Post-processing)是如何实现的?## WebGL 后期处理概述
后期处理(Post-processing)是在场景渲染完成后,对渲染结果进行图像处理的技术。通过后期处理可以实现各种视觉效果,如模糊、辉光、色调映射、抗锯齿等。
## 后期处理的基本原理
后期处理的核心流程:
1. 将场景渲染到纹理(离屏渲染)
2. 对纹理进行各种图像处理
3. 将处理后的结果渲染到屏幕
```
场景渲染 → 颜色纹理 → 后期处理着色器 → 屏幕
↓
深度纹理(可选)
```
## 基本后期处理框架
### 创建后期处理所需的资源
```javascript
class PostProcess {
constructor(gl, width, height) {
this.gl = gl;
this.width = width;
this.height = height;
// 创建帧缓冲区
this.framebuffer = gl.createFramebuffer();
// 创建颜色纹理
this.colorTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.colorTexture);
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 绑定到帧缓冲区
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.colorTexture, 0);
// 创建深度渲染缓冲区
this.depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, this.depthBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.depthBuffer);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// 创建全屏四边形
this.quad = this.createFullscreenQuad();
}
createFullscreenQuad() {
// 返回 VAO 或顶点数据
const vertices = new Float32Array([
// 位置 // 纹理坐标
-1, 1, 0, 1,
-1, -1, 0, 0,
1, 1, 1, 1,
1, -1, 1, 0
]);
// 创建 VBO 等...
return { vertices, vbo: this.gl.createBuffer() };
}
beginScene() {
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
this.gl.viewport(0, 0, this.width, this.height);
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
}
endScene() {
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
}
apply(effectProgram) {
this.gl.useProgram(effectProgram);
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, this.colorTexture);
this.gl.uniform1i(this.gl.getUniformLocation(effectProgram, 'u_texture'), 0);
// 绘制全屏四边形
this.drawQuad();
}
drawQuad() {
// 绑定 VAO 并绘制
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
}
}
```
### 后期处理顶点着色器
```glsl
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}
```
## 常用后期处理效果
### 1. 灰度效果(Grayscale)
```glsl
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
void main() {
vec4 color = texture2D(u_texture, v_texCoord);
// 加权灰度转换
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
gl_FragColor = vec4(vec3(gray), color.a);
}
```
### 2. 模糊效果(Blur)
#### 高斯模糊
```glsl
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform vec2 u_texelSize; // 1.0 / textureSize
uniform vec2 u_direction; // 模糊方向 (1,0) 或 (0,1)
void main() {
vec4 color = vec4(0.0);
// 5x5 高斯核
float weights[5];
weights[0] = 0.227027;
weights[1] = 0.1945946;
weights[2] = 0.1216216;
weights[3] = 0.054054;
weights[4] = 0.016216;
// 中心像素
color += texture2D(u_texture, v_texCoord) * weights[0];
// 两侧像素
for (int i = 1; i < 5; i++) {
vec2 offset = u_direction * u_texelSize * float(i);
color += texture2D(u_texture, v_texCoord + offset) * weights[i];
color += texture2D(u_texture, v_texCoord - offset) * weights[i];
}
gl_FragColor = color;
}
```
#### 双通道模糊(优化性能)
```javascript
// 先水平模糊
horizontalBlurProgram.setUniform('u_direction', [1, 0]);
postProcess.apply(horizontalBlurProgram);
// 再垂直模糊
verticalBlurProgram.setUniform('u_direction', [0, 1]);
postProcess.apply(verticalBlurProgram);
```
### 3. 边缘检测(Edge Detection)
```glsl
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform vec2 u_texelSize;
void main() {
// Sobel 算子
float kernelX[9];
kernelX[0] = -1.0; kernelX[1] = 0.0; kernelX[2] = 1.0;
kernelX[3] = -2.0; kernelX[4] = 0.0; kernelX[5] = 2.0;
kernelX[6] = -1.0; kernelX[7] = 0.0; kernelX[8] = 1.0;
float kernelY[9];
kernelY[0] = -1.0; kernelY[1] = -2.0; kernelY[2] = -1.0;
kernelY[3] = 0.0; kernelY[4] = 0.0; kernelY[5] = 0.0;
kernelY[6] = 1.0; kernelY[7] = 2.0; kernelY[8] = 1.0;
vec2 offsets[9];
offsets[0] = vec2(-1, -1); offsets[1] = vec2(0, -1); offsets[2] = vec2(1, -1);
offsets[3] = vec2(-1, 0); offsets[4] = vec2(0, 0); offsets[5] = vec2(1, 0);
offsets[6] = vec2(-1, 1); offsets[7] = vec2(0, 1); offsets[8] = vec2(1, 1);
float edgeX = 0.0;
float edgeY = 0.0;
for (int i = 0; i < 9; i++) {
vec2 coord = v_texCoord + offsets[i] * u_texelSize;
float gray = dot(texture2D(u_texture, coord).rgb, vec3(0.299, 0.587, 0.114));
edgeX += gray * kernelX[i];
edgeY += gray * kernelY[i];
}
float edge = sqrt(edgeX * edgeX + edgeY * edgeY);
gl_FragColor = vec4(vec3(edge), 1.0);
}
```
### 4. 辉光效果(Bloom)
```glsl
// 提取高亮部分
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float u_threshold;
void main() {
vec4 color = texture2D(u_texture, v_texCoord);
float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));
if (brightness > u_threshold) {
gl_FragColor = color;
} else {
gl_FragColor = vec4(0.0);
}
}
// 合成辉光
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_sceneTexture;
uniform sampler2D u_bloomTexture;
uniform float u_bloomIntensity;
void main() {
vec4 sceneColor = texture2D(u_sceneTexture, v_texCoord);
vec4 bloomColor = texture2D(u_bloomTexture, v_texCoord);
gl_FragColor = sceneColor + bloomColor * u_bloomIntensity;
}
```
### 5. 色调映射(Tone Mapping)
```glsl
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float u_exposure;
// Reinhard 色调映射
vec3 reinhardToneMapping(vec3 color) {
return color / (color + vec3(1.0));
}
// ACES 色调映射
vec3 acesToneMapping(vec3 color) {
const float a = 2.51;
const float b = 0.03;
const float c = 2.43;
const float d = 0.59;
const float e = 0.14;
return clamp((color * (a * color + b)) / (color * (c * color + d) + e), 0.0, 1.0);
}
void main() {
vec4 color = texture2D(u_texture, v_texCoord);
// 曝光调整
vec3 mapped = vec3(1.0) - exp(-color.rgb * u_exposure);
// Gamma 校正
mapped = pow(mapped, vec3(1.0 / 2.2));
gl_FragColor = vec4(mapped, color.a);
}
```
### 6. 色彩调整
```glsl
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float u_brightness;
uniform float u_contrast;
uniform float u_saturation;
void main() {
vec4 color = texture2D(u_texture, v_texCoord);
// 亮度
color.rgb += u_brightness;
// 对比度
color.rgb = (color.rgb - 0.5) * u_contrast + 0.5;
// 饱和度
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
color.rgb = mix(vec3(gray), color.rgb, u_saturation);
gl_FragColor = color;
}
```
### 7. 屏幕空间环境光遮蔽(SSAO)
```glsl
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_colorTexture;
uniform sampler2D u_depthTexture;
uniform sampler2D u_normalTexture;
uniform mat4 u_projectionMatrix;
uniform mat4 u_viewMatrix;
uniform vec2 u_texelSize;
uniform vec3 u_samples[64]; // 采样核
vec3 getViewPosition(vec2 texCoord) {
float depth = texture2D(u_depthTexture, texCoord).r;
vec4 clipPos = vec4(texCoord * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
vec4 viewPos = inverse(u_projectionMatrix) * clipPos;
return viewPos.xyz / viewPos.w;
}
void main() {
vec3 viewPos = getViewPosition(v_texCoord);
vec3 normal = texture2D(u_normalTexture, v_texCoord).xyz * 2.0 - 1.0;
float occlusion = 0.0;
float radius = 0.5;
for (int i = 0; i < 64; i++) {
// 采样点
vec3 samplePos = viewPos + u_samples[i] * radius;
// 投影到屏幕空间
vec4 offset = u_projectionMatrix * vec4(samplePos, 1.0);
offset.xyz = offset.xyz / offset.w * 0.5 + 0.5;
// 获取采样点深度
float sampleDepth = getViewPosition(offset.xy).z;
// 范围检查
float rangeCheck = smoothstep(0.0, 1.0, radius / abs(viewPos.z - sampleDepth));
// 如果采样点深度小于当前点深度,则遮挡
occlusion += (sampleDepth >= samplePos.z ? 1.0 : 0.0) * rangeCheck;
}
occlusion = 1.0 - (occlusion / 64.0);
vec3 color = texture2D(u_colorTexture, v_texCoord).rgb;
gl_FragColor = vec4(color * occlusion, 1.0);
}
```
## 多效果链式处理
```javascript
class PostProcessChain {
constructor(gl, width, height) {
this.gl = gl;
// 创建两个帧缓冲区用于乒乓渲染
this.fbo1 = new PostProcess(gl, width, height);
this.fbo2 = new PostProcess(gl, width, height);
this.effects = [];
}
addEffect(effect) {
this.effects.push(effect);
}
render(sceneRenderFunc) {
// 第一步:渲染场景到 FBO1
this.fbo1.beginScene();
sceneRenderFunc();
this.fbo1.endScene();
let readFBO = this.fbo1;
let writeFBO = this.fbo2;
// 应用每个效果
for (let effect of this.effects) {
writeFBO.beginScene();
effect.apply(readFBO.colorTexture);
writeFBO.endScene();
// 交换读写缓冲区
[readFBO, writeFBO] = [writeFBO, readFBO];
}
// 最后渲染到屏幕
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
finalPass.apply(readFBO.colorTexture);
}
}
```
## 性能优化建议
1. **降低分辨率**:
- 后期处理可以使用半分辨率或四分之一分辨率
- 特别适合模糊等效果
2. **合并效果**:
- 将多个简单效果合并到一个着色器中
- 减少渲染通道
3. **使用 Mipmap**:
- 模糊效果可以使用 mipmap 快速降采样
4. **智能更新**:
- 静态场景不需要每帧都进行后期处理
- 可以隔帧更新某些效果
5. **移动端优化**:
- 减少采样次数
- 使用简单的近似算法
服务端 · 3月6日 21:57
WebGL 中的帧缓冲区(Framebuffer)和离屏渲染是什么?## WebGL 帧缓冲区概述
帧缓冲区(Framebuffer)是 WebGL 中用于离屏渲染的目标。与默认的屏幕缓冲区不同,帧缓冲区可以将渲染结果输出到纹理或渲染缓冲区,而不是直接显示在屏幕上。
## 帧缓冲区的组成
一个完整的帧缓冲区(FBO)可以包含以下附件:
1. **颜色附件(Color Attachment)**:存储颜色信息,可以是纹理或渲染缓冲区
2. **深度附件(Depth Attachment)**:存储深度值
3. **模板附件(Stencil Attachment)**:存储模板值
```
┌─────────────────────────────────────┐
│ Framebuffer │
├─────────────────────────────────────┤
│ Color Attachment │ Texture/ │
│ │ Renderbuffer │
├─────────────────────────────────────┤
│ Depth Attachment │ Renderbuffer │
├─────────────────────────────────────┤
│ Stencil Attachment │ Renderbuffer │
└─────────────────────────────────────┘
```
## 创建帧缓冲区
### 基本创建流程
```javascript
// 1. 创建帧缓冲区
const framebuffer = gl.createFramebuffer();
// 2. 创建颜色附件(纹理)
const colorTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, colorTexture);
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 3. 创建深度附件(渲染缓冲区)
const depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
// 4. 绑定帧缓冲区并附加附件
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
colorTexture,
0
);
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
gl.RENDERBUFFER,
depthBuffer
);
// 5. 检查帧缓冲区完整性
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
console.error('Framebuffer incomplete:', status);
}
// 6. 解绑
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
```
## 离屏渲染
### 基本渲染流程
```javascript
function renderToTexture() {
// 1. 绑定帧缓冲区(离屏渲染)
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
// 2. 设置视口
gl.viewport(0, 0, framebufferWidth, framebufferHeight);
// 3. 清除缓冲区
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 4. 渲染场景
drawScene();
// 5. 解绑帧缓冲区(恢复屏幕渲染)
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// 6. 恢复视口
gl.viewport(0, 0, canvas.width, canvas.height);
// 7. 使用渲染结果(colorTexture 现在包含渲染结果)
gl.bindTexture(gl.TEXTURE_2D, colorTexture);
drawFullscreenQuad();
}
```
## 纹理 vs 渲染缓冲区
| 特性 | 纹理(Texture) | 渲染缓冲区(Renderbuffer) |
|------|-----------------|---------------------------|
| **可读性** | 可以作为纹理采样 | 不能直接读取 |
| **性能** | 稍慢 | 更快(针对写入优化) |
| **用途** | 需要后续处理的颜色附件 | 深度/模板附件 |
| **灵活性** | 高 | 低 |
### 选择建议
- **颜色附件**:通常使用纹理(需要后续采样)
- **深度/模板附件**:通常使用渲染缓冲区(性能更好)
## 多重渲染目标(MRT - WebGL 2.0)
```javascript
// WebGL 2.0 支持同时渲染到多个颜色附件
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
// 创建多个颜色附件
const textures = [];
for (let i = 0; i < 4; i++) {
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 + i,
gl.TEXTURE_2D,
texture,
0
);
textures.push(texture);
}
// 指定要绘制的附件
gl.drawBuffers([
gl.COLOR_ATTACHMENT0, // 输出到纹理 0
gl.COLOR_ATTACHMENT1, // 输出到纹理 1
gl.COLOR_ATTACHMENT2, // 输出到纹理 2
gl.COLOR_ATTACHMENT3 // 输出到纹理 3
]);
```
```glsl
#version 300 es
layout(location = 0) out vec4 gPosition;
layout(location = 1) out vec4 gNormal;
layout(location = 2) out vec4 gAlbedo;
layout(location = 3) out vec4 gMaterial;
void main() {
gPosition = vec4(worldPos, 1.0);
gNormal = vec4(normal, 0.0);
gAlbedo = texture(u_diffuseMap, texCoord);
gMaterial = vec4(roughness, metallic, ao, 1.0);
}
```
## 常见应用场景
### 1. 后期处理(Post-processing)
```javascript
// 渲染场景到纹理
renderToTexture();
// 应用后期处理效果
applyBloomEffect();
applyToneMapping();
```
### 2. 阴影贴图(Shadow Mapping)
```javascript
// 第一步:从光源视角渲染深度
gl.bindFramebuffer(gl.FRAMEBUFFER, shadowFramebuffer);
renderSceneFromLightPerspective();
// 第二步:使用深度纹理渲染场景
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
useShadowMapTexture();
renderSceneWithShadows();
```
### 3. 反射/折射
```javascript
// 渲染环境到立方体贴图
for (let face = 0; face < 6; face++) {
gl.bindFramebuffer(gl.FRAMEBUFFER, cubeFramebuffer[face]);
setupCameraForFace(face);
renderEnvironment();
}
// 使用环境贴图
useCubeMapTexture();
renderReflectiveObject();
```
### 4. 延迟渲染(Deferred Rendering)
```javascript
// G-Buffer 渲染
gl.bindFramebuffer(gl.FRAMEBUFFER, gBuffer);
gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2]);
renderGeometry();
// 光照计算
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
useGBufferTextures();
applyLighting();
```
## 帧缓冲区管理类
```javascript
class Framebuffer {
constructor(gl, width, height, options = {}) {
this.gl = gl;
this.width = width;
this.height = height;
this.framebuffer = gl.createFramebuffer();
this.textures = {};
this.renderbuffers = {};
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
// 创建颜色附件
if (options.color !== false) {
this.addColorAttachment(0, options.colorFormat || gl.RGBA);
}
// 创建深度附件
if (options.depth) {
this.addDepthAttachment(options.depthFormat || gl.DEPTH_COMPONENT16);
}
// 创建模板附件
if (options.stencil) {
this.addStencilAttachment();
}
// 检查完整性
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
throw new Error(`Framebuffer incomplete: ${status}`);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
addColorAttachment(index, internalFormat) {
const gl = this.gl;
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + index, gl.TEXTURE_2D, texture, 0);
this.textures[`color${index}`] = texture;
return texture;
}
addDepthAttachment(internalFormat) {
const gl = this.gl;
const renderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, this.width, this.height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);
this.renderbuffers.depth = renderbuffer;
return renderbuffer;
}
bind() {
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
this.gl.viewport(0, 0, this.width, this.height);
}
unbind() {
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
}
getTexture(name = 'color0') {
return this.textures[name];
}
resize(width, height) {
this.width = width;
this.height = height;
// 重新创建纹理和渲染缓冲区...
}
destroy() {
const gl = this.gl;
gl.deleteFramebuffer(this.framebuffer);
Object.values(this.textures).forEach(t => gl.deleteTexture(t));
Object.values(this.renderbuffers).forEach(r => gl.deleteRenderbuffer(r));
}
}
// 使用示例
const fbo = new Framebuffer(gl, 1024, 1024, {
color: true,
depth: true
});
fbo.bind();
renderScene();
fbo.unbind();
// 使用渲染结果
const texture = fbo.getTexture('color0');
gl.bindTexture(gl.TEXTURE_2D, texture);
```
## 性能优化建议
1. **复用帧缓冲区**:避免频繁创建和销毁
2. **使用适当尺寸**:不要创建过大的帧缓冲区
3. **共享深度缓冲区**:多个帧缓冲区可以共享同一个深度缓冲区
4. **延迟创建**:只在需要时创建帧缓冲区
5. **使用渲染缓冲区存储深度**:比纹理更高效
## 常见问题
### 帧缓冲区不完整
```javascript
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
switch (status) {
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
console.error('附件不完整');
break;
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
console.error('缺少附件');
break;
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
console.error('附件尺寸不匹配');
break;
case gl.FRAMEBUFFER_UNSUPPORTED:
console.error('不支持的格式组合');
break;
}
```
### 纹理尺寸限制
```javascript
const maxSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
const maxRenderbufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
console.log(`Max texture size: ${maxSize}`);
console.log(`Max renderbuffer size: ${maxRenderbufferSize}`);
```
服务端 · 3月6日 21:57
WebGL 性能优化有哪些常用技巧?## WebGL 性能优化概述
WebGL 性能优化是 3D Web 应用开发的关键。由于 JavaScript 和 GPU 之间的通信开销,以及移动设备的资源限制,合理的优化策略能显著提升渲染性能。
## 1. 减少绘制调用(Draw Calls)
### 问题
每次 `gl.drawArrays` 或 `gl.drawElements` 都有 CPU 到 GPU 的通信开销。
### 优化方案
#### 批量绘制(Batching)
```javascript
// 优化前:多次绘制调用
for (let mesh of meshes) {
gl.bindBuffer(gl.ARRAY_BUFFER, mesh.vbo);
gl.drawArrays(gl.TRIANGLES, 0, mesh.vertexCount);
}
// 优化后:合并到一个缓冲区
const mergedBuffer = mergeMeshes(meshes);
gl.bindBuffer(gl.ARRAY_BUFFER, mergedBuffer);
gl.drawArrays(gl.TRIANGLES, 0, totalVertexCount);
```
#### 实例化渲染(Instanced Rendering)
```javascript
// WebGL 2.0 原生支持
// 一次绘制调用渲染多个相同几何体
const instanceCount = 1000;
gl.drawArraysInstanced(gl.TRIANGLES, 0, vertexCount, instanceCount);
```
## 2. 减少状态切换
### 问题
频繁切换着色器程序、纹理、缓冲区等状态会造成性能开销。
### 优化方案
#### 按状态排序
```javascript
// 按着色器程序排序
meshes.sort((a, b) => a.program.id - b.program.id);
// 按纹理排序
meshes.sort((a, b) => a.texture.id - b.texture.id);
let currentProgram = null;
let currentTexture = null;
for (let mesh of meshes) {
// 只在需要时切换程序
if (mesh.program !== currentProgram) {
gl.useProgram(mesh.program);
currentProgram = mesh.program;
}
// 只在需要时切换纹理
if (mesh.texture !== currentTexture) {
gl.bindTexture(gl.TEXTURE_2D, mesh.texture);
currentTexture = mesh.texture;
}
mesh.draw();
}
```
#### 使用纹理图集(Texture Atlas)
```javascript
// 将多个小纹理合并为一个大纹理
// 减少纹理绑定切换次数
const atlasTexture = createTextureAtlas([
'texture1.png',
'texture2.png',
'texture3.png'
]);
// 在着色器中使用纹理坐标偏移
uniform vec2 u_atlasOffset;
uniform vec2 u_atlasScale;
vec2 atlasCoord = a_texCoord * u_atlasScale + u_atlasOffset;
vec4 color = texture2D(u_texture, atlasCoord);
```
## 3. 优化着色器
### 顶点着色器优化
```glsl
// 优化前:在顶点着色器中进行复杂计算
attribute vec3 a_position;
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
void main() {
// 每次顶点都进行矩阵乘法
mat4 mvp = u_projectionMatrix * u_viewMatrix * u_modelMatrix;
gl_Position = mvp * vec4(a_position, 1.0);
}
// 优化后:在 CPU 预计算 MVP 矩阵
uniform mat4 u_mvpMatrix;
void main() {
gl_Position = u_mvpMatrix * vec4(a_position, 1.0);
}
```
### 片段着色器优化
```glsl
// 优化前:复杂的逐像素计算
void main() {
vec3 lightDir = normalize(u_lightPos - v_worldPos);
float diff = max(dot(v_normal, lightDir), 0.0);
vec3 diffuse = diff * u_lightColor;
// ... 更多计算
}
// 优化后:顶点着色器计算光照
// 顶点着色器
varying vec3 v_lightIntensity;
void main() {
// 在顶点级别计算光照
vec3 lightDir = normalize(u_lightPos - worldPos);
float diff = max(dot(normal, lightDir), 0.0);
v_lightIntensity = diff * u_lightColor;
}
// 片段着色器
void main() {
// 使用插值后的光照强度
gl_FragColor = vec4(v_lightIntensity * textureColor, 1.0);
}
```
### 使用适当精度
```glsl
// 高精度(highp)- 顶点位置、变换矩阵
attribute highp vec3 a_position;
uniform highp mat4 u_mvpMatrix;
// 中精度(mediump)- 颜色、纹理坐标
attribute mediump vec2 a_texCoord;
varying mediump vec2 v_texCoord;
// 低精度(lowp)- 光照计算结果
varying lowp vec3 v_lightColor;
```
## 4. 缓冲区优化
### 使用 VAO 减少状态设置
```javascript
// WebGL 2.0 或扩展支持
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// 配置顶点属性(只执行一次)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(1);
gl.bindVertexArray(null);
// 绘制时只需绑定 VAO
gl.bindVertexArray(vao);
gl.drawArrays(gl.TRIANGLES, 0, count);
```
### 使用索引绘制
```javascript
// 优化前:36 个顶点定义一个立方体
const vertices = new Float32Array([
// 每个面 6 个顶点,共 6 个面
// 大量重复顶点数据
]);
// 优化后:8 个顶点 + 36 个索引
const vertices = new Float32Array([
// 8 个唯一顶点
]);
const indices = new Uint16Array([
// 36 个索引定义 12 个三角形
]);
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
```
### 使用 Interleaved Arrays
```javascript
// 优化前:分离的缓冲区
const positions = new Float32Array([/* ... */]);
const colors = new Float32Array([/* ... */]);
const texCoords = new Float32Array([/* ... */]);
// 优化后:交错的顶点数据
const vertices = new Float32Array([
// x, y, z, r, g, b, u, v
0, 0, 0, 1, 0, 0, 0, 0, // 顶点 1
1, 0, 0, 0, 1, 0, 1, 0, // 顶点 2
// ...
]);
// 更好的缓存局部性
```
## 5. 纹理优化
### 纹理压缩
```javascript
// 使用压缩纹理格式
const compressedExtension = gl.getExtension('WEBGL_compressed_texture_s3tc');
// 上传压缩纹理数据
gl.compressedTexImage2D(
gl.TEXTURE_2D,
0,
compressedExtension.COMPRESSED_RGBA_S3TC_DXT5_EXT,
width,
height,
0,
compressedData
);
```
### Mipmap 使用
```javascript
// 启用 mipmap 提高渲染质量和性能
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
```
### 纹理尺寸优化
- 使用 2 的幂次尺寸(支持 mipmap)
- 避免过大的纹理(内存和带宽开销)
- 根据距离使用不同分辨率的纹理(LOD)
## 6. 遮挡剔除和视锥剔除
### 视锥剔除
```javascript
function isInFrustum(boundingBox, viewProjectionMatrix) {
// 将包围盒转换到裁剪空间
// 检查是否在视锥体内
const corners = boundingBox.getCorners();
for (let corner of corners) {
const clipPos = transformPoint(corner, viewProjectionMatrix);
if (Math.abs(clipPos.x) <= clipPos.w &&
Math.abs(clipPos.y) <= clipPos.w &&
0 <= clipPos.z && clipPos.z <= clipPos.w) {
return true;
}
}
return false;
}
// 只渲染在视锥体内的物体
for (let object of scene.objects) {
if (isInFrustum(object.boundingBox, vpMatrix)) {
object.render();
}
}
```
### 遮挡查询(WebGL 2.0)
```javascript
const query = gl.createQuery();
// 开始遮挡查询
gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
drawBoundingBox(object);
gl.endQuery(gl.ANY_SAMPLES_PASSED);
// 检查结果
const available = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
if (available) {
const visible = gl.getQueryParameter(query, gl.QUERY_RESULT) > 0;
if (visible) {
drawDetailedMesh(object);
}
}
```
## 7. 帧缓冲区优化
### 减少分辨率
```javascript
// 在高 DPI 屏幕上使用适当分辨率
const dpr = Math.min(window.devicePixelRatio, 2);
canvas.width = canvas.clientWidth * dpr;
canvas.height = canvas.clientHeight * dpr;
```
### 延迟渲染优化
```javascript
// G-Buffer 优化:使用适当精度
// 位置:RGB16F 或 RGBA16F
// 法线:RGB10_A2 或 RGBA8
// 材质:RGBA8
```
## 8. JavaScript 优化
### 避免垃圾回收
```javascript
// 优化前:每帧创建新数组
function update() {
const matrix = new Float32Array(16); // 创建垃圾
// ...
}
// 优化后:重用数组
const matrix = new Float32Array(16);
function update() {
// 重用 matrix,不创建新对象
mat4.identity(matrix);
// ...
}
```
### 使用 TypedArrays
```javascript
// 使用 Float32Array 而不是普通数组
const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
```
## 9. 移动端优化
### 减少过度绘制
```javascript
// 从前到后绘制不透明物体
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
opaqueObjects.sort((a, b) => b.distance - a.distance);
for (let obj of opaqueObjects) {
obj.draw();
}
```
### 使用适当精度
```glsl
// 移动端使用 mediump 优化性能
precision mediump float;
```
### 避免复杂着色器
- 减少纹理采样次数
- 避免动态分支
- 简化光照计算
## 10. 性能监控
```javascript
// 使用 EXT_disjoint_timer_query 测量 GPU 时间
const ext = gl.getExtension('EXT_disjoint_timer_query');
const query = ext.createQueryEXT();
ext.beginQueryEXT(ext.TIME_ELAPSED_EXT, query);
drawScene();
ext.endQueryEXT(ext.TIME_ELAPSED_EXT);
// 获取结果
const available = ext.getQueryObjectEXT(query, ext.QUERY_RESULT_AVAILABLE_EXT);
if (available) {
const timeElapsed = ext.getQueryObjectEXT(query, ext.QUERY_RESULT_EXT);
console.log(`GPU time: ${timeElapsed / 1000000} ms`);
}
```
## 总结
| 优化方向 | 主要技巧 |
|----------|----------|
| 绘制调用 | 批量绘制、实例化渲染 |
| 状态切换 | 状态排序、纹理图集、VAO |
| 着色器 | 预计算、适当精度、简化计算 |
| 缓冲区 | 索引绘制、交错数组 |
| 纹理 | 压缩、mipmap、合理尺寸 |
| 剔除 | 视锥剔除、遮挡查询 |
| JavaScript | 避免 GC、TypedArrays |
服务端 · 3月6日 21:57
WebGL 1.0 和 WebGL 2.0 有什么区别?## WebGL 版本概述
WebGL 1.0 于 2011 年发布,基于 OpenGL ES 2.0。WebGL 2.0 于 2017 年发布,基于 OpenGL ES 3.0,带来了大量新功能和性能改进。
## 主要区别对比
| 特性 | WebGL 1.0 | WebGL 2.0 |
|------|-----------|-----------|
| **基础规范** | OpenGL ES 2.0 | OpenGL ES 3.0 |
| **发布年份** | 2011 | 2017 |
| **着色器版本** | GLSL ES 1.0 | GLSL ES 3.0 |
| **3D 纹理** | 需要扩展 | 原生支持 |
| **多重渲染目标(MRT)** | 需要扩展 | 原生支持 |
| **实例化渲染** | 需要扩展 | 原生支持 |
| **变换反馈** | 不支持 | 支持 |
| **采样器对象** | 不支持 | 支持 |
| **顶点数组对象(VAO)** | 需要扩展 | 原生支持 |
| **非2的幂次纹理** | 有限制 | 完全支持 |
## 着色器语言差异
### WebGL 1.0 (GLSL ES 1.0)
```glsl
// 顶点着色器
attribute vec3 a_position;
attribute vec2 a_texCoord;
uniform mat4 u_mvpMatrix;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
gl_Position = u_mvpMatrix * vec4(a_position, 1.0);
v_texCoord = a_texCoord;
}
// 片段着色器
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
}
```
### WebGL 2.0 (GLSL ES 3.0)
```glsl
#version 300 es
// 顶点着色器
in vec3 a_position; // attribute → in
in vec2 a_texCoord;
uniform mat4 u_mvpMatrix;
out vec2 v_texCoord; // varying → out
void main() {
gl_Position = u_mvpMatrix * vec4(a_position, 1.0);
v_texCoord = a_texCoord;
}
// 片段着色器
#version 300 es
precision mediump float;
in vec2 v_texCoord; // varying → in
uniform sampler2D u_texture;
out vec4 fragColor; // gl_FragColor → out 变量
void main() {
fragColor = texture(u_texture, v_texCoord); // texture2D → texture
}
```
### 着色器语法变化
| WebGL 1.0 | WebGL 2.0 | 说明 |
|-----------|-----------|------|
| `attribute` | `in` | 顶点输入 |
| `varying` | `in/out` | 顶点/片段间传递数据 |
| `gl_FragColor` | `out` 变量 | 片段着色器输出 |
| `texture2D()` | `texture()` | 2D 纹理采样 |
| `textureCube()` | `texture()` | 立方体纹理采样 |
| - | `#version 300 es` | 版本声明(必需) |
## WebGL 2.0 新增功能详解
### 1. 3D 纹理
```javascript
// WebGL 2.0 原生支持 3D 纹理
const texture3D = gl.createTexture();
gl.bindTexture(gl.TEXTURE_3D, texture3D);
gl.texImage3D(
gl.TEXTURE_3D, // 目标
0, // 级别
gl.RGBA, // 内部格式
width,
height,
depth, // 深度
0, // 边框
gl.RGBA,
gl.UNSIGNED_BYTE,
data
);
```
```glsl
#version 300 es
uniform sampler3D u_volumeTexture;
void main() {
vec4 color = texture(u_volumeTexture, vec3(x, y, z));
}
```
### 2. 多重渲染目标(MRT)
```javascript
// 创建帧缓冲区,绑定多个颜色附件
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
// 附加多个纹理
const textures = [];
for (let i = 0; i < 4; i++) {
const texture = gl.createTexture();
// ... 配置纹理
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0 + i, // COLOR_ATTACHMENT0, COLOR_ATTACHMENT1, ...
gl.TEXTURE_2D,
texture,
0
);
textures.push(texture);
}
// 指定绘制到哪些附件
gl.drawBuffers([
gl.COLOR_ATTACHMENT0,
gl.COLOR_ATTACHMENT1,
gl.COLOR_ATTACHMENT2,
gl.COLOR_ATTACHMENT3
]);
```
```glsl
#version 300 es
layout(location = 0) out vec4 color0;
layout(location = 1) out vec4 color1;
layout(location = 2) out vec4 color2;
layout(location = 3) out vec4 color3;
void main() {
color0 = vec4(1.0, 0.0, 0.0, 1.0);
color1 = vec4(0.0, 1.0, 0.0, 1.0);
color2 = vec4(0.0, 0.0, 1.0, 1.0);
color3 = vec4(1.0, 1.0, 1.0, 1.0);
}
```
### 3. 实例化渲染(Instanced Rendering)
```javascript
// WebGL 2.0 原生支持
// 绘制 1000 个实例,每个实例使用不同的变换
const instanceCount = 1000;
gl.drawArraysInstanced(gl.TRIANGLES, 0, vertexCount, instanceCount);
// 或使用索引绘制
gl.drawElementsInstanced(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0, instanceCount);
```
```glsl
#version 300 es
in vec3 a_position;
in mat4 a_instanceMatrix; // 实例化矩阵
void main() {
gl_Position = u_projectionMatrix * u_viewMatrix * a_instanceMatrix * vec4(a_position, 1.0);
}
```
### 4. 变换反馈(Transform Feedback)
```javascript
// 创建变换反馈对象
const transformFeedback = gl.createTransformFeedback();
// 设置顶点着色器输出
const vertexShaderSource = `#version 300 es
in vec3 a_position;
out vec3 v_newPosition; // 变换后的位置
void main() {
v_newPosition = a_position * 2.0; // 某种变换
}
`;
// 配置变换反馈
const program = gl.createProgram();
// ... 编译链接着色器
gl.transformFeedbackVaryings(
program,
['v_newPosition'], // 要捕获的输出变量
gl.SEPARATE_ATTRIBS // 或 INTERLEAVED_ATTRIBS
);
// 执行变换反馈
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedback);
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, count);
gl.endTransformFeedback();
```
### 5. 采样器对象(Sampler Objects)
```javascript
// 将纹理参数从纹理对象分离
const sampler = gl.createSampler();
// 配置采样器参数
gl.samplerParameteri(sampler, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.samplerParameteri(sampler, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.samplerParameteri(sampler, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.samplerParameteri(sampler, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 绑定采样器到纹理单元
gl.bindSampler(0, sampler); // 绑定到纹理单元 0
```
### 6. 顶点数组对象(VAO)原生支持
```javascript
// WebGL 2.0 原生支持,无需扩展
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// 配置顶点属性(存储在 VAO 中)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
gl.bindVertexArray(null);
// 绘制时只需绑定 VAO
gl.bindVertexArray(vao);
gl.drawArrays(gl.TRIANGLES, 0, count);
```
## 新纹理功能
### 非2的幂次纹理完整支持
```javascript
// WebGL 2.0 中,非2的幂次纹理支持所有功能
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
gl.generateMipmap(gl.TEXTURE_2D); // 也支持 mipmap
```
### 纹理数组
```javascript
const textureArray = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D_ARRAY, textureArray);
gl.texImage3D(
gl.TEXTURE_2D_ARRAY,
0,
gl.RGBA,
width,
height,
layerCount, // 层数
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
data
);
```
## 如何检测 WebGL 2.0 支持
```javascript
function getWebGLContext(canvas) {
// 优先尝试 WebGL 2.0
let gl = canvas.getContext('webgl2');
if (gl) {
console.log('Using WebGL 2.0');
return gl;
}
// 回退到 WebGL 1.0
gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (gl) {
console.log('Using WebGL 1.0');
return gl;
}
console.error('WebGL not supported');
return null;
}
```
## 浏览器支持情况
| 浏览器 | WebGL 1.0 | WebGL 2.0 |
|--------|-----------|-----------|
| Chrome | ✓ | ✓ (56+) |
| Firefox | ✓ | ✓ (51+) |
| Safari | ✓ | ✓ (15+) |
| Edge | ✓ | ✓ (79+) |
| IE 11 | ✓ | ✗ |
## 迁移建议
1. **渐进增强**:先检测 WebGL 2.0 支持,不支持时回退到 1.0
2. **着色器版本**:为两个版本准备不同的着色器代码
3. **功能检测**:使用特定功能前检查是否可用
4. **性能考虑**:WebGL 2.0 功能更强大,但 1.0 兼容性更好
服务端 · 3月6日 21:57