Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

屏幕后处理的基本原理

一、什么是屏幕后处理


屏幕后处理效果(Screen Post-Processing Effects)是一种在渲染管线的最后阶段应用的视觉效果
允许你在场景渲染完后对最终图像进行各调整和效果处理,从而增强视觉体验


常见的屏幕后处理效果:
景深、模糊、色彩调整 等等

二、基本原理

最关键的问题: 1. 如何获取游戏画面渲染完毕后的画面信息 2. 如何为 获取到的画面信息添加自定义效果

解决方法: 1. 如何获取游戏画面渲染完毕后的画面信息
在Unity中获取纹理有三种常用方法 - RendererTexture - GrabPass - OnRenderImage
我们在处理屏幕后期处理效果时,会使用
OnRenderImage函数来获取游戏画面渲染完毕后的画面信息 2. 如何为 获取到的画面信息添加自定义效果
主要思路是将获取到的游戏画面 作为自定义Shader的主纹理
通过自定义Shader利用捕获的画面来实现自定义效果

三、捕获画面的关键——OnRenderImage函数


OnRenderImage函数
它是继承了MonoBehaviour的脚本中能够被自动调用的函数(类似于生命周期函数)
它会在图像的渲染操作完成后调用
固定写法:

1
2
3
4
5
//第一个参数:原渲染纹理,当前渲染得到的屏幕图像存储在该函数中
//第二个参数:目标渲染纹理,将经过处理的的图像写入到目标纹理中用于最终的显示
void OnRendererImage(RenderTexture source,RenderTexture destination)
{
}


通过该函数我们便可以得到当前渲染纹理的游戏画面
并在该函数中对画面对应的渲染纹理进行处理后用于最终显示

注意:
该函数得到的原纹理默认为所有不透明和透明的Pass执行完毕后调用的
基于该纹理进行修改,会对游戏场景中所有的游戏对象产生影响
如果想要在不透明的Pass执行完毕后就调用该函数,需要在该函数前添加[ImageEffetQpaque]

四、实现效果的关键——Graphics.Blit函数


主要用于将一个图像从一个纹理复制到另一个纹理


这里只讲常用的几个重载: 1. 将源纹理直接复制到目标纹理 Graphics.Blit(Texture soure,RenderTexture dest) 2. 将源纹理复制到目标纹理并应用一个材质 Graphics.Blit(Texture source,RenderTexture dest,Material mat,int Pass = -1) - 参数一:源纹理 - 参数二:目标纹理 - 参数三:应用材质 source纹理会被传递到材质中Shader 并应用到名为_MainTex的纹理属性中 - 参数四:调用Shader中Pass的索引。-1为依次调用全部的Pass 且默认为-1

1
2
3
4
5
void OnRendererImage(RenderTexture source,RenderTexture destination)
{
//将源纹理直接复制到目标纹理
Graphics.Blit(source,destination);
}

五、屏幕后处理基类

1.补充知识

材质球中的 HideFlags 枚举 从材质球对象中可以点出 HideFlags 枚举 - HideFlags.None:对象是完全可见和可编辑的。这是默认值。 - HideFlags.HideInHierarchy:对象在层级视图中被隐藏,但仍然存在于场景中。 - HideFlags.HideInInspector:对象在检査器中被隐藏,但仍然存在于层级视图中。 - HideFlags.DontSaveInEditor:对象不会被保存到场景中。适用于编辑器模式,不会影响播放模式 - HideFlags.NotEditable:对象在检査器中是只读的,不能被修改。 - HideFlags.DontSaveInBuild:对象不会被包含在构建中。 - HideFlags.DontUnloadUnusedAsset:对象在资源清理时不会被卸载,即使它没有被引用。 - HideFlags.DontSave:过象不会被保存到场景中,不会在构建中保存,也不会在编辑器中保存。 这是DontSaveInEditor | DontSaveInBuild | DontUnloadUnusedAsset 的组合。

注意: 如果想要设置枚举满足多个条件 直接多个枚举 进行位或运算即可

2.为什么要实现屏幕后处理的基类

  1. 为了实现屏幕后处理的效果,我们每一次都要做几件事
    1. 实现一个继承自MonoBehavior的自定义基类脚本
    2. 关联对应的材质球或者Shader
    3. 实现OnRendererImage函数
    4. 在OnRenderImage函数中使用Graphics.Blit函数
    这些基本逻辑可以完全抽象到一个基类中去完成
    再在子类中去实现各自的基本逻辑
  2. 我们可以在基类中动态的创建材质球 不需要为每一个后处理效果都手动的创建材质球 只需要在Inspector窗口中关联对应的Shader即可
  3. 在屏幕后处理之前,我们往往需要一系列的检查某一些条件是否满足
    比如:
    当前平台是否支持当前使用的UnityShader(Shader.isSupported),这可以在基类中判断,避免重复书写

3.实现基类功能

  1. 声明基类功能,让其依赖Camera,并且让其在编辑模式下可以运行,保证我们随时可以看见效果
  2. 基类中声明 公共Shader 用于在Inspector窗口中关联
  3. 基类中声明 私有的Material 用于动态创建
  4. 基类中实现判断Shader是否可用 并且动态创建Material的方法
  5. 基类中是实现OnRenderImage的虚方法,完成基本逻辑
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
35
36
37
38
39
40
41
42
43
44
45
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffectBase : MonoBehaviour
{
//屏幕后处理的Shader
public Shader shader;
//一个用于动态创建出来的材质球
private Material _material;
protected virtual void OnRenderImage(RenderTexture source, RenderTexture destination)
{
//渲染之前先更新属性 在子类中进行重写即可
UpadateProerty();
//判断材质球是否为空 如果不为空 就证明这个Shader是支持的 可以用来进行屏幕后处理
if (material != null)
Graphics.Blit(source, destination, material);
else
Graphics.Blit(source, destination);
}
/// <summary>
/// 提供给子类更新材质球的属性
/// </summary>
protected virtual void UpadateProerty(){}
protected Material material
{
get
{
if (shader == null || !shader.isSupported)
return null;
else
{
//避免每次都去创建材质球
if (_material != null && _material.shader == shader)
return _material;

//用支持的Shader创建一个材质球
_material = new Material(shader);
//这里我们不希望材质球被保存,所以设置一个标识
_material.hideFlags = HideFlags.DontSave;
return _material;
}
}

}
}

评论