Unity移动端基于CG语言实现快速平滑模糊着色器的最佳标准方案咨询
嘿,正好在移动设备上实现高效又平滑的模糊是Unity开发者常遇到的需求,而《盗贼之王》的方案确实是行业内经过验证的标杆级实践。我来给你拆解下移动平台的最佳标准方案,附上手写的CG着色器代码:
Unity移动设备快速平滑CG模糊着色器最佳实践
核心思路:分离式高斯模糊(双Pass)
移动GPU的计算带宽有限,直接做二维高斯模糊的开销会随着模糊半径呈平方增长,完全不适用。分离式高斯模糊是行业标准解决方案——把二维模糊拆成水平模糊+垂直模糊两个一维Pass,计算量从O(n²)降到O(2n),这也是《盗贼之王》采用的核心基础逻辑。
移动设备专属优化要点
这些细节是保证速度与画质平衡的关键,也是《盗贼之王》的优化精髓:
- 先降采样再模糊:在模糊前把渲染纹理缩小到原分辨率的1/2或1/4,大幅减少后续采样次数,这是移动平台性价比最高的性能优化,《盗贼之王》会根据设备性能动态调整降采样比例。
- 硬件加速线性采样:Unity移动平台的
tex2D默认用硬件线性采样,比手动插值快数倍,无需额外计算就能获得平滑的采样过渡。 - 低精度计算:在CG代码中用
half代替float,减少内存带宽占用和计算量,移动GPU对低精度数据的处理效率更高。 - 预计算采样UV:在顶点着色器中提前计算好模糊所需的采样坐标,避免在片元着色器中重复计算,进一步降低开销。
完整CG着色器实现代码
这个实现整合了降采样+双Pass模糊,完全适配移动平台:
Shader "Custom/FastMobileBlur" { Properties { _MainTex ("Source Texture", 2D) = "white" {} _BlurRadius ("Blur Radius", Range(0, 10)) = 2.5 _DownsampleFactor ("Downsample Scale", Range(1, 4)) = 2.0 } SubShader { Tags { "RenderType"="Transparent" "Queue"="PostProcessing" "RenderPipeline"="UniversalPipeline" } LOD 100 // 降采样Pass:先缩小纹理,减少后续模糊计算量 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float _DownsampleFactor; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag (v2f i) : SV_Target { float2 texelSize = 1.0 / (_ScreenParams.xy / _DownsampleFactor); // 4邻域采样平均,快速降采样 fixed4 col = 0; col += tex2D(_MainTex, i.uv); col += tex2D(_MainTex, i.uv + float2(texelSize.x, 0)); col += tex2D(_MainTex, i.uv + float2(0, texelSize.y)); col += tex2D(_MainTex, i.uv + texelSize); return col * 0.25; } ENDCG } // 水平模糊Pass Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float2 blurUV[5] : TEXCOORD1; // 预计算采样UV }; sampler2D _MainTex; float _BlurRadius; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; float2 step = float2(_BlurRadius / _ScreenParams.x, 0); // 预计算5个采样点的UV,减少片元计算 o.blurUV[0] = o.uv - 2.0 * step; o.blurUV[1] = o.uv - 1.0 * step; o.blurUV[2] = o.uv; o.blurUV[3] = o.uv + 1.0 * step; o.blurUV[4] = o.uv + 2.0 * step; return o; } fixed4 frag (v2f i) : SV_Target { // 近似高斯权重,避免指数计算开销 fixed weights[5] = {0.12, 0.22, 0.32, 0.22, 0.12}; fixed4 col = 0; for(int j=0; j<5; j++){ col += tex2D(_MainTex, i.blurUV[j]) * weights[j]; } return col; } ENDCG } // 垂直模糊Pass Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float2 blurUV[5] : TEXCOORD1; }; sampler2D _MainTex; float _BlurRadius; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; float2 step = float2(0, _BlurRadius / _ScreenParams.y); o.blurUV[0] = o.uv - 2.0 * step; o.blurUV[1] = o.uv - 1.0 * step; o.blurUV[2] = o.uv; o.blurUV[3] = o.uv + 1.0 * step; o.blurUV[4] = o.uv + 2.0 * step; return o; } fixed4 frag (v2f i) : SV_Target { fixed weights[5] = {0.12, 0.22, 0.32, 0.22, 0.12}; fixed4 col = 0; for(int j=0; j<5; j++){ col += tex2D(_MainTex, i.blurUV[j]) * weights[j]; } return col; } ENDCG } } FallBack "Hidden/Universal Render Pipeline/FallbackError" }
《盗贼之王》的进阶优化细节
为了在移动平台达到极致的体验,他们还做了这些额外优化:
- 动态分辨率适配:通过
SystemInfo.graphicsDeviceType判断设备性能,中低端设备自动切换到1/4降采样,高端设备用1/2,平衡帧率与画质。 - 轻量边缘保留:在模糊后加入简易Sobel边缘检测,对高对比度边缘做保留处理,避免模糊掉角色、UI等核心元素。
- SRP管线集成:将模糊效果作为Post Process Volume的一部分,利用Unity SRP的批处理和渲染优化,减少额外Draw Call开销。
内容的提问来源于stack exchange,提问作者Mahdi GB
相关产品推荐
相关产品推荐

