news 2026/10/7 6:18:31

Unity3D全局雾效实战:从原理到避坑的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity3D全局雾效实战:从原理到避坑的完整指南

简介:这份资源面向Unity3D开发者与图形渲染学习者,聚焦全局雾效的完整实现方案,帮助解决场景缺乏纵深感、物体边缘生硬以及沉浸感不足等问题。包内共16159个文件,以cs脚本、meta元数据、png贴图、md说明文档为主,另含shader、cginc、mat材质、prefab预制体、unity场景及dll依赖库等,压缩包约686.42MB,覆盖从渲染设置到着色器编写的完整工程结构。资源围绕全局雾与体积雾的实现原理展开,涉及Render Settings参数调节、深度纹理采样、自定义Shader中基于距离衰减的雾浓度计算与颜色混合,以及C#脚本对雾效开关和动态参数的交互控制,并附带Fog.sln解决方案与ProjectSettings等工程配置,便于在Visual Studio中直接编译调试。目前已有657人学习下载,适合希望掌握深度纹理应用、Shader编程与雾效调优的中高级开发者参考借鉴。

1. 场景拆解:Unity3D 全局雾效到底解决什么问题

很多人在 Unity3D 里做场景时,第一反应是堆模型、调材质、加后处理,结果画面一跑起来,远处物体边缘像被刀切过一样生硬,天空盒和地面之间缺少过渡,整个场景的纵深感全靠美术硬撑。全局雾效就是用来解决这个问题的——它不挑单个物体,而是对整个场景的空间深度做统一处理,让远处的东西自然融进环境色里。和常见的粒子雾、面片雾不同,全局雾效是屏幕空间或深度缓冲级别的计算,性能开销可控,效果也更统一。如果你正在做开放世界、生存类、恐怖氛围或者任何需要大气透视的项目,这套东西基本是绕不开的。它适合已经能跑通基础渲染管线、想进一步提升画面整体感的开发者,也适合被美术追着要“空气感”的程序。下面我从选型、参数、代码到踩坑,把这条链路拆开讲。

2. 选型与原理:为什么内置雾不够用,什么时候该上自定义

2.1 内置雾效的边界在哪里

Unity3D 自带 Lighting 面板里的 Fog 开关,支持 Linear、Exponential、Exponential Squared 三种模式。它的计算发生在每个物体的片元着色器里,基于相机距离做插值。优点是零代码、零额外 Draw Call,缺点是它只作用于不透明物体,透明物体、粒子、天空盒都不吃这套,而且雾的颜色是全局统一的,没法做高度雾、体积光或者局部浓度变化。很多项目一开始用内置雾觉得够用,等到加了半透明水面、体积云或者需要地面附近雾更浓的效果时,就发现内置雾完全使不上劲。这时候就得考虑自定义全局雾效。

2.2 自定义全局雾效的三种常见路线

第一种是屏幕空间后处理雾,用 OnRenderImage 或者 Renderer Feature 拿到深度纹理,在屏幕空间重建世界坐标,然后按距离和高度算雾的浓度。这种方案对透明物体也有效,因为它在最后画面上叠加。第二种是 Command Buffer 注入,在渲染管线的特定阶段插入雾的计算,控制粒度更细,但代码复杂度高。第三种是写一个全局 Shader 变体,让所有材质都 include 同一个雾计算函数,这种最灵活但维护成本大,改一个参数要动所有 Shader。我一般推荐屏幕空间后处理路线,尤其是 URP 下用 Renderer Feature,Built-in 下用 OnRenderImage,平衡了效果和可维护性。

2.3 深度纹理的获取与精度问题

不管哪条路线,核心都是拿到深度。Built-in 管线里相机需要开启 depthTextureMode,代码是Camera.main.depthTextureMode |= DepthTextureMode.Depth;。URP 里要在 Renderer 资产里勾选 Depth Texture。深度纹理的精度直接决定雾效在远处的表现,Reversed-Z 能缓解远处精度不足,但移动端要确认设备支持。如果深度图精度不够,远处会出现条带状的雾,看起来像色阶断裂,这时候要么提高深度精度,要么在雾计算里加一点抖动。

2.4 一个最小可跑的屏幕空间雾效 Shader

下面这段代码是 Built-in 管线下的最小实现,挂在相机上,材质用这个 Shader 即可。它做了三件事:采样深度、重建世界坐标、按距离和高度混合雾色。

Shader "Custom/GlobalFog" { Properties { _FogColor ("Fog Color", Color) = (0.5, 0.6, 0.7, 1) _FogStart ("Fog Start", Float) = 10 _FogEnd ("Fog End", Float) = 200 _FogHeight ("Fog Height", Float) = 0 _FogHeightScale ("Fog Height Scale", Float) = 0.1 } SubShader { Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _CameraDepthTexture; float4 _FogColor; float _FogStart; float _FogEnd; float _FogHeight; float _FogHeightScale; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 ray : TEXCOORD1; }; v2f vert(appdata_img v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord; // 计算从相机出发的射线方向,用于重建世界坐标 float3 viewDir = mul(unity_CameraInvProjection, float4(v.texcoord * 2 - 1, 1, 1)).xyz; o.ray = mul(unity_CameraToWorld, float4(viewDir, 0)).xyz; return o; } fixed4 frag(v2f i) : SV_Target { // 采样深度并转成线性深度 float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); float linearDepth = LinearEyeDepth(depth); // 重建世界坐标 float3 worldPos = _WorldSpaceCameraPos + i.ray * linearDepth; // 距离雾因子 float distFactor = saturate((linearDepth - _FogStart) / (_FogEnd - _FogStart)); // 高度雾因子:低于 _FogHeight 的区域雾更浓 float heightFactor = saturate((_FogHeight - worldPos.y) * _FogHeightScale); float fogFactor = saturate(distFactor + heightFactor); return fixed4(_FogColor.rgb, fogFactor); } ENDCG } } }

逻辑说明:vert 阶段算出每个像素对应的世界空间射线,frag 阶段用深度重建世界坐标,然后分别算距离因子和高度因子,相加后 saturate 得到最终雾浓度。参数说明:_FogStart 和 _FogEnd 控制距离雾的起始和结束距离,单位是世界单位;_FogHeight 是高度基准,低于这个值的区域开始受高度雾影响;_FogHeightScale 控制高度雾的衰减速度,值越大雾越浓。这个 Shader 输出的是雾的颜色和 alpha,配合 Blit 做混合即可。

2.5 混合方式与 Blit 调用

拿到雾的 RenderTexture 后,需要和原画面混合。常见做法是用 Graphics.Blit 配合一个简单的混合 Shader,或者直接在雾 Shader 里做 Blend SrcAlpha OneMinusSrcAlpha。如果走 OnRenderImage,代码大致是:

[ImageEffectAllowedInSceneView] private void OnRenderImage(RenderTexture src, RenderTexture dst) { if (fogMaterial != null) { // 确保相机有深度纹理 Camera.main.depthTextureMode |= DepthTextureMode.Depth; Graphics.Blit(src, dst, fogMaterial); } else { Graphics.Blit(src, dst); } }

这段代码挂在相机上,fogMaterial 就是上面那个 Shader 创建的材质。注意[ImageEffectAllowedInSceneView]让雾效在 Scene 视图也可见,方便调试。如果发现雾没出来,先检查相机是否开启了深度纹理,再检查 Shader 里的 _CameraDepthTexture 是否被正确赋值。

3. 参数调优:把雾从“能看”调到“好看”的五个旋钮

3.1 距离参数:Start 和 End 的黄金比例

_FogStart 和 _FogEnd 决定了雾在纵深方向上的分布。很多人把 Start 设得太小,结果近处物体也蒙了一层灰,画面发闷。我的经验是 Start 至少放在相机到主要兴趣点的距离之外,End 放在场景最远可见距离的 1.2 倍左右。比如一个 200 米见方的场景,Start 可以设 30,End 设 250。如果场景有天空盒,End 要大于天空盒的渲染距离,否则天空盒不受雾影响,会出现地面有雾、天空干净的割裂感。

3.2 高度参数:让雾贴地而不是糊在天上

高度雾是全局雾效里最出效果的部分。_FogHeight 一般设在地面高度或者略高一点,_FogHeightScale 控制雾从地面往上衰减的快慢。做清晨或者山谷场景时,把 _FogHeightScale 调大,雾会紧贴地面,形成平流雾的感觉;做整体大气透视时,把 _FogHeightScale 调小,雾会均匀分布在整个空间。注意高度雾和距离雾是叠加的,如果两个都调得很浓,远处会变成一堵墙,失去层次。

3.3 颜色与天空盒的匹配

雾的颜色不能随便取。最稳妥的做法是从天空盒的地平线颜色取色,然后稍微降低饱和度。如果雾色和天空盒色差太大,远处物体会像贴上去的。另一个技巧是给雾色加一点环境光的色调,比如场景主光是暖色,雾色就偏一点橙,这样整体更协调。如果项目有昼夜循环,雾色要跟着时间插值,不能写死。

3.4 性能参数:降采样与模糊

全分辨率算雾对移动端压力不小。常见优化是把深度图降采样到半分辨率甚至四分之一,算完雾再上采样混合。降采样会带来边缘锯齿,可以配合一个小的模糊核。另一个优化是只在相机移动时更新雾的 RenderTexture,静止时复用上一帧,但要注意深度变化。如果场景有大量透明物体,雾的计算不能跳过,否则透明物体上会出现硬边。

3.5 与后处理栈的顺序

雾效在後处理链里的位置很关键。它应该在 Bloom 之前、Color Grading 之后,这样雾的颜色会被 Bloom 轻微晕开,更自然。如果放在 Tone Mapping 之前,雾的亮度会被压缩,看起来发灰。URP 下用 Renderer Feature 可以精确控制注入点,Built-in 下 OnRenderImage 的顺序由脚本执行顺序决定,可以用[DefaultExecutionOrder]调整。

4. 避坑排查:全局雾效翻车的五个血泪现场

4.1 雾在 Scene 视图正常,Game 视图没有

现象:编辑器里 Scene 窗口能看到雾,运行起来 Game 窗口一片清晰。原因:OnRenderImage 只在相机渲染时调用,如果脚本挂在空物体上或者相机没开深度纹理,Game 视图就不生效。解决:确认脚本挂在 Camera 组件上,并且depthTextureMode包含 Depth。另外检查相机的 Rendering Path 是否是 Forward,Deferred 下深度纹理的获取方式不同。

4.2 透明物体上雾效断层

现象:不透明物体雾效正常,但水面、玻璃、粒子这些透明物体上雾突然消失或者出现硬边。原因:透明物体不写深度,深度图里没有它们的深度信息,重建世界坐标时用的是背后不透明物体的深度。解决:要么在透明物体的 Shader 里手动写入深度,要么改用基于距离的雾计算而不是深度重建。URP 下可以开启 Depth Priming 缓解。

4.3 移动端上雾效导致画面过暗

现象:PC 上正常的雾效,打包到手机后整体画面变暗,雾色发黑。原因:移动端浮点精度和 PC 不同,LinearEyeDepth 在移动端可能返回异常值,导致雾因子计算错误。解决:在 Shader 里加#if defined(SHADER_API_GLES)分支,用近似公式替代,或者把深度图精度从 16 位提到 24 位。另外检查 Color Space 是否一致,Linear 和 Gamma 下雾的混合结果差异很大。

4.4 雾效和天空盒接缝明显

现象:远处物体被雾覆盖,但天空盒还是原色,天地交界处有一条明显的线。原因:天空盒不参与深度测试,深度值是 1,重建世界坐标时得到的是远平面,雾因子算出来是 1,但天空盒本身没被雾混合。解决:在雾 Shader 里对深度为 1 的像素单独处理,或者用一个天空盒专用的雾混合 Pass。更简单的做法是把天空盒的材质也换成受雾影响的版本。

4.5 相机快速移动时雾出现闪烁

现象:相机平移或旋转时,雾的浓度出现跳变或者闪烁。原因:深度纹理的采样和相机矩阵更新不同步,或者降采样后的上采样滤波不稳定。解决:确保深度纹理在相机渲染前更新,URP 下检查 Renderer Feature 的 Event 是否设在了正确阶段。降采样时用双线性滤波而不是点采样,并且给雾因子加一点时间上的平滑,比如lerp(lastFog, currentFog, 0.1)。

5. 进阶技巧:用高度雾做局部氛围,以及一个验证雾效正确性的土办法

5.1 用噪声扰动高度雾做局部团雾

全局雾效的高度因子是均匀的,但真实场景里雾往往是一团一团的。可以在高度雾的基础上叠加一层 3D 噪声,用世界坐标采样,噪声值乘到高度因子上。这样地面附近会出现不规则的浓雾区域,适合做沼泽、战场或者恐怖场景。噪声的缩放要控制好,太大像云,太小像噪点。我一般用两层噪声,一层低频控制大团,一层高频控制边缘细节。

// 在 frag 里叠加噪声 float noise = tex3D(_NoiseTex, worldPos * _NoiseScale).r; float heightFactor = saturate((_FogHeight - worldPos.y) * _FogHeightScale); heightFactor *= lerp(1.0, noise, _NoiseStrength);

_NoiseScale 控制噪声的世界空间频率,_NoiseStrength 控制噪声对雾浓度的影响程度。注意噪声纹理的 Wrap Mode 要设成 Repeat,否则世界坐标超出 0-1 范围后会采样到边缘。

5.2 验证雾效深度的土办法

调雾效最头疼的是不知道深度重建对不对。我的土办法是在 Shader 里临时把 linearDepth 除以一个固定值直接输出成灰度图,比如return linearDepth / 100;。这样近处黑远处白,如果深度图有问题,一眼就能看出来。另一个办法是在场景里放几个已知距离的参考物,比如 10 米、50 米、100 米各放一个纯色方块,看雾的浓度变化是否符合预期。这个办法比盯着参数猜快得多。

5.3 雾效和视频流场景的配合

最近 unity3d 视频流相关的需求不少,比如把实时视频投到场景里的屏幕上。这种场景下雾效要特别注意:视频屏幕本身是自发光材质,如果雾效把它也蒙上一层,画面会发灰。解决办法是在视频材质的 Shader 里加一个雾效忽略的开关,或者在雾效混合时根据材质 ID 跳过。另一个坑是视频流的深度信息通常没有,如果视频屏幕后面有物体,雾效重建深度时会用到屏幕的深度,导致屏幕后面的雾浓度不对。这时候要么给视频屏幕写一个假深度,要么把雾效的深度采样限制在不透明物体上。

5.4 一个我常犯的错误

我早期做全局雾效时,总想把参数一次调到位,结果在编辑器里看着不错,打包出来完全不是那么回事。后来养成的习惯是:先在目标平台上跑一个最简场景,把距离、高度、颜色三个参数各拉一个极端值,确认雾效的响应方向是对的,再往回调。另一个习惯是每次改完雾效参数,一定在 Scene 视图和 Game 视图之间来回切三次,因为 Scene 视图的相机参数和 Game 视图经常不一致,只看一个容易翻车。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 6:17:32

招商加盟GEO实战:从问题库到监测闭环,让AI推荐你的品牌

上周有个做招商加盟的朋友发来一条消息:我们在百度上排名前三,但让AI推荐“值得加盟的茶饮品牌”,它列了一圈竞品,连我们的名字都没提。这个现象不是我朋友一个人遇到,我这一年里看了不下十个连锁品牌,几乎…

作者头像 李华
网站建设 2026/10/7 6:17:30

PCB叠层设计:从电磁原理到高速信号阻抗控制的底层逻辑

1. 为什么叠层设计不是“画完走线就完事”的收尾动作,而是PCB成败的底层地基?你手头正赶着一个四层板项目,原理图刚定稿,嘉立创EDA里新建工程、导入网表、摆好器件——一切顺利。直到你点开“层叠管理器”,面对那几行空…

作者头像 李华
网站建设 2026/10/7 6:15:03

LLM直接生成PTX汇编:跳过编译器后端的AI编译新范式

1. 这篇论文到底想干什么:把编译器后端整个拿掉第一次看到“AI 就是编译器”这个说法,我的反应是:又是一个标题党。但把论文翻完,我发现它讲的事情其实非常具体——让大语言模型直接输出 PTX(Parallel Thread Executio…

作者头像 李华
网站建设 2026/10/7 6:14:14

从频域理解滤波器:低通、高通与带通的设计与选型

1. 从频域视角重新认识滤波器:它到底在做什么说起低通、高通、带通滤波器,很多人的第一反应是"书上背过定义":低通让低频通过、高通让高频通过、带通只让一段频率通过。这当然没错,但如果你只是把这句话记下来&#xff…

作者头像 李华
网站建设 2026/10/7 6:14:10

Windows 上 Codex 抢鼠标怎么办?Cua Driver 驱动级隔离方案

1. 从“抢鼠标”说起:Windows 上 Codex 类工具的真实痛点如果你在 Windows 上跑过 Codex 这类命令行 AI 编程助手,大概率经历过一个非常具体的场景:你正开着 Codex 在终端里跑任务,它需要调用浏览器、点击界面、读取屏幕内容&…

作者头像 李华
网站建设 2026/10/7 6:12:18

ERNIE微调情感分析实战:句子级与属性级任务全流程解析

简介:一套面向自然语言处理开发者的情感分析完整实现,基于百度预训练大模型ERNIE,覆盖句子级与属性级两种粒度,提供从数据预处理、模型加载、特征提取到分类器训练评估的可运行源码,可直接用于实际项目或作为预训练模型…

作者头像 李华