屏幕后处理效果

本文介绍了Unity中实现屏幕后处理技术,包括边缘检测和高斯模糊。边缘检测通过卷积操作和Sobel算子检测图像边缘,高斯模糊则利用卷积核进行图像模糊。此外,还探讨了如何通过屏幕后处理脚本系统实现这些效果,包括对渲染纹理的操作和使用特定的Shader进行处理。

建立一个基本的屏幕后处理脚本系统
屏幕后处理:指的是渲染完整个场景得到屏幕图像后,再对整个图像进行一系列操作,实现各种屏幕特效。使用这种技术,可以为游戏画面添加更多效果,景深,运动模糊想要实现屏幕后处理的基础在于得到渲染后的屏幕图像,抓取屏幕Unity提供了接口OnRenderImage函数Unity会把当前渲染得到的图像存储在第一个参数对应的渲染纹理中,通过函数中一系列操作后,再把目标渲染纹理,第二个参数对应的渲染纹理显示到屏幕上。在OnRenderImage函数中,通常利用Garphics.Bilt函数来完成对渲染纹理的处理。函数声明:

public static void Bilt(Texture src,RenderTexture dest);
public static void Bilt(Texture src,RenderTexture dest,Material mat,int pass=-1);
public static void Bilt(Texture src,Material mat,int pass=-1);

参数为src对应了源纹理,屏幕后处理技术中,这个参数通常就是当前屏幕的渲染纹理或是上一步处理后得到的渲染纹理。dest是目标渲染纹理,值为null就会直接将结果显示在屏幕上。参数mat是使用的材质,这个材质使用的shader将会进行各种屏幕后处理操作,src纹理将会被传递给Shader中名为_MainTex的纹理属性。参数pass默认是-1,表示将会依次调用Shader内的所有Pass,否则只会调用给定索引的Pass。

默认情况下,OnRenderImage函数会在所有不透明和透明Pass执行完毕后被调用,以便对场景中所有游戏对象产生影响。但有时,希望在不透明Pass执行完毕立即调用OnRenderImage函数,从而对不透明物体产生任何影响。此时,可以在函数前添加ImageEffectOpaque属性来实现这样的目的。

Unity实现屏幕后处理效果过程如下:摄像机添加一个用于屏幕后处理的脚本。在这个脚本实现OnRenderImage函数来获取当前屏幕的渲染纹理。然后,调用Graphics.Blit函数使用特定的Unity Shader对当前图像进行处理,再把返回的渲染纹理显示到屏幕。对于一些复杂的屏幕特效,可能需要多次调动Graphics.Blit函数对上一步输出的结果进行下一步处理。
进行屏幕后处理之前,需要检查一系列条件是否满足,当前平台是否支持渲染纹理和屏幕特效,是否支持当前使用的Shader。为此,我们创建了一个用于屏幕后处理效果的基类,实现各种屏幕特效时候,只需要继承自该基类,再实现派生类中不同的操作即可。

using UnityEngine;
using System.Collections;

//编辑器状态下可以执行脚本查看效果
[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
public class ScreenPostEffectBse : MonoBehaviour {
   
   

	protected void CheckResources()
	{
   
   
		bool isSupported = CheckSupport ();

		if (isSupported == false) {
   
   
			NotSupported ();
		}
	}

	protected bool CheckSupport()
	{
   
   
		if (SystemInfo.supportsImageEffects == false) {
   
   
			return false;
		}
		return true;
	}

	protected void NotSupported()
	{
   
   
		enabled = false;
	}

	protected void Start()
	{
   
   
		CheckResources ();
	}

	//由于每个屏幕后处理效果通常需要指定一个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;
			} else {
   
   
				return null;
			}
		}

	}
}

对应的Shader

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 12/Brightness Saturation And Contrast" {
   
   
	//Graphics.Blit将把第一个参数传递给Shader中名为_MainTex属性
	//还声明了用于调整亮度饱和度和对比度的属性,这些值将会由脚本传递
	//这里的材质是临时创建的,不需要在材质面板上调整参数,直接从脚本传递给Unity Shader
	Properties {
   
   
		_MainTex ("Base (RGB)", 2D) = "white" {
   
   }
		_Brightness ("Brightness", Float) = 1
		_Saturation("Saturation", Float) = 1
		_Contrast("Contrast", Float) = 1
	}
	SubShader {
   
   
		Pass {
   
     
			//屏幕后处理实际是场景中绘制了一个与屏幕同高同宽的四边形面片,为了防止对其他物体产生影响,设置相关渲染状态。
			//这里关闭深度写入,防止挡住在其后面被渲染的物体。例如,如果当前的OnRenderImage函数在所有不透明的Pass执行完毕后立即调用
			//不关闭深度写入就会影响后面的Pass渲染,这些状态设置可以认为是用于屏幕后处理的Shader的标配。
			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
}

边缘检测
边缘检测的原理是利用一些边缘检测算子对图像进行卷积(convolution)操作。
在图像处理中,卷积操作指的就是使用一个卷积核(kernel)对一张图像中的每个像素进行一系列操作。卷积核通常是一个四方形网格结构(例如2×2、3×3的方形区域),该区域内每个方格都有一个权重值。当对图像中的某个像素进行卷积时,我们会把卷积核的中心放置于该像素上,如图所示,翻转核之后再依次计算核中每个元素和其覆盖的图像像素值的乘积并求和,得到的结果就是该位置的新像素值
在这里插入图片描述

这样的计算过程虽然简单,但可以实现很多常见的图像处理效果,例如图像模糊、边缘检测等。例如,如果我们想要对图像
进行均值模糊,可以使用一个3×3的卷积核,核内每个元素的值均为1/9。

常见的边缘检测算子
卷积操作的神奇之处在于选择的卷积核。那么,用于边缘检测的卷积核(也被称为边缘检测算子)应该长什么样呢?在回答这个问题前,我们可以首先回想一下边到底是如何形成的。如果相邻像素之间存在差别明显的颜色、亮度、纹理等属性,我们就会认为它们之间应该有一条边界。这种相邻像素之间的差值可以用梯度(gradient)来表示,可以想象得到,边缘处的梯度绝对值会比较大。基于这样的理解,有几种不同的边缘检测算子被先后提出来
在这里插入图片描述

3种常见的边缘检测算子如图12.5所示,它们都包含了两个方向的卷积核,分别用于检测水平方向和竖直方向上的边缘信息。
在进行边缘检测时,我们需要对每个像素分别进行一次卷积计算,得到两个方向上的梯度值Gx和Gy,而整体的梯度可按下面的公
式计算而得:
在这里插入图片描述

由于上述计算包含了开根号操作,出于性能的考虑,我们有时会使用绝对值操作来代替开根号操作
在这里插入图片描述

当得到梯度G后,我们就可以据此来判断哪些像素对应了边缘(梯度值越大,越有可能是边缘点)。

using UnityEngine;
using System.Collections;

public class EdgeDetectionEffect : PostEffectsBase {
   
   

    public Shader edgeDetectShader;

    private Material edgeDetectMaterial = null;

    public Material material
    {
   
   
        get
        {
   
   
            edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
            return edgeDetectMaterial;
        }
    }

    [Range(0.0f, 1.0f)]
    public float edgesOnly = 0.0f; //为0边缘会叠加在原渲染图像 为1只显示边缘不显示原来渲染图像
    public Color edgeColor = Color.black;

    public Color backgroundColor = Color.white;

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
   
   
        if(material!=null)
        {
   
   
            material.SetFloat("_EdgeOnly", edgesOnly);
            material.SetColor("_EdgeColor", edgeColor);
            material.SetColor("_BackgroundColor", backgroundColor);

            Graphics.Blit(source, destination, material);
        }
        else
        {
   
   
            Graphics.Blit(source, destination);
        }
    }

}
Shader "Unity Shaders Book/EdgeDetection" {
   
   
	Properties {
   
   
		_MainTex ("Base (RGB)", 2D) = "white" {
   
   }
		_EdgeOnly ("Edge Only", Float) = 1
		_EdgeColor("Edge Color", Color) =(0,0,0,0)
		_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;
				
				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 *<
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值