1.定义:
屏幕后处理通常指在渲染完整个场景得到屏幕图像后,再对这个图像进行各种操作,实现各种屏幕特效。使用屏幕后处理技术,可以实现景深(Depth of Field),运动模糊(Motion Blur)等。
2.实现步骤
2.1 先抓取屏幕图像,调用方法如下:
monoBehaviour.OnRenderImage(RenderTexture src,RenderTexture dest)
Unity会把当前渲染完场景得到的屏幕纹理 或 上一步处理后得到的渲染纹理存储到 src 中。然后再经过一系列操作后再把 dest渲染到屏幕上。
2.2OnRenderImage函数中利用Graphics.Blit函数对 src 纹理进行操作。
public static void Blit(Texture source, RenderTexture dest);
public static void Blit(Texture source, RenderTexture dest, Material mat, int pass = -1);
public static void Blit(Texture source, Material mat, int pass = -1);
public static void Blit(Texture source, RenderTexture dest, Vector2 scale, Vector2 offset);
- 参数source是屏幕纹理 或 上一步处理后得到的渲染纹理
- 参数dest是目标渲染纹理,如果dest = null,会直接渲染到屏幕上。
- 参数mat是使用的材质,source会被传递到Shader中的_MainTex的纹理属性。
- 参数pass默认= - 1,标识一次调用Shader中的所有Pass,否则只会调用给定索引的Pass
默认情况下 OnRenderImage函数在所有的不透明和透明的的Pass执行完毕后被调用,以便对场景中所有物体都产生影响,但有时希望在不透明的Pass执行完毕后被调用,这样不对透明物体产生影响。此时可以在OnRenderImage添加ImageEffectOpaque函数来实现目的。
https://docs.unity3d.com/2018.3/Documentation/ScriptReference/Graphics.Blit.html
实践:
前期准备,在传入Shader参数时候,初始化一个Material,用于屏幕后处理。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class PostEffectsBase_Chan : MonoBehaviour {
protected void CheckResources()
{
bool isSurrorted = SystemInfo.supportsImageEffects;
if (!isSurrorted) {
NotSupported ();
}
}
protected void NotSupported()
{
this.enabled = false;
}
protected void Start()
{
CheckResources ();
}
//指定一个Shader,并创建一个材质使用这个Shader
protected Material CheckShaderAndCreateMaterial(Shader _shader,Material _material)
{
if (_shader == null) {
return null;
}
if (_shader.isSupported && _material && _material.shader == _shader) {
return _material;
}
if (!_shader.isSupported) {
return null;
} else {
_material = new Material (_shader);
_material.hideFlags = HideFlags.DontSave;
if (_material) {
return _material;
}
return null;
}
}
}
1.调整屏幕图像的亮度,饱和度和对比度
1.1 写一个可调节图像亮度,饱和度,对比度的Shader。
Shader "Chan/Brightness Saturation And Contrast" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Brightness ("Brightness", Float) = 1
_Saturation("Saturation", Float) = 1
_Contrast("Contrast", Float) = 1
}
SubShader {
Pass {
ZTest Always Cull Off
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
half _Brightness;
half _Saturation;
half _Contrast;
struct v2f {
float4 pos : SV_POSITION;
half2 uv: TEXCOORD0;
};
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed4 renderTex = tex2D(_MainTex, i.uv);
// Apply brightness
fixed3 finalColor = renderTex.rgb * _Brightness;
// Apply saturation
fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
finalColor = lerp(luminanceColor, finalColor, _Saturation);
// Apply contrast
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
finalColor = lerp(avgColor, finalColor, _Contrast);
return fixed4(finalColor, renderTex.a);
}
ENDCG
}
}
Fallback Off
}
1.2 脚本BrightnessSaturationAndContrast_Chan 继承自 PostEffectsBase_Chan,并挂到Camera上。在脚本中调节Shader中的亮度,饱和度,对比度 三个变量的值。
using UnityEngine;
using System.Collections;
public class BrightnessSaturationAndContrast_Chan : PostEffectsBase_Chan {
public Shader briSatConShader;
private Material briSatConMaterial;
public Material material {
get {
briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
return briSatConMaterial;
}
}
[Range(0.0f, 3.0f)]
public float brightness = 1.0f;
[Range(0.0f, 3.0f)]
public float saturation = 1.0f;
[Range(0.0f, 3.0f)]
public float contrast = 1.0f;
void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (material != null) {
material.SetFloat("_Brightness", brightness);
material.SetFloat("_Saturation", saturation);
material.SetFloat("_Contrast", contrast);
Graphics.Blit(src, dest, material);
} else {
Graphics.Blit(src, dest);
}
}
}
参数source是屏幕纹理 或 上一步处理后得到的渲染纹理。会传递给Shader中名字叫_MainTex的纹理,所以Shader中一定要定义名字为_MainTex的纹理。
由于屏幕后处理,其实是绘制了一个与屏幕同宽高的四边形面片,因此需要关闭四边形面片的深度写入,为了防止挡住场景中的物体。
2.边缘检测
边缘检测的原理是利用卷积核对图像上所有像素进行卷积操作,操作时候把卷积核中心放到该像素上。
将原始像素点"1",使用中间卷积核,进行卷积操作得到卷积结果"8"。
常用卷积算子的优缺点:
Shader "Chan/Chapter 12/Edge Detection" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_EdgeOnly ("Edge Only", Float) = 1.0
//边缘颜色
_EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
//背景颜色
_BackgroundColor ("Background Color", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
ZTest Always Cull Off
ZWrite Off
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment fragSobel
sampler2D _MainTex;
uniform half4 _MainTex_TexelSize;
fixed _EdgeOnly;
fixed4 _EdgeColor;
fixed4 _BackgroundColor;
struct v2f {
float4 pos : SV_POSITION;
half2 uv[9] : TEXCOORD0;
};
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
//由于卷积操作需要采样相邻区域的纹理,因此先取得相邻区域的uv坐标
//采样纹理坐标放到顶点着色器中,提高性能
o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);
o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);
o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);
o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);
o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);
o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);
o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);
o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);
return o;
}
//将颜色转换为亮度(灰度)
fixed luminance(fixed4 color) {
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
//卷子算子Sobel 进行边缘检测 得到当前像素的梯度值
half Sobel(v2f i) {
//水平方向的卷积核Gx
const half Gx[9] = {-1, 0, 1,
-2, 0, 2,
-1, 0, 1};
//垂直方向的卷积核Gy
const half Gy[9] = {-1, -2, -1,
0, 0, 0,
1, 2, 1};
half texColor;
half edgeX = 0;
half edgeY = 0;
for (int it = 0; it < 9; it++) {
texColor = luminance(tex2D(_MainTex, i.uv[it]));
edgeX += texColor * Gx[it];
edgeY += texColor * Gy[it];
}
//1. 本来是开跟操作,处于性能考虑 使用绝对值代替
//2. 1 - (横竖方向梯度) = 值越小,越有可能是边缘点
half edge = 1 - abs(edgeX) - abs(edgeY);
return edge;
}
fixed4 fragSobel(v2f i) : SV_Target {
half edge = Sobel(i);
//使用edge 梯度值计算原图下的颜色值
fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);
//使用edge 梯度值计算纯色背景下的颜色值
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
//使用_EdgeOnly 融合边缘颜色和原图颜色值
return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
}
ENDCG
}
}
FallBack Off
}
https://blog.csdn.net/zouxy09/article/details/49080029
https://blog.csdn.net/tigerda/article/details/61192943
3.高斯模糊
利用高斯卷积核进行卷积计算,高斯核是正方形大小的滤波核。每个元素计算都基于二维高斯方程。
高斯函数二维正态分布:
模拟了距离越近的元素,对中心点的影响越大。
出于性能考虑,将二维高斯核改为两个一维高速核。同时去掉一维高斯核中重复的权重。
Shader "Chan/Chapter 12/Gaussian Blur" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_BlurSize ("Blur Size", Float) = 1.0
}
SubShader {
//CGINCLUDE ENDCG 类似C++头文件
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
float _BlurSize;
struct v2f {
float4 pos : SV_POSITION;
half2 uv[5]: TEXCOORD0;
};
// 由于使用的是5x5的高斯核进行模糊计算,因此取中心点相邻的4个垂直uv区域,用于采样纹理
v2f vertBlurVertical(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;//当前操作的纹理的uv坐标
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
return o;
}
// 由于使用的是5x5的高斯核进行模糊计算,因此取中心点相邻的4个水平uv区域,用于采样纹理
v2f vertBlurHorizontal(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
return o;
}
fixed4 fragBlur(v2f i) : SV_Target {
float weight[3] = {0.4026, 0.2442, 0.0545};
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
for (int it = 1; it < 3; it++) {
sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
}
return fixed4(sum, 1.0);
}
ENDCG
ZTest Always Cull Off
ZWrite Off
Pass {
//使用Name语义,其他Shader 通过Name后的名字就可直接调用这个Pass
NAME "GAUSSIAN_BLUR_VERTICAL"
CGPROGRAM
#pragma vertex vertBlurVertical
#pragma fragment fragBlur
ENDCG
}
Pass {
NAME "GAUSSIAN_BLUR_HORIZONTAL"
CGPROGRAM
#pragma vertex vertBlurHorizontal
#pragma fragment fragBlur
ENDCG
}
}
FallBack Off
}
注解:
- 本例中使用 5x5 的二维高斯核进行模糊计算,但是被分为垂直方向和水平方向的两个一维数组,因此Shader中 vertBlurVertical vertBlurHorizontal 分别取下图中当前计算的UV区域(涂黑区域)相邻的上下和左右2个UV区域。
- 由于“距离”计算中心点(涂黑区域)大小一样的区域,具有相同的权重。横竖两个一维5个元素的数组,定义3个权重即可。
- Shader 中fragBlur根据UV采样到计算点(涂黑区域)相邻的纹理,然后乘以区域相对应的权重。
配合使用的C#脚本:
using UnityEngine;
using System.Collections;
public class GaussianBlur : PostEffectsBase {
public Shader gaussianBlurShader;
private Material gaussianBlurMaterial = null;
public Material material {
get {
gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
return gaussianBlurMaterial;
}
}
//高斯模糊迭代次数
[Range(0, 4)]
public int iterations = 3;
//模糊范围
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f;
//缩放系数
[Range(1, 8)]
public int downSample = 2;
//
void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (material != null) {
int rtW = src.width;
int rtH = src.height;
// 使用RenderTexture.GetTemporary创建一个与屏幕大小相当的缓存
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
// 将Shader中第一个Pass对竖直方向进行高斯核卷积操作计算得到的图像放到缓存中
Graphics.Blit(src, buffer, material, 0);
// 使用Shader中的第二个Pass对buffer进行水平方向的高斯核卷积操作
Graphics.Blit(buffer, dest, material, 1);
// 释放创建的缓存
RenderTexture.ReleaseTemporary(buffer);
} else {
Graphics.Blit(src, dest);
}
}
//图片降采样处理
// void OnRenderImage (RenderTexture src, RenderTexture dest) {
// if (material != null) {
// int rtW = src.width/downSample;
// int rtH = src.height/downSample;
// // 截取的屏幕纹理缩小,并将纹理滤波模式设置为双线性。因此处理的像素数量变低
// RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
// buffer.filterMode = FilterMode.Bilinear;
// // 将Shader中第一个Pass对竖直方向进行高斯核卷积操作计算得到的图像放到缓存中
// Graphics.Blit(src, buffer, material, 0);
// // 使用Shader中的第二个Pass对buffer进行水平方向的高斯核卷积操作
// Graphics.Blit(buffer, dest, material, 1);
// // 释放创建的缓存
// RenderTexture.ReleaseTemporary(buffer);
// } else {
// Graphics.Blit(src, dest);
// }
// }
// 图片降采样处理 + 增加高斯模糊的迭代次数
// void OnRenderImage (RenderTexture src, RenderTexture dest) {
// if (material != null) {
// int rtW = src.width/downSample;
// int rtH = src.height/downSample;
// RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
// buffer0.filterMode = FilterMode.Bilinear;
// Graphics.Blit(src, buffer0);
// for (int i = 0; i < iterations; i++) {
// material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
// //将Shader中第一个Pass对竖直方向进行高斯核卷积操作计算得到的图像放到缓存中
// RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
// Graphics.Blit(buffer0, buffer1, material, 0);
// RenderTexture.ReleaseTemporary(buffer0);
// buffer0 = buffer1;
// //使用Shader中的第二个Pass对buffer进行水平方向的高斯核卷积操作
// buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
// Graphics.Blit(buffer0, buffer1, material, 1);
// RenderTexture.ReleaseTemporary(buffer0);
// buffer0 = buffer1;
// }
// Graphics.Blit(buffer0, dest);
// RenderTexture.ReleaseTemporary(buffer0);
// } else {
// Graphics.Blit(src, dest);
// }
// }
}
4.Bloom效果
原理:
- 根据一个阈值提取图像红较亮的区域,把它们存储在一张渲染纹理中。
- 然后再利用高斯模糊对渲染纹理进行模糊处理,模拟管线扩散效果。
- 最后和原图像进行混合,得到最终效果。
Shader "Chan/Chapter 12/Bloom" {
Properties
{
_MainTex("Base Tex",2D) = "white"{}
//存储高斯模糊后的较亮区域
_Bloom("Bloom",2D) = "black"{}
//提取较亮区域使用的阈值
_LuminanceThreshold("Luminance Threshold",Float) = 0.5
//控制不同迭代之间高斯模糊的模糊区域范围
_BlurSize("Blur Size",Float) = 1.0
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _Bloom;
float _LuminanceThreshold;
float _BlurSize;
struct v2f
{
float4 pos:SV_POSITION;
half2 uv:TEXCOORD0;
};
v2f vertExtractBright(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
//将颜色转化为亮度(灰度)
fixed luminance(fixed4 color)
{
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
//提取得到的亮度值和原纹理颜色值相乘,得到亮部区域
fixed4 fragExtractBright(v2f i):SV_Target
{
float4 c = tex2D(_MainTex,i.uv);
fixed val = clamp(luminance(c) - _LuminanceThreshold,0.0,1.0);
return c * val;
}
struct v2fBloom
{
float4 pos:SV_POSITION;
half4 uv:TEXCOORD0;
};
v2fBloom vertBloom(appdata_img v)
{
v2fBloom o;
o.pos = UnityObjectToClipPos(v.vertex);
//获得原纹理uv坐标
o.uv.xy = v.texcoord;
//获得bloom后的纹理uv坐标
o.uv.zw = v.texcoord;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0.0)
o.uv.w = 1.0 - o.uv.w;
#endif
return o;
}
//原纹理和高斯模糊后的亮部区域纹理叠加
fixed4 fragBloom(v2fBloom i):SV_Target
{
return tex2D(_MainTex,i.uv.xy) + tex2D(_Bloom,i.uv.zw);
}
ENDCG
ZTest Always Cull Off ZWrite Off
//根据阈值提取得到亮部纹理
Pass
{
CGPROGRAM
#pragma vertex vertExtractBright
#pragma fragment fragExtractBright
ENDCG
}
//垂直方向 & 水平方向 利用高斯核进行模糊计算
UsePass "Chan/Chapter12-GaussianBlur_Chan/GAUSSIAN_BLUR_VERTICAL"
UsePass "Chan/Chapter12-GaussianBlur_Chan/GAUSSIAN_BLUR_HORIZONTAL"
//原纹理和高斯模糊纹理叠加,得到最终输出显示纹理
Pass
{
CGPROGRAM
#pragma vertex vertBloom
#pragma fragment fragBloom
ENDCG
}
}
FallBack Off
}
配合使用的C#:
using UnityEngine;
using System.Collections;
public class Bloom : PostEffectsBase {
public Shader bloomShader;
private Material bloomMaterial = null;
public Material material {
get {
bloomMaterial = CheckShaderAndCreateMaterial(bloomShader, bloomMaterial);
return bloomMaterial;
}
}
// Blur iterations - larger number means more blur.
[Range(0, 4)]
public int iterations = 3;
// Blur spread for each iteration - larger value means more blur
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f;
[Range(1, 8)]
public int downSample = 2;
//设置一个阈值,提取纹理亮部
[Range(0.0f, 4.0f)]
public float luminanceThreshold = 0.6f;
void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
material.SetFloat("_LuminanceThreshold", luminanceThreshold);
int rtW = src.width/downSample;
int rtH = src.height/downSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
buffer0.filterMode = FilterMode.Bilinear;
//创建一个纹理,调用Shader 中的 Bloom的第一个Pass,计算高亮部分
Graphics.Blit(src, buffer0, material, 0);
for (int i = 0; i < iterations; i++) {
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
//material 中shader的 第二pass进行垂直方向的莫斯计算
//对应Shader Bloom的 UsePass "Chan/Chapter12-GaussianBlur_Chan/GAUSSIAN_BLUR_VERTICAL"
Graphics.Blit(buffer0, buffer1, material, 1);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
//UsePass "Chan/Chapter12-GaussianBlur_Chan/GAUSSIAN_BLUR_HORIZONTAL"
Graphics.Blit(buffer0, buffer1, material, 2);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
//计算好的带亮度的高斯模糊的纹理,传入Shader中的_Bloom,调用第四个Pass,和原纹理叠加
material.SetTexture ("_Bloom", buffer0);
Graphics.Blit (src, dest, material, 3);
RenderTexture.ReleaseTemporary(buffer0);
} else {
Graphics.Blit(src, dest);
}
}
}
5.运动模糊
实现方法:
- 利用一块累积缓存来混合多张连续的图像,当物体快速移动产生多张图像后,取它们之间的平均值作为最后的运动模糊图像。此方法,性能消耗很大。
- 利用速度缓存,缓存中存储了当前各个像素当前的运动速度,然后利用该值决定模糊的方向和大小。
Shader "Chan/Chapter 12/Motion Blur" {
Properties
{
_MainTex("Base Tex",2D) = "white"{}
//模糊参数
_BlurAmount("Blur Amount",Float) = 1.0
}
SubShader
{
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed _BlurAmount;
struct v2f
{
float4 pos:SV_POSITION;
half2 uv:TEXCOORD0;
};
v2f vert(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
//用于更新渲染纹理的RGB通道
fixed4 fragRGB(v2f i):SV_Target
{
return fixed4(tex2D(_MainTex,i.uv).rgb,_BlurAmount);
}
//用于更新渲染纹理的A通道
half4 fragA(v2f i):SV_Target
{
return tex2D(_MainTex,i.uv);
}
ENDCG
ZTest Always Cull Off
ZWrite Off
Pass
{
//开启混合,并设置混合模式
Blend SrcAlpha OneMinusSrcAlpha
//ColorMask 可以指定渲染结果的输出通道,而不是通常的 RGBA 四个通道都被写入。
ColorMask RGB
CGPROGRAM
#pragma vertex vert
#pragma fragment fragRGB
ENDCG
}
Pass
{
//开启混合,并设置混合模式
Blend One Zero
//ColorMask 可以指定渲染结果的输出通道,而不是通常的 RGBA 四个通道都被写入。
ColorMask A
CGPROGRAM
//为了保证颜色缓存的透明度不变,这样保证后续屏幕后处理中有关透明度操作没问题
#pragma vertex vert
#pragma fragment fragA
ENDCG
}
}
FallBack Off
}
注解:
分为两个Pass渲染,是为了
//为了保证颜色缓存的透明度不变,这样保证后续屏幕后处理中有关透明度操作没问题
配合使用的C#
using UnityEngine;
using System.Collections;
public class MotionBlur : PostEffectsBase {
public Shader motionBlurShader;
private Material motionBlurMaterial = null;
public Material material {
get {
motionBlurMaterial = CheckShaderAndCreateMaterial(motionBlurShader, motionBlurMaterial);
return motionBlurMaterial;
}
}
//模糊参数
[Range(0.0f, 0.9f)]
public float blurAmount = 0.5f;
private RenderTexture accumulationTexture;
void OnDisable() {
DestroyImmediate(accumulationTexture);
}
void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
//创建一块累积缓存,并用当前帧图像初始化
if (accumulationTexture == null || accumulationTexture.width != src.width || accumulationTexture.height != src.height) {
DestroyImmediate(accumulationTexture);
accumulationTexture = new RenderTexture(src.width, src.height, 0);
accumulationTexture.hideFlags = HideFlags.HideAndDontSave;
Graphics.Blit(src, accumulationTexture);
}
//表明我们需要进行一个渲染纹理的操作,但是为了叠加纹理,不清空accumulationTexture
//为了不让Unity发出警告误以为是忘记清空了。所以写这么一句
accumulationTexture.MarkRestoreExpected();
material.SetFloat("_BlurAmount", 1.0f - blurAmount);
//把当前屏幕图像src叠加到 accumulationTexture中
Graphics.Blit (src, accumulationTexture, material);
//把混合结果显示到屏幕中
Graphics.Blit (accumulationTexture, dest);
} else {
Graphics.Blit(src, dest);
}
}
}
注解:
//进行一个渲染纹理的操作,但是为了叠加纹理,不清空accumulationTexture
//为了不让Unity发出警告误以为是忘记清空了。所以写这么一句
accumulationTexture.MarkRestoreExpected();
https://github.com/candycat1992/Unity_Shaders_Book/issues/51