刚好最近独立游戏中写了一个简单的屏幕后期特效,所以开一篇聊聊所谓的引擎中屏幕后期特效处理。
一般这个词美术用的比较多,什么影视CG视频后期特效、后期处理啊。原理上来说就是在视频成品的基础上进行帧图像的纹理处理,比如一帧视频纹理上叠加一个特效图片、或者做人物抠图、或者做绿布替换等。
那么在我们的三维引擎中,屏幕特效意味着什么呢?其实就是eyeCamera渲染到的场景最后提交颜色缓冲区的那一帧图像,然后我们对那一帧图像进行着色器层面的再操作。三维场景经过建模/世界/视口/裁剪/标准设备等空间,最后经过视图/深度变换到最终的画面,而我们开发就将最终的画面进行再处理,得到最最终的画面再提交显示硬件。
unity因为其良好的构架设计,直接提供完善的接口供我们开发进行屏幕特效制作,c#函数如下:
函数明确的意义就是提供给我们开发source(源渲染画面)进行shader层面的操作后绑定到destination(目标渲染图像),最后提交渲染。
同时unity也直接提供给我们创建ImageEffectShader的示例shader文件。
接下来就是具体操作过程了,代码分为c#以及shader,c#代码主要负责接口实现,也就是获取源渲染画面,shader则将源画面当作_MainTex(主纹理)进行操作,如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraImageEffect : MonoBehaviour {
private Material mMat;
[SerializeField]public float _Weight = 1.0f;
private void Awake()
{
mMat = new Material(Shader.Find("Hidden/ImageColorEffectShader"));
}
void Start () {
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
mMat.SetTexture("_MainTex", source);
mMat.SetFloat("_Weight", _Weight);
Graphics.Blit(source, destination, mMat);
}
}
这个c#脚本绑定在mainCamera上,这样函数才能起作用。
接着是一个简单的变换亮度的Shader,如下:
Shader "Hidden/ImageColorEffectShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Weight("Weight",Range(0,2.0)) = 1.0
}
SubShader
{
//因为是屏幕特效,所以这些都关闭掉
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float _Weight;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
//只进行最简单的颜色值操作
col *= _Weight;
return col;
}
ENDCG
}
}
}
两份简单的代码描叙了屏幕后期特效操作流程,效果也单调,就改变整体亮度而已,如下:
接下来做一个影视制作常用的绿布处理效果,如下:
1.首先创建一个绿布背景场景。
2.接下来编写c#和shader代码,c#代码控制场景源渲染图像,和要替换的背景图纹理,shader则根据源图像的片段颜色值进行重采样替换。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CamereGreenEffect : MonoBehaviour {
public Texture2D mSubTex;
private Material mMat;
private void Awake()
{
mMat = new Material(Shader.Find("Hidden/ImageGreenEffectShader"));
mMat.SetTexture("_SubTex", mSubTex);
}
void Start()
{
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
mMat.SetTexture("_MainTex", source);
Graphics.Blit(source, destination, mMat);
}
}
Shader "Hidden/ImageGreenEffectShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_SubTex("SubTex", 2D) = "white" {}
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
sampler2D _SubTex; /*因为屏幕的uv已经确定是左下角00,右上角11,所以不需要_ST*/
//判断rgb分量是否相同
bool CompareColor(fixed4 src, fixed4 compare)
{
if (src.x == compare.x && src.y == compare.y && src.z == compare.z)
return true;
return false;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
//如果主纹理采样是绿色,则替换成subtex背景图的采样颜色值
if (CompareColor(col, fixed4(0, 1, 0, 1)))
{
col = tex2D(_SubTex, i.uv);
}
return col;
}
ENDCG
}
}
}
3.运行后最终效果就是,源采样图像中颜色值为green会被重采样的subTex的颜色值替换。
4.因为我写的绝对等于green颜色值,所以存在一些“毛边”效应,只需要实现一个“约等于”判断函数即可。
最后,屏幕特效的原理和应用大致如此,后面我们需要的时候会实现一些复杂的屏幕后期特效,但是原理万变不离其宗,我们了解原理后,很多效果都可以在日常学习工作中摸索出来。
so,我们接下来继续。