ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

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 FeatureBuilt-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 可以设 30End 设 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 是否是 ForwardDeferred 下深度纹理的获取方式不同。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 视图经常不一致只看一个容易翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表