后处理原理及过程

后处理是在渲染完后对图像进行处理,unity使用的是 C# 脚本

就是渲染完后不显示,多走一步,经由脚本处理后再显示到屏幕上,以实现诸如各种模糊效果、Bloom、描边等等


后处理一般过程

获取当前屏幕图像,利用C#脚本调用shader对图像进行处理,在将处理后的图像显示到屏幕上

原理

创建了一个同屏幕宽高完全一样的面,将之前的渲染结果作为渲染纹理传入脚本,调用shader对纹理采样渲染回面片

所以在后处理时需要关闭深度写入等,防止渲染纹理覆盖了特殊情况下最后渲染的半透明物体


Unity提供了OnRenderImage() 函数获取屏幕图像

MonoBehaviour.OnRenderImage (RenderTexture src, RenderTexture dest)

1
2
3
4
5
6
/*OnRenderImage中利用Graphics.Blit实现处理*/
public static void Blit(Texture src, Texture dest);

public static void Blit(Texture src, Texture dest, Material mat, int pass = -1);

public static void Blit(Texture src, Material mat, int pass = -1);

Blit函数获取屏幕图像后调用shader,将源图像src作为一张渲染纹理赋值给shader_MainTex

边缘检测(描边)

梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)

二维空间下的偏微分方程
$$
\frac{∂f(x,y)}{∂x}=\lim_{\epsilon->0}\frac{f(x+\epsilon,y)-f(x,y)}{\epsilon}
$$

$$
\frac{∂f(x,y)}{∂y}=\lim_{\epsilon->0}\frac{f(x,y+\epsilon)-f(x,y)}{\epsilon}
$$

由于像素不是连续的,只要计算当前像素沿偏微分方向的差值,ε=1,所以只需进行简单的加减运算

在xy得到的值经过勾股定理后就得到,二维空间下灰度的变化率

边缘检测算子

edge_detection_kernel

注意:图中后两个算子是错的,xy方向反了!!!

一般不采用 Roberts 这样偶数的算子,因为结果对原像素存在偏移


Prewitt:$Gx = f(x+1) + 0 * f(x)-f(x-1)$,提取系数就是[-1, 0, 1]

1
2
3
4
所以该算子Gx就是:     同理Gy:
[-1, 0, 1] [-1, -1, -1]
[-1, 0, 1] [ 0, 0, 0]
[-1, 0, 1] [ 1, 1, 1]

Sobel就是prewitt的基础上,加强了对直线上像素的检测

高斯模糊和 Bloom 效果

均值模糊

卷积核的各元素值相同,n*n的核每个的值就是1/n^2

高斯模糊

高斯模糊的卷积核又叫高斯核

高斯模糊的名字来源于高斯分布(正态分布),其概率密度函数所表示的就是均值与样本值之间的关系,所以高斯模糊就是一种权值按距离减少的方法
$$
G(x,y)=\frac{1}{2\pi\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2}}
$$
$\mu$就是0,因为当前像素的原点距离为0,方差$\sigma$取标准方差1

n*n 的高斯核可以使用两个一维的高斯核替换

而且由于概率密度函数是对称的,所以存储上可以优化


1
2
3
4
5
6
7
8
9
10
11
12
13
/*Shader*/
_MainTex获取屏幕图像
_MainTex_TexelSize获取纹素尺寸

/*计算纹理坐标*/
half2 uv[5] : TEXCOORDS0
//Vertical
uv[0]=uv;
uv[1]=uv + float2(0.0, _MainTex_TexelSize * 1.0);//上方一格的坐标
uv[2]=uv - float2(0.0, _MainTex_TexelSize * 1.0);//下方一格的坐标
uv[3]=uv + float2(0.0, _MainTex_TexelSize * 2.0);//上方两格的坐标
uv[4]=uv - float2(0.0, _MainTex_TexelSize * 2.0);//下方两格的坐标
//horizontal同理

Bloom效果

步骤:提取图像中的亮部(像素的亮度可由rgb通过一定比例的计算获得),对提取的亮部进行高斯模糊,混合原图和Bloom

luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b,可以看出绿色对亮度的贡献最大,红色次之,蓝色最小

运动模糊

方法:累计缓存(混合多张图像),速度缓存(按照像素的运动速度来模糊处理)

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*一般格式*/
using UnityEngine;
using System.Collections;

public class MyScript : MonoBehaviour {
public Shader MyScriptShader;
private Material MyScriptMaterial = null;

public Material material {
get {
MyScriptMaterial = function(MyScriptShader, MyScriptMaterial);
return MyScriptMaterial;
}
}


//暴露的属性
[Range(0, 4)]
public int temp = 3;
...

//调用shader
void OnRangeImage(RenderTexture src, RenderTexture dest) {
if(material != null) {
material.setFloat("_temp", temp);
//后处理
Graphics.Blit(src, dest, material);
}
else {
//直接返回
Graphics.Blit(src, dest);
}
}
}