屏幕后处理效果

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效果


(未完待续)

以上是关于屏幕后处理效果的主要内容,如果未能解决你的问题,请参考以下文章

Unity Shader入门精要学习笔记 - 第12章 屏幕后处理效果

如何设置主活动中显示的默认片段?

炫酷 CSS 背景效果的 10 个代码片段

屏幕后处理——亮度.饱和度.对比度

片段处理屏幕方向与操作栏中的选项卡

屏幕旋转后Android片段重叠