乐闻世界logo
搜索文章和话题

WebGL 中的雾效(Fog)是如何实现的?

3月7日 12:04

WebGL 雾效概述

雾效(Fog)是一种模拟大气散射效果的渲染技术,使远处的物体逐渐融入到背景颜色中。雾效不仅能增加场景的真实感,还能隐藏远处的裁剪边界,优化性能。

雾效的基本原理

雾效的核心思想是根据物体与相机的距离,在物体颜色和雾颜色之间进行线性或指数插值:

shell
最终颜色 = 物体颜色 × (1 - 雾因子) + 雾颜色 × 雾因子

雾的类型

1. 线性雾(Linear Fog)

雾的浓度随距离线性增加。

公式

shell
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)

雾的浓度随距离指数增加,效果更自然。

公式

shell
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)

雾的浓度随距离平方指数增加,效果更加柔和。

公式

shell
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. 与其他效果结合

    • 雾效可以与体积光、大气散射结合
    • 注意雾效对透明物体的影响
标签:WebGL