屏幕后处理效果
Posted cchoop
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了屏幕后处理效果相关的知识,希望对你有一定的参考价值。
屏幕后处理效果(screen post-processing effects) 是游戏中实现屏幕特效的常见方法
建立一个基本的后处理脚本系统
屏幕后处理通常指的是在渲染完整个场景得到屏幕图像后,再对这个图像进行一系列操作,实现各种屏幕特效。使用屏幕后处理可以为游戏画面添加更多的艺术效果,例如景深、运动模糊。
OnRenderImage函数
Unity为我们提供了一个接口——OnRenderImage函数,用于得到渲染后的屏幕图像,即抓取屏幕。
MonoBehaviour.OnRenderImage(RenderTexture src, RenderTexture dest)
- src: 源纹理(当前渲染得到的图像)
- dest: 目标渲染纹理
当我们在脚本中声明了此函数后,Unity会把当前渲染得到的图像存储在src参数中,通过一系列操作后,再把目标渲染纹理dest显示在屏幕上。
Graphics.Blit函数
在OnRenderImage函数当中,我们通常利用Graphics.Blit函数来完成对渲染纹理的处理
public static void Blit(Texture src, RenderTexture dest);
public static void Blit(Texture src, RenderTexture dest, Material mat, int pass = -1);
public static void Blit(Texture src, Material mat, int pass = -1);
- src: 源纹理(当前渲染得到的图像),将会传递给Shader中命名为_MainTex的纹理属性
- dest: 目标渲染纹理
- mat: 使用的材质,该材质使用的Shader将会进行各种屏幕后处理的操作
- pass: pass的索引,默认值为-1,表示会依次调用Shader内所有的Pass,否则调用给定索引的Pass
using UnityEngine;
[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffectsBase : MonoBehaviour
{
protected void Start()
{
CheckResources();
}
protected void CheckResources()
{
bool isSupported = CheckSupport();
if (isSupported == false)
{
NotSupported();
}
}
protected bool CheckSupport()
{
if (SystemInfo.supportsImageEffects == false)
{
Debug.LogWarning("该设备不支持图像处理");
return false;
}
return true;
}
protected void NotSupported()
{
enabled = false;
}
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;
return material;
}
}
}
调整屏幕的亮度、饱和度和对比度
using UnityEngine;
public class BrightnessSaturationAndContrast : PostEffectsBase
{
public Shader briSatConShader;
private Material briSatConMaterial;
public Material material
{
get
{
briSatConMaterial = CheckShaderAndCreateMaterial(briSatConShader, briSatConMaterial);
return briSatConMaterial;
}
}
[Range(0f, 3f)]
public float brightness = 1.0f;
[Range(0f, 3f)]
public float saturation = 1.0f;
[Range(0f, 3f)]
public float contrast = 1.0f;
private 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);
}
}
}
Shader "Unlit/BrightnessSaturationAndContrast"
{
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);
// 调整亮度 = 原颜色 * 亮度值
fixed3 finalColor = renderTex.rgb * _Brightness;
// 调整饱和度
// 亮度值(饱和度为0的颜色) = 每个颜色分量 * 特定系数
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);
// 调整对比度
// 对比度为0的颜色
fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
finalColor = lerp(avgColor, finalColor, _Contrast);
return fixed4(finalColor, renderTex.a);
}
ENDCG
}
}
}
边缘检测
using UnityEngine;
public class EdgeDetection : PostEffectsBase
{
public Shader edgeDetecShader;
private Material edgeDetectMaterial;
public Material material
{
get
{
edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetecShader, edgeDetectMaterial);
return edgeDetectMaterial;
}
}
// 边缘线强度
[Range(0f, 1f)]
public float edgesOnly = 0f;
public Color edgeColor = Color.black;
// 背景颜色
public Color backgroundColor = Color.white;
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
material.SetFloat("_EdgeOnly", edgesOnly);
material.SetColor("_EdgeColor", edgeColor);
material.SetColor("_BackgroundColor", backgroundColor);
Graphics.Blit(src, dest, material);
}
else
{
Graphics.Blit(src, dest);
}
}
}
Shader "Unlit/EdgeDetection"
{
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;
//用于访问_MainTex纹理对应的每个像素大小
uniform half4 _MainTex_TexelSize;
fixed _EdgeOnly;
fixed4 _EdgeColor;
fixed4 _BackgroundColor;
struct v2f
{
float4 pos: SV_POSITION;
// 定义了维数为9的纹理数组, 对应了Sobel算子采样时需要的9个领域纹理坐标
half2 uv[9]: TEXCOORD0;
};
v2f vert(appdata_img v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
// 获取当前纹理坐标周围的9个纹理坐标
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;
}
// 算子计算
half Sobel(v2f i)
{
// 定义垂直于水平方向的卷积核
const half Gx[9] = {
-1, 0, 1,
-2, 0, 2,
-1, 0, 1
};
const half Gy[9] = {
-1, -2, -1,
0, 0, 0,
1, 2, 1
};
// 对周围的9个
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减去梯度值对应的绝对值, 结果越小越可能是边缘点
half edge = 1 - abs(edgeX) - abs(edgeY);
return edge;
}
fixed4 fragSobel(v2f i): SV_Target
{
// 通用算子计算当前像素的梯度值
half edge = Sobel(i);
// 背景为原图下的颜色值
fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);
// 背景为纯色下的颜色值
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
// 插值原图于纯色背景
return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
}
ENDCG
}
}
Fallback Off
}
高斯模糊
Bloom效果
(未完待续)
以上是关于屏幕后处理效果的主要内容,如果未能解决你的问题,请参考以下文章