什么是后处理
屏幕后处理,指的是在渲染完场景内所有物体到颜色缓冲区后,再对该颜色缓冲区进行处理,实现各种屏幕特效,提升最终画面呈现品质的重要渲染技术
与渲染模型的区别
正常模型渲染时,cpu将模型数据提交给gpu,执行顶点着色器和像素着色器,在shader中可以对模型数据包括顶点坐标,法线,uv坐标等等进行访问。
而unity后处理的实现为,生成一个只有四个顶点的正方形面片覆盖整个屏幕,将之前已经渲染好的颜色缓冲作为一张纹理贴图传进后处理使用的shader,因此后处理shader的顶点着色器只会执行4次。正常情况下,后处理使用的shader只能访问到之前的颜色缓冲每个像素的颜色,而访问不到该像素对应的模型信息,所以后处理时能访问到的数据其实很少,能实现的效果和对单个模型使用的shader相比也有限。不过通过一些特殊pass的帮助,也可以访问到一些有限的额外信息例如该像素点对应的深度和法线。
因此对某些后处理效果,例如全局雾效,既可以将其写在模型所使用的shader上,也可以写在后处理中统一处理,统一处理的好处为不用修改所有的已有shader代码来添加某种效果,坏处就是可能获取不到需要的数据,可以实现的效果有限。
一些后处理算法与数字图像处理领域所用的算法类似,但是一般图像处理都是使用cpu进行离线处理,而游戏中的后处理,需要每帧进行更新而且大部分运行在gpu上,所以算法细节有所不同,很多耗费大量时间的离线计算就没法用在游戏的后处理中。
后处理示例图
下图为unity中一个场景开启和关闭一些常见后处理的效果对比。其中用到了全局雾效,bloom,景深,移轴模糊,体积光,环境光遮蔽,抗锯齿,色调映射,对比度饱和度调整等
可以看出,后处理对最终画面品质的影响相当大。
Unity如何实现后处理
OnRenderImage
在unity中挂载在摄像机上的脚本可以声明OnRenderImage函数,和Start,Update之类的函数类似,OnRenderImage也是由unity调用的,OnRenderImage的默认调用时机为所有透明和非透明的pass执行完毕之后。但是可以通过添加[ImageEffectOpaque]属性调整为只在非透明shader执行完之后调用,而不对透明shader起效果。
当我们在脚本中声明此函数后,Unity会把当前颜色缓冲存储在第一个参数对应的源渲染纹理中,第二个参数对应的渲染纹理最后会显示到屏幕上,因此我们在该函数中的操作就是需要根据源纹理做一些操作,最后写入到第二个参数中。
void OnRenderImage(RenderTexture src, RenderTexture dest)
Graphics.Blit
我们通常使用Graphics.Blit函数来完成对渲染纹理的处理,参数src对应了源纹理,参数dest是目标纹理,参数mat是我们使用的材质,其中材质又需要设置该材质对应的shader,src纹理会被固定传入材质对应的shader中命名为_MainTex的纹理属性。除了固定传入的源纹理外,还可以设置其他属性来调节参数。对于一些复杂的屏幕效果,需要多次调用Graphics.Blit来重复处理。
public static void Blit(Texture source, RenderTexture dest, Material mat)
一个调用的例子
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Material material = new Material(shader);
material.SetFloat("brightness", brightness);
material.SetFloat("saturation", saturation);
material.SetFloat("contrast", contrast);
Graphics.Blit(src, dest, material);
}
后处理效果的堆叠
多个后处理效果,可以都挂载在同个摄像机上,执行顺序为从上到下,上一个输出的目标纹理会被设置为下一个脚本里OnRenderImage函数的源纹理,直到执行完所有的OnRenderImage,最后输出的目标纹理会显示到屏幕上。
目前已经很少用这种原始的形式来堆叠后处理效果,一般为了优化会对多种后处理效果进行合并,例如合并某些共用某一阶段的shader。而unity官方也有提供一个Post Processing的包,其中包括各种写好的常见后处理效果,也可以把自己写的后处理继承它的基类,合并到它的后处理堆栈中。
后处理shader通用设置
在后处理shader中,一般会设置以下几个状态,
ZTest Always 深度测试总是通过,这是因为屏幕后处理效果就是显示在最上层的,无需进行深度测试。
ZWrite Off 关闭深度写入,如果不关闭且该后处理执行完之后还有物体需要渲染,则该物体会被挡住。
Cull Off 关闭背面剔除,主要用于不同设备兼容性
这些状态可以认为是用于屏幕后处理的Shader的‘标配’
Cull Off ZWrite Off ZTest Always
模糊后处理
模糊是一种后处理的类别,效果就是让图像变得模糊,其中包括均值模糊,高斯模糊,径向模糊,方向模糊,中值模糊,光圈模糊,粒状模糊,散景模糊等等…模糊不仅是是作为单一的后处理效果来使用,其还用在很多其他后处理效果的某一阶段,例如bloom效果,Sun Shaft,镜头眩光光晕,景深等等。
后处理效果都需要c#脚本和shader文件配合来实现效果,其中模糊算法c#脚本都具有类似的过程,大概如下所示
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
//-----------降采样----------
var width = src.width >> downSample;
var height = src.height >> downSample;
var tempBuffer = RenderTexture.GetTemporary(width, height);
tempBuffer.filterMode = FilterMode.Bilinear;
Graphics.Blit(src,tempBuffer);
//------------------------------
//-----------迭代----------------
for (int i = 0; i < interation; i++)
{
var tempBuffer2 = RenderTexture.GetTemporary<