WebGL 雾效概述
雾效(Fog)是一种模拟大气散射效果的渲染技术,使远处的物体逐渐融入到背景颜色中。雾效不仅能增加场景的真实感,还能隐藏远处的裁剪边界,优化性能。
雾效的基本原理
雾效的核心思想是根据物体与相机的距离,在物体颜色和雾颜色之间进行线性或指数插值:
shell最终颜色 = 物体颜色 × (1 - 雾因子) + 雾颜色 × 雾因子
雾的类型
1. 线性雾(Linear Fog)
雾的浓度随距离线性增加。
公式:
shellfogFactor = (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)
雾的浓度随距离指数增加,效果更自然。
公式:
shellfogFactor = exp(-density × distance)
glsluniform 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)
雾的浓度随距离平方指数增加,效果更加柔和。
公式:
shellfogFactor = exp(-(density × distance)²)
glslvoid 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); }
完整的雾效实现
顶点着色器
glslattribute 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; }
片段着色器
glslprecision 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 控制
javascriptclass 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); }
雾效与光照结合
glslvoid 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 轴 | 适合山谷、水面等场景 |
实际应用建议
-
选择合适的雾类型:
- 大多数场景使用指数或指数平方雾
- 需要精确控制雾边界时使用线性雾
-
调整雾的颜色:
- 通常与天空盒或背景色一致
- 可以随时间变化模拟昼夜效果
-
性能考虑:
- 移动端建议使用顶点级雾效
- 复杂场景可以使用后处理雾效
-
与其他效果结合:
- 雾效可以与体积光、大气散射结合
- 注意雾效对透明物体的影响