在 WebGL 三维可视化中,雾效(Fog) 是一种常见的渲染技巧,用来模拟远处物体因空气中的颗粒而变得模糊、朦胧的视觉效果。在 Three.js 中,雾效不仅能增强画面的真实感,还能增加空间层次,优化视觉体验。

本文将深入介绍 Three.js 中的雾效,包括 FogFogExp2 的区别、使用方式、参数解析、注意事项,并附带完整示例代码。


🧠一、什么是雾效(Fog)?

雾效可以让场景中的物体根据距离相机的远近被逐渐“吞没”于雾中。主要目的包括:

  • 模拟自然界大气透视现象;

  • 缓解远距离模型贴图粗糙问题;

  • 提升沉浸感和美术表现力。


📊二、Three.js 中的雾类型

Three.js 提供了两种雾类型,分别是:

类别 类名 特点说明
线性雾 THREE.Fog 雾的密度随距离线性增加
指数雾 THREE.FogExp2 雾的密度随距离指数级增加,更自然柔和

🔁三、Fog 和 FogExp2 参数详解

1. Fog 构造函数

const fog = new THREE.Fog(color, near, far);

参数 说明
color 雾的颜色(可与背景一致或略有差异)
near 雾效开始距离(距离相机)
far 雾效结束距离(超过该距离的物体完全被雾遮挡)

2. FogExp2 构造函数

const fogExp = new THREE.FogExp2(color, density);

参数 说明
color 雾的颜色
density 雾的密度,值越大雾越浓,通常设置在 0.001~0.05

🧪四、如何在场景中添加雾效?

只需要将雾对象赋值给 scene.fog 即可:

scene.fog = new THREE.Fog(0xffffff, 10, 100);

或者使用指数雾:

scene.fog = new THREE.FogExp2(0xcccccc, 0.02);

✅五、配合背景颜色使用

为了达到更自然的效果,建议将渲染器的背景色与雾的颜色保持一致:

renderer.setClearColor(scene.fog.color);

📁六、雾对哪些材质生效?

雾效默认对以下材质生效(内置支持):

  • MeshBasicMaterial

  • MeshLambertMaterial

  • MeshPhongMaterial

  • MeshStandardMaterial

  • ShaderMaterial(需自行实现雾相关 shader)

注意:对使用自定义 ShaderMaterial 的材质,需手动处理雾的混合逻辑。


📦七、Fog 与相机距离配合建议

  • near 太小 → 近处物体也被雾覆盖,画面变灰;

  • far 太大 → 雾效不明显;

  • 可根据相机远裁剪面 (camera.far) 的值做合理分布。


🔧八、Fog vs FogExp2 的区别

特性 Fog(线性雾) FogExp2(指数雾)
控制粒度 更精确 更柔和
表现自然程度 稍显突兀 更自然、渐变平滑
使用场景 工业建筑、可控雾效 游戏、影视、自然场景

🌀九、完整示例代码(Fog)

<!--
 * @Author: 彭麒
 * @Date: 2025/4/20
 * @Email: 1062470959@qq.com
 * @Description: 此源码版权归吉檀迦俐所有,可供学习和借鉴或商用。
 -->  
<template>
  <div ref="containerRef" class="w-full h-full"></div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import * as THREE from 'three'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

const containerRef = ref()
let scene, camera, renderer, animationId, gui
let fogMesh

const fogParams = {
  showFog: true,
  fogOpacity: 0.3,
  fogScale: 5,
  fogColor: '#ffffff',
}

onMounted(() => {
  const container = containerRef.value
  const width = container.clientWidth
  const height = container.clientHeight

  scene = new THREE.Scene()

  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false })
  renderer.setSize(width, height)
  renderer.setClearColor(0x87ceeb) // 蓝天背景
  container.appendChild(renderer.domElement)

  camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000)
  camera.position.set(0, 5, 20)

  // 环境光
  scene.add(new THREE.AmbientLight(0xffffff, 0.5))

  // 平行光
  const light = new THREE.DirectionalLight(0xffffff, 0.8)
  light.position.set(10, 10, 10)
  scene.add(light)

  // 地面
  const ground = new THREE.Mesh(
    new THREE.PlaneGeometry(100, 100),
    new THREE.MeshStandardMaterial({ color: 0x228822 })
  )
  ground.rotation.x = -Math.PI / 2
  ground.position.y = 0
  scene.add(ground)

  // 山体(球体)
  for (let i = 0; i < 10; i++) {
    const mountain = new THREE.Mesh(
      new THREE.SphereGeometry(1 + Math.random() * 2, 32, 32),
      new THREE.MeshStandardMaterial({ color: 0x888888 })
    )
    mountain.position.set(
      Math.random() * 30 - 15,
      1,
      Math.random() * -20
    )
    scene.add(mountain)
  }

  // 局部雾层:使用透明材质的球体模拟
  const fogGeo = new THREE.SphereGeometry(1, 32, 32)
  const fogMat = new THREE.MeshBasicMaterial({
    color: fogParams.fogColor,
    transparent: true,
    opacity: fogParams.fogOpacity,
    depthWrite: false,
  })
  fogMesh = new THREE.Mesh(fogGeo, fogMat)
  fogMesh.scale.set(fogParams.fogScale, fogParams.fogScale, fogParams.fogScale)
  fogMesh.position.set(0, 2, -5)
  scene.add(fogMesh)

  // GUI
  gui = new GUI()
  gui.add(fogParams, 'showFog').name('显示雾层').onChange(() => {
    fogMesh.visible = fogParams.showFog
  })
  gui.add(fogParams, 'fogOpacity', 0.05, 1).step(0.01).name('雾透明度').onChange(val => {
    fogMesh.material.opacity = val
  })
  gui.add(fogParams, 'fogScale', 1, 10).step(0.1).name('雾大小').onChange(val => {
    fogMesh.scale.set(val, val, val)
  })
  gui.addColor(fogParams, 'fogColor').name('雾颜色').onChange(val => {
    fogMesh.material.color.set(val)
  })

  // 动画
  const animate = () => {
    animationId = requestAnimationFrame(animate)
    fogMesh.rotation.y += 0.002
    renderer.render(scene, camera)
  }

  animate()
})

onBeforeUnmount(() => {
  cancelAnimationFrame(animationId)
  renderer.dispose()
  gui.destroy()
})
</script>

<style scoped>
div {
  width: 100%;
  height: 100vh;
  overflow: hidden;
}
</style>

🎨十、FogExp2 示例代码(指数雾)

只需替换:

scene.fog = new THREE.FogExp2(0xaabbcc, 0.04);

即可实现指数雾,变化更加平滑。


🌄十一、常见问题及优化建议

  • 材质无效? 检查是否是支持雾的材质;

  • 雾太浓或看不到? 合理调整 near / fardensity

  • 背景色突兀? 使用 renderer.setClearColor 同步背景;

  • 性能问题? 雾本身性能开销小,但如果场景过大应考虑 LOD 机制结合雾优化;


📐十二、自定义 Shader 如何支持雾效?

Three.js 提供内置雾的宏定义和代码片段:

#ifdef USE_FOG 
// 自动注入雾效 uniforms 和代码 
#endif

如果使用 ShaderMaterial,需手动添加:

#include <fog_pars_fragment> 
// 在 gl_FragColor 混合前加 #include 
<fog_fragment>

📝总结

雾效作为 Three.js 中的一项基础但非常重要的视觉增强手段,使用简单、效果直观,适合各种场景。通过合理设置 FogFogExp2,可以让三维画面更具空间层次与真实感,尤其在大型地图、游戏场景、科幻模拟等项目中尤为重要。


如果你觉得本文有帮助,欢迎点赞、收藏、评论支持 👇
也欢迎关注我,后续将持续更新更多 Three.js 实战系列文章

如有问题或建议,也欢迎留言交流!

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐