关于 mobile-post-processing
面向 Unity URP 移动端和 WebGL 项目的后处理技能,提供效果开销取舍与单通道实现指导,适用于优化屏幕空间效果。
主要功能
- 01合并效果以减少全屏传递
- 02提供单通道后处理模板
- 03比较常见效果的移动端开销
- 04建议使用 URP Renderer Features
适用场景
- 01优化移动游戏的后处理帧耗
- 02为 URP 添加自定义后处理通道
- 03选择适合受限硬件的屏幕效果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ adevra/ unity-shader-agent-skills --skill mobile-post-processing安装后在 Claude Code 中输入/mobile-post-processing确认已加载。
详细介绍
Unity URP 移动端后处理
触发条件
当用户希望在 Unity 中为移动端或 WebGL 目标添加、创建或优化后处理效果时,使用此技能。触发词包括:“post-processing”“bloom”“vignette”“color grading”“blur”“outline”“edge detection”“chromatic aberration”“Renderer Feature”“Full Screen Pass”“Blit”“post-process mobile”,或任何提及在硬件资源受限的设备上使用屏幕空间效果的情况。用户报告后处理导致移动端帧率下降时,也应触发。
核心规则
-
每个后处理通道都需要一次全屏 Blit——从主内存读取整个帧缓冲区,再将其写回。对于移动端平铺式 GPU,这会刷新瓦片内存。中端移动设备上,每个通道预计耗时 0.5–2ms。
-
**尽可能将所有效果合并到单个通道中。**Unity 默认的后处理堆栈会让每个效果分别使用一个通道。仅此一项在移动端就可能增加 6ms 以上(实测:某些设备上仅暗角效果就耗时 6ms)。应使用单通道方案。
-
**除非确实需要 Bloom,否则禁用 HDR。**HDR 会使帧缓冲区带宽翻倍(每通道使用 16 位浮点,而非 8 位)。如果只需要颜色分级,请在 LDR 中应用。
-
**Bloom 是移动端默认效果中开销最大的效果。**它需要降采样、模糊和升采样——涉及多个通道。如果必须使用,请减少迭代次数(最多 2–3 次),使用半分辨率模糊,并跳过高质量预滤波。
-
**切勿在移动端使用 Motion Blur 或 Depth of Field。**它们需要在多个通道中为每个片元进行多次纹理采样。
-
**对于 URP 中的自定义效果,优先使用 Renderer Features,而不是基于 Volume 的后处理。**Renderer Features 可让你直接控制 Blit 的时机和方式。
单通道后处理模板
此方案会将暗角 + 颜色分级 + 简单 Bloom 合并到一个通道中:
Shader "Mobile/PostProcess_SinglePass"
{
Properties
{
_MainTex ("Source", 2D) = "white" {}
_VignetteIntensity ("Vignette Intensity", Range(0, 1)) = 0.3
_VignetteRadius ("Vignette Radius", Range(0, 1)) = 0.7
_Contrast ("Contrast", Range(0.5, 1.5)) = 1.1
_Saturation ("Saturation", Range(0, 2)) = 1.1
_Brightness ("Brightness", Range(0.5, 1.5)) = 1.0
}
SubShader
{
Tags { "RenderPipeline" = "UniversalPipeline" }
Pass
{
ZTest Always ZWrite Off Cull Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
half _VignetteIntensity;
half _VignetteRadius;
half _Contrast;
half _Saturation;
half _Brightness;
CBUFFER_END
TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; };
struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; };
Varyings vert(Attributes v)
{
Varyings o;
o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
o.uv = v.uv;
return o;
}
half4 frag(Varyings i) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
// --- Vignette (no extra texture sample) ---
half2 uvCentered = i.uv - 0.5h;
half vignette = 1.0h - saturate(
length(uvCentered) / _VignetteRadius
);
vignette = smoothstep(0.0h, 1.0h, vignette);
color.rgb *= lerp(1.0h, vignette, _VignetteIntensity);
// --- Brightness / Contrast ---
color.rgb *= _Brightness;
color.rgb = (color.rgb - 0.5h) * _Contrast + 0.5h;
// --- Saturation ---
half luminance = dot(color.rgb, half3(0.2126h, 0.7152h, 0.0722h));
color.rgb = lerp(luminance.xxx, color.rgb, _Saturation);
return saturate(color);
}
ENDHLSL
}
}
}
URP Renderer Feature(C# 设置)
要将后处理通道注入 URP:
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class MobilePostProcessFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
{
public Material material;
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
}
public Settings settings = new Settings();
MobilePostProcessPass pass;
public override void Create()
{
pass = new MobilePostProcessPass(settings);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.material == null) return;
renderer.EnqueuePass(pass);
}
class MobilePostProcessPass : ScriptableRenderPass
{
Settings settings;
RTHandle tempRT;
public MobilePostProcessPass(Settings settings)
{
this.settings = settings;
this.renderPassEvent = settings.renderPassEvent;
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
var cmd = CommandBufferPool.Get("MobilePostProcess");
var source = renderingData.cameraData.renderer.cameraColorTargetHandle;
// Single blit with our combined shader
Blitter.BlitCameraTexture(cmd, source, source, settings.material, 0);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
}
移动端安全效果(开销排序)
| 效果 | 开销 | 移动端评估 | 备注 |
|---|---|---|---|
| 颜色分级(LDR) | ~0.1ms | 安全 | 纯数学运算,不增加采样 |
| 暗角 | ~0.1ms | 安全 | 仅计算 UV 距离 |
| 胶片颗粒 | ~0.2ms | 安全 | 根据 UV 数学运算生成噪声,无需纹理 |
| 简单描边(深度) | ~0.5ms | 谨慎 | 需要深度纹理 + 4 个邻近采样 |
| Bloom(2 次迭代) | ~1-2ms | 谨慎 | 使用半分辨率,最多迭代 2 次 |
| 色差 | ~0.3ms | 谨慎 | 需要 3 次纹理采样,而非 1 次 |
| 模糊(单通道方框) | ~0.5ms | 谨慎 | 使用 4 次采样的方框模糊,切勿使用高斯模糊 |
| Motion Blur | ~3-5ms | 避免 | 沿速度方向进行多次采样 |
| Depth of Field | ~3-5ms | 避免 | CoC 计算 + 可变模糊 |
| 屏幕空间反射 | ~4-8ms | 避免 | 在屏幕空间中进行光线步进 |
| 环境光遮蔽(SSAO) | ~2-4ms | 避免 | 每个片元进行多次深度采样 |
参考仓库
| 仓库 | 用途 | 重要原因 |
|---|---|---|
| demonixis/FastPostProcessing | 单通道移动端后处理效果 | **移动端首选。**所有效果都在一个通道中。 |
| TPiotr/SimpleMobilePostProcessing-Unity | 模糊 + 暗角 | 因为 Unity 的暗角效果单独就耗时 6ms,所以创建了此项目 |
| yahiaetman/URPCustomPostProcessingStack | 兼容 URP Volume 的自定义效果 | 提供 8 个正确集成到 URP 的效果示例 |
| QianMo/X-PostProcessing-Library | 数十种效果(MIT) | 桌面级质量的参考;需针对移动端调整 |
| GarrettGunnell/Post-Processing | 模块化 Bloom/颜色分级/边缘效果 | 包含单通道合并的说明 |
来源
- Febucci:《URP 中的自定义后处理》— https://blog.febucci.com/2022/05/custom-post-processing-in-urp/
- ARM Community:《移动设备上的后处理》— https://community.arm.com/arm-community-blogs/b/graphics-gaming-and-vr-blog/posts/post-processing-effects-on-mobile-optimization-and-alternatives
- Unity Forum:《Unity 6 移动端技巧(Bloom/HDR 基准测试)》— https://discussions.unity.com/t/unity-6-mobile-graphics-optimization-tips-and-tricks/1593293
- Android Developer:《URP 资源设置》— https://developer.android.com/develop/xr/unity/performance/urp-asset-settings
- UWA Blog:《Bloom 优化》— https://blog.en.uwa4d.com/2022/12/28/unity-mobile-game-performance-optimization-the-balance-between-graphics-performance-and-gpu-pressure/